mandrel 2.9.0 → 2.10.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 (57) 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 +16 -4
  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 +24 -2
  13. package/.agents/schemas/validation-evidence.schema.json +3 -1
  14. package/.agents/scripts/acceptance-eval.js +1 -1
  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/config/temp-paths.js +121 -1
  22. package/.agents/scripts/lib/config-settings-schema-delivery.js +30 -0
  23. package/.agents/scripts/lib/config-settings-schema.js +1 -1
  24. package/.agents/scripts/lib/observability/metrics-ledger.js +217 -0
  25. package/.agents/scripts/lib/observability/runtime-friction.js +7 -0
  26. package/.agents/scripts/lib/orchestration/complexity-gate.js +113 -2
  27. package/.agents/scripts/lib/orchestration/deliver-recover.js +137 -10
  28. package/.agents/scripts/lib/orchestration/merge-block-class.js +36 -15
  29. package/.agents/scripts/lib/orchestration/merge-poll.js +213 -0
  30. package/.agents/scripts/lib/orchestration/plan-context.js +57 -0
  31. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +182 -9
  32. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +29 -2
  33. package/.agents/scripts/lib/orchestration/plan-metrics.js +31 -82
  34. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +102 -2
  35. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +215 -14
  36. package/.agents/scripts/lib/orchestration/resolve-stories.js +7 -0
  37. package/.agents/scripts/lib/orchestration/review-providers/native.js +34 -16
  38. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +8 -3
  39. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +230 -79
  40. package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +89 -1
  41. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +73 -0
  42. package/.agents/scripts/lib/templates/decomposer-prompts.js +13 -6
  43. package/.agents/scripts/lib/test-env.js +65 -0
  44. package/.agents/scripts/plan-context.js +66 -9
  45. package/.agents/scripts/plan-critics.js +115 -3
  46. package/.agents/scripts/plan-persist.js +11 -1
  47. package/.agents/scripts/plan-run-epilogue.js +1 -1
  48. package/.agents/scripts/single-story-confirm-merge.js +65 -5
  49. package/.agents/scripts/stories-wave-tick.js +1 -1
  50. package/.agents/workflows/deliver.md +86 -230
  51. package/.agents/workflows/helpers/deliver-reference.md +167 -0
  52. package/.agents/workflows/helpers/deliver-story-reference.md +203 -0
  53. package/.agents/workflows/helpers/deliver-story.md +114 -432
  54. package/.agents/workflows/helpers/plan-reference.md +211 -0
  55. package/.agents/workflows/plan.md +107 -304
  56. package/docs/CHANGELOG.md +27 -0
  57. package/package.json +1 -1
