mandrel 1.83.0 → 1.85.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 (35) hide show
  1. package/.agents/docs/agentrc-reference.json +8 -2
  2. package/.agents/docs/configuration.md +7 -2
  3. package/.agents/instructions.md +4 -0
  4. package/.agents/rules/ci-remediation.md +131 -0
  5. package/.agents/rules/testing-standards.md +14 -0
  6. package/.agents/schemas/agentrc.schema.json +29 -6
  7. package/.agents/schemas/lifecycle/epic.watch.end.schema.json +2 -1
  8. package/.agents/scripts/git-pr-quality-gate.js +7 -5
  9. package/.agents/scripts/lib/config/ci.js +24 -3
  10. package/.agents/scripts/lib/config/explain.js +11 -3
  11. package/.agents/scripts/lib/config/github.js +11 -7
  12. package/.agents/scripts/lib/config-settings-schema-delivery.js +21 -0
  13. package/.agents/scripts/lib/config-settings-schema.js +6 -6
  14. package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +65 -0
  15. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +401 -84
  16. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +48 -3
  17. package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +6 -1
  18. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +172 -58
  19. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +19 -0
  20. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +2 -0
  21. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +17 -16
  22. package/.agents/scripts/lib/templates/decomposer-prompts.js +17 -3
  23. package/.agents/scripts/pr-watch-with-update.js +324 -37
  24. package/.agents/scripts/run-verify.js +18 -3
  25. package/.agents/scripts/single-story-confirm-merge.js +1 -1
  26. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +32 -1
  27. package/.agents/skills/core/scope-triage/SKILL.md +5 -4
  28. package/.agents/workflows/helpers/code-review.md +70 -5
  29. package/.agents/workflows/helpers/deliver-epic-reference.md +22 -8
  30. package/.agents/workflows/helpers/deliver-epic.md +123 -28
  31. package/.agents/workflows/helpers/deliver-stories.md +2 -2
  32. package/.agents/workflows/helpers/single-story-deliver-reference.md +3 -3
  33. package/.agents/workflows/helpers/single-story-deliver.md +56 -19
  34. package/docs/CHANGELOG.md +16 -0
  35. package/package.json +1 -1
@@ -342,14 +342,28 @@ just fires the emit and reads the resulting ledger.
342
342
  refusing to run when the body has no `## Acceptance Table` section
343
343
  and no waiver is set (the
344
344
  start gate in Phase 1 would normally catch that first).
