@ybotok/loam 0.2.0-alpha.6 → 0.2.0-alpha.9
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 +1477 -1
- package/COMPARISON.md +91 -3
- package/CONTRIBUTING.md +37 -0
- package/MIGRATING-from-OpenSpec.md +40 -22
- package/QUICKSTART.md +181 -9
- package/README.md +256 -57
- package/ROADMAP.md +108 -11
- package/SCHEMA.md +721 -130
- package/WORKFLOW.md +272 -62
- package/dist/cli.js +26 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt/adopt.js +5 -10
- package/dist/commands/adopt/adopt.js.map +1 -1
- package/dist/commands/archive/archive.js +15 -18
- package/dist/commands/archive/archive.js.map +1 -1
- package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
- package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/consumers.js +56 -0
- package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
- package/dist/commands/archive/plan/contracts/openapi.js +25 -42
- package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
- package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
- package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
- package/dist/commands/archive/plan/gate.js +3 -7
- package/dist/commands/archive/plan/gate.js.map +1 -1
- package/dist/commands/archive/plan/landscape.js +4 -4
- package/dist/commands/archive/plan/landscape.js.map +1 -1
- package/dist/commands/archive/plan/refusal.js +8 -18
- package/dist/commands/archive/plan/refusal.js.map +1 -1
- package/dist/commands/archive/plan/specs.js +134 -25
- package/dist/commands/archive/plan/specs.js.map +1 -1
- package/dist/commands/archive/plan/state.js +1 -0
- package/dist/commands/archive/plan/state.js.map +1 -1
- package/dist/commands/archive/run.js +82 -33
- package/dist/commands/archive/run.js.map +1 -1
- package/dist/commands/archive/shipped.js +49 -0
- package/dist/commands/archive/shipped.js.map +1 -0
- package/dist/commands/context/context.js +4 -7
- package/dist/commands/context/context.js.map +1 -1
- package/dist/commands/context/print.js +2 -2
- package/dist/commands/context/print.js.map +1 -1
- package/dist/commands/delta/delta.js +44 -12
- package/dist/commands/delta/delta.js.map +1 -1
- package/dist/commands/delta/print.js +1 -1
- package/dist/commands/delta/print.js.map +1 -1
- package/dist/commands/dependencies.js +3 -7
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/diff/diff.js +20 -27
- package/dist/commands/diff/diff.js.map +1 -1
- package/dist/commands/explore.js +4 -8
- package/dist/commands/explore.js.map +1 -1
- package/dist/commands/gate/gate.js +45 -8
- package/dist/commands/gate/gate.js.map +1 -1
- package/dist/commands/gate/print.js +15 -3
- package/dist/commands/gate/print.js.map +1 -1
- package/dist/commands/gherkin/commit.js +13 -54
- package/dist/commands/gherkin/commit.js.map +1 -1
- package/dist/commands/gherkin/gherkin.js +36 -20
- package/dist/commands/gherkin/gherkin.js.map +1 -1
- package/dist/commands/gherkin/reconcile.js +10 -13
- package/dist/commands/gherkin/reconcile.js.map +1 -1
- package/dist/commands/init/first-hour.js +44 -0
- package/dist/commands/init/first-hour.js.map +1 -1
- package/dist/commands/init/init.js +18 -22
- package/dist/commands/init/init.js.map +1 -1
- package/dist/commands/list/campaign/campaign.js +4 -6
- package/dist/commands/list/campaign/campaign.js.map +1 -1
- package/dist/commands/list/json.js +0 -4
- package/dist/commands/list/json.js.map +1 -1
- package/dist/commands/list/list.js +38 -23
- package/dist/commands/list/list.js.map +1 -1
- package/dist/commands/list/print.js +38 -6
- package/dist/commands/list/print.js.map +1 -1
- package/dist/commands/list/suites/suites.js +140 -0
- package/dist/commands/list/suites/suites.js.map +1 -0
- package/dist/commands/list/views.js +28 -33
- package/dist/commands/list/views.js.map +1 -1
- package/dist/commands/mcp/dispatch.js +91 -36
- package/dist/commands/mcp/dispatch.js.map +1 -1
- package/dist/commands/mcp/mcp.js +56 -15
- package/dist/commands/mcp/mcp.js.map +1 -1
- package/dist/commands/mcp/serve.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/active.js +27 -22
- package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
- package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
- package/dist/commands/migrate-openspec/materialize/target.js +2 -2
- package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
- package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/error.js +7 -8
- package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
- package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
- package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
- package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
- package/dist/commands/new/capability-template.js +118 -0
- package/dist/commands/new/capability-template.js.map +1 -0
- package/dist/commands/new/commit.js +8 -22
- package/dist/commands/new/commit.js.map +1 -1
- package/dist/commands/new/modifies.js +206 -0
- package/dist/commands/new/modifies.js.map +1 -0
- package/dist/commands/new/new.js +59 -14
- package/dist/commands/new/new.js.map +1 -1
- package/dist/commands/new/templates.js +48 -124
- package/dist/commands/new/templates.js.map +1 -1
- package/dist/commands/open.js +3 -7
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/policy/gate.js +27 -0
- package/dist/commands/policy/gate.js.map +1 -1
- package/dist/commands/rebase/plan.js +2 -2
- package/dist/commands/rebase/plan.js.map +1 -1
- package/dist/commands/rebase/rebase.js +60 -35
- package/dist/commands/rebase/rebase.js.map +1 -1
- package/dist/commands/seed/commit.js +55 -26
- package/dist/commands/seed/commit.js.map +1 -1
- package/dist/commands/seed/plan.js +64 -13
- package/dist/commands/seed/plan.js.map +1 -1
- package/dist/commands/seed/seed.js +6 -9
- package/dist/commands/seed/seed.js.map +1 -1
- package/dist/commands/show/show.js +3 -7
- package/dist/commands/show/show.js.map +1 -1
- package/dist/commands/status/print.js +13 -4
- package/dist/commands/status/print.js.map +1 -1
- package/dist/commands/status/status.js +3 -7
- package/dist/commands/status/status.js.map +1 -1
- package/dist/commands/steps/steps.js +3 -7
- package/dist/commands/steps/steps.js.map +1 -1
- package/dist/commands/subsystem/edit.js +7 -7
- package/dist/commands/subsystem/edit.js.map +1 -1
- package/dist/commands/subsystem/move.js +60 -25
- package/dist/commands/subsystem/move.js.map +1 -1
- package/dist/commands/subsystem/query.js +2 -4
- package/dist/commands/subsystem/query.js.map +1 -1
- package/dist/commands/subsystem/subsystem.js +4 -7
- package/dist/commands/subsystem/subsystem.js.map +1 -1
- package/dist/commands/subsystem/sync.js +141 -71
- package/dist/commands/subsystem/sync.js.map +1 -1
- package/dist/commands/subsystem/txn/report.js +55 -5
- package/dist/commands/subsystem/txn/report.js.map +1 -1
- package/dist/commands/subsystem/txn/txn.js +121 -45
- package/dist/commands/subsystem/txn/txn.js.map +1 -1
- package/dist/commands/subsystem/txn/views.js +82 -12
- package/dist/commands/subsystem/txn/views.js.map +1 -1
- package/dist/commands/tasks/render.js +116 -0
- package/dist/commands/tasks/render.js.map +1 -0
- package/dist/commands/tasks/tasks.js +113 -0
- package/dist/commands/tasks/tasks.js.map +1 -0
- package/dist/commands/unarchive/manifest.js +6 -9
- package/dist/commands/unarchive/manifest.js.map +1 -1
- package/dist/commands/unarchive/restore.js +17 -28
- package/dist/commands/unarchive/restore.js.map +1 -1
- package/dist/commands/unarchive/unarchive.js +22 -30
- package/dist/commands/unarchive/unarchive.js.map +1 -1
- package/dist/commands/validate/arch-coverage.js +6 -15
- package/dist/commands/validate/arch-coverage.js.map +1 -1
- package/dist/commands/validate/checks/fleet-shape.js +4 -49
- package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
- package/dist/commands/validate/checks/health-deps.js +9 -6
- package/dist/commands/validate/checks/health-deps.js.map +1 -1
- package/dist/commands/validate/checks/requirements.js +18 -8
- package/dist/commands/validate/checks/requirements.js.map +1 -1
- package/dist/commands/validate/checks/sources.js +6 -2
- package/dist/commands/validate/checks/sources.js.map +1 -1
- package/dist/commands/validate/feature.js +23 -19
- package/dist/commands/validate/feature.js.map +1 -1
- package/dist/commands/validate/fleet/census.js +26 -3
- package/dist/commands/validate/fleet/census.js.map +1 -1
- package/dist/commands/validate/fleet/landscape.js +35 -32
- package/dist/commands/validate/fleet/landscape.js.map +1 -1
- package/dist/commands/validate/fleet/map/attest.js +7 -12
- package/dist/commands/validate/fleet/map/attest.js.map +1 -1
- package/dist/commands/validate/fleet/map/bindings.js +16 -5
- package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
- package/dist/commands/validate/fleet/map/consumers.js +43 -122
- package/dist/commands/validate/fleet/map/consumers.js.map +1 -1
- package/dist/commands/validate/fleet/map/usecases.js +5 -11
- package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
- package/dist/commands/validate/fleet/obligations.js +1 -5
- package/dist/commands/validate/fleet/obligations.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/print.js +5 -1
- package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
- package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
- package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
- package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
- package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js +1 -1
- package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
- package/dist/commands/validate/fleet/views/ids.js +33 -7
- package/dist/commands/validate/fleet/views/ids.js.map +1 -1
- package/dist/commands/validate/fleet/views/root-documents.js +17 -35
- package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
- package/dist/commands/validate/fleet/views/stale.js +31 -14
- package/dist/commands/validate/fleet/views/stale.js.map +1 -1
- package/dist/commands/validate/links/corpus.js +1 -4
- package/dist/commands/validate/links/corpus.js.map +1 -1
- package/dist/commands/validate/links/glossary.js.map +1 -1
- package/dist/commands/validate/report.js +5 -4
- package/dist/commands/validate/report.js.map +1 -1
- package/dist/commands/validate/scope.js +113 -0
- package/dist/commands/validate/scope.js.map +1 -0
- package/dist/commands/validate/service/api.js +16 -104
- package/dist/commands/validate/service/api.js.map +1 -1
- package/dist/commands/validate/service/contracts/generated.js +134 -0
- package/dist/commands/validate/service/contracts/generated.js.map +1 -0
- package/dist/commands/validate/service/events/events.js +1 -1
- package/dist/commands/validate/service/events/events.js.map +1 -1
- package/dist/commands/validate/service/events/message-producers.js +1 -1
- package/dist/commands/validate/service/events/message-producers.js.map +1 -1
- package/dist/commands/validate/service/evidence-pins.js +56 -10
- package/dist/commands/validate/service/evidence-pins.js.map +1 -1
- package/dist/commands/validate/service/service.js +16 -26
- package/dist/commands/validate/service/service.js.map +1 -1
- package/dist/commands/validate/service/specs.js +31 -38
- package/dist/commands/validate/service/specs.js.map +1 -1
- package/dist/commands/validate/service/spine.js +8 -11
- package/dist/commands/validate/service/spine.js.map +1 -1
- package/dist/commands/validate/service/usecases/usecases.js +3 -3
- package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
- package/dist/commands/validate/validate.js +44 -26
- package/dist/commands/validate/validate.js.map +1 -1
- package/dist/commands/verify/cross/diff.js +2 -6
- package/dist/commands/verify/cross/diff.js.map +1 -1
- package/dist/commands/verify/evidence/contract.js +1 -1
- package/dist/commands/verify/evidence/contract.js.map +1 -1
- package/dist/commands/verify/evidence/read.js +12 -3
- package/dist/commands/verify/evidence/read.js.map +1 -1
- package/dist/commands/verify/frozen.js +45 -23
- package/dist/commands/verify/frozen.js.map +1 -1
- package/dist/commands/verify/record.js +69 -17
- package/dist/commands/verify/record.js.map +1 -1
- package/dist/commands/verify/report.js +27 -44
- package/dist/commands/verify/report.js.map +1 -1
- package/dist/commands/verify/results.js +112 -30
- package/dist/commands/verify/results.js.map +1 -1
- package/dist/commands/verify/verify.js +54 -51
- package/dist/commands/verify/verify.js.map +1 -1
- package/dist/commands/vouch/pack/pack.js +2 -2
- package/dist/commands/vouch/pack/pack.js.map +1 -1
- package/dist/commands/vouch/pack/print.js +1 -6
- package/dist/commands/vouch/pack/print.js.map +1 -1
- package/dist/commands/vouch/pack/sections.js +1 -2
- package/dist/commands/vouch/pack/sections.js.map +1 -1
- package/dist/commands/vouch/run.js +26 -81
- package/dist/commands/vouch/run.js.map +1 -1
- package/dist/commands/vouch/sample/plan.js +20 -34
- package/dist/commands/vouch/sample/plan.js.map +1 -1
- package/dist/commands/vouch/vet/verify.js +39 -7
- package/dist/commands/vouch/vet/verify.js.map +1 -1
- package/dist/commands/vouch/vouch.js +10 -17
- package/dist/commands/vouch/vouch.js.map +1 -1
- package/dist/core/agent/agents-md/command-map.js +57 -102
- package/dist/core/agent/agents-md/command-map.js.map +1 -1
- package/dist/core/agent/agents-md/map/explain.js +4 -3
- package/dist/core/agent/agents-md/map/explain.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
- package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
- package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
- package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
- package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
- package/dist/core/agent/agents-md/map/mcp.js +10 -7
- package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
- package/dist/core/agent/agents-md/map/status.js +9 -3
- package/dist/core/agent/agents-md/map/status.js.map +1 -1
- package/dist/core/agent/agents-md/map/subsystem.js +31 -17
- package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
- package/dist/core/agent/agents-md/map/usecases.js +24 -6
- package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
- package/dist/core/agent/agents-md/refusals.js +37 -21
- package/dist/core/agent/agents-md/refusals.js.map +1 -1
- package/dist/core/agent/agents-md.js +25 -10
- package/dist/core/agent/agents-md.js.map +1 -1
- package/dist/core/agent/agents-stamp.js +10 -0
- package/dist/core/agent/agents-stamp.js.map +1 -1
- package/dist/core/agent/scaffold.js +10 -15
- package/dist/core/agent/scaffold.js.map +1 -1
- package/dist/core/agent/tools/dialects.js +1 -0
- package/dist/core/agent/tools/dialects.js.map +1 -1
- package/dist/core/agent/tools/registry.js +41 -76
- package/dist/core/agent/tools/registry.js.map +1 -1
- package/dist/core/agent/workflows/adopt.js +5 -3
- package/dist/core/agent/workflows/adopt.js.map +1 -1
- package/dist/core/agent/workflows/check.js +25 -22
- package/dist/core/agent/workflows/check.js.map +1 -1
- package/dist/core/agent/workflows/closing.js +54 -21
- package/dist/core/agent/workflows/closing.js.map +1 -1
- package/dist/core/agent/workflows/feature.js +79 -11
- package/dist/core/agent/workflows/feature.js.map +1 -1
- package/dist/core/agent/workflows/implement.js +91 -13
- package/dist/core/agent/workflows/implement.js.map +1 -1
- package/dist/core/agent/workflows/reference/authoring.js +3 -1
- package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
- package/dist/core/agent/workflows/reference/codes.js +8 -6
- package/dist/core/agent/workflows/reference/codes.js.map +1 -1
- package/dist/core/agent/workflows/reference/done-check.js +37 -15
- package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
- package/dist/core/agent/workflows/reference/reference.js.map +1 -1
- package/dist/core/agent/workflows/reference/spine.js +62 -24
- package/dist/core/agent/workflows/reference/spine.js.map +1 -1
- package/dist/core/asyncapi/baseline/plan.js +14 -81
- package/dist/core/asyncapi/baseline/plan.js.map +1 -1
- package/dist/core/asyncapi/digest.js +30 -25
- package/dist/core/asyncapi/digest.js.map +1 -1
- package/dist/core/asyncapi/merge/components.js +5 -4
- package/dist/core/asyncapi/merge/components.js.map +1 -1
- package/dist/core/asyncapi/merge/error.js +7 -2
- package/dist/core/asyncapi/merge/error.js.map +1 -1
- package/dist/core/asyncapi/merge/markers.js +30 -50
- package/dist/core/asyncapi/merge/markers.js.map +1 -1
- package/dist/core/asyncapi/merge/merge.js +31 -53
- package/dist/core/asyncapi/merge/merge.js.map +1 -1
- package/dist/core/asyncapi/merge/pin.js +10 -36
- package/dist/core/asyncapi/merge/pin.js.map +1 -1
- package/dist/core/asyncapi/model.js.map +1 -1
- package/dist/core/asyncapi/read.js +36 -12
- package/dist/core/asyncapi/read.js.map +1 -1
- package/dist/core/brief/brief.js +30 -25
- package/dist/core/brief/brief.js.map +1 -1
- package/dist/core/brief/landscape.js +8 -12
- package/dist/core/brief/landscape.js.map +1 -1
- package/dist/core/brief/map/extend.js +8 -5
- package/dist/core/brief/map/extend.js.map +1 -1
- package/dist/core/brief/map/owed.js +8 -26
- package/dist/core/brief/map/owed.js.map +1 -1
- package/dist/core/brief/targets.js +79 -5
- package/dist/core/brief/targets.js.map +1 -1
- package/dist/core/brief/unchecked.js +1 -1
- package/dist/core/brief/unchecked.js.map +1 -1
- package/dist/core/c4/likec4.js +147 -16
- package/dist/core/c4/likec4.js.map +1 -1
- package/dist/core/c4/locate/unlocated.js +150 -0
- package/dist/core/c4/locate/unlocated.js.map +1 -0
- package/dist/core/c4/parsed/values.js +54 -1
- package/dist/core/c4/parsed/values.js.map +1 -1
- package/dist/core/c4/prefetch/fleet.js +1 -9
- package/dist/core/c4/prefetch/fleet.js.map +1 -1
- package/dist/core/c4/project/architecture.js +31 -9
- package/dist/core/c4/project/architecture.js.map +1 -1
- package/dist/core/c4/project/documents.js +36 -10
- package/dist/core/c4/project/documents.js.map +1 -1
- package/dist/core/c4/project/load.js +14 -21
- package/dist/core/c4/project/load.js.map +1 -1
- package/dist/core/c4/project/staged.js +7 -3
- package/dist/core/c4/project/staged.js.map +1 -1
- package/dist/core/c4/resolve/external.js +51 -0
- package/dist/core/c4/resolve/external.js.map +1 -0
- package/dist/core/c4/resolve/service.js +25 -1
- package/dist/core/c4/resolve/service.js.map +1 -1
- package/dist/core/c4/seed/template.js +15 -1
- package/dist/core/c4/seed/template.js.map +1 -1
- package/dist/core/c4/service-model/shape.js +2 -22
- package/dist/core/c4/service-model/shape.js.map +1 -1
- package/dist/core/c4/source-mask.js +288 -4
- package/dist/core/c4/source-mask.js.map +1 -1
- package/dist/core/c4/source-scan.js +203 -147
- package/dist/core/c4/source-scan.js.map +1 -1
- package/dist/core/c4/splice/authored-source.js +19 -0
- package/dist/core/c4/splice/authored-source.js.map +1 -1
- package/dist/core/c4/splice/delta-blocks.js +5 -18
- package/dist/core/c4/splice/delta-blocks.js.map +1 -1
- package/dist/core/c4/splice/landscape-merge.js +13 -8
- package/dist/core/c4/splice/landscape-merge.js.map +1 -1
- package/dist/core/c4/splice/model/merge.js +10 -6
- package/dist/core/c4/splice/model/merge.js.map +1 -1
- package/dist/core/c4/splice/placement.js +108 -18
- package/dist/core/c4/splice/placement.js.map +1 -1
- package/dist/core/c4/workspace.js +13 -22
- package/dist/core/c4/workspace.js.map +1 -1
- package/dist/core/capabilities/capabilities.js +6 -6
- package/dist/core/capabilities/capabilities.js.map +1 -1
- package/dist/core/capabilities/delta/doc.js +7 -10
- package/dist/core/capabilities/delta/doc.js.map +1 -1
- package/dist/core/capabilities/findings.js +15 -20
- package/dist/core/capabilities/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/findings.js +12 -19
- package/dist/core/capabilities/realizes/findings.js.map +1 -1
- package/dist/core/capabilities/realizes/join.js +0 -20
- package/dist/core/capabilities/realizes/join.js.map +1 -1
- package/dist/core/capabilities/rollup.js +6 -0
- package/dist/core/capabilities/rollup.js.map +1 -1
- package/dist/core/capabilities/tree.js +6 -28
- package/dist/core/capabilities/tree.js.map +1 -1
- package/dist/core/capabilities/usecase-join.js +3 -13
- package/dist/core/capabilities/usecase-join.js.map +1 -1
- package/dist/core/coherence/authoring/scaffold.js +11 -3
- package/dist/core/coherence/authoring/scaffold.js.map +1 -1
- package/dist/core/coherence/authoring/sentinels.js +2 -1
- package/dist/core/coherence/authoring/sentinels.js.map +1 -1
- package/dist/core/coherence/coherence.js +30 -16
- package/dist/core/coherence/coherence.js.map +1 -1
- package/dist/core/coherence/consumed/operations.js +70 -0
- package/dist/core/coherence/consumed/operations.js.map +1 -0
- package/dist/core/coherence/consumed/refusal.js +38 -0
- package/dist/core/coherence/consumed/refusal.js.map +1 -0
- package/dist/core/coherence/declared.js +60 -34
- package/dist/core/coherence/declared.js.map +1 -1
- package/dist/core/coherence/events/declared.js +44 -59
- package/dist/core/coherence/events/declared.js.map +1 -1
- package/dist/core/coherence/events/events.js +119 -8
- package/dist/core/coherence/events/events.js.map +1 -1
- package/dist/core/coherence/events/grades.js +31 -8
- package/dist/core/coherence/events/grades.js.map +1 -1
- package/dist/core/coherence/events/lookups.js +14 -82
- package/dist/core/coherence/events/lookups.js.map +1 -1
- package/dist/core/coherence/lookups.js +18 -78
- package/dist/core/coherence/lookups.js.map +1 -1
- package/dist/core/coherence/retired/archived.js +285 -0
- package/dist/core/coherence/retired/archived.js.map +1 -0
- package/dist/core/coherence/retired/event-pair.js +62 -0
- package/dist/core/coherence/retired/event-pair.js.map +1 -0
- package/dist/core/coherence/retired/merged.js +79 -0
- package/dist/core/coherence/retired/merged.js.map +1 -0
- package/dist/core/coherence/retired/retirement.js +49 -0
- package/dist/core/coherence/retired/retirement.js.map +1 -0
- package/dist/core/coherence/retired/revival/arrivals.js +131 -0
- package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
- package/dist/core/coherence/retired/revival/revived.js +297 -0
- package/dist/core/coherence/retired/revival/revived.js.map +1 -0
- package/dist/core/consumers/after.js +113 -0
- package/dist/core/consumers/after.js.map +1 -0
- package/dist/core/consumers/consumers.js +296 -0
- package/dist/core/consumers/consumers.js.map +1 -0
- package/dist/core/consumers/current.js +211 -0
- package/dist/core/consumers/current.js.map +1 -0
- package/dist/core/consumers/scan.js +46 -0
- package/dist/core/consumers/scan.js.map +1 -0
- package/dist/core/consumers/state.js +27 -0
- package/dist/core/consumers/state.js.map +1 -0
- package/dist/core/delta/delta.js +1 -1
- package/dist/core/delta/delta.js.map +1 -1
- package/dist/core/deployment/delta.js +15 -16
- package/dist/core/deployment/delta.js.map +1 -1
- package/dist/core/diff/base-git.js +94 -12
- package/dist/core/diff/base-git.js.map +1 -1
- package/dist/core/diff/base-state.js +28 -16
- package/dist/core/diff/base-state.js.map +1 -1
- package/dist/core/diff/joins.js +136 -0
- package/dist/core/diff/joins.js.map +1 -0
- package/dist/core/diff/scope/changed-paths.js +22 -6
- package/dist/core/diff/scope/changed-paths.js.map +1 -1
- package/dist/core/diff/scope/edges.js +82 -0
- package/dist/core/diff/scope/edges.js.map +1 -0
- package/dist/core/diff/semantic.js +100 -31
- package/dist/core/diff/semantic.js.map +1 -1
- package/dist/core/doctor/agents.js +34 -18
- package/dist/core/doctor/agents.js.map +1 -1
- package/dist/core/doctor/report.js.map +1 -1
- package/dist/core/document/apply.js +10 -2
- package/dist/core/document/apply.js.map +1 -1
- package/dist/core/document/frontmatter.js +55 -0
- package/dist/core/document/frontmatter.js.map +1 -1
- package/dist/core/document/parse.js +29 -6
- package/dist/core/document/parse.js.map +1 -1
- package/dist/core/document/scenarios.js +17 -0
- package/dist/core/document/scenarios.js.map +1 -1
- package/dist/core/document/spec.js +4 -11
- package/dist/core/document/spec.js.map +1 -1
- package/dist/core/envelope/config.js +10 -0
- package/dist/core/envelope/config.js.map +1 -1
- package/dist/core/envelope/json.js +34 -0
- package/dist/core/envelope/json.js.map +1 -1
- package/dist/core/explain/families.js +17 -12
- package/dist/core/explain/families.js.map +1 -1
- package/dist/core/explain/refusals.js +3 -3
- package/dist/core/explain/refusals.js.map +1 -1
- package/dist/core/explain/terms.js +18 -9
- package/dist/core/explain/terms.js.map +1 -1
- package/dist/core/explore/explore.js +2 -7
- package/dist/core/explore/explore.js.map +1 -1
- package/dist/core/fleet-context.js +34 -95
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/gate/checks.js +10 -3
- package/dist/core/gate/checks.js.map +1 -1
- package/dist/core/gate/partners.js +64 -19
- package/dist/core/gate/partners.js.map +1 -1
- package/dist/core/gate/report.js.map +1 -1
- package/dist/core/gate/verification.js +75 -12
- package/dist/core/gate/verification.js.map +1 -1
- package/dist/core/gherkin/stale.js +13 -37
- package/dist/core/gherkin/stale.js.map +1 -1
- package/dist/core/glossary/tree.js +6 -19
- package/dist/core/glossary/tree.js.map +1 -1
- package/dist/core/kernel/concurrency.js +19 -0
- package/dist/core/kernel/concurrency.js.map +1 -1
- package/dist/core/kernel/records.js +11 -0
- package/dist/core/kernel/records.js.map +1 -1
- package/dist/core/links/backlinks.js +1 -3
- package/dist/core/links/backlinks.js.map +1 -1
- package/dist/core/links/corpus.js +5 -36
- package/dist/core/links/corpus.js.map +1 -1
- package/dist/core/mcp/argv.js +101 -0
- package/dist/core/mcp/argv.js.map +1 -1
- package/dist/core/mcp/author-tools.js +2 -1
- package/dist/core/mcp/author-tools.js.map +1 -1
- package/dist/core/mcp/protocol.js +2 -2
- package/dist/core/mcp/protocol.js.map +1 -1
- package/dist/core/mcp/tools.js +43 -33
- package/dist/core/mcp/tools.js.map +1 -1
- package/dist/core/openapi/baseline/gate.js +53 -12
- package/dist/core/openapi/baseline/gate.js.map +1 -1
- package/dist/core/openapi/baseline/plan.js +22 -8
- package/dist/core/openapi/baseline/plan.js.map +1 -1
- package/dist/core/openapi/baseline/record.js +0 -1
- package/dist/core/openapi/baseline/record.js.map +1 -1
- package/dist/core/openapi/contract-text.js +33 -0
- package/dist/core/openapi/contract-text.js.map +1 -0
- package/dist/core/openapi/contract-tree.js +56 -0
- package/dist/core/openapi/contract-tree.js.map +1 -0
- package/dist/core/openapi/depth.js +38 -24
- package/dist/core/openapi/depth.js.map +1 -1
- package/dist/core/openapi/digest.js +52 -2
- package/dist/core/openapi/digest.js.map +1 -1
- package/dist/core/openapi/doc.js +12 -4
- package/dist/core/openapi/doc.js.map +1 -1
- package/dist/core/openapi/merge/components.js +25 -60
- package/dist/core/openapi/merge/components.js.map +1 -1
- package/dist/core/openapi/merge/error.js +3 -13
- package/dist/core/openapi/merge/error.js.map +1 -1
- package/dist/core/openapi/merge/markers.js +26 -30
- package/dist/core/openapi/merge/markers.js.map +1 -1
- package/dist/core/openapi/merge/merge.js +26 -52
- package/dist/core/openapi/merge/merge.js.map +1 -1
- package/dist/core/openapi/merge/pin.js +15 -35
- package/dist/core/openapi/merge/pin.js.map +1 -1
- package/dist/core/openspec/decide/verdict.js.map +1 -1
- package/dist/core/openspec/inventory.js +4 -5
- package/dist/core/openspec/inventory.js.map +1 -1
- package/dist/core/openspec/model/mapping.js +30 -28
- package/dist/core/openspec/model/mapping.js.map +1 -1
- package/dist/core/openspec/model/model.js +12 -7
- package/dist/core/openspec/model/model.js.map +1 -1
- package/dist/core/openspec/scan/changes.js +10 -4
- package/dist/core/openspec/scan/changes.js.map +1 -1
- package/dist/core/openspec/scan/config.js +7 -16
- package/dist/core/openspec/scan/config.js.map +1 -1
- package/dist/core/openspec/scan/shape.js +63 -19
- package/dist/core/openspec/scan/shape.js.map +1 -1
- package/dist/core/openspec/scan/walk.js +21 -38
- package/dist/core/openspec/scan/walk.js.map +1 -1
- package/dist/core/pack/living.js +2 -16
- package/dist/core/pack/living.js.map +1 -1
- package/dist/core/provenance/findings.js +48 -12
- package/dist/core/provenance/findings.js.map +1 -1
- package/dist/core/provenance/git.js +33 -57
- package/dist/core/provenance/git.js.map +1 -1
- package/dist/core/provenance/gitq/moves.js +71 -114
- package/dist/core/provenance/gitq/moves.js.map +1 -1
- package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
- package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
- package/dist/core/provenance/sources.js +23 -6
- package/dist/core/provenance/sources.js.map +1 -1
- package/dist/core/provenance/stamp.js +5 -17
- package/dist/core/provenance/stamp.js.map +1 -1
- package/dist/core/repo/entries.js.map +1 -1
- package/dist/core/repo/paths.js +34 -29
- package/dist/core/repo/paths.js.map +1 -1
- package/dist/core/repo/repo.js +24 -14
- package/dist/core/repo/repo.js.map +1 -1
- package/dist/core/repo/tree/find.js +12 -3
- package/dist/core/repo/tree/find.js.map +1 -1
- package/dist/core/repo/tree/fs.js +27 -1
- package/dist/core/repo/tree/fs.js.map +1 -1
- package/dist/core/repo/tree/render/body.js +45 -6
- package/dist/core/repo/tree/render/body.js.map +1 -1
- package/dist/core/repo/tree/render/service/header.js +125 -0
- package/dist/core/repo/tree/render/service/header.js.map +1 -0
- package/dist/core/repo/tree/render/service/peers.js +28 -0
- package/dist/core/repo/tree/render/service/peers.js.map +1 -0
- package/dist/core/repo/tree/render/service/view-id.js +67 -0
- package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
- package/dist/core/repo/tree/render/service/views.js +78 -0
- package/dist/core/repo/tree/render/service/views.js.map +1 -0
- package/dist/core/repo/tree/render/stale.js +19 -2
- package/dist/core/repo/tree/render/stale.js.map +1 -1
- package/dist/core/repo/tree/render/views.js +102 -33
- package/dist/core/repo/tree/render/views.js.map +1 -1
- package/dist/core/repo/tree/walk.js +8 -30
- package/dist/core/repo/tree/walk.js.map +1 -1
- package/dist/core/results.js +47 -12
- package/dist/core/results.js.map +1 -1
- package/dist/core/staging/commit.js +10 -12
- package/dist/core/staging/commit.js.map +1 -1
- package/dist/core/staging/lock.js +74 -12
- package/dist/core/staging/lock.js.map +1 -1
- package/dist/core/staging/recovery/intent.js +2 -5
- package/dist/core/staging/recovery/intent.js.map +1 -1
- package/dist/core/staging/recovery/recover.js +60 -1
- package/dist/core/staging/recovery/recover.js.map +1 -1
- package/dist/core/staging/snapshot/manifest.js +152 -0
- package/dist/core/staging/snapshot/manifest.js.map +1 -0
- package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
- package/dist/core/staging/snapshot/snapshot.js.map +1 -0
- package/dist/core/status/actions/execution.js +11 -0
- package/dist/core/status/actions/execution.js.map +1 -1
- package/dist/core/status/feature/artifacts.js +2 -2
- package/dist/core/status/feature/artifacts.js.map +1 -1
- package/dist/core/status/feature/feature.js +5 -11
- package/dist/core/status/feature/feature.js.map +1 -1
- package/dist/core/status/fleet/fleet.js +6 -2
- package/dist/core/status/fleet/fleet.js.map +1 -1
- package/dist/core/status/fleet/next.js +26 -3
- package/dist/core/status/fleet/next.js.map +1 -1
- package/dist/core/status/report.js.map +1 -1
- package/dist/core/status/verification.js +30 -15
- package/dist/core/status/verification.js.map +1 -1
- package/dist/core/tasks/packet.js +174 -0
- package/dist/core/tasks/packet.js.map +1 -0
- package/dist/core/tasks/record.js +43 -0
- package/dist/core/tasks/record.js.map +1 -0
- package/dist/core/tasks/sources.js +181 -0
- package/dist/core/tasks/sources.js.map +1 -0
- package/dist/core/tasks/stamps.js +76 -0
- package/dist/core/tasks/stamps.js.map +1 -0
- package/dist/core/tasks/unit.js +0 -0
- package/dist/core/tasks/unit.js.map +1 -0
- package/dist/core/usecases/capability.js +11 -14
- package/dist/core/usecases/capability.js.map +1 -1
- package/dist/core/usecases/delta/flows.js +5 -7
- package/dist/core/usecases/delta/flows.js.map +1 -1
- package/dist/core/usecases/fleet.js +178 -31
- package/dist/core/usecases/fleet.js.map +1 -1
- package/dist/core/usecases/operations.js +5 -4
- package/dist/core/usecases/operations.js.map +1 -1
- package/dist/core/usecases/service/flows.js +9 -6
- package/dist/core/usecases/service/flows.js.map +1 -1
- package/dist/core/usecases/suites/participants.js +83 -0
- package/dist/core/usecases/suites/participants.js.map +1 -0
- package/dist/core/usecases/suites/suites.js +45 -0
- package/dist/core/usecases/suites/suites.js.map +1 -0
- package/dist/core/verify/answers.js +12 -5
- package/dist/core/verify/answers.js.map +1 -1
- package/dist/core/verify/build.js +102 -82
- package/dist/core/verify/build.js.map +1 -1
- package/dist/core/verify/checklist.js +45 -10
- package/dist/core/verify/checklist.js.map +1 -1
- package/dist/core/verify/location.js +67 -0
- package/dist/core/verify/location.js.map +1 -0
- package/dist/core/verify/pins/lint.js +3 -2
- package/dist/core/verify/pins/lint.js.map +1 -1
- package/dist/core/verify/pins/pin.js +1 -1
- package/dist/core/verify/pins/pin.js.map +1 -1
- package/dist/core/verify/read/file.js +224 -0
- package/dist/core/verify/read/file.js.map +1 -0
- package/dist/core/verify/{file.js → read/shape.js} +16 -104
- package/dist/core/verify/read/shape.js.map +1 -0
- package/dist/core/verify/read/union.js +105 -0
- package/dist/core/verify/read/union.js.map +1 -0
- package/dist/core/verify/record.js +35 -12
- package/dist/core/verify/record.js.map +1 -1
- package/dist/core/verify/store/commit.js +40 -30
- package/dist/core/verify/store/commit.js.map +1 -1
- package/dist/core/verify/store/render.js +8 -4
- package/dist/core/verify/store/render.js.map +1 -1
- package/dist/core/vocabulary/issue.js +1 -0
- package/dist/core/vocabulary/issue.js.map +1 -1
- package/dist/core/vocabulary/maturity.js +53 -3
- package/dist/core/vocabulary/maturity.js.map +1 -1
- package/examples/README.md +28 -9
- package/examples/docs/architecture/landscape.likec4 +21 -7
- package/examples/docs/architecture/services.likec4 +70 -0
- package/examples/docs/architecture/subsystems.likec4 +3 -0
- package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
- package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
- package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
- package/examples/docs/services/order-service/asyncapi.yaml +3 -2
- package/examples/docs/services/order-service/model.likec4 +10 -8
- package/examples/docs/services/payment-service/model.likec4 +6 -4
- package/examples/docs/services/platform/identity-service/model.likec4 +9 -7
- package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
- package/examples/docs/services/platform/notification-service/model.likec4 +4 -5
- package/package.json +1 -1
- package/dist/commands/validate/checks/vocabulary.js +0 -16
- package/dist/commands/validate/checks/vocabulary.js.map +0 -1
- package/dist/core/agent/agents-md/artifacts.js +0 -221
- package/dist/core/agent/agents-md/artifacts.js.map +0 -1
- package/dist/core/agent/agents-md/cycle.js +0 -180
- package/dist/core/agent/agents-md/cycle.js.map +0 -1
- package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
- package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
- package/dist/core/agent/agents-md/spine.js +0 -40
- package/dist/core/agent/agents-md/spine.js.map +0 -1
- package/dist/core/coherence/events/merged.js +0 -40
- package/dist/core/coherence/events/merged.js.map +0 -1
- package/dist/core/diff/current-state.js +0 -96
- package/dist/core/diff/current-state.js.map +0 -1
- package/dist/core/diff/victims.js +0 -256
- package/dist/core/diff/victims.js.map +0 -1
- package/dist/core/staging/snapshot.js.map +0 -1
- package/dist/core/verify/file.js.map +0 -1
|
@@ -37,10 +37,14 @@ reads the service, so a verdict is worth exactly what its evidence is worth.
|
|
|
37
37
|
must change.
|
|
38
38
|
2. The \`scenario.tested\` claims are the runner's to answer, not yours. In the
|
|
39
39
|
service's repo, run the generated suite with a JSON report —
|
|
40
|
-
\`npx cucumber-js --format json:report.json\` (cucumber-jvm, behave
|
|
41
|
-
SpecFlow
|
|
42
|
-
\`@loam-digest-…\` tags that ride into
|
|
43
|
-
|
|
40
|
+
\`npx cucumber-js --format json:report.json\` (cucumber-jvm, behave,
|
|
41
|
+
SpecFlow/Reqnroll, pytest-bdd \`--cucumberjson\` and godog emit the same
|
|
42
|
+
format). The generated scenarios carry \`@loam-digest-…\` tags that ride into
|
|
43
|
+
the report; that digest is the match, and the run must have executed every
|
|
44
|
+
step of the stamped scenario, so never retitle or hand-edit a generated
|
|
45
|
+
scenario to make one pass. A runner with no cucumber formatter answers
|
|
46
|
+
through loam's runner-neutral shape instead, keyed by the same digests:
|
|
47
|
+
\`{"loamScenarioReport": 1, "results": [{"digest": "…", "status": "passed"}]}\`.
|
|
44
48
|
If there is no runnable suite yet you MAY answer them yourself, and the record
|
|
45
49
|
will say so: \`answered_by: agent\` makes the feature **attested**, not verified
|
|
46
50
|
(\`verified: false\`, \`verdict: "attested"\`, the \`verify.scenario-attested\`
|
|
@@ -105,29 +109,36 @@ reads the service, so a verdict is worth exactly what its evidence is worth.
|
|
|
105
109
|
\`\`\`
|
|
106
110
|
|
|
107
111
|
\`--service <id>\` narrows the checklist to that service's claims and binds the
|
|
108
|
-
answers to this repository's commit, writing
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
answers to this repository's commit, writing that service's attestation to
|
|
113
|
+
its OWN file, \`features/$1/verification/<id>.yaml\` — the directory is the
|
|
114
|
+
record, and loam adds the files up when it reads. Running it in the next repo
|
|
115
|
+
writes that repo's file and touches no other, so two teams' docs PRs never
|
|
116
|
+
conflict over the record. In a repo whose ./loam.json
|
|
111
117
|
declares a \`service\`, \`--service\` may be omitted — it is substituted, and the
|
|
112
118
|
run still federates. \`--results report.json\` alone is legal when the
|
|
113
119
|
checklist is all scenarios.
|
|
114
120
|
|
|
115
121
|
⚠ **Never run \`loam verify $1 --record answers.json\` without \`--service\`.**
|
|
116
122
|
That is the legacy all-at-once form: it writes the WHOLE record from one place,
|
|
117
|
-
and over a record that already carries
|
|
118
|
-
refused (\`record-federated\`, naming them)
|
|
119
|
-
|
|
123
|
+
into the one file \`verification.yaml\`, and over a record that already carries
|
|
124
|
+
other services' attestations it is refused (\`record-federated\`, naming them)
|
|
125
|
+
precisely because it would put one unattested answer set over them. If you
|
|
126
|
+
meet that refusal, re-run with \`--service\`; do not work around it.
|
|
120
127
|
|
|
121
128
|
The other refusals: \`answers-mismatch\` (an id
|
|
122
129
|
nobody asked about, a claim you left out — re-run step 1, the feature moved —
|
|
123
130
|
or an answers entry for a \`scenario.tested\` claim: the runner owns those),
|
|
124
131
|
\`answers-unevidenced\` (a \`confirmed\` with nothing behind it),
|
|
125
132
|
\`answers-unreadable\` (a report or answers file it cannot read),
|
|
126
|
-
\`record-unreadable\` (a
|
|
127
|
-
|
|
128
|
-
\`
|
|
129
|
-
|
|
130
|
-
|
|
133
|
+
\`record-unreadable\` (a record file that exists but cannot be read — broken
|
|
134
|
+
YAML, git conflict markers a merge left, or \`verify.record-miscounted\` when
|
|
135
|
+
its \`summary\` block contradicts its own \`claims[]\`. It is never overwritten,
|
|
136
|
+
and the message names the file and the safe repair: a service's own
|
|
137
|
+
\`verification/<id>.yaml\` holds only that service's word, so that service
|
|
138
|
+
may delete it and record again; the one-file \`verification.yaml\` may hold
|
|
139
|
+
other services' attestations, so it is repaired or restored from version
|
|
140
|
+
control — after a merge, the copy on the branch you merged into — and
|
|
141
|
+
**never deleted**. Never edit the counts to make them agree),
|
|
131
142
|
\`service-mismatch\` (\`--service\` names a service this repo does not declare) and
|
|
132
143
|
\`repository-unavailable\` (\`--service\` from a repo that declares none, or whose
|
|
133
144
|
commit cannot be read — an attestation must be bound to the code it is about).
|
|
@@ -161,8 +172,8 @@ gathered from the steps above; branch on the code, never the prose:
|
|
|
161
172
|
| \`verify.scenario-attested\` (warn) | scenario claims were answered on an agent's word (\`answered_by: agent\`), so the verdict is **attested**, not verified — nothing gates on it, but say so when you hand back | run the generated suite with a JSON report and re-record with \`--results report.json\` the moment a suite exists |
|
|
162
173
|
| \`verify.digest-contested\` (warn) | two or more services word a scenario identically, so they share one \`@loam-digest-…\` tag and no single cucumber report can say whose suite ran it — those scenario claims stay unconfirmed | each service records its own claims from its own repository with \`--service <id>\` |
|
|
163
174
|
| \`verify.operation-contested\` (warn) | two services on the checklist expose the same operationId, so a contract report — which names no service — cannot say whose suite exercised it; those claims stay unconfirmed | each service records its own with \`--service <id>\`, passing its own contract report |
|
|
164
|
-
| \`verify.checklist-forked\` (warn) | one or more services answered a DIFFERENT version of the feature's question set than the one it now asks — each attestation carries the \`checklist\` digest it answered, and the notice names the ones that do not match the current checklist. Measured against the present, not pairwise: services that all attested against a since-rewritten delta agree with each other perfectly and are all stale. Normal mid-rollout, since services attest as they finish. Attestations written before the field existed carry no digest and are never called stale | re-record the services on the older version from their own repositories (\`loam verify <FEAT> --service <id> --record\`)
|
|
165
|
-
| \`verify.record-miscounted\` (error) | a
|
|
175
|
+
| \`verify.checklist-forked\` (warn) | one or more services answered a DIFFERENT version of the feature's question set than the one it now asks — each attestation carries the \`checklist\` digest it answered, and the notice names the ones that do not match the current checklist. Measured against the present, not pairwise: services that all attested against a since-rewritten delta agree with each other perfectly and are all stale. Normal mid-rollout, since services attest as they finish. Attestations written before the field existed carry no digest and are never called stale. In a per-service record the answers of the services named do not count until they record again | re-record the services on the older version from their own repositories (\`loam verify <FEAT> --service <id> --record\`) — nothing gates on it |
|
|
176
|
+
| \`verify.record-miscounted\` (error) | a record file whose \`summary\` block contradicts its own \`claims[]\` — neither half can be believed, so the record is refused as unreadable rather than trusted in either direction | a service's own \`verification/<id>.yaml\`: delete it and re-record from that service's repo; the one-file \`verification.yaml\`: restore it from version control, then re-record — never delete it, and never edit the counts by hand |
|
|
166
177
|
| \`verify.evidence-token-missing\` (warn) | a just-confirmed citation's file at the attested commit does not contain the claim's own token (the operationId, message name or edge op) — the evidence may not show what the answer says | re-read that evidence while the answer can still be corrected; verdict-neutral, it gates nothing |
|
|
167
178
|
| \`cross.agree-confirmed\` / \`cross.agree-unconfirmed\` (ok) | a \`--diff-answers\` row where both blind answer sets reach the same verdict — the cheap-to-skip review queue, though same-model agreement is correlated and proves nothing | nothing on its own; record only the answer set you stand behind, in a separate run |
|
|
168
179
|
| \`cross.disagree\` (the rows a human reads first) | a \`--diff-answers\` row where the two blind answer sets contradict each other — one of them is wrong about the code | read both cited evidences and decide; the diff writes nothing and never moves a verdict |
|
|
@@ -181,6 +192,7 @@ export const LOAM_SHIP = {
|
|
|
181
192
|
"`loam validate --feature $1` must come back valid; if it says nothing pinned the delta, rebase and re-validate first",
|
|
182
193
|
"`loam archive $1 --dry-run` — read the whole plan and every warning before the merge touches anything",
|
|
183
194
|
"`loam archive $1` for real, and read what it reports it did",
|
|
195
|
+
"every vouched spec on `revouchOwed[]` owes its owner's re-vouch — tell them; it lands in this same PR",
|
|
184
196
|
"`loam unarchive $1` is the undo if the plan turns out to have been wrong",
|
|
185
197
|
],
|
|
186
198
|
body: `Archive a shipped feature.
|
|
@@ -202,8 +214,10 @@ export const LOAM_SHIP = {
|
|
|
202
214
|
truth, and stop if a file you did not expect is on it. \`warnings[]\` is what the
|
|
203
215
|
merge will do that is legal but lossy — advisory warnings never block, which is
|
|
204
216
|
exactly why you read them now: \`openapi.op-modified\` means an operation the living
|
|
205
|
-
contract already defines gets overwritten wholesale
|
|
206
|
-
|
|
217
|
+
contract already defines gets overwritten wholesale, and its \`details\` name the
|
|
218
|
+
current consumers that overwrite lands on (\`asyncapi.message-modified\` likewise for
|
|
219
|
+
a message; \`openapi.component-modified\` is the same story for a component the
|
|
220
|
+
merged operations carry, with no consumer list),
|
|
207
221
|
\`service.no-model\` means a service arrives with no C4 centre behind it, and
|
|
208
222
|
\`delta.added-conflict\`
|
|
209
223
|
means another feature in flight adds the same requirement — the first to archive
|
|
@@ -215,7 +229,11 @@ export const LOAM_SHIP = {
|
|
|
215
229
|
elements and edges into \`architecture/landscape.likec4\` and, for an addition nested
|
|
216
230
|
under a service whose model EXTENDS the map, into that
|
|
217
231
|
\`services/<…>/<svc>/model.likec4\` instead — then moves the feature
|
|
218
|
-
under \`features/archive
|
|
232
|
+
under \`features/archive/\`, whole: its verification record travels with it,
|
|
233
|
+
the per-service \`verification/<id>.yaml\` files and any one-file
|
|
234
|
+
\`verification.yaml\` alike, and \`loam verify $1\` reads it back afterwards as
|
|
235
|
+
frozen history. Verification never gates the archive — an open or missing
|
|
236
|
+
record ships as exactly that. Success is \`ok: true\`; on \`ok: false\`, branch on
|
|
219
237
|
\`error.code\`:
|
|
220
238
|
- \`not-coherent\` — gating coherence issues; \`issues[]\` in the envelope lists them,
|
|
221
239
|
each with \`gates\` and \`overridable\` resolved (advisory warnings do not block).
|
|
@@ -228,7 +246,12 @@ export const LOAM_SHIP = {
|
|
|
228
246
|
\`overridable: false\`, and the fix is a rename, never the flag.
|
|
229
247
|
\`openapi.remove-marker-path-level\` arrives
|
|
230
248
|
here too: an \`x-loam-remove\` written beside the methods instead of inside the
|
|
231
|
-
operation retires nothing.
|
|
249
|
+
operation retires nothing. So do \`openapi.op-revived\` and
|
|
250
|
+
\`asyncapi.slot-revived\`: the delta spells an operation, slot or message an
|
|
251
|
+
ARCHIVED feature retired, and the merge would put it back. If the delta only
|
|
252
|
+
quoted it, delete the quote; whether the feature brings it back on purpose is
|
|
253
|
+
the human's \`--approve\`, and the archive output then names what came back
|
|
254
|
+
and whose retirement it reverses.
|
|
232
255
|
- \`living-outside-requirements\` — the LIVING spec holds a requirement outside
|
|
233
256
|
\`## Requirements\`, and the merge would duplicate it. Re-home it, then re-run.
|
|
234
257
|
\`--approve\` does not move this one.
|
|
@@ -250,6 +273,16 @@ export const LOAM_SHIP = {
|
|
|
250
273
|
name the same state) and hand it to a human — re-running does not help.
|
|
251
274
|
- \`rollback-incomplete\` — the merge failed AND some files could not be restored.
|
|
252
275
|
Stop and hand it to a human; the message lists the files to check.
|
|
276
|
+
5. Re-vouches are owed. \`revouchOwed[]\` — on the dry run and the real merge alike —
|
|
277
|
+
names every VOUCHED spec the merge moves under its vouch stamp, with the
|
|
278
|
+
document's \`owner\`. \`content.stale\` follows on each, and honestly: the merged
|
|
279
|
+
text is words nobody has yet read against the code. You cannot clear it — a
|
|
280
|
+
vouch is a person's, run in that service's own repo (\`loam vouch --service <id>\`,
|
|
281
|
+
after \`loam vouch --pack --service <id>\` for what to re-read). Name the
|
|
282
|
+
documents and owners in your hand-back, and say the re-vouches belong in the
|
|
283
|
+
SAME PR as this archive: a docs repo gated on
|
|
284
|
+
\`loam validate --all --base <ref> --strict\` keeps the archive's PR red until they
|
|
285
|
+
land. Never edit a stamp by hand to turn it green.
|
|
253
286
|
|
|
254
287
|
Archived by mistake? \`loam unarchive $1\` restores the living docs from the snapshot
|
|
255
288
|
archive left in \`features/archive/$1*/.loam-before/\` and re-opens the feature. Do not
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"closing.js","sourceRoot":"","sources":["../../../../src/core/agent/workflows/closing.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAAmB;IACzC,IAAI,EAAE,aAAa;IACnB,WAAW,EACT,qFAAqF;IACvF,YAAY,EAAE,WAAW;IACzB,OAAO,EACL,6NAA6N;IAC/N,UAAU,EAAE,kCAAkC;IAC9C,YAAY,EAAE,CAAC,SAAS,CAAC;IACzB,KAAK,EAAE;QACL,yEAAyE;QACzE,uGAAuG;QACvG,sFAAsF;QACtF,2EAA2E;QAC3E,6FAA6F;KAC9F;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoJP;CACA,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAmB;IACvC,IAAI,EAAE,WAAW;IACjB,WAAW,EAAE,yEAAyE;IACtF,YAAY,EAAE,WAAW;IACzB,OAAO,EACL,0MAA0M;IAC5M,UAAU,EAAE,gCAAgC;IAC5C,YAAY,EAAE,CAAC,SAAS,CAAC;IACzB,KAAK,EAAE;QACL,oHAAoH;QACpH,sHAAsH;QACtH,uGAAuG;QACvG,6DAA6D;QAC7D,0EAA0E;KAC3E;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4EP;CACA,CAAC","sourcesContent":["import type { CommandContent } from \"../contract.js\";\n\n/**\n * The two post-build workflows, paired: the done-check (`loam-verify`) and the\n * merge it precedes (`loam-ship`). One file for two commands because six\n * workflows meet the five-file package cap somewhere, and this is the seam the\n * cycle already has — both describe what happens after the code is built, and\n * each body names the other's refusals (`record-federated`, `not-coherent`).\n */\nexport const LOAM_VERIFY: CommandContent = {\n name: \"loam-verify\",\n description:\n \"Verify a built loam feature against its own promises, claim by claim, with evidence\",\n argumentHint: \"<FEAT-id>\",\n purpose:\n \"Check that the code somebody built is the feature that was designed. loam derives the questions; a test runner and you answer them. loam never reads the service, so a verdict is worth exactly what its evidence is worth.\",\n invocation: \"loam instructions loam-verify $1\",\n placeholders: [\"feature\"],\n spine: [\n \"`loam verify` — the checklist, derived from the feature's own artifacts\",\n \"run the generated suite with a JSON report: the scenario claims are the runner's to answer, not yours\",\n \"answer the remaining claims yourself, every one carrying a `file:line` that resolves\",\n \"record from each affected service's own repository, bound to that service\",\n \"read the verdict honestly — a scenario confirmed on your word is `attested`, not `verified`\",\n ],\n body: `Check that the code somebody just built is the feature that was designed.\n\nloam derives the questions; the test runner and you answer them. loam never\nreads the service, so a verdict is worth exactly what its evidence is worth.\n\n1. \\`loam verify $1 --json\\`. \\`claims[]\\` is the checklist, derived from the feature's\n own artifacts — one claim per new service, per operation its openapi delta adds,\n per (direction, message) its asyncapi delta newly declares against the living\n contract's send/receive sets (quotes and removals are never questions),\n per tagged edge that names an operation, and per scenario of every changed\n requirement, arch.spec.md deltas included. Each has a stable \\`id\\` and a\n \\`subject\\` (the service whose code answers it); a claim that says arch.spec.md\n wants an integration/ops test, not an acceptance test. An ARCHIVED feature returns its record as frozen history instead\n (\\`frozen: true\\`) and \\`--record\\` / \\`--results\\` / \\`--contract-results\\` /\n \\`--diff-answers\\` refuse it — \\`loam unarchive\\` first if the answers really\n must change.\n2. The \\`scenario.tested\\` claims are the runner's to answer, not yours. In the\n service's repo, run the generated suite with a JSON report —\n \\`npx cucumber-js --format json:report.json\\` (cucumber-jvm, behave and\n SpecFlow emit the same format). The generated scenarios carry\n \\`@loam-digest-…\\` tags that ride into the report; that digest is the match,\n so never retitle or hand-edit a generated scenario to make one pass.\n If there is no runnable suite yet you MAY answer them yourself, and the record\n will say so: \\`answered_by: agent\\` makes the feature **attested**, not verified\n (\\`verified: false\\`, \\`verdict: \"attested\"\\`, the \\`verify.scenario-attested\\`\n notice, and a \\`next.verify-attested\\` step in \\`loam status\\`). Nothing gates on\n it — a legacy service with no suite must still be able to ship — but say so\n when you hand back, and close it with \\`--results\\` the moment a suite runs.\n \\`verify.digest-contested\\` (warn) is the case where two services word a\n scenario identically: they share one digest, so no single report can say whose\n suite ran it, and each service must record its own with \\`--service\\`.\n The \\`api.exposes\\` claims have a mechanical channel too, when an API\n contract-test suite exists: emit loam's contract-results shape from its\n report — \\`{\"loamContractReport\": 1, \"results\": [{\"operationId\": \"…\",\n \"status\": \"passed\"}]}\\`, a one-line transform for Specmatic, Pact, Dredd or a\n property harness — and pass \\`--contract-results contract.json\\` alongside.\n Only a status of exactly \\`passed\\` confirms the named operation\n (\\`answered_by: external-runner\\`); anything else, and an operation the report\n never exercised, stays \\`unconfirmed\\` with the reason, and under the flag the\n report owns every api.exposes claim, so take those out of the answers file.\n3. Answer every OTHER claim by finding it in the code. Not by reasoning that it\n must be there — by opening the file:\n - \\`service.exists\\` — the service is deployable: its build, its entry point.\n - \\`api.exposes\\` — the route handler serving that operationId, not the spec that\n declares it — unless a contract-test report already answered it in step 2.\n - \\`event.declares\\` — the code that puts the message on the wire (sends) or\n handles it off the wire (receives), not the contract that names it.\n - \\`c4.calls\\` — the call site in the CALLER.\n4. Write those answers as JSON — evidence is \\`file:line\\`, one entry per place it can\n be seen:\n \\`\\`\\`json\n { \"answers\": [\n { \"id\": \"<claim id>\", \"verdict\": \"confirmed\", \"evidence\": [\"src/split/Api.ts:42\"] },\n { \"id\": \"<claim id>\", \"verdict\": \"unconfirmed\", \"note\": \"the split service has no entry point yet\" }\n ] }\n \\`\\`\\`\n5. Optional, before recording: cross-examine. Have a SECOND agent answer the\n same checklist blind — a fresh context, given only \\`loam verify $1 --json\\`\n and the code, never your answers file — then compare the two sets:\n\n \\`\\`\\`sh\n loam verify $1 --diff-answers yours.json theirs.json --json\n \\`\\`\\`\n\n Both files must answer EVERY claim in the compared scope,\n \\`scenario.tested\\` included: the diff consults no test report, so this is a\n DIFFERENT file from the one step 6 records under \\`--results\\` (that one\n must omit the runner's claims; a step-6-shaped file here refuses\n \\`answers-mismatch\\`). Narrowing with \\`--service <id>\\` compares one\n service's claims — give the second agent the same lens, or its file will\n not match the compared scope. An archived feature refuses the diff too:\n frozen history has no current checklist. It writes nothing and changes no\n verdict: \\`cross.agree-confirmed\\` / \\`cross.agree-unconfirmed\\` rows are the\n cheap-to-skip queue, \\`disagreements[]\\` (\\`cross.disagree\\`) is what a human\n reads first, and the \\`cross.evidence-disjoint\\` notice flags agreements\n citing non-overlapping files — read those second. Agreement between two\n same-model agents is correlated, so it ranks review; it never upgrades\n \\`attested\\` to \\`verified\\`. Record only the answer set you stand behind, in\n a separate run.\n6. Record, **in each affected service's own repository**, one service at a time:\n\n \\`\\`\\`sh\n loam verify $1 --service <id> --results report.json [--contract-results contract.json] --record answers.json --json\n \\`\\`\\`\n\n \\`--service <id>\\` narrows the checklist to that service's claims and binds the\n answers to this repository's commit, writing ONE attestation into a shared\n \\`schema: 2\\` \\`verification.yaml\\`. Running it in the next repo adds that repo's\n attestation and leaves the others standing. In a repo whose ./loam.json\n declares a \\`service\\`, \\`--service\\` may be omitted — it is substituted, and the\n run still federates. \\`--results report.json\\` alone is legal when the\n checklist is all scenarios.\n\n ⚠ **Never run \\`loam verify $1 --record answers.json\\` without \\`--service\\`.**\n That is the legacy all-at-once form: it writes the WHOLE record from one place,\n and over a record that already carries other services' attestations it is\n refused (\\`record-federated\\`, naming them) precisely because it would erase\n them. If you meet that refusal, re-run with \\`--service\\`; do not work around it.\n\n The other refusals: \\`answers-mismatch\\` (an id\n nobody asked about, a claim you left out — re-run step 1, the feature moved —\n or an answers entry for a \\`scenario.tested\\` claim: the runner owns those),\n \\`answers-unevidenced\\` (a \\`confirmed\\` with nothing behind it),\n \\`answers-unreadable\\` (a report or answers file it cannot read),\n \\`record-unreadable\\` (a \\`verification.yaml\\` that exists but cannot be read as a\n record — it is never overwritten; a human repairs it, and\n \\`verify.record-miscounted\\` is the reason when its \\`summary\\` block contradicts\n its own \\`claims[]\\`: neither half can be believed, so re-record rather than\n editing the counts),\n \\`service-mismatch\\` (\\`--service\\` names a service this repo does not declare) and\n \\`repository-unavailable\\` (\\`--service\\` from a repo that declares none, or whose\n commit cannot be read — an attestation must be bound to the code it is about).\n7. Report every \\`unconfirmed\\` claim and what it would take to close it, and read\n \\`discarded\\` in the payload: it names every previous answer the new record does\n not carry, and each one is either evidence that went stale or evidence that was\n just dropped. A scenario claim the report failed or missed closes by fixing the\n code and re-running the suite — never by editing the generated .feature. While\n any claim stays unconfirmed or unanswered, \\`notices[]\\` carries\n \\`verify.claims-open\\` (warn, gating nothing): the one-line \"not a clean\n result\" summary with all four counts, so a partial record can never read as a\n finished one from the notice lines alone.\n8. Reading is safe from anywhere: \\`loam verify $1 --service <id> --json\\` without\n \\`--record\\` writes nothing and works from the docs repo too. \\`services\\` lists\n every subject of the checklist, so it is how you find out which repos still\n owe an attestation, and \\`loam status $1 --json\\` says the same thing in the\n shape of a next step.\n\nAnything you cannot show is \\`unconfirmed\\` with a note. That is a successful record,\nand it is the useful one: nothing gates on this file, so the only thing it is for is\ntelling the next reader the truth. \\`loam archive\\` will ship the feature either way —\nwhich is exactly why a \\`confirmed\\` you cannot back up costs nothing now and misleads\neveryone later.\n\n\\`loam verify\\` — the notice and record codes its reports carry in \\`notices[]\\`,\ngathered from the steps above; branch on the code, never the prose:\n\n| code | what it means | what to do |\n|---|---|---|\n| \\`verify.claims-open\\` (warn) | a recorded feature still has unconfirmed or unanswered claims — the one-line \"not a clean result\" summary with all four counts, so a partial record can never read as finished from the notice lines alone; a feature with no record at all never carries it | close each open claim: fix the code and re-run the suite for a failed scenario, re-answer the rest with evidence, then re-record |\n| \\`verify.scenario-attested\\` (warn) | scenario claims were answered on an agent's word (\\`answered_by: agent\\`), so the verdict is **attested**, not verified — nothing gates on it, but say so when you hand back | run the generated suite with a JSON report and re-record with \\`--results report.json\\` the moment a suite exists |\n| \\`verify.digest-contested\\` (warn) | two or more services word a scenario identically, so they share one \\`@loam-digest-…\\` tag and no single cucumber report can say whose suite ran it — those scenario claims stay unconfirmed | each service records its own claims from its own repository with \\`--service <id>\\` |\n| \\`verify.operation-contested\\` (warn) | two services on the checklist expose the same operationId, so a contract report — which names no service — cannot say whose suite exercised it; those claims stay unconfirmed | each service records its own with \\`--service <id>\\`, passing its own contract report |\n| \\`verify.checklist-forked\\` (warn) | one or more services answered a DIFFERENT version of the feature's question set than the one it now asks — each attestation carries the \\`checklist\\` digest it answered, and the notice names the ones that do not match the current checklist. Measured against the present, not pairwise: services that all attested against a since-rewritten delta agree with each other perfectly and are all stale. Normal mid-rollout, since services attest as they finish. Attestations written before the field existed carry no digest and are never called stale | re-record the services on the older version from their own repositories (\\`loam verify <FEAT> --service <id> --record\\`), or accept the split knowingly — nothing gates on it |\n| \\`verify.record-miscounted\\` (error) | a verification.yaml whose \\`summary\\` block contradicts its own \\`claims[]\\` — neither half can be believed, so the record is refused as unreadable rather than trusted in either direction | re-record from the service's repo; never edit the counts by hand |\n| \\`verify.evidence-token-missing\\` (warn) | a just-confirmed citation's file at the attested commit does not contain the claim's own token (the operationId, message name or edge op) — the evidence may not show what the answer says | re-read that evidence while the answer can still be corrected; verdict-neutral, it gates nothing |\n| \\`cross.agree-confirmed\\` / \\`cross.agree-unconfirmed\\` (ok) | a \\`--diff-answers\\` row where both blind answer sets reach the same verdict — the cheap-to-skip review queue, though same-model agreement is correlated and proves nothing | nothing on its own; record only the answer set you stand behind, in a separate run |\n| \\`cross.disagree\\` (the rows a human reads first) | a \\`--diff-answers\\` row where the two blind answer sets contradict each other — one of them is wrong about the code | read both cited evidences and decide; the diff writes nothing and never moves a verdict |\n| \\`cross.evidence-disjoint\\` (warn) | a \\`--diff-answers\\` notice: claims BOTH sets confirm while citing non-overlapping files — two yeses that looked at different evidence | read those agreements second, after the disagreements; agreement over disjoint evidence is two guesses, not one proof |\n`,\n};\n\nexport const LOAM_SHIP: CommandContent = {\n name: \"loam-ship\",\n description: \"Archive a finished loam feature — merge its deltas into the living docs\",\n argumentHint: \"<FEAT-id>\",\n purpose:\n \"Merge a shipped feature into the living specs, API and landscape. This is the one command that rewrites the source of truth, so every step before it exists to make the merge reviewable before it runs.\",\n invocation: \"loam instructions loam-ship $1\",\n placeholders: [\"feature\"],\n spine: [\n \"confirm the code is actually built and merged — archiving early makes the docs claim something that does not exist\",\n \"`loam validate --feature $1` must come back valid; if it says nothing pinned the delta, rebase and re-validate first\",\n \"`loam archive $1 --dry-run` — read the whole plan and every warning before the merge touches anything\",\n \"`loam archive $1` for real, and read what it reports it did\",\n \"`loam unarchive $1` is the undo if the plan turns out to have been wrong\",\n ],\n body: `Archive a shipped feature.\n\n1. Confirm the code is actually built and merged. Archiving folds the delta into the\n living docs; doing it early makes the docs claim something that does not exist.\n \\`loam status $1 --json\\` answers \"is this actually finished\" in one call, and its\n \\`next[]\\` names what is outstanding if it is not.\n2. \\`loam validate --feature $1 --json\\` — must come back \\`valid: true\\`.\n If it reports \\`delta.baseline-missing\\` or \\`openapi.baseline-missing\\`, run\n \\`loam rebase $1\\` and re-validate BEFORE going on: those two say nothing has\n pinned what this delta was written against, so the merge cannot tell the text\n you EDITED from the text you merely restated. Both gate the archive — the\n dry run in step 3 refuses on them too — and the only way past without the\n pins is a human's \\`--approve\\`.\n3. \\`loam archive $1 --dry-run --json\\` first. \\`plan[]\\` is every file the merge would\n write (\\`create\\` / \\`update\\`, then the final \\`move\\` into \\`features/archive/\\`), and\n none of them are written — read it before letting the merge touch the source of\n truth, and stop if a file you did not expect is on it. \\`warnings[]\\` is what the\n merge will do that is legal but lossy — advisory warnings never block, which is\n exactly why you read them now: \\`openapi.op-modified\\` means an operation the living\n contract already defines gets overwritten wholesale (\\`openapi.component-modified\\`\n is the same story for a component the merged operations carry),\n \\`service.no-model\\` means a service arrives with no C4 centre behind it, and\n \\`delta.added-conflict\\`\n means another feature in flight adds the same requirement — the first to archive\n lands it, and the other's archive is then refused (\\`delta.added-duplicate\\`: its\n ADDED now collides with the living spec) unless a human \\`--approve\\`s the\n replacement.\n4. \\`loam archive $1 --json\\`. It merges three axes into the living state — requirements\n into \\`services/<svc>/spec.md\\`, endpoints into \\`services/<svc>/openapi.yaml\\`,\n elements and edges into \\`architecture/landscape.likec4\\` and, for an addition nested\n under a service whose model EXTENDS the map, into that\n \\`services/<…>/<svc>/model.likec4\\` instead — then moves the feature\n under \\`features/archive/\\`. Success is \\`ok: true\\`; on \\`ok: false\\`, branch on\n \\`error.code\\`:\n - \\`not-coherent\\` — gating coherence issues; \\`issues[]\\` in the envelope lists them,\n each with \\`gates\\` and \\`overridable\\` resolved (advisory warnings do not block).\n Fix the breaches. \\`--approve\\` overrides the gating issues and is a human's\n call to make, not yours: report the breach and stop. Two codes it does not\n override even then — \\`delta.service-id-invalid\\` (a \\`specs/<svc>/\\` name the\n id grammar refuses) and \\`c4.service-binding-invalid\\` (a \\`metadata { service }\\`\n binding it refuses, anywhere inside a tagged element's block): each names a\n path the merge would write mechanically, they arrive with\n \\`overridable: false\\`, and the fix is a rename, never the flag.\n \\`openapi.remove-marker-path-level\\` arrives\n here too: an \\`x-loam-remove\\` written beside the methods instead of inside the\n operation retires nothing.\n - \\`living-outside-requirements\\` — the LIVING spec holds a requirement outside\n \\`## Requirements\\`, and the merge would duplicate it. Re-home it, then re-run.\n \\`--approve\\` does not move this one.\n - \\`archive-exists\\` — \\`features/archive/\\` already has that directory; a human\n decides what it is.\n - \\`merge-failed\\` — the merge could not be computed, or failed and was rolled\n back. Either way the living docs are unchanged; fix the reported cause, re-run.\n A file loam cannot decode as UTF-8 refuses here by name rather than being\n rewritten with replacement characters. So does a living document that still\n holds git conflict markers — the \\`spec.merge-conflict\\` /\n \\`landscape.merge-conflict\\` findings ride in \\`findings[]\\`, and \\`--approve\\`\n does not override them: the loss is mechanical, not a judgment call.\n - \\`docs-busy\\` — another \\`loam archive\\` or \\`loam unarchive\\` holds the docs\n repo's lock. Nothing was read or written; wait for it and re-run once.\n - \\`commit-interrupted\\` — a previous archive or unarchive was killed mid-commit\n and this run cannot repair it on its own: a half-written file was edited\n since, its pre-image is gone or altered, or \\`.loam-commit\\` cannot be read.\n Run \\`loam doctor\\` (\\`doctor.commit-interrupted\\` / \\`doctor.commit-unreadable\\`\n name the same state) and hand it to a human — re-running does not help.\n - \\`rollback-incomplete\\` — the merge failed AND some files could not be restored.\n Stop and hand it to a human; the message lists the files to check.\n\nArchived by mistake? \\`loam unarchive $1\\` restores the living docs from the snapshot\narchive left in \\`features/archive/$1*/.loam-before/\\` and re-opens the feature. Do not\nhand-edit the living docs back instead: the previous text of a \\`MODIFIED\\` requirement\nis in that snapshot and nowhere else, so anything you write by hand is a guess. It\nrefuses under \\`snapshot-corrupt\\` when a pre-image's bytes no longer match the digest\nthe archive recorded — \\`--force\\` does not override that one, because the damage is\nto the undo itself — and under \\`snapshot-missing\\` for an archive older than the\ncurrent snapshot format. Both mean version control is the way back.\n`,\n};\n"]}
|
|
1
|
+
{"version":3,"file":"closing.js","sourceRoot":"","sources":["../../../../src/core/agent/workflows/closing.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAAmB;IACzC,IAAI,EAAE,aAAa;IACnB,WAAW,EACT,qFAAqF;IACvF,YAAY,EAAE,WAAW;IACzB,OAAO,EACL,6NAA6N;IAC/N,UAAU,EAAE,kCAAkC;IAC9C,YAAY,EAAE,CAAC,SAAS,CAAC;IACzB,KAAK,EAAE;QACL,yEAAyE;QACzE,uGAAuG;QACvG,sFAAsF;QACtF,2EAA2E;QAC3E,6FAA6F;KAC9F;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+JP;CACA,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAmB;IACvC,IAAI,EAAE,WAAW;IACjB,WAAW,EAAE,yEAAyE;IACtF,YAAY,EAAE,WAAW;IACzB,OAAO,EACL,0MAA0M;IAC5M,UAAU,EAAE,gCAAgC;IAC5C,YAAY,EAAE,CAAC,SAAS,CAAC;IACzB,KAAK,EAAE;QACL,oHAAoH;QACpH,sHAAsH;QACtH,uGAAuG;QACvG,6DAA6D;QAC7D,uGAAuG;QACvG,0EAA0E;KAC3E;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiGP;CACA,CAAC","sourcesContent":["import type { CommandContent } from \"../contract.js\";\n\n/**\n * The two post-build workflows, paired: the done-check (`loam-verify`) and the\n * merge it precedes (`loam-ship`). One file for two commands because six\n * workflows meet the five-file package cap somewhere, and this is the seam the\n * cycle already has — both describe what happens after the code is built, and\n * each body names the other's refusals (`record-federated`, `not-coherent`).\n */\nexport const LOAM_VERIFY: CommandContent = {\n name: \"loam-verify\",\n description:\n \"Verify a built loam feature against its own promises, claim by claim, with evidence\",\n argumentHint: \"<FEAT-id>\",\n purpose:\n \"Check that the code somebody built is the feature that was designed. loam derives the questions; a test runner and you answer them. loam never reads the service, so a verdict is worth exactly what its evidence is worth.\",\n invocation: \"loam instructions loam-verify $1\",\n placeholders: [\"feature\"],\n spine: [\n \"`loam verify` — the checklist, derived from the feature's own artifacts\",\n \"run the generated suite with a JSON report: the scenario claims are the runner's to answer, not yours\",\n \"answer the remaining claims yourself, every one carrying a `file:line` that resolves\",\n \"record from each affected service's own repository, bound to that service\",\n \"read the verdict honestly — a scenario confirmed on your word is `attested`, not `verified`\",\n ],\n body: `Check that the code somebody just built is the feature that was designed.\n\nloam derives the questions; the test runner and you answer them. loam never\nreads the service, so a verdict is worth exactly what its evidence is worth.\n\n1. \\`loam verify $1 --json\\`. \\`claims[]\\` is the checklist, derived from the feature's\n own artifacts — one claim per new service, per operation its openapi delta adds,\n per (direction, message) its asyncapi delta newly declares against the living\n contract's send/receive sets (quotes and removals are never questions),\n per tagged edge that names an operation, and per scenario of every changed\n requirement, arch.spec.md deltas included. Each has a stable \\`id\\` and a\n \\`subject\\` (the service whose code answers it); a claim that says arch.spec.md\n wants an integration/ops test, not an acceptance test. An ARCHIVED feature returns its record as frozen history instead\n (\\`frozen: true\\`) and \\`--record\\` / \\`--results\\` / \\`--contract-results\\` /\n \\`--diff-answers\\` refuse it — \\`loam unarchive\\` first if the answers really\n must change.\n2. The \\`scenario.tested\\` claims are the runner's to answer, not yours. In the\n service's repo, run the generated suite with a JSON report —\n \\`npx cucumber-js --format json:report.json\\` (cucumber-jvm, behave,\n SpecFlow/Reqnroll, pytest-bdd \\`--cucumberjson\\` and godog emit the same\n format). The generated scenarios carry \\`@loam-digest-…\\` tags that ride into\n the report; that digest is the match, and the run must have executed every\n step of the stamped scenario, so never retitle or hand-edit a generated\n scenario to make one pass. A runner with no cucumber formatter answers\n through loam's runner-neutral shape instead, keyed by the same digests:\n \\`{\"loamScenarioReport\": 1, \"results\": [{\"digest\": \"…\", \"status\": \"passed\"}]}\\`.\n If there is no runnable suite yet you MAY answer them yourself, and the record\n will say so: \\`answered_by: agent\\` makes the feature **attested**, not verified\n (\\`verified: false\\`, \\`verdict: \"attested\"\\`, the \\`verify.scenario-attested\\`\n notice, and a \\`next.verify-attested\\` step in \\`loam status\\`). Nothing gates on\n it — a legacy service with no suite must still be able to ship — but say so\n when you hand back, and close it with \\`--results\\` the moment a suite runs.\n \\`verify.digest-contested\\` (warn) is the case where two services word a\n scenario identically: they share one digest, so no single report can say whose\n suite ran it, and each service must record its own with \\`--service\\`.\n The \\`api.exposes\\` claims have a mechanical channel too, when an API\n contract-test suite exists: emit loam's contract-results shape from its\n report — \\`{\"loamContractReport\": 1, \"results\": [{\"operationId\": \"…\",\n \"status\": \"passed\"}]}\\`, a one-line transform for Specmatic, Pact, Dredd or a\n property harness — and pass \\`--contract-results contract.json\\` alongside.\n Only a status of exactly \\`passed\\` confirms the named operation\n (\\`answered_by: external-runner\\`); anything else, and an operation the report\n never exercised, stays \\`unconfirmed\\` with the reason, and under the flag the\n report owns every api.exposes claim, so take those out of the answers file.\n3. Answer every OTHER claim by finding it in the code. Not by reasoning that it\n must be there — by opening the file:\n - \\`service.exists\\` — the service is deployable: its build, its entry point.\n - \\`api.exposes\\` — the route handler serving that operationId, not the spec that\n declares it — unless a contract-test report already answered it in step 2.\n - \\`event.declares\\` — the code that puts the message on the wire (sends) or\n handles it off the wire (receives), not the contract that names it.\n - \\`c4.calls\\` — the call site in the CALLER.\n4. Write those answers as JSON — evidence is \\`file:line\\`, one entry per place it can\n be seen:\n \\`\\`\\`json\n { \"answers\": [\n { \"id\": \"<claim id>\", \"verdict\": \"confirmed\", \"evidence\": [\"src/split/Api.ts:42\"] },\n { \"id\": \"<claim id>\", \"verdict\": \"unconfirmed\", \"note\": \"the split service has no entry point yet\" }\n ] }\n \\`\\`\\`\n5. Optional, before recording: cross-examine. Have a SECOND agent answer the\n same checklist blind — a fresh context, given only \\`loam verify $1 --json\\`\n and the code, never your answers file — then compare the two sets:\n\n \\`\\`\\`sh\n loam verify $1 --diff-answers yours.json theirs.json --json\n \\`\\`\\`\n\n Both files must answer EVERY claim in the compared scope,\n \\`scenario.tested\\` included: the diff consults no test report, so this is a\n DIFFERENT file from the one step 6 records under \\`--results\\` (that one\n must omit the runner's claims; a step-6-shaped file here refuses\n \\`answers-mismatch\\`). Narrowing with \\`--service <id>\\` compares one\n service's claims — give the second agent the same lens, or its file will\n not match the compared scope. An archived feature refuses the diff too:\n frozen history has no current checklist. It writes nothing and changes no\n verdict: \\`cross.agree-confirmed\\` / \\`cross.agree-unconfirmed\\` rows are the\n cheap-to-skip queue, \\`disagreements[]\\` (\\`cross.disagree\\`) is what a human\n reads first, and the \\`cross.evidence-disjoint\\` notice flags agreements\n citing non-overlapping files — read those second. Agreement between two\n same-model agents is correlated, so it ranks review; it never upgrades\n \\`attested\\` to \\`verified\\`. Record only the answer set you stand behind, in\n a separate run.\n6. Record, **in each affected service's own repository**, one service at a time:\n\n \\`\\`\\`sh\n loam verify $1 --service <id> --results report.json [--contract-results contract.json] --record answers.json --json\n \\`\\`\\`\n\n \\`--service <id>\\` narrows the checklist to that service's claims and binds the\n answers to this repository's commit, writing that service's attestation to\n its OWN file, \\`features/$1/verification/<id>.yaml\\` — the directory is the\n record, and loam adds the files up when it reads. Running it in the next repo\n writes that repo's file and touches no other, so two teams' docs PRs never\n conflict over the record. In a repo whose ./loam.json\n declares a \\`service\\`, \\`--service\\` may be omitted — it is substituted, and the\n run still federates. \\`--results report.json\\` alone is legal when the\n checklist is all scenarios.\n\n ⚠ **Never run \\`loam verify $1 --record answers.json\\` without \\`--service\\`.**\n That is the legacy all-at-once form: it writes the WHOLE record from one place,\n into the one file \\`verification.yaml\\`, and over a record that already carries\n other services' attestations it is refused (\\`record-federated\\`, naming them)\n precisely because it would put one unattested answer set over them. If you\n meet that refusal, re-run with \\`--service\\`; do not work around it.\n\n The other refusals: \\`answers-mismatch\\` (an id\n nobody asked about, a claim you left out — re-run step 1, the feature moved —\n or an answers entry for a \\`scenario.tested\\` claim: the runner owns those),\n \\`answers-unevidenced\\` (a \\`confirmed\\` with nothing behind it),\n \\`answers-unreadable\\` (a report or answers file it cannot read),\n \\`record-unreadable\\` (a record file that exists but cannot be read — broken\n YAML, git conflict markers a merge left, or \\`verify.record-miscounted\\` when\n its \\`summary\\` block contradicts its own \\`claims[]\\`. It is never overwritten,\n and the message names the file and the safe repair: a service's own\n \\`verification/<id>.yaml\\` holds only that service's word, so that service\n may delete it and record again; the one-file \\`verification.yaml\\` may hold\n other services' attestations, so it is repaired or restored from version\n control — after a merge, the copy on the branch you merged into — and\n **never deleted**. Never edit the counts to make them agree),\n \\`service-mismatch\\` (\\`--service\\` names a service this repo does not declare) and\n \\`repository-unavailable\\` (\\`--service\\` from a repo that declares none, or whose\n commit cannot be read — an attestation must be bound to the code it is about).\n7. Report every \\`unconfirmed\\` claim and what it would take to close it, and read\n \\`discarded\\` in the payload: it names every previous answer the new record does\n not carry, and each one is either evidence that went stale or evidence that was\n just dropped. A scenario claim the report failed or missed closes by fixing the\n code and re-running the suite — never by editing the generated .feature. While\n any claim stays unconfirmed or unanswered, \\`notices[]\\` carries\n \\`verify.claims-open\\` (warn, gating nothing): the one-line \"not a clean\n result\" summary with all four counts, so a partial record can never read as a\n finished one from the notice lines alone.\n8. Reading is safe from anywhere: \\`loam verify $1 --service <id> --json\\` without\n \\`--record\\` writes nothing and works from the docs repo too. \\`services\\` lists\n every subject of the checklist, so it is how you find out which repos still\n owe an attestation, and \\`loam status $1 --json\\` says the same thing in the\n shape of a next step.\n\nAnything you cannot show is \\`unconfirmed\\` with a note. That is a successful record,\nand it is the useful one: nothing gates on this file, so the only thing it is for is\ntelling the next reader the truth. \\`loam archive\\` will ship the feature either way —\nwhich is exactly why a \\`confirmed\\` you cannot back up costs nothing now and misleads\neveryone later.\n\n\\`loam verify\\` — the notice and record codes its reports carry in \\`notices[]\\`,\ngathered from the steps above; branch on the code, never the prose:\n\n| code | what it means | what to do |\n|---|---|---|\n| \\`verify.claims-open\\` (warn) | a recorded feature still has unconfirmed or unanswered claims — the one-line \"not a clean result\" summary with all four counts, so a partial record can never read as finished from the notice lines alone; a feature with no record at all never carries it | close each open claim: fix the code and re-run the suite for a failed scenario, re-answer the rest with evidence, then re-record |\n| \\`verify.scenario-attested\\` (warn) | scenario claims were answered on an agent's word (\\`answered_by: agent\\`), so the verdict is **attested**, not verified — nothing gates on it, but say so when you hand back | run the generated suite with a JSON report and re-record with \\`--results report.json\\` the moment a suite exists |\n| \\`verify.digest-contested\\` (warn) | two or more services word a scenario identically, so they share one \\`@loam-digest-…\\` tag and no single cucumber report can say whose suite ran it — those scenario claims stay unconfirmed | each service records its own claims from its own repository with \\`--service <id>\\` |\n| \\`verify.operation-contested\\` (warn) | two services on the checklist expose the same operationId, so a contract report — which names no service — cannot say whose suite exercised it; those claims stay unconfirmed | each service records its own with \\`--service <id>\\`, passing its own contract report |\n| \\`verify.checklist-forked\\` (warn) | one or more services answered a DIFFERENT version of the feature's question set than the one it now asks — each attestation carries the \\`checklist\\` digest it answered, and the notice names the ones that do not match the current checklist. Measured against the present, not pairwise: services that all attested against a since-rewritten delta agree with each other perfectly and are all stale. Normal mid-rollout, since services attest as they finish. Attestations written before the field existed carry no digest and are never called stale. In a per-service record the answers of the services named do not count until they record again | re-record the services on the older version from their own repositories (\\`loam verify <FEAT> --service <id> --record\\`) — nothing gates on it |\n| \\`verify.record-miscounted\\` (error) | a record file whose \\`summary\\` block contradicts its own \\`claims[]\\` — neither half can be believed, so the record is refused as unreadable rather than trusted in either direction | a service's own \\`verification/<id>.yaml\\`: delete it and re-record from that service's repo; the one-file \\`verification.yaml\\`: restore it from version control, then re-record — never delete it, and never edit the counts by hand |\n| \\`verify.evidence-token-missing\\` (warn) | a just-confirmed citation's file at the attested commit does not contain the claim's own token (the operationId, message name or edge op) — the evidence may not show what the answer says | re-read that evidence while the answer can still be corrected; verdict-neutral, it gates nothing |\n| \\`cross.agree-confirmed\\` / \\`cross.agree-unconfirmed\\` (ok) | a \\`--diff-answers\\` row where both blind answer sets reach the same verdict — the cheap-to-skip review queue, though same-model agreement is correlated and proves nothing | nothing on its own; record only the answer set you stand behind, in a separate run |\n| \\`cross.disagree\\` (the rows a human reads first) | a \\`--diff-answers\\` row where the two blind answer sets contradict each other — one of them is wrong about the code | read both cited evidences and decide; the diff writes nothing and never moves a verdict |\n| \\`cross.evidence-disjoint\\` (warn) | a \\`--diff-answers\\` notice: claims BOTH sets confirm while citing non-overlapping files — two yeses that looked at different evidence | read those agreements second, after the disagreements; agreement over disjoint evidence is two guesses, not one proof |\n`,\n};\n\nexport const LOAM_SHIP: CommandContent = {\n name: \"loam-ship\",\n description: \"Archive a finished loam feature — merge its deltas into the living docs\",\n argumentHint: \"<FEAT-id>\",\n purpose:\n \"Merge a shipped feature into the living specs, API and landscape. This is the one command that rewrites the source of truth, so every step before it exists to make the merge reviewable before it runs.\",\n invocation: \"loam instructions loam-ship $1\",\n placeholders: [\"feature\"],\n spine: [\n \"confirm the code is actually built and merged — archiving early makes the docs claim something that does not exist\",\n \"`loam validate --feature $1` must come back valid; if it says nothing pinned the delta, rebase and re-validate first\",\n \"`loam archive $1 --dry-run` — read the whole plan and every warning before the merge touches anything\",\n \"`loam archive $1` for real, and read what it reports it did\",\n \"every vouched spec on `revouchOwed[]` owes its owner's re-vouch — tell them; it lands in this same PR\",\n \"`loam unarchive $1` is the undo if the plan turns out to have been wrong\",\n ],\n body: `Archive a shipped feature.\n\n1. Confirm the code is actually built and merged. Archiving folds the delta into the\n living docs; doing it early makes the docs claim something that does not exist.\n \\`loam status $1 --json\\` answers \"is this actually finished\" in one call, and its\n \\`next[]\\` names what is outstanding if it is not.\n2. \\`loam validate --feature $1 --json\\` — must come back \\`valid: true\\`.\n If it reports \\`delta.baseline-missing\\` or \\`openapi.baseline-missing\\`, run\n \\`loam rebase $1\\` and re-validate BEFORE going on: those two say nothing has\n pinned what this delta was written against, so the merge cannot tell the text\n you EDITED from the text you merely restated. Both gate the archive — the\n dry run in step 3 refuses on them too — and the only way past without the\n pins is a human's \\`--approve\\`.\n3. \\`loam archive $1 --dry-run --json\\` first. \\`plan[]\\` is every file the merge would\n write (\\`create\\` / \\`update\\`, then the final \\`move\\` into \\`features/archive/\\`), and\n none of them are written — read it before letting the merge touch the source of\n truth, and stop if a file you did not expect is on it. \\`warnings[]\\` is what the\n merge will do that is legal but lossy — advisory warnings never block, which is\n exactly why you read them now: \\`openapi.op-modified\\` means an operation the living\n contract already defines gets overwritten wholesale, and its \\`details\\` name the\n current consumers that overwrite lands on (\\`asyncapi.message-modified\\` likewise for\n a message; \\`openapi.component-modified\\` is the same story for a component the\n merged operations carry, with no consumer list),\n \\`service.no-model\\` means a service arrives with no C4 centre behind it, and\n \\`delta.added-conflict\\`\n means another feature in flight adds the same requirement — the first to archive\n lands it, and the other's archive is then refused (\\`delta.added-duplicate\\`: its\n ADDED now collides with the living spec) unless a human \\`--approve\\`s the\n replacement.\n4. \\`loam archive $1 --json\\`. It merges three axes into the living state — requirements\n into \\`services/<svc>/spec.md\\`, endpoints into \\`services/<svc>/openapi.yaml\\`,\n elements and edges into \\`architecture/landscape.likec4\\` and, for an addition nested\n under a service whose model EXTENDS the map, into that\n \\`services/<…>/<svc>/model.likec4\\` instead — then moves the feature\n under \\`features/archive/\\`, whole: its verification record travels with it,\n the per-service \\`verification/<id>.yaml\\` files and any one-file\n \\`verification.yaml\\` alike, and \\`loam verify $1\\` reads it back afterwards as\n frozen history. Verification never gates the archive — an open or missing\n record ships as exactly that. Success is \\`ok: true\\`; on \\`ok: false\\`, branch on\n \\`error.code\\`:\n - \\`not-coherent\\` — gating coherence issues; \\`issues[]\\` in the envelope lists them,\n each with \\`gates\\` and \\`overridable\\` resolved (advisory warnings do not block).\n Fix the breaches. \\`--approve\\` overrides the gating issues and is a human's\n call to make, not yours: report the breach and stop. Two codes it does not\n override even then — \\`delta.service-id-invalid\\` (a \\`specs/<svc>/\\` name the\n id grammar refuses) and \\`c4.service-binding-invalid\\` (a \\`metadata { service }\\`\n binding it refuses, anywhere inside a tagged element's block): each names a\n path the merge would write mechanically, they arrive with\n \\`overridable: false\\`, and the fix is a rename, never the flag.\n \\`openapi.remove-marker-path-level\\` arrives\n here too: an \\`x-loam-remove\\` written beside the methods instead of inside the\n operation retires nothing. So do \\`openapi.op-revived\\` and\n \\`asyncapi.slot-revived\\`: the delta spells an operation, slot or message an\n ARCHIVED feature retired, and the merge would put it back. If the delta only\n quoted it, delete the quote; whether the feature brings it back on purpose is\n the human's \\`--approve\\`, and the archive output then names what came back\n and whose retirement it reverses.\n - \\`living-outside-requirements\\` — the LIVING spec holds a requirement outside\n \\`## Requirements\\`, and the merge would duplicate it. Re-home it, then re-run.\n \\`--approve\\` does not move this one.\n - \\`archive-exists\\` — \\`features/archive/\\` already has that directory; a human\n decides what it is.\n - \\`merge-failed\\` — the merge could not be computed, or failed and was rolled\n back. Either way the living docs are unchanged; fix the reported cause, re-run.\n A file loam cannot decode as UTF-8 refuses here by name rather than being\n rewritten with replacement characters. So does a living document that still\n holds git conflict markers — the \\`spec.merge-conflict\\` /\n \\`landscape.merge-conflict\\` findings ride in \\`findings[]\\`, and \\`--approve\\`\n does not override them: the loss is mechanical, not a judgment call.\n - \\`docs-busy\\` — another \\`loam archive\\` or \\`loam unarchive\\` holds the docs\n repo's lock. Nothing was read or written; wait for it and re-run once.\n - \\`commit-interrupted\\` — a previous archive or unarchive was killed mid-commit\n and this run cannot repair it on its own: a half-written file was edited\n since, its pre-image is gone or altered, or \\`.loam-commit\\` cannot be read.\n Run \\`loam doctor\\` (\\`doctor.commit-interrupted\\` / \\`doctor.commit-unreadable\\`\n name the same state) and hand it to a human — re-running does not help.\n - \\`rollback-incomplete\\` — the merge failed AND some files could not be restored.\n Stop and hand it to a human; the message lists the files to check.\n5. Re-vouches are owed. \\`revouchOwed[]\\` — on the dry run and the real merge alike —\n names every VOUCHED spec the merge moves under its vouch stamp, with the\n document's \\`owner\\`. \\`content.stale\\` follows on each, and honestly: the merged\n text is words nobody has yet read against the code. You cannot clear it — a\n vouch is a person's, run in that service's own repo (\\`loam vouch --service <id>\\`,\n after \\`loam vouch --pack --service <id>\\` for what to re-read). Name the\n documents and owners in your hand-back, and say the re-vouches belong in the\n SAME PR as this archive: a docs repo gated on\n \\`loam validate --all --base <ref> --strict\\` keeps the archive's PR red until they\n land. Never edit a stamp by hand to turn it green.\n\nArchived by mistake? \\`loam unarchive $1\\` restores the living docs from the snapshot\narchive left in \\`features/archive/$1*/.loam-before/\\` and re-opens the feature. Do not\nhand-edit the living docs back instead: the previous text of a \\`MODIFIED\\` requirement\nis in that snapshot and nowhere else, so anything you write by hand is a guess. It\nrefuses under \\`snapshot-corrupt\\` when a pre-image's bytes no longer match the digest\nthe archive recorded — \\`--force\\` does not override that one, because the damage is\nto the undo itself — and under \\`snapshot-missing\\` for an archive older than the\ncurrent snapshot format. Both mean version control is the way back.\n`,\n};\n"]}
|
|
@@ -8,16 +8,41 @@ export const LOAM_FEATURE = {
|
|
|
8
8
|
// $2 is the feature's human title: any string is a legal one.
|
|
9
9
|
placeholders: ["feature", "free"],
|
|
10
10
|
spine: [
|
|
11
|
+
"decide whether this is a feature at all — a change that touches no requirement, operation or edge needs none",
|
|
11
12
|
"read `AGENTS.md` at the docs repo root — it defines the ID spine everything else depends on",
|
|
12
13
|
"`loam explore` around the services you think are involved: the ring one hop out, and what is already in flight over the same ground",
|
|
13
14
|
"`loam status` / `loam list` / `loam show` — what exists and what is already owed",
|
|
14
15
|
"`loam new` with a `--touches` per service it changes and a `--new-service` per service it introduces",
|
|
15
16
|
"author what the templates left as TODO: intent, the C4 delta, the per-service spec / arch spec / openapi",
|
|
16
|
-
"
|
|
17
|
+
"analyze before you pin — write what the documents leave open under `## Open questions` in intent.md, for a human to answer",
|
|
18
|
+
"`loam rebase` — pin every MODIFIED requirement and every living operation you edited to the version you wrote against",
|
|
17
19
|
"validate the feature, then read `loam dependencies` before anyone starts building",
|
|
18
20
|
],
|
|
21
|
+
// Step 0 and step 4b are the two this protocol was missing, and neither is
|
|
22
|
+
// graded — on purpose. Step 0 exists because the lightest lane loam has (no
|
|
23
|
+
// feature at all, for a change that moves no requirement, operation or edge)
|
|
24
|
+
// was true and written down nowhere, so the work that is most of a legacy
|
|
25
|
+
// fleet's week either walked the whole feature cycle or routed around loam.
|
|
26
|
+
// Step 4b exists because every check after it is a join between exact
|
|
27
|
+
// tokens: two services that agree on every operationId and disagree on what
|
|
28
|
+
// it means (one confirms a split the other answers 422 for) validate, archive
|
|
29
|
+
// and ship green, and a `Realizes:` whose requirement does not deliver the
|
|
30
|
+
// promise reads as a clean join. Nothing in the binary can read meaning, and
|
|
31
|
+
// a check pretending to would be the false green this tool exists to refuse,
|
|
32
|
+
// so the step asks for questions and leaves the answers to a person.
|
|
19
33
|
body: `Start a new feature in the loam docs repo (its path is \`docsDir\` in ./loam.json).
|
|
20
34
|
|
|
35
|
+
0. First decide whether this needs a feature at all. A change that touches no
|
|
36
|
+
requirement, no operation and no fleet edge — a refactor, a dependency bump,
|
|
37
|
+
a fix that brings the code back to behaviour the living spec already
|
|
38
|
+
documents — needs NONE: nothing in the docs repo changes, so there is nothing
|
|
39
|
+
to scaffold, pin or archive. It is a code PR in the service repo, then
|
|
40
|
+
\`loam validate --service <id>\` there (the service repo is the only place
|
|
41
|
+
\`sources.stale\` is visible), then \`loam vouch --pack --service <id>\` to
|
|
42
|
+
prepare the re-read the moved sources owe — the vouch itself is a human's.
|
|
43
|
+
If the change alters what a requirement promises, it IS a feature, however
|
|
44
|
+
small: one \`--touches\`, a Why, the ADDED/MODIFIED requirement, and
|
|
45
|
+
\`loam rebase\` for a MODIFIED one — the rest of this page still applies.
|
|
21
46
|
1. Read \`AGENTS.md\` at the root of the docs repo first — it defines the ID spine
|
|
22
47
|
everything here depends on.
|
|
23
48
|
2. Understand the current state before proposing a change:
|
|
@@ -71,20 +96,63 @@ export const LOAM_FEATURE = {
|
|
|
71
96
|
- \`specs/<svc>/arch.spec.md\` — the architectural obligations (outbox, retries,
|
|
72
97
|
idempotency, alerts), same grammar; a \`Covers:\` line per requirement naming the
|
|
73
98
|
tagged elements/edges it accounts for, or \`c4.uncovered\` says nothing does
|
|
74
|
-
- \`specs/<svc>/openapi.yaml\` — define every operationId the edges reference
|
|
99
|
+
- \`specs/<svc>/openapi.yaml\` — define every operationId the edges reference.
|
|
100
|
+
A self-contained document holding ONLY the operations this feature adds,
|
|
101
|
+
changes or removes — never a copy of the living contract. An operation you
|
|
102
|
+
leave out stays exactly as the living contract has it, and a local \`$ref\`
|
|
103
|
+
(\`#/components/schemas/Order\`) resolves into the living document, so there
|
|
104
|
+
is nothing to restate. Every operation you quote costs a pin, and once a
|
|
105
|
+
parallel feature retires it — or moves it, and you quoted the old
|
|
106
|
+
address — a pinned quote is \`openapi.baseline-invalid\` and
|
|
107
|
+
\`openapi.op-revived\` both, an unpinned one \`openapi.op-revived\` alone
|
|
108
|
+
(\`asyncapi.slot-revived\` on the event axis) — archive refuses either.
|
|
109
|
+
Delete the quote; only a feature that MEANS to bring a retired operation
|
|
110
|
+
back keeps it, and archives past the finding with a human's \`--approve\`
|
|
75
111
|
- \`specs/<svc>/asyncapi.yaml\` — the event axis, only when the feature changes
|
|
76
|
-
what a service puts on or takes off the bus:
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
112
|
+
what a service puts on or takes off the bus: an AsyncAPI 3.0 document
|
|
113
|
+
holding only the channel, operation and message slots you add, change or
|
|
114
|
+
remove (an edited slot is written out whole), new messages under
|
|
115
|
+
\`components.messages\`, and a \`Publishes:\`/\`Consumes:\` line on each
|
|
116
|
+
requirement that names them. \`loam new\` scaffolds none — an event
|
|
80
117
|
contract is genuinely optional
|
|
118
|
+
4b. **Analyze before you pin.** Nothing grades this step, which is why it is here:
|
|
119
|
+
every check after it is a join between exact tokens, so two services that
|
|
120
|
+
agree on every operationId and disagree on what it means validate, archive
|
|
121
|
+
and ship green. Read the feature as its reviewer will, before rebase freezes it:
|
|
122
|
+
- \`loam show $1 --json\` — its \`review\` object joins the intent, the C4
|
|
123
|
+
objects, the API/event slices and the dependencies
|
|
124
|
+
- \`loam context <service> --feature $1 --json\` for every service it touches —
|
|
125
|
+
the living requirements verbatim, the edges one hop out, and this delta
|
|
126
|
+
- the \`neighbours\` step 2b's \`loam explore\` reported
|
|
127
|
+
Then write what the documents leave open under \`## Open questions\` in
|
|
128
|
+
\`intent.md\` (the scaffold already carries the heading), one line per question,
|
|
129
|
+
naming the requirement id or edge it is about:
|
|
130
|
+
- a SHALL two engineers could build two ways — an unstated unit, bound,
|
|
131
|
+
ordering, or an "eventually" with no window
|
|
132
|
+
- a value, limit or window two services state differently (a refund window,
|
|
133
|
+
a rounding rule, a retry budget), and which side refuses when they
|
|
134
|
+
disagree — a caller that accepts what its provider answers 4xx for is a
|
|
135
|
+
contradiction no check can see
|
|
136
|
+
- a \`Realizes:\` line whose requirement does not actually deliver the
|
|
137
|
+
capability promise it names, or a promise this feature changes that no
|
|
138
|
+
realizing requirement keeps
|
|
139
|
+
- per call edge in the delta: the timeout, the retry and what makes it safe
|
|
140
|
+
to repeat, what a partial failure leaves behind, and what compensates it —
|
|
141
|
+
an edge no requirement answers these for is a question, not a default
|
|
142
|
+
- each \`explore\` neighbour outside \`--touches\`: why it is untouched, or that
|
|
143
|
+
it is a candidate — adding one means a \`specs/<svc>/\` delta in this feature,
|
|
144
|
+
because that directory IS the feature's service list
|
|
145
|
+
Ask; do not answer for the author. A human answers by editing the file —
|
|
146
|
+
replacing a question with the decision, or with a change to the delta.
|
|
147
|
+
Nothing is graded: an empty section is legal, and so is an answered one.
|
|
81
148
|
5. **\`loam rebase $1\`** — the step between authoring and checking, and the one that
|
|
82
149
|
stops two features in flight from silently deleting each other's work. It pins
|
|
83
|
-
every MODIFIED/REMOVED requirement (\`Based-On:\`), every operation
|
|
84
|
-
|
|
85
|
-
(\`x-loam-based-on\`) to the living version you wrote against.
|
|
86
|
-
|
|
87
|
-
|
|
150
|
+
every MODIFIED/REMOVED requirement (\`Based-On:\`), and every operation and
|
|
151
|
+
asyncapi channel/operation/message slot the living contract already holds
|
|
152
|
+
(\`x-loam-based-on\`), to the living version you wrote against. What the
|
|
153
|
+
feature ADDS has no living version and takes no pin, so an add-only contract
|
|
154
|
+
delta needs no rebase. Any living operation or slot the delta spells is one
|
|
155
|
+
the merge upserts, so it needs its pin even when you changed one line of it.
|
|
88
156
|
Without the pins, \`delta.baseline-missing\` / \`openapi.baseline-missing\` /
|
|
89
157
|
\`asyncapi.baseline-missing\` GATE
|
|
90
158
|
the archive: before they did, the second feature to archive reverted the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature.js","sourceRoot":"","sources":["../../../../src/core/agent/workflows/feature.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,MAAM,CAAC,MAAM,YAAY,GAAmB;IAC1C,IAAI,EAAE,cAAc;IACpB,WAAW,EACT,yFAAyF;IAC3F,YAAY,EAAE,qBAAqB;IACnC,OAAO,EACL,uQAAuQ;IACzQ,UAAU,EAAE,wCAAwC;IACpD,8DAA8D;IAC9D,YAAY,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC;IACjC,KAAK,EAAE;QACL,6FAA6F;QAC7F,qIAAqI;QACrI,kFAAkF;QAClF,sGAAsG;QACtG,0GAA0G;QAC1G,4GAA4G;QAC5G,mFAAmF;KACpF;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoFP;CACA,CAAC","sourcesContent":["import type { CommandContent } from \"../contract.js\";\n\n/** Starting a feature: explore, scaffold, author the three axes, rebase, validate. */\nexport const LOAM_FEATURE: CommandContent = {\n name: \"loam-feature\",\n description:\n \"Start a new loam feature — scaffold it, then author the C4 delta and requirement deltas\",\n argumentHint: `<FEAT-id> \"<title>\"`,\n purpose:\n \"Start a feature in the docs repo: decide which services it touches, scaffold it, then author the C4 delta and one requirement delta per service. The `--touches` list is the hardest call in the cycle and nothing downstream catches one that is short by a service.\",\n invocation: 'loam instructions loam-feature $1 \"$2\"',\n // $2 is the feature's human title: any string is a legal one.\n placeholders: [\"feature\", \"free\"],\n spine: [\n \"read `AGENTS.md` at the docs repo root — it defines the ID spine everything else depends on\",\n \"`loam explore` around the services you think are involved: the ring one hop out, and what is already in flight over the same ground\",\n \"`loam status` / `loam list` / `loam show` — what exists and what is already owed\",\n \"`loam new` with a `--touches` per service it changes and a `--new-service` per service it introduces\",\n \"author what the templates left as TODO: intent, the C4 delta, the per-service spec / arch spec / openapi\",\n \"`loam rebase` — pin every MODIFIED requirement and every operation to the living version you wrote against\",\n \"validate the feature, then read `loam dependencies` before anyone starts building\",\n ],\n body: `Start a new feature in the loam docs repo (its path is \\`docsDir\\` in ./loam.json).\n\n1. Read \\`AGENTS.md\\` at the root of the docs repo first — it defines the ID spine\n everything here depends on.\n2. Understand the current state before proposing a change:\n - \\`loam status --json\\` — what is already in flight and what it is waiting on.\n Run this FIRST if you joined this repository halfway or lost the session:\n \\`next[]\\` is ordered and each entry carries the literal command to run, so it\n tells you whether the work is to start $1 at all. \\`loam status $1 --json\\`\n narrows it to one feature once $1 exists\n - \\`loam list --json\\` — what services exist, and what documentation they are missing\n - \\`loam show <service> --json\\` — what a service owns, exposes, and who already calls it\n2b. \\`loam explore <service> --json --as $1\\` around every service you believe this\n feature touches. This is the step that decides step 3's \\`--touches\\` list, and it\n is the only decision in the cycle nothing downstream catches — a list short by one\n service produces a feature that validates, archives and ships with a consumer\n nobody updated. Read four fields:\n - \\`neighbours\\` — the services one hop out in the fleet map. Weigh each one. loam\n knows they are connected, not whether you change them, and it deliberately\n leaves them OUT of the suggested command line rather than guessing for you\n - \\`services[].maturity\\` — a service at \\`empty\\` or \\`partial\\` has no baseline to\n write a delta against, so the work starts with \\`loam adopt --service <id>\\`, not\n with this feature\n - \\`services[].operations\\` — what each already exposes, so an operation you are\n about to \"add\" that is already there becomes a MODIFIED requirement instead\n - \\`overlaps\\` — active features already carrying a delta for these services.\n \\`loam dependencies --json\\` (step 7) then says which must archive first\n \\`--op <operationId>\\` seeds from an operation when you know the call but not who\n owns it. A seed naming no \\`services/<id>/\\` is reported in \\`unknown\\` with the\n closest real ids rather than refused — the feature may be introducing it, and a\n typo looks identical until you read that list. \\`scaffold\\` is the literal step-3\n command line the seeds imply, with \\`--new-service\\` where the service does not\n exist yet.\n3. Scaffold it: \\`loam new $1 --title \"$2\"\\`, adding \\`--touches <id>\\` for every service the\n feature touches and \\`--new-service <id>\\` for every one it introduces. Every id goes\n through the \\`services/<id>/\\` grammar, so a typo is refused (\\`invalid-option\\`)\n rather than scaffolded; a \\`--touches\\` that matches nothing existing comes back as\n a \\`note\\` with the close ids, not as a failure — read it, because a \\`--touches\\`\n naming a service that does not exist is \\`delta.service-unknown\\` at validate time\n and a phantom \\`services/<typo>/\\` directory if it ever archives.\n4. Author what the templates left as TODO. This step is gated, not advisory:\n \\`loam archive\\` refuses a scaffold nobody edited (\\`scaffold.placeholder\\`,\n \\`intent.empty\\`), so the placeholder text cannot reach the living docs.\n - \\`intent.md\\` — the problem in business terms, in prose; the scaffold's\n comment-only Why/Scope count as unwritten\n - \\`delta.likec4\\` — new elements and edges, each tagged \\`#$1\\`; every call edge carries\n \\`metadata { op '<operationId>' }\\`; replace the scaffolded TODO description on each\n new service. For a requirements-only feature DELETE this\n file: an empty delta is legal, but a scaffold left half-filled is not\n - \\`specs/<svc>/spec.md\\` — the example requirement ships INSIDE the template's\n comment: copy it out, unindent, one behaviour per requirement, SHALL, at\n least one Given/When/Then scenario; add \\`Operations:\\` once the operation exists\n - \\`specs/<svc>/arch.spec.md\\` — the architectural obligations (outbox, retries,\n idempotency, alerts), same grammar; a \\`Covers:\\` line per requirement naming the\n tagged elements/edges it accounts for, or \\`c4.uncovered\\` says nothing does\n - \\`specs/<svc>/openapi.yaml\\` — define every operationId the edges reference\n - \\`specs/<svc>/asyncapi.yaml\\` — the event axis, only when the feature changes\n what a service puts on or takes off the bus: a complete AsyncAPI 3.0\n document restating the living contract around the slots you change, new\n messages under \\`components.messages\\`, and a \\`Publishes:\\`/\\`Consumes:\\` line\n on each requirement that names them. \\`loam new\\` scaffolds none — an event\n contract is genuinely optional\n5. **\\`loam rebase $1\\`** — the step between authoring and checking, and the one that\n stops two features in flight from silently deleting each other's work. It pins\n every MODIFIED/REMOVED requirement (\\`Based-On:\\`), every operation in the\n contract delta and every asyncapi channel/operation/message slot\n (\\`x-loam-based-on\\`) to the living version you wrote against.\n Run it on the contract axes even if you changed exactly one endpoint: that is\n what marks the REST of the document as quotation the merge must not write back.\n Without the pins, \\`delta.baseline-missing\\` / \\`openapi.baseline-missing\\` /\n \\`asyncapi.baseline-missing\\` GATE\n the archive: before they did, the second feature to archive reverted the\n first — with \\`+0 ~1 -0\\`, exit 0, and nobody told. Rebase clears them; a\n human's \\`--approve\\` is the only other way past.\n Restamping is not resolving: if rebase reports a requirement as MOVED, re-read\n the living text and fold in what you still mean BEFORE you ship.\n6. \\`loam validate --feature $1 --json\\`. Do not stop while \\`valid\\` is false.\n7. \\`loam dependencies $1 --json\\` before anyone starts building: it says which\n features in flight have to archive first (\\`order\\`) and which collisions no\n ordering fixes (\\`conflicts\\`, \\`cycles\\`). Every conflict it names is one \\`loam rebase\\`\n cannot fix for you — two intentions have to be merged by people.\n\nThe operation's three spellings — the edge's \\`op\\`, the requirement's \\`Operations:\\`\nline, and the OpenAPI \\`operationId\\` — must match exactly.\n`,\n};\n"]}
|
|
1
|
+
{"version":3,"file":"feature.js","sourceRoot":"","sources":["../../../../src/core/agent/workflows/feature.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,MAAM,CAAC,MAAM,YAAY,GAAmB;IAC1C,IAAI,EAAE,cAAc;IACpB,WAAW,EACT,yFAAyF;IAC3F,YAAY,EAAE,qBAAqB;IACnC,OAAO,EACL,uQAAuQ;IACzQ,UAAU,EAAE,wCAAwC;IACpD,8DAA8D;IAC9D,YAAY,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC;IACjC,KAAK,EAAE;QACL,8GAA8G;QAC9G,6FAA6F;QAC7F,qIAAqI;QACrI,kFAAkF;QAClF,sGAAsG;QACtG,0GAA0G;QAC1G,4HAA4H;QAC5H,uHAAuH;QACvH,mFAAmF;KACpF;IACD,2EAA2E;IAC3E,4EAA4E;IAC5E,6EAA6E;IAC7E,0EAA0E;IAC1E,4EAA4E;IAC5E,sEAAsE;IACtE,4EAA4E;IAC5E,8EAA8E;IAC9E,2EAA2E;IAC3E,6EAA6E;IAC7E,6EAA6E;IAC7E,qEAAqE;IACrE,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0IP;CACA,CAAC","sourcesContent":["import type { CommandContent } from \"../contract.js\";\n\n/** Starting a feature: explore, scaffold, author the three axes, rebase, validate. */\nexport const LOAM_FEATURE: CommandContent = {\n name: \"loam-feature\",\n description:\n \"Start a new loam feature — scaffold it, then author the C4 delta and requirement deltas\",\n argumentHint: `<FEAT-id> \"<title>\"`,\n purpose:\n \"Start a feature in the docs repo: decide which services it touches, scaffold it, then author the C4 delta and one requirement delta per service. The `--touches` list is the hardest call in the cycle and nothing downstream catches one that is short by a service.\",\n invocation: 'loam instructions loam-feature $1 \"$2\"',\n // $2 is the feature's human title: any string is a legal one.\n placeholders: [\"feature\", \"free\"],\n spine: [\n \"decide whether this is a feature at all — a change that touches no requirement, operation or edge needs none\",\n \"read `AGENTS.md` at the docs repo root — it defines the ID spine everything else depends on\",\n \"`loam explore` around the services you think are involved: the ring one hop out, and what is already in flight over the same ground\",\n \"`loam status` / `loam list` / `loam show` — what exists and what is already owed\",\n \"`loam new` with a `--touches` per service it changes and a `--new-service` per service it introduces\",\n \"author what the templates left as TODO: intent, the C4 delta, the per-service spec / arch spec / openapi\",\n \"analyze before you pin — write what the documents leave open under `## Open questions` in intent.md, for a human to answer\",\n \"`loam rebase` — pin every MODIFIED requirement and every living operation you edited to the version you wrote against\",\n \"validate the feature, then read `loam dependencies` before anyone starts building\",\n ],\n // Step 0 and step 4b are the two this protocol was missing, and neither is\n // graded — on purpose. Step 0 exists because the lightest lane loam has (no\n // feature at all, for a change that moves no requirement, operation or edge)\n // was true and written down nowhere, so the work that is most of a legacy\n // fleet's week either walked the whole feature cycle or routed around loam.\n // Step 4b exists because every check after it is a join between exact\n // tokens: two services that agree on every operationId and disagree on what\n // it means (one confirms a split the other answers 422 for) validate, archive\n // and ship green, and a `Realizes:` whose requirement does not deliver the\n // promise reads as a clean join. Nothing in the binary can read meaning, and\n // a check pretending to would be the false green this tool exists to refuse,\n // so the step asks for questions and leaves the answers to a person.\n body: `Start a new feature in the loam docs repo (its path is \\`docsDir\\` in ./loam.json).\n\n0. First decide whether this needs a feature at all. A change that touches no\n requirement, no operation and no fleet edge — a refactor, a dependency bump,\n a fix that brings the code back to behaviour the living spec already\n documents — needs NONE: nothing in the docs repo changes, so there is nothing\n to scaffold, pin or archive. It is a code PR in the service repo, then\n \\`loam validate --service <id>\\` there (the service repo is the only place\n \\`sources.stale\\` is visible), then \\`loam vouch --pack --service <id>\\` to\n prepare the re-read the moved sources owe — the vouch itself is a human's.\n If the change alters what a requirement promises, it IS a feature, however\n small: one \\`--touches\\`, a Why, the ADDED/MODIFIED requirement, and\n \\`loam rebase\\` for a MODIFIED one — the rest of this page still applies.\n1. Read \\`AGENTS.md\\` at the root of the docs repo first — it defines the ID spine\n everything here depends on.\n2. Understand the current state before proposing a change:\n - \\`loam status --json\\` — what is already in flight and what it is waiting on.\n Run this FIRST if you joined this repository halfway or lost the session:\n \\`next[]\\` is ordered and each entry carries the literal command to run, so it\n tells you whether the work is to start $1 at all. \\`loam status $1 --json\\`\n narrows it to one feature once $1 exists\n - \\`loam list --json\\` — what services exist, and what documentation they are missing\n - \\`loam show <service> --json\\` — what a service owns, exposes, and who already calls it\n2b. \\`loam explore <service> --json --as $1\\` around every service you believe this\n feature touches. This is the step that decides step 3's \\`--touches\\` list, and it\n is the only decision in the cycle nothing downstream catches — a list short by one\n service produces a feature that validates, archives and ships with a consumer\n nobody updated. Read four fields:\n - \\`neighbours\\` — the services one hop out in the fleet map. Weigh each one. loam\n knows they are connected, not whether you change them, and it deliberately\n leaves them OUT of the suggested command line rather than guessing for you\n - \\`services[].maturity\\` — a service at \\`empty\\` or \\`partial\\` has no baseline to\n write a delta against, so the work starts with \\`loam adopt --service <id>\\`, not\n with this feature\n - \\`services[].operations\\` — what each already exposes, so an operation you are\n about to \"add\" that is already there becomes a MODIFIED requirement instead\n - \\`overlaps\\` — active features already carrying a delta for these services.\n \\`loam dependencies --json\\` (step 7) then says which must archive first\n \\`--op <operationId>\\` seeds from an operation when you know the call but not who\n owns it. A seed naming no \\`services/<id>/\\` is reported in \\`unknown\\` with the\n closest real ids rather than refused — the feature may be introducing it, and a\n typo looks identical until you read that list. \\`scaffold\\` is the literal step-3\n command line the seeds imply, with \\`--new-service\\` where the service does not\n exist yet.\n3. Scaffold it: \\`loam new $1 --title \"$2\"\\`, adding \\`--touches <id>\\` for every service the\n feature touches and \\`--new-service <id>\\` for every one it introduces. Every id goes\n through the \\`services/<id>/\\` grammar, so a typo is refused (\\`invalid-option\\`)\n rather than scaffolded; a \\`--touches\\` that matches nothing existing comes back as\n a \\`note\\` with the close ids, not as a failure — read it, because a \\`--touches\\`\n naming a service that does not exist is \\`delta.service-unknown\\` at validate time\n and a phantom \\`services/<typo>/\\` directory if it ever archives.\n4. Author what the templates left as TODO. This step is gated, not advisory:\n \\`loam archive\\` refuses a scaffold nobody edited (\\`scaffold.placeholder\\`,\n \\`intent.empty\\`), so the placeholder text cannot reach the living docs.\n - \\`intent.md\\` — the problem in business terms, in prose; the scaffold's\n comment-only Why/Scope count as unwritten\n - \\`delta.likec4\\` — new elements and edges, each tagged \\`#$1\\`; every call edge carries\n \\`metadata { op '<operationId>' }\\`; replace the scaffolded TODO description on each\n new service. For a requirements-only feature DELETE this\n file: an empty delta is legal, but a scaffold left half-filled is not\n - \\`specs/<svc>/spec.md\\` — the example requirement ships INSIDE the template's\n comment: copy it out, unindent, one behaviour per requirement, SHALL, at\n least one Given/When/Then scenario; add \\`Operations:\\` once the operation exists\n - \\`specs/<svc>/arch.spec.md\\` — the architectural obligations (outbox, retries,\n idempotency, alerts), same grammar; a \\`Covers:\\` line per requirement naming the\n tagged elements/edges it accounts for, or \\`c4.uncovered\\` says nothing does\n - \\`specs/<svc>/openapi.yaml\\` — define every operationId the edges reference.\n A self-contained document holding ONLY the operations this feature adds,\n changes or removes — never a copy of the living contract. An operation you\n leave out stays exactly as the living contract has it, and a local \\`$ref\\`\n (\\`#/components/schemas/Order\\`) resolves into the living document, so there\n is nothing to restate. Every operation you quote costs a pin, and once a\n parallel feature retires it — or moves it, and you quoted the old\n address — a pinned quote is \\`openapi.baseline-invalid\\` and\n \\`openapi.op-revived\\` both, an unpinned one \\`openapi.op-revived\\` alone\n (\\`asyncapi.slot-revived\\` on the event axis) — archive refuses either.\n Delete the quote; only a feature that MEANS to bring a retired operation\n back keeps it, and archives past the finding with a human's \\`--approve\\`\n - \\`specs/<svc>/asyncapi.yaml\\` — the event axis, only when the feature changes\n what a service puts on or takes off the bus: an AsyncAPI 3.0 document\n holding only the channel, operation and message slots you add, change or\n remove (an edited slot is written out whole), new messages under\n \\`components.messages\\`, and a \\`Publishes:\\`/\\`Consumes:\\` line on each\n requirement that names them. \\`loam new\\` scaffolds none — an event\n contract is genuinely optional\n4b. **Analyze before you pin.** Nothing grades this step, which is why it is here:\n every check after it is a join between exact tokens, so two services that\n agree on every operationId and disagree on what it means validate, archive\n and ship green. Read the feature as its reviewer will, before rebase freezes it:\n - \\`loam show $1 --json\\` — its \\`review\\` object joins the intent, the C4\n objects, the API/event slices and the dependencies\n - \\`loam context <service> --feature $1 --json\\` for every service it touches —\n the living requirements verbatim, the edges one hop out, and this delta\n - the \\`neighbours\\` step 2b's \\`loam explore\\` reported\n Then write what the documents leave open under \\`## Open questions\\` in\n \\`intent.md\\` (the scaffold already carries the heading), one line per question,\n naming the requirement id or edge it is about:\n - a SHALL two engineers could build two ways — an unstated unit, bound,\n ordering, or an \"eventually\" with no window\n - a value, limit or window two services state differently (a refund window,\n a rounding rule, a retry budget), and which side refuses when they\n disagree — a caller that accepts what its provider answers 4xx for is a\n contradiction no check can see\n - a \\`Realizes:\\` line whose requirement does not actually deliver the\n capability promise it names, or a promise this feature changes that no\n realizing requirement keeps\n - per call edge in the delta: the timeout, the retry and what makes it safe\n to repeat, what a partial failure leaves behind, and what compensates it —\n an edge no requirement answers these for is a question, not a default\n - each \\`explore\\` neighbour outside \\`--touches\\`: why it is untouched, or that\n it is a candidate — adding one means a \\`specs/<svc>/\\` delta in this feature,\n because that directory IS the feature's service list\n Ask; do not answer for the author. A human answers by editing the file —\n replacing a question with the decision, or with a change to the delta.\n Nothing is graded: an empty section is legal, and so is an answered one.\n5. **\\`loam rebase $1\\`** — the step between authoring and checking, and the one that\n stops two features in flight from silently deleting each other's work. It pins\n every MODIFIED/REMOVED requirement (\\`Based-On:\\`), and every operation and\n asyncapi channel/operation/message slot the living contract already holds\n (\\`x-loam-based-on\\`), to the living version you wrote against. What the\n feature ADDS has no living version and takes no pin, so an add-only contract\n delta needs no rebase. Any living operation or slot the delta spells is one\n the merge upserts, so it needs its pin even when you changed one line of it.\n Without the pins, \\`delta.baseline-missing\\` / \\`openapi.baseline-missing\\` /\n \\`asyncapi.baseline-missing\\` GATE\n the archive: before they did, the second feature to archive reverted the\n first — with \\`+0 ~1 -0\\`, exit 0, and nobody told. Rebase clears them; a\n human's \\`--approve\\` is the only other way past.\n Restamping is not resolving: if rebase reports a requirement as MOVED, re-read\n the living text and fold in what you still mean BEFORE you ship.\n6. \\`loam validate --feature $1 --json\\`. Do not stop while \\`valid\\` is false.\n7. \\`loam dependencies $1 --json\\` before anyone starts building: it says which\n features in flight have to archive first (\\`order\\`) and which collisions no\n ordering fixes (\\`conflicts\\`, \\`cycles\\`). Every conflict it names is one \\`loam rebase\\`\n cannot fix for you — two intentions have to be merged by people.\n\nThe operation's three spellings — the edge's \\`op\\`, the requirement's \\`Operations:\\`\nline, and the OpenAPI \\`operationId\\` — must match exactly.\n`,\n};\n"]}
|
|
@@ -1,4 +1,18 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Building one service's slice: the delta brief, then a unit at a time —
|
|
3
|
+
* generated gherkin, steps, code, verify.
|
|
4
|
+
*
|
|
5
|
+
* Steps 3-5 are a LOOP, not a sequence, and that is why `loam tasks` sits at
|
|
6
|
+
* the head of them. They used to be three things done in order — generate the
|
|
7
|
+
* suite, write the definitions, implement — which is only the right order for a
|
|
8
|
+
* slice whose every requirement already owns a scenario. Where one does not,
|
|
9
|
+
* the next thing owed is a sentence in the docs repo, and the body had no step
|
|
10
|
+
* that could say so; where the suite is half written, nothing here answered
|
|
11
|
+
* which half. `at` answers both: the projection names which of four states the
|
|
12
|
+
* next unit is in, and each state names exactly one action, so the numbering
|
|
13
|
+
* below is a table of destinations rather than an itinerary. An editor adding a
|
|
14
|
+
* step must therefore say which `at` value routes to it, or it is unreachable.
|
|
15
|
+
*/
|
|
2
16
|
export const LOAM_IMPLEMENT = {
|
|
3
17
|
name: "loam-implement",
|
|
4
18
|
description: "Implement a loam feature's slice for one service — generated gherkin first, tests from it, then code",
|
|
@@ -10,6 +24,7 @@ export const LOAM_IMPLEMENT = {
|
|
|
10
24
|
"`loam status` — where this feature actually is, and what is owed before you start",
|
|
11
25
|
"`loam context` — the briefing: the service's living slice, and every delta in flight over it",
|
|
12
26
|
"`loam delta` — the task: the intent, every requirement verbatim, the endpoints, and the calls in and out",
|
|
27
|
+
"`loam tasks` — the open units, ordered; `--next` is the single next unit, with the scenario verbatim and the fact that closes it",
|
|
13
28
|
"`loam gherkin` in the service's repo — the digest-stamped `.feature` files. Never edit them",
|
|
14
29
|
"write step definitions for the generated scenarios first, outside the generated directory",
|
|
15
30
|
"implement until the suite passes, running it with a JSON report",
|
|
@@ -18,7 +33,8 @@ export const LOAM_IMPLEMENT = {
|
|
|
18
33
|
],
|
|
19
34
|
body: `Implement one service's part of a feature. Every step runs in the SERVICE'S own
|
|
20
35
|
repository, which needs its own committed ./loam.json — if there is none,
|
|
21
|
-
\`loam init --docs <path-to-docs-repo> --service $2
|
|
36
|
+
\`loam init --docs <path-to-docs-repo> --service $2 --agent-profile service\` then
|
|
37
|
+
\`loam doctor\` first.
|
|
22
38
|
|
|
23
39
|
0. \`loam status $1 --json\` — where this feature actually is. Run it before
|
|
24
40
|
anything else if you joined halfway or lost the session: it says which
|
|
@@ -59,12 +75,64 @@ repository, which needs its own committed ./loam.json — if there is none,
|
|
|
59
75
|
document before building anything — for the delta, \`loam validate $1 --json\` names
|
|
60
76
|
the error; the contract delta is not graded by validate, so read the parser message
|
|
61
77
|
the payload carries.
|
|
62
|
-
3.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
78
|
+
3. \`loam tasks $1 --service $2 --next --json\` — ONE unit of work, and the loop
|
|
79
|
+
every step below runs inside. Step 2 is the whole brief; this is the next thing
|
|
80
|
+
to do in it. It writes nothing: the projection is derived from the verify
|
|
81
|
+
checklist, the delta scans, the feature's verification record and — in this,
|
|
82
|
+
the bound service repo — the digest stamps in the generated \`.feature\` files.
|
|
83
|
+
Drop \`--next\` to see every open unit instead of only the head.
|
|
84
|
+
Each entry of \`units[]\` carries its \`id\` (the same id the verify checklist uses
|
|
85
|
+
for that claim), \`kind\` (\`service.exists\`, \`api.exposes\`, \`event.declares\`,
|
|
86
|
+
\`c4.calls\`, \`scenario.tested\` or \`requirement.described\`), \`subject\`, \`claim\`,
|
|
87
|
+
\`digest\`, \`dependsOn\`, \`source\` — the path, line, requirement and scenario it was
|
|
88
|
+
derived from, with the scenario's own \`lines\` verbatim — and \`done.check\`.
|
|
89
|
+
|
|
90
|
+
\`at\` is the instruction. It says what the unit NEEDS, and each of its four
|
|
91
|
+
values names exactly one action:
|
|
92
|
+
- \`describe\` — the requirement owns no scenarios yet. Write the Given/When/Then
|
|
93
|
+
in the DOCS repo, on the requirement \`source\` names, and nothing else. loam
|
|
94
|
+
does not author, and no amount of code closes a unit sitting here.
|
|
95
|
+
- \`generate\` — no stamped \`.feature\` carries this scenario's digest. The only
|
|
96
|
+
action is \`loam gherkin $1\` in this repo (step 4). Do not write code.
|
|
97
|
+
- \`implement\` — the stamped scenario exists and no recorded run has confirmed
|
|
98
|
+
it. Write step definitions outside \`loam/\`, implement until the suite passes,
|
|
99
|
+
then \`loam verify $1 --service $2 --results report.json\` (step 5).
|
|
100
|
+
- \`attested\` — the claim was closed on somebody's word rather than on a run.
|
|
101
|
+
Run the suite and re-record it so it stops resting on an assertion. An
|
|
102
|
+
agent-attested scenario stays visible at \`attested\` forever, deliberately: it
|
|
103
|
+
is the one state that cannot be made to go away by ignoring it.
|
|
104
|
+
|
|
105
|
+
\`done.check\` is the FACT that closes the unit, and the RUNNER decides whether
|
|
106
|
+
that fact holds — never your own reading of the code you just wrote. Done means
|
|
107
|
+
the unit no longer appears, and it stops appearing only once its claim is
|
|
108
|
+
recorded confirmed in a verification record that is not stale. So re-run
|
|
109
|
+
\`loam tasks $1 --service $2 --json\` after every recording instead of working
|
|
110
|
+
down a list you copied out: \`frontier\` (the first ten open unit ids, in order)
|
|
111
|
+
and \`counts\` (\`open\`, \`done\`, \`attested\`) are recomputed from the documents on
|
|
112
|
+
every run, which is what makes taking the head of it always safe.
|
|
113
|
+
\`checklist\` is the digest of the question set the whole projection derives from.
|
|
114
|
+
If it differs from the one you started against, the question set moved under
|
|
115
|
+
you — re-read the feature before continuing, because what you closed earlier may
|
|
116
|
+
not be what is being asked now.
|
|
117
|
+
\`record\` says whether the answers were found at all (\`absent\`, \`recorded\`,
|
|
118
|
+
\`stale\`, \`unreadable\`) and \`stamps\` whether this repo's generated suite could be
|
|
119
|
+
read (\`read\`, \`unknown\`); an \`unknown\` makes every \`generate\` verdict a guess,
|
|
120
|
+
so fix the binding before acting on one. \`contracts\` restates step 6 as data
|
|
121
|
+
(\`expose[]\` and \`call[]\`), \`size\` the shape of the packet, \`review[]\` what a
|
|
122
|
+
reviewer of it will read, and \`waitingOn\` — \`--service\` runs only — what another
|
|
123
|
+
service still owes before your frontier can move.
|
|
124
|
+
Exit 1 with \`ok: true\` is step 2's rule again: delta.likec4 or one of the
|
|
125
|
+
contract axes did not parse, so the projection is SHORT by everything that
|
|
126
|
+
document would have contributed. Fix the document; do not work the units that
|
|
127
|
+
happened to survive it.
|
|
128
|
+
4. \`at: "generate"\` — in the service's repo, \`loam gherkin $1 --json\`. One
|
|
129
|
+
\`.feature\` file per changed requirement lands under \`<gherkinDir>/loam/\`
|
|
130
|
+
(default \`features/loam/\`), scenarios digest-stamped, arch requirements tagged
|
|
131
|
+
\`@architecture\`. Those files ARE the acceptance criteria. Never edit them:
|
|
132
|
+
regeneration rewrites the directory, and \`loam validate\` reports the suite stale
|
|
133
|
+
by digest, not by your intentions. Generating is the WHOLE action for a unit at
|
|
134
|
+
\`generate\`: go back to step 3, where the same unit now reads \`implement\`.
|
|
135
|
+
5. \`at: "implement"\` — step definitions FIRST, outside \`loam/\`, then the code.
|
|
68
136
|
Do not paraphrase a scenario into something easier to pass; it is the acceptance
|
|
69
137
|
criterion someone else reviews against.
|
|
70
138
|
Run \`loam steps --service $2 --json\` before you write any: it collapses every
|
|
@@ -75,12 +143,20 @@ repository, which needs its own committed ./loam.json — if there is none,
|
|
|
75
143
|
and regenerate, rather than writing the second definition.
|
|
76
144
|
A step's argument arrives as an argument: a \`"""\` docstring is the payload, an
|
|
77
145
|
indented table is a DataTable. Assert against them; do not re-parse the step text.
|
|
78
|
-
|
|
79
|
-
(\`cucumber-js --format json:report.json
|
|
146
|
+
Then implement until the suite passes — run it with a JSON report
|
|
147
|
+
(\`cucumber-js --format json:report.json\`; cucumber-jvm, behave, Reqnroll,
|
|
148
|
+
pytest-bdd \`--cucumberjson\` and godog write the same cucumber JSON):
|
|
80
149
|
\`loam verify $1 --service $2 --results report.json\` consumes that report as the
|
|
81
|
-
done-check's answer sheet.
|
|
150
|
+
done-check's answer sheet. A runner with no cucumber formatter answers through
|
|
151
|
+
loam's runner-neutral shape instead, keyed by each scenario's \`@loam-digest\`
|
|
152
|
+
tag — adapting the runner's report into it is a build step you own:
|
|
153
|
+
\`{"loamScenarioReport": 1, "results": [{"digest": "<16 hex>", "status": "passed"}]}\`.
|
|
154
|
+
A cucumber run must have run every step of the stamped scenario: a report
|
|
155
|
+
entry whose step count differs is not that scenario's run and stays
|
|
156
|
+
unconfirmed. Record from THIS repo, with \`--service\` — the
|
|
82
157
|
\`--service\`-less \`--record\` form writes the whole record from one place and is
|
|
83
|
-
refused (\`record-federated\`) once another service has attested.
|
|
158
|
+
refused (\`record-federated\`) once another service has attested. Recording is what
|
|
159
|
+
closes the unit, so step 3 is the command AFTER it, not the end of the run.
|
|
84
160
|
6. Honour the contract: every operation in \`architecture.inbound\` must exist under
|
|
85
161
|
exactly that operationId, and every one in \`architecture.outbound\` must be called.
|
|
86
162
|
7. If building made you change the feature's documents at all — a requirement's
|
|
@@ -99,7 +175,9 @@ repository, which needs its own committed ./loam.json — if there is none,
|
|
|
99
175
|
8. \`loam validate --feature $1 --json\` before handing back.
|
|
100
176
|
|
|
101
177
|
If the requirement is ambiguous, say so and stop — do not invent behaviour and
|
|
102
|
-
leave the spec disagreeing with the code.
|
|
178
|
+
leave the spec disagreeing with the code. Write the question under
|
|
179
|
+
\`## Open questions\` in the feature's \`intent.md\`, where loam-feature's analyze
|
|
180
|
+
step put the ones it found, so a person answers it once for every service.
|
|
103
181
|
`,
|
|
104
182
|
};
|
|
105
183
|
//# sourceMappingURL=implement.js.map
|