mandrel 2.58.0 → 2.60.0

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.
Files changed (124) hide show
  1. package/.agents/README.md +17 -12
  2. package/.agents/agents/acceptance-critic.md +24 -43
  3. package/.agents/agents/story-worker.md +18 -19
  4. package/.agents/docs/SDLC.md +12 -13
  5. package/.agents/docs/agentrc-reference.json +1 -2
  6. package/.agents/docs/configuration.md +29 -46
  7. package/.agents/docs/quality-gates.md +9 -5
  8. package/.agents/docs/workflows.md +1 -1
  9. package/.agents/instructions.md +5 -7
  10. package/.agents/rules/ci-remediation.md +41 -8
  11. package/.agents/rules/known-tooling-behavior.md +65 -15
  12. package/.agents/runtime-deps.json +7 -2
  13. package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
  14. package/.agents/schemas/agentrc.schema.json +6 -11
  15. package/.agents/schemas/crap-baseline.schema.json +1 -1
  16. package/.agents/schemas/crap-report.schema.json +1 -1
  17. package/.agents/schemas/story-deliver-terminal.schema.json +3 -3
  18. package/.agents/scripts/README.md +11 -1
  19. package/.agents/scripts/acceptance-eval.js +25 -27
  20. package/.agents/scripts/ceremony-derive.js +15 -10
  21. package/.agents/scripts/check-context-budget.js +148 -228
  22. package/.agents/scripts/check-schema-references.js +5 -3
  23. package/.agents/scripts/check-workflow-citations.js +33 -147
  24. package/.agents/scripts/coverage-capture.js +7 -4
  25. package/.agents/scripts/deliver-light.js +41 -100
  26. package/.agents/scripts/deliver-run.js +631 -0
  27. package/.agents/scripts/file-ci-gap.js +59 -11
  28. package/.agents/scripts/install-matrix-assert.js +48 -3
  29. package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +51 -33
  30. package/.agents/scripts/lib/baselines/crap-preview-incremental.js +6 -2
  31. package/.agents/scripts/lib/baselines/kinds/_crap-read.js +0 -8
  32. package/.agents/scripts/lib/baselines/kinds/crap.js +35 -18
  33. package/.agents/scripts/lib/changed-files.js +30 -0
  34. package/.agents/scripts/lib/config/delivery-routing.js +5 -4
  35. package/.agents/scripts/lib/config/explain.js +1 -3
  36. package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +1 -1
  37. package/.agents/scripts/lib/config-resolver.js +1 -0
  38. package/.agents/scripts/lib/config-settings-schema-delivery.js +28 -21
  39. package/.agents/scripts/lib/coverage-capture-fullscope.js +10 -2
  40. package/.agents/scripts/lib/coverage-capture-incremental.js +3 -2
  41. package/.agents/scripts/lib/coverage-capture-usage.js +4 -1
  42. package/.agents/scripts/lib/crap-engine.js +2 -2
  43. package/.agents/scripts/lib/crap-utils.js +21 -5
  44. package/.agents/scripts/lib/doc-tiers.js +4 -2
  45. package/.agents/scripts/lib/escomplex-ast-compat.js +39 -17
  46. package/.agents/scripts/lib/escomplex-kernel.js +298 -0
  47. package/.agents/scripts/lib/feedback-loop/graduator-core.js +7 -6
  48. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +7 -5
  49. package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
  50. package/.agents/scripts/lib/gh-exec.js +160 -0
  51. package/.agents/scripts/lib/maintainability-engine.js +3 -3
  52. package/.agents/scripts/lib/observability/source-classifier.js +1 -0
  53. package/.agents/scripts/lib/orchestration/ceremony-routing.js +74 -132
  54. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +123 -12
  55. package/.agents/scripts/lib/orchestration/complexity-gate.js +180 -352
  56. package/.agents/scripts/lib/orchestration/light-suitability.js +71 -136
  57. package/.agents/scripts/lib/orchestration/plan-context.js +44 -50
  58. package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +8 -6
  59. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +104 -119
  60. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +41 -25
  61. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +11 -11
  62. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +63 -29
  63. package/.agents/scripts/lib/orchestration/plan-persist/wave-collision-gate.js +107 -0
  64. package/.agents/scripts/lib/orchestration/review-depth.js +14 -11
  65. package/.agents/scripts/lib/orchestration/run-epilogue.js +260 -182
  66. package/.agents/scripts/lib/orchestration/run-scoped-config.js +63 -99
  67. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +3 -3
  68. package/.agents/scripts/lib/orchestration/single-story-close/phases/graphql-preflight.js +137 -0
  69. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +105 -18
  70. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +4 -3
  71. package/.agents/scripts/lib/orchestration/story-follow-ups.js +156 -39
  72. package/.agents/scripts/lib/orchestration/story-init-envelope.js +71 -0
  73. package/.agents/scripts/lib/orchestration/task-body-validator.js +8 -17
  74. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +25 -209
  75. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +8 -5
  76. package/.agents/scripts/lib/orchestration/ticket-validator.js +44 -183
  77. package/.agents/scripts/lib/orchestration/ticketing/reads.js +14 -25
  78. package/.agents/scripts/lib/runtime-deps/dep-resolution.js +155 -0
  79. package/.agents/scripts/lib/runtime-deps/ensure-installed.js +44 -9
  80. package/.agents/scripts/lib/runtime-deps/parser-major.js +110 -0
  81. package/.agents/scripts/lib/runtime-deps/preflight.js +6 -25
  82. package/.agents/scripts/lib/runtime-deps/scan-imports.js +46 -1
  83. package/.agents/scripts/lib/skills/walk-skill-files.js +1 -1
  84. package/.agents/scripts/lib/story-body/body-format-lints.js +58 -12
  85. package/.agents/scripts/lib/story-body/story-body.js +83 -29
  86. package/.agents/scripts/lib/templates/decomposer-prompts.js +28 -33
  87. package/.agents/scripts/lib/wave-runner/live-probe.js +31 -5
  88. package/.agents/scripts/merge-baseline.js +4 -5
  89. package/.agents/scripts/plan-context.js +117 -28
  90. package/.agents/scripts/plan-persist.js +79 -39
  91. package/.agents/scripts/plan-run-epilogue.js +11 -8
  92. package/.agents/scripts/pr-watch-with-update.js +9 -2
  93. package/.agents/scripts/run-verify.js +13 -6
  94. package/.agents/scripts/single-story-init.js +7 -57
  95. package/.agents/scripts/stories-wave-tick.js +160 -26
  96. package/.agents/skills/core/gates-and-baselines/reference.md +0 -1
  97. package/.agents/skills/skills.index.json +2 -12
  98. package/.agents/skills/stack/qa/playwright/SKILL.md +26 -0
  99. package/.agents/workflows/audit-to-stories.md +14 -11
  100. package/.agents/workflows/helpers/acceptance-self-eval.md +84 -157
  101. package/.agents/workflows/helpers/code-review.md +4 -2
  102. package/.agents/workflows/helpers/deliver-digest.md +31 -24
  103. package/.agents/workflows/helpers/deliver-light.md +92 -101
  104. package/.agents/workflows/helpers/deliver-reference.md +116 -100
  105. package/.agents/workflows/helpers/deliver-story-reference.md +58 -124
  106. package/.agents/workflows/helpers/deliver-story.md +17 -18
  107. package/.agents/workflows/helpers/plan-reference.md +82 -60
  108. package/.agents/workflows/mandrel-deliver.md +47 -31
  109. package/.agents/workflows/mandrel-plan.md +32 -30
  110. package/.agents/workflows/mandrel-update.md +36 -21
  111. package/README.md +3 -3
  112. package/docs/CHANGELOG.md +43 -0
  113. package/lib/cli/registry.js +45 -25
  114. package/lib/cli/update.js +376 -17
  115. package/lib/migrations/index.js +2 -0
  116. package/lib/migrations/steps/2.60.0-retire-audit-results-autofile.js +40 -0
  117. package/package.json +8 -2
  118. package/.agents/schemas/model-attribution.schema.json +0 -53
  119. package/.agents/scripts/lib/orchestration/model-attribution.js +0 -418
  120. package/.agents/scripts/lib/orchestration/split-policy-validator.js +0 -188
  121. package/.agents/scripts/lib/orchestration/story-plan-state.js +0 -33
  122. package/.agents/scripts/lib/orchestration/structured-comment-parser.js +0 -67
  123. package/.agents/scripts/lib/templates/spec-author-prompts.js +0 -76
  124. package/.agents/skills/core/scope-triage/SKILL.md +0 -48