345
- 2. **PR open — bus-driven (Story #2894).** On
346
- `acceptance.reconcile.ok` the `Finalizer` listener invokes
347
- [`openOrLocatePr`](../../scripts/lib/orchestration/finalize/open-or-locate-pr.js)
348
- with `{ epicId, headBranch: 'epic/<id>', baseBranch: 'main' }`.
349
- The helper probes for an existing open PR on the head branch
350
- first (idempotent locate patha re-run of `/deliver`
351
- on the same branch short-circuits without opening a duplicate)
352
- and only opens a new PR when none exists. The listener then
345
+ 2. **PR open / ready — bus-driven (Story #2894; earlyPr split Story
346
+ #4359).** On `acceptance.reconcile.ok` the `Finalizer` listener
347
+ resolves `delivery.ci.earlyPr` via
348
+ [`getCiDelivery`](../../scripts/lib/config/ci.js) (default `true`) and
349
+ branches:
350
+ - **`earlyPr` on (default)**the Epic PR already exists as a draft
351
+ (Phase 2 opened it at wave 1 with
352
+ [`openOrLocatePr`](../../scripts/lib/orchestration/finalize/open-or-locate-pr.js)
353
+ `{ draft: true }`). Finalize locates it and flips it ready-for-review
354
+ via
355
+ [`markPrReady`](../../scripts/lib/orchestration/finalize/open-or-locate-pr.js)
356
+ rather than creating a PR. `gh pr ready` on an already-ready PR is a
357
+ no-op, so replay is idempotent.
358
+ - **`earlyPr` off** — finalize invokes `openOrLocatePr` with
359
+ `{ epicId, headBranch: 'epic/<id>', baseBranch: 'main' }` (no
360
+ `draft`). The helper probes for an existing open PR on the head branch
361
+ first (idempotent locate path — a re-run of `/deliver` on the same
362
+ branch short-circuits without opening a duplicate) and only opens a
363
+ new PR when none exists.
364
+
365
+ In both modes the PR title/body contract (`feat: Epic #<id>` /
366
+ `Closes #<id>`) is identical. The listener then
353
367
  emits `pr.created` → `epic.finalize.end` and **stops** (Story
354
368
  #3367). It does **not** emit `epic.merge.ready`: that event is
355
369
  the sole `AutomergeArmer` trigger, and emitting it from finalize
@@ -220,6 +220,41 @@ returned Story's terminal status, and re-tick until terminal. There is no
220
220
  `record-wave` / `currentWave` step — the checkpoint carries only a flat
221
221
  per-Story status map (for resume + the operator rollup) and the global cap.
222
222
 
223
+ ### 2.0. Open the Epic PR as a draft at wave 1 (Story #4359)
224
+
225
+ When `delivery.ci.earlyPr` is on (the default), open the Epic PR as a
226
+ **draft** once, before the first `tick`, so every subsequent per-wave push
227
+ to `epic/<epicId>` runs CI attributed to its own wave. (CI is keyed on the PR
228
+ ref with `cancel-in-progress`, so each new wave push **supersedes** the prior
229
+ wave's in-flight run rather than queuing behind it — the latest wave always
230
+ gets the verdict, and CI-minute use stays bounded; intermediate wave runs are
231
+ cancelled, not completed.) Resolve the flag through the
232
+ [`getCiDelivery`](../../scripts/lib/config/ci.js) accessor (default `true`);
233
+ do not read `delivery.ci.earlyPr` directly.
234
+
235
+ > **This step is host-LLM-driven, with no runtime enforcement seam** — unlike
236
+ > the Phase 7 ready-flip, which the `Finalizer` listener wires in
237
+ > deterministically (`finalizer.js` resolves `earlyPr` and calls
238
+ > `markPrReady`). The asymmetry is intentional and safe: if this wave-1
239
+ > draft-open is skipped, the `earlyPr`-on Phase 7 `markPrReady` call degrades
240
+ > to a no-op on the PR that finalize opens at close time (`gh pr ready` is a
241
+ > no-op on an already-ready PR), so the merge gate is never stranded — the run
242
+ > only loses the per-wave CI attribution this step buys.
243
+
244
+ - **`earlyPr` on** — call
245
+ [`openOrLocatePr`](../../scripts/lib/orchestration/finalize/open-or-locate-pr.js)
246
+ with `{ epicId, headBranch: 'epic/<epicId>', baseBranch: 'main', draft: true }`.
247
+ The helper probes for an existing open PR first, so this is idempotent — a
248
+ resumed `/deliver` run re-locates the same draft and opens no duplicate.
249
+ Phase 7 later flips this draft to ready-for-review (it does **not**
250
+ re-create the PR).
251
+ - **`earlyPr` off** — skip this step entirely. No draft is opened at wave 1;
252
+ Phase 7 opens the PR at close time on the pre-Story timing.
253
+
254
+ The draft carries the same title/body contract Phase 7 uses
255
+ (`feat: Epic #<epicId>` / `Closes #<epicId>`), so no title/body reconciliation
256
+ is needed when it is marked ready.
257
+
223
258
  ### 2a. Tick — plan the next action
224
259
 
225
260
  ```bash
@@ -556,7 +591,7 @@ degradation contract, and the fail-safe-and-loud security note.
556
591
 
557
592
  ---
558
593
 
559
- ## Phase 7 — Finalize (open PR to main)
594
+ ## Phase 7 — Finalize (ready the PR / open PR to main)
560
595
 
561
596
  Before the close-tail emit, sync the Epic branch with `origin/main` so the
562
597
  PR opens with the latest base commits already integrated (a stale base
@@ -577,12 +612,28 @@ node .agents/scripts/lifecycle-emit.js --epic <epicId> --event epic.close.end
577
612
 
578
613
  `epic.close.end` drives the bus-owned `Finalizer` chain: acceptance-table
579
614
  reconciliation (throws and aborts finalize on a coverage gap, `waived` under
580
- `acceptance::n-a`), idempotent PR open/locate against `main`, and the
615
+ `acceptance::n-a`), the PR-open/ready step (below), and the
581
616
  `epic-handoff` comment naming the PR URL. The chain emits `pr.created` →
582
617
  `epic.finalize.end` and **stops** — it never emits `epic.merge.ready` (the
583
618
  auto-merge arm is driven later from the Phase 8.5 gated watch path). The
584
619
  operator shells nothing beyond the sync and the single emit.
585
620
 
621
+ **PR-open/ready is gated by `delivery.ci.earlyPr` (Story #4359).** Resolve
622
+ the flag through the [`getCiDelivery`](../../scripts/lib/config/ci.js)
623
+ accessor (default `true`); do not read `delivery.ci.earlyPr` directly.
624
+
625
+ - **`earlyPr` on (default)** — the Epic PR already exists as a draft (Phase
626
+ 2 opened it at wave 1). Finalize **locates** the existing PR and flips it
627
+ ready-for-review via
628
+ [`markPrReady`](../../scripts/lib/orchestration/finalize/open-or-locate-pr.js)
629
+ rather than creating a PR. `gh pr ready` on an already-ready PR is a
630
+ no-op, so a re-run is idempotent.
631
+ - **`earlyPr` off** — no draft was opened at wave 1; finalize opens the PR
632
+ now via `openOrLocatePr` (no `draft`), exactly as the pre-Story timing.
633
+
634
+ In both modes the PR title/body contract (`feat: Epic #<epicId>` /
635
+ `Closes #<epicId>`) is identical.
636
+
586
637
  See
587
638
  [`deliver-epic-reference.md` § Phase 7 — Finalize](deliver-epic-reference.md#phase-7--finalize-close-tail-listener-chain)
588
639
  for the branch-sync outcome table (conflict / fetch-failed recovery) and the
@@ -595,25 +646,51 @@ sweep).
595
646
  ## Phase 8 — Watch-and-iterate until CI is green
596
647
 
597
648
  The host LLM owns the green-bar loop until the operator merges. Use
598
- the shared watch-and-recover helper, which wraps `gh pr checks --watch`
599
- and additionally auto-recovers from `mergeStateStatus: BEHIND` by
600
- calling `gh pr update-branch` once every required check is green
601
- (branch-protection rules requiring "up to date before merging"
602
- otherwise park the PR until the operator clicks **Update branch**
603
- manually):
649
+ `pr-watch-with-update.js` the **single CI-watch mechanism** shared with
650
+ the standalone single-Story Step 4 path (Story #4358). It polls the PR's
651
+ required checks to a terminal state and additionally auto-recovers from
652
+ `mergeStateStatus: BEHIND` by calling `gh pr update-branch` once every
653
+ required check is green (branch-protection rules requiring "up to date
654
+ before merging" otherwise park the PR until the operator clicks **Update
655
+ branch** manually):
604
656
 
605
657
  ```bash
606
- node <agentRoot>/scripts/pr-watch-with-update.js --pr <prNumber>
658
+ node <agentRoot>/scripts/pr-watch-with-update.js --pr <prNumber> --epic <epicId>
607
659
  ```
608
660
 
609
- `<agentRoot>` resolves from `project.paths.agentRoot` (default
610
- `.agents`). Pass `--max-updates N` (default 3) to cap update-branch
611
- calls per session and `--poll-interval-ms MS` (default 10000) to
612
- override the polling cadence.
613
-
614
- Exit 0 → proceed to Phase 8.5. Non-zero → remediate and re-run the helper
615
- (push fixes to `epic/<epicId>`; auto-merge stays armed across retries).
616
-
661
+ `<agentRoot>` resolves from `project.paths.agentRoot` (default `.agents`).
662
+ Poll cadence and caps come from `delivery.ci.watch.*`
663
+ (`pollIntervalMs`, `maxPolls`, `maxResumes`); pass `--poll-interval-ms`,
664
+ `--max-polls`, `--max-resumes`, or `--max-updates` to override for one
665
+ run. Passing `--epic <epicId>` scopes the red-path failure digest to
666
+ `temp/epic-<epicId>-ci-digest.{json,md}`.
667
+
668
+ **Three-way exit (slow-vs-failed semantics):**
669
+
670
+ - **Exit 0** — every required check is green → proceed to Phase 8.5.
671
+ - **Exit 1** — a required check genuinely failed (red). The CLI writes
672
+ `temp/epic-<epicId>-ci-digest.{json,md}` (failing check, run id,
673
+ `gh run view --log-failed` tail, coarse classification) and surfaces
674
+ the fix-loop handoff. Remediate on `epic/<epicId>` and re-run the
675
+ helper (auto-merge stays armed across retries). If the same failure
676
+ class recurs, hand the convergence off to the host loop:
677
+ `/loop /loops:fix-failing-tests`.
678
+ - **Exit 2** — **still-running** (slow CI, not red): the poll cap fired
679
+ with checks still pending and the watcher exhausted its
680
+ `delivery.ci.watch.maxResumes` re-arm budget with nothing red. This is
681
+ **never** a failure and **never** `timed_out`. Hand the wait off to the
682
+ host's interval loop rather than blocking the delivery turn:
683
+ `/loop 5m /loops:watch-ci`.
684
+
685
+ > **Triage authority.** How to classify and remediate a red (or repeatedly
686
+ > slow) check — the root-cause-only decision tree for infra/transient and
687
+ > flaky failures (reproduce → check `main` → bisect env vs code → fix in-scope
688
+ > or file a `meta::framework-gap` issue), the never-rerun / never-quarantine
689
+ > prohibitions, and the escalation criteria (three-strikes, the 30-minute
690
+ > wall-clock timebox, and the clearly-environmental fast path) — is defined
691
+ > once in [`.agents/rules/ci-remediation.md`](../../rules/ci-remediation.md).
692
+ > Read it before remediating.
693
+ >
617
694
  > **Remediation + hard prohibitions.** For the per-check fix table (lint,
618
695
  > baseline drift, test, coverage), the three-strikes halt rule, and the
619
696
  > never-merge / never-force-push / never-dodge prohibitions, see
@@ -631,14 +708,31 @@ node .agents/scripts/lifecycle-emit.js --epic <epicId> \
631
708
  --event epic.automerge.start --pr-url <prUrl>
632
709
  ```
633
710
 
634
- `AutomergePredicate` evaluates the structured-signal verdict and emits
635
- `epic.merge.ready` on a clean verdict or `epic.merge.blocked` otherwise. The
636
- downstream `AutomergeArmer` fires `gh pr merge --auto --squash
637
- --delete-branch` **only** when `clean: true` (empty manual-interventions,
638
- every wave complete, no story blocked, `0` 🔴 + `0` 🟠 review findings, and
639
- the retro's `automerge-verdict` trailer reports `cleanSprint: true`).
640
- Otherwise it records disqualifying reasons and exits without merging — the
641
- operator merges manually.
711
+ `AutomergePredicate` first runs a **live `gh pr checks --required` probe**
712
+ (Story #4361): green required CI is the arming signal, so if any required
713
+ check is red, pending, or the probe is unreadable it emits
714
+ `epic.merge.blocked` immediately even if the Phase 8 watch was interrupted
715
+ before it observed green (closing the Story #3901 interrupted-watch hole).
716
+ When the probe is green it evaluates the structured-signal verdict under the
717
+ `delivery.ci.autoMerge` policy (default `"trust-ci"`; see
718
+ [`configuration.md`](../../docs/configuration.md)):
719
+
720
+ - **`trust-ci`** (default) — the ONLY structured conditions that block
721
+ arming are an unresolved 🔴 critical (red) code-review finding or an
722
+ `agent::blocked` state (a story-level blocker recorded in run-state, a
723
+ non-done story, or a missing run-state checkpoint). Manual interventions,
724
+ 🟠 warning-level findings, and a non-clean retro are **recorded for audit**
725
+ (surfaced on the classification log and the arm-reason) but no longer block.
726
+ - **`strict`** — restores the prior clean-sprint predicate exactly: empty
727
+ manual-interventions, every story done, no story blocked, `0` 🔴 + `0` 🟠
728
+ review findings, and the retro's `automerge-verdict` trailer reporting
729
+ `cleanSprint: true`. Any dirty signal blocks.
730
+
731
+ On an arming decision the predicate emits `epic.merge.ready`; the downstream
732
+ `AutomergeArmer` (the sole authorized `gh pr merge` call site) fires
733
+ `gh pr merge --auto --squash --delete-branch`. Otherwise the predicate emits
734
+ `epic.merge.blocked` with the disqualifying reasons and exits without merging
735
+ — the operator merges manually.
642
736
 
643
737
  Close the phase wrapper by emitting `epic.automerge.end` (records the arm
644
738
  outcome on the ledger; `merged: true` once GitHub completes the squash,
@@ -650,9 +744,10 @@ node .agents/scripts/lifecycle-emit.js --epic <epicId> \
650
744
  ```
651
745
 
652
746
  > **Predicate wiring + manual-intervention recording.** For the full
653
- > `clean: true` predicate contract (Story #3901 — the trailer read, the
654
- > CI-freshness skip) and the `epic-deliver-note-intervention.js` command +
655
- > its trigger list, see
747
+ > predicate contract (the trailer read, the `delivery.ci.autoMerge` policy
748
+ > split, and the Story #4361 live `gh pr checks --required` probe that
749
+ > replaced the former CI-freshness skip) and the
750
+ > `epic-deliver-note-intervention.js` command + its trigger list, see
656
751
  > [`deliver-epic-reference.md` § Phase 8.5 — Auto-merge predicate detail](deliver-epic-reference.md#phase-85--auto-merge-predicate-detail).
657
752
 
658
753
  ---
@@ -235,7 +235,7 @@ Each Agent call:
235
235
  child MUST drive the close → CI-watch → merge-confirm → `agent::done`
236
236
  sequence to a terminal state *within its own turn* and end **only** by
237
237
  returning the § 2c JSON object. The auto-merge wait is an
238
- internally-blocking step (`gh pr checks --watch` blocks the turn), **not**
238
+ internally-blocking step (`pr-watch-with-update.js` blocks the turn), **not**
239
239
  a reason to suspend and hand back. A child that ends its turn with
240
240
  free-form prose and an unconfirmed merge (e.g. "I'll wait for the
241
241
  background watch task…") has violated the contract — the loop cannot
@@ -279,7 +279,7 @@ free-form prose:
279
279
 
280
280
  The status enum is **closed** — `done`, `blocked`, or `failed`. There is no
281
281
  "pending" / "waiting" status, because the close-phase auto-merge wait is
282
- **not** a returnable suspension: the child blocks on `gh pr checks --watch`
282
+ **not** a returnable suspension: the child blocks on `pr-watch-with-update.js`
283
283
  *inside its own turn*, confirms the merge, flips `agent::done`, and only then
284
284
  returns `status: "done"`. A child that returns prose instead — parking on the
285
285
  CI wait with an unconfirmed merge — breaks the loop's ability to advance
@@ -197,8 +197,8 @@ then ends its turn with **free-form prose** — e.g. "I'll wait for the
197
197
  background watch task to complete" or "the next event will be its completion
198
198
  notification" — leaving the merge unconfirmed and the Story stranded at
199
199
  `agent::closing` (observed on Story #1553 / PR #1554). **Do not do this.**
200
- `gh pr checks <prNumber> --watch` *blocks the current turn* until CI resolves
201
- — that is the mechanism by which you wait. You MUST keep your turn alive
200
+ `pr-watch-with-update.js --pr <prNumber>` *blocks the current turn* until CI
201
+ resolves — that is the mechanism by which you wait. You MUST keep your turn alive
202
202
  across the wait: watch → (fix + push + re-watch on red) → confirm the merge
203
203
  (Step 5) → flip `agent::done` → run the post-merge steps → and only then
204
204
  return the terminal JSON status contract. The CI wait NEVER terminates your
@@ -389,7 +389,7 @@ up").
389
389
 
390
390
  **The auto-merge wait does not produce a fourth status.** There is no
391
391
  "pending" or "waiting" terminal — the CI/auto-merge wait is handled
392
- *internally* by blocking on `gh pr checks --watch` (Step 4) and confirming
392
+ *internally* by blocking on `pr-watch-with-update.js` (Step 4) and confirming
393
393
  the merge (Step 5). You return **only** when you have reached a genuinely
394
394
  terminal state:
395
395
 
@@ -223,10 +223,23 @@ The script runs the close-validation gates against `baseBranch`, syncs the
223
223
  Story branch from `origin/<baseBranch>` (Story #2580 — the parallel-race
224
224
  defence), pushes `story-<id>`, opens (or reuses) a PR against `baseBranch`
225
225
  with a `Closes #<storyId>` footer, enables GitHub native auto-merge
226
- (`--auto --squash --delete-branch`), flips the Story to **`agent::closing`**
226
+ (`--auto --squash --delete-branch`) **when `delivery.ci.autoMerge` is
227
+ `"trust-ci"` (the default)**, flips the Story to **`agent::closing`**
227
228
  (NOT `agent::done` — the issue stays OPEN until Step 5 confirms the merge,
228
229
  Story #3385), reaps the worktree, and releases the Story lease.
229
230
 
231
+ > **`delivery.ci.autoMerge` policy (shared with the Epic path).** The
232
+ > standalone close honours the same config knob the Epic Phase 8.5 gate
233
+ > reads. Under the default `"trust-ci"`, GitHub native auto-merge is armed
234
+ > and the PR squash-merges once its **required** checks pass (GitHub's
235
+ > `--auto` is the required-check gate — no client-side predicate is needed
236
+ > here, unlike the Epic path, because a standalone Story runs no
237
+ > audit/review/retro phase to gate on). Under `"strict"`, the close **does
238
+ > not arm auto-merge** — the PR opens and waits for an **operator merge**,
239
+ > exactly as `--no-auto-merge` does per-run. This lets a consumer who tightens
240
+ > Epic merges with `"strict"` get the same operator-in-the-loop behaviour for
241
+ > standalone Stories.
242
+
230
243
  Flags:
231
244
 
232
245
  - `--skip-validation` — bypass the gates (Step 1). Use only when re-running
@@ -255,31 +268,55 @@ timing-sensitive tests routinely drift between the two. The agent owns the
255
268
  green-CI outcome, not just the push.
256
269
 
257
270
  > **The auto-merge wait is an internally-blocking step, not a reason to end
258
- > your turn.** `gh pr checks <prNumber> --watch` blocks the current turn
259
- > until CI resolves — that IS how you wait. Keep the turn alive: watch →
260
- > (fix + push + re-watch on red) → confirm the merge (Step 5) → flip
271
+ > your turn.** `pr-watch-with-update.js` blocks the current turn until CI
272
+ > resolves — that IS how you wait. Keep the turn alive: watch → (fix +
273
+ > push + re-watch on red) → confirm the merge (Step 5) → flip
261
274
  > `agent::done` → post-merge steps → return the terminal JSON contract.
262
275
  > Ending the turn with prose and an unconfirmed merge is a contract
263
276
  > violation (the Story #1553 / PR #1554 failure mode). See
264
277
  > [`single-story-deliver-reference.md` § The auto-merge wait is an internally-blocking step](single-story-deliver-reference.md#the-auto-merge-wait-is-an-internally-blocking-step).
265
278
 
266
- After `single-story-close.js` succeeds, enter the watch + fix loop:
279
+ After `single-story-close.js` succeeds, enter the watch + fix loop. Drive
280
+ `pr-watch-with-update.js` — the **single CI-watch mechanism** shared with
281
+ the Epic Phase 8 path (Story #4358). It polls the required checks to a
282
+ terminal state and auto-recovers from `mergeStateStatus: BEHIND`; do
283
+ **not** fall back to a bare `gh pr checks` watch invocation:
267
284
 
268
285
  ```bash
269
- gh pr checks <prNumber> --watch
286
+ node <agentRoot>/scripts/pr-watch-with-update.js --pr <prNumber>
270
287
  ```
271
288
 
272
- When the watch exits:
273
-
274
- - **All checks ✓** — auto-merge will fire (or has already). The Story is
275
- still at `agent::closing` with its issue OPEN. **Proceed to Step 5 within
276
- the same turn** green CI is the *start* of the merge-confirm sequence,
277
- not a terminal state.
278
- - **Any check ✗**diagnose, fix, and push a new commit on
279
- `story-<storyId>`, then re-watch. Auto-merge stays enabled across
280
- retries; no need to re-arm it. The Story stays at `agent::closing`
281
- throughout, so a failed/abandoned PR never strands a CLOSED issue.
282
-
289
+ Poll cadence and caps come from `delivery.ci.watch.*`
290
+ (`pollIntervalMs`, `maxPolls`, `maxResumes`); pass `--poll-interval-ms`,
291
+ `--max-polls`, or `--max-resumes` to override for one run.
292
+
293
+ When the watch exits, branch on the exit code:
294
+
295
+ - **Exit 0 (all checks ✓)** auto-merge will fire (or has already). The
296
+ Story is still at `agent::closing` with its issue OPEN. **Proceed to
297
+ Step 5 within the same turn** green CI is the *start* of the
298
+ merge-confirm sequence, not a terminal state.
299
+ - **Exit 1 (a check genuinely failed)** — diagnose, fix, and push a new
300
+ commit on `story-<storyId>`, then re-watch. Auto-merge stays enabled
301
+ across retries; no need to re-arm it. The Story stays at
302
+ `agent::closing` throughout, so a failed/abandoned PR never strands a
303
+ CLOSED issue. If the same failure class recurs, hand convergence off to
304
+ the host loop: `/loop /loops:fix-failing-tests`.
305
+ - **Exit 2 (still-running — slow CI, not red)** — the poll cap fired with
306
+ checks still pending and the watcher exhausted its resume budget with
307
+ nothing red. This is **never** a failure. Hand the wait off to the
308
+ host's interval loop rather than ending your turn:
309
+ `/loop 5m /loops:watch-ci`.
310
+
311
+ > **Triage authority.** How to classify and remediate a red (or repeatedly
312
+ > slow) check — the root-cause-only decision tree for infra/transient and
313
+ > flaky failures (reproduce → check `main` → bisect env vs code → fix in-scope
314
+ > or file a `meta::framework-gap` issue), the never-rerun / never-quarantine
315
+ > prohibitions, and the escalation criteria (three-strikes, the 30-minute
316
+ > wall-clock timebox, and the clearly-environmental fast path) — is defined
317
+ > once in [`.agents/rules/ci-remediation.md`](../../rules/ci-remediation.md).
318
+ > Read it before remediating a red check above.
319
+ >
283
320
  > **CI recovery procedures.** For resurrecting the worktree after
284
321
  > `reapOnSuccess`, pulling the failing job log, fixing coverage/CRAP
285
322
  > baselines without re-running close-validation, and the when-to-stop
@@ -401,8 +438,8 @@ worker so the contract is self-contained when this workflow is the entry
401
438
  point.
402
439
 
403
440
  There is **no fourth "pending" status** — the CI/auto-merge wait is handled
404
- internally by blocking on `gh pr checks --watch` (Step 4) and confirming the
405
- merge (Step 5). Return **only** on a confirmed `MERGED` PR (`status: "done"`),
441
+ internally by blocking on `pr-watch-with-update.js` (Step 4) and confirming
442
+ the merge (Step 5). Return **only** on a confirmed `MERGED` PR (`status: "done"`),
406
443
  an `agent::blocked` transition (`status: "blocked"`), or an unrecoverable
407
444
  failure (`status: "failed"`).
408
445
 
package/docs/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.85.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.84.0...mandrel-v1.85.0) (2026-07-06)
6
+
7
+
8
+ ### Added
9
+
10
+ * **delivery:** honor delivery.ci.autoMerge policy in standalone Story close ([#4371](https://github.com/dsj1984/mandrel/issues/4371)) ([0938d6b](https://github.com/dsj1984/mandrel/commit/0938d6b45bc0357bdcbc3d3ae148f18ab7ee5627))
11
+ * Epic [#4355](https://github.com/dsj1984/mandrel/issues/4355) ([#4368](https://github.com/dsj1984/mandrel/issues/4368)) ([585b823](https://github.com/dsj1984/mandrel/commit/585b82364190ea5676f96616e4c7a36a6d3468b5))
12
+ * **planning:** remove hard acceptance ceiling and add delivery-schedule simulation to decomposer ([#4366](https://github.com/dsj1984/mandrel/issues/4366)) ([bfc3f12](https://github.com/dsj1984/mandrel/commit/bfc3f12812ea5bc6e4d9f599d130b6f53b1009c3))
13
+
14
+ ## [1.84.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.83.0...mandrel-v1.84.0) (2026-07-05)
15
+
16
+
17
+ ### Added
18
+
19
+ * Epic [#4349](https://github.com/dsj1984/mandrel/issues/4349) ([#4353](https://github.com/dsj1984/mandrel/issues/4353)) ([6d17937](https://github.com/dsj1984/mandrel/commit/6d1793753fc193cd21b2126587af86957fb2aa24))
20
+
5
21
  ## [1.83.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.82.0...mandrel-v1.83.0) (2026-07-05)
6
22
 
7
23
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mandrel",
3
- "version": "1.83.0",
3
+ "version": "1.85.0",
4
4
  "description": "Claude Code-first opinionated workflow framework: instructions, personas, skills, and SDLC workflows that govern AI coding assistants.",
5
5
  "files": [
6
6
  ".agents/",