@ybotok/loam 0.1.0-beta.2 → 0.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2570 -61
- package/COMPARISON.md +80 -63
- package/CONTRIBUTING.md +84 -0
- package/MIGRATING-from-OpenSpec.md +213 -54
- package/QUICKSTART.md +125 -0
- package/README.md +450 -150
- package/ROADMAP.md +494 -0
- package/SCHEMA.md +2362 -245
- package/SECURITY.md +21 -3
- package/WORKFLOW.md +701 -0
- package/dist/cli.js +113 -19
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +209 -0
- package/dist/commands/adopt/adopt.js.map +1 -0
- package/dist/commands/adopt/render.js +268 -0
- package/dist/commands/adopt/render.js.map +1 -0
- package/dist/commands/archive/archive.js +77 -0
- package/dist/commands/archive/archive.js.map +1 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +159 -0
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +317 -0
- package/dist/commands/archive/plan/gate.js.map +1 -0
- package/dist/commands/archive/plan/landscape.js +161 -0
- package/dist/commands/archive/plan/landscape.js.map +1 -0
- package/dist/commands/archive/plan/refusal.js +111 -0
- package/dist/commands/archive/plan/refusal.js.map +1 -0
- package/dist/commands/archive/plan/specs.js +237 -0
- package/dist/commands/archive/plan/specs.js.map +1 -0
- package/dist/commands/archive/plan/state.js +27 -0
- package/dist/commands/archive/plan/state.js.map +1 -0
- package/dist/commands/archive/run.js +251 -0
- package/dist/commands/archive/run.js.map +1 -0
- package/dist/commands/context/context.js +99 -0
- package/dist/commands/context/context.js.map +1 -0
- package/dist/commands/context/print.js +283 -0
- package/dist/commands/context/print.js.map +1 -0
- package/dist/commands/delta/delta.js +279 -0
- package/dist/commands/delta/delta.js.map +1 -0
- package/dist/commands/delta/print.js +205 -0
- package/dist/commands/delta/print.js.map +1 -0
- package/dist/commands/dependencies.js +9 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +93 -0
- package/dist/commands/diff/diff.js.map +1 -0
- package/dist/commands/diff/print.js +38 -0
- package/dist/commands/diff/print.js.map +1 -0
- package/dist/commands/doctor.js +5 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain/explain.js +151 -0
- package/dist/commands/explain/explain.js.map +1 -0
- package/dist/commands/explore.js +29 -24
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +115 -0
- package/dist/commands/gate/gate.js.map +1 -0
- package/dist/commands/gate/print.js +54 -0
- package/dist/commands/gate/print.js.map +1 -0
- package/dist/commands/gherkin/commit.js +146 -0
- package/dist/commands/gherkin/commit.js.map +1 -0
- package/dist/commands/gherkin/gherkin.js +246 -0
- package/dist/commands/gherkin/gherkin.js.map +1 -0
- package/dist/commands/gherkin/reconcile.js +149 -0
- package/dist/commands/gherkin/reconcile.js.map +1 -0
- package/dist/commands/gherkin/render.js +77 -0
- package/dist/commands/gherkin/render.js.map +1 -0
- package/dist/commands/init/example.js +204 -0
- package/dist/commands/init/example.js.map +1 -0
- package/dist/commands/init/first-hour.js +28 -0
- package/dist/commands/init/first-hour.js.map +1 -0
- package/dist/commands/{init.js → init/init.js} +146 -71
- package/dist/commands/init/init.js.map +1 -0
- package/dist/commands/init/mcp.js +131 -0
- package/dist/commands/init/mcp.js.map +1 -0
- package/dist/commands/init/options.js +78 -0
- package/dist/commands/init/options.js.map +1 -0
- package/dist/commands/instructions.js +82 -14
- package/dist/commands/instructions.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +65 -0
- package/dist/commands/list/campaign/campaign.js.map +1 -0
- package/dist/commands/list/campaign/owners.js +65 -0
- package/dist/commands/list/campaign/owners.js.map +1 -0
- package/dist/commands/list/json.js +157 -0
- package/dist/commands/list/json.js.map +1 -0
- package/dist/commands/list/list.js +343 -0
- package/dist/commands/list/list.js.map +1 -0
- package/dist/commands/list/print.js +301 -0
- package/dist/commands/list/print.js.map +1 -0
- package/dist/commands/list/review.js +120 -0
- package/dist/commands/list/review.js.map +1 -0
- package/dist/commands/list/views.js +105 -0
- package/dist/commands/list/views.js.map +1 -0
- package/dist/commands/mcp/dispatch.js +183 -0
- package/dist/commands/mcp/dispatch.js.map +1 -0
- package/dist/commands/mcp/mcp.js +34 -0
- package/dist/commands/mcp/mcp.js.map +1 -0
- package/dist/commands/mcp/serve.js +55 -0
- package/dist/commands/mcp/serve.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/active.js +170 -0
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
- package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/living.js +62 -0
- package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
- package/dist/commands/migrate-openspec/materialize/target.js +219 -0
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
- package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/error.js +19 -0
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
- package/dist/commands/migrate-openspec/openspec/read.js +49 -0
- package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
- package/dist/commands/migrate-openspec/print.js +89 -0
- package/dist/commands/migrate-openspec/print.js.map +1 -0
- package/dist/commands/new/commit.js +76 -0
- package/dist/commands/new/commit.js.map +1 -0
- package/dist/commands/new/new.js +283 -0
- package/dist/commands/new/new.js.map +1 -0
- package/dist/commands/new/templates.js +355 -0
- package/dist/commands/new/templates.js.map +1 -0
- package/dist/commands/open.js +203 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/policy/format.js +152 -0
- package/dist/commands/policy/format.js.map +1 -0
- package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
- package/dist/commands/policy/gate.js.map +1 -0
- package/dist/commands/rebase/edit.js +106 -0
- package/dist/commands/rebase/edit.js.map +1 -0
- package/dist/commands/rebase/living.js +185 -0
- package/dist/commands/rebase/living.js.map +1 -0
- package/dist/commands/rebase/plan.js +245 -0
- package/dist/commands/rebase/plan.js.map +1 -0
- package/dist/commands/rebase/rebase.js +275 -0
- package/dist/commands/rebase/rebase.js.map +1 -0
- package/dist/commands/seed/commit.js +192 -0
- package/dist/commands/seed/commit.js.map +1 -0
- package/dist/commands/seed/plan.js +133 -0
- package/dist/commands/seed/plan.js.map +1 -0
- package/dist/commands/seed/seed.js +172 -0
- package/dist/commands/seed/seed.js.map +1 -0
- package/dist/commands/show/feature.js +178 -0
- package/dist/commands/show/feature.js.map +1 -0
- package/dist/commands/show/marks.js +24 -0
- package/dist/commands/show/marks.js.map +1 -0
- package/dist/commands/{show.js → show/service.js} +63 -152
- package/dist/commands/show/service.js.map +1 -0
- package/dist/commands/show/show.js +77 -0
- package/dist/commands/show/show.js.map +1 -0
- package/dist/commands/status/json.js +45 -0
- package/dist/commands/status/json.js.map +1 -0
- package/dist/commands/status/print.js +157 -0
- package/dist/commands/status/print.js.map +1 -0
- package/dist/commands/status/status.js +119 -0
- package/dist/commands/status/status.js.map +1 -0
- package/dist/commands/steps/steps.js +189 -0
- package/dist/commands/steps/steps.js.map +1 -0
- package/dist/commands/subsystem/edit.js +192 -0
- package/dist/commands/subsystem/edit.js.map +1 -0
- package/dist/commands/subsystem/move.js +265 -0
- package/dist/commands/subsystem/move.js.map +1 -0
- package/dist/commands/subsystem/query.js +115 -0
- package/dist/commands/subsystem/query.js.map +1 -0
- package/dist/commands/subsystem/subsystem.js +101 -0
- package/dist/commands/subsystem/subsystem.js.map +1 -0
- package/dist/commands/subsystem/sync.js +103 -0
- package/dist/commands/subsystem/sync.js.map +1 -0
- package/dist/commands/subsystem/txn/txn.js +94 -0
- package/dist/commands/subsystem/txn/txn.js.map +1 -0
- package/dist/commands/subsystem/txn/views.js +70 -0
- package/dist/commands/subsystem/txn/views.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +160 -0
- package/dist/commands/unarchive/manifest.js.map +1 -0
- package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
- package/dist/commands/unarchive/restore.js.map +1 -0
- package/dist/commands/unarchive/unarchive.js +67 -0
- package/dist/commands/unarchive/unarchive.js.map +1 -0
- package/dist/commands/validate/arch-coverage.js +194 -0
- package/dist/commands/validate/arch-coverage.js.map +1 -0
- package/dist/commands/validate/checks/fleet-shape.js +296 -0
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
- package/dist/commands/validate/checks/health-deps.js +55 -0
- package/dist/commands/validate/checks/health-deps.js.map +1 -0
- package/dist/commands/validate/checks/requirements.js +254 -0
- package/dist/commands/validate/checks/requirements.js.map +1 -0
- package/dist/commands/validate/checks/sources.js +76 -0
- package/dist/commands/validate/checks/sources.js.map +1 -0
- package/dist/commands/validate/checks/vocabulary.js +31 -0
- package/dist/commands/validate/checks/vocabulary.js.map +1 -0
- package/dist/commands/validate/feature.js +216 -0
- package/dist/commands/validate/feature.js.map +1 -0
- package/dist/commands/validate/fleet/census.js +69 -0
- package/dist/commands/validate/fleet/census.js.map +1 -0
- package/dist/commands/validate/fleet/kind-tags.js +39 -0
- package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
- package/dist/commands/validate/fleet/landscape.js +353 -0
- package/dist/commands/validate/fleet/landscape.js.map +1 -0
- package/dist/commands/validate/fleet/load.js +52 -0
- package/dist/commands/validate/fleet/load.js.map +1 -0
- package/dist/commands/validate/fleet/obligations.js +236 -0
- package/dist/commands/validate/fleet/obligations.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
- package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
- package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/print.js +63 -0
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
- package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/place.js +13 -0
- package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
- package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/steps.js +197 -0
- package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
- package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
- package/dist/commands/validate/fleet/views/ids.js +40 -0
- package/dist/commands/validate/fleet/views/ids.js.map +1 -0
- package/dist/commands/validate/fleet/views/stale.js +59 -0
- package/dist/commands/validate/fleet/views/stale.js.map +1 -0
- package/dist/commands/validate/links/corpus.js +92 -0
- package/dist/commands/validate/links/corpus.js.map +1 -0
- package/dist/commands/validate/links/glossary.js +75 -0
- package/dist/commands/validate/links/glossary.js.map +1 -0
- package/dist/commands/validate/report.js +269 -0
- package/dist/commands/validate/report.js.map +1 -0
- package/dist/commands/validate/service/api.js +338 -0
- package/dist/commands/validate/service/api.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +219 -0
- package/dist/commands/validate/service/events/events.js.map +1 -0
- package/dist/commands/validate/service/events/message-producers.js +77 -0
- package/dist/commands/validate/service/events/message-producers.js.map +1 -0
- package/dist/commands/validate/service/evidence-pins.js +131 -0
- package/dist/commands/validate/service/evidence-pins.js.map +1 -0
- package/dist/commands/validate/service/service.js +250 -0
- package/dist/commands/validate/service/service.js.map +1 -0
- package/dist/commands/validate/service/specs.js +219 -0
- package/dist/commands/validate/service/specs.js.map +1 -0
- package/dist/commands/validate/service/spine.js +113 -0
- package/dist/commands/validate/service/spine.js.map +1 -0
- package/dist/commands/validate/validate.js +338 -0
- package/dist/commands/validate/validate.js.map +1 -0
- package/dist/commands/verify/cross/diff.js +182 -0
- package/dist/commands/verify/cross/diff.js.map +1 -0
- package/dist/commands/verify/evidence/contract.js +32 -0
- package/dist/commands/verify/evidence/contract.js.map +1 -0
- package/dist/commands/verify/evidence/pins.js +52 -0
- package/dist/commands/verify/evidence/pins.js.map +1 -0
- package/dist/commands/verify/evidence/read.js +72 -0
- package/dist/commands/verify/evidence/read.js.map +1 -0
- package/dist/commands/verify/frozen.js +241 -0
- package/dist/commands/verify/frozen.js.map +1 -0
- package/dist/commands/verify/record.js +226 -0
- package/dist/commands/verify/record.js.map +1 -0
- package/dist/commands/verify/report.js +223 -0
- package/dist/commands/verify/report.js.map +1 -0
- package/dist/commands/verify/results.js +246 -0
- package/dist/commands/verify/results.js.map +1 -0
- package/dist/commands/verify/verify.js +217 -0
- package/dist/commands/verify/verify.js.map +1 -0
- package/dist/commands/vouch/contract.js +11 -0
- package/dist/commands/vouch/contract.js.map +1 -0
- package/dist/commands/vouch/pack/contract.js +2 -0
- package/dist/commands/vouch/pack/contract.js.map +1 -0
- package/dist/commands/vouch/pack/pack.js +266 -0
- package/dist/commands/vouch/pack/pack.js.map +1 -0
- package/dist/commands/vouch/pack/print.js +271 -0
- package/dist/commands/vouch/pack/print.js.map +1 -0
- package/dist/commands/vouch/pack/sections.js +142 -0
- package/dist/commands/vouch/pack/sections.js.map +1 -0
- package/dist/commands/vouch/prompt/confirm.js +46 -0
- package/dist/commands/vouch/prompt/confirm.js.map +1 -0
- package/dist/commands/vouch/report.js +101 -0
- package/dist/commands/vouch/report.js.map +1 -0
- package/dist/commands/vouch/run.js +285 -0
- package/dist/commands/vouch/run.js.map +1 -0
- package/dist/commands/vouch/sample/plan.js +125 -0
- package/dist/commands/vouch/sample/plan.js.map +1 -0
- package/dist/commands/vouch/sample/print.js +109 -0
- package/dist/commands/vouch/sample/print.js.map +1 -0
- package/dist/commands/vouch/vet/verify.js +123 -0
- package/dist/commands/vouch/vet/verify.js.map +1 -0
- package/dist/commands/vouch/vouch.js +176 -0
- package/dist/commands/vouch/vouch.js.map +1 -0
- package/dist/core/agent/agents-md/artifacts.js +220 -0
- package/dist/core/agent/agents-md/artifacts.js.map +1 -0
- package/dist/core/agent/agents-md/command-map.js +324 -0
- package/dist/core/agent/agents-md/command-map.js.map +1 -0
- package/dist/core/agent/agents-md/cycle.js +165 -0
- package/dist/core/agent/agents-md/cycle.js.map +1 -0
- package/dist/core/agent/agents-md/map/explain.js +34 -0
- package/dist/core/agent/agents-md/map/explain.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
- package/dist/core/agent/agents-md/map/mcp.js +41 -0
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
- package/dist/core/agent/agents-md/map/status.js +62 -0
- package/dist/core/agent/agents-md/map/status.js.map +1 -0
- package/dist/core/agent/agents-md/map/subsystem.js +32 -0
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
- package/dist/core/agent/agents-md/map/usecases.js +60 -0
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
- package/dist/core/agent/agents-md/refusals.js +246 -0
- package/dist/core/agent/agents-md/refusals.js.map +1 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
- package/dist/core/agent/agents-md/spine.js +40 -0
- package/dist/core/agent/agents-md/spine.js.map +1 -0
- package/dist/core/agent/agents-md.js +170 -0
- package/dist/core/agent/agents-md.js.map +1 -0
- package/dist/core/agent/agents-stamp.js +155 -0
- package/dist/core/agent/agents-stamp.js.map +1 -0
- package/dist/core/agent/contract.js +2 -0
- package/dist/core/agent/contract.js.map +1 -0
- package/dist/core/agent/protocol.js +193 -0
- package/dist/core/agent/protocol.js.map +1 -0
- package/dist/core/agent/scaffold.js +259 -0
- package/dist/core/agent/scaffold.js.map +1 -0
- package/dist/core/agent/support/report.js +103 -0
- package/dist/core/agent/support/report.js.map +1 -0
- package/dist/core/agent/tools/dialects.js +150 -0
- package/dist/core/agent/tools/dialects.js.map +1 -0
- package/dist/core/agent/tools/registry.js +228 -0
- package/dist/core/agent/tools/registry.js.map +1 -0
- package/dist/core/agent/workflows/adopt.js +133 -0
- package/dist/core/agent/workflows/adopt.js.map +1 -0
- package/dist/core/agent/workflows/check.js +334 -0
- package/dist/core/agent/workflows/check.js.map +1 -0
- package/dist/core/agent/workflows/closing.js +262 -0
- package/dist/core/agent/workflows/closing.js.map +1 -0
- package/dist/core/agent/workflows/feature.js +105 -0
- package/dist/core/agent/workflows/feature.js.map +1 -0
- package/dist/core/agent/workflows/implement.js +105 -0
- package/dist/core/agent/workflows/implement.js.map +1 -0
- package/dist/core/agent/workflows/reference/authoring.js +209 -0
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
- package/dist/core/agent/workflows/reference/codes.js +65 -0
- package/dist/core/agent/workflows/reference/codes.js.map +1 -0
- package/dist/core/agent/workflows/reference/done-check.js +188 -0
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
- package/dist/core/agent/workflows/reference/reference.js +16 -0
- package/dist/core/agent/workflows/reference/reference.js.map +1 -0
- package/dist/core/agent/workflows/reference/spine.js +297 -0
- package/dist/core/agent/workflows/reference/spine.js.map +1 -0
- package/dist/core/asyncapi/baseline/plan.js +110 -0
- package/dist/core/asyncapi/baseline/plan.js.map +1 -0
- package/dist/core/asyncapi/baseline/surfaces.js +54 -0
- package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
- package/dist/core/asyncapi/depth.js +164 -0
- package/dist/core/asyncapi/depth.js.map +1 -0
- package/dist/core/asyncapi/digest.js +141 -0
- package/dist/core/asyncapi/digest.js.map +1 -0
- package/dist/core/asyncapi/merge/components.js +91 -0
- package/dist/core/asyncapi/merge/components.js.map +1 -0
- package/dist/core/asyncapi/merge/error.js +33 -0
- package/dist/core/asyncapi/merge/error.js.map +1 -0
- package/dist/core/asyncapi/merge/markers.js +308 -0
- package/dist/core/asyncapi/merge/markers.js.map +1 -0
- package/dist/core/asyncapi/merge/merge.js +215 -0
- package/dist/core/asyncapi/merge/merge.js.map +1 -0
- package/dist/core/asyncapi/merge/pin.js +107 -0
- package/dist/core/asyncapi/merge/pin.js.map +1 -0
- package/dist/core/asyncapi/model.js +11 -0
- package/dist/core/asyncapi/model.js.map +1 -0
- package/dist/core/asyncapi/producers.js +50 -0
- package/dist/core/asyncapi/producers.js.map +1 -0
- package/dist/core/asyncapi/read.js +239 -0
- package/dist/core/asyncapi/read.js.map +1 -0
- package/dist/core/brief/brief.js +225 -0
- package/dist/core/brief/brief.js.map +1 -0
- package/dist/core/brief/checks.js +250 -0
- package/dist/core/brief/checks.js.map +1 -0
- package/dist/core/brief/landscape.js +194 -0
- package/dist/core/brief/landscape.js.map +1 -0
- package/dist/core/brief/targets.js +226 -0
- package/dist/core/brief/targets.js.map +1 -0
- package/dist/core/brief/unchecked.js +47 -0
- package/dist/core/brief/unchecked.js.map +1 -0
- package/dist/core/c4/arch.js +282 -0
- package/dist/core/c4/arch.js.map +1 -0
- package/dist/core/c4/likec4.js +88 -0
- package/dist/core/c4/likec4.js.map +1 -0
- package/dist/core/c4/parsed/dynamic-views.js +132 -0
- package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
- package/dist/core/c4/parsed/specification.js +78 -0
- package/dist/core/c4/parsed/specification.js.map +1 -0
- package/dist/core/c4/parsed/values.js +57 -0
- package/dist/core/c4/parsed/values.js.map +1 -0
- package/dist/core/c4/parsed/view-ids.js +75 -0
- package/dist/core/c4/parsed/view-ids.js.map +1 -0
- package/dist/core/c4/project/architecture.js +45 -0
- package/dist/core/c4/project/architecture.js.map +1 -0
- package/dist/core/c4/project/documents.js +58 -0
- package/dist/core/c4/project/documents.js.map +1 -0
- package/dist/core/c4/project/load.js +183 -0
- package/dist/core/c4/project/load.js.map +1 -0
- package/dist/core/c4/resolve/service.js +113 -0
- package/dist/core/c4/resolve/service.js.map +1 -0
- package/dist/core/c4/seed/fleet-file.js +201 -0
- package/dist/core/c4/seed/fleet-file.js.map +1 -0
- package/dist/core/c4/seed/items.js +91 -0
- package/dist/core/c4/seed/items.js.map +1 -0
- package/dist/core/c4/seed/stamp.js +48 -0
- package/dist/core/c4/seed/stamp.js.map +1 -0
- package/dist/core/c4/seed/template.js +109 -0
- package/dist/core/c4/seed/template.js.map +1 -0
- package/dist/core/c4/source-mask.js +82 -0
- package/dist/core/c4/source-mask.js.map +1 -0
- package/dist/core/c4/source-scan.js +227 -0
- package/dist/core/c4/source-scan.js.map +1 -0
- package/dist/core/c4/splice/authored-source.js +101 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -0
- package/dist/core/c4/splice/contract.js +10 -0
- package/dist/core/c4/splice/contract.js.map +1 -0
- package/dist/core/c4/splice/delta-blocks.js +126 -0
- package/dist/core/c4/splice/delta-blocks.js.map +1 -0
- package/dist/core/c4/splice/identity/edges.js +36 -0
- package/dist/core/c4/splice/identity/edges.js.map +1 -0
- package/dist/core/c4/splice/landscape-merge.js +269 -0
- package/dist/core/c4/splice/landscape-merge.js.map +1 -0
- package/dist/core/c4/splice/placement.js +194 -0
- package/dist/core/c4/splice/placement.js.map +1 -0
- package/dist/core/c4/workspace.js +172 -0
- package/dist/core/c4/workspace.js.map +1 -0
- package/dist/core/capabilities/authoring/notes.js +141 -0
- package/dist/core/capabilities/authoring/notes.js.map +1 -0
- package/dist/core/capabilities/capabilities.js +124 -0
- package/dist/core/capabilities/capabilities.js.map +1 -0
- package/dist/core/capabilities/delta/doc.js +50 -0
- package/dist/core/capabilities/delta/doc.js.map +1 -0
- package/dist/core/capabilities/delta/overlay.js +129 -0
- package/dist/core/capabilities/delta/overlay.js.map +1 -0
- package/dist/core/capabilities/delta/summary.js +57 -0
- package/dist/core/capabilities/delta/summary.js.map +1 -0
- package/dist/core/capabilities/delta/tree.js +43 -0
- package/dist/core/capabilities/delta/tree.js.map +1 -0
- package/dist/core/capabilities/delta/uncovered.js +222 -0
- package/dist/core/capabilities/delta/uncovered.js.map +1 -0
- package/dist/core/capabilities/findings.js +342 -0
- package/dist/core/capabilities/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/findings.js +181 -0
- package/dist/core/capabilities/realizes/findings.js.map +1 -0
- package/dist/core/capabilities/realizes/join.js +143 -0
- package/dist/core/capabilities/realizes/join.js.map +1 -0
- package/dist/core/capabilities/rollup.js +199 -0
- package/dist/core/capabilities/rollup.js.map +1 -0
- package/dist/core/capabilities/tree.js +126 -0
- package/dist/core/capabilities/tree.js.map +1 -0
- package/dist/core/capabilities/usecase-join.js +277 -0
- package/dist/core/capabilities/usecase-join.js.map +1 -0
- package/dist/core/coherence/authoring/scaffold.js +177 -0
- package/dist/core/coherence/authoring/scaffold.js.map +1 -0
- package/dist/core/coherence/authoring/sentinels.js +74 -0
- package/dist/core/coherence/authoring/sentinels.js.map +1 -0
- package/dist/core/coherence/coherence.js +251 -0
- package/dist/core/coherence/coherence.js.map +1 -0
- package/dist/core/coherence/declared.js +269 -0
- package/dist/core/coherence/declared.js.map +1 -0
- package/dist/core/coherence/events/declared.js +206 -0
- package/dist/core/coherence/events/declared.js.map +1 -0
- package/dist/core/coherence/events/events.js +204 -0
- package/dist/core/coherence/events/events.js.map +1 -0
- package/dist/core/coherence/events/grades.js +166 -0
- package/dist/core/coherence/events/grades.js.map +1 -0
- package/dist/core/coherence/events/lookups.js +122 -0
- package/dist/core/coherence/events/lookups.js.map +1 -0
- package/dist/core/coherence/events/merged.js +40 -0
- package/dist/core/coherence/events/merged.js.map +1 -0
- package/dist/core/coherence/living.js +198 -0
- package/dist/core/coherence/living.js.map +1 -0
- package/dist/core/coherence/lookups.js +155 -0
- package/dist/core/coherence/lookups.js.map +1 -0
- package/dist/core/coherence/pending.js +39 -0
- package/dist/core/coherence/pending.js.map +1 -0
- package/dist/core/conflict-markers.js +60 -0
- package/dist/core/conflict-markers.js.map +1 -0
- package/dist/core/delta/claims.js +73 -0
- package/dist/core/delta/claims.js.map +1 -0
- package/dist/core/delta/delta.js +296 -0
- package/dist/core/delta/delta.js.map +1 -0
- package/dist/core/delta/document.js +177 -0
- package/dist/core/delta/document.js.map +1 -0
- package/dist/core/delta/scope.js +48 -0
- package/dist/core/delta/scope.js.map +1 -0
- package/dist/core/delta/select.js +237 -0
- package/dist/core/delta/select.js.map +1 -0
- package/dist/core/dependencies/dependencies.js +141 -0
- package/dist/core/dependencies/dependencies.js.map +1 -0
- package/dist/core/dependencies/facts.js +165 -0
- package/dist/core/dependencies/facts.js.map +1 -0
- package/dist/core/dependencies/fanin.js +88 -0
- package/dist/core/dependencies/fanin.js.map +1 -0
- package/dist/core/dependencies/graph.js +106 -0
- package/dist/core/dependencies/graph.js.map +1 -0
- package/dist/core/diff/base-git.js +148 -0
- package/dist/core/diff/base-git.js.map +1 -0
- package/dist/core/diff/base-state.js +209 -0
- package/dist/core/diff/base-state.js.map +1 -0
- package/dist/core/diff/current-state.js +96 -0
- package/dist/core/diff/current-state.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +188 -0
- package/dist/core/diff/scope/changed-paths.js.map +1 -0
- package/dist/core/diff/semantic.js +243 -0
- package/dist/core/diff/semantic.js.map +1 -0
- package/dist/core/diff/victims.js +247 -0
- package/dist/core/diff/victims.js.map +1 -0
- package/dist/core/docs.js +59 -55
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor/agents.js +222 -0
- package/dist/core/doctor/agents.js.map +1 -0
- package/dist/core/doctor/config.js +135 -0
- package/dist/core/doctor/config.js.map +1 -0
- package/dist/core/doctor/doctor.js +275 -0
- package/dist/core/doctor/doctor.js.map +1 -0
- package/dist/core/doctor/report.js +13 -0
- package/dist/core/doctor/report.js.map +1 -0
- package/dist/core/doctor/residue.js +110 -0
- package/dist/core/doctor/residue.js.map +1 -0
- package/dist/core/document/apply.js +77 -0
- package/dist/core/document/apply.js.map +1 -0
- package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
- package/dist/core/document/frontmatter.js.map +1 -0
- package/dist/core/document/grammar/directives.js +143 -0
- package/dist/core/document/grammar/directives.js.map +1 -0
- package/dist/core/document/parse.js +263 -0
- package/dist/core/document/parse.js.map +1 -0
- package/dist/core/document/scenarios.js +221 -0
- package/dist/core/document/scenarios.js.map +1 -0
- package/dist/core/document/spec.js +241 -0
- package/dist/core/document/spec.js.map +1 -0
- package/dist/core/{config.js → envelope/config.js} +79 -18
- package/dist/core/envelope/config.js.map +1 -0
- package/dist/core/envelope/json.js +175 -0
- package/dist/core/envelope/json.js.map +1 -0
- package/dist/core/{version.js → envelope/version.js} +3 -3
- package/dist/core/envelope/version.js.map +1 -0
- package/dist/core/explain/families.js +175 -0
- package/dist/core/explain/families.js.map +1 -0
- package/dist/core/explain/fix-tables.js +178 -0
- package/dist/core/explain/fix-tables.js.map +1 -0
- package/dist/core/explain/lookup.js +181 -0
- package/dist/core/explain/lookup.js.map +1 -0
- package/dist/core/explain/refusals.js +49 -0
- package/dist/core/explain/refusals.js.map +1 -0
- package/dist/core/explain/terms.js +144 -0
- package/dist/core/explain/terms.js.map +1 -0
- package/dist/core/explore/describe.js +105 -0
- package/dist/core/explore/describe.js.map +1 -0
- package/dist/core/{explore.js → explore/explore.js} +50 -101
- package/dist/core/explore/explore.js.map +1 -0
- package/dist/core/fleet-context.js +132 -77
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +201 -0
- package/dist/core/gate/checks.js.map +1 -0
- package/dist/core/gate/partners.js +205 -0
- package/dist/core/gate/partners.js.map +1 -0
- package/dist/core/gate/report.js +47 -0
- package/dist/core/gate/report.js.map +1 -0
- package/dist/core/gate/verification.js +113 -0
- package/dist/core/gate/verification.js.map +1 -0
- package/dist/core/gherkin/digest.js +60 -0
- package/dist/core/gherkin/digest.js.map +1 -0
- package/dist/core/gherkin/emit.js +177 -0
- package/dist/core/gherkin/emit.js.map +1 -0
- package/dist/core/gherkin/read.js +76 -0
- package/dist/core/gherkin/read.js.map +1 -0
- package/dist/core/gherkin/stale.js +234 -0
- package/dist/core/gherkin/stale.js.map +1 -0
- package/dist/core/gherkin/stamp.js +77 -0
- package/dist/core/gherkin/stamp.js.map +1 -0
- package/dist/core/gherkin/steps/catalogue.js +154 -0
- package/dist/core/gherkin/steps/catalogue.js.map +1 -0
- package/dist/core/gherkin/steps/inventory.js +98 -0
- package/dist/core/gherkin/steps/inventory.js.map +1 -0
- package/dist/core/gherkin/steps/phrase.js +72 -0
- package/dist/core/gherkin/steps/phrase.js.map +1 -0
- package/dist/core/glossary/delta.js +106 -0
- package/dist/core/glossary/delta.js.map +1 -0
- package/dist/core/glossary/tree.js +86 -0
- package/dist/core/glossary/tree.js.map +1 -0
- package/dist/core/kernel/concurrency.js.map +1 -0
- package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
- package/dist/core/kernel/document-bytes.js.map +1 -0
- package/dist/core/kernel/fences.js +42 -0
- package/dist/core/kernel/fences.js.map +1 -0
- package/dist/core/kernel/ids/capability.js +72 -0
- package/dist/core/kernel/ids/capability.js.map +1 -0
- package/dist/core/kernel/ids/dirs.js +33 -0
- package/dist/core/kernel/ids/dirs.js.map +1 -0
- package/dist/core/kernel/ids/feature.js +75 -0
- package/dist/core/kernel/ids/feature.js.map +1 -0
- package/dist/core/kernel/ids/service.js +156 -0
- package/dist/core/kernel/ids/service.js.map +1 -0
- package/dist/core/kernel/ids/subsystem.js +63 -0
- package/dist/core/kernel/ids/subsystem.js.map +1 -0
- package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
- package/dist/core/kernel/path-safety.js.map +1 -0
- package/dist/core/kernel/records.js.map +1 -0
- package/dist/core/links/backlinks.js +68 -0
- package/dist/core/links/backlinks.js.map +1 -0
- package/dist/core/links/case.js +74 -0
- package/dist/core/links/case.js.map +1 -0
- package/dist/core/links/corpus.js +104 -0
- package/dist/core/links/corpus.js.map +1 -0
- package/dist/core/links/findings.js +110 -0
- package/dist/core/links/findings.js.map +1 -0
- package/dist/core/links/parse.js +236 -0
- package/dist/core/links/parse.js.map +1 -0
- package/dist/core/mcp/argv.js +111 -0
- package/dist/core/mcp/argv.js.map +1 -0
- package/dist/core/mcp/author-tools.js +72 -0
- package/dist/core/mcp/author-tools.js.map +1 -0
- package/dist/core/mcp/framing.js +46 -0
- package/dist/core/mcp/framing.js.map +1 -0
- package/dist/core/mcp/protocol.js +280 -0
- package/dist/core/mcp/protocol.js.map +1 -0
- package/dist/core/mcp/tools.js +314 -0
- package/dist/core/mcp/tools.js.map +1 -0
- package/dist/core/obligations/obligations.js +106 -0
- package/dist/core/obligations/obligations.js.map +1 -0
- package/dist/core/openapi/baseline/gate.js +166 -0
- package/dist/core/openapi/baseline/gate.js.map +1 -0
- package/dist/core/openapi/baseline/plan.js +112 -0
- package/dist/core/openapi/baseline/plan.js.map +1 -0
- package/dist/core/openapi/baseline/record.js +175 -0
- package/dist/core/openapi/baseline/record.js.map +1 -0
- package/dist/core/openapi/depth.js +122 -0
- package/dist/core/openapi/depth.js.map +1 -0
- package/dist/core/openapi/digest.js +189 -0
- package/dist/core/openapi/digest.js.map +1 -0
- package/dist/core/{openapi.js → openapi/doc.js} +44 -106
- package/dist/core/openapi/doc.js.map +1 -0
- package/dist/core/openapi/merge/components.js +234 -0
- package/dist/core/openapi/merge/components.js.map +1 -0
- package/dist/core/openapi/merge/error.js +26 -0
- package/dist/core/openapi/merge/error.js.map +1 -0
- package/dist/core/openapi/merge/markers.js +208 -0
- package/dist/core/openapi/merge/markers.js.map +1 -0
- package/dist/core/openapi/merge/merge.js +256 -0
- package/dist/core/openapi/merge/merge.js.map +1 -0
- package/dist/core/openapi/merge/pin.js +128 -0
- package/dist/core/openapi/merge/pin.js.map +1 -0
- package/dist/core/openspec/decide/capabilities.js +244 -0
- package/dist/core/openspec/decide/capabilities.js.map +1 -0
- package/dist/core/openspec/decide/dispositions.js +63 -0
- package/dist/core/openspec/decide/dispositions.js.map +1 -0
- package/dist/core/openspec/decide/renames.js +179 -0
- package/dist/core/openspec/decide/renames.js.map +1 -0
- package/dist/core/openspec/decide/verdict.js +84 -0
- package/dist/core/openspec/decide/verdict.js.map +1 -0
- package/dist/core/openspec/inventory.js +221 -0
- package/dist/core/openspec/inventory.js.map +1 -0
- package/dist/core/openspec/model/mapping.js +76 -0
- package/dist/core/openspec/model/mapping.js.map +1 -0
- package/dist/core/openspec/model/model.js +65 -0
- package/dist/core/openspec/model/model.js.map +1 -0
- package/dist/core/openspec/scan/changes.js +235 -0
- package/dist/core/openspec/scan/changes.js.map +1 -0
- package/dist/core/openspec/scan/config.js +200 -0
- package/dist/core/openspec/scan/config.js.map +1 -0
- package/dist/core/openspec/scan/shape.js +260 -0
- package/dist/core/openspec/scan/shape.js.map +1 -0
- package/dist/core/openspec/scan/walk.js +133 -0
- package/dist/core/openspec/scan/walk.js.map +1 -0
- package/dist/core/openspec/scan/workspace.js +71 -0
- package/dist/core/openspec/scan/workspace.js.map +1 -0
- package/dist/core/owners/codeowners.js +141 -0
- package/dist/core/owners/codeowners.js.map +1 -0
- package/dist/core/pack/features.js +120 -0
- package/dist/core/pack/features.js.map +1 -0
- package/dist/core/pack/joins.js +112 -0
- package/dist/core/pack/joins.js.map +1 -0
- package/dist/core/pack/living.js +118 -0
- package/dist/core/pack/living.js.map +1 -0
- package/dist/core/pack/pack.js +76 -0
- package/dist/core/pack/pack.js.map +1 -0
- package/dist/core/permissions/permissions.js +87 -0
- package/dist/core/permissions/permissions.js.map +1 -0
- package/dist/core/projection/api.js +84 -0
- package/dist/core/projection/api.js.map +1 -0
- package/dist/core/projection/arch-slice.js +63 -0
- package/dist/core/projection/arch-slice.js.map +1 -0
- package/dist/core/projection/events.js +34 -0
- package/dist/core/projection/events.js.map +1 -0
- package/dist/core/provenance/findings.js +208 -0
- package/dist/core/provenance/findings.js.map +1 -0
- package/dist/core/provenance/git.js +225 -0
- package/dist/core/provenance/git.js.map +1 -0
- package/dist/core/provenance/gitq/moves.js +182 -0
- package/dist/core/provenance/gitq/moves.js.map +1 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
- package/dist/core/provenance/sample/scope.js +238 -0
- package/dist/core/provenance/sample/scope.js.map +1 -0
- package/dist/core/provenance/sample/sections.js +61 -0
- package/dist/core/provenance/sample/sections.js.map +1 -0
- package/dist/core/provenance/sources.js +263 -0
- package/dist/core/provenance/sources.js.map +1 -0
- package/dist/core/provenance/stamp.js +232 -0
- package/dist/core/provenance/stamp.js.map +1 -0
- package/dist/core/provenance/walk.js +157 -0
- package/dist/core/provenance/walk.js.map +1 -0
- package/dist/core/repo/authored/paths.js +192 -0
- package/dist/core/repo/authored/paths.js.map +1 -0
- package/dist/core/repo/entries.js +126 -0
- package/dist/core/repo/entries.js.map +1 -0
- package/dist/core/repo/paths.js +236 -0
- package/dist/core/repo/paths.js.map +1 -0
- package/dist/core/repo/repo.js +248 -0
- package/dist/core/repo/repo.js.map +1 -0
- package/dist/core/repo/service-target.js +153 -0
- package/dist/core/repo/service-target.js.map +1 -0
- package/dist/core/repo/state.js +58 -0
- package/dist/core/repo/state.js.map +1 -0
- package/dist/core/repo/tree/find.js +52 -0
- package/dist/core/repo/tree/find.js.map +1 -0
- package/dist/core/repo/tree/fs.js +64 -0
- package/dist/core/repo/tree/fs.js.map +1 -0
- package/dist/core/repo/tree/marker.js +61 -0
- package/dist/core/repo/tree/marker.js.map +1 -0
- package/dist/core/repo/tree/views.js +172 -0
- package/dist/core/repo/tree/views.js.map +1 -0
- package/dist/core/repo/tree/walk.js +234 -0
- package/dist/core/repo/tree/walk.js.map +1 -0
- package/dist/core/results.js +99 -2
- package/dist/core/results.js.map +1 -1
- package/dist/core/scaffold/landscape.js +131 -0
- package/dist/core/scaffold/landscape.js.map +1 -0
- package/dist/core/scaffold/readme.js +84 -0
- package/dist/core/scaffold/readme.js.map +1 -0
- package/dist/core/staging/commit.js +241 -0
- package/dist/core/staging/commit.js.map +1 -0
- package/dist/core/staging/interrupted.js +96 -0
- package/dist/core/staging/interrupted.js.map +1 -0
- package/dist/core/staging/lock.js +206 -0
- package/dist/core/staging/lock.js.map +1 -0
- package/dist/core/staging/recovery/finding.js +35 -0
- package/dist/core/staging/recovery/finding.js.map +1 -0
- package/dist/core/staging/recovery/intent.js +98 -0
- package/dist/core/staging/recovery/intent.js.map +1 -0
- package/dist/core/staging/recovery/recover.js +126 -0
- package/dist/core/staging/recovery/recover.js.map +1 -0
- package/dist/core/staging/recovery/residue.js +62 -0
- package/dist/core/staging/recovery/residue.js.map +1 -0
- package/dist/core/staging/snapshot.js +202 -0
- package/dist/core/staging/snapshot.js.map +1 -0
- package/dist/core/staging/txn/forward.js +171 -0
- package/dist/core/staging/txn/forward.js.map +1 -0
- package/dist/core/staging/txn/journal.js +139 -0
- package/dist/core/staging/txn/journal.js.map +1 -0
- package/dist/core/staging/txn/transaction.js +75 -0
- package/dist/core/staging/txn/transaction.js.map +1 -0
- package/dist/core/staging/writes.js +113 -0
- package/dist/core/staging/writes.js.map +1 -0
- package/dist/core/status/actions/execution.js +49 -0
- package/dist/core/status/actions/execution.js.map +1 -0
- package/dist/core/status/contracts.js +102 -0
- package/dist/core/status/contracts.js.map +1 -0
- package/dist/core/status/feature/artifacts.js +171 -0
- package/dist/core/status/feature/artifacts.js.map +1 -0
- package/dist/core/status/feature/feature.js +207 -0
- package/dist/core/status/feature/feature.js.map +1 -0
- package/dist/core/status/feature/next.js +150 -0
- package/dist/core/status/feature/next.js.map +1 -0
- package/dist/core/status/feature/state.js +21 -0
- package/dist/core/status/feature/state.js.map +1 -0
- package/dist/core/status/fleet/fleet.js +187 -0
- package/dist/core/status/fleet/fleet.js.map +1 -0
- package/dist/core/status/fleet/next.js +207 -0
- package/dist/core/status/fleet/next.js.map +1 -0
- package/dist/core/status/interrupted.js +97 -0
- package/dist/core/status/interrupted.js.map +1 -0
- package/dist/core/status/report.js +31 -0
- package/dist/core/status/report.js.map +1 -0
- package/dist/core/status/scan.js +41 -0
- package/dist/core/status/scan.js.map +1 -0
- package/dist/core/status/verification.js +201 -0
- package/dist/core/status/verification.js.map +1 -0
- package/dist/core/usecases/capability.js +169 -0
- package/dist/core/usecases/capability.js.map +1 -0
- package/dist/core/usecases/delta/claims.js +116 -0
- package/dist/core/usecases/delta/claims.js.map +1 -0
- package/dist/core/usecases/delta/flows.js +103 -0
- package/dist/core/usecases/delta/flows.js.map +1 -0
- package/dist/core/usecases/delta/overlay.js +235 -0
- package/dist/core/usecases/delta/overlay.js.map +1 -0
- package/dist/core/usecases/fleet.js +137 -0
- package/dist/core/usecases/fleet.js.map +1 -0
- package/dist/core/usecases/operations.js +111 -0
- package/dist/core/usecases/operations.js.map +1 -0
- package/dist/core/usecases/place.js +44 -0
- package/dist/core/usecases/place.js.map +1 -0
- package/dist/core/usecases/touch.js +101 -0
- package/dist/core/usecases/touch.js.map +1 -0
- package/dist/core/verify/answers.js +173 -0
- package/dist/core/verify/answers.js.map +1 -0
- package/dist/core/verify/build.js +150 -0
- package/dist/core/verify/build.js.map +1 -0
- package/dist/core/verify/checklist.js +201 -0
- package/dist/core/verify/checklist.js.map +1 -0
- package/dist/core/verify/claims/identity.js +61 -0
- package/dist/core/verify/claims/identity.js.map +1 -0
- package/dist/core/verify/cross/diff.js +97 -0
- package/dist/core/verify/cross/diff.js.map +1 -0
- package/dist/core/verify/evidence/contract.js +169 -0
- package/dist/core/verify/evidence/contract.js.map +1 -0
- package/dist/core/verify/file.js +257 -0
- package/dist/core/verify/file.js.map +1 -0
- package/dist/core/verify/pins/lint.js +157 -0
- package/dist/core/verify/pins/lint.js.map +1 -0
- package/dist/core/verify/pins/pin.js +95 -0
- package/dist/core/verify/pins/pin.js.map +1 -0
- package/dist/core/verify/record.js +142 -0
- package/dist/core/verify/record.js.map +1 -0
- package/dist/core/verify/store/commit.js +89 -0
- package/dist/core/verify/store/commit.js.map +1 -0
- package/dist/core/verify/store/render.js +57 -0
- package/dist/core/verify/store/render.js.map +1 -0
- package/dist/core/vocabulary/health.js +91 -0
- package/dist/core/vocabulary/health.js.map +1 -0
- package/dist/core/vocabulary/issue.js +55 -0
- package/dist/core/vocabulary/issue.js.map +1 -0
- package/dist/core/vocabulary/maturity.js +145 -0
- package/dist/core/vocabulary/maturity.js.map +1 -0
- package/dist/core/{report.js → vocabulary/report.js} +30 -2
- package/dist/core/vocabulary/report.js.map +1 -0
- package/dist/core/vocabulary/steps.js +266 -0
- package/dist/core/vocabulary/steps.js.map +1 -0
- package/dist/core/workspace/discover.js +239 -0
- package/dist/core/workspace/discover.js.map +1 -0
- package/dist/core/workspace/render.js +52 -0
- package/dist/core/workspace/render.js.map +1 -0
- package/examples/README.md +189 -0
- package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
- package/examples/docs/architecture/capabilities.yaml +31 -0
- package/examples/docs/architecture/landscape.likec4 +280 -0
- package/examples/docs/architecture/obligations.yaml +23 -0
- package/examples/docs/architecture/permissions.yaml +50 -0
- package/examples/docs/architecture/subsystems.likec4 +11 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
- package/examples/docs/capabilities/checkout/spec.md +55 -0
- package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
- package/examples/docs/capabilities/order-notifications/spec.md +43 -0
- package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
- package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
- package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
- package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
- package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
- package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
- package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
- package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
- package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
- package/examples/docs/glossary/order.md +15 -0
- package/examples/docs/glossary/payments/authorization.md +14 -0
- package/examples/docs/likec4.config.json +9 -0
- package/examples/docs/loam.json +3 -0
- package/examples/docs/services/checkout-web/model.likec4 +109 -0
- package/examples/docs/services/checkout-web/spec.md +91 -0
- package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
- package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
- package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
- package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
- package/examples/docs/services/order-service/arch.spec.md +127 -0
- package/examples/docs/services/order-service/asyncapi.yaml +108 -0
- package/examples/docs/services/order-service/health.yaml +57 -0
- package/examples/docs/services/order-service/model.likec4 +138 -0
- package/examples/docs/services/order-service/openapi.yaml +257 -0
- package/examples/docs/services/order-service/runbook.md +62 -0
- package/examples/docs/services/order-service/spec.md +134 -0
- package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
- package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
- package/examples/docs/services/payment-service/arch.spec.md +73 -0
- package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
- package/examples/docs/services/payment-service/health.yaml +39 -0
- package/examples/docs/services/payment-service/model.likec4 +67 -0
- package/examples/docs/services/payment-service/openapi.yaml +151 -0
- package/examples/docs/services/payment-service/runbook.md +37 -0
- package/examples/docs/services/payment-service/spec.md +88 -0
- package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
- package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
- package/examples/docs/services/platform/identity-service/health.yaml +70 -0
- package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
- package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
- package/examples/docs/services/platform/identity-service/runbook.md +95 -0
- package/examples/docs/services/platform/identity-service/spec.md +119 -0
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
- package/examples/docs/services/platform/notification-service/health.yaml +35 -0
- package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
- package/examples/docs/services/platform/notification-service/runbook.md +48 -0
- package/examples/docs/services/platform/notification-service/spec.md +112 -0
- package/examples/docs/services/platform/subsystem.yaml +2 -0
- package/examples/fleet.yaml +57 -0
- package/package.json +19 -6
- package/dist/commands/adopt.js +0 -240
- package/dist/commands/adopt.js.map +0 -1
- package/dist/commands/archive.js +0 -1313
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/delta.js +0 -388
- package/dist/commands/delta.js.map +0 -1
- package/dist/commands/docs-repo-gate.js.map +0 -1
- package/dist/commands/format.js +0 -17
- package/dist/commands/format.js.map +0 -1
- package/dist/commands/gherkin.js +0 -309
- package/dist/commands/gherkin.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.js +0 -291
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/migrate-openspec.js +0 -950
- package/dist/commands/migrate-openspec.js.map +0 -1
- package/dist/commands/new.js +0 -413
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/rebase.js +0 -356
- package/dist/commands/rebase.js.map +0 -1
- package/dist/commands/show.js.map +0 -1
- package/dist/commands/status.js +0 -254
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/unarchive.js.map +0 -1
- package/dist/commands/validate.js +0 -1591
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/verify.js +0 -775
- package/dist/commands/verify.js.map +0 -1
- package/dist/commands/vouch.js +0 -343
- package/dist/commands/vouch.js.map +0 -1
- package/dist/core/agent.js +0 -2065
- package/dist/core/agent.js.map +0 -1
- package/dist/core/agents-stamp.js +0 -68
- package/dist/core/agents-stamp.js.map +0 -1
- package/dist/core/arch.js +0 -130
- package/dist/core/arch.js.map +0 -1
- package/dist/core/brief.js +0 -733
- package/dist/core/brief.js.map +0 -1
- package/dist/core/coherence.js +0 -577
- package/dist/core/coherence.js.map +0 -1
- package/dist/core/concurrency.js.map +0 -1
- package/dist/core/config.js.map +0 -1
- package/dist/core/delta.js +0 -476
- package/dist/core/delta.js.map +0 -1
- package/dist/core/dependencies.js +0 -356
- package/dist/core/dependencies.js.map +0 -1
- package/dist/core/doctor.js +0 -559
- package/dist/core/doctor.js.map +0 -1
- package/dist/core/document-bytes.js.map +0 -1
- package/dist/core/explore.js.map +0 -1
- package/dist/core/frontmatter.js.map +0 -1
- package/dist/core/gherkin.js +0 -463
- package/dist/core/gherkin.js.map +0 -1
- package/dist/core/health.js +0 -67
- package/dist/core/health.js.map +0 -1
- package/dist/core/ids.js +0 -122
- package/dist/core/ids.js.map +0 -1
- package/dist/core/issue.js +0 -9
- package/dist/core/issue.js.map +0 -1
- package/dist/core/json.js +0 -74
- package/dist/core/json.js.map +0 -1
- package/dist/core/likec4.js +0 -479
- package/dist/core/likec4.js.map +0 -1
- package/dist/core/maturity.js +0 -66
- package/dist/core/maturity.js.map +0 -1
- package/dist/core/openapi-merge.js +0 -552
- package/dist/core/openapi-merge.js.map +0 -1
- package/dist/core/openapi.js.map +0 -1
- package/dist/core/openspec-inventory.js +0 -1440
- package/dist/core/openspec-inventory.js.map +0 -1
- package/dist/core/path-safety.js.map +0 -1
- package/dist/core/provenance.js +0 -720
- package/dist/core/provenance.js.map +0 -1
- package/dist/core/records.js.map +0 -1
- package/dist/core/repo.js +0 -436
- package/dist/core/repo.js.map +0 -1
- package/dist/core/report.js.map +0 -1
- package/dist/core/spec.js +0 -481
- package/dist/core/spec.js.map +0 -1
- package/dist/core/staging.js +0 -890
- package/dist/core/staging.js.map +0 -1
- package/dist/core/status.js +0 -1104
- package/dist/core/status.js.map +0 -1
- package/dist/core/steps.js +0 -72
- package/dist/core/steps.js.map +0 -1
- package/dist/core/verify.js +0 -807
- package/dist/core/verify.js.map +0 -1
- package/dist/core/version.js.map +0 -1
- /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
- /package/dist/core/{records.js → kernel/records.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,128 +1,2632 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
All notable project changes are recorded here. The format follows Keep a Changelog, and release
|
|
3
|
+
All notable project changes are recorded here. The format follows Keep a Changelog, and release
|
|
4
|
+
versions follow Semantic Versioning.
|
|
4
5
|
|
|
5
6
|
## [Unreleased]
|
|
6
7
|
|
|
8
|
+
_Each entry states the change and names every code, flag, path and payload key it introduced. The
|
|
9
|
+
case for a change — the alternative that was rejected, the defect it came from, the rule it
|
|
10
|
+
generalises — lives where it is maintained: [SCHEMA.md](SCHEMA.md) for the rule,
|
|
11
|
+
[ROADMAP.md](ROADMAP.md) for the priority and its exit criteria, and the commit that landed it._
|
|
12
|
+
|
|
13
|
+
## [0.2.0-alpha.1] - 2026-09-01
|
|
14
|
+
|
|
15
|
+
The first release of the 0.2 line, and the whole accumulated body since `0.1.0-beta.3` — 116
|
|
16
|
+
entries, 53 of them additions. It opens a new line at `alpha` rather than continuing `0.1.0-beta.4`
|
|
17
|
+
because the surface moved that far: the use-case, business, glossary and obligations axes all
|
|
18
|
+
landed, and the agent interface became a dual-entry contract of generated commands and skills.
|
|
19
|
+
|
|
20
|
+
Still a prerelease, and the 5–10 service pilot still has not run. The entries below describe
|
|
21
|
+
implementation behavior and repository-owned synthetic fixtures; they claim no production-fleet or
|
|
22
|
+
external execution evidence.
|
|
23
|
+
|
|
24
|
+
**It publishes under the `alpha` npm dist-tag**, so `npm i @ybotok/loam@alpha` — or the exact
|
|
25
|
+
version — installs this one. `latest` is moved onto a release by hand once it has published, and
|
|
26
|
+
until it is, a plain `npm i @ybotok/loam` still resolves to `0.1.0-beta.3`. No
|
|
27
|
+
entry below is marked breaking, but a repository upgrading from beta.3 should read the `Changed`
|
|
28
|
+
sections first: the view doctrine narrowed, a use case became a consumer (`loam diff` refuses where
|
|
29
|
+
it warned), and the scaffolded `AGENTS.md` became a pointer. Every generated file stamped by an
|
|
30
|
+
earlier loam now reports against this binary — `doctor.agent-files-stale` for the command and skill
|
|
31
|
+
files, `agents.stale` for a docs repo's `AGENTS.md`. Both are detection, not damage: a file whose
|
|
32
|
+
recorded digest still matches is refreshed by re-running `loam init`, and a customized one stays
|
|
33
|
+
yours to review and re-stamp by hand.
|
|
34
|
+
|
|
35
|
+
### Security — the private reporting route is live, and every document says so
|
|
36
|
+
|
|
37
|
+
GitHub Private Vulnerability Reporting is enabled on `ybotok/loam`, verified from the repository's
|
|
38
|
+
PUBLIC advisories page — the same view an outside researcher gets — rather than taken on trust.
|
|
39
|
+
`SECURITY.md` now names [the advisory form](https://github.com/ybotok/loam/security/advisories/new),
|
|
40
|
+
says what to include and what to leave out, and keeps the one warning the old text was right about:
|
|
41
|
+
a GitHub issue, pull request or discussion is public the moment it is opened. The
|
|
42
|
+
`private-security-route` release blocker and the detail-free-issue fallback are gone — the fallback
|
|
43
|
+
existed only while the private form did not, and leaving it standing would offer a public route
|
|
44
|
+
beside a private one. `test/docs-drift.test.ts` inverts and keeps the blocker constant, so absence
|
|
45
|
+
is now the invariant.
|
|
46
|
+
|
|
47
|
+
### Added — the agent interface is progressive, executable and ownership-aware
|
|
48
|
+
|
|
49
|
+
- **`QUICKSTART.md` now ships in the npm package.** It gives the shortest complete path from
|
|
50
|
+
install through adoption and the first explicit agent-driven feature, with the host-specific
|
|
51
|
+
command spellings and the human-only vouch/archive boundaries in one place.
|
|
52
|
+
- **`loam-report` is a generated command and Agent Skill in every profile.** It collects an
|
|
53
|
+
unexpected binary or agent-integration run into
|
|
54
|
+
`loam-reports/YYYY-MM-DD-<short-slug>.md`: version, expected/actual behavior, stable codes and
|
|
55
|
+
locations, doctor/status and write-state evidence, minimal safe reproduction, classification and
|
|
56
|
+
missing evidence. The protocol redacts secrets and home paths, never retries a writer merely to
|
|
57
|
+
reproduce it, and never repairs, uploads or submits anything automatically. Bare
|
|
58
|
+
`loam instructions --json` adds the support entry under the additive `support` key; the six-item
|
|
59
|
+
`workflows` array remains the lifecycle contract.
|
|
60
|
+
- **The documented chat contract is explicitly dual-entry.** The generated command is the
|
|
61
|
+
recommended predictable path (`loam-feature` → `loam-implement` → `loam-check` → `loam-verify`
|
|
62
|
+
→ `loam-ship`), while natural language may load the matching Agent Skill. Both deliveries retain
|
|
63
|
+
one shared body and defer to the same binary-owned `loam instructions` page; no command or skill
|
|
64
|
+
name changed.
|
|
65
|
+
- **The always-loaded generated `AGENTS.md` is now 8,793 bytes**, measured on the file `loam init
|
|
66
|
+
--create` actually writes. Detailed workflow, authoring and
|
|
67
|
+
code material stays behind `loam instructions`; `loam-check` pointers start with
|
|
68
|
+
`--no-fix-tables` and fetch individual fixes through `loam explain <code>`.
|
|
69
|
+
- **`loam status --json` adds `next[].execution`.** It distinguishes runnable commands from edits,
|
|
70
|
+
external-repository work and human review; carries `cwd`; and never presents a command containing
|
|
71
|
+
unresolved `<placeholders>` as runnable. The original `command` field remains compatible.
|
|
72
|
+
- **Serialized findings add `locations[]`, and validate targets add `path`.** Producers may emit
|
|
73
|
+
exact file/line/pointer locations; otherwise the shared serializer supplies the narrowest proved
|
|
74
|
+
service, feature or landscape scope. Codes and message text are unchanged.
|
|
75
|
+
- **`loam show <FEAT> --json` adds `review`.** The pack joins the intent excerpt, tagged C4 objects,
|
|
76
|
+
API/event changes, dependencies, artifact and verification state, use cases, blockers, advisories
|
|
77
|
+
and executable next actions from the existing semantic projections.
|
|
78
|
+
- **`loam init` adds `--agent-profile <full|service|docs>` and safe refresh.** `loam.json` gains
|
|
79
|
+
additive `agentProfile` and `agentFiles` fields. An unchanged generated command/skill pointer is
|
|
80
|
+
refreshed when its recorded sha256 still matches; a customized file is never overwritten.
|
|
81
|
+
- **`loam mcp` adds resources, a common output schema and opt-in `--author`.** Orientation and every
|
|
82
|
+
workflow/support/reference page are readable at `loam://orientation` and
|
|
83
|
+
`loam://instructions/<name>`. `--author` adds `loam_new`, `loam_rebase`, `loam_gherkin` and
|
|
84
|
+
`loam_archive_plan` (server-enforced `--dry-run`); `vouch`, verification recording and a committing
|
|
85
|
+
archive remain excluded. `loam init --mcp-author` writes this launch mode.
|
|
86
|
+
|
|
87
|
+
### Changed — the product boundary is a governed system, not a microservice topology
|
|
88
|
+
|
|
89
|
+
- **The public position is now “architecture-first semantic integrity and change governance for
|
|
90
|
+
evolving software systems.”** README and npm metadata no longer define loam as a microservice-only
|
|
91
|
+
framework. Distributed service fleets remain the richest contract-spine case; modular monoliths
|
|
92
|
+
and hybrid systems take the same lifecycle without pretending their internal modules are network
|
|
93
|
+
services.
|
|
94
|
+
- **The existing `service` vocabulary is defined, not renamed.** In flags, paths, config and stable
|
|
95
|
+
codes it remains the frozen term for one governed implementation boundary. A modular monolith uses
|
|
96
|
+
one `services/<app>/` directory and one bound C4 root; nested modules participate in feature
|
|
97
|
+
deltas and `Covers:` while fleet maturity, dependency and scorecard views continue to aggregate at
|
|
98
|
+
the bound service level. No command, flag, path, envelope field or stable code changed.
|
|
99
|
+
- **README, WORKFLOW, SCHEMA, the ecosystem overview and the generated docs-repo README and agent
|
|
100
|
+
contract carry the same topology contract.** They distinguish modular, distributed and hybrid
|
|
101
|
+
shapes, show the nested C4 spelling, and state the current module-level reporting limit rather
|
|
102
|
+
than turning the broader position into a claim the binary does not make.
|
|
103
|
+
- **The ecosystem page no longer defines loam against OpenSpec.** It now gives short, non-adversarial
|
|
104
|
+
boundaries with architecture-as-code tools, catalogs, contract tooling, traceability systems and
|
|
105
|
+
code-level architecture checks. OpenSpec remains documented only where the product has a real
|
|
106
|
+
compatibility concern: the one-way migration guide and its pinned parser corpus.
|
|
107
|
+
|
|
108
|
+
### Fixed — a Windows checkout read the generated subsystem views file as stale
|
|
109
|
+
|
|
110
|
+
- **The generated `architecture/subsystems.likec4` is now compared by content, not by bytes, in all
|
|
111
|
+
four places that ask the question.** `renderSubsystemViews` only ever emits LF; `core.autocrlf=true`
|
|
112
|
+
is Git for Windows' installer default and a docs repo ships no `.gitattributes`, so an ordinary
|
|
113
|
+
Windows clone got the file back with CRLF and not one fact in it changed. Under a byte compare
|
|
114
|
+
`loam validate --all` reported one error, and the repair the finding advertises made it worse:
|
|
115
|
+
`loam subsystem sync` answered `updated`, rewrote with LF, git then showed the file permanently
|
|
116
|
+
modified, and the next checkout restored the CRLF — a loop with no stable exit. The rule now lives
|
|
117
|
+
once, as `viewsAgree` beside the generator that mints the bytes, and `loam validate`,
|
|
118
|
+
`loam seed`, `loam subsystem sync` and every subsystem transaction take it. The compare is still
|
|
119
|
+
never a parse, absent / must-be-absent / mismatched are still three distinct messages, and a real
|
|
120
|
+
content edit under CRLF is still `subsystem.views-stale`. Measured on this repository's own
|
|
121
|
+
`examples/docs`: 1 error to 0, with no other check moving.
|
|
122
|
+
- **`loam seed` and `loam subsystem move` no longer journal a no-op rewrite of that file** on such a
|
|
123
|
+
clone. What gets written when the file really has moved is unchanged.
|
|
124
|
+
|
|
125
|
+
### Fixed — `loam status`'s first rung sent the agent to the gate
|
|
126
|
+
|
|
127
|
+
- **`next.author-landscape` now carries the command its own statement names** — `loam init --create`
|
|
128
|
+
when `architecture/landscape.likec4` is absent, `loam seed --from fleet.yaml` when it is still the
|
|
129
|
+
scaffold's untouched stub — where both arms used to carry `loam validate --all --json`. Over a
|
|
130
|
+
repository with no services `validate --all` answers `ok: true` with `landscape.matched`, so an
|
|
131
|
+
agent running the documented loop (WORKFLOW.md: read `next[0]`, run the command it names, repeat)
|
|
132
|
+
changed nothing and read back the identical `next[0]` on every pass: a livelock on the first rung
|
|
133
|
+
of the first hour, on the one command built to answer "what now". No new `next.*` code and no new
|
|
134
|
+
payload key.
|
|
135
|
+
- **`next.fleet-gate`'s statement writes `` `loam validate --all` `` where it wrote
|
|
136
|
+
`` `validate --all` ``**, so the sentence is visible to the checks that read a backticked
|
|
137
|
+
`loam <verb>` span as an instruction rather than exempt from them.
|
|
138
|
+
|
|
139
|
+
### Fixed — `loam gherkin` and `loam vouch` said `invalid-option` when the flags were fine
|
|
140
|
+
|
|
141
|
+
- **A wrong-repo refusal now carries a wrong-repo code.** `loam gherkin --service <id>` and
|
|
142
|
+
`loam vouch --service <id>` run from the docs repo, or from a repo bound to a different service,
|
|
143
|
+
both refused under `invalid-option` — whose `loam explain` row says the invocation itself is
|
|
144
|
+
wrong, which was false for both and sent a user whose flags were correct off to re-read their
|
|
145
|
+
flags. Each now emits the pair `loam verify --record --service` has always emitted for the
|
|
146
|
+
identical condition: **`repository-unavailable`** when loam.json declares no `service` at all, and
|
|
147
|
+
**`service-mismatch`** when it declares a different one. Both human-facing messages are unchanged,
|
|
148
|
+
exit stays 1, the `--json` envelope is unchanged, and no code is new. Refusing with no service
|
|
149
|
+
named anywhere is still `invalid-option`; that one really is a bad invocation.
|
|
150
|
+
- **`loam explain service-mismatch` and `loam explain repository-unavailable` name all three
|
|
151
|
+
commands** that can now answer with them, where both rows described only `loam verify`.
|
|
152
|
+
- **The generated `AGENTS.md` lists the two codes on the `gherkin` and `vouch` rows.**
|
|
153
|
+
|
|
154
|
+
### Fixed — the example fleet could not be run
|
|
155
|
+
|
|
156
|
+
- **`examples/docs/` now carries its own `loam.json`**, holding the same `{ "docsDir": "." }` that
|
|
157
|
+
`loam init --docs . --create` writes for a docs repo that governs itself. `cd examples/docs &&
|
|
158
|
+
loam status` used to answer "No loam.json found" and exit 1, and the workaround both READMEs
|
|
159
|
+
printed — a throwaway `loam.json` at the loam repository root — then governed every directory
|
|
160
|
+
under it, so `loam init` from a sibling refused with "loam.json already governs this directory"
|
|
161
|
+
and forgetting to delete it silently redirected later commands at the example fleet.
|
|
162
|
+
- **`examples/README.md`'s runnable block is now `cd examples/docs` plus the installed binary**,
|
|
163
|
+
with no file to write first and none to delete afterwards, and it names the from-source
|
|
164
|
+
substitute (`npx tsx ../../src/cli.ts`) explicitly. `test/examples.test.ts` pins the file the
|
|
165
|
+
walkthrough depends on; neither archive dry-run plan enumerates it.
|
|
166
|
+
|
|
167
|
+
### Fixed — three README claims the tree had stopped supporting
|
|
168
|
+
|
|
169
|
+
- **The Windows caveat under "Explore the example fleet" is gone.** It advised a re-clone with
|
|
170
|
+
`core.autocrlf=false` — advice that never transferred to the reader's own docs repo, where the
|
|
171
|
+
same defect lived and no re-clone was on offer. The comparison is by content now, so the caveat is
|
|
172
|
+
simply false.
|
|
173
|
+
- **"Explore the example fleet" is `cd examples/docs` plus the installed binary**, the same commands
|
|
174
|
+
in the same order. ROADMAP's claim that the throwaway root config is the documented first
|
|
175
|
+
experience is dropped with it.
|
|
176
|
+
- **The commands table said `--errors-only` "prints just the errors". It never did.** The report
|
|
177
|
+
drops only the `ok` confirmations, so every warning survives the filter. The row now carries the
|
|
178
|
+
sentence the workflow protocol and the generated `AGENTS.md` already use, and
|
|
179
|
+
`test/agent-contract.test.ts` pins it against both the corrected wording and the old claim. No
|
|
180
|
+
flag changed and no behaviour moved.
|
|
181
|
+
|
|
182
|
+
### Added — three narrowing flags, because the agent-facing payloads had outgrown the work they describe
|
|
183
|
+
|
|
184
|
+
- **`loam instructions [<workflow>] --no-fix-tables`.** Every generated command and skill file says
|
|
185
|
+
"run this first" and points at `loam instructions loam-check`, which was **83,731 bytes** — about
|
|
186
|
+
21k tokens, 223 fix-table rows — with no way to narrow it. The flag collapses each
|
|
187
|
+
`| code | what it means | what to do |` block to one line naming how many rows went and how to get
|
|
188
|
+
them back, and the page becomes **3,541 bytes**; `--json` goes 84,201 to 3,769, the narrowed text
|
|
189
|
+
riding in the existing `body` field so no envelope key is added or renamed. `/loam-verify`'s
|
|
190
|
+
notice table strips through the same path (12,439 to 8,839); the four protocols carrying no table
|
|
191
|
+
come back untouched. **The paragraph that introduces each table stays** — it is what says which
|
|
192
|
+
scope graded a code, and `spec.merge-conflict` means different things in two of them. Off by
|
|
193
|
+
default, so all six protocols are byte-identical without it, asserted against the shipped bodies
|
|
194
|
+
rather than claimed.
|
|
195
|
+
- **The stripper shares the parser's block detection** (`withoutFixTables` beside `parseFixRows` in
|
|
196
|
+
`src/core/explain/fix-tables.ts`) rather than carrying a regex of its own. A stripper that
|
|
197
|
+
disagreed with the parser about where a table begins would ship a page that reads perfectly and
|
|
198
|
+
sends an agent to look up a code nothing explains; a test asserts every code the narrowing removes
|
|
199
|
+
still resolves through `explainSubject`.
|
|
200
|
+
- **A generated command and skill file now says how to READ the protocol, not only to run it** — add
|
|
201
|
+
`--no-fix-tables`, then `loam explain <code>` for each code the run actually reports. One
|
|
202
|
+
sentence; the stub stays under 2.3 KB. Existing generated files are never rewritten, so the clause
|
|
203
|
+
reaches repositories scaffolded from this release on.
|
|
204
|
+
- **`loam adopt --targets`.** The brief was **42,873 bytes** per service and had no narrowing flag,
|
|
205
|
+
while `walk`, `checks` and `unchecked` are the same bytes for every service — a twelve-service
|
|
206
|
+
adoption paid that invariant text twelve times. `--targets` emits only what varies by service and
|
|
207
|
+
comes back at **17,805 bytes**. It carries one required extra key, **`full`**, naming the run that
|
|
208
|
+
returns the rest, because a narrowing flag that made
|
|
209
|
+
[the fifteen statements of what nothing checks](https://github.com/ybotok/loam/blob/main/src/core/brief/unchecked.ts)
|
|
210
|
+
invisible would quietly undo the one promise the brief exists to keep. The default payload is
|
|
211
|
+
unchanged.
|
|
212
|
+
- **`loam explain --codes`.** The code vocabulary could not be enumerated through the machine
|
|
213
|
+
contract: `loam explain --json` with no subject returned six concept terms, and the ~100 issue
|
|
214
|
+
codes and 46 refusal codes were reachable only from TypeScript source or from a 223-row prose
|
|
215
|
+
table inside an 84 KB body. `--codes` lists **273 codes — 227 findings and 46 refusals** — each row
|
|
216
|
+
the same answer `explain <code> --json` already gives for that one, so there is no second shape to
|
|
217
|
+
keep in sync. No derived `severity` or `gates` field: those have one authority in
|
|
218
|
+
`core/vocabulary/issue.ts` and a second answer here could disagree with it. Like every other
|
|
219
|
+
`explain` form it reads no config, so it works in an unfamiliar directory before anything is
|
|
220
|
+
wired.
|
|
221
|
+
- **A named backlog rather than a silent gap.** The coverage test that holds `--codes` against every
|
|
222
|
+
stable code loam emits ships with an explicit list of the families that have no `explain` row yet —
|
|
223
|
+
`doctor.*`, `next.*`, `diff.*`, `gate.*` and the `openspec.*` / `mapping.*` migration surface —
|
|
224
|
+
with a written reason per family. It can only shrink.
|
|
225
|
+
|
|
226
|
+
### Added — `loam explain` answers the four families it has never been able to
|
|
227
|
+
|
|
228
|
+
- **`doctor.*`, `next.*`, `diff.*` and `gate.*` — 65 codes no fix table anywhere graded.**
|
|
229
|
+
`loam explain`'s finding content is parsed at runtime out of the `/loam-check` tables, and those
|
|
230
|
+
grade documents; these families grade wiring, a recommendation, a change between two refs and a
|
|
231
|
+
deploy question, so `explain` returned nothing for any of them. `loam explain <code>`,
|
|
232
|
+
`<code> --json` and `--codes` now answer them in the shape they already returned — no new key, no
|
|
233
|
+
second envelope. The listing goes from 274 codes to **339** (293 finding, 46 refusal).
|
|
234
|
+
- **A registry, and it is not the duplication `fix-tables.ts` argues against.** That argument is
|
|
235
|
+
about a second copy of prose the `/loam-check` bodies already ship, which would silently trail
|
|
236
|
+
them. These 65 have no row anywhere, so the registry is the only copy — the same shape, and the
|
|
237
|
+
same reason, as the sibling `refusals.ts`. Every meaning and fix is written from the emitter:
|
|
238
|
+
`doctor`'s own `fix` fields, each `next[]` step's `statement` and `command`, the `DiffCode` union's
|
|
239
|
+
doc comments. Where the "what to do" column is genuinely empty — a `next.*` step's action IS the
|
|
240
|
+
step — it says so instead of inventing a second instruction.
|
|
241
|
+
- **A test keeps the two sources disjoint**: the registry may not answer a code a fix table already
|
|
242
|
+
answers, so the parsed and hand-written halves can never drift into two answers a reader cannot
|
|
243
|
+
tell apart. The backlog of unanswerable codes shrinks from 116 to **51** — the OpenSpec migration
|
|
244
|
+
surface and two `ok`-severity confirmations, each with its written reason.
|
|
245
|
+
|
|
246
|
+
### Changed — the scaffolded `AGENTS.md` becomes a pointer, and the reference ships with the binary
|
|
247
|
+
|
|
248
|
+
- **109,399 B to 29,720 B (−73%), under the 32,768 bytes Codex truncates the AGENTS.md chain at for
|
|
249
|
+
the first time.** Every agents.md-aware host auto-loads that file from the working directory, every
|
|
250
|
+
session, and it is never regenerated — so for those hosts roughly 70% of it was being silently
|
|
251
|
+
dropped, with no error and no way for the reader to tell which half it got.
|
|
252
|
+
- **This is loam's own doctrine, finally applied to the file that never got it.** `scaffold.ts`
|
|
253
|
+
argues it for the command files in its own words — *"Thinning the file is the third option. What
|
|
254
|
+
remains is what does not move between releases"* — and README's "a generated file is a pointer,
|
|
255
|
+
not a protocol" makes the same case. Nothing new was decided here.
|
|
256
|
+
- **`loam instructions` now serves four reference pages beside the six workflow protocols**:
|
|
257
|
+
`loam-codes` (44,132 B — which codes each invocation can raise), `loam-spine` (15,590 — the ID
|
|
258
|
+
spine and the `Based-On:` pins), `loam-authoring` (12,021 — `arch.spec.md`, the generated Gherkin
|
|
259
|
+
suite, frontmatter) and `loam-done-check` (11,126 — how `loam verify` derives its claims, and
|
|
260
|
+
verified versus attested). Each takes no arguments and prints whole. Bare `loam instructions`
|
|
261
|
+
lists them under their own heading — **not** flattened in with the workflows, because a list of
|
|
262
|
+
ten names would read as a ten-step process, which is the one thing the six-step cycle must not be
|
|
263
|
+
confused with. `--json` carries them in an additive `references` key beside `workflows`.
|
|
264
|
+
- **`loam init` writes no new file for a page.** They feed `PROTOCOLS`, never `SLASH_COMMANDS` or
|
|
265
|
+
the scaffold's file list, so moving 44 KB out of `AGENTS.md` did not put it back as four new
|
|
266
|
+
artifacts in every repository loam touches. Printed by the binary, they describe the loam you are
|
|
267
|
+
about to run rather than the one that scaffolded the repository — which is strictly better than
|
|
268
|
+
what they replaced, since `AGENTS.md` is written once and never refreshed.
|
|
269
|
+
- **What stays is what a reader needs to form a question at all**: the layout, the cycle, which
|
|
270
|
+
element is which service, `loam.json`, linking, the glossary, obligations, the validator's rules,
|
|
271
|
+
the archive gate and its undo. A new `## The reference pages` section names each page with the
|
|
272
|
+
exact command that prints it, and the cycle's steps 4 and 7 name the command where they used to
|
|
273
|
+
name a heading that has left the file.
|
|
274
|
+
- **The move was verbatim, and that was checked rather than assumed**: zero headings and zero stable
|
|
275
|
+
codes lost, audited against the index. `codes-drift` is untouched and green by construction — the
|
|
276
|
+
codes moved from one half of the `AGENTS_MD + PROTOCOLS` corpus into the other.
|
|
277
|
+
- **Still over Windsurf's 12,000-character cap**, and no further move fixes that: what remains is
|
|
278
|
+
the teaching, not duplication.
|
|
279
|
+
|
|
280
|
+
### Changed — the scaffolded `AGENTS.md` stops storing the code vocabulary twice
|
|
281
|
+
|
|
282
|
+
- **124,587 B to 109,399 B (−12%).** `loam init` writes that file into the docs repo and every
|
|
283
|
+
agents.md-aware host auto-loads it from the working directory. Its `## Reading loam's output`
|
|
284
|
+
section is a per-invocation inventory — which codes each way of running loam can raise — and each
|
|
285
|
+
code carried a gloss of what it means. The `/loam-check` tables carry the same codes with
|
|
286
|
+
meaning-and-fix. **143 codes were explained twice, in two independently worded places, in a file
|
|
287
|
+
nothing regenerates** — so the two drift and nothing says which copy rotted.
|
|
288
|
+
- **The inventory stays; the gloss goes.** All 335 codes the file named are still named, still
|
|
289
|
+
backticked, still grouped by the invocation that raises them — that is unique content and
|
|
290
|
+
`codes-drift` reads this file as half its corpus. What a code MEANS now comes from
|
|
291
|
+
`loam explain <code>`, out of the running binary. `## Reading loam's output` falls from 59,621 B
|
|
292
|
+
to 44,433 B and every other section is byte-identical. A parenthetical that carried something
|
|
293
|
+
other than the meaning — a cross-reference, a severity that differs by scope — was kept.
|
|
294
|
+
- **The OpenSpec migration codes keep their notes inline**, because `loam explain` does not answer
|
|
295
|
+
them. **A test fails by name if any other code this file mentions cannot be explained**, so the
|
|
296
|
+
pointer can never outrun the catalogue.
|
|
297
|
+
- **A size assertion** now stands beside the file, because the number is load-bearing: Codex
|
|
298
|
+
truncates the AGENTS.md chain at 32,768 bytes by default and Windsurf caps a workspace rule file
|
|
299
|
+
at 12,000 characters, so a file over the cap loses its tail with no error at all. **This change
|
|
300
|
+
does not reach that cap and does not claim to** — the twenty-two remaining sections are 64,548 B
|
|
301
|
+
of teaching, not duplication, and cutting them is a separate decision about what a scaffolded
|
|
302
|
+
`AGENTS.md` is for.
|
|
303
|
+
|
|
304
|
+
### Added — `loam validate --all --base <ref>`, the ratchet a partly-adopted system had no setting for
|
|
305
|
+
|
|
306
|
+
- **Grade only the targets changed since a base git ref of the docs repo.** `--all` is green over
|
|
307
|
+
undocumented boundaries because warnings do not gate, and `--all --strict` is red from the first
|
|
308
|
+
minute of adoption until the last boundary is written — so a team could install a gate that never
|
|
309
|
+
notices adoption stalling at 8%, or one they could not turn on until the work everyone hoped CI
|
|
310
|
+
would motivate was already done. Measured on a twelve-boundary system with one documented:
|
|
311
|
+
`--all --strict` exits 1 on thirteen warnings, eleven of them on boundaries nobody touched;
|
|
312
|
+
`--all --base HEAD~1 --strict` exits 1 on two, both on the branch's own boundary.
|
|
313
|
+
- **Spelled `--base`, not `--since`** — already loam's word for "a base git ref of the docs repo" on
|
|
314
|
+
`loam diff` and `loam vouch --pack`. It requires `--all` and refuses `invalid-option` beside a
|
|
315
|
+
positional target, `--service` or `--feature`: two scopes is a contradiction, not a narrowing.
|
|
316
|
+
- **Changed paths map to targets through the service and feature enumerations, never a path split.**
|
|
317
|
+
A service filed under a subsystem does not live at `services/<id>/`, and a `split("/")` would
|
|
318
|
+
return the subsystem name, match no id, and print a green over nothing. Untracked files count:
|
|
319
|
+
`validate` grades the working tree, so a boundary adopted five minutes ago and not yet committed
|
|
320
|
+
is in scope.
|
|
321
|
+
- **The run says what it looked at, in both views.** The summary reads "N of M services, K of L
|
|
322
|
+
features in scope since `<ref>`", and the payload gains an additive `scope` object naming the base,
|
|
323
|
+
the resolved commit and the targets. **A run with zero targets in scope prints that it graded
|
|
324
|
+
nothing and exits 0 — never the bare word "valid".** The fleet scorecard is omitted from a scoped
|
|
325
|
+
run: its denominators are the graded targets, so over a narrowed set it would describe adoption the
|
|
326
|
+
run never measured.
|
|
327
|
+
- No new stable code: an unreachable repository or an unresolvable ref refuse with the codes
|
|
328
|
+
`loam diff` already uses.
|
|
329
|
+
- **The honest limit, stated in the docs beside the recipe:** a `--base`-scoped validate cannot see a
|
|
330
|
+
victim boundary the change did not touch. `loam diff --base` remains the gate for that.
|
|
331
|
+
|
|
332
|
+
### Added — one vendor-neutral skills root, and the machine surface wired at `init`
|
|
333
|
+
|
|
334
|
+
- **`loam init --tools` gains `agents`: the `.agents/skills/<name>/SKILL.md` root**, read by Cursor,
|
|
335
|
+
GitHub Copilot, Codex, Gemini CLI, Zed and Roo Code, each cited per its own documentation. Skills
|
|
336
|
+
only — none of the six documents a command file under that root, so none is written. Purely
|
|
337
|
+
additive: no existing tool's skill path moved. Detection is `.agents/skills`, never a bare
|
|
338
|
+
`.agents/` — the same rule that keeps a bare `.github/` from meaning Copilot.
|
|
339
|
+
- **`loam init --mcp` writes a repo-root `.mcp.json`** pointing an MCP host at `loam mcp` for this
|
|
340
|
+
repository. The MCP facade is the only typed, schema-carrying delivery loam has, and it was the
|
|
341
|
+
one that required a human to hand-edit JSON — so in practice scaffolded repositories got the prose
|
|
342
|
+
deliveries and never the machine one. Opt-in and default off: the file arranges for a process to be
|
|
343
|
+
launched on every machine that checks the repository out.
|
|
344
|
+
- **The launch form follows the install.** Where `node_modules/.bin/loam` exists — the per-repo
|
|
345
|
+
devDependency install the README documents — the entry is `npx --no loam mcp`; otherwise the bare
|
|
346
|
+
binary. A fixed `"loam"` would have written an unlaunchable entry into exactly those repositories.
|
|
347
|
+
- **Only the repo-root `.mcp.json`.** `.github/.mcp.json` is already a Copilot detection marker, so
|
|
348
|
+
writing it would make the next scan detect a tool loam itself installed; `.cursor/mcp.json` and
|
|
349
|
+
`.vscode/mcp.json` carry different schemas; `.gemini/settings.json` is a general settings file that
|
|
350
|
+
could only be merged into. An existing file is reported skipped and left byte for byte alone.
|
|
351
|
+
|
|
352
|
+
### Added — an evaluator can see loam working without modelling anything first
|
|
353
|
+
|
|
354
|
+
- **`loam init --example <dir>`** copies the packaged example fleet — five services, four features
|
|
355
|
+
at four points in their life, and its own `loam.json` — into a fresh directory through the same
|
|
356
|
+
journaled transaction every other loam writer uses, and prints the three commands that make the
|
|
357
|
+
point. It binds no repository, records no `docsDir` and generates no agent files, so it refuses
|
|
358
|
+
`invalid-option` beside `--docs`, `--service`, `--create` or `--tools`, and `already-exists` when
|
|
359
|
+
the target is occupied. No new stable code.
|
|
360
|
+
- **The `examples/` tree now ships in the published package.** `npm i -g @ybotok/loam` delivered no
|
|
361
|
+
example at all, so the first question an evaluating team asks — "show me a working one" — was
|
|
362
|
+
answered with "model your own boundaries first, then come back". The tarball grows by 89 files and
|
|
363
|
+
about 77 kB packed.
|
|
364
|
+
- **Fixed with it: the shipped-page link audit resolved relative targets against the package root
|
|
365
|
+
rather than against the page carrying them.** Every audited page sat at the root until `examples/`
|
|
366
|
+
shipped, so the two were the same string. For a page in a subdirectory they are not, and the audit
|
|
367
|
+
would have called correct links broken and missed broken ones.
|
|
368
|
+
|
|
369
|
+
### Added — `loam --help` says where to start, and what to run when lost
|
|
370
|
+
|
|
371
|
+
- **The help page ends with two lines: `loam init --create` for a first run, `loam doctor` for a
|
|
372
|
+
lost one.** The seven headings already put thirty commands in the order of the work, but nothing
|
|
373
|
+
on the page said that `init` must precede all of them or which command answers "what now" — the
|
|
374
|
+
last thing a newcomer read was `help [command]`. Both lines name a command that parses:
|
|
375
|
+
`test/agent-commands-runnable.test.ts` takes `HELP_EPILOG` by import and hands both invocations to
|
|
376
|
+
the real program, exactly as it takes the `init` first-hour lines.
|
|
377
|
+
|
|
378
|
+
### Changed — `loam explain` is reachable from the message that needs it
|
|
379
|
+
|
|
380
|
+
- **Every text-mode refusal now prints its stable code and the lookup on stderr**, as one line after
|
|
381
|
+
the message: `<code> · loam explain <code>`. Measured over eight wrong first-hour invocations,
|
|
382
|
+
all eight printed prose and no code, and only `loam validate` mentioned `loam explain` at all — so
|
|
383
|
+
reaching an explanation meant re-running the whole command with `--json` and reading `error.code`,
|
|
384
|
+
which is the knowledge a new user does not yet have. `vouch-declined` is the one refusal that
|
|
385
|
+
stays silent: loam asked, a person said no, and there is nothing to look up.
|
|
386
|
+
- **`loam validate`'s text report carries each finding's code**, appended to every line whose
|
|
387
|
+
severity is not `ok`; the `ok` confirmations stay clean, so a mostly-green report keeps its
|
|
388
|
+
contrast. A code is printed only where `loam explain` can answer it — a pointer to an explanation
|
|
389
|
+
that does not exist is worse than none. `gherkin.path-outside` gained the fix-table row it had
|
|
390
|
+
been missing, which is what made that true rather than nearly true.
|
|
391
|
+
- **The explain footer was reworded to match**: the old "codes ride in `--json`" clause existed only
|
|
392
|
+
because no code was ever on screen.
|
|
393
|
+
- **`--json` stdout is byte-identical on every path**: the refusal pointer is a stderr line, and the
|
|
394
|
+
finding codes are the ones the envelope already carried.
|
|
395
|
+
|
|
396
|
+
### Fixed — `loam adopt`'s human view printed 650 lines with nothing telling a reader what they were
|
|
397
|
+
|
|
398
|
+
- **The default view opens with a three-line orientation block**: how many briefed artifacts are
|
|
399
|
+
required and still outstanding and which, a decode of the artifact table's flags (`MISSING` in
|
|
400
|
+
capitals is required and absent, lowercase `missing` optional, `present` means diff it, `UNDRAWN`
|
|
401
|
+
is the shared map this boundary owes an element), and what the remaining four hundred lines are.
|
|
402
|
+
This is step 1 of the five-minute trial, and the two rows that orient a person landed around line
|
|
403
|
+
470 of 646.
|
|
404
|
+
- **The outstanding count includes the fleet-map edit, which `required && !exists` misses.**
|
|
405
|
+
`architecture/landscape.likec4` exists for every docs repo after its first service, so the file is
|
|
406
|
+
present while nothing in it resolves to the new boundary — an error the moment `validate --all`
|
|
407
|
+
runs. A fresh boundary is briefed as three artifacts owed, not two.
|
|
408
|
+
- Every number is computed from the brief just assembled. No new flag, nothing moved behind one, and
|
|
409
|
+
no change to `--json` or to `--targets`.
|
|
410
|
+
|
|
411
|
+
### Changed — `loam status` leads with the boundary you are standing in
|
|
412
|
+
|
|
413
|
+
- **The fleet form now leads its `next[]` with the service this repository's `loam.json` binds.**
|
|
414
|
+
The binding was visible to the worklist only while that service was *un*adopted; once adopted, the
|
|
415
|
+
repository you are standing in became invisible again and the list reverted to every service,
|
|
416
|
+
alphabetically. In a ten-repo system that opened every developer's `status` with nine other
|
|
417
|
+
boundaries' adoption work, and past ten services the ten-entry cap elided their own step entirely
|
|
418
|
+
— so working down the list as documented never reached it.
|
|
419
|
+
- **A stable partition applied before the cap, not a sort**: the bound service's steps move to the
|
|
420
|
+
front and every other service keeps the order it had, so an unbound reader sees exactly the list
|
|
421
|
+
it saw before. `--service <id>` is unaffected — that flag is an explicit question about one
|
|
422
|
+
service. No `next.*` code added, removed or changed.
|
|
423
|
+
|
|
424
|
+
### Added — a `--json` envelope now says which loam wrote it, and which command answered
|
|
425
|
+
|
|
426
|
+
- **Every envelope carries `version`, the binary's own, beside `contractVersion`.** The two are
|
|
427
|
+
separate on purpose: `contractVersion` versions the SHAPE, `version` versions the BUILD, so a
|
|
428
|
+
release never reads as a contract break. Only `instructions` and `explain` said which loam
|
|
429
|
+
produced their output; `validate --all`, `status`, `list`, `context` and `gate` did not — so a
|
|
430
|
+
consumer holding a payload could not apply the caution `docs.binary-behind` exists to express, and
|
|
431
|
+
could not detect a mixed-version system from the contract at all. It is stamped by the two
|
|
432
|
+
emitters in `src/core/envelope/json.ts`, not by each command, which is why no command can be added
|
|
433
|
+
without it. **`version` is on the refusal envelope too**: a refusal is what a caller is most
|
|
434
|
+
likely to be holding when the build is the question.
|
|
435
|
+
- **Every success payload now carries `command`, the CLI command name.** `adopt`, `archive`,
|
|
436
|
+
`delta`, `gherkin`, `init`, `list`, `new`, `rebase`, `seed`, `show`, `subsystem`, `unarchive`,
|
|
437
|
+
`validate`, `verify` and `vouch`'s stamp payload gained it, so an MCP host multiplexing tool
|
|
438
|
+
results branches on one field instead of sniffing for the presence of `valid` or `services`. All
|
|
439
|
+
seven `subsystem` verbs emit `command: "subsystem"` — it is one registered command with a verb
|
|
440
|
+
positional. **A refusal deliberately carries no `command`, and a test now says so**: the error
|
|
441
|
+
emitter has no command in scope, and threading one would cost module-level mutable state or an
|
|
442
|
+
extra argument through every deep caller of `fail()`.
|
|
443
|
+
- Every key here is additive: no existing key changed name, type, value or relative order.
|
|
444
|
+
|
|
445
|
+
### Added — `loam validate --json` now says what to do about the codes it raised
|
|
446
|
+
|
|
447
|
+
- **The payload carries `fixes`, a top-level map from each raised code to its fix.**
|
|
448
|
+
`loam validate --all --json` is the command every generated protocol names at step 1, and it was
|
|
449
|
+
the one that answered "what do I do next" in prose only. An agent holding the payload either had
|
|
450
|
+
the fix tables in context already or spawned a separate `loam explain` process per code — having
|
|
451
|
+
first guessed that it could.
|
|
452
|
+
- **A map keyed by code, not a field on each finding** — the argument `report.ts` already makes
|
|
453
|
+
about its `DETAIL_LIMIT`: ten services raising `sources.absent` produce ONE entry, and the whole
|
|
454
|
+
map costs 398 bytes there. On `examples/docs`, 51 findings become 9 entries and the payload grows
|
|
455
|
+
16,433 → 17,731 bytes.
|
|
456
|
+
- **Only codes a non-`ok` finding raised**, **resolved through the same function
|
|
457
|
+
`loam explain <code>` calls** so the two surfaces cannot disagree (a test asserts every value
|
|
458
|
+
equals what `explain --json` returns), and **a code no fix table grades is absent, not a
|
|
459
|
+
placeholder** — the named backlog in `test/explain.test.ts` is where that gap is supposed to itch.
|
|
460
|
+
Nine codes that two tables grade differently carry both, each prefixed by its scope.
|
|
461
|
+
- Not behind a flag: a flag would mean an agent has to already know the fixes exist in order to ask
|
|
462
|
+
for them, which is the gap being closed. `{}` on a green run, so no consumer branches on the key.
|
|
463
|
+
|
|
464
|
+
### Added — `loam mcp`: read-only annotations, and `instructions` + `steps` as tools
|
|
465
|
+
|
|
466
|
+
- **Every MCP tool advertises `annotations: { readOnlyHint: true, openWorldHint: false }`.**
|
|
467
|
+
`tools/list` emitted exactly `{name, description, inputSchema}`, so a host had no machine signal
|
|
468
|
+
that `loam_validate` or `loam_status` only reads files, and every call fell into the same approval
|
|
469
|
+
path as a mutating tool — which is the friction the read-only-only table was built to avoid.
|
|
470
|
+
`idempotentHint` and `destructiveHint` are deliberately absent: MCP defines both as meaningful
|
|
471
|
+
only when `readOnlyHint` is false. `outputSchema` was considered and deferred, with the reason
|
|
472
|
+
recorded beside the annotations.
|
|
473
|
+
- **The read-only claim is derived, not attested.** `test/mcp-protocol.test.ts` computes the
|
|
474
|
+
transitive value-import closure of every served command from `scripts/source-graph.mjs` and fails
|
|
475
|
+
if one reaches a module that commits. Adding a writer to the table now goes red instead of
|
|
476
|
+
shipping a hint that tells every host it is safe to auto-approve; a negative control asserts nine
|
|
477
|
+
known writers still DO reach that seam, so the guard cannot pass for everything if the seam moves.
|
|
478
|
+
- **`loam_instructions` and `loam_steps` join the table — twelve read tools become fourteen.** Both
|
|
479
|
+
read nothing and write nothing, and both were unreachable over MCP, so an MCP-only host could not
|
|
480
|
+
fetch the protocol its own generated skill points at. `--no-fix-tables` rides as the JSON property
|
|
481
|
+
`noFixTables`, named for what setting it true DOES, because commander's own attribute for a
|
|
482
|
+
negated option is `fixTables` and the two would otherwise mean opposite things.
|
|
483
|
+
- **`tools/call` can actually run them, and a test now proves that for every advertised tool.** The
|
|
484
|
+
dispatcher builds its own program from a second, hand-written roster — `core/` may not import
|
|
485
|
+
`commands/`, so the two lists cannot be one — and nothing counted them against each other. Landing
|
|
486
|
+
the tools without the dispatcher made `tools/list` promise fourteen while two of them answered
|
|
487
|
+
`{ ok: false, error: { code: "invalid-option", message: "unknown command 'instructions'" } }`, an
|
|
488
|
+
envelope that reads as the CALLER's mistake. `test/mcp-serve.test.ts` now calls every tool in
|
|
489
|
+
`MCP_TOOLS` and fails on `unknown command`.
|
|
490
|
+
|
|
491
|
+
### Fixed — the generated allowlist stopped short of the verbs the protocols instruct
|
|
492
|
+
|
|
493
|
+
- **`allowed-tools:` in every generated skill file now names 23 of loam's 29 verbs, and the six it
|
|
494
|
+
omits carry a written reason.** It named 17, and four of the missing were verbs loam's own
|
|
495
|
+
protocols call mandatory: `/loam-implement` opens with `loam context` and requires `loam steps`,
|
|
496
|
+
`/loam-check`'s fix table says to run `loam subsystem sync`, and every generated file now tells
|
|
497
|
+
the agent to read the protocol narrow and run `loam explain <code>` per reported code. Each
|
|
498
|
+
stalled on a permission prompt at exactly the step its own protocol calls required — worst for
|
|
499
|
+
`loam explain`, the documented escape from an 84 KB page. Added: `context`, `diff`, `explain`,
|
|
500
|
+
`gate`, `steps`, `subsystem`.
|
|
501
|
+
- **The rule is now "every verb the CLI registers except the named exclusions", and it admits no
|
|
502
|
+
silent third category.** `UNAPPROVED` maps each excluded verb to its reason — `vouch` (an agent
|
|
503
|
+
must not be able to say a spec matches the code), `mcp` (a long-running server, not a call that
|
|
504
|
+
returns), `seed` and `open` (a human's statement and a human's editor), `audit-openspec` and
|
|
505
|
+
`migrate-openspec` (a one-time on-ramp against a foreign workspace). Still an enumeration, never
|
|
506
|
+
`Bash(loam:*)`: the wildcard is what would let an agent promote its own draft to `verified`.
|
|
507
|
+
- **`test/agents.test.ts` grades both halves against the binary** instead of spot-checking six
|
|
508
|
+
verbs: one test requires every backticked `loam <verb>` the protocols teach to be pre-approved or
|
|
509
|
+
named in `UNAPPROVED`; the other asserts the emitted list equals the registered commands minus
|
|
510
|
+
`UNAPPROVED`'s keys.
|
|
511
|
+
|
|
512
|
+
### Fixed — a tool that does not substitute `$1` got a refusal instead of the protocol
|
|
513
|
+
|
|
514
|
+
- **`loam instructions <workflow> $1` prints the protocol instead of refusing.** Every generated
|
|
515
|
+
command file carries Claude's positional `$1`/`$2` spelling into all twenty dialects, and Cline,
|
|
516
|
+
Kilo Code and Roo Code substitute nothing — so those three sent loam the literal `$1` and got
|
|
517
|
+
`invalid-option` on the first instruction of the workflow. A literal `$1`…`$9` now reads as an
|
|
518
|
+
unsupplied argument and the placeholder is left standing, exactly as passing no argument does. An
|
|
519
|
+
argument that merely contains one (`FEAT-$1`) is still checked and still refused.
|
|
520
|
+
|
|
521
|
+
### Fixed — `loam delta` printed `Covers:` twice
|
|
522
|
+
|
|
523
|
+
- **The text briefing printed each requirement's body verbatim and then re-printed `Operations:`
|
|
524
|
+
and `Covers:` from the parsed fields**, so a requirement carrying either line showed it twice
|
|
525
|
+
while the five other directives showed it once. A directive line is a body line by design — it
|
|
526
|
+
stays in the requirement's text so the document round-trips and its digest is stable — so the
|
|
527
|
+
re-print was always redundant. The duplicated lines are gone; `--json` is unchanged, and so is
|
|
528
|
+
every other command. Found by running `loam delta` over loam's own self-model.
|
|
529
|
+
|
|
530
|
+
### Added — a feature can bring a use case
|
|
531
|
+
|
|
532
|
+
- **New feature-delta slot: `features/<FEAT>/usecases/<name>.likec4`.** A views-only LikeC4 document
|
|
533
|
+
holding the `dynamic view`s a change brings with it. `loam archive` copies each one into
|
|
534
|
+
`architecture/usecases/<name>.likec4` and `loam unarchive` removes it again, through the same
|
|
535
|
+
transaction and the same `.loam-before/` snapshot as every other axis. The route is CREATE-ONLY:
|
|
536
|
+
to change a living flow, edit `architecture/usecases/<name>.likec4` directly in the same pull
|
|
537
|
+
request.
|
|
538
|
+
- **New issue code `usecase.flow-exists`** (error, archive-gating, **not** `--approve`-overridable):
|
|
539
|
+
the feature introduces a flow the living `architecture/usecases/` already holds, and the merge is a
|
|
540
|
+
whole-file copy that would replace an authored hop sequence wholesale. The same shape and severity
|
|
541
|
+
as `glossary.term-exists`.
|
|
542
|
+
- **New issue code `usecase.flow-invalid`** (error, archive-gating, **not** `--approve`-overridable):
|
|
543
|
+
the feature's flows could not be read against the map its own merge would leave behind — a parse
|
|
544
|
+
error, a hop naming an element the merge does not land, or a landscape merge that itself refuses.
|
|
545
|
+
Nothing is written; the archive stops at plan time rather than copying a flow into `architecture/`
|
|
546
|
+
for the next reader's `loam validate --all` to fail on.
|
|
547
|
+
- **`capability.uncovered` now counts a feature-local flow as cover.** A `dynamic view` under the
|
|
548
|
+
feature's own `usecases/` whose `#cap-` and `#req-` tags both resolve keeps the promise, resolving
|
|
549
|
+
against the capability vocabulary and requirement ids the same feature's merge would leave behind —
|
|
550
|
+
the both-corpora rule `Realizes:` already follows. Only a resolved claim counts, so a broken tag
|
|
551
|
+
still earns its own error and silences nothing. **Its message changed**: it now names
|
|
552
|
+
`features/<FEAT>/usecases/<name>.likec4` and the two tags to write, where it used to say the flow
|
|
553
|
+
route opened only after the promise was living. When the flow half could not be evaluated — the
|
|
554
|
+
flows unreadable, or `architecture/capabilities.yaml` not readable as a vocabulary so no `#cap-`
|
|
555
|
+
tag resolves — the message says so and makes no claim about the flows, rather than reporting that
|
|
556
|
+
none of them keeps the promise. The `Realizes:` half is still checked and still gates.
|
|
557
|
+
- **`delta.likec4` still refuses a `dynamic view`, and now says where one goes.** The refusal is
|
|
558
|
+
unchanged and mechanical — that document re-declares the landscape's identifiers and carries its
|
|
559
|
+
own `specification` block, so it cannot be read beside the map in one LikeC4 project — but its
|
|
560
|
+
message names the new slot instead of sending the author to `architecture/landscape.likec4`. A
|
|
561
|
+
`deployment view` still goes to the living map.
|
|
562
|
+
- **New path in the layout: `features/<FEAT>/usecases/`** (`SCHEMA.md`, the shipped `AGENTS.md`).
|
|
563
|
+
|
|
564
|
+
### Added — one predicate decides what a use case is
|
|
565
|
+
|
|
566
|
+
- **A `dynamic view` tagged only `#req-<slug>` is now visible to every reader**, not just to
|
|
567
|
+
`loam validate --all`. `loam diff`'s victim walk, `loam delta` and `loam status`'s blast radius,
|
|
568
|
+
the context and explore packs and `loam list capabilities` all take the same opt-in, which is
|
|
569
|
+
exported as `isUseCase`; no caller spells the prefix test itself any more. The cheap byte scan that
|
|
570
|
+
lets a fleet with no use cases skip the LikeC4 load was widened with it — a fleet whose only flow
|
|
571
|
+
was `#req-`-tagged declared `tag req-…` and no `cap-` anywhere, so the scan answered "no use cases"
|
|
572
|
+
and nothing loaded the project at all.
|
|
573
|
+
- No code, flag, exit code or payload key changed. A fleet whose flows all carry `#cap-` sees no
|
|
574
|
+
difference; a fleet with a `#req-`-only flow sees it appear in reports that previously omitted it.
|
|
575
|
+
|
|
576
|
+
### Added — the step catalogue
|
|
577
|
+
|
|
578
|
+
- **New authored document: `services/<svc>/steps.yaml`** — a `steps:` list of step TEXTS recording
|
|
579
|
+
which phrases a team has decided its suite defines. Optional; the file's existence is the opt-in.
|
|
580
|
+
- **`loam steps` reports written phrases against catalogued ones.** New additive `--json` key
|
|
581
|
+
`catalogue` carrying `present`, `path`, `entries`, and — when the file exists and parses —
|
|
582
|
+
`uncatalogued` (written, not catalogued: the work-list, in the inventory's own order), `unwritten`
|
|
583
|
+
(catalogued, nothing writes it) and `duplicated` (two entries collapsing onto one phrase). An
|
|
584
|
+
absent catalogue and an unreadable one are different answers and neither reports a work-list:
|
|
585
|
+
`unreadable` carries the reason instead. Existing keys are untouched.
|
|
586
|
+
- **No new stable code, and `loam validate` does not read the catalogue.** A phrase written before
|
|
587
|
+
its glue is the normal order of work; the near-duplicate groups stay a report, and no
|
|
588
|
+
phrase-similarity finding ships. The exit code is unchanged in every case — `loam steps` remains a
|
|
589
|
+
read.
|
|
590
|
+
|
|
591
|
+
### Fixed — a slot-less AsyncAPI delta merges instead of archiving silently
|
|
592
|
+
|
|
593
|
+
- **A feature `asyncapi.yaml` that declares component surfaces and no channel, operation or message
|
|
594
|
+
slot now merges them.** It archived at exit 0 having written nothing, and unlike its OpenAPI twin
|
|
595
|
+
the early return was the symptom rather than the cause: the only write loop ran over the slots, so there was no writer for anything else, no enumeration of what lay outside the three slot
|
|
596
|
+
sections, and no pin that could reach a `components.schemas` value — the event pin is written INTO
|
|
597
|
+
a slot value, which works because every slot value is a mapping and a JSON Schema is not.
|
|
598
|
+
- **`loam rebase` writes a root `x-loam-baselines` record into a feature `asyncapi.yaml`**, pinning
|
|
599
|
+
each component surface to the digest of the living value, ordered after the slot pins so a surface
|
|
600
|
+
digest never contains a slot pin and the record never enters a slot digest.
|
|
601
|
+
`stripAsyncapiMarkers` removes that record on every branch, including the early one — otherwise
|
|
602
|
+
the archive's create path published it into the living contract and nothing caught it, because the
|
|
603
|
+
removal-marker walk only looks for `x-loam-remove`.
|
|
604
|
+
- **New code `asyncapi.component-modified`** (warn, archive plan), the event-axis sibling of
|
|
605
|
+
`openapi.component-modified`. `asyncapi.baseline-missing`, `-stale` and `-invalid` now grade
|
|
606
|
+
component surfaces as well as slots, with unpinned surfaces folded into the SAME per-service
|
|
607
|
+
counter rather than a second warning.
|
|
608
|
+
- **`asyncapi.ref-unresolved` no longer fires for a target the delta itself declares.** A feature
|
|
609
|
+
that adds `components.messages.Refunded` alongside the `components.schemas.RefundPayload` its
|
|
610
|
+
payload references now merges both and resolves; one whose target nothing anywhere defines still
|
|
611
|
+
gates, and that case is the negative control in the suite.
|
|
612
|
+
|
|
613
|
+
### loam models its own architecture in loam
|
|
614
|
+
|
|
615
|
+
- **`meta/docs/` is a docs repo describing loam itself.** Its landscape holds 70 containers — the
|
|
616
|
+
top-level subjects of `src/` — and 403 relationships, the real value imports;
|
|
617
|
+
`services/loam/arch.spec.md` carries fourteen of `docs/DESIGN.md`'s numbered rules as requirements
|
|
618
|
+
with `Covers:` lines onto real model objects, and opens by naming the twelve it cannot cover,
|
|
619
|
+
because `Covers:` can only point at an object that EXISTS and roughly a third of loam's own rules
|
|
620
|
+
prohibit edges that do not.
|
|
621
|
+
- **`npm run meta:check` (`scripts/self-model.mjs`) runs in CI beside `npm run arch:check`.** It
|
|
622
|
+
fails on a package with no box, a box with no package, an import with no relationship, a
|
|
623
|
+
relationship with no import, and a mutual dependency between top-level subjects outside its named
|
|
624
|
+
baseline. It prints the fix and never applies it: loam reads no source tree, by standing non-goal,
|
|
625
|
+
so this script CONVICTS the model and does not generate it. `scripts/source-graph.mjs` is the one
|
|
626
|
+
definition of an edge, shared with `scripts/package-graph.mjs` so `import type` cannot be exempt
|
|
627
|
+
on one side and not the other.
|
|
628
|
+
- **`.gitignore`'s `loam.json` is anchored to `/loam.json`.** Unanchored, it ignored a file of that
|
|
629
|
+
name at any depth — which is why a self-model directory could not be committed at all. The
|
|
630
|
+
throwaway root config the Quick start writes is still ignored; a committed one in a subdirectory
|
|
631
|
+
is not.
|
|
632
|
+
- No command, flag, exit code, envelope field or stable code changed.
|
|
633
|
+
|
|
634
|
+
### Fixed — `docs/DESIGN.md` had stopped describing the tree, and its pin had never fired
|
|
635
|
+
|
|
636
|
+
- **The package-layout table had no row for nine `core/` packages** (`brief`, `explain`, `glossary`,
|
|
637
|
+
`links`, `obligations`, `owners`, `provenance`, `scaffold`, `usecases`) and a false `Depends on`
|
|
638
|
+
cell in eleven more — `core/c4/` claimed to depend on nothing while importing `repo` and
|
|
639
|
+
`kernel`. The column is recomputed from the derived graph and now has a machine-checked
|
|
640
|
+
counterpart. The DAG-levels table placed `verify/` below `gherkin/`, which it imports, and the
|
|
641
|
+
"7-level DAG" is ten levels per package and eleven per file.
|
|
642
|
+
- **DESIGN said loam makes 27 `register*` calls producing 28 commands**; it is 28 and 29, and the
|
|
643
|
+
page contradicted itself two rows later. **The pin that should have caught it had never matched**:
|
|
644
|
+
it required a literal space before `commands` and the page wraps at exactly that point. That loop
|
|
645
|
+
in `test/docs-facts.test.ts` now runs over the flattened text — every other pattern in it had the
|
|
646
|
+
same hole — and a new assertion requires the sentence to be PRESENT before anything grades it. A
|
|
647
|
+
pin that can pass vacuously is worse than none, which is the doctrine that file already states.
|
|
648
|
+
- **"Every edge points up the row order" is now qualified**, because it was false at a granularity
|
|
649
|
+
nothing checked: `core/c4` ↔ `core/repo` and `core/repo` ↔ `core/provenance` are mutual, and
|
|
650
|
+
`scripts/package-graph.mjs` cannot see either — it keys on the full relative directory, so
|
|
651
|
+
`core/c4/project` and `core/c4` are different nodes. Both are held in an `ACCEPTED_CYCLES`
|
|
652
|
+
baseline that may only shrink; breaking them is a hub move and a follow-up.
|
|
653
|
+
|
|
654
|
+
### Fixed — a components-only OpenAPI delta merges instead of archiving silently
|
|
655
|
+
|
|
656
|
+
- **A feature `openapi.yaml` that declares `components` and no `paths` now merges those components
|
|
657
|
+
into the living contract.** It used to archive at exit 0 having written nothing: `mergeOpenapiPaths`
|
|
658
|
+
answered no-op on the absence of `paths` before it had even parsed the living document, and the
|
|
659
|
+
component closure is the last thing the merge runs — so components merged only as a side effect of
|
|
660
|
+
merging a path. The no-op now consults the surface enumeration the baseline gate and `loam rebase`
|
|
661
|
+
already share, so a contract delta whose entire content is a component is a merge, not silence.
|
|
662
|
+
- **A genuinely new component in such a delta is promoted before the ref fixpoint is seeded**, not
|
|
663
|
+
after, so its own `$ref`s ride the same sweep: a components-only delta pointing at a schema neither
|
|
664
|
+
document defines still gates `openapi.ref-unresolved`, naming `components/<kind>/<name>` as the
|
|
665
|
+
source. Reachability still decides the new-component question whenever the delta merges paths at
|
|
666
|
+
all — an unreferenced schema riding in beside a path change stays behind, as before.
|
|
667
|
+
- The archive plan names what it merged (`openapi: <svc> — merged (component schemas/Split)`) and no
|
|
668
|
+
longer prints an empty `merged ()`. No command, flag, exit code, envelope key or stable code
|
|
669
|
+
changed.
|
|
670
|
+
|
|
671
|
+
### The two-fleet pilot is removed
|
|
672
|
+
|
|
673
|
+
- **Removed `docs/pilot/`, `scripts/pilot-harness.mjs`, the `pilot:run` and `pilot:check` npm
|
|
674
|
+
scripts, and the CI step that ran them.** The pilot was a plan for evidence, not evidence, and it
|
|
675
|
+
had become the one `## Now` item that no work inside this repository could advance. Nothing in
|
|
676
|
+
the shipped binary referenced it: no command, flag, code or payload key changes, and no page loam
|
|
677
|
+
writes into a docs repo mentioned it.
|
|
678
|
+
- **`docs/pilot/RELEASE-READINESS.md` moved to `docs/RELEASE-READINESS.md`.** It is a release
|
|
679
|
+
document — npm trusted publishing, tag discipline, provenance, post-publication verification — and
|
|
680
|
+
it outlives the pilot that happened to file it. Its one pilot-specific checklist item is now the
|
|
681
|
+
security review alone.
|
|
682
|
+
|
|
683
|
+
### `loam steps` — how many step definitions a suite actually needs
|
|
684
|
+
|
|
685
|
+
- **New read-only command `loam steps`** (`--service <id>`, `--duplicates`, `--json`). It collapses
|
|
686
|
+
every written step in a service's living `spec.md` and `arch.spec.md` onto the phrase a step
|
|
687
|
+
definition matches, and reports each phrase with its count, the keywords it appears under and the
|
|
688
|
+
requirements it comes from — plus the two numbers a team plans from: how many steps are written,
|
|
689
|
+
and how few phrases cover 80% of them.
|
|
690
|
+
- **Why it is a command and not a check.** The glue layer is where a generated suite dies: the step
|
|
691
|
+
text is free prose from a spec bullet, so one precondition gets three spellings and the registry
|
|
692
|
+
grows without bound until people go back to hand-written tests. Nothing in loam could previously
|
|
693
|
+
say how bad that was. It emits **no finding and no stable code** — a near-duplicate is a judgement
|
|
694
|
+
about writing, and a phrase-similarity warning is the kind of check a fleet turns off, taking the
|
|
695
|
+
real findings beside it.
|
|
696
|
+
- **The phrase key is a contract** and `test/steps-inventory.test.ts` pins it case by case: the
|
|
697
|
+
Gherkin keyword is dropped (every runner resolves Given/When/Then/And/But against one registry, so
|
|
698
|
+
`Given the ledger is open` and `And the ledger is open` are one definition); quoted strings,
|
|
699
|
+
`<placeholders>` and standalone numbers collapse to `{s}` / `{p}` / `{n}`, because those three are
|
|
700
|
+
exactly what a definition captures as an argument; `v1` and `oauth2` keep their digits. Nothing
|
|
701
|
+
measures edit distance.
|
|
702
|
+
- **`nearDuplicates`** groups phrases that differ only by a leading article or a trailing
|
|
703
|
+
`because …` / `while …` clause — two definitions where one was meant, and the fix is the spec's
|
|
704
|
+
wording rather than a second definition.
|
|
705
|
+
- **It reads the living specs, not the emitted files**, so it answers the same way in a repo that has
|
|
706
|
+
never run `loam gherkin`, and needs no service repo to stand in. `/loam-implement` and the
|
|
707
|
+
generated `AGENTS.md` now both send an agent here before it writes any glue.
|
|
708
|
+
|
|
709
|
+
### An outline's header and its placeholders must agree
|
|
710
|
+
|
|
711
|
+
- **New `requirements.examples-unbound` (error)**: a scenario with an `Examples` table uses a
|
|
712
|
+
`<placeholder>` no column supplies. Cucumber substitutes nothing, so the step definition receives
|
|
713
|
+
the literal angle-bracket text and either fails obscurely or matches and asserts against a string
|
|
714
|
+
nobody meant.
|
|
715
|
+
- **New `requirements.examples-unreferenced` (warn)**: an `Examples` column nothing refers to —
|
|
716
|
+
usually the fossil of a rename. The column still lists six cases, the step that used it now names
|
|
717
|
+
something else, and the outline runs six identical passes that read as six cases of coverage.
|
|
718
|
+
- **Steps, docstrings and data-table cells all count as references**, because cucumber substitutes
|
|
719
|
+
into all three: `<fee>` inside a request payload is a real use, and a check that read only the step
|
|
720
|
+
lines would call its column dead and invite the author to delete it.
|
|
721
|
+
- **Scoped to scenarios that HAVE an `Examples` table.** A plain scenario's angle brackets are prose
|
|
722
|
+
— a URL template, a generic, one of loam's own scaffold sentinels — and a fleet that writes no
|
|
723
|
+
outline earns nothing from either code.
|
|
724
|
+
|
|
725
|
+
### A scenario that asserts nothing is a finding
|
|
726
|
+
|
|
727
|
+
- **New `requirements.assertionless-scenario` (error)**: a scenario whose body has steps and no
|
|
728
|
+
`Then` anywhere in it. It arranges and acts and asserts nothing, so the run confirms only that the
|
|
729
|
+
setup did not throw — and answers its `scenario.tested` claim on that. `And`/`But` are
|
|
730
|
+
continuations, so with no `Then` above them they continue the setup.
|
|
731
|
+
- **Disjoint from `requirements.stepless-scenario` by construction** (that one has no steps at all),
|
|
732
|
+
and graded in the same four places: living `spec.md` and `arch.spec.md`, and a feature's deltas of
|
|
733
|
+
both — a delta merges into the living spec and is called covered from then on.
|
|
734
|
+
- It matters most where the least is left to check: under the runner-neutral answer sheet there are
|
|
735
|
+
no step results to count, so a document-side check is the only thing that can tell a dense scenario
|
|
736
|
+
from a mute one.
|
|
737
|
+
|
|
738
|
+
### A step can carry an argument: payload docstrings and expected-state tables
|
|
739
|
+
|
|
740
|
+
- **A fenced block written under a step becomes that step's Gherkin docstring**, info string carried
|
|
741
|
+
through as the content type and **indentation preserved rather than trimmed** — a request or
|
|
742
|
+
response payload whose leading whitespace has been eaten is one no step definition can compare
|
|
743
|
+
against. **An indented Markdown table under a step becomes that step's data table** (the markdown
|
|
744
|
+
separator row is dropped; Gherkin has none), which is how an expected-state assertion — the rows an
|
|
745
|
+
outbox, a topic or a table must hold after one data pass — can be written at all.
|
|
746
|
+
- **Indentation is the whole disambiguation**, and it is the one behaviour change to existing
|
|
747
|
+
documents: a table INDENTED under a step is now that step's rows, while a table at the scenario
|
|
748
|
+
body's left margin is still the `Examples:` of a `Scenario Outline`, unchanged. A spec that wrote
|
|
749
|
+
its expected rows indented under a `Then` was previously emitting them as the scenario's case
|
|
750
|
+
matrix — the same scenario run once per expected row, with the step itself receiving nothing.
|
|
751
|
+
- **No digest moves.** The scenario digest hashes the markdown source, not the rendering, so every
|
|
752
|
+
`@loam-digest-…` stamp, every `scenario.tested` claim and every recorded answer is unchanged by
|
|
753
|
+
this. A suite generated by an earlier loam keeps its stamps and still grades `gherkin.current`;
|
|
754
|
+
regenerate to pick up the new rendering.
|
|
755
|
+
- **New per-scenario emission loss `strandedBlocks`** (text, and a new `files[].strandedBlocks` key
|
|
756
|
+
under `loam gherkin --json` — additive): a fenced block or an indented table that no step precedes,
|
|
757
|
+
or an indented table loam cannot read. It stays in the description, so the document still reads
|
|
758
|
+
correctly and the file is still valid Gherkin; what is lost is the ARGUMENT, and nothing downstream
|
|
759
|
+
can otherwise notice.
|
|
760
|
+
- **Two defects fixed on the way in.** A Markdown table inside a fenced payload was hoisted out and
|
|
761
|
+
emitted as the scenario's `Examples:`, turning one data pass into an outline parameterized by the
|
|
762
|
+
contents of its own request body — fences are now recognised before tables. And a table cell
|
|
763
|
+
holding a literal pipe written `\|` was read as two cells, so the row's width stopped matching the
|
|
764
|
+
header and the WHOLE table was reported unreadable against the one row that needed the escape;
|
|
765
|
+
`\|` and `\\` now unescape on the way in and are re-escaped on the way out, along with `|`, `\` and
|
|
766
|
+
newline in every emitted cell.
|
|
767
|
+
- `parseStampedFeature` now skips docstring bodies. Every staleness verdict rests on that parse, so a
|
|
768
|
+
payload containing a line reading `Scenario: …` or beginning with `@` would otherwise have been
|
|
769
|
+
deciding what the suite promises.
|
|
770
|
+
|
|
771
|
+
### `loam --help` is grouped
|
|
772
|
+
|
|
773
|
+
- The twenty-eight commands now print under seven headings — **Set up · Read the fleet · Adopt what
|
|
774
|
+
exists · Change it · Check it · Ship it · Migrate** — in the order the work happens, mirroring the
|
|
775
|
+
six shipped workflow protocols rather than inventing a second taxonomy.
|
|
776
|
+
- **Nothing is hidden and nothing is renamed.** Every command still parses, still appears, and still
|
|
777
|
+
carries the same flags; `test/help-groups.test.ts` asserts that no command is hidden, that every
|
|
778
|
+
one has a heading, and that no stray heading exists. `src/cli.ts` throws on a command with no
|
|
779
|
+
heading, so a twenty-ninth added without one fails the first time anybody runs the binary rather
|
|
780
|
+
than landing quietly at the bottom of the page.
|
|
781
|
+
- [AGENTS.md](https://github.com/ybotok/loam/blob/main/AGENTS.md) now states that **help layout is
|
|
782
|
+
not part of the frozen surface**: names,
|
|
783
|
+
flags, exit codes, the envelope and the codes are frozen; which heading a command prints under is
|
|
784
|
+
typography, and regrouping is not a compatibility event.
|
|
785
|
+
|
|
786
|
+
### The build's own contract, read as a check
|
|
787
|
+
|
|
788
|
+
- **New `contracts` block in `loam.json`**: `{ "contracts": { "openapi": "build/openapi.yaml" } }`
|
|
789
|
+
names where this repository's BUILD writes its contract. `loam validate --service` digests that
|
|
790
|
+
document and compares it with the committed `services/<id>/openapi.yaml` — **new finding
|
|
791
|
+
`openapi.generated-stale` (warn)** when they differ, **`contracts.source-missing` (error)** when
|
|
792
|
+
the path holds nothing (CI that validates before it builds), and **`contracts.source-invalid`
|
|
793
|
+
(error)** when it escapes the repository or does not parse.
|
|
794
|
+
- **It reads, and never writes.** The copy into the docs repo stays a human `cp` reviewed in a pull
|
|
795
|
+
request, which is what keeps the committed contract a document somebody agreed to rather than a
|
|
796
|
+
cache of the build. This is the same category as `verify --results` ingesting a cucumber report:
|
|
797
|
+
loam parses a standard document the team's build emitted, at a path a human named, and derives no
|
|
798
|
+
meaning from it — no line of service code is read.
|
|
799
|
+
- **The digest is over canonical JSON, not bytes**, so two generator versions ordering keys
|
|
800
|
+
differently, or a dumper re-wrapping a description, produce silence rather than a permanent
|
|
801
|
+
warning nobody can clear. Service-repo only, and entirely silent for a repo with no `contracts`
|
|
802
|
+
block — which is every existing repo.
|
|
803
|
+
- The premise this closes is SCHEMA's most load-bearing untested one: that the committed contract is
|
|
804
|
+
what the service actually serves. Most fleets generate OpenAPI and copy it by hand, and until now
|
|
805
|
+
nothing in the product could notice when the copy stopped being current.
|
|
806
|
+
|
|
807
|
+
### The example fleet now demonstrates both verdicts
|
|
808
|
+
|
|
809
|
+
- **New archived feature `FEAT-120-refund-notification`** in `examples/docs/`, and it reads
|
|
810
|
+
**`verified`**. It is the sequel FEAT-088 left open — the money went back and nobody told the
|
|
811
|
+
customer — so it publishes `payment.PaymentRefunded` from payment-service and has
|
|
812
|
+
notification-service turn it into the customer's message: the event spine end to end, across two
|
|
813
|
+
services, with no HTTP anywhere in it.
|
|
814
|
+
- **The two archived records are a matched pair, and that is the point.** FEAT-088's scenario claims
|
|
815
|
+
rest on an agent's word (`attested`); FEAT-120's on a digest-matched runner report (`verified`).
|
|
816
|
+
A showcase demonstrating only the lesser verdict teaches that the distinction is decorative —
|
|
817
|
+
which is the one thing this product may not teach, since keeping those two answers apart is most
|
|
818
|
+
of what it claims to be for.
|
|
819
|
+
- **Every artifact was produced by the real commands**, not hand-written: `loam new`, `loam rebase`,
|
|
820
|
+
`loam gherkin` (which is where the five scenario digests came from), `loam verify --results`, then
|
|
821
|
+
`loam archive` — snapshot, manifest and all. The `scenario-report.json` beside the record is a
|
|
822
|
+
real `loamScenarioReport` file, and the record's `report:` block carries that file's true sha256.
|
|
823
|
+
- Its two `event.declares` claims still rest on an agent's word, and it is `verified` anyway — the
|
|
824
|
+
verdict turns on the scenario claims alone. That mix is the honest common case: a suite that
|
|
825
|
+
answers the scenarios, and wiring claims a human vouches for.
|
|
826
|
+
- The fleet still reports **0 errors and exactly the same ten deliberate warnings**: the merge
|
|
827
|
+
disturbed nothing. `test/examples.test.ts` now pins both verdicts and asserts they stay different,
|
|
828
|
+
so "fixing" the example by making both sides agree fails the suite.
|
|
829
|
+
- `examples/README.md` also separates two words that were being read as one: a SERVICE's ladder rung
|
|
830
|
+
(`draft` → … → `vouched`) is not a feature's verdict. FEAT-120 is `verified` in a fleet whose
|
|
831
|
+
services are all `draft`, which is exactly where a team stands the day its first suite goes green.
|
|
832
|
+
|
|
833
|
+
### `docs.binary-behind` — the run that reports green without having graded the corpus
|
|
834
|
+
|
|
835
|
+
- **New finding `docs.binary-behind` (warn)**, on `loam validate --all` and `loam doctor`: the docs
|
|
836
|
+
repo's `AGENTS.md` stamp names a loam NEWER than the binary reading it.
|
|
837
|
+
- **It is the only finding that changes what a PASS means rather than reporting a defect.** Every
|
|
838
|
+
check loam has is an existence constraint over a value the parser recognised, so a binary that
|
|
839
|
+
predates a grammar addition does not fail on the newer directive — it reads it as prose, produces
|
|
840
|
+
no join, and reports green. Until now nothing said so: `agents.stale` grades only the opposite
|
|
841
|
+
direction, and explicitly declined this case because the fix is different (upgrade the binary, do
|
|
842
|
+
not edit the file).
|
|
843
|
+
- **Warn, not error**, on the `sources.stale` doctrine: a mixed-version fleet is ordinary, and
|
|
844
|
+
failing every command in that repo aims a refusal at the wrong person. `--strict` is the lever
|
|
845
|
+
for a fleet that wants it to gate. The message says plainly that a pass from this binary is
|
|
846
|
+
incomplete rather than clean, and never sends anyone to edit `AGENTS.md`.
|
|
847
|
+
- Prerelease identifiers are compared, not ignored — in a 0.x product a prerelease is exactly where
|
|
848
|
+
a generated file's form and a corpus grammar actually move, which `agents.stale` already learned
|
|
849
|
+
the hard way at beta.2.
|
|
850
|
+
|
|
851
|
+
### Any test runner can answer a scenario claim
|
|
852
|
+
|
|
853
|
+
- **`--results` now also accepts `{"loamScenarioReport": 1, "results": [{"digest": "…", "status":
|
|
854
|
+
"passed", "test": "…"}]}`**, chosen by its marker key, beside the cucumber JSON it already read.
|
|
855
|
+
`digest` is the `@loam-digest-…` tag `loam gherkin` stamped; `status` is `passed` or `failed`;
|
|
856
|
+
`test` is optional free text used as the evidence string.
|
|
857
|
+
- **This is a widened input dialect, not a weakened standard of proof.** The contract was never
|
|
858
|
+
cucumber's JSON — it is the content-derived digest plus a status saying a real run reported it
|
|
859
|
+
green, and both halves are present here. An answer from this shape is `answered_by: runner`,
|
|
860
|
+
identical to a cucumber one, because it is the same claim answered to the same standard by the
|
|
861
|
+
same identity. A fleet on JUnit, pytest, Playwright, Vitest or a house runner was previously held
|
|
862
|
+
at `attested` **by a file format**, which is the one thing that distinction was never meant to
|
|
863
|
+
mean.
|
|
864
|
+
- It adds no forgeability: loam cannot prove any JSON came from executing a commit — which is why
|
|
865
|
+
the record stores the file's sha256 and mtime and claims nothing more — and a hand-written
|
|
866
|
+
cucumber array was always exactly as easy to write.
|
|
867
|
+
- **Parsing is strict, and a `skipped` status is refused rather than guessed.** A scenario that did
|
|
868
|
+
not run has no place in an answer sheet; omitting it leaves the claim unanswered rather than
|
|
869
|
+
confirmed. A file carrying the marker is always graded as that shape even when malformed, so an
|
|
870
|
+
author is told the real mistake instead of "this is not a cucumber array".
|
|
871
|
+
|
|
872
|
+
### Two corrections from review, before either shipped in a release
|
|
873
|
+
|
|
874
|
+
- **`spec.unknown-directive` no longer fires inside a fenced block.** A requirement body
|
|
875
|
+
legitimately holds a fenced example, and inside one `Realises:` is a sample value rather than a
|
|
876
|
+
misspelled directive — the check convicted a document that was exactly right, which is the one
|
|
877
|
+
thing a near-miss guard may never do. Backtick and tilde fences both toggle; a real typo after a
|
|
878
|
+
closed fence is still caught. Table cells never fired (a pipe-led line does not match the
|
|
879
|
+
candidate pattern) and there is now a test saying so.
|
|
880
|
+
- **`verify.checklist-forked` is measured against the current checklist, not pairwise between
|
|
881
|
+
attestations.** The pairwise reading answers the wrong question: three services that all attested
|
|
882
|
+
against a since-rewritten delta agree with each other perfectly and are every one of them stale,
|
|
883
|
+
which pairwise called clean. The notice now names the services whose answers are not about the
|
|
884
|
+
questions being asked now. Differing `docsCommit` values remain **not** a finding — recording a
|
|
885
|
+
week apart is normal for a feature in flight.
|
|
886
|
+
|
|
887
|
+
### A grammar guard: `spec.unknown-directive`
|
|
888
|
+
|
|
889
|
+
- **New finding `spec.unknown-directive` (warn)** — a requirement body line whose key is one or two
|
|
890
|
+
edits from a directive and is not one: `Realises:`, `Capabilties:`, `Opertaions:`, `Publsihes:`.
|
|
891
|
+
- This was the one place the corpus was quieter than it looked. Every join is an existence
|
|
892
|
+
constraint over a *parsed* value, so a key the parser does not recognise yields no value, no join
|
|
893
|
+
and therefore no finding — the requirement claims to realize a promise, gets nothing, and validates
|
|
894
|
+
clean. It is the judgement `obligation.unknown` already makes one axis over ("a mistyped tag reads
|
|
895
|
+
exactly like a rule"), applied to the keys themselves.
|
|
896
|
+
- **Measured by edit distance, not by shared prefix**, deliberately: `closeIds`'s three-character
|
|
897
|
+
prefix rule would report `Context:` as a near miss for `Consumes:`, in a check whose entire value
|
|
898
|
+
is that it does not cry wolf. `Note:`, `Owner:`, `Rationale:`, `Status:` and `See:` are silent,
|
|
899
|
+
and the example fleet's warning count is unchanged at ten.
|
|
900
|
+
|
|
901
|
+
### An attestation says what it answered, and against what
|
|
902
|
+
|
|
903
|
+
- **Two optional keys on each `ServiceAttestation`**: `checklist` (the digest that attestation
|
|
904
|
+
answered) and `docsCommit` (the docs-repo HEAD it was derived from, when the docs repo is a git
|
|
905
|
+
checkout). `commit` already pinned the service repo — the code the evidence points into — and
|
|
906
|
+
nothing pinned the side the *question* came from.
|
|
907
|
+
- **New finding `verify.checklist-forked` (warn)** — two or more services on one record answered
|
|
908
|
+
different versions of the feature's question set. The record carried a single top-level
|
|
909
|
+
`checklist` digest, so this state could not be represented at all: the staleness check flagged
|
|
910
|
+
both services or neither, and never said which answers went stale.
|
|
911
|
+
- **An attestation with no `checklist` field is not counted as a third version.** Silence is not
|
|
912
|
+
disagreement, and reading it as such would fire this on every federated record written before the
|
|
913
|
+
field existed. `docsCommit` is omitted rather than refused when the docs repo is not a git
|
|
914
|
+
checkout — a docs repo is not obliged to be one.
|
|
915
|
+
|
|
916
|
+
### The pages say where loam is behind, not only where it is ahead
|
|
917
|
+
|
|
918
|
+
- **README's "Why" now carries what green does *not* mean**, pointing at
|
|
919
|
+
`src/core/brief/unchecked.ts` — the fifteen statements of what no check will ever tell you, which
|
|
920
|
+
the binary already prints into the adoption brief and no page had ever quoted. Completeness is the
|
|
921
|
+
one worth naming out loud: forty behaviours documented as one requirement passes every check loam
|
|
922
|
+
has. The `verified`/`attested` distinction moved up beside it.
|
|
923
|
+
- **COMPARISON.md gains "The wider field, and who is actually nearest"** — OpenFastTrace, Doorstop,
|
|
924
|
+
Pact Broker, Backstage, EventCatalog, oasdiff/buf, StrictDoc/Sphinx-Needs and ArchUnit, each with
|
|
925
|
+
the mechanism it shares and **who is ahead at it**. The page compared loam only to OpenSpec, which
|
|
926
|
+
is the nearest tool on one of loam's seven joins and on none of the other six. Two entries are
|
|
927
|
+
direct credit: OpenFastTrace put the version inside the coverage token years ago, and Doorstop
|
|
928
|
+
stored a parent's fingerprint on a link thirteen years before `capability.realizes-stale`.
|
|
929
|
+
|
|
930
|
+
### A living `Realizes:` pin, so a moved promise stops being invisible
|
|
931
|
+
|
|
932
|
+
- **New finding `capability.realizes-stale` (warn).** A `Realizes:` entry may now carry the digest of
|
|
933
|
+
the capability requirement it was written against — `Realizes: checkout#CHK-1@9f2c1a4b` — and
|
|
934
|
+
`loam validate` reports one warning per pinned entry whose target has been rewritten since. Until
|
|
935
|
+
now every capability check was an EXISTENCE constraint: `capability.realizes-unknown` fires when
|
|
936
|
+
the target is *gone* and stays silent when the target merely *changed*, so an analyst could narrow a
|
|
937
|
+
promise and every requirement claiming to keep it went on claiming so, in a corpus loam rewrote
|
|
938
|
+
itself. It is a warn and not an error because re-reading a moved promise has three legitimate
|
|
939
|
+
outcomes and loam cannot tell which; it never gates `loam archive`.
|
|
940
|
+
- **New flag `loam rebase --living`**, which writes and refreshes those pins across the living
|
|
941
|
+
corpus and takes no feature id — so `rebase`'s `<FEAT>` argument is now optional, and every
|
|
942
|
+
existing invocation still parses. It commits through the same journaled transaction as every other
|
|
943
|
+
writer, under the same docs lock, and rewrites exactly one `Realizes:` line per requirement that
|
|
944
|
+
already has one: it never invents a join on an author's behalf. Its stored rerun is
|
|
945
|
+
`loam rebase --living`. `--service <id>` and `--dry-run` apply.
|
|
946
|
+
- **Nothing changes for a corpus that has not been pinned.** An entry without a pin grades exactly as
|
|
947
|
+
it did before, forever, which is what lets this land with no migration.
|
|
948
|
+
- **`requirementDigest` now strips `Realizes:` pins as well as `Based-On:` lines.** Both are
|
|
949
|
+
bookkeeping a requirement carries about its own joins, and a digest that moved when bookkeeping
|
|
950
|
+
moved would make every pin self-invalidating — one `rebase --living` would go on to invalidate
|
|
951
|
+
every `Based-On:` baseline in the fleet. The pinned and unpinned spellings of a requirement
|
|
952
|
+
therefore digest identically, which `test/capability-realizes-pin.test.ts` asserts directly.
|
|
953
|
+
|
|
954
|
+
### The first hour starts from the fleet spine
|
|
955
|
+
|
|
956
|
+
- **`examples/fleet.yaml`** ships as a real `loam seed` input for the five-service example fleet, and
|
|
957
|
+
**WORKFLOW.md's day zero step 2 now names `loam seed --from fleet.yaml`** instead of telling a
|
|
958
|
+
reader to draw `architecture/landscape.likec4` by hand. `seed` has shipped since beta.1 and
|
|
959
|
+
appeared in exactly one place in the prose: a command-table row.
|
|
960
|
+
- **README leads with two entry points that pay off before adoption starts** — "Start from the fleet
|
|
961
|
+
spine" (seed, drop in the OpenAPI you already have, name the operation on each call edge, then
|
|
962
|
+
`validate --all` convicts `spine.op-undefined` with no requirement Markdown anywhere) and "Guard
|
|
963
|
+
the fleet's edges on every PR" (a copy-pasteable GitHub Actions job around `loam diff --base`,
|
|
964
|
+
which exits 1 on a removal the fleet still consumes). Both state plainly that the run is red:
|
|
965
|
+
`service.no-model` and `service.no-spec` are what a fleet at the bottom rung honestly looks like.
|
|
966
|
+
- **`test/spine-first.test.ts`** pins that sequence against the shipped `examples/fleet.yaml`,
|
|
967
|
+
including the exact counts of the expected `service.no-model` and `service.no-spec` findings, so
|
|
968
|
+
the documented first hour cannot drift from the binary.
|
|
969
|
+
- One stale cross-reference fixed: WORKFLOW.md sent readers to "the README's `loam diff` command
|
|
970
|
+
note" for the oasdiff invocation, which lives in WORKFLOW.md's own command notes.
|
|
971
|
+
|
|
972
|
+
### Documentation — the pages say what the binary does, and four more claims are checked
|
|
973
|
+
|
|
974
|
+
- **README is the front door again, not the manual.** It carries the pitch, the install, the
|
|
975
|
+
five-minute trial, one table row per command **with every flag the binary registers**, and links.
|
|
976
|
+
Day zero, the per-command notes, the archive-protection walkthrough and the MCP server moved to
|
|
977
|
+
[WORKFLOW.md](WORKFLOW.md); the fleet scorecard to [SCHEMA.md](SCHEMA.md); the measured fleet-run
|
|
978
|
+
costs to `docs/BENCHMARKS.md`. Nothing was deleted, and the page is 53% shorter.
|
|
979
|
+
- **Five drift fixes, each one a claim that had stopped being true.** `loam list glossary` was
|
|
980
|
+
missing from the command table; thirteen shipped flags were named on no page; the example fleet's
|
|
981
|
+
headline read "0 errors, 7 deliberate warnings" against a fixture that has reported ten since the
|
|
982
|
+
glossary and obligation axes landed; `loam mcp`'s prose listed eleven read commands after
|
|
983
|
+
`loam_explain` made twelve; and SCHEMA's layout block — the tree readers take for the whole map —
|
|
984
|
+
never drew `architecture/obligations.yaml`, `glossary/<term>.md`, `features/<FEAT>/glossary/`,
|
|
985
|
+
`features/<FEAT>/capabilities/<cap>/spec.md` or `features/<FEAT>/specs/<svc>/asyncapi.yaml`.
|
|
986
|
+
- **Four new pins in `test/docs-facts.test.ts`, placed where the drift actually happened**: every
|
|
987
|
+
registered long flag is named in README, the `loam list` row's sections equal the CLI's, SCHEMA's
|
|
988
|
+
layout block names every artifact path `src/core/repo/` still builds, and the example fleet's
|
|
989
|
+
counts are read out of `test/examples.test.ts` rather than carried as a fourth copy. Each was
|
|
990
|
+
checked by breaking the fact it guards and watching it fail.
|
|
991
|
+
- **A derivable count no longer has a dated exemption.** ROADMAP's assessment claimed 261 modules
|
|
992
|
+
and 21 commands for nine days against a tree holding 394 and 28, legally, because a dated heading
|
|
993
|
+
covered counts that derive from one readdir. Only a passed/total test count keeps the exemption
|
|
994
|
+
now; module, package, test-file and command counts are graded live on every page.
|
|
995
|
+
- **Every Markdown page wraps at 100 columns**, fences, tables and headings excepted, so a prose
|
|
996
|
+
change reviews as a line-sized diff. The phrase assertions in `docs-drift`, `docs-facts` and
|
|
997
|
+
`agent-contract` match through a whitespace flattener, which is what makes the wrap a formatting
|
|
998
|
+
decision rather than a test-breaking one.
|
|
999
|
+
- **`[Unreleased]` states its changes rather than arguing them**, under a contract that is checked
|
|
1000
|
+
rather than promised: every code, flag, path and command an entry introduced — every one written
|
|
1001
|
+
in bold, which is how this file marks its subjects — is still named by the entry. What left is the
|
|
1002
|
+
argument, 31,342 words down to 12,572. One `ALLOWED_PROSE_TOKENS` entry was pruned with it,
|
|
1003
|
+
because the sentence carrying it was the case for a change, not the change.
|
|
1004
|
+
|
|
1005
|
+
### Added — architectural obligations: what the architect hands the team, for the rules that vary
|
|
1006
|
+
|
|
1007
|
+
- **`architecture/obligations.yaml`** declares the fleet's architectural obligations —
|
|
1008
|
+
`obligations: {<id>: {description, adr}}` — and a **`#obl-<name>` tag** on an element or an edge
|
|
1009
|
+
in `architecture/landscape.likec4` says where each one applies. loam had exactly one checked
|
|
1010
|
+
architect→team channel before this, and it works: an edge carrying `metadata { op }` obliges the
|
|
1011
|
+
provider to define that operationId or `spine.op-undefined` fails the gate.
|
|
1012
|
+
- **`obligation.uncovered` (warn) is the prerequisite the roadmap named, discharged.**
|
|
1013
|
+
`c4.uncovered` could always say "this architecture object owes a requirement" — but only about a
|
|
1014
|
+
NEW tagged element in a feature's `delta.likec4`, never about the map the fleet actually runs on.
|
|
1015
|
+
- **`obligation.unknown` (error) and `obligation.unapplied` (warn)** are the same join read in both
|
|
1016
|
+
directions, with the asymmetry `permissions.unknown`/`permissions.unenforced` already carry: a tag
|
|
1017
|
+
that resolves to nothing reads exactly like a rule the fleet keeps, while a declaration nobody has
|
|
1018
|
+
placed yet is an honest state.
|
|
1019
|
+
- **`obligation.adr-missing` (error)** — an `adr:` field naming no file.
|
|
1020
|
+
- **`obligation.invalid` (error)** suspends the family, as the permission and capability
|
|
1021
|
+
vocabularies' invalid states do.
|
|
1022
|
+
- **The file is the opt-in**: a fleet without `architecture/obligations.yaml` produces no obligation
|
|
1023
|
+
finding at all, however many tags its map already carries.
|
|
1024
|
+
|
|
1025
|
+
### Added — a feature brings its own words, and the merge keeps their links pointing at them
|
|
1026
|
+
|
|
1027
|
+
- **`features/<FEAT>/glossary/<term>.md`** is copied into `glossary/` by `loam archive` and removed
|
|
1028
|
+
again by `loam unarchive`, so the vocabulary a change introduces ships and unships with it and is
|
|
1029
|
+
reviewed beside the requirements that use it.
|
|
1030
|
+
- **The route is CREATE-ONLY**, and that is a decision rather than an unfinished half.
|
|
1031
|
+
**`glossary.term-exists` (error)** refuses a term the living glossary already defines, and
|
|
1032
|
+
**`--approve` does not override it** — approving it would be approving a deletion nobody
|
|
1033
|
+
described.
|
|
1034
|
+
- **A feature may cite the word it is introducing** at that word's future living path.
|
|
1035
|
+
- **`loam archive` now re-expresses relative links in the text it moves**, and this closes a defect
|
|
1036
|
+
that predates the glossary.
|
|
1037
|
+
|
|
1038
|
+
### Added — the domain glossary: `glossary/<term>.md`, checked through the links that cite it
|
|
1039
|
+
|
|
1040
|
+
- **`glossary/<term>.md`** is the fleet's domain vocabulary — one FILE per term, nesting allowed
|
|
1041
|
+
(`glossary/payments/authorization.md` is the term `payments/authorization`), and **no
|
|
1042
|
+
`glossary.yaml` anywhere**.
|
|
1043
|
+
- **`glossary.unlinked` (warn)** — a term no document outside `glossary/` cites: a word the fleet
|
|
1044
|
+
never adopted, or a definition left behind by a rename.
|
|
1045
|
+
- **A whole glossary nobody cites is ONE finding**, not one per term.
|
|
1046
|
+
- **`loam list glossary`** — a fourth explicit-only section beside `capabilities`.
|
|
1047
|
+
|
|
1048
|
+
### Added — a markdown link is a join, and `loam validate` now resolves it
|
|
1049
|
+
|
|
1050
|
+
- **`link.unresolved` (error)** — a relative markdown link in an authored document whose target does
|
|
1051
|
+
not exist.
|
|
1052
|
+
- **The corpus is every authored markdown document**: both spec axes living and delta, `runbook.md`,
|
|
1053
|
+
every `adrs/` file at all three altitudes, `intent.md`, and each capability document.
|
|
1054
|
+
- **`link.unreadable` (error)** — a document in that corpus that could not be decoded, almost always
|
|
1055
|
+
UTF-16 (PowerShell's `>` and `Out-File` write it unasked).
|
|
1056
|
+
|
|
1057
|
+
### Added — `loam archive` refuses a business promise nothing keeps, in both directions
|
|
1058
|
+
|
|
1059
|
+
- **`capability.uncovered`** — a feature ADDS a capability requirement and no `Realizes:` line in
|
|
1060
|
+
the same feature's service deltas names it.
|
|
1061
|
+
- **`capability.remove-requirement-realized`** — the same join taken in the removal direction, and
|
|
1062
|
+
it closes a hole that used to archive at exit 0: a feature removing a capability requirement that
|
|
1063
|
+
a living service requirement still realizes merged cleanly, and the next `loam validate --all`
|
|
1064
|
+
then failed with `capability.realizes-unknown` against a service document nobody had touched.
|
|
1065
|
+
- **`loam status --json` gains `capabilities` artifact rows** (additive; present only when the
|
|
1066
|
+
feature carries the directory, with an optional `capability` key).
|
|
1067
|
+
|
|
1068
|
+
### Added — a feature can change a capability, and `loam archive` merges it
|
|
1069
|
+
|
|
1070
|
+
- **`features/<FEAT>/capabilities/<cap>/spec.md`** carries a delta against the living
|
|
1071
|
+
`capabilities/<cap>/spec.md`, in the requirement grammar and delta algebra every service spec
|
|
1072
|
+
already uses: `## ADDED|MODIFIED|REMOVED Requirements`, `Requirement-ID:` identity, `Based-On:`
|
|
1073
|
+
pins.
|
|
1074
|
+
- **`loam rebase` learned the capability axis**, because `delta.baseline-missing`'s own message
|
|
1075
|
+
tells you to run it. Additive `--json` keys: `capabilities` and `capabilityPins` (a `PinOutcome`
|
|
1076
|
+
carrying `capability` instead of `service`).
|
|
1077
|
+
- **The three capability-document rules now gate archive**, not just `loam validate`:
|
|
1078
|
+
`capability.requirement-unidentified`, `capability.requirement-service-scoped` and
|
|
1079
|
+
`capability.requirement-inert-join` are graded on the DELTA as well as on the living document.
|
|
1080
|
+
- **A `--json` nuance worth knowing:** `Issue.subject` on a `delta.*` code no longer always names a
|
|
1081
|
+
service.
|
|
1082
|
+
- **A fleet with no `features/*/capabilities/` sees no change and pays nothing** — one `existsSync`
|
|
1083
|
+
per feature, no read, no finding.
|
|
1084
|
+
|
|
1085
|
+
### Changed — `loam list capabilities` and `loam validate` now give the SAME answer to "is this promise kept"
|
|
1086
|
+
|
|
1087
|
+
- **`loam list capabilities --json` carries both corpora.** Each `requirements[]` row gains `keptBy`
|
|
1088
|
+
(additive): the ids of the `#cap-`/`#req-` tagged flows that keep that promise, beside the
|
|
1089
|
+
`realizedBy` service requirements that realize it.
|
|
1090
|
+
- **An unreadable `architecture/` degrades rather than refusing.** The rows themselves come from
|
|
1091
|
+
`capabilities.yaml` and `capabilities/`, both readable, and `validate --all` already reports the
|
|
1092
|
+
broken document as `landscape.invalid` — so the listing prints what it has and marks the flows
|
|
1093
|
+
unread.
|
|
1094
|
+
- **`capability.unrealized` counts flows too.** A capability whose every promise is kept by a tagged
|
|
1095
|
+
flow, named by no service requirement at all, was being told it was "a promise nobody implemented
|
|
1096
|
+
or a word nobody adopted" while loam's own report said the promise was kept.
|
|
1097
|
+
- **Bare `loam list --json` is byte-identical**, and pays nothing: the capabilities section stays
|
|
1098
|
+
explicit-opt-in, and the flow read keeps its byte-scan gate in front of any LikeC4 load.
|
|
1099
|
+
|
|
1100
|
+
### Added — `#req-`: a use case can keep a business promise, which is the only way a cross-service one gets kept
|
|
1101
|
+
|
|
1102
|
+
- **A `dynamic view` already tagged `#cap-<slug>` may now carry `#req-<slug>`**, naming one of that
|
|
1103
|
+
capability's requirements.
|
|
1104
|
+
- **`usecase.requirement-unresolved` (error)** — six failures with six fixes, and the message says
|
|
1105
|
+
which happened: no resolved `#cap-` tag, two of them, a capability with no document, a document
|
|
1106
|
+
with no requirements yet, an id that document does not declare (close ids offered, each already
|
|
1107
|
+
spelled as the tag to write), or two ids flattening to one slug.
|
|
1108
|
+
- **A tag name accepts exactly `[A-Za-z0-9_-]`, measured at the `likec4@1.59.2` pin**, so the slug
|
|
1109
|
+
rule is now a whitelist rather than "flatten a slash": a `Requirement-ID` may legally contain a
|
|
1110
|
+
`.`, and a capability id its `/`.
|
|
1111
|
+
- **Only a RESOLVED claim keeps a promise.** A broken `#req-` tag suppresses nothing, so
|
|
1112
|
+
`capability.requirement-unrealized` goes on firing beside the error — a typo that silenced it
|
|
1113
|
+
would turn a mistake into a green fleet.
|
|
1114
|
+
|
|
1115
|
+
### Added — `Realizes:`, the join that makes the business tree checkable
|
|
1116
|
+
|
|
1117
|
+
- **A service requirement says which promise it serves**: `Realizes: checkout#CHECKOUT-CHARGE-ONCE`,
|
|
1118
|
+
a list line resolving against a requirement in `capabilities/<cap>/spec.md`.
|
|
1119
|
+
- **The separator is the LAST `#`, not the first.** The requirement half's grammar excludes `#`
|
|
1120
|
+
while a capability id is a YAML key and a directory name and is constrained nowhere, so splitting
|
|
1121
|
+
at the last one is unambiguous for every id there is.
|
|
1122
|
+
- **`capability.realizes-unknown` (error)** — the entry names no capability requirement.
|
|
1123
|
+
- **`capability.requirement-unrealized` (warn, fleet scope)** — a capability requirement no living
|
|
1124
|
+
`Realizes:` line names, one warning per requirement, subject `<capability>#<id>`.
|
|
1125
|
+
- **`capability.requirement-inert-join` (error)** — a requirement in a capability document carrying
|
|
1126
|
+
`Capability:` or `Realizes:`.
|
|
1127
|
+
- **Realizing a REQUIREMENT realizes its capability.** A requirement carrying only `Realizes:`
|
|
1128
|
+
counts toward `capability.unrealized`, and one carrying both joins is counted once.
|
|
1129
|
+
- **`loam list capabilities --json` gains `requirements[]` per capability** (additive), each with
|
|
1130
|
+
what realizes it.
|
|
1131
|
+
|
|
1132
|
+
### Added — `capabilities/<cap>/spec.md`: the business tree an analyst can actually write in
|
|
1133
|
+
|
|
1134
|
+
- **A capability may now be declared by a DOCUMENT, not only by a name in
|
|
1135
|
+
`architecture/capabilities.yaml`, and the vocabulary is the union of the two.** Nothing is removed
|
|
1136
|
+
and nothing is deprecated: a fleet that declares twenty names in YAML and has written four
|
|
1137
|
+
documents is the normal state of an adoption, and both sides grade.
|
|
1138
|
+
- **The directory IS the list.** A directory under `capabilities/` is a capability if and only if it
|
|
1139
|
+
holds `spec.md`; nesting is spelled by the tree, so the id `payments/refunds` lives at
|
|
1140
|
+
`capabilities/payments/refunds/spec.md` and `payments` may be a capability in its own right beside
|
|
1141
|
+
it. There is no manifest, for the reason `loam init`'s removed `loam.docs.json` was removed: a
|
|
1142
|
+
second list is a second thing that can disagree.
|
|
1143
|
+
- **The opt-in widened, so a fleet can start grading capabilities without touching the YAML.**
|
|
1144
|
+
`architecture/capabilities.yaml` was this axis's only opt-in; now either file is.
|
|
1145
|
+
- **`capability.doc-missing` (warn)** — a directory under `capabilities/` holding neither the
|
|
1146
|
+
document nor a capability beneath it: what `mkdir` leaves behind halfway through creating one.
|
|
1147
|
+
- **`capability.requirement-unidentified` (error)** — a requirement in a capability document with no
|
|
1148
|
+
`Requirement-ID:`.
|
|
1149
|
+
- **`capability.requirement-service-scoped` (error)** — a capability requirement carrying
|
|
1150
|
+
`Operations:`, `Covers:`, `Publishes:` or `Consumes:`.
|
|
1151
|
+
- **The authored tree's own grades are not suppressed behind `capability.invalid`.** The suppression
|
|
1152
|
+
exists to stop a cascade of grades resolved against a file nobody can read; a directory holding no
|
|
1153
|
+
document, and a requirement with no stable id, consult no vocabulary at all.
|
|
1154
|
+
- **What is NOT in this release, each owned by the roadmap:** the `Realizes:` join from a service
|
|
1155
|
+
requirement back to a capability requirement, the feature-local
|
|
1156
|
+
`features/<FEAT>/capabilities/<cap>/` delta merged by the transactional archive, and
|
|
1157
|
+
`loam new <FEAT> --capability <cap>`.
|
|
1158
|
+
|
|
1159
|
+
### Changed — a use case is now a CONSUMER, so `loam diff` can refuse where it used to warn
|
|
1160
|
+
|
|
1161
|
+
- **Read this one before upgrading CI.** A use-case hop is now a consumer, so a removal whose ONLY
|
|
1162
|
+
consumer is a step in a `#cap-`-tagged flow moves from `diff.op-removed` (warn, exit 0) to
|
|
1163
|
+
`diff.op-removed-consumed` (error, exit 1) — the same removal, the same command, a different
|
|
1164
|
+
verdict. A repository with tagged use cases may see `loam diff` start failing a pipeline that
|
|
1165
|
+
passed.
|
|
1166
|
+
- **A CONTESTED hop is never a victim.** When the relationships backing a hop disagree about the
|
|
1167
|
+
operation, a removal touching it is reported as a suspension rather than as a broken flow: silence
|
|
1168
|
+
would claim nothing depends on it, and a victim would claim something does.
|
|
1169
|
+
- **`loam context` gains one new exit-1 condition**: `architecture/` failing to parse as a LikeC4
|
|
1170
|
+
project is now a hole in the pack.
|
|
1171
|
+
- **`loam diff` spends no new code for any of this.** Use-case victims ride the `details[]` of the
|
|
1172
|
+
two codes above, naming the flow, the hop number, the step title and the file:
|
|
1173
|
+
`use case 'uc_checkout' step 4 'authorizes the payment' (architecture/usecases/checkout.likec4)`.
|
|
1174
|
+
|
|
1175
|
+
### Added — the blast radius of a feature, in hops
|
|
1176
|
+
|
|
1177
|
+
- **`loam delta` and `loam status <FEAT>` gain a `useCases` key** (additive; nothing removed): the
|
|
1178
|
+
flows whose hops touch a service the feature addresses, each with its ordered steps and the
|
|
1179
|
+
services each step resolves to.
|
|
1180
|
+
- **The landscape is loaded lazily**, because `loam delta` sits in `/loam-implement`'s hot path and
|
|
1181
|
+
a LikeC4 workspace spin there is felt on every iteration.
|
|
1182
|
+
- **`loam context` and `loam explore --capability` see flows too.** The pack carries `useCaseSteps`
|
|
1183
|
+
for the service it is packing and `capabilities[].useCases` fleet-wide, and an `architecture/`
|
|
1184
|
+
that will not parse becomes a **hole** rather than an absence — the pack says it could not look,
|
|
1185
|
+
never that there are none.
|
|
1186
|
+
|
|
1187
|
+
### Added — a home for fleet-level decisions, and one way to link between documents
|
|
1188
|
+
|
|
1189
|
+
- **`architecture/adrs/NNNN-*.md` — ADRs about the fleet.** They existed at exactly two altitudes
|
|
1190
|
+
before this: a service's own decisions and a feature's.
|
|
1191
|
+
|
|
1192
|
+
### Added — `loam validate --all` grades a business use case, hop by hop
|
|
1193
|
+
|
|
1194
|
+
- **A `dynamic view` tagged `#cap-<slug>` is now a checked use case**, and four new stable codes
|
|
1195
|
+
grade it.
|
|
1196
|
+
- **`usecase.step-unbacked` (error)** — a hop the model does not declare.
|
|
1197
|
+
- **`usecase.capability-unresolved` (error)** — a `#cap-` tag that names no declared capability, or
|
|
1198
|
+
two.
|
|
1199
|
+
- **`usecase.step-contested` (warn)** — the relationships backing one hop disagree about the
|
|
1200
|
+
operation, listed as candidates. It suppresses `usecase.step-unlinked` for the same step,
|
|
1201
|
+
structurally rather than by rule — the warn is derived from the attributed verdict, and the two
|
|
1202
|
+
verdicts are disjoint.
|
|
1203
|
+
- **`usecase.step-unlinked` (warn)** — a backed hop that names no operation of the provider's
|
|
1204
|
+
contract, grouped under the OpenAPI adoption axis.
|
|
1205
|
+
|
|
1206
|
+
### Changed — the source-file limit is 400 lines, up from 300
|
|
1207
|
+
|
|
1208
|
+
- **A house limit, not a published contract — but it is in `CONTRIBUTING.md` and `AGENTS.md`, so it
|
|
1209
|
+
is stated here.** `test/code-limits.test.ts` now fails a `src/` file over **400** lines rather
|
|
1210
|
+
than 300.
|
|
1211
|
+
- **What moved it was a measurement.** Across 359 files in `src/` the median was 150 and p90 was 283
|
|
1212
|
+
— and **thirteen files sat at exactly 300**, with sixteen more between 290 and 300.
|
|
1213
|
+
- **400 rather than 500 or 600, because a ceiling has to keep asking.** The case for having one is
|
|
1214
|
+
that `src/core/agent.ts` reached 2,387 lines with nothing asking; at p90 283 a 500-line ceiling
|
|
1215
|
+
would be inert for years.
|
|
1216
|
+
|
|
1217
|
+
### Changed — `architecture/` is read as one LikeC4 project, so a use case can live in its own file
|
|
1218
|
+
|
|
1219
|
+
- **`validate --all` now loads `architecture/landscape.likec4` together with every
|
|
1220
|
+
`architecture/usecases/*.likec4` as ONE project**, the way the LikeC4 renderer has always read
|
|
1221
|
+
that directory.
|
|
1222
|
+
- **Why a file of its own, rather than the landscape's `views { }` block.** Readability is the
|
|
1223
|
+
visible half — an analyst opens `usecases/checkout.likec4` and reads one business flow instead of
|
|
1224
|
+
scrolling a fleet-sized map.
|
|
1225
|
+
- **Diagnosis improves rather than widening.** A typo'd element in a use-case file is attributed to
|
|
1226
|
+
THAT file: `landscape: architecture/usecases/checkout.likec4 has 2 error(s)`, and when more than
|
|
1227
|
+
one document is broken every detail line carries its own file.
|
|
1228
|
+
- **Two authoring rules invert inside `architecture/`, and both are enforced by LikeC4 rather than
|
|
1229
|
+
by loam.** Exactly one document declares the `specification` block: every `.likec4` file loam
|
|
1230
|
+
parses alone must declare its own, while a second declaration inside one project is a duplicate
|
|
1231
|
+
error blamed on BOTH files (measured).
|
|
1232
|
+
- **The generated `architecture/subsystems.likec4` is excluded from that project**, and
|
|
1233
|
+
docs/DESIGN.md rule 26 already said why: it stays a byte compare, and loam reads its contents in
|
|
1234
|
+
no document.
|
|
1235
|
+
- **No new command, no new flag and no `--json` key for THIS change** — the loader is a mechanism,
|
|
1236
|
+
and the codes that grade on it are the use-case section above.
|
|
1237
|
+
|
|
1238
|
+
### Fixed — a service filed under a subsystem was named at a directory that does not exist
|
|
1239
|
+
|
|
1240
|
+
- **Every message that names an existing service's directory now spells it from the enumeration.**
|
|
1241
|
+
`services/<id>/` is right for an unfiled fleet and wrong for every filed one: a service under
|
|
1242
|
+
`services/platform/` lives at `services/platform/<id>/`, and eight separate messages joined the
|
|
1243
|
+
root form anyway — so `loam status` told a reader to go to `services/checkout-web/` when the
|
|
1244
|
+
directory was `services/commerce/checkout-web/`, and `loam gate --service identity-service`
|
|
1245
|
+
reported on `services/identity-service/` for a service at `services/platform/identity-service/`.
|
|
1246
|
+
- **Eight sites, one rule.** `loam status`'s two adoption rungs, `loam gate`'s target and partner
|
|
1247
|
+
rungs, `loam validate --all`'s `landscape.service-unmodelled` and the datastore-placement fix in
|
|
1248
|
+
`fleet-shape`, the `loam adopt` brief's landscape instruction and artifact shape, `loam vouch`'s
|
|
1249
|
+
confirmation prompt, and `loam seed`'s "left exactly where it is" line — which was the worst of
|
|
1250
|
+
them, because that sentence is ABOUT placement.
|
|
1251
|
+
- **`serviceTreePath` in `core/kernel/ids/dirs.ts` is that implementation**, with
|
|
1252
|
+
`serviceTreePathOf(docsDir, id)` beside `locateServicePaths` for callers that hold only an id.
|
|
1253
|
+
- No new code, no new flag, no `--json` key: every change is message text and the internals that
|
|
1254
|
+
feed it.
|
|
1255
|
+
|
|
1256
|
+
### Added — a docs repo now scaffolds a README, the one file in it addressed to a person
|
|
1257
|
+
|
|
1258
|
+
- **`loam init --create` writes `README.md` first.** Everything else a docs repo starts with is
|
|
1259
|
+
either a document about the fleet or `AGENTS.md`, which runs to some 1500 generated lines and is
|
|
1260
|
+
named so that a human does not open it.
|
|
1261
|
+
- `migrate-openspec` stages the same file, because its target is meant to be the docs repo
|
|
1262
|
+
`loam init` makes.
|
|
1263
|
+
|
|
1264
|
+
### Fixed — an authored view id could take the whole fleet map down while the gate printed green
|
|
1265
|
+
|
|
1266
|
+
- **`validate --all` now refuses a landscape view whose id loam generates**
|
|
1267
|
+
(`subsystem.view-id-collision`, **error**, one finding per colliding id, fleet scope).
|
|
1268
|
+
- **Exactly one pair is compared, and that is the measured scope rather than a simplification.** A
|
|
1269
|
+
duplicate view id *inside* one document is already two LikeC4 errors, so `landscape.invalid` has
|
|
1270
|
+
had it all along (measured).
|
|
1271
|
+
- **The check asks the generator what it mints.** `core/repo/tree/views.ts` now exports its id
|
|
1272
|
+
function and the `subsystem_` prefix instead of keeping them private, because a check carrying its
|
|
1273
|
+
own copy of that escaping would go quiet the day the encoding changed — and going quiet is the
|
|
1274
|
+
failure this whole family exists to prevent.
|
|
1275
|
+
|
|
1276
|
+
### Changed — the view doctrine narrows: loam reads what a view DECLARES, never what a view SHOWS
|
|
1277
|
+
|
|
1278
|
+
- **`docs/DESIGN.md` gains rule 26, and the written doctrine finally matches the enforced one.**
|
|
1279
|
+
loam's prose said it "never parses views"; what it actually never does is COMPUTE one.
|
|
1280
|
+
- **Nothing is asked of any fleet, and nothing grades a views block's absence.** A repo that
|
|
1281
|
+
declares no dynamic view owes loam nothing, today or ever; `loam init` still scaffolds no `views`
|
|
1282
|
+
block — and that stays true after the use-case section above, whose four codes grade only a view
|
|
1283
|
+
somebody deliberately tagged. **Rule 26 itself adds no command, no flag, no stable code, no
|
|
1284
|
+
`--json` key and no severity change** — it records a boundary, not a behaviour.
|
|
1285
|
+
- **`npm run arch:check` gains a `view-stage` scan, so the doctrine is a failing build rather than a
|
|
1286
|
+
paragraph.** `computedModel` and `layoutedModel` may not appear in `src/` at all (zero
|
|
1287
|
+
occurrences, so no whitelist — the one mention is inside a comment, which the scan's `codeOnly`
|
|
1288
|
+
blanks), and `$data` may appear only under `src/core/c4/parsed/`, which confines the blast radius
|
|
1289
|
+
of an upstream shape change to one module.
|
|
1290
|
+
- **`test/likec4-view-shape.test.ts` pins the parsed record's shape at the `likec4@1.59.2` pin,
|
|
1291
|
+
written before any reader exists.** Ten measured facts, each load-bearing: LikeC4 synthesises an
|
|
1292
|
+
`index` view into every document (`_type: "element"`, no `sourcePath`) that a reader must ignore;
|
|
1293
|
+
a `<-` reply is recorded reversed AND flagged `isBackward`, while a forward step carries no such
|
|
1294
|
+
key; a `loop` is a nested entry with no endpoints and no `astPath` of its own; an untagged view
|
|
1295
|
+
reads `tags: null`; a step carrying `metadata` is a parse error, and a `#cap-` tag written after
|
|
1296
|
+
`title` is five of them.
|
|
1297
|
+
- **loam now READS a declared `dynamic view`, and grades nothing on it yet.**
|
|
1298
|
+
`src/core/c4/parsed/dynamic-views.ts` normalizes the parsed record into `ParsedView`/`ParsedStep`
|
|
1299
|
+
— id, tags, title, description, and ordered steps carrying `source`, `target`, `title`, `notes`,
|
|
1300
|
+
`isBackward` and `astPath` — and it is the only module permitted to touch `$data`.
|
|
1301
|
+
- **`astPath` is carried but optional, and that is a refusal rather than an oversight.** Every leaf
|
|
1302
|
+
carries one at the pin, so requiring it would look free — and would mean that an upstream release
|
|
1303
|
+
which stopped emitting the field turned every use case in the fleet into a view with zero steps at
|
|
1304
|
+
exit 0.
|
|
1305
|
+
- **Two modules split, no behaviour changed.** `core/c4/likec4.ts` reached the 300-line limit, and
|
|
1306
|
+
the seam was already in it: resolving an element id to the `services/<id>/` directory it stands
|
|
1307
|
+
for is a distinct phase from loading a document, and runs over loam's own records rather than
|
|
1308
|
+
LikeC4's — it moved verbatim to `core/c4/resolve/service.ts` (28 importers rewritten, 22 of them
|
|
1309
|
+
one line longer, which is what splitting a widely-imported module costs and is not payable by
|
|
1310
|
+
squeezing a comment).
|
|
1311
|
+
- **`SCHEMA.md`'s planned `services/<svc>/flows/` tree is retired rather than built.** A
|
|
1312
|
+
cross-service interaction flow is a `dynamic view` in `architecture/landscape.likec4` — the one
|
|
1313
|
+
document that already holds every cross-service edge.
|
|
1314
|
+
|
|
1315
|
+
### Fixed — a tag declared on a LikeC4 specification KIND could switch the fleet gate off
|
|
1316
|
+
|
|
1317
|
+
- **`validate --all` no longer reports a fleet it has stopped checking as agreeing.** LikeC4 1.59.0
|
|
1318
|
+
added tags on specification kinds, and 1.59.2 applies them to every element of the kind before
|
|
1319
|
+
loam sees anything: `specification { element softwareSystem { #external } }` puts `#external` on
|
|
1320
|
+
every `softwareSystem` in the document. loam reads `#external` as "deliberately not ours — stop
|
|
1321
|
+
grading it", so those six words silenced the landscape↔services reconciliation for the whole fleet
|
|
1322
|
+
at once, and the run ended on `landscape.matched` — *"N service(s) modelled —
|
|
1323
|
+
architecture/landscape.likec4 and services/ agree"* — over a map nothing had checked.
|
|
1324
|
+
- **One new stable finding code, `landscape.kind-tag-graded` (error).** It fires when the
|
|
1325
|
+
landscape's `specification` block declares `#external` or `#platform` on an element kind **and**
|
|
1326
|
+
at least one element of that kind stands for a real `services/<id>/` directory — by an explicit
|
|
1327
|
+
`metadata { service }` binding, or by a title naming one.
|
|
1328
|
+
- **A graded tag on a kind is still legal where it exempts nothing of ours** — and it has to be:
|
|
1329
|
+
`examples/docs/architecture/landscape.likec4` declares `element topic { #external #platform }` so
|
|
1330
|
+
that the Kafka topics nested inside an external broker do not each demand a
|
|
1331
|
+
`services/payment.events/` nobody owes, and that spelling is the one that scales.
|
|
1332
|
+
- **`loam archive` now refuses a delta that declares its own feature tag on a kind**
|
|
1333
|
+
(`merge-failed`, exit 1, nothing written).
|
|
1334
|
+
|
|
1335
|
+
### Fixed — a feature delta's non-model blocks were silently discarded on archive
|
|
1336
|
+
|
|
1337
|
+
- **`loam archive` now refuses a delta whose `deployment { }` or `global { }` block, or whose
|
|
1338
|
+
`dynamic view`, the landscape merge cannot carry** (`merge-failed`, exit 1, nothing written;
|
|
1339
|
+
`--approve` does not override — the loss is mechanical, not a judgement about coherence).
|
|
1340
|
+
- **A static `views { }` block is still accepted and still not merged** — `loam new` scaffolds one
|
|
1341
|
+
into every delta, and it is meant to be rendered while the feature is in flight rather than to
|
|
1342
|
+
travel.
|
|
1343
|
+
|
|
1344
|
+
### Added — `loam seed`: the fleet map templated from human-stated facts
|
|
1345
|
+
|
|
1346
|
+
- **New command `loam seed [--from <file>] [--json]`** — a tiny human-authored `fleet.yaml` (a
|
|
1347
|
+
`services:` list of ids, optional `subsystems:`, `externals:` and `calls:` lines like
|
|
1348
|
+
`checkout -> payments`) templated mechanically into `architecture/landscape.likec4` — one
|
|
1349
|
+
`softwareSystem` per service bound with `metadata { service '<id>' }`, `#external` systems, plain
|
|
1350
|
+
edges, the scaffold's own two views — plus one `services/<id>/` directory per service (under its
|
|
1351
|
+
subsystem where one is declared, with the marker and the regenerated
|
|
1352
|
+
`architecture/subsystems.likec4` in the same commit), all in one journaled transaction under the
|
|
1353
|
+
docs lock.
|
|
1354
|
+
- **Seed never overwrites human work.** It writes the landscape only when the file is absent, is the
|
|
1355
|
+
scaffold's untouched stub, or carries seed's own line-1 stamp (`// loam-seed sha256:<digest of the
|
|
1356
|
+
rest>`) with a matching digest — a self-verifying record, no state anywhere else.
|
|
1357
|
+
- **`loam status`'s empty-fleet teaching ladder now names the mechanical path**: the
|
|
1358
|
+
`next.author-landscape` rung over an untouched scaffold stub teaches `loam seed --from fleet.yaml`
|
|
1359
|
+
alongside authoring by hand.
|
|
1360
|
+
|
|
1361
|
+
### Added — the single-repo trial: `loam init` teaches the first hour
|
|
1362
|
+
|
|
1363
|
+
- **`loam init`'s human output now ends with the first-hour sequence** —
|
|
1364
|
+
`loam adopt --service <id> --json` → `loam validate --service <id>` → `loam vouch --service <id>`
|
|
1365
|
+
→ `loam status`, each with a one-line reason — printed exactly when a single run both creates the
|
|
1366
|
+
docs repo (`--create`) and ends with a service bound (`--service`, or a binding the repo's
|
|
1367
|
+
existing `loam.json` already carried): the composition of the README's new five-minute trial.
|
|
1368
|
+
|
|
1369
|
+
### Added — `loam explain`: the vocabulary wall, answered from the binary
|
|
1370
|
+
|
|
1371
|
+
- **New command `loam explain [<subject>] [--json]`** — what a finding code, a refusal code or a
|
|
1372
|
+
loam concept means, without a docs repo and without wiring: like `loam instructions` it reads no
|
|
1373
|
+
`loam.json`, because the vocabulary wall is hit in the first minute in an unfamiliar repository. A
|
|
1374
|
+
finding code (`spine.op-undefined`) answers with the /loam-check fix table's own row — severity
|
|
1375
|
+
note, "what it means" and "what to do" verbatim, plus the `loam validate` invocation that surfaces
|
|
1376
|
+
it where the adoption brief knows one — **parsed at runtime from the same workflow body the binary
|
|
1377
|
+
ships**, so the explanation cannot drift from the documentation; a code graded in more than one
|
|
1378
|
+
table (service scope and `loam archive` plan time, say) explains every context.
|
|
1379
|
+
- **`loam validate` and `loam verify` text reports gain a one-line footer** — `` → codes ride in
|
|
1380
|
+
`--json`; `loam explain <code>` says what one means and how to fix it `` — printed only when a
|
|
1381
|
+
non-ok finding (validate) or a notice (verify) was printed, appended as the report's last line,
|
|
1382
|
+
and only in text mode: every `--json` payload is byte-identical to before.
|
|
1383
|
+
- **`loam mcp` gains `loam_explain`** — the read-tool table grows to twelve; one optional `subject`
|
|
1384
|
+
argument, read-only like the rest.
|
|
1385
|
+
|
|
1386
|
+
### Added — `loam list --subsystem` and `--owners`: the adoption campaign, sliced by group and by team
|
|
1387
|
+
|
|
1388
|
+
- **`loam list` gains `--subsystem <name>`** — the services section limited to one subsystem's
|
|
1389
|
+
services, at any depth, resolved against the same flat namespace every `loam subsystem` verb
|
|
1390
|
+
reads.
|
|
1391
|
+
- **`loam list` gains `--owners <path>`** — a mechanical, read-only join of each listed service's
|
|
1392
|
+
directory to the owning teams in the user-named CODEOWNERS file (loam still has no permission
|
|
1393
|
+
model of its own; the forge's file stays the authority). `--json` gains the additive `owners` key:
|
|
1394
|
+
`path`, `teams[]` (owner plus that team's services in the listing's own filtered-and-ordered row
|
|
1395
|
+
order — the per-team campaign worklists), `unowned[]` (rows no rule matched, listed explicitly),
|
|
1396
|
+
`skippedRules[]`.
|
|
1397
|
+
- **One new stable refusal, `owners-unreadable`** (exit 1): the CODEOWNERS path cannot be read, or a
|
|
1398
|
+
line in it cannot be parsed as `pattern owner…` — the message names the path and line.
|
|
1399
|
+
- Considered and deferred: joining `features/**` to teams (the campaign worklist is services-only
|
|
1400
|
+
until someone asks), and any wider CODEOWNERS pattern grammar — implementing the fleet-wide `*`
|
|
1401
|
+
default above all — a half-implemented glob would attribute services the forge never did, so the
|
|
1402
|
+
subset grows only deliberately, with its own CHANGELOG line.
|
|
1403
|
+
|
|
1404
|
+
### Added — the axis-adoption rollup on `validate --all`
|
|
1405
|
+
|
|
1406
|
+
- **`loam validate --all --json` gains the additive `adoption` key on the `scorecard` payload** —
|
|
1407
|
+
services participating in each contract axis (`requirements`, `arch`, `openapi`, `asyncapi`,
|
|
1408
|
+
`permissions`, `capabilities`), one mechanical rule per axis (a requirement block for the two spec
|
|
1409
|
+
axes, file presence for the two contract files, a non-REMOVED `Requires:`/`Capability:` entry for
|
|
1410
|
+
the two vocabularies), the denominator carried once as the payload's existing `services` count.
|
|
1411
|
+
- **Text mode groups the warnings a fleet-wide not-started axis alone causes under one banner per
|
|
1412
|
+
axis** — e.g. a freshly declared capability vocabulary's per-capability `capability.unrealized`
|
|
1413
|
+
warns collapse to
|
|
1414
|
+
`⚠ capabilities axis not started fleet-wide (0 of N services), expected during staged adoption — k warning(s) grouped: …`.
|
|
1415
|
+
|
|
1416
|
+
### Added — `loam vouch --sample <n>`: a partial read, recorded as one
|
|
1417
|
+
|
|
1418
|
+
- **New flag `loam vouch --sample <n> [--service <id>] [--yes] [--json]`** — vouch after reading a
|
|
1419
|
+
deterministic sample of `<n>` sections per spec-axis file, instead of the whole document.
|
|
1420
|
+
`--sample` never loosens attendance — no TTY and no `--yes` is still `vouch-unattended`, `--json`
|
|
1421
|
+
without `--yes` still refuses, and vouch stays off the generated agent allowlist.
|
|
1422
|
+
- **The scope is stamped beside `vouched_by`, never inside `status`** — a new frontmatter field
|
|
1423
|
+
`vouch_scope: sampled <k>/<n> seed=<16 hex>`, written by `loam vouch` and never by hand.
|
|
1424
|
+
- **A sampled vouch is distinguishable from a full one on every surface that reports trust.**
|
|
1425
|
+
`loam list` shows `vouched (sampled)` in text and adds `vouchScope: "sampled"` to the row in
|
|
1426
|
+
`--json` (additive, omitted for a full vouch; `maturity` stays `vouched`). `loam validate` reports
|
|
1427
|
+
the new warning `sources.sampled-vouch`.
|
|
1428
|
+
- **One new stable code, `sources.sampled-vouch` (warn)** — one finding per sampled spec file,
|
|
1429
|
+
naming k of n, the seed and `last_verified`, from the docs repo as well (it needs no service repo,
|
|
1430
|
+
like `content.stale`).
|
|
1431
|
+
- **Both spec axes count, and the fleet summaries say so.** The sample is per file, so a service
|
|
1432
|
+
whose `arch.spec.md` alone was sampled is one part of whose documentation nobody read:
|
|
1433
|
+
`loam list`, `loam show` and `loam status` grade the service from EITHER axis, and `show` prints
|
|
1434
|
+
the scope per file (with `archSpec.vouch_scope` additive in `--json`).
|
|
1435
|
+
- **A later full vouch clears it.** `loam vouch` with no `--sample` deletes the `vouch_scope` field
|
|
1436
|
+
(the shared frontmatter writer gained an explicit list of keys to remove — deletion is something a
|
|
1437
|
+
call site names, never a null value it happens to hold — leaving the body byte-identical and every
|
|
1438
|
+
neighbouring line in place), so a document that has since been read in full stops reporting as
|
|
1439
|
+
sampled.
|
|
1440
|
+
- **`--sample` composes with `--pack`.** `loam vouch --pack --sample <n>` prints the reading list
|
|
1441
|
+
for the sampled vouch that follows, through the same seeded derivation — so the pack and the stamp
|
|
1442
|
+
can never prescribe different sections — and prints only those sections in place of the full
|
|
1443
|
+
heading list.
|
|
1444
|
+
|
|
1445
|
+
### Added — `loam vouch --pack`: the re-vouch reading pack
|
|
1446
|
+
|
|
1447
|
+
- **New flag `loam vouch --pack [--service <id>] [--json]`** — a re-vouch now starts from a printed
|
|
1448
|
+
delta instead of a full re-read: for each spec-axis file (spec.md, and arch.spec.md when present)
|
|
1449
|
+
the pack prints (a) the file's git diff from the last commit in the DOCS repo's history whose body
|
|
1450
|
+
hashes to the stamped `content_digest`, with a section-level summary (changed/added/removed
|
|
1451
|
+
headings), (b) the source files that moved against the stamped `sources_files` index —
|
|
1452
|
+
added/changed/removed paths in the stale finding's own one-column layout — and (c) the sections
|
|
1453
|
+
whose text is identical to the vouched ancestor's, listed under `vouched_by`/`last_verified` so
|
|
1454
|
+
the read can stop where it should.
|
|
1455
|
+
- **`--pack --yes` is refused** (`invalid-option`, exit 1): `--pack` is the reading list and `--yes`
|
|
1456
|
+
the unattended stamp, and a pack that immediately stamped would defeat the read it just
|
|
1457
|
+
prescribed.
|
|
1458
|
+
|
|
1459
|
+
### Added — `loam verify --diff-answers`: mechanical cross-examination of two blind answer sets
|
|
1460
|
+
|
|
1461
|
+
- **New flag `loam verify <FEAT> --diff-answers a.json b.json [--service <id>] [--json]`** — a
|
|
1462
|
+
read-only lens that validates BOTH answer sets with the same `checkAnswers` discipline the record
|
|
1463
|
+
path uses, against the same derived checklist, then reports per claim who agrees with whom:
|
|
1464
|
+
`cross.agree-confirmed`, `cross.agree-unconfirmed`, `cross.disagree` — joined by deterministic
|
|
1465
|
+
claim id only, never by claim text — plus the `cross.evidence-disjoint` warn notice naming
|
|
1466
|
+
agreed-confirmed claims whose cited FILE sets do not overlap (a trailing `:line` or `:start-end`
|
|
1467
|
+
suffix is normalized away, so two citations into one file never read as disjoint).
|
|
1468
|
+
- **Agreement is not verification, structurally**: the payload deliberately carries no `verified`,
|
|
1469
|
+
`verdict` or `attested` key, `verification.yaml` is untouched (byte-identical before and after, no
|
|
1470
|
+
docs lock taken, works while a writer holds one), and nothing this flag does can upgrade
|
|
1471
|
+
`attested` to `verified` — two agents agreeing (usually two contexts of the same model, so
|
|
1472
|
+
correlated) ranks where a reviewer reads first, it proves nothing.
|
|
1473
|
+
- **Refusals reuse existing codes and name WHICH file**: `answers-unreadable` / `answers-mismatch` /
|
|
1474
|
+
`answers-unevidenced` prefixed by "first/second answer set (`<file>`)"; `invalid-option` for a
|
|
1475
|
+
combination with `--record`/`--results`/`--contract-results`, for an arity other than exactly two
|
|
1476
|
+
files, and for an archived feature (frozen history has no current checklist to answer).
|
|
1477
|
+
|
|
1478
|
+
### Added — evidence pins: federated records stamp what was cited, and `loam validate` convicts drift
|
|
1479
|
+
|
|
1480
|
+
- **`loam verify <FEAT> --service <id> --record` now stamps `evidence_pins` under each
|
|
1481
|
+
agent-confirmed claim** — one pin per `file:line` citation: the cited file's sha256 at the
|
|
1482
|
+
attested commit (CRLF-normalized, so a Windows checkout's line endings can never read as drift),
|
|
1483
|
+
the cited line's text (trimmed, capped at 200 characters), and the literal token the claim asserts
|
|
1484
|
+
(the operationId, message name or edge op), carried structurally so nothing ever parses claim
|
|
1485
|
+
prose — stamped only when the attested blob actually contains it, so the validate-side token lint
|
|
1486
|
+
can only ever convict a disappearance, never re-litigate an absence the record-time notice already
|
|
1487
|
+
reported once.
|
|
1488
|
+
- **One new record-time notice, `verify.evidence-token-missing`** (warn, in the existing
|
|
1489
|
+
`notices[]`, text and `--json` alike): a just-confirmed citation's file at the attested commit
|
|
1490
|
+
does not contain the claim's own token.
|
|
1491
|
+
- **`loam validate`, run in a service's own repository, re-checks the pins** of every ACTIVE feature
|
|
1492
|
+
record that service has attested and reports the new `evidence.*` finding family: ok
|
|
1493
|
+
`evidence.checked` / `evidence.unpinned`, warn `evidence.unresolved` / `evidence.moved` /
|
|
1494
|
+
`evidence.line-changed` / `evidence.token-missing`, plus warn `evidence.record-unreadable` for a
|
|
1495
|
+
verification.yaml that exists but cannot be read — a hand edit that breaks the record must surface
|
|
1496
|
+
here as a finding, not as a silently green run.
|
|
1497
|
+
- **No new command, no new flag, no new `ErrorCode`, no exit-code change**; the `--json` additions —
|
|
1498
|
+
`evidence_pins` inside the read and frozen views' `claims[]`, the notice, the findings — are
|
|
1499
|
+
additive payload keys.
|
|
1500
|
+
|
|
1501
|
+
### Added — `loam list --needs-work --review-order`: the blast-radius review queue
|
|
1502
|
+
|
|
1503
|
+
- **`loam list --needs-work` gains `--review-order`** — the adoption worklist sorted by blast
|
|
1504
|
+
radius: for each service, the number of DISTINCT other services that depend on it — drawing a call
|
|
1505
|
+
edge into it (with or without an `op`; a delivery edge carrying `consumes` is not a call, because
|
|
1506
|
+
on the event spine the arrow follows the message), subscribing to a message it produces via a
|
|
1507
|
+
drawn `consumes` edge, or carrying a living `Consumes:` requirement line naming a message its
|
|
1508
|
+
asyncapi.yaml declares `action: send` for.
|
|
1509
|
+
|
|
1510
|
+
### Changed — `loam status` walks the first hour of an empty docs repo
|
|
1511
|
+
|
|
1512
|
+
- **`loam status` in a docs repo with zero services and zero features now teaches the first hour
|
|
1513
|
+
instead of reporting it clean.** Three new stable `next[]` codes, in order:
|
|
1514
|
+
`next.author-landscape` (also emitted when `architecture/landscape.likec4` is still the scaffold's
|
|
1515
|
+
untouched bytes, not only when it is absent — the step names the file and the literal command),
|
|
1516
|
+
`next.bind-service` (wire a service repo's `loam.json` to this fleet) and `next.adopt-first`
|
|
1517
|
+
(write the first baseline).
|
|
1518
|
+
|
|
1519
|
+
### Added — `loam diff`: semantic branch diff of the docs repo, with the victims named
|
|
1520
|
+
|
|
1521
|
+
- **New command `loam diff --base <ref> [--json]`** — the fleet-meaningful delta between the living
|
|
1522
|
+
docs and a base git ref of the docs repo (a branch, `origin/main`, a commit sha): services
|
|
1523
|
+
added/removed, living requirements added/removed/modified (by `Requirement-ID:`/heading identity
|
|
1524
|
+
and content digest, so a `loam rebase` pin never reads as a change), OpenAPI operations
|
|
1525
|
+
added/removed/newly-deprecated, AsyncAPI messages added/removed, and cross-service joins that
|
|
1526
|
+
appeared or went away.
|
|
1527
|
+
- **Fourteen new stable finding codes, zero new `ErrorCode` union members**:
|
|
1528
|
+
`diff.service-added`/`-removed`, `diff.requirement-added`/`-removed`/`-modified`,
|
|
1529
|
+
`diff.op-added`/`-removed`/`-removed-consumed`/`-deprecated`,
|
|
1530
|
+
`diff.message-added`/`-removed`/`-removed-consumed`, `diff.consumer-added`/`-removed` — documented
|
|
1531
|
+
in the generated AGENTS.md's command map.
|
|
1532
|
+
- **loam now asks read-only git questions in the DOCS repo** (`rev-parse`/`ls-tree`/`show` — no
|
|
1533
|
+
checkout, no temp writes, every child process bounded by the same timeout and output caps
|
|
1534
|
+
provenance uses).
|
|
1535
|
+
- **`loam mcp` gains `loam_diff`** — the read-tool table grows to eleven; the tool schema marks
|
|
1536
|
+
`base` required (the table now models required flags, mirrored against commander's own
|
|
1537
|
+
`requiredOption` by the shape test), and a call without it is refused as invalid params before any
|
|
1538
|
+
argv is built.
|
|
1539
|
+
|
|
1540
|
+
### Added — `loam verify --contract-results`: contract-test reports answer the `api.exposes` claims
|
|
1541
|
+
|
|
1542
|
+
- **New flag `loam verify <FEAT> --contract-results <file>`** — answer the `api.exposes` claims
|
|
1543
|
+
mechanically from an API contract-test report, the way `--results` answers the `scenario.tested`
|
|
1544
|
+
claims from a cucumber one.
|
|
1545
|
+
- **Records gain a `contractReport:` pin and `answered_by: external-runner`** — the consumed
|
|
1546
|
+
report's path, sha256, mtime, distinct-operation count and format land on the record (all-at-once)
|
|
1547
|
+
or inside the recording service's attestation (federated, pruned with its answers, resolved inside
|
|
1548
|
+
the attesting repository and bound to the attested commit exactly as `report:` is), and each
|
|
1549
|
+
contract-confirmed claim says an external runner answered it (`[contract]` on the printed line).
|
|
1550
|
+
- **The verdict ladder does not move** — a contract report can never answer a `scenario.tested`
|
|
1551
|
+
claim, so `attested` versus `verified` still turns on scenario claims alone; a contract-confirmed
|
|
1552
|
+
`api.exposes` claim counts toward `verified` exactly as an agent-confirmed one always has, with
|
|
1553
|
+
strictly more provenance behind it.
|
|
1554
|
+
- **Ownership mirrors `--results`, contested operations included**: under the flag the contract
|
|
1555
|
+
report owns every `api.exposes` claim in scope — an answers-file entry for one refuses
|
|
1556
|
+
`answers-mismatch`, a mechanical flag alone refuses while other claims are outstanding, an
|
|
1557
|
+
unreadable or unrecognizable report refuses `answers-unreadable` naming the accepted shape, and
|
|
1558
|
+
the flag refuses on archived features exactly as `--record` does. An operationId that more than
|
|
1559
|
+
one service on the checklist exposes is `verify.digest-contested`'s case on this axis — a report
|
|
1560
|
+
entry names no service, so those claims stay `unconfirmed` and the run carries the new
|
|
1561
|
+
**`verify.operation-contested`** notice (warn, gating nothing, an open-vocabulary notice code
|
|
1562
|
+
exactly as `verify.claims-open` was); `--service` resolves it.
|
|
1563
|
+
|
|
1564
|
+
### Added — `loam mcp`: the read commands as MCP tools over stdio
|
|
1565
|
+
|
|
1566
|
+
- **New command `loam mcp`** — a hand-rolled MCP stdio server (JSON-RPC 2.0, newline-delimited,
|
|
1567
|
+
protocol revision 2025-06-18 negotiated down to 2025-03-26/2024-11-05) for hosts that reach tools
|
|
1568
|
+
through MCP rather than a shell.
|
|
1569
|
+
- **One machine contract, not two**: every tool result carries the command's `--json` envelope
|
|
1570
|
+
VERBATIM in `content[0].text` (and parsed again as `structuredContent`), with `isError` following
|
|
1571
|
+
the envelope's `ok` — so `ok`, `error.code` and every payload key mean exactly what they mean at
|
|
1572
|
+
the CLI, and a refusal (`no-config`, `unknown-target`, …) arrives as a readable envelope with
|
|
1573
|
+
`isError: true`, not as a dead call.
|
|
1574
|
+
- **No new stable codes, no envelope changes, no new dependency** — the server is hand-rolled on
|
|
1575
|
+
`node:stream` and the existing commander wiring; `@modelcontextprotocol/sdk` was considered and
|
|
1576
|
+
declined (three runtime dependencies is a product decision).
|
|
1577
|
+
|
|
1578
|
+
### Added — `loam gate`: can-i-deploy, answered from recorded evidence
|
|
1579
|
+
|
|
1580
|
+
- **New command `loam gate [--service <id>] [--strict] [--json]`** — a can-i-deploy-shaped PURE
|
|
1581
|
+
QUERY for deploy pipelines outside loam's lifecycle: it executes nothing, writes nothing, takes no
|
|
1582
|
+
lock, and answers only from evidence previous runs recorded.
|
|
1583
|
+
- **Four new stable finding codes, all in the open finding vocabulary — zero new `ErrorCode` union
|
|
1584
|
+
members.** `gate.service-undocumented` (error): the gated service itself sits below `documented`,
|
|
1585
|
+
so the docs cannot say what its joins are; re-running succeeds once `loam adopt`'s required
|
|
1586
|
+
artifact set exists — and under a map that cannot be read it fires only when the service is below
|
|
1587
|
+
`documented` regardless of whether an API is owed, because the api question is then unanswerable
|
|
1588
|
+
and one unanswerable fact yields one finding.
|
|
1589
|
+
- **The `--json` payload** carries `verdict`, `strict`, `landscape` (`read`/`absent`/`invalid`),
|
|
1590
|
+
`partners[]` (maturity rung, consumer/provider role, the joining operations and messages,
|
|
1591
|
+
`external`), `features[]` (the record's own tallies and three-valued verdict per touching feature)
|
|
1592
|
+
and `checks[]` (findings by check, in the shared finding shape) — so a pipeline wanting a stricter
|
|
1593
|
+
bar (vouched partners, no attested scenarios) branches on data rather than waiting for a flag.
|
|
1594
|
+
- **No new ErrorCode strings, no changed envelope fields, and no change to what gates the archive**:
|
|
1595
|
+
verify still never gates the merge, archive still reads no verification record — `loam gate`'s
|
|
1596
|
+
verdict is advice to a deploy pipeline loam does not own (WORKFLOW.md's "What actually gates" now
|
|
1597
|
+
says so beside the other two gates).
|
|
1598
|
+
|
|
1599
|
+
### Added — `loam context`: one service's docs slice as one deterministic briefing
|
|
1600
|
+
|
|
1601
|
+
- **New command `loam context <service>`** — assemble, for one service, the exact docs slice bound
|
|
1602
|
+
to it as one payload an agent loads before working in that service's repository: the living
|
|
1603
|
+
requirements and arch requirements VERBATIM (bodies and Given/When/Then lines, with the
|
|
1604
|
+
`Operations:`/`Covers:`/`Requires:`/`Capability:`/`Publishes:`/`Consumes:` joins parsed out beside
|
|
1605
|
+
them), the OpenAPI operations with the requirements governing each (`x-loam-remove` filtered), the
|
|
1606
|
+
AsyncAPI messages with their send/receive direction, the fleet edges one hop out with the map's
|
|
1607
|
+
own health beside them (the `landscape` key carries `present` and `parses`, so silence is evidence
|
|
1608
|
+
rather than absence), the `Requires:` permissions resolved against `architecture/permissions.yaml`
|
|
1609
|
+
(an undeclared entry is carried with `declared: false`, never refused), the capabilities the
|
|
1610
|
+
service realizes — with both fleet vocabularies' own health beside their sections, so
|
|
1611
|
+
`declared: false` under an unreadable vocabulary reads as "nobody could look" — maturity and
|
|
1612
|
+
frontmatter provenance, presence pointers for runbook/health/ADRs, and every ACTIVE feature's
|
|
1613
|
+
delta over the service in `loam delta`'s own payload shapes, each ending with the runnable
|
|
1614
|
+
`loam delta <FEAT> --service <id>` line.
|
|
1615
|
+
- **Exit 1 with `ok: true` when the pack has a silent hole** — a landscape that is present but does
|
|
1616
|
+
not parse, an unreadable living `openapi.yaml`/`asyncapi.yaml`, an unreadable fleet vocabulary
|
|
1617
|
+
(permissions or capabilities), another service's spec file the capability rollup could not read
|
|
1618
|
+
(degraded per file into the `capabilitiesUnread` payload key, never a refusal — one service's
|
|
1619
|
+
briefing is not hostage to a sibling's encoding), or an included feature whose `delta.likec4` has
|
|
1620
|
+
errors or whose contract delta does not parse — and a feature whose delta does not parse is still
|
|
1621
|
+
INCLUDED in every service's pack with its errors in the payload, because a change nobody can read
|
|
1622
|
+
is in flight, not absent.
|
|
1623
|
+
- **Internal relocation**: the delta projection helpers (`apiChanges`, `eventChanges`, `archSlice`,
|
|
1624
|
+
`introducedServices`, `livingServices`) moved from `src/commands/delta/slices.ts` to the new
|
|
1625
|
+
`src/core/projection/` package, and `stripFrontmatter` to `src/core/document/frontmatter.ts`, the
|
|
1626
|
+
day the pack became their second caller — commands do not import commands, so the shared
|
|
1627
|
+
projection now lives in core.
|
|
1628
|
+
- **`/loam-implement` gains the pack as its load step** — `loam context $2 --feature $1 --json`
|
|
1629
|
+
right after `loam status`, before `loam delta`: the delta is what changes, the pack is what it
|
|
1630
|
+
changes into, and reading it first is how a MODIFIED requirement lands as an edit rather than a
|
|
1631
|
+
rewrite.
|
|
1632
|
+
|
|
1633
|
+
### Added — `loam open`: an editor workspace derived from the committed bindings
|
|
1634
|
+
|
|
1635
|
+
- **New command `loam open`** — writes a `.code-workspace` file joining the docs repo and every
|
|
1636
|
+
service checkout whose committed `loam.json` binds to it.
|
|
1637
|
+
- A sibling whose `loam.json` exists but cannot be read is reported in `skipped[]` — never fatal,
|
|
1638
|
+
and never a member, because an unreadable binding proves nothing; a sibling bound to a different
|
|
1639
|
+
docs repo is silently not a member.
|
|
1640
|
+
|
|
1641
|
+
### Added — the fleet scorecard
|
|
1642
|
+
|
|
1643
|
+
- **`loam validate --all --json` gains one additive payload key, `scorecard`** — ceiling-vs-actual
|
|
1644
|
+
aggregates for the whole fleet, recomputed per invocation from the reads the run already makes and
|
|
1645
|
+
never stored: `services` with the maturity rollup (every rung present), `provenance` (vouched
|
|
1646
|
+
services · stale source digests · sources unverifiable from here — the latter two counted off this
|
|
1647
|
+
run's own findings by subject, so each is the same number the report prints beside them),
|
|
1648
|
+
`verification` (recorded records — the features axis's `active` count is their one shared
|
|
1649
|
+
denominator — the three-valued verdicts, and `claims.answered` — the confirmed claims split by
|
|
1650
|
+
their own `answered_by`, one count per provenance with all three keys always present: `runner` (a
|
|
1651
|
+
digest-matched cucumber run), `external-runner` (an operationId-matched contract report) and
|
|
1652
|
+
`agent` (somebody's word, and every record too old to carry the field)), `operations` (defined →
|
|
1653
|
+
governed · deprecated, plus deprecated operations something still joins to), `messages` (defined →
|
|
1654
|
+
linked), `c4` (the fleet map's drawn systems, by the map's own census — actors, externals,
|
|
1655
|
+
groupings and containers excluded — → covered by an arch requirement) and `features` (active, with
|
|
1656
|
+
each feature's fleet stage over all five stage keys).
|
|
1657
|
+
- **`loam validate --all` text output appends a "fleet scorecard" table** after the summary footer —
|
|
1658
|
+
one line per axis, ceiling before actual.
|
|
1659
|
+
|
|
1660
|
+
### Added — inverse coverage on the event axis, and the partial-verification honesty line
|
|
1661
|
+
|
|
1662
|
+
- **New stable finding code `event.ungoverned`** (warn) — `loam validate --service` / `--all` now
|
|
1663
|
+
warns, per living asyncapi.yaml, when declared message(s) are named by no requirement's
|
|
1664
|
+
`Publishes:`/`Consumes:` line, listing the orphaned names.
|
|
1665
|
+
- **New verify notice `verify.claims-open`** (warn) — `loam verify`'s read view and the frozen
|
|
1666
|
+
post-archive view now carry a one-line honesty summary whenever a record EXISTS and leaves claims
|
|
1667
|
+
unconfirmed or unanswered: "not a clean result", with all four counts and the agent's-word share
|
|
1668
|
+
of the confirmed.
|
|
1669
|
+
- **WORKFLOW.md gains "The honestly-small change"** — the walkthrough for a one-service,
|
|
1670
|
+
requirements-only change: scaffold with `--touches`, delete the scaffolded C4 delta on the
|
|
1671
|
+
scaffold's own printed advice, author intent.md and one spec delta, and ship down the three-step
|
|
1672
|
+
`next[]` (`next.generate-tests`, `next.verify`, `next.archive`), with `loam status` printing "(not
|
|
1673
|
+
written — none owed)" beside every artifact the change legitimately skips.
|
|
1674
|
+
- No new command, flag, exit code, or envelope key; both new codes are open-vocabulary
|
|
1675
|
+
finding/notice codes, exactly as `api.ungoverned` and `verify.scenario-attested` were before them.
|
|
1676
|
+
|
|
1677
|
+
### Added — the LikeC4 canary
|
|
1678
|
+
|
|
1679
|
+
- **A weekly scheduled workflow** (`.github/workflows/likec4-canary.yml`, manual dispatch included)
|
|
1680
|
+
**installs `likec4@latest` over the exact 1.59.2 lockfile pin and runs the LikeC4-touching suites
|
|
1681
|
+
against it**, then the committed 120-service benchmark as informational evidence in the run's step
|
|
1682
|
+
summary.
|
|
1683
|
+
|
|
1684
|
+
### Documentation
|
|
1685
|
+
|
|
1686
|
+
- **The product comparison now follows OpenSpec v1.10.0** (released 2026-08-19) while the certified
|
|
1687
|
+
parser/migration corpus stays pinned at the v1.9.0 commit
|
|
1688
|
+
`2826b8889e5223a9a8095d4428b60b56597e1020` — v1.10.0 changed no requirement-Markdown format
|
|
1689
|
+
upstream, and COMPARISON.md, README.md and MIGRATING-from-OpenSpec.md now name the divergence as
|
|
1690
|
+
deliberate.
|
|
1691
|
+
|
|
1692
|
+
### Added — the shipped documentation is now guarded like a contract
|
|
1693
|
+
|
|
1694
|
+
- **package.json, the release preflight, and the smoke share one reviewed package-file list**
|
|
1695
|
+
(`scripts/package-docs.mjs`): `files[]` set-inequality with the reviewed list is now a release
|
|
1696
|
+
blocker and an `npm test` failure, and every reviewed page must be relatively linked from README —
|
|
1697
|
+
adding a shipped document is deliberately a three-place edit, and the blocker names all three
|
|
1698
|
+
places.
|
|
1699
|
+
- **New drift guards run inside `npm test`** (test/docs-facts.test.ts, test/package-docs.test.ts):
|
|
1700
|
+
counted facts in the shipped pages must match the live tree or sit under ROADMAP's dated
|
|
1701
|
+
assessment snapshot; the README command table is set-equal to the built program's commands;
|
|
1702
|
+
SCHEMA's `architecture/permissions.yaml` example is parsed through the real vocabulary reader,
|
|
1703
|
+
pinning the documented `owned_by`/`enforced_by`/`description` spellings; every dotted backticked
|
|
1704
|
+
token in the shipped pages must be a stable code loam actually emits, a verify claim kind, or a
|
|
1705
|
+
reasoned allowlist entry; and each documented known gap is paired with the roadmap sentence that
|
|
1706
|
+
owns closing it, so shipping a roadmap item fails the gate until the gap prose leaves with it.
|
|
1707
|
+
- No new command, flag, exit code, envelope key, or stable code; nothing in `dist/` changed.
|
|
1708
|
+
|
|
1709
|
+
### Added — the capability axis
|
|
1710
|
+
|
|
1711
|
+
- **New fleet document `architecture/capabilities.yaml`** — shape
|
|
1712
|
+
`capabilities: {<id>: {description, owner}}`, both leaf fields optional (`{}` is a legal
|
|
1713
|
+
declaration); ids are flat keys with `/` allowed for nesting (`payments/refunds` stays one key,
|
|
1714
|
+
never collapsed).
|
|
1715
|
+
- **New requirement-grammar line `Capability:`** (also accepted as `Capabilities:`) — a
|
|
1716
|
+
comma-separated LIST, legal in both spec files, living and delta alike, parsed beside
|
|
1717
|
+
`Operations:`/`Covers:`/`Requires:` with the same keep-last quirk.
|
|
1718
|
+
- **New stable finding code `capability.unknown`** (error) — a `Capability:` entry that the
|
|
1719
|
+
vocabulary does not declare, with close-name suggestions; graded on living spec.md/arch.spec.md at
|
|
1720
|
+
the service target and on a feature's deltas of both via coherence.
|
|
1721
|
+
- **New IssueCode `capability.unknown`** — the delta-side shape of the same rule **gates
|
|
1722
|
+
`loam archive`** (not-coherent), overridable with `--approve`; the only new archive gate in this
|
|
1723
|
+
item.
|
|
1724
|
+
- **New stable finding code `capability.invalid`** (error) — the file exists but does not read as a
|
|
1725
|
+
vocabulary; reported exactly once per `validate --all` run (single-target runs stay silent about
|
|
1726
|
+
the file), suppressing the rest of the family, because a hundred findings about one broken file is
|
|
1727
|
+
a cascade rather than a diagnosis.
|
|
1728
|
+
- **New stable finding code `capability.unrealized`** (warn) — a declared capability no living
|
|
1729
|
+
non-`REMOVED` requirement names; one finding per capability, subject = the id.
|
|
1730
|
+
- **`loam list capabilities`** — new explicit-only section (the no-argument default and every
|
|
1731
|
+
existing section's payload are byte-unchanged); new additive `--json` payload key `capabilities`
|
|
1732
|
+
with deterministic, diff-stable ordering (rows by id, `realizedBy` by service/file/requirement,
|
|
1733
|
+
`statuses` keys sorted).
|
|
1734
|
+
- **`loam explore --capability <id>`** — new repeatable flag seeding the exploration from a declared
|
|
1735
|
+
capability's realizing services; every miss (undeclared, or declared and realized by nothing)
|
|
1736
|
+
lands in the new additive payload field `unresolvedCapabilities` — explore still refuses nothing.
|
|
1737
|
+
- **`migrate-openspec --apply` preserves capability identity**: the staged target declares the union
|
|
1738
|
+
of living and active-horizon OpenSpec capability ids in `architecture/capabilities.yaml` (empty
|
|
1739
|
+
bodies — no description is invented, the authored `## Purpose` prose stays verbatim under
|
|
1740
|
+
`legacy/`), and every routed requirement — living, delta and rename-materialized alike — carries a
|
|
1741
|
+
`Capability: <id>` line.
|
|
1742
|
+
|
|
1743
|
+
### Added — subsystems: `services/` may now be a tree, and no identity notices
|
|
1744
|
+
|
|
1745
|
+
Nested directories under `services/` are now legal: a directory marked by a `subsystem.yaml` is a
|
|
1746
|
+
**subsystem** and is walked, at any depth.
|
|
1747
|
+
- **Three-way classification, the third branch a refusal.** A directory under `services/` holding
|
|
1748
|
+
`subsystem.yaml` is a subsystem (walked); one holding any service artifact is a service (not
|
|
1749
|
+
walked deeper); one holding **neither** while containing subdirectories is the new error
|
|
1750
|
+
`subsystem.unmarked`, naming every service stranded beneath it — those services stay enumerated
|
|
1751
|
+
and counted, so the fleet is never silently smaller (the deleted-marker merge race).
|
|
1752
|
+
- **`subsystem.yaml` is a marker, not a manifest** — optional `title`, `description`, `owner`; never
|
|
1753
|
+
members (SCHEMA.md, "Subsystems").
|
|
1754
|
+
- **The tree is mirrored into a GENERATED views file, never into the authored landscape.**
|
|
1755
|
+
`architecture/subsystems.likec4` holds LikeC4 views only — no model, no tags, no `specification` —
|
|
1756
|
+
one view per subsystem enumerating every service beneath it (members resolved to landscape
|
|
1757
|
+
*element* ids through the same `metadata { service }`-then-title join every check uses; a member
|
|
1758
|
+
nothing models is omitted, which `landscape.service-unmodelled` already reports). New fleet-scope
|
|
1759
|
+
error **`subsystem.views-stale`** in `validate --all`: the file's bytes do not match the tree —
|
|
1760
|
+
exactly one finding on exactly one file, graded by byte comparison (nothing in loam ever parses
|
|
1761
|
+
the generated file), fixed by the one command.
|
|
1762
|
+
- **New command `loam subsystem <verb>`** — `new`, `move`, `rename`, `rm`, `list`, `history`,
|
|
1763
|
+
`sync`; options `--into`, `--under`, `--title`, `--description`, `--owner`, `--json`.
|
|
1764
|
+
- **`loam subsystem move <name>... --into <sub|.>` is ONE transaction over N directory renames plus
|
|
1765
|
+
the generated views file** — the first consumer of the crash journal's new moves half.
|
|
1766
|
+
- **`loam adopt --subsystem <name>`** — the brief's artifact paths land inside the named group, so
|
|
1767
|
+
an adoption need not land unfiled and cost a second command.
|
|
1768
|
+
- **`loam list` gains additive `--json` keys**: `services[].subsystem` (the path as a string array,
|
|
1769
|
+
`[]` = unfiled), top-level `subsystems[]` (`name`, `path`, `title`, transitive `memberCount`) and
|
|
1770
|
+
`unfiledServices` (a count — unfiled is permanent and normal, so it is never a finding).
|
|
1771
|
+
- **`subsystem move`/`rename` notice old snapshots.** When an archived feature holds a version-2
|
|
1772
|
+
snapshot addressing a directory being moved by its literal pre-move path, the move prints a notice
|
|
1773
|
+
(JSON: additive `warnings` key) naming the feature — `loam unarchive <FEAT> --force` after the
|
|
1774
|
+
move would restore into the old location, resurrecting the pre-move directory beside the moved
|
|
1775
|
+
one.
|
|
1776
|
+
|
|
1777
|
+
### Changed — archive snapshots are re-keyed by service id (manifest version 2 → 3)
|
|
1778
|
+
|
|
1779
|
+
The undo snapshot's manifest (`features/archive/<dir>/.loam-before/manifest.json`) bumps to
|
|
1780
|
+
**version 3**: every entry under `services/` now carries `(service, artifact)` — the service id and
|
|
1781
|
+
the artifact's path inside the service directory — beside the as-archived literal `path`, and
|
|
1782
|
+
`loam unarchive` resolves the write target through the **current** services enumeration, falling
|
|
1783
|
+
back to the literal path when the id is not enumerated.
|
|
1784
|
+
|
|
1785
|
+
### Added — the AsyncAPI feature lifecycle
|
|
1786
|
+
|
|
1787
|
+
- **File-format contract** (SCHEMA.md): a feature changes an async contract through
|
|
1788
|
+
`features/<FEAT>/specs/<svc>/asyncapi.yaml` — a complete AsyncAPI 3.0 document.
|
|
1789
|
+
- **`loam rebase` pins asyncapi slots**: every slot a feature's asyncapi.yaml restates is pinned
|
|
1790
|
+
against the living contract; `rebase --json`'s `pins[]` entries may now carry
|
|
1791
|
+
`file: "asyncapi.yaml"` with `kind` = the section name and `target` = the slot key (new values in
|
|
1792
|
+
existing fields — no envelope key changes).
|
|
1793
|
+
- **New validate finding `asyncapi.remove-marker-living`** (error): a living asyncapi.yaml carrying
|
|
1794
|
+
`x-loam-remove: true` — at ANY depth: a slot, an inline channel message, or anywhere else one
|
|
1795
|
+
leaked (the document root, `info`), the sweep exactly as deep as the strip — names where it sits,
|
|
1796
|
+
the `openapi.remove-marker-living` discipline on the event axis.
|
|
1797
|
+
- **The coherence gate grades the event axis** (`validate --feature` and the archive gate alike,
|
|
1798
|
+
since the gate consumes coherence issues wholesale). Three strings validate already emitted joined
|
|
1799
|
+
the IssueCode union unchanged: `asyncapi.invalid` (now also fired for a FEATURE's own
|
|
1800
|
+
asyncapi.yaml, suspending that service's event checks and gating archive),
|
|
1801
|
+
`asyncapi.ref-unresolved`, and `spec-event.message-undefined` (now also graded on delta
|
|
1802
|
+
requirement lines in feature scope). **What a `--json` consumer notices.** The event axis grades
|
|
1803
|
+
two things that need no asyncapi.yaml at all: a feature delta's tagged edges carrying
|
|
1804
|
+
`metadata { publishes }` / `{ consumes }` (`c4-event.message-undefined`) and a delta requirement's
|
|
1805
|
+
`Publishes:`/`Consumes:` lines (`spec-event.message-undefined`).
|
|
1806
|
+
- **The merge is verdict-driven, per slot** (`channels.<key>` / `operations.<key>` /
|
|
1807
|
+
`components.messages.<key>`): a pinned QUOTE is never a merge input — not even under `--approve` —
|
|
1808
|
+
so a change that landed on the living contract after the delta was written survives an archive
|
|
1809
|
+
that merely restates the slot (the lost-update case); an EDIT overwrites; a `stale` pin still
|
|
1810
|
+
writes, because reaching the merge at all means `--approve` said to, and the plan names what it
|
|
1811
|
+
cost.
|
|
1812
|
+
- **New stable plan-warn codes**, each with a /loam-check fix-table row:
|
|
1813
|
+
`asyncapi.message-modified`, `asyncapi.channel-modified`, `asyncapi.operation-modified` — the
|
|
1814
|
+
delta redefines a slot the living AsyncAPI already has and the merge overwrites it wholesale, the
|
|
1815
|
+
event axis's mirror of `openapi.op-modified`'s family.
|
|
1816
|
+
- **`asyncapi.ref-unresolved` is now also a gating plan issue** (it stays a validate warn on living
|
|
1817
|
+
contracts): a `$ref` the MERGED document would carry that resolves in neither the feature's
|
|
1818
|
+
asyncapi.yaml nor the living one — including a removal that deletes a slot the living document
|
|
1819
|
+
still references — refuses the archive at `not-coherent` unless `--approve`.
|
|
1820
|
+
- **New gating plan code `asyncapi.remove-marker-inline`** (error, `--approve` overrides, with a
|
|
1821
|
+
/loam-check fix-table row): an `x-loam-remove: true` nested on an INLINE channel message
|
|
1822
|
+
(`channels.<ck>.messages.<mk>`).
|
|
1823
|
+
- **`archive --json` gains the additive `asyncapiRemovals` key** (`[{service, slots}]`, the labels
|
|
1824
|
+
of living slots the plan deletes), beside `openapiRemovals`.
|
|
1825
|
+
- **New verify claim kind `event.declares`** in `CLAIM_KINDS` (the `verification.yaml` value
|
|
1826
|
+
vocabulary), after `api.exposes` in the checklist's story order: one claim per genuinely NEW
|
|
1827
|
+
(direction, message) a feature's asyncapi delta declares relative to the living contract's
|
|
1828
|
+
send/receive sets — `"<svc> declares it <sends|receives> message '<name>'"`.
|
|
1829
|
+
- **`delta --json` gains the additive per-service `events` key** —
|
|
1830
|
+
`{changes: [{slot, message, direction, remove}], unreadable, error?}`, the event-axis mirror of
|
|
1831
|
+
the `api`/`openapi` pair: every message declaration in the feature's asyncapi.yaml with the slot
|
|
1832
|
+
it lives in, the direction the document's own operations give it (`send`/`receive`/null), and its
|
|
1833
|
+
removal marker; the text view prints the same rows. **An unreadable feature asyncapi.yaml now
|
|
1834
|
+
makes `loam delta` exit 1** exactly as the openapi path does — the brief is an implementation
|
|
1835
|
+
task, and "no event work here" over a YAML error is work silently dropped.
|
|
1836
|
+
- **`loam status` sees the event axis.** The feature artifact table gains an `asyncapi` row per
|
|
1837
|
+
touched service (additive `--json` value in the `ARTIFACT_IDS` vocabulary; never required — an
|
|
1838
|
+
event contract is genuinely optional, so absence is `done` like arch.spec.md), the `asyncapi.*` /
|
|
1839
|
+
`c4-event.*` / `spec-event.*` finding families now turn their responsible artifact row `draft`
|
|
1840
|
+
(asyncapi.yaml, delta.likec4 and the spec delta respectively), a feature whose findings include
|
|
1841
|
+
`asyncapi.baseline-missing` gets a `next.rebase` step naming `loam rebase`, and a blocked
|
|
1842
|
+
verification's `blockedBy` list names `asyncapi` among the feeders it waits on.
|
|
1843
|
+
- **The authoring surfaces teach the axis**: `loam new`'s spec template comment documents the
|
|
1844
|
+
`Publishes:`/`Consumes:` requirement lines and when to hand-create `specs/<svc>/asyncapi.yaml`
|
|
1845
|
+
(nothing is scaffolded — the axis's absence-grading rests on the contract being optional), and the
|
|
1846
|
+
`/loam-feature` workflow carries the event-axis authoring step beside the openapi one, rebase
|
|
1847
|
+
pinning included.
|
|
1848
|
+
|
|
1849
|
+
### Changed — `validate --all` parses the fleet's C4 documents in one workspace
|
|
1850
|
+
|
|
1851
|
+
`loam validate --all` now parses the run's C4 documents — every service model, every active
|
|
1852
|
+
feature's delta, and the landscape — in **one** LikeC4 workspace instead of spinning up one
|
|
1853
|
+
workspace per document: measured **~18.8x faster** (13.7s → 0.73s median) on the committed
|
|
1854
|
+
120-service benchmark, with peak memory roughly halved (docs/BENCHMARKS.md).
|
|
1855
|
+
|
|
1856
|
+
### Changed — a restated component or path-level key can no longer silently revert a landed change
|
|
1857
|
+
|
|
1858
|
+
The operation pin's discipline now reaches the two surfaces it deliberately did not: path-item
|
|
1859
|
+
non-method keys (`parameters`, `servers`, `summary`, `x-*`) and every `components/<kind>/<name>`.
|
|
1860
|
+
The merge skips quoted surfaces exactly as it skips quoted operations, and component copying is
|
|
1861
|
+
verdict-driven: **archives that used to exit 0 while a restated component or path-level key
|
|
1862
|
+
overwrote another feature's landed change now refuse until `loam rebase <FEAT>` runs or `--approve`
|
|
1863
|
+
says the overwrite is meant.** `loam validate` moves with it: a feature whose delta restates a
|
|
1864
|
+
path-level key or a component another change has since moved now fails where it passed, on the same
|
|
1865
|
+
`openapi.baseline-stale`, before any archive is attempted.
|
|
1866
|
+
|
|
1867
|
+
### Changed — the architecture rules became one executable gate
|
|
1868
|
+
|
|
1869
|
+
- **New contributor gate `npm run arch:check`** runs every architecture check the design docs state
|
|
1870
|
+
— file-level import cycles, the package graph, the core→commands ban (named and type-only imports;
|
|
1871
|
+
a bare side-effect or dynamic import is not seen), the barrel ban, the console/process boundary
|
|
1872
|
+
with `core/envelope/json.ts` as the one named exception, the child-process timeout/`maxBuffer`
|
|
1873
|
+
policy, and brand-cast containment — each with a negative self-test in `test/arch-gate.test.ts`.
|
|
1874
|
+
- **Under `--json`, an unreadable loam.json no longer prints a stray line on stderr.** The
|
|
1875
|
+
`config-invalid` envelope message now carries the actual parse problem (stdout stays the single
|
|
1876
|
+
machine-readable stream); in text mode the same sentence prints from the command layer.
|
|
1877
|
+
- **`loam verify --record`'s git questions are bounded** — a 10-second deadline whose refusal says
|
|
1878
|
+
the deadline fired (a blocking credential helper used to hang the record forever), and a
|
|
1879
|
+
deliberate 64 MiB output cap in place of Node's implicit 1 MiB that refused sound evidence over 1
|
|
1880
|
+
MiB.
|
|
1881
|
+
- **Validated identities are branded types end to end.** Feature ids, docs directories and feature
|
|
1882
|
+
directories join service ids: the smart constructors in `core/kernel/ids/` are the only bridge,
|
|
1883
|
+
the path builders in `core/repo/paths.ts` refuse raw strings at compile time, and `arch:check`
|
|
1884
|
+
fails a brand cast outside the constructor modules.
|
|
1885
|
+
|
|
1886
|
+
### Changed — every multi-file writer now commits through a journaled, crash-consistent transaction
|
|
1887
|
+
|
|
1888
|
+
`loam archive` and `loam unarchive` have long committed through a lock, a byte-level
|
|
1889
|
+
compare-and-swap, a snapshot and a fsynced `.loam-commit` journal.
|
|
1890
|
+
- **`loam rebase`** journals its pin writes.
|
|
1891
|
+
- **`loam vouch`** now takes the docs lock for its commit window (the roadmap called this lock
|
|
1892
|
+
missing) — the slow half, reading specs and hashing sources, stays unlocked so independent
|
|
1893
|
+
services' vouches do not serialise — and journals the spec.md/arch.spec.md pair.
|
|
1894
|
+
- **`loam new`** builds its entire scaffold in memory, then commits it under the lock as one set of
|
|
1895
|
+
exclusive creates.
|
|
1896
|
+
- **`loam gherkin`** — the writer the roadmap named, a plain write/delete loop in the service repo —
|
|
1897
|
+
commits through staging, the lock and the journal at **`<gherkinDir>/loam/.loam-lock`** /
|
|
1898
|
+
**`.loam-commit`**, two new transient dotfiles in the directory loam already owns.
|
|
1899
|
+
- **`loam verify --record`** recovers (or refuses **`commit-interrupted`**) under its held lock
|
|
1900
|
+
before the authoritative read — the last docs-repo writer that could still silently write over a
|
|
1901
|
+
half-commit.
|
|
1902
|
+
- **`loam validate` leads every mode with the new error finding `docs.commit-interrupted`** while a
|
|
1903
|
+
`.loam-commit` sits in the docs repo: a half-merged tree graded green would certify bytes no run
|
|
1904
|
+
produced, and in CI that green merges.
|
|
1905
|
+
- **`loam doctor` covers the service repo too:** when loam.json names a service, the same residue
|
|
1906
|
+
scan runs over `<gherkinDir>/loam/` and grades with the same `doctor.docs-locked` /
|
|
1907
|
+
`doctor.commit-interrupted` / `doctor.commit-unreadable` / `doctor.staging-temps` codes; the
|
|
1908
|
+
report gains an additive `serviceWritePath` key.
|
|
1909
|
+
- **Recovery is reported, not silent:** any journaled writer that first rolls a predecessor's commit
|
|
1910
|
+
forward carries an additive `recovered` key in its `--json` payload and says so in the human view
|
|
1911
|
+
— docs changing beyond the command's own writes would otherwise read as its doing.
|
|
1912
|
+
- **`recovered.feature` and `status`'s `interrupted.feature` no longer always name a feature.** When
|
|
1913
|
+
the journal being recovered was left by `loam vouch` or `loam gherkin`, the field holds the
|
|
1914
|
+
**service** id those commands were committing for, and `recovered.command` / `interrupted.command`
|
|
1915
|
+
can now read `rebase`, `vouch`, `new` or `gherkin` where they only ever read `archive` or
|
|
1916
|
+
`unarchive`.
|
|
1917
|
+
- **`loam doctor --json`'s `writePath.intent` now has two shapes.** A journal from the smaller
|
|
1918
|
+
transaction reads `{version: 2, command, rerun, target, …}` — no `feature`, and each file carries
|
|
1919
|
+
the staged `tmp` beside its digests.
|
|
1920
|
+
- **`loam gherkin --dry-run` recovers before it plans** — archive's own dry-run precedent.
|
|
1921
|
+
- A journal written by this loam and found by an older one grades as `doctor.commit-unreadable`
|
|
1922
|
+
(fail-safe, human-directed) — worth knowing where fleets pin loam per repo.
|
|
1923
|
+
|
|
1924
|
+
### Changed — recording a verification is now atomic, locked, and compare-and-swap
|
|
1925
|
+
|
|
1926
|
+
- **`loam verify --record` / `--results` takes the docs repo's advisory lock and commits the record
|
|
1927
|
+
atomically.** The record used to be one plain `writeFile` from an unlocked read, which lost work
|
|
1928
|
+
three ways: two services recording one feature concurrently could read the same pre-image and the
|
|
1929
|
+
last writer silently discarded the other's attestation; a process killed mid-write left the only
|
|
1930
|
+
record truncated (and every later `--record` refusing on `record-unreadable`); and an edit landing
|
|
1931
|
+
between the read and the write was buried. A kill mid-record leaves the previous record whole plus
|
|
1932
|
+
a staging temp that `loam doctor` already names under its write-path residue.
|
|
1933
|
+
- **The git questions `verify --record` asks now have a deadline and an output cap.** Every `git`
|
|
1934
|
+
call on the record path (`rev-parse`, `diff`, `show`) runs while the docs lock is held, and none
|
|
1935
|
+
of them was bounded: a git blocked on a credential-helper prompt hung the record forever with the
|
|
1936
|
+
lock in hand, wedging every `archive`, `rebase` and other `--record` in the fleet behind a live
|
|
1937
|
+
pid that stale-lock breaking rightly refuses to touch.
|
|
1938
|
+
- **A staging failure while recording answers `merge-failed`, not `internal`.** A read-only feature
|
|
1939
|
+
directory or a full disk at the staging step escaped as the one code with no stable meaning; it is
|
|
1940
|
+
now the same refusal archive gives the identical condition — nothing was recorded, the record is
|
|
1941
|
+
as the last writer left it.
|
|
1942
|
+
- **`loam doctor` grades a `.loam-lock` that cannot name its holder as damage, and exits 1 where it
|
|
1943
|
+
exited 0.** An empty or unparseable lock file — what a crash inside a lock create used to leave,
|
|
1944
|
+
or an editor's accident — got the warning "wait for it to finish", advice that can never work:
|
|
1945
|
+
stale-lock breaking rightly refuses to interpret a lock it cannot read, so nothing was ever going
|
|
1946
|
+
to release it while every writer refused `docs-busy` forever. **What a `--json` consumer
|
|
1947
|
+
notices:** the finding keeps its `doctor.docs-locked` code but arrives with `severity: "blocker"`,
|
|
1948
|
+
so `healthy` is `false` and a preflight that passed over such a lock now fails.
|
|
1949
|
+
- **A `verification.yaml` that is a symlink to a target that does not exist now refuses
|
|
1950
|
+
`record-unreadable`, naming the broken link.** The read side used to call it absent while the
|
|
1951
|
+
commit's exclusive create saw the link itself and refused — so every `--record` answered
|
|
1952
|
+
`record-raced` with advice (re-run) that could never work.
|
|
1953
|
+
|
|
1954
|
+
### Changed — the OpenSpec compatibility baseline moves to v1.9.0, and the gate that backs it runs again
|
|
1955
|
+
|
|
1956
|
+
- **`audit-openspec` and `migrate-openspec` now report v1.9.0 as the certified release baseline.**
|
|
1957
|
+
The banner line and `baselines.release` under `--json` carry `1.9.0` /
|
|
1958
|
+
`2826b8889e5223a9a8095d4428b60b56597e1020` (2026-08-13, and the tip of `main` when pinned) instead
|
|
1959
|
+
of `1.7.0` / `4e16790`.
|
|
1960
|
+
|
|
1961
|
+
### Fixed
|
|
1962
|
+
|
|
1963
|
+
- **The OpenSpec corpus gate had not run for eight days: it failed at module resolution, not at a
|
|
1964
|
+
corpus.** `scripts/check-openspec-corpus.ts` imported `../src/core/spec.js`, which moved into
|
|
1965
|
+
`src/core/document/` when core's leaves became packages, so every scheduled and manual invocation
|
|
1966
|
+
died with `ERR_MODULE_NOT_FOUND` while three documents pointed at it as the evidence for the
|
|
1967
|
+
compatibility claim.
|
|
1968
|
+
|
|
1969
|
+
### Documentation
|
|
1970
|
+
|
|
1971
|
+
- Added the evidence-backed `ROADMAP.md`, updated the product comparison to OpenSpec v1.9 while
|
|
1972
|
+
preserving the exact v1.7 compatibility baseline, documented
|
|
1973
|
+
AsyncAPI/authorization/response-governance joins, and reconciled release, contributor,
|
|
1974
|
+
security and generated-agent guidance with the implemented contracts.
|
|
1975
|
+
|
|
1976
|
+
### Added — the decision layer gets somewhere to live: outlines, an authorization vocabulary, and a walk that asks for both
|
|
1977
|
+
|
|
1978
|
+
- **A markdown table in a scenario body is now a `Scenario Outline`.** `loam gherkin` emits the
|
|
1979
|
+
table as `Examples` — one row per case, columns padded, deterministic to the byte — instead of
|
|
1980
|
+
dropping it into the scenario description where cucumber ran it as one vacuous scenario.
|
|
1981
|
+
- **`architecture/permissions.yaml` — the fleet's authorization vocabulary — and the `Requires:`
|
|
1982
|
+
line that joins to it.** A third spine beside `Operations:` and `Covers:`, because neither can
|
|
1983
|
+
carry a permission: the cardinality is wrong for an operation, a permission is not a C4 element,
|
|
1984
|
+
and OpenAPI's `security` carries permission NAMES only for oauth2/openIdConnect — the spec
|
|
1985
|
+
requires an empty scope array for `http bearer` and `apiKey`, which is what most of a legacy fleet
|
|
1986
|
+
authenticates with. Three codes: **`permissions.unknown`** (error — an entry the vocabulary does
|
|
1987
|
+
not declare; an ERROR where its `Covers:` sibling is a warning, because an invented permission
|
|
1988
|
+
reads exactly like a real one in the requirement, in the generated scenario and in the test
|
|
1989
|
+
somebody then writes from it), **`permissions.invalid`** (the file exists and does not read as a
|
|
1990
|
+
vocabulary — reported alone, since grading every `Requires:` line against it would be a cascade
|
|
1991
|
+
rather than a diagnosis), and **`permissions.unenforced`** (warn — a declared permission no
|
|
1992
|
+
requirement anywhere in the fleet names, the mirror of `api.ungoverned`).
|
|
1993
|
+
- **`api.response-ungoverned` (warn) — a declared 4xx/5xx response no scenario reaches.**
|
|
1994
|
+
`api.ungoverned` grades whole OPERATIONS against requirements, so an endpoint with one happy-path
|
|
1995
|
+
requirement and twelve declared failure codes was fully governed by every check loam had — and the
|
|
1996
|
+
refusals are precisely where a service's decision layer surfaces.
|
|
1997
|
+
- **`sources` is described as digest input rather than a reading list.** Nobody follows those paths
|
|
1998
|
+
and nothing asks them to — the value is that `vouch` hashes their content so a later `validate`
|
|
1999
|
+
can say the code moved.
|
|
2000
|
+
|
|
2001
|
+
### Fixed
|
|
2002
|
+
|
|
2003
|
+
- **An optional `arch.spec.md` could refuse an entire `--all` run.** The new fleet-wide `Requires:`
|
|
2004
|
+
sweep read both requirement documents of every service without checking existence first, and
|
|
2005
|
+
`FleetContext.readRequirements` throws ENOENT — which surfaced as `repository-unavailable` with no
|
|
2006
|
+
targets and nothing validated.
|
|
2007
|
+
|
|
2008
|
+
### Changed — three archives that used to exit 0 now refuse, because each one was a silent loss
|
|
2009
|
+
|
|
2010
|
+
- **Unpinned deltas gate the archive.** `delta.baseline-missing` and `openapi.baseline-missing` keep
|
|
2011
|
+
their warn severity — `loam validate` stays green, the documents are legal — but both now carry
|
|
2012
|
+
`gates`, so `loam archive` (dry run included) refuses instead of merging.
|
|
2013
|
+
- **A scaffold nobody edited cannot archive.** Two new gating warnings from
|
|
2014
|
+
`loam validate --feature`, `loam status` and the archive gate: **`scaffold.placeholder`** — a
|
|
2015
|
+
feature document still carries `loam new`'s exact template text (a `TODO — name the behaviour`
|
|
2016
|
+
requirement, a `TODO — name the case` scenario, an `<angle-bracket>` fill-in, a
|
|
2017
|
+
`TODO — what this service owns` description) — and **`intent.empty`** — intent.md is missing or
|
|
2018
|
+
says nothing outside the scaffold's own comments.
|
|
2019
|
+
- **A feature's own broken openapi.yaml is now named.** `validate --feature` reports
|
|
2020
|
+
**`openapi.invalid`** (error — the same code the living contract gets) when a feature's
|
|
2021
|
+
`specs/<svc>/openapi.yaml` exists but does not parse, and suspends the service's contract-axis
|
|
2022
|
+
checks instead of grading against the empty parse — which used to skip every baseline pin and
|
|
2023
|
+
removal marker in the broken file silently and false-fire `spec-api.op-undefined` against
|
|
2024
|
+
requirements whose operations the unreadable file defined.
|
|
2025
|
+
|
|
2026
|
+
### Fixed — the fleet map's containers are visible to the checks that guard removals and ordering
|
|
2027
|
+
|
|
2028
|
+
- The element→service resolver was called without the enumerated fleet in exactly the places that
|
|
2029
|
+
are repository-aware: the removal gate's consumer scan, the feature dependency graph, coherence's
|
|
2030
|
+
edge grading, arch coverage and its `Covers:` matcher, the verify checklist, and `loam delta`'s
|
|
2031
|
+
projection.
|
|
2032
|
+
|
|
2033
|
+
### Fixed — edge metadata loam parsed but did not keep
|
|
2034
|
+
|
|
2035
|
+
- **`loam archive` dropped an event binding at exit 0.** What makes two C4 edges the same edge
|
|
2036
|
+
accounted for `metadata { op }` and ignored `metadata { publishes }` and `metadata { consumes }`
|
|
2037
|
+
entirely.
|
|
2038
|
+
|
|
2039
|
+
### Added — fleet-shape and contract-depth checks
|
|
2040
|
+
|
|
2041
|
+
These checks are specified with repository-owned synthetic docs fixtures and compare artifacts loam
|
|
2042
|
+
already parses.
|
|
2043
|
+
- **`landscape.platform-candidate` (warn)** — an `#external` element consumed by three or more
|
|
2044
|
+
distinct services and not tagged `#platform`.
|
|
2045
|
+
- **`landscape.datastore-private` / `landscape.datastore-shared` (warn)** — a fleet-level `database`
|
|
2046
|
+
element graded by its consumer count.
|
|
2047
|
+
- **`c4.no-relationships` (warn)** — a parsed model with elements and zero relationships, when its
|
|
2048
|
+
own evidence says it should reach something: more than one nested element, or dependencies
|
|
2049
|
+
declared in this service's health.yaml.
|
|
2050
|
+
- **`health.dependency-unmodelled` (warn)** — every `dependencies:` id in health.yaml must resolve
|
|
2051
|
+
in the service's OWN `model.likec4`, by element id, `metadata { service }` binding, or title
|
|
2052
|
+
(exact match, did-you-mean hints, no case folding).
|
|
2053
|
+
- **The contract-depth probes** — form validated and depth did not: an operation whose one response
|
|
2054
|
+
is `description: OK`, a message whose payload is bare `type: object`, and a `$ref` to a schema
|
|
2055
|
+
nobody wrote all validated exactly as cleanly as complete contracts. Four warns close it:
|
|
2056
|
+
**`openapi.response-undescribed`** (no response declares a schema while at least one should —
|
|
2057
|
+
204/304 excepted), **`asyncapi.payload-undescribed`** (a payload declaring no shape at all; a
|
|
2058
|
+
non-JSON `schemaFormat` is never judged, so the Avro migration stays a document change), and
|
|
2059
|
+
**`openapi.ref-unresolved` / `asyncapi.ref-unresolved`** (internal `$ref`s to nothing — on the
|
|
2060
|
+
async axis these used to vanish silently).
|
|
2061
|
+
- **`spine.message-external` (warn), and `spine.message-unproduced` narrows to mean what it says** —
|
|
2062
|
+
a message produced outside the fleet was inexpressible: the landscape said the producer was
|
|
2063
|
+
`#external` and the error fired anyway, so a truthful map cost a red build and the exit ramp was
|
|
2064
|
+
deleting the link.
|
|
2065
|
+
|
|
2066
|
+
### Changed — the adopt protocol makes deployment evidence boundaries explicit
|
|
2067
|
+
|
|
2068
|
+
- **The reproducibility bar is stated**, beside the honest half: `loam validate` grades form and
|
|
2069
|
+
joins, never depth, so green means the files agree with each other — and `unchecked[]` now says
|
|
2070
|
+
nothing measures a shallow baseline against the service.
|
|
2071
|
+
- **`api.covered` carries both counts** — operations governed and living requirements — because the
|
|
2072
|
+
ratio is the one signal that lets a reviewer smell a thin baseline from the summary line.
|
|
2073
|
+
|
|
2074
|
+
### What a `--json` consumer notices, upgrading to this release
|
|
2075
|
+
|
|
2076
|
+
- **Repos that were green can newly show warnings** — the eight codes above, plus `agents.stale`
|
|
2077
|
+
from the version bump itself (and this time the AGENTS.md review is real work: the code tables
|
|
2078
|
+
materially changed). `valid` stays true and plain exit codes stay 0, but **a `--strict` CI job
|
|
2079
|
+
that exited 0 yesterday can exit 1 today**. A freshly migrated OpenSpec repo now reports
|
|
2080
|
+
`openapi.response-undescribed` as its honest state — OpenSpec never carried response schemas.
|
|
2081
|
+
|
|
2082
|
+
- **One red case turns green**: a consumed message whose only producer is an `#external` `publishes`
|
|
2083
|
+
edge no longer errors. Fleets that took the old exit ramp — deleting the link to get green — get
|
|
2084
|
+
no signal to restore it; re-add the edge and the consumption, the map is allowed to be truthful
|
|
2085
|
+
now.
|
|
2086
|
+
|
|
2087
|
+
- **`landscape.matched` no longer appears** on a fleet where any fleet-shape warning fires — a map
|
|
2088
|
+
with a shape warning did not fully agree.
|
|
2089
|
+
|
|
2090
|
+
- No envelope keys changed shape; the new findings ride the existing `findings[]`/`details`
|
|
2091
|
+
contract, and prose (including `api.covered`'s message) was reworded freely as always. A feature's
|
|
2092
|
+
per-service deltas live in `features/<FEAT>/specs/<svc>/`, and that `<svc>` is a directory name
|
|
2093
|
+
somebody typed — but unlike `--service`, nothing ever asked the id grammar about it.
|
|
2094
|
+
`specs/Payment Service/` (with the space) validated green —
|
|
2095
|
+
`✓ Payment Service: requirements covered`, even, because a tagged element whose *title* matched
|
|
2096
|
+
the directory counted as introducing the service — and `loam archive` then exited 0 and
|
|
2097
|
+
materialised `services/Payment Service/`. The very next `loam validate --all` failed it with
|
|
2098
|
+
`service.id-invalid`: a directory loam itself wrote, and one no loam command can address or
|
|
2099
|
+
re-create. New finding: **`delta.service-id-invalid`** (error), from `loam validate --feature`,
|
|
2100
|
+
`loam status`, and the archive gate, naming the `specs/<svc>/` directory and the exact rule the
|
|
2101
|
+
name breaks — the same sentence `service.id-invalid` uses, Windows-reserved stems (`NUL`) and
|
|
2102
|
+
trailing dots (`payments.`) included. `loam archive` refuses on it before anything joins the name
|
|
2103
|
+
into a path, `--dry-run` included, and **`--approve` does not override it**: which service you
|
|
2104
|
+
meant is a judgment call, but a name the grammar refuses is a directory loam can never address,
|
|
2105
|
+
mechanically. The fix is a rename of the specs/ directory. The same guarantee on the architecture
|
|
2106
|
+
axis. A tagged element's explicit `metadata { service '…' }` binding is a name the landscape merge
|
|
2107
|
+
splices into the living `architecture/landscape.likec4` verbatim, and the archive probes
|
|
2108
|
+
`services/<binding>/` with it — `metadata { service '../outside-svc' }` parses in LikeC4 without a
|
|
2109
|
+
single error, validated with nothing but a warning, archived at exit 0, and the `../` collapsed
|
|
2110
|
+
the probe right out of `services/`. The next `validate --all` then failed the very map archive had
|
|
2111
|
+
just written (`landscape.binding-unknown`). New coherence issue: **`c4.service-binding-invalid`**
|
|
2112
|
+
(error), from `loam validate --feature`, `loam status`, and the archive gate, naming the element
|
|
2113
|
+
and the rule its binding breaks; **`--approve` does not override it either**, for the same reason.
|
|
2114
|
+
The check's scope is the splice's, not the tag's: the merge carries a tagged element's authored
|
|
2115
|
+
block into the living landscape byte for byte, untagged children included, so an untagged
|
|
2116
|
+
`container` nested inside a tagged system — `metadata { service '../outside-svc' }` on the child —
|
|
2117
|
+
is held to the same grammar, because its binding reaches the living map exactly as its parent's
|
|
2118
|
+
does. A prose title with no binding stays legal C4 — a title becomes a path only through
|
|
2119
|
+
`specs/<svc>/`, which the finding above now guards. Together the two codes close every text→path
|
|
2120
|
+
route into the archive merge. **What a `--json` consumer notices.** `loam validate --feature` can
|
|
2121
|
+
now exit 1 on repos that used to pass, with `delta.service-id-invalid` or
|
|
2122
|
+
`c4.service-binding-invalid` in `findings[]` — and the same two findings reach
|
|
2123
|
+
`loam validate --all` and the bare positional `loam validate <FEAT>`, which grade every feature
|
|
2124
|
+
through the same check: a repo that exited 0 yesterday exits 1 today, and a CI job running
|
|
2125
|
+
`validate --all` fails naming the feature. The fix is renaming the `specs/<svc>/` directory or
|
|
2126
|
+
editing the `metadata { service }` binding, never a flag. `loam archive` refuses both with the
|
|
2127
|
+
envelope code `not-coherent` and the offending finding in `issues[]`, whether or not `--approve`
|
|
2128
|
+
was passed; a refusal `--approve` cannot move now also says "--approve does not override this" in
|
|
2129
|
+
`error.message`, and every issue `archive --json` emits (`issues[]`, `warnings[]`, `overridden[]`)
|
|
2130
|
+
carries a new additive `overridable` key — whether `--approve` can move it — so a consumer
|
|
2131
|
+
branches on data instead of keeping its own code list. One ordering change rides along: the
|
|
2132
|
+
illegal `specs/<svc>/` name is refused ahead of the conflict-marker scan, so a feature carrying
|
|
2133
|
+
both defects reports `not-coherent` where it used to report `merge-failed`. A `services/<bad>/` a
|
|
2134
|
+
previous archive already created is repaired by renaming — the `service.id-invalid` message spells
|
|
2135
|
+
the three places the old name is written.
|
|
2136
|
+
|
|
2137
|
+
### Fixed — coherence grading resolves an edge's target before it builds a path
|
|
2138
|
+
|
|
2139
|
+
The coherence check behind `loam validate --feature`, `loam status` and the archive gate joined an
|
|
2140
|
+
op-edge's *declared* target — the element's `metadata { service '…' }` binding, or its title —
|
|
2141
|
+
straight into `services/<target>/` to read the living spec, the living OpenAPI and the deprecation
|
|
2142
|
+
flags. **What a `--json` consumer notices.** Nothing, unless a feature's delta declared a target
|
|
2143
|
+
that never matched a `services/` or `specs/` directory *and* the joined path landed on a real file:
|
|
2144
|
+
those repos can newly see `c4-api.op-undefined` (error, gates archive) and `c4.op-ungoverned` (warn)
|
|
2145
|
+
where the out-of-repo file used to satisfy the check.
|
|
2146
|
+
|
|
2147
|
+
### Fixed — `loam validate` resolves a service name before it builds a path
|
|
2148
|
+
|
|
2149
|
+
`loam validate --service <name>` and `loam validate <name>` both spelled
|
|
2150
|
+
`<docsDir>/services/<name>/` out of whatever the caller typed, and neither ever checked it.
|
|
2151
|
+
`loam validate --service ../../outside/services/x` therefore resolved **above the docs repo**, and
|
|
2152
|
+
where a `spec.md` happened to sit at that path loam opened it, graded it, and reported its
|
|
2153
|
+
frontmatter back through `--json`.
|
|
2154
|
+
|
|
2155
|
+
### Added — `loam adopt` states the walk, and `validate` grades it
|
|
2156
|
+
|
|
2157
|
+
- **`walk[]` in the brief** — nine ordered stops, each naming what to open, what to take from it,
|
|
2158
|
+
and which artifacts it feeds (`lands`).
|
|
2159
|
+
- **`sources.unwalked` (warn) / `sources.walked` (ok)** — the walk graded against the repository,
|
|
2160
|
+
reported by `loam validate --service <id>` run inside the service's own repo.
|
|
2161
|
+
|
|
2162
|
+
### Added — how to draw a shared broker, before the fleet map becomes a star
|
|
2163
|
+
|
|
2164
|
+
A broker modelled as ONE element is the node every service in the fleet points at: a map that read
|
|
2165
|
+
fine at five services is a star with sixty spokes at sixty, and nothing warns on the way there,
|
|
2166
|
+
because loam parses the model and renders no view.
|
|
2167
|
+
|
|
2168
|
+
## [0.1.0-beta.3] - 2026-08-08
|
|
2169
|
+
|
|
2170
|
+
Still a prerelease under the `beta` dist-tag; the 5–10 service pilot has not run yet. The entries
|
|
2171
|
+
below describe implementation behavior and repository-owned synthetic fixtures; they claim no
|
|
2172
|
+
production-fleet or external execution evidence. One breaking change, in `loam vouch`; read that
|
|
2173
|
+
section before upgrading a CI job.
|
|
2174
|
+
|
|
2175
|
+
### Changed — `loam vouch` now records a person, and refuses when it cannot
|
|
2176
|
+
|
|
2177
|
+
**This is a breaking change for any script or CI job that runs `loam vouch`: add `--yes`.**
|
|
2178
|
+
|
|
2179
|
+
`vouch` is the one command in loam whose output is a claim about a human act — everything else it
|
|
2180
|
+
checks is internal consistency, which fluent prose satisfies on its own. Its own docblock called it
|
|
2181
|
+
"the one command that records a person", and it recorded none: no identity of any kind, no
|
|
2182
|
+
interactive gate. Run twice, unattended, from a script, it stamped `status: verified` both times.
|
|
2183
|
+
Meanwhile every generated command and skill file pre-approved `Bash(loam:*)`, which handed the agent
|
|
2184
|
+
that wrote a draft the power to promote its own draft. That inverts the argument loam makes about
|
|
2185
|
+
test evidence everywhere else — an agent must not be able to *say* a scenario is tested — by letting
|
|
2186
|
+
one say a spec matches the code.
|
|
2187
|
+
|
|
2188
|
+
Three changes close it, and none of them is a signature:
|
|
2189
|
+
|
|
2190
|
+
- **`vouched_by` is stamped beside `status` and `last_verified`**, taken from git —
|
|
2191
|
+
`GIT_COMMITTER_*`/`GIT_AUTHOR_*` first and then `user.name`/`user.email`, which is git's own
|
|
2192
|
+
precedence, so a CI job and a laptop resolve it the same way. It is a name, not a proof: git
|
|
2193
|
+
config is a text file. What it buys is that a reviewer can ask a *specific person* what they read,
|
|
2194
|
+
which is the question `status: verified` was silently answering with nobody. It rides `--json` as
|
|
2195
|
+
`vouched_by`, and a re-vouch refreshes it.
|
|
2196
|
+
- **`--yes`, and a confirmation when there is a terminal.** Without `--yes`, `vouch` refuses
|
|
2197
|
+
`vouch-unattended` when stdin is not a TTY, and in `--json` mode regardless (a question cannot be
|
|
2198
|
+
asked on a stream whose contract is one JSON document). On a terminal it states what is about to
|
|
2199
|
+
be claimed and takes only an explicit yes; `vouch-declined` is the answer when the person says no.
|
|
2200
|
+
`vouch-unattributable` is the refusal when git can name nobody at all — better than stamping an
|
|
2201
|
+
anonymous claim.
|
|
2202
|
+
- **The generated `allowed-tools` allowlist names loam's verbs one by one** —
|
|
2203
|
+
`Bash(loam adopt:*), Bash(loam validate:*), …` — and deliberately not `vouch`. A verb missing from
|
|
2204
|
+
the list is not forbidden, only unapproved: the agent asks, and a person answers. That is the
|
|
2205
|
+
correct cost for this one, and it is what makes the refusal something a human sees rather than
|
|
2206
|
+
something an agent routes around.
|
|
2207
|
+
|
|
2208
|
+
New error codes: `vouch-unattended`, `vouch-unattributable`, `vouch-declined`. Existing specs
|
|
2209
|
+
carrying `status: verified` with no `vouched_by` are not re-graded — nothing new fires on them — but
|
|
2210
|
+
the next vouch stamps a name.
|
|
2211
|
+
|
|
2212
|
+
### Fixed — a docs repo that could be cloned, and rendered
|
|
2213
|
+
|
|
2214
|
+
- **`loam init --create` writes `services/.gitkeep` and `features/.gitkeep`.** Both directories were
|
|
2215
|
+
created empty; git tracks files, not directories, so after the first push neither existed for
|
|
2216
|
+
anyone who cloned. A missing `services/` is a *blocker* in `doctor`, so the second person to touch
|
|
2217
|
+
the repo got a red preflight on a repository the first person left green.
|
|
2218
|
+
- **`loam init --create` writes `likec4.config.json`, and the docs repo is a loadable LikeC4
|
|
2219
|
+
workspace again.** loam parses every `.likec4` file *alone*, so the landscape, each
|
|
2220
|
+
`services/<id>/model.likec4` and each `features/<FEAT>/delta.likec4` legitimately declares its own
|
|
2221
|
+
`specification { … }` block and re-declares the elements it names. LikeC4's own loader merges the
|
|
2222
|
+
whole tree into one model, so pointing `npx likec4 start` at the repo root — the command loam's
|
|
2223
|
+
own brief recommends — reported every one of those declarations as a duplicate: on loam's
|
|
2224
|
+
four-file `examples/docs`, 16 errors from the renderer and 0 from `loam validate --all`. The
|
|
2225
|
+
scaffolded config declares the root as one project scoped to `architecture/`; a service model or
|
|
2226
|
+
feature delta renders from its own directory (`npx likec4 start services/<id>`), which is what
|
|
2227
|
+
parsing them in isolation meant all along. `doctor` reports `doctor.likec4-config-missing`
|
|
2228
|
+
(warning) on a docs repo created before this, with the exact file to write in its `fix`.
|
|
2229
|
+
|
|
2230
|
+
### Fixed — three commands that answered the wrong question
|
|
2231
|
+
|
|
2232
|
+
- **`loam status` now sees the repository it is standing in.** With `loam.json` naming a service the
|
|
2233
|
+
docs repo has no directory for — a freshly wired service repo, the most common repo there is — the
|
|
2234
|
+
fleet count made "nothing is in flight and every service is written down" vacuously true, and
|
|
2235
|
+
`next.fleet-clean` came out on top. `doctor` had the right answer (`doctor.service-unknown`) at
|
|
2236
|
+
the same moment, and the documented agent loop reads `status --json` and runs `next[0]`: an agent
|
|
2237
|
+
following it was sent to start a feature instead of adopting the service under its feet. The new
|
|
2238
|
+
step is **`next.adopt-bound`**, first in `next[]` (behind only `next.recover-commit`), and
|
|
2239
|
+
suppressed under an explicit `--service`, which is a question about a different service.
|
|
2240
|
+
- **`doctor.service-unbound` is no longer raised inside the docs repo.** Having no service binding
|
|
2241
|
+
there is the correct state — the docs repo is the fleet, not any one service — and the `fix` it
|
|
2242
|
+
printed (`loam init --service <id>` here) would have made the repo wrong. `currentService` still
|
|
2243
|
+
reports `unbound`; only the advice is gone.
|
|
2244
|
+
- **`loam instructions` checks the arguments it substitutes.** `loam instructions loam-adopt "$PWD"`
|
|
2245
|
+
rendered a protocol reading `services//Users/someone/work/svc/` — a page of confident instructions
|
|
2246
|
+
built around a value no loam command accepts. Each workflow now declares what its `$1`, `$2`, …
|
|
2247
|
+
stand for, and a service-id or feature-id placeholder is checked against the same grammar
|
|
2248
|
+
`loam adopt` and `loam new` use before anything is printed (`invalid-option`). An *unsupplied*
|
|
2249
|
+
placeholder is still left standing, and a free-text one (a feature title) is still free.
|
|
2250
|
+
|
|
2251
|
+
### Fixed — `doctor.agent-files-stale` and `agents.stale` across a prerelease
|
|
2252
|
+
|
|
2253
|
+
`versionTrails` compared only the numeric triple, so `0.1.0-beta.1` neither trailed nor outranked
|
|
2254
|
+
`0.1.0-beta.2`. But beta.2 is precisely the release that changed the *form* of every generated file
|
|
2255
|
+
— embedded protocol text became a pointer at `loam instructions` — and its own changelog told
|
|
2256
|
+
readers to delete the files and re-run `loam init`. The one upgrade that most needed the warning was
|
|
2257
|
+
the one bump shape that could never raise it. Prerelease identifiers are now compared per semver:
|
|
2258
|
+
numerically where they are numbers, a prerelease behind its own final release, and a longer
|
|
2259
|
+
identifier list ahead of a prefix of itself. A stamp whose version is not valid semver still reads
|
|
2260
|
+
as no stamp at all, unchanged.
|
|
2261
|
+
|
|
2262
|
+
### Added — the async contract axis (AsyncAPI 3), phase one
|
|
2263
|
+
|
|
2264
|
+
Kafka was already drawable in the fleet map and describable in prose, and that was all: a
|
|
2265
|
+
`paymentService -> kafka 'Publishes PaymentAuthorized'` edge read as fully documented while naming
|
|
2266
|
+
no contract anybody could check. The event axis gives it the treatment the HTTP axis has — an
|
|
2267
|
+
artifact, a join token, and findings — in a first slice that reads and grades but does not yet
|
|
2268
|
+
merge.
|
|
2269
|
+
|
|
2270
|
+
- **`services/<svc>/asyncapi.yaml`** — the async contract, sibling of `openapi.yaml`, optional and
|
|
2271
|
+
adopt-seeded. **AsyncAPI 3.0 only**: its operations are named top-level objects carrying
|
|
2272
|
+
`action: send|receive`, which is the exact analog of an `operationId`, where 2.x spells the same
|
|
2273
|
+
thing as `publish`/`subscribe` under a channel and leaves whose perspective they take ambiguous. A
|
|
2274
|
+
2.x document declares no `operations` and reads as a contract with no messages rather than as a
|
|
2275
|
+
mis-parsed one. No new runtime dependency: the document is read with the `yaml` parser already
|
|
2276
|
+
present, by the same shallow structural walk `core/openapi.ts` uses.
|
|
2277
|
+
- **The join token is a message name**, spelled three times and required to be identical: a
|
|
2278
|
+
landscape edge's `metadata { publishes '...' }` / `metadata { consumes '...' }`, a requirement's
|
|
2279
|
+
`Publishes:` / `Consumes:` line, and the message's `name` in `asyncapi.yaml` (its declaration key
|
|
2280
|
+
when it has no `name`).
|
|
2281
|
+
- **loam never reads inside a message's `payload`.** It joins on names and on the `action` of the
|
|
2282
|
+
operation carrying them, and nothing else. Write payloads as JSON Schema; a fleet that later
|
|
2283
|
+
adopts Avro changes a `schemaFormat` line in its documents and nothing in loam. Keep payloads
|
|
2284
|
+
inside the document rather than `$ref`-ing an external `.avsc` — external references are out of
|
|
2285
|
+
scope here exactly as on the OpenAPI axis.
|
|
2286
|
+
- **New finding codes**, all from `loam validate --service <id>`: `service.no-asyncapi`,
|
|
2287
|
+
`asyncapi.invalid`, `asyncapi.duplicate-message`, `spine.message-undefined`,
|
|
2288
|
+
`spec-event.message-undefined`, `spine.message-unproduced`, `asyncapi.message-contested`,
|
|
2289
|
+
`event.messages-unlinked`, and the `ok` confirmation `event.covered`. No existing code changed,
|
|
2290
|
+
and no refusal that used to succeed now fails: every one of these fires only on a file or a
|
|
2291
|
+
`metadata` key that did not exist before this release.
|
|
2292
|
+
- **`spine.message-unproduced` is the check with no HTTP analog**, and it is why the axis exists. On
|
|
2293
|
+
the API axis the provider owns the contract, so "does this operation exist" is answered inside one
|
|
2294
|
+
service's directory. An event's schema lives in the *producer's* repository, so "does anybody
|
|
2295
|
+
publish what I consume" is a fleet question — and a consumer joined to nothing was previously
|
|
2296
|
+
indistinguishable from a healthy one. `asyncapi.message-contested` is its other half: two services
|
|
2297
|
+
claiming to send one name means every consumer's join picks one arbitrarily.
|
|
2298
|
+
- **`service.no-asyncapi` has two grades, not three.** Its `service.no-openapi` sibling warns when
|
|
2299
|
+
the landscape cannot prove nobody calls a service, because most services expose HTTP. An event
|
|
2300
|
+
contract is genuinely optional and most services touch no topic, so the absent file is an
|
|
2301
|
+
**error** when something already joins into it — the stranded message names ride in `details` —
|
|
2302
|
+
and **silence** otherwise.
|
|
2303
|
+
- `asyncapi.yaml` presence rides in `loam list`/`loam show` (`has.asyncapi`, and a lowercase `e` in
|
|
2304
|
+
the text flags, beside the arch spec's `a`). It is deliberately **not** part of the maturity
|
|
2305
|
+
ladder: making it a fourth required artifact would demote every already-`documented` service in
|
|
2306
|
+
the fleet without one byte of their files changing.
|
|
2307
|
+
|
|
2308
|
+
**Not in this slice, and stated so nobody reads the absence as a bug:** a feature cannot yet add a
|
|
2309
|
+
message. There is no `features/<FEAT>/specs/<svc>/asyncapi.yaml`, no merge, no baseline pin, and no
|
|
2310
|
+
`verify` claim on this axis — a message is retired, for now, by editing the living contract in a
|
|
2311
|
+
reviewed PR. Deprecation markers and a removal family are deliberately absent rather than pending:
|
|
2312
|
+
an async contract that only ever grows has nothing to mark, because a consumer reading a topic with
|
|
2313
|
+
lag breaks on a removed field regardless of what the document says about it.
|
|
2314
|
+
|
|
7
2315
|
## [0.1.0-beta.2] - 2026-08-07
|
|
8
2316
|
|
|
9
|
-
Still a prerelease, still under the `beta` dist-tag: the 5–10 service pilot has not run yet, and
|
|
2317
|
+
Still a prerelease, still under the `beta` dist-tag: the 5–10 service pilot has not run yet, and
|
|
2318
|
+
this release changes what `loam init` leaves in a repository, which is exactly the kind of thing a
|
|
2319
|
+
pilot is for. Two new commands, that change to the generated files, and a code-quality pass over
|
|
2320
|
+
`src/` with the defects it turned up. The refactoring half is invisible by construction; what
|
|
2321
|
+
follows is only the part a user can observe.
|
|
10
2322
|
|
|
11
2323
|
### Added
|
|
12
2324
|
|
|
13
|
-
- **`loam explore [<service>...]`** — read the fleet around a change nobody has written down yet,
|
|
14
|
-
|
|
15
|
-
|
|
2325
|
+
- **`loam explore [<service>...]`** — read the fleet around a change nobody has written down yet,
|
|
2326
|
+
and write nothing. It answers the one question `loam new` takes as an argument: which services a
|
|
2327
|
+
feature touches. For each seed it reports the ring one hop out in the fleet map, each service's
|
|
2328
|
+
maturity rung and living operations, who already calls whom, the active features already covering
|
|
2329
|
+
the same services, and the literal `loam new` line the seeds imply. `--op <operationId>` seeds
|
|
2330
|
+
from an operation when you know the call but not who owns it; `--as <FEAT>` names the feature in
|
|
2331
|
+
the suggested line. A seed naming no `services/<id>/` is reported with its near-misses rather than
|
|
2332
|
+
refused — a feature may be introducing that service, and a typo looks identical until you read the
|
|
2333
|
+
list. The neighbours are deliberately **not** folded into the suggested command: loam knows those
|
|
2334
|
+
services are connected, not whether you change them.
|
|
2335
|
+
- **`loam instructions [<workflow>] [args...]`** — print one of the six workflow protocols
|
|
2336
|
+
(`loam-adopt`, `loam-feature`, `loam-implement`, `loam-check`, `loam-verify`, `loam-ship`) with
|
|
2337
|
+
`$1`, `$2` filled in from the arguments given. With no argument it lists them. It is the only
|
|
2338
|
+
command that reads no `loam.json` and no docs repo, deliberately: `loam-adopt`'s own first step is
|
|
2339
|
+
to run `loam init` when there is no config, so it cannot be the step that requires one.
|
|
2340
|
+
- **[WORKFLOW.md](WORKFLOW.md)** — the working protocol as a document: the artifact graph and its
|
|
2341
|
+
five derived states, what actually gates and what only advises, the six workflows, how an agent
|
|
2342
|
+
drives the cycle from `--json`, and why there is no task-list artifact.
|
|
16
2343
|
|
|
17
2344
|
### Changed — what `loam init` writes into a command or skill file
|
|
18
2345
|
|
|
19
|
-
- **A generated file is now a pointer at `loam instructions`, not a copy of the protocol.** It
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
2346
|
+
- **A generated file is now a pointer at `loam instructions`, not a copy of the protocol.** It
|
|
2347
|
+
carries the workflow's purpose and its spine — the verbs, in order — and defers this release's
|
|
2348
|
+
flags, finding codes and fix tables to the binary; a fresh `.claude/commands/` drops from about 53
|
|
2349
|
+
KB to about 11 KB, and the finding-code tables move with it. The protocol itself is unchanged and
|
|
2350
|
+
complete; only its delivery moved. The reason is that the two go stale in opposite directions and
|
|
2351
|
+
only one is fixable: loam never regenerates a generated file (your edits outrank the template, and
|
|
2352
|
+
that is not changing), so a protocol copied into a repository will eventually describe a different
|
|
2353
|
+
loam with total confidence, while a pointer cannot. `doctor.agent-files-stale` still reports a
|
|
2354
|
+
file whose version stamp has fallen behind, and now has almost nothing to be wrong about.
|
|
2355
|
+
- **Nothing rewrites an existing file.** A repository scaffolded by an earlier loam keeps exactly
|
|
2356
|
+
the files it was given, full protocol text and all — re-running `loam init` over it leaves them
|
|
2357
|
+
byte-unchanged. The version stamp is how you tell, and `loam doctor` raises
|
|
2358
|
+
`doctor.agent-files-stale` against them only once this binary's stamp is ahead of theirs. To take
|
|
2359
|
+
the new form, delete the files and re-run `loam init`.
|
|
2360
|
+
- Both new commands reuse the existing refusal codes (`invalid-option`, `unknown-target`). No stable
|
|
2361
|
+
code was added, changed or removed.
|
|
2362
|
+
- **The feature-id grammar has one home.** It was spelled privately in `loam new` and again in the
|
|
2363
|
+
OpenSpec migration; `core/ids.ts` owns it now, so an id one command accepts cannot be an id the
|
|
2364
|
+
other refuses. The only user-visible edge is wording: `migrate-openspec`'s
|
|
2365
|
+
`mapping.feature-id-invalid` message now ends with the same "e.g. FEAT-101 or BUG-42" example
|
|
2366
|
+
every other refusal gives. The code is unchanged.
|
|
23
2367
|
|
|
24
2368
|
### Fixed — commands that wrote or attested on evidence they had not checked
|
|
25
2369
|
|
|
26
|
-
- **`archive` no longer installs an unreadable feature contract as the living one.** A feature
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
2370
|
+
- **`archive` no longer installs an unreadable feature contract as the living one.** A feature
|
|
2371
|
+
`openapi.yaml` that does not parse as an OpenAPI document was planned verbatim into
|
|
2372
|
+
`services/<svc>/openapi.yaml` and reported `ok: true`; every other command reads `readOpenapi`'s
|
|
2373
|
+
`unreadable` flag, and the one that writes was the one that did not. It now refuses with
|
|
2374
|
+
`merge-failed`, naming the file. The same code now answers when the contract is being created
|
|
2375
|
+
rather than merged — that case used to escape as `internal`.
|
|
2376
|
+
- **`verify --record` no longer attests when it could not check the report.** The committed-report
|
|
2377
|
+
binding check tested for git's exit code 1 and nothing else, so a `git diff` killed by a signal —
|
|
2378
|
+
a CI timeout, the OOM killer, a fork failure — skipped the check and wrote the attestation anyway.
|
|
2379
|
+
A run that cannot reach an exit status is now a refusal that says git could not be run to
|
|
2380
|
+
completion.
|
|
2381
|
+
- **An interrupted `archive` can be retried on a docs repo composed of symlinks.** Checking whether
|
|
2382
|
+
the snapshot was stale resolved the manifest's paths through a symlink test meant for writes, so a
|
|
2383
|
+
`services/<svc>/` mounted from a sibling checkout — a layout every walk of the repo follows on
|
|
2384
|
+
purpose — turned every retry into a permanent refusal. That check only reads a file and compares a
|
|
2385
|
+
digest; containment is now judged without resolving the mount. `unarchive`, which writes the
|
|
2386
|
+
pre-image back, deliberately keeps the stricter test.
|
|
2387
|
+
- **A malformed `.loam-before/manifest.json` or `.loam-lock` is answered, not assumed.** Both were
|
|
2388
|
+
read through a cast: well-formed JSON that was not an object slipped past as "a different loam",
|
|
2389
|
+
and a bare `null` threw a `TypeError` out of the refusal itself. They now answer
|
|
2390
|
+
`commit-interrupted` and `docs-busy` respectively — the codes that case always deserved.
|
|
2391
|
+
- **A `spec.md` whose bytes are UTF-16LE is refused on the write path.** The write path's UTF-8
|
|
2392
|
+
guard was a weaker copy of the read path's and missed exactly the file the read path had been
|
|
2393
|
+
hardened against: such a document parsed as zero requirements, so a merge computed over an empty
|
|
2394
|
+
baseline and wrote that back.
|
|
31
2395
|
|
|
32
2396
|
### Fixed — user errors reported as `internal`
|
|
33
2397
|
|
|
34
|
-
- **`list`, `show` and `validate` report `repository-unavailable`** where a filesystem failure
|
|
35
|
-
|
|
36
|
-
|
|
2398
|
+
- **`list`, `show` and `validate` report `repository-unavailable`** where a filesystem failure
|
|
2399
|
+
carries no path — a directory where `spec.md` belongs reports EISDIR with no `path`, which
|
|
2400
|
+
defeated the guard. `status` had this fix; the other three copies of the same catch block did not.
|
|
2401
|
+
- **`loam.json` that cannot be read reports `config-invalid`.** The read sat one line above its own
|
|
2402
|
+
`try`, so a config file that was a directory, or carried a restrictive permission bit, crashed
|
|
2403
|
+
every command in the CLI.
|
|
2404
|
+
- **`loam new` refuses instead of scaffolding into a directory that is not a docs repo**, and
|
|
2405
|
+
**`loam adopt` refuses (`docs-missing`)** instead of briefing an agent to write a baseline into a
|
|
2406
|
+
docs repo that does not exist. Both previously exited 0.
|
|
37
2407
|
|
|
38
2408
|
### Fixed — checks that answered differently depending on where they were asked
|
|
39
2409
|
|
|
40
|
-
- **A relocated operation grades the same under `validate`, `status` and `archive`.** `FleetContext`
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
- **`
|
|
2410
|
+
- **A relocated operation grades the same under `validate`, `status` and `archive`.** `FleetContext`
|
|
2411
|
+
carried a second implementation of `serviceOperationIds` that interleaved removals with upserts,
|
|
2412
|
+
so an operation removed at one path and defined at another answered "undefined" or "defined"
|
|
2413
|
+
depending on which the author spelled first — and `spec-api.op-undefined` is a gating error.
|
|
2414
|
+
- **`c4.op-ungoverned` joins per service.** Operation ids are unique only within a contract, so an
|
|
2415
|
+
operation governed on one service suppressed the warning for an unrelated service that shared its
|
|
2416
|
+
name.
|
|
2417
|
+
- **`api.covered` no longer counts a REMOVED requirement as governing.** An operation whose only
|
|
2418
|
+
governing requirement was removed now reports `api.ungoverned` (a warning; nothing about gating or
|
|
2419
|
+
exit codes changes).
|
|
2420
|
+
- **`loam status` (fleet form) reports `blocked` for a repo holding an interrupted commit journal**,
|
|
2421
|
+
agreeing with the per-feature form, which already did. It previously said `done` and "ship it"
|
|
2422
|
+
over a tree the other form called blocked.
|
|
2423
|
+
- **`loam delta` exits 1 when the feature's `openapi.yaml` does not parse**, matching the rule the
|
|
2424
|
+
architecture axis already had, and its `--json` payload carries an `openapi` key saying so. An
|
|
2425
|
+
unparseable contract used to project as "no operations".
|
|
45
2426
|
|
|
46
2427
|
### Fixed — deletion that reached outside the repository
|
|
47
2428
|
|
|
48
|
-
- **`loam gherkin` orphan cleanup no longer follows a symlinked directory out of the repo.**
|
|
2429
|
+
- **`loam gherkin` orphan cleanup no longer follows a symlinked directory out of the repo.**
|
|
2430
|
+
Enumeration follows symlinks by design, so a planted `<gherkinDir>/loam/sub -> /outside` produced
|
|
2431
|
+
orphan paths that `unlink` resolved and deleted elsewhere. A symlinked `.feature` sitting directly
|
|
2432
|
+
in `loam/` is still removed — the link goes, its target survives — which is the distinction the
|
|
2433
|
+
guard now draws.
|
|
49
2434
|
|
|
50
2435
|
### Changed — internal structure
|
|
51
2436
|
|
|
52
|
-
No behaviour depends on this, and it is recorded because the next contributor will notice it: the
|
|
2437
|
+
No behaviour depends on this, and it is recorded because the next contributor will notice it: the
|
|
2438
|
+
eight runtime import cycles in `src/core/` are gone, five helper modules were extracted from the
|
|
2439
|
+
modules that happened to hold them (`document-bytes`, `records`, `steps`, `concurrency`, and the
|
|
2440
|
+
docs-repo gate), and the duplicated spellings of six shared rules now have one home each.
|
|
2441
|
+
`docs/CODE-STYLE.md` records the conventions and the defect behind each one.
|
|
53
2442
|
|
|
54
2443
|
## [0.1.0-beta.1] - 2026-08-06
|
|
55
2444
|
|
|
56
|
-
The first published release, and a prerelease on purpose: it ships under the `beta` dist-tag, not
|
|
2445
|
+
The first published release, and a prerelease on purpose: it ships under the `beta` dist-tag, not
|
|
2446
|
+
`latest`. This entry covers the pre-publication hardening pass. Most of it is not new capability —
|
|
2447
|
+
it is the difference between a command that reports a problem and one that was quietly blind to it,
|
|
2448
|
+
and in four places it is the difference between an undo that works and one that certifies text
|
|
2449
|
+
nobody wrote. Grouped by what a user would notice.
|
|
57
2450
|
|
|
58
2451
|
### Fixed — data loss and silent failure
|
|
59
2452
|
|
|
60
|
-
- **A file whose bytes are not UTF-8 is no longer rewritten.** The whole write path moves bytes
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
-
|
|
2453
|
+
- **A file whose bytes are not UTF-8 is no longer rewritten.** The whole write path moves bytes
|
|
2454
|
+
rather than strings; text is produced only where a parser needs it. Undecodable content refuses
|
|
2455
|
+
the merge (`merge-failed`, naming the file) instead of replacing every such byte with U+FFFD — in
|
|
2456
|
+
the living document *and* in the snapshot meant to undo it, which left nothing to restore from. A
|
|
2457
|
+
non-UTF-8 `openapi.yaml` now grades `openapi.invalid` instead of being handed to the YAML parser
|
|
2458
|
+
with the damage already done.
|
|
2459
|
+
- **`unarchive` no longer restores an edited snapshot and certifies it.** The snapshot manifest is
|
|
2460
|
+
version 2: every entry records `before`, a sha256 of the pre-image it will restore, alongside the
|
|
2461
|
+
existing `after`. `unarchive` hashes each pre-image before staging anything and refuses on a
|
|
2462
|
+
mismatch (`snapshot-corrupt`) — and `--force` deliberately does not override it, because `--force`
|
|
2463
|
+
discards later changes to the living docs while the damage here is to the undo itself. A version-1
|
|
2464
|
+
snapshot is refused as `snapshot-missing`.
|
|
2465
|
+
- **A commit killed halfway can now be repaired.** `archive` and `unarchive` fsync an intent journal
|
|
2466
|
+
(`.loam-commit`) before the first swap and recover from it on the next run, under the lock: an
|
|
2467
|
+
archive is undone, an unarchive is finished (the merged text it was replacing is recorded nowhere
|
|
2468
|
+
else). Where the files no longer permit a safe repair the answer is `commit-interrupted`, a
|
|
2469
|
+
refusal. Previously a SIGKILL between two renames left a half-merged repo that `doctor` called
|
|
2470
|
+
healthy, `validate` blamed on the author's delta, and nothing could roll back.
|
|
2471
|
+
- **A removal marker can no longer be published into a living contract.** `x-loam-remove` written at
|
|
2472
|
+
path level, beside the methods, names no operation and retires nothing; `archive` gates it
|
|
2473
|
+
(`openapi.remove-marker-path-level`) and the marker is stripped from every merge branch, so it
|
|
2474
|
+
never reaches the fleet's living OpenAPI under any flag.
|
|
2475
|
+
- **Symlinked service and feature directories are enumerated.** A `services/<svc>` or
|
|
2476
|
+
`features/<FEAT>` reached through a symlink used to vanish from every listing, which also produced
|
|
2477
|
+
a false `landscape.binding-unknown` for the service that was right there. Dangling links are
|
|
2478
|
+
skipped exactly as an absent directory is.
|
|
65
2479
|
|
|
66
2480
|
### Fixed — checks that were blind
|
|
67
2481
|
|
|
68
|
-
- **Nested landscape elements are graded.** The landscape cross-check kept only top-level elements,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
2482
|
+
- **Nested landscape elements are graded.** The landscape cross-check kept only top-level elements,
|
|
2483
|
+
so ordinary grouped C4 — services under an `enterprise`, `group` or `boundary` — reported *every*
|
|
2484
|
+
service as unmodelled. The tree is walked instead: an element is at service level when no ancestor
|
|
2485
|
+
already stands for a service, an element that contains a service is a grouping, and
|
|
2486
|
+
`landscape.binding-unknown` is now graded at any depth.
|
|
2487
|
+
- **A missing `openapi.yaml` no longer silences the API axis.** It is an **error** when a living
|
|
2488
|
+
non-`REMOVED` `Operations:` line or an op-linked landscape edge already points into the absent
|
|
2489
|
+
file, with the stranded operationIds in `details`; a warning when nothing does and the landscape
|
|
2490
|
+
cannot prove nobody calls the service; silent when it can. Correspondingly, one absent contract is
|
|
2491
|
+
one finding rather than one `spine.op-undefined` per inbound consumer — a file that is not there
|
|
2492
|
+
proves nothing about an edge.
|
|
2493
|
+
- **A feature delta addressed to a nonexistent service is refused** (`delta.service-unknown`, naming
|
|
2494
|
+
close ids). A typo in `--touches` used to validate green and materialise a phantom service
|
|
2495
|
+
directory on archive.
|
|
2496
|
+
- **`openapi.duplicate-operationid` fires in service scope**, not only through an unrelated
|
|
2497
|
+
feature's delta — so `validate --all` sees it on a fleet with no feature in flight.
|
|
2498
|
+
- **`c4.uncovered` no longer fires on a re-declared edge.** A delta that restates a living edge to
|
|
2499
|
+
hang a requirement on it was told to write a decorative architecture requirement.
|
|
2500
|
+
- **`loam doctor` reports what it could not see before**: a held or stale `.loam-lock`, an
|
|
2501
|
+
interrupted commit, orphaned staging temp files, and generated command/skill files that have
|
|
2502
|
+
fallen behind (below).
|
|
74
2503
|
|
|
75
2504
|
### Changed — verification says which answers came from a test run
|
|
76
2505
|
|
|
77
|
-
`--record` may still confirm a `scenario.tested` claim on an agent's word; a service with no
|
|
2506
|
+
`--record` may still confirm a `scenario.tested` claim on an agent's word; a service with no
|
|
2507
|
+
runnable suite yet has to be able to record its answers. What changed is that the record now says
|
|
2508
|
+
so, everywhere:
|
|
78
2509
|
|
|
79
|
-
- a three-valued **`verdict`** — `verified`, `attested`, `unverified` — recomputed from `claims[]`
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
-
|
|
2510
|
+
- a three-valued **`verdict`** — `verified`, `attested`, `unverified` — recomputed from `claims[]`
|
|
2511
|
+
on every read and never taken from the record's own `summary:` block. `verified` in `--json` is
|
|
2512
|
+
exactly `verdict === "verified"`, so a record with zero claims no longer reads as verified by
|
|
2513
|
+
arithmetic;
|
|
2514
|
+
- `verify.scenario-attested` (warning, gating nothing) naming each claim, on the read view, the
|
|
2515
|
+
recording view and the frozen post-archive view alike; `loam status` re-reports it and offers
|
|
2516
|
+
`next.verify-attested`, and such a feature reaches `stage: ready`, never `done`;
|
|
2517
|
+
- a `summary:` block contradicting its own `claims[]` makes the whole record `record-unreadable`
|
|
2518
|
+
(`verify.record-miscounted`) — neither half can be believed, so neither is reported as fact;
|
|
2519
|
+
- `--results` writes down **which report it read** (path, sha256, mtime, tagged-scenario count),
|
|
2520
|
+
must resolve inside the attesting repository in federated mode, and refuses a committed report
|
|
2521
|
+
that differs from the attested commit. That says which file was consumed; it does not say the file
|
|
2522
|
+
came from executing that commit, and no digest can;
|
|
2523
|
+
- two services wording a scenario identically share one digest, so a single report cannot say whose
|
|
2524
|
+
suite ran it: those claims are left unconfirmed under `verify.digest-contested` rather than
|
|
2525
|
+
confirming both.
|
|
84
2526
|
|
|
85
2527
|
### Changed — `loam status` is a projection over the gates
|
|
86
2528
|
|
|
87
|
-
`status` used to say "ship it" on trees `archive` refused. It now takes `stage` and `next[]` from
|
|
2529
|
+
`status` used to say "ship it" on trees `archive` refused. It now takes `stage` and `next[]` from
|
|
2530
|
+
the union of what `validate --feature` errors on and what `archive` refuses to merge, so it may be
|
|
2531
|
+
more pessimistic than either and is never greener than both — an invariant with a test behind it.
|
|
2532
|
+
Also: `checks.coherent` runs the same functions `validate` does (coherence, provenance, missing
|
|
2533
|
+
scenarios); `owesContract` keys on the contract *file* rather than the service directory; `next[]`
|
|
2534
|
+
names `loam delta`, `loam gherkin` and `loam rebase`; the fleet form is capped at ten steps plus a
|
|
2535
|
+
`next.elided` notice and always ends on `next.fleet-gate`; and a filesystem failure that carries no
|
|
2536
|
+
path (a directory where a file belongs) is reported rather than crashing the command.
|
|
88
2537
|
|
|
89
2538
|
### Changed — generated agent files carry a version stamp
|
|
90
2539
|
|
|
91
|
-
Every generated command and skill body now opens with `<!-- generated by loam vX.Y.Z -->`, and
|
|
2540
|
+
Every generated command and skill body now opens with `<!-- generated by loam vX.Y.Z -->`, and
|
|
2541
|
+
`loam doctor` raises `doctor.agent-files-stale` for a file with no stamp or an older one. loam still
|
|
2542
|
+
never rewrites a generated file — the fix is a human's — but drift is now detectable, which it
|
|
2543
|
+
previously was not: a command file mangled to one line left `doctor: healthy: true`. Doctor also
|
|
2544
|
+
stops reporting a repo initialized `--no-skills` as missing its skills.
|
|
92
2545
|
|
|
93
|
-
`loam rebase` and `loam status` were added to the shipped workflow bodies, which is where the
|
|
2546
|
+
`loam rebase` and `loam status` were added to the shipped workflow bodies, which is where the
|
|
2547
|
+
mechanism that prevents two in-flight features from overwriting each other actually gets run.
|
|
2548
|
+
`doctor`'s fix line now spells `loam adopt --service <id>`; the positional form it used to print is
|
|
2549
|
+
refused by the CLI, and a new test parses every command loam prints against the real CLI so that
|
|
2550
|
+
class of defect cannot return silently.
|
|
94
2551
|
|
|
95
2552
|
### Changed — `loam init` keeps the pointer it was given
|
|
96
2553
|
|
|
97
|
-
`--docs` wins only when it is actually passed. A re-run in a wired repository keeps the `docsDir`
|
|
2554
|
+
`--docs` wins only when it is actually passed. A re-run in a wired repository keeps the `docsDir`
|
|
2555
|
+
its committed `loam.json` already names, `--create` included, and spreads the rest of the config
|
|
2556
|
+
forward; `--json` reports `docsDirSource` as `flag`, `config` or `default`. Following `doctor`'s
|
|
2557
|
+
advice to re-run `init` used to repoint the repo at an empty decoy over which `validate --all` went
|
|
2558
|
+
green.
|
|
98
2559
|
|
|
99
2560
|
### Fixed — the OpenSpec on-ramp
|
|
100
2561
|
|
|
101
|
-
- **A Store checkout is audited by its planning shape, not by its marker.** A
|
|
2562
|
+
- **A Store checkout is audited by its planning shape, not by its marker.** A
|
|
2563
|
+
`.openspec-store/store.yaml` beside real `specs/` used to make audit look for
|
|
2564
|
+
`<checkout>/openspec`, find nothing, and report `ready: true, capabilities: 0` — then apply a
|
|
2565
|
+
target holding no requirements at all. The shape picks the root; the marker picks only the kind; a
|
|
2566
|
+
checkout with planning content in neither place is refused by name.
|
|
102
2567
|
- **A workspace nobody could read is never `ready`** (`openspec.workspace-empty`).
|
|
103
|
-
- New blockers for shapes that silently migrated nothing: `openspec.change-quoted-requirements`
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
-
|
|
2568
|
+
- New blockers for shapes that silently migrated nothing: `openspec.change-quoted-requirements`
|
|
2569
|
+
(requirements under `## Requirements` inside an active delta — the shape OpenSpec's own living
|
|
2570
|
+
template mandates, which stages nothing in a change), `openspec.nonstandard-living-spec` (markdown
|
|
2571
|
+
under `specs/` named neither `spec.md` nor `design.md`), `openspec.hidden-change-directory` (a
|
|
2572
|
+
dot-prefixed directory under `changes/`, which enumeration skips).
|
|
2573
|
+
- **The source digest no longer covers archived changes.** Frozen history never gates, so it must
|
|
2574
|
+
not be able to invalidate a completed mapping either; a living or active edit still does.
|
|
2575
|
+
- **The staged target is a real docs repo** — its own `loam.json`, `AGENTS.md` and an empty
|
|
2576
|
+
`architecture/landscape.likec4` — so `FOLLOW-UP.md`'s instructions run where they are written, and
|
|
2577
|
+
`FOLLOW-UP.md` now ends with the cutover procedure: `services/` and `features/` move, everything
|
|
2578
|
+
else is review residue.
|
|
2579
|
+
- **A target inside a live loam fleet is refused**, instead of producing phantom features in every
|
|
2580
|
+
`loam list`.
|
|
2581
|
+
- The living capability tree is copied verbatim under `legacy/openspec/specs/`, so `## Purpose`
|
|
2582
|
+
prose, section prose and capability `design.md` are preserved rather than dropped; an ISO-8601
|
|
2583
|
+
`created` timestamp is a valid date; fenced FROM/TO examples inside a `## RENAMED Requirements`
|
|
2584
|
+
block are no longer parsed as renames; and a rename's TO name no longer demands a service
|
|
2585
|
+
allocation the router never asks for.
|
|
108
2586
|
|
|
109
2587
|
### Performance
|
|
110
2588
|
|
|
111
|
-
`loam list` and `loam validate --service` are now flat in landscape edge count: loam reads LikeC4's
|
|
2589
|
+
`loam list` and `loam validate --service` are now flat in landscape edge count: loam reads LikeC4's
|
|
2590
|
+
parsed model and never computes a view, which it had no use for. On a generated 120-service fleet
|
|
2591
|
+
the same commands previously did not finish above roughly 200 edges. Measured at 120 services and
|
|
2592
|
+
400/800 edges on an 8-core laptop: `list --json` 1.1–1.5 s, `validate --service <id> --json` 1.4–1.6
|
|
2593
|
+
s. `loam init` no longer scaffolds a `views { view index { include * } }` block it never reads.
|
|
112
2594
|
|
|
113
|
-
`loam validate --all` remains ~30 s on that fleet — it parses a fresh LikeC4 workspace per service —
|
|
2595
|
+
`loam validate --all` remains ~30 s on that fleet — it parses a fresh LikeC4 workspace per service —
|
|
2596
|
+
and no speedup is claimed for it. Its target loop is now a bounded pool rather than a serial
|
|
2597
|
+
`await`, which is byte-for-byte output-identical and deterministic in ordering, but measured as a
|
|
2598
|
+
wash on the hardware available; the lever that moved this command was the parsed model, not the loop
|
|
2599
|
+
shape.
|
|
114
2600
|
|
|
115
2601
|
### Fixed — release engineering
|
|
116
2602
|
|
|
117
|
-
- The CI `package` job could never pass: `release-check.mjs` refused `--fixture-ready` whenever
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
- `
|
|
121
|
-
|
|
2603
|
+
- The CI `package` job could never pass: `release-check.mjs` refused `--fixture-ready` whenever
|
|
2604
|
+
`GITHUB_ACTIONS` was set. The guard now keys on a tag ref, which is what it was actually
|
|
2605
|
+
protecting against. Any claim that CI had validated release readiness was false before this.
|
|
2606
|
+
- `npm run test:package` failed against the current tarball — the smoke test called
|
|
2607
|
+
`loam init --docs docs` without `--create`.
|
|
2608
|
+
- Five release scripts spawned `npm.cmd`/`loam.cmd` without a shell, which Node ≥ 20.12 rejects on
|
|
2609
|
+
Windows; they now resolve npm's CLI and run it through `process.execPath`. Windows itself is
|
|
2610
|
+
unexercised here — there is no Windows host — so this is verified structurally and a platform
|
|
2611
|
+
runtime check remains outstanding.
|
|
2612
|
+
- `pack-release` and `verify-release-artifact` compared an unpeeled `GITHUB_SHA` against `HEAD`, so
|
|
2613
|
+
an annotated or signed tag — the `npm version` and `git tag -a/-s` default — could never pass.
|
|
2614
|
+
Both peel now; `git tag -s v<version>` works.
|
|
2615
|
+
- Release artifact retention raised 1 → 14 days, so a candidate awaiting human approval outlives a
|
|
2616
|
+
weekend; CI narrowed to pushes on `main` with a concurrency group, so tags no longer run both
|
|
2617
|
+
workflows in parallel.
|
|
122
2618
|
|
|
123
2619
|
### Documentation
|
|
124
2620
|
|
|
125
|
-
README, COMPARISON, SCHEMA and MIGRATING were re-checked claim by claim against the code and against
|
|
2621
|
+
README, COMPARISON, SCHEMA and MIGRATING were re-checked claim by claim against the code and against
|
|
2622
|
+
upstream OpenSpec live on 2026-08-05. Corrected: the documented migration entry point
|
|
2623
|
+
(`migrate-openspec <root>` returns `invalid-option`; `audit-openspec` is the real first step and
|
|
2624
|
+
appeared nowhere in the README), the corpus and test counts, the `--baseline` flag the reproduction
|
|
2625
|
+
recipe needs, the Executability and agent-surface comparison rows, and the claim that OpenSpec has
|
|
2626
|
+
no multi-repo story — Stores shipped in v1.5.0, and the honest argument is their documented "No
|
|
2627
|
+
sync, ever — by design" plus the absence of any lockfile or commit pin. Removed: the argument that
|
|
2628
|
+
upstream's generated instructions are broken, which upstream fixed inside 48 hours and which loam
|
|
2629
|
+
had in its own surface.
|
|
126
2630
|
|
|
127
2631
|
### Added
|
|
128
2632
|
|
|
@@ -135,4 +2639,9 @@ README, COMPARISON, SCHEMA and MIGRATING were re-checked claim by claim against
|
|
|
135
2639
|
|
|
136
2640
|
## Upgrading to this release
|
|
137
2641
|
|
|
138
|
-
Nothing to upgrade from — this is the first published version. One note for a repository initialized
|
|
2642
|
+
Nothing to upgrade from — this is the first published version. One note for a repository initialized
|
|
2643
|
+
from a pre-release build: the first `loam doctor` after installing this one reports
|
|
2644
|
+
`doctor.agent-files-stale` for **every** generated command and skill file. Earlier builds wrote no
|
|
2645
|
+
version stamp, so "no stamp" and "an old stamp" are indistinguishable, and loam refuses to assume
|
|
2646
|
+
the instructions on disk describe this binary. Read each file, then bump its stamp by hand, or
|
|
2647
|
+
delete it and re-run `loam init` — the drift is reported and never repaired for you.
|