@ybotok/loam 0.1.0-beta.3 → 0.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2548 -86
- package/COMPARISON.md +80 -63
- package/CONTRIBUTING.md +84 -0
- package/MIGRATING-from-OpenSpec.md +213 -54
- package/QUICKSTART.md +125 -0
- package/README.md +450 -150
- package/ROADMAP.md +494 -0
- package/SCHEMA.md +2355 -256
- package/SECURITY.md +21 -3
- package/WORKFLOW.md +701 -0
- package/dist/cli.js +113 -19
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +209 -0
- package/dist/commands/adopt/adopt.js.map +1 -0
- package/dist/commands/adopt/render.js +268 -0
- package/dist/commands/adopt/render.js.map +1 -0
- package/dist/commands/archive/archive.js +77 -0
- package/dist/commands/archive/archive.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +159 -0
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +317 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +161 -0
- package/dist/commands/archive/plan/landscape.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +111 -0
- package/dist/commands/archive/plan/refusal.js.map +1 -0
- package/dist/commands/archive/plan/specs.js +237 -0
- package/dist/commands/archive/plan/specs.js.map +1 -0
- package/dist/commands/archive/plan/state.js +27 -0
- package/dist/commands/archive/plan/state.js.map +1 -0
- package/dist/commands/archive/run.js +251 -0
- package/dist/commands/archive/run.js.map +1 -0
- package/dist/commands/context/context.js +99 -0
- package/dist/commands/context/context.js.map +1 -0
- package/dist/commands/context/print.js +283 -0
- package/dist/commands/context/print.js.map +1 -0
- package/dist/commands/delta/delta.js +279 -0
- package/dist/commands/delta/delta.js.map +1 -0
- package/dist/commands/delta/print.js +205 -0
- package/dist/commands/delta/print.js.map +1 -0
- package/dist/commands/dependencies.js +9 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +93 -0
- package/dist/commands/diff/diff.js.map +1 -0
- package/dist/commands/diff/print.js +38 -0
- package/dist/commands/diff/print.js.map +1 -0
- package/dist/commands/doctor.js +5 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +151 -0
- package/dist/commands/explain/explain.js.map +1 -0
- package/dist/commands/explore.js +29 -24
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +115 -0
- package/dist/commands/gate/gate.js.map +1 -0
- package/dist/commands/gate/print.js +54 -0
- package/dist/commands/gate/print.js.map +1 -0
- package/dist/commands/gherkin/commit.js +146 -0
- package/dist/commands/gherkin/commit.js.map +1 -0
- package/dist/commands/gherkin/gherkin.js +246 -0
- package/dist/commands/gherkin/gherkin.js.map +1 -0
- package/dist/commands/gherkin/reconcile.js +149 -0
- package/dist/commands/gherkin/reconcile.js.map +1 -0
- package/dist/commands/gherkin/render.js +77 -0
- package/dist/commands/gherkin/render.js.map +1 -0
- package/dist/commands/init/example.js +204 -0
- package/dist/commands/init/example.js.map +1 -0
- package/dist/commands/init/first-hour.js +28 -0
- package/dist/commands/init/first-hour.js.map +1 -0
- package/dist/commands/{init.js → init/init.js} +146 -71
- package/dist/commands/init/init.js.map +1 -0
- package/dist/commands/init/mcp.js +131 -0
- package/dist/commands/init/mcp.js.map +1 -0
- package/dist/commands/init/options.js +78 -0
- package/dist/commands/init/options.js.map +1 -0
- package/dist/commands/instructions.js +72 -14
- package/dist/commands/instructions.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +65 -0
- package/dist/commands/list/campaign/campaign.js.map +1 -0
- package/dist/commands/list/campaign/owners.js +65 -0
- package/dist/commands/list/campaign/owners.js.map +1 -0
- package/dist/commands/list/json.js +157 -0
- package/dist/commands/list/json.js.map +1 -0
- package/dist/commands/list/list.js +343 -0
- package/dist/commands/list/list.js.map +1 -0
- package/dist/commands/list/print.js +301 -0
- package/dist/commands/list/print.js.map +1 -0
- package/dist/commands/list/review.js +120 -0
- package/dist/commands/list/review.js.map +1 -0
- package/dist/commands/list/views.js +105 -0
- package/dist/commands/list/views.js.map +1 -0
- package/dist/commands/mcp/dispatch.js +183 -0
- package/dist/commands/mcp/dispatch.js.map +1 -0
- package/dist/commands/mcp/mcp.js +34 -0
- package/dist/commands/mcp/mcp.js.map +1 -0
- package/dist/commands/mcp/serve.js +55 -0
- package/dist/commands/mcp/serve.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/active.js +170 -0
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
- package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/living.js +62 -0
- package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/target.js +219 -0
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/error.js +19 -0
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/read.js +49 -0
- package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
- package/dist/commands/migrate-openspec/print.js +89 -0
- package/dist/commands/migrate-openspec/print.js.map +1 -0
- package/dist/commands/new/commit.js +76 -0
- package/dist/commands/new/commit.js.map +1 -0
- package/dist/commands/new/new.js +283 -0
- package/dist/commands/new/new.js.map +1 -0
- package/dist/commands/new/templates.js +355 -0
- package/dist/commands/new/templates.js.map +1 -0
- package/dist/commands/open.js +203 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/policy/format.js +152 -0
- package/dist/commands/policy/format.js.map +1 -0
- package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
- package/dist/commands/policy/gate.js.map +1 -0
- package/dist/commands/rebase/edit.js +106 -0
- package/dist/commands/rebase/edit.js.map +1 -0
- package/dist/commands/rebase/living.js +185 -0
- package/dist/commands/rebase/living.js.map +1 -0
- package/dist/commands/rebase/plan.js +245 -0
- package/dist/commands/rebase/plan.js.map +1 -0
- package/dist/commands/rebase/rebase.js +275 -0
- package/dist/commands/rebase/rebase.js.map +1 -0
- package/dist/commands/seed/commit.js +192 -0
- package/dist/commands/seed/commit.js.map +1 -0
- package/dist/commands/seed/plan.js +133 -0
- package/dist/commands/seed/plan.js.map +1 -0
- package/dist/commands/seed/seed.js +172 -0
- package/dist/commands/seed/seed.js.map +1 -0
- package/dist/commands/show/feature.js +178 -0
- package/dist/commands/show/feature.js.map +1 -0
- package/dist/commands/show/marks.js +24 -0
- package/dist/commands/show/marks.js.map +1 -0
- package/dist/commands/{show.js → show/service.js} +63 -152
- package/dist/commands/show/service.js.map +1 -0
- package/dist/commands/show/show.js +77 -0
- package/dist/commands/show/show.js.map +1 -0
- package/dist/commands/status/json.js +45 -0
- package/dist/commands/status/json.js.map +1 -0
- package/dist/commands/status/print.js +157 -0
- package/dist/commands/status/print.js.map +1 -0
- package/dist/commands/status/status.js +119 -0
- package/dist/commands/status/status.js.map +1 -0
- package/dist/commands/steps/steps.js +189 -0
- package/dist/commands/steps/steps.js.map +1 -0
- package/dist/commands/subsystem/edit.js +192 -0
- package/dist/commands/subsystem/edit.js.map +1 -0
- package/dist/commands/subsystem/move.js +265 -0
- package/dist/commands/subsystem/move.js.map +1 -0
- package/dist/commands/subsystem/query.js +115 -0
- package/dist/commands/subsystem/query.js.map +1 -0
- package/dist/commands/subsystem/subsystem.js +101 -0
- package/dist/commands/subsystem/subsystem.js.map +1 -0
- package/dist/commands/subsystem/sync.js +103 -0
- package/dist/commands/subsystem/sync.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +94 -0
- package/dist/commands/subsystem/txn/txn.js.map +1 -0
- package/dist/commands/subsystem/txn/views.js +70 -0
- package/dist/commands/subsystem/txn/views.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +160 -0
- package/dist/commands/unarchive/manifest.js.map +1 -0
- package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
- package/dist/commands/unarchive/restore.js.map +1 -0
- package/dist/commands/unarchive/unarchive.js +67 -0
- package/dist/commands/unarchive/unarchive.js.map +1 -0
- package/dist/commands/validate/arch-coverage.js +194 -0
- package/dist/commands/validate/arch-coverage.js.map +1 -0
- package/dist/commands/validate/checks/fleet-shape.js +296 -0
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
- package/dist/commands/validate/checks/health-deps.js +55 -0
- package/dist/commands/validate/checks/health-deps.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +254 -0
- package/dist/commands/validate/checks/requirements.js.map +1 -0
- package/dist/commands/validate/checks/sources.js +76 -0
- package/dist/commands/validate/checks/sources.js.map +1 -0
- package/dist/commands/validate/checks/vocabulary.js +31 -0
- package/dist/commands/validate/checks/vocabulary.js.map +1 -0
- package/dist/commands/validate/feature.js +216 -0
- package/dist/commands/validate/feature.js.map +1 -0
- package/dist/commands/validate/fleet/census.js +69 -0
- package/dist/commands/validate/fleet/census.js.map +1 -0
- package/dist/commands/validate/fleet/kind-tags.js +39 -0
- package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
- package/dist/commands/validate/fleet/landscape.js +353 -0
- package/dist/commands/validate/fleet/landscape.js.map +1 -0
- package/dist/commands/validate/fleet/load.js +52 -0
- package/dist/commands/validate/fleet/load.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +236 -0
- package/dist/commands/validate/fleet/obligations.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/print.js +63 -0
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/place.js +13 -0
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/steps.js +197 -0
- package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +40 -0
- package/dist/commands/validate/fleet/views/ids.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +59 -0
- package/dist/commands/validate/fleet/views/stale.js.map +1 -0
- package/dist/commands/validate/links/corpus.js +92 -0
- package/dist/commands/validate/links/corpus.js.map +1 -0
- package/dist/commands/validate/links/glossary.js +75 -0
- package/dist/commands/validate/links/glossary.js.map +1 -0
- package/dist/commands/validate/report.js +269 -0
- package/dist/commands/validate/report.js.map +1 -0
- package/dist/commands/validate/service/api.js +338 -0
- package/dist/commands/validate/service/api.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +219 -0
- package/dist/commands/validate/service/events/events.js.map +1 -0
- package/dist/commands/validate/service/events/message-producers.js +77 -0
- package/dist/commands/validate/service/events/message-producers.js.map +1 -0
- package/dist/commands/validate/service/evidence-pins.js +131 -0
- package/dist/commands/validate/service/evidence-pins.js.map +1 -0
- package/dist/commands/validate/service/service.js +250 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +219 -0
- package/dist/commands/validate/service/specs.js.map +1 -0
- package/dist/commands/validate/service/spine.js +113 -0
- package/dist/commands/validate/service/spine.js.map +1 -0
- package/dist/commands/validate/validate.js +338 -0
- package/dist/commands/validate/validate.js.map +1 -0
- package/dist/commands/verify/cross/diff.js +182 -0
- package/dist/commands/verify/cross/diff.js.map +1 -0
- package/dist/commands/verify/evidence/contract.js +32 -0
- package/dist/commands/verify/evidence/contract.js.map +1 -0
- package/dist/commands/verify/evidence/pins.js +52 -0
- package/dist/commands/verify/evidence/pins.js.map +1 -0
- package/dist/commands/verify/evidence/read.js +72 -0
- package/dist/commands/verify/evidence/read.js.map +1 -0
- package/dist/commands/verify/frozen.js +241 -0
- package/dist/commands/verify/frozen.js.map +1 -0
- package/dist/commands/verify/record.js +226 -0
- package/dist/commands/verify/record.js.map +1 -0
- package/dist/commands/verify/report.js +223 -0
- package/dist/commands/verify/report.js.map +1 -0
- package/dist/commands/verify/results.js +246 -0
- package/dist/commands/verify/results.js.map +1 -0
- package/dist/commands/verify/verify.js +217 -0
- package/dist/commands/verify/verify.js.map +1 -0
- package/dist/commands/vouch/contract.js +11 -0
- package/dist/commands/vouch/contract.js.map +1 -0
- package/dist/commands/vouch/pack/contract.js +2 -0
- package/dist/commands/vouch/pack/contract.js.map +1 -0
- package/dist/commands/vouch/pack/pack.js +266 -0
- package/dist/commands/vouch/pack/pack.js.map +1 -0
- package/dist/commands/vouch/pack/print.js +271 -0
- package/dist/commands/vouch/pack/print.js.map +1 -0
- package/dist/commands/vouch/pack/sections.js +142 -0
- package/dist/commands/vouch/pack/sections.js.map +1 -0
- package/dist/commands/vouch/prompt/confirm.js +46 -0
- package/dist/commands/vouch/prompt/confirm.js.map +1 -0
- package/dist/commands/vouch/report.js +101 -0
- package/dist/commands/vouch/report.js.map +1 -0
- package/dist/commands/vouch/run.js +285 -0
- package/dist/commands/vouch/run.js.map +1 -0
- package/dist/commands/vouch/sample/plan.js +125 -0
- package/dist/commands/vouch/sample/plan.js.map +1 -0
- package/dist/commands/vouch/sample/print.js +109 -0
- package/dist/commands/vouch/sample/print.js.map +1 -0
- package/dist/commands/vouch/vet/verify.js +123 -0
- package/dist/commands/vouch/vet/verify.js.map +1 -0
- package/dist/commands/vouch/vouch.js +176 -0
- package/dist/commands/vouch/vouch.js.map +1 -0
- package/dist/core/agent/agents-md/artifacts.js +220 -0
- package/dist/core/agent/agents-md/artifacts.js.map +1 -0
- package/dist/core/agent/agents-md/command-map.js +324 -0
- package/dist/core/agent/agents-md/command-map.js.map +1 -0
- package/dist/core/agent/agents-md/cycle.js +165 -0
- package/dist/core/agent/agents-md/cycle.js.map +1 -0
- package/dist/core/agent/agents-md/map/explain.js +34 -0
- package/dist/core/agent/agents-md/map/explain.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
- package/dist/core/agent/agents-md/map/mcp.js +41 -0
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
- package/dist/core/agent/agents-md/map/status.js +62 -0
- package/dist/core/agent/agents-md/map/status.js.map +1 -0
- package/dist/core/agent/agents-md/map/subsystem.js +32 -0
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
- package/dist/core/agent/agents-md/map/usecases.js +60 -0
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
- package/dist/core/agent/agents-md/refusals.js +246 -0
- package/dist/core/agent/agents-md/refusals.js.map +1 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
- package/dist/core/agent/agents-md/spine.js +40 -0
- package/dist/core/agent/agents-md/spine.js.map +1 -0
- package/dist/core/agent/agents-md.js +170 -0
- package/dist/core/agent/agents-md.js.map +1 -0
- package/dist/core/{agents-stamp.js → agent/agents-stamp.js} +50 -0
- package/dist/core/agent/agents-stamp.js.map +1 -0
- package/dist/core/agent/contract.js +2 -0
- package/dist/core/agent/contract.js.map +1 -0
- package/dist/core/agent/protocol.js +193 -0
- package/dist/core/agent/protocol.js.map +1 -0
- package/dist/core/agent/scaffold.js +259 -0
- package/dist/core/agent/scaffold.js.map +1 -0
- package/dist/core/agent/support/report.js +103 -0
- package/dist/core/agent/support/report.js.map +1 -0
- package/dist/core/agent/tools/dialects.js +150 -0
- package/dist/core/agent/tools/dialects.js.map +1 -0
- package/dist/core/agent/tools/registry.js +228 -0
- package/dist/core/agent/tools/registry.js.map +1 -0
- package/dist/core/agent/workflows/adopt.js +133 -0
- package/dist/core/agent/workflows/adopt.js.map +1 -0
- package/dist/core/agent/workflows/check.js +334 -0
- package/dist/core/agent/workflows/check.js.map +1 -0
- package/dist/core/agent/workflows/closing.js +262 -0
- package/dist/core/agent/workflows/closing.js.map +1 -0
- package/dist/core/agent/workflows/feature.js +105 -0
- package/dist/core/agent/workflows/feature.js.map +1 -0
- package/dist/core/agent/workflows/implement.js +105 -0
- package/dist/core/agent/workflows/implement.js.map +1 -0
- package/dist/core/agent/workflows/reference/authoring.js +209 -0
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
- package/dist/core/agent/workflows/reference/codes.js +65 -0
- package/dist/core/agent/workflows/reference/codes.js.map +1 -0
- package/dist/core/agent/workflows/reference/done-check.js +188 -0
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +16 -0
- package/dist/core/agent/workflows/reference/reference.js.map +1 -0
- package/dist/core/agent/workflows/reference/spine.js +297 -0
- package/dist/core/agent/workflows/reference/spine.js.map +1 -0
- package/dist/core/asyncapi/baseline/plan.js +110 -0
- package/dist/core/asyncapi/baseline/plan.js.map +1 -0
- package/dist/core/asyncapi/baseline/surfaces.js +54 -0
- package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
- package/dist/core/asyncapi/depth.js +164 -0
- package/dist/core/asyncapi/depth.js.map +1 -0
- package/dist/core/asyncapi/digest.js +141 -0
- package/dist/core/asyncapi/digest.js.map +1 -0
- package/dist/core/asyncapi/merge/components.js +91 -0
- package/dist/core/asyncapi/merge/components.js.map +1 -0
- package/dist/core/asyncapi/merge/error.js +33 -0
- package/dist/core/asyncapi/merge/error.js.map +1 -0
- package/dist/core/asyncapi/merge/markers.js +308 -0
- package/dist/core/asyncapi/merge/markers.js.map +1 -0
- package/dist/core/asyncapi/merge/merge.js +215 -0
- package/dist/core/asyncapi/merge/merge.js.map +1 -0
- package/dist/core/asyncapi/merge/pin.js +107 -0
- package/dist/core/asyncapi/merge/pin.js.map +1 -0
- package/dist/core/asyncapi/model.js +11 -0
- package/dist/core/asyncapi/model.js.map +1 -0
- package/dist/core/asyncapi/producers.js +50 -0
- package/dist/core/asyncapi/producers.js.map +1 -0
- package/dist/core/{asyncapi.js → asyncapi/read.js} +95 -109
- package/dist/core/asyncapi/read.js.map +1 -0
- package/dist/core/brief/brief.js +225 -0
- package/dist/core/brief/brief.js.map +1 -0
- package/dist/core/brief/checks.js +250 -0
- package/dist/core/brief/checks.js.map +1 -0
- package/dist/core/brief/landscape.js +194 -0
- package/dist/core/brief/landscape.js.map +1 -0
- package/dist/core/brief/targets.js +226 -0
- package/dist/core/brief/targets.js.map +1 -0
- package/dist/core/brief/unchecked.js +47 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +282 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/likec4.js +88 -0
- package/dist/core/c4/likec4.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js +132 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
- package/dist/core/c4/parsed/specification.js +78 -0
- package/dist/core/c4/parsed/specification.js.map +1 -0
- package/dist/core/c4/parsed/values.js +57 -0
- package/dist/core/c4/parsed/values.js.map +1 -0
- package/dist/core/c4/parsed/view-ids.js +75 -0
- package/dist/core/c4/parsed/view-ids.js.map +1 -0
- package/dist/core/c4/project/architecture.js +45 -0
- package/dist/core/c4/project/architecture.js.map +1 -0
- package/dist/core/c4/project/documents.js +58 -0
- package/dist/core/c4/project/documents.js.map +1 -0
- package/dist/core/c4/project/load.js +183 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/resolve/service.js +113 -0
- package/dist/core/c4/resolve/service.js.map +1 -0
- package/dist/core/c4/seed/fleet-file.js +201 -0
- package/dist/core/c4/seed/fleet-file.js.map +1 -0
- package/dist/core/c4/seed/items.js +91 -0
- package/dist/core/c4/seed/items.js.map +1 -0
- package/dist/core/c4/seed/stamp.js +48 -0
- package/dist/core/c4/seed/stamp.js.map +1 -0
- package/dist/core/c4/seed/template.js +109 -0
- package/dist/core/c4/seed/template.js.map +1 -0
- package/dist/core/c4/source-mask.js +82 -0
- package/dist/core/c4/source-mask.js.map +1 -0
- package/dist/core/c4/source-scan.js +227 -0
- package/dist/core/c4/source-scan.js.map +1 -0
- package/dist/core/c4/splice/authored-source.js +101 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -0
- package/dist/core/c4/splice/contract.js +10 -0
- package/dist/core/c4/splice/contract.js.map +1 -0
- package/dist/core/c4/splice/delta-blocks.js +126 -0
- package/dist/core/c4/splice/delta-blocks.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +36 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +269 -0
- package/dist/core/c4/splice/landscape-merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +194 -0
- package/dist/core/c4/splice/placement.js.map +1 -0
- package/dist/core/c4/workspace.js +172 -0
- package/dist/core/c4/workspace.js.map +1 -0
- package/dist/core/capabilities/authoring/notes.js +141 -0
- package/dist/core/capabilities/authoring/notes.js.map +1 -0
- package/dist/core/capabilities/capabilities.js +124 -0
- package/dist/core/capabilities/capabilities.js.map +1 -0
- package/dist/core/capabilities/delta/doc.js +50 -0
- package/dist/core/capabilities/delta/doc.js.map +1 -0
- package/dist/core/capabilities/delta/overlay.js +129 -0
- package/dist/core/capabilities/delta/overlay.js.map +1 -0
- package/dist/core/capabilities/delta/summary.js +57 -0
- package/dist/core/capabilities/delta/summary.js.map +1 -0
- package/dist/core/capabilities/delta/tree.js +43 -0
- package/dist/core/capabilities/delta/tree.js.map +1 -0
- package/dist/core/capabilities/delta/uncovered.js +222 -0
- package/dist/core/capabilities/delta/uncovered.js.map +1 -0
- package/dist/core/capabilities/findings.js +342 -0
- package/dist/core/capabilities/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/findings.js +181 -0
- package/dist/core/capabilities/realizes/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/join.js +143 -0
- package/dist/core/capabilities/realizes/join.js.map +1 -0
- package/dist/core/capabilities/rollup.js +199 -0
- package/dist/core/capabilities/rollup.js.map +1 -0
- package/dist/core/capabilities/tree.js +126 -0
- package/dist/core/capabilities/tree.js.map +1 -0
- package/dist/core/capabilities/usecase-join.js +277 -0
- package/dist/core/capabilities/usecase-join.js.map +1 -0
- package/dist/core/coherence/authoring/scaffold.js +177 -0
- package/dist/core/coherence/authoring/scaffold.js.map +1 -0
- package/dist/core/coherence/authoring/sentinels.js +74 -0
- package/dist/core/coherence/authoring/sentinels.js.map +1 -0
- package/dist/core/coherence/coherence.js +251 -0
- package/dist/core/coherence/coherence.js.map +1 -0
- package/dist/core/coherence/declared.js +269 -0
- package/dist/core/coherence/declared.js.map +1 -0
- package/dist/core/coherence/events/declared.js +206 -0
- package/dist/core/coherence/events/declared.js.map +1 -0
- package/dist/core/coherence/events/events.js +204 -0
- package/dist/core/coherence/events/events.js.map +1 -0
- package/dist/core/coherence/events/grades.js +166 -0
- package/dist/core/coherence/events/grades.js.map +1 -0
- package/dist/core/coherence/events/lookups.js +122 -0
- package/dist/core/coherence/events/lookups.js.map +1 -0
- package/dist/core/coherence/events/merged.js +40 -0
- package/dist/core/coherence/events/merged.js.map +1 -0
- package/dist/core/coherence/living.js +198 -0
- package/dist/core/coherence/living.js.map +1 -0
- package/dist/core/coherence/lookups.js +155 -0
- package/dist/core/coherence/lookups.js.map +1 -0
- package/dist/core/coherence/pending.js +39 -0
- package/dist/core/coherence/pending.js.map +1 -0
- package/dist/core/conflict-markers.js +60 -0
- package/dist/core/conflict-markers.js.map +1 -0
- package/dist/core/delta/claims.js +73 -0
- package/dist/core/delta/claims.js.map +1 -0
- package/dist/core/delta/delta.js +296 -0
- package/dist/core/delta/delta.js.map +1 -0
- package/dist/core/delta/document.js +177 -0
- package/dist/core/delta/document.js.map +1 -0
- package/dist/core/delta/scope.js +48 -0
- package/dist/core/delta/scope.js.map +1 -0
- package/dist/core/delta/select.js +237 -0
- package/dist/core/delta/select.js.map +1 -0
- package/dist/core/dependencies/dependencies.js +141 -0
- package/dist/core/dependencies/dependencies.js.map +1 -0
- package/dist/core/dependencies/facts.js +165 -0
- package/dist/core/dependencies/facts.js.map +1 -0
- package/dist/core/dependencies/fanin.js +88 -0
- package/dist/core/dependencies/fanin.js.map +1 -0
- package/dist/core/dependencies/graph.js +106 -0
- package/dist/core/dependencies/graph.js.map +1 -0
- package/dist/core/diff/base-git.js +148 -0
- package/dist/core/diff/base-git.js.map +1 -0
- package/dist/core/diff/base-state.js +209 -0
- package/dist/core/diff/base-state.js.map +1 -0
- package/dist/core/diff/current-state.js +96 -0
- package/dist/core/diff/current-state.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +188 -0
- package/dist/core/diff/scope/changed-paths.js.map +1 -0
- package/dist/core/diff/semantic.js +243 -0
- package/dist/core/diff/semantic.js.map +1 -0
- package/dist/core/diff/victims.js +247 -0
- package/dist/core/diff/victims.js.map +1 -0
- package/dist/core/docs.js +7 -64
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +222 -0
- package/dist/core/doctor/agents.js.map +1 -0
- package/dist/core/doctor/config.js +135 -0
- package/dist/core/doctor/config.js.map +1 -0
- package/dist/core/doctor/doctor.js +275 -0
- package/dist/core/doctor/doctor.js.map +1 -0
- package/dist/core/doctor/report.js +13 -0
- package/dist/core/doctor/report.js.map +1 -0
- package/dist/core/doctor/residue.js +110 -0
- package/dist/core/doctor/residue.js.map +1 -0
- package/dist/core/document/apply.js +77 -0
- package/dist/core/document/apply.js.map +1 -0
- package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
- package/dist/core/document/frontmatter.js.map +1 -0
- package/dist/core/document/grammar/directives.js +143 -0
- package/dist/core/document/grammar/directives.js.map +1 -0
- package/dist/core/document/parse.js +263 -0
- package/dist/core/document/parse.js.map +1 -0
- package/dist/core/document/scenarios.js +221 -0
- package/dist/core/document/scenarios.js.map +1 -0
- package/dist/core/document/spec.js +241 -0
- package/dist/core/document/spec.js.map +1 -0
- package/dist/core/{config.js → envelope/config.js} +79 -18
- package/dist/core/envelope/config.js.map +1 -0
- package/dist/core/envelope/json.js +175 -0
- package/dist/core/envelope/json.js.map +1 -0
- package/dist/core/{version.js → envelope/version.js} +3 -3
- package/dist/core/envelope/version.js.map +1 -0
- package/dist/core/explain/families.js +175 -0
- package/dist/core/explain/families.js.map +1 -0
- package/dist/core/explain/fix-tables.js +178 -0
- package/dist/core/explain/fix-tables.js.map +1 -0
- package/dist/core/explain/lookup.js +181 -0
- package/dist/core/explain/lookup.js.map +1 -0
- package/dist/core/explain/refusals.js +49 -0
- package/dist/core/explain/refusals.js.map +1 -0
- package/dist/core/explain/terms.js +144 -0
- package/dist/core/explain/terms.js.map +1 -0
- package/dist/core/explore/describe.js +105 -0
- package/dist/core/explore/describe.js.map +1 -0
- package/dist/core/{explore.js → explore/explore.js} +50 -101
- package/dist/core/explore/explore.js.map +1 -0
- package/dist/core/fleet-context.js +120 -78
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +201 -0
- package/dist/core/gate/checks.js.map +1 -0
- package/dist/core/gate/partners.js +205 -0
- package/dist/core/gate/partners.js.map +1 -0
- package/dist/core/gate/report.js +47 -0
- package/dist/core/gate/report.js.map +1 -0
- package/dist/core/gate/verification.js +113 -0
- package/dist/core/gate/verification.js.map +1 -0
- package/dist/core/gherkin/digest.js +60 -0
- package/dist/core/gherkin/digest.js.map +1 -0
- package/dist/core/gherkin/emit.js +177 -0
- package/dist/core/gherkin/emit.js.map +1 -0
- package/dist/core/gherkin/read.js +76 -0
- package/dist/core/gherkin/read.js.map +1 -0
- package/dist/core/gherkin/stale.js +234 -0
- package/dist/core/gherkin/stale.js.map +1 -0
- package/dist/core/gherkin/stamp.js +77 -0
- package/dist/core/gherkin/stamp.js.map +1 -0
- package/dist/core/gherkin/steps/catalogue.js +154 -0
- package/dist/core/gherkin/steps/catalogue.js.map +1 -0
- package/dist/core/gherkin/steps/inventory.js +98 -0
- package/dist/core/gherkin/steps/inventory.js.map +1 -0
- package/dist/core/gherkin/steps/phrase.js +72 -0
- package/dist/core/gherkin/steps/phrase.js.map +1 -0
- package/dist/core/glossary/delta.js +106 -0
- package/dist/core/glossary/delta.js.map +1 -0
- package/dist/core/glossary/tree.js +86 -0
- package/dist/core/glossary/tree.js.map +1 -0
- package/dist/core/kernel/concurrency.js.map +1 -0
- package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
- package/dist/core/kernel/document-bytes.js.map +1 -0
- package/dist/core/kernel/fences.js +42 -0
- package/dist/core/kernel/fences.js.map +1 -0
- package/dist/core/kernel/ids/capability.js +72 -0
- package/dist/core/kernel/ids/capability.js.map +1 -0
- package/dist/core/kernel/ids/dirs.js +33 -0
- package/dist/core/kernel/ids/dirs.js.map +1 -0
- package/dist/core/kernel/ids/feature.js +75 -0
- package/dist/core/kernel/ids/feature.js.map +1 -0
- package/dist/core/kernel/ids/service.js +156 -0
- package/dist/core/kernel/ids/service.js.map +1 -0
- package/dist/core/kernel/ids/subsystem.js +63 -0
- package/dist/core/kernel/ids/subsystem.js.map +1 -0
- package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
- package/dist/core/kernel/path-safety.js.map +1 -0
- package/dist/core/kernel/records.js.map +1 -0
- package/dist/core/links/backlinks.js +68 -0
- package/dist/core/links/backlinks.js.map +1 -0
- package/dist/core/links/case.js +74 -0
- package/dist/core/links/case.js.map +1 -0
- package/dist/core/links/corpus.js +104 -0
- package/dist/core/links/corpus.js.map +1 -0
- package/dist/core/links/findings.js +110 -0
- package/dist/core/links/findings.js.map +1 -0
- package/dist/core/links/parse.js +236 -0
- package/dist/core/links/parse.js.map +1 -0
- package/dist/core/mcp/argv.js +111 -0
- package/dist/core/mcp/argv.js.map +1 -0
- package/dist/core/mcp/author-tools.js +72 -0
- package/dist/core/mcp/author-tools.js.map +1 -0
- package/dist/core/mcp/framing.js +46 -0
- package/dist/core/mcp/framing.js.map +1 -0
- package/dist/core/mcp/protocol.js +280 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/tools.js +314 -0
- package/dist/core/mcp/tools.js.map +1 -0
- package/dist/core/obligations/obligations.js +106 -0
- package/dist/core/obligations/obligations.js.map +1 -0
- package/dist/core/openapi/baseline/gate.js +166 -0
- package/dist/core/openapi/baseline/gate.js.map +1 -0
- package/dist/core/openapi/baseline/plan.js +112 -0
- package/dist/core/openapi/baseline/plan.js.map +1 -0
- package/dist/core/openapi/baseline/record.js +175 -0
- package/dist/core/openapi/baseline/record.js.map +1 -0
- package/dist/core/openapi/depth.js +122 -0
- package/dist/core/openapi/depth.js.map +1 -0
- package/dist/core/openapi/digest.js +189 -0
- package/dist/core/openapi/digest.js.map +1 -0
- package/dist/core/{openapi.js → openapi/doc.js} +44 -106
- package/dist/core/openapi/doc.js.map +1 -0
- package/dist/core/openapi/merge/components.js +234 -0
- package/dist/core/openapi/merge/components.js.map +1 -0
- package/dist/core/openapi/merge/error.js +26 -0
- package/dist/core/openapi/merge/error.js.map +1 -0
- package/dist/core/openapi/merge/markers.js +208 -0
- package/dist/core/openapi/merge/markers.js.map +1 -0
- package/dist/core/openapi/merge/merge.js +256 -0
- package/dist/core/openapi/merge/merge.js.map +1 -0
- package/dist/core/openapi/merge/pin.js +128 -0
- package/dist/core/openapi/merge/pin.js.map +1 -0
- package/dist/core/openspec/decide/capabilities.js +244 -0
- package/dist/core/openspec/decide/capabilities.js.map +1 -0
- package/dist/core/openspec/decide/dispositions.js +63 -0
- package/dist/core/openspec/decide/dispositions.js.map +1 -0
- package/dist/core/openspec/decide/renames.js +179 -0
- package/dist/core/openspec/decide/renames.js.map +1 -0
- package/dist/core/openspec/decide/verdict.js +84 -0
- package/dist/core/openspec/decide/verdict.js.map +1 -0
- package/dist/core/openspec/inventory.js +221 -0
- package/dist/core/openspec/inventory.js.map +1 -0
- package/dist/core/openspec/model/mapping.js +76 -0
- package/dist/core/openspec/model/mapping.js.map +1 -0
- package/dist/core/openspec/model/model.js +65 -0
- package/dist/core/openspec/model/model.js.map +1 -0
- package/dist/core/openspec/scan/changes.js +235 -0
- package/dist/core/openspec/scan/changes.js.map +1 -0
- package/dist/core/openspec/scan/config.js +200 -0
- package/dist/core/openspec/scan/config.js.map +1 -0
- package/dist/core/openspec/scan/shape.js +260 -0
- package/dist/core/openspec/scan/shape.js.map +1 -0
- package/dist/core/openspec/scan/walk.js +133 -0
- package/dist/core/openspec/scan/walk.js.map +1 -0
- package/dist/core/openspec/scan/workspace.js +71 -0
- package/dist/core/openspec/scan/workspace.js.map +1 -0
- package/dist/core/owners/codeowners.js +141 -0
- package/dist/core/owners/codeowners.js.map +1 -0
- package/dist/core/pack/features.js +120 -0
- package/dist/core/pack/features.js.map +1 -0
- package/dist/core/pack/joins.js +112 -0
- package/dist/core/pack/joins.js.map +1 -0
- package/dist/core/pack/living.js +118 -0
- package/dist/core/pack/living.js.map +1 -0
- package/dist/core/pack/pack.js +76 -0
- package/dist/core/pack/pack.js.map +1 -0
- package/dist/core/permissions/permissions.js +87 -0
- package/dist/core/permissions/permissions.js.map +1 -0
- package/dist/core/projection/api.js +84 -0
- package/dist/core/projection/api.js.map +1 -0
- package/dist/core/projection/arch-slice.js +63 -0
- package/dist/core/projection/arch-slice.js.map +1 -0
- package/dist/core/projection/events.js +34 -0
- package/dist/core/projection/events.js.map +1 -0
- package/dist/core/provenance/findings.js +208 -0
- package/dist/core/provenance/findings.js.map +1 -0
- package/dist/core/provenance/git.js +225 -0
- package/dist/core/provenance/git.js.map +1 -0
- package/dist/core/provenance/gitq/moves.js +182 -0
- package/dist/core/provenance/gitq/moves.js.map +1 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
- package/dist/core/provenance/sample/scope.js +238 -0
- package/dist/core/provenance/sample/scope.js.map +1 -0
- package/dist/core/provenance/sample/sections.js +61 -0
- package/dist/core/provenance/sample/sections.js.map +1 -0
- package/dist/core/provenance/sources.js +263 -0
- package/dist/core/provenance/sources.js.map +1 -0
- package/dist/core/provenance/stamp.js +232 -0
- package/dist/core/provenance/stamp.js.map +1 -0
- package/dist/core/provenance/walk.js +157 -0
- package/dist/core/provenance/walk.js.map +1 -0
- package/dist/core/repo/authored/paths.js +192 -0
- package/dist/core/repo/authored/paths.js.map +1 -0
- package/dist/core/repo/entries.js +126 -0
- package/dist/core/repo/entries.js.map +1 -0
- package/dist/core/repo/paths.js +236 -0
- package/dist/core/repo/paths.js.map +1 -0
- package/dist/core/repo/repo.js +248 -0
- package/dist/core/repo/repo.js.map +1 -0
- package/dist/core/repo/service-target.js +153 -0
- package/dist/core/repo/service-target.js.map +1 -0
- package/dist/core/repo/state.js +58 -0
- package/dist/core/repo/state.js.map +1 -0
- package/dist/core/repo/tree/find.js +52 -0
- package/dist/core/repo/tree/find.js.map +1 -0
- package/dist/core/repo/tree/fs.js +64 -0
- package/dist/core/repo/tree/fs.js.map +1 -0
- package/dist/core/repo/tree/marker.js +61 -0
- package/dist/core/repo/tree/marker.js.map +1 -0
- package/dist/core/repo/tree/views.js +172 -0
- package/dist/core/repo/tree/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +234 -0
- package/dist/core/repo/tree/walk.js.map +1 -0
- package/dist/core/results.js +99 -2
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +131 -0
- package/dist/core/scaffold/landscape.js.map +1 -0
- package/dist/core/scaffold/readme.js +84 -0
- package/dist/core/scaffold/readme.js.map +1 -0
- package/dist/core/staging/commit.js +241 -0
- package/dist/core/staging/commit.js.map +1 -0
- package/dist/core/staging/interrupted.js +96 -0
- package/dist/core/staging/interrupted.js.map +1 -0
- package/dist/core/staging/lock.js +206 -0
- package/dist/core/staging/lock.js.map +1 -0
- package/dist/core/staging/recovery/finding.js +35 -0
- package/dist/core/staging/recovery/finding.js.map +1 -0
- package/dist/core/staging/recovery/intent.js +98 -0
- package/dist/core/staging/recovery/intent.js.map +1 -0
- package/dist/core/staging/recovery/recover.js +126 -0
- package/dist/core/staging/recovery/recover.js.map +1 -0
- package/dist/core/staging/recovery/residue.js +62 -0
- package/dist/core/staging/recovery/residue.js.map +1 -0
- package/dist/core/staging/snapshot.js +202 -0
- package/dist/core/staging/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +171 -0
- package/dist/core/staging/txn/forward.js.map +1 -0
- package/dist/core/staging/txn/journal.js +139 -0
- package/dist/core/staging/txn/journal.js.map +1 -0
- package/dist/core/staging/txn/transaction.js +75 -0
- package/dist/core/staging/txn/transaction.js.map +1 -0
- package/dist/core/staging/writes.js +113 -0
- package/dist/core/staging/writes.js.map +1 -0
- package/dist/core/status/actions/execution.js +49 -0
- package/dist/core/status/actions/execution.js.map +1 -0
- package/dist/core/status/contracts.js +102 -0
- package/dist/core/status/contracts.js.map +1 -0
- package/dist/core/status/feature/artifacts.js +171 -0
- package/dist/core/status/feature/artifacts.js.map +1 -0
- package/dist/core/status/feature/feature.js +207 -0
- package/dist/core/status/feature/feature.js.map +1 -0
- package/dist/core/status/feature/next.js +150 -0
- package/dist/core/status/feature/next.js.map +1 -0
- package/dist/core/status/feature/state.js +21 -0
- package/dist/core/status/feature/state.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +187 -0
- package/dist/core/status/fleet/fleet.js.map +1 -0
- package/dist/core/status/fleet/next.js +207 -0
- package/dist/core/status/fleet/next.js.map +1 -0
- package/dist/core/status/interrupted.js +97 -0
- package/dist/core/status/interrupted.js.map +1 -0
- package/dist/core/status/report.js +31 -0
- package/dist/core/status/report.js.map +1 -0
- package/dist/core/status/scan.js +41 -0
- package/dist/core/status/scan.js.map +1 -0
- package/dist/core/status/verification.js +201 -0
- package/dist/core/status/verification.js.map +1 -0
- package/dist/core/usecases/capability.js +169 -0
- package/dist/core/usecases/capability.js.map +1 -0
- package/dist/core/usecases/delta/claims.js +116 -0
- package/dist/core/usecases/delta/claims.js.map +1 -0
- package/dist/core/usecases/delta/flows.js +103 -0
- package/dist/core/usecases/delta/flows.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +235 -0
- package/dist/core/usecases/delta/overlay.js.map +1 -0
- package/dist/core/usecases/fleet.js +137 -0
- package/dist/core/usecases/fleet.js.map +1 -0
- package/dist/core/usecases/operations.js +111 -0
- package/dist/core/usecases/operations.js.map +1 -0
- package/dist/core/usecases/place.js +44 -0
- package/dist/core/usecases/place.js.map +1 -0
- package/dist/core/usecases/touch.js +101 -0
- package/dist/core/usecases/touch.js.map +1 -0
- package/dist/core/verify/answers.js +173 -0
- package/dist/core/verify/answers.js.map +1 -0
- package/dist/core/verify/build.js +150 -0
- package/dist/core/verify/build.js.map +1 -0
- package/dist/core/verify/checklist.js +201 -0
- package/dist/core/verify/checklist.js.map +1 -0
- package/dist/core/verify/claims/identity.js +61 -0
- package/dist/core/verify/claims/identity.js.map +1 -0
- package/dist/core/verify/cross/diff.js +97 -0
- package/dist/core/verify/cross/diff.js.map +1 -0
- package/dist/core/verify/evidence/contract.js +169 -0
- package/dist/core/verify/evidence/contract.js.map +1 -0
- package/dist/core/verify/file.js +257 -0
- package/dist/core/verify/file.js.map +1 -0
- package/dist/core/verify/pins/lint.js +157 -0
- package/dist/core/verify/pins/lint.js.map +1 -0
- package/dist/core/verify/pins/pin.js +95 -0
- package/dist/core/verify/pins/pin.js.map +1 -0
- package/dist/core/verify/record.js +142 -0
- package/dist/core/verify/record.js.map +1 -0
- package/dist/core/verify/store/commit.js +89 -0
- package/dist/core/verify/store/commit.js.map +1 -0
- package/dist/core/verify/store/render.js +57 -0
- package/dist/core/verify/store/render.js.map +1 -0
- package/dist/core/vocabulary/health.js +91 -0
- package/dist/core/vocabulary/health.js.map +1 -0
- package/dist/core/vocabulary/issue.js +55 -0
- package/dist/core/vocabulary/issue.js.map +1 -0
- package/dist/core/vocabulary/maturity.js +145 -0
- package/dist/core/vocabulary/maturity.js.map +1 -0
- package/dist/core/{report.js → vocabulary/report.js} +30 -2
- package/dist/core/vocabulary/report.js.map +1 -0
- package/dist/core/vocabulary/steps.js +266 -0
- package/dist/core/vocabulary/steps.js.map +1 -0
- package/dist/core/workspace/discover.js +239 -0
- package/dist/core/workspace/discover.js.map +1 -0
- package/dist/core/workspace/render.js +52 -0
- package/dist/core/workspace/render.js.map +1 -0
- package/examples/README.md +189 -0
- package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
- package/examples/docs/architecture/capabilities.yaml +31 -0
- package/examples/docs/architecture/landscape.likec4 +280 -0
- package/examples/docs/architecture/obligations.yaml +23 -0
- package/examples/docs/architecture/permissions.yaml +50 -0
- package/examples/docs/architecture/subsystems.likec4 +11 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
- package/examples/docs/capabilities/checkout/spec.md +55 -0
- package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
- package/examples/docs/capabilities/order-notifications/spec.md +43 -0
- package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
- package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
- package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
- package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
- package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
- package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
- package/examples/docs/glossary/order.md +15 -0
- package/examples/docs/glossary/payments/authorization.md +14 -0
- package/examples/docs/likec4.config.json +9 -0
- package/examples/docs/loam.json +3 -0
- package/examples/docs/services/checkout-web/model.likec4 +109 -0
- package/examples/docs/services/checkout-web/spec.md +91 -0
- package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
- package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
- package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
- package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
- package/examples/docs/services/order-service/arch.spec.md +127 -0
- package/examples/docs/services/order-service/asyncapi.yaml +108 -0
- package/examples/docs/services/order-service/health.yaml +57 -0
- package/examples/docs/services/order-service/model.likec4 +138 -0
- package/examples/docs/services/order-service/openapi.yaml +257 -0
- package/examples/docs/services/order-service/runbook.md +62 -0
- package/examples/docs/services/order-service/spec.md +134 -0
- package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
- package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
- package/examples/docs/services/payment-service/arch.spec.md +73 -0
- package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
- package/examples/docs/services/payment-service/health.yaml +39 -0
- package/examples/docs/services/payment-service/model.likec4 +67 -0
- package/examples/docs/services/payment-service/openapi.yaml +151 -0
- package/examples/docs/services/payment-service/runbook.md +37 -0
- package/examples/docs/services/payment-service/spec.md +88 -0
- package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
- package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
- package/examples/docs/services/platform/identity-service/health.yaml +70 -0
- package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
- package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
- package/examples/docs/services/platform/identity-service/runbook.md +95 -0
- package/examples/docs/services/platform/identity-service/spec.md +119 -0
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
- package/examples/docs/services/platform/notification-service/health.yaml +35 -0
- package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
- package/examples/docs/services/platform/notification-service/runbook.md +48 -0
- package/examples/docs/services/platform/notification-service/spec.md +112 -0
- package/examples/docs/services/platform/subsystem.yaml +2 -0
- package/examples/fleet.yaml +57 -0
- package/package.json +19 -6
- package/dist/commands/adopt.js +0 -240
- package/dist/commands/adopt.js.map +0 -1
- package/dist/commands/archive.js +0 -1313
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/delta.js +0 -388
- package/dist/commands/delta.js.map +0 -1
- package/dist/commands/docs-repo-gate.js.map +0 -1
- package/dist/commands/format.js +0 -17
- package/dist/commands/format.js.map +0 -1
- package/dist/commands/gherkin.js +0 -309
- package/dist/commands/gherkin.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.js +0 -295
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/migrate-openspec.js +0 -950
- package/dist/commands/migrate-openspec.js.map +0 -1
- package/dist/commands/new.js +0 -413
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/rebase.js +0 -356
- package/dist/commands/rebase.js.map +0 -1
- package/dist/commands/show.js.map +0 -1
- package/dist/commands/status.js +0 -262
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1773
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/verify.js +0 -775
- package/dist/commands/verify.js.map +0 -1
- package/dist/commands/vouch.js +0 -421
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2173
- package/dist/core/agent.js.map +0 -1
- package/dist/core/agents-stamp.js.map +0 -1
- package/dist/core/arch.js +0 -130
- package/dist/core/arch.js.map +0 -1
- package/dist/core/asyncapi.js.map +0 -1
- package/dist/core/brief.js +0 -755
- package/dist/core/brief.js.map +0 -1
- package/dist/core/coherence.js +0 -577
- package/dist/core/coherence.js.map +0 -1
- package/dist/core/concurrency.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/delta.js +0 -476
- package/dist/core/delta.js.map +0 -1
- package/dist/core/dependencies.js +0 -356
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/doctor.js +0 -596
- package/dist/core/doctor.js.map +0 -1
- package/dist/core/document-bytes.js.map +0 -1
- package/dist/core/explore.js.map +0 -1
- package/dist/core/frontmatter.js.map +0 -1
- package/dist/core/gherkin.js +0 -463
- package/dist/core/gherkin.js.map +0 -1
- package/dist/core/health.js +0 -67
- package/dist/core/health.js.map +0 -1
- package/dist/core/ids.js +0 -122
- package/dist/core/ids.js.map +0 -1
- package/dist/core/issue.js +0 -9
- package/dist/core/issue.js.map +0 -1
- package/dist/core/json.js +0 -74
- package/dist/core/json.js.map +0 -1
- package/dist/core/likec4.js +0 -489
- package/dist/core/likec4.js.map +0 -1
- package/dist/core/maturity.js +0 -66
- package/dist/core/maturity.js.map +0 -1
- package/dist/core/openapi-merge.js +0 -552
- package/dist/core/openapi-merge.js.map +0 -1
- package/dist/core/openapi.js.map +0 -1
- package/dist/core/openspec-inventory.js +0 -1440
- package/dist/core/openspec-inventory.js.map +0 -1
- package/dist/core/path-safety.js.map +0 -1
- package/dist/core/provenance.js +0 -787
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -438
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -505
- package/dist/core/spec.js.map +0 -1
- package/dist/core/staging.js +0 -890
- package/dist/core/staging.js.map +0 -1
- package/dist/core/status.js +0 -1132
- package/dist/core/status.js.map +0 -1
- package/dist/core/steps.js +0 -72
- package/dist/core/steps.js.map +0 -1
- package/dist/core/verify.js +0 -807
- package/dist/core/verify.js.map +0 -1
- package/dist/core/version.js.map +0 -1
- /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
- /package/dist/core/{records.js → kernel/records.js} +0 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The six files `loam new` scaffolds, and what each one says.
|
|
3
|
+
*
|
|
4
|
+
* They are here rather than inline because every one of them is a document a
|
|
5
|
+
* person edits next, and the wording IS the instruction: the delta's comments
|
|
6
|
+
* teach the grammar `core/document/parse.ts` matches on, and the intent's
|
|
7
|
+
* frontmatter is serialized rather than interpolated because a title is free
|
|
8
|
+
* text somebody types — `Checkout: split payments` reopens the mapping and the
|
|
9
|
+
* file stops parsing.
|
|
10
|
+
*/
|
|
11
|
+
import { stringify as stringifyYaml } from "yaml";
|
|
12
|
+
import { REQUIREMENT_ID_RE } from "../../core/document/spec.js";
|
|
13
|
+
import { ARCH_REQUIREMENT_SENTINEL, ARCH_SHALL_SENTINEL, CAPABILITY_REQUIREMENT_SENTINEL, CAPABILITY_SHALL_SENTINEL, REQUIREMENT_SENTINEL, SCENARIO_SENTINEL, SERVICE_DESCRIPTION_SENTINEL, GIVEN_SENTINEL, SHALL_SENTINEL, THEN_SENTINEL, WHEN_SENTINEL, } from "../../core/coherence/authoring/sentinels.js";
|
|
14
|
+
function identifier(name, taken) {
|
|
15
|
+
const parts = name.split(/[^A-Za-z0-9]+/).filter((p) => p.length > 0);
|
|
16
|
+
const head = (parts[0] ?? "svc").replace(/^\d+/, "") || "svc";
|
|
17
|
+
const base = head[0].toLowerCase() +
|
|
18
|
+
head.slice(1) +
|
|
19
|
+
parts.slice(1).map((p) => p[0].toUpperCase() + p.slice(1)).join("");
|
|
20
|
+
let id = base;
|
|
21
|
+
for (let n = 2; taken.has(id); n += 1)
|
|
22
|
+
id = `${base}${n}`;
|
|
23
|
+
taken.add(id);
|
|
24
|
+
return id;
|
|
25
|
+
}
|
|
26
|
+
/** `FEAT-101` -> `feat_101`: LikeC4 view names take no dashes. */
|
|
27
|
+
function viewName(featureId) {
|
|
28
|
+
return featureId.toLowerCase().replace(/[^a-z0-9]+/g, "_");
|
|
29
|
+
}
|
|
30
|
+
/* ------------------------------------------------------------------ */
|
|
31
|
+
/* Templates */
|
|
32
|
+
/* ------------------------------------------------------------------ */
|
|
33
|
+
export function intentTemplate(featureId, title) {
|
|
34
|
+
const heading = title ?? featureId;
|
|
35
|
+
// The frontmatter is SERIALIZED, never interpolated. A title is free text a
|
|
36
|
+
// person types — `Checkout: split payments` used to be pasted straight after
|
|
37
|
+
// `title: `, where the colon reopens the mapping and the file stops parsing;
|
|
38
|
+
// `Orders #42 rework` lost everything from the `#` as a YAML comment, so the
|
|
39
|
+
// feature quietly answered to a title nobody wrote. The yaml serializer is the
|
|
40
|
+
// only thing that knows every case, and it is already how migrate-openspec
|
|
41
|
+
// writes the same three keys.
|
|
42
|
+
//
|
|
43
|
+
// `owner` is deliberately NOT a key here. Scaffolding `owner:` writes an
|
|
44
|
+
// explicit null, which is a claim ("this feature has no owner") rather than
|
|
45
|
+
// the truth ("nobody has said yet") — and every reader then has to tell an
|
|
46
|
+
// absent key from a null one. The prompt survives as a comment.
|
|
47
|
+
const frontmatter = stringifyYaml({ feature: featureId, ...(title === undefined ? {} : { title }), status: "proposed" }, { lineWidth: 0 }).trimEnd();
|
|
48
|
+
return `---
|
|
49
|
+
${frontmatter}
|
|
50
|
+
# owner: <the team or person who answers for this>
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
# ${heading}
|
|
54
|
+
|
|
55
|
+
## Why
|
|
56
|
+
|
|
57
|
+
<!-- The problem in business terms. This is what a reviewer reads first, and what
|
|
58
|
+
the requirement deltas below have to be justified by. -->
|
|
59
|
+
|
|
60
|
+
## Scope
|
|
61
|
+
|
|
62
|
+
<!-- Which services this touches — and, more usefully, which it deliberately does not. -->
|
|
63
|
+
`;
|
|
64
|
+
}
|
|
65
|
+
export function deltaTemplate(featureId, touched, created) {
|
|
66
|
+
const taken = new Set();
|
|
67
|
+
const touchedIds = touched.map((s) => [identifier(s, taken), s]);
|
|
68
|
+
const createdIds = created.map((s) => [identifier(s, taken), s]);
|
|
69
|
+
const lines = [];
|
|
70
|
+
if (touchedIds.length > 0) {
|
|
71
|
+
// Context elements ship COMMENTED OUT, exactly like the example edge below.
|
|
72
|
+
// Declared-and-untagged is the one shape `delta.nothing-tagged` exists to
|
|
73
|
+
// refuse (an author who forgot the tags), so writing these live made every
|
|
74
|
+
// `loam new --touches X` fail its own validator on the very first run —
|
|
75
|
+
// which teaches people that the validator is noise. Uncomment a line when an
|
|
76
|
+
// edge below actually needs the identifier.
|
|
77
|
+
lines.push(" // Services this feature touches, as context for the diagram. Uncomment the");
|
|
78
|
+
lines.push(" // ones an edge below names, and reuse the identifiers from");
|
|
79
|
+
lines.push(" // architecture/landscape.likec4 so the merge lines up. They stay UNTAGGED:");
|
|
80
|
+
lines.push(` // only #${featureId} is the change, and everything else here is context.`);
|
|
81
|
+
for (const [id, name] of touchedIds)
|
|
82
|
+
lines.push(` // ${id} = softwareSystem '${name}'`);
|
|
83
|
+
lines.push("");
|
|
84
|
+
}
|
|
85
|
+
if (createdIds.length > 0) {
|
|
86
|
+
lines.push(" // Services this feature introduces. The tag is what `loam archive` folds");
|
|
87
|
+
lines.push(" // into the living landscape.");
|
|
88
|
+
for (const [id, name] of createdIds) {
|
|
89
|
+
lines.push(` ${id} = softwareSystem '${name}' {`);
|
|
90
|
+
lines.push(` #${featureId}`);
|
|
91
|
+
// Interpolated from sentinels.ts, never spelled here: the placeholder
|
|
92
|
+
// gate refuses this exact string at archive, and a second spelling is
|
|
93
|
+
// how a template rewording silently retires the check that guards it.
|
|
94
|
+
lines.push(` description '${SERVICE_DESCRIPTION_SENTINEL}'`);
|
|
95
|
+
lines.push(" }");
|
|
96
|
+
}
|
|
97
|
+
lines.push("");
|
|
98
|
+
}
|
|
99
|
+
// The example edge uses identifiers already declared above when there are two
|
|
100
|
+
// to join; otherwise placeholders, so it never invents a service.
|
|
101
|
+
const from = touchedIds[0]?.[0] ?? createdIds[0]?.[0] ?? "consumer";
|
|
102
|
+
const to = createdIds[0]?.[0] ?? touchedIds[1]?.[0] ?? "provider";
|
|
103
|
+
lines.push(" // New calls. `metadata { op }` is the spine: it names the OpenAPI operationId");
|
|
104
|
+
lines.push(" // the call uses, and `loam validate` checks it against the target's contract.");
|
|
105
|
+
lines.push(" // Uncomment and adjust (both endpoints have to be declared above):");
|
|
106
|
+
lines.push(" //");
|
|
107
|
+
lines.push(` // ${from} -> ${to} 'Calls createSplit' {`);
|
|
108
|
+
lines.push(` // #${featureId}`);
|
|
109
|
+
lines.push(" // metadata { op 'createSplit' }");
|
|
110
|
+
lines.push(" // }");
|
|
111
|
+
return `// ${featureId} — architecture delta.
|
|
112
|
+
//
|
|
113
|
+
// Everything tagged #${featureId} is exactly what \`loam archive\` folds into
|
|
114
|
+
// architecture/landscape.likec4. Everything else here is context for the diagram.
|
|
115
|
+
//
|
|
116
|
+
// If ${featureId} changes no architecture, DELETE this file: a requirements-only
|
|
117
|
+
// feature is complete without it, and an empty \`model {}\` is just as legal.
|
|
118
|
+
|
|
119
|
+
specification {
|
|
120
|
+
element softwareSystem
|
|
121
|
+
tag ${featureId}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
model {
|
|
125
|
+
${lines.join("\n")}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
views {
|
|
129
|
+
view ${viewName(featureId)} {
|
|
130
|
+
include *
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
`;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The business axis, scaffolded with its example INSIDE the comment — the same
|
|
137
|
+
* mechanism as `archSpecTemplate` below, for the same reason: this template
|
|
138
|
+
* used to ship a live `### Requirement: TODO — name the behaviour` that
|
|
139
|
+
* parsed as a real requirement, validated clean, and archived a literal TODO
|
|
140
|
+
* into the living spec at exit 0. The indented headings sit past the
|
|
141
|
+
* line-anchored patterns core/document/parse.ts matches on, so the scaffold
|
|
142
|
+
* declares nothing until a person copies the block out and writes over the
|
|
143
|
+
* fill-ins — and `scaffold.placeholder` gates the archive if the fill-ins
|
|
144
|
+
* survive the copy.
|
|
145
|
+
*
|
|
146
|
+
* No nested HTML comments: the `Operations:` guidance that used to be its own
|
|
147
|
+
* comment lives in this one's prose, because an inner `-->` would end the
|
|
148
|
+
* outer comment early and re-expose the example to the parser.
|
|
149
|
+
*/
|
|
150
|
+
export function specTemplate(featureId, service) {
|
|
151
|
+
return `# ${service} — requirement delta for ${featureId}
|
|
152
|
+
|
|
153
|
+
<!-- Sections: ADDED / MODIFIED / REMOVED. Delete the ones you do not need.
|
|
154
|
+
A MODIFIED requirement carries its full new text, not a diff.
|
|
155
|
+
Every requirement needs at least one scenario — \`loam validate\` gates on it.
|
|
156
|
+
|
|
157
|
+
An \`Operations:\` body line names the operationIds the requirement
|
|
158
|
+
governs. \`loam validate\` checks each one against the service's OpenAPI,
|
|
159
|
+
and \`loam archive\` refuses to merge a requirement that governs an
|
|
160
|
+
operation nobody defines — add the line once the contract exists.
|
|
161
|
+
|
|
162
|
+
A \`Publishes:\` / \`Consumes:\` body line does the same for event messages:
|
|
163
|
+
it names what the requirement puts on or takes off the bus, checked
|
|
164
|
+
against the service's AsyncAPI contract. If ${featureId} changes that
|
|
165
|
+
contract, hand-create specs/${service}/asyncapi.yaml — a complete
|
|
166
|
+
AsyncAPI 3.0 document, messages declared under \`components.messages\`
|
|
167
|
+
(loam scaffolds none: an event contract is genuinely optional) — and run
|
|
168
|
+
\`loam rebase ${featureId}\` so every restated slot pins as a quote.
|
|
169
|
+
|
|
170
|
+
Copy the block below out of this comment, unindent it, and replace the
|
|
171
|
+
TODOs and every <angle-bracket> fill-in — \`loam archive\` refuses the
|
|
172
|
+
scaffold's own wording (\`scaffold.placeholder\`):
|
|
173
|
+
|
|
174
|
+
## ADDED Requirements
|
|
175
|
+
|
|
176
|
+
### Requirement: ${REQUIREMENT_SENTINEL}
|
|
177
|
+
Requirement-ID: ${featureId}.${service}.requirement
|
|
178
|
+
|
|
179
|
+
The service SHALL ${SHALL_SENTINEL}.
|
|
180
|
+
|
|
181
|
+
#### Scenario: ${SCENARIO_SENTINEL}
|
|
182
|
+
- **Given** ${GIVEN_SENTINEL}
|
|
183
|
+
- **When** ${WHEN_SENTINEL}
|
|
184
|
+
- **Then** ${THEN_SENTINEL}
|
|
185
|
+
-->
|
|
186
|
+
`;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* The architecture axis, scaffolded EMPTY on purpose.
|
|
190
|
+
*
|
|
191
|
+
* Same grammar as spec.md, so the template body has to be unparseable as
|
|
192
|
+
* requirements or the scaffold would ship a requirement nobody wrote — the
|
|
193
|
+
* headings are therefore indented inside an HTML comment, which puts them past
|
|
194
|
+
* the line-anchored `## ADDED Requirements` / `### Requirement:` patterns
|
|
195
|
+
* core/document/parse.ts matches on. Copy the block out of the comment and unindent it.
|
|
196
|
+
*/
|
|
197
|
+
export function archSpecTemplate(featureId, service) {
|
|
198
|
+
return `# ${service} — architecture requirement delta for ${featureId}
|
|
199
|
+
|
|
200
|
+
<!-- The architecture axis: the obligations no business scenario was ever going
|
|
201
|
+
to mention — the outbox, the retries, the timeouts, the alerts.
|
|
202
|
+
|
|
203
|
+
\`Covers:\` is to an arch requirement what \`Operations:\` is to a business
|
|
204
|
+
one — it names the MODEL OBJECTS the scenarios below exercise, so coverage
|
|
205
|
+
is derived instead of trusted. Three forms, comma-separated:
|
|
206
|
+
|
|
207
|
+
Covers: ${service} a C4 element — its id, or the service a
|
|
208
|
+
bound/titled element stands for
|
|
209
|
+
Covers: consumer -> ${service} an edge, each side resolved the same way
|
|
210
|
+
Covers: alert:<id>, sli:<id> a signal declared in health.yaml
|
|
211
|
+
|
|
212
|
+
Every tagged element and edge in delta.likec4 wants one (\`c4.uncovered\`).
|
|
213
|
+
Delete this file if ${featureId} adds no architectural obligation.
|
|
214
|
+
|
|
215
|
+
Copy the block below out of this comment and unindent it:
|
|
216
|
+
|
|
217
|
+
## ADDED Requirements
|
|
218
|
+
|
|
219
|
+
### Requirement: ${ARCH_REQUIREMENT_SENTINEL}
|
|
220
|
+
Requirement-ID: ${featureId}.${service}.arch
|
|
221
|
+
|
|
222
|
+
The service SHALL ${ARCH_SHALL_SENTINEL}.
|
|
223
|
+
|
|
224
|
+
Covers: ${service}
|
|
225
|
+
|
|
226
|
+
#### Scenario: ${SCENARIO_SENTINEL}
|
|
227
|
+
- **Given** ${GIVEN_SENTINEL}
|
|
228
|
+
- **When** ${WHEN_SENTINEL}
|
|
229
|
+
- **Then** ${THEN_SENTINEL}
|
|
230
|
+
-->
|
|
231
|
+
`;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* The BUSINESS axis, scaffolded by `--capability`: a delta against the living
|
|
235
|
+
* `capabilities/<id>/spec.md`.
|
|
236
|
+
*
|
|
237
|
+
* Same idiom as the two spec templates above and for the same reason — the
|
|
238
|
+
* example lives INSIDE an HTML comment, indented past the line-anchored
|
|
239
|
+
* `## ADDED Requirements` / `### Requirement:` patterns core/document/parse.ts
|
|
240
|
+
* matches on — so the scaffold declares nothing until a person copies the block
|
|
241
|
+
* out. That is what lets a freshly scaffolded feature validate clean while
|
|
242
|
+
* still being refused by `loam archive` the moment the block is copied out and
|
|
243
|
+
* left unedited (`scaffold.placeholder`, which reads
|
|
244
|
+
* `features/<FEAT>/capabilities/` as well as `features/<FEAT>/specs/`).
|
|
245
|
+
*
|
|
246
|
+
* WHAT THE COMMENT TEACHES IS THE ALTITUDE, because that is the mistake this
|
|
247
|
+
* document invites. A capability requirement is a promise a customer could
|
|
248
|
+
* check; the four service-scoped lines (`Operations:`, `Covers:`, `Publishes:`,
|
|
249
|
+
* `Consumes:`) and the axis's own two (`Capability:`, `Realizes:`) are all
|
|
250
|
+
* ERRORS here — `capability.requirement-service-scoped` and
|
|
251
|
+
* `capability.requirement-inert-join` — and every one of them parses, so an
|
|
252
|
+
* author who is not told writes one and finds out at archive. `Requirement-ID:`
|
|
253
|
+
* is spelled as mandatory for the same reason: a capability document outlives
|
|
254
|
+
* every service that realizes it, so identity by heading is refused
|
|
255
|
+
* (`capability.requirement-unidentified`).
|
|
256
|
+
*
|
|
257
|
+
* No nested HTML comments, exactly as `specTemplate` states: an inner `-->`
|
|
258
|
+
* would end the outer comment early and re-expose the example to the parser.
|
|
259
|
+
*/
|
|
260
|
+
export function capabilityDeltaTemplate(featureId, capability) {
|
|
261
|
+
return `# ${capability} — capability delta for ${featureId}
|
|
262
|
+
|
|
263
|
+
<!-- The business axis. This is a DELTA against the living
|
|
264
|
+
capabilities/${capability}/spec.md, not the document itself: sections are
|
|
265
|
+
ADDED / MODIFIED / REMOVED, delete the ones you do not need, and a MODIFIED
|
|
266
|
+
requirement carries its full new text rather than a diff. If the fleet has
|
|
267
|
+
no capabilities/${capability}/spec.md yet, this feature's archive creates it.
|
|
268
|
+
|
|
269
|
+
WHAT BELONGS HERE is a promise somebody outside the fleet could check —
|
|
270
|
+
'a refund reaches the customer's card within five days'. What does NOT is
|
|
271
|
+
the mechanism: \`Operations:\`, \`Covers:\`, \`Publishes:\` and \`Consumes:\`
|
|
272
|
+
all resolve against ONE service's own contract or model, so a requirement
|
|
273
|
+
carrying one is a service requirement filed at the wrong altitude and
|
|
274
|
+
\`loam validate\` says so. \`Capability:\` and \`Realizes:\` are refused here
|
|
275
|
+
too — they are the joins written on the SERVICE requirement that keeps this
|
|
276
|
+
promise, one directory over in specs/<svc>/spec.md, as
|
|
277
|
+
\`Realizes: ${capability}#<Requirement-ID>\`.
|
|
278
|
+
|
|
279
|
+
\`Requirement-ID:\` is mandatory on every requirement below. A capability
|
|
280
|
+
document outlives the services that realize it, so its requirements are
|
|
281
|
+
addressed by a stable id rather than by their heading — rewording a heading
|
|
282
|
+
would otherwise be a removal and an addition, and every \`Realizes:\` line
|
|
283
|
+
pointed at it would break in silence.
|
|
284
|
+
|
|
285
|
+
A MODIFIED or REMOVED requirement also needs a \`Based-On:\` pin quoting the
|
|
286
|
+
living text it rewrites, so two features touching one capability collide
|
|
287
|
+
loudly instead of overwriting each other. Run \`loam rebase ${featureId}\`
|
|
288
|
+
and the pins are written for you.
|
|
289
|
+
|
|
290
|
+
Copy the block below out of this comment, unindent it, and replace the TODO
|
|
291
|
+
and every <angle-bracket> fill-in — \`loam archive\` refuses the scaffold's
|
|
292
|
+
own wording (\`scaffold.placeholder\`):
|
|
293
|
+
|
|
294
|
+
## ADDED Requirements
|
|
295
|
+
|
|
296
|
+
### Requirement: ${CAPABILITY_REQUIREMENT_SENTINEL}
|
|
297
|
+
Requirement-ID: ${idHint(capability)}
|
|
298
|
+
|
|
299
|
+
The fleet SHALL ${CAPABILITY_SHALL_SENTINEL}.
|
|
300
|
+
|
|
301
|
+
#### Scenario: ${SCENARIO_SENTINEL}
|
|
302
|
+
- **Given** ${GIVEN_SENTINEL}
|
|
303
|
+
- **When** ${WHEN_SENTINEL}
|
|
304
|
+
- **Then** ${THEN_SENTINEL}
|
|
305
|
+
-->
|
|
306
|
+
`;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* A plausible `Requirement-ID:` for a capability, offered as a shape rather than
|
|
310
|
+
* a name: `payments/refunds` -> `PAYMENTS-REFUNDS-1`.
|
|
311
|
+
*
|
|
312
|
+
* Deliberately NOT the feature id. A service requirement's scaffolded id is
|
|
313
|
+
* `<FEAT>.<svc>.requirement` because that requirement is created by, and lives
|
|
314
|
+
* and dies with, one feature; a capability requirement outlives every feature
|
|
315
|
+
* that touches it, so an id carrying the id of the feature that happened to
|
|
316
|
+
* introduce it is exactly the identity-by-accident this axis refuses. The
|
|
317
|
+
* suffix is `-1` and not a count of anything: it is a placeholder digit inside
|
|
318
|
+
* a placeholder id, and the author is being asked to name the promise.
|
|
319
|
+
*
|
|
320
|
+
* CHECKED AGAINST THE REAL GRAMMAR, never assumed to satisfy it. A capability id
|
|
321
|
+
* may begin with a digit (`3ds`, `2fa`, `1099-filing` — `dirNameHazard` allows an
|
|
322
|
+
* alphanumeric head) while `REQUIREMENT_ID_RE` demands a LETTER, so the obvious
|
|
323
|
+
* slug hands the author `3DS-1` and `loam validate` then refuses it with
|
|
324
|
+
* `delta.requirement-id-invalid` — the author refused for using the shape the
|
|
325
|
+
* scaffold offered. Length is the same class of failure at 128 characters. The
|
|
326
|
+
* fallback prefixes rather than truncates, because a truncated id is a plausible
|
|
327
|
+
* name that means something else.
|
|
328
|
+
*/
|
|
329
|
+
function idHint(capability) {
|
|
330
|
+
const slug = capability.toUpperCase().replace(/[^A-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
331
|
+
const candidate = `${slug}-1`;
|
|
332
|
+
if (REQUIREMENT_ID_RE.test(candidate))
|
|
333
|
+
return candidate;
|
|
334
|
+
const prefixed = `CAP-${candidate}`;
|
|
335
|
+
return REQUIREMENT_ID_RE.test(prefixed) ? prefixed : "CAP-1";
|
|
336
|
+
}
|
|
337
|
+
export function openapiTemplate(service) {
|
|
338
|
+
return `openapi: 3.1.0
|
|
339
|
+
info:
|
|
340
|
+
title: ${service}
|
|
341
|
+
version: "0.1"
|
|
342
|
+
|
|
343
|
+
# The operations this feature adds. \`operationId\` is the token the C4 edge
|
|
344
|
+
# (metadata { op }) and the requirement (Operations:) both point at — keep the
|
|
345
|
+
# three spellings identical or \`loam validate\` will say so.
|
|
346
|
+
paths: {}
|
|
347
|
+
# /splits:
|
|
348
|
+
# post:
|
|
349
|
+
# operationId: createSplit
|
|
350
|
+
# responses:
|
|
351
|
+
# "201":
|
|
352
|
+
# description: Created
|
|
353
|
+
`;
|
|
354
|
+
}
|
|
355
|
+
//# sourceMappingURL=templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../../src/commands/new/templates.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The six files `loam new` scaffolds, and what each one says.\n *\n * They are here rather than inline because every one of them is a document a\n * person edits next, and the wording IS the instruction: the delta's comments\n * teach the grammar `core/document/parse.ts` matches on, and the intent's\n * frontmatter is serialized rather than interpolated because a title is free\n * text somebody types — `Checkout: split payments` reopens the mapping and the\n * file stops parsing.\n */\nimport { stringify as stringifyYaml } from \"yaml\";\nimport { REQUIREMENT_ID_RE } from \"../../core/document/spec.js\";\nimport {\n ARCH_REQUIREMENT_SENTINEL,\n ARCH_SHALL_SENTINEL,\n CAPABILITY_REQUIREMENT_SENTINEL,\n CAPABILITY_SHALL_SENTINEL,\n REQUIREMENT_SENTINEL,\n SCENARIO_SENTINEL,\n SERVICE_DESCRIPTION_SENTINEL,\n GIVEN_SENTINEL,\n SHALL_SENTINEL,\n THEN_SENTINEL,\n WHEN_SENTINEL,\n} from \"../../core/coherence/authoring/sentinels.js\";\n\nfunction identifier(name: string, taken: Set<string>): string {\n const parts = name.split(/[^A-Za-z0-9]+/).filter((p) => p.length > 0);\n const head = (parts[0] ?? \"svc\").replace(/^\\d+/, \"\") || \"svc\";\n const base =\n head[0]!.toLowerCase() +\n head.slice(1) +\n parts.slice(1).map((p) => p[0]!.toUpperCase() + p.slice(1)).join(\"\");\n let id = base;\n for (let n = 2; taken.has(id); n += 1) id = `${base}${n}`;\n taken.add(id);\n return id;\n}\n\n/** `FEAT-101` -> `feat_101`: LikeC4 view names take no dashes. */\nfunction viewName(featureId: string): string {\n return featureId.toLowerCase().replace(/[^a-z0-9]+/g, \"_\");\n}\n\n/* ------------------------------------------------------------------ */\n/* Templates */\n/* ------------------------------------------------------------------ */\n\nexport function intentTemplate(featureId: string, title: string | undefined): string {\n const heading = title ?? featureId;\n // The frontmatter is SERIALIZED, never interpolated. A title is free text a\n // person types — `Checkout: split payments` used to be pasted straight after\n // `title: `, where the colon reopens the mapping and the file stops parsing;\n // `Orders #42 rework` lost everything from the `#` as a YAML comment, so the\n // feature quietly answered to a title nobody wrote. The yaml serializer is the\n // only thing that knows every case, and it is already how migrate-openspec\n // writes the same three keys.\n //\n // `owner` is deliberately NOT a key here. Scaffolding `owner:` writes an\n // explicit null, which is a claim (\"this feature has no owner\") rather than\n // the truth (\"nobody has said yet\") — and every reader then has to tell an\n // absent key from a null one. The prompt survives as a comment.\n const frontmatter = stringifyYaml(\n { feature: featureId, ...(title === undefined ? {} : { title }), status: \"proposed\" },\n { lineWidth: 0 },\n ).trimEnd();\n return `---\n${frontmatter}\n# owner: <the team or person who answers for this>\n---\n\n# ${heading}\n\n## Why\n\n<!-- The problem in business terms. This is what a reviewer reads first, and what\n the requirement deltas below have to be justified by. -->\n\n## Scope\n\n<!-- Which services this touches — and, more usefully, which it deliberately does not. -->\n`;\n}\n\nexport function deltaTemplate(featureId: string, touched: string[], created: string[]): string {\n const taken = new Set<string>();\n const touchedIds = touched.map((s) => [identifier(s, taken), s] as const);\n const createdIds = created.map((s) => [identifier(s, taken), s] as const);\n\n const lines: string[] = [];\n if (touchedIds.length > 0) {\n // Context elements ship COMMENTED OUT, exactly like the example edge below.\n // Declared-and-untagged is the one shape `delta.nothing-tagged` exists to\n // refuse (an author who forgot the tags), so writing these live made every\n // `loam new --touches X` fail its own validator on the very first run —\n // which teaches people that the validator is noise. Uncomment a line when an\n // edge below actually needs the identifier.\n lines.push(\" // Services this feature touches, as context for the diagram. Uncomment the\");\n lines.push(\" // ones an edge below names, and reuse the identifiers from\");\n lines.push(\" // architecture/landscape.likec4 so the merge lines up. They stay UNTAGGED:\");\n lines.push(` // only #${featureId} is the change, and everything else here is context.`);\n for (const [id, name] of touchedIds) lines.push(` // ${id} = softwareSystem '${name}'`);\n lines.push(\"\");\n }\n if (createdIds.length > 0) {\n lines.push(\" // Services this feature introduces. The tag is what `loam archive` folds\");\n lines.push(\" // into the living landscape.\");\n for (const [id, name] of createdIds) {\n lines.push(` ${id} = softwareSystem '${name}' {`);\n lines.push(` #${featureId}`);\n // Interpolated from sentinels.ts, never spelled here: the placeholder\n // gate refuses this exact string at archive, and a second spelling is\n // how a template rewording silently retires the check that guards it.\n lines.push(` description '${SERVICE_DESCRIPTION_SENTINEL}'`);\n lines.push(\" }\");\n }\n lines.push(\"\");\n }\n\n // The example edge uses identifiers already declared above when there are two\n // to join; otherwise placeholders, so it never invents a service.\n const from = touchedIds[0]?.[0] ?? createdIds[0]?.[0] ?? \"consumer\";\n const to = createdIds[0]?.[0] ?? touchedIds[1]?.[0] ?? \"provider\";\n lines.push(\" // New calls. `metadata { op }` is the spine: it names the OpenAPI operationId\");\n lines.push(\" // the call uses, and `loam validate` checks it against the target's contract.\");\n lines.push(\" // Uncomment and adjust (both endpoints have to be declared above):\");\n lines.push(\" //\");\n lines.push(` // ${from} -> ${to} 'Calls createSplit' {`);\n lines.push(` // #${featureId}`);\n lines.push(\" // metadata { op 'createSplit' }\");\n lines.push(\" // }\");\n\n return `// ${featureId} — architecture delta.\n//\n// Everything tagged #${featureId} is exactly what \\`loam archive\\` folds into\n// architecture/landscape.likec4. Everything else here is context for the diagram.\n//\n// If ${featureId} changes no architecture, DELETE this file: a requirements-only\n// feature is complete without it, and an empty \\`model {}\\` is just as legal.\n\nspecification {\n element softwareSystem\n tag ${featureId}\n}\n\nmodel {\n${lines.join(\"\\n\")}\n}\n\nviews {\n view ${viewName(featureId)} {\n include *\n }\n}\n`;\n}\n\n/**\n * The business axis, scaffolded with its example INSIDE the comment — the same\n * mechanism as `archSpecTemplate` below, for the same reason: this template\n * used to ship a live `### Requirement: TODO — name the behaviour` that\n * parsed as a real requirement, validated clean, and archived a literal TODO\n * into the living spec at exit 0. The indented headings sit past the\n * line-anchored patterns core/document/parse.ts matches on, so the scaffold\n * declares nothing until a person copies the block out and writes over the\n * fill-ins — and `scaffold.placeholder` gates the archive if the fill-ins\n * survive the copy.\n *\n * No nested HTML comments: the `Operations:` guidance that used to be its own\n * comment lives in this one's prose, because an inner `-->` would end the\n * outer comment early and re-expose the example to the parser.\n */\nexport function specTemplate(featureId: string, service: string): string {\n return `# ${service} — requirement delta for ${featureId}\n\n<!-- Sections: ADDED / MODIFIED / REMOVED. Delete the ones you do not need.\n A MODIFIED requirement carries its full new text, not a diff.\n Every requirement needs at least one scenario — \\`loam validate\\` gates on it.\n\n An \\`Operations:\\` body line names the operationIds the requirement\n governs. \\`loam validate\\` checks each one against the service's OpenAPI,\n and \\`loam archive\\` refuses to merge a requirement that governs an\n operation nobody defines — add the line once the contract exists.\n\n A \\`Publishes:\\` / \\`Consumes:\\` body line does the same for event messages:\n it names what the requirement puts on or takes off the bus, checked\n against the service's AsyncAPI contract. If ${featureId} changes that\n contract, hand-create specs/${service}/asyncapi.yaml — a complete\n AsyncAPI 3.0 document, messages declared under \\`components.messages\\`\n (loam scaffolds none: an event contract is genuinely optional) — and run\n \\`loam rebase ${featureId}\\` so every restated slot pins as a quote.\n\n Copy the block below out of this comment, unindent it, and replace the\n TODOs and every <angle-bracket> fill-in — \\`loam archive\\` refuses the\n scaffold's own wording (\\`scaffold.placeholder\\`):\n\n ## ADDED Requirements\n\n ### Requirement: ${REQUIREMENT_SENTINEL}\n Requirement-ID: ${featureId}.${service}.requirement\n\n The service SHALL ${SHALL_SENTINEL}.\n\n #### Scenario: ${SCENARIO_SENTINEL}\n - **Given** ${GIVEN_SENTINEL}\n - **When** ${WHEN_SENTINEL}\n - **Then** ${THEN_SENTINEL}\n-->\n`;\n}\n\n/**\n * The architecture axis, scaffolded EMPTY on purpose.\n *\n * Same grammar as spec.md, so the template body has to be unparseable as\n * requirements or the scaffold would ship a requirement nobody wrote — the\n * headings are therefore indented inside an HTML comment, which puts them past\n * the line-anchored `## ADDED Requirements` / `### Requirement:` patterns\n * core/document/parse.ts matches on. Copy the block out of the comment and unindent it.\n */\nexport function archSpecTemplate(featureId: string, service: string): string {\n return `# ${service} — architecture requirement delta for ${featureId}\n\n<!-- The architecture axis: the obligations no business scenario was ever going\n to mention — the outbox, the retries, the timeouts, the alerts.\n\n \\`Covers:\\` is to an arch requirement what \\`Operations:\\` is to a business\n one — it names the MODEL OBJECTS the scenarios below exercise, so coverage\n is derived instead of trusted. Three forms, comma-separated:\n\n Covers: ${service} a C4 element — its id, or the service a\n bound/titled element stands for\n Covers: consumer -> ${service} an edge, each side resolved the same way\n Covers: alert:<id>, sli:<id> a signal declared in health.yaml\n\n Every tagged element and edge in delta.likec4 wants one (\\`c4.uncovered\\`).\n Delete this file if ${featureId} adds no architectural obligation.\n\n Copy the block below out of this comment and unindent it:\n\n ## ADDED Requirements\n\n ### Requirement: ${ARCH_REQUIREMENT_SENTINEL}\n Requirement-ID: ${featureId}.${service}.arch\n\n The service SHALL ${ARCH_SHALL_SENTINEL}.\n\n Covers: ${service}\n\n #### Scenario: ${SCENARIO_SENTINEL}\n - **Given** ${GIVEN_SENTINEL}\n - **When** ${WHEN_SENTINEL}\n - **Then** ${THEN_SENTINEL}\n-->\n`;\n}\n\n/**\n * The BUSINESS axis, scaffolded by `--capability`: a delta against the living\n * `capabilities/<id>/spec.md`.\n *\n * Same idiom as the two spec templates above and for the same reason — the\n * example lives INSIDE an HTML comment, indented past the line-anchored\n * `## ADDED Requirements` / `### Requirement:` patterns core/document/parse.ts\n * matches on — so the scaffold declares nothing until a person copies the block\n * out. That is what lets a freshly scaffolded feature validate clean while\n * still being refused by `loam archive` the moment the block is copied out and\n * left unedited (`scaffold.placeholder`, which reads\n * `features/<FEAT>/capabilities/` as well as `features/<FEAT>/specs/`).\n *\n * WHAT THE COMMENT TEACHES IS THE ALTITUDE, because that is the mistake this\n * document invites. A capability requirement is a promise a customer could\n * check; the four service-scoped lines (`Operations:`, `Covers:`, `Publishes:`,\n * `Consumes:`) and the axis's own two (`Capability:`, `Realizes:`) are all\n * ERRORS here — `capability.requirement-service-scoped` and\n * `capability.requirement-inert-join` — and every one of them parses, so an\n * author who is not told writes one and finds out at archive. `Requirement-ID:`\n * is spelled as mandatory for the same reason: a capability document outlives\n * every service that realizes it, so identity by heading is refused\n * (`capability.requirement-unidentified`).\n *\n * No nested HTML comments, exactly as `specTemplate` states: an inner `-->`\n * would end the outer comment early and re-expose the example to the parser.\n */\nexport function capabilityDeltaTemplate(featureId: string, capability: string): string {\n return `# ${capability} — capability delta for ${featureId}\n\n<!-- The business axis. This is a DELTA against the living\n capabilities/${capability}/spec.md, not the document itself: sections are\n ADDED / MODIFIED / REMOVED, delete the ones you do not need, and a MODIFIED\n requirement carries its full new text rather than a diff. If the fleet has\n no capabilities/${capability}/spec.md yet, this feature's archive creates it.\n\n WHAT BELONGS HERE is a promise somebody outside the fleet could check —\n 'a refund reaches the customer's card within five days'. What does NOT is\n the mechanism: \\`Operations:\\`, \\`Covers:\\`, \\`Publishes:\\` and \\`Consumes:\\`\n all resolve against ONE service's own contract or model, so a requirement\n carrying one is a service requirement filed at the wrong altitude and\n \\`loam validate\\` says so. \\`Capability:\\` and \\`Realizes:\\` are refused here\n too — they are the joins written on the SERVICE requirement that keeps this\n promise, one directory over in specs/<svc>/spec.md, as\n \\`Realizes: ${capability}#<Requirement-ID>\\`.\n\n \\`Requirement-ID:\\` is mandatory on every requirement below. A capability\n document outlives the services that realize it, so its requirements are\n addressed by a stable id rather than by their heading — rewording a heading\n would otherwise be a removal and an addition, and every \\`Realizes:\\` line\n pointed at it would break in silence.\n\n A MODIFIED or REMOVED requirement also needs a \\`Based-On:\\` pin quoting the\n living text it rewrites, so two features touching one capability collide\n loudly instead of overwriting each other. Run \\`loam rebase ${featureId}\\`\n and the pins are written for you.\n\n Copy the block below out of this comment, unindent it, and replace the TODO\n and every <angle-bracket> fill-in — \\`loam archive\\` refuses the scaffold's\n own wording (\\`scaffold.placeholder\\`):\n\n ## ADDED Requirements\n\n ### Requirement: ${CAPABILITY_REQUIREMENT_SENTINEL}\n Requirement-ID: ${idHint(capability)}\n\n The fleet SHALL ${CAPABILITY_SHALL_SENTINEL}.\n\n #### Scenario: ${SCENARIO_SENTINEL}\n - **Given** ${GIVEN_SENTINEL}\n - **When** ${WHEN_SENTINEL}\n - **Then** ${THEN_SENTINEL}\n-->\n`;\n}\n\n/**\n * A plausible `Requirement-ID:` for a capability, offered as a shape rather than\n * a name: `payments/refunds` -> `PAYMENTS-REFUNDS-1`.\n *\n * Deliberately NOT the feature id. A service requirement's scaffolded id is\n * `<FEAT>.<svc>.requirement` because that requirement is created by, and lives\n * and dies with, one feature; a capability requirement outlives every feature\n * that touches it, so an id carrying the id of the feature that happened to\n * introduce it is exactly the identity-by-accident this axis refuses. The\n * suffix is `-1` and not a count of anything: it is a placeholder digit inside\n * a placeholder id, and the author is being asked to name the promise.\n *\n * CHECKED AGAINST THE REAL GRAMMAR, never assumed to satisfy it. A capability id\n * may begin with a digit (`3ds`, `2fa`, `1099-filing` — `dirNameHazard` allows an\n * alphanumeric head) while `REQUIREMENT_ID_RE` demands a LETTER, so the obvious\n * slug hands the author `3DS-1` and `loam validate` then refuses it with\n * `delta.requirement-id-invalid` — the author refused for using the shape the\n * scaffold offered. Length is the same class of failure at 128 characters. The\n * fallback prefixes rather than truncates, because a truncated id is a plausible\n * name that means something else.\n */\nfunction idHint(capability: string): string {\n const slug = capability.toUpperCase().replace(/[^A-Z0-9]+/g, \"-\").replace(/^-+|-+$/g, \"\");\n const candidate = `${slug}-1`;\n if (REQUIREMENT_ID_RE.test(candidate)) return candidate;\n const prefixed = `CAP-${candidate}`;\n return REQUIREMENT_ID_RE.test(prefixed) ? prefixed : \"CAP-1\";\n}\n\nexport function openapiTemplate(service: string): string {\n return `openapi: 3.1.0\ninfo:\n title: ${service}\n version: \"0.1\"\n\n# The operations this feature adds. \\`operationId\\` is the token the C4 edge\n# (metadata { op }) and the requirement (Operations:) both point at — keep the\n# three spellings identical or \\`loam validate\\` will say so.\npaths: {}\n# /splits:\n# post:\n# operationId: createSplit\n# responses:\n# \"201\":\n# description: Created\n`;\n}\n"]}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { statSync } from "node:fs";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { loadConfig } from "../core/envelope/config.js";
|
|
5
|
+
import { emitJson, fail, reportNoConfig } from "../core/envelope/json.js";
|
|
6
|
+
import { DocsRepoUnavailableError } from "../core/repo/state.js";
|
|
7
|
+
import { discoverMembers } from "../core/workspace/discover.js";
|
|
8
|
+
import { renderWorkspace } from "../core/workspace/render.js";
|
|
9
|
+
import { docsRepoReady, reportDocsRepoError } from "./policy/gate.js";
|
|
10
|
+
/**
|
|
11
|
+
* A fixed default name, not `<basename(docsDir)>.code-workspace`: predictable
|
|
12
|
+
* beats collision-proof for a file that is regenerable on demand, and `--out`
|
|
13
|
+
* is the lever for anyone holding two fleets' workspaces in one directory.
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_OUT = "loam.code-workspace";
|
|
16
|
+
/**
|
|
17
|
+
* Is there a directory at `path`? A probe that cannot answer says "no": for a
|
|
18
|
+
* value the user typed, "cannot be read" earns the same `invalid-option`
|
|
19
|
+
* refusal as "is not there" — never an `internal` escape. throwIfNoEntry folds
|
|
20
|
+
* the absent case into the same answer with no window between probe and use;
|
|
21
|
+
* the catch covers the errnos statSync can still raise (EACCES, ENOTDIR — a
|
|
22
|
+
* path routed through a file).
|
|
23
|
+
*/
|
|
24
|
+
function directoryAt(path) {
|
|
25
|
+
try {
|
|
26
|
+
return statSync(path, { throwIfNoEntry: false })?.isDirectory() === true;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** The write-path errnos that mean the OUT PATH is wrong, not loam. */
|
|
33
|
+
const OUT_PATH_ERRNOS = new Set(["EISDIR", "EACCES", "EPERM", "ENOTDIR", "EINVAL"]);
|
|
34
|
+
export function registerOpen(program) {
|
|
35
|
+
program
|
|
36
|
+
.command("open")
|
|
37
|
+
.description("Write a .code-workspace joining the docs repo and every bound service checkout")
|
|
38
|
+
.option("--root <dir>", "scan this directory for bound checkouts instead of the defaults; repeatable", (value, prev = []) => [...prev, value])
|
|
39
|
+
.option("--out <file>", `workspace file to write (default ${DEFAULT_OUT} in the current directory)`)
|
|
40
|
+
.option("--force", "overwrite the workspace file if it already exists")
|
|
41
|
+
.option("--json", "emit the machine contract instead of the human view")
|
|
42
|
+
.action(async (opts) => {
|
|
43
|
+
const json = opts.json === true;
|
|
44
|
+
const loaded = await loadConfig();
|
|
45
|
+
if (loaded.kind !== "loaded") {
|
|
46
|
+
reportNoConfig(json, loaded);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const config = loaded.config;
|
|
50
|
+
const { docsDir } = config;
|
|
51
|
+
// "docs", not "services": this command enumerates checkouts on the LOCAL
|
|
52
|
+
// filesystem, not services/ in the docs repo, and a docs repo nobody has
|
|
53
|
+
// run `loam adopt` against is still worth opening an editor on.
|
|
54
|
+
if (!docsRepoReady(json, docsDir, "docs"))
|
|
55
|
+
return;
|
|
56
|
+
// Both typed paths are validated before any discovery runs: a --root or
|
|
57
|
+
// --out typo is a fact about the INVOCATION, not the fleet, and it must
|
|
58
|
+
// refuse before the scan — not after verdicts were computed over
|
|
59
|
+
// directories nobody meant, and not from the write at the very end.
|
|
60
|
+
const roots = (opts.root ?? []).map((dir) => resolve(dir));
|
|
61
|
+
for (const dir of roots) {
|
|
62
|
+
if (!directoryAt(dir)) {
|
|
63
|
+
fail(json, "invalid-option", `--root does not name a readable directory: ${dir}`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const out = resolve(opts.out ?? DEFAULT_OUT);
|
|
68
|
+
const parent = dirname(out);
|
|
69
|
+
if (!directoryAt(parent)) {
|
|
70
|
+
fail(json, "invalid-option", `--out points into a directory that does not exist: ${parent}`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
let discovery;
|
|
74
|
+
try {
|
|
75
|
+
discovery = await discoverMembers({
|
|
76
|
+
docsDir,
|
|
77
|
+
// parseConfig always derives `root`; the fallback only documents
|
|
78
|
+
// which absence the optional type would otherwise leave a guess.
|
|
79
|
+
configRoot: config.root ?? process.cwd(),
|
|
80
|
+
...(config.service === undefined ? {} : { service: config.service }),
|
|
81
|
+
...(roots.length === 0 ? {} : { roots }),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
// The gate above passed, so this is the repo vanishing mid-run —
|
|
86
|
+
// the same breach the enumeration reports, spelled the same way.
|
|
87
|
+
if (!(err instanceof DocsRepoUnavailableError))
|
|
88
|
+
throw err;
|
|
89
|
+
reportDocsRepoError(json, err);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// An explicit --root that cannot be LISTED refuses: the caller named the
|
|
93
|
+
// directory, so "I could not look" must never come back as an empty
|
|
94
|
+
// scan. The default roots keep degrading to empty — a CI sandbox that
|
|
95
|
+
// denies the docs repo's parent is survivable, and both views still
|
|
96
|
+
// report the degrade below.
|
|
97
|
+
if (roots.length > 0 && discovery.unreadableRoots.length > 0) {
|
|
98
|
+
const listing = discovery.unreadableRoots
|
|
99
|
+
.map((entry) => `${entry.root} (${entry.problem})`)
|
|
100
|
+
.join("; ");
|
|
101
|
+
fail(json, "invalid-option", `--root cannot be scanned: ${listing}`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (discovery.duplicates.length > 0) {
|
|
105
|
+
const claims = discovery.duplicates.map((d) => `'${d.service}' is declared by ${d.paths.join(" and ")}`);
|
|
106
|
+
fail(json, "binding-duplicate", `Two checkouts declare the same service for this docs repo: ${claims.join("; ")}. ` +
|
|
107
|
+
"loam will not guess which checkout speaks for the service — narrow the scan " +
|
|
108
|
+
"with --root, or fix the stray loam.json, then re-run.");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
// Only reachable from the docs repo itself — a service-repo invocation
|
|
112
|
+
// always contributes its own checkout as a member. Skipped candidates
|
|
113
|
+
// ride the message: a checkout whose binding would not parse is the
|
|
114
|
+
// likeliest reason the scan came up empty, and the real fix is that
|
|
115
|
+
// file, not --root.
|
|
116
|
+
if (!discovery.members.some((member) => member.via !== "docs-repo")) {
|
|
117
|
+
const unread = discovery.skipped.length === 0
|
|
118
|
+
? ""
|
|
119
|
+
: ` ${discovery.skipped.length} candidate binding(s) could not be read and did not` +
|
|
120
|
+
` count: ${discovery.skipped.map((s) => `${s.path} (${s.problem})`).join("; ")}.`;
|
|
121
|
+
fail(json, "no-members", `No service checkout bound to ${docsDir} was found under ` +
|
|
122
|
+
`${discovery.scannedRoots.join(", ")} — the workspace would hold only the docs ` +
|
|
123
|
+
"repo. Clone a bound service repo beside it, run `loam open` from a service " +
|
|
124
|
+
`repo, or pass --root naming the directory the checkouts live in.${unread}`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const rendered = renderWorkspace(discovery.members, out);
|
|
128
|
+
try {
|
|
129
|
+
// "wx" makes probe-and-write one operation, so a concurrent writer
|
|
130
|
+
// cannot land between an existsSync and the write. --force is honest
|
|
131
|
+
// here where init's generated files needed skip-forever: a workspace
|
|
132
|
+
// file is wholly derived, so overwriting it loses nothing authored.
|
|
133
|
+
await writeFile(out, rendered.text, {
|
|
134
|
+
encoding: "utf8",
|
|
135
|
+
flag: opts.force === true ? "w" : "wx",
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
const e = err;
|
|
140
|
+
if (e.code === "EEXIST") {
|
|
141
|
+
fail(json, "already-exists", `${out} already exists. Pass --out naming another file, or --force to overwrite it.`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
// The path-shaped failures: --out names a directory (EISDIR — the
|
|
145
|
+
// exact target the EEXIST remedy above just told the caller to
|
|
146
|
+
// --force), or a place this process may not write. Each is a fact
|
|
147
|
+
// about the out path, so it earns invalid-option, not internal.
|
|
148
|
+
if (typeof e.code === "string" && OUT_PATH_ERRNOS.has(e.code)) {
|
|
149
|
+
fail(json, "invalid-option", `The workspace file cannot be written at ${out}: ${e.message}. ` +
|
|
150
|
+
"Pass --out naming a writable file path.");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
if (json) {
|
|
156
|
+
emitJson({
|
|
157
|
+
command: "open",
|
|
158
|
+
docsDir,
|
|
159
|
+
out,
|
|
160
|
+
written: true,
|
|
161
|
+
members: rendered.folders.map((folder) => ({
|
|
162
|
+
path: folder.member.path,
|
|
163
|
+
// The exact spelling written into the workspace file, so a
|
|
164
|
+
// consumer never re-derives (and mis-derives) the relative form.
|
|
165
|
+
folder: folder.path,
|
|
166
|
+
name: folder.name,
|
|
167
|
+
service: folder.member.service,
|
|
168
|
+
via: folder.member.via,
|
|
169
|
+
...(folder.member.via === "scan" ? { root: folder.member.root } : {}),
|
|
170
|
+
})),
|
|
171
|
+
scannedRoots: discovery.scannedRoots,
|
|
172
|
+
unreadableRoots: discovery.unreadableRoots,
|
|
173
|
+
skipped: discovery.skipped,
|
|
174
|
+
});
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
printWorkspace(out, discovery, rendered.folders);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
function viaText(folder) {
|
|
181
|
+
const member = folder.member;
|
|
182
|
+
if (member.via === "docs-repo")
|
|
183
|
+
return "docs repo";
|
|
184
|
+
if (member.via === "current-repo")
|
|
185
|
+
return "this repo";
|
|
186
|
+
return `found under ${member.root}`;
|
|
187
|
+
}
|
|
188
|
+
function printWorkspace(out, discovery, folders) {
|
|
189
|
+
console.log(`workspace: ${out} (${folders.length} folders)`);
|
|
190
|
+
for (const folder of folders) {
|
|
191
|
+
console.log(` ${folder.name} ${folder.path} (${viaText(folder)})`);
|
|
192
|
+
}
|
|
193
|
+
// Reported beside the result, not folded into it: an unreadable binding or
|
|
194
|
+
// an unlistable default root proves nothing, but silence about either would
|
|
195
|
+
// read as "not bound" / "nothing there".
|
|
196
|
+
for (const entry of discovery.unreadableRoots) {
|
|
197
|
+
console.log(` ! could not scan ${entry.root}: ${entry.problem}`);
|
|
198
|
+
}
|
|
199
|
+
for (const entry of discovery.skipped) {
|
|
200
|
+
console.log(` ! skipped ${entry.path}: ${entry.problem}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=open.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open.js","sourceRoot":"","sources":["../../src/commands/open.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Command } from \"commander\";\nimport { statSync } from \"node:fs\";\nimport { writeFile } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\nimport { loadConfig } from \"../core/envelope/config.js\";\nimport { emitJson, fail, reportNoConfig } from \"../core/envelope/json.js\";\nimport { DocsRepoUnavailableError } from \"../core/repo/state.js\";\nimport { discoverMembers, type Discovery } from \"../core/workspace/discover.js\";\nimport { renderWorkspace, type RenderedFolder } from \"../core/workspace/render.js\";\nimport { docsRepoReady, reportDocsRepoError } from \"./policy/gate.js\";\n\ninterface OpenOptions {\n json?: boolean;\n /** --root, collected: each value REPLACES the default scan roots. */\n root?: string[];\n out?: string;\n force?: boolean;\n}\n\n/**\n * A fixed default name, not `<basename(docsDir)>.code-workspace`: predictable\n * beats collision-proof for a file that is regenerable on demand, and `--out`\n * is the lever for anyone holding two fleets' workspaces in one directory.\n */\nconst DEFAULT_OUT = \"loam.code-workspace\";\n\n/**\n * Is there a directory at `path`? A probe that cannot answer says \"no\": for a\n * value the user typed, \"cannot be read\" earns the same `invalid-option`\n * refusal as \"is not there\" — never an `internal` escape. throwIfNoEntry folds\n * the absent case into the same answer with no window between probe and use;\n * the catch covers the errnos statSync can still raise (EACCES, ENOTDIR — a\n * path routed through a file).\n */\nfunction directoryAt(path: string): boolean {\n try {\n return statSync(path, { throwIfNoEntry: false })?.isDirectory() === true;\n } catch {\n return false;\n }\n}\n\n/** The write-path errnos that mean the OUT PATH is wrong, not loam. */\nconst OUT_PATH_ERRNOS = new Set([\"EISDIR\", \"EACCES\", \"EPERM\", \"ENOTDIR\", \"EINVAL\"]);\n\nexport function registerOpen(program: Command): void {\n program\n .command(\"open\")\n .description(\"Write a .code-workspace joining the docs repo and every bound service checkout\")\n .option(\n \"--root <dir>\",\n \"scan this directory for bound checkouts instead of the defaults; repeatable\",\n (value: string, prev: string[] = []) => [...prev, value],\n )\n .option(\"--out <file>\", `workspace file to write (default ${DEFAULT_OUT} in the current directory)`)\n .option(\"--force\", \"overwrite the workspace file if it already exists\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (opts: OpenOptions) => {\n const json = opts.json === true;\n const loaded = await loadConfig();\n if (loaded.kind !== \"loaded\") {\n reportNoConfig(json, loaded);\n return;\n }\n const config = loaded.config;\n const { docsDir } = config;\n // \"docs\", not \"services\": this command enumerates checkouts on the LOCAL\n // filesystem, not services/ in the docs repo, and a docs repo nobody has\n // run `loam adopt` against is still worth opening an editor on.\n if (!docsRepoReady(json, docsDir, \"docs\")) return;\n\n // Both typed paths are validated before any discovery runs: a --root or\n // --out typo is a fact about the INVOCATION, not the fleet, and it must\n // refuse before the scan — not after verdicts were computed over\n // directories nobody meant, and not from the write at the very end.\n const roots = (opts.root ?? []).map((dir) => resolve(dir));\n for (const dir of roots) {\n if (!directoryAt(dir)) {\n fail(json, \"invalid-option\", `--root does not name a readable directory: ${dir}`);\n return;\n }\n }\n const out = resolve(opts.out ?? DEFAULT_OUT);\n const parent = dirname(out);\n if (!directoryAt(parent)) {\n fail(json, \"invalid-option\", `--out points into a directory that does not exist: ${parent}`);\n return;\n }\n\n let discovery: Discovery;\n try {\n discovery = await discoverMembers({\n docsDir,\n // parseConfig always derives `root`; the fallback only documents\n // which absence the optional type would otherwise leave a guess.\n configRoot: config.root ?? process.cwd(),\n ...(config.service === undefined ? {} : { service: config.service }),\n ...(roots.length === 0 ? {} : { roots }),\n });\n } catch (err) {\n // The gate above passed, so this is the repo vanishing mid-run —\n // the same breach the enumeration reports, spelled the same way.\n if (!(err instanceof DocsRepoUnavailableError)) throw err;\n reportDocsRepoError(json, err);\n return;\n }\n\n // An explicit --root that cannot be LISTED refuses: the caller named the\n // directory, so \"I could not look\" must never come back as an empty\n // scan. The default roots keep degrading to empty — a CI sandbox that\n // denies the docs repo's parent is survivable, and both views still\n // report the degrade below.\n if (roots.length > 0 && discovery.unreadableRoots.length > 0) {\n const listing = discovery.unreadableRoots\n .map((entry) => `${entry.root} (${entry.problem})`)\n .join(\"; \");\n fail(json, \"invalid-option\", `--root cannot be scanned: ${listing}`);\n return;\n }\n\n if (discovery.duplicates.length > 0) {\n const claims = discovery.duplicates.map(\n (d) => `'${d.service}' is declared by ${d.paths.join(\" and \")}`,\n );\n fail(\n json,\n \"binding-duplicate\",\n `Two checkouts declare the same service for this docs repo: ${claims.join(\"; \")}. ` +\n \"loam will not guess which checkout speaks for the service — narrow the scan \" +\n \"with --root, or fix the stray loam.json, then re-run.\",\n );\n return;\n }\n // Only reachable from the docs repo itself — a service-repo invocation\n // always contributes its own checkout as a member. Skipped candidates\n // ride the message: a checkout whose binding would not parse is the\n // likeliest reason the scan came up empty, and the real fix is that\n // file, not --root.\n if (!discovery.members.some((member) => member.via !== \"docs-repo\")) {\n const unread = discovery.skipped.length === 0\n ? \"\"\n : ` ${discovery.skipped.length} candidate binding(s) could not be read and did not` +\n ` count: ${discovery.skipped.map((s) => `${s.path} (${s.problem})`).join(\"; \")}.`;\n fail(\n json,\n \"no-members\",\n `No service checkout bound to ${docsDir} was found under ` +\n `${discovery.scannedRoots.join(\", \")} — the workspace would hold only the docs ` +\n \"repo. Clone a bound service repo beside it, run `loam open` from a service \" +\n `repo, or pass --root naming the directory the checkouts live in.${unread}`,\n );\n return;\n }\n\n const rendered = renderWorkspace(discovery.members, out);\n try {\n // \"wx\" makes probe-and-write one operation, so a concurrent writer\n // cannot land between an existsSync and the write. --force is honest\n // here where init's generated files needed skip-forever: a workspace\n // file is wholly derived, so overwriting it loses nothing authored.\n await writeFile(out, rendered.text, {\n encoding: \"utf8\",\n flag: opts.force === true ? \"w\" : \"wx\",\n });\n } catch (err) {\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"EEXIST\") {\n fail(\n json,\n \"already-exists\",\n `${out} already exists. Pass --out naming another file, or --force to overwrite it.`,\n );\n return;\n }\n // The path-shaped failures: --out names a directory (EISDIR — the\n // exact target the EEXIST remedy above just told the caller to\n // --force), or a place this process may not write. Each is a fact\n // about the out path, so it earns invalid-option, not internal.\n if (typeof e.code === \"string\" && OUT_PATH_ERRNOS.has(e.code)) {\n fail(\n json,\n \"invalid-option\",\n `The workspace file cannot be written at ${out}: ${e.message}. ` +\n \"Pass --out naming a writable file path.\",\n );\n return;\n }\n throw err;\n }\n\n if (json) {\n emitJson({\n command: \"open\",\n docsDir,\n out,\n written: true,\n members: rendered.folders.map((folder) => ({\n path: folder.member.path,\n // The exact spelling written into the workspace file, so a\n // consumer never re-derives (and mis-derives) the relative form.\n folder: folder.path,\n name: folder.name,\n service: folder.member.service,\n via: folder.member.via,\n ...(folder.member.via === \"scan\" ? { root: folder.member.root } : {}),\n })),\n scannedRoots: discovery.scannedRoots,\n unreadableRoots: discovery.unreadableRoots,\n skipped: discovery.skipped,\n });\n return;\n }\n printWorkspace(out, discovery, rendered.folders);\n });\n}\n\nfunction viaText(folder: RenderedFolder): string {\n const member = folder.member;\n if (member.via === \"docs-repo\") return \"docs repo\";\n if (member.via === \"current-repo\") return \"this repo\";\n return `found under ${member.root}`;\n}\n\nfunction printWorkspace(out: string, discovery: Discovery, folders: RenderedFolder[]): void {\n console.log(`workspace: ${out} (${folders.length} folders)`);\n for (const folder of folders) {\n console.log(` ${folder.name} ${folder.path} (${viaText(folder)})`);\n }\n // Reported beside the result, not folded into it: an unreadable binding or\n // an unlistable default root proves nothing, but silence about either would\n // read as \"not bound\" / \"nothing there\".\n for (const entry of discovery.unreadableRoots) {\n console.log(` ! could not scan ${entry.root}: ${entry.problem}`);\n }\n for (const entry of discovery.skipped) {\n console.log(` ! skipped ${entry.path}: ${entry.problem}`);\n }\n}\n"]}
|