@ybotok/loam 0.1.0-beta.2 → 0.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2570 -61
- package/COMPARISON.md +80 -63
- package/CONTRIBUTING.md +84 -0
- package/MIGRATING-from-OpenSpec.md +213 -54
- package/QUICKSTART.md +125 -0
- package/README.md +450 -150
- package/ROADMAP.md +494 -0
- package/SCHEMA.md +2362 -245
- package/SECURITY.md +21 -3
- package/WORKFLOW.md +701 -0
- package/dist/cli.js +113 -19
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +209 -0
- package/dist/commands/adopt/adopt.js.map +1 -0
- package/dist/commands/adopt/render.js +268 -0
- package/dist/commands/adopt/render.js.map +1 -0
- package/dist/commands/archive/archive.js +77 -0
- package/dist/commands/archive/archive.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +159 -0
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +317 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +161 -0
- package/dist/commands/archive/plan/landscape.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +111 -0
- package/dist/commands/archive/plan/refusal.js.map +1 -0
- package/dist/commands/archive/plan/specs.js +237 -0
- package/dist/commands/archive/plan/specs.js.map +1 -0
- package/dist/commands/archive/plan/state.js +27 -0
- package/dist/commands/archive/plan/state.js.map +1 -0
- package/dist/commands/archive/run.js +251 -0
- package/dist/commands/archive/run.js.map +1 -0
- package/dist/commands/context/context.js +99 -0
- package/dist/commands/context/context.js.map +1 -0
- package/dist/commands/context/print.js +283 -0
- package/dist/commands/context/print.js.map +1 -0
- package/dist/commands/delta/delta.js +279 -0
- package/dist/commands/delta/delta.js.map +1 -0
- package/dist/commands/delta/print.js +205 -0
- package/dist/commands/delta/print.js.map +1 -0
- package/dist/commands/dependencies.js +9 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +93 -0
- package/dist/commands/diff/diff.js.map +1 -0
- package/dist/commands/diff/print.js +38 -0
- package/dist/commands/diff/print.js.map +1 -0
- package/dist/commands/doctor.js +5 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +151 -0
- package/dist/commands/explain/explain.js.map +1 -0
- package/dist/commands/explore.js +29 -24
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +115 -0
- package/dist/commands/gate/gate.js.map +1 -0
- package/dist/commands/gate/print.js +54 -0
- package/dist/commands/gate/print.js.map +1 -0
- package/dist/commands/gherkin/commit.js +146 -0
- package/dist/commands/gherkin/commit.js.map +1 -0
- package/dist/commands/gherkin/gherkin.js +246 -0
- package/dist/commands/gherkin/gherkin.js.map +1 -0
- package/dist/commands/gherkin/reconcile.js +149 -0
- package/dist/commands/gherkin/reconcile.js.map +1 -0
- package/dist/commands/gherkin/render.js +77 -0
- package/dist/commands/gherkin/render.js.map +1 -0
- package/dist/commands/init/example.js +204 -0
- package/dist/commands/init/example.js.map +1 -0
- package/dist/commands/init/first-hour.js +28 -0
- package/dist/commands/init/first-hour.js.map +1 -0
- package/dist/commands/{init.js → init/init.js} +146 -71
- package/dist/commands/init/init.js.map +1 -0
- package/dist/commands/init/mcp.js +131 -0
- package/dist/commands/init/mcp.js.map +1 -0
- package/dist/commands/init/options.js +78 -0
- package/dist/commands/init/options.js.map +1 -0
- package/dist/commands/instructions.js +82 -14
- package/dist/commands/instructions.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +65 -0
- package/dist/commands/list/campaign/campaign.js.map +1 -0
- package/dist/commands/list/campaign/owners.js +65 -0
- package/dist/commands/list/campaign/owners.js.map +1 -0
- package/dist/commands/list/json.js +157 -0
- package/dist/commands/list/json.js.map +1 -0
- package/dist/commands/list/list.js +343 -0
- package/dist/commands/list/list.js.map +1 -0
- package/dist/commands/list/print.js +301 -0
- package/dist/commands/list/print.js.map +1 -0
- package/dist/commands/list/review.js +120 -0
- package/dist/commands/list/review.js.map +1 -0
- package/dist/commands/list/views.js +105 -0
- package/dist/commands/list/views.js.map +1 -0
- package/dist/commands/mcp/dispatch.js +183 -0
- package/dist/commands/mcp/dispatch.js.map +1 -0
- package/dist/commands/mcp/mcp.js +34 -0
- package/dist/commands/mcp/mcp.js.map +1 -0
- package/dist/commands/mcp/serve.js +55 -0
- package/dist/commands/mcp/serve.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/active.js +170 -0
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
- package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/living.js +62 -0
- package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/target.js +219 -0
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/error.js +19 -0
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/read.js +49 -0
- package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
- package/dist/commands/migrate-openspec/print.js +89 -0
- package/dist/commands/migrate-openspec/print.js.map +1 -0
- package/dist/commands/new/commit.js +76 -0
- package/dist/commands/new/commit.js.map +1 -0
- package/dist/commands/new/new.js +283 -0
- package/dist/commands/new/new.js.map +1 -0
- package/dist/commands/new/templates.js +355 -0
- package/dist/commands/new/templates.js.map +1 -0
- package/dist/commands/open.js +203 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/policy/format.js +152 -0
- package/dist/commands/policy/format.js.map +1 -0
- package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
- package/dist/commands/policy/gate.js.map +1 -0
- package/dist/commands/rebase/edit.js +106 -0
- package/dist/commands/rebase/edit.js.map +1 -0
- package/dist/commands/rebase/living.js +185 -0
- package/dist/commands/rebase/living.js.map +1 -0
- package/dist/commands/rebase/plan.js +245 -0
- package/dist/commands/rebase/plan.js.map +1 -0
- package/dist/commands/rebase/rebase.js +275 -0
- package/dist/commands/rebase/rebase.js.map +1 -0
- package/dist/commands/seed/commit.js +192 -0
- package/dist/commands/seed/commit.js.map +1 -0
- package/dist/commands/seed/plan.js +133 -0
- package/dist/commands/seed/plan.js.map +1 -0
- package/dist/commands/seed/seed.js +172 -0
- package/dist/commands/seed/seed.js.map +1 -0
- package/dist/commands/show/feature.js +178 -0
- package/dist/commands/show/feature.js.map +1 -0
- package/dist/commands/show/marks.js +24 -0
- package/dist/commands/show/marks.js.map +1 -0
- package/dist/commands/{show.js → show/service.js} +63 -152
- package/dist/commands/show/service.js.map +1 -0
- package/dist/commands/show/show.js +77 -0
- package/dist/commands/show/show.js.map +1 -0
- package/dist/commands/status/json.js +45 -0
- package/dist/commands/status/json.js.map +1 -0
- package/dist/commands/status/print.js +157 -0
- package/dist/commands/status/print.js.map +1 -0
- package/dist/commands/status/status.js +119 -0
- package/dist/commands/status/status.js.map +1 -0
- package/dist/commands/steps/steps.js +189 -0
- package/dist/commands/steps/steps.js.map +1 -0
- package/dist/commands/subsystem/edit.js +192 -0
- package/dist/commands/subsystem/edit.js.map +1 -0
- package/dist/commands/subsystem/move.js +265 -0
- package/dist/commands/subsystem/move.js.map +1 -0
- package/dist/commands/subsystem/query.js +115 -0
- package/dist/commands/subsystem/query.js.map +1 -0
- package/dist/commands/subsystem/subsystem.js +101 -0
- package/dist/commands/subsystem/subsystem.js.map +1 -0
- package/dist/commands/subsystem/sync.js +103 -0
- package/dist/commands/subsystem/sync.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +94 -0
- package/dist/commands/subsystem/txn/txn.js.map +1 -0
- package/dist/commands/subsystem/txn/views.js +70 -0
- package/dist/commands/subsystem/txn/views.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +160 -0
- package/dist/commands/unarchive/manifest.js.map +1 -0
- package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
- package/dist/commands/unarchive/restore.js.map +1 -0
- package/dist/commands/unarchive/unarchive.js +67 -0
- package/dist/commands/unarchive/unarchive.js.map +1 -0
- package/dist/commands/validate/arch-coverage.js +194 -0
- package/dist/commands/validate/arch-coverage.js.map +1 -0
- package/dist/commands/validate/checks/fleet-shape.js +296 -0
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
- package/dist/commands/validate/checks/health-deps.js +55 -0
- package/dist/commands/validate/checks/health-deps.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +254 -0
- package/dist/commands/validate/checks/requirements.js.map +1 -0
- package/dist/commands/validate/checks/sources.js +76 -0
- package/dist/commands/validate/checks/sources.js.map +1 -0
- package/dist/commands/validate/checks/vocabulary.js +31 -0
- package/dist/commands/validate/checks/vocabulary.js.map +1 -0
- package/dist/commands/validate/feature.js +216 -0
- package/dist/commands/validate/feature.js.map +1 -0
- package/dist/commands/validate/fleet/census.js +69 -0
- package/dist/commands/validate/fleet/census.js.map +1 -0
- package/dist/commands/validate/fleet/kind-tags.js +39 -0
- package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
- package/dist/commands/validate/fleet/landscape.js +353 -0
- package/dist/commands/validate/fleet/landscape.js.map +1 -0
- package/dist/commands/validate/fleet/load.js +52 -0
- package/dist/commands/validate/fleet/load.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +236 -0
- package/dist/commands/validate/fleet/obligations.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/print.js +63 -0
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/place.js +13 -0
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/steps.js +197 -0
- package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +40 -0
- package/dist/commands/validate/fleet/views/ids.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +59 -0
- package/dist/commands/validate/fleet/views/stale.js.map +1 -0
- package/dist/commands/validate/links/corpus.js +92 -0
- package/dist/commands/validate/links/corpus.js.map +1 -0
- package/dist/commands/validate/links/glossary.js +75 -0
- package/dist/commands/validate/links/glossary.js.map +1 -0
- package/dist/commands/validate/report.js +269 -0
- package/dist/commands/validate/report.js.map +1 -0
- package/dist/commands/validate/service/api.js +338 -0
- package/dist/commands/validate/service/api.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +219 -0
- package/dist/commands/validate/service/events/events.js.map +1 -0
- package/dist/commands/validate/service/events/message-producers.js +77 -0
- package/dist/commands/validate/service/events/message-producers.js.map +1 -0
- package/dist/commands/validate/service/evidence-pins.js +131 -0
- package/dist/commands/validate/service/evidence-pins.js.map +1 -0
- package/dist/commands/validate/service/service.js +250 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +219 -0
- package/dist/commands/validate/service/specs.js.map +1 -0
- package/dist/commands/validate/service/spine.js +113 -0
- package/dist/commands/validate/service/spine.js.map +1 -0
- package/dist/commands/validate/validate.js +338 -0
- package/dist/commands/validate/validate.js.map +1 -0
- package/dist/commands/verify/cross/diff.js +182 -0
- package/dist/commands/verify/cross/diff.js.map +1 -0
- package/dist/commands/verify/evidence/contract.js +32 -0
- package/dist/commands/verify/evidence/contract.js.map +1 -0
- package/dist/commands/verify/evidence/pins.js +52 -0
- package/dist/commands/verify/evidence/pins.js.map +1 -0
- package/dist/commands/verify/evidence/read.js +72 -0
- package/dist/commands/verify/evidence/read.js.map +1 -0
- package/dist/commands/verify/frozen.js +241 -0
- package/dist/commands/verify/frozen.js.map +1 -0
- package/dist/commands/verify/record.js +226 -0
- package/dist/commands/verify/record.js.map +1 -0
- package/dist/commands/verify/report.js +223 -0
- package/dist/commands/verify/report.js.map +1 -0
- package/dist/commands/verify/results.js +246 -0
- package/dist/commands/verify/results.js.map +1 -0
- package/dist/commands/verify/verify.js +217 -0
- package/dist/commands/verify/verify.js.map +1 -0
- package/dist/commands/vouch/contract.js +11 -0
- package/dist/commands/vouch/contract.js.map +1 -0
- package/dist/commands/vouch/pack/contract.js +2 -0
- package/dist/commands/vouch/pack/contract.js.map +1 -0
- package/dist/commands/vouch/pack/pack.js +266 -0
- package/dist/commands/vouch/pack/pack.js.map +1 -0
- package/dist/commands/vouch/pack/print.js +271 -0
- package/dist/commands/vouch/pack/print.js.map +1 -0
- package/dist/commands/vouch/pack/sections.js +142 -0
- package/dist/commands/vouch/pack/sections.js.map +1 -0
- package/dist/commands/vouch/prompt/confirm.js +46 -0
- package/dist/commands/vouch/prompt/confirm.js.map +1 -0
- package/dist/commands/vouch/report.js +101 -0
- package/dist/commands/vouch/report.js.map +1 -0
- package/dist/commands/vouch/run.js +285 -0
- package/dist/commands/vouch/run.js.map +1 -0
- package/dist/commands/vouch/sample/plan.js +125 -0
- package/dist/commands/vouch/sample/plan.js.map +1 -0
- package/dist/commands/vouch/sample/print.js +109 -0
- package/dist/commands/vouch/sample/print.js.map +1 -0
- package/dist/commands/vouch/vet/verify.js +123 -0
- package/dist/commands/vouch/vet/verify.js.map +1 -0
- package/dist/commands/vouch/vouch.js +176 -0
- package/dist/commands/vouch/vouch.js.map +1 -0
- package/dist/core/agent/agents-md/artifacts.js +220 -0
- package/dist/core/agent/agents-md/artifacts.js.map +1 -0
- package/dist/core/agent/agents-md/command-map.js +324 -0
- package/dist/core/agent/agents-md/command-map.js.map +1 -0
- package/dist/core/agent/agents-md/cycle.js +165 -0
- package/dist/core/agent/agents-md/cycle.js.map +1 -0
- package/dist/core/agent/agents-md/map/explain.js +34 -0
- package/dist/core/agent/agents-md/map/explain.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
- package/dist/core/agent/agents-md/map/mcp.js +41 -0
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
- package/dist/core/agent/agents-md/map/status.js +62 -0
- package/dist/core/agent/agents-md/map/status.js.map +1 -0
- package/dist/core/agent/agents-md/map/subsystem.js +32 -0
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
- package/dist/core/agent/agents-md/map/usecases.js +60 -0
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
- package/dist/core/agent/agents-md/refusals.js +246 -0
- package/dist/core/agent/agents-md/refusals.js.map +1 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
- package/dist/core/agent/agents-md/spine.js +40 -0
- package/dist/core/agent/agents-md/spine.js.map +1 -0
- package/dist/core/agent/agents-md.js +170 -0
- package/dist/core/agent/agents-md.js.map +1 -0
- package/dist/core/agent/agents-stamp.js +155 -0
- package/dist/core/agent/agents-stamp.js.map +1 -0
- package/dist/core/agent/contract.js +2 -0
- package/dist/core/agent/contract.js.map +1 -0
- package/dist/core/agent/protocol.js +193 -0
- package/dist/core/agent/protocol.js.map +1 -0
- package/dist/core/agent/scaffold.js +259 -0
- package/dist/core/agent/scaffold.js.map +1 -0
- package/dist/core/agent/support/report.js +103 -0
- package/dist/core/agent/support/report.js.map +1 -0
- package/dist/core/agent/tools/dialects.js +150 -0
- package/dist/core/agent/tools/dialects.js.map +1 -0
- package/dist/core/agent/tools/registry.js +228 -0
- package/dist/core/agent/tools/registry.js.map +1 -0
- package/dist/core/agent/workflows/adopt.js +133 -0
- package/dist/core/agent/workflows/adopt.js.map +1 -0
- package/dist/core/agent/workflows/check.js +334 -0
- package/dist/core/agent/workflows/check.js.map +1 -0
- package/dist/core/agent/workflows/closing.js +262 -0
- package/dist/core/agent/workflows/closing.js.map +1 -0
- package/dist/core/agent/workflows/feature.js +105 -0
- package/dist/core/agent/workflows/feature.js.map +1 -0
- package/dist/core/agent/workflows/implement.js +105 -0
- package/dist/core/agent/workflows/implement.js.map +1 -0
- package/dist/core/agent/workflows/reference/authoring.js +209 -0
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
- package/dist/core/agent/workflows/reference/codes.js +65 -0
- package/dist/core/agent/workflows/reference/codes.js.map +1 -0
- package/dist/core/agent/workflows/reference/done-check.js +188 -0
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +16 -0
- package/dist/core/agent/workflows/reference/reference.js.map +1 -0
- package/dist/core/agent/workflows/reference/spine.js +297 -0
- package/dist/core/agent/workflows/reference/spine.js.map +1 -0
- package/dist/core/asyncapi/baseline/plan.js +110 -0
- package/dist/core/asyncapi/baseline/plan.js.map +1 -0
- package/dist/core/asyncapi/baseline/surfaces.js +54 -0
- package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
- package/dist/core/asyncapi/depth.js +164 -0
- package/dist/core/asyncapi/depth.js.map +1 -0
- package/dist/core/asyncapi/digest.js +141 -0
- package/dist/core/asyncapi/digest.js.map +1 -0
- package/dist/core/asyncapi/merge/components.js +91 -0
- package/dist/core/asyncapi/merge/components.js.map +1 -0
- package/dist/core/asyncapi/merge/error.js +33 -0
- package/dist/core/asyncapi/merge/error.js.map +1 -0
- package/dist/core/asyncapi/merge/markers.js +308 -0
- package/dist/core/asyncapi/merge/markers.js.map +1 -0
- package/dist/core/asyncapi/merge/merge.js +215 -0
- package/dist/core/asyncapi/merge/merge.js.map +1 -0
- package/dist/core/asyncapi/merge/pin.js +107 -0
- package/dist/core/asyncapi/merge/pin.js.map +1 -0
- package/dist/core/asyncapi/model.js +11 -0
- package/dist/core/asyncapi/model.js.map +1 -0
- package/dist/core/asyncapi/producers.js +50 -0
- package/dist/core/asyncapi/producers.js.map +1 -0
- package/dist/core/asyncapi/read.js +239 -0
- package/dist/core/asyncapi/read.js.map +1 -0
- package/dist/core/brief/brief.js +225 -0
- package/dist/core/brief/brief.js.map +1 -0
- package/dist/core/brief/checks.js +250 -0
- package/dist/core/brief/checks.js.map +1 -0
- package/dist/core/brief/landscape.js +194 -0
- package/dist/core/brief/landscape.js.map +1 -0
- package/dist/core/brief/targets.js +226 -0
- package/dist/core/brief/targets.js.map +1 -0
- package/dist/core/brief/unchecked.js +47 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +282 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/likec4.js +88 -0
- package/dist/core/c4/likec4.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js +132 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
- package/dist/core/c4/parsed/specification.js +78 -0
- package/dist/core/c4/parsed/specification.js.map +1 -0
- package/dist/core/c4/parsed/values.js +57 -0
- package/dist/core/c4/parsed/values.js.map +1 -0
- package/dist/core/c4/parsed/view-ids.js +75 -0
- package/dist/core/c4/parsed/view-ids.js.map +1 -0
- package/dist/core/c4/project/architecture.js +45 -0
- package/dist/core/c4/project/architecture.js.map +1 -0
- package/dist/core/c4/project/documents.js +58 -0
- package/dist/core/c4/project/documents.js.map +1 -0
- package/dist/core/c4/project/load.js +183 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/resolve/service.js +113 -0
- package/dist/core/c4/resolve/service.js.map +1 -0
- package/dist/core/c4/seed/fleet-file.js +201 -0
- package/dist/core/c4/seed/fleet-file.js.map +1 -0
- package/dist/core/c4/seed/items.js +91 -0
- package/dist/core/c4/seed/items.js.map +1 -0
- package/dist/core/c4/seed/stamp.js +48 -0
- package/dist/core/c4/seed/stamp.js.map +1 -0
- package/dist/core/c4/seed/template.js +109 -0
- package/dist/core/c4/seed/template.js.map +1 -0
- package/dist/core/c4/source-mask.js +82 -0
- package/dist/core/c4/source-mask.js.map +1 -0
- package/dist/core/c4/source-scan.js +227 -0
- package/dist/core/c4/source-scan.js.map +1 -0
- package/dist/core/c4/splice/authored-source.js +101 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -0
- package/dist/core/c4/splice/contract.js +10 -0
- package/dist/core/c4/splice/contract.js.map +1 -0
- package/dist/core/c4/splice/delta-blocks.js +126 -0
- package/dist/core/c4/splice/delta-blocks.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +36 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +269 -0
- package/dist/core/c4/splice/landscape-merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +194 -0
- package/dist/core/c4/splice/placement.js.map +1 -0
- package/dist/core/c4/workspace.js +172 -0
- package/dist/core/c4/workspace.js.map +1 -0
- package/dist/core/capabilities/authoring/notes.js +141 -0
- package/dist/core/capabilities/authoring/notes.js.map +1 -0
- package/dist/core/capabilities/capabilities.js +124 -0
- package/dist/core/capabilities/capabilities.js.map +1 -0
- package/dist/core/capabilities/delta/doc.js +50 -0
- package/dist/core/capabilities/delta/doc.js.map +1 -0
- package/dist/core/capabilities/delta/overlay.js +129 -0
- package/dist/core/capabilities/delta/overlay.js.map +1 -0
- package/dist/core/capabilities/delta/summary.js +57 -0
- package/dist/core/capabilities/delta/summary.js.map +1 -0
- package/dist/core/capabilities/delta/tree.js +43 -0
- package/dist/core/capabilities/delta/tree.js.map +1 -0
- package/dist/core/capabilities/delta/uncovered.js +222 -0
- package/dist/core/capabilities/delta/uncovered.js.map +1 -0
- package/dist/core/capabilities/findings.js +342 -0
- package/dist/core/capabilities/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/findings.js +181 -0
- package/dist/core/capabilities/realizes/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/join.js +143 -0
- package/dist/core/capabilities/realizes/join.js.map +1 -0
- package/dist/core/capabilities/rollup.js +199 -0
- package/dist/core/capabilities/rollup.js.map +1 -0
- package/dist/core/capabilities/tree.js +126 -0
- package/dist/core/capabilities/tree.js.map +1 -0
- package/dist/core/capabilities/usecase-join.js +277 -0
- package/dist/core/capabilities/usecase-join.js.map +1 -0
- package/dist/core/coherence/authoring/scaffold.js +177 -0
- package/dist/core/coherence/authoring/scaffold.js.map +1 -0
- package/dist/core/coherence/authoring/sentinels.js +74 -0
- package/dist/core/coherence/authoring/sentinels.js.map +1 -0
- package/dist/core/coherence/coherence.js +251 -0
- package/dist/core/coherence/coherence.js.map +1 -0
- package/dist/core/coherence/declared.js +269 -0
- package/dist/core/coherence/declared.js.map +1 -0
- package/dist/core/coherence/events/declared.js +206 -0
- package/dist/core/coherence/events/declared.js.map +1 -0
- package/dist/core/coherence/events/events.js +204 -0
- package/dist/core/coherence/events/events.js.map +1 -0
- package/dist/core/coherence/events/grades.js +166 -0
- package/dist/core/coherence/events/grades.js.map +1 -0
- package/dist/core/coherence/events/lookups.js +122 -0
- package/dist/core/coherence/events/lookups.js.map +1 -0
- package/dist/core/coherence/events/merged.js +40 -0
- package/dist/core/coherence/events/merged.js.map +1 -0
- package/dist/core/coherence/living.js +198 -0
- package/dist/core/coherence/living.js.map +1 -0
- package/dist/core/coherence/lookups.js +155 -0
- package/dist/core/coherence/lookups.js.map +1 -0
- package/dist/core/coherence/pending.js +39 -0
- package/dist/core/coherence/pending.js.map +1 -0
- package/dist/core/conflict-markers.js +60 -0
- package/dist/core/conflict-markers.js.map +1 -0
- package/dist/core/delta/claims.js +73 -0
- package/dist/core/delta/claims.js.map +1 -0
- package/dist/core/delta/delta.js +296 -0
- package/dist/core/delta/delta.js.map +1 -0
- package/dist/core/delta/document.js +177 -0
- package/dist/core/delta/document.js.map +1 -0
- package/dist/core/delta/scope.js +48 -0
- package/dist/core/delta/scope.js.map +1 -0
- package/dist/core/delta/select.js +237 -0
- package/dist/core/delta/select.js.map +1 -0
- package/dist/core/dependencies/dependencies.js +141 -0
- package/dist/core/dependencies/dependencies.js.map +1 -0
- package/dist/core/dependencies/facts.js +165 -0
- package/dist/core/dependencies/facts.js.map +1 -0
- package/dist/core/dependencies/fanin.js +88 -0
- package/dist/core/dependencies/fanin.js.map +1 -0
- package/dist/core/dependencies/graph.js +106 -0
- package/dist/core/dependencies/graph.js.map +1 -0
- package/dist/core/diff/base-git.js +148 -0
- package/dist/core/diff/base-git.js.map +1 -0
- package/dist/core/diff/base-state.js +209 -0
- package/dist/core/diff/base-state.js.map +1 -0
- package/dist/core/diff/current-state.js +96 -0
- package/dist/core/diff/current-state.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +188 -0
- package/dist/core/diff/scope/changed-paths.js.map +1 -0
- package/dist/core/diff/semantic.js +243 -0
- package/dist/core/diff/semantic.js.map +1 -0
- package/dist/core/diff/victims.js +247 -0
- package/dist/core/diff/victims.js.map +1 -0
- package/dist/core/docs.js +59 -55
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +222 -0
- package/dist/core/doctor/agents.js.map +1 -0
- package/dist/core/doctor/config.js +135 -0
- package/dist/core/doctor/config.js.map +1 -0
- package/dist/core/doctor/doctor.js +275 -0
- package/dist/core/doctor/doctor.js.map +1 -0
- package/dist/core/doctor/report.js +13 -0
- package/dist/core/doctor/report.js.map +1 -0
- package/dist/core/doctor/residue.js +110 -0
- package/dist/core/doctor/residue.js.map +1 -0
- package/dist/core/document/apply.js +77 -0
- package/dist/core/document/apply.js.map +1 -0
- package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
- package/dist/core/document/frontmatter.js.map +1 -0
- package/dist/core/document/grammar/directives.js +143 -0
- package/dist/core/document/grammar/directives.js.map +1 -0
- package/dist/core/document/parse.js +263 -0
- package/dist/core/document/parse.js.map +1 -0
- package/dist/core/document/scenarios.js +221 -0
- package/dist/core/document/scenarios.js.map +1 -0
- package/dist/core/document/spec.js +241 -0
- package/dist/core/document/spec.js.map +1 -0
- package/dist/core/{config.js → envelope/config.js} +79 -18
- package/dist/core/envelope/config.js.map +1 -0
- package/dist/core/envelope/json.js +175 -0
- package/dist/core/envelope/json.js.map +1 -0
- package/dist/core/{version.js → envelope/version.js} +3 -3
- package/dist/core/envelope/version.js.map +1 -0
- package/dist/core/explain/families.js +175 -0
- package/dist/core/explain/families.js.map +1 -0
- package/dist/core/explain/fix-tables.js +178 -0
- package/dist/core/explain/fix-tables.js.map +1 -0
- package/dist/core/explain/lookup.js +181 -0
- package/dist/core/explain/lookup.js.map +1 -0
- package/dist/core/explain/refusals.js +49 -0
- package/dist/core/explain/refusals.js.map +1 -0
- package/dist/core/explain/terms.js +144 -0
- package/dist/core/explain/terms.js.map +1 -0
- package/dist/core/explore/describe.js +105 -0
- package/dist/core/explore/describe.js.map +1 -0
- package/dist/core/{explore.js → explore/explore.js} +50 -101
- package/dist/core/explore/explore.js.map +1 -0
- package/dist/core/fleet-context.js +132 -77
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +201 -0
- package/dist/core/gate/checks.js.map +1 -0
- package/dist/core/gate/partners.js +205 -0
- package/dist/core/gate/partners.js.map +1 -0
- package/dist/core/gate/report.js +47 -0
- package/dist/core/gate/report.js.map +1 -0
- package/dist/core/gate/verification.js +113 -0
- package/dist/core/gate/verification.js.map +1 -0
- package/dist/core/gherkin/digest.js +60 -0
- package/dist/core/gherkin/digest.js.map +1 -0
- package/dist/core/gherkin/emit.js +177 -0
- package/dist/core/gherkin/emit.js.map +1 -0
- package/dist/core/gherkin/read.js +76 -0
- package/dist/core/gherkin/read.js.map +1 -0
- package/dist/core/gherkin/stale.js +234 -0
- package/dist/core/gherkin/stale.js.map +1 -0
- package/dist/core/gherkin/stamp.js +77 -0
- package/dist/core/gherkin/stamp.js.map +1 -0
- package/dist/core/gherkin/steps/catalogue.js +154 -0
- package/dist/core/gherkin/steps/catalogue.js.map +1 -0
- package/dist/core/gherkin/steps/inventory.js +98 -0
- package/dist/core/gherkin/steps/inventory.js.map +1 -0
- package/dist/core/gherkin/steps/phrase.js +72 -0
- package/dist/core/gherkin/steps/phrase.js.map +1 -0
- package/dist/core/glossary/delta.js +106 -0
- package/dist/core/glossary/delta.js.map +1 -0
- package/dist/core/glossary/tree.js +86 -0
- package/dist/core/glossary/tree.js.map +1 -0
- package/dist/core/kernel/concurrency.js.map +1 -0
- package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
- package/dist/core/kernel/document-bytes.js.map +1 -0
- package/dist/core/kernel/fences.js +42 -0
- package/dist/core/kernel/fences.js.map +1 -0
- package/dist/core/kernel/ids/capability.js +72 -0
- package/dist/core/kernel/ids/capability.js.map +1 -0
- package/dist/core/kernel/ids/dirs.js +33 -0
- package/dist/core/kernel/ids/dirs.js.map +1 -0
- package/dist/core/kernel/ids/feature.js +75 -0
- package/dist/core/kernel/ids/feature.js.map +1 -0
- package/dist/core/kernel/ids/service.js +156 -0
- package/dist/core/kernel/ids/service.js.map +1 -0
- package/dist/core/kernel/ids/subsystem.js +63 -0
- package/dist/core/kernel/ids/subsystem.js.map +1 -0
- package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
- package/dist/core/kernel/path-safety.js.map +1 -0
- package/dist/core/kernel/records.js.map +1 -0
- package/dist/core/links/backlinks.js +68 -0
- package/dist/core/links/backlinks.js.map +1 -0
- package/dist/core/links/case.js +74 -0
- package/dist/core/links/case.js.map +1 -0
- package/dist/core/links/corpus.js +104 -0
- package/dist/core/links/corpus.js.map +1 -0
- package/dist/core/links/findings.js +110 -0
- package/dist/core/links/findings.js.map +1 -0
- package/dist/core/links/parse.js +236 -0
- package/dist/core/links/parse.js.map +1 -0
- package/dist/core/mcp/argv.js +111 -0
- package/dist/core/mcp/argv.js.map +1 -0
- package/dist/core/mcp/author-tools.js +72 -0
- package/dist/core/mcp/author-tools.js.map +1 -0
- package/dist/core/mcp/framing.js +46 -0
- package/dist/core/mcp/framing.js.map +1 -0
- package/dist/core/mcp/protocol.js +280 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/tools.js +314 -0
- package/dist/core/mcp/tools.js.map +1 -0
- package/dist/core/obligations/obligations.js +106 -0
- package/dist/core/obligations/obligations.js.map +1 -0
- package/dist/core/openapi/baseline/gate.js +166 -0
- package/dist/core/openapi/baseline/gate.js.map +1 -0
- package/dist/core/openapi/baseline/plan.js +112 -0
- package/dist/core/openapi/baseline/plan.js.map +1 -0
- package/dist/core/openapi/baseline/record.js +175 -0
- package/dist/core/openapi/baseline/record.js.map +1 -0
- package/dist/core/openapi/depth.js +122 -0
- package/dist/core/openapi/depth.js.map +1 -0
- package/dist/core/openapi/digest.js +189 -0
- package/dist/core/openapi/digest.js.map +1 -0
- package/dist/core/{openapi.js → openapi/doc.js} +44 -106
- package/dist/core/openapi/doc.js.map +1 -0
- package/dist/core/openapi/merge/components.js +234 -0
- package/dist/core/openapi/merge/components.js.map +1 -0
- package/dist/core/openapi/merge/error.js +26 -0
- package/dist/core/openapi/merge/error.js.map +1 -0
- package/dist/core/openapi/merge/markers.js +208 -0
- package/dist/core/openapi/merge/markers.js.map +1 -0
- package/dist/core/openapi/merge/merge.js +256 -0
- package/dist/core/openapi/merge/merge.js.map +1 -0
- package/dist/core/openapi/merge/pin.js +128 -0
- package/dist/core/openapi/merge/pin.js.map +1 -0
- package/dist/core/openspec/decide/capabilities.js +244 -0
- package/dist/core/openspec/decide/capabilities.js.map +1 -0
- package/dist/core/openspec/decide/dispositions.js +63 -0
- package/dist/core/openspec/decide/dispositions.js.map +1 -0
- package/dist/core/openspec/decide/renames.js +179 -0
- package/dist/core/openspec/decide/renames.js.map +1 -0
- package/dist/core/openspec/decide/verdict.js +84 -0
- package/dist/core/openspec/decide/verdict.js.map +1 -0
- package/dist/core/openspec/inventory.js +221 -0
- package/dist/core/openspec/inventory.js.map +1 -0
- package/dist/core/openspec/model/mapping.js +76 -0
- package/dist/core/openspec/model/mapping.js.map +1 -0
- package/dist/core/openspec/model/model.js +65 -0
- package/dist/core/openspec/model/model.js.map +1 -0
- package/dist/core/openspec/scan/changes.js +235 -0
- package/dist/core/openspec/scan/changes.js.map +1 -0
- package/dist/core/openspec/scan/config.js +200 -0
- package/dist/core/openspec/scan/config.js.map +1 -0
- package/dist/core/openspec/scan/shape.js +260 -0
- package/dist/core/openspec/scan/shape.js.map +1 -0
- package/dist/core/openspec/scan/walk.js +133 -0
- package/dist/core/openspec/scan/walk.js.map +1 -0
- package/dist/core/openspec/scan/workspace.js +71 -0
- package/dist/core/openspec/scan/workspace.js.map +1 -0
- package/dist/core/owners/codeowners.js +141 -0
- package/dist/core/owners/codeowners.js.map +1 -0
- package/dist/core/pack/features.js +120 -0
- package/dist/core/pack/features.js.map +1 -0
- package/dist/core/pack/joins.js +112 -0
- package/dist/core/pack/joins.js.map +1 -0
- package/dist/core/pack/living.js +118 -0
- package/dist/core/pack/living.js.map +1 -0
- package/dist/core/pack/pack.js +76 -0
- package/dist/core/pack/pack.js.map +1 -0
- package/dist/core/permissions/permissions.js +87 -0
- package/dist/core/permissions/permissions.js.map +1 -0
- package/dist/core/projection/api.js +84 -0
- package/dist/core/projection/api.js.map +1 -0
- package/dist/core/projection/arch-slice.js +63 -0
- package/dist/core/projection/arch-slice.js.map +1 -0
- package/dist/core/projection/events.js +34 -0
- package/dist/core/projection/events.js.map +1 -0
- package/dist/core/provenance/findings.js +208 -0
- package/dist/core/provenance/findings.js.map +1 -0
- package/dist/core/provenance/git.js +225 -0
- package/dist/core/provenance/git.js.map +1 -0
- package/dist/core/provenance/gitq/moves.js +182 -0
- package/dist/core/provenance/gitq/moves.js.map +1 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
- package/dist/core/provenance/sample/scope.js +238 -0
- package/dist/core/provenance/sample/scope.js.map +1 -0
- package/dist/core/provenance/sample/sections.js +61 -0
- package/dist/core/provenance/sample/sections.js.map +1 -0
- package/dist/core/provenance/sources.js +263 -0
- package/dist/core/provenance/sources.js.map +1 -0
- package/dist/core/provenance/stamp.js +232 -0
- package/dist/core/provenance/stamp.js.map +1 -0
- package/dist/core/provenance/walk.js +157 -0
- package/dist/core/provenance/walk.js.map +1 -0
- package/dist/core/repo/authored/paths.js +192 -0
- package/dist/core/repo/authored/paths.js.map +1 -0
- package/dist/core/repo/entries.js +126 -0
- package/dist/core/repo/entries.js.map +1 -0
- package/dist/core/repo/paths.js +236 -0
- package/dist/core/repo/paths.js.map +1 -0
- package/dist/core/repo/repo.js +248 -0
- package/dist/core/repo/repo.js.map +1 -0
- package/dist/core/repo/service-target.js +153 -0
- package/dist/core/repo/service-target.js.map +1 -0
- package/dist/core/repo/state.js +58 -0
- package/dist/core/repo/state.js.map +1 -0
- package/dist/core/repo/tree/find.js +52 -0
- package/dist/core/repo/tree/find.js.map +1 -0
- package/dist/core/repo/tree/fs.js +64 -0
- package/dist/core/repo/tree/fs.js.map +1 -0
- package/dist/core/repo/tree/marker.js +61 -0
- package/dist/core/repo/tree/marker.js.map +1 -0
- package/dist/core/repo/tree/views.js +172 -0
- package/dist/core/repo/tree/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +234 -0
- package/dist/core/repo/tree/walk.js.map +1 -0
- package/dist/core/results.js +99 -2
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +131 -0
- package/dist/core/scaffold/landscape.js.map +1 -0
- package/dist/core/scaffold/readme.js +84 -0
- package/dist/core/scaffold/readme.js.map +1 -0
- package/dist/core/staging/commit.js +241 -0
- package/dist/core/staging/commit.js.map +1 -0
- package/dist/core/staging/interrupted.js +96 -0
- package/dist/core/staging/interrupted.js.map +1 -0
- package/dist/core/staging/lock.js +206 -0
- package/dist/core/staging/lock.js.map +1 -0
- package/dist/core/staging/recovery/finding.js +35 -0
- package/dist/core/staging/recovery/finding.js.map +1 -0
- package/dist/core/staging/recovery/intent.js +98 -0
- package/dist/core/staging/recovery/intent.js.map +1 -0
- package/dist/core/staging/recovery/recover.js +126 -0
- package/dist/core/staging/recovery/recover.js.map +1 -0
- package/dist/core/staging/recovery/residue.js +62 -0
- package/dist/core/staging/recovery/residue.js.map +1 -0
- package/dist/core/staging/snapshot.js +202 -0
- package/dist/core/staging/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +171 -0
- package/dist/core/staging/txn/forward.js.map +1 -0
- package/dist/core/staging/txn/journal.js +139 -0
- package/dist/core/staging/txn/journal.js.map +1 -0
- package/dist/core/staging/txn/transaction.js +75 -0
- package/dist/core/staging/txn/transaction.js.map +1 -0
- package/dist/core/staging/writes.js +113 -0
- package/dist/core/staging/writes.js.map +1 -0
- package/dist/core/status/actions/execution.js +49 -0
- package/dist/core/status/actions/execution.js.map +1 -0
- package/dist/core/status/contracts.js +102 -0
- package/dist/core/status/contracts.js.map +1 -0
- package/dist/core/status/feature/artifacts.js +171 -0
- package/dist/core/status/feature/artifacts.js.map +1 -0
- package/dist/core/status/feature/feature.js +207 -0
- package/dist/core/status/feature/feature.js.map +1 -0
- package/dist/core/status/feature/next.js +150 -0
- package/dist/core/status/feature/next.js.map +1 -0
- package/dist/core/status/feature/state.js +21 -0
- package/dist/core/status/feature/state.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +187 -0
- package/dist/core/status/fleet/fleet.js.map +1 -0
- package/dist/core/status/fleet/next.js +207 -0
- package/dist/core/status/fleet/next.js.map +1 -0
- package/dist/core/status/interrupted.js +97 -0
- package/dist/core/status/interrupted.js.map +1 -0
- package/dist/core/status/report.js +31 -0
- package/dist/core/status/report.js.map +1 -0
- package/dist/core/status/scan.js +41 -0
- package/dist/core/status/scan.js.map +1 -0
- package/dist/core/status/verification.js +201 -0
- package/dist/core/status/verification.js.map +1 -0
- package/dist/core/usecases/capability.js +169 -0
- package/dist/core/usecases/capability.js.map +1 -0
- package/dist/core/usecases/delta/claims.js +116 -0
- package/dist/core/usecases/delta/claims.js.map +1 -0
- package/dist/core/usecases/delta/flows.js +103 -0
- package/dist/core/usecases/delta/flows.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +235 -0
- package/dist/core/usecases/delta/overlay.js.map +1 -0
- package/dist/core/usecases/fleet.js +137 -0
- package/dist/core/usecases/fleet.js.map +1 -0
- package/dist/core/usecases/operations.js +111 -0
- package/dist/core/usecases/operations.js.map +1 -0
- package/dist/core/usecases/place.js +44 -0
- package/dist/core/usecases/place.js.map +1 -0
- package/dist/core/usecases/touch.js +101 -0
- package/dist/core/usecases/touch.js.map +1 -0
- package/dist/core/verify/answers.js +173 -0
- package/dist/core/verify/answers.js.map +1 -0
- package/dist/core/verify/build.js +150 -0
- package/dist/core/verify/build.js.map +1 -0
- package/dist/core/verify/checklist.js +201 -0
- package/dist/core/verify/checklist.js.map +1 -0
- package/dist/core/verify/claims/identity.js +61 -0
- package/dist/core/verify/claims/identity.js.map +1 -0
- package/dist/core/verify/cross/diff.js +97 -0
- package/dist/core/verify/cross/diff.js.map +1 -0
- package/dist/core/verify/evidence/contract.js +169 -0
- package/dist/core/verify/evidence/contract.js.map +1 -0
- package/dist/core/verify/file.js +257 -0
- package/dist/core/verify/file.js.map +1 -0
- package/dist/core/verify/pins/lint.js +157 -0
- package/dist/core/verify/pins/lint.js.map +1 -0
- package/dist/core/verify/pins/pin.js +95 -0
- package/dist/core/verify/pins/pin.js.map +1 -0
- package/dist/core/verify/record.js +142 -0
- package/dist/core/verify/record.js.map +1 -0
- package/dist/core/verify/store/commit.js +89 -0
- package/dist/core/verify/store/commit.js.map +1 -0
- package/dist/core/verify/store/render.js +57 -0
- package/dist/core/verify/store/render.js.map +1 -0
- package/dist/core/vocabulary/health.js +91 -0
- package/dist/core/vocabulary/health.js.map +1 -0
- package/dist/core/vocabulary/issue.js +55 -0
- package/dist/core/vocabulary/issue.js.map +1 -0
- package/dist/core/vocabulary/maturity.js +145 -0
- package/dist/core/vocabulary/maturity.js.map +1 -0
- package/dist/core/{report.js → vocabulary/report.js} +30 -2
- package/dist/core/vocabulary/report.js.map +1 -0
- package/dist/core/vocabulary/steps.js +266 -0
- package/dist/core/vocabulary/steps.js.map +1 -0
- package/dist/core/workspace/discover.js +239 -0
- package/dist/core/workspace/discover.js.map +1 -0
- package/dist/core/workspace/render.js +52 -0
- package/dist/core/workspace/render.js.map +1 -0
- package/examples/README.md +189 -0
- package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
- package/examples/docs/architecture/capabilities.yaml +31 -0
- package/examples/docs/architecture/landscape.likec4 +280 -0
- package/examples/docs/architecture/obligations.yaml +23 -0
- package/examples/docs/architecture/permissions.yaml +50 -0
- package/examples/docs/architecture/subsystems.likec4 +11 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
- package/examples/docs/capabilities/checkout/spec.md +55 -0
- package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
- package/examples/docs/capabilities/order-notifications/spec.md +43 -0
- package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
- package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
- package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
- package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
- package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
- package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
- package/examples/docs/glossary/order.md +15 -0
- package/examples/docs/glossary/payments/authorization.md +14 -0
- package/examples/docs/likec4.config.json +9 -0
- package/examples/docs/loam.json +3 -0
- package/examples/docs/services/checkout-web/model.likec4 +109 -0
- package/examples/docs/services/checkout-web/spec.md +91 -0
- package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
- package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
- package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
- package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
- package/examples/docs/services/order-service/arch.spec.md +127 -0
- package/examples/docs/services/order-service/asyncapi.yaml +108 -0
- package/examples/docs/services/order-service/health.yaml +57 -0
- package/examples/docs/services/order-service/model.likec4 +138 -0
- package/examples/docs/services/order-service/openapi.yaml +257 -0
- package/examples/docs/services/order-service/runbook.md +62 -0
- package/examples/docs/services/order-service/spec.md +134 -0
- package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
- package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
- package/examples/docs/services/payment-service/arch.spec.md +73 -0
- package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
- package/examples/docs/services/payment-service/health.yaml +39 -0
- package/examples/docs/services/payment-service/model.likec4 +67 -0
- package/examples/docs/services/payment-service/openapi.yaml +151 -0
- package/examples/docs/services/payment-service/runbook.md +37 -0
- package/examples/docs/services/payment-service/spec.md +88 -0
- package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
- package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
- package/examples/docs/services/platform/identity-service/health.yaml +70 -0
- package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
- package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
- package/examples/docs/services/platform/identity-service/runbook.md +95 -0
- package/examples/docs/services/platform/identity-service/spec.md +119 -0
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
- package/examples/docs/services/platform/notification-service/health.yaml +35 -0
- package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
- package/examples/docs/services/platform/notification-service/runbook.md +48 -0
- package/examples/docs/services/platform/notification-service/spec.md +112 -0
- package/examples/docs/services/platform/subsystem.yaml +2 -0
- package/examples/fleet.yaml +57 -0
- package/package.json +19 -6
- package/dist/commands/adopt.js +0 -240
- package/dist/commands/adopt.js.map +0 -1
- package/dist/commands/archive.js +0 -1313
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/delta.js +0 -388
- package/dist/commands/delta.js.map +0 -1
- package/dist/commands/docs-repo-gate.js.map +0 -1
- package/dist/commands/format.js +0 -17
- package/dist/commands/format.js.map +0 -1
- package/dist/commands/gherkin.js +0 -309
- package/dist/commands/gherkin.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.js +0 -291
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/migrate-openspec.js +0 -950
- package/dist/commands/migrate-openspec.js.map +0 -1
- package/dist/commands/new.js +0 -413
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/rebase.js +0 -356
- package/dist/commands/rebase.js.map +0 -1
- package/dist/commands/show.js.map +0 -1
- package/dist/commands/status.js +0 -254
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1591
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/verify.js +0 -775
- package/dist/commands/verify.js.map +0 -1
- package/dist/commands/vouch.js +0 -343
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2065
- package/dist/core/agent.js.map +0 -1
- package/dist/core/agents-stamp.js +0 -68
- package/dist/core/agents-stamp.js.map +0 -1
- package/dist/core/arch.js +0 -130
- package/dist/core/arch.js.map +0 -1
- package/dist/core/brief.js +0 -733
- package/dist/core/brief.js.map +0 -1
- package/dist/core/coherence.js +0 -577
- package/dist/core/coherence.js.map +0 -1
- package/dist/core/concurrency.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/delta.js +0 -476
- package/dist/core/delta.js.map +0 -1
- package/dist/core/dependencies.js +0 -356
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/doctor.js +0 -559
- package/dist/core/doctor.js.map +0 -1
- package/dist/core/document-bytes.js.map +0 -1
- package/dist/core/explore.js.map +0 -1
- package/dist/core/frontmatter.js.map +0 -1
- package/dist/core/gherkin.js +0 -463
- package/dist/core/gherkin.js.map +0 -1
- package/dist/core/health.js +0 -67
- package/dist/core/health.js.map +0 -1
- package/dist/core/ids.js +0 -122
- package/dist/core/ids.js.map +0 -1
- package/dist/core/issue.js +0 -9
- package/dist/core/issue.js.map +0 -1
- package/dist/core/json.js +0 -74
- package/dist/core/json.js.map +0 -1
- package/dist/core/likec4.js +0 -479
- package/dist/core/likec4.js.map +0 -1
- package/dist/core/maturity.js +0 -66
- package/dist/core/maturity.js.map +0 -1
- package/dist/core/openapi-merge.js +0 -552
- package/dist/core/openapi-merge.js.map +0 -1
- package/dist/core/openapi.js.map +0 -1
- package/dist/core/openspec-inventory.js +0 -1440
- package/dist/core/openspec-inventory.js.map +0 -1
- package/dist/core/path-safety.js.map +0 -1
- package/dist/core/provenance.js +0 -720
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -436
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -481
- package/dist/core/spec.js.map +0 -1
- package/dist/core/staging.js +0 -890
- package/dist/core/staging.js.map +0 -1
- package/dist/core/status.js +0 -1104
- package/dist/core/status.js.map +0 -1
- package/dist/core/steps.js +0 -72
- package/dist/core/steps.js.map +0 -1
- package/dist/core/verify.js +0 -807
- package/dist/core/verify.js.map +0 -1
- package/dist/core/version.js.map +0 -1
- /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
- /package/dist/core/{records.js → kernel/records.js} +0 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import {} from "../likec4.js";
|
|
2
|
+
import { elementService, serviceOf } from "../resolve/service.js";
|
|
3
|
+
import { relSortKey } from "./identity/edges.js";
|
|
4
|
+
/** The model's top-level statements in document order, joined to the parsed elements for services. */
|
|
5
|
+
export function topStatements(scan, els) {
|
|
6
|
+
const topEls = scan.elements.filter((e) => !e.id.includes("."));
|
|
7
|
+
const bound = new Map(els.map((e) => [e.id, elementService(e)]));
|
|
8
|
+
const stmts = topEls.map((e) => ({
|
|
9
|
+
kind: "element",
|
|
10
|
+
id: e.id,
|
|
11
|
+
key: "",
|
|
12
|
+
services: [bound.get(e.id) ?? e.id],
|
|
13
|
+
start: e.start,
|
|
14
|
+
end: e.end,
|
|
15
|
+
}));
|
|
16
|
+
for (const r of scan.rels) {
|
|
17
|
+
// A rel declared inside an element's body is not a top-level statement —
|
|
18
|
+
// anchoring after it would splice into the parent's block.
|
|
19
|
+
if (topEls.some((e) => r.start > e.start && r.start < e.end))
|
|
20
|
+
continue;
|
|
21
|
+
stmts.push({
|
|
22
|
+
kind: "rel",
|
|
23
|
+
id: "",
|
|
24
|
+
key: relSortKey(els, r),
|
|
25
|
+
// Deliberately no `known` fleet set here: this resolution only decides
|
|
26
|
+
// WHERE in the file a statement anchors, both sides of every comparison
|
|
27
|
+
// resolve through the same call, and the merged document is re-parsed
|
|
28
|
+
// before anything is written — a wrong grouping is cosmetic, never a
|
|
29
|
+
// wrong merge.
|
|
30
|
+
services: [serviceOf(els, r.source), serviceOf(els, r.target)],
|
|
31
|
+
start: r.start,
|
|
32
|
+
end: r.end,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return stmts.sort((a, b) => a.start - b.start);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The trailing walks move in PLACEMENT UNITS, never single statements. A unit
|
|
39
|
+
* is a service neighborhood: a contiguous run of top-level elements resolving
|
|
40
|
+
* to one service, or a contiguous cluster of relationships grown around the
|
|
41
|
+
* statements touching one — keyed by its LEADING statement's id/key. Anchored
|
|
42
|
+
* inserts extend a neighborhood without changing its head, so the insertion
|
|
43
|
+
* sort the trailing region maintains is over neighborhood heads, which
|
|
44
|
+
* archive order cannot reshuffle. Comparing per statement instead let an
|
|
45
|
+
* anchored insert legally sit a LOWER id/key after a higher one (its anchor
|
|
46
|
+
* decides where it lives, not its own sort key), and a later trailing walk
|
|
47
|
+
* then stopped at different statements depending on which archive had run
|
|
48
|
+
* first — order-DEPENDENT bytes for features touching disjoint services, the
|
|
49
|
+
* exact regime the scheme guarantees.
|
|
50
|
+
*/
|
|
51
|
+
export function elementSpot(region, id, service) {
|
|
52
|
+
const { text, stmts, close } = region;
|
|
53
|
+
let anchor;
|
|
54
|
+
for (const s of stmts) {
|
|
55
|
+
if (s.kind === "element" && s.services.includes(service))
|
|
56
|
+
anchor = s;
|
|
57
|
+
}
|
|
58
|
+
if (anchor !== undefined)
|
|
59
|
+
return spotAfter(text, close, anchor.end);
|
|
60
|
+
let i = stmts.length - 1;
|
|
61
|
+
let before;
|
|
62
|
+
while (i >= 0 && stmts[i].kind === "rel") {
|
|
63
|
+
before = stmts[i];
|
|
64
|
+
i -= 1;
|
|
65
|
+
}
|
|
66
|
+
while (i >= 0 && stmts[i].kind === "element") {
|
|
67
|
+
// The same-service run ending here is one unit: skip it whole or stop
|
|
68
|
+
// before it whole — an anchored member may carry any id, the run's
|
|
69
|
+
// LEADING id is its sort key.
|
|
70
|
+
let j = i;
|
|
71
|
+
while (j > 0 && stmts[j - 1].kind === "element" && stmts[j - 1].services[0] === stmts[i].services[0])
|
|
72
|
+
j -= 1;
|
|
73
|
+
if (!(stmts[j].id > id))
|
|
74
|
+
break;
|
|
75
|
+
before = stmts[j];
|
|
76
|
+
i = j - 1;
|
|
77
|
+
}
|
|
78
|
+
return before !== undefined ? beforeSpot(text, before) : closeSpot(text, close);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Each top-level statement's cluster, as the index of the cluster's leading
|
|
82
|
+
* statement. A relationship whose SOURCE's service some earlier relationship
|
|
83
|
+
* in the same contiguous stretch already touches is an anchored join — it
|
|
84
|
+
* belongs to that neighborhood wherever its own key would sort. One whose
|
|
85
|
+
* source's service nothing before it touches opens a new cluster; sharing
|
|
86
|
+
* only a TARGET does not join (every trailing edge into a busy hub would
|
|
87
|
+
* otherwise dissolve into one giant cluster, and the trailing sort with it).
|
|
88
|
+
* Recomputed from bytes alone, so the next archive sees the same units this
|
|
89
|
+
* one placed.
|
|
90
|
+
*/
|
|
91
|
+
function relClusterHeads(stmts) {
|
|
92
|
+
const heads = stmts.map((_, i) => i);
|
|
93
|
+
let head = -1;
|
|
94
|
+
let services = null;
|
|
95
|
+
for (let i = 0; i < stmts.length; i += 1) {
|
|
96
|
+
const s = stmts[i];
|
|
97
|
+
if (s.kind !== "rel") {
|
|
98
|
+
head = -1;
|
|
99
|
+
services = null;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (services !== null && services.has(s.services[0])) {
|
|
103
|
+
for (const svc of s.services)
|
|
104
|
+
services.add(svc);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
head = i;
|
|
108
|
+
services = new Set(s.services);
|
|
109
|
+
}
|
|
110
|
+
heads[i] = head;
|
|
111
|
+
}
|
|
112
|
+
return heads;
|
|
113
|
+
}
|
|
114
|
+
export function relSpot(region, sourceService, key) {
|
|
115
|
+
const { text, stmts, close } = region;
|
|
116
|
+
const heads = relClusterHeads(stmts);
|
|
117
|
+
let anchorAt = -1;
|
|
118
|
+
for (let i = 0; i < stmts.length; i += 1) {
|
|
119
|
+
const s = stmts[i];
|
|
120
|
+
if (s.kind === "rel" && s.services.includes(sourceService))
|
|
121
|
+
anchorAt = i;
|
|
122
|
+
}
|
|
123
|
+
if (anchorAt !== -1) {
|
|
124
|
+
// Past the anchor, order among the cluster's OWN members is by key — two
|
|
125
|
+
// edges anchored to the same statement must order by key, not by which
|
|
126
|
+
// archived first — but the walk never leaves the cluster: the next
|
|
127
|
+
// cluster is another neighborhood's, and stepping over it would depend
|
|
128
|
+
// on which archive put it there.
|
|
129
|
+
let last = stmts[anchorAt];
|
|
130
|
+
for (let j = anchorAt + 1; j < stmts.length; j += 1) {
|
|
131
|
+
const s = stmts[j];
|
|
132
|
+
if (s.kind !== "rel" || heads[j] !== heads[anchorAt] || s.key >= key)
|
|
133
|
+
break;
|
|
134
|
+
last = s;
|
|
135
|
+
}
|
|
136
|
+
return spotAfter(text, close, last.end);
|
|
137
|
+
}
|
|
138
|
+
let i = stmts.length - 1;
|
|
139
|
+
let before;
|
|
140
|
+
while (i >= 0 && stmts[i].kind === "rel") {
|
|
141
|
+
const j = heads[i];
|
|
142
|
+
if (!(stmts[j].key > key))
|
|
143
|
+
break;
|
|
144
|
+
before = stmts[j];
|
|
145
|
+
i = j - 1;
|
|
146
|
+
}
|
|
147
|
+
return before !== undefined ? beforeSpot(text, before) : closeSpot(text, close);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Insertion before an existing statement: at the start of its line when only
|
|
151
|
+
* whitespace precedes it there, at the statement itself — bare, displacing it
|
|
152
|
+
* onto a fresh line — when other text shares the line (a statement written on
|
|
153
|
+
* the `model {` line, say). The unguarded line start spliced the block BEFORE
|
|
154
|
+
* the model keyword, and the parse net refused a perfectly legal landscape.
|
|
155
|
+
*/
|
|
156
|
+
function beforeSpot(text, before) {
|
|
157
|
+
const ls = lineStartAt(text, before.start);
|
|
158
|
+
if (/^[ \t]*$/.test(text.slice(ls, before.start)))
|
|
159
|
+
return { at: ls, bare: false };
|
|
160
|
+
return { at: before.start, bare: true };
|
|
161
|
+
}
|
|
162
|
+
/** The line following the statement that ends at `end` — clamped to the closing brace's own spot. */
|
|
163
|
+
function spotAfter(text, close, end) {
|
|
164
|
+
const nl = text.indexOf("\n", end);
|
|
165
|
+
const at = nl === -1 ? text.length : nl + 1;
|
|
166
|
+
return at > close ? closeSpot(text, close) : { at, bare: false };
|
|
167
|
+
}
|
|
168
|
+
/** The last resort: directly before the model's closing brace. */
|
|
169
|
+
function closeSpot(text, close) {
|
|
170
|
+
const ls = lineStartAt(text, close);
|
|
171
|
+
if (/^[ \t]*$/.test(text.slice(ls, close)))
|
|
172
|
+
return { at: ls, bare: false };
|
|
173
|
+
return { at: close, bare: true };
|
|
174
|
+
}
|
|
175
|
+
function lineStartAt(text, at) {
|
|
176
|
+
return text.lastIndexOf("\n", at - 1) + 1;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Where a child block enters an existing living parent: before the closing
|
|
180
|
+
* brace of the parent's body, on its own line at the parent's inner indent —
|
|
181
|
+
* and a parent that never had a body gains one around the child.
|
|
182
|
+
*/
|
|
183
|
+
export function nestedInsert(text, parent, block) {
|
|
184
|
+
if (parent.bodyOpen === -1) {
|
|
185
|
+
return { at: parent.end, insert: ` {\n${block}\n${parent.indent}}` };
|
|
186
|
+
}
|
|
187
|
+
const lineStart = text.lastIndexOf("\n", parent.bodyClose - 1) + 1;
|
|
188
|
+
if (/^[ \t]*$/.test(text.slice(lineStart, parent.bodyClose))) {
|
|
189
|
+
return { at: lineStart, insert: `${block}\n` };
|
|
190
|
+
}
|
|
191
|
+
// `{ ... }` on one line — break the brace onto its own line to make room.
|
|
192
|
+
return { at: parent.bodyClose, insert: `\n${block}\n${parent.indent}` };
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=placement.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"placement.js","sourceRoot":"","sources":["../../../../src/core/c4/splice/placement.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Elem } from \"../likec4.js\";\nimport { elementService, serviceOf } from \"../resolve/service.js\";\nimport type { ScannedElement, ScannedModel } from \"../source-scan.js\";\nimport { relSortKey } from \"./identity/edges.js\";\n\n/* ------------------------------------------------------------------ */\n/* Landscape placement — where a top-level addition lands */\n/* ------------------------------------------------------------------ */\n\n/**\n * The placement scheme, in full. It never moves an authored byte — placement\n * only chooses where NEW spliced blocks go — and it is deterministic in a\n * stronger sense: archiving feature A then feature B (touching different\n * services) yields the SAME final bytes as B then A, which is what lets two\n * concurrent archive PRs merge in git instead of colliding on one shared\n * append point.\n *\n * - An ELEMENT whose service already has top-level elements in the model\n * (the elementService join — binding first, title as the fallback) lands\n * immediately after the last of them: it joins its service's neighborhood.\n * Two features touching the SAME service therefore land adjacently, and\n * their concurrent PRs still conflict — expected, and documented\n * (SCHEMA.md sends that conflict to the PR flow).\n * - An element opening a NEW service lands in the trailing region before the\n * model's closing brace: skip the trailing relationship run, then walk left\n * per same-service RUN, past every run whose leading id is\n * lexicographically greater — an insertion sort of neighborhood heads\n * against whatever is already there, which is what makes the region's\n * final order independent of archive order (see the note on elementSpot:\n * per-statement comparison was order-dependent).\n * - A RELATIONSHIP lands after the last top-level relationship touching its\n * SOURCE's service (either endpoint counts), then past the anchor's own\n * CLUSTER members with a smaller sort key — two edges anchored to the same\n * statement must order by key, not by which archived first. With no such\n * neighborhood it falls to the trailing region, cluster-head-key-sorted\n * the same insertion-sort way, after the trailing elements.\n *\n * LikeC4 reference resolution is not order-dependent, so a relationship\n * placed before an element it names still parses; the in-memory parse net in\n * landscape-merge.ts's planLandscapeMerge stays as the backstop regardless.\n */\nexport interface Spot {\n /** Offset into the living text: a line start, or a point inside a shared line. */\n at: number;\n /** True when `at` shares its line with other text (the closing brace, or a displaced statement) — the block must bring its own newlines. */\n bare: boolean;\n}\n\n/** A top-level statement of the living model, with everything placement asks of it. */\nexport interface TopStmt {\n kind: \"element\" | \"rel\";\n /** Element: its id. */\n id: string;\n /** Relationship: its placement sort key (relSortKey). */\n key: string;\n /** The service(s) this statement resolves to — one for an element, both endpoints for a rel. */\n services: string[];\n start: number;\n end: number;\n}\n\n/**\n * The living model as placement reads it: the text being spliced into, its\n * top-level statement layout, and the model block's closing brace. One value\n * because every spot is computed against all three at the same instant — a\n * layout paired with text a splice has since moved would place against a\n * document that no longer exists (see `modelRegion` in landscape-merge.ts's\n * planLandscapeMerge).\n */\nexport interface ModelRegion {\n text: string;\n stmts: TopStmt[];\n close: number;\n}\n\n/** The model's top-level statements in document order, joined to the parsed elements for services. */\nexport function topStatements(scan: ScannedModel, els: Elem[]): TopStmt[] {\n const topEls = scan.elements.filter((e) => !e.id.includes(\".\"));\n const bound = new Map(els.map((e) => [e.id, elementService(e)]));\n const stmts: TopStmt[] = topEls.map((e) => ({\n kind: \"element\" as const,\n id: e.id,\n key: \"\",\n services: [bound.get(e.id) ?? e.id],\n start: e.start,\n end: e.end,\n }));\n for (const r of scan.rels) {\n // A rel declared inside an element's body is not a top-level statement —\n // anchoring after it would splice into the parent's block.\n if (topEls.some((e) => r.start > e.start && r.start < e.end)) continue;\n stmts.push({\n kind: \"rel\",\n id: \"\",\n key: relSortKey(els, r),\n // Deliberately no `known` fleet set here: this resolution only decides\n // WHERE in the file a statement anchors, both sides of every comparison\n // resolve through the same call, and the merged document is re-parsed\n // before anything is written — a wrong grouping is cosmetic, never a\n // wrong merge.\n services: [serviceOf(els, r.source), serviceOf(els, r.target)],\n start: r.start,\n end: r.end,\n });\n }\n return stmts.sort((a, b) => a.start - b.start);\n}\n\n\n/**\n * The trailing walks move in PLACEMENT UNITS, never single statements. A unit\n * is a service neighborhood: a contiguous run of top-level elements resolving\n * to one service, or a contiguous cluster of relationships grown around the\n * statements touching one — keyed by its LEADING statement's id/key. Anchored\n * inserts extend a neighborhood without changing its head, so the insertion\n * sort the trailing region maintains is over neighborhood heads, which\n * archive order cannot reshuffle. Comparing per statement instead let an\n * anchored insert legally sit a LOWER id/key after a higher one (its anchor\n * decides where it lives, not its own sort key), and a later trailing walk\n * then stopped at different statements depending on which archive had run\n * first — order-DEPENDENT bytes for features touching disjoint services, the\n * exact regime the scheme guarantees.\n */\nexport function elementSpot(region: ModelRegion, id: string, service: string): Spot {\n const { text, stmts, close } = region;\n let anchor: TopStmt | undefined;\n for (const s of stmts) {\n if (s.kind === \"element\" && s.services.includes(service)) anchor = s;\n }\n if (anchor !== undefined) return spotAfter(text, close, anchor.end);\n let i = stmts.length - 1;\n let before: TopStmt | undefined;\n while (i >= 0 && stmts[i]!.kind === \"rel\") {\n before = stmts[i];\n i -= 1;\n }\n while (i >= 0 && stmts[i]!.kind === \"element\") {\n // The same-service run ending here is one unit: skip it whole or stop\n // before it whole — an anchored member may carry any id, the run's\n // LEADING id is its sort key.\n let j = i;\n while (j > 0 && stmts[j - 1]!.kind === \"element\" && stmts[j - 1]!.services[0] === stmts[i]!.services[0]) j -= 1;\n if (!(stmts[j]!.id > id)) break;\n before = stmts[j];\n i = j - 1;\n }\n return before !== undefined ? beforeSpot(text, before) : closeSpot(text, close);\n}\n\n/**\n * Each top-level statement's cluster, as the index of the cluster's leading\n * statement. A relationship whose SOURCE's service some earlier relationship\n * in the same contiguous stretch already touches is an anchored join — it\n * belongs to that neighborhood wherever its own key would sort. One whose\n * source's service nothing before it touches opens a new cluster; sharing\n * only a TARGET does not join (every trailing edge into a busy hub would\n * otherwise dissolve into one giant cluster, and the trailing sort with it).\n * Recomputed from bytes alone, so the next archive sees the same units this\n * one placed.\n */\nfunction relClusterHeads(stmts: TopStmt[]): number[] {\n const heads = stmts.map((_, i) => i);\n let head = -1;\n let services: Set<string> | null = null;\n for (let i = 0; i < stmts.length; i += 1) {\n const s = stmts[i]!;\n if (s.kind !== \"rel\") {\n head = -1;\n services = null;\n continue;\n }\n if (services !== null && services.has(s.services[0]!)) {\n for (const svc of s.services) services.add(svc);\n } else {\n head = i;\n services = new Set(s.services);\n }\n heads[i] = head;\n }\n return heads;\n}\n\nexport function relSpot(region: ModelRegion, sourceService: string, key: string): Spot {\n const { text, stmts, close } = region;\n const heads = relClusterHeads(stmts);\n let anchorAt = -1;\n for (let i = 0; i < stmts.length; i += 1) {\n const s = stmts[i]!;\n if (s.kind === \"rel\" && s.services.includes(sourceService)) anchorAt = i;\n }\n if (anchorAt !== -1) {\n // Past the anchor, order among the cluster's OWN members is by key — two\n // edges anchored to the same statement must order by key, not by which\n // archived first — but the walk never leaves the cluster: the next\n // cluster is another neighborhood's, and stepping over it would depend\n // on which archive put it there.\n let last = stmts[anchorAt]!;\n for (let j = anchorAt + 1; j < stmts.length; j += 1) {\n const s = stmts[j]!;\n if (s.kind !== \"rel\" || heads[j] !== heads[anchorAt] || s.key >= key) break;\n last = s;\n }\n return spotAfter(text, close, last.end);\n }\n let i = stmts.length - 1;\n let before: TopStmt | undefined;\n while (i >= 0 && stmts[i]!.kind === \"rel\") {\n const j = heads[i]!;\n if (!(stmts[j]!.key > key)) break;\n before = stmts[j];\n i = j - 1;\n }\n return before !== undefined ? beforeSpot(text, before) : closeSpot(text, close);\n}\n\n/**\n * Insertion before an existing statement: at the start of its line when only\n * whitespace precedes it there, at the statement itself — bare, displacing it\n * onto a fresh line — when other text shares the line (a statement written on\n * the `model {` line, say). The unguarded line start spliced the block BEFORE\n * the model keyword, and the parse net refused a perfectly legal landscape.\n */\nfunction beforeSpot(text: string, before: TopStmt): Spot {\n const ls = lineStartAt(text, before.start);\n if (/^[ \\t]*$/.test(text.slice(ls, before.start))) return { at: ls, bare: false };\n return { at: before.start, bare: true };\n}\n\n/** The line following the statement that ends at `end` — clamped to the closing brace's own spot. */\nfunction spotAfter(text: string, close: number, end: number): Spot {\n const nl = text.indexOf(\"\\n\", end);\n const at = nl === -1 ? text.length : nl + 1;\n return at > close ? closeSpot(text, close) : { at, bare: false };\n}\n\n/** The last resort: directly before the model's closing brace. */\nfunction closeSpot(text: string, close: number): Spot {\n const ls = lineStartAt(text, close);\n if (/^[ \\t]*$/.test(text.slice(ls, close))) return { at: ls, bare: false };\n return { at: close, bare: true };\n}\n\nfunction lineStartAt(text: string, at: number): number {\n return text.lastIndexOf(\"\\n\", at - 1) + 1;\n}\n\n/**\n * Where a child block enters an existing living parent: before the closing\n * brace of the parent's body, on its own line at the parent's inner indent —\n * and a parent that never had a body gains one around the child.\n */\nexport function nestedInsert(text: string, parent: ScannedElement, block: string): { at: number; insert: string } {\n if (parent.bodyOpen === -1) {\n return { at: parent.end, insert: ` {\\n${block}\\n${parent.indent}}` };\n }\n const lineStart = text.lastIndexOf(\"\\n\", parent.bodyClose - 1) + 1;\n if (/^[ \\t]*$/.test(text.slice(lineStart, parent.bodyClose))) {\n return { at: lineStart, insert: `${block}\\n` };\n }\n // `{ ... }` on one line — break the brace onto its own line to make room.\n return { at: parent.bodyClose, insert: `\\n${block}\\n${parent.indent}` };\n}\n"]}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch loader: many self-contained `.likec4` documents through ONE
|
|
3
|
+
* LikeC4/Langium workspace.
|
|
4
|
+
*
|
|
5
|
+
* `loadFile` pays a fresh workspace per call — ~100ms even warm — which is what
|
|
6
|
+
* made `validate --all` O(documents) workspace spins (13.7s median over the
|
|
7
|
+
* 120-service benchmark fixture, docs/BENCHMARKS.md). This module copies every
|
|
8
|
+
* document into a mkdtemp workspace as one single-file PROJECT each (a
|
|
9
|
+
* `likec4.config.json` per folder), calls `LikeC4.fromWorkspace` once, splits
|
|
10
|
+
* `getErrors()` per document, and flattens each clean project through the same
|
|
11
|
+
* `flattenModel` the per-file path uses. Parity — byte-identical error
|
|
12
|
+
* messages/lines and identical elements/relationships — is pinned by
|
|
13
|
+
* test/likec4-batch-parity.test.ts, which is also the tripwire for the exact
|
|
14
|
+
* likec4 version pin: multi-project workspaces are public API, but this use of
|
|
15
|
+
* them is not documented, so an upgrade must fail that suite before it lands.
|
|
16
|
+
*
|
|
17
|
+
* Isolation is load-bearing, not cosmetic: `fromSource` gives each document a
|
|
18
|
+
* workspace of its own, so an author-written `import ... from 'name'` can never
|
|
19
|
+
* resolve. In a shared workspace it COULD — a sibling project is one lucky name
|
|
20
|
+
* away — so every folder carries one crypto-random per-invocation hex token in
|
|
21
|
+
* its project name. A document cannot import what it cannot name, and the
|
|
22
|
+
* parity suite pins that a guessed sibling name errors identically in both
|
|
23
|
+
* modes.
|
|
24
|
+
*
|
|
25
|
+
* Callers own the failure story: a document that cannot be staged (unreadable,
|
|
26
|
+
* missing) is silently DROPPED from the result so the caller's ordinary
|
|
27
|
+
* per-path load reproduces today's error, and a batch-infrastructure failure
|
|
28
|
+
* (tmpdir denied — the sandboxed-runner class ROADMAP documents) rejects the
|
|
29
|
+
* whole call for the caller to catch and degrade. FleetContext.prefetchLikeC4
|
|
30
|
+
* does exactly that: findings can never change because a workspace could not be
|
|
31
|
+
* created, only the speed can.
|
|
32
|
+
*
|
|
33
|
+
* This file took core/c4/ to exactly five files, and the seam that note
|
|
34
|
+
* predicted is now open: `./project/load.ts` loads a DIRECTORY as one project,
|
|
35
|
+
* which is the opposite isolation contract and belongs beside this one rather
|
|
36
|
+
* than inside it.
|
|
37
|
+
*/
|
|
38
|
+
import { randomBytes } from "node:crypto";
|
|
39
|
+
import { inOrder } from "../kernel/concurrency.js";
|
|
40
|
+
import { copyFile, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
41
|
+
import { tmpdir } from "node:os";
|
|
42
|
+
import { basename, join, resolve } from "node:path";
|
|
43
|
+
import { LikeC4 } from "likec4";
|
|
44
|
+
import { flattenModel } from "./likec4.js";
|
|
45
|
+
import { readDynamicViews } from "./parsed/dynamic-views.js";
|
|
46
|
+
import { readViewIds } from "./parsed/view-ids.js";
|
|
47
|
+
import { readSpecification } from "./parsed/specification.js";
|
|
48
|
+
/**
|
|
49
|
+
* Stage every readable document as its own single-file project. A copy failure
|
|
50
|
+
* removes the half-made folder and drops the path — an empty project must not
|
|
51
|
+
* reach the workspace scan, and the caller's per-path load owns the error. The
|
|
52
|
+
* copy is `copyFile` (raw bytes), not read/decode/write: BOM, CRLF and even
|
|
53
|
+
* non-UTF-8 bytes reach LikeC4's own reader exactly as they would from the
|
|
54
|
+
* original file on disk.
|
|
55
|
+
*/
|
|
56
|
+
async function stage(root, targets) {
|
|
57
|
+
const token = randomBytes(8).toString("hex");
|
|
58
|
+
// Concurrent, capped, in input order (kernel/concurrency's contract): the
|
|
59
|
+
// per-document folders share nothing, and on a network-mounted docs repo
|
|
60
|
+
// the sequential form paid three round-trips per document. Nulls mark the
|
|
61
|
+
// drops so indices — and with them the p<i> project names — stay aligned.
|
|
62
|
+
const results = await inOrder([...targets.entries()], async ([i, path]) => {
|
|
63
|
+
const project = `p${i}_${token}`;
|
|
64
|
+
const folder = join(root, project);
|
|
65
|
+
try {
|
|
66
|
+
await mkdir(folder);
|
|
67
|
+
await writeFile(join(folder, "likec4.config.json"), JSON.stringify({ name: project }), "utf8");
|
|
68
|
+
await copyFile(path, join(folder, basename(path)));
|
|
69
|
+
return { path, project };
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
await rm(folder, { recursive: true, force: true });
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
return results.filter((r) => r !== null);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* `getErrors()` split per document, each error's `sourceFsPath` rewritten from
|
|
80
|
+
* the workspace copy to the document's real absolute path.
|
|
81
|
+
*
|
|
82
|
+
* Grouping keys on the unique `p<i>_<token>` path SEGMENT, never on a
|
|
83
|
+
* tmp-root prefix compare: darwin hands back realpathed workspace paths
|
|
84
|
+
* (`/var/...` becomes `/private/var/...`) in some environments and the literal
|
|
85
|
+
* mkdtemp path in others, and a prefix compare silently attributes every error
|
|
86
|
+
* to nobody in exactly one of those two worlds. The segment survives both.
|
|
87
|
+
*/
|
|
88
|
+
function groupErrors(likec4, staged) {
|
|
89
|
+
const pathOf = new Map(staged.map((doc) => [doc.project, doc.path]));
|
|
90
|
+
const grouped = new Map();
|
|
91
|
+
for (const err of likec4.getErrors()) {
|
|
92
|
+
const project = err.sourceFsPath.split(/[\\/]/).find((segment) => pathOf.has(segment));
|
|
93
|
+
if (project === undefined) {
|
|
94
|
+
// An error this walk cannot attribute would otherwise be DROPPED — and a
|
|
95
|
+
// dropped error grades its document clean, which is failing open in the
|
|
96
|
+
// fleet gate. Unreachable at the pinned likec4 version (every corpus
|
|
97
|
+
// error carries its project folder in sourceFsPath), so the one place a
|
|
98
|
+
// dependency upgrade could break the attribution is the one place that
|
|
99
|
+
// must reject the whole batch: the caller degrades to per-path loads,
|
|
100
|
+
// where the error reappears.
|
|
101
|
+
throw new Error(`unattributable LikeC4 error in batch workspace: ${err.message}`);
|
|
102
|
+
}
|
|
103
|
+
const list = grouped.get(project) ?? [];
|
|
104
|
+
list.push({ ...err, sourceFsPath: pathOf.get(project) });
|
|
105
|
+
grouped.set(project, list);
|
|
106
|
+
}
|
|
107
|
+
return grouped;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Load and validate many self-contained `.likec4` documents in one workspace.
|
|
111
|
+
* Returns `resolve(path)` → LoadedDoc; paths are deduped through the same
|
|
112
|
+
* resolution, and a path that could not be staged is absent from the result.
|
|
113
|
+
*
|
|
114
|
+
* A document with errors keeps `loadSource`'s exact rule — empty elements and
|
|
115
|
+
* relationships, WITHOUT calling `parsedModel`: the parsed stage succeeds even
|
|
116
|
+
* over unresolved references, so the "errors mean no model" boundary is loam's
|
|
117
|
+
* to apply, not LikeC4's. A clean document is flattened from
|
|
118
|
+
* `parsedModel(project)` — parsed, never computed: the computed stage RESOLVES
|
|
119
|
+
* every view against the model — which loam never wants, because it reads a
|
|
120
|
+
* view's DECLARATIONS and never a view's resolved contents (docs/DESIGN.md
|
|
121
|
+
* rule 26) — and which is superlinear in edge count (see loadSource).
|
|
122
|
+
*/
|
|
123
|
+
export async function loadBatch(paths) {
|
|
124
|
+
const targets = [...new Set(paths.map((path) => resolve(path)))];
|
|
125
|
+
const out = new Map();
|
|
126
|
+
if (targets.length === 0)
|
|
127
|
+
return out;
|
|
128
|
+
const root = await mkdtemp(join(tmpdir(), "loam-c4-"));
|
|
129
|
+
try {
|
|
130
|
+
const staged = await stage(root, targets);
|
|
131
|
+
if (staged.length === 0)
|
|
132
|
+
return out;
|
|
133
|
+
// One workspace for the whole batch — the entire point. The logger is
|
|
134
|
+
// disabled exactly as in loadSource: diagnostics are returned per document,
|
|
135
|
+
// not written out-of-band to stderr (core never prints).
|
|
136
|
+
const likec4 = await LikeC4.fromWorkspace(root, { logger: false });
|
|
137
|
+
try {
|
|
138
|
+
const grouped = groupErrors(likec4, staged);
|
|
139
|
+
for (const { path, project } of staged) {
|
|
140
|
+
const errors = grouped.get(project) ?? [];
|
|
141
|
+
if (errors.length > 0) {
|
|
142
|
+
out.set(path, { errors, elements: [], relationships: [] });
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
const model = (await likec4.parsedModel(project));
|
|
146
|
+
// The specification travels with the batch loader too, and must: this
|
|
147
|
+
// is the loader `validate --all` reads the landscape through, so a
|
|
148
|
+
// kind-tag check wired only into `loadSource` would be a check the
|
|
149
|
+
// fleet gate — the one run it exists for — never actually ran.
|
|
150
|
+
out.set(path, {
|
|
151
|
+
errors,
|
|
152
|
+
specification: readSpecification(model.specification),
|
|
153
|
+
views: readDynamicViews(model),
|
|
154
|
+
viewIds: readViewIds(model),
|
|
155
|
+
...flattenModel(model),
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
await likec4.dispose();
|
|
162
|
+
}
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
// The workspace is this invocation's alone; nothing else may observe it.
|
|
167
|
+
// A kill between mkdtemp and here can strand a loam-c4-* dir in OS tmp —
|
|
168
|
+
// never in the docs repo — which docs/DESIGN.md records as accepted.
|
|
169
|
+
await rm(root, { recursive: true, force: true });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=workspace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.js","sourceRoot":"","sources":["../../../src/core/c4/workspace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC3E,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,YAAY,EAAwD,MAAM,aAAa,CAAC;AACjG,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAQ9D;;;;;;;GAOG;AACH,KAAK,UAAU,KAAK,CAAC,IAAY,EAAE,OAAiB;IAClD,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC7C,0EAA0E;IAC1E,yEAAyE;IACzE,0EAA0E;IAC1E,0EAA0E;IAC1E,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE;QACxE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,KAAK,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACnC,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,MAAM,CAAC,CAAC;YACpB,MAAM,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,oBAAoB,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;YAC/F,MAAM,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACnD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACnD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;IACH,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAkB,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,WAAW,CAAC,MAAc,EAAE,MAAmB;IACtD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACrE,MAAM,OAAO,GAAG,IAAI,GAAG,EAAyB,CAAC;IACjD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC;QACrC,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;QACvF,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,yEAAyE;YACzE,wEAAwE;YACxE,qEAAqE;YACrE,wEAAwE;YACxE,uEAAuE;YACvE,sEAAsE;YACtE,6BAA6B;YAC7B,MAAM,IAAI,KAAK,CAAC,mDAAmD,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACpF,CAAC;QACD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QACxC,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,EAAE,YAAY,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACzD,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,KAAe;IAC7C,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACjE,MAAM,GAAG,GAAG,IAAI,GAAG,EAAqB,CAAC;IACzC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC;IACrC,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC;IACvD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC1C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,GAAG,CAAC;QACpC,sEAAsE;QACtE,4EAA4E;QAC5E,yDAAyD;QACzD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QACnE,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAC5C,KAAK,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,MAAM,EAAE,CAAC;gBACvC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;gBAC1C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACtB,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC,CAAC;gBAC7D,CAAC;qBAAM,CAAC;oBACN,MAAM,KAAK,GAAG,CAAC,MAAM,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAkB,CAAC;oBACnE,sEAAsE;oBACtE,mEAAmE;oBACnE,mEAAmE;oBACnE,+DAA+D;oBAC/D,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE;wBACZ,MAAM;wBACN,aAAa,EAAE,iBAAiB,CAAC,KAAK,CAAC,aAAa,CAAC;wBACrD,KAAK,EAAE,gBAAgB,CAAC,KAAK,CAAC;wBAC9B,OAAO,EAAE,WAAW,CAAC,KAAK,CAAC;wBAC3B,GAAG,YAAY,CAAC,KAAK,CAAC;qBACvB,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;QACzB,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;YAAS,CAAC;QACT,yEAAyE;QACzE,yEAAyE;QACzE,qEAAqE;QACrE,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;AACH,CAAC","sourcesContent":["/**\n * Batch loader: many self-contained `.likec4` documents through ONE\n * LikeC4/Langium workspace.\n *\n * `loadFile` pays a fresh workspace per call — ~100ms even warm — which is what\n * made `validate --all` O(documents) workspace spins (13.7s median over the\n * 120-service benchmark fixture, docs/BENCHMARKS.md). This module copies every\n * document into a mkdtemp workspace as one single-file PROJECT each (a\n * `likec4.config.json` per folder), calls `LikeC4.fromWorkspace` once, splits\n * `getErrors()` per document, and flattens each clean project through the same\n * `flattenModel` the per-file path uses. Parity — byte-identical error\n * messages/lines and identical elements/relationships — is pinned by\n * test/likec4-batch-parity.test.ts, which is also the tripwire for the exact\n * likec4 version pin: multi-project workspaces are public API, but this use of\n * them is not documented, so an upgrade must fail that suite before it lands.\n *\n * Isolation is load-bearing, not cosmetic: `fromSource` gives each document a\n * workspace of its own, so an author-written `import ... from 'name'` can never\n * resolve. In a shared workspace it COULD — a sibling project is one lucky name\n * away — so every folder carries one crypto-random per-invocation hex token in\n * its project name. A document cannot import what it cannot name, and the\n * parity suite pins that a guessed sibling name errors identically in both\n * modes.\n *\n * Callers own the failure story: a document that cannot be staged (unreadable,\n * missing) is silently DROPPED from the result so the caller's ordinary\n * per-path load reproduces today's error, and a batch-infrastructure failure\n * (tmpdir denied — the sandboxed-runner class ROADMAP documents) rejects the\n * whole call for the caller to catch and degrade. FleetContext.prefetchLikeC4\n * does exactly that: findings can never change because a workspace could not be\n * created, only the speed can.\n *\n * This file took core/c4/ to exactly five files, and the seam that note\n * predicted is now open: `./project/load.ts` loads a DIRECTORY as one project,\n * which is the opposite isolation contract and belongs beside this one rather\n * than inside it.\n */\nimport { randomBytes } from \"node:crypto\";\nimport { inOrder } from \"../kernel/concurrency.js\";\nimport { copyFile, mkdir, mkdtemp, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { basename, join, resolve } from \"node:path\";\nimport { LikeC4 } from \"likec4\";\nimport { flattenModel, type LikeC4Error, type LoadedDoc, type ReadableModel } from \"./likec4.js\";\nimport { readDynamicViews } from \"./parsed/dynamic-views.js\";\nimport { readViewIds } from \"./parsed/view-ids.js\";\nimport { readSpecification } from \"./parsed/specification.js\";\n\n/** A staged document: the real path it came from, and its workspace project. */\ninterface StagedDoc {\n path: string;\n project: string;\n}\n\n/**\n * Stage every readable document as its own single-file project. A copy failure\n * removes the half-made folder and drops the path — an empty project must not\n * reach the workspace scan, and the caller's per-path load owns the error. The\n * copy is `copyFile` (raw bytes), not read/decode/write: BOM, CRLF and even\n * non-UTF-8 bytes reach LikeC4's own reader exactly as they would from the\n * original file on disk.\n */\nasync function stage(root: string, targets: string[]): Promise<StagedDoc[]> {\n const token = randomBytes(8).toString(\"hex\");\n // Concurrent, capped, in input order (kernel/concurrency's contract): the\n // per-document folders share nothing, and on a network-mounted docs repo\n // the sequential form paid three round-trips per document. Nulls mark the\n // drops so indices — and with them the p<i> project names — stay aligned.\n const results = await inOrder([...targets.entries()], async ([i, path]) => {\n const project = `p${i}_${token}`;\n const folder = join(root, project);\n try {\n await mkdir(folder);\n await writeFile(join(folder, \"likec4.config.json\"), JSON.stringify({ name: project }), \"utf8\");\n await copyFile(path, join(folder, basename(path)));\n return { path, project };\n } catch {\n await rm(folder, { recursive: true, force: true });\n return null;\n }\n });\n return results.filter((r): r is StagedDoc => r !== null);\n}\n\n/**\n * `getErrors()` split per document, each error's `sourceFsPath` rewritten from\n * the workspace copy to the document's real absolute path.\n *\n * Grouping keys on the unique `p<i>_<token>` path SEGMENT, never on a\n * tmp-root prefix compare: darwin hands back realpathed workspace paths\n * (`/var/...` becomes `/private/var/...`) in some environments and the literal\n * mkdtemp path in others, and a prefix compare silently attributes every error\n * to nobody in exactly one of those two worlds. The segment survives both.\n */\nfunction groupErrors(likec4: LikeC4, staged: StagedDoc[]): Map<string, LikeC4Error[]> {\n const pathOf = new Map(staged.map((doc) => [doc.project, doc.path]));\n const grouped = new Map<string, LikeC4Error[]>();\n for (const err of likec4.getErrors()) {\n const project = err.sourceFsPath.split(/[\\\\/]/).find((segment) => pathOf.has(segment));\n if (project === undefined) {\n // An error this walk cannot attribute would otherwise be DROPPED — and a\n // dropped error grades its document clean, which is failing open in the\n // fleet gate. Unreachable at the pinned likec4 version (every corpus\n // error carries its project folder in sourceFsPath), so the one place a\n // dependency upgrade could break the attribution is the one place that\n // must reject the whole batch: the caller degrades to per-path loads,\n // where the error reappears.\n throw new Error(`unattributable LikeC4 error in batch workspace: ${err.message}`);\n }\n const list = grouped.get(project) ?? [];\n list.push({ ...err, sourceFsPath: pathOf.get(project) });\n grouped.set(project, list);\n }\n return grouped;\n}\n\n/**\n * Load and validate many self-contained `.likec4` documents in one workspace.\n * Returns `resolve(path)` → LoadedDoc; paths are deduped through the same\n * resolution, and a path that could not be staged is absent from the result.\n *\n * A document with errors keeps `loadSource`'s exact rule — empty elements and\n * relationships, WITHOUT calling `parsedModel`: the parsed stage succeeds even\n * over unresolved references, so the \"errors mean no model\" boundary is loam's\n * to apply, not LikeC4's. A clean document is flattened from\n * `parsedModel(project)` — parsed, never computed: the computed stage RESOLVES\n * every view against the model — which loam never wants, because it reads a\n * view's DECLARATIONS and never a view's resolved contents (docs/DESIGN.md\n * rule 26) — and which is superlinear in edge count (see loadSource).\n */\nexport async function loadBatch(paths: string[]): Promise<Map<string, LoadedDoc>> {\n const targets = [...new Set(paths.map((path) => resolve(path)))];\n const out = new Map<string, LoadedDoc>();\n if (targets.length === 0) return out;\n const root = await mkdtemp(join(tmpdir(), \"loam-c4-\"));\n try {\n const staged = await stage(root, targets);\n if (staged.length === 0) return out;\n // One workspace for the whole batch — the entire point. The logger is\n // disabled exactly as in loadSource: diagnostics are returned per document,\n // not written out-of-band to stderr (core never prints).\n const likec4 = await LikeC4.fromWorkspace(root, { logger: false });\n try {\n const grouped = groupErrors(likec4, staged);\n for (const { path, project } of staged) {\n const errors = grouped.get(project) ?? [];\n if (errors.length > 0) {\n out.set(path, { errors, elements: [], relationships: [] });\n } else {\n const model = (await likec4.parsedModel(project)) as ReadableModel;\n // The specification travels with the batch loader too, and must: this\n // is the loader `validate --all` reads the landscape through, so a\n // kind-tag check wired only into `loadSource` would be a check the\n // fleet gate — the one run it exists for — never actually ran.\n out.set(path, {\n errors,\n specification: readSpecification(model.specification),\n views: readDynamicViews(model),\n viewIds: readViewIds(model),\n ...flattenModel(model),\n });\n }\n }\n } finally {\n await likec4.dispose();\n }\n return out;\n } finally {\n // The workspace is this invocation's alone; nothing else may observe it.\n // A kill between mkdtemp and here can strand a loam-c4-* dir in OS tmp —\n // never in the docs repo — which docs/DESIGN.md records as accepted.\n await rm(root, { recursive: true, force: true });\n }\n}\n"]}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `loam new --capability <id>` says about the capability it just scaffolded
|
|
3
|
+
* a delta for.
|
|
4
|
+
*
|
|
5
|
+
* NOTES, NEVER REFUSALS, and the whole judgement is in that word. A capability
|
|
6
|
+
* the fleet has never named is exactly what an analyst opening a new business
|
|
7
|
+
* area types, and it is the case the flag exists for: `--touches <services>`
|
|
8
|
+
* asks for the services before the business change is written, and inverting
|
|
9
|
+
* that only works if naming a new promise is the ordinary path rather than the
|
|
10
|
+
* refused one. The archive that lands this feature is what creates
|
|
11
|
+
* `capabilities/<id>/spec.md` (`commands/archive/plan/specs.ts`), so there is
|
|
12
|
+
* nothing to declare first.
|
|
13
|
+
*
|
|
14
|
+
* THE OTHER READING STAYS AVAILABLE IN THE MESSAGE. The failure a refusal would
|
|
15
|
+
* have caught is the typo — `payments/refund` against a living
|
|
16
|
+
* `payments/refunds` is a second capability created out of nothing, with the
|
|
17
|
+
* promise filed where nobody looks — so the close names are spelled out
|
|
18
|
+
* alongside, exactly as `--touches`' own near-miss note spells them, together
|
|
19
|
+
* with the way back out. That is the same trade `unknownServiceNotes` makes in
|
|
20
|
+
* `commands/new/new.ts`, and it is deliberate that the two flags behave alike:
|
|
21
|
+
* "names something that does not exist yet" is legitimate on both.
|
|
22
|
+
*
|
|
23
|
+
* THE NEAR-MISS RULE IS `nearestIds`, NOT `closeIds`, and on this axis the two
|
|
24
|
+
* disagree loudly. `closeIds` matches on substring either way, so
|
|
25
|
+
* `payments/refunds` reports its own PARENT `payments` as a candidate — nesting
|
|
26
|
+
* spelled by the tree is the axis's headline shape, and a note telling an author
|
|
27
|
+
* their new sub-capability is a misspelling of the group it sits in is worse
|
|
28
|
+
* than no note. `nearestIds` is the edit-distance "did you misspell a directory"
|
|
29
|
+
* rule, which is the question actually being asked here, and
|
|
30
|
+
* `core/repo/entries.ts` states that division at its own definition.
|
|
31
|
+
*
|
|
32
|
+
* THE WAY OUT NAMES THIS FEATURE'S DIRECTORY, never `features/`. loam ships
|
|
33
|
+
* instructions people and agents type back; `features/` holds every in-flight
|
|
34
|
+
* feature in a SHARED docs repo, so an instruction to delete it is an
|
|
35
|
+
* instruction to destroy other authors' work. `dirName` is threaded in for that
|
|
36
|
+
* one sentence — the same care `validate/feature.ts` takes to spell
|
|
37
|
+
* `features/FEAT-1-split/` rather than `features/FEAT-1/`.
|
|
38
|
+
*
|
|
39
|
+
* WHAT IT DOES NOT SAY, and why not: which services realize this capability
|
|
40
|
+
* today. `capabilityRollup` knows, and the line would be useful, but it walks
|
|
41
|
+
* every service's `spec.md` AND `arch.spec.md` across the whole fleet — a cost
|
|
42
|
+
* proportional to the service tree, paid by a command whose job is to create
|
|
43
|
+
* four files, and `loam new` sits in the authoring hot path. `loam list
|
|
44
|
+
* capabilities` already computes exactly that answer and the note points at it.
|
|
45
|
+
* The half that IS read here is the one an author cannot write the delta
|
|
46
|
+
* without — the living document's own requirement ids, which is what a MODIFIED
|
|
47
|
+
* section addresses and what an ADDED section must not repeat — and that is one
|
|
48
|
+
* file, the very file this delta diffs against.
|
|
49
|
+
*
|
|
50
|
+
* IN CORE, not beside the command, for the rule the command layer is held to:
|
|
51
|
+
* `commands/` parses arguments, calls core and prints. Which of a fleet's two
|
|
52
|
+
* vocabularies declared an id, and which of a capability's requirements are
|
|
53
|
+
* addressable, are questions about the DOCUMENTS — the same questions
|
|
54
|
+
* `../findings.ts` answers as findings — and the only thing this module does
|
|
55
|
+
* differently is answer them for a person who has not written anything yet.
|
|
56
|
+
*/
|
|
57
|
+
import { readFile } from "node:fs/promises";
|
|
58
|
+
import { compareIds, nearestIds } from "../../repo/entries.js";
|
|
59
|
+
import { parseRequirements } from "../../document/parse.js";
|
|
60
|
+
import { readCapabilityVocabulary } from "../capabilities.js";
|
|
61
|
+
/**
|
|
62
|
+
* One note per `--capability` id: is this promise already declared, and if so
|
|
63
|
+
* what does it already say?
|
|
64
|
+
*
|
|
65
|
+
* The vocabulary read is the UNION of `architecture/capabilities.yaml` and the
|
|
66
|
+
* `capabilities/` tree, never one side, for the reason `capabilities.ts` states:
|
|
67
|
+
* a second spelling of "the vocabulary" is a second answer to whether an id is
|
|
68
|
+
* known, and this note and `capability.unknown` must not be able to disagree.
|
|
69
|
+
*/
|
|
70
|
+
export async function capabilityNotes(docsDir, scope) {
|
|
71
|
+
if (scope.ids.length === 0)
|
|
72
|
+
return [];
|
|
73
|
+
const vocab = await readCapabilityVocabulary(docsDir);
|
|
74
|
+
const declared = [...vocab.byId.keys()].sort(compareIds);
|
|
75
|
+
const notes = [];
|
|
76
|
+
for (const id of scope.ids) {
|
|
77
|
+
const known = vocab.byId.get(id);
|
|
78
|
+
if (known === undefined) {
|
|
79
|
+
const near = nearestIds(id, declared);
|
|
80
|
+
notes.push(`--capability '${id}' is a capability this fleet has not named yet — nothing declares it in ` +
|
|
81
|
+
`architecture/capabilities.yaml or capabilities/${id}/spec.md. That is legal and often the point: ` +
|
|
82
|
+
`archiving this feature CREATES capabilities/${id}/spec.md from the delta just scaffolded` +
|
|
83
|
+
(near.length === 0
|
|
84
|
+
? "."
|
|
85
|
+
: `. If you meant an existing one — ${near.map((n) => `'${n}'`).join(" or ")} — rename ` +
|
|
86
|
+
`features/${scope.dirName}/capabilities/${id}/ to that spelling, or delete features/${scope.dirName}/ and re-run \`loam new\`.`));
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (known.spec === undefined) {
|
|
90
|
+
// Declared in the YAML with no document behind it: the ordinary
|
|
91
|
+
// mid-adoption state, and the one case where this feature is writing the
|
|
92
|
+
// fleet's first prose for a word it already had.
|
|
93
|
+
notes.push(`--capability '${id}' is declared in architecture/capabilities.yaml but has no capabilities/${id}/spec.md yet — ` +
|
|
94
|
+
"this feature's delta is the first prose behind that word, and archiving it writes the document.");
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
notes.push(livingNote(id, await requirementIds(known.spec)));
|
|
98
|
+
}
|
|
99
|
+
return notes;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The living document's requirement ids, in document order.
|
|
103
|
+
*
|
|
104
|
+
* A requirement WITHOUT a `Requirement-ID:` is skipped rather than named by its
|
|
105
|
+
* heading, matching every other reader on this axis: nothing can address it, so
|
|
106
|
+
* offering the heading as an address would teach exactly the identity-by-heading
|
|
107
|
+
* `capability.requirement-unidentified` refuses.
|
|
108
|
+
*
|
|
109
|
+
* An unreadable document is NOT fatal here. This is a note printed after the
|
|
110
|
+
* scaffold has already committed, and the alternative — throwing out of a
|
|
111
|
+
* successful `loam new` — would report the write as a failure. `loam validate`
|
|
112
|
+
* owns the diagnosis of a living document nobody can read.
|
|
113
|
+
*/
|
|
114
|
+
async function requirementIds(spec) {
|
|
115
|
+
try {
|
|
116
|
+
return parseRequirements(await readFile(spec, "utf8")).flatMap((r) => r.kind === "REMOVED" || r.id === undefined ? [] : [r.id]);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** The note for a capability that already has a document — what the delta is a diff against. */
|
|
123
|
+
function livingNote(id, ids) {
|
|
124
|
+
const head = `--capability '${id}' already has capabilities/${id}/spec.md`;
|
|
125
|
+
if (ids === null)
|
|
126
|
+
return `${head}, which could not be read — run \`loam validate --all\` before authoring against it.`;
|
|
127
|
+
if (ids.length === 0) {
|
|
128
|
+
return (`${head}, and it declares no identified requirements yet — everything in this delta is an ADDED promise. ` +
|
|
129
|
+
"`loam list capabilities` shows what the fleet already realizes.");
|
|
130
|
+
}
|
|
131
|
+
// The ids, not a count: a MODIFIED section addresses one of these and an
|
|
132
|
+
// ADDED one must not repeat it, so the list IS the thing the author needs in
|
|
133
|
+
// front of them. Capped, because a long-lived capability can carry dozens and
|
|
134
|
+
// a note that scrolls is a note nobody reads.
|
|
135
|
+
const shown = ids.slice(0, 8);
|
|
136
|
+
return (`${head}, declaring ${ids.length} identified requirement(s): ${shown.join(", ")}` +
|
|
137
|
+
(ids.length > shown.length ? `, … (\`loam show\` for the rest)` : "") +
|
|
138
|
+
". A MODIFIED section addresses one of those ids; an ADDED one must not repeat it. " +
|
|
139
|
+
"`loam list capabilities` shows which services realize them today.");
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=notes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notes.js","sourceRoot":"","sources":["../../../../src/core/capabilities/authoring/notes.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What `loam new --capability <id>` says about the capability it just scaffolded\n * a delta for.\n *\n * NOTES, NEVER REFUSALS, and the whole judgement is in that word. A capability\n * the fleet has never named is exactly what an analyst opening a new business\n * area types, and it is the case the flag exists for: `--touches <services>`\n * asks for the services before the business change is written, and inverting\n * that only works if naming a new promise is the ordinary path rather than the\n * refused one. The archive that lands this feature is what creates\n * `capabilities/<id>/spec.md` (`commands/archive/plan/specs.ts`), so there is\n * nothing to declare first.\n *\n * THE OTHER READING STAYS AVAILABLE IN THE MESSAGE. The failure a refusal would\n * have caught is the typo — `payments/refund` against a living\n * `payments/refunds` is a second capability created out of nothing, with the\n * promise filed where nobody looks — so the close names are spelled out\n * alongside, exactly as `--touches`' own near-miss note spells them, together\n * with the way back out. That is the same trade `unknownServiceNotes` makes in\n * `commands/new/new.ts`, and it is deliberate that the two flags behave alike:\n * \"names something that does not exist yet\" is legitimate on both.\n *\n * THE NEAR-MISS RULE IS `nearestIds`, NOT `closeIds`, and on this axis the two\n * disagree loudly. `closeIds` matches on substring either way, so\n * `payments/refunds` reports its own PARENT `payments` as a candidate — nesting\n * spelled by the tree is the axis's headline shape, and a note telling an author\n * their new sub-capability is a misspelling of the group it sits in is worse\n * than no note. `nearestIds` is the edit-distance \"did you misspell a directory\"\n * rule, which is the question actually being asked here, and\n * `core/repo/entries.ts` states that division at its own definition.\n *\n * THE WAY OUT NAMES THIS FEATURE'S DIRECTORY, never `features/`. loam ships\n * instructions people and agents type back; `features/` holds every in-flight\n * feature in a SHARED docs repo, so an instruction to delete it is an\n * instruction to destroy other authors' work. `dirName` is threaded in for that\n * one sentence — the same care `validate/feature.ts` takes to spell\n * `features/FEAT-1-split/` rather than `features/FEAT-1/`.\n *\n * WHAT IT DOES NOT SAY, and why not: which services realize this capability\n * today. `capabilityRollup` knows, and the line would be useful, but it walks\n * every service's `spec.md` AND `arch.spec.md` across the whole fleet — a cost\n * proportional to the service tree, paid by a command whose job is to create\n * four files, and `loam new` sits in the authoring hot path. `loam list\n * capabilities` already computes exactly that answer and the note points at it.\n * The half that IS read here is the one an author cannot write the delta\n * without — the living document's own requirement ids, which is what a MODIFIED\n * section addresses and what an ADDED section must not repeat — and that is one\n * file, the very file this delta diffs against.\n *\n * IN CORE, not beside the command, for the rule the command layer is held to:\n * `commands/` parses arguments, calls core and prints. Which of a fleet's two\n * vocabularies declared an id, and which of a capability's requirements are\n * addressable, are questions about the DOCUMENTS — the same questions\n * `../findings.ts` answers as findings — and the only thing this module does\n * differently is answer them for a person who has not written anything yet.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { compareIds, nearestIds } from \"../../repo/entries.js\";\nimport { parseRequirements } from \"../../document/parse.js\";\nimport { readCapabilityVocabulary } from \"../capabilities.js\";\nimport type { DocsDir } from \"../../kernel/ids/dirs.js\";\n\n/** Which feature was just scaffolded, and which promises it named. */\nexport interface CapabilityNoteScope {\n /** The feature's directory NAME (`FEAT-1-split`) — what the way-out sentence spells. */\n dirName: string;\n /** The `--capability` ids, in the order they were given. */\n ids: readonly string[];\n}\n\n/**\n * One note per `--capability` id: is this promise already declared, and if so\n * what does it already say?\n *\n * The vocabulary read is the UNION of `architecture/capabilities.yaml` and the\n * `capabilities/` tree, never one side, for the reason `capabilities.ts` states:\n * a second spelling of \"the vocabulary\" is a second answer to whether an id is\n * known, and this note and `capability.unknown` must not be able to disagree.\n */\nexport async function capabilityNotes(docsDir: DocsDir, scope: CapabilityNoteScope): Promise<string[]> {\n if (scope.ids.length === 0) return [];\n const vocab = await readCapabilityVocabulary(docsDir);\n const declared = [...vocab.byId.keys()].sort(compareIds);\n const notes: string[] = [];\n for (const id of scope.ids) {\n const known = vocab.byId.get(id);\n if (known === undefined) {\n const near = nearestIds(id, declared);\n notes.push(\n `--capability '${id}' is a capability this fleet has not named yet — nothing declares it in ` +\n `architecture/capabilities.yaml or capabilities/${id}/spec.md. That is legal and often the point: ` +\n `archiving this feature CREATES capabilities/${id}/spec.md from the delta just scaffolded` +\n (near.length === 0\n ? \".\"\n : `. If you meant an existing one — ${near.map((n) => `'${n}'`).join(\" or \")} — rename ` +\n `features/${scope.dirName}/capabilities/${id}/ to that spelling, or delete features/${scope.dirName}/ and re-run \\`loam new\\`.`),\n );\n continue;\n }\n if (known.spec === undefined) {\n // Declared in the YAML with no document behind it: the ordinary\n // mid-adoption state, and the one case where this feature is writing the\n // fleet's first prose for a word it already had.\n notes.push(\n `--capability '${id}' is declared in architecture/capabilities.yaml but has no capabilities/${id}/spec.md yet — ` +\n \"this feature's delta is the first prose behind that word, and archiving it writes the document.\",\n );\n continue;\n }\n notes.push(livingNote(id, await requirementIds(known.spec)));\n }\n return notes;\n}\n\n/**\n * The living document's requirement ids, in document order.\n *\n * A requirement WITHOUT a `Requirement-ID:` is skipped rather than named by its\n * heading, matching every other reader on this axis: nothing can address it, so\n * offering the heading as an address would teach exactly the identity-by-heading\n * `capability.requirement-unidentified` refuses.\n *\n * An unreadable document is NOT fatal here. This is a note printed after the\n * scaffold has already committed, and the alternative — throwing out of a\n * successful `loam new` — would report the write as a failure. `loam validate`\n * owns the diagnosis of a living document nobody can read.\n */\nasync function requirementIds(spec: string): Promise<string[] | null> {\n try {\n return parseRequirements(await readFile(spec, \"utf8\")).flatMap((r) =>\n r.kind === \"REMOVED\" || r.id === undefined ? [] : [r.id],\n );\n } catch {\n return null;\n }\n}\n\n/** The note for a capability that already has a document — what the delta is a diff against. */\nfunction livingNote(id: string, ids: string[] | null): string {\n const head = `--capability '${id}' already has capabilities/${id}/spec.md`;\n if (ids === null) return `${head}, which could not be read — run \\`loam validate --all\\` before authoring against it.`;\n if (ids.length === 0) {\n return (\n `${head}, and it declares no identified requirements yet — everything in this delta is an ADDED promise. ` +\n \"`loam list capabilities` shows what the fleet already realizes.\"\n );\n }\n // The ids, not a count: a MODIFIED section addresses one of these and an\n // ADDED one must not repeat it, so the list IS the thing the author needs in\n // front of them. Capped, because a long-lived capability can carry dozens and\n // a note that scrolls is a note nobody reads.\n const shown = ids.slice(0, 8);\n return (\n `${head}, declaring ${ids.length} identified requirement(s): ${shown.join(\", \")}` +\n (ids.length > shown.length ? `, … (\\`loam show\\` for the rest)` : \"\") +\n \". A MODIFIED section addresses one of those ids; an ADDED one must not repeat it. \" +\n \"`loam list capabilities` shows which services realize them today.\"\n );\n}\n"]}
|