@@ -66,16 +66,30 @@ commands** the script knows nothing about, so a locally green
66
66
  | `check-dead-exports.js` | no | no | **yes** |
67
67
  | `check-dead-exports.js --production` | no | no | **yes** |
68
68
  | `check-context-budget.js` | no | no | **yes** |
69
- | `check-workflow-citations.js` | no | no | **no** |
69
+ | `check-workflow-citations.js` | no | no | **yes** |
70
70
  | `check-cyclomatic.js` | no | no | **yes** |
71
71
  | `check-schema-references.js` | no | no | **yes** |
72
72
  | `check-knip-entries.js` | no | no | **yes** |
73
73
  | `check-baseline-scope.js` | no | no | **no** |
74
74
 
75
- Two are still in **no** local aggregate command —
76
- `check-workflow-citations.js` and `check-baseline-scope.js`. Each is reachable
77
- only as its own npm script (`check:workflow-citations`, `baselines:scope`) or a
78
- direct invocation.
75
+ One is still in **no** local aggregate command — `check-baseline-scope.js`,
76
+ reachable only as its own npm script (`baselines:scope`) or a direct
77
+ invocation. `check-workflow-citations.js` joined `verify` when Story #5340
78
+ demoted it to a report; before that it was exempted because
79
+ `tests/check-workflow-citations.test.js` re-ran its ratchet through the `test`
80
+ step.
81
+
82
+ **Two of the nine cannot fail this job any more** (Story #5340, ADR
83
+ 20260917-5340). `check-workflow-citations.js` prints a per-file provenance
84
+ count and always exits 0 — it reads no baseline at all, and the one it used
85
+ to ratchet against is deleted.
86
+ `check-context-budget.js` fails on the `alwaysLoaded` tier alone: its
87
+ `workflow` and `mandatoryRead` tiers are printed, and the per-file 8 KB
88
+ agent-boot ceiling and the row-vs-tree drift gate are gone. A green run of
89
+ either is therefore **not** evidence the numbers held — read the report. Both
90
+ ratchets used to fail a *rise*, so a prose fix had to be paid for with an
91
+ unrelated trim in the same commit; the always-loaded gate is the one that
92
+ stays because every session and every subagent spawn re-pays that closure.
79
93
 
80
94
  `prune-baseline-orphans.js --check` left this table in v2.32.0: it no longer
81
95
  runs in CI in any mode. It reports the same absent / out-of-scope rows as
@@ -84,25 +98,24 @@ the required job made the scope gate's inherited-divergence warning
84
98
  unreachable — a stale row on `main` red every open PR regardless of who landed
85
99
  it. It stays the operator's remedy, via `npm run baselines:prune`.
86
100
 
87
- `check-context-budget.js` additionally runs in `.husky/pre-push`. It is
88
- zero-tolerance in **both** directions — a change that *shrinks* the
89
- always-loaded doc closure reds it exactly as growth does, and the remedy is a
90
- committed baseline refresh, not a smaller diff.
101
+ `check-context-budget.js` and `check-workflow-citations.js` additionally run in
102
+ `.husky/pre-push`, as reports. A shrink in the always-loaded closure no longer
103
+ reds anything either (Story #5313): it is reported, and the close's write-back
104
+ seam commits the lower total on the Story branch.
91
105
 
92
106
  **Reproduce.**
93
107
 
94
108
  ```bash
95
109
  node .agents/scripts/check-baselines.js --format text # names the 3 gates it ran
96
110
  sed -n '/name: baselines/,/windows-smoke/p' .github/workflows/ci.yml | grep 'js'
97
- grep "label: '" .agents/scripts/run-verify.js # the 9 steps verify covers
111
+ sed -n '/^const STEPS/,/^];/p' .agents/scripts/run-verify.js # the steps verify covers
98
112
  ```
99
113
 
100
114
  **Safe move.** `npm run verify` is the closest local mirror. Run
101
- `node .agents/scripts/check-workflow-citations.js` alongside it when the change
102
- touches workflow prose under `.agents/workflows/`, and
103
- `npm run baselines:scope && npm run baselines:prune -- --check` when it adds,
104
- deletes or moves files inside a scored `targetDirs` root. Reproducing only the
105
- `.agentrc.json` command before a push is a false green.
115
+ `npm run baselines:scope && npm run baselines:prune -- --check` alongside it
116
+ when the change adds, deletes or moves files inside a scored `targetDirs`
117
+ root. Reproducing only the `.agentrc.json` command before a push is a false
118
+ green.
106
119
 
107
120
  ## 3. The two dead-export passes disagree, and the production pass is silent without `!`
108
121
 
@@ -148,3 +161,40 @@ hand-edit `baselines/dead-exports*.json`: a hand-written row set is the one
148
161
  input no gate re-derives. `.agents/rules/test-seams.md` governs which seams
149
162
  are sanctioned. Never remove the `!` suffixes from `knip.json` to quieten the
150
163
  production pass.
164
+
165
+ ## 4. A green pre-push is only evidence the two scopes agreed because `--ref` outranks config
166
+
167
+ **Behavior.** `.husky/pre-push` captures coverage and then scores it, both
168
+ against a literal `origin/main`. The CRAP half of `quality-preview.js` is a
169
+ function of complexity **and** coverage, so the preview is only reading its
170
+ own tree if the artifact under it was captured over the same change set.
171
+ Both steps resolve that set through **one** rule, stated once in
172
+ `resolveChangedFilesRef` (`.agents/scripts/lib/changed-files.js`): the ref the
173
+ caller named wins, and
174
+ `delivery.quality.gates.crap.incrementalCoverage.baseRef` is the default for a
175
+ caller that names none — the close-validation gate, which passes no `--ref`.
176
+ Both `coverage-capture` paths and the preview's CRAP baseline join call it, so
177
+ one hook invocation cannot resolve two refs.
178
+
179
+ Before Story #5365 the configured value outranked the flag. The preview has
180
+ no config ref to consult, so a consumer that set `baseRef` captured against
181
+ one ref while the preview scored another, and the preview could read an
182
+ artifact whose scope was not its own — the stale-artifact read the
183
+ capture-before-preview ordering (Story #5356) exists to prevent, reopened by
184
+ configuration rather than by editing the hook. **This repository sets no
185
+ `baseRef`**, so the divergence was invisible here: a green local run proved
186
+ nothing about a consumer's.
187
+
188
+ **Reproduce.**
189
+
190
+ ```bash
191
+ node -e "import('./.agents/scripts/lib/changed-files.js').then(({ resolveChangedFilesRef }) => { const crap = { incrementalCoverage: { baseRef: 'develop' } }; console.log(resolveChangedFilesRef({ crap, ref: 'origin/main' })); console.log(resolveChangedFilesRef({ crap, ref: null })); })"
192
+ # → origin/main (the hook's flag wins over a configured baseRef)
193
+ # → develop (config still answers a caller that named no ref)
194
+ grep -n 'origin/main' .husky/pre-push # the same literal on both steps
195
+ ```
196
+
197
+ **Safe move.** Read a green pre-push as evidence about CRAP only when both
198
+ hook steps still carry the same literal ref. Moving one means moving the
199
+ other; adding another consumer of the change set means routing it through
200
+ `resolveChangedFilesRef` rather than reading `baseRef` directly.
@@ -1,12 +1,17 @@
1
1
  {
2
- "description": "Single source of truth for the third-party npm packages the .agents/ framework scripts import at runtime. This manifest ships *inside* .agents/ so it travels with the mandrel package into consumer projects. Consumers must provide these packages in the node_modules resolvable from their repository root (the framework scripts free-ride on the consumer's install). The `dependencies` block is fail-fast enforced by the preflight guard (.agents/scripts/lib/runtime-deps/ensure-installed.js); the `optionalDependencies` block lists packages that are imported behind graceful-degradation paths (try/catch or dev-only watch tooling) and are therefore declared but never preflight-blocked. A drift test (tests/scripts/runtime-deps-drift.test.js) asserts every third-party import under .agents/scripts/** is declared here. Version ranges mirror the framework's own root package.json.",
2
+ "description": "Single source of truth for the third-party npm packages the .agents/ framework scripts import at runtime. This manifest ships *inside* .agents/ so it travels with the mandrel package into consumer projects. Consumers must provide these packages in the node_modules resolvable from their repository root (the framework scripts free-ride on the consumer's install). The `dependencies` block is fail-fast enforced by the preflight guard (.agents/scripts/lib/runtime-deps/ensure-installed.js); the `optionalDependencies` block lists packages that are imported behind graceful-degradation paths (try/catch or dev-only watch tooling) and are therefore declared but never preflight-blocked. A drift test (tests/scripts/runtime-deps-drift.test.js) asserts every third-party import under .agents/scripts/** is declared here. Version ranges mirror the framework's own root package.json. Two entries need their reason recorded. `@babel/parser` is pinned to ^7 because the kernel's fixed plugin list does not parse under 8.x, and the range is documentation rather than enforcement \u2014 `.agents/` resolves from the consumer's own node_modules \u2014 so the kernel asserts the resolved major at load. `babel-runtime` is declared even though no framework script imports it: four of the metric-core packages require it and none of them declares it, so it resolved only by being hoisted from the parse/dispatch plumbing this framework used to depend on. Declaring it makes the closure explicit and preflight-enforced. The import-vs-manifest drift guard only flags imported-but-undeclared, so a declared-but-unimported peer repair like this is legal by design.",
3
3
  "dependencies": {
4
+ "@babel/parser": "^7.29.3",
4
5
  "ajv": "^8.20.0",
5
6
  "ajv-formats": "^3.0.1",
7
+ "babel-runtime": "^6.26.0",
8
+ "escomplex-plugin-metrics-module": "^0.1.0",
9
+ "escomplex-plugin-syntax-babylon": "^0.1.0",
6
10
  "js-yaml": "^4.3.1",
7
11
  "minimatch": "^10.0.0",
8
12
  "picomatch": "^4.0.4",
9
- "typhonjs-escomplex": "^0.1.0"
13
+ "typhonjs-ast-walker": "^0.2.1",
14
+ "typhonjs-escomplex-commons": "^0.1.1"
10
15
  },
11
16
  "optionalDependencies": {
12
17
  "@commitlint/load": "^21.0.0",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "acceptance-eval-verdict",
4
4
  "title": "Acceptance Self-Eval Verdict",
5
- "description": "Structured output of one independent (fresh-context) acceptance self-eval round (Story #3819). Produced by the critic pass in the Story-implementation phase (helpers/deliver-story.md Step 1a), written under the gitignored temp tree, and consumed by acceptance-eval.js to enforce the bounded round cap and decide whether the Story proceeds to `closing`, redrafts, or escalates to `agent::blocked`. The author MUST NOT be the evaluator — the verdict is an independent judgment of the caller-injected change set against each inline acceptance[] item.",
5
+ "description": "Structured output of one acceptance self-eval round (Story #3819). One file per Story covering every inline acceptance[] item exactly once, authored by the round's single verdict owner — the inline self-eval under ceremonyProfile minimal/standard, a fresh-context maker-blind critic under strict (Story #5343) — in the Story-implementation phase (helpers/deliver-story.md Step 1a). Written under the gitignored temp tree and consumed by acceptance-eval.js, which enforces the bounded round cap and decides whether the Story proceeds to `closing`, redrafts, or escalates to `agent::blocked`. Whoever owns it scores the caller-injected change set afresh against each criterion, treating the implementation reasoning as untrusted; the gate is the scorer of that verdict, never a second pass over the criteria.",
6
6
  "type": "object",
7
7
  "required": ["storyId", "schemaVersion", "round", "criteria"],
8
8
  "properties": {
@@ -904,7 +904,7 @@
904
904
  "baseRef": {
905
905
  "type": "string",
906
906
  "minLength": 1,
907
- "description": "Git ref the changed-file set is computed against. Omitted falls back to the gate’s own `--ref` (`main`)."
907
+ "description": "Default git ref the changed-file set is computed against, for a caller that passes no `--ref`. A `--ref` the caller named wins over this value, so a caller that anchors another gate on the same ref — `.husky/pre-push`, which passes `--ref origin/main` and then previews `--changed-since origin/main` — resolves ONE scope for both steps (Story #5365). Omitted and unpassed, the gate’s own default `main` applies."
908
908
  }
909
909
  },
910
910
  "additionalProperties": false
@@ -1742,17 +1742,12 @@
1742
1742
  },
1743
1743
  "feedbackLoop": {
1744
1744
  "type": "object",
1745
- "description": "Opt-out toggles for the close-time auto-file graduators. All default to auto-filing on.",
1745
+ "description": "Opt-in toggle for the close-time retro auto-file graduator, plus the friction recurrence window. Auto-filing defaults to OFF (Story #5341).",
1746
1746
  "properties": {
1747
- "auditResultsAutoFile": {
1748
- "type": "boolean",
1749
- "description": "When true (default), the close-time audit-results graduator auto-files non-blocking audit-results findings as follow-up issues routed by source classification. Set to false to suppress auto-filing; findings remain accessible in the structured comments on the Story.",
1750
- "default": true
1751
- },
1752
1747
  "retroProposals": {
1753
1748
  "type": "boolean",
1754
- "description": "When true (default), the retro auto-files its actionable routed proposals as meta::<framework-gap|consumer-improvement> + friction::<category> issues via the graduator pre-parsed-findings seam, and the rendered retro sections list the filed issue numbers instead of paste-ready gh command stanzas. Set to false to fall back to the command stanzas.",
1755
- "default": true
1749
+ "description": "When true, the retro auto-files its actionable routed proposals as meta::<framework-gap|consumer-improvement> + friction::<category> issues via the graduator pre-parsed-findings seam, and the rendered retro sections list the filed issue numbers instead of paste-ready gh command stanzas. Defaults to false (Story #5341), which renders the command stanzas instead.",
1750
+ "default": false
1756
1751
  },
1757
1752
  "frictionWindowDays": {
1758
1753
  "type": "integer",
@@ -1843,7 +1838,7 @@
1843
1838
  },
1844
1839
  "routing": {
1845
1840
  "type": "object",
1846
- "description": "v2 delivery-spawn routing: role-scoped boot contexts and the ceremony profile. The v1 singleDelivery epic-route kill-switch was removed in Stage 6; the freshCriticSampleRate sampling floor was retired in Story #5313.",
1841
+ "description": "v2 delivery-spawn routing: role-scoped boot contexts and the ceremony profile. The v1 singleDelivery epic-route kill-switch was removed in Stage 6; the freshCriticSampleRate sampling floor was retired in Story #5313 and the derived-level ceremony routing in Story #5343.",
1847
1842
  "properties": {
1848
1843
  "roleScopedAgents": {
1849
1844
  "type": "boolean",
@@ -1853,7 +1848,7 @@
1853
1848
  "ceremonyProfile": {
1854
1849
  "type": "string",
1855
1850
  "enum": ["minimal", "standard", "strict"],
1856
- "description": "Acceptance-ceremony depth. minimal = always inline critic; strict = always fresh-context critic; standard (default) = routed off the change level derived from the Story diff: high or underivable → fresh, low → inline.",
1851
+ "description": "Acceptance-ceremony depth — who authors the Story acceptance verdict. minimal and standard (default) = the inline self-eval, whatever the diff touches; strict = a fresh-context maker-blind critic. Review depth is a separate decision and still derives `deep` for any sensitive path (review-depth.js).",
1857
1852
  "default": "standard"
1858
1853
  },
1859
1854
  "closeAndLand": {
@@ -18,7 +18,7 @@
18
18
  },
19
19
  "escomplexVersion": {
20
20
  "type": "string",
21
- "description": "Version of the typhonjs-escomplex dependency used to produce this baseline. A mismatch against the installed dependency fails the gate closed (different kernel semantics are not negotiable)."
21
+ "description": "Version of the package that computes the metrics (escomplex-plugin-metrics-module) used to produce this baseline. Informational: the axis that compared it was removed in Story #5336 — the v2 envelope does not carry the field, so it could not fire. scoringSemantics is the axis that rejects an incompatible scorer."
22
22
  },
23
23
  "tsTranspilerVersion": {
24
24
  "type": "string",
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "escomplexVersion": {
16
16
  "type": "string",
17
- "description": "typhonjs-escomplex version used for the scan."
17
+ "description": "Version of the package that computes the metrics (escomplex-plugin-metrics-module) used for the scan."
18
18
  },
19
19
  "summary": {
20
20
  "type": "object",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://github.com/dsj1984/mandrel/blob/main/.agents/schemas/story-deliver-terminal.schema.json",
4
4
  "title": "story-deliver-terminal",
5
- "description": "The single terminal envelope a Story delivery invocation emits (Story #4543). Before this schema, the delivery tail had two divergent prose return contracts — one in .agents/workflows/helpers/deliver-story.md, a different one in .agents/agents/story-worker.md — and neither was validated by anything, so a caller could not tell a landed Story from a parked one without re-probing GitHub. This is the SSOT both now reference rather than restate. status is exactly one of landed | pending | blocked | failed | escalated; phase names where the run ended; tail carries per-step booleans so a partial-tail degradation is visible without failing an otherwise-landed merge; nextCommand names the single command that resumes or remediates the run, drawn from the same vocabulary deliver-recover.js prints. escalated (Story #4746) is the one status emitted BEFORE a Story exists — /deliver-light's suitability gate refusing an over-scope prompt — which is why storyId is null exactly there and non-null everywhere else.",
5
+ "description": "The single terminal envelope a Story delivery invocation emits (Story #4543). Before this schema, the delivery tail had two divergent prose return contracts — one in .agents/workflows/helpers/deliver-story.md, a different one in .agents/agents/story-worker.md — and neither was validated by anything, so a caller could not tell a landed Story from a parked one without re-probing GitHub. This is the SSOT both now reference rather than restate. status is exactly one of landed | pending | blocked | failed | escalated; phase names where the run ended; tail carries per-step booleans so a partial-tail degradation is visible without failing an otherwise-landed merge; nextCommand names the single command that resumes or remediates the run, drawn from the same vocabulary deliver-recover.js prints. escalated (Story #4746) is the one status emitted BEFORE a Story exists — /deliver-light's suitability gate refusing a prompt — which is why storyId is null exactly there and non-null everywhere else.",
6
6
  "type": "object",
7
7
  "required": [
8
8
  "kind",
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "status": {
23
23
  "type": "string",
24
- "description": "landed — the PR merged, the Story is agent::done, and the post-land tail was attempted. pending — a bounded wait expired with the PR still in flight; NO label was mutated and no merge.unlanded event was emitted, so the run is resumable via nextCommand. blocked — a classified hard block; the Story carries agent::blocked and blocked.blockClass names the class. failed — a phase crashed; phase names which one. escalated — the /deliver-light suitability gate refused an over-scope prompt under --yes; nothing was created and the session ENDS here, nextCommand naming the /mandrel-plan invocation that owns the work instead.",
24
+ "description": "landed — the PR merged, the Story is agent::done, and the post-land tail was attempted. pending — a bounded wait expired with the PR still in flight; NO label was mutated and no merge.unlanded event was emitted, so the run is resumable via nextCommand. blocked — a classified hard block; the Story carries agent::blocked and blocked.blockClass names the class. failed — a phase crashed; phase names which one. escalated — /deliver-light's suitability gate refused a prompt on an un-waivable risk rule or an un-ledgered verdict; nothing was created and the LIGHT PATH ends here, nextCommand naming the /mandrel-plan invocation that owns the work instead. Story #5344 narrowed what refuses (the predicted-shape ceilings are gone) and loosened what follows (that command may be run in the same session, seeded with escalation.reasons) without touching this shape.",
25
25
  "enum": ["landed", "pending", "blocked", "failed", "escalated"]
26
26
  },
27
27
  "phase": {
@@ -179,7 +179,7 @@
179
179
  },
180
180
  "nextCommand": {
181
181
  "type": ["string", "null"],
182
- "description": "The single command that advances this work from where it stopped, or null when status === \"landed\" and nothing remains. Shares its vocabulary with deliver-recover.js so recovery and normal resumption speak one language. For status escalated it is the /mandrel-plan invocation the operator runs in a FRESH session — the one case where the command is a slash command rather than a script, because the work needs planning, not resumption.",
182
+ "description": "The single command that advances this work from where it stopped, or null when status === \"landed\" and nothing remains. Shares its vocabulary with deliver-recover.js so recovery and normal resumption speak one language. For status escalated it is the /mandrel-plan invocation that owns the work — the one case where the command is a slash command rather than a script, because the work needs planning, not resumption. Story #4746 required it to run in a FRESH session; Story #5344 permits the same session when it is seeded with escalation.reasons, and helpers/deliver-light.md carries the seeding contract.",
183
183
  "minLength": 1
184
184
  },
185
185
  "elapsedSeconds": { "type": "number", "minimum": 0 },
@@ -13,7 +13,17 @@ GitHub Actions surfaces first. `check-knip-entries.js` derives that
13
13
  caller set mechanically, so a CLI no invoker names is dead, not
14
14
  operator-only.
15
15
 
16
- It reads the entry list from whatever configuration knip itself would
16
+ The one script an operator-facing workflow drives **by name, repeatedly**, is
17
+ [`deliver-run.js`](deliver-run.js): one beat of a multi-Story
18
+ `/mandrel-deliver` run — it ticks the ready set from live state, writes each
19
+ ready Story's dispatch prompt under `<tempRoot>/run-<id>/`, keeps the run
20
+ ledger that replaces hand-maintained dispatch bookkeeping, and renders the
21
+ `single-story-close.js` command for every hand-off. Everything else in the
22
+ delivery chain (`resolve-stories.js`, `single-story-init.js`,
23
+ `single-story-close.js`, `stories-wave-tick.js`) is reached through it or
24
+ through a workflow step.
25
+
26
+ `check-knip-entries.js` reads the entry list from whatever configuration knip itself would
17
27
  load — `knip.json`, `knip.jsonc`, `.knip.json(c)`, `knip.ts`, `knip.js`,
18
28
  `knip.config.ts`, `knip.config.js`, or `package.json#knip` — evaluating
19
29
  TS/JS modules rather than parsing them, and counting entries declared
@@ -3,12 +3,11 @@
3
3
  /**
4
4
  * acceptance-eval.js — bounded per-Story acceptance self-eval gate (Story #3819).
5
5
  *
6
- * The Story-implementation phase runs ONE verdict-owner per acceptance
7
- * cluster (Story #4723) — the fresh-context critic when
8
- * `ceremony-routing.js` sensitivity-routes the cluster fresh, the
9
- * contract-identical inline self-eval otherwise — which scores the
10
- * caller-injected change set against each inline `acceptance[]` item and
11
- * emits one verdict file per round
6
+ * The Story-implementation phase runs ONE verdict-owner per Story
7
+ * (Story #4723, narrowed by #5343) — the contract-identical inline self-eval
8
+ * under `ceremonyProfile` `minimal` / `standard`, a fresh-context maker-blind
9
+ * critic under `strict` — which scores the caller-injected change set against
10
+ * each inline `acceptance[]` item and emits one verdict file per round
12
11
  * (`.agents/schemas/acceptance-eval-verdict.schema.json`). This CLI is the
13
12
  * deterministic SCORER of that single authored verdict — it validates and
14
13
  * decides, it never re-scores the criteria as an independent additional
@@ -41,18 +40,17 @@
41
40
  * tier along with the per-AC-cluster `--epic <id> --cluster <id>` mode that
42
41
  * scored an Epic `## Acceptance Table` against a `main..epic/<id>` diff.)
43
42
  *
44
- * One gate call per round (Story #4951). A round may fan out into N parallel
45
- * maker-blind cluster critics, but their per-cluster verdicts are merged by
46
- * the caller into ONE verdict — `criteria[]` in acceptance-array order — and
47
- * scored here exactly once. Invoking the gate per cluster instead would burn
48
- * one Story-level round per cluster (distinct fingerprints defeat the replay
49
- * guard) and race the `signals.ndjson` round ledger. The gate reads the
50
- * Story's own `acceptance[]` count off its body (Story #5313) and rejects a
51
- * verdict whose `criteria[]` length differs **before** scoring, so the
52
- * mistake costs no round. `--expected-criteria` is still accepted but is
53
- * redundant with the derived count: when both are known they must agree.
54
- * An inline-owned verdict is one file scored in one call — the cluster
55
- * merge applies only to fresh critics.
43
+ * One gate call per round (Story #4951; Story #5343 retired the cluster
44
+ * protocol that used to fan a round out). The round's owner authors ONE
45
+ * verdict covering every `acceptance[]` item — `criteria[]` in
46
+ * acceptance-array order — and it is scored here exactly once. A second call
47
+ * inside one round would burn a Story-level round for nothing (distinct
48
+ * fingerprints defeat the replay guard) and race the `signals.ndjson` round
49
+ * ledger. The gate reads the Story's own `acceptance[]` count off its body
50
+ * (Story #5313) and rejects a verdict whose `criteria[]` length differs
51
+ * **before** scoring, so the mistake costs no round. `--expected-criteria` is
52
+ * still accepted but is redundant with the derived count: when both are known
53
+ * they must agree.
56
54
  *
57
55
  * CLI:
58
56
  * --story <id> Story ID (required).
@@ -180,13 +178,13 @@ function parseCliArgs(argv) {
180
178
  }
181
179
 
182
180
  /**
183
- * The merge contract, stated once so both the flag error and the coverage
181
+ * The coverage contract, stated once so both the flag error and the coverage
184
182
  * error name the same shape the caller has to produce.
185
183
  */
186
184
  const MERGE_CONTRACT =
187
- 'One round = N parallel cluster critics -> ONE merged verdict -> ONE gate call: ' +
188
- "merge every cluster's records into a single criteria[] in acceptance[] order, " +
189
- 'one per acceptance item, before scoring.';
185
+ 'One round = ONE verdict -> ONE gate call: the verdict must carry one ' +
186
+ 'criteria[] record per acceptance[] item, in acceptance-array order, ' +
187
+ 'before scoring.';
190
188
 
191
189
  /**
192
190
  * Read the Story's `acceptance[]` count off its body (Story #5313), so the
@@ -292,8 +290,8 @@ export function resolveExpectedCriteria(raw) {
292
290
  * Reject a verdict that does not cover exactly `expectedCriteria` criteria.
293
291
  *
294
292
  * Called **before** `runAcceptanceEval`, which is where the round ledger is
295
- * read and appended — so a partial cluster verdict handed to the gate by
296
- * mistake costs no round and can never escalate a `redraft` into a `block`.
293
+ * read and appended — so a partial verdict handed to the gate by mistake
294
+ * costs no round and can never escalate a `redraft` into a `block`.
297
295
  *
298
296
  * Exported for tests.
299
297
  *
@@ -544,9 +542,9 @@ export async function runAcceptanceEvalCli(
544
542
 
545
543
  const verdict = validateVerdictImpl(parsed);
546
544
 
547
- // Story #4951 / #5313: a merged verdict must cover every acceptance[] item,
548
- // and the count comes from the Story body itself. This runs before the
549
- // round ledger is touched, so a partial cluster verdict is a free mistake.
545
+ // Story #4951 / #5313: the verdict must cover every acceptance[] item, and
546
+ // the count comes from the Story body itself. This runs before the round
547
+ // ledger is touched, so a partial verdict is a free mistake.
550
548
  const config = resolveConfigImpl();
551
549
  const expected = await resolveExpectedCriteriaCount({
552
550
  storyId,
@@ -23,9 +23,16 @@
23
23
  * { storyId, baseRef, headRef, files, enumerated, level, classes, profile,
24
24
  * mode, reason, verdictOwner }
25
25
  *
26
- * `files` is the one change set every acceptance critic must be handed; the
27
- * caller never lets a critic re-enumerate it. `files: null` means the diff
28
- * could not be enumerated, which routes to the fail-safe fresh critic.
26
+ * `files` is the one change set the verdict owner must be handed; the caller
27
+ * never lets it re-enumerate the diff. `files: null` means the diff could not
28
+ * be enumerated.
29
+ *
30
+ * `level` / `classes` are what **review depth** reads (`resolveDepth`), and a
31
+ * sensitive class still resolves `deep`. They do not route the verdict owner:
32
+ * `verdictOwner` follows the ceremony profile alone (Story #5343) —
33
+ * `inline-self-eval` under `minimal` / `standard`, `fresh-critic` under
34
+ * `strict` — and since Story #5366 the resolver does not even accept the
35
+ * level, so the two signals cannot be confused for one decision.
29
36
  *
30
37
  * Exit codes: 0 on a derived decision (including the `null` fail-safe — an
31
38
  * unenumerable diff is a decision, not an error), 1 on a usage error.
@@ -114,13 +121,11 @@ export function deriveCeremony(
114
121
  const { level, classes } = deriveChangeLevelImpl({
115
122
  changedFiles: changeSet.files,
116
123
  });
117
- // `derivedLevel` is the level STRING — handing the `{ level, classes }`
118
- // object here was the transcription slip this CLI exists to retire.
119
- const ceremony = resolveCeremonyImpl({
120
- derivedLevel: level,
121
- clusterIndex: 0,
122
- ceremonyProfile,
123
- });
124
+ // The level is still derived and still printed — review depth reads it —
125
+ // but the ceremony profile alone resolves the verdict owner, and the
126
+ // resolver accepts nothing else, so nothing here can be talked into a
127
+ // different owner by the diff.
128
+ const ceremony = resolveCeremonyImpl({ ceremonyProfile });
124
129
  return {
125
130
  storyId,
126
131
  baseRef,