mandrel 2.9.0 → 2.11.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 (75) hide show
  1. package/.agents/agents/.markdownlint.json +4 -0
  2. package/.agents/agents/acceptance-critic.md +30 -5
  3. package/.agents/agents/auditor.md +36 -19
  4. package/.agents/agents/plan-critic.md +31 -5
  5. package/.agents/agents/story-worker.md +91 -100
  6. package/.agents/docs/configuration.md +39 -25
  7. package/.agents/docs/execution-reference.md +13 -0
  8. package/.agents/docs/workflows.md +1 -1
  9. package/.agents/instructions.md +131 -265
  10. package/.agents/rules/git-conventions.md +47 -83
  11. package/.agents/rules/orchestration-error-handling.md +28 -0
  12. package/.agents/schemas/agentrc.schema.json +36 -9
  13. package/.agents/schemas/validation-evidence.schema.json +3 -1
  14. package/.agents/scripts/acceptance-eval.js +10 -6
  15. package/.agents/scripts/apply-quality-bootstrap.js +1 -1
  16. package/.agents/scripts/check-test-temp-hygiene.js +438 -0
  17. package/.agents/scripts/deliver-recover.js +23 -6
  18. package/.agents/scripts/lib/audit-suite/index.js +5 -0
  19. package/.agents/scripts/lib/audit-suite/lens-diff-floor.js +179 -0
  20. package/.agents/scripts/lib/audit-suite/selector.js +1 -1
  21. package/.agents/scripts/lib/baselines/env-overrides.js +33 -0
  22. package/.agents/scripts/lib/baselines/git-base.js +0 -0
  23. package/.agents/scripts/lib/baselines/preview-gates.js +5 -0
  24. package/.agents/scripts/lib/config/gates/maintainability.schema.js +10 -1
  25. package/.agents/scripts/lib/config/quality.js +13 -0
  26. package/.agents/scripts/lib/config/temp-paths.js +121 -1
  27. package/.agents/scripts/lib/config-settings-schema-delivery.js +30 -0
  28. package/.agents/scripts/lib/config-settings-schema.js +12 -16
  29. package/.agents/scripts/lib/observability/metrics-ledger.js +217 -0
  30. package/.agents/scripts/lib/observability/runtime-friction.js +7 -0
  31. package/.agents/scripts/lib/orchestration/ceremony-routing.js +45 -0
  32. package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +97 -4
  33. package/.agents/scripts/lib/orchestration/check-baselines/phases/parse-args.js +7 -0
  34. package/.agents/scripts/lib/orchestration/complexity-gate.js +533 -93
  35. package/.agents/scripts/lib/orchestration/deliver-recover.js +137 -10
  36. package/.agents/scripts/lib/orchestration/merge-block-class.js +36 -15
  37. package/.agents/scripts/lib/orchestration/merge-poll.js +213 -0
  38. package/.agents/scripts/lib/orchestration/plan-context.js +119 -3
  39. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +182 -9
  40. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +29 -2
  41. package/.agents/scripts/lib/orchestration/plan-metrics.js +31 -82
  42. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +153 -2
  43. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +221 -14
  44. package/.agents/scripts/lib/orchestration/resolve-stories.js +12 -1
  45. package/.agents/scripts/lib/orchestration/review-depth.js +9 -4
  46. package/.agents/scripts/lib/orchestration/review-providers/native.js +34 -16
  47. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +8 -3
  48. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +230 -79
  49. package/.agents/scripts/lib/orchestration/spec-budget.js +78 -0
  50. package/.agents/scripts/lib/orchestration/story-body-gate.js +72 -0
  51. package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +89 -1
  52. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +73 -0
  53. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +6 -0
  54. package/.agents/scripts/lib/orchestration/ticket-validator.js +18 -62
  55. package/.agents/scripts/lib/templates/decomposer-prompts.js +13 -6
  56. package/.agents/scripts/lib/test-env.js +65 -0
  57. package/.agents/scripts/plan-context.js +84 -9
  58. package/.agents/scripts/plan-critics.js +115 -3
  59. package/.agents/scripts/plan-persist.js +11 -1
  60. package/.agents/scripts/plan-run-epilogue.js +1 -1
  61. package/.agents/scripts/resolve-stories.js +2 -0
  62. package/.agents/scripts/single-story-confirm-merge.js +65 -5
  63. package/.agents/scripts/stories-wave-tick.js +1 -1
  64. package/.agents/workflows/deliver.md +88 -230
  65. package/.agents/workflows/helpers/acceptance-self-eval.md +16 -5
  66. package/.agents/workflows/helpers/deliver-reference.md +171 -0
  67. package/.agents/workflows/helpers/deliver-story-reference.md +223 -0
  68. package/.agents/workflows/helpers/deliver-story.md +115 -432
  69. package/.agents/workflows/helpers/plan-reference.md +246 -0
  70. package/.agents/workflows/plan.md +108 -304
  71. package/docs/CHANGELOG.md +46 -0
  72. package/lib/cli/registry.js +31 -14
  73. package/lib/migrations/index.js +2 -0
  74. package/lib/migrations/steps/2.11.0-retire-max-seed-words.js +92 -0
  75. package/package.json +1 -1
@@ -7,20 +7,26 @@ description:
7
7
 