@@ -0,0 +1,167 @@
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-routed Stories execute inline (Story #4707).** 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 persisted `route::lite`
58
+ marker): a Story with `dispatchMode: "inline"` executes
59
+ [`deliver-story.md`](deliver-story.md) **inline in this session** — no
60
+ `story-worker` sub-agent boot and no fresh acceptance-critic sub-agents
61
+ (sub-agent boots are the dominant deliver-phase token cost at trivial scope) —
62
+ threading the same `docsDigestPath` / `checklistPath` / change-set discipline
63
+ as a spawned worker. Inline removes model-side fan-out only: every
64
+ `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.
67
+
68
+ **Dispatch each `ready` Story (role-scoped by default).** When
69
+ `delivery.routing.roleScopedAgents` is enabled (the **default**) and the host
70
+ exposes agent dispatch, spawn each ready Story as its own
71
+ `subagent_type: story-worker` sub-agent — it boots on the role-scoped
72
+ [`story-worker`](../../agents/story-worker.md) context (its own system prompt, no
73
+ `CLAUDE.md` @-closure) carrying the load-bearing delivery MUSTs standalone. The
74
+ sub-agent executes [`deliver-story.md`](deliver-story.md) end to end
75
+ (init → implement → acceptance self-eval → close-and-land). Thread into its
76
+ prompt: `storyId`; `docsDigestPath` (the per-run docs digest, null when
77
+ `project.docsContextFiles` is unset); `checklistPath` (the footprint-matched
78
+ write-time audit checklist, produced at dispatch, below); and the
79
+ **change-set discipline** — the worker computes the change set once with
80
+ `computeChangeSet` and hands that one list to every acceptance critic (Story #4593); it never lets a critic re-derive the diff.
81
+
82
+ **Produce `checklistPath` before the spawn (Story #4627).** Compute the payload
83
+ from the Story's predicted footprint (its `changes[]` / `references[]` path
84
+ entries) with `buildDispatchChecklist` and write it to the run temp dir, then
85
+ thread the resulting path (empty when nothing matched):
86
+
87
+ ```bash
88
+ node --input-type=module -e '
89
+ import { buildDispatchChecklist } from "<main-repo>/.agents/scripts/lib/audit-suite/index.js";
90
+ import { parse } from "<main-repo>/.agents/scripts/lib/story-body/story-body.js";
91
+ // storyBody is the fetched Story issue body.
92
+ const { changes, references } = parse(process.env.STORY_BODY);
93
+ const { checklistPath } = buildDispatchChecklist({
94
+ storyId: <storyId>, changes, references, runTempDir: "temp/run-<id>",
95
+ });
96
+ console.log(checklistPath ?? "");
97
+ '
98
+ ```
99
+
100
+ `buildDispatchChecklist` (`lib/audit-suite/dispatch-checklist.js`) is a pure
101
+ function of the footprint and the on-disk checklists; an empty match prints
102
+ nothing and the worker runs with no write-time checklist — the maker-blind
103
+ close-scope pass still covers it.
104
+
105
+ **Inline fallback (`roleScopedAgents: false` / no-nesting harness).** When the
106
+ kill-switch is off, or the host cannot spawn a sub-agent at this nesting depth,
107
+ do **not** stall: read [`deliver-story.md`](deliver-story.md) **in full** and
108
+ execute it directly, in this turn, threading the same `docsDigestPath` /
109
+ `checklistPath` / change-set discipline. Under `--yes` / injected helper
110
+ content, execute directly without a re-read turn. The engine, gates, and
111
+ terminal envelope are identical either way — only the isolation differs.
112
+
113
+ ## Per-run epilogue (N>1)
114
+
115
+ Once the sequence reports `epilogueDue: true` (every Story done), keyed on the
116
+ delivered id set:
117
+
118
+ ```bash
119
+ node .agents/scripts/plan-run-epilogue.js --stories 101,102
120
+ ```
121
+
122
+ This executes, in order:
123
+
124
+ - `audit-roster` — selects cross-Story audit lenses over the combined landed
125
+ tip and posts `plan-run-audit-roster` on the primary Story; the host MUST
126
+ walk each listed lens against the combined diff.
127
+ - `follow-up-rollup` — friction follow-ups across every Story in the run
128
+ (files issues when auto-file is on; posts `follow-ups`).
129
+ - `sibling-coherence` — Spec/Acceptance coherence check across sibling bodies
130
+ (`plan-run-sibling-coherence`).
131
+
132
+ A single-Story run skips the epilogue — follow-ups are captured on merge
133
+ confirm instead (`captureStoryFollowUps`).
134
+
135
+ ## Ceremony (profiles + two scopes)
136
+
137
+ Ceremony depth is selected by `delivery.routing.ceremonyProfile`
138
+ (`minimal` | `standard` | `strict`, default `standard`) and the **change level
139
+ derived from the Story's own diff** — the changed files' intersection with the
140
+ sensitive-path classes in `audit-rules.json`
141
+ (`review-depth.js#deriveChangeLevel`), not a planner-authored verdict
142
+ (Story #4542):
143
+
144
+ | Profile | Acceptance critic | When to use |
145
+ | --- | --- | --- |
146
+ | `minimal` | Always inline | Tiny trusted N=1 Stories |
147
+ | `standard` | Derived-level routed (+ sampling floor) | Default |
148
+ | `strict` | Always fresh-context | High-assurance / regulated surfaces |
149
+
150
+ | Scope | What runs | Mechanism |
151
+ | --- | --- | --- |
152
+ | **Per-Story (always)** | Gates, branch discipline, close-and-land | `deliver-story` / `single-story-close` |
153
+ | **Per-Story (profile + derived level)** | Acceptance critic mode; review depth | `ceremony-routing.js` + `review-depth.js` + `code-review.js` |
154
+ | **Per-run (N>1)** | Audit roster · follow-up roll-up · sibling coherence | `plan-run-epilogue.js` once at run end |
155
+ | **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) |
156
+
157
+ ## Async merge-confirm mode (`delivery.mergeWatch.mode: "async"`, Story #4698)
158
+
159
+ A slow-CI consumer can opt the close into `"async"` mode so the merge wait
160
+ probes once for ~60s (catching an instant merge or an instantly-red required
161
+ check) and then returns `pending` instead of burning ~5 minutes of the host
162
+ tool slot polling a merge that lands after the wait would have expired anyway.
163
+ When a worker returns that `pending` envelope, launch its `nextCommand` as a
164
+ **background** invocation (host background Bash — its completion re-invokes the
165
+ agent) and move on to the next Story; `single-story-confirm-merge.js` is
166
+ idempotent and owns the whole tail. Do not foreground-poll the merge. The
167
+ default `"sync"` behaviour is unchanged.
@@ -112,6 +112,209 @@ not a substitute for prefixing paths correctly.
112
112
 
