mandrel 2.10.0 → 2.12.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 (42) hide show
  1. package/.agents/docs/configuration.md +35 -33
  2. package/.agents/rules/orchestration-error-handling.md +9 -1
  3. package/.agents/schemas/agentrc.schema.json +13 -8
  4. package/.agents/scripts/acceptance-eval.js +9 -5
  5. package/.agents/scripts/lib/audit-suite/audit-rules-reader.js +48 -0
  6. package/.agents/scripts/lib/audit-suite/selector.js +1 -26
  7. package/.agents/scripts/lib/baselines/env-overrides.js +33 -0
  8. package/.agents/scripts/lib/baselines/git-base.js +0 -0
  9. package/.agents/scripts/lib/baselines/preview-gates.js +5 -0
  10. package/.agents/scripts/lib/config/gates/maintainability.schema.js +10 -1
  11. package/.agents/scripts/lib/config/quality.js +13 -0
  12. package/.agents/scripts/lib/config-settings-schema.js +12 -16
  13. package/.agents/scripts/lib/orchestration/ceremony-routing.js +45 -0
  14. package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +97 -4
  15. package/.agents/scripts/lib/orchestration/check-baselines/phases/parse-args.js +7 -0
  16. package/.agents/scripts/lib/orchestration/complexity-gate.js +561 -184
  17. package/.agents/scripts/lib/orchestration/plan-context.js +69 -10
  18. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +117 -60
  19. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +21 -15
  20. package/.agents/scripts/lib/orchestration/resolve-stories.js +28 -7
  21. package/.agents/scripts/lib/orchestration/review-depth.js +9 -4
  22. package/.agents/scripts/lib/orchestration/single-story-close/gate-log.js +186 -0
  23. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +21 -3
  24. package/.agents/scripts/lib/orchestration/spec-budget.js +78 -0
  25. package/.agents/scripts/lib/orchestration/story-body-gate.js +72 -0
  26. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +6 -0
  27. package/.agents/scripts/lib/orchestration/ticket-validator.js +18 -62
  28. package/.agents/scripts/plan-context.js +23 -5
  29. package/.agents/scripts/resolve-stories.js +2 -0
  30. package/.agents/workflows/deliver.md +28 -28
  31. package/.agents/workflows/helpers/acceptance-self-eval.md +16 -5
  32. package/.agents/workflows/helpers/deliver-digest.md +126 -0
  33. package/.agents/workflows/helpers/deliver-reference.md +30 -5
  34. package/.agents/workflows/helpers/deliver-story-reference.md +38 -12
  35. package/.agents/workflows/helpers/deliver-story.md +34 -37
  36. package/.agents/workflows/helpers/plan-reference.md +79 -44
  37. package/.agents/workflows/plan.md +11 -10
  38. package/docs/CHANGELOG.md +31 -0
  39. package/lib/cli/registry.js +31 -14
  40. package/lib/migrations/index.js +2 -0
  41. package/lib/migrations/steps/2.11.0-retire-max-seed-words.js +92 -0
  42. package/package.json +1 -1
@@ -10,7 +10,9 @@ description:
10
10
  > **Lean spine.** Happy path + gate list. Sequencing edge cases, dispatch
11
11
  > mechanics, lite-route inline execution, checklist threading, ceremony, and
12
12
  > the per-run epilogue live in the on-demand
13
- > [`helpers/deliver-reference.md`](helpers/deliver-reference.md).
13
+ > [`helpers/deliver-reference.md`](helpers/deliver-reference.md). What every
14
+ > delivery always needs is bundled into one read:
15
+ > [`helpers/deliver-digest.md`](helpers/deliver-digest.md) (Story #4736).
14
16
 
15
17
  ## Role
16
18
 
@@ -20,18 +22,22 @@ owns input resolution and sequencing only — every Story runs through
20
22
  `epic/<id>` integration branch, no `--no-ff` wave merges.
21
23
 
22
24
  The dependency graph is **discovered, not declared**: `resolve-stories.js`
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
+ reads it from live state (body edges ∪ native GitHub `blocked_by` edges, each
26
+ blocker resolved against its real issue state). You never hand it a graph and
25
27
  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
+ runs and over time**. `plan-run::<id>` is filter metadata, never a resolution
29
+ input.
30
+ Per-Story routes are **body-derived** too (#4722); `route::lite` is a hint
31
+ only. Ahead of that: a **single-Story run runs the engine inline** whatever the
32
+ shape (#4736) — sub-agent isolation only earns its cost against a concurrent
33
+ sibling.
28
34
 
29
35
  ## Inputs
30
36
 
31
37
  | Invocation | Behavior |
32
38
  | --- | --- |
33
- | `/deliver <storyId>` | Deliver one Story via `helpers/deliver-story.md`. |
34
- | `/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**. |
39
+ | `/deliver <storyId>` | Deliver one Story via `helpers/deliver-story.md`, executed **inline in this session** — no `story-worker` spawn. |
40
+ | `/deliver <storyId> <storyId> ...` | Resolve the set with `resolve-stories.js`, then sequence by the discovered graph via `stories-wave-tick.js`, dispatching role-scoped sub-agents. Default concurrency is **3**. |
35
41
 
36
42
  Any named ticket that is not `type::story`, or still carrying an `Epic: #N`
37
43
  footer, is a **hard error** naming the id and the fix (close or re-plan as a v2
@@ -41,18 +47,16 @@ Story). Resolution refuses the whole set rather than silently under-delivering.
41
47
 
42
48
  | Flag | Meaning |
43
49
  | --- | --- |
44
- | `--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. |
50
+ | `--concurrency <n>` | **Optional** per-run override of the fan-out cap. Omit it to honor `delivery.deliverRunner.concurrencyCap` (config default **3**, incl. any `.agentrc.local.json` override); pass **only** for a one-run cap. `1` = sequential. |
45
51
  | `--yes` | Suppress the multi-Story confirmation gate. |
46
52
  | `--steal` | Forwarded to `single-story-init.js` / lease steal. |
47
53
  | `--wait-merge` | Force close-and-land (the default; `delivery.routing.closeAndLand`). |
48
54
  | `--no-wait-merge` | Opt out; stop at `agent::closing` for a human land. |
49
55
 
50
56
  **Operator-merge implies no-wait.** `--no-auto-merge` and
51
- `delivery.ci.autoMerge: "strict"` leave the PR un-armed: the Story rests at
52
- `agent::closing` for the human merge and is **not** flipped to `agent::blocked`
53
- (`--wait-merge` does not override this). A genuine *arm failure* differs — it
54
- still waits and still blocks, because that is a fault to report, not an operator
55
- decision to respect.
57
+ `delivery.ci.autoMerge: "strict"` rest the Story at `agent::closing`, not
58
+ `agent::blocked` a genuine *arm failure* still waits and still blocks
59
+ ([`helpers/deliver-reference.md` § Operator-merge](helpers/deliver-reference.md)).
56
60
 
57
61
  ## Procedure
58
62
 
@@ -62,8 +66,8 @@ decision to respect.
62
66
  present the order in step 2. You do **not** thread them into step 3 — the
63
67
  tick re-resolves the graph itself every beat. Resolution hard-errors
64
68
  (exit 1) on a named id that is not a Story, carries an `Epic: #N` footer, or
65
- whose native edges cannot be read — a missing gate would co-dispatch a Story
66
- against an unlanded blocker.
69
+ whose native edges cannot be read — a missing gate would co-dispatch against
70
+ an unlanded blocker.
67
71
 
68
72
  2. **Confirm (N>1).** Present the order; wait unless `--yes`.
69
73
 
@@ -76,10 +80,8 @@ decision to respect.
76
80
  ```
77
81
 
78
82
  **Do not add `--concurrency` unless the operator explicitly asked for a
79
- per-run cap.** Omitting it lets the tick resolve the cap from
80
- `delivery.deliverRunner.concurrencyCap` including a `.agentrc.local.json`
81
- override. An explicit `--concurrency <n>` wins over config for that run, so a
82
- filled-in literal (e.g. `3`) silently defeats the operator's override.
83
+ per-run cap** an explicit value wins over config, so a filled-in literal
84
+ silently defeats a `.agentrc.local.json` override (see Flags).
83
85
 
84
86
  Each beat re-probes live state to derive done / in-flight itself; you never
85
87
  compute them (Story #4594). `--dispatched` is the one thing you must supply —
@@ -127,10 +129,10 @@ review depth reading the same level) and the mechanism table:
127
129
 
128
130
  ## Reading a Story's outcome
129
131
 
130
- Each Story's delivery ends in exactly one schema-validated terminal envelope
131
- ([`story-deliver-terminal.schema.json`](../schemas/story-deliver-terminal.schema.json),
132
- Story #4543) — `landed` | `pending` | `blocked` | `failed`, the SSOT for the
133
- shape; this workflow does not restate its fields.
132
+ Each Story's delivery ends in exactly one schema-validated terminal envelope
133
+ `landed` | `pending` | `blocked` | `failed`. Statuses, exits, and fields:
134
+ [`helpers/deliver-digest.md`](helpers/deliver-digest.md) § 5, over the shipped
135
+ [schema](../schemas/story-deliver-terminal.schema.json) (Story #4543).
134
136
 
135
137
  `pending` is **not** a failure: the bounded merge wait expired with the PR
136
138
  healthy (or a human owns the merge), nothing was mutated, and the
@@ -144,11 +146,9 @@ For a Story in an unclear state — including the merged-but-label-stale one a
144
146
 
145
147
  ## Constraints
146
148
 
147
- - **Land or block — never a silent local build.** Worktrees, `story-<id>`
148
- branches, close-validation, and PR-to-`main` are the only sanctioned delivery
149
- mechanism. Attended delivers default to close-and-land
150
- (`delivery.routing.closeAndLand: true`); use `--no-wait-merge` only when a
151
- human lands the PR.
149
+ - **Land or block — never a silent local build** (digest § 2). Attended
150
+ delivers default to close-and-land (`delivery.routing.closeAndLand: true`);
151
+ use `--no-wait-merge` only when a human lands the PR.
152
152
  - `/deliver` never plans — tickets come from [`/plan`](plan.md). The router
153
153
  performs no git/label mutations; `deliver-story` owns every script.
154
154
 
@@ -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,126 @@
1
+ ---
2
+ description: >-
3
+ The deliver path's one bundled framework read (Story #4736). Carries what
4
+ every Story delivery always needs — dispatch decision, engine invariants,
5
+ the change-set/ceremony incantation, the acceptance-eval gate, and the
6
+ terminal envelope contract — so the engine reads one file instead of
7
+ re-reading the helper/schema set each session.
8
+ ---
9
+
10
+ # Deliver digest (read once per session)
11
+
12
+ > **Bundle, not a procedure.** [`deliver-story.md`](deliver-story.md) is still
13
+ > the steps. This file is the material those steps referenced across five
14
+ > separate files and a JSON schema — bundled so one read covers the whole happy
15
+ > path. Situational material (lease preflight, recovery routers, merge-wait
16
+ > budgets, CI remediation) stays on demand in
17
+ > [`deliver-story-reference.md`](deliver-story-reference.md) and
18
+ > [`deliver-reference.md`](deliver-reference.md); read those **only** when an
19
+ > envelope or a failure routes you there.
20
+
21
+ ## 1. Dispatch — where the engine runs
22
+
23
+ Read `stories[].dispatchMode` from the `resolve-stories.js` envelope. Two
24
+ rules produce it, in order:
25
+
26
+ 1. **Run topology (#4736).** A run resolving **one** Story is `inline`
27
+ whatever its shape — sub-agent isolation is load-bearing only against a
28
+ *concurrent* sibling racing the same checkout, and a one-Story run has none.
29
+ 2. **Body shape (#4722).** In a multi-Story run, a lite-shaped body is
30
+ `inline`; a full-shaped body, an unparseable one, or a footprint touching a
31
+ sensitive-path class is `subagent`. The `route::lite` label is a
32
+ human-visible hint, never the control signal.
33
+
34
+ `inline` removes model-side fan-out only — no `story-worker` boot, no fresh
35
+ acceptance-critic spawn. **`subagent` and `inline` run the same engine**: same
36
+ gates, same PR to `main`, same terminal envelope, byte for byte.
37
+
38
+ ## 2. Engine invariants
39
+
40
+ | Trait | Contract |
41
+ | --- | --- |
42
+ | Ticket type | `type::story` only; an `Epic: #N` footer means **stop and re-plan** |
43
+ | Branch | `story-<id>`, seeded from `project.baseBranch` (`main`) |
44
+ | Merge target | `main` via PR (squash + required checks) — never a direct push |
45
+ | Integration branch | **None** — no `epic/<id>`, no `--no-ff` wave merge |
46
+ | Gates | Every close gate runs regardless of route; no route bypasses one |
47
+ | State | Only via `update-ticket-state.js --ticket <id> --state <state>` |
48
+ | Paths | Prefix every path-based tool with the absolute `workCwd` — `cd` does not scope them |
49
+
50
+ **Land or block.** Worktree → `story-<id>` → close-validation → PR to `main` is
51
+ the only sanctioned landing. A silent local build is not a delivery.
52
+
53
+ ## 3. Change set — computed once, handed to everyone
54
+
55
+ One enumeration per Story (#4593). A critic that re-runs its own `git diff`
56
+ can score a different set than the one that routed it:
57
+
58
+ ```bash
59
+ node --input-type=module -e '
60
+ import { computeChangeSet } from "<main-repo>/.agents/scripts/lib/orchestration/change-set.js";
61
+ const { files } = computeChangeSet({ baseRef: "main", headRef: "story-<storyId>" });
62
+ console.log(JSON.stringify(files));
63
+ '
64
+ ```
65
+
66
+ Derive the level with `deriveChangeLevel`
67
+ ([`review-depth.js`](../../scripts/lib/orchestration/review-depth.js)) over
68
+ that one list: a sensitive path registered in `audit-rules.json` → `high`, none
69
+ → `low`, an unenumerable diff (`files === null`) → `null`. Resolve
70
+ fresh-vs-inline critics with `resolveCeremonyForRisk`
71
+ ([`ceremony-routing.js`](../../scripts/lib/orchestration/ceremony-routing.js)):
72
+ `minimal` → always inline, `strict` → always fresh, `standard` → `high`/`null`
73
+ → fresh and `low` → inline unless the `freshCriticSampleRate` floor forces
74
+ fresh. An `inline` dispatch mode overrides all of it to inline critics. Close's
75
+ `review-depth.js` reads the same derived level, so the two cannot disagree.
76
+
77
+ ## 4. Acceptance self-eval (Step 1a, required)
78
+
79
+ **One verdict-owner per cluster** (#4723) — the fresh critic *or* the inline
80
+ self-eval, named by `verdictOwner`, never both and never a warm-up pass. It
81
+ scores each `acceptance[]` item against the change set above, with `verify[]`
82
+ output as evidence. Bounded by `delivery.acceptanceEval.maxRounds` (default 2).
83
+ Then score the authored verdict:
84
+
85
+ ```bash
86
+ node <main-repo>/.agents/scripts/acceptance-eval.js \
87
+ --story <storyId> --verdict <verdict-path>
88
+ ```
89
+
90
+ `proceed` → close. `redraft` → one more round inside the cap. `block` → **do
91
+ not close**: post a `friction` comment and flip `agent::blocked`.
92
+ Per-round mechanics: [`acceptance-self-eval.md`](acceptance-self-eval.md).
93
+
94
+ ## 5. Terminal envelope — the return contract
95
+
96
+ `single-story-close.js` emits exactly one envelope on stdout between
97
+ `--- STORY DELIVER TERMINAL ---` markers, schema-validated against
98
+ [`story-deliver-terminal.schema.json`](../../schemas/story-deliver-terminal.schema.json)
99
+ (#4543 — the SSOT; read the JSON only when you need a field this table omits).
100
+ Relay it verbatim; never hand-compose one, never substitute prose.
101
+
102
+ | `status` | Exit | Meaning | You do |
103
+ | --- | --- | --- | --- |
104
+ | `landed` | 0 | PR merged, `agent::done`, tail ran (`tail.*: false` degrades the report, not the land) | Relay it. Done. |
105
+ | `pending` | 3 | **Resumable, not a failure** — the bounded wait expired healthy, or a human owns the merge. Nothing was mutated. | Run `nextCommand`. |
106
+ | `blocked` | 1 | Hard block; `blocked.blockClass` names it | `checks-failed` → fix + resume; else relay |
107
+ | `failed` | 1 | A phase crashed; `phase` names which | Diagnose, fix, re-run close |
108
+
109
+ Required fields: `kind` (`story-deliver-terminal`), `storyId`, `status`,
110
+ `phase`, `elapsedSeconds`, `nextCommand`. `phase` is one of `init`,
111
+ `wrong-tree-guard`, `close-validation`, `base-sync`, `push`, `pull-request`,
112
+ `code-review`, `auto-merge`, `confirm-merge`, `post-land`, `done`. `gates`
113
+ reports every gate as `passed` / `failed` / `skipped` — a skipped gate is
114
+ reported, never omitted, so a missing gate is never read as a passing one.
115
+
116
+ **Gate output is captured, not streamed (#4736).** Close writes gate lines to
117
+ `temp/orchestration/close-gates-<storyId>.log` and reports a one-line digest on
118
+ success; a failed gate replays its tail inline. `AGENT_LOG_LEVEL=verbose`
119
+ restores live streaming.
120
+
121
+ ## 6. When to leave this file
122
+
123
+ - Unclear state / a re-run refusal → `deliver-recover.js --story <id>` (read-only).
124
+ - Lease, sweep, worktree-scope detail → [`deliver-story-reference.md`](deliver-story-reference.md).
125
+ - CI red after the PR opens → [`rules/ci-remediation.md`](../../rules/ci-remediation.md).
126
+ - Sequencing, epilogue, checklist threading → [`deliver-reference.md`](deliver-reference.md).
@@ -51,19 +51,33 @@ probe logs a warning and leans on init's lease refusal alone.
51
51
 
52
52
  ## Dispatch mechanics (role-scoped by default)
53
53
 
54
- **Lite-routed Stories execute inline (Story #4707).** Before spawning anything,
54
+ **A single-Story run executes inline (Story #4736).** Sub-agent isolation is
55
+ load-bearing only for **concurrent** dispatch — two workers sharing a checkout
56
+ would race on worktrees and branch refs — so a run resolving exactly one Story
57
+ has no sibling to isolate from and pays the spawn premium for nothing (a boot is
58
+ a cache write at full rate; an inline continuation is a cache read at ~10%).
59
+ `resolve-stories.js` already reports it: a one-id run comes back with
60
+ `dispatchMode: "inline"` whatever the Story's shape. Role-scoped spawning is
61
+ retained in full for multi-Story waves, and the rule changes **where** the
62
+ engine runs, never what runs — gates, PR, and terminal envelope are identical.
63
+
64
+ **Lite-shaped Stories execute inline (Story #4722).** Before spawning anything,
55
65
  read the Story's `dispatchMode` from the resolver envelope
56
66
  (`stories[].dispatchMode`, derived by `resolveStoryDispatchMode` in
57
- `lib/orchestration/complexity-gate.js` from the persisted `route::lite`
58
- marker): a Story with `dispatchMode: "inline"` executes
67
+ `lib/orchestration/complexity-gate.js` **from the fetched Story body's own
68
+ shape** `changes[]` count, acceptance count, creates-vs-refactors mix, and
69
+ sensitive-path classes; the `route::lite` label is a human-visible hint only,
70
+ never the control signal, so a lost or never-written label cannot misroute
71
+ delivery): a Story with `dispatchMode: "inline"` executes
59
72
  [`deliver-story.md`](deliver-story.md) **inline in this session** — no
60
73
  `story-worker` sub-agent boot and no fresh acceptance-critic sub-agents
61
74
  (sub-agent boots are the dominant deliver-phase token cost at trivial scope) —
62
75
  threading the same `docsDigestPath` / `checklistPath` / change-set discipline
63
76
  as a spawned worker. Inline removes model-side fan-out only: every
64
77
  `single-story-close.js` gate, the PR to `main`, and the terminal envelope are
65
- identical. A Story without the marker (or with unreadable labels) takes the
66
- standard sub-agent path.
78
+ identical. Everything else a full-shaped body, a missing/unparseable body,
79
+ or a footprint intersecting a sensitive-path class (sensitivity wins and
80
+ keeps the fresh acceptance critic) — takes the standard sub-agent path.
67
81
 
68
82
  **Dispatch each `ready` Story (role-scoped by default).** When
69
83
  `delivery.routing.roleScopedAgents` is enabled (the **default**) and the host
@@ -110,6 +124,17 @@ execute it directly, in this turn, threading the same `docsDigestPath` /
110
124
  content, execute directly without a re-read turn. The engine, gates, and
111
125
  terminal envelope are identical either way — only the isolation differs.
112
126
 
127
+ ## Operator-merge implies no-wait
128
+
129
+ `--no-auto-merge` and `delivery.ci.autoMerge: "strict"` leave the PR
130
+ deliberately un-armed: there is nothing for close to land, so the Story rests
131
+ at `agent::closing` for the human merge and is **not** flipped to
132
+ `agent::blocked` — `--wait-merge` does not override this, because the operator
133
+ owning the merge is a decision to respect, not a fault to report. A genuine
134
+ *arm failure* is the opposite case: nobody chose it, so close still waits and
135
+ still blocks. That asymmetry is what keeps the must-land contract intact
136
+ without misfiling deliberate human merges as blocks.
137
+
113
138
  ## Per-run epilogue (N>1)
114
139
 
115
140
  Once the sequence reports `epilogueDue: true` (every Story done), keyed on the
@@ -125,8 +125,7 @@ The v2 engine's trait table:
125
125
  | Spec / slices | Folded `## Spec` + optional `## Slicing` checkpoints in-session |
126
126
  | Ceremony | Per-Story, routed off the derived change level via `ceremony-routing.js` |
127
127
 
128
- **Ceremony-lite Stories still land through this engine unchanged (Story #4683).** A Story that `/plan` routed onto the ceremony-lite path (its
129
- `complexityRoute.route === "lite"`) collapses only the *advisory* plan/deliver
128
+ **Ceremony-lite Stories still land through this engine unchanged (Story #4683).** A lite-routed Story collapses only the *advisory* plan/deliver
130
129
  ceremony — the fresh-critic / Tech-Spec authoring a one-artifact scope does
131
130
  not earn. It does **not** get a cheaper landing: the close-validation gates
132
131
  (lint / test / format / coverage / CRAP / maintainability), the PR to `main`,
@@ -134,15 +133,22 @@ and the `rules/security-baseline.md` MUSTs all run exactly as for a
134
133
  full-ceremony Story. The lite route's `preserves` field is the machine-readable
135
134
  record of those non-negotiables; there is no lite-specific gate bypass.
136
135
 
137
- **The lite route persists to delivery as the `route::lite` label (Story #4707).** Persist stamps every Story of a lite-routed plan with that marker
138
- (and ledgers the route including any audited planner-downgrade reason on
139
- its `story-plan-state` checkpoint); a full-routed Story carries no marker.
140
- `/deliver` reads the label via `resolveStoryDispatchMode`
141
- (`lib/orchestration/complexity-gate.js`) and executes a lite Story
142
- **inline in the deliver session** no `story-worker` sub-agent boot, and
143
- the Step 1a acceptance self-eval runs its critics **inline** (no
136
+ **Deliver derives the route from the Story body's shape (Story #4722).**
137
+ Persist stamps a lite cohort's Stories with the `route::lite` label as a
138
+ *human-visible hint only* (and ledgers the authored verdict — recorded
139
+ reason plus per-Story shape evidence — on the `story-plan-state`
140
+ checkpoint); the label is never the control signal. `/deliver` computes the
141
+ route from the fetched Story body via `resolveStoryDispatchMode`
142
+ (`lib/orchestration/complexity-gate.js`) the same shape taxonomy
143
+ `deriveChangeLevel` applies to the landed diff at close: `changes[]` count,
144
+ acceptance count, creates-vs-refactors mix, sensitive-path classes. A
145
+ lite-shaped Story executes **inline in the deliver session** — even when the
146
+ label is absent or its write failed — with no `story-worker` sub-agent boot,
147
+ and the Step 1a acceptance self-eval runs its critics **inline** (no
144
148
  fresh-context acceptance-critic sub-agent dispatch; sub-agent boots are the
145
- dominant deliver-phase token cost at trivial scope). Inline execution
149
+ dominant deliver-phase token cost at trivial scope). A footprint
150
+ intersecting a sensitive-path class derives `full` — sensitivity wins, and
151
+ the Story keeps its fresh acceptance critic. Inline execution
146
152
  changes the isolation only: the engine, every script gate, and the
147
153
  terminal envelope are byte-identical either way.
148
154
 
@@ -175,6 +181,20 @@ directly.
175
181
 
176
182
  ### Step 1a — self-eval mechanics
177
183
 
184
+ **One verdict-owner per cluster (Story #4723).** The ceremony routing's
185
+ resolved decision names each cluster's single verdict owner
186
+ (`verdictOwner: 'fresh-critic' | 'inline-self-eval'` from
187
+ `resolveCeremonyForRisk`): the fresh maker-blind critic when sensitivity
188
+ routes the cluster `fresh`, the contract-identical inline self-eval when it
189
+ routes `inline`. Exactly one pass authors the verdict — never both, and
190
+ never a preliminary self-assessment pass before dispatching the fresh
191
+ critic (the redundant pre-pass buys no measurable quality and roughly
192
+ triples the acceptance-block cost). `acceptance-eval.js` is the
193
+ deterministic **scorer** of that one authored verdict — schema validation,
194
+ round cap, proceed / redraft / block — not an independent additional pass
195
+ over the criteria. The M4-B floor holds: one verdict per cluster, the
196
+ cluster count owned by `acceptance-clusters.js` alone.
197
+
178
198
  **Critic evidence-share (Story #4250).** When the critic runs a `verify[]`
179
199
  command that is byte-identical to a close gate (`lint` / `typecheck`), it
180
200
  records the pass into the Story evidence keyspace via `--standalone` so
@@ -227,8 +247,8 @@ Resolve fresh-vs-inline acceptance critics per AC-cluster with
227
247
  floor forces `fresh`). Review depth reads the same derived level via
228
248
  `review-depth.js` inside close, so the two decisions cannot disagree.
229
249
 
230
- **Lite-route override (Story #4707).** When the Story carries the
231
- `route::lite` marker (`resolveStoryDispatchMode` → `inline`), run every
250
+ **Lite-route override (Story #4722).** When the Story's body derives the
251
+ lite shape (`resolveStoryDispatchMode` → `inline`), run every
232
252
  acceptance critic **inline** — do not spawn fresh-context critic sub-agents
233
253
  regardless of what the profile would otherwise resolve. The self-eval rigor
234
254
  (scoring each `acceptance[]` item against the one computed change set, with
@@ -321,6 +341,12 @@ The `single-story-close.js` script, in order:
321
341
 
322
342
  1. Runs the close-validation gates against `baseBranch` as the baseline.
323
343
  On any gate failure it throws — the operator fixes and re-runs close.
344
+ **Gate output is captured, not streamed (Story #4736).** Every gate line
345
+ goes to `temp/orchestration/close-gates-<storyId>.log`; a clean run reports
346
+ one digest line naming that artifact, and a **failed** gate replays its
347
+ captured tail inline so the evidence is in front of you without opening a
348
+ file. Read the artifact when you need the full text — or re-run under
349
+ `AGENT_LOG_LEVEL=verbose` for live streaming.
324
350
  1a. **Syncs the Story branch from `origin/<baseBranch>`** before push
325
351
  (Story #2580). Runs `git fetch origin <baseBranch>` followed by
326
352
  `git merge --no-edit origin/<baseBranch>` inside the worktree. This
@@ -11,11 +11,19 @@ description:
11
11
  > reference detail lives in
12
12
  > [`deliver-story-reference.md`](deliver-story-reference.md) ("reference"
13
13
  > below); consult on demand. Invoked by [`/deliver`](../deliver.md).
14
+ >
15
+ > **Read [`deliver-digest.md`](deliver-digest.md) once, first.** It is the
16
+ > one bundled read of what every delivery needs — dispatch decision, engine
17
+ > invariants, the change-set/ceremony incantation, the acceptance-eval gate,
18
+ > and the terminal-envelope contract — replacing the per-session re-reads of
19
+ > the helper set and `story-deliver-terminal.schema.json` (Story #4736). The
20
+ > steps below cite it as "digest § N" rather than restating it.
14
21
 
15
22
  ## Overview
16
23
 
17
- The **one** delivery engine in v2 — every Story (`route::lite` runs
18
- inline with inline critics; engine, gates, envelope byte-identical):
24
+ The **one** delivery engine in v2 — every Story (a lite-**shaped** Story
25
+ runs inline with inline critics, #4722; engine, gates, envelope
26
+ byte-identical):
19
27
 
20
28
  ```text
21
29
  single-story-init.js → implement + commits → derived-level ceremony
@@ -73,15 +81,10 @@ One branch, one PR to `main`, commits against the inline `acceptance[]` /
73
81
 
74
82
  ### Step 1a — Bounded acceptance self-eval loop (**required**)
75
83
 
76
- Follow the single-homed include
77
- [`acceptance-self-eval.md`](acceptance-self-eval.md) (fresh-context critic,
78
- `verify[]`-as-evidence, proceed / redraft / block). Gate invocation (omit
79
- `--epic`):
80
-
81
- ```bash
82
- node <main-repo>/.agents/scripts/acceptance-eval.js \
83
- --story <storyId> --verdict <verdict-path>
84
- ```
84
+ Run the loop and score it with `acceptance-eval.js` — **digest § 4** carries
85
+ the invocation and the proceed / redraft / block contract; per-round critic
86
+ mechanics live in the single-homed include
87
+ [`acceptance-self-eval.md`](acceptance-self-eval.md).
85
88
 
86
89
  **`proceed`** → Step 2 then Step 3. **`block`** → **do not close**: post a
87
90
  `friction` comment and flip `agent::blocked` — commands and the
@@ -91,16 +94,12 @@ node <main-repo>/.agents/scripts/acceptance-eval.js \
91
94
  ## Step 2 — Ceremony (profile + derived level)
92
95
 
93
96
  Ceremony is `delivery.routing.ceremonyProfile` × the **derived change
94
- level** — never a planner-authored verdict (Story #4542).
95
- **Compute the change set once** (Story #4593) with the shared enumerator
96
- [`computeChangeSet`](../../scripts/lib/orchestration/change-set.js) the
97
- same module close uses and hand that one list to every critic. Derive the
98
- level with `deriveChangeLevel`, resolve fresh-vs-inline critics with
99
- `resolveCeremonyForRisk` (`ceremony-routing.js`); a lite Story runs inline
100
- regardless (exact incantation and routing rules: reference § Step 2).
101
- Hard gates (lint / test / format / coverage / CRAP / maintainability)
102
- always run in Step 3 — the derived level never disables them; do **not**
103
- pre-run the full close chain here.
97
+ level** — never a planner-authored verdict (Story #4542). **Digest § 3** is
98
+ the incantation: compute the change set once (Story #4593), derive the level,
99
+ resolve fresh-vs-inline critics with `ceremony-routing.js`; a lite Story runs
100
+ inline regardless (routing edge cases: reference § Step 2). Hard gates always
101
+ run in Step 3 — the derived level never disables them; do **not** pre-run the
102
+ close chain here.
104
103
 
105
104
  ## Step 3 — Close and land (`single-story-close.js`)
106
105
 
@@ -109,15 +108,12 @@ node <main-repo>/.agents/scripts/single-story-close.js --story <storyId> --cwd <
109
108
  ```
110
109
 
111
110
  **The whole delivery tail** — gates, PR, merge wait, `agent::done` flip,
112
- post-land tail in one process. Run it and **branch on the terminal
113
- envelope's `status`** (Story #4543):
114
-
115
- | `status` | Exit | Meaning | You do |
116
- | --- | --- | --- | --- |
117
- | `landed` | 0 | PR merged, `agent::done`, tail ran (`tail.*: false` degrades the report, not the land). | Relay the envelope (Step 7). |
118
- | `pending` | 3 | **Resumable, not a failure** — wait expired healthy, or a human owns the merge. | Run `nextCommand` until resolved. |
119
- | `blocked` | 1 | Hard block; `blocked.blockClass` names it. | `checks-failed` → Step 4; else relay. |
120
- | `failed` | 1 | A phase crashed; `phase` names which. | Diagnose, fix, re-run close. |
111
+ post-land tail in one process. Run it and **branch on the terminal envelope's
112
+ `status`** per the table in **digest § 5** (`landed` → Step 7; `pending` → run
113
+ `nextCommand`; `blocked`/`checks-failed` → Step 4; `failed` → diagnose and
114
+ re-run). Gate output is captured to
115
+ `temp/orchestration/close-gates-<storyId>.log` a clean run prints a digest
116
+ line, a red gate replays its tail inline (Story #4736).
121
117
 
122
118
  Internals (gate order, base-sync, auto-merge arming), the merge-wait
123
119
  budgets, the slow-CI **async** confirm mode (Story #4698 — launch the
@@ -142,13 +138,13 @@ recovery path **only** when the envelope routes you there:
142
138
 
143
139
  ## Step 7 — Return contract (**required as a sub-agent**) {#return-contract}
144
140
 
145
- The contract is the shipped schema
146
- [`story-deliver-terminal.schema.json`](../../schemas/story-deliver-terminal.schema.json)
147
- the SSOT for every field (Story #4543). End your turn by relaying the
148
- validated envelope close emits between its `--- STORY DELIVER TERMINAL ---`
149
- markers — never free-form prose, never a hand-composed object. `pending` is
150
- the only sanctioned no-merge ending, returned only when your own budget is
151
- exhausted (Story #1553). Reference § Step 7.
141
+ End your turn by relaying the validated envelope close emits between its
142
+ `--- STORY DELIVER TERMINAL ---` markers — never free-form prose, never a
143
+ hand-composed object. Statuses, exits, and required fields: **digest § 5**
144
+ (whose SSOT is the shipped
145
+ [schema](../../schemas/story-deliver-terminal.schema.json), Story #4543).
146
+ `pending` is the only sanctioned no-merge ending, returned only when your own
147
+ budget is exhausted (Story #1553). Reference § Step 7.
152
148
 
153
149
  ## Recovering a stranded Story {#recover}
154
150
 
@@ -174,6 +170,7 @@ reuses an open PR).
174
170
 
175
171
  ## See also
176
172
 
173
+ - [`deliver-digest.md`](deliver-digest.md) — the one bundled framework read.
177
174
  - [`/deliver`](../deliver.md) — unified entry point.
178
175
  - [`deliver-story-reference.md`](deliver-story-reference.md) — all on-demand
179
176
  detail.