@ybotok/loam 0.1.0-beta.3 → 0.2.0-alpha.13
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 +5992 -82
- package/COMPARISON.md +168 -63
- package/CONTRIBUTING.md +121 -0
- package/MIGRATING-from-OpenSpec.md +238 -55
- package/QUICKSTART.md +302 -0
- package/README.md +653 -144
- package/ROADMAP.md +1035 -0
- package/SCHEMA.md +3916 -263
- package/SECURITY.md +21 -3
- package/WORKFLOW.md +927 -0
- package/dist/cli.js +133 -21
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +204 -0
- package/dist/commands/adopt/adopt.js.map +1 -0
- package/dist/commands/adopt/render.js +309 -0
- package/dist/commands/adopt/render.js.map +1 -0
- package/dist/commands/archive/archive.js +74 -0
- package/dist/commands/archive/archive.js.map +1 -0
- package/dist/commands/archive/closing.js +42 -0
- package/dist/commands/archive/closing.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +172 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
- package/dist/commands/archive/plan/contracts/consumers.js +56 -0
- package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +193 -0
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
- package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
- package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
- package/dist/commands/archive/plan/document/head.js +151 -0
- package/dist/commands/archive/plan/document/head.js.map +1 -0
- package/dist/commands/archive/plan/document/links.js +284 -0
- package/dist/commands/archive/plan/document/links.js.map +1 -0
- package/dist/commands/archive/plan/document/run.js +136 -0
- package/dist/commands/archive/plan/document/run.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +319 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +298 -0
- package/dist/commands/archive/plan/landscape.js.map +1 -0
- package/dist/commands/archive/plan/model/extending.js +85 -0
- package/dist/commands/archive/plan/model/extending.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +106 -0
- package/dist/commands/archive/plan/refusal.js.map +1 -0
- package/dist/commands/archive/plan/specs.js +318 -0
- package/dist/commands/archive/plan/specs.js.map +1 -0
- package/dist/commands/archive/plan/state.js +30 -0
- package/dist/commands/archive/plan/state.js.map +1 -0
- package/dist/commands/archive/run.js +355 -0
- package/dist/commands/archive/run.js.map +1 -0
- package/dist/commands/archive/shipped.js +49 -0
- package/dist/commands/archive/shipped.js.map +1 -0
- package/dist/commands/context/context.js +166 -0
- package/dist/commands/context/context.js.map +1 -0
- package/dist/commands/context/print.js +305 -0
- package/dist/commands/context/print.js.map +1 -0
- package/dist/commands/delta/delta.js +367 -0
- package/dist/commands/delta/delta.js.map +1 -0
- package/dist/commands/delta/print.js +221 -0
- package/dist/commands/delta/print.js.map +1 -0
- package/dist/commands/dependencies.js +6 -8
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +86 -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 +65 -5
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +227 -0
- package/dist/commands/explain/explain.js.map +1 -0
- package/dist/commands/explore.js +39 -28
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +152 -0
- package/dist/commands/gate/gate.js.map +1 -0
- package/dist/commands/gate/print.js +66 -0
- package/dist/commands/gate/print.js.map +1 -0
- package/dist/commands/gherkin/commit.js +105 -0
- package/dist/commands/gherkin/commit.js.map +1 -0
- package/dist/commands/gherkin/gherkin.js +262 -0
- package/dist/commands/gherkin/gherkin.js.map +1 -0
- package/dist/commands/gherkin/reconcile.js +146 -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 +72 -0
- package/dist/commands/init/first-hour.js.map +1 -0
- package/dist/commands/{init.js → init/init.js} +142 -71
- package/dist/commands/init/init.js.map +1 -0
- package/dist/commands/init/mcp.js +131 -0
- package/dist/commands/init/mcp.js.map +1 -0
- package/dist/commands/init/options.js +78 -0
- package/dist/commands/init/options.js.map +1 -0
- package/dist/commands/instructions.js +72 -14
- package/dist/commands/instructions.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +63 -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 +155 -0
- package/dist/commands/list/json.js.map +1 -0
- package/dist/commands/list/list.js +358 -0
- package/dist/commands/list/list.js.map +1 -0
- package/dist/commands/list/print.js +344 -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/suites/suites.js +140 -0
- package/dist/commands/list/suites/suites.js.map +1 -0
- package/dist/commands/list/views.js +108 -0
- package/dist/commands/list/views.js.map +1 -0
- package/dist/commands/mcp/dispatch.js +238 -0
- package/dist/commands/mcp/dispatch.js.map +1 -0
- package/dist/commands/mcp/mcp.js +75 -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 +175 -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 +143 -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 +86 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/error.js +18 -0
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js +162 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/paths.js +40 -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/capability-template.js +118 -0
- package/dist/commands/new/capability-template.js.map +1 -0
- package/dist/commands/new/commit.js +62 -0
- package/dist/commands/new/commit.js.map +1 -0
- package/dist/commands/new/modifies.js +206 -0
- package/dist/commands/new/modifies.js.map +1 -0
- package/dist/commands/new/new.js +328 -0
- package/dist/commands/new/new.js.map +1 -0
- package/dist/commands/new/templates.js +279 -0
- package/dist/commands/new/templates.js.map +1 -0
- package/dist/commands/open.js +199 -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} +38 -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 +300 -0
- package/dist/commands/rebase/rebase.js.map +1 -0
- package/dist/commands/seed/commit.js +221 -0
- package/dist/commands/seed/commit.js.map +1 -0
- package/dist/commands/seed/plan.js +278 -0
- package/dist/commands/seed/plan.js.map +1 -0
- package/dist/commands/seed/seed.js +174 -0
- package/dist/commands/seed/seed.js.map +1 -0
- package/dist/commands/show/feature.js +182 -0
- package/dist/commands/show/feature.js.map +1 -0
- package/dist/commands/show/introduced.js +157 -0
- package/dist/commands/show/introduced.js.map +1 -0
- package/dist/commands/show/marks.js +53 -0
- package/dist/commands/show/marks.js.map +1 -0
- package/dist/commands/show/service.js +239 -0
- package/dist/commands/show/service.js.map +1 -0
- package/dist/commands/show/show.js +87 -0
- package/dist/commands/show/show.js.map +1 -0
- package/dist/commands/status/fleet-next.js +90 -0
- package/dist/commands/status/fleet-next.js.map +1 -0
- package/dist/commands/status/json.js +47 -0
- package/dist/commands/status/json.js.map +1 -0
- package/dist/commands/status/print.js +209 -0
- package/dist/commands/status/print.js.map +1 -0
- package/dist/commands/status/status.js +115 -0
- package/dist/commands/status/status.js.map +1 -0
- package/dist/commands/steps/steps.js +269 -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 +325 -0
- package/dist/commands/subsystem/move.js.map +1 -0
- package/dist/commands/subsystem/query.js +113 -0
- package/dist/commands/subsystem/query.js.map +1 -0
- package/dist/commands/subsystem/subsystem.js +98 -0
- package/dist/commands/subsystem/subsystem.js.map +1 -0
- package/dist/commands/subsystem/sync.js +318 -0
- package/dist/commands/subsystem/sync.js.map +1 -0
- package/dist/commands/subsystem/txn/exclude.js +132 -0
- package/dist/commands/subsystem/txn/exclude.js.map +1 -0
- package/dist/commands/subsystem/txn/links.js +128 -0
- package/dist/commands/subsystem/txn/links.js.map +1 -0
- package/dist/commands/subsystem/txn/report.js +174 -0
- package/dist/commands/subsystem/txn/report.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +196 -0
- package/dist/commands/subsystem/txn/txn.js.map +1 -0
- package/dist/commands/subsystem/txn/views.js +249 -0
- package/dist/commands/subsystem/txn/views.js.map +1 -0
- package/dist/commands/tasks/actions.js +107 -0
- package/dist/commands/tasks/actions.js.map +1 -0
- package/dist/commands/tasks/render.js +253 -0
- package/dist/commands/tasks/render.js.map +1 -0
- package/dist/commands/tasks/tasks.js +119 -0
- package/dist/commands/tasks/tasks.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +176 -0
- package/dist/commands/unarchive/manifest.js.map +1 -0
- package/dist/commands/unarchive/restore.js +254 -0
- package/dist/commands/unarchive/restore.js.map +1 -0
- package/dist/commands/unarchive/unarchive.js +59 -0
- package/dist/commands/unarchive/unarchive.js.map +1 -0
- package/dist/commands/validate/arch-coverage.js +245 -0
- package/dist/commands/validate/arch-coverage.js.map +1 -0
- package/dist/commands/validate/checks/fleet-shape.js +204 -0
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
- package/dist/commands/validate/checks/health-deps.js +58 -0
- package/dist/commands/validate/checks/health-deps.js.map +1 -0
- package/dist/commands/validate/checks/introduced.js +102 -0
- package/dist/commands/validate/checks/introduced.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +264 -0
- package/dist/commands/validate/checks/requirements.js.map +1 -0
- package/dist/commands/validate/checks/sources.js +85 -0
- package/dist/commands/validate/checks/sources.js.map +1 -0
- package/dist/commands/validate/coverage/covers-after.js +105 -0
- package/dist/commands/validate/coverage/covers-after.js.map +1 -0
- package/dist/commands/validate/feature.js +222 -0
- package/dist/commands/validate/feature.js.map +1 -0
- package/dist/commands/validate/fleet/census.js +93 -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 +371 -0
- package/dist/commands/validate/fleet/landscape.js.map +1 -0
- package/dist/commands/validate/fleet/load.js +118 -0
- package/dist/commands/validate/fleet/load.js.map +1 -0
- package/dist/commands/validate/fleet/map/attest.js +98 -0
- package/dist/commands/validate/fleet/map/attest.js.map +1 -0
- package/dist/commands/validate/fleet/map/bindings.js +107 -0
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
- package/dist/commands/validate/fleet/map/consumers.js +244 -0
- package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
- package/dist/commands/validate/fleet/map/isolation.js +116 -0
- package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
- package/dist/commands/validate/fleet/map/usecases.js +54 -0
- package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +266 -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 +71 -0
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js +162 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.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/views/fleet-project.js +217 -0
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +75 -0
- package/dist/commands/validate/fleet/views/ids.js.map +1 -0
- package/dist/commands/validate/fleet/views/projects.js +278 -0
- package/dist/commands/validate/fleet/views/projects.js.map +1 -0
- package/dist/commands/validate/fleet/views/root-documents.js +83 -0
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +52 -0
- package/dist/commands/validate/fleet/views/stale.js.map +1 -0
- package/dist/commands/validate/links/corpus.js +89 -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/render/repeats.js +63 -0
- package/dist/commands/validate/render/repeats.js.map +1 -0
- package/dist/commands/validate/render/rollups.js +115 -0
- package/dist/commands/validate/render/rollups.js.map +1 -0
- package/dist/commands/validate/render/text.js +199 -0
- package/dist/commands/validate/render/text.js.map +1 -0
- package/dist/commands/validate/report.js +145 -0
- package/dist/commands/validate/report.js.map +1 -0
- package/dist/commands/validate/scope.js +113 -0
- package/dist/commands/validate/scope.js.map +1 -0
- package/dist/commands/validate/service/api.js +252 -0
- package/dist/commands/validate/service/api.js.map +1 -0
- package/dist/commands/validate/service/contracts/generated.js +134 -0
- package/dist/commands/validate/service/contracts/generated.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +241 -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 +177 -0
- package/dist/commands/validate/service/evidence-pins.js.map +1 -0
- package/dist/commands/validate/service/model/diverged.js +226 -0
- package/dist/commands/validate/service/model/diverged.js.map +1 -0
- package/dist/commands/validate/service/model/grade.js +163 -0
- package/dist/commands/validate/service/model/grade.js.map +1 -0
- package/dist/commands/validate/service/service.js +310 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +279 -0
- package/dist/commands/validate/service/specs.js.map +1 -0
- package/dist/commands/validate/service/spine.js +148 -0
- package/dist/commands/validate/service/spine.js.map +1 -0
- package/dist/commands/validate/service/usecases/tags.js +113 -0
- package/dist/commands/validate/service/usecases/tags.js.map +1 -0
- package/dist/commands/validate/service/usecases/usecases.js +209 -0
- package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/validate.js +363 -0
- package/dist/commands/validate/validate.js.map +1 -0
- package/dist/commands/verify/cross/diff.js +178 -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 +81 -0
- package/dist/commands/verify/evidence/read.js.map +1 -0
- package/dist/commands/verify/frozen.js +263 -0
- package/dist/commands/verify/frozen.js.map +1 -0
- package/dist/commands/verify/record.js +278 -0
- package/dist/commands/verify/record.js.map +1 -0
- package/dist/commands/verify/report.js +206 -0
- package/dist/commands/verify/report.js.map +1 -0
- package/dist/commands/verify/results.js +328 -0
- package/dist/commands/verify/results.js.map +1 -0
- package/dist/commands/verify/verify.js +220 -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 +266 -0
- package/dist/commands/vouch/pack/print.js.map +1 -0
- package/dist/commands/vouch/pack/sections.js +141 -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 +230 -0
- package/dist/commands/vouch/run.js.map +1 -0
- package/dist/commands/vouch/sample/plan.js +111 -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 +155 -0
- package/dist/commands/vouch/vet/verify.js.map +1 -0
- package/dist/commands/vouch/vouch.js +169 -0
- package/dist/commands/vouch/vouch.js.map +1 -0
- package/dist/core/agent/agents-md/command-map.js +299 -0
- package/dist/core/agent/agents-md/command-map.js.map +1 -0
- package/dist/core/agent/agents-md/map/explain.js +46 -0
- package/dist/core/agent/agents-md/map/explain.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/context.js +50 -0
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js +59 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js +65 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
- package/dist/core/agent/agents-md/map/mcp.js +44 -0
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
- package/dist/core/agent/agents-md/map/status.js +78 -0
- package/dist/core/agent/agents-md/map/status.js.map +1 -0
- package/dist/core/agent/agents-md/map/subsystem.js +104 -0
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
- package/dist/core/agent/agents-md/map/usecases.js +138 -0
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
- package/dist/core/agent/agents-md/refusals.js +273 -0
- package/dist/core/agent/agents-md/refusals.js.map +1 -0
- package/dist/core/agent/agents-md.js +203 -0
- package/dist/core/agent/agents-md.js.map +1 -0
- package/dist/core/{agents-stamp.js → agent/agents-stamp.js} +60 -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 +254 -0
- package/dist/core/agent/scaffold.js.map +1 -0
- package/dist/core/agent/support/report.js +144 -0
- package/dist/core/agent/support/report.js.map +1 -0
- package/dist/core/agent/tools/dialects.js +151 -0
- package/dist/core/agent/tools/dialects.js.map +1 -0
- package/dist/core/agent/tools/registry.js +193 -0
- package/dist/core/agent/tools/registry.js.map +1 -0
- package/dist/core/agent/workflows/adopt.js +280 -0
- package/dist/core/agent/workflows/adopt.js.map +1 -0
- package/dist/core/agent/workflows/check.js +373 -0
- package/dist/core/agent/workflows/check.js.map +1 -0
- package/dist/core/agent/workflows/closing.js +323 -0
- package/dist/core/agent/workflows/closing.js.map +1 -0
- package/dist/core/agent/workflows/feature.js +220 -0
- package/dist/core/agent/workflows/feature.js.map +1 -0
- package/dist/core/agent/workflows/implement.js +188 -0
- package/dist/core/agent/workflows/implement.js.map +1 -0
- package/dist/core/agent/workflows/reference/authoring.js +311 -0
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
- package/dist/core/agent/workflows/reference/codes.js +67 -0
- package/dist/core/agent/workflows/reference/codes.js.map +1 -0
- package/dist/core/agent/workflows/reference/done-check.js +210 -0
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
- package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
- package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +19 -0
- package/dist/core/agent/workflows/reference/reference.js.map +1 -0
- package/dist/core/agent/workflows/reference/spine.js +338 -0
- package/dist/core/agent/workflows/reference/spine.js.map +1 -0
- package/dist/core/asyncapi/baseline/plan.js +43 -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 +146 -0
- package/dist/core/asyncapi/digest.js.map +1 -0
- package/dist/core/asyncapi/merge/components.js +92 -0
- package/dist/core/asyncapi/merge/components.js.map +1 -0
- package/dist/core/asyncapi/merge/error.js +38 -0
- package/dist/core/asyncapi/merge/error.js.map +1 -0
- package/dist/core/asyncapi/merge/markers.js +290 -0
- package/dist/core/asyncapi/merge/markers.js.map +1 -0
- package/dist/core/asyncapi/merge/merge.js +195 -0
- package/dist/core/asyncapi/merge/merge.js.map +1 -0
- package/dist/core/asyncapi/merge/pin.js +83 -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 +263 -0
- package/dist/core/asyncapi/read.js.map +1 -0
- package/dist/core/brief/brief.js +285 -0
- package/dist/core/brief/brief.js.map +1 -0
- package/dist/core/brief/checks.js +385 -0
- package/dist/core/brief/checks.js.map +1 -0
- package/dist/core/brief/landscape.js +273 -0
- package/dist/core/brief/landscape.js.map +1 -0
- package/dist/core/brief/map/extend.js +84 -0
- package/dist/core/brief/map/extend.js.map +1 -0
- package/dist/core/brief/map/owed.js +266 -0
- package/dist/core/brief/map/owed.js.map +1 -0
- package/dist/core/brief/targets.js +304 -0
- package/dist/core/brief/targets.js.map +1 -0
- package/dist/core/brief/unchecked.js +58 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +190 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/direction/direction.js +114 -0
- package/dist/core/c4/direction/direction.js.map +1 -0
- package/dist/core/c4/likec4.js +243 -0
- package/dist/core/c4/likec4.js.map +1 -0
- package/dist/core/c4/locate/unlocated.js +150 -0
- package/dist/core/c4/locate/unlocated.js.map +1 -0
- package/dist/core/c4/parsed/deployment.js +160 -0
- package/dist/core/c4/parsed/deployment.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 +110 -0
- package/dist/core/c4/parsed/values.js.map +1 -0
- package/dist/core/c4/parsed/view-ids.js +113 -0
- package/dist/core/c4/parsed/view-ids.js.map +1 -0
- package/dist/core/c4/prefetch/fleet.js +134 -0
- package/dist/core/c4/prefetch/fleet.js.map +1 -0
- package/dist/core/c4/project/architecture.js +161 -0
- package/dist/core/c4/project/architecture.js.map +1 -0
- package/dist/core/c4/project/batch.js +140 -0
- package/dist/core/c4/project/batch.js.map +1 -0
- package/dist/core/c4/project/documents.js +89 -0
- package/dist/core/c4/project/documents.js.map +1 -0
- package/dist/core/c4/project/load.js +219 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/project/staged.js +277 -0
- package/dist/core/c4/project/staged.js.map +1 -0
- package/dist/core/c4/resolve/attested.js +0 -0
- package/dist/core/c4/resolve/attested.js.map +1 -0
- package/dist/core/c4/resolve/external.js +51 -0
- package/dist/core/c4/resolve/external.js.map +1 -0
- package/dist/core/c4/resolve/service.js +174 -0
- package/dist/core/c4/resolve/service.js.map +1 -0
- package/dist/core/c4/resolve/steps.js +290 -0
- package/dist/core/c4/resolve/steps.js.map +1 -0
- package/dist/core/c4/resolve/systems.js +154 -0
- package/dist/core/c4/resolve/systems.js.map +1 -0
- package/dist/core/c4/root-project/exclude.js +352 -0
- package/dist/core/c4/root-project/exclude.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 +173 -0
- package/dist/core/c4/seed/template.js.map +1 -0
- package/dist/core/c4/service-model/created/models.js +96 -0
- package/dist/core/c4/service-model/created/models.js.map +1 -0
- package/dist/core/c4/service-model/documents.js +47 -0
- package/dist/core/c4/service-model/documents.js.map +1 -0
- package/dist/core/c4/service-model/fleet/extending.js +82 -0
- package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
- package/dist/core/c4/service-model/load.js +165 -0
- package/dist/core/c4/service-model/load.js.map +1 -0
- package/dist/core/c4/service-model/renderer.js +128 -0
- package/dist/core/c4/service-model/renderer.js.map +1 -0
- package/dist/core/c4/service-model/shape.js +106 -0
- package/dist/core/c4/service-model/shape.js.map +1 -0
- package/dist/core/c4/service-model/slice.js +188 -0
- package/dist/core/c4/service-model/slice.js.map +1 -0
- package/dist/core/c4/source-mask.js +369 -0
- package/dist/core/c4/source-mask.js.map +1 -0
- package/dist/core/c4/source-scan.js +308 -0
- package/dist/core/c4/source-scan.js.map +1 -0
- package/dist/core/c4/splice/authored-source.js +167 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -0
- package/dist/core/c4/splice/contract.js +30 -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/declared.js +87 -0
- package/dist/core/c4/splice/identity/declared.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +73 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -0
- package/dist/core/c4/splice/identity/existing.js +80 -0
- package/dist/core/c4/splice/identity/existing.js.map +1 -0
- package/dist/core/c4/splice/identity/interior.js +80 -0
- package/dist/core/c4/splice/identity/interior.js.map +1 -0
- package/dist/core/c4/splice/identity/owner.js +50 -0
- package/dist/core/c4/splice/identity/owner.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +337 -0
- package/dist/core/c4/splice/landscape-merge.js.map +1 -0
- package/dist/core/c4/splice/model/merge.js +151 -0
- package/dist/core/c4/splice/model/merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +298 -0
- package/dist/core/c4/splice/placement.js.map +1 -0
- package/dist/core/c4/splice/text/spot.js +131 -0
- package/dist/core/c4/splice/text/spot.js.map +1 -0
- package/dist/core/c4/workspace.js +166 -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 +47 -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 +338 -0
- package/dist/core/capabilities/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/findings.js +174 -0
- package/dist/core/capabilities/realizes/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/join.js +123 -0
- package/dist/core/capabilities/realizes/join.js.map +1 -0
- package/dist/core/capabilities/rollup.js +205 -0
- package/dist/core/capabilities/rollup.js.map +1 -0
- package/dist/core/capabilities/tree.js +104 -0
- package/dist/core/capabilities/tree.js.map +1 -0
- package/dist/core/capabilities/usecase-join.js +267 -0
- package/dist/core/capabilities/usecase-join.js.map +1 -0
- package/dist/core/coherence/authoring/scaffold.js +185 -0
- package/dist/core/coherence/authoring/scaffold.js.map +1 -0
- package/dist/core/coherence/authoring/sentinels.js +75 -0
- package/dist/core/coherence/authoring/sentinels.js.map +1 -0
- package/dist/core/coherence/calls/called.js +52 -0
- package/dist/core/coherence/calls/called.js.map +1 -0
- package/dist/core/coherence/coherence.js +332 -0
- package/dist/core/coherence/coherence.js.map +1 -0
- package/dist/core/coherence/consumed/operations.js +70 -0
- package/dist/core/coherence/consumed/operations.js.map +1 -0
- package/dist/core/coherence/consumed/refusal.js +38 -0
- package/dist/core/coherence/consumed/refusal.js.map +1 -0
- package/dist/core/coherence/covers/health.js +85 -0
- package/dist/core/coherence/covers/health.js.map +1 -0
- package/dist/core/coherence/declared.js +295 -0
- package/dist/core/coherence/declared.js.map +1 -0
- package/dist/core/coherence/events/declared.js +191 -0
- package/dist/core/coherence/events/declared.js.map +1 -0
- package/dist/core/coherence/events/events.js +335 -0
- package/dist/core/coherence/events/events.js.map +1 -0
- package/dist/core/coherence/events/grades.js +189 -0
- package/dist/core/coherence/events/grades.js.map +1 -0
- package/dist/core/coherence/events/lookups.js +63 -0
- package/dist/core/coherence/events/lookups.js.map +1 -0
- package/dist/core/coherence/events/produced.js +100 -0
- package/dist/core/coherence/events/produced.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 +95 -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/coherence/retired/archived.js +285 -0
- package/dist/core/coherence/retired/archived.js.map +1 -0
- package/dist/core/coherence/retired/event-pair.js +62 -0
- package/dist/core/coherence/retired/event-pair.js.map +1 -0
- package/dist/core/coherence/retired/merged.js +79 -0
- package/dist/core/coherence/retired/merged.js.map +1 -0
- package/dist/core/coherence/retired/retirement.js +49 -0
- package/dist/core/coherence/retired/retirement.js.map +1 -0
- package/dist/core/coherence/retired/revival/arrivals.js +131 -0
- package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
- package/dist/core/coherence/retired/revival/revived.js +297 -0
- package/dist/core/coherence/retired/revival/revived.js.map +1 -0
- package/dist/core/conflict-markers.js +60 -0
- package/dist/core/conflict-markers.js.map +1 -0
- package/dist/core/consumers/after.js +113 -0
- package/dist/core/consumers/after.js.map +1 -0
- package/dist/core/consumers/consumers.js +296 -0
- package/dist/core/consumers/consumers.js.map +1 -0
- package/dist/core/consumers/current.js +211 -0
- package/dist/core/consumers/current.js.map +1 -0
- package/dist/core/consumers/scan.js +46 -0
- package/dist/core/consumers/scan.js.map +1 -0
- package/dist/core/consumers/state.js +27 -0
- package/dist/core/consumers/state.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 +341 -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/deployment/delta.js +191 -0
- package/dist/core/deployment/delta.js.map +1 -0
- package/dist/core/deployment/objects.js +145 -0
- package/dist/core/deployment/objects.js.map +1 -0
- package/dist/core/diff/base-git.js +230 -0
- package/dist/core/diff/base-git.js.map +1 -0
- package/dist/core/diff/base-state.js +237 -0
- package/dist/core/diff/base-state.js.map +1 -0
- package/dist/core/diff/joins.js +136 -0
- package/dist/core/diff/joins.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +204 -0
- package/dist/core/diff/scope/changed-paths.js.map +1 -0
- package/dist/core/diff/scope/edges.js +82 -0
- package/dist/core/diff/scope/edges.js.map +1 -0
- package/dist/core/diff/semantic.js +312 -0
- package/dist/core/diff/semantic.js.map +1 -0
- package/dist/core/docs.js +37 -85
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +238 -0
- package/dist/core/doctor/agents.js.map +1 -0
- package/dist/core/doctor/config.js +139 -0
- package/dist/core/doctor/config.js.map +1 -0
- package/dist/core/doctor/doctor.js +355 -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/reports/scan.js +242 -0
- package/dist/core/doctor/reports/scan.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 +224 -0
- package/dist/core/document/apply.js.map +1 -0
- package/dist/core/{frontmatter.js → document/frontmatter.js} +103 -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 +286 -0
- package/dist/core/document/parse.js.map +1 -0
- package/dist/core/document/scenarios.js +238 -0
- package/dist/core/document/scenarios.js.map +1 -0
- package/dist/core/document/spec.js +234 -0
- package/dist/core/document/spec.js.map +1 -0
- package/dist/core/{config.js → envelope/config.js} +89 -18
- package/dist/core/envelope/config.js.map +1 -0
- package/dist/core/envelope/json.js +209 -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 +180 -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 +194 -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/documents.js +227 -0
- package/dist/core/explain/terms/documents.js.map +1 -0
- package/dist/core/explain/terms/done-check.js +144 -0
- package/dist/core/explain/terms/done-check.js.map +1 -0
- package/dist/core/explain/terms/entry.js +14 -0
- package/dist/core/explain/terms/entry.js.map +1 -0
- package/dist/core/explain/terms/stages.js +71 -0
- package/dist/core/explain/terms/stages.js.map +1 -0
- package/dist/core/explain/terms/tasks.js +97 -0
- package/dist/core/explain/terms/tasks.js.map +1 -0
- package/dist/core/explain/terms.js +103 -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/explore.js +204 -0
- package/dist/core/explore/explore.js.map +1 -0
- package/dist/core/fleet-context.js +174 -146
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +208 -0
- package/dist/core/gate/checks.js.map +1 -0
- package/dist/core/gate/partners.js +246 -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 +176 -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 +210 -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 +109 -0
- package/dist/core/glossary/delta.js.map +1 -0
- package/dist/core/glossary/tree.js +73 -0
- package/dist/core/glossary/tree.js.map +1 -0
- package/dist/core/{concurrency.js → kernel/concurrency.js} +19 -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/fqn/ancestors.js +65 -0
- package/dist/core/kernel/ids/fqn/ancestors.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} +56 -4
- package/dist/core/kernel/path-safety.js.map +1 -0
- package/dist/core/{records.js → kernel/records.js} +11 -0
- package/dist/core/kernel/records.js.map +1 -0
- package/dist/core/links/backlinks.js +66 -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 +118 -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 +265 -0
- package/dist/core/links/parse.js.map +1 -0
- package/dist/core/mcp/argv.js +212 -0
- package/dist/core/mcp/argv.js.map +1 -0
- package/dist/core/mcp/author-tools.js +73 -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 +324 -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 +207 -0
- package/dist/core/openapi/baseline/gate.js.map +1 -0
- package/dist/core/openapi/baseline/plan.js +142 -0
- package/dist/core/openapi/baseline/plan.js.map +1 -0
- package/dist/core/openapi/baseline/record.js +174 -0
- package/dist/core/openapi/baseline/record.js.map +1 -0
- package/dist/core/openapi/contract-text.js +33 -0
- package/dist/core/openapi/contract-text.js.map +1 -0
- package/dist/core/openapi/contract-tree.js +301 -0
- package/dist/core/openapi/contract-tree.js.map +1 -0
- package/dist/core/openapi/depth.js +136 -0
- package/dist/core/openapi/depth.js.map +1 -0
- package/dist/core/openapi/digest.js +239 -0
- package/dist/core/openapi/digest.js.map +1 -0
- package/dist/core/{openapi.js → openapi/doc.js} +54 -108
- package/dist/core/openapi/doc.js.map +1 -0
- package/dist/core/openapi/merge/components.js +199 -0
- package/dist/core/openapi/merge/components.js.map +1 -0
- package/dist/core/openapi/merge/error.js +16 -0
- package/dist/core/openapi/merge/error.js.map +1 -0
- package/dist/core/openapi/merge/markers.js +204 -0
- package/dist/core/openapi/merge/markers.js.map +1 -0
- package/dist/core/openapi/merge/merge.js +232 -0
- package/dist/core/openapi/merge/merge.js.map +1 -0
- package/dist/core/openapi/merge/pin.js +110 -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 +220 -0
- package/dist/core/openspec/inventory.js.map +1 -0
- package/dist/core/openspec/model/mapping.js +78 -0
- package/dist/core/openspec/model/mapping.js.map +1 -0
- package/dist/core/openspec/model/model.js +70 -0
- package/dist/core/openspec/model/model.js.map +1 -0
- package/dist/core/openspec/scan/changes.js +241 -0
- package/dist/core/openspec/scan/changes.js.map +1 -0
- package/dist/core/openspec/scan/config.js +191 -0
- package/dist/core/openspec/scan/config.js.map +1 -0
- package/dist/core/openspec/scan/shape.js +304 -0
- package/dist/core/openspec/scan/shape.js.map +1 -0
- package/dist/core/openspec/scan/walk.js +116 -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 +157 -0
- package/dist/core/pack/living.js.map +1 -0
- package/dist/core/pack/pack.js +120 -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/projection/introduced.js +14 -0
- package/dist/core/projection/introduced.js.map +1 -0
- package/dist/core/provenance/findings.js +244 -0
- package/dist/core/provenance/findings.js.map +1 -0
- package/dist/core/provenance/git.js +201 -0
- package/dist/core/provenance/git.js.map +1 -0
- package/dist/core/provenance/gitq/moves.js +139 -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 +280 -0
- package/dist/core/provenance/sources.js.map +1 -0
- package/dist/core/provenance/stamp.js +220 -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 +210 -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 +322 -0
- package/dist/core/repo/paths.js.map +1 -0
- package/dist/core/repo/repo.js +308 -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 +61 -0
- package/dist/core/repo/tree/find.js.map +1 -0
- package/dist/core/repo/tree/fs.js +90 -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/render/body.js +258 -0
- package/dist/core/repo/tree/render/body.js.map +1 -0
- package/dist/core/repo/tree/render/projects.js +182 -0
- package/dist/core/repo/tree/render/projects.js.map +1 -0
- package/dist/core/repo/tree/render/service/header.js +125 -0
- package/dist/core/repo/tree/render/service/header.js.map +1 -0
- package/dist/core/repo/tree/render/service/peers.js +28 -0
- package/dist/core/repo/tree/render/service/peers.js.map +1 -0
- package/dist/core/repo/tree/render/service/view-id.js +67 -0
- package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
- package/dist/core/repo/tree/render/service/views.js +78 -0
- package/dist/core/repo/tree/render/service/views.js.map +1 -0
- package/dist/core/repo/tree/render/stale.js +42 -0
- package/dist/core/repo/tree/render/stale.js.map +1 -0
- package/dist/core/repo/tree/render/text.js +84 -0
- package/dist/core/repo/tree/render/text.js.map +1 -0
- package/dist/core/repo/tree/render/views.js +293 -0
- package/dist/core/repo/tree/render/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +255 -0
- package/dist/core/repo/tree/walk.js.map +1 -0
- package/dist/core/results.js +146 -14
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +239 -0
- package/dist/core/scaffold/landscape.js.map +1 -0
- package/dist/core/scaffold/legacy.js +271 -0
- package/dist/core/scaffold/legacy.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 +252 -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 +268 -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 +95 -0
- package/dist/core/staging/recovery/intent.js.map +1 -0
- package/dist/core/staging/recovery/recover.js +204 -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/manifest.js +188 -0
- package/dist/core/staging/snapshot/manifest.js.map +1 -0
- package/dist/core/staging/snapshot/snapshot.js +196 -0
- package/dist/core/staging/snapshot/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +181 -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 +60 -0
- package/dist/core/status/actions/execution.js.map +1 -0
- package/dist/core/status/contracts.js +126 -0
- package/dist/core/status/contracts.js.map +1 -0
- package/dist/core/status/feature/artifacts.js +196 -0
- package/dist/core/status/feature/artifacts.js.map +1 -0
- package/dist/core/status/feature/feature.js +214 -0
- package/dist/core/status/feature/feature.js.map +1 -0
- package/dist/core/status/feature/next.js +163 -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/feature/words.js +96 -0
- package/dist/core/status/feature/words.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +216 -0
- package/dist/core/status/fleet/fleet.js.map +1 -0
- package/dist/core/status/fleet/next.js +302 -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 +216 -0
- package/dist/core/status/verification.js.map +1 -0
- package/dist/core/tasks/packet.js +199 -0
- package/dist/core/tasks/packet.js.map +1 -0
- package/dist/core/tasks/record.js +43 -0
- package/dist/core/tasks/record.js.map +1 -0
- package/dist/core/tasks/sources.js +181 -0
- package/dist/core/tasks/sources.js.map +1 -0
- package/dist/core/tasks/stamps.js +76 -0
- package/dist/core/tasks/stamps.js.map +1 -0
- package/dist/core/tasks/unit.js +0 -0
- package/dist/core/tasks/unit.js.map +1 -0
- package/dist/core/usecases/capability.js +166 -0
- package/dist/core/usecases/capability.js.map +1 -0
- package/dist/core/usecases/delta/claims.js +140 -0
- package/dist/core/usecases/delta/claims.js.map +1 -0
- package/dist/core/usecases/delta/flows.js +101 -0
- package/dist/core/usecases/delta/flows.js.map +1 -0
- package/dist/core/usecases/delta/modified.js +315 -0
- package/dist/core/usecases/delta/modified.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +76 -0
- package/dist/core/usecases/delta/overlay.js.map +1 -0
- package/dist/core/usecases/fleet.js +296 -0
- package/dist/core/usecases/fleet.js.map +1 -0
- package/dist/core/usecases/grade/capability-tag.js +93 -0
- package/dist/core/usecases/grade/capability-tag.js.map +1 -0
- package/dist/core/usecases/grade/feature.js +88 -0
- package/dist/core/usecases/grade/feature.js.map +1 -0
- package/dist/core/usecases/grade/requirement-tag.js +94 -0
- package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
- package/dist/core/usecases/grade/steps.js +266 -0
- package/dist/core/usecases/grade/steps.js.map +1 -0
- package/dist/core/usecases/grade/usecases.js +112 -0
- package/dist/core/usecases/grade/usecases.js.map +1 -0
- package/dist/core/usecases/operations.js +123 -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/service/exclude.js +119 -0
- package/dist/core/usecases/service/exclude.js.map +1 -0
- package/dist/core/usecases/service/flows.js +164 -0
- package/dist/core/usecases/service/flows.js.map +1 -0
- package/dist/core/usecases/suites/participants.js +83 -0
- package/dist/core/usecases/suites/participants.js.map +1 -0
- package/dist/core/usecases/suites/suites.js +45 -0
- package/dist/core/usecases/suites/suites.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 +180 -0
- package/dist/core/verify/answers.js.map +1 -0
- package/dist/core/verify/build.js +170 -0
- package/dist/core/verify/build.js.map +1 -0
- package/dist/core/verify/checklist.js +259 -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/location.js +67 -0
- package/dist/core/verify/location.js.map +1 -0
- package/dist/core/verify/pins/lint.js +158 -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/read/file.js +224 -0
- package/dist/core/verify/read/file.js.map +1 -0
- package/dist/core/verify/read/shape.js +169 -0
- package/dist/core/verify/read/shape.js.map +1 -0
- package/dist/core/verify/read/union.js +105 -0
- package/dist/core/verify/read/union.js.map +1 -0
- package/dist/core/verify/record.js +165 -0
- package/dist/core/verify/record.js.map +1 -0
- package/dist/core/verify/store/commit.js +99 -0
- package/dist/core/verify/store/commit.js.map +1 -0
- package/dist/core/verify/store/render.js +61 -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 +59 -0
- package/dist/core/vocabulary/issue.js.map +1 -0
- package/dist/core/vocabulary/maturity.js +216 -0
- package/dist/core/vocabulary/maturity.js.map +1 -0
- package/dist/core/vocabulary/report.js +90 -0
- 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 +232 -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 +336 -0
- package/examples/docs/architecture/obligations.yaml +23 -0
- package/examples/docs/architecture/permissions.yaml +50 -0
- package/examples/docs/architecture/services.likec4 +70 -0
- package/examples/docs/architecture/subsystems.likec4 +18 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +74 -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 +132 -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 +31 -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 +73 -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 +8 -0
- package/examples/docs/loam.json +3 -0
- package/examples/docs/services/checkout-web/model.likec4 +92 -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 +134 -0
- package/examples/docs/services/order-service/asyncapi.yaml +109 -0
- package/examples/docs/services/order-service/health.yaml +57 -0
- package/examples/docs/services/order-service/model.likec4 +126 -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 +76 -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 +68 -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 +120 -0
- package/examples/docs/services/platform/identity-service/health.yaml +70 -0
- package/examples/docs/services/platform/identity-service/model.likec4 +81 -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 +200 -0
- package/examples/docs/services/platform/notification-service/health.yaml +40 -0
- package/examples/docs/services/platform/notification-service/model.likec4 +97 -0
- package/examples/docs/services/platform/notification-service/runbook.md +48 -0
- package/examples/docs/services/platform/notification-service/spec.md +112 -0
- package/examples/docs/services/platform/subsystem.yaml +2 -0
- package/examples/fleet.yaml +57 -0
- package/package.json +19 -6
- package/dist/commands/adopt.js +0 -240
- package/dist/commands/adopt.js.map +0 -1
- package/dist/commands/archive.js +0 -1313
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/delta.js +0 -388
- package/dist/commands/delta.js.map +0 -1
- package/dist/commands/docs-repo-gate.js.map +0 -1
- package/dist/commands/format.js +0 -17
- package/dist/commands/format.js.map +0 -1
- package/dist/commands/gherkin.js +0 -309
- package/dist/commands/gherkin.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.js +0 -295
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/migrate-openspec.js +0 -950
- package/dist/commands/migrate-openspec.js.map +0 -1
- package/dist/commands/new.js +0 -413
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/rebase.js +0 -356
- package/dist/commands/rebase.js.map +0 -1
- package/dist/commands/show.js +0 -320
- package/dist/commands/show.js.map +0 -1
- package/dist/commands/status.js +0 -262
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js +0 -317
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1773
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/verify.js +0 -775
- package/dist/commands/verify.js.map +0 -1
- package/dist/commands/vouch.js +0 -421
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2173
- package/dist/core/agent.js.map +0 -1
- package/dist/core/agents-stamp.js.map +0 -1
- package/dist/core/arch.js +0 -130
- package/dist/core/arch.js.map +0 -1
- package/dist/core/asyncapi.js +0 -253
- package/dist/core/asyncapi.js.map +0 -1
- package/dist/core/brief.js +0 -755
- package/dist/core/brief.js.map +0 -1
- package/dist/core/coherence.js +0 -577
- package/dist/core/coherence.js.map +0 -1
- package/dist/core/concurrency.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/delta.js +0 -476
- package/dist/core/delta.js.map +0 -1
- package/dist/core/dependencies.js +0 -356
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/doctor.js +0 -596
- package/dist/core/doctor.js.map +0 -1
- package/dist/core/document-bytes.js.map +0 -1
- package/dist/core/explore.js +0 -225
- package/dist/core/explore.js.map +0 -1
- package/dist/core/frontmatter.js.map +0 -1
- package/dist/core/gherkin.js +0 -463
- package/dist/core/gherkin.js.map +0 -1
- package/dist/core/health.js +0 -67
- package/dist/core/health.js.map +0 -1
- package/dist/core/ids.js +0 -122
- package/dist/core/ids.js.map +0 -1
- package/dist/core/issue.js +0 -9
- package/dist/core/issue.js.map +0 -1
- package/dist/core/json.js +0 -74
- package/dist/core/json.js.map +0 -1
- package/dist/core/likec4.js +0 -489
- package/dist/core/likec4.js.map +0 -1
- package/dist/core/maturity.js +0 -66
- package/dist/core/maturity.js.map +0 -1
- package/dist/core/openapi-merge.js +0 -552
- package/dist/core/openapi-merge.js.map +0 -1
- package/dist/core/openapi.js.map +0 -1
- package/dist/core/openspec-inventory.js +0 -1440
- package/dist/core/openspec-inventory.js.map +0 -1
- package/dist/core/path-safety.js.map +0 -1
- package/dist/core/provenance.js +0 -787
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -438
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js +0 -47
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -505
- package/dist/core/spec.js.map +0 -1
- package/dist/core/staging.js +0 -890
- package/dist/core/staging.js.map +0 -1
- package/dist/core/status.js +0 -1132
- package/dist/core/status.js.map +0 -1
- package/dist/core/steps.js +0 -72
- package/dist/core/steps.js.map +0 -1
- package/dist/core/verify.js +0 -807
- package/dist/core/verify.js.map +0 -1
- package/dist/core/version.js.map +0 -1
|
@@ -22,6 +22,44 @@ export function isPathInside(root, target) {
|
|
|
22
22
|
const rel = relative(resolve(root), resolve(target));
|
|
23
23
|
return rel === "" || (!isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${sep}`));
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Where `target` is once the directory `from` has been renamed to `to`: its
|
|
27
|
+
* image under `to` when it is inside `from`, `target` itself when it is not.
|
|
28
|
+
* Absolute paths in and out. The question every reader of a directory move
|
|
29
|
+
* asks of one path: the link rewrites of both renames loam makes (`subsystem
|
|
30
|
+
* move`, and `archive` for the feature's own documents), and the archive's
|
|
31
|
+
* snapshot, crash repair and `unarchive`'s report, which meet those documents
|
|
32
|
+
* on either side of the move.
|
|
33
|
+
*/
|
|
34
|
+
export function acrossRename(target, from, to) {
|
|
35
|
+
return isPathInside(from, target) ? resolve(to, relative(resolve(from), resolve(target))) : target;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* `path` in the spelling every other path here has: the Win32 file-namespace
|
|
39
|
+
* prefix taken off a drive or UNC path (`\\?\C:\x` → `C:\x`,
|
|
40
|
+
* `\\?\UNC\host\share\x` → `\\host\share\x`), anything else unchanged.
|
|
41
|
+
*
|
|
42
|
+
* Measured 2026-10-21 on Node 24.13.0, Windows 11: `mkdir(dir, { recursive:
|
|
43
|
+
* true })` — and `mkdirSync` — reports the first directory it created as
|
|
44
|
+
* `\\?\C:\…\features\archive` for a `dir` of `C:\…\features\archive`, at short
|
|
45
|
+
* and long path lengths alike; `mkdtemp` and `realpath` return the plain
|
|
46
|
+
* spelling. `resolve()` keeps the prefix and `relative()` between the two
|
|
47
|
+
* spellings is an absolute path, so `isPathInside` judged
|
|
48
|
+
* `C:\…\features\archive` to lie outside `\\?\C:\…\features\archive` — and every
|
|
49
|
+
* rollback that pruned "up to the directory mkdir created" pruned nothing,
|
|
50
|
+
* leaving an empty `features/archive/` or `services/<new-service>/` behind at a
|
|
51
|
+
* clean `merge-failed`. Linux never reports the prefix, so CI could not see it.
|
|
52
|
+
*
|
|
53
|
+
* Only the drive and UNC forms are taken off: `\\?\Volume{…}\` and
|
|
54
|
+
* `\\?\GLOBALROOT\…` have no plain spelling, and stripping theirs would leave a
|
|
55
|
+
* relative path. A POSIX absolute path starts with `/` and never matches.
|
|
56
|
+
*/
|
|
57
|
+
export function withoutNamespacePrefix(path) {
|
|
58
|
+
const unc = /^\\\\\?\\UNC\\/i.exec(path);
|
|
59
|
+
if (unc !== null)
|
|
60
|
+
return `\\\\${path.slice(unc[0].length)}`;
|
|
61
|
+
return /^\\\\\?\\[A-Za-z]:(?:\\|$)/.test(path) ? path.slice(4) : path;
|
|
62
|
+
}
|
|
25
63
|
/**
|
|
26
64
|
* Resolve a relative path beneath `root`, rejecting absolute paths, explicit
|
|
27
65
|
* parent traversal, and symlink escape. The final path may be absent: its
|
|
@@ -73,9 +111,10 @@ export function resolveInside(root, candidate, label = "path") {
|
|
|
73
111
|
/**
|
|
74
112
|
* Snapshot manifests are portable data, so their path spelling is stricter
|
|
75
113
|
* than an interactive path: forward slashes, no redundant components, and a
|
|
76
|
-
* file (not directory) name at the end.
|
|
114
|
+
* file (not directory) name at the end. The one constructor of `PortablePath`,
|
|
115
|
+
* because these checks are what the brand asserts.
|
|
77
116
|
*/
|
|
78
|
-
export function
|
|
117
|
+
export function portablePathOf(candidate, label = "path") {
|
|
79
118
|
if (candidate.includes("\\")) {
|
|
80
119
|
throw new UnsafePathError(candidate, `${label} must use forward slashes`);
|
|
81
120
|
}
|
|
@@ -83,7 +122,20 @@ export function resolvePortableFileInside(root, candidate, label = "path") {
|
|
|
83
122
|
if (parts.some((part) => part === "" || part === "." || part === "..")) {
|
|
84
123
|
throw new UnsafePathError(candidate, `${label} must be a canonical relative file path`);
|
|
85
124
|
}
|
|
86
|
-
|
|
125
|
+
// The cast, on the line immediately after the checks that earn it.
|
|
126
|
+
return candidate;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Containment for a path that already EARNED the portable spelling — the one
|
|
130
|
+
* signature that demands the brand, so `PortablePath` has a consumer the
|
|
131
|
+
* compiler checks rather than a comment claiming the order of two calls.
|
|
132
|
+
*/
|
|
133
|
+
function resolvePortableInside(root, portable, label) {
|
|
134
|
+
return resolveInside(root, portable, label);
|
|
135
|
+
}
|
|
136
|
+
/** The portable spelling checks, then `resolveInside`'s containment proof — the ONE spelling of the pair. */
|
|
137
|
+
export function resolvePortableFileInside(root, candidate, label = "path") {
|
|
138
|
+
return resolvePortableInside(root, portablePathOf(candidate, label), label);
|
|
87
139
|
}
|
|
88
140
|
/**
|
|
89
141
|
* The same portable spelling rules, with containment judged LEXICALLY — for a
|
|
@@ -99,7 +151,7 @@ export function resolvePortableFileInside(root, candidate, label = "path") {
|
|
|
99
151
|
* between two checkouts — is a supported layout that every walk of the repo
|
|
100
152
|
* FOLLOWS rather than refuses (repo.ts's `entryIs` says so in full), and that
|
|
101
153
|
* `archive` already writes through: its living-document writes are planned from
|
|
102
|
-
* `
|
|
154
|
+
* `unfiledServicePaths()`/`servicePathsAt()` and never see this file. With the realpath test in force on
|
|
103
155
|
* the manifest's `path` field, a `services/<svc>/` mounted that way made an
|
|
104
156
|
* interrupted archive permanently unrepairable — the retry was told the
|
|
105
157
|
* snapshot could not be read when it was perfectly intact, and the half-merge it
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-safety.js","sourceRoot":"","sources":["../../../src/core/kernel/path-safety.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAE/E,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAE7B,SAAS;IADpB,YACW,SAAiB,EAC1B,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;yBAHN,SAAS;QAIlB,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED,8DAA8D;AAC9D,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,MAAc;IACvD,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IACrD,OAAO,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC;AACzF,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,IAAY,EAAE,EAAU;IACnE,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AACrG,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,MAAM,GAAG,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzC,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;IAC5D,OAAO,4BAA4B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACxE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,SAAiB,EAAE,KAAK,GAAG,MAAM;IAC3E,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,eAAe,CAAC,SAAS,EAAE,GAAG,KAAK,oBAAoB,CAAC,CAAC;IACrE,CAAC;IACD,4EAA4E;IAC5E,6EAA6E;IAC7E,yEAAyE;IACzE,IAAI,UAAU,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,eAAe,CAAC,SAAS,EAAE,GAAG,KAAK,2CAA2C,CAAC,CAAC;IAC5F,CAAC;IACD,IAAI,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,eAAe,CAAC,SAAS,EAAE,GAAG,KAAK,sCAAsC,CAAC,CAAC;IACvF,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAC3C,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,eAAe,CAAC,SAAS,EAAE,GAAG,KAAK,wCAAwC,CAAC,CAAC;IACzF,CAAC;IAED,IAAI,QAAgB,CAAC;IACrB,IAAI,QAAgB,CAAC;IACrB,IAAI,CAAC;QACH,QAAQ,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QACjC,QAAQ,GAAG,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAChE,MAAM,IAAI,eAAe,CAAC,SAAS,EAAE,GAAG,KAAK,+BAA+B,MAAM,EAAE,CAAC,CAAC;IACxF,CAAC;IAED,IAAI,YAAoB,CAAC;IACzB,IAAI,CAAC;QACH,YAAY,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,wEAAwE;QACxE,qEAAqE;QACrE,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAChE,MAAM,IAAI,eAAe,CAAC,SAAS,EAAE,GAAG,KAAK,6CAA6C,MAAM,EAAE,CAAC,CAAC;IACtG,CAAC;IACD,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,eAAe,CAAC,SAAS,EAAE,GAAG,KAAK,iDAAiD,CAAC,CAAC;IAClG,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAiBD;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,SAAiB,EAAE,KAAK,GAAG,MAAM;IAC9D,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,eAAe,CAAC,SAAS,EAAE,GAAG,KAAK,2BAA2B,CAAC,CAAC;IAC5E,CAAC;IACD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QACvE,MAAM,IAAI,eAAe,CAAC,SAAS,EAAE,GAAG,KAAK,yCAAyC,CAAC,CAAC;IAC1F,CAAC;IACD,mEAAmE;IACnE,OAAO,SAAyB,CAAC;AACnC,CAAC;AAED;;;;GAIG;AACH,SAAS,qBAAqB,CAAC,IAAY,EAAE,QAAsB,EAAE,KAAa;IAChF,OAAO,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;AAC9C,CAAC;AAED,6GAA6G;AAC7G,MAAM,UAAU,yBAAyB,CAAC,IAAY,EAAE,SAAiB,EAAE,KAAK,GAAG,MAAM;IACvF,OAAO,qBAAqB,CAAC,IAAI,EAAE,cAAc,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,UAAU,kCAAkC,CAAC,IAAY,EAAE,SAAiB,EAAE,KAAK,GAAG,MAAM;IAChG,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,eAAe,CAAC,SAAS,EAAE,GAAG,KAAK,oBAAoB,CAAC,CAAC;IACrE,CAAC;IACD,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,eAAe,CAAC,SAAS,EAAE,GAAG,KAAK,2BAA2B,CAAC,CAAC;IAC5E,CAAC;IACD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QACvE,MAAM,IAAI,eAAe,CAAC,SAAS,EAAE,GAAG,KAAK,yCAAyC,CAAC,CAAC;IAC1F,CAAC;IACD,gFAAgF;IAChF,yEAAyE;IACzE,4EAA4E;IAC5E,kDAAkD;IAClD,IAAI,UAAU,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,eAAe,CAAC,SAAS,EAAE,GAAG,KAAK,2CAA2C,CAAC,CAAC;IAC5F,CAAC;IACD,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,CAAC;IAC1C,4EAA4E;IAC5E,2EAA2E;IAC3E,iCAAiC;IACjC,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,eAAe,CAAC,SAAS,EAAE,GAAG,KAAK,wCAAwC,CAAC,CAAC;IACzF,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,iFAAiF;AACjF,SAAS,eAAe,CAAC,MAAc,EAAE,IAAY;IACnD,IAAI,OAAO,GAAG,MAAM,CAAC;IACrB,SAAS,CAAC;QACR,IAAI,CAAC;YACH,SAAS,CAAC,OAAO,CAAC,CAAC;YACnB,OAAO,OAAO,CAAC;QACjB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,GAAI,GAA6B,CAAC,IAAI,CAAC;YACjD,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,SAAS;gBAAE,MAAM,GAAG,CAAC;QACzD,CAAC;QACD,IAAI,OAAO,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAClC,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QAChC,IAAI,MAAM,KAAK,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC;QACnE,OAAO,GAAG,MAAM,CAAC;IACnB,CAAC;AACH,CAAC","sourcesContent":["/**\n * Resolve caller-controlled paths without letting them leave an owned tree.\n *\n * A lexical `relative()` check is not enough: an existing component may be a\n * symlink whose target lives elsewhere. Conversely, checking only the final\n * path misses destinations that do not exist yet but whose nearest existing\n * parent is a symlink. This helper checks both views before a caller reads,\n * writes, walks, or removes anything.\n */\nimport { lstatSync, realpathSync } from \"node:fs\";\nimport { dirname, isAbsolute, relative, resolve, sep, win32 } from \"node:path\";\n\nexport class UnsafePathError extends Error {\n constructor(\n readonly candidate: string,\n message: string,\n ) {\n super(message);\n this.name = \"UnsafePathError\";\n }\n}\n\n/** True when `target` is `root` or one of its descendants. */\nexport function isPathInside(root: string, target: string): boolean {\n const rel = relative(resolve(root), resolve(target));\n return rel === \"\" || (!isAbsolute(rel) && rel !== \"..\" && !rel.startsWith(`..${sep}`));\n}\n\n/**\n * Where `target` is once the directory `from` has been renamed to `to`: its\n * image under `to` when it is inside `from`, `target` itself when it is not.\n * Absolute paths in and out. The question every reader of a directory move\n * asks of one path: the link rewrites of both renames loam makes (`subsystem\n * move`, and `archive` for the feature's own documents), and the archive's\n * snapshot, crash repair and `unarchive`'s report, which meet those documents\n * on either side of the move.\n */\nexport function acrossRename(target: string, from: string, to: string): string {\n return isPathInside(from, target) ? resolve(to, relative(resolve(from), resolve(target))) : target;\n}\n\n/**\n * `path` in the spelling every other path here has: the Win32 file-namespace\n * prefix taken off a drive or UNC path (`\\\\?\\C:\\x` → `C:\\x`,\n * `\\\\?\\UNC\\host\\share\\x` → `\\\\host\\share\\x`), anything else unchanged.\n *\n * Measured 2026-10-21 on Node 24.13.0, Windows 11: `mkdir(dir, { recursive:\n * true })` — and `mkdirSync` — reports the first directory it created as\n * `\\\\?\\C:\\…\\features\\archive` for a `dir` of `C:\\…\\features\\archive`, at short\n * and long path lengths alike; `mkdtemp` and `realpath` return the plain\n * spelling. `resolve()` keeps the prefix and `relative()` between the two\n * spellings is an absolute path, so `isPathInside` judged\n * `C:\\…\\features\\archive` to lie outside `\\\\?\\C:\\…\\features\\archive` — and every\n * rollback that pruned \"up to the directory mkdir created\" pruned nothing,\n * leaving an empty `features/archive/` or `services/<new-service>/` behind at a\n * clean `merge-failed`. Linux never reports the prefix, so CI could not see it.\n *\n * Only the drive and UNC forms are taken off: `\\\\?\\Volume{…}\\` and\n * `\\\\?\\GLOBALROOT\\…` have no plain spelling, and stripping theirs would leave a\n * relative path. A POSIX absolute path starts with `/` and never matches.\n */\nexport function withoutNamespacePrefix(path: string): string {\n const unc = /^\\\\\\\\\\?\\\\UNC\\\\/i.exec(path);\n if (unc !== null) return `\\\\\\\\${path.slice(unc[0].length)}`;\n return /^\\\\\\\\\\?\\\\[A-Za-z]:(?:\\\\|$)/.test(path) ? path.slice(4) : path;\n}\n\n/**\n * Resolve a relative path beneath `root`, rejecting absolute paths, explicit\n * parent traversal, and symlink escape. The final path may be absent: its\n * nearest existing ancestor is checked in that case.\n */\nexport function resolveInside(root: string, candidate: string, label = \"path\"): string {\n if (candidate.length === 0) {\n throw new UnsafePathError(candidate, `${label} must not be empty`);\n }\n // win32.isAbsolute is intentional even on POSIX. Persisted/configured paths\n // may be moved between operating systems, and `C:\\\\...` must never turn into\n // an innocuous relative filename merely because validation ran on Linux.\n if (isAbsolute(candidate) || win32.isAbsolute(candidate)) {\n throw new UnsafePathError(candidate, `${label} must be relative to its owning directory`);\n }\n if (candidate.split(/[\\\\/]/).includes(\"..\")) {\n throw new UnsafePathError(candidate, `${label} must not contain '..' path segments`);\n }\n\n const rootAbs = resolve(root);\n const target = resolve(rootAbs, candidate);\n if (!isPathInside(rootAbs, target)) {\n throw new UnsafePathError(candidate, `${label} resolves outside its owning directory`);\n }\n\n let rootReal: string;\n let existing: string;\n try {\n rootReal = realpathSync(rootAbs);\n existing = nearestExisting(target, rootAbs);\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new UnsafePathError(candidate, `${label} cannot be resolved safely: ${detail}`);\n }\n\n let existingReal: string;\n try {\n existingReal = realpathSync(existing);\n } catch (err) {\n // A broken symlink is an existing path according to lstat, but realpath\n // cannot prove where a later write through it would land. Refuse it.\n const detail = err instanceof Error ? err.message : String(err);\n throw new UnsafePathError(candidate, `${label} crosses an unreadable or broken symlink: ${detail}`);\n }\n if (!isPathInside(rootReal, existingReal)) {\n throw new UnsafePathError(candidate, `${label} crosses a symlink outside its owning directory`);\n }\n return target;\n}\n\n/**\n * A relative file path in the portable spelling — forward slashes, no\n * redundant components. The brand's provenance is `portablePathOf` below: the\n * spelling checks there are the only way to construct one, so a value carrying\n * it has demonstrably passed them. Deliberately thin: it says nothing about\n * containment (that stays `resolveInside`'s question, asked per root), and it\n * is consumed by `resolvePortableInside` below — the one signature that\n * demands it — and every boundary (verify's evidence, unarchive's manifests)\n * reaches it through `resolvePortableFileInside`, the pair's one spelling.\n * The staging manifests keep their plain strings until the writer work that\n * owns those formats re-keys them.\n */\ndeclare const portable: unique symbol;\nexport type PortablePath = string & { readonly [portable]: true };\n\n/**\n * Snapshot manifests are portable data, so their path spelling is stricter\n * than an interactive path: forward slashes, no redundant components, and a\n * file (not directory) name at the end. The one constructor of `PortablePath`,\n * because these checks are what the brand asserts.\n */\nexport function portablePathOf(candidate: string, label = \"path\"): PortablePath {\n if (candidate.includes(\"\\\\\")) {\n throw new UnsafePathError(candidate, `${label} must use forward slashes`);\n }\n const parts = candidate.split(\"/\");\n if (parts.some((part) => part === \"\" || part === \".\" || part === \"..\")) {\n throw new UnsafePathError(candidate, `${label} must be a canonical relative file path`);\n }\n // The cast, on the line immediately after the checks that earn it.\n return candidate as PortablePath;\n}\n\n/**\n * Containment for a path that already EARNED the portable spelling — the one\n * signature that demands the brand, so `PortablePath` has a consumer the\n * compiler checks rather than a comment claiming the order of two calls.\n */\nfunction resolvePortableInside(root: string, portable: PortablePath, label: string): string {\n return resolveInside(root, portable, label);\n}\n\n/** The portable spelling checks, then `resolveInside`'s containment proof — the ONE spelling of the pair. */\nexport function resolvePortableFileInside(root: string, candidate: string, label = \"path\"): string {\n return resolvePortableInside(root, portablePathOf(candidate, label), label);\n}\n\n/**\n * The same portable spelling rules, with containment judged LEXICALLY — for a\n * path that names a file the docs repo itself mounts.\n *\n * Every spelling defence above is kept, and for its reason: a snapshot manifest\n * is loam's own file, but a corrupted or hostile one could carry `..` segments,\n * an absolute path or a `C:\\` spelling, and what comes out of this join is a\n * path a caller then goes and reads.\n *\n * What is deliberately not applied is the realpath test. Composing a docs repo\n * out of symlinks — a worktree, a submodule, one service's directory shared\n * between two checkouts — is a supported layout that every walk of the repo\n * FOLLOWS rather than refuses (repo.ts's `entryIs` says so in full), and that\n * `archive` already writes through: its living-document writes are planned from\n * `unfiledServicePaths()`/`servicePathsAt()` and never see this file. With the realpath test in force on\n * the manifest's `path` field, a `services/<svc>/` mounted that way made an\n * interrupted archive permanently unrepairable — the retry was told the\n * snapshot could not be read when it was perfectly intact, and the half-merge it\n * was the only record of could never be repaired.\n *\n * This is only ever safe on a READ. A symlink that leaves the repo is\n * lexically indistinguishable from a service directory the operator mounted on\n * purpose — `escape/owned.txt` and `services/<svc>/spec.md` are both \"inside\n * docsDir\" until something resolves them — so the realpath test is the only\n * check that can refuse the first, at the price of also refusing the second.\n * A caller that goes on to WRITE through the path must pay that price and use\n * `resolvePortableFileInside`; `unarchive` does, and says so where it does.\n * A caller that only reads a file and compares a digest it never discloses\n * gains nothing from refusing, and loses the operator's ability to repair an\n * interrupted archive.\n */\nexport function resolvePortableFileInsideLexically(root: string, candidate: string, label = \"path\"): string {\n if (candidate.length === 0) {\n throw new UnsafePathError(candidate, `${label} must not be empty`);\n }\n if (candidate.includes(\"\\\\\")) {\n throw new UnsafePathError(candidate, `${label} must use forward slashes`);\n }\n const parts = candidate.split(\"/\");\n if (parts.some((part) => part === \"\" || part === \".\" || part === \"..\")) {\n throw new UnsafePathError(candidate, `${label} must be a canonical relative file path`);\n }\n // win32.isAbsolute is intentional even on POSIX, for the reason `resolveInside`\n // gives: a snapshot travels with its feature directory between operating\n // systems, and `C:\\...` must never turn into an innocuous relative filename\n // merely because the question was asked on Linux.\n if (isAbsolute(candidate) || win32.isAbsolute(candidate)) {\n throw new UnsafePathError(candidate, `${label} must be relative to its owning directory`);\n }\n const rootAbs = resolve(root);\n const target = resolve(rootAbs, ...parts);\n // Already implied by the segment test above, and asked anyway: this line is\n // the containment claim itself, so a later loosening of the spelling rules\n // cannot quietly stop making it.\n if (!isPathInside(rootAbs, target)) {\n throw new UnsafePathError(candidate, `${label} resolves outside its owning directory`);\n }\n return target;\n}\n\n/** Find the closest lexically contained path that exists, including symlinks. */\nfunction nearestExisting(target: string, root: string): string {\n let current = target;\n for (;;) {\n try {\n lstatSync(current);\n return current;\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"ENOENT\" && code !== \"ENOTDIR\") throw err;\n }\n if (current === root) return root;\n const parent = dirname(current);\n if (parent === current || !isPathInside(root, parent)) return root;\n current = parent;\n }\n}\n"]}
|
|
@@ -16,6 +16,17 @@ export function isRecord(value) {
|
|
|
16
16
|
export function asRecord(value) {
|
|
17
17
|
return isRecord(value) ? value : null;
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* A list of strings. `Array.isArray` narrows an `unknown` to `any[]`, so an
|
|
21
|
+
* element check written below it proved nothing to tsc and the elements had to
|
|
22
|
+
* be asserted back to `string` at the point of use — how a checked element
|
|
23
|
+
* type went on being asserted back in a line later. Deciding both halves in
|
|
24
|
+
* one predicate makes the narrowing carry the check. (It was written twice,
|
|
25
|
+
* once in the OpenSpec scan and once in the migration's mapping reader.)
|
|
26
|
+
*/
|
|
27
|
+
export function isStringArray(value) {
|
|
28
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
29
|
+
}
|
|
19
30
|
/**
|
|
20
31
|
* A map whose keys come from a document somebody else wrote.
|
|
21
32
|
*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"records.js","sourceRoot":"","sources":["../../../src/core/kernel/records.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,+DAA+D;AAC/D,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AACxC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC;AACjF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAsB,CAAC;AAClD,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,QAAQ,CAAI,MAAyB,EAAE,GAAW;IAChE,OAAO,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACrF,CAAC","sourcesContent":["/**\n * The shapes an unknown parsed document is asked about, in one place.\n *\n * Every YAML and JSON file loam reads arrives as `unknown`, and every reader\n * has to answer the same first question before it can look at a key: is this an\n * object at all? Six modules each carried their own copy of the answer, two of\n * them spelled with a cast — and a cast is exactly what this test exists to\n * make unnecessary, since the predicate form hands the caller a narrowed type\n * that tsc keeps checking. A leaf with no imports, so any layer can ask.\n */\n\n/** An object with string keys — not null, and not an array. */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** The same question where the answer is used rather than branched on. */\nexport function asRecord(value: unknown): Record<string, unknown> | null {\n return isRecord(value) ? value : null;\n}\n\n/**\n * A list of strings. `Array.isArray` narrows an `unknown` to `any[]`, so an\n * element check written below it proved nothing to tsc and the elements had to\n * be asserted back to `string` at the point of use — how a checked element\n * type went on being asserted back in a line later. Deciding both halves in\n * one predicate makes the narrowing carry the check. (It was written twice,\n * once in the OpenSpec scan and once in the migration's mapping reader.)\n */\nexport function isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === \"string\");\n}\n\n/**\n * A map whose keys come from a document somebody else wrote.\n *\n * `Object.create(null)` rather than `{}` because a key read out of untrusted\n * YAML can be `__proto__` or `constructor`, and on a normal object those either\n * mutate the prototype or read back a function that was never in the file.\n */\nexport function dictionary<T>(): Record<string, T> {\n return Object.create(null) as Record<string, T>;\n}\n\n/** The value a record actually holds for `key`, never one it inherited. */\nexport function ownValue<T>(record: Record<string, T>, key: string): T | undefined {\n return Object.prototype.hasOwnProperty.call(record, key) ? record[key] : undefined;\n}\n"]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who links to what, across the whole repository — the link graph read
|
|
3
|
+
* backwards.
|
|
4
|
+
*
|
|
5
|
+
* `link.unresolved` asks each document about its own links. This asks the
|
|
6
|
+
* opposite question, and it is the one the glossary is built on: "which
|
|
7
|
+
* documents use this term" is exactly "which documents link to `glossary/<term>.md`",
|
|
8
|
+
* and no single target can answer it. A term is cited from a service's spec, a
|
|
9
|
+
* capability document and a feature's intent, so the index is fleet-scope by
|
|
10
|
+
* construction.
|
|
11
|
+
*
|
|
12
|
+
* IT IS THE SAME WALK AS THE FINDING, deliberately — `resolveLinks` produces
|
|
13
|
+
* both halves of one answer, and the corpus is `core/links/corpus.ts`'s single
|
|
14
|
+
* definition. An index built from a second enumeration would report a term as
|
|
15
|
+
* unused while the document citing it was being graded one target over.
|
|
16
|
+
*
|
|
17
|
+
* AN UNREADABLE DOCUMENT IS RETURNED, not swallowed. A term can only be shown as
|
|
18
|
+
* cited by nobody if loam actually read everybody, and a UTF-16 spec.md would
|
|
19
|
+
* otherwise turn into a silently missing citation. Under `validate` the same
|
|
20
|
+
* file already earns `link.unreadable`; `loam list` has no such finding, so it
|
|
21
|
+
* needs the list to say what it did not read.
|
|
22
|
+
*/
|
|
23
|
+
import { existsSync } from "node:fs";
|
|
24
|
+
import { resolve } from "node:path";
|
|
25
|
+
import { repoPath } from "../envelope/json.js";
|
|
26
|
+
import { pathCaseIndex } from "./case.js";
|
|
27
|
+
import { allAuthoredDocuments } from "./corpus.js";
|
|
28
|
+
import { resolveLinks } from "./findings.js";
|
|
29
|
+
/**
|
|
30
|
+
* Build the index over every authored document in the repository.
|
|
31
|
+
*
|
|
32
|
+
* One `pathCaseIndex` for the whole walk, because this one really is fleet-wide
|
|
33
|
+
* and the same directories are asked about hundreds of times. Still not a
|
|
34
|
+
* module-level cache: it lives exactly as long as this call.
|
|
35
|
+
*/
|
|
36
|
+
export async function backlinkIndex(docsDir, fleet) {
|
|
37
|
+
const linkers = new Map();
|
|
38
|
+
const unreadable = [];
|
|
39
|
+
const scope = { docsDir, cases: pathCaseIndex() };
|
|
40
|
+
for (const path of await allAuthoredDocuments(docsDir, fleet)) {
|
|
41
|
+
if (!existsSync(path))
|
|
42
|
+
continue;
|
|
43
|
+
let text;
|
|
44
|
+
try {
|
|
45
|
+
text = await fleet.readText(path);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
unreadable.push(repoPath(docsDir, path));
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const from = repoPath(docsDir, path);
|
|
52
|
+
for (const { target } of resolveLinks({ path, text }, scope).resolved) {
|
|
53
|
+
const key = resolve(target);
|
|
54
|
+
let sources = linkers.get(key);
|
|
55
|
+
if (sources === undefined)
|
|
56
|
+
linkers.set(key, (sources = new Set()));
|
|
57
|
+
sources.add(from);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
unreadable.sort();
|
|
61
|
+
return {
|
|
62
|
+
linkersOf: (target) => [...(linkers.get(resolve(target)) ?? [])].sort(),
|
|
63
|
+
unreadable,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=backlinks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backlinks.js","sourceRoot":"","sources":["../../../src/core/links/backlinks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAG/C,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAS7C;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAAgB,EAAE,KAAmB;IACvE,MAAM,OAAO,GAAG,IAAI,GAAG,EAAuB,CAAC;IAC/C,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,CAAC;IAClD,KAAK,MAAM,IAAI,IAAI,MAAM,oBAAoB,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC;QAC9D,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,SAAS;QAChC,IAAI,IAAY,CAAC;QACjB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;YACzC,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACrC,KAAK,MAAM,EAAE,MAAM,EAAE,IAAI,YAAY,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC;YACtE,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;YAC5B,IAAI,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC/B,IAAI,OAAO,KAAK,SAAS;gBAAE,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,OAAO,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC;YACnE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IACD,UAAU,CAAC,IAAI,EAAE,CAAC;IAClB,OAAO;QACL,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE;QACvE,UAAU;KACX,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Who links to what, across the whole repository — the link graph read\n * backwards.\n *\n * `link.unresolved` asks each document about its own links. This asks the\n * opposite question, and it is the one the glossary is built on: \"which\n * documents use this term\" is exactly \"which documents link to `glossary/<term>.md`\",\n * and no single target can answer it. A term is cited from a service's spec, a\n * capability document and a feature's intent, so the index is fleet-scope by\n * construction.\n *\n * IT IS THE SAME WALK AS THE FINDING, deliberately — `resolveLinks` produces\n * both halves of one answer, and the corpus is `core/links/corpus.ts`'s single\n * definition. An index built from a second enumeration would report a term as\n * unused while the document citing it was being graded one target over.\n *\n * AN UNREADABLE DOCUMENT IS RETURNED, not swallowed. A term can only be shown as\n * cited by nobody if loam actually read everybody, and a UTF-16 spec.md would\n * otherwise turn into a silently missing citation. Under `validate` the same\n * file already earns `link.unreadable`; `loam list` has no such finding, so it\n * needs the list to say what it did not read.\n */\nimport { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { repoPath } from \"../envelope/json.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport { pathCaseIndex } from \"./case.js\";\nimport { allAuthoredDocuments } from \"./corpus.js\";\nimport { resolveLinks } from \"./findings.js\";\n\nexport interface BacklinkIndex {\n /** Repo-relative paths of the documents linking to `target`, ordered, without duplicates. */\n linkersOf(target: string): string[];\n /** Repo-relative paths of documents whose links could not be read at all. */\n unreadable: string[];\n}\n\n/**\n * Build the index over every authored document in the repository.\n *\n * One `pathCaseIndex` for the whole walk, because this one really is fleet-wide\n * and the same directories are asked about hundreds of times. Still not a\n * module-level cache: it lives exactly as long as this call.\n */\nexport async function backlinkIndex(docsDir: DocsDir, fleet: FleetContext): Promise<BacklinkIndex> {\n const linkers = new Map<string, Set<string>>();\n const unreadable: string[] = [];\n const scope = { docsDir, cases: pathCaseIndex() };\n for (const path of await allAuthoredDocuments(docsDir, fleet)) {\n if (!existsSync(path)) continue;\n let text: string;\n try {\n text = await fleet.readText(path);\n } catch {\n unreadable.push(repoPath(docsDir, path));\n continue;\n }\n const from = repoPath(docsDir, path);\n for (const { target } of resolveLinks({ path, text }, scope).resolved) {\n const key = resolve(target);\n let sources = linkers.get(key);\n if (sources === undefined) linkers.set(key, (sources = new Set()));\n sources.add(from);\n }\n }\n unreadable.sort();\n return {\n linkersOf: (target) => [...(linkers.get(resolve(target)) ?? [])].sort(),\n unreadable,\n };\n}\n"]}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is a path spelled the way it is stored — the half of "does this link resolve"
|
|
3
|
+
* that `existsSync` cannot answer.
|
|
4
|
+
*
|
|
5
|
+
* `[Order](Order.md)` beside a file called `order.md` resolves on Windows and
|
|
6
|
+
* macOS and 404s on GitHub, which renders these documents and is
|
|
7
|
+
* case-sensitive, as is every Linux CI runner. That is the exact shape loam
|
|
8
|
+
* refuses everywhere else: a check that is green on the author's machine and red
|
|
9
|
+
* where the work is actually read. `link.unresolved` shipped without it as a
|
|
10
|
+
* recorded limit; this closes it.
|
|
11
|
+
*
|
|
12
|
+
* THE ANSWER IS A DIRECTORY LISTING, not a filesystem probe, because a probe is
|
|
13
|
+
* what is already wrong: on a case-insensitive volume every spelling of a name
|
|
14
|
+
* stats successfully, so the only place the true spelling exists is the parent's
|
|
15
|
+
* own listing. Each path segment between the docs root and the target is
|
|
16
|
+
* checked against that listing, verbatim.
|
|
17
|
+
*
|
|
18
|
+
* THE INDEX IS PER-INVOCATION AND PASSED, never module-level. A cached listing
|
|
19
|
+
* that outlived one command would answer for a directory that has since
|
|
20
|
+
* changed, and in this repository's forked test processes a module-level memo
|
|
21
|
+
* leaks between fixtures — `AGENTS.md` names that hazard directly. Cost is one
|
|
22
|
+
* `readdir` per distinct directory a link points into, which for a docs repo is
|
|
23
|
+
* a handful.
|
|
24
|
+
*/
|
|
25
|
+
import { readdirSync } from "node:fs";
|
|
26
|
+
import { relative, resolve, sep } from "node:path";
|
|
27
|
+
/** A fresh index. One per command invocation — see the module note on why it is not shared. */
|
|
28
|
+
export function pathCaseIndex() {
|
|
29
|
+
const listings = new Map();
|
|
30
|
+
const namesIn = (dir) => {
|
|
31
|
+
let names = listings.get(dir);
|
|
32
|
+
if (names === undefined) {
|
|
33
|
+
try {
|
|
34
|
+
names = new Set(readdirSync(dir));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// An unreadable directory answers nothing, and the honest reading is
|
|
38
|
+
// "not disproved": the target stat'ed, so something is there. Refusing
|
|
39
|
+
// a link because its parent denied a listing would convict the author
|
|
40
|
+
// for a permission bit.
|
|
41
|
+
names = new Set();
|
|
42
|
+
}
|
|
43
|
+
listings.set(dir, names);
|
|
44
|
+
}
|
|
45
|
+
return names;
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
spelledExactly(root, target) {
|
|
49
|
+
const rootAbs = resolve(root);
|
|
50
|
+
const rel = relative(rootAbs, resolve(target));
|
|
51
|
+
if (rel === "")
|
|
52
|
+
return true;
|
|
53
|
+
let dir = rootAbs;
|
|
54
|
+
for (const segment of rel.split(sep)) {
|
|
55
|
+
const names = namesIn(dir);
|
|
56
|
+
// The empty set is the unreadable-directory case above: stop asking
|
|
57
|
+
// rather than convict every segment beneath it.
|
|
58
|
+
if (names.size > 0 && !names.has(segment))
|
|
59
|
+
return false;
|
|
60
|
+
dir = resolve(dir, segment);
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
},
|
|
64
|
+
storedAs(dir, name) {
|
|
65
|
+
const wanted = name.toLowerCase();
|
|
66
|
+
for (const stored of namesIn(resolve(dir))) {
|
|
67
|
+
if (stored !== name && stored.toLowerCase() === wanted)
|
|
68
|
+
return stored;
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=case.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"case.js","sourceRoot":"","sources":["../../../src/core/links/case.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAsBnD,+FAA+F;AAC/F,MAAM,UAAU,aAAa;IAC3B,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAuB,CAAC;IAChD,MAAM,OAAO,GAAG,CAAC,GAAW,EAAe,EAAE;QAC3C,IAAI,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,IAAI,CAAC;gBACH,KAAK,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;YACpC,CAAC;YAAC,MAAM,CAAC;gBACP,qEAAqE;gBACrE,uEAAuE;gBACvE,sEAAsE;gBACtE,wBAAwB;gBACxB,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;YAC5B,CAAC;YACD,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC3B,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IAEF,OAAO;QACL,cAAc,CAAC,IAAI,EAAE,MAAM;YACzB,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;YAC9B,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;YAC/C,IAAI,GAAG,KAAK,EAAE;gBAAE,OAAO,IAAI,CAAC;YAC5B,IAAI,GAAG,GAAG,OAAO,CAAC;YAClB,KAAK,MAAM,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;gBAC3B,oEAAoE;gBACpE,gDAAgD;gBAChD,IAAI,KAAK,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC;oBAAE,OAAO,KAAK,CAAC;gBACxD,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YAC9B,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,QAAQ,CAAC,GAAG,EAAE,IAAI;YAChB,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YAClC,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;gBAC3C,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,WAAW,EAAE,KAAK,MAAM;oBAAE,OAAO,MAAM,CAAC;YACxE,CAAC;YACD,OAAO,SAAS,CAAC;QACnB,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Is a path spelled the way it is stored — the half of \"does this link resolve\"\n * that `existsSync` cannot answer.\n *\n * `[Order](Order.md)` beside a file called `order.md` resolves on Windows and\n * macOS and 404s on GitHub, which renders these documents and is\n * case-sensitive, as is every Linux CI runner. That is the exact shape loam\n * refuses everywhere else: a check that is green on the author's machine and red\n * where the work is actually read. `link.unresolved` shipped without it as a\n * recorded limit; this closes it.\n *\n * THE ANSWER IS A DIRECTORY LISTING, not a filesystem probe, because a probe is\n * what is already wrong: on a case-insensitive volume every spelling of a name\n * stats successfully, so the only place the true spelling exists is the parent's\n * own listing. Each path segment between the docs root and the target is\n * checked against that listing, verbatim.\n *\n * THE INDEX IS PER-INVOCATION AND PASSED, never module-level. A cached listing\n * that outlived one command would answer for a directory that has since\n * changed, and in this repository's forked test processes a module-level memo\n * leaks between fixtures — `AGENTS.md` names that hazard directly. Cost is one\n * `readdir` per distinct directory a link points into, which for a docs repo is\n * a handful.\n */\nimport { readdirSync } from \"node:fs\";\nimport { relative, resolve, sep } from \"node:path\";\n\nexport interface PathCaseIndex {\n /**\n * True when every segment of `target` below `root` is spelled exactly as the\n * filesystem stores it. `target` must already be known to lie inside `root`;\n * this asks only about spelling.\n */\n spelledExactly(root: string, target: string): boolean;\n /**\n * The name `dir` actually stores for `name`, when one differs from it only by\n * case — the hint that turns \"resolves to nothing\" into \"you spelled it\n * `Order.md` and it is stored as `order.md`\".\n *\n * Asked on both platforms and not only the case-insensitive one, which is the\n * whole point: on Linux the same link fails `existsSync` outright and on\n * Windows it fails `spelledExactly`, and a diagnosis that differed by\n * operating system would be a machine contract that does not hold.\n */\n storedAs(dir: string, name: string): string | undefined;\n}\n\n/** A fresh index. One per command invocation — see the module note on why it is not shared. */\nexport function pathCaseIndex(): PathCaseIndex {\n const listings = new Map<string, Set<string>>();\n const namesIn = (dir: string): Set<string> => {\n let names = listings.get(dir);\n if (names === undefined) {\n try {\n names = new Set(readdirSync(dir));\n } catch {\n // An unreadable directory answers nothing, and the honest reading is\n // \"not disproved\": the target stat'ed, so something is there. Refusing\n // a link because its parent denied a listing would convict the author\n // for a permission bit.\n names = new Set<string>();\n }\n listings.set(dir, names);\n }\n return names;\n };\n\n return {\n spelledExactly(root, target) {\n const rootAbs = resolve(root);\n const rel = relative(rootAbs, resolve(target));\n if (rel === \"\") return true;\n let dir = rootAbs;\n for (const segment of rel.split(sep)) {\n const names = namesIn(dir);\n // The empty set is the unreadable-directory case above: stop asking\n // rather than convict every segment beneath it.\n if (names.size > 0 && !names.has(segment)) return false;\n dir = resolve(dir, segment);\n }\n return true;\n },\n storedAs(dir, name) {\n const wanted = name.toLowerCase();\n for (const stored of namesIn(resolve(dir))) {\n if (stored !== name && stored.toLowerCase() === wanted) return stored;\n }\n return undefined;\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { featureGlossary } from "../glossary/delta.js";
|
|
4
|
+
import { readGlossary } from "../glossary/tree.js";
|
|
5
|
+
import { featurePaths, featureSpecPaths, fleetAdrsDir, servicePathsAt } from "../repo/paths.js";
|
|
6
|
+
import { glossaryDir } from "../repo/authored/paths.js";
|
|
7
|
+
import { featureSpecServices, listFeatures, listServices } from "../repo/repo.js";
|
|
8
|
+
import { markdownFiles } from "../repo/tree/fs.js";
|
|
9
|
+
/**
|
|
10
|
+
* One service's authored documents: the two requirement specs, the runbook, and
|
|
11
|
+
* every ADR. The specs cost nothing extra: the run's `FleetContext` has already
|
|
12
|
+
* read both to parse their requirements, and the memo hands back the same text.
|
|
13
|
+
*/
|
|
14
|
+
export async function serviceDocuments(paths) {
|
|
15
|
+
return [paths.spec, paths.archSpec, paths.runbook, ...(await markdownFiles(paths.adrsDir))];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* One feature's authored documents: the intent, each addressed service's two
|
|
19
|
+
* spec deltas, every ADR, and each capability delta document.
|
|
20
|
+
*/
|
|
21
|
+
export async function featureDocuments(featureDir, fleet) {
|
|
22
|
+
const paths = featurePaths(featureDir);
|
|
23
|
+
const docs = [paths.intent, ...(await markdownFiles(paths.adrsDir))];
|
|
24
|
+
for (const svc of await featureSpecServices(featureDir, fleet)) {
|
|
25
|
+
const spec = featureSpecPaths(featureDir, svc);
|
|
26
|
+
docs.push(spec.spec, spec.archSpec);
|
|
27
|
+
}
|
|
28
|
+
const capabilities = await fleet.featureCapabilityDeltas(featureDir);
|
|
29
|
+
docs.push(...capabilities.docs.map((d) => d.spec));
|
|
30
|
+
// The definitions this feature introduces. Their own links are graded like
|
|
31
|
+
// any other document's — a term that cites a term the feature is not adding
|
|
32
|
+
// is a broken join whether or not it has shipped yet.
|
|
33
|
+
docs.push(...(await featureGlossary(featureDir)).terms.map((t) => t.path));
|
|
34
|
+
return docs;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The documents that belong to no service and no feature: the fleet's own
|
|
38
|
+
* decision records, the living capability tree, and the glossary.
|
|
39
|
+
*
|
|
40
|
+
* Graded at fleet scope, so `validate --all` reports them once. A single-target
|
|
41
|
+
* run says nothing about them — the rule `permissions.unenforced` and
|
|
42
|
+
* `capability.invalid` already follow, and for the same reason: a finding about
|
|
43
|
+
* the fleet repeated on every service target is the report.
|
|
44
|
+
*/
|
|
45
|
+
export async function fleetDocuments(docsDir, fleet) {
|
|
46
|
+
const capabilities = (await fleet.capabilities(docsDir)).tree;
|
|
47
|
+
const glossary = await readGlossary(glossaryDir(docsDir));
|
|
48
|
+
return [
|
|
49
|
+
...(await markdownFiles(fleetAdrsDir(docsDir))),
|
|
50
|
+
...capabilities.docs.map((d) => d.spec),
|
|
51
|
+
...glossary.terms.map((t) => t.path),
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Every authored document in the repository — the union of the three above over
|
|
56
|
+
* every service and every in-flight feature.
|
|
57
|
+
*
|
|
58
|
+
* ARCHIVED FEATURES ARE OUT, and the omission is the point rather than an
|
|
59
|
+
* oversight. `features/archive/<FEAT>/` is the evolution history: its documents
|
|
60
|
+
* describe a change that already shipped, its links point at whatever the tree
|
|
61
|
+
* looked like then, and grading them would make every directory rename a
|
|
62
|
+
* retroactive error against a record nobody may edit. `listFeatures` excludes
|
|
63
|
+
* them by default, which is the same judgement one enumeration up. "Then" is
|
|
64
|
+
* the commit that archived the feature, and not the one before it: the archive
|
|
65
|
+
* re-expresses the links in the documents it moves (`movedMarkdown` below,
|
|
66
|
+
* `commands/archive/plan/document/links.ts`), because its own rename is the one
|
|
67
|
+
* change to the tree it knows exactly and makes in the same breath.
|
|
68
|
+
*/
|
|
69
|
+
export async function allAuthoredDocuments(docsDir, fleet) {
|
|
70
|
+
const docs = await fleetDocuments(docsDir, fleet);
|
|
71
|
+
for (const service of await listServices(docsDir, fleet)) {
|
|
72
|
+
docs.push(...(await serviceDocuments(servicePathsAt(service.dir))));
|
|
73
|
+
}
|
|
74
|
+
for (const feature of await listFeatures(docsDir, {}, fleet)) {
|
|
75
|
+
docs.push(...(await featureDocuments(feature.dir, fleet)));
|
|
76
|
+
}
|
|
77
|
+
return docs;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Every `.md` file under `dir`, at any depth, sorted — the documents a rename
|
|
81
|
+
* of `dir` carries with it, which is a different list from the corpus above:
|
|
82
|
+
* a rename moves a runbook, a subsystem's own notes, a feature's
|
|
83
|
+
* `legacy/proposal.md` exactly as it moves an ADR, and owes them all the same
|
|
84
|
+
* link correction whether or not anything grades them.
|
|
85
|
+
*
|
|
86
|
+
* Real directories and files only: a symlink is not followed — what it points
|
|
87
|
+
* at is not moved by the rename — and dot-directories and `node_modules` are
|
|
88
|
+
* machinery, never authored documents, exactly as every other walk in loam
|
|
89
|
+
* treats them. The archive's own `.loam-before/` is one of them: its
|
|
90
|
+
* pre-images are bytes to put back, not documents to correct.
|
|
91
|
+
*
|
|
92
|
+
* Two callers, the two directory renames loam makes: `subsystem move`
|
|
93
|
+
* (`commands/subsystem/txn/links.ts`) and `archive`
|
|
94
|
+
* (`commands/archive/plan/document/links.ts`). Here, below both, because the
|
|
95
|
+
* rule for "what does the rename carry" is one rule.
|
|
96
|
+
*/
|
|
97
|
+
export async function movedMarkdown(dir) {
|
|
98
|
+
let entries;
|
|
99
|
+
try {
|
|
100
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
const out = [];
|
|
106
|
+
for (const entry of entries) {
|
|
107
|
+
const path = join(dir, entry.name);
|
|
108
|
+
if (entry.isDirectory()) {
|
|
109
|
+
if (!entry.name.startsWith(".") && entry.name !== "node_modules")
|
|
110
|
+
out.push(...(await movedMarkdown(path)));
|
|
111
|
+
}
|
|
112
|
+
else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
113
|
+
out.push(path);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return out.sort();
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=corpus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"corpus.js","sourceRoot":"","sources":["../../../src/core/links/corpus.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * WHICH documents loam reads links out of — the corpus, spelled once.\n *\n * Two readers need it and must not disagree. `commands/validate/links/` grades\n * each target's own documents for `link.unresolved`; `core/links/backlinks.ts`\n * reads the WHOLE fleet's, because \"which documents use this term\" is a question\n * no single target can answer. A corpus that differed between them would report\n * a term as unused while the very document citing it was being graded one target\n * over. The two directory renames read it too — `subsystem move`\n * (`commands/subsystem/txn/links.ts`) and `archive`\n * (`commands/archive/plan/document/links.ts`) rewrite the links INTO what they\n * move from every document here — for the same reason from the other side: a\n * document `validate` grades that a rename did not relink is `link.unresolved`\n * the commit after.\n *\n * THE CORPUS IS EVERY AUTHORED MARKDOWN DOCUMENT, and the word doing the work is\n * authored. `AGENTS.md` and the scaffolded `README.md` are excluded because loam\n * WRITES them: a finding against generated prose names a defect its reader\n * cannot fix by editing, since the next `loam init` restores it. Their links are\n * loam's own problem, held on this side of the boundary by\n * `test/docs-facts.test.ts` and `test/package-docs.test.ts`.\n *\n * ADRs AND RUNBOOKS ARE IN IT, and they are the reason the convention was\n * written down at all: an ADR that supersedes another says which, by linking to\n * it. They are also the first documents loam reads that it previously only\n * counted, which is what `link.unreadable` exists for.\n *\n * `delta.likec4` IS NOT, and neither is any YAML: this is a markdown question.\n * The one thing a delta holds that addresses another file — `metadata { op }` —\n * has its own resolution and its own findings.\n */\nimport type { Dirent } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport { featureGlossary } from \"../glossary/delta.js\";\nimport { readGlossary } from \"../glossary/tree.js\";\nimport type { DocsDir, FeatureDir } from \"../kernel/ids/dirs.js\";\nimport { featurePaths, featureSpecPaths, fleetAdrsDir, servicePathsAt, type ServicePaths } from \"../repo/paths.js\";\nimport { glossaryDir } from \"../repo/authored/paths.js\";\nimport { featureSpecServices, listFeatures, listServices } from \"../repo/repo.js\";\nimport { markdownFiles } from \"../repo/tree/fs.js\";\n\n/**\n * One service's authored documents: the two requirement specs, the runbook, and\n * every ADR. The specs cost nothing extra: the run's `FleetContext` has already\n * read both to parse their requirements, and the memo hands back the same text.\n */\nexport async function serviceDocuments(paths: ServicePaths): Promise<string[]> {\n return [paths.spec, paths.archSpec, paths.runbook, ...(await markdownFiles(paths.adrsDir))];\n}\n\n/**\n * One feature's authored documents: the intent, each addressed service's two\n * spec deltas, every ADR, and each capability delta document.\n */\nexport async function featureDocuments(featureDir: FeatureDir, fleet: FleetContext): Promise<string[]> {\n const paths = featurePaths(featureDir);\n const docs = [paths.intent, ...(await markdownFiles(paths.adrsDir))];\n for (const svc of await featureSpecServices(featureDir, fleet)) {\n const spec = featureSpecPaths(featureDir, svc);\n docs.push(spec.spec, spec.archSpec);\n }\n const capabilities = await fleet.featureCapabilityDeltas(featureDir);\n docs.push(...capabilities.docs.map((d) => d.spec));\n // The definitions this feature introduces. Their own links are graded like\n // any other document's — a term that cites a term the feature is not adding\n // is a broken join whether or not it has shipped yet.\n docs.push(...(await featureGlossary(featureDir)).terms.map((t) => t.path));\n return docs;\n}\n\n/**\n * The documents that belong to no service and no feature: the fleet's own\n * decision records, the living capability tree, and the glossary.\n *\n * Graded at fleet scope, so `validate --all` reports them once. A single-target\n * run says nothing about them — the rule `permissions.unenforced` and\n * `capability.invalid` already follow, and for the same reason: a finding about\n * the fleet repeated on every service target is the report.\n */\nexport async function fleetDocuments(docsDir: DocsDir, fleet: FleetContext): Promise<string[]> {\n const capabilities = (await fleet.capabilities(docsDir)).tree;\n const glossary = await readGlossary(glossaryDir(docsDir));\n return [\n ...(await markdownFiles(fleetAdrsDir(docsDir))),\n ...capabilities.docs.map((d) => d.spec),\n ...glossary.terms.map((t) => t.path),\n ];\n}\n\n/**\n * Every authored document in the repository — the union of the three above over\n * every service and every in-flight feature.\n *\n * ARCHIVED FEATURES ARE OUT, and the omission is the point rather than an\n * oversight. `features/archive/<FEAT>/` is the evolution history: its documents\n * describe a change that already shipped, its links point at whatever the tree\n * looked like then, and grading them would make every directory rename a\n * retroactive error against a record nobody may edit. `listFeatures` excludes\n * them by default, which is the same judgement one enumeration up. \"Then\" is\n * the commit that archived the feature, and not the one before it: the archive\n * re-expresses the links in the documents it moves (`movedMarkdown` below,\n * `commands/archive/plan/document/links.ts`), because its own rename is the one\n * change to the tree it knows exactly and makes in the same breath.\n */\nexport async function allAuthoredDocuments(docsDir: DocsDir, fleet: FleetContext): Promise<string[]> {\n const docs = await fleetDocuments(docsDir, fleet);\n for (const service of await listServices(docsDir, fleet)) {\n docs.push(...(await serviceDocuments(servicePathsAt(service.dir))));\n }\n for (const feature of await listFeatures(docsDir, {}, fleet)) {\n docs.push(...(await featureDocuments(feature.dir, fleet)));\n }\n return docs;\n}\n\n/**\n * Every `.md` file under `dir`, at any depth, sorted — the documents a rename\n * of `dir` carries with it, which is a different list from the corpus above:\n * a rename moves a runbook, a subsystem's own notes, a feature's\n * `legacy/proposal.md` exactly as it moves an ADR, and owes them all the same\n * link correction whether or not anything grades them.\n *\n * Real directories and files only: a symlink is not followed — what it points\n * at is not moved by the rename — and dot-directories and `node_modules` are\n * machinery, never authored documents, exactly as every other walk in loam\n * treats them. The archive's own `.loam-before/` is one of them: its\n * pre-images are bytes to put back, not documents to correct.\n *\n * Two callers, the two directory renames loam makes: `subsystem move`\n * (`commands/subsystem/txn/links.ts`) and `archive`\n * (`commands/archive/plan/document/links.ts`). Here, below both, because the\n * rule for \"what does the rename carry\" is one rule.\n */\nexport async function movedMarkdown(dir: string): Promise<string[]> {\n let entries: Dirent[];\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n const out: string[] = [];\n for (const entry of entries) {\n const path = join(dir, entry.name);\n if (entry.isDirectory()) {\n if (!entry.name.startsWith(\".\") && entry.name !== \"node_modules\") out.push(...(await movedMarkdown(path)));\n } else if (entry.isFile() && entry.name.endsWith(\".md\")) {\n out.push(path);\n }\n }\n return out.sort();\n}\n"]}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `link.unresolved` — a markdown link in an authored document whose target is
|
|
3
|
+
* not there, or is there under a different spelling.
|
|
4
|
+
*
|
|
5
|
+
* The convention this grades has been written down since the fleet-ADR work and
|
|
6
|
+
* said, in both `SCHEMA.md` and the generated `AGENTS.md`, that nothing
|
|
7
|
+
* validated it. This module is what makes that sentence false, and the reason
|
|
8
|
+
* the convention was chosen is the reason it can: the target of a standard
|
|
9
|
+
* markdown link is a real relative path, so "does this resolve" has a
|
|
10
|
+
* filesystem answer, where a wikilink would need Obsidian's
|
|
11
|
+
* shortest-unique-path search reimplemented — guessing, which loam does not do.
|
|
12
|
+
*
|
|
13
|
+
* ERROR, and the severity is the decision the maintainer took against the
|
|
14
|
+
* alternative of warning outside `glossary/`. A link is a join of the same kind
|
|
15
|
+
* as `Operations:` and `Covers:`, both of which error when they resolve to
|
|
16
|
+
* nothing; the fact that this one is written in prose changes who typed it, not
|
|
17
|
+
* whether it points at something. A fleet adopting loam with rotted links sees
|
|
18
|
+
* them on its first run — a user-visible widening of the same class as the two
|
|
19
|
+
* the use-case and business axes shipped, and recorded in CHANGELOG as one.
|
|
20
|
+
*
|
|
21
|
+
* ONE FINDING PER DOCUMENT, with every broken link in `details`. A document
|
|
22
|
+
* whose links have rotted is one editing session and one reviewer, not ten
|
|
23
|
+
* separate breaches, and per-link findings would let a single stale directory
|
|
24
|
+
* rename fill a `validate --all` report with the same fix restated.
|
|
25
|
+
*
|
|
26
|
+
* THE RESOLUTION IS EXPORTED SEPARATELY from the finding, because a second
|
|
27
|
+
* reader needs the other half of the answer: the glossary grades a term by who
|
|
28
|
+
* links TO it, which is this same walk read forwards instead of backwards.
|
|
29
|
+
*/
|
|
30
|
+
import { existsSync } from "node:fs";
|
|
31
|
+
import { basename, dirname, resolve } from "node:path";
|
|
32
|
+
import { repoPath } from "../envelope/json.js";
|
|
33
|
+
import { isPathInside } from "../kernel/path-safety.js";
|
|
34
|
+
import { pathCaseIndex } from "./case.js";
|
|
35
|
+
import { documentLinks } from "./parse.js";
|
|
36
|
+
/**
|
|
37
|
+
* Every link in one document, resolved against the docs repo.
|
|
38
|
+
*
|
|
39
|
+
* TARGETS OUTSIDE THE DOCS REPO ARE NEITHER RESOLVED NOR BROKEN, and the
|
|
40
|
+
* exclusion is honesty rather than leniency. A docs repo is routinely checked
|
|
41
|
+
* out beside nothing else, so `../../payments-svc/README.md` is a path loam
|
|
42
|
+
* cannot answer for: the file's absence here is evidence about this checkout,
|
|
43
|
+
* not about the link. Only a target that stays inside `docsDir` gets a verdict,
|
|
44
|
+
* which is the same boundary every other reader in loam observes and the reason
|
|
45
|
+
* the finding below can afford to be an error at all.
|
|
46
|
+
*
|
|
47
|
+
* CASE COUNTS, and it must, because the alternative is a check that passes on
|
|
48
|
+
* the author's machine and fails where the work is read. `existsSync` is
|
|
49
|
+
* case-insensitive on Windows and macOS; GitHub's renderer and every Linux CI
|
|
50
|
+
* runner are not. So a resolving link is one that exists AND is spelled the way
|
|
51
|
+
* the filesystem stores it — and when it is not, `storedAs` carries the real
|
|
52
|
+
* name into the message, on every platform alike, so the diagnosis does not
|
|
53
|
+
* depend on which operating system ran the check.
|
|
54
|
+
*/
|
|
55
|
+
export function resolveLinks(doc, scope) {
|
|
56
|
+
const cases = scope.cases ?? pathCaseIndex();
|
|
57
|
+
const dir = dirname(doc.path);
|
|
58
|
+
const linkage = { resolved: [], broken: [] };
|
|
59
|
+
for (const link of documentLinks(doc.text)) {
|
|
60
|
+
const target = resolve(dir, link.path);
|
|
61
|
+
if (!isPathInside(scope.docsDir, target))
|
|
62
|
+
continue;
|
|
63
|
+
// The overlay is checked FIRST and without the spelling test: the file is
|
|
64
|
+
// not on disk, so there is no stored name to compare against, and the id it
|
|
65
|
+
// came from was a `readdir` entry in the feature's own tree.
|
|
66
|
+
if (scope.overlay?.has(resolve(target)) === true) {
|
|
67
|
+
linkage.resolved.push({ link, target });
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (existsSync(target) && cases.spelledExactly(scope.docsDir, target)) {
|
|
71
|
+
linkage.resolved.push({ link, target });
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const storedAs = cases.storedAs(dirname(target), basename(target));
|
|
75
|
+
linkage.broken.push({ link, ...(storedAs === undefined ? {} : { storedAs }) });
|
|
76
|
+
}
|
|
77
|
+
return linkage;
|
|
78
|
+
}
|
|
79
|
+
/** The links in one document that resolve to nothing, as at most one finding. */
|
|
80
|
+
export function unresolvedLinkFindings(doc, scope) {
|
|
81
|
+
const where = repoPath(scope.docsDir, doc.path);
|
|
82
|
+
const { broken } = resolveLinks(doc, scope);
|
|
83
|
+
if (broken.length === 0)
|
|
84
|
+
return [];
|
|
85
|
+
return [
|
|
86
|
+
{
|
|
87
|
+
severity: "error",
|
|
88
|
+
code: "link.unresolved",
|
|
89
|
+
...(scope.subject === undefined ? {} : { subject: scope.subject }),
|
|
90
|
+
message: `${where}: ${broken.length} markdown link(s) resolve to nothing. ` +
|
|
91
|
+
"A link between documents here is a join — fix the path, or write the document it points at. " +
|
|
92
|
+
"Targets outside the docs repo and links to a heading (`#section`) are not graded.",
|
|
93
|
+
details: broken.map((one) => detail(where, one)),
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* One broken link, spelled so the author can find it: the file and line first,
|
|
99
|
+
* then the link exactly as written. `raw` rather than the decoded path, because
|
|
100
|
+
* the decoded form is not the string that is in the document.
|
|
101
|
+
*
|
|
102
|
+
* The stored spelling is appended when the only thing wrong is case, because
|
|
103
|
+
* without it the message reads as a lie — the file the author is being told to
|
|
104
|
+
* write is already sitting beside the one they linked from.
|
|
105
|
+
*/
|
|
106
|
+
function detail(where, one) {
|
|
107
|
+
const line = `${where}:${one.link.line}: [${one.link.text}](${one.link.raw})`;
|
|
108
|
+
return one.storedAs === undefined ? line : `${line} — stored as '${one.storedAs}'`;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=findings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findings.js","sourceRoot":"","sources":["../../../src/core/links/findings.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `link.unresolved` — a markdown link in an authored document whose target is\n * not there, or is there under a different spelling.\n *\n * The convention this grades has been written down since the fleet-ADR work and\n * said, in both `SCHEMA.md` and the generated `AGENTS.md`, that nothing\n * validated it. This module is what makes that sentence false, and the reason\n * the convention was chosen is the reason it can: the target of a standard\n * markdown link is a real relative path, so \"does this resolve\" has a\n * filesystem answer, where a wikilink would need Obsidian's\n * shortest-unique-path search reimplemented — guessing, which loam does not do.\n *\n * ERROR, and the severity is the decision the maintainer took against the\n * alternative of warning outside `glossary/`. A link is a join of the same kind\n * as `Operations:` and `Covers:`, both of which error when they resolve to\n * nothing; the fact that this one is written in prose changes who typed it, not\n * whether it points at something. A fleet adopting loam with rotted links sees\n * them on its first run — a user-visible widening of the same class as the two\n * the use-case and business axes shipped, and recorded in CHANGELOG as one.\n *\n * ONE FINDING PER DOCUMENT, with every broken link in `details`. A document\n * whose links have rotted is one editing session and one reviewer, not ten\n * separate breaches, and per-link findings would let a single stale directory\n * rename fill a `validate --all` report with the same fix restated.\n *\n * THE RESOLUTION IS EXPORTED SEPARATELY from the finding, because a second\n * reader needs the other half of the answer: the glossary grades a term by who\n * links TO it, which is this same walk read forwards instead of backwards.\n */\nimport { existsSync } from \"node:fs\";\nimport { basename, dirname, resolve } from \"node:path\";\nimport { repoPath } from \"../envelope/json.js\";\nimport { isPathInside } from \"../kernel/path-safety.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport type { Finding } from \"../vocabulary/report.js\";\nimport { pathCaseIndex, type PathCaseIndex } from \"./case.js\";\nimport { documentLinks, type DocumentLink } from \"./parse.js\";\n\n/** One authored document, and the text somebody already read out of it. */\nexport interface LinkedDocument {\n /** Absolute path of the document — the directory every relative target resolves against. */\n path: string;\n text: string;\n}\n\n/** What the finding is filed under, and the tree it may look inside. */\nexport interface LinkScope {\n docsDir: DocsDir;\n /** The service or feature the report is about; absent at fleet scope. */\n subject?: string;\n /**\n * The directory-listing memo behind the case check, shared across a whole\n * run by whoever owns the walk. Omitted, each call builds its own — correct,\n * just not shared. Never a module-level cache: a listing that outlived one\n * command would answer for a directory that has since changed.\n */\n cases?: PathCaseIndex;\n /**\n * Absolute paths that do not exist yet but WILL once an in-flight feature\n * archives — today, the glossary terms that feature introduces.\n *\n * A feature may cite the word it is adding at the word's FUTURE living path,\n * because that is where the link points once the change lands. Without this\n * set, the axis would refuse its own headline case — which is exactly the\n * overlay the capability axis had to add to `Realizes:` after shipping\n * without one. (Citing the term inside the feature resolves without it, and\n * archive re-points that spelling when it merges the text that carries it —\n * SCHEMA \"Conventions\" states the rule.)\n */\n overlay?: ReadonlySet<string>;\n}\n\n/** A link that points at a real file inside the docs repo, and the file it points at. */\nexport interface ResolvedLink {\n link: DocumentLink;\n /** The absolute path the link names, as `node:path` resolves it. */\n target: string;\n}\n\n/** One document's links, split by whether the docs repo answers for them. */\nexport interface DocumentLinkage {\n resolved: ResolvedLink[];\n /** Links inside the docs repo that name nothing — with the stored spelling, where there is one. */\n broken: Array<{ link: DocumentLink; storedAs?: string }>;\n}\n\n/**\n * Every link in one document, resolved against the docs repo.\n *\n * TARGETS OUTSIDE THE DOCS REPO ARE NEITHER RESOLVED NOR BROKEN, and the\n * exclusion is honesty rather than leniency. A docs repo is routinely checked\n * out beside nothing else, so `../../payments-svc/README.md` is a path loam\n * cannot answer for: the file's absence here is evidence about this checkout,\n * not about the link. Only a target that stays inside `docsDir` gets a verdict,\n * which is the same boundary every other reader in loam observes and the reason\n * the finding below can afford to be an error at all.\n *\n * CASE COUNTS, and it must, because the alternative is a check that passes on\n * the author's machine and fails where the work is read. `existsSync` is\n * case-insensitive on Windows and macOS; GitHub's renderer and every Linux CI\n * runner are not. So a resolving link is one that exists AND is spelled the way\n * the filesystem stores it — and when it is not, `storedAs` carries the real\n * name into the message, on every platform alike, so the diagnosis does not\n * depend on which operating system ran the check.\n */\nexport function resolveLinks(doc: LinkedDocument, scope: LinkScope): DocumentLinkage {\n const cases = scope.cases ?? pathCaseIndex();\n const dir = dirname(doc.path);\n const linkage: DocumentLinkage = { resolved: [], broken: [] };\n for (const link of documentLinks(doc.text)) {\n const target = resolve(dir, link.path);\n if (!isPathInside(scope.docsDir, target)) continue;\n // The overlay is checked FIRST and without the spelling test: the file is\n // not on disk, so there is no stored name to compare against, and the id it\n // came from was a `readdir` entry in the feature's own tree.\n if (scope.overlay?.has(resolve(target)) === true) {\n linkage.resolved.push({ link, target });\n continue;\n }\n if (existsSync(target) && cases.spelledExactly(scope.docsDir, target)) {\n linkage.resolved.push({ link, target });\n continue;\n }\n const storedAs = cases.storedAs(dirname(target), basename(target));\n linkage.broken.push({ link, ...(storedAs === undefined ? {} : { storedAs }) });\n }\n return linkage;\n}\n\n/** The links in one document that resolve to nothing, as at most one finding. */\nexport function unresolvedLinkFindings(doc: LinkedDocument, scope: LinkScope): Finding[] {\n const where = repoPath(scope.docsDir, doc.path);\n const { broken } = resolveLinks(doc, scope);\n if (broken.length === 0) return [];\n return [\n {\n severity: \"error\",\n code: \"link.unresolved\",\n ...(scope.subject === undefined ? {} : { subject: scope.subject }),\n message:\n `${where}: ${broken.length} markdown link(s) resolve to nothing. ` +\n \"A link between documents here is a join — fix the path, or write the document it points at. \" +\n \"Targets outside the docs repo and links to a heading (`#section`) are not graded.\",\n details: broken.map((one) => detail(where, one)),\n },\n ];\n}\n\n/**\n * One broken link, spelled so the author can find it: the file and line first,\n * then the link exactly as written. `raw` rather than the decoded path, because\n * the decoded form is not the string that is in the document.\n *\n * The stored spelling is appended when the only thing wrong is case, because\n * without it the message reads as a lie — the file the author is being told to\n * write is already sitting beside the one they linked from.\n */\nfunction detail(where: string, one: { link: DocumentLink; storedAs?: string }): string {\n const line = `${where}:${one.link.line}: [${one.link.text}](${one.link.raw})`;\n return one.storedAs === undefined ? line : `${line} — stored as '${one.storedAs}'`;\n}\n"]}
|