113
113
  ---
114
114
 
115
+ ## Engine invariants and the lite route
116
+
117
+ The v2 engine's trait table:
118
+
119
+ | Trait | v2 `/deliver-story` |
120
+ | --- | --- |
121
+ | Ticket type | `type::story` only |
122
+ | Branch | `story-<id>` seeded from `project.baseBranch` (`main`) |
123
+ | Merge target | `main` via PR (squash + required checks) |
124
+ | Epic integration branch | **None** — no `epic/<id>`, no `--no-ff` wave merge |
125
+ | Spec / slices | Folded `## Spec` + optional `## Slicing` checkpoints in-session |
126
+ | Ceremony | Per-Story, routed off the derived change level via `ceremony-routing.js` |
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
130
+ ceremony — the fresh-critic / Tech-Spec authoring a one-artifact scope does
131
+ not earn. It does **not** get a cheaper landing: the close-validation gates
132
+ (lint / test / format / coverage / CRAP / maintainability), the PR to `main`,
133
+ and the `rules/security-baseline.md` MUSTs all run exactly as for a
134
+ full-ceremony Story. The lite route's `preserves` field is the machine-readable
135
+ record of those non-negotiables; there is no lite-specific gate bypass.
136
+
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
144
+ fresh-context acceptance-critic sub-agent dispatch; sub-agent boots are the
145
+ dominant deliver-phase token cost at trivial scope). Inline execution
146
+ changes the isolation only: the engine, every script gate, and the
147
+ terminal envelope are byte-identical either way.
148
+
149
+ ---
150
+
151
+ ## Step 1 — Implementation detail
152
+
153
+ **Docs context — digest-first.** Read a full doc only when the Story's own
154
+ context points you at one — do not ingest the whole
155
+ `project.docsContextFiles` set up front. If the caller provides a
156
+ `docsDigestPath`, prefer that compact outline and pull individual files on
157
+ demand. See [`.agents/instructions.md` § 3](../../instructions.md).
158
+
159
+ **Write-time audit checklists.** When the caller provides a `checklistPath`
160
+ (footprint-matched **local**-lens authoring checklists), read it before you
161
+ write and self-check as you author. When absent, lens-aware coverage still
162
+ runs maker-blind at Story-scope review inside the close subprocess. The
163
+ dispatch step produces `checklistPath` from the Story's predicted footprint
164
+ before it spawns the worker (Story #4627) — see [`/deliver`](../deliver.md).
165
+
166
+ **Pre-eval full-suite discipline (spine step 5).** Repo-invariant guards —
167
+ drift-guard and schema tests living outside the Story's scoped greps — are
168
+ the failure class that actually bounces deliveries: close-validation
169
+ discovers them only after the whole close pipeline has run, at several times
170
+ the cost of one pre-eval full-suite run.
171
+
172
+ **Conflict with `main` mid-implementation** → resolve as you would any branch
173
+ rebase. There is no `epic/<id>` intermediate, so the rebase base is `main`
174
+ directly.
175
+
176
+ ### Step 1a — self-eval mechanics
177
+
178
+ **Critic evidence-share (Story #4250).** When the critic runs a `verify[]`
179
+ command that is byte-identical to a close gate (`lint` / `typecheck`), it
180
+ records the pass into the Story evidence keyspace via `--standalone` so
181
+ close short-circuits the gate at unchanged HEAD. Run it in the **Story
182
+ worktree** (`workCwd` from Step 0):
183
+
184
+ ```bash
185
+ node <main-repo>/.agents/scripts/evidence-gate.js \
186
+ --standalone --scope-id <storyId> --gate lint \
187
+ --worktree <workCwd> -- npm run lint
188
+ ```
189
+
190
+ **On `decision: "block"`** — post a `friction` comment naming the unmet
191
+ criteria, then transition the Story to `agent::blocked`:
192
+
193
+ ```bash
194
+ node .agents/scripts/diagnose-friction.js --story <storyId> \
195
+ --cmd node .agents/scripts/acceptance-eval.js --story <storyId> --verdict <verdict-path>
196
+ node .agents/scripts/update-ticket-state.js --ticket <storyId> --state agent::blocked
197
+ ```
198
+
199
+ ---
200
+
201
+ ## Step 2 — Ceremony detail
202
+
203
+ **Compute the change set once** (Story #4593) with the shared enumerator —
204
+ the same module close uses — and reuse that one list downstream:
205
+
206
+ ```bash
207
+ node --input-type=module -e '
208
+ import { computeChangeSet } from "<main-repo>/.agents/scripts/lib/orchestration/change-set.js";
209
+ const { files } = computeChangeSet({ baseRef: "main", headRef: "story-<storyId>" });
210
+ console.log(JSON.stringify(files));
211
+ '
212
+ ```
213
+
214
+ Derive the level with
215
+ [`deriveChangeLevel`](../../scripts/lib/orchestration/review-depth.js) over
216
+ the one computed change-set list: a diff touching a sensitive path registered
217
+ in `.agents/schemas/audit-rules.json` derives `high`, one touching none
218
+ derives `low`, and an unenumerable diff (`files === null`) derives `null`.
219
+ Hand the **same** list to every acceptance critic you spawn (Step 1a) — a
220
+ critic that re-ran its own `git diff` could score against a different set
221
+ than the one that routed it.
222
+
223
+ Resolve fresh-vs-inline acceptance critics per AC-cluster with
224
+ [`resolveCeremonyForRisk`](../../scripts/lib/orchestration/ceremony-routing.js)
225
+ (`minimal` → always inline; `strict` → always fresh; `standard` →
226
+ `high`/`null` → `fresh`, `low` → `inline` unless the `freshCriticSampleRate`
227
+ floor forces `fresh`). Review depth reads the same derived level via
228
+ `review-depth.js` inside close, so the two decisions cannot disagree.
229
+
230
+ **Lite-route override (Story #4707).** When the Story carries the
231
+ `route::lite` marker (`resolveStoryDispatchMode` → `inline`), run every
232
+ acceptance critic **inline** — do not spawn fresh-context critic sub-agents
233
+ regardless of what the profile would otherwise resolve. The self-eval rigor
234
+ (scoring each `acceptance[]` item against the one computed change set, with
235
+ `verify[]` output as evidence) is unchanged; only the sub-agent boot is
236
+ removed. Hard gates are untouched.
237
+
238
+ ---
239
+
240
+ ## Step 3 — Merge wait, async mode, and flags
241
+
242
+ **What close does internally.** The script runs the close-validation gates
243
+ against `baseBranch`, syncs the Story branch from `origin/<baseBranch>`
244
+ (Story #2580 — the parallel-race defence), pushes `story-<id>`, opens (or
245
+ reuses) a PR against `baseBranch` with a `Closes #<storyId>` footer, enables
246
+ GitHub native auto-merge (`--auto --squash --delete-branch`) **when
247
+ `delivery.ci.autoMerge` is `"trust-ci"` (the default)**, flips the Story to
248
+ `agent::closing`, reaps the worktree, releases the lease, then **waits for
249
+ the merge** and — on a confirmed merge — flips `agent::done` and runs the
250
+ post-land tail.
251
+
252
+ **The merge wait is bounded and resumable.** Two budgets, deliberately
253
+ separate (`delivery.mergeWatch.*`):
254
+
255
+ - **`maxWaitSeconds`** (default 300) bounds **one invocation**, sized to fit
256
+ inside a single host tool invocation (~10 min ceiling) alongside the gates
257
+ that precede it. Expiry → `pending`. Pass `--max-wait-seconds <n>` to raise
258
+ it when your host has no such ceiling and you want to land in one block.
259
+ - **`maxBudgetSeconds`** (default 3600) bounds the **cumulative** wait across
260
+ resumes, anchored at the PR's `createdAt` so resuming does not restart the
261
+ clock. Exhausting *this* is the genuine give-up → `blocked`.
262
+
263
+ The wait probes the checks every poll: a red required check fails fast as
264
+ `checks-failed` instead of burning the budget, and a PR that falls behind its
265
+ base is brought up to date within `updateAttempts` tries.
266
+
267
+ **Async merge-confirm mode (`delivery.mergeWatch.mode: "async"`, Story #4698).** Under the default `"sync"` the merge wait runs in the foreground as
268
+ described above. When a consumer's CI routinely takes longer than the host
269
+ tool ceiling (~10 min) can hold a single close invocation, the foreground
270
+ wait almost always expires `pending` after burning ~5 minutes of the slot —
271
+ so `"async"` makes that async confirm a designed mode instead of an expiry
272
+ accident. In async mode the close arms auto-merge, runs one short **~60s
273
+ probe window** (long enough to catch an instant merge and, via the
274
+ head-anchored required-check predicate, an instantly-red required check),
275
+ then returns the standard `pending` terminal with a `nextCommand`. When you
276
+ receive that `pending` envelope, launch its `nextCommand`
277
+ (`single-story-confirm-merge.js … --wait`) as a **background** invocation —
278
+ host **background Bash** (`run_in_background`), whose completion re-invokes
279
+ the agent — and continue; do **not** sit in a foreground poll the tool
280
+ ceiling will kill. `single-story-confirm-merge.js` is already idempotent and
281
+ owns the whole tail, so no new state holder is needed, and
282
+ `deliver-recover.js` remains the recovery path for an orphaned confirm. The
283
+ cumulative `maxBudgetSeconds` give-up is unchanged; `"sync"` behaviour is
284
+ byte-compatible.
285
+
286
+ **`delivery.ci.autoMerge` policy.** Under the default `"trust-ci"`, GitHub
287
+ native auto-merge is armed and the PR squash-merges once its **required**
288
+ checks pass. Under `"strict"`, the close **does not arm auto-merge** — the
289
+ PR opens and waits for an **operator merge**, exactly as `--no-auto-merge`
290
+ does per-run.
291
+
292
+ **Close flags:**
293
+
294
+ - `--skip-validation` — bypass the gates. Use only when re-running close
295
+ after a fixed gate failure that's already known to pass.
296
+ - `--skip-sync` — bypass the base-sync (Story #2580). Use only after a
297
+ hand-resolved sync, or in tests.
298
+ - `--no-auto-merge` — disable auto-merge. Use when the PR materially changes
299
+ behaviour and warrants a pre-merge eyeball; the operator then merges via
300
+ the GitHub UI.
301
+ - `--wait-merge` — **close-and-land** (Story #4428). Forces close to poll
302
+ the armed PR to merge confirmation and flip `agent::done` itself. When
303
+ neither land flag is passed, close defaults from
304
+ `delivery.routing.closeAndLand` (**true**): attended and headless delivers
305
+ share the land-in-one-close happy path.
306
+ - `--no-wait-merge` — explicit opt-out that always wins. Use when the
307
+ operator wants the PR left at `agent::closing` for a human land (or a
308
+ wrapper that will invoke `single-story-confirm-merge.js` itself). Reports
309
+ `pending` — the work is not done, nothing is broken, and one named command
310
+ finishes it.
311
+ - `--max-wait-seconds <n>` — raise the merge wait's per-invocation bound for
312
+ this run (Story #4543). Use from a headless caller with no host
313
+ tool-invocation ceiling to keep single-block semantics without editing the
314
+ consumer's config.
315
+
316
+ ---
317
+
115
318
  ## Step 3 — Close pipeline detail
116
319
 
117
320
  The `single-story-close.js` script, in order: