@ybotok/loam 0.1.0-beta.2 → 0.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2570 -61
- package/COMPARISON.md +80 -63
- package/CONTRIBUTING.md +84 -0
- package/MIGRATING-from-OpenSpec.md +213 -54
- package/QUICKSTART.md +125 -0
- package/README.md +450 -150
- package/ROADMAP.md +494 -0
- package/SCHEMA.md +2362 -245
- package/SECURITY.md +21 -3
- package/WORKFLOW.md +701 -0
- package/dist/cli.js +113 -19
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +209 -0
- package/dist/commands/adopt/adopt.js.map +1 -0
- package/dist/commands/adopt/render.js +268 -0
- package/dist/commands/adopt/render.js.map +1 -0
- package/dist/commands/archive/archive.js +77 -0
- package/dist/commands/archive/archive.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +159 -0
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +317 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +161 -0
- package/dist/commands/archive/plan/landscape.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +111 -0
- package/dist/commands/archive/plan/refusal.js.map +1 -0
- package/dist/commands/archive/plan/specs.js +237 -0
- package/dist/commands/archive/plan/specs.js.map +1 -0
- package/dist/commands/archive/plan/state.js +27 -0
- package/dist/commands/archive/plan/state.js.map +1 -0
- package/dist/commands/archive/run.js +251 -0
- package/dist/commands/archive/run.js.map +1 -0
- package/dist/commands/context/context.js +99 -0
- package/dist/commands/context/context.js.map +1 -0
- package/dist/commands/context/print.js +283 -0
- package/dist/commands/context/print.js.map +1 -0
- package/dist/commands/delta/delta.js +279 -0
- package/dist/commands/delta/delta.js.map +1 -0
- package/dist/commands/delta/print.js +205 -0
- package/dist/commands/delta/print.js.map +1 -0
- package/dist/commands/dependencies.js +9 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +93 -0
- package/dist/commands/diff/diff.js.map +1 -0
- package/dist/commands/diff/print.js +38 -0
- package/dist/commands/diff/print.js.map +1 -0
- package/dist/commands/doctor.js +5 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +151 -0
- package/dist/commands/explain/explain.js.map +1 -0
- package/dist/commands/explore.js +29 -24
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +115 -0
- package/dist/commands/gate/gate.js.map +1 -0
- package/dist/commands/gate/print.js +54 -0
- package/dist/commands/gate/print.js.map +1 -0
- package/dist/commands/gherkin/commit.js +146 -0
- package/dist/commands/gherkin/commit.js.map +1 -0
- package/dist/commands/gherkin/gherkin.js +246 -0
- package/dist/commands/gherkin/gherkin.js.map +1 -0
- package/dist/commands/gherkin/reconcile.js +149 -0
- package/dist/commands/gherkin/reconcile.js.map +1 -0
- package/dist/commands/gherkin/render.js +77 -0
- package/dist/commands/gherkin/render.js.map +1 -0
- package/dist/commands/init/example.js +204 -0
- package/dist/commands/init/example.js.map +1 -0
- package/dist/commands/init/first-hour.js +28 -0
- package/dist/commands/init/first-hour.js.map +1 -0
- package/dist/commands/{init.js → init/init.js} +146 -71
- package/dist/commands/init/init.js.map +1 -0
- package/dist/commands/init/mcp.js +131 -0
- package/dist/commands/init/mcp.js.map +1 -0
- package/dist/commands/init/options.js +78 -0
- package/dist/commands/init/options.js.map +1 -0
- package/dist/commands/instructions.js +82 -14
- package/dist/commands/instructions.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +65 -0
- package/dist/commands/list/campaign/campaign.js.map +1 -0
- package/dist/commands/list/campaign/owners.js +65 -0
- package/dist/commands/list/campaign/owners.js.map +1 -0
- package/dist/commands/list/json.js +157 -0
- package/dist/commands/list/json.js.map +1 -0
- package/dist/commands/list/list.js +343 -0
- package/dist/commands/list/list.js.map +1 -0
- package/dist/commands/list/print.js +301 -0
- package/dist/commands/list/print.js.map +1 -0
- package/dist/commands/list/review.js +120 -0
- package/dist/commands/list/review.js.map +1 -0
- package/dist/commands/list/views.js +105 -0
- package/dist/commands/list/views.js.map +1 -0
- package/dist/commands/mcp/dispatch.js +183 -0
- package/dist/commands/mcp/dispatch.js.map +1 -0
- package/dist/commands/mcp/mcp.js +34 -0
- package/dist/commands/mcp/mcp.js.map +1 -0
- package/dist/commands/mcp/serve.js +55 -0
- package/dist/commands/mcp/serve.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/active.js +170 -0
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
- package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/living.js +62 -0
- package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/target.js +219 -0
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/error.js +19 -0
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/read.js +49 -0
- package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
- package/dist/commands/migrate-openspec/print.js +89 -0
- package/dist/commands/migrate-openspec/print.js.map +1 -0
- package/dist/commands/new/commit.js +76 -0
- package/dist/commands/new/commit.js.map +1 -0
- package/dist/commands/new/new.js +283 -0
- package/dist/commands/new/new.js.map +1 -0
- package/dist/commands/new/templates.js +355 -0
- package/dist/commands/new/templates.js.map +1 -0
- package/dist/commands/open.js +203 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/policy/format.js +152 -0
- package/dist/commands/policy/format.js.map +1 -0
- package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
- package/dist/commands/policy/gate.js.map +1 -0
- package/dist/commands/rebase/edit.js +106 -0
- package/dist/commands/rebase/edit.js.map +1 -0
- package/dist/commands/rebase/living.js +185 -0
- package/dist/commands/rebase/living.js.map +1 -0
- package/dist/commands/rebase/plan.js +245 -0
- package/dist/commands/rebase/plan.js.map +1 -0
- package/dist/commands/rebase/rebase.js +275 -0
- package/dist/commands/rebase/rebase.js.map +1 -0
- package/dist/commands/seed/commit.js +192 -0
- package/dist/commands/seed/commit.js.map +1 -0
- package/dist/commands/seed/plan.js +133 -0
- package/dist/commands/seed/plan.js.map +1 -0
- package/dist/commands/seed/seed.js +172 -0
- package/dist/commands/seed/seed.js.map +1 -0
- package/dist/commands/show/feature.js +178 -0
- package/dist/commands/show/feature.js.map +1 -0
- package/dist/commands/show/marks.js +24 -0
- package/dist/commands/show/marks.js.map +1 -0
- package/dist/commands/{show.js → show/service.js} +63 -152
- package/dist/commands/show/service.js.map +1 -0
- package/dist/commands/show/show.js +77 -0
- package/dist/commands/show/show.js.map +1 -0
- package/dist/commands/status/json.js +45 -0
- package/dist/commands/status/json.js.map +1 -0
- package/dist/commands/status/print.js +157 -0
- package/dist/commands/status/print.js.map +1 -0
- package/dist/commands/status/status.js +119 -0
- package/dist/commands/status/status.js.map +1 -0
- package/dist/commands/steps/steps.js +189 -0
- package/dist/commands/steps/steps.js.map +1 -0
- package/dist/commands/subsystem/edit.js +192 -0
- package/dist/commands/subsystem/edit.js.map +1 -0
- package/dist/commands/subsystem/move.js +265 -0
- package/dist/commands/subsystem/move.js.map +1 -0
- package/dist/commands/subsystem/query.js +115 -0
- package/dist/commands/subsystem/query.js.map +1 -0
- package/dist/commands/subsystem/subsystem.js +101 -0
- package/dist/commands/subsystem/subsystem.js.map +1 -0
- package/dist/commands/subsystem/sync.js +103 -0
- package/dist/commands/subsystem/sync.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +94 -0
- package/dist/commands/subsystem/txn/txn.js.map +1 -0
- package/dist/commands/subsystem/txn/views.js +70 -0
- package/dist/commands/subsystem/txn/views.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +160 -0
- package/dist/commands/unarchive/manifest.js.map +1 -0
- package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
- package/dist/commands/unarchive/restore.js.map +1 -0
- package/dist/commands/unarchive/unarchive.js +67 -0
- package/dist/commands/unarchive/unarchive.js.map +1 -0
- package/dist/commands/validate/arch-coverage.js +194 -0
- package/dist/commands/validate/arch-coverage.js.map +1 -0
- package/dist/commands/validate/checks/fleet-shape.js +296 -0
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
- package/dist/commands/validate/checks/health-deps.js +55 -0
- package/dist/commands/validate/checks/health-deps.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +254 -0
- package/dist/commands/validate/checks/requirements.js.map +1 -0
- package/dist/commands/validate/checks/sources.js +76 -0
- package/dist/commands/validate/checks/sources.js.map +1 -0
- package/dist/commands/validate/checks/vocabulary.js +31 -0
- package/dist/commands/validate/checks/vocabulary.js.map +1 -0
- package/dist/commands/validate/feature.js +216 -0
- package/dist/commands/validate/feature.js.map +1 -0
- package/dist/commands/validate/fleet/census.js +69 -0
- package/dist/commands/validate/fleet/census.js.map +1 -0
- package/dist/commands/validate/fleet/kind-tags.js +39 -0
- package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
- package/dist/commands/validate/fleet/landscape.js +353 -0
- package/dist/commands/validate/fleet/landscape.js.map +1 -0
- package/dist/commands/validate/fleet/load.js +52 -0
- package/dist/commands/validate/fleet/load.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +236 -0
- package/dist/commands/validate/fleet/obligations.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/print.js +63 -0
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/place.js +13 -0
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/steps.js +197 -0
- package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +40 -0
- package/dist/commands/validate/fleet/views/ids.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +59 -0
- package/dist/commands/validate/fleet/views/stale.js.map +1 -0
- package/dist/commands/validate/links/corpus.js +92 -0
- package/dist/commands/validate/links/corpus.js.map +1 -0
- package/dist/commands/validate/links/glossary.js +75 -0
- package/dist/commands/validate/links/glossary.js.map +1 -0
- package/dist/commands/validate/report.js +269 -0
- package/dist/commands/validate/report.js.map +1 -0
- package/dist/commands/validate/service/api.js +338 -0
- package/dist/commands/validate/service/api.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +219 -0
- package/dist/commands/validate/service/events/events.js.map +1 -0
- package/dist/commands/validate/service/events/message-producers.js +77 -0
- package/dist/commands/validate/service/events/message-producers.js.map +1 -0
- package/dist/commands/validate/service/evidence-pins.js +131 -0
- package/dist/commands/validate/service/evidence-pins.js.map +1 -0
- package/dist/commands/validate/service/service.js +250 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +219 -0
- package/dist/commands/validate/service/specs.js.map +1 -0
- package/dist/commands/validate/service/spine.js +113 -0
- package/dist/commands/validate/service/spine.js.map +1 -0
- package/dist/commands/validate/validate.js +338 -0
- package/dist/commands/validate/validate.js.map +1 -0
- package/dist/commands/verify/cross/diff.js +182 -0
- package/dist/commands/verify/cross/diff.js.map +1 -0
- package/dist/commands/verify/evidence/contract.js +32 -0
- package/dist/commands/verify/evidence/contract.js.map +1 -0
- package/dist/commands/verify/evidence/pins.js +52 -0
- package/dist/commands/verify/evidence/pins.js.map +1 -0
- package/dist/commands/verify/evidence/read.js +72 -0
- package/dist/commands/verify/evidence/read.js.map +1 -0
- package/dist/commands/verify/frozen.js +241 -0
- package/dist/commands/verify/frozen.js.map +1 -0
- package/dist/commands/verify/record.js +226 -0
- package/dist/commands/verify/record.js.map +1 -0
- package/dist/commands/verify/report.js +223 -0
- package/dist/commands/verify/report.js.map +1 -0
- package/dist/commands/verify/results.js +246 -0
- package/dist/commands/verify/results.js.map +1 -0
- package/dist/commands/verify/verify.js +217 -0
- package/dist/commands/verify/verify.js.map +1 -0
- package/dist/commands/vouch/contract.js +11 -0
- package/dist/commands/vouch/contract.js.map +1 -0
- package/dist/commands/vouch/pack/contract.js +2 -0
- package/dist/commands/vouch/pack/contract.js.map +1 -0
- package/dist/commands/vouch/pack/pack.js +266 -0
- package/dist/commands/vouch/pack/pack.js.map +1 -0
- package/dist/commands/vouch/pack/print.js +271 -0
- package/dist/commands/vouch/pack/print.js.map +1 -0
- package/dist/commands/vouch/pack/sections.js +142 -0
- package/dist/commands/vouch/pack/sections.js.map +1 -0
- package/dist/commands/vouch/prompt/confirm.js +46 -0
- package/dist/commands/vouch/prompt/confirm.js.map +1 -0
- package/dist/commands/vouch/report.js +101 -0
- package/dist/commands/vouch/report.js.map +1 -0
- package/dist/commands/vouch/run.js +285 -0
- package/dist/commands/vouch/run.js.map +1 -0
- package/dist/commands/vouch/sample/plan.js +125 -0
- package/dist/commands/vouch/sample/plan.js.map +1 -0
- package/dist/commands/vouch/sample/print.js +109 -0
- package/dist/commands/vouch/sample/print.js.map +1 -0
- package/dist/commands/vouch/vet/verify.js +123 -0
- package/dist/commands/vouch/vet/verify.js.map +1 -0
- package/dist/commands/vouch/vouch.js +176 -0
- package/dist/commands/vouch/vouch.js.map +1 -0
- package/dist/core/agent/agents-md/artifacts.js +220 -0
- package/dist/core/agent/agents-md/artifacts.js.map +1 -0
- package/dist/core/agent/agents-md/command-map.js +324 -0
- package/dist/core/agent/agents-md/command-map.js.map +1 -0
- package/dist/core/agent/agents-md/cycle.js +165 -0
- package/dist/core/agent/agents-md/cycle.js.map +1 -0
- package/dist/core/agent/agents-md/map/explain.js +34 -0
- package/dist/core/agent/agents-md/map/explain.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
- package/dist/core/agent/agents-md/map/mcp.js +41 -0
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
- package/dist/core/agent/agents-md/map/status.js +62 -0
- package/dist/core/agent/agents-md/map/status.js.map +1 -0
- package/dist/core/agent/agents-md/map/subsystem.js +32 -0
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
- package/dist/core/agent/agents-md/map/usecases.js +60 -0
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
- package/dist/core/agent/agents-md/refusals.js +246 -0
- package/dist/core/agent/agents-md/refusals.js.map +1 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
- package/dist/core/agent/agents-md/spine.js +40 -0
- package/dist/core/agent/agents-md/spine.js.map +1 -0
- package/dist/core/agent/agents-md.js +170 -0
- package/dist/core/agent/agents-md.js.map +1 -0
- package/dist/core/agent/agents-stamp.js +155 -0
- package/dist/core/agent/agents-stamp.js.map +1 -0
- package/dist/core/agent/contract.js +2 -0
- package/dist/core/agent/contract.js.map +1 -0
- package/dist/core/agent/protocol.js +193 -0
- package/dist/core/agent/protocol.js.map +1 -0
- package/dist/core/agent/scaffold.js +259 -0
- package/dist/core/agent/scaffold.js.map +1 -0
- package/dist/core/agent/support/report.js +103 -0
- package/dist/core/agent/support/report.js.map +1 -0
- package/dist/core/agent/tools/dialects.js +150 -0
- package/dist/core/agent/tools/dialects.js.map +1 -0
- package/dist/core/agent/tools/registry.js +228 -0
- package/dist/core/agent/tools/registry.js.map +1 -0
- package/dist/core/agent/workflows/adopt.js +133 -0
- package/dist/core/agent/workflows/adopt.js.map +1 -0
- package/dist/core/agent/workflows/check.js +334 -0
- package/dist/core/agent/workflows/check.js.map +1 -0
- package/dist/core/agent/workflows/closing.js +262 -0
- package/dist/core/agent/workflows/closing.js.map +1 -0
- package/dist/core/agent/workflows/feature.js +105 -0
- package/dist/core/agent/workflows/feature.js.map +1 -0
- package/dist/core/agent/workflows/implement.js +105 -0
- package/dist/core/agent/workflows/implement.js.map +1 -0
- package/dist/core/agent/workflows/reference/authoring.js +209 -0
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
- package/dist/core/agent/workflows/reference/codes.js +65 -0
- package/dist/core/agent/workflows/reference/codes.js.map +1 -0
- package/dist/core/agent/workflows/reference/done-check.js +188 -0
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +16 -0
- package/dist/core/agent/workflows/reference/reference.js.map +1 -0
- package/dist/core/agent/workflows/reference/spine.js +297 -0
- package/dist/core/agent/workflows/reference/spine.js.map +1 -0
- package/dist/core/asyncapi/baseline/plan.js +110 -0
- package/dist/core/asyncapi/baseline/plan.js.map +1 -0
- package/dist/core/asyncapi/baseline/surfaces.js +54 -0
- package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
- package/dist/core/asyncapi/depth.js +164 -0
- package/dist/core/asyncapi/depth.js.map +1 -0
- package/dist/core/asyncapi/digest.js +141 -0
- package/dist/core/asyncapi/digest.js.map +1 -0
- package/dist/core/asyncapi/merge/components.js +91 -0
- package/dist/core/asyncapi/merge/components.js.map +1 -0
- package/dist/core/asyncapi/merge/error.js +33 -0
- package/dist/core/asyncapi/merge/error.js.map +1 -0
- package/dist/core/asyncapi/merge/markers.js +308 -0
- package/dist/core/asyncapi/merge/markers.js.map +1 -0
- package/dist/core/asyncapi/merge/merge.js +215 -0
- package/dist/core/asyncapi/merge/merge.js.map +1 -0
- package/dist/core/asyncapi/merge/pin.js +107 -0
- package/dist/core/asyncapi/merge/pin.js.map +1 -0
- package/dist/core/asyncapi/model.js +11 -0
- package/dist/core/asyncapi/model.js.map +1 -0
- package/dist/core/asyncapi/producers.js +50 -0
- package/dist/core/asyncapi/producers.js.map +1 -0
- package/dist/core/asyncapi/read.js +239 -0
- package/dist/core/asyncapi/read.js.map +1 -0
- package/dist/core/brief/brief.js +225 -0
- package/dist/core/brief/brief.js.map +1 -0
- package/dist/core/brief/checks.js +250 -0
- package/dist/core/brief/checks.js.map +1 -0
- package/dist/core/brief/landscape.js +194 -0
- package/dist/core/brief/landscape.js.map +1 -0
- package/dist/core/brief/targets.js +226 -0
- package/dist/core/brief/targets.js.map +1 -0
- package/dist/core/brief/unchecked.js +47 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +282 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/likec4.js +88 -0
- package/dist/core/c4/likec4.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js +132 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
- package/dist/core/c4/parsed/specification.js +78 -0
- package/dist/core/c4/parsed/specification.js.map +1 -0
- package/dist/core/c4/parsed/values.js +57 -0
- package/dist/core/c4/parsed/values.js.map +1 -0
- package/dist/core/c4/parsed/view-ids.js +75 -0
- package/dist/core/c4/parsed/view-ids.js.map +1 -0
- package/dist/core/c4/project/architecture.js +45 -0
- package/dist/core/c4/project/architecture.js.map +1 -0
- package/dist/core/c4/project/documents.js +58 -0
- package/dist/core/c4/project/documents.js.map +1 -0
- package/dist/core/c4/project/load.js +183 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/resolve/service.js +113 -0
- package/dist/core/c4/resolve/service.js.map +1 -0
- package/dist/core/c4/seed/fleet-file.js +201 -0
- package/dist/core/c4/seed/fleet-file.js.map +1 -0
- package/dist/core/c4/seed/items.js +91 -0
- package/dist/core/c4/seed/items.js.map +1 -0
- package/dist/core/c4/seed/stamp.js +48 -0
- package/dist/core/c4/seed/stamp.js.map +1 -0
- package/dist/core/c4/seed/template.js +109 -0
- package/dist/core/c4/seed/template.js.map +1 -0
- package/dist/core/c4/source-mask.js +82 -0
- package/dist/core/c4/source-mask.js.map +1 -0
- package/dist/core/c4/source-scan.js +227 -0
- package/dist/core/c4/source-scan.js.map +1 -0
- package/dist/core/c4/splice/authored-source.js +101 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -0
- package/dist/core/c4/splice/contract.js +10 -0
- package/dist/core/c4/splice/contract.js.map +1 -0
- package/dist/core/c4/splice/delta-blocks.js +126 -0
- package/dist/core/c4/splice/delta-blocks.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +36 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +269 -0
- package/dist/core/c4/splice/landscape-merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +194 -0
- package/dist/core/c4/splice/placement.js.map +1 -0
- package/dist/core/c4/workspace.js +172 -0
- package/dist/core/c4/workspace.js.map +1 -0
- package/dist/core/capabilities/authoring/notes.js +141 -0
- package/dist/core/capabilities/authoring/notes.js.map +1 -0
- package/dist/core/capabilities/capabilities.js +124 -0
- package/dist/core/capabilities/capabilities.js.map +1 -0
- package/dist/core/capabilities/delta/doc.js +50 -0
- package/dist/core/capabilities/delta/doc.js.map +1 -0
- package/dist/core/capabilities/delta/overlay.js +129 -0
- package/dist/core/capabilities/delta/overlay.js.map +1 -0
- package/dist/core/capabilities/delta/summary.js +57 -0
- package/dist/core/capabilities/delta/summary.js.map +1 -0
- package/dist/core/capabilities/delta/tree.js +43 -0
- package/dist/core/capabilities/delta/tree.js.map +1 -0
- package/dist/core/capabilities/delta/uncovered.js +222 -0
- package/dist/core/capabilities/delta/uncovered.js.map +1 -0
- package/dist/core/capabilities/findings.js +342 -0
- package/dist/core/capabilities/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/findings.js +181 -0
- package/dist/core/capabilities/realizes/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/join.js +143 -0
- package/dist/core/capabilities/realizes/join.js.map +1 -0
- package/dist/core/capabilities/rollup.js +199 -0
- package/dist/core/capabilities/rollup.js.map +1 -0
- package/dist/core/capabilities/tree.js +126 -0
- package/dist/core/capabilities/tree.js.map +1 -0
- package/dist/core/capabilities/usecase-join.js +277 -0
- package/dist/core/capabilities/usecase-join.js.map +1 -0
- package/dist/core/coherence/authoring/scaffold.js +177 -0
- package/dist/core/coherence/authoring/scaffold.js.map +1 -0
- package/dist/core/coherence/authoring/sentinels.js +74 -0
- package/dist/core/coherence/authoring/sentinels.js.map +1 -0
- package/dist/core/coherence/coherence.js +251 -0
- package/dist/core/coherence/coherence.js.map +1 -0
- package/dist/core/coherence/declared.js +269 -0
- package/dist/core/coherence/declared.js.map +1 -0
- package/dist/core/coherence/events/declared.js +206 -0
- package/dist/core/coherence/events/declared.js.map +1 -0
- package/dist/core/coherence/events/events.js +204 -0
- package/dist/core/coherence/events/events.js.map +1 -0
- package/dist/core/coherence/events/grades.js +166 -0
- package/dist/core/coherence/events/grades.js.map +1 -0
- package/dist/core/coherence/events/lookups.js +122 -0
- package/dist/core/coherence/events/lookups.js.map +1 -0
- package/dist/core/coherence/events/merged.js +40 -0
- package/dist/core/coherence/events/merged.js.map +1 -0
- package/dist/core/coherence/living.js +198 -0
- package/dist/core/coherence/living.js.map +1 -0
- package/dist/core/coherence/lookups.js +155 -0
- package/dist/core/coherence/lookups.js.map +1 -0
- package/dist/core/coherence/pending.js +39 -0
- package/dist/core/coherence/pending.js.map +1 -0
- package/dist/core/conflict-markers.js +60 -0
- package/dist/core/conflict-markers.js.map +1 -0
- package/dist/core/delta/claims.js +73 -0
- package/dist/core/delta/claims.js.map +1 -0
- package/dist/core/delta/delta.js +296 -0
- package/dist/core/delta/delta.js.map +1 -0
- package/dist/core/delta/document.js +177 -0
- package/dist/core/delta/document.js.map +1 -0
- package/dist/core/delta/scope.js +48 -0
- package/dist/core/delta/scope.js.map +1 -0
- package/dist/core/delta/select.js +237 -0
- package/dist/core/delta/select.js.map +1 -0
- package/dist/core/dependencies/dependencies.js +141 -0
- package/dist/core/dependencies/dependencies.js.map +1 -0
- package/dist/core/dependencies/facts.js +165 -0
- package/dist/core/dependencies/facts.js.map +1 -0
- package/dist/core/dependencies/fanin.js +88 -0
- package/dist/core/dependencies/fanin.js.map +1 -0
- package/dist/core/dependencies/graph.js +106 -0
- package/dist/core/dependencies/graph.js.map +1 -0
- package/dist/core/diff/base-git.js +148 -0
- package/dist/core/diff/base-git.js.map +1 -0
- package/dist/core/diff/base-state.js +209 -0
- package/dist/core/diff/base-state.js.map +1 -0
- package/dist/core/diff/current-state.js +96 -0
- package/dist/core/diff/current-state.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +188 -0
- package/dist/core/diff/scope/changed-paths.js.map +1 -0
- package/dist/core/diff/semantic.js +243 -0
- package/dist/core/diff/semantic.js.map +1 -0
- package/dist/core/diff/victims.js +247 -0
- package/dist/core/diff/victims.js.map +1 -0
- package/dist/core/docs.js +59 -55
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +222 -0
- package/dist/core/doctor/agents.js.map +1 -0
- package/dist/core/doctor/config.js +135 -0
- package/dist/core/doctor/config.js.map +1 -0
- package/dist/core/doctor/doctor.js +275 -0
- package/dist/core/doctor/doctor.js.map +1 -0
- package/dist/core/doctor/report.js +13 -0
- package/dist/core/doctor/report.js.map +1 -0
- package/dist/core/doctor/residue.js +110 -0
- package/dist/core/doctor/residue.js.map +1 -0
- package/dist/core/document/apply.js +77 -0
- package/dist/core/document/apply.js.map +1 -0
- package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
- package/dist/core/document/frontmatter.js.map +1 -0
- package/dist/core/document/grammar/directives.js +143 -0
- package/dist/core/document/grammar/directives.js.map +1 -0
- package/dist/core/document/parse.js +263 -0
- package/dist/core/document/parse.js.map +1 -0
- package/dist/core/document/scenarios.js +221 -0
- package/dist/core/document/scenarios.js.map +1 -0
- package/dist/core/document/spec.js +241 -0
- package/dist/core/document/spec.js.map +1 -0
- package/dist/core/{config.js → envelope/config.js} +79 -18
- package/dist/core/envelope/config.js.map +1 -0
- package/dist/core/envelope/json.js +175 -0
- package/dist/core/envelope/json.js.map +1 -0
- package/dist/core/{version.js → envelope/version.js} +3 -3
- package/dist/core/envelope/version.js.map +1 -0
- package/dist/core/explain/families.js +175 -0
- package/dist/core/explain/families.js.map +1 -0
- package/dist/core/explain/fix-tables.js +178 -0
- package/dist/core/explain/fix-tables.js.map +1 -0
- package/dist/core/explain/lookup.js +181 -0
- package/dist/core/explain/lookup.js.map +1 -0
- package/dist/core/explain/refusals.js +49 -0
- package/dist/core/explain/refusals.js.map +1 -0
- package/dist/core/explain/terms.js +144 -0
- package/dist/core/explain/terms.js.map +1 -0
- package/dist/core/explore/describe.js +105 -0
- package/dist/core/explore/describe.js.map +1 -0
- package/dist/core/{explore.js → explore/explore.js} +50 -101
- package/dist/core/explore/explore.js.map +1 -0
- package/dist/core/fleet-context.js +132 -77
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +201 -0
- package/dist/core/gate/checks.js.map +1 -0
- package/dist/core/gate/partners.js +205 -0
- package/dist/core/gate/partners.js.map +1 -0
- package/dist/core/gate/report.js +47 -0
- package/dist/core/gate/report.js.map +1 -0
- package/dist/core/gate/verification.js +113 -0
- package/dist/core/gate/verification.js.map +1 -0
- package/dist/core/gherkin/digest.js +60 -0
- package/dist/core/gherkin/digest.js.map +1 -0
- package/dist/core/gherkin/emit.js +177 -0
- package/dist/core/gherkin/emit.js.map +1 -0
- package/dist/core/gherkin/read.js +76 -0
- package/dist/core/gherkin/read.js.map +1 -0
- package/dist/core/gherkin/stale.js +234 -0
- package/dist/core/gherkin/stale.js.map +1 -0
- package/dist/core/gherkin/stamp.js +77 -0
- package/dist/core/gherkin/stamp.js.map +1 -0
- package/dist/core/gherkin/steps/catalogue.js +154 -0
- package/dist/core/gherkin/steps/catalogue.js.map +1 -0
- package/dist/core/gherkin/steps/inventory.js +98 -0
- package/dist/core/gherkin/steps/inventory.js.map +1 -0
- package/dist/core/gherkin/steps/phrase.js +72 -0
- package/dist/core/gherkin/steps/phrase.js.map +1 -0
- package/dist/core/glossary/delta.js +106 -0
- package/dist/core/glossary/delta.js.map +1 -0
- package/dist/core/glossary/tree.js +86 -0
- package/dist/core/glossary/tree.js.map +1 -0
- package/dist/core/kernel/concurrency.js.map +1 -0
- package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
- package/dist/core/kernel/document-bytes.js.map +1 -0
- package/dist/core/kernel/fences.js +42 -0
- package/dist/core/kernel/fences.js.map +1 -0
- package/dist/core/kernel/ids/capability.js +72 -0
- package/dist/core/kernel/ids/capability.js.map +1 -0
- package/dist/core/kernel/ids/dirs.js +33 -0
- package/dist/core/kernel/ids/dirs.js.map +1 -0
- package/dist/core/kernel/ids/feature.js +75 -0
- package/dist/core/kernel/ids/feature.js.map +1 -0
- package/dist/core/kernel/ids/service.js +156 -0
- package/dist/core/kernel/ids/service.js.map +1 -0
- package/dist/core/kernel/ids/subsystem.js +63 -0
- package/dist/core/kernel/ids/subsystem.js.map +1 -0
- package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
- package/dist/core/kernel/path-safety.js.map +1 -0
- package/dist/core/kernel/records.js.map +1 -0
- package/dist/core/links/backlinks.js +68 -0
- package/dist/core/links/backlinks.js.map +1 -0
- package/dist/core/links/case.js +74 -0
- package/dist/core/links/case.js.map +1 -0
- package/dist/core/links/corpus.js +104 -0
- package/dist/core/links/corpus.js.map +1 -0
- package/dist/core/links/findings.js +110 -0
- package/dist/core/links/findings.js.map +1 -0
- package/dist/core/links/parse.js +236 -0
- package/dist/core/links/parse.js.map +1 -0
- package/dist/core/mcp/argv.js +111 -0
- package/dist/core/mcp/argv.js.map +1 -0
- package/dist/core/mcp/author-tools.js +72 -0
- package/dist/core/mcp/author-tools.js.map +1 -0
- package/dist/core/mcp/framing.js +46 -0
- package/dist/core/mcp/framing.js.map +1 -0
- package/dist/core/mcp/protocol.js +280 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/tools.js +314 -0
- package/dist/core/mcp/tools.js.map +1 -0
- package/dist/core/obligations/obligations.js +106 -0
- package/dist/core/obligations/obligations.js.map +1 -0
- package/dist/core/openapi/baseline/gate.js +166 -0
- package/dist/core/openapi/baseline/gate.js.map +1 -0
- package/dist/core/openapi/baseline/plan.js +112 -0
- package/dist/core/openapi/baseline/plan.js.map +1 -0
- package/dist/core/openapi/baseline/record.js +175 -0
- package/dist/core/openapi/baseline/record.js.map +1 -0
- package/dist/core/openapi/depth.js +122 -0
- package/dist/core/openapi/depth.js.map +1 -0
- package/dist/core/openapi/digest.js +189 -0
- package/dist/core/openapi/digest.js.map +1 -0
- package/dist/core/{openapi.js → openapi/doc.js} +44 -106
- package/dist/core/openapi/doc.js.map +1 -0
- package/dist/core/openapi/merge/components.js +234 -0
- package/dist/core/openapi/merge/components.js.map +1 -0
- package/dist/core/openapi/merge/error.js +26 -0
- package/dist/core/openapi/merge/error.js.map +1 -0
- package/dist/core/openapi/merge/markers.js +208 -0
- package/dist/core/openapi/merge/markers.js.map +1 -0
- package/dist/core/openapi/merge/merge.js +256 -0
- package/dist/core/openapi/merge/merge.js.map +1 -0
- package/dist/core/openapi/merge/pin.js +128 -0
- package/dist/core/openapi/merge/pin.js.map +1 -0
- package/dist/core/openspec/decide/capabilities.js +244 -0
- package/dist/core/openspec/decide/capabilities.js.map +1 -0
- package/dist/core/openspec/decide/dispositions.js +63 -0
- package/dist/core/openspec/decide/dispositions.js.map +1 -0
- package/dist/core/openspec/decide/renames.js +179 -0
- package/dist/core/openspec/decide/renames.js.map +1 -0
- package/dist/core/openspec/decide/verdict.js +84 -0
- package/dist/core/openspec/decide/verdict.js.map +1 -0
- package/dist/core/openspec/inventory.js +221 -0
- package/dist/core/openspec/inventory.js.map +1 -0
- package/dist/core/openspec/model/mapping.js +76 -0
- package/dist/core/openspec/model/mapping.js.map +1 -0
- package/dist/core/openspec/model/model.js +65 -0
- package/dist/core/openspec/model/model.js.map +1 -0
- package/dist/core/openspec/scan/changes.js +235 -0
- package/dist/core/openspec/scan/changes.js.map +1 -0
- package/dist/core/openspec/scan/config.js +200 -0
- package/dist/core/openspec/scan/config.js.map +1 -0
- package/dist/core/openspec/scan/shape.js +260 -0
- package/dist/core/openspec/scan/shape.js.map +1 -0
- package/dist/core/openspec/scan/walk.js +133 -0
- package/dist/core/openspec/scan/walk.js.map +1 -0
- package/dist/core/openspec/scan/workspace.js +71 -0
- package/dist/core/openspec/scan/workspace.js.map +1 -0
- package/dist/core/owners/codeowners.js +141 -0
- package/dist/core/owners/codeowners.js.map +1 -0
- package/dist/core/pack/features.js +120 -0
- package/dist/core/pack/features.js.map +1 -0
- package/dist/core/pack/joins.js +112 -0
- package/dist/core/pack/joins.js.map +1 -0
- package/dist/core/pack/living.js +118 -0
- package/dist/core/pack/living.js.map +1 -0
- package/dist/core/pack/pack.js +76 -0
- package/dist/core/pack/pack.js.map +1 -0
- package/dist/core/permissions/permissions.js +87 -0
- package/dist/core/permissions/permissions.js.map +1 -0
- package/dist/core/projection/api.js +84 -0
- package/dist/core/projection/api.js.map +1 -0
- package/dist/core/projection/arch-slice.js +63 -0
- package/dist/core/projection/arch-slice.js.map +1 -0
- package/dist/core/projection/events.js +34 -0
- package/dist/core/projection/events.js.map +1 -0
- package/dist/core/provenance/findings.js +208 -0
- package/dist/core/provenance/findings.js.map +1 -0
- package/dist/core/provenance/git.js +225 -0
- package/dist/core/provenance/git.js.map +1 -0
- package/dist/core/provenance/gitq/moves.js +182 -0
- package/dist/core/provenance/gitq/moves.js.map +1 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
- package/dist/core/provenance/sample/scope.js +238 -0
- package/dist/core/provenance/sample/scope.js.map +1 -0
- package/dist/core/provenance/sample/sections.js +61 -0
- package/dist/core/provenance/sample/sections.js.map +1 -0
- package/dist/core/provenance/sources.js +263 -0
- package/dist/core/provenance/sources.js.map +1 -0
- package/dist/core/provenance/stamp.js +232 -0
- package/dist/core/provenance/stamp.js.map +1 -0
- package/dist/core/provenance/walk.js +157 -0
- package/dist/core/provenance/walk.js.map +1 -0
- package/dist/core/repo/authored/paths.js +192 -0
- package/dist/core/repo/authored/paths.js.map +1 -0
- package/dist/core/repo/entries.js +126 -0
- package/dist/core/repo/entries.js.map +1 -0
- package/dist/core/repo/paths.js +236 -0
- package/dist/core/repo/paths.js.map +1 -0
- package/dist/core/repo/repo.js +248 -0
- package/dist/core/repo/repo.js.map +1 -0
- package/dist/core/repo/service-target.js +153 -0
- package/dist/core/repo/service-target.js.map +1 -0
- package/dist/core/repo/state.js +58 -0
- package/dist/core/repo/state.js.map +1 -0
- package/dist/core/repo/tree/find.js +52 -0
- package/dist/core/repo/tree/find.js.map +1 -0
- package/dist/core/repo/tree/fs.js +64 -0
- package/dist/core/repo/tree/fs.js.map +1 -0
- package/dist/core/repo/tree/marker.js +61 -0
- package/dist/core/repo/tree/marker.js.map +1 -0
- package/dist/core/repo/tree/views.js +172 -0
- package/dist/core/repo/tree/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +234 -0
- package/dist/core/repo/tree/walk.js.map +1 -0
- package/dist/core/results.js +99 -2
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +131 -0
- package/dist/core/scaffold/landscape.js.map +1 -0
- package/dist/core/scaffold/readme.js +84 -0
- package/dist/core/scaffold/readme.js.map +1 -0
- package/dist/core/staging/commit.js +241 -0
- package/dist/core/staging/commit.js.map +1 -0
- package/dist/core/staging/interrupted.js +96 -0
- package/dist/core/staging/interrupted.js.map +1 -0
- package/dist/core/staging/lock.js +206 -0
- package/dist/core/staging/lock.js.map +1 -0
- package/dist/core/staging/recovery/finding.js +35 -0
- package/dist/core/staging/recovery/finding.js.map +1 -0
- package/dist/core/staging/recovery/intent.js +98 -0
- package/dist/core/staging/recovery/intent.js.map +1 -0
- package/dist/core/staging/recovery/recover.js +126 -0
- package/dist/core/staging/recovery/recover.js.map +1 -0
- package/dist/core/staging/recovery/residue.js +62 -0
- package/dist/core/staging/recovery/residue.js.map +1 -0
- package/dist/core/staging/snapshot.js +202 -0
- package/dist/core/staging/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +171 -0
- package/dist/core/staging/txn/forward.js.map +1 -0
- package/dist/core/staging/txn/journal.js +139 -0
- package/dist/core/staging/txn/journal.js.map +1 -0
- package/dist/core/staging/txn/transaction.js +75 -0
- package/dist/core/staging/txn/transaction.js.map +1 -0
- package/dist/core/staging/writes.js +113 -0
- package/dist/core/staging/writes.js.map +1 -0
- package/dist/core/status/actions/execution.js +49 -0
- package/dist/core/status/actions/execution.js.map +1 -0
- package/dist/core/status/contracts.js +102 -0
- package/dist/core/status/contracts.js.map +1 -0
- package/dist/core/status/feature/artifacts.js +171 -0
- package/dist/core/status/feature/artifacts.js.map +1 -0
- package/dist/core/status/feature/feature.js +207 -0
- package/dist/core/status/feature/feature.js.map +1 -0
- package/dist/core/status/feature/next.js +150 -0
- package/dist/core/status/feature/next.js.map +1 -0
- package/dist/core/status/feature/state.js +21 -0
- package/dist/core/status/feature/state.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +187 -0
- package/dist/core/status/fleet/fleet.js.map +1 -0
- package/dist/core/status/fleet/next.js +207 -0
- package/dist/core/status/fleet/next.js.map +1 -0
- package/dist/core/status/interrupted.js +97 -0
- package/dist/core/status/interrupted.js.map +1 -0
- package/dist/core/status/report.js +31 -0
- package/dist/core/status/report.js.map +1 -0
- package/dist/core/status/scan.js +41 -0
- package/dist/core/status/scan.js.map +1 -0
- package/dist/core/status/verification.js +201 -0
- package/dist/core/status/verification.js.map +1 -0
- package/dist/core/usecases/capability.js +169 -0
- package/dist/core/usecases/capability.js.map +1 -0
- package/dist/core/usecases/delta/claims.js +116 -0
- package/dist/core/usecases/delta/claims.js.map +1 -0
- package/dist/core/usecases/delta/flows.js +103 -0
- package/dist/core/usecases/delta/flows.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +235 -0
- package/dist/core/usecases/delta/overlay.js.map +1 -0
- package/dist/core/usecases/fleet.js +137 -0
- package/dist/core/usecases/fleet.js.map +1 -0
- package/dist/core/usecases/operations.js +111 -0
- package/dist/core/usecases/operations.js.map +1 -0
- package/dist/core/usecases/place.js +44 -0
- package/dist/core/usecases/place.js.map +1 -0
- package/dist/core/usecases/touch.js +101 -0
- package/dist/core/usecases/touch.js.map +1 -0
- package/dist/core/verify/answers.js +173 -0
- package/dist/core/verify/answers.js.map +1 -0
- package/dist/core/verify/build.js +150 -0
- package/dist/core/verify/build.js.map +1 -0
- package/dist/core/verify/checklist.js +201 -0
- package/dist/core/verify/checklist.js.map +1 -0
- package/dist/core/verify/claims/identity.js +61 -0
- package/dist/core/verify/claims/identity.js.map +1 -0
- package/dist/core/verify/cross/diff.js +97 -0
- package/dist/core/verify/cross/diff.js.map +1 -0
- package/dist/core/verify/evidence/contract.js +169 -0
- package/dist/core/verify/evidence/contract.js.map +1 -0
- package/dist/core/verify/file.js +257 -0
- package/dist/core/verify/file.js.map +1 -0
- package/dist/core/verify/pins/lint.js +157 -0
- package/dist/core/verify/pins/lint.js.map +1 -0
- package/dist/core/verify/pins/pin.js +95 -0
- package/dist/core/verify/pins/pin.js.map +1 -0
- package/dist/core/verify/record.js +142 -0
- package/dist/core/verify/record.js.map +1 -0
- package/dist/core/verify/store/commit.js +89 -0
- package/dist/core/verify/store/commit.js.map +1 -0
- package/dist/core/verify/store/render.js +57 -0
- package/dist/core/verify/store/render.js.map +1 -0
- package/dist/core/vocabulary/health.js +91 -0
- package/dist/core/vocabulary/health.js.map +1 -0
- package/dist/core/vocabulary/issue.js +55 -0
- package/dist/core/vocabulary/issue.js.map +1 -0
- package/dist/core/vocabulary/maturity.js +145 -0
- package/dist/core/vocabulary/maturity.js.map +1 -0
- package/dist/core/{report.js → vocabulary/report.js} +30 -2
- package/dist/core/vocabulary/report.js.map +1 -0
- package/dist/core/vocabulary/steps.js +266 -0
- package/dist/core/vocabulary/steps.js.map +1 -0
- package/dist/core/workspace/discover.js +239 -0
- package/dist/core/workspace/discover.js.map +1 -0
- package/dist/core/workspace/render.js +52 -0
- package/dist/core/workspace/render.js.map +1 -0
- package/examples/README.md +189 -0
- package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
- package/examples/docs/architecture/capabilities.yaml +31 -0
- package/examples/docs/architecture/landscape.likec4 +280 -0
- package/examples/docs/architecture/obligations.yaml +23 -0
- package/examples/docs/architecture/permissions.yaml +50 -0
- package/examples/docs/architecture/subsystems.likec4 +11 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
- package/examples/docs/capabilities/checkout/spec.md +55 -0
- package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
- package/examples/docs/capabilities/order-notifications/spec.md +43 -0
- package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
- package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
- package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
- package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
- package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
- package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
- package/examples/docs/glossary/order.md +15 -0
- package/examples/docs/glossary/payments/authorization.md +14 -0
- package/examples/docs/likec4.config.json +9 -0
- package/examples/docs/loam.json +3 -0
- package/examples/docs/services/checkout-web/model.likec4 +109 -0
- package/examples/docs/services/checkout-web/spec.md +91 -0
- package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
- package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
- package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
- package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
- package/examples/docs/services/order-service/arch.spec.md +127 -0
- package/examples/docs/services/order-service/asyncapi.yaml +108 -0
- package/examples/docs/services/order-service/health.yaml +57 -0
- package/examples/docs/services/order-service/model.likec4 +138 -0
- package/examples/docs/services/order-service/openapi.yaml +257 -0
- package/examples/docs/services/order-service/runbook.md +62 -0
- package/examples/docs/services/order-service/spec.md +134 -0
- package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
- package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
- package/examples/docs/services/payment-service/arch.spec.md +73 -0
- package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
- package/examples/docs/services/payment-service/health.yaml +39 -0
- package/examples/docs/services/payment-service/model.likec4 +67 -0
- package/examples/docs/services/payment-service/openapi.yaml +151 -0
- package/examples/docs/services/payment-service/runbook.md +37 -0
- package/examples/docs/services/payment-service/spec.md +88 -0
- package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
- package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
- package/examples/docs/services/platform/identity-service/health.yaml +70 -0
- package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
- package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
- package/examples/docs/services/platform/identity-service/runbook.md +95 -0
- package/examples/docs/services/platform/identity-service/spec.md +119 -0
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
- package/examples/docs/services/platform/notification-service/health.yaml +35 -0
- package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
- package/examples/docs/services/platform/notification-service/runbook.md +48 -0
- package/examples/docs/services/platform/notification-service/spec.md +112 -0
- package/examples/docs/services/platform/subsystem.yaml +2 -0
- package/examples/fleet.yaml +57 -0
- package/package.json +19 -6
- package/dist/commands/adopt.js +0 -240
- package/dist/commands/adopt.js.map +0 -1
- package/dist/commands/archive.js +0 -1313
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/delta.js +0 -388
- package/dist/commands/delta.js.map +0 -1
- package/dist/commands/docs-repo-gate.js.map +0 -1
- package/dist/commands/format.js +0 -17
- package/dist/commands/format.js.map +0 -1
- package/dist/commands/gherkin.js +0 -309
- package/dist/commands/gherkin.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.js +0 -291
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/migrate-openspec.js +0 -950
- package/dist/commands/migrate-openspec.js.map +0 -1
- package/dist/commands/new.js +0 -413
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/rebase.js +0 -356
- package/dist/commands/rebase.js.map +0 -1
- package/dist/commands/show.js.map +0 -1
- package/dist/commands/status.js +0 -254
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1591
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/verify.js +0 -775
- package/dist/commands/verify.js.map +0 -1
- package/dist/commands/vouch.js +0 -343
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2065
- package/dist/core/agent.js.map +0 -1
- package/dist/core/agents-stamp.js +0 -68
- package/dist/core/agents-stamp.js.map +0 -1
- package/dist/core/arch.js +0 -130
- package/dist/core/arch.js.map +0 -1
- package/dist/core/brief.js +0 -733
- package/dist/core/brief.js.map +0 -1
- package/dist/core/coherence.js +0 -577
- package/dist/core/coherence.js.map +0 -1
- package/dist/core/concurrency.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/delta.js +0 -476
- package/dist/core/delta.js.map +0 -1
- package/dist/core/dependencies.js +0 -356
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/doctor.js +0 -559
- package/dist/core/doctor.js.map +0 -1
- package/dist/core/document-bytes.js.map +0 -1
- package/dist/core/explore.js.map +0 -1
- package/dist/core/frontmatter.js.map +0 -1
- package/dist/core/gherkin.js +0 -463
- package/dist/core/gherkin.js.map +0 -1
- package/dist/core/health.js +0 -67
- package/dist/core/health.js.map +0 -1
- package/dist/core/ids.js +0 -122
- package/dist/core/ids.js.map +0 -1
- package/dist/core/issue.js +0 -9
- package/dist/core/issue.js.map +0 -1
- package/dist/core/json.js +0 -74
- package/dist/core/json.js.map +0 -1
- package/dist/core/likec4.js +0 -479
- package/dist/core/likec4.js.map +0 -1
- package/dist/core/maturity.js +0 -66
- package/dist/core/maturity.js.map +0 -1
- package/dist/core/openapi-merge.js +0 -552
- package/dist/core/openapi-merge.js.map +0 -1
- package/dist/core/openapi.js.map +0 -1
- package/dist/core/openspec-inventory.js +0 -1440
- package/dist/core/openspec-inventory.js.map +0 -1
- package/dist/core/path-safety.js.map +0 -1
- package/dist/core/provenance.js +0 -720
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -436
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -481
- package/dist/core/spec.js.map +0 -1
- package/dist/core/staging.js +0 -890
- package/dist/core/staging.js.map +0 -1
- package/dist/core/status.js +0 -1104
- package/dist/core/status.js.map +0 -1
- package/dist/core/steps.js +0 -72
- package/dist/core/steps.js.map +0 -1
- package/dist/core/verify.js +0 -807
- package/dist/core/verify.js.map +0 -1
- package/dist/core/version.js.map +0 -1
- /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
- /package/dist/core/{records.js → kernel/records.js} +0 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning a name a caller typed into a name loam may join into a path.
|
|
3
|
+
*
|
|
4
|
+
* `unfiledServicePaths(docsDir, name)` spells `<docsDir>/services/<name>/`,
|
|
5
|
+
* and its parameter is `PathableService`: an unchecked name at that call site
|
|
6
|
+
* no longer compiles, and this module is where a name a caller typed EARNS the
|
|
7
|
+
* type. The history is why it exists. Six commands guard their argument with
|
|
8
|
+
* `assertServiceId` at the boundary; `validate` never did, and both of its
|
|
9
|
+
* entry points — `--service <id>` and the positional `<target>` — reached
|
|
10
|
+
* the path builder with whatever argv held. `--service ../../outside/services/x`
|
|
11
|
+
* resolved ABOVE the docs repo, and where a `spec.md` happened to sit there
|
|
12
|
+
* loam opened it, graded it, and reported its frontmatter through `--json`.
|
|
13
|
+
*
|
|
14
|
+
* The grammar alone is not the answer, and this is the whole reason this module
|
|
15
|
+
* exists rather than a call to `assertServiceId`. `services/Payment Service/`
|
|
16
|
+
* is a directory `loam list` shows, `validate --all` grades, and
|
|
17
|
+
* `service.id-invalid` calls an error nobody can fix without a rename — so it
|
|
18
|
+
* is exactly the directory somebody points `validate --service` at. Refusing on
|
|
19
|
+
* the grammar would make the one service loam complains about the one service
|
|
20
|
+
* loam cannot look at.
|
|
21
|
+
*
|
|
22
|
+
* So the enumeration answers first and the grammar answers second:
|
|
23
|
+
*
|
|
24
|
+
* in `services/` → the enumeration's OWN id, which is a name loam
|
|
25
|
+
* already reads off disk every time it lists the fleet.
|
|
26
|
+
* Legal or not, it exists, and joining it lands where
|
|
27
|
+
* the directory is.
|
|
28
|
+
* not there, legal → the caller's name. Nothing exists yet; the run
|
|
29
|
+
* continues and grades it `service.unknown`, with the
|
|
30
|
+
* near-miss hints that make a typo diagnosable.
|
|
31
|
+
* not there, illegal → refused, before any path is built.
|
|
32
|
+
*
|
|
33
|
+
* `show` and `status` already resolve against the enumeration this way. What
|
|
34
|
+
* they do not do is fall through to the grammar, which is why they answer
|
|
35
|
+
* "no such service" to a name that could never BE a service — a worse sentence
|
|
36
|
+
* for the same input.
|
|
37
|
+
*/
|
|
38
|
+
import { listServices } from "./repo.js";
|
|
39
|
+
import { parseServiceId } from "../kernel/ids/service.js";
|
|
40
|
+
import { serviceTreePath } from "../kernel/ids/dirs.js";
|
|
41
|
+
import { servicePathsAt, unfiledServicePaths } from "./paths.js";
|
|
42
|
+
/**
|
|
43
|
+
* The enumerated services, or the empty list when `services/` cannot be
|
|
44
|
+
* enumerated at all. The swallow is deliberate and spelled ONCE: every
|
|
45
|
+
* repository-aware resolver wants "which services exist" as context, and an
|
|
46
|
+
* unenumerable `services/` is that repository's own diagnosis
|
|
47
|
+
* (`services-missing`), never the asking check's. Six call sites each carried
|
|
48
|
+
* this try/catch in their own spelling before it lived here — which is exactly
|
|
49
|
+
* the shape docs/CODE-STYLE.md warns about: an errno fix lands in one copy and
|
|
50
|
+
* the other five keep the old behaviour. The existence probes that used to ask
|
|
51
|
+
* `existsSync(services/<id>/)` ask membership HERE for the same tolerance:
|
|
52
|
+
* `validate --feature` must run in a docs repo with no `services/` at all,
|
|
53
|
+
* where enumerating is a refusal but "does this service exist" still has the
|
|
54
|
+
* honest answer no.
|
|
55
|
+
*/
|
|
56
|
+
export async function enumeratedServices(docsDir, fleet) {
|
|
57
|
+
try {
|
|
58
|
+
return await listServices(docsDir, fleet);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** The id projection of `enumeratedServices` — most membership asks want only the names. */
|
|
65
|
+
export async function enumeratedServiceIds(docsDir, fleet) {
|
|
66
|
+
return (await enumeratedServices(docsDir, fleet)).map((s) => s.id);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The artifact paths of a service wherever it lives: the enumeration's own
|
|
70
|
+
* directory when the id exists anywhere in the tree, the unfiled root
|
|
71
|
+
* spelling otherwise — so an absent service still probes, grades
|
|
72
|
+
* (`service.unknown`) and CREATES exactly as it did when the tree was flat.
|
|
73
|
+
* This is the one bridge the ~40 former `servicePaths` call sites resolve
|
|
74
|
+
* through — and it is NOT free: without a `fleet` context every call is a
|
|
75
|
+
* fresh fleet enumeration (one readdir per directory walked plus a
|
|
76
|
+
* frontmatter read and several existence probes per service), which measured
|
|
77
|
+
* ~13 ms per call on a 120-service fleet. A call site inside a loop must
|
|
78
|
+
* thread the command's `FleetContext`; a caller that already holds a
|
|
79
|
+
* `ServiceEntry` should spell `servicePathsAt(entry.dir)` directly and pay
|
|
80
|
+
* nothing.
|
|
81
|
+
*/
|
|
82
|
+
/**
|
|
83
|
+
* Where a service sits under `services/`, repo-relative and WITHOUT a trailing
|
|
84
|
+
* slash — for a message that names an existing directory to a reader.
|
|
85
|
+
*
|
|
86
|
+
* The join it replaces is `services/${id}`, which is right for an unfiled
|
|
87
|
+
* service and wrong for every filed one; `paths.ts` states the same rule for
|
|
88
|
+
* artifact paths and this is its display sibling. A name the enumeration does
|
|
89
|
+
* not answer to falls back to the unfiled spelling, which is the only honest
|
|
90
|
+
* answer: there is no directory to point at, and that is usually what the
|
|
91
|
+
* message is about.
|
|
92
|
+
*/
|
|
93
|
+
export async function serviceTreePathOf(docsDir, service, fleet) {
|
|
94
|
+
const entry = (await enumeratedServices(docsDir, fleet)).find((s) => s.id === service);
|
|
95
|
+
return entry === undefined ? `services/${service}` : serviceTreePath(entry);
|
|
96
|
+
}
|
|
97
|
+
export async function locateServicePaths(docsDir, service, fleet) {
|
|
98
|
+
const entry = (await enumeratedServices(docsDir, fleet)).find((s) => s.id === service);
|
|
99
|
+
return entry === undefined ? unfiledServicePaths(docsDir, service) : servicePathsAt(entry.dir);
|
|
100
|
+
}
|
|
101
|
+
export async function resolveServiceTarget(docsDir, name, label, fleet) {
|
|
102
|
+
// The enumeration is asked first, and its answer is the value that travels
|
|
103
|
+
// on: `entry.id` rather than `name`. They are equal as strings, and only one
|
|
104
|
+
// of them carries the fact that a `readdir` produced it. The resolved `dir`
|
|
105
|
+
// travels too, so a caller that goes on to read artifacts can spell
|
|
106
|
+
// `servicePathsAt(dir)` without a second enumeration; it is absent exactly
|
|
107
|
+
// when the name resolved by grammar alone — nothing exists to point at.
|
|
108
|
+
const entry = (await listServices(docsDir, fleet)).find((s) => s.id === name);
|
|
109
|
+
if (entry !== undefined)
|
|
110
|
+
return { ok: true, id: entry.id, dir: entry.dir };
|
|
111
|
+
return parseServiceId(name, label);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The same resolution for a name a DOCUMENT declared — an edge target's
|
|
115
|
+
* `metadata { service '…' }` binding, or its title standing in for one — where
|
|
116
|
+
* the caller needs an answer instead of a refusal: which enumerated directory,
|
|
117
|
+
* if any, does this text name?
|
|
118
|
+
*
|
|
119
|
+
* The enumeration is the fleet's `services/` plus whatever directory names the
|
|
120
|
+
* caller has already read for itself — a feature's own `specs/<svc>/`, the only
|
|
121
|
+
* place a service the feature INTRODUCES has a directory at all. There is
|
|
122
|
+
* deliberately no grammar arm here, in either direction: `services/Payment
|
|
123
|
+
* Service/` keeps resolving for the banner's reason, and a legal-looking name
|
|
124
|
+
* that matches no directory has no living file anywhere, so the caller's
|
|
125
|
+
* business is to grade it absent — never to build a path from it.
|
|
126
|
+
*
|
|
127
|
+
* The index is built on the first ask, not at construction: coherence asks per
|
|
128
|
+
* op-edge and features routinely carry none, so the enumeration only runs once
|
|
129
|
+
* an answer matters. A repo whose `services/` cannot be enumerated resolves
|
|
130
|
+
* only the caller's own names — every living probe would have found nothing
|
|
131
|
+
* there anyway, and `services-missing` is that repository's diagnosis, not
|
|
132
|
+
* this document's.
|
|
133
|
+
*/
|
|
134
|
+
export function enumeratedServiceIndex(docsDir, known, fleet) {
|
|
135
|
+
let index;
|
|
136
|
+
return async (name) => {
|
|
137
|
+
if (index === undefined) {
|
|
138
|
+
index = new Map();
|
|
139
|
+
for (const id of known)
|
|
140
|
+
index.set(id, id);
|
|
141
|
+
try {
|
|
142
|
+
for (const s of await listServices(docsDir, fleet))
|
|
143
|
+
index.set(s.id, s.id);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// The docblock's last paragraph: no enumerable services/ means the
|
|
147
|
+
// fleet half of the index is empty, not that the question is an error.
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return index.get(name);
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=service-target.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-target.js","sourceRoot":"","sources":["../../../src/core/repo/service-target.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,cAAc,EAA2C,MAAM,0BAA0B,CAAC;AAEnG,OAAO,EAAE,eAAe,EAAiC,MAAM,uBAAuB,CAAC;AACvF,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAqB,MAAM,YAAY,CAAC;AAOpF;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,OAAgB,EAChB,KAAoB;IAEpB,IAAI,CAAC;QACH,OAAO,MAAM,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,4FAA4F;AAC5F,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAgB,EAChB,KAAoB;IAEpB,OAAO,CAAC,MAAM,kBAAkB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAAgB,EAChB,OAAe,EACf,KAAoB;IAEpB,MAAM,KAAK,GAAG,CAAC,MAAM,kBAAkB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC;IACvF,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,OAAO,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,OAAgB,EAChB,OAAwB,EACxB,KAAoB;IAEpB,MAAM,KAAK,GAAG,CAAC,MAAM,kBAAkB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC;IACvF,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AACjG,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAgB,EAChB,IAAY,EACZ,KAAa,EACb,KAAoB;IAEpB,2EAA2E;IAC3E,6EAA6E;IAC7E,4EAA4E;IAC5E,oEAAoE;IACpE,2EAA2E;IAC3E,wEAAwE;IACxE,MAAM,KAAK,GAAG,CAAC,MAAM,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC;IAC9E,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC;IAC3E,OAAO,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAgB,EAChB,KAA8B,EAC9B,KAAoB;IAEpB,IAAI,KAA4C,CAAC;IACjD,OAAO,KAAK,EAAE,IAAY,EAAqC,EAAE;QAC/D,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,KAAK,GAAG,IAAI,GAAG,EAAwB,CAAC;YACxC,KAAK,MAAM,EAAE,IAAI,KAAK;gBAAE,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAC1C,IAAI,CAAC;gBACH,KAAK,MAAM,CAAC,IAAI,MAAM,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC;oBAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;YAC5E,CAAC;YAAC,MAAM,CAAC;gBACP,mEAAmE;gBACnE,uEAAuE;YACzE,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Turning a name a caller typed into a name loam may join into a path.\n *\n * `unfiledServicePaths(docsDir, name)` spells `<docsDir>/services/<name>/`,\n * and its parameter is `PathableService`: an unchecked name at that call site\n * no longer compiles, and this module is where a name a caller typed EARNS the\n * type. The history is why it exists. Six commands guard their argument with\n * `assertServiceId` at the boundary; `validate` never did, and both of its\n * entry points — `--service <id>` and the positional `<target>` — reached\n * the path builder with whatever argv held. `--service ../../outside/services/x`\n * resolved ABOVE the docs repo, and where a `spec.md` happened to sit there\n * loam opened it, graded it, and reported its frontmatter through `--json`.\n *\n * The grammar alone is not the answer, and this is the whole reason this module\n * exists rather than a call to `assertServiceId`. `services/Payment Service/`\n * is a directory `loam list` shows, `validate --all` grades, and\n * `service.id-invalid` calls an error nobody can fix without a rename — so it\n * is exactly the directory somebody points `validate --service` at. Refusing on\n * the grammar would make the one service loam complains about the one service\n * loam cannot look at.\n *\n * So the enumeration answers first and the grammar answers second:\n *\n * in `services/` → the enumeration's OWN id, which is a name loam\n * already reads off disk every time it lists the fleet.\n * Legal or not, it exists, and joining it lands where\n * the directory is.\n * not there, legal → the caller's name. Nothing exists yet; the run\n * continues and grades it `service.unknown`, with the\n * near-miss hints that make a typo diagnosable.\n * not there, illegal → refused, before any path is built.\n *\n * `show` and `status` already resolve against the enumeration this way. What\n * they do not do is fall through to the grammar, which is why they answer\n * \"no such service\" to a name that could never BE a service — a worse sentence\n * for the same input.\n */\nimport { listServices } from \"./repo.js\";\nimport { parseServiceId, type PathableService, type RawServiceId } from \"../kernel/ids/service.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport { serviceTreePath, type DocsDir, type ServiceDir } from \"../kernel/ids/dirs.js\";\nimport { servicePathsAt, unfiledServicePaths, type ServicePaths } from \"./paths.js\";\nimport type { ServiceEntry } from \"./entries.js\";\n\nexport type ServiceTarget =\n | { readonly ok: true; readonly id: RawServiceId; readonly dir?: ServiceDir }\n | { readonly ok: false; readonly problem: string };\n\n/**\n * The enumerated services, or the empty list when `services/` cannot be\n * enumerated at all. The swallow is deliberate and spelled ONCE: every\n * repository-aware resolver wants \"which services exist\" as context, and an\n * unenumerable `services/` is that repository's own diagnosis\n * (`services-missing`), never the asking check's. Six call sites each carried\n * this try/catch in their own spelling before it lived here — which is exactly\n * the shape docs/CODE-STYLE.md warns about: an errno fix lands in one copy and\n * the other five keep the old behaviour. The existence probes that used to ask\n * `existsSync(services/<id>/)` ask membership HERE for the same tolerance:\n * `validate --feature` must run in a docs repo with no `services/` at all,\n * where enumerating is a refusal but \"does this service exist\" still has the\n * honest answer no.\n */\nexport async function enumeratedServices(\n docsDir: DocsDir,\n fleet?: FleetContext,\n): Promise<ServiceEntry[]> {\n try {\n return await listServices(docsDir, fleet);\n } catch {\n return [];\n }\n}\n\n/** The id projection of `enumeratedServices` — most membership asks want only the names. */\nexport async function enumeratedServiceIds(\n docsDir: DocsDir,\n fleet?: FleetContext,\n): Promise<RawServiceId[]> {\n return (await enumeratedServices(docsDir, fleet)).map((s) => s.id);\n}\n\n/**\n * The artifact paths of a service wherever it lives: the enumeration's own\n * directory when the id exists anywhere in the tree, the unfiled root\n * spelling otherwise — so an absent service still probes, grades\n * (`service.unknown`) and CREATES exactly as it did when the tree was flat.\n * This is the one bridge the ~40 former `servicePaths` call sites resolve\n * through — and it is NOT free: without a `fleet` context every call is a\n * fresh fleet enumeration (one readdir per directory walked plus a\n * frontmatter read and several existence probes per service), which measured\n * ~13 ms per call on a 120-service fleet. A call site inside a loop must\n * thread the command's `FleetContext`; a caller that already holds a\n * `ServiceEntry` should spell `servicePathsAt(entry.dir)` directly and pay\n * nothing.\n */\n/**\n * Where a service sits under `services/`, repo-relative and WITHOUT a trailing\n * slash — for a message that names an existing directory to a reader.\n *\n * The join it replaces is `services/${id}`, which is right for an unfiled\n * service and wrong for every filed one; `paths.ts` states the same rule for\n * artifact paths and this is its display sibling. A name the enumeration does\n * not answer to falls back to the unfiled spelling, which is the only honest\n * answer: there is no directory to point at, and that is usually what the\n * message is about.\n */\nexport async function serviceTreePathOf(\n docsDir: DocsDir,\n service: string,\n fleet?: FleetContext,\n): Promise<string> {\n const entry = (await enumeratedServices(docsDir, fleet)).find((s) => s.id === service);\n return entry === undefined ? `services/${service}` : serviceTreePath(entry);\n}\n\nexport async function locateServicePaths(\n docsDir: DocsDir,\n service: PathableService,\n fleet?: FleetContext,\n): Promise<ServicePaths> {\n const entry = (await enumeratedServices(docsDir, fleet)).find((s) => s.id === service);\n return entry === undefined ? unfiledServicePaths(docsDir, service) : servicePathsAt(entry.dir);\n}\n\nexport async function resolveServiceTarget(\n docsDir: DocsDir,\n name: string,\n label: string,\n fleet?: FleetContext,\n): Promise<ServiceTarget> {\n // The enumeration is asked first, and its answer is the value that travels\n // on: `entry.id` rather than `name`. They are equal as strings, and only one\n // of them carries the fact that a `readdir` produced it. The resolved `dir`\n // travels too, so a caller that goes on to read artifacts can spell\n // `servicePathsAt(dir)` without a second enumeration; it is absent exactly\n // when the name resolved by grammar alone — nothing exists to point at.\n const entry = (await listServices(docsDir, fleet)).find((s) => s.id === name);\n if (entry !== undefined) return { ok: true, id: entry.id, dir: entry.dir };\n return parseServiceId(name, label);\n}\n\n/**\n * The same resolution for a name a DOCUMENT declared — an edge target's\n * `metadata { service '…' }` binding, or its title standing in for one — where\n * the caller needs an answer instead of a refusal: which enumerated directory,\n * if any, does this text name?\n *\n * The enumeration is the fleet's `services/` plus whatever directory names the\n * caller has already read for itself — a feature's own `specs/<svc>/`, the only\n * place a service the feature INTRODUCES has a directory at all. There is\n * deliberately no grammar arm here, in either direction: `services/Payment\n * Service/` keeps resolving for the banner's reason, and a legal-looking name\n * that matches no directory has no living file anywhere, so the caller's\n * business is to grade it absent — never to build a path from it.\n *\n * The index is built on the first ask, not at construction: coherence asks per\n * op-edge and features routinely carry none, so the enumeration only runs once\n * an answer matters. A repo whose `services/` cannot be enumerated resolves\n * only the caller's own names — every living probe would have found nothing\n * there anyway, and `services-missing` is that repository's diagnosis, not\n * this document's.\n */\nexport function enumeratedServiceIndex(\n docsDir: DocsDir,\n known: readonly RawServiceId[],\n fleet?: FleetContext,\n): (name: string) => Promise<RawServiceId | undefined> {\n let index: Map<string, RawServiceId> | undefined;\n return async (name: string): Promise<RawServiceId | undefined> => {\n if (index === undefined) {\n index = new Map<string, RawServiceId>();\n for (const id of known) index.set(id, id);\n try {\n for (const s of await listServices(docsDir, fleet)) index.set(s.id, s.id);\n } catch {\n // The docblock's last paragraph: no enumerable services/ means the\n // fleet half of the index is empty, not that the question is an error.\n }\n }\n return index.get(name);\n };\n}\n"]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is there a docs repo there at all?
|
|
3
|
+
*
|
|
4
|
+
* Three answers, not two: missing, present-but-empty, and ok. The middle one
|
|
5
|
+
* exists because a freshly cloned docs repo whose `services/` git never tracked
|
|
6
|
+
* looks identical to a directory nobody wired — and the fix differs.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, statSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
export function docsRepoState(docsDir) {
|
|
11
|
+
const path = docsDir;
|
|
12
|
+
let isDir = false;
|
|
13
|
+
try {
|
|
14
|
+
isDir = statSync(path).isDirectory();
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
isDir = false;
|
|
18
|
+
}
|
|
19
|
+
if (!isDir)
|
|
20
|
+
return { kind: "missing", path };
|
|
21
|
+
return { kind: existsSync(join(path, "services")) ? "ok" : "no-services", path };
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The docs repo is not there. Thrown rather than returned because every caller
|
|
25
|
+
* of the enumeration wants the same thing — to stop — and a `[]` return is the
|
|
26
|
+
* bug this closes. `message` is written for a human at a terminal: the CLI's
|
|
27
|
+
* top-level handler prints it verbatim, and commands that catch it report
|
|
28
|
+
* `repository-unavailable`.
|
|
29
|
+
*/
|
|
30
|
+
export class DocsRepoUnavailableError extends Error {
|
|
31
|
+
state;
|
|
32
|
+
constructor(state, message) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.state = state;
|
|
35
|
+
this.name = "DocsRepoUnavailableError";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Refuse to enumerate a docs repo that is not one. `allow` names the states the
|
|
40
|
+
* caller can honestly survive: features/ may be absent from a real docs repo
|
|
41
|
+
* (nothing is in flight yet), so feature enumeration only insists the docs repo
|
|
42
|
+
* EXISTS, while service enumeration insists it is shaped like a docs repo — a
|
|
43
|
+
* `services/` directory is what makes it one.
|
|
44
|
+
*/
|
|
45
|
+
export function requireDocsRepo(docsDir, allow) {
|
|
46
|
+
const state = docsRepoState(docsDir);
|
|
47
|
+
if (allow.includes(state.kind))
|
|
48
|
+
return;
|
|
49
|
+
if (state.kind === "missing") {
|
|
50
|
+
throw new DocsRepoUnavailableError(state, `The configured docs repo does not exist: ${state.path}. ` +
|
|
51
|
+
"Fix `docsDir` in loam.json, clone the docs repo there, " +
|
|
52
|
+
"or run `loam init --docs <dir> --create` to make a new one.");
|
|
53
|
+
}
|
|
54
|
+
throw new DocsRepoUnavailableError(state, `${state.path} is not a docs repo — it has no services/ directory. ` +
|
|
55
|
+
"Point `docsDir` in loam.json at the shared docs repo, " +
|
|
56
|
+
"or run `loam init --docs <dir> --create` to make one.");
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../../src/core/repo/state.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AA8BjC,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,MAAM,IAAI,GAAG,OAAO,CAAC;IACrB,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,IAAI,CAAC;QACH,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,KAAK,GAAG,KAAK,CAAC;IAChB,CAAC;IACD,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAC7C,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,EAAE,IAAI,EAAE,CAAC;AACnF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IAEtC,KAAK;IADhB,YACW,KAAoB,EAC7B,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;qBAHN,KAAK;QAId,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;IACzC,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,OAAe,EAAE,KAAqB;IACpE,MAAM,KAAK,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACrC,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO;IACvC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,wBAAwB,CAChC,KAAK,EACL,4CAA4C,KAAK,CAAC,IAAI,IAAI;YACxD,yDAAyD;YACzD,6DAA6D,CAChE,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,wBAAwB,CAChC,KAAK,EACL,GAAG,KAAK,CAAC,IAAI,uDAAuD;QAClE,wDAAwD;QACxD,uDAAuD,CAC1D,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Is there a docs repo there at all?\n *\n * Three answers, not two: missing, present-but-empty, and ok. The middle one\n * exists because a freshly cloned docs repo whose `services/` git never tracked\n * looks identical to a directory nobody wired — and the fix differs.\n */\nimport { existsSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n/* ------------------------------------------------------------------ */\n/* Is there a docs repo there at all? */\n/* ------------------------------------------------------------------ */\n\n/**\n * What is actually at `docsDir`. Three answers, because they point at three\n * different fixes and the enumeration below used to give all of them the same\n * one — an empty list:\n *\n * - `missing` — nothing there (or not a directory): `docsDir` in loam.json\n * is wrong, or the docs repo was never cloned;\n * - `no-services` — a directory, but with no `services/`: it is some other\n * directory, most often the service repo itself after a typo;\n * - `ok` — a docs repo. `services/` may still be EMPTY, and that is a\n * legitimate state: a docs repo before the first `loam adopt`.\n *\n * \"Empty fleet\" and \"wrong path\" are the same output only if nobody asks this\n * question, and a green `loam list` over a docsDir that does not exist is worse\n * than a red one: it says the fleet is fine.\n */\nexport type DocsRepoKind = \"missing\" | \"no-services\" | \"ok\";\n\nexport interface DocsRepoState {\n kind: DocsRepoKind;\n /** The path examined, so a caller can quote it without re-deriving it. */\n path: string;\n}\n\nexport function docsRepoState(docsDir: string): DocsRepoState {\n const path = docsDir;\n let isDir = false;\n try {\n isDir = statSync(path).isDirectory();\n } catch {\n isDir = false;\n }\n if (!isDir) return { kind: \"missing\", path };\n return { kind: existsSync(join(path, \"services\")) ? \"ok\" : \"no-services\", path };\n}\n\n/**\n * The docs repo is not there. Thrown rather than returned because every caller\n * of the enumeration wants the same thing — to stop — and a `[]` return is the\n * bug this closes. `message` is written for a human at a terminal: the CLI's\n * top-level handler prints it verbatim, and commands that catch it report\n * `repository-unavailable`.\n */\nexport class DocsRepoUnavailableError extends Error {\n constructor(\n readonly state: DocsRepoState,\n message: string,\n ) {\n super(message);\n this.name = \"DocsRepoUnavailableError\";\n }\n}\n\n/**\n * Refuse to enumerate a docs repo that is not one. `allow` names the states the\n * caller can honestly survive: features/ may be absent from a real docs repo\n * (nothing is in flight yet), so feature enumeration only insists the docs repo\n * EXISTS, while service enumeration insists it is shaped like a docs repo — a\n * `services/` directory is what makes it one.\n */\nexport function requireDocsRepo(docsDir: string, allow: DocsRepoKind[]): void {\n const state = docsRepoState(docsDir);\n if (allow.includes(state.kind)) return;\n if (state.kind === \"missing\") {\n throw new DocsRepoUnavailableError(\n state,\n `The configured docs repo does not exist: ${state.path}. ` +\n \"Fix `docsDir` in loam.json, clone the docs repo there, \" +\n \"or run `loam init --docs <dir> --create` to make a new one.\",\n );\n }\n throw new DocsRepoUnavailableError(\n state,\n `${state.path} is not a docs repo — it has no services/ directory. ` +\n \"Point `docsDir` in loam.json at the shared docs repo, \" +\n \"or run `loam init --docs <dir> --create` to make one.\",\n );\n}\n"]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Name resolution over a walked tree — the questions every `loam subsystem`
|
|
3
|
+
* verb asks before it acts: what does this name denote, what sits beneath a
|
|
4
|
+
* subsystem, and which names come close to a miss. One module so `move`,
|
|
5
|
+
* `rm`, `new` and `history` cannot grow four private spellings of "the flat
|
|
6
|
+
* namespace" — the walk already enforces one namespace, and resolution must
|
|
7
|
+
* read it the same way.
|
|
8
|
+
*/
|
|
9
|
+
import { closeIds } from "../../c4/arch.js";
|
|
10
|
+
/**
|
|
11
|
+
* Resolve one name against the flat namespace. Services win a tie on
|
|
12
|
+
* purpose: a tie IS `subsystem.name-collision`, already an error finding,
|
|
13
|
+
* and while it stands the service reading keeps every id-addressed command
|
|
14
|
+
* coherent.
|
|
15
|
+
*/
|
|
16
|
+
export function findInTree(tree, name) {
|
|
17
|
+
const service = tree.services.find((s) => s.id === name);
|
|
18
|
+
if (service !== undefined)
|
|
19
|
+
return { kind: "service", service };
|
|
20
|
+
const subsystem = tree.subsystems.find((s) => s.name === name);
|
|
21
|
+
if (subsystem !== undefined)
|
|
22
|
+
return { kind: "subsystem", subsystem };
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
/** Every name the flat namespace holds — service ids and subsystem names together. */
|
|
26
|
+
export function treeNames(tree) {
|
|
27
|
+
return [...tree.services.map((s) => s.id), ...tree.subsystems.map((s) => s.name)];
|
|
28
|
+
}
|
|
29
|
+
/** The near-miss hints a refusal offers, over whichever pool the verb resolves against. */
|
|
30
|
+
export function nearestTreeNames(name, pool) {
|
|
31
|
+
return closeIds(name, pool);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Does the subsystem at `subPath` contain `path` — itself, or anywhere beneath
|
|
35
|
+
* it? The move-into-own-subtree refusal. The parameter is the name chain, not
|
|
36
|
+
* a `SubsystemEntry`, because a chain is all this comparison reads — the one
|
|
37
|
+
* caller outside this module (`move`) has a chain it just composed and no
|
|
38
|
+
* entry to hand, and a structural parameter it never reads would force a
|
|
39
|
+
* placeholder there.
|
|
40
|
+
*/
|
|
41
|
+
export function withinSubsystem(subPath, path) {
|
|
42
|
+
return subPath.length <= path.length && subPath.every((name, i) => path[i] === name);
|
|
43
|
+
}
|
|
44
|
+
/** Services anywhere beneath a subsystem — the transitive membership the views and counts share. */
|
|
45
|
+
export function servicesUnder(tree, sub) {
|
|
46
|
+
return tree.services.filter((s) => sub.path.every((name, i) => s.subsystem[i] === name));
|
|
47
|
+
}
|
|
48
|
+
/** Child subsystems anywhere beneath one — `rm`'s other half of "empty". */
|
|
49
|
+
export function subsystemsUnder(tree, sub) {
|
|
50
|
+
return tree.subsystems.filter((c) => c !== sub && withinSubsystem(sub.path, c.path));
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=find.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find.js","sourceRoot":"","sources":["../../../../src/core/repo/tree/find.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAS5C;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,IAAe,EAAE,IAAY;IACtD,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC;IACzD,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;IAC/D,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAC/D,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC;IACrE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,SAAS,CAAC,IAAe;IACvC,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACpF,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,IAAc;IAC3D,OAAO,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC9B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,OAAiB,EAAE,IAAc;IAC/D,OAAO,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;AACvF,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,aAAa,CAAC,IAAe,EAAE,GAAmB;IAChE,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC;AAC3F,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,eAAe,CAAC,IAAe,EAAE,GAAmB;IAClE,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACvF,CAAC","sourcesContent":["/**\n * Name resolution over a walked tree — the questions every `loam subsystem`\n * verb asks before it acts: what does this name denote, what sits beneath a\n * subsystem, and which names come close to a miss. One module so `move`,\n * `rm`, `new` and `history` cannot grow four private spellings of \"the flat\n * namespace\" — the walk already enforces one namespace, and resolution must\n * read it the same way.\n */\nimport { closeIds } from \"../../c4/arch.js\";\nimport type { FleetTree, SubsystemEntry, WalkedService } from \"./walk.js\";\n\n/** What a name denotes in the tree — a service, a subsystem, or nothing. */\nexport type TreeHit =\n | { kind: \"service\"; service: WalkedService }\n | { kind: \"subsystem\"; subsystem: SubsystemEntry }\n | null;\n\n/**\n * Resolve one name against the flat namespace. Services win a tie on\n * purpose: a tie IS `subsystem.name-collision`, already an error finding,\n * and while it stands the service reading keeps every id-addressed command\n * coherent.\n */\nexport function findInTree(tree: FleetTree, name: string): TreeHit {\n const service = tree.services.find((s) => s.id === name);\n if (service !== undefined) return { kind: \"service\", service };\n const subsystem = tree.subsystems.find((s) => s.name === name);\n if (subsystem !== undefined) return { kind: \"subsystem\", subsystem };\n return null;\n}\n\n/** Every name the flat namespace holds — service ids and subsystem names together. */\nexport function treeNames(tree: FleetTree): string[] {\n return [...tree.services.map((s) => s.id), ...tree.subsystems.map((s) => s.name)];\n}\n\n/** The near-miss hints a refusal offers, over whichever pool the verb resolves against. */\nexport function nearestTreeNames(name: string, pool: string[]): string[] {\n return closeIds(name, pool);\n}\n\n/**\n * Does the subsystem at `subPath` contain `path` — itself, or anywhere beneath\n * it? The move-into-own-subtree refusal. The parameter is the name chain, not\n * a `SubsystemEntry`, because a chain is all this comparison reads — the one\n * caller outside this module (`move`) has a chain it just composed and no\n * entry to hand, and a structural parameter it never reads would force a\n * placeholder there.\n */\nexport function withinSubsystem(subPath: string[], path: string[]): boolean {\n return subPath.length <= path.length && subPath.every((name, i) => path[i] === name);\n}\n\n/** Services anywhere beneath a subsystem — the transitive membership the views and counts share. */\nexport function servicesUnder(tree: FleetTree, sub: SubsystemEntry): WalkedService[] {\n return tree.services.filter((s) => sub.path.every((name, i) => s.subsystem[i] === name));\n}\n\n/** Child subsystems anywhere beneath one — `rm`'s other half of \"empty\". */\nexport function subsystemsUnder(tree: FleetTree, sub: SubsystemEntry): SubsystemEntry[] {\n return tree.subsystems.filter((c) => c !== sub && withinSubsystem(sub.path, c.path));\n}\n"]}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Directory-entry questions the enumerations share, symlinks followed.
|
|
3
|
+
*
|
|
4
|
+
* Moved here from `repo/repo.ts` when the tree walk arrived: the walk and the
|
|
5
|
+
* feature enumeration must answer "what IS this entry" identically, and the
|
|
6
|
+
* package direction is `repo → repo/tree` (never back — `walk.ts` says why),
|
|
7
|
+
* so the shared spelling lives on this side of the edge.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, statSync } from "node:fs";
|
|
10
|
+
import { readdir } from "node:fs/promises";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
/**
|
|
13
|
+
* What a directory entry IS, following a symlink to find out.
|
|
14
|
+
*
|
|
15
|
+
* `readdir(withFileTypes)` does not follow symlinks: for a symlink `Dirent`
|
|
16
|
+
* every `isDirectory()`/`isFile()` is false, whatever it points at. That made a
|
|
17
|
+
* symlinked `services/<id>/` or `features/<id>/` vanish from every enumeration
|
|
18
|
+
* at once — `list`, `validate --all`, the fleet gate — with no diagnostic
|
|
19
|
+
* anywhere, which is the one outcome a shared docs repo cannot survive: the
|
|
20
|
+
* fleet gate reported a service that is not there as fine because it never saw
|
|
21
|
+
* it. Composing a docs repo out of symlinks (a worktree, a submodule, one
|
|
22
|
+
* service's directory shared between two checkouts) is legitimate, so they are
|
|
23
|
+
* FOLLOWED rather than refused. A dangling link stats as nothing and is skipped
|
|
24
|
+
* exactly as an absent directory is — it is not a service, and saying so would
|
|
25
|
+
* mean inventing an entry to hang the complaint on.
|
|
26
|
+
*/
|
|
27
|
+
export function entryIs(dir, e, want) {
|
|
28
|
+
if (!e.isSymbolicLink())
|
|
29
|
+
return want === "dir" ? e.isDirectory() : e.isFile();
|
|
30
|
+
try {
|
|
31
|
+
const s = statSync(join(dir, e.name));
|
|
32
|
+
return want === "dir" ? s.isDirectory() : s.isFile();
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Markdown files sitting directly in `dir`, sorted, as full paths — the ADR
|
|
40
|
+
* corpus, and the only place that rule is spelled. Symlinks are resolved
|
|
41
|
+
* through `entryIs`, which is the whole reason this is shared: `show` once kept
|
|
42
|
+
* a second copy that tested `Dirent.isFile()` itself — always false for a
|
|
43
|
+
* symlink, whatever it points at — so a docs repo composed of symlinks (the
|
|
44
|
+
* layout `entryIs` above exists to support) had `loam list` and `loam show`
|
|
45
|
+
* reporting different ADR counts for the same service. One rule, one answer.
|
|
46
|
+
*
|
|
47
|
+
* The list, not the count, is the primitive: the link check reads these same
|
|
48
|
+
* files, and a corpus that disagreed with the count would grade documents the
|
|
49
|
+
* fleet listing says are not there.
|
|
50
|
+
*/
|
|
51
|
+
export async function markdownFiles(dir) {
|
|
52
|
+
if (!existsSync(dir))
|
|
53
|
+
return [];
|
|
54
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
55
|
+
return entries
|
|
56
|
+
.filter((e) => e.name.endsWith(".md") && entryIs(dir, e, "file"))
|
|
57
|
+
.map((e) => join(dir, e.name))
|
|
58
|
+
.sort();
|
|
59
|
+
}
|
|
60
|
+
/** How many markdown files sit directly in `dir` — the ADR count, over the corpus above. */
|
|
61
|
+
export async function countMarkdown(dir) {
|
|
62
|
+
return (await markdownFiles(dir)).length;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=fs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../../../src/core/repo/tree/fs.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAe,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,OAAO,CAAC,GAAW,EAAE,CAAS,EAAE,IAAoB;IAClE,IAAI,CAAC,CAAC,CAAC,cAAc,EAAE;QAAE,OAAO,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC9E,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACtC,OAAO,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAW;IAC7C,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAChC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,OAAO,OAAO;SACX,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;SAChE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;SAC7B,IAAI,EAAE,CAAC;AACZ,CAAC;AAED,4FAA4F;AAC5F,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAW;IAC7C,OAAO,CAAC,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;AAC3C,CAAC","sourcesContent":["/**\n * Directory-entry questions the enumerations share, symlinks followed.\n *\n * Moved here from `repo/repo.ts` when the tree walk arrived: the walk and the\n * feature enumeration must answer \"what IS this entry\" identically, and the\n * package direction is `repo → repo/tree` (never back — `walk.ts` says why),\n * so the shared spelling lives on this side of the edge.\n */\nimport { existsSync, statSync, type Dirent } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\n/**\n * What a directory entry IS, following a symlink to find out.\n *\n * `readdir(withFileTypes)` does not follow symlinks: for a symlink `Dirent`\n * every `isDirectory()`/`isFile()` is false, whatever it points at. That made a\n * symlinked `services/<id>/` or `features/<id>/` vanish from every enumeration\n * at once — `list`, `validate --all`, the fleet gate — with no diagnostic\n * anywhere, which is the one outcome a shared docs repo cannot survive: the\n * fleet gate reported a service that is not there as fine because it never saw\n * it. Composing a docs repo out of symlinks (a worktree, a submodule, one\n * service's directory shared between two checkouts) is legitimate, so they are\n * FOLLOWED rather than refused. A dangling link stats as nothing and is skipped\n * exactly as an absent directory is — it is not a service, and saying so would\n * mean inventing an entry to hang the complaint on.\n */\nexport function entryIs(dir: string, e: Dirent, want: \"dir\" | \"file\"): boolean {\n if (!e.isSymbolicLink()) return want === \"dir\" ? e.isDirectory() : e.isFile();\n try {\n const s = statSync(join(dir, e.name));\n return want === \"dir\" ? s.isDirectory() : s.isFile();\n } catch {\n return false;\n }\n}\n\n/**\n * Markdown files sitting directly in `dir`, sorted, as full paths — the ADR\n * corpus, and the only place that rule is spelled. Symlinks are resolved\n * through `entryIs`, which is the whole reason this is shared: `show` once kept\n * a second copy that tested `Dirent.isFile()` itself — always false for a\n * symlink, whatever it points at — so a docs repo composed of symlinks (the\n * layout `entryIs` above exists to support) had `loam list` and `loam show`\n * reporting different ADR counts for the same service. One rule, one answer.\n *\n * The list, not the count, is the primitive: the link check reads these same\n * files, and a corpus that disagreed with the count would grade documents the\n * fleet listing says are not there.\n */\nexport async function markdownFiles(dir: string): Promise<string[]> {\n if (!existsSync(dir)) return [];\n const entries = await readdir(dir, { withFileTypes: true });\n return entries\n .filter((e) => e.name.endsWith(\".md\") && entryIs(dir, e, \"file\"))\n .map((e) => join(dir, e.name))\n .sort();\n}\n\n/** How many markdown files sit directly in `dir` — the ADR count, over the corpus above. */\nexport async function countMarkdown(dir: string): Promise<number> {\n return (await markdownFiles(dir)).length;\n}\n"]}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The subsystem marker: `subsystem.yaml`, the one file that makes a directory
|
|
3
|
+
* under `services/` a GROUP rather than a service.
|
|
4
|
+
*
|
|
5
|
+
* Presence classifies; content is metadata. The walk decides what a directory
|
|
6
|
+
* IS from the marker's existence alone, so a marker that cannot be read still
|
|
7
|
+
* classifies its directory as a subsystem — anything else would let a corrupt
|
|
8
|
+
* byte reinterpret a whole subtree as a service and swallow the fleet beneath
|
|
9
|
+
* it, the exact silent-shrink the three-way classification exists to refuse.
|
|
10
|
+
* An unreadable marker is reported as exactly one `subsystem.invalid` finding
|
|
11
|
+
* instead (`walk.ts` emits it from the `invalid` field here).
|
|
12
|
+
*
|
|
13
|
+
* The defensive-read shape is `core/permissions/permissions.ts`'s: every wrong
|
|
14
|
+
* shape is an `invalid` sentence naming what is wrong, never a throw — this is
|
|
15
|
+
* a read model, and a marker somebody hand-edited badly must not take down the
|
|
16
|
+
* enumeration that would report it.
|
|
17
|
+
*/
|
|
18
|
+
import { readFile } from "node:fs/promises";
|
|
19
|
+
import { parse as parseYaml } from "yaml";
|
|
20
|
+
import { isRecord } from "../../kernel/records.js";
|
|
21
|
+
/** The marker's filename — spelled here and nowhere else. */
|
|
22
|
+
export const SUBSYSTEM_MARKER = "subsystem.yaml";
|
|
23
|
+
/**
|
|
24
|
+
* Read one marker. An EMPTY file is a valid marker with no metadata — the
|
|
25
|
+
* marker's job is done by existing, and `title`/`description`/`owner` are all
|
|
26
|
+
* optional. A `members` key is invalid on purpose: placement is recorded
|
|
27
|
+
* exactly once, in the directory itself, and a membership list would be a
|
|
28
|
+
* second copy that merges, drifts and lies (SCHEMA.md's marker-not-manifest
|
|
29
|
+
* rule).
|
|
30
|
+
*/
|
|
31
|
+
export async function readSubsystemMarker(path) {
|
|
32
|
+
let doc;
|
|
33
|
+
try {
|
|
34
|
+
doc = parseYaml(await readFile(path, "utf8"));
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
return { invalid: e instanceof Error ? e.message : String(e) };
|
|
38
|
+
}
|
|
39
|
+
if (doc === null || doc === undefined)
|
|
40
|
+
return {};
|
|
41
|
+
if (!isRecord(doc))
|
|
42
|
+
return { invalid: "the document is not a mapping" };
|
|
43
|
+
if (doc["members"] !== undefined) {
|
|
44
|
+
return {
|
|
45
|
+
invalid: "it declares `members` — a subsystem's members are the service directories inside it, " +
|
|
46
|
+
"recorded exactly once, in the directory itself; a list here would be a second copy that drifts",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
for (const field of ["title", "description", "owner"]) {
|
|
50
|
+
const value = doc[field];
|
|
51
|
+
if (value !== undefined && typeof value !== "string") {
|
|
52
|
+
return { invalid: `\`${field}\` is not a string` };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
...(typeof doc["title"] === "string" ? { title: doc["title"] } : {}),
|
|
57
|
+
...(typeof doc["description"] === "string" ? { description: doc["description"] } : {}),
|
|
58
|
+
...(typeof doc["owner"] === "string" ? { owner: doc["owner"] } : {}),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=marker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"marker.js","sourceRoot":"","sources":["../../../../src/core/repo/tree/marker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAEnD,6DAA6D;AAC7D,MAAM,CAAC,MAAM,gBAAgB,GAAG,gBAAgB,CAAC;AAUjD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAAY;IACpD,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,SAAS,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,OAAO,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACjE,CAAC;IACD,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACjD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,+BAA+B,EAAE,CAAC;IACxE,IAAI,GAAG,CAAC,SAAS,CAAC,KAAK,SAAS,EAAE,CAAC;QACjC,OAAO;YACL,OAAO,EACL,uFAAuF;gBACvF,gGAAgG;SACnG,CAAC;IACJ,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,CAAU,EAAE,CAAC;QAC/D,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,KAAK,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACrD,OAAO,EAAE,OAAO,EAAE,KAAK,KAAK,oBAAoB,EAAE,CAAC;QACrD,CAAC;IACH,CAAC;IACD,OAAO;QACL,GAAG,CAAC,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpE,GAAG,CAAC,OAAO,GAAG,CAAC,aAAa,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtF,GAAG,CAAC,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACrE,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The subsystem marker: `subsystem.yaml`, the one file that makes a directory\n * under `services/` a GROUP rather than a service.\n *\n * Presence classifies; content is metadata. The walk decides what a directory\n * IS from the marker's existence alone, so a marker that cannot be read still\n * classifies its directory as a subsystem — anything else would let a corrupt\n * byte reinterpret a whole subtree as a service and swallow the fleet beneath\n * it, the exact silent-shrink the three-way classification exists to refuse.\n * An unreadable marker is reported as exactly one `subsystem.invalid` finding\n * instead (`walk.ts` emits it from the `invalid` field here).\n *\n * The defensive-read shape is `core/permissions/permissions.ts`'s: every wrong\n * shape is an `invalid` sentence naming what is wrong, never a throw — this is\n * a read model, and a marker somebody hand-edited badly must not take down the\n * enumeration that would report it.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { parse as parseYaml } from \"yaml\";\nimport { isRecord } from \"../../kernel/records.js\";\n\n/** The marker's filename — spelled here and nowhere else. */\nexport const SUBSYSTEM_MARKER = \"subsystem.yaml\";\n\nexport interface SubsystemMeta {\n title?: string;\n description?: string;\n owner?: string;\n /** Why the file could not be read as subsystem metadata, when it could not. */\n invalid?: string;\n}\n\n/**\n * Read one marker. An EMPTY file is a valid marker with no metadata — the\n * marker's job is done by existing, and `title`/`description`/`owner` are all\n * optional. A `members` key is invalid on purpose: placement is recorded\n * exactly once, in the directory itself, and a membership list would be a\n * second copy that merges, drifts and lies (SCHEMA.md's marker-not-manifest\n * rule).\n */\nexport async function readSubsystemMarker(path: string): Promise<SubsystemMeta> {\n let doc: unknown;\n try {\n doc = parseYaml(await readFile(path, \"utf8\"));\n } catch (e) {\n return { invalid: e instanceof Error ? e.message : String(e) };\n }\n if (doc === null || doc === undefined) return {};\n if (!isRecord(doc)) return { invalid: \"the document is not a mapping\" };\n if (doc[\"members\"] !== undefined) {\n return {\n invalid:\n \"it declares `members` — a subsystem's members are the service directories inside it, \" +\n \"recorded exactly once, in the directory itself; a list here would be a second copy that drifts\",\n };\n }\n for (const field of [\"title\", \"description\", \"owner\"] as const) {\n const value = doc[field];\n if (value !== undefined && typeof value !== \"string\") {\n return { invalid: `\\`${field}\\` is not a string` };\n }\n }\n return {\n ...(typeof doc[\"title\"] === \"string\" ? { title: doc[\"title\"] } : {}),\n ...(typeof doc[\"description\"] === \"string\" ? { description: doc[\"description\"] } : {}),\n ...(typeof doc[\"owner\"] === \"string\" ? { owner: doc[\"owner\"] } : {}),\n };\n}\n"]}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The generated views file: `architecture/subsystems.likec4`, one LikeC4 view
|
|
3
|
+
* per subsystem enumerating the services beneath it. GENERATED, never
|
|
4
|
+
* authored — authored bytes stay authored (archive's verbatim-splice promise),
|
|
5
|
+
* so the tree is mirrored into a file of its own that `loam subsystem sync`
|
|
6
|
+
* owns outright, and staleness against the tree is a byte compare
|
|
7
|
+
* (`subsystem.views-stale` in `validate --all`), never a parse: a spike
|
|
8
|
+
* confirmed a views-only document does NOT parse standalone (its `include`
|
|
9
|
+
* lines reference elements the landscape defines), so nothing in loam ever
|
|
10
|
+
* loads this file (and rule 26 does not change that: this file is GENERATED and
|
|
11
|
+
* holds STATIC views, whose contents loam may not read in any document) — only
|
|
12
|
+
* the LikeC4 renderer, which merges the whole `architecture/` project per
|
|
13
|
+
* likec4.config.json, resolves it.
|
|
14
|
+
*
|
|
15
|
+
* The output is deterministic and line-oriented, and both halves are the
|
|
16
|
+
* contract: subsystems sorted by path, members sorted by id, ONE `include`
|
|
17
|
+
* per line — so two concurrent moves into different groups touch different
|
|
18
|
+
* lines and git merges the file without intervention, exactly as the
|
|
19
|
+
* service-grouped landscape splice already achieves. Byte-reproducible on any
|
|
20
|
+
* machine: no timestamps, no absolute paths, no readdir-order dependence (the
|
|
21
|
+
* walk sorts, and every list is re-sorted here). Plain lexicographic sorts on
|
|
22
|
+
* purpose — the ids' own grammar is ASCII, an INVALID name is still ordered
|
|
23
|
+
* deterministically, and `compareIds`' numeric tokenizing lives in `repo/`,
|
|
24
|
+
* which this package must not import (`walk.ts` says why the edge only points
|
|
25
|
+
* the other way).
|
|
26
|
+
*
|
|
27
|
+
* Membership is TRANSITIVE: a subsystem's view includes every service beneath
|
|
28
|
+
* it at any depth, so a parent's view is the whole group's map rather than an
|
|
29
|
+
* empty frame around its children. An empty view body is legal LikeC4 (same
|
|
30
|
+
* spike), so an empty subsystem renders as one — `subsystem new` must be
|
|
31
|
+
* usable before anything moves in.
|
|
32
|
+
*
|
|
33
|
+
* Include lines name the landscape ELEMENT id, resolved through the same
|
|
34
|
+
* element→service join `validate` uses (`serviceResolver`: explicit
|
|
35
|
+
* `metadata { service }` binding first, then a title naming a real service) —
|
|
36
|
+
* because element ids in real landscapes are not service ids, and a view of
|
|
37
|
+
* raw service ids would silently render empty for every such fleet. A member
|
|
38
|
+
* with no landscape element is omitted from the includes: the omission is
|
|
39
|
+
* already an error (`landscape.service-unmodelled`), and rendering only what
|
|
40
|
+
* the committed landscape resolves keeps the output a function of committed
|
|
41
|
+
* bytes alone.
|
|
42
|
+
*/
|
|
43
|
+
import {} from "../../c4/likec4.js";
|
|
44
|
+
import { serviceResolver } from "../../c4/resolve/service.js";
|
|
45
|
+
import { servicesUnder } from "./find.js";
|
|
46
|
+
/**
|
|
47
|
+
* The header every generated file opens with — stable bytes, part of the
|
|
48
|
+
* byte-compare contract, naming the regenerator so a reader who found the
|
|
49
|
+
* file first knows what owns it.
|
|
50
|
+
*/
|
|
51
|
+
const HEADER = "// generated by loam — the subsystem tree under services/, one view per subsystem.\n" +
|
|
52
|
+
"// Do not edit: `loam subsystem sync` regenerates this file from the directories,\n" +
|
|
53
|
+
"// and `loam validate --all` reports `subsystem.views-stale` when it drifts.\n";
|
|
54
|
+
/**
|
|
55
|
+
* Render the views file for a walked tree, or null when the tree has no
|
|
56
|
+
* subsystems — the file must then be ABSENT, so a flat fleet never carries a
|
|
57
|
+
* generated artifact that says nothing.
|
|
58
|
+
*/
|
|
59
|
+
export function renderSubsystemViews(tree, elements) {
|
|
60
|
+
if (tree.subsystems.length === 0)
|
|
61
|
+
return null;
|
|
62
|
+
const elementOf = elementByService(tree, elements);
|
|
63
|
+
const lines = [HEADER, "views {"];
|
|
64
|
+
const subsystems = [...tree.subsystems].sort((a, b) => (pathKey(a) < pathKey(b) ? -1 : 1));
|
|
65
|
+
for (const [i, sub] of subsystems.entries()) {
|
|
66
|
+
if (i > 0)
|
|
67
|
+
lines.push("");
|
|
68
|
+
lines.push(` // services/${pathKey(sub)}`);
|
|
69
|
+
lines.push(` view ${subsystemViewId(sub)} {`);
|
|
70
|
+
for (const member of membersOf(tree, sub)) {
|
|
71
|
+
const element = elementOf.get(member.id);
|
|
72
|
+
if (element !== undefined)
|
|
73
|
+
lines.push(` include ${element}`);
|
|
74
|
+
}
|
|
75
|
+
lines.push(" }");
|
|
76
|
+
}
|
|
77
|
+
lines.push("}");
|
|
78
|
+
return lines.join("\n") + "\n";
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The landscape element standing for each service id: for every element,
|
|
82
|
+
* resolve it through the shared join, and let the SHALLOWEST element win
|
|
83
|
+
* (document order breaking ties). Shallowest, because `serviceResolver`
|
|
84
|
+
* resolves a service's own containers to the service too, and a view should
|
|
85
|
+
* include the service-level box, not whichever nested container happened to
|
|
86
|
+
* be declared first.
|
|
87
|
+
*/
|
|
88
|
+
function elementByService(tree, elements) {
|
|
89
|
+
const known = new Set(tree.services.map((s) => s.id));
|
|
90
|
+
const resolve = serviceResolver(elements, known);
|
|
91
|
+
const depth = (id) => id.split(".").length;
|
|
92
|
+
const out = new Map();
|
|
93
|
+
for (const e of elements) {
|
|
94
|
+
const service = resolve(e.id);
|
|
95
|
+
if (!known.has(service))
|
|
96
|
+
continue;
|
|
97
|
+
const held = out.get(service);
|
|
98
|
+
if (held === undefined || depth(e.id) < depth(held))
|
|
99
|
+
out.set(service, e.id);
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
/** Services beneath this subsystem at any depth, ordered by id — the transitive membership. */
|
|
104
|
+
function membersOf(tree, sub) {
|
|
105
|
+
return [...servicesUnder(tree, sub)].sort((a, b) => (a.id < b.id ? -1 : 1));
|
|
106
|
+
}
|
|
107
|
+
/** The sort key and the comment spelling: the subsystem's path from services/, joined. */
|
|
108
|
+
function pathKey(sub) {
|
|
109
|
+
return sub.path.join("/");
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* A LikeC4 identifier for the view, derived from the subsystem's path so
|
|
113
|
+
* nesting reads in the name — and INJECTIVE over every path the name grammar
|
|
114
|
+
* admits, because the renderer refuses the whole `architecture/` project over
|
|
115
|
+
* a duplicate view id. The naive folding (every non-identifier byte and the
|
|
116
|
+
* join separator alike becoming `_`) collided three DISTINCT healthy names
|
|
117
|
+
* onto one identifier — `a_b`, and `b` under `a`, both rendered
|
|
118
|
+
* `subsystem_a_b` — so `validate --all` was green while the renderer refused
|
|
119
|
+
* the fleet map; `subsystem.name-collision` never fires because the flat
|
|
120
|
+
* namespace really is intact. Hence the encoding: an identifier byte
|
|
121
|
+
* (`[A-Za-z0-9]`) passes through, any other byte becomes `_` + two lowercase
|
|
122
|
+
* hex digits (`.` → `_2e`, `-` → `_2d`, `_` itself → `_5f` — the underscore
|
|
123
|
+
* must be escaped precisely because it is BOTH legal in a name and the
|
|
124
|
+
* separator), and segments join on `__`. Underscore runs then decode
|
|
125
|
+
* uniquely — one is an escape, two a separator, three a separator followed
|
|
126
|
+
* by an escape — so no two distinct legal paths share an identifier. A name
|
|
127
|
+
* `subsystem.name-invalid` already flagged may hold a code point above 0xff
|
|
128
|
+
* whose longer hex could in principle re-collide; it is deterministic still,
|
|
129
|
+
* and that directory is already an error finding by name.
|
|
130
|
+
*
|
|
131
|
+
* EXPORTED, with its prefix, because `subsystem.view-id-collision` has to ask
|
|
132
|
+
* the generator what it mints rather than re-deriving it. A check carrying its
|
|
133
|
+
* own copy of the escaping above would go quiet the day the encoding changed,
|
|
134
|
+
* and going quiet is the failure this whole family exists to prevent.
|
|
135
|
+
*/
|
|
136
|
+
export const SUBSYSTEM_VIEW_PREFIX = "subsystem_";
|
|
137
|
+
export function subsystemViewId(sub) {
|
|
138
|
+
const segment = (name) => [...name].map((ch) => (/[A-Za-z0-9]/.test(ch) ? ch : `_${ch.codePointAt(0).toString(16).padStart(2, "0")}`)).join("");
|
|
139
|
+
return `subsystem_${sub.path.map(segment).join("__")}`;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Does the file on disk already SAY what `renderSubsystemViews` renders?
|
|
143
|
+
*
|
|
144
|
+
* The generator above only ever emits LF. `core.autocrlf=true` is Git for
|
|
145
|
+
* Windows' installer default and a docs repo ships no `.gitattributes`, so an
|
|
146
|
+
* ordinary Windows clone hands the file back with CRLF and not one fact
|
|
147
|
+
* changed. Four call sites asked this question with a byte equality and all
|
|
148
|
+
* four answered "stale" on such a clone: `loam validate` reported an error,
|
|
149
|
+
* `loam subsystem sync` reported `updated` and rewrote with LF, git then showed
|
|
150
|
+
* the file permanently modified, and the next checkout restored the CRLF. That
|
|
151
|
+
* is a loop rather than a diagnosis, which is why the comparison lives here
|
|
152
|
+
* now — the module that mints the bytes is the one that owes an answer about
|
|
153
|
+
* them, and three near-copies of this rule would go stale one at a time.
|
|
154
|
+
*
|
|
155
|
+
* Still never a parse: a views-only document does not parse standalone, and no
|
|
156
|
+
* check anywhere reads this file's content. The narrowing is from BYTES to
|
|
157
|
+
* CONTENT, which is what `subsystem.views-stale`'s own message ("does not match
|
|
158
|
+
* the tree") already claims. The symmetric cost is accepted and is the one
|
|
159
|
+
* `core/verify/pins/pin.ts` documents for `pinnedDigest`: a pure line-ending
|
|
160
|
+
* change is invisible, correctly, because it is not a change to what the file
|
|
161
|
+
* says.
|
|
162
|
+
*
|
|
163
|
+
* `null` is carried through as itself rather than collapsing into `""`: absent,
|
|
164
|
+
* must-be-absent and present-but-different are three distinct states with three
|
|
165
|
+
* distinct messages, and a fleet whose views file does not exist is not a fleet
|
|
166
|
+
* whose views file is blank.
|
|
167
|
+
*/
|
|
168
|
+
export function viewsAgree(actual, expected) {
|
|
169
|
+
const lf = (text) => (text === null ? null : text.replace(/\r\n|\r/g, "\n"));
|
|
170
|
+
return lf(actual) === lf(expected);
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=views.js.map
|