8
8
  # /deliver <storyId...>
9
9
 
10
+ > **Lean spine.** Happy path + gate list. Sequencing edge cases, dispatch
11
+ > mechanics, lite-route inline execution, checklist threading, ceremony, and
12
+ > the per-run epilogue live in the on-demand
13
+ > [`helpers/deliver-reference.md`](helpers/deliver-reference.md).
14
+
10
15
  ## Role
11
16
 
12
- Single delivery path with a single input shape: **a list of Story ids**.
13
- `/deliver` owns input resolution and sequencing only — every Story runs
14
- through [`helpers/deliver-story.md`](helpers/deliver-story.md). There is no
15
- Epic wave loop, no `epic/<id>` integration branch, and no `--no-ff` wave
16
- merges.
17
+ Single delivery path, single input shape: **a list of Story ids**. `/deliver`
18
+ owns input resolution and sequencing only — every Story runs through
19
+ [`helpers/deliver-story.md`](helpers/deliver-story.md). No Epic wave loop, no
20
+ `epic/<id>` integration branch, no `--no-ff` wave merges.
17
21
 
18
22
  The dependency graph is **discovered, not declared**: `resolve-stories.js`
19
- reads it from live state (body edges ∪ native GitHub `blocked_by` edges,
20
- with every blocker resolved against its real issue state). You never hand it
21
- a graph, and there is no batch label — which is what lets you deliver
22
- Stories **across plan runs and over time**: a Story whose blocker landed
23
- weeks ago in a different run is simply ready.
23
+ reads it from live state (body edges ∪ native GitHub `blocked_by` edges, every
24
+ blocker resolved against its real issue state). You never hand it a graph, and
25
+ there is no batch label — which is what lets you deliver Stories **across plan
26
+ runs and over time**. The `plan-run::<id>` grouping label is filter metadata
27
+ only never a resolution input (there is no `--run` or `--dep` axis).
28
+ Per-Story routes are **body-derived** too (#4722); `route::lite` is a hint
29
+ only.
24
30
 
25
31
  ## Inputs
26
32
 
@@ -29,55 +35,39 @@ weeks ago in a different run is simply ready.
29
35
  | `/deliver <storyId>` | Deliver one Story via `helpers/deliver-story.md`. |
30
36
  | `/deliver <storyId> <storyId> ...` | Resolve the set with `resolve-stories.js`, then sequence by the discovered graph via `stories-wave-tick.js`. Default concurrency is **3**. |
31
37
 
32
- Any named ticket that is not `type::story`, or that still carries an
33
- `Epic: #N` footer, is a hard error naming the id and the fix (close or
34
- re-plan as a v2 Story). Resolution refuses the whole set rather than
35
- silently dropping the offending id and under-delivering.
36
-
37
- > **No batch identity (Story #4540).** There is no `--run`, `plan-run::<id>`,
38
- > or `--dep` axis: ordering lives in the dependency edges, so delivering the
39
- > ids resolves the graph itself.
38
+ Any named ticket that is not `type::story`, or still carrying an `Epic: #N`
39
+ footer, is a **hard error** naming the id and the fix (close or re-plan as a v2
40
+ Story). Resolution refuses the whole set rather than silently under-delivering.
40
41
 
41
42
  ## Flags
42
43
 
43
44
  | Flag | Meaning |
44
45
  | --- | --- |
45
- | `--concurrency <n>` | **Optional** per-run override of the ready-set fan-out cap. Omit it to honor `delivery.deliverRunner.concurrencyCap` (config default **3**, including any `.agentrc.local.json` override); pass it **only** when the operator explicitly wants a one-run cap. Set `1` for sequential. |
46
+ | `--concurrency <n>` | **Optional** per-run override of the fan-out cap. Omit it to honor `delivery.deliverRunner.concurrencyCap` (config default **3**, including any `.agentrc.local.json` override); pass it **only** for a one-run cap. `1` = sequential. |
46
47
  | `--yes` | Suppress the multi-Story confirmation gate. |
47
48
  | `--steal` | Forwarded to `single-story-init.js` / lease steal. |
48
- | `--wait-merge` | Force close-and-land (the default; `delivery.routing.closeAndLand`, default `true`). |
49
- | `--no-wait-merge` | Opt out of close-and-land; stop at `agent::closing` for a human land. |
49
+ | `--wait-merge` | Force close-and-land (the default; `delivery.routing.closeAndLand`). |
50
+ | `--no-wait-merge` | Opt out; stop at `agent::closing` for a human land. |
50
51
 
51
52
  **Operator-merge implies no-wait.** `--no-auto-merge` and
52
- `delivery.ci.autoMerge: "strict"` deliberately leave the PR un-armed, so
53
- there is nothing for close to land: the Story rests at `agent::closing` for
54
- the human merge, and is **not** flipped to `agent::blocked`. An explicit
55
- `--wait-merge` does not override this close cannot land a PR that was
56
- never armed. A genuine *arm failure* is different: it still waits and still
57
- blocks, because that is a fault to report rather than an operator decision
58
- to respect.
53
+ `delivery.ci.autoMerge: "strict"` leave the PR un-armed: the Story rests at
54
+ `agent::closing` for the human merge and is **not** flipped to `agent::blocked`
55
+ (`--wait-merge` does not override this). A genuine *arm failure* differs — it
56
+ still waits and still blocks, because that is a fault to report, not an operator
57
+ decision to respect.
59
58
 
60
59
  ## Procedure
61
60
 
62
61
  1. **Resolve the set.** One command, for one Story or many:
62
+ `node .agents/scripts/resolve-stories.js --ids <id,id,...>`. It validates
63
+ the set and shows what will run: read `stories[]`, `dag[]`, and `done[]` to
64
+ present the order in step 2. You do **not** thread them into step 3 — the
65
+ tick re-resolves the graph itself every beat. Resolution hard-errors
66
+ (exit 1) on a named id that is not a Story, carries an `Epic: #N` footer, or
67
+ whose native edges cannot be read — a missing gate would co-dispatch a Story
68
+ against an unlanded blocker.
63
69
 
64
- ```bash
65
- node .agents/scripts/resolve-stories.js --ids <id,id,...>
66
- ```
67
-
68
- This validates the set and shows the operator what will run: read
69
- `stories[]`, `dag[]`, and `done[]` to present the order in step 2. You do
70
- **not** thread them into step 3 — the tick re-resolves the graph itself
71
- from the same machinery, every beat. Do **not** rebuild the graph by hand;
72
- it is discovered from live state, including edges a body does not spell
73
- out and blockers outside the delivered set.
74
-
75
- Resolution hard-errors (exit 1) on a named id that is not a Story, still
76
- carries an `Epic: #N` footer, or whose native dependency edges cannot be
77
- read. A failed edge read is fatal by design: a missing gate would
78
- co-dispatch a Story against an unlanded blocker.
79
-
80
- 2. **Confirm (N>1).** Present the order and wait unless `--yes`.
70
+ 2. **Confirm (N>1).** Present the order; wait unless `--yes`.
81
71
 
82
72
  3. **Sequence.** Loop until the tick reports `epilogueDue: true`:
83
73
 
@@ -88,218 +78,86 @@ to respect.
88
78
  ```
89
79
 
90
80
  **Do not add `--concurrency` unless the operator explicitly asked for a
91
- per-run cap.** Omitting it is what lets the tick resolve the cap from
81
+ per-run cap.** Omitting it lets the tick resolve the cap from
92
82
  `delivery.deliverRunner.concurrencyCap` — including a `.agentrc.local.json`
93
- override. An explicit `--concurrency <n>` wins over config for that run
94
- (`resolveConcurrencyCap` returns the flag before it ever reads config), so
95
- filling in a literal — e.g. the documented default `3` — silently defeats
96
- the operator's configured override. Thread `--concurrency` through here
97
- only when it was passed to `/deliver`.
98
-
99
- Each beat re-probes live state: it re-resolves the graph, classifies done
100
- (`agent::done` or a closed issue — including foreign blockers that landed
101
- in another run), and derives in-flight from live `agent::executing` /
102
- `agent::closing` labels. You never compute `done` or `in-flight` — that
103
- accounting is read from reality every beat (Story #4594).
83
+ override. An explicit `--concurrency <n>` wins over config for that run, so a
84
+ filled-in literal (e.g. `3`) silently defeats the operator's override.
104
85
 
105
- **`--dispatched` is the one thing you must tell it (Story #4601).** List
106
- every Story id you have spawned this run. Live state cannot instantly report
107
- a Story you dispatched moments ago: `single-story-init.js` publishes
108
- `agent::executing` before the worktree install (Story #4620 moved it ahead
109
- of the multi-minute install, so the window is now short rather than
110
- minutes-long), but it is not zero — until the label lands the Story still
111
- reads `agent::ready` and, without `--dispatched`, the next beat would hand
112
- it back and a second sub-agent would join the first on the same branch and
113
- worktree, interleaving commits. `--dispatched` closes that residual
114
- same-run window.
115
-
116
- **Cross-run de-confliction is automatic (Story #4620).** A Story another
117
- operator is delivering is withheld without any bookkeeping from you: the
118
- probe reads the Story's assignee lease and, when it belongs to a different
119
- operator, withholds the Story and reports it in the envelope's
120
- `foreignHeld: [{ id, holder }]` (with `foreignHeldReason`). That is not a
121
- failure or a wedge — the holder's run owns the branch, and this run picks
122
- the Story up automatically once their lease clears. Init is the backstop:
123
- it refuses a Story already labelled `agent::executing`, or one whose lease a
124
- different operator holds, unless you pass `--steal`. Assignee-based
125
- withholding needs `github.operatorHandle` set (in `.agentrc.local.json`);
126
- without it the probe logs a warning and leans on init's lease refusal alone.
127
-
128
- The rule is **append-only: add each id as you dispatch it and never remove
129
- one.** The flag is additive, not authoritative — the probe unions it into
130
- the label-derived set and then filters it against live state, so an id that
131
- has since gone `agent::done` is dropped for you. Re-listing an id costs
132
- nothing and cannot double-count a slot; *omitting* one is the only way to
133
- get this wrong. This is why `--dispatched` is not the `--done` bookkeeping
134
- #4594 retired, and why `--in-flight` remains rejected under `--probe-live`.
86
+ Each beat re-probes live state to derive done / in-flight itself; you never
87
+ compute them (Story #4594). `--dispatched` is the one thing you must supply
88
+ the append-only list of every id you spawned this run — and cross-run
89
+ de-confliction via the assignee lease is automatic
90
+ ([`helpers/deliver-reference.md` § Sequencing edge cases](helpers/deliver-reference.md);
91
+ [§ Dispatch mechanics](helpers/deliver-reference.md) covers role-scoped
92
+ spawn, lite-route execution, and `checklistPath`).
135
93
 
136
94
  Branch on the exit code:
137
- - **0** — dispatch each `ready` id (the set is already capped and
138
- overlap-free). An empty `ready` with work in flight means "waiting";
139
- keep looping. `epilogueDue: true` means every Story is done — leave the
140
- loop and go to step 4.
141
- - **2** `cycleError`: the graph is self-referential. Fix the
142
- `depends_on` declarations; do not retry.
143
- - **3** `wedged`: nothing is dispatchable, nothing is in flight, and
144
- undone Stories are waiting on blockers that are not done. The envelope
145
- names the stuck ids and their unmet blockers. Either land the blocker
146
- first or include it in `--ids`. Do not retry unchanged — the state
147
- cannot improve on its own.
148
- - **4** — `blocked`: one or more Stories carry `agent::blocked`, named in
149
- `blocked[]` with `blockedReason`. This is the protocol's HITL pause
150
- ([`instructions.md` § 1.J](../instructions.md)) **stop the loop and
151
- surface it to the operator; do not poll.** No beat can clear it, because
152
- a human owes a decision. Read the Story's friction comment, and resume
153
- only once the operator has unblocked it:
154
-
155
- ```bash
156
- gh issue view <id> --comments
157
- node .agents/scripts/update-ticket-state.js --ticket <id> --state agent::ready
158
- ```
159
-
160
- A blocked Story outranks a wedge (its blockers are moot while a human
161
- owes a decision) but not a cycle (exit 2 — fix the graph first).
162
-
163
- **Dispatch each `ready` Story (role-scoped by default).** When
164
- `delivery.routing.roleScopedAgents` is enabled (the **default**) and the
165
- host exposes agent dispatch, spawn each ready Story as its own
166
- `subagent_type: story-worker` sub-agent — it boots on the role-scoped
167
- [`story-worker`](../agents/story-worker.md) context (its own system prompt,
168
- no `CLAUDE.md` @-closure) carrying the load-bearing delivery MUSTs
169
- standalone. The sub-agent executes
170
- [`helpers/deliver-story.md`](helpers/deliver-story.md) end to end
171
- (init → implement → acceptance self-eval → close-and-land). Thread into its
172
- prompt:
173
- - `storyId` — the id to deliver.
174
- - `docsDigestPath` — the per-run docs digest (digest-first reading,
175
- [`instructions.md` § 3](../instructions.md)); null when
176
- `project.docsContextFiles` is unset.
177
- - `checklistPath` — the footprint-matched write-time audit checklist,
178
- produced at dispatch (below).
179
- - the **change-set discipline** — the worker computes the change set once
180
- with `computeChangeSet` and hands that one list to every acceptance critic
181
- (Story #4593); it never lets a critic re-derive the diff.
182
-
183
- **Produce `checklistPath` before the spawn (Story #4627).** Compute the
184
- payload from the Story's predicted footprint (its `changes[]` /
185
- `references[]` path entries) with `buildDispatchChecklist` and write it to
186
- the run temp dir, then thread the resulting path (empty when nothing
187
- matched):
188
-
189
- ```bash
190
- node --input-type=module -e '
191
- import { buildDispatchChecklist } from "<main-repo>/.agents/scripts/lib/audit-suite/index.js";
192
- import { parse } from "<main-repo>/.agents/scripts/lib/story-body/story-body.js";
193
- // storyBody is the fetched Story issue body.
194
- const { changes, references } = parse(process.env.STORY_BODY);
195
- const { checklistPath } = buildDispatchChecklist({
196
- storyId: <storyId>, changes, references, runTempDir: "temp/run-<id>",
197
- });
198
- console.log(checklistPath ?? "");
199
- '
200
- ```
201
-
202
- `buildDispatchChecklist` (`lib/audit-suite/dispatch-checklist.js`) is a pure
203
- function of the footprint and the on-disk checklists; an empty match prints
204
- nothing and the worker runs with no write-time checklist — the maker-blind
205
- close-scope pass still covers it.
206
-
207
- **Inline fallback (`roleScopedAgents: false` / no-nesting harness).** When
208
- the kill-switch is off, or the host cannot spawn a sub-agent at this nesting
209
- depth, do **not** stall: read
210
- [`helpers/deliver-story.md`](helpers/deliver-story.md) **in full** and
211
- execute it directly, in this turn, threading the same `docsDigestPath` /
212
- `checklistPath` / change-set discipline. Under `--yes` / injected helper
213
- content, execute directly without a re-read turn. The engine, gates, and
214
- terminal envelope are identical either way — only the isolation differs.
215
-
216
- 4. **Per-run epilogue (N>1).** Once step 3 reports `epilogueDue: true`
217
- (every Story done), keyed on the delivered id set:
218
-
219
- ```bash
220
- node .agents/scripts/plan-run-epilogue.js --stories 101,102
221
- ```
222
-
223
- This executes, in order:
224
- - `audit-roster` — selects cross-Story audit lenses over the combined
225
- landed tip and posts `plan-run-audit-roster` on the primary Story;
226
- the host MUST walk each listed lens against the combined diff
227
- - `follow-up-rollup` — friction follow-ups across every Story in the
228
- run (files issues when auto-file is on; posts `follow-ups`)
229
- - `sibling-coherence` — Spec/Acceptance coherence check across sibling
230
- bodies (`plan-run-sibling-coherence`)
231
-
232
- A single-Story run skips the epilogue — follow-ups are captured on
233
- merge confirm instead (`captureStoryFollowUps`).
95
+ - **0** — dispatch each `ready` id (already capped and overlap-free). Empty
96
+ `ready` with work in flight means "waiting"; keep looping.
97
+ `epilogueDue: true` means every Story is done — go to step 4.
98
+ - **2** `cycleError`: the graph is self-referential. Fix the `depends_on`
99
+ declarations; do not retry.
100
+ - **3** — `wedged`: nothing dispatchable, nothing in flight, undone Stories
101
+ waiting on blockers that are not done. The envelope names the stuck ids and
102
+ unmet blockers. Land the blocker or include it in `--ids`; do not retry
103
+ unchanged.
104
+ - **4** `blocked`: a Story carries `agent::blocked`, named in `blocked[]`
105
+ with `blockedReason` the protocol's HITL pause
106
+ ([`instructions.md` § 1.J](../instructions.md)). **Stop the loop and
107
+ surface it; do not poll.** Read the friction comment
108
+ (`gh issue view <id> --comments`) and resume only once the operator
109
+ unblocks it (`update-ticket-state.js --ticket <id> --state agent::ready`).
110
+ A blocked Story outranks a wedge but not a cycle (fix the graph first).
111
+
112
+ 4. **Per-run epilogue (N>1).** Once step 3 reports `epilogueDue: true`, run
113
+ `node .agents/scripts/plan-run-epilogue.js --stories 101,102` — audit
114
+ roster, follow-up roll-up, sibling coherence. A single-Story run skips it.
115
+ Detail:
116
+ [`helpers/deliver-reference.md` § Per-run epilogue](helpers/deliver-reference.md).
234
117
 
235
118
  ## Branch model (authoritative)
236
119
 
237
- Every Story:
238
-
239
120
  ```text
240
121
  story-<id> → PR → main (squash + required checks)
241
122
  ```
242
123
 
243
- There is no `epic/<id>` integration branch and no `--no-ff` wave merge.
244
- Dependent Stories land sequentially so each builds on the previous merge
245
- to `main`.
246
-
247
- ## Ceremony (profiles + two scopes)
248
-
249
- Ceremony depth is selected by `delivery.routing.ceremonyProfile`
250
- (`minimal` | `standard` | `strict`, default `standard`) and the **change
251
- level derived from the Story's own diff** — the changed files' intersection
252
- with the sensitive-path classes in `audit-rules.json`
253
- (`review-depth.js#deriveChangeLevel`), not a planner-authored verdict
254
- (Story #4542):
255
-
256
- | Profile | Acceptance critic | When to use |
257
- | --- | --- | --- |
258
- | `minimal` | Always inline | Tiny trusted N=1 Stories |
259
- | `standard` | Derived-level routed (+ sampling floor) | Default |
260
- | `strict` | Always fresh-context | High-assurance / regulated surfaces |
261
-
262
- | Scope | What runs | Mechanism |
263
- | --- | --- | --- |
264
- | **Per-Story (always)** | Gates, branch discipline, close-and-land | `deliver-story` / `single-story-close` |
265
- | **Per-Story (profile + derived level)** | Acceptance critic mode; review depth | `ceremony-routing.js` + `review-depth.js` + `code-review.js` |
266
- | **Per-run (N>1)** | Audit roster · follow-up roll-up · sibling coherence | `plan-run-epilogue.js` once at run end |
267
- | **Per-Story land tail** | Follow-up capture · status resync · ref cleanup · base fast-forward | `single-story-close/phases/post-land.js` (in-process, per-step reported) |
124
+ No `epic/<id>` integration branch and no `--no-ff` wave merge. Dependent
125
+ Stories land sequentially so each builds on the previous merge to `main`.
126
+ Ceremony depth (profiles + derived level via `ceremony-routing.js`,
127
+ review depth reading the same level) and the mechanism table:
128
+ [`helpers/deliver-reference.md` § Ceremony](helpers/deliver-reference.md).
268
129
 
269
130
  ## Reading a Story's outcome
270
131
 
271
132
  Each Story's delivery ends in exactly one schema-validated terminal envelope
272
133
  ([`story-deliver-terminal.schema.json`](../schemas/story-deliver-terminal.schema.json),
273
- Story #4543) — `landed` | `pending` | `blocked` | `failed`. That schema is the
274
- SSOT for the shape; this workflow does not restate its fields.
134
+ Story #4543) — `landed` | `pending` | `blocked` | `failed`, the SSOT for the
135
+ shape; this workflow does not restate its fields.
275
136
 
276
137
  `pending` is **not** a failure: the bounded merge wait expired with the PR
277
- healthy and in flight (or a human owns the merge), nothing was mutated, and
278
- the envelope's `nextCommand` names what resumes it. Run that command rather
279
- than re-dispatching the Story.
138
+ healthy (or a human owns the merge), nothing was mutated, and the
139
+ `nextCommand` resumes it run that rather than re-dispatching. The slow-CI
140
+ `async` mode (Story #4698) returns `pending` by design — launch its
141
+ `nextCommand` as a background invocation (reference appendix).
280
142
 
281
143
  For a Story in an unclear state — including the merged-but-label-stale one a
282
- `/deliver` re-run refuses outright — probe it read-only:
283
-
284
- ```bash
285
- node .agents/scripts/deliver-recover.js --story <storyId>
286
- ```
144
+ `/deliver` re-run refuses outright — probe it read-only with
145
+ `node .agents/scripts/deliver-recover.js --story <storyId>`.
287
146
 
288
147
  ## Constraints
289
148
 
290
149
  - **Land or block — never a silent local build.** Worktrees, `story-<id>`
291
- branches, close-validation, and PR-to-`main` are the only sanctioned
292
- delivery mechanism. Attended delivers default to close-and-land
293
- (`delivery.routing.closeAndLand: true`); use `--no-wait-merge` only when
294
- a human will land the PR.
295
- - `/deliver` never plans — tickets come from [`/plan`](plan.md).
296
- - The router performs no git/label mutations; `deliver-story` owns every
297
- script invocation per Story.
150
+ branches, close-validation, and PR-to-`main` are the only sanctioned delivery
151
+ mechanism. Attended delivers default to close-and-land
152
+ (`delivery.routing.closeAndLand: true`); use `--no-wait-merge` only when a
153
+ human lands the PR.
154
+ - `/deliver` never plans — tickets come from [`/plan`](plan.md). The router
155
+ performs no git/label mutations; `deliver-story` owns every script.
298
156
 
299
157
  ## See also
300
158
 
301
159
  - [`/plan`](plan.md) — unified planning entry point.
302
160
  - [`helpers/deliver-story.md`](helpers/deliver-story.md) — the one Story
303
161
  delivery engine.
304
- - Placeholder design Story for a fully deterministic deliver-run
305
- orchestrator: [#4521](https://github.com/dsj1984/mandrel/issues/4521).
162
+ - [`helpers/deliver-reference.md`](helpers/deliver-reference.md) sequencing,
163
+ dispatch, ceremony, and epilogue detail.
@@ -30,10 +30,19 @@ mid-delivery, and evaluates the actual work product.
30
30
 
31
31
  ## Per round
32
32
 
33
- 1. **Eval pass (fresh context, independent of the author).** Run a **separate
34
- critic pass** a fresh-context sub-agent (`Agent` tool), *not* a
35
- continuation of your implementing turn so the evaluator does not grade its
36
- own homework.
33
+ 1. **Eval pass one verdict-owner per cluster (Story #4723).** Exactly
34
+ **one** pass authors each cluster's verdict: the **fresh-context critic**
35
+ when the ceremony routing below resolves `fresh` (a sub-agent via the
36
+ `Agent` tool, *not* a continuation of your implementing turn — the
37
+ evaluator does not grade its own homework), or the **inline self-eval**
38
+ when it resolves `inline`. The resolved decision names the owner
39
+ explicitly (`verdictOwner: 'fresh-critic' | 'inline-self-eval'` from
40
+ `resolveCeremonyForRisk`). **Never run both**, and never run a
41
+ preliminary self-assessment pass before dispatching the fresh critic —
42
+ the redundant pre-pass buys no measurable quality and roughly triples
43
+ the acceptance-block cost. Step 2's gate is the deterministic **scorer**
44
+ of the one authored verdict, not a second (or third) pass over the
45
+ criteria.
37
46
 
38
47
  > **Sub-agent type + derived-level ceremony (Epic #4478, M7-B).** When
39
48
  > `delivery.routing.roleScopedAgents` is enabled (the **default**), dispatch
@@ -129,7 +138,9 @@ mid-delivery, and evaluates the actual work product.
129
138
  one `{ index, criterion, verdict: met|partial|unmet, evidence,
130
139
  verifyEvidence[] }` record per acceptance item.
131
140
  2. **Decide.** Run the gate against the verdict (the caller's Step 1a names the
132
- exact invocation — omit `--epic`):
141
+ exact invocation — omit `--epic`). The gate **scores the single verdict
142
+ the round's owner authored** — schema validation, round cap, decision —
143
+ and never re-scores the criteria itself (Story #4723):
133
144
 
134
145
  ```bash
135
146
  node <main-repo>/.agents/scripts/acceptance-eval.js \
@@ -0,0 +1,171 @@
1
+ ---
2
+ description:
3
+ On-demand reference appendix for /deliver — the sequencing edge cases,
4
+ role-scoped dispatch mechanics, lite-route inline execution, checklist
5
+ threading, and the per-run epilogue. Read it when the matching lever is in
6
+ play; the lean spine in deliver.md links here.
7
+ ---
8
+
9
+ # /deliver — reference appendix (on-demand)
10
+
11
+ Reference-only detail split out of [`deliver.md`](../deliver.md) so the
12
+ always-resident spine stays lean (Story #4708). Nothing here is a new MUST —
13
+ it is the mechanics an operator consults when the matching lever is engaged.
14
+
15
+ ## Sequencing edge cases (`stories-wave-tick.js`)
16
+
17
+ Each beat re-probes live state: it re-resolves the graph, classifies **done**
18
+ (`agent::done` or a closed issue — including foreign blockers that landed in
19
+ another run), and derives **in-flight** from live `agent::executing` /
20
+ `agent::closing` labels. You never compute `done` or `in-flight` — that
21
+ accounting is read from reality every beat (Story #4594).
22
+
23
+ **`--dispatched` is the one thing you must tell it (Story #4601).** List every
24
+ Story id you have spawned this run. Live state cannot instantly report a Story
25
+ you dispatched moments ago: `single-story-init.js` publishes `agent::executing`
26
+ before the worktree install (Story #4620 moved it ahead of the multi-minute
27
+ install, so the window is now short rather than minutes-long), but it is not
28
+ zero — until the label lands the Story still reads `agent::ready` and, without
29
+ `--dispatched`, the next beat would hand it back and a second sub-agent would
30
+ join the first on the same branch and worktree, interleaving commits.
31
+ `--dispatched` closes that residual same-run window. The rule is
32
+ **append-only: add each id as you dispatch it and never remove one.** The flag
33
+ is additive, not authoritative — the probe unions it into the label-derived set
34
+ and then filters it against live state, so an id that has since gone
35
+ `agent::done` is dropped for you. Re-listing an id costs nothing and cannot
36
+ double-count a slot; *omitting* one is the only way to get this wrong. This is
37
+ why `--dispatched` is not the `--done` bookkeeping #4594 retired, and why
38
+ `--in-flight` remains rejected under `--probe-live`.
39
+
40
+ **Cross-run de-confliction is automatic (Story #4620).** A Story another
41
+ operator is delivering is withheld without any bookkeeping from you: the probe
42
+ reads the Story's assignee lease and, when it belongs to a different operator,
43
+ withholds the Story and reports it in the envelope's
44
+ `foreignHeld: [{ id, holder }]` (with `foreignHeldReason`). That is not a
45
+ failure or a wedge — the holder's run owns the branch, and this run picks the
46
+ Story up automatically once their lease clears. Init is the backstop: it
47
+ refuses a Story already labelled `agent::executing`, or one whose lease a
48
+ different operator holds, unless you pass `--steal`. Assignee-based withholding
49
+ needs `github.operatorHandle` set (in `.agentrc.local.json`); without it the
50
+ probe logs a warning and leans on init's lease refusal alone.
51
+
52
+ ## Dispatch mechanics (role-scoped by default)
53
+
54
+ **Lite-shaped Stories execute inline (Story #4722).** Before spawning anything,
55
+ read the Story's `dispatchMode` from the resolver envelope
56
+ (`stories[].dispatchMode`, derived by `resolveStoryDispatchMode` in
57
+ `lib/orchestration/complexity-gate.js` **from the fetched Story body's own
58
+ shape** — `changes[]` count, acceptance count, creates-vs-refactors mix, and
59
+ sensitive-path classes; the `route::lite` label is a human-visible hint only,
60
+ never the control signal, so a lost or never-written label cannot misroute
61
+ delivery): a Story with `dispatchMode: "inline"` executes
62
+ [`deliver-story.md`](deliver-story.md) **inline in this session** — no
63
+ `story-worker` sub-agent boot and no fresh acceptance-critic sub-agents
64
+ (sub-agent boots are the dominant deliver-phase token cost at trivial scope) —
65
+ threading the same `docsDigestPath` / `checklistPath` / change-set discipline
66
+ as a spawned worker. Inline removes model-side fan-out only: every
67
+ `single-story-close.js` gate, the PR to `main`, and the terminal envelope are
68
+ identical. Everything else — a full-shaped body, a missing/unparseable body,
69
+ or a footprint intersecting a sensitive-path class (sensitivity wins and
70
+ keeps the fresh acceptance critic) — takes the standard sub-agent path.
71
+
72
+ **Dispatch each `ready` Story (role-scoped by default).** When
73
+ `delivery.routing.roleScopedAgents` is enabled (the **default**) and the host
74
+ exposes agent dispatch, spawn each ready Story as its own
75
+ `subagent_type: story-worker` sub-agent — it boots on the role-scoped
76
+ [`story-worker`](../../agents/story-worker.md) context (its own system prompt, no
77
+ `CLAUDE.md` @-closure) carrying the load-bearing delivery MUSTs standalone. The
78
+ sub-agent executes [`deliver-story.md`](deliver-story.md) end to end
79
+ (init → implement → acceptance self-eval → close-and-land). Thread into its
80
+ prompt: `storyId`; `docsDigestPath` (the per-run docs digest, null when
81
+ `project.docsContextFiles` is unset); `checklistPath` (the footprint-matched
82
+ write-time audit checklist, produced at dispatch, below); and the
83
+ **change-set discipline** — the worker computes the change set once with
84
+ `computeChangeSet` and hands that one list to every acceptance critic (Story #4593); it never lets a critic re-derive the diff.
85
+
86
+ **Produce `checklistPath` before the spawn (Story #4627).** Compute the payload
87
+ from the Story's predicted footprint (its `changes[]` / `references[]` path
88
+ entries) with `buildDispatchChecklist` and write it to the run temp dir, then
89
+ thread the resulting path (empty when nothing matched):
90
+
91
+ ```bash
92
+ node --input-type=module -e '
93
+ import { buildDispatchChecklist } from "<main-repo>/.agents/scripts/lib/audit-suite/index.js";
94
+ import { parse } from "<main-repo>/.agents/scripts/lib/story-body/story-body.js";
95
+ // storyBody is the fetched Story issue body.
96
+ const { changes, references } = parse(process.env.STORY_BODY);
97
+ const { checklistPath } = buildDispatchChecklist({
98
+ storyId: <storyId>, changes, references, runTempDir: "temp/run-<id>",
99
+ });
100
+ console.log(checklistPath ?? "");
101
+ '
102
+ ```
103
+
104
+ `buildDispatchChecklist` (`lib/audit-suite/dispatch-checklist.js`) is a pure
105
+ function of the footprint and the on-disk checklists; an empty match prints
106
+ nothing and the worker runs with no write-time checklist — the maker-blind
107
+ close-scope pass still covers it.
108
+
109
+ **Inline fallback (`roleScopedAgents: false` / no-nesting harness).** When the
110
+ kill-switch is off, or the host cannot spawn a sub-agent at this nesting depth,
111
+ do **not** stall: read [`deliver-story.md`](deliver-story.md) **in full** and
112
+ execute it directly, in this turn, threading the same `docsDigestPath` /
113
+ `checklistPath` / change-set discipline. Under `--yes` / injected helper
114
+ content, execute directly without a re-read turn. The engine, gates, and
115
+ terminal envelope are identical either way — only the isolation differs.
116
+
117
+ ## Per-run epilogue (N>1)
118
+
119
+ Once the sequence reports `epilogueDue: true` (every Story done), keyed on the
120
+ delivered id set:
121
+
122
+ ```bash
123
+ node .agents/scripts/plan-run-epilogue.js --stories 101,102
124
+ ```
125
+
126
+ This executes, in order:
127
+
128
+ - `audit-roster` — selects cross-Story audit lenses over the combined landed
129
+ tip and posts `plan-run-audit-roster` on the primary Story; the host MUST
130
+ walk each listed lens against the combined diff.
131
+ - `follow-up-rollup` — friction follow-ups across every Story in the run
132
+ (files issues when auto-file is on; posts `follow-ups`).
133
+ - `sibling-coherence` — Spec/Acceptance coherence check across sibling bodies
134
+ (`plan-run-sibling-coherence`).
135
+
136
+ A single-Story run skips the epilogue — follow-ups are captured on merge
137
+ confirm instead (`captureStoryFollowUps`).
138
+
139
+ ## Ceremony (profiles + two scopes)
140
+
141
+ Ceremony depth is selected by `delivery.routing.ceremonyProfile`
142
+ (`minimal` | `standard` | `strict`, default `standard`) and the **change level
143
+ derived from the Story's own diff** — the changed files' intersection with the
144
+ sensitive-path classes in `audit-rules.json`
145
+ (`review-depth.js#deriveChangeLevel`), not a planner-authored verdict
146
+ (Story #4542):
147
+
148
+ | Profile | Acceptance critic | When to use |
149
+ | --- | --- | --- |
150
+ | `minimal` | Always inline | Tiny trusted N=1 Stories |
151
+ | `standard` | Derived-level routed (+ sampling floor) | Default |
152
+ | `strict` | Always fresh-context | High-assurance / regulated surfaces |
153
+
154
+ | Scope | What runs | Mechanism |
155
+ | --- | --- | --- |
156
+ | **Per-Story (always)** | Gates, branch discipline, close-and-land | `deliver-story` / `single-story-close` |
157
+ | **Per-Story (profile + derived level)** | Acceptance critic mode; review depth | `ceremony-routing.js` + `review-depth.js` + `code-review.js` |
158
+ | **Per-run (N>1)** | Audit roster · follow-up roll-up · sibling coherence | `plan-run-epilogue.js` once at run end |
159
+ | **Per-Story land tail** | Follow-up capture · status resync · ref cleanup · base fast-forward | `single-story-close/phases/post-land.js` (in-process, per-step reported) |
160
+
161
+ ## Async merge-confirm mode (`delivery.mergeWatch.mode: "async"`, Story #4698)
162
+
163
+ A slow-CI consumer can opt the close into `"async"` mode so the merge wait
164
+ probes once for ~60s (catching an instant merge or an instantly-red required
165
+ check) and then returns `pending` instead of burning ~5 minutes of the host
166
+ tool slot polling a merge that lands after the wait would have expired anyway.
167
+ When a worker returns that `pending` envelope, launch its `nextCommand` as a
168
+ **background** invocation (host background Bash — its completion re-invokes the
169
+ agent) and move on to the next Story; `single-story-confirm-merge.js` is
170
+ idempotent and owns the whole tail. Do not foreground-poll the merge. The
171
+ default `"sync"` behaviour is unchanged.