@ybotok/loam 0.1.0-beta.3 → 0.2.0-alpha.2
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 +2635 -86
- package/COMPARISON.md +80 -63
- package/CONTRIBUTING.md +84 -0
- package/MIGRATING-from-OpenSpec.md +213 -54
- package/QUICKSTART.md +125 -0
- package/README.md +450 -150
- package/ROADMAP.md +599 -0
- package/SCHEMA.md +2355 -256
- package/SECURITY.md +21 -3
- package/WORKFLOW.md +701 -0
- package/dist/cli.js +113 -19
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +209 -0
- package/dist/commands/adopt/adopt.js.map +1 -0
- package/dist/commands/adopt/render.js +268 -0
- package/dist/commands/adopt/render.js.map +1 -0
- package/dist/commands/archive/archive.js +77 -0
- package/dist/commands/archive/archive.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +159 -0
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +323 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +180 -0
- package/dist/commands/archive/plan/landscape.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +111 -0
- package/dist/commands/archive/plan/refusal.js.map +1 -0
- package/dist/commands/archive/plan/specs.js +237 -0
- package/dist/commands/archive/plan/specs.js.map +1 -0
- package/dist/commands/archive/plan/state.js +27 -0
- package/dist/commands/archive/plan/state.js.map +1 -0
- package/dist/commands/archive/run.js +252 -0
- package/dist/commands/archive/run.js.map +1 -0
- package/dist/commands/context/context.js +99 -0
- package/dist/commands/context/context.js.map +1 -0
- package/dist/commands/context/print.js +283 -0
- package/dist/commands/context/print.js.map +1 -0
- package/dist/commands/delta/delta.js +279 -0
- package/dist/commands/delta/delta.js.map +1 -0
- package/dist/commands/delta/print.js +205 -0
- package/dist/commands/delta/print.js.map +1 -0
- package/dist/commands/dependencies.js +9 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +93 -0
- package/dist/commands/diff/diff.js.map +1 -0
- package/dist/commands/diff/print.js +38 -0
- package/dist/commands/diff/print.js.map +1 -0
- package/dist/commands/doctor.js +5 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +151 -0
- package/dist/commands/explain/explain.js.map +1 -0
- package/dist/commands/explore.js +29 -24
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +115 -0
- package/dist/commands/gate/gate.js.map +1 -0
- package/dist/commands/gate/print.js +54 -0
- package/dist/commands/gate/print.js.map +1 -0
- package/dist/commands/gherkin/commit.js +146 -0
- package/dist/commands/gherkin/commit.js.map +1 -0
- package/dist/commands/gherkin/gherkin.js +246 -0
- package/dist/commands/gherkin/gherkin.js.map +1 -0
- package/dist/commands/gherkin/reconcile.js +149 -0
- package/dist/commands/gherkin/reconcile.js.map +1 -0
- package/dist/commands/gherkin/render.js +77 -0
- package/dist/commands/gherkin/render.js.map +1 -0
- package/dist/commands/init/example.js +204 -0
- package/dist/commands/init/example.js.map +1 -0
- package/dist/commands/init/first-hour.js +28 -0
- package/dist/commands/init/first-hour.js.map +1 -0
- package/dist/commands/{init.js → init/init.js} +146 -71
- package/dist/commands/init/init.js.map +1 -0
- package/dist/commands/init/mcp.js +131 -0
- package/dist/commands/init/mcp.js.map +1 -0
- package/dist/commands/init/options.js +78 -0
- package/dist/commands/init/options.js.map +1 -0
- package/dist/commands/instructions.js +72 -14
- package/dist/commands/instructions.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +65 -0
- package/dist/commands/list/campaign/campaign.js.map +1 -0
- package/dist/commands/list/campaign/owners.js +65 -0
- package/dist/commands/list/campaign/owners.js.map +1 -0
- package/dist/commands/list/json.js +157 -0
- package/dist/commands/list/json.js.map +1 -0
- package/dist/commands/list/list.js +343 -0
- package/dist/commands/list/list.js.map +1 -0
- package/dist/commands/list/print.js +301 -0
- package/dist/commands/list/print.js.map +1 -0
- package/dist/commands/list/review.js +120 -0
- package/dist/commands/list/review.js.map +1 -0
- package/dist/commands/list/views.js +105 -0
- package/dist/commands/list/views.js.map +1 -0
- package/dist/commands/mcp/dispatch.js +183 -0
- package/dist/commands/mcp/dispatch.js.map +1 -0
- package/dist/commands/mcp/mcp.js +34 -0
- package/dist/commands/mcp/mcp.js.map +1 -0
- package/dist/commands/mcp/serve.js +55 -0
- package/dist/commands/mcp/serve.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/active.js +170 -0
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
- package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/living.js +62 -0
- package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/target.js +219 -0
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/error.js +19 -0
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/read.js +49 -0
- package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
- package/dist/commands/migrate-openspec/print.js +89 -0
- package/dist/commands/migrate-openspec/print.js.map +1 -0
- package/dist/commands/new/commit.js +76 -0
- package/dist/commands/new/commit.js.map +1 -0
- package/dist/commands/new/new.js +283 -0
- package/dist/commands/new/new.js.map +1 -0
- package/dist/commands/new/templates.js +355 -0
- package/dist/commands/new/templates.js.map +1 -0
- package/dist/commands/open.js +203 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/policy/format.js +152 -0
- package/dist/commands/policy/format.js.map +1 -0
- package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
- package/dist/commands/policy/gate.js.map +1 -0
- package/dist/commands/rebase/edit.js +106 -0
- package/dist/commands/rebase/edit.js.map +1 -0
- package/dist/commands/rebase/living.js +185 -0
- package/dist/commands/rebase/living.js.map +1 -0
- package/dist/commands/rebase/plan.js +245 -0
- package/dist/commands/rebase/plan.js.map +1 -0
- package/dist/commands/rebase/rebase.js +275 -0
- package/dist/commands/rebase/rebase.js.map +1 -0
- package/dist/commands/seed/commit.js +192 -0
- package/dist/commands/seed/commit.js.map +1 -0
- package/dist/commands/seed/plan.js +133 -0
- package/dist/commands/seed/plan.js.map +1 -0
- package/dist/commands/seed/seed.js +172 -0
- package/dist/commands/seed/seed.js.map +1 -0
- package/dist/commands/show/feature.js +178 -0
- package/dist/commands/show/feature.js.map +1 -0
- package/dist/commands/show/marks.js +24 -0
- package/dist/commands/show/marks.js.map +1 -0
- package/dist/commands/{show.js → show/service.js} +63 -152
- package/dist/commands/show/service.js.map +1 -0
- package/dist/commands/show/show.js +77 -0
- package/dist/commands/show/show.js.map +1 -0
- package/dist/commands/status/json.js +45 -0
- package/dist/commands/status/json.js.map +1 -0
- package/dist/commands/status/print.js +157 -0
- package/dist/commands/status/print.js.map +1 -0
- package/dist/commands/status/status.js +119 -0
- package/dist/commands/status/status.js.map +1 -0
- package/dist/commands/steps/steps.js +189 -0
- package/dist/commands/steps/steps.js.map +1 -0
- package/dist/commands/subsystem/edit.js +192 -0
- package/dist/commands/subsystem/edit.js.map +1 -0
- package/dist/commands/subsystem/move.js +265 -0
- package/dist/commands/subsystem/move.js.map +1 -0
- package/dist/commands/subsystem/query.js +115 -0
- package/dist/commands/subsystem/query.js.map +1 -0
- package/dist/commands/subsystem/subsystem.js +101 -0
- package/dist/commands/subsystem/subsystem.js.map +1 -0
- package/dist/commands/subsystem/sync.js +103 -0
- package/dist/commands/subsystem/sync.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +94 -0
- package/dist/commands/subsystem/txn/txn.js.map +1 -0
- package/dist/commands/subsystem/txn/views.js +70 -0
- package/dist/commands/subsystem/txn/views.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +160 -0
- package/dist/commands/unarchive/manifest.js.map +1 -0
- package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
- package/dist/commands/unarchive/restore.js.map +1 -0
- package/dist/commands/unarchive/unarchive.js +67 -0
- package/dist/commands/unarchive/unarchive.js.map +1 -0
- package/dist/commands/validate/arch-coverage.js +204 -0
- package/dist/commands/validate/arch-coverage.js.map +1 -0
- package/dist/commands/validate/checks/fleet-shape.js +296 -0
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
- package/dist/commands/validate/checks/health-deps.js +55 -0
- package/dist/commands/validate/checks/health-deps.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +254 -0
- package/dist/commands/validate/checks/requirements.js.map +1 -0
- package/dist/commands/validate/checks/sources.js +76 -0
- package/dist/commands/validate/checks/sources.js.map +1 -0
- package/dist/commands/validate/checks/vocabulary.js +31 -0
- package/dist/commands/validate/checks/vocabulary.js.map +1 -0
- package/dist/commands/validate/feature.js +216 -0
- package/dist/commands/validate/feature.js.map +1 -0
- package/dist/commands/validate/fleet/census.js +69 -0
- package/dist/commands/validate/fleet/census.js.map +1 -0
- package/dist/commands/validate/fleet/kind-tags.js +39 -0
- package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
- package/dist/commands/validate/fleet/landscape.js +353 -0
- package/dist/commands/validate/fleet/landscape.js.map +1 -0
- package/dist/commands/validate/fleet/load.js +52 -0
- package/dist/commands/validate/fleet/load.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +270 -0
- package/dist/commands/validate/fleet/obligations.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/print.js +63 -0
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/place.js +13 -0
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/steps.js +197 -0
- package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +40 -0
- package/dist/commands/validate/fleet/views/ids.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +59 -0
- package/dist/commands/validate/fleet/views/stale.js.map +1 -0
- package/dist/commands/validate/links/corpus.js +92 -0
- package/dist/commands/validate/links/corpus.js.map +1 -0
- package/dist/commands/validate/links/glossary.js +75 -0
- package/dist/commands/validate/links/glossary.js.map +1 -0
- package/dist/commands/validate/report.js +269 -0
- package/dist/commands/validate/report.js.map +1 -0
- package/dist/commands/validate/service/api.js +338 -0
- package/dist/commands/validate/service/api.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +219 -0
- package/dist/commands/validate/service/events/events.js.map +1 -0
- package/dist/commands/validate/service/events/message-producers.js +77 -0
- package/dist/commands/validate/service/events/message-producers.js.map +1 -0
- package/dist/commands/validate/service/evidence-pins.js +131 -0
- package/dist/commands/validate/service/evidence-pins.js.map +1 -0
- package/dist/commands/validate/service/service.js +251 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +251 -0
- package/dist/commands/validate/service/specs.js.map +1 -0
- package/dist/commands/validate/service/spine.js +113 -0
- package/dist/commands/validate/service/spine.js.map +1 -0
- package/dist/commands/validate/validate.js +338 -0
- package/dist/commands/validate/validate.js.map +1 -0
- package/dist/commands/verify/cross/diff.js +182 -0
- package/dist/commands/verify/cross/diff.js.map +1 -0
- package/dist/commands/verify/evidence/contract.js +32 -0
- package/dist/commands/verify/evidence/contract.js.map +1 -0
- package/dist/commands/verify/evidence/pins.js +52 -0
- package/dist/commands/verify/evidence/pins.js.map +1 -0
- package/dist/commands/verify/evidence/read.js +72 -0
- package/dist/commands/verify/evidence/read.js.map +1 -0
- package/dist/commands/verify/frozen.js +241 -0
- package/dist/commands/verify/frozen.js.map +1 -0
- package/dist/commands/verify/record.js +226 -0
- package/dist/commands/verify/record.js.map +1 -0
- package/dist/commands/verify/report.js +223 -0
- package/dist/commands/verify/report.js.map +1 -0
- package/dist/commands/verify/results.js +246 -0
- package/dist/commands/verify/results.js.map +1 -0
- package/dist/commands/verify/verify.js +217 -0
- package/dist/commands/verify/verify.js.map +1 -0
- package/dist/commands/vouch/contract.js +11 -0
- package/dist/commands/vouch/contract.js.map +1 -0
- package/dist/commands/vouch/pack/contract.js +2 -0
- package/dist/commands/vouch/pack/contract.js.map +1 -0
- package/dist/commands/vouch/pack/pack.js +266 -0
- package/dist/commands/vouch/pack/pack.js.map +1 -0
- package/dist/commands/vouch/pack/print.js +271 -0
- package/dist/commands/vouch/pack/print.js.map +1 -0
- package/dist/commands/vouch/pack/sections.js +142 -0
- package/dist/commands/vouch/pack/sections.js.map +1 -0
- package/dist/commands/vouch/prompt/confirm.js +46 -0
- package/dist/commands/vouch/prompt/confirm.js.map +1 -0
- package/dist/commands/vouch/report.js +101 -0
- package/dist/commands/vouch/report.js.map +1 -0
- package/dist/commands/vouch/run.js +285 -0
- package/dist/commands/vouch/run.js.map +1 -0
- package/dist/commands/vouch/sample/plan.js +125 -0
- package/dist/commands/vouch/sample/plan.js.map +1 -0
- package/dist/commands/vouch/sample/print.js +109 -0
- package/dist/commands/vouch/sample/print.js.map +1 -0
- package/dist/commands/vouch/vet/verify.js +123 -0
- package/dist/commands/vouch/vet/verify.js.map +1 -0
- package/dist/commands/vouch/vouch.js +176 -0
- package/dist/commands/vouch/vouch.js.map +1 -0
- package/dist/core/agent/agents-md/artifacts.js +220 -0
- package/dist/core/agent/agents-md/artifacts.js.map +1 -0
- package/dist/core/agent/agents-md/command-map.js +324 -0
- package/dist/core/agent/agents-md/command-map.js.map +1 -0
- package/dist/core/agent/agents-md/cycle.js +165 -0
- package/dist/core/agent/agents-md/cycle.js.map +1 -0
- package/dist/core/agent/agents-md/map/explain.js +34 -0
- package/dist/core/agent/agents-md/map/explain.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
- package/dist/core/agent/agents-md/map/mcp.js +41 -0
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
- package/dist/core/agent/agents-md/map/status.js +62 -0
- package/dist/core/agent/agents-md/map/status.js.map +1 -0
- package/dist/core/agent/agents-md/map/subsystem.js +32 -0
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
- package/dist/core/agent/agents-md/map/usecases.js +60 -0
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
- package/dist/core/agent/agents-md/refusals.js +246 -0
- package/dist/core/agent/agents-md/refusals.js.map +1 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
- package/dist/core/agent/agents-md/spine.js +40 -0
- package/dist/core/agent/agents-md/spine.js.map +1 -0
- package/dist/core/agent/agents-md.js +170 -0
- package/dist/core/agent/agents-md.js.map +1 -0
- package/dist/core/{agents-stamp.js → agent/agents-stamp.js} +50 -0
- package/dist/core/agent/agents-stamp.js.map +1 -0
- package/dist/core/agent/contract.js +2 -0
- package/dist/core/agent/contract.js.map +1 -0
- package/dist/core/agent/protocol.js +193 -0
- package/dist/core/agent/protocol.js.map +1 -0
- package/dist/core/agent/scaffold.js +259 -0
- package/dist/core/agent/scaffold.js.map +1 -0
- package/dist/core/agent/support/report.js +103 -0
- package/dist/core/agent/support/report.js.map +1 -0
- package/dist/core/agent/tools/dialects.js +150 -0
- package/dist/core/agent/tools/dialects.js.map +1 -0
- package/dist/core/agent/tools/registry.js +228 -0
- package/dist/core/agent/tools/registry.js.map +1 -0
- package/dist/core/agent/workflows/adopt.js +133 -0
- package/dist/core/agent/workflows/adopt.js.map +1 -0
- package/dist/core/agent/workflows/check.js +337 -0
- package/dist/core/agent/workflows/check.js.map +1 -0
- package/dist/core/agent/workflows/closing.js +262 -0
- package/dist/core/agent/workflows/closing.js.map +1 -0
- package/dist/core/agent/workflows/feature.js +105 -0
- package/dist/core/agent/workflows/feature.js.map +1 -0
- package/dist/core/agent/workflows/implement.js +105 -0
- package/dist/core/agent/workflows/implement.js.map +1 -0
- package/dist/core/agent/workflows/reference/authoring.js +209 -0
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
- package/dist/core/agent/workflows/reference/codes.js +65 -0
- package/dist/core/agent/workflows/reference/codes.js.map +1 -0
- package/dist/core/agent/workflows/reference/done-check.js +188 -0
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +16 -0
- package/dist/core/agent/workflows/reference/reference.js.map +1 -0
- package/dist/core/agent/workflows/reference/spine.js +297 -0
- package/dist/core/agent/workflows/reference/spine.js.map +1 -0
- package/dist/core/asyncapi/baseline/plan.js +110 -0
- package/dist/core/asyncapi/baseline/plan.js.map +1 -0
- package/dist/core/asyncapi/baseline/surfaces.js +54 -0
- package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
- package/dist/core/asyncapi/depth.js +164 -0
- package/dist/core/asyncapi/depth.js.map +1 -0
- package/dist/core/asyncapi/digest.js +141 -0
- package/dist/core/asyncapi/digest.js.map +1 -0
- package/dist/core/asyncapi/merge/components.js +91 -0
- package/dist/core/asyncapi/merge/components.js.map +1 -0
- package/dist/core/asyncapi/merge/error.js +33 -0
- package/dist/core/asyncapi/merge/error.js.map +1 -0
- package/dist/core/asyncapi/merge/markers.js +308 -0
- package/dist/core/asyncapi/merge/markers.js.map +1 -0
- package/dist/core/asyncapi/merge/merge.js +215 -0
- package/dist/core/asyncapi/merge/merge.js.map +1 -0
- package/dist/core/asyncapi/merge/pin.js +107 -0
- package/dist/core/asyncapi/merge/pin.js.map +1 -0
- package/dist/core/asyncapi/model.js +11 -0
- package/dist/core/asyncapi/model.js.map +1 -0
- package/dist/core/asyncapi/producers.js +50 -0
- package/dist/core/asyncapi/producers.js.map +1 -0
- package/dist/core/{asyncapi.js → asyncapi/read.js} +95 -109
- package/dist/core/asyncapi/read.js.map +1 -0
- package/dist/core/brief/brief.js +225 -0
- package/dist/core/brief/brief.js.map +1 -0
- package/dist/core/brief/checks.js +250 -0
- package/dist/core/brief/checks.js.map +1 -0
- package/dist/core/brief/landscape.js +194 -0
- package/dist/core/brief/landscape.js.map +1 -0
- package/dist/core/brief/targets.js +226 -0
- package/dist/core/brief/targets.js.map +1 -0
- package/dist/core/brief/unchecked.js +48 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +174 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/likec4.js +90 -0
- package/dist/core/c4/likec4.js.map +1 -0
- package/dist/core/c4/parsed/deployment.js +158 -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 +57 -0
- package/dist/core/c4/parsed/values.js.map +1 -0
- package/dist/core/c4/parsed/view-ids.js +75 -0
- package/dist/core/c4/parsed/view-ids.js.map +1 -0
- package/dist/core/c4/project/architecture.js +45 -0
- package/dist/core/c4/project/architecture.js.map +1 -0
- package/dist/core/c4/project/documents.js +58 -0
- package/dist/core/c4/project/documents.js.map +1 -0
- package/dist/core/c4/project/load.js +187 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/project/staged.js +220 -0
- package/dist/core/c4/project/staged.js.map +1 -0
- package/dist/core/c4/resolve/service.js +159 -0
- package/dist/core/c4/resolve/service.js.map +1 -0
- package/dist/core/c4/resolve/steps.js +144 -0
- package/dist/core/c4/resolve/steps.js.map +1 -0
- package/dist/core/c4/seed/fleet-file.js +201 -0
- package/dist/core/c4/seed/fleet-file.js.map +1 -0
- package/dist/core/c4/seed/items.js +91 -0
- package/dist/core/c4/seed/items.js.map +1 -0
- package/dist/core/c4/seed/stamp.js +48 -0
- package/dist/core/c4/seed/stamp.js.map +1 -0
- package/dist/core/c4/seed/template.js +109 -0
- package/dist/core/c4/seed/template.js.map +1 -0
- package/dist/core/c4/source-mask.js +82 -0
- package/dist/core/c4/source-mask.js.map +1 -0
- package/dist/core/c4/source-scan.js +227 -0
- package/dist/core/c4/source-scan.js.map +1 -0
- package/dist/core/c4/splice/authored-source.js +101 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -0
- package/dist/core/c4/splice/contract.js +10 -0
- package/dist/core/c4/splice/contract.js.map +1 -0
- package/dist/core/c4/splice/delta-blocks.js +139 -0
- package/dist/core/c4/splice/delta-blocks.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +36 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +269 -0
- package/dist/core/c4/splice/landscape-merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +194 -0
- package/dist/core/c4/splice/placement.js.map +1 -0
- package/dist/core/c4/workspace.js +174 -0
- package/dist/core/c4/workspace.js.map +1 -0
- package/dist/core/capabilities/authoring/notes.js +141 -0
- package/dist/core/capabilities/authoring/notes.js.map +1 -0
- package/dist/core/capabilities/capabilities.js +124 -0
- package/dist/core/capabilities/capabilities.js.map +1 -0
- package/dist/core/capabilities/delta/doc.js +50 -0
- package/dist/core/capabilities/delta/doc.js.map +1 -0
- package/dist/core/capabilities/delta/overlay.js +129 -0
- package/dist/core/capabilities/delta/overlay.js.map +1 -0
- package/dist/core/capabilities/delta/summary.js +57 -0
- package/dist/core/capabilities/delta/summary.js.map +1 -0
- package/dist/core/capabilities/delta/tree.js +43 -0
- package/dist/core/capabilities/delta/tree.js.map +1 -0
- package/dist/core/capabilities/delta/uncovered.js +222 -0
- package/dist/core/capabilities/delta/uncovered.js.map +1 -0
- package/dist/core/capabilities/findings.js +342 -0
- package/dist/core/capabilities/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/findings.js +181 -0
- package/dist/core/capabilities/realizes/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/join.js +143 -0
- package/dist/core/capabilities/realizes/join.js.map +1 -0
- package/dist/core/capabilities/rollup.js +199 -0
- package/dist/core/capabilities/rollup.js.map +1 -0
- package/dist/core/capabilities/tree.js +126 -0
- package/dist/core/capabilities/tree.js.map +1 -0
- package/dist/core/capabilities/usecase-join.js +277 -0
- package/dist/core/capabilities/usecase-join.js.map +1 -0
- package/dist/core/coherence/authoring/scaffold.js +177 -0
- package/dist/core/coherence/authoring/scaffold.js.map +1 -0
- package/dist/core/coherence/authoring/sentinels.js +74 -0
- package/dist/core/coherence/authoring/sentinels.js.map +1 -0
- package/dist/core/coherence/coherence.js +287 -0
- package/dist/core/coherence/coherence.js.map +1 -0
- package/dist/core/coherence/declared.js +269 -0
- package/dist/core/coherence/declared.js.map +1 -0
- package/dist/core/coherence/events/declared.js +206 -0
- package/dist/core/coherence/events/declared.js.map +1 -0
- package/dist/core/coherence/events/events.js +204 -0
- package/dist/core/coherence/events/events.js.map +1 -0
- package/dist/core/coherence/events/grades.js +166 -0
- package/dist/core/coherence/events/grades.js.map +1 -0
- package/dist/core/coherence/events/lookups.js +122 -0
- package/dist/core/coherence/events/lookups.js.map +1 -0
- package/dist/core/coherence/events/merged.js +40 -0
- package/dist/core/coherence/events/merged.js.map +1 -0
- package/dist/core/coherence/living.js +198 -0
- package/dist/core/coherence/living.js.map +1 -0
- package/dist/core/coherence/lookups.js +155 -0
- package/dist/core/coherence/lookups.js.map +1 -0
- package/dist/core/coherence/pending.js +39 -0
- package/dist/core/coherence/pending.js.map +1 -0
- package/dist/core/conflict-markers.js +60 -0
- package/dist/core/conflict-markers.js.map +1 -0
- package/dist/core/delta/claims.js +73 -0
- package/dist/core/delta/claims.js.map +1 -0
- package/dist/core/delta/delta.js +296 -0
- package/dist/core/delta/delta.js.map +1 -0
- package/dist/core/delta/document.js +177 -0
- package/dist/core/delta/document.js.map +1 -0
- package/dist/core/delta/scope.js +48 -0
- package/dist/core/delta/scope.js.map +1 -0
- package/dist/core/delta/select.js +237 -0
- package/dist/core/delta/select.js.map +1 -0
- package/dist/core/dependencies/dependencies.js +141 -0
- package/dist/core/dependencies/dependencies.js.map +1 -0
- package/dist/core/dependencies/facts.js +165 -0
- package/dist/core/dependencies/facts.js.map +1 -0
- package/dist/core/dependencies/fanin.js +88 -0
- package/dist/core/dependencies/fanin.js.map +1 -0
- package/dist/core/dependencies/graph.js +106 -0
- package/dist/core/dependencies/graph.js.map +1 -0
- package/dist/core/deployment/delta.js +188 -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 +148 -0
- package/dist/core/diff/base-git.js.map +1 -0
- package/dist/core/diff/base-state.js +209 -0
- package/dist/core/diff/base-state.js.map +1 -0
- package/dist/core/diff/current-state.js +96 -0
- package/dist/core/diff/current-state.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +188 -0
- package/dist/core/diff/scope/changed-paths.js.map +1 -0
- package/dist/core/diff/semantic.js +243 -0
- package/dist/core/diff/semantic.js.map +1 -0
- package/dist/core/diff/victims.js +247 -0
- package/dist/core/diff/victims.js.map +1 -0
- package/dist/core/docs.js +7 -64
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +222 -0
- package/dist/core/doctor/agents.js.map +1 -0
- package/dist/core/doctor/config.js +135 -0
- package/dist/core/doctor/config.js.map +1 -0
- package/dist/core/doctor/doctor.js +275 -0
- package/dist/core/doctor/doctor.js.map +1 -0
- package/dist/core/doctor/report.js +13 -0
- package/dist/core/doctor/report.js.map +1 -0
- package/dist/core/doctor/residue.js +110 -0
- package/dist/core/doctor/residue.js.map +1 -0
- package/dist/core/document/apply.js +77 -0
- package/dist/core/document/apply.js.map +1 -0
- package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
- package/dist/core/document/frontmatter.js.map +1 -0
- package/dist/core/document/grammar/directives.js +143 -0
- package/dist/core/document/grammar/directives.js.map +1 -0
- package/dist/core/document/parse.js +263 -0
- package/dist/core/document/parse.js.map +1 -0
- package/dist/core/document/scenarios.js +221 -0
- package/dist/core/document/scenarios.js.map +1 -0
- package/dist/core/document/spec.js +241 -0
- package/dist/core/document/spec.js.map +1 -0
- package/dist/core/{config.js → envelope/config.js} +79 -18
- package/dist/core/envelope/config.js.map +1 -0
- package/dist/core/envelope/json.js +175 -0
- package/dist/core/envelope/json.js.map +1 -0
- package/dist/core/{version.js → envelope/version.js} +3 -3
- package/dist/core/envelope/version.js.map +1 -0
- package/dist/core/explain/families.js +175 -0
- package/dist/core/explain/families.js.map +1 -0
- package/dist/core/explain/fix-tables.js +178 -0
- package/dist/core/explain/fix-tables.js.map +1 -0
- package/dist/core/explain/lookup.js +181 -0
- package/dist/core/explain/lookup.js.map +1 -0
- package/dist/core/explain/refusals.js +49 -0
- package/dist/core/explain/refusals.js.map +1 -0
- package/dist/core/explain/terms.js +144 -0
- package/dist/core/explain/terms.js.map +1 -0
- package/dist/core/explore/describe.js +105 -0
- package/dist/core/explore/describe.js.map +1 -0
- package/dist/core/{explore.js → explore/explore.js} +50 -101
- package/dist/core/explore/explore.js.map +1 -0
- package/dist/core/fleet-context.js +120 -78
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +201 -0
- package/dist/core/gate/checks.js.map +1 -0
- package/dist/core/gate/partners.js +205 -0
- package/dist/core/gate/partners.js.map +1 -0
- package/dist/core/gate/report.js +47 -0
- package/dist/core/gate/report.js.map +1 -0
- package/dist/core/gate/verification.js +113 -0
- package/dist/core/gate/verification.js.map +1 -0
- package/dist/core/gherkin/digest.js +60 -0
- package/dist/core/gherkin/digest.js.map +1 -0
- package/dist/core/gherkin/emit.js +177 -0
- package/dist/core/gherkin/emit.js.map +1 -0
- package/dist/core/gherkin/read.js +76 -0
- package/dist/core/gherkin/read.js.map +1 -0
- package/dist/core/gherkin/stale.js +234 -0
- package/dist/core/gherkin/stale.js.map +1 -0
- package/dist/core/gherkin/stamp.js +77 -0
- package/dist/core/gherkin/stamp.js.map +1 -0
- package/dist/core/gherkin/steps/catalogue.js +154 -0
- package/dist/core/gherkin/steps/catalogue.js.map +1 -0
- package/dist/core/gherkin/steps/inventory.js +98 -0
- package/dist/core/gherkin/steps/inventory.js.map +1 -0
- package/dist/core/gherkin/steps/phrase.js +72 -0
- package/dist/core/gherkin/steps/phrase.js.map +1 -0
- package/dist/core/glossary/delta.js +106 -0
- package/dist/core/glossary/delta.js.map +1 -0
- package/dist/core/glossary/tree.js +86 -0
- package/dist/core/glossary/tree.js.map +1 -0
- package/dist/core/kernel/concurrency.js.map +1 -0
- package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
- package/dist/core/kernel/document-bytes.js.map +1 -0
- package/dist/core/kernel/fences.js +42 -0
- package/dist/core/kernel/fences.js.map +1 -0
- package/dist/core/kernel/ids/capability.js +72 -0
- package/dist/core/kernel/ids/capability.js.map +1 -0
- package/dist/core/kernel/ids/dirs.js +33 -0
- package/dist/core/kernel/ids/dirs.js.map +1 -0
- package/dist/core/kernel/ids/feature.js +75 -0
- package/dist/core/kernel/ids/feature.js.map +1 -0
- package/dist/core/kernel/ids/service.js +156 -0
- package/dist/core/kernel/ids/service.js.map +1 -0
- package/dist/core/kernel/ids/subsystem.js +63 -0
- package/dist/core/kernel/ids/subsystem.js.map +1 -0
- package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
- package/dist/core/kernel/path-safety.js.map +1 -0
- package/dist/core/kernel/records.js.map +1 -0
- package/dist/core/links/backlinks.js +68 -0
- package/dist/core/links/backlinks.js.map +1 -0
- package/dist/core/links/case.js +74 -0
- package/dist/core/links/case.js.map +1 -0
- package/dist/core/links/corpus.js +104 -0
- package/dist/core/links/corpus.js.map +1 -0
- package/dist/core/links/findings.js +110 -0
- package/dist/core/links/findings.js.map +1 -0
- package/dist/core/links/parse.js +236 -0
- package/dist/core/links/parse.js.map +1 -0
- package/dist/core/mcp/argv.js +111 -0
- package/dist/core/mcp/argv.js.map +1 -0
- package/dist/core/mcp/author-tools.js +72 -0
- package/dist/core/mcp/author-tools.js.map +1 -0
- package/dist/core/mcp/framing.js +46 -0
- package/dist/core/mcp/framing.js.map +1 -0
- package/dist/core/mcp/protocol.js +280 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/tools.js +314 -0
- package/dist/core/mcp/tools.js.map +1 -0
- package/dist/core/obligations/obligations.js +106 -0
- package/dist/core/obligations/obligations.js.map +1 -0
- package/dist/core/openapi/baseline/gate.js +166 -0
- package/dist/core/openapi/baseline/gate.js.map +1 -0
- package/dist/core/openapi/baseline/plan.js +112 -0
- package/dist/core/openapi/baseline/plan.js.map +1 -0
- package/dist/core/openapi/baseline/record.js +175 -0
- package/dist/core/openapi/baseline/record.js.map +1 -0
- package/dist/core/openapi/depth.js +122 -0
- package/dist/core/openapi/depth.js.map +1 -0
- package/dist/core/openapi/digest.js +189 -0
- package/dist/core/openapi/digest.js.map +1 -0
- package/dist/core/{openapi.js → openapi/doc.js} +44 -106
- package/dist/core/openapi/doc.js.map +1 -0
- package/dist/core/openapi/merge/components.js +234 -0
- package/dist/core/openapi/merge/components.js.map +1 -0
- package/dist/core/openapi/merge/error.js +26 -0
- package/dist/core/openapi/merge/error.js.map +1 -0
- package/dist/core/openapi/merge/markers.js +208 -0
- package/dist/core/openapi/merge/markers.js.map +1 -0
- package/dist/core/openapi/merge/merge.js +256 -0
- package/dist/core/openapi/merge/merge.js.map +1 -0
- package/dist/core/openapi/merge/pin.js +128 -0
- package/dist/core/openapi/merge/pin.js.map +1 -0
- package/dist/core/openspec/decide/capabilities.js +244 -0
- package/dist/core/openspec/decide/capabilities.js.map +1 -0
- package/dist/core/openspec/decide/dispositions.js +63 -0
- package/dist/core/openspec/decide/dispositions.js.map +1 -0
- package/dist/core/openspec/decide/renames.js +179 -0
- package/dist/core/openspec/decide/renames.js.map +1 -0
- package/dist/core/openspec/decide/verdict.js +84 -0
- package/dist/core/openspec/decide/verdict.js.map +1 -0
- package/dist/core/openspec/inventory.js +221 -0
- package/dist/core/openspec/inventory.js.map +1 -0
- package/dist/core/openspec/model/mapping.js +76 -0
- package/dist/core/openspec/model/mapping.js.map +1 -0
- package/dist/core/openspec/model/model.js +65 -0
- package/dist/core/openspec/model/model.js.map +1 -0
- package/dist/core/openspec/scan/changes.js +235 -0
- package/dist/core/openspec/scan/changes.js.map +1 -0
- package/dist/core/openspec/scan/config.js +200 -0
- package/dist/core/openspec/scan/config.js.map +1 -0
- package/dist/core/openspec/scan/shape.js +260 -0
- package/dist/core/openspec/scan/shape.js.map +1 -0
- package/dist/core/openspec/scan/walk.js +133 -0
- package/dist/core/openspec/scan/walk.js.map +1 -0
- package/dist/core/openspec/scan/workspace.js +71 -0
- package/dist/core/openspec/scan/workspace.js.map +1 -0
- package/dist/core/owners/codeowners.js +141 -0
- package/dist/core/owners/codeowners.js.map +1 -0
- package/dist/core/pack/features.js +120 -0
- package/dist/core/pack/features.js.map +1 -0
- package/dist/core/pack/joins.js +112 -0
- package/dist/core/pack/joins.js.map +1 -0
- package/dist/core/pack/living.js +137 -0
- package/dist/core/pack/living.js.map +1 -0
- package/dist/core/pack/pack.js +76 -0
- package/dist/core/pack/pack.js.map +1 -0
- package/dist/core/permissions/permissions.js +87 -0
- package/dist/core/permissions/permissions.js.map +1 -0
- package/dist/core/projection/api.js +84 -0
- package/dist/core/projection/api.js.map +1 -0
- package/dist/core/projection/arch-slice.js +63 -0
- package/dist/core/projection/arch-slice.js.map +1 -0
- package/dist/core/projection/events.js +34 -0
- package/dist/core/projection/events.js.map +1 -0
- package/dist/core/provenance/findings.js +208 -0
- package/dist/core/provenance/findings.js.map +1 -0
- package/dist/core/provenance/git.js +225 -0
- package/dist/core/provenance/git.js.map +1 -0
- package/dist/core/provenance/gitq/moves.js +182 -0
- package/dist/core/provenance/gitq/moves.js.map +1 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
- package/dist/core/provenance/sample/scope.js +238 -0
- package/dist/core/provenance/sample/scope.js.map +1 -0
- package/dist/core/provenance/sample/sections.js +61 -0
- package/dist/core/provenance/sample/sections.js.map +1 -0
- package/dist/core/provenance/sources.js +263 -0
- package/dist/core/provenance/sources.js.map +1 -0
- package/dist/core/provenance/stamp.js +232 -0
- package/dist/core/provenance/stamp.js.map +1 -0
- package/dist/core/provenance/walk.js +157 -0
- package/dist/core/provenance/walk.js.map +1 -0
- package/dist/core/repo/authored/paths.js +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 +277 -0
- package/dist/core/repo/paths.js.map +1 -0
- package/dist/core/repo/repo.js +248 -0
- package/dist/core/repo/repo.js.map +1 -0
- package/dist/core/repo/service-target.js +153 -0
- package/dist/core/repo/service-target.js.map +1 -0
- package/dist/core/repo/state.js +58 -0
- package/dist/core/repo/state.js.map +1 -0
- package/dist/core/repo/tree/find.js +52 -0
- package/dist/core/repo/tree/find.js.map +1 -0
- package/dist/core/repo/tree/fs.js +64 -0
- package/dist/core/repo/tree/fs.js.map +1 -0
- package/dist/core/repo/tree/marker.js +61 -0
- package/dist/core/repo/tree/marker.js.map +1 -0
- package/dist/core/repo/tree/views.js +172 -0
- package/dist/core/repo/tree/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +234 -0
- package/dist/core/repo/tree/walk.js.map +1 -0
- package/dist/core/results.js +99 -2
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +131 -0
- package/dist/core/scaffold/landscape.js.map +1 -0
- package/dist/core/scaffold/readme.js +84 -0
- package/dist/core/scaffold/readme.js.map +1 -0
- package/dist/core/staging/commit.js +241 -0
- package/dist/core/staging/commit.js.map +1 -0
- package/dist/core/staging/interrupted.js +96 -0
- package/dist/core/staging/interrupted.js.map +1 -0
- package/dist/core/staging/lock.js +206 -0
- package/dist/core/staging/lock.js.map +1 -0
- package/dist/core/staging/recovery/finding.js +35 -0
- package/dist/core/staging/recovery/finding.js.map +1 -0
- package/dist/core/staging/recovery/intent.js +98 -0
- package/dist/core/staging/recovery/intent.js.map +1 -0
- package/dist/core/staging/recovery/recover.js +126 -0
- package/dist/core/staging/recovery/recover.js.map +1 -0
- package/dist/core/staging/recovery/residue.js +62 -0
- package/dist/core/staging/recovery/residue.js.map +1 -0
- package/dist/core/staging/snapshot.js +202 -0
- package/dist/core/staging/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +171 -0
- package/dist/core/staging/txn/forward.js.map +1 -0
- package/dist/core/staging/txn/journal.js +139 -0
- package/dist/core/staging/txn/journal.js.map +1 -0
- package/dist/core/staging/txn/transaction.js +75 -0
- package/dist/core/staging/txn/transaction.js.map +1 -0
- package/dist/core/staging/writes.js +113 -0
- package/dist/core/staging/writes.js.map +1 -0
- package/dist/core/status/actions/execution.js +49 -0
- package/dist/core/status/actions/execution.js.map +1 -0
- package/dist/core/status/contracts.js +102 -0
- package/dist/core/status/contracts.js.map +1 -0
- package/dist/core/status/feature/artifacts.js +196 -0
- package/dist/core/status/feature/artifacts.js.map +1 -0
- package/dist/core/status/feature/feature.js +207 -0
- package/dist/core/status/feature/feature.js.map +1 -0
- package/dist/core/status/feature/next.js +150 -0
- package/dist/core/status/feature/next.js.map +1 -0
- package/dist/core/status/feature/state.js +21 -0
- package/dist/core/status/feature/state.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +197 -0
- package/dist/core/status/fleet/fleet.js.map +1 -0
- package/dist/core/status/fleet/next.js +207 -0
- package/dist/core/status/fleet/next.js.map +1 -0
- package/dist/core/status/interrupted.js +97 -0
- package/dist/core/status/interrupted.js.map +1 -0
- package/dist/core/status/report.js +31 -0
- package/dist/core/status/report.js.map +1 -0
- package/dist/core/status/scan.js +41 -0
- package/dist/core/status/scan.js.map +1 -0
- package/dist/core/status/verification.js +201 -0
- package/dist/core/status/verification.js.map +1 -0
- package/dist/core/usecases/capability.js +169 -0
- package/dist/core/usecases/capability.js.map +1 -0
- package/dist/core/usecases/delta/claims.js +116 -0
- package/dist/core/usecases/delta/claims.js.map +1 -0
- package/dist/core/usecases/delta/flows.js +103 -0
- package/dist/core/usecases/delta/flows.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +72 -0
- package/dist/core/usecases/delta/overlay.js.map +1 -0
- package/dist/core/usecases/fleet.js +137 -0
- package/dist/core/usecases/fleet.js.map +1 -0
- package/dist/core/usecases/operations.js +111 -0
- package/dist/core/usecases/operations.js.map +1 -0
- package/dist/core/usecases/place.js +44 -0
- package/dist/core/usecases/place.js.map +1 -0
- package/dist/core/usecases/touch.js +101 -0
- package/dist/core/usecases/touch.js.map +1 -0
- package/dist/core/verify/answers.js +173 -0
- package/dist/core/verify/answers.js.map +1 -0
- package/dist/core/verify/build.js +150 -0
- package/dist/core/verify/build.js.map +1 -0
- package/dist/core/verify/checklist.js +201 -0
- package/dist/core/verify/checklist.js.map +1 -0
- package/dist/core/verify/claims/identity.js +61 -0
- package/dist/core/verify/claims/identity.js.map +1 -0
- package/dist/core/verify/cross/diff.js +97 -0
- package/dist/core/verify/cross/diff.js.map +1 -0
- package/dist/core/verify/evidence/contract.js +169 -0
- package/dist/core/verify/evidence/contract.js.map +1 -0
- package/dist/core/verify/file.js +257 -0
- package/dist/core/verify/file.js.map +1 -0
- package/dist/core/verify/pins/lint.js +157 -0
- package/dist/core/verify/pins/lint.js.map +1 -0
- package/dist/core/verify/pins/pin.js +95 -0
- package/dist/core/verify/pins/pin.js.map +1 -0
- package/dist/core/verify/record.js +142 -0
- package/dist/core/verify/record.js.map +1 -0
- package/dist/core/verify/store/commit.js +89 -0
- package/dist/core/verify/store/commit.js.map +1 -0
- package/dist/core/verify/store/render.js +57 -0
- package/dist/core/verify/store/render.js.map +1 -0
- package/dist/core/vocabulary/health.js +91 -0
- package/dist/core/vocabulary/health.js.map +1 -0
- package/dist/core/vocabulary/issue.js +58 -0
- package/dist/core/vocabulary/issue.js.map +1 -0
- package/dist/core/vocabulary/maturity.js +145 -0
- package/dist/core/vocabulary/maturity.js.map +1 -0
- package/dist/core/{report.js → vocabulary/report.js} +30 -2
- package/dist/core/vocabulary/report.js.map +1 -0
- package/dist/core/vocabulary/steps.js +266 -0
- package/dist/core/vocabulary/steps.js.map +1 -0
- package/dist/core/workspace/discover.js +239 -0
- package/dist/core/workspace/discover.js.map +1 -0
- package/dist/core/workspace/render.js +52 -0
- package/dist/core/workspace/render.js.map +1 -0
- package/examples/README.md +189 -0
- package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
- package/examples/docs/architecture/capabilities.yaml +31 -0
- package/examples/docs/architecture/landscape.likec4 +280 -0
- package/examples/docs/architecture/obligations.yaml +23 -0
- package/examples/docs/architecture/permissions.yaml +50 -0
- package/examples/docs/architecture/subsystems.likec4 +11 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
- package/examples/docs/capabilities/checkout/spec.md +55 -0
- package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
- package/examples/docs/capabilities/order-notifications/spec.md +43 -0
- package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
- package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
- package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
- package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
- package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
- package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
- package/examples/docs/glossary/order.md +15 -0
- package/examples/docs/glossary/payments/authorization.md +14 -0
- package/examples/docs/likec4.config.json +9 -0
- package/examples/docs/loam.json +3 -0
- package/examples/docs/services/checkout-web/model.likec4 +109 -0
- package/examples/docs/services/checkout-web/spec.md +91 -0
- package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
- package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
- package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
- package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
- package/examples/docs/services/order-service/arch.spec.md +127 -0
- package/examples/docs/services/order-service/asyncapi.yaml +108 -0
- package/examples/docs/services/order-service/health.yaml +57 -0
- package/examples/docs/services/order-service/model.likec4 +138 -0
- package/examples/docs/services/order-service/openapi.yaml +257 -0
- package/examples/docs/services/order-service/runbook.md +62 -0
- package/examples/docs/services/order-service/spec.md +134 -0
- package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
- package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
- package/examples/docs/services/payment-service/arch.spec.md +73 -0
- package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
- package/examples/docs/services/payment-service/health.yaml +39 -0
- package/examples/docs/services/payment-service/model.likec4 +67 -0
- package/examples/docs/services/payment-service/openapi.yaml +151 -0
- package/examples/docs/services/payment-service/runbook.md +37 -0
- package/examples/docs/services/payment-service/spec.md +88 -0
- package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
- package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
- package/examples/docs/services/platform/identity-service/health.yaml +70 -0
- package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
- package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
- package/examples/docs/services/platform/identity-service/runbook.md +95 -0
- package/examples/docs/services/platform/identity-service/spec.md +119 -0
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
- package/examples/docs/services/platform/notification-service/health.yaml +35 -0
- package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
- package/examples/docs/services/platform/notification-service/runbook.md +48 -0
- package/examples/docs/services/platform/notification-service/spec.md +112 -0
- package/examples/docs/services/platform/subsystem.yaml +2 -0
- package/examples/fleet.yaml +57 -0
- package/package.json +19 -6
- package/dist/commands/adopt.js +0 -240
- package/dist/commands/adopt.js.map +0 -1
- package/dist/commands/archive.js +0 -1313
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/delta.js +0 -388
- package/dist/commands/delta.js.map +0 -1
- package/dist/commands/docs-repo-gate.js.map +0 -1
- package/dist/commands/format.js +0 -17
- package/dist/commands/format.js.map +0 -1
- package/dist/commands/gherkin.js +0 -309
- package/dist/commands/gherkin.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.js +0 -295
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/migrate-openspec.js +0 -950
- package/dist/commands/migrate-openspec.js.map +0 -1
- package/dist/commands/new.js +0 -413
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/rebase.js +0 -356
- package/dist/commands/rebase.js.map +0 -1
- package/dist/commands/show.js.map +0 -1
- package/dist/commands/status.js +0 -262
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1773
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/verify.js +0 -775
- package/dist/commands/verify.js.map +0 -1
- package/dist/commands/vouch.js +0 -421
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2173
- package/dist/core/agent.js.map +0 -1
- package/dist/core/agents-stamp.js.map +0 -1
- package/dist/core/arch.js +0 -130
- package/dist/core/arch.js.map +0 -1
- package/dist/core/asyncapi.js.map +0 -1
- package/dist/core/brief.js +0 -755
- package/dist/core/brief.js.map +0 -1
- package/dist/core/coherence.js +0 -577
- package/dist/core/coherence.js.map +0 -1
- package/dist/core/concurrency.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/delta.js +0 -476
- package/dist/core/delta.js.map +0 -1
- package/dist/core/dependencies.js +0 -356
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/doctor.js +0 -596
- package/dist/core/doctor.js.map +0 -1
- package/dist/core/document-bytes.js.map +0 -1
- package/dist/core/explore.js.map +0 -1
- package/dist/core/frontmatter.js.map +0 -1
- package/dist/core/gherkin.js +0 -463
- package/dist/core/gherkin.js.map +0 -1
- package/dist/core/health.js +0 -67
- package/dist/core/health.js.map +0 -1
- package/dist/core/ids.js +0 -122
- package/dist/core/ids.js.map +0 -1
- package/dist/core/issue.js +0 -9
- package/dist/core/issue.js.map +0 -1
- package/dist/core/json.js +0 -74
- package/dist/core/json.js.map +0 -1
- package/dist/core/likec4.js +0 -489
- package/dist/core/likec4.js.map +0 -1
- package/dist/core/maturity.js +0 -66
- package/dist/core/maturity.js.map +0 -1
- package/dist/core/openapi-merge.js +0 -552
- package/dist/core/openapi-merge.js.map +0 -1
- package/dist/core/openapi.js.map +0 -1
- package/dist/core/openspec-inventory.js +0 -1440
- package/dist/core/openspec-inventory.js.map +0 -1
- package/dist/core/path-safety.js.map +0 -1
- package/dist/core/provenance.js +0 -787
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -438
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -505
- package/dist/core/spec.js.map +0 -1
- package/dist/core/staging.js +0 -890
- package/dist/core/staging.js.map +0 -1
- package/dist/core/status.js +0 -1132
- package/dist/core/status.js.map +0 -1
- package/dist/core/steps.js +0 -72
- package/dist/core/steps.js.map +0 -1
- package/dist/core/verify.js +0 -807
- package/dist/core/verify.js.map +0 -1
- package/dist/core/version.js.map +0 -1
- /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
- /package/dist/core/{records.js → kernel/records.js} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sources.js","sourceRoot":"","sources":["../../../src/core/provenance/sources.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The `sources` list: vetting the entries an author wrote — patterns, escapes,\n * missing paths, the refusal vocabulary `loam vouch` and `loam validate` share\n * — and grading the vetted list against the service repo: resolved, unvouched,\n * current, stale, and whether the walk reached the whole repository. Vetting\n * and grading are one doctrine about one field, so they live in one file.\n */\nimport { existsSync } from \"node:fs\";\nimport { listField, stringField, type Frontmatter } from \"../document/frontmatter.js\";\nimport type { Finding } from \"../vocabulary/report.js\";\nimport { resolveInside } from \"../kernel/path-safety.js\";\nimport { decodeSourceIndex, movedSources, sourcesDigest } from \"./stamp.js\";\nimport type { SkippedSource } from \"./walk.js\";\nimport { gitTrackedFiles } from \"./git.js\";\n\nexport async function sourceFindings(\n fm: Frontmatter,\n service: string,\n label: string,\n repoDir: string | undefined,\n): Promise<Finding[]> {\n const sources = listField(fm, \"sources\");\n if (sources.length === 0) {\n return [\n {\n severity: \"warn\",\n code: \"sources.absent\",\n message: `${label} names no sources — nothing ties it to the code, so nothing can tell you when it goes out of date`,\n },\n ];\n }\n // Someone else's repository: the paths describe a tree loam is not standing in.\n if (repoDir === undefined) return [];\n\n // Before existence: a pattern entry would \"not exist\" as a literal path, and\n // grading it missing would send the author fixing the wrong thing.\n const patterns = patternSources(sources);\n if (patterns.length > 0) {\n return [\n {\n severity: \"error\",\n code: \"sources.path-missing\",\n message: `${label}: ${patterns.length} source(s) are glob patterns — ${patterns.join(\", \")}. Patterns are no longer supported: name files or directories (a directory already covers everything beneath it).`,\n details: patterns,\n text: { detailPrefix: \"- \" },\n },\n ];\n }\n\n const unsafe = unsafeSources(repoDir, sources);\n if (unsafe.length > 0) {\n return [\n {\n severity: \"error\",\n code: \"sources.path-outside\",\n message: `${label}: ${unsafe.length} source(s) escape the service repo — ${unsafe.join(\", \")}. Sources must be relative paths contained by this repository, including through symlinks.`,\n details: unsafe,\n text: { detailPrefix: \"- \" },\n },\n ];\n }\n\n const missing = missingSources(repoDir, sources);\n if (missing.length > 0) {\n return [\n {\n severity: \"error\",\n code: \"sources.path-missing\",\n message: `${label}: ${missing.length} source(s) do not exist — ${missing.join(\", \")}`,\n details: missing,\n text: { detailPrefix: \"- \" },\n },\n ];\n }\n const resolved: Finding = {\n severity: \"ok\",\n code: \"sources.resolved\",\n message: `${label}: ${sources.length} source(s) resolve`,\n };\n\n // The walk happens here rather than inside the staleness branch below, even\n // though only staleness needs the hashes: what the walk REFUSED to follow is\n // a hole in the tie to the code whether or not anybody has vouched yet, and\n // an unvouched service is exactly where a reader is about to decide the\n // sources list is honest.\n const { digest, files, index, skipped } = await sourcesDigest(repoDir, sources);\n const findings: Finding[] = [\n resolved,\n ...skippedFindings(skipped, label),\n ...(await walkFindings(repoDir, files, label)),\n ];\n\n // The paths are there; the question staleness answers is whether what is AT\n // them is still what somebody read.\n const stamped = stringField(fm, \"sources_digest\");\n if (stamped === undefined) {\n return [\n ...findings,\n {\n severity: \"warn\",\n code: \"sources.unvouched\",\n message: `${label}: no sources_digest — nobody has vouched for this against the code, so nothing can tell you when it goes stale. Run \\`loam vouch --service ${service}\\`.`,\n },\n ];\n }\n\n const since = stringField(fm, \"last_verified\") ?? \"it was stamped\";\n if (digest === stamped) {\n return [\n ...findings,\n {\n severity: \"ok\",\n code: \"sources.current\",\n message: `${label}: sources unchanged since ${since} (${files.length} file(s), digest ${digest})`,\n },\n ];\n }\n\n // What MOVED, when the stamp recorded enough to say. Repeating the frontmatter's\n // own `sources` entries back at a reader — which is all this finding could do\n // before `sources_files` existed — answers a question nobody asked: they wrote\n // that list, and \"one of these five directories changed somehow\" is where the\n // search starts, not where it ends.\n const moved = movedSources(decodeSourceIndex(stringField(fm, \"sources_files\")), index);\n return [\n ...findings,\n {\n // A warning, not an error: the doc may still be right, and only a person\n // can say. What loam knows is that nobody has looked since the code moved.\n severity: \"warn\",\n code: \"sources.stale\",\n message: `${label}: sources changed since ${since}${moved.summary} — run \\`loam vouch --pack --service ${service}\\` for the reading list, then \\`loam vouch --service ${service}\\``,\n details: moved.paths ?? sources,\n text: { detailPrefix: \"- \" },\n },\n ];\n}\n\n/** The first path segment — the top-level directory, or a root file's own name. */\nfunction topLevel(path: string): string {\n const cut = path.indexOf(\"/\");\n return cut === -1 ? path : path.slice(0, cut);\n}\n\n/**\n * Was the service WALKED, or only the part somebody wrote about?\n *\n * Every other check loam owns is computed from the documents, so a corpus can\n * agree with itself perfectly while describing a third of a service — the\n * failure `UNCHECKED` names as COMPLETENESS and then, correctly, declines to\n * grade. This is the one measurement that escapes that circle, because it\n * compares the document's `sources` against the REPOSITORY: a top-level path\n * git tracks and no source reaches into is a part of the service nobody opened.\n *\n * git is the denominator on purpose. The alternative is walking the tree and\n * excluding what does not look like source, which means a hand-maintained list\n * of build directories that is wrong for the first language nobody thought of —\n * and a check that is wrong for a whole ecosystem is a warning that ecosystem\n * learns to ignore. `git ls-files` already answers \"what does this repository\n * consider its own\", it is the author's own answer, and `node_modules` and\n * `target/` fall out of it for free. Where git cannot answer — not a\n * repository, not installed, a timeout — there is no denominator and therefore\n * no finding: proving nothing and saying nothing is the same doctrine\n * `sources.unverifiable-from-here` follows.\n *\n * TOP-LEVEL granularity, not a percentage threshold. A ratio needs a line\n * drawn somewhere, and an invented threshold is exactly the number this\n * codebase refuses to write into other people's dashboards; a named directory\n * needs no threshold and is answerable — either it holds nothing this document\n * owes, and the hand-back says so in one sentence, or the walk stopped early.\n * The ratio still rides along in the message as a dial, where it informs\n * without grading.\n */\nasync function walkFindings(repoDir: string, covered: string[], label: string): Promise<Finding[]> {\n const tracked = await gitTrackedFiles(repoDir);\n if (tracked === null || tracked.length === 0) return [];\n\n // The honest intersection, not \"every file under a directory somebody\n // touched\": reading one file in src/ does not make the other four hundred\n // read, and a coverage number that says it does is worse than none.\n const coveredSet = new Set(covered);\n const hit = tracked.filter((p) => coveredSet.has(p)).length;\n const ratio = `${String(hit)} of ${String(tracked.length)} tracked file(s)`;\n\n const touched = new Set(covered.map(topLevel));\n const untouched = [...new Set(tracked.map(topLevel))].filter((t) => !touched.has(t)).sort();\n if (untouched.length === 0) {\n return [\n {\n severity: \"ok\",\n code: \"sources.walked\",\n message: `${label}: sources reach into every top-level path this repository tracks (${ratio})`,\n },\n ];\n }\n return [\n {\n severity: \"warn\",\n code: \"sources.unwalked\",\n message:\n `${label}: sources cover ${ratio}, and ${String(untouched.length)} top-level path(s) were never opened. ` +\n `Either they hold nothing this document owes — say which, and why, in the hand-back — or the walk stopped early ` +\n `and the baseline describes part of a service.`,\n details: untouched,\n text: { detailPrefix: \"- \" },\n },\n ];\n}\n\n/**\n * The paths the digest walk would not follow. Never silent: a symlink dropped\n * on the floor is a file whose content nobody is watching, while the document\n * beside it says `verified` — the digest cannot go stale over bytes it never\n * hashed, so the one thing the stamp promises is quietly untrue for them.\n *\n * A warning, not an error, and for the same reason staleness is: the doc may be\n * perfectly right about the code behind that link. What loam can say is that it\n * is not watching it.\n */\nfunction skippedFindings(skipped: SkippedSource[], label: string): Finding[] {\n if (skipped.length === 0) return [];\n return [\n {\n severity: \"warn\",\n code: \"sources.skipped\",\n message: `${label}: ${skipped.length} path(s) under the listed sources were not hashed — the digest says nothing about what is behind them`,\n details: skipped.map((s) => `${s.path} — ${s.reason}`),\n text: { detailPrefix: \"- \" },\n },\n ];\n}\n\n/** The characters that made an entry a pattern under the glob dialect loam no longer ships. */\nconst PATTERN_CHARS = /[*?[]/;\n\n/**\n * The `sources` entries that look like glob patterns — anything containing\n * `*`, `?` or `[`. Patterns are refused loudly everywhere sources are consumed\n * (`loam vouch` refuses the run, `loam validate` grades an error), never\n * resolved: the glob dialect loam used to ship silently differed from the\n * gitignore/minimatch conventions authors assume — bracket classes literal,\n * its own `**` handling — so a pattern quietly digested a different file set\n * than intended, corrupting the staleness signal in both directions. A\n * directory already means \"everything beneath it\", so the fix is to name one.\n *\n * The cost accepted with the rule: a real file with `[` in its name (a Next.js\n * route, say) cannot be listed on its own — its parent directory covers it.\n */\nexport function patternSources(sources: string[]): string[] {\n return sources.filter((s) => PATTERN_CHARS.test(s.trim()));\n}\n\n/** The `sources` entries that resolve to nothing in this repo. */\nexport function missingSources(repoDir: string, sources: string[]): string[] {\n return sources.filter((s) => !sourceExists(repoDir, s));\n}\n\n/** Entries that are absolute, traverse upward, or escape through a symlink. */\nexport function unsafeSources(repoDir: string, sources: string[]): string[] {\n return sources.filter((source) => {\n const cleaned = source.trim();\n if (cleaned.length === 0) return false;\n try {\n resolveInside(repoDir, cleaned, `source '${source}'`);\n return false;\n } catch {\n return true;\n }\n });\n}\n\n/** Does a `sources` entry point at something real? Literal paths only, checked exactly. */\nfunction sourceExists(repoDir: string, source: string): boolean {\n const cleaned = source.trim();\n if (cleaned.length === 0) return false;\n try {\n return existsSync(resolveInside(repoDir, cleaned, `source '${source}'`));\n } catch {\n return false;\n }\n}\n"]}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The stamp vocabulary: what `loam vouch` computes for a frontmatter stamp and
|
|
3
|
+
* what a later check reads back out of one — `sources_digest` and the per-file
|
|
4
|
+
* `sources_files` index beside it, the document's own `content_digest`, and
|
|
5
|
+
* the local calendar day `last_verified` carries.
|
|
6
|
+
*/
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import { readFile } from "node:fs/promises";
|
|
9
|
+
import { rawBody } from "../document/frontmatter.js";
|
|
10
|
+
import { collectSources } from "./walk.js";
|
|
11
|
+
/**
|
|
12
|
+
* The local calendar day, for the two commands that date a stamp. A vouch or a
|
|
13
|
+
* verification record is a person saying "today I read this", so it is their
|
|
14
|
+
* date, not UTC's — `toISOString` files an evening in the Americas under
|
|
15
|
+
* tomorrow. It sits here because this module already owns the stamp vocabulary
|
|
16
|
+
* the date is written into.
|
|
17
|
+
*/
|
|
18
|
+
export function today(now) {
|
|
19
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
20
|
+
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
|
|
21
|
+
}
|
|
22
|
+
/* ------------------------------------------------------------------ */
|
|
23
|
+
/* The content digest */
|
|
24
|
+
/* ------------------------------------------------------------------ */
|
|
25
|
+
/** How much of the sha256 is written into the document. */
|
|
26
|
+
const DIGEST_LENGTH = 16;
|
|
27
|
+
/** How many source files the digest reads at once — see `sourcesDigest`. */
|
|
28
|
+
const DIGEST_READ_BATCH = 32;
|
|
29
|
+
/**
|
|
30
|
+
* Digest the CONTENT of the files `sources` names. The recipe is spelled out
|
|
31
|
+
* because the value is written into documents, and anything reading them has to
|
|
32
|
+
* be able to reproduce it:
|
|
33
|
+
*
|
|
34
|
+
* 1. expand each entry to repo-relative file paths (`/` separators) — a file
|
|
35
|
+
* is itself, a directory everything beneath it, minus what the repository
|
|
36
|
+
* does not consider its own (see collectSources) — sorted and de-duplicated;
|
|
37
|
+
* 2. per file, `sha256(bytes)`;
|
|
38
|
+
* 3. feed `<path>\0<hex>\n` for each file, in that order, into an outer
|
|
39
|
+
* sha256;
|
|
40
|
+
* 4. keep the first 16 hex characters.
|
|
41
|
+
*
|
|
42
|
+
* Content, not mtime: git does not preserve modification times, so after a
|
|
43
|
+
* fresh clone every file looks changed and the check would be false positives
|
|
44
|
+
* end to end. Bytes survive the clone. Hashing the path alongside the content
|
|
45
|
+
* means a rename registers, which is what a reader of the doc would want.
|
|
46
|
+
*
|
|
47
|
+
* 64 bits is a change detector, not a seal — it answers "did this move?", and
|
|
48
|
+
* an adversary who wants a collision can have one.
|
|
49
|
+
*/
|
|
50
|
+
export async function sourcesDigest(repoDir, sources) {
|
|
51
|
+
const { found, skipped } = await collectSources(repoDir, sources);
|
|
52
|
+
const outer = createHash("sha256");
|
|
53
|
+
const index = [];
|
|
54
|
+
// Read in fixed batches, hash in array order. `found` is already sorted and
|
|
55
|
+
// every file's own sha depends on nothing but its own bytes, so the sequence
|
|
56
|
+
// fed to `outer` — and therefore the digest — is unchanged by construction;
|
|
57
|
+
// only the waiting overlaps. A `sources: [src/]` over a real service repo is
|
|
58
|
+
// thousands of files, and one-await-per-file spent nearly all of its time
|
|
59
|
+
// idle. The batch is bounded rather than a single `Promise.all` over the
|
|
60
|
+
// whole list because an unbounded fan-out over a source tree runs the process
|
|
61
|
+
// out of file descriptors, which would turn a stamp into an EMFILE.
|
|
62
|
+
for (let start = 0; start < found.length; start += DIGEST_READ_BATCH) {
|
|
63
|
+
const hashed = await Promise.all(found.slice(start, start + DIGEST_READ_BATCH).map(async (file) => ({
|
|
64
|
+
rel: file.rel,
|
|
65
|
+
content: createHash("sha256").update(await readFile(file.abs)).digest("hex"),
|
|
66
|
+
})));
|
|
67
|
+
for (const { rel, content } of hashed) {
|
|
68
|
+
outer.update(`${rel}\0${content}\n`);
|
|
69
|
+
index.push({ path: rel, sha: content.slice(0, DIGEST_LENGTH) });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// `empty` is deliberately not set here: it is a sentence about a named
|
|
73
|
+
// document, and a digest has no name to put in it. Callers that need it say
|
|
74
|
+
// emptySourcesMessage(label, sources) — the same words, from one definition.
|
|
75
|
+
return { digest: outer.digest("hex").slice(0, DIGEST_LENGTH), files: index.map((e) => e.path), index, skipped };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* What a `sources` list actually covers — the digest's file set without the
|
|
79
|
+
* hashing.
|
|
80
|
+
*
|
|
81
|
+
* Exported because two commands must give the SAME answer to "does this list
|
|
82
|
+
* cover anything?". `loam vouch` refuses to stamp an expansion that covers no
|
|
83
|
+
* files (a digest over nothing never changes, so the stamp would read as
|
|
84
|
+
* current forever), and until `loam validate` could say the same thing in the
|
|
85
|
+
* same words, an author got a green validate followed by a vouch that refused —
|
|
86
|
+
* two commands contradicting each other about one document, with nothing in the
|
|
87
|
+
* green run hinting at it.
|
|
88
|
+
*/
|
|
89
|
+
export async function expandSourceFiles(repoDir, sources, label) {
|
|
90
|
+
const { found, skipped } = await collectSources(repoDir, sources);
|
|
91
|
+
const files = found.map((f) => f.rel);
|
|
92
|
+
return { files, skipped, ...(files.length === 0 ? { empty: emptySourcesMessage(label, sources) } : {}) };
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The one sentence for "these paths exist and cover no file". One definition,
|
|
96
|
+
* because `vouch`'s refusal and `validate`'s finding are the same diagnosis
|
|
97
|
+
* about the same document and must not drift into two descriptions of it — the
|
|
98
|
+
* author fixes it once, in the sources list.
|
|
99
|
+
*/
|
|
100
|
+
export function emptySourcesMessage(label, sources) {
|
|
101
|
+
return `${label}: the sources listed match no files — ${sources.join(", ")}. A digest over nothing would read as current forever.`;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The stamp `loam vouch` writes into `content_digest`: sha256 of the
|
|
105
|
+
* document's own BODY — every byte after the frontmatter block (below the
|
|
106
|
+
* closing `---` line and its newline; `rawBody` is the one definition of that
|
|
107
|
+
* cut) — first 16 hex characters, the sources recipe's length.
|
|
108
|
+
*
|
|
109
|
+
* Byte-exact, no normalization. Body-only is load-bearing: vouch itself
|
|
110
|
+
* rewrites the frontmatter as it stamps, and a later frontmatter-only edit
|
|
111
|
+
* (another vouch, a corrected owner) must not read as the document moving.
|
|
112
|
+
*/
|
|
113
|
+
export function contentDigest(source) {
|
|
114
|
+
return createHash("sha256").update(rawBody(source), "utf8").digest("hex").slice(0, DIGEST_LENGTH);
|
|
115
|
+
}
|
|
116
|
+
/* ------------------------------------------------------------------ */
|
|
117
|
+
/* The per-file index: what `sources.stale` names */
|
|
118
|
+
/* ------------------------------------------------------------------ */
|
|
119
|
+
/**
|
|
120
|
+
* How many files `sources_files` will list one by one before it gives up and
|
|
121
|
+
* records only how many there were.
|
|
122
|
+
*
|
|
123
|
+
* A readability budget, not a correctness one: the index lives in the header of
|
|
124
|
+
* a document a person reads, and a `sources: [src/]` over a legacy service can
|
|
125
|
+
* expand to thousands of files. Past the limit `sources.stale` falls back to
|
|
126
|
+
* repeating the `sources` entries — worse advice, but a spec.md whose
|
|
127
|
+
* frontmatter is longer than its requirements is worse still.
|
|
128
|
+
*/
|
|
129
|
+
const SOURCE_INDEX_LIMIT = 100;
|
|
130
|
+
/**
|
|
131
|
+
* The `sources_files` value `loam vouch` stamps beside `sources_digest`: one
|
|
132
|
+
* `<sha> <path>` line per file (sha256sum's layout, and its column order, so
|
|
133
|
+
* the path may contain spaces), or just the file count once the list would run
|
|
134
|
+
* past SOURCE_INDEX_LIMIT.
|
|
135
|
+
*
|
|
136
|
+
* Its whole purpose is the next `sources.stale`. `sources_digest` can say THAT
|
|
137
|
+
* the code moved and never which part of it; with the index a reader gets the
|
|
138
|
+
* added, removed and changed paths, which is the difference between re-reading
|
|
139
|
+
* one file and re-reading a directory. It is also a readable git diff in the
|
|
140
|
+
* docs repo: the vouch commit shows exactly which source files the stamp now
|
|
141
|
+
* stands over.
|
|
142
|
+
*/
|
|
143
|
+
export function encodeSourceIndex(index) {
|
|
144
|
+
if (index.length > SOURCE_INDEX_LIMIT)
|
|
145
|
+
return String(index.length);
|
|
146
|
+
return index.map((e) => `${e.sha} ${e.path}`).join("\n");
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Read back what `encodeSourceIndex` wrote. Anything unrecognised reads as "the
|
|
150
|
+
* stamp said nothing" rather than as an empty index: a header nobody can parse
|
|
151
|
+
* must not turn into a `sources.stale` claiming every file was deleted.
|
|
152
|
+
*/
|
|
153
|
+
export function decodeSourceIndex(stamped) {
|
|
154
|
+
if (stamped === undefined)
|
|
155
|
+
return { entries: null };
|
|
156
|
+
const text = stamped.trim();
|
|
157
|
+
if (text.length === 0)
|
|
158
|
+
return { entries: null };
|
|
159
|
+
if (/^\d+$/.test(text))
|
|
160
|
+
return { entries: null, count: Number(text) };
|
|
161
|
+
const entries = new Map();
|
|
162
|
+
for (const line of text.split("\n")) {
|
|
163
|
+
const match = /^([0-9a-f]{16})\s+(.+)$/.exec(line.trim());
|
|
164
|
+
if (match === null)
|
|
165
|
+
return { entries: null };
|
|
166
|
+
entries.set(match[2], match[1]);
|
|
167
|
+
}
|
|
168
|
+
return { entries, count: entries.size };
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* The structured half of the answer: which paths were added, changed and
|
|
172
|
+
* removed between a stamp and the current index — or null when the stamp
|
|
173
|
+
* cannot say. Null covers two distinct silences on purpose, both of which mean
|
|
174
|
+
* "do not print a path list": a stamp that recorded no per-file entries (the
|
|
175
|
+
* count-only >100-file fallback, or a header nobody could parse), and an index
|
|
176
|
+
* that AGREES with the current tree while the caller's digest comparison said
|
|
177
|
+
* stale — a contradiction (a truncation collision, or a hand-edited header),
|
|
178
|
+
* where an empty delta under a staleness warning would read as proof that
|
|
179
|
+
* nothing moved. Callers that need the counts anyway read `stamp.count`.
|
|
180
|
+
*
|
|
181
|
+
* Extracted from `movedSources` (which now consumes it) when the re-vouch
|
|
182
|
+
* reading pack became a second reader: the pack wants the three lists as
|
|
183
|
+
* data, the stale finding wants them annotated in one column.
|
|
184
|
+
*/
|
|
185
|
+
export function sourceDelta(stamp, now) {
|
|
186
|
+
if (stamp.entries === null)
|
|
187
|
+
return null;
|
|
188
|
+
const added = [];
|
|
189
|
+
const changed = [];
|
|
190
|
+
const current = new Map(now.map((e) => [e.path, e.sha]));
|
|
191
|
+
for (const [path, sha] of current) {
|
|
192
|
+
const before = stamp.entries.get(path);
|
|
193
|
+
if (before === undefined)
|
|
194
|
+
added.push(path);
|
|
195
|
+
else if (before !== sha)
|
|
196
|
+
changed.push(path);
|
|
197
|
+
}
|
|
198
|
+
const removed = [...stamp.entries.keys()].filter((path) => !current.has(path));
|
|
199
|
+
if (added.length + changed.length + removed.length === 0)
|
|
200
|
+
return null;
|
|
201
|
+
added.sort();
|
|
202
|
+
changed.sort();
|
|
203
|
+
removed.sort();
|
|
204
|
+
return { added, changed, removed };
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* added / removed / changed, from the stamped index against the current one.
|
|
208
|
+
* The annotation travels IN the detail line rather than in three separate
|
|
209
|
+
* lists: a reader scanning a stale report wants the paths in one column, and
|
|
210
|
+
* "which of the three" is one word wide.
|
|
211
|
+
*/
|
|
212
|
+
export function movedSources(stamp, now) {
|
|
213
|
+
const delta = sourceDelta(stamp, now);
|
|
214
|
+
if (delta === null) {
|
|
215
|
+
// A recorded index that came back null is the contradiction case — say
|
|
216
|
+
// nothing rather than print an empty list under a staleness warning. With
|
|
217
|
+
// no index at all, the count alone still beats silence: "12 files then,
|
|
218
|
+
// 14 now" tells a reader to go looking for two new files.
|
|
219
|
+
return {
|
|
220
|
+
summary: stamp.entries !== null || stamp.count === undefined
|
|
221
|
+
? ""
|
|
222
|
+
: ` (${stamp.count} file(s) then, ${now.length} now)`,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
const paths = [
|
|
226
|
+
...delta.added.map((path) => `added ${path}`),
|
|
227
|
+
...delta.changed.map((path) => `changed ${path}`),
|
|
228
|
+
...delta.removed.map((path) => `removed ${path}`),
|
|
229
|
+
].sort();
|
|
230
|
+
return { paths, summary: ` (${paths.length} path(s) moved)` };
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=stamp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stamp.js","sourceRoot":"","sources":["../../../src/core/provenance/stamp.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The stamp vocabulary: what `loam vouch` computes for a frontmatter stamp and\n * what a later check reads back out of one — `sources_digest` and the per-file\n * `sources_files` index beside it, the document's own `content_digest`, and\n * the local calendar day `last_verified` carries.\n */\nimport { createHash } from \"node:crypto\";\nimport { readFile } from \"node:fs/promises\";\nimport { rawBody } from \"../document/frontmatter.js\";\nimport { collectSources, type SkippedSource } from \"./walk.js\";\n\n/**\n * The local calendar day, for the two commands that date a stamp. A vouch or a\n * verification record is a person saying \"today I read this\", so it is their\n * date, not UTC's — `toISOString` files an evening in the Americas under\n * tomorrow. It sits here because this module already owns the stamp vocabulary\n * the date is written into.\n */\nexport function today(now: Date): string {\n const pad = (n: number): string => String(n).padStart(2, \"0\");\n return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;\n}\n\n/* ------------------------------------------------------------------ */\n/* The content digest */\n/* ------------------------------------------------------------------ */\n\n/** How much of the sha256 is written into the document. */\nconst DIGEST_LENGTH = 16;\n\n/** How many source files the digest reads at once — see `sourcesDigest`. */\nconst DIGEST_READ_BATCH = 32;\n\nexport interface SourcesDigest extends SourcesExpansion {\n /** The stamp that goes in `sources_digest`. */\n digest: string;\n /**\n * Per file, its own short sha — the record `sources_files` carries so a later\n * `sources.stale` can name what moved instead of guessing. Same order as `files`.\n */\n index: SourceIndexEntry[];\n}\n\n/** One file's share of the stamp: what it is called, and what it said. */\nexport interface SourceIndexEntry {\n /** Repo-relative, `/`-separated. */\n path: string;\n /** sha256 of its bytes, truncated the way `sources_digest` is. */\n sha: string;\n}\n\n/** What a `sources` list covers, and what it does not. */\nexport interface SourcesExpansion {\n /** Repo-relative paths, sorted — exactly what a digest would hash. */\n files: string[];\n /** Everything the walk refused to follow. Empty in the ordinary case. */\n skipped: SkippedSource[];\n /**\n * Set only when `files` is empty: the one sentence that says the list covers\n * nothing. `loam vouch` refuses with it, `loam validate` grades it — see\n * emptySourcesMessage for why they must be the same words.\n */\n empty?: string;\n}\n\n/**\n * Digest the CONTENT of the files `sources` names. The recipe is spelled out\n * because the value is written into documents, and anything reading them has to\n * be able to reproduce it:\n *\n * 1. expand each entry to repo-relative file paths (`/` separators) — a file\n * is itself, a directory everything beneath it, minus what the repository\n * does not consider its own (see collectSources) — sorted and de-duplicated;\n * 2. per file, `sha256(bytes)`;\n * 3. feed `<path>\\0<hex>\\n` for each file, in that order, into an outer\n * sha256;\n * 4. keep the first 16 hex characters.\n *\n * Content, not mtime: git does not preserve modification times, so after a\n * fresh clone every file looks changed and the check would be false positives\n * end to end. Bytes survive the clone. Hashing the path alongside the content\n * means a rename registers, which is what a reader of the doc would want.\n *\n * 64 bits is a change detector, not a seal — it answers \"did this move?\", and\n * an adversary who wants a collision can have one.\n */\nexport async function sourcesDigest(repoDir: string, sources: string[]): Promise<SourcesDigest> {\n const { found, skipped } = await collectSources(repoDir, sources);\n const outer = createHash(\"sha256\");\n const index: SourceIndexEntry[] = [];\n // Read in fixed batches, hash in array order. `found` is already sorted and\n // every file's own sha depends on nothing but its own bytes, so the sequence\n // fed to `outer` — and therefore the digest — is unchanged by construction;\n // only the waiting overlaps. A `sources: [src/]` over a real service repo is\n // thousands of files, and one-await-per-file spent nearly all of its time\n // idle. The batch is bounded rather than a single `Promise.all` over the\n // whole list because an unbounded fan-out over a source tree runs the process\n // out of file descriptors, which would turn a stamp into an EMFILE.\n for (let start = 0; start < found.length; start += DIGEST_READ_BATCH) {\n const hashed = await Promise.all(\n found.slice(start, start + DIGEST_READ_BATCH).map(async (file) => ({\n rel: file.rel,\n content: createHash(\"sha256\").update(await readFile(file.abs)).digest(\"hex\"),\n })),\n );\n for (const { rel, content } of hashed) {\n outer.update(`${rel}\\0${content}\\n`);\n index.push({ path: rel, sha: content.slice(0, DIGEST_LENGTH) });\n }\n }\n // `empty` is deliberately not set here: it is a sentence about a named\n // document, and a digest has no name to put in it. Callers that need it say\n // emptySourcesMessage(label, sources) — the same words, from one definition.\n return { digest: outer.digest(\"hex\").slice(0, DIGEST_LENGTH), files: index.map((e) => e.path), index, skipped };\n}\n\n/**\n * What a `sources` list actually covers — the digest's file set without the\n * hashing.\n *\n * Exported because two commands must give the SAME answer to \"does this list\n * cover anything?\". `loam vouch` refuses to stamp an expansion that covers no\n * files (a digest over nothing never changes, so the stamp would read as\n * current forever), and until `loam validate` could say the same thing in the\n * same words, an author got a green validate followed by a vouch that refused —\n * two commands contradicting each other about one document, with nothing in the\n * green run hinting at it.\n */\nexport async function expandSourceFiles(\n repoDir: string,\n sources: string[],\n label: string,\n): Promise<SourcesExpansion> {\n const { found, skipped } = await collectSources(repoDir, sources);\n const files = found.map((f) => f.rel);\n return { files, skipped, ...(files.length === 0 ? { empty: emptySourcesMessage(label, sources) } : {}) };\n}\n\n/**\n * The one sentence for \"these paths exist and cover no file\". One definition,\n * because `vouch`'s refusal and `validate`'s finding are the same diagnosis\n * about the same document and must not drift into two descriptions of it — the\n * author fixes it once, in the sources list.\n */\nexport function emptySourcesMessage(label: string, sources: string[]): string {\n return `${label}: the sources listed match no files — ${sources.join(\", \")}. A digest over nothing would read as current forever.`;\n}\n\n/**\n * The stamp `loam vouch` writes into `content_digest`: sha256 of the\n * document's own BODY — every byte after the frontmatter block (below the\n * closing `---` line and its newline; `rawBody` is the one definition of that\n * cut) — first 16 hex characters, the sources recipe's length.\n *\n * Byte-exact, no normalization. Body-only is load-bearing: vouch itself\n * rewrites the frontmatter as it stamps, and a later frontmatter-only edit\n * (another vouch, a corrected owner) must not read as the document moving.\n */\nexport function contentDigest(source: string): string {\n return createHash(\"sha256\").update(rawBody(source), \"utf8\").digest(\"hex\").slice(0, DIGEST_LENGTH);\n}\n\n/* ------------------------------------------------------------------ */\n/* The per-file index: what `sources.stale` names */\n/* ------------------------------------------------------------------ */\n\n/**\n * How many files `sources_files` will list one by one before it gives up and\n * records only how many there were.\n *\n * A readability budget, not a correctness one: the index lives in the header of\n * a document a person reads, and a `sources: [src/]` over a legacy service can\n * expand to thousands of files. Past the limit `sources.stale` falls back to\n * repeating the `sources` entries — worse advice, but a spec.md whose\n * frontmatter is longer than its requirements is worse still.\n */\nconst SOURCE_INDEX_LIMIT = 100;\n\n/**\n * The `sources_files` value `loam vouch` stamps beside `sources_digest`: one\n * `<sha> <path>` line per file (sha256sum's layout, and its column order, so\n * the path may contain spaces), or just the file count once the list would run\n * past SOURCE_INDEX_LIMIT.\n *\n * Its whole purpose is the next `sources.stale`. `sources_digest` can say THAT\n * the code moved and never which part of it; with the index a reader gets the\n * added, removed and changed paths, which is the difference between re-reading\n * one file and re-reading a directory. It is also a readable git diff in the\n * docs repo: the vouch commit shows exactly which source files the stamp now\n * stands over.\n */\nexport function encodeSourceIndex(index: SourceIndexEntry[]): string {\n if (index.length > SOURCE_INDEX_LIMIT) return String(index.length);\n return index.map((e) => `${e.sha} ${e.path}`).join(\"\\n\");\n}\n\n/** What a stamped `sources_files` says: the per-file shas, the count, or nothing. */\ninterface StampedIndex {\n /** path -> sha, or null when the stamp did not record them. */\n entries: Map<string, string> | null;\n /** How many files the stamp covered, when that is knowable. */\n count?: number;\n}\n\n/**\n * Read back what `encodeSourceIndex` wrote. Anything unrecognised reads as \"the\n * stamp said nothing\" rather than as an empty index: a header nobody can parse\n * must not turn into a `sources.stale` claiming every file was deleted.\n */\nexport function decodeSourceIndex(stamped: string | undefined): StampedIndex {\n if (stamped === undefined) return { entries: null };\n const text = stamped.trim();\n if (text.length === 0) return { entries: null };\n if (/^\\d+$/.test(text)) return { entries: null, count: Number(text) };\n\n const entries = new Map<string, string>();\n for (const line of text.split(\"\\n\")) {\n const match = /^([0-9a-f]{16})\\s+(.+)$/.exec(line.trim());\n if (match === null) return { entries: null };\n entries.set(match[2]!, match[1]!);\n }\n return { entries, count: entries.size };\n}\n\n/** The paths that moved between a stamp and now, with a phrase for the message. */\ninterface MovedSources {\n /** Annotated paths, or undefined when the stamp did not record enough to say. */\n paths?: string[];\n /** Appended to the `sources.stale` message; empty when there is nothing to add. */\n summary: string;\n}\n\n/**\n * The structured half of the answer: which paths were added, changed and\n * removed between a stamp and the current index — or null when the stamp\n * cannot say. Null covers two distinct silences on purpose, both of which mean\n * \"do not print a path list\": a stamp that recorded no per-file entries (the\n * count-only >100-file fallback, or a header nobody could parse), and an index\n * that AGREES with the current tree while the caller's digest comparison said\n * stale — a contradiction (a truncation collision, or a hand-edited header),\n * where an empty delta under a staleness warning would read as proof that\n * nothing moved. Callers that need the counts anyway read `stamp.count`.\n *\n * Extracted from `movedSources` (which now consumes it) when the re-vouch\n * reading pack became a second reader: the pack wants the three lists as\n * data, the stale finding wants them annotated in one column.\n */\nexport function sourceDelta(\n stamp: StampedIndex,\n now: SourceIndexEntry[],\n): { added: string[]; changed: string[]; removed: string[] } | null {\n if (stamp.entries === null) return null;\n const added: string[] = [];\n const changed: string[] = [];\n const current = new Map(now.map((e) => [e.path, e.sha]));\n for (const [path, sha] of current) {\n const before = stamp.entries.get(path);\n if (before === undefined) added.push(path);\n else if (before !== sha) changed.push(path);\n }\n const removed = [...stamp.entries.keys()].filter((path) => !current.has(path));\n if (added.length + changed.length + removed.length === 0) return null;\n added.sort();\n changed.sort();\n removed.sort();\n return { added, changed, removed };\n}\n\n/**\n * added / removed / changed, from the stamped index against the current one.\n * The annotation travels IN the detail line rather than in three separate\n * lists: a reader scanning a stale report wants the paths in one column, and\n * \"which of the three\" is one word wide.\n */\nexport function movedSources(stamp: StampedIndex, now: SourceIndexEntry[]): MovedSources {\n const delta = sourceDelta(stamp, now);\n if (delta === null) {\n // A recorded index that came back null is the contradiction case — say\n // nothing rather than print an empty list under a staleness warning. With\n // no index at all, the count alone still beats silence: \"12 files then,\n // 14 now\" tells a reader to go looking for two new files.\n return {\n summary:\n stamp.entries !== null || stamp.count === undefined\n ? \"\"\n : ` (${stamp.count} file(s) then, ${now.length} now)`,\n };\n }\n const paths = [\n ...delta.added.map((path) => `added ${path}`),\n ...delta.changed.map((path) => `changed ${path}`),\n ...delta.removed.map((path) => `removed ${path}`),\n ].sort();\n return { paths, summary: ` (${paths.length} path(s) moved)` };\n}\n"]}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The source walk: expanding a `sources` list to the concrete files it covers
|
|
3
|
+
* — found and skipped. What the walk refuses to follow is reported, never
|
|
4
|
+
* dropped, because the digest downstream cannot go stale over bytes it never
|
|
5
|
+
* hashed; the skipped list is how that hole stays visible.
|
|
6
|
+
*/
|
|
7
|
+
import { readdir, realpath, stat } from "node:fs/promises";
|
|
8
|
+
import { join, relative, resolve, sep } from "node:path";
|
|
9
|
+
import { isPathInside, resolveInside } from "../kernel/path-safety.js";
|
|
10
|
+
import { gitIgnoredPaths } from "./git.js";
|
|
11
|
+
/**
|
|
12
|
+
* The files a `sources` list names: a file is itself, a directory is everything
|
|
13
|
+
* beneath it. Nothing else — glob patterns used to be matched here, by a
|
|
14
|
+
* hand-rolled dialect that silently differed from the gitignore/minimatch
|
|
15
|
+
* conventions authors assume, so a pattern quietly digested a DIFFERENT file
|
|
16
|
+
* set than its author intended. Pattern-looking entries are now refused loudly
|
|
17
|
+
* upstream (see patternSources); this function only ever sees literal paths.
|
|
18
|
+
*
|
|
19
|
+
* Three exclusions, all part of the digest recipe's contract:
|
|
20
|
+
*
|
|
21
|
+
* - dot-entries are skipped while walking — `.git` is not what the doc was
|
|
22
|
+
* written from — though a path naming one outright is still honoured;
|
|
23
|
+
* - `node_modules` is skipped the same way, unconditionally: it is the one
|
|
24
|
+
* build input every ecosystem agrees is not source, and it is the one that
|
|
25
|
+
* makes the walk take minutes;
|
|
26
|
+
* - anything the repository's own `.gitignore` covers is dropped, when the
|
|
27
|
+
* repo is a git checkout and git is on the PATH (see gitIgnoredPaths).
|
|
28
|
+
*
|
|
29
|
+
* The third is the one worth stating a reason for. `sources_digest` answers
|
|
30
|
+
* "did the code move since a person read it", and BUILD OUTPUT moves on every
|
|
31
|
+
* CI run without anybody touching the code — a `sources: [src/]` over a repo
|
|
32
|
+
* that compiles into `src/generated/` went stale on a schedule, so the warning
|
|
33
|
+
* that means "re-read this" arrived when nothing had been written. A signal
|
|
34
|
+
* that fires every night is a signal people learn to close. Git's answer is
|
|
35
|
+
* used rather than a list of our own because it is the answer the repository
|
|
36
|
+
* already gives every other tool, and it is the author's to change.
|
|
37
|
+
*
|
|
38
|
+
* The fallback is deliberate: no git, no checkout, a git that errors — hash
|
|
39
|
+
* everything. Missing an exclusion costs noise; inventing one costs a file the
|
|
40
|
+
* stamp silently stops watching, and this walk exists to have nothing in that
|
|
41
|
+
* category.
|
|
42
|
+
*/
|
|
43
|
+
export async function collectSources(repoDir, sources) {
|
|
44
|
+
const found = new Map();
|
|
45
|
+
const skipped = new Map();
|
|
46
|
+
const relOf = (abs) => relative(repoDir, abs).split(sep).join("/");
|
|
47
|
+
// realpath once, up front: every containment question below is asked against
|
|
48
|
+
// the resolved root, so a repo reached through a symlink (a worktree under
|
|
49
|
+
// /var -> /private/var on macOS, say) does not make its own files look external.
|
|
50
|
+
const repoReal = await realpath(repoDir).catch(() => resolve(repoDir));
|
|
51
|
+
// The realpaths of the directories currently being walked — the cycle guard.
|
|
52
|
+
// A STACK rather than a set of everything ever entered, because the two rules
|
|
53
|
+
// answer different questions: a directory reachable by two spellings (a
|
|
54
|
+
// `current -> versions/3` link beside the real tree) is content under both
|
|
55
|
+
// names and belongs in the digest twice, while a directory reachable from
|
|
56
|
+
// INSIDE itself is a loop. A global visited-set conflates them, and then which
|
|
57
|
+
// spelling survives depends on the order readdir happened to return — a
|
|
58
|
+
// digest that differs between two machines holding identical bytes.
|
|
59
|
+
const walking = [];
|
|
60
|
+
const walk = async (dir) => {
|
|
61
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
62
|
+
// Sorted, so a tree walks the same way on every filesystem.
|
|
63
|
+
entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
64
|
+
for (const entry of entries) {
|
|
65
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
66
|
+
continue;
|
|
67
|
+
const child = join(dir, entry.name);
|
|
68
|
+
if (entry.isDirectory())
|
|
69
|
+
await enterDir(child);
|
|
70
|
+
else if (entry.isFile())
|
|
71
|
+
found.set(relOf(child), child);
|
|
72
|
+
// readdir reports symlinks by lstat, so isDirectory() and isFile() are
|
|
73
|
+
// BOTH false for one: before this branch existed a symlinked file, and a
|
|
74
|
+
// whole subtree behind a symlinked directory, fell between the two arms
|
|
75
|
+
// and left the digest without a word said. That is the failure this
|
|
76
|
+
// module is here to prevent, arriving through its own walk.
|
|
77
|
+
else if (entry.isSymbolicLink())
|
|
78
|
+
await follow(child);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const enterDir = async (dir) => {
|
|
82
|
+
const real = await realpath(dir).catch(() => null);
|
|
83
|
+
if (real === null || walking.includes(real))
|
|
84
|
+
return;
|
|
85
|
+
walking.push(real);
|
|
86
|
+
try {
|
|
87
|
+
await walk(dir);
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
walking.pop();
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
const follow = async (link) => {
|
|
94
|
+
const real = await realpath(link).catch(() => null);
|
|
95
|
+
if (real === null) {
|
|
96
|
+
skipped.set(relOf(link), "a symlink that does not resolve");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Outside the repo the file is not this service's to vouch for, and the
|
|
100
|
+
// same rule already refuses it as a top-level `sources` entry
|
|
101
|
+
// (sources.path-outside). Reported rather than dropped: an author who
|
|
102
|
+
// vendored a sibling repo through a symlink needs to know the stamp stops
|
|
103
|
+
// at the link.
|
|
104
|
+
if (!isPathInside(repoReal, real)) {
|
|
105
|
+
skipped.set(relOf(link), "a symlink whose target is outside this repository");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const info = await stat(link).catch(() => null);
|
|
109
|
+
if (info === null) {
|
|
110
|
+
skipped.set(relOf(link), "a symlink that does not resolve");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// A link's own spelling is what the doc's author wrote, so that is the path
|
|
114
|
+
// that goes into the digest — the content comes from the target either way.
|
|
115
|
+
if (info.isFile())
|
|
116
|
+
found.set(relOf(link), link);
|
|
117
|
+
// A link that closes a loop is not reported: everything behind it is already
|
|
118
|
+
// in the digest under the spelling it was reached by, so nothing has stopped
|
|
119
|
+
// being watched. Only unhashed CONTENT is worth a warning.
|
|
120
|
+
else if (info.isDirectory())
|
|
121
|
+
await enterDir(link);
|
|
122
|
+
else
|
|
123
|
+
skipped.set(relOf(link), "a symlink to neither a file nor a directory");
|
|
124
|
+
};
|
|
125
|
+
for (const source of sources) {
|
|
126
|
+
const cleaned = source.trim();
|
|
127
|
+
if (cleaned.length === 0)
|
|
128
|
+
continue;
|
|
129
|
+
const root = resolveInside(repoDir, cleaned, `source '${source}'`);
|
|
130
|
+
const info = await stat(root).catch(() => null);
|
|
131
|
+
if (info === null)
|
|
132
|
+
continue;
|
|
133
|
+
if (info.isFile())
|
|
134
|
+
found.set(relOf(root), root);
|
|
135
|
+
else if (info.isDirectory())
|
|
136
|
+
await enterDir(root);
|
|
137
|
+
}
|
|
138
|
+
// One git invocation for the whole expansion, not one per file: the skipped
|
|
139
|
+
// list rides along so an ignored symlink does not warn about a path the
|
|
140
|
+
// repository already says is not its own.
|
|
141
|
+
const ignored = await gitIgnoredPaths(repoDir, [...found.keys(), ...skipped.keys()]);
|
|
142
|
+
for (const rel of ignored) {
|
|
143
|
+
found.delete(rel);
|
|
144
|
+
skipped.delete(rel);
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
// Plain codepoint order, not locale order: the digest has to be the same
|
|
148
|
+
// everywhere it is computed.
|
|
149
|
+
found: [...found.entries()]
|
|
150
|
+
.map(([rel, abs]) => ({ rel, abs }))
|
|
151
|
+
.sort((a, b) => (a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0)),
|
|
152
|
+
skipped: [...skipped.entries()]
|
|
153
|
+
.map(([path, reason]) => ({ path, reason }))
|
|
154
|
+
.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=walk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"walk.js","sourceRoot":"","sources":["../../../src/core/provenance/walk.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The source walk: expanding a `sources` list to the concrete files it covers\n * — found and skipped. What the walk refuses to follow is reported, never\n * dropped, because the digest downstream cannot go stale over bytes it never\n * hashed; the skipped list is how that hole stays visible.\n */\nimport { readdir, realpath, stat } from \"node:fs/promises\";\nimport { join, relative, resolve, sep } from \"node:path\";\nimport { isPathInside, resolveInside } from \"../kernel/path-safety.js\";\nimport { gitIgnoredPaths } from \"./git.js\";\n\n/** A path the walk found but would not hash, and the reason a reader needs. */\nexport interface SkippedSource {\n /** Repo-relative, `/`-separated. */\n path: string;\n /** Why it was left out — the half of the sentence after the path. */\n reason: string;\n}\n\nexport interface SourceFile {\n /** Repo-relative, `/`-separated — the spelling that goes into the hash. */\n rel: string;\n abs: string;\n}\n\n/**\n * The files a `sources` list names: a file is itself, a directory is everything\n * beneath it. Nothing else — glob patterns used to be matched here, by a\n * hand-rolled dialect that silently differed from the gitignore/minimatch\n * conventions authors assume, so a pattern quietly digested a DIFFERENT file\n * set than its author intended. Pattern-looking entries are now refused loudly\n * upstream (see patternSources); this function only ever sees literal paths.\n *\n * Three exclusions, all part of the digest recipe's contract:\n *\n * - dot-entries are skipped while walking — `.git` is not what the doc was\n * written from — though a path naming one outright is still honoured;\n * - `node_modules` is skipped the same way, unconditionally: it is the one\n * build input every ecosystem agrees is not source, and it is the one that\n * makes the walk take minutes;\n * - anything the repository's own `.gitignore` covers is dropped, when the\n * repo is a git checkout and git is on the PATH (see gitIgnoredPaths).\n *\n * The third is the one worth stating a reason for. `sources_digest` answers\n * \"did the code move since a person read it\", and BUILD OUTPUT moves on every\n * CI run without anybody touching the code — a `sources: [src/]` over a repo\n * that compiles into `src/generated/` went stale on a schedule, so the warning\n * that means \"re-read this\" arrived when nothing had been written. A signal\n * that fires every night is a signal people learn to close. Git's answer is\n * used rather than a list of our own because it is the answer the repository\n * already gives every other tool, and it is the author's to change.\n *\n * The fallback is deliberate: no git, no checkout, a git that errors — hash\n * everything. Missing an exclusion costs noise; inventing one costs a file the\n * stamp silently stops watching, and this walk exists to have nothing in that\n * category.\n */\nexport async function collectSources(\n repoDir: string,\n sources: string[],\n): Promise<{ found: SourceFile[]; skipped: SkippedSource[] }> {\n const found = new Map<string, string>();\n const skipped = new Map<string, string>();\n const relOf = (abs: string): string => relative(repoDir, abs).split(sep).join(\"/\");\n\n // realpath once, up front: every containment question below is asked against\n // the resolved root, so a repo reached through a symlink (a worktree under\n // /var -> /private/var on macOS, say) does not make its own files look external.\n const repoReal = await realpath(repoDir).catch(() => resolve(repoDir));\n // The realpaths of the directories currently being walked — the cycle guard.\n // A STACK rather than a set of everything ever entered, because the two rules\n // answer different questions: a directory reachable by two spellings (a\n // `current -> versions/3` link beside the real tree) is content under both\n // names and belongs in the digest twice, while a directory reachable from\n // INSIDE itself is a loop. A global visited-set conflates them, and then which\n // spelling survives depends on the order readdir happened to return — a\n // digest that differs between two machines holding identical bytes.\n const walking: string[] = [];\n\n const walk = async (dir: string): Promise<void> => {\n const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);\n // Sorted, so a tree walks the same way on every filesystem.\n entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));\n for (const entry of entries) {\n if (entry.name.startsWith(\".\") || entry.name === \"node_modules\") continue;\n const child = join(dir, entry.name);\n if (entry.isDirectory()) await enterDir(child);\n else if (entry.isFile()) found.set(relOf(child), child);\n // readdir reports symlinks by lstat, so isDirectory() and isFile() are\n // BOTH false for one: before this branch existed a symlinked file, and a\n // whole subtree behind a symlinked directory, fell between the two arms\n // and left the digest without a word said. That is the failure this\n // module is here to prevent, arriving through its own walk.\n else if (entry.isSymbolicLink()) await follow(child);\n }\n };\n\n const enterDir = async (dir: string): Promise<void> => {\n const real = await realpath(dir).catch(() => null);\n if (real === null || walking.includes(real)) return;\n walking.push(real);\n try {\n await walk(dir);\n } finally {\n walking.pop();\n }\n };\n\n const follow = async (link: string): Promise<void> => {\n const real = await realpath(link).catch(() => null);\n if (real === null) {\n skipped.set(relOf(link), \"a symlink that does not resolve\");\n return;\n }\n // Outside the repo the file is not this service's to vouch for, and the\n // same rule already refuses it as a top-level `sources` entry\n // (sources.path-outside). Reported rather than dropped: an author who\n // vendored a sibling repo through a symlink needs to know the stamp stops\n // at the link.\n if (!isPathInside(repoReal, real)) {\n skipped.set(relOf(link), \"a symlink whose target is outside this repository\");\n return;\n }\n const info = await stat(link).catch(() => null);\n if (info === null) {\n skipped.set(relOf(link), \"a symlink that does not resolve\");\n return;\n }\n // A link's own spelling is what the doc's author wrote, so that is the path\n // that goes into the digest — the content comes from the target either way.\n if (info.isFile()) found.set(relOf(link), link);\n // A link that closes a loop is not reported: everything behind it is already\n // in the digest under the spelling it was reached by, so nothing has stopped\n // being watched. Only unhashed CONTENT is worth a warning.\n else if (info.isDirectory()) await enterDir(link);\n else skipped.set(relOf(link), \"a symlink to neither a file nor a directory\");\n };\n\n for (const source of sources) {\n const cleaned = source.trim();\n if (cleaned.length === 0) continue;\n const root = resolveInside(repoDir, cleaned, `source '${source}'`);\n const info = await stat(root).catch(() => null);\n if (info === null) continue;\n if (info.isFile()) found.set(relOf(root), root);\n else if (info.isDirectory()) await enterDir(root);\n }\n\n // One git invocation for the whole expansion, not one per file: the skipped\n // list rides along so an ignored symlink does not warn about a path the\n // repository already says is not its own.\n const ignored = await gitIgnoredPaths(repoDir, [...found.keys(), ...skipped.keys()]);\n for (const rel of ignored) {\n found.delete(rel);\n skipped.delete(rel);\n }\n\n return {\n // Plain codepoint order, not locale order: the digest has to be the same\n // everywhere it is computed.\n found: [...found.entries()]\n .map(([rel, abs]) => ({ rel, abs }))\n .sort((a, b) => (a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0)),\n skipped: [...skipped.entries()]\n .map(([path, reason]) => ({ path, reason }))\n .sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)),\n };\n}\n"]}
|