mandrel 1.84.0 → 1.86.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 (46) 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/git-conventions.md +33 -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/boot-sweep.js +183 -0
  9. package/.agents/scripts/epic-deliver-prepare.js +55 -0
  10. package/.agents/scripts/git-pr-quality-gate.js +7 -5
  11. package/.agents/scripts/lib/config/ci.js +24 -3
  12. package/.agents/scripts/lib/config/explain.js +11 -3
  13. package/.agents/scripts/lib/config/github.js +11 -7
  14. package/.agents/scripts/lib/config-settings-schema-delivery.js +21 -0
  15. package/.agents/scripts/lib/config-settings-schema.js +6 -6
  16. package/.agents/scripts/lib/orchestration/epic-cleanup.js +289 -1
  17. package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +65 -0
  18. package/.agents/scripts/lib/orchestration/git-cleanup/phases/git-probes-ff.js +83 -30
  19. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +401 -84
  20. package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +8 -3
  21. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +48 -3
  22. package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +6 -1
  23. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +172 -58
  24. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +19 -0
  25. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +2 -0
  26. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/gate-failure.js +54 -6
  27. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +35 -4
  28. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +17 -16
  29. package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +75 -0
  30. package/.agents/scripts/lib/single-story-sweep.js +181 -54
  31. package/.agents/scripts/lib/templates/decomposer-prompts.js +17 -3
  32. package/.agents/scripts/pr-watch-with-update.js +324 -37
  33. package/.agents/scripts/run-verify.js +18 -3
  34. package/.agents/scripts/single-story-confirm-merge.js +1 -1
  35. package/.agents/scripts/single-story-init.js +7 -51
  36. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +32 -1
  37. package/.agents/skills/core/scope-triage/SKILL.md +5 -4
  38. package/.agents/workflows/git-deliver.md +36 -0
  39. package/.agents/workflows/helpers/deliver-epic-reference.md +41 -21
  40. package/.agents/workflows/helpers/deliver-epic.md +148 -28
  41. package/.agents/workflows/helpers/deliver-stories.md +18 -5
  42. package/.agents/workflows/helpers/single-story-deliver-reference.md +3 -3
  43. package/.agents/workflows/helpers/single-story-deliver.md +56 -19
  44. package/.agents/workflows/plan.md +32 -4
  45. package/docs/CHANGELOG.md +21 -0
  46. package/package.json +1 -1
@@ -70,6 +70,33 @@ it is about to run** before it acts.
70
70
 
71
71
  ---
72
72
 
73
+ ## Boot sweep
74
+
75
+ Before detecting the git setup, run the **protected boot sweep** so
76
+ `/git-deliver` starts from a tidy local checkout — a feature branch this
77
+ command opened and pushed on a prior run, once its PR has merged, is reaped
78
+ here rather than left to accumulate:
79
+
80
+ ```bash
81
+ node .agents/scripts/boot-sweep.js \
82
+ --include 'feat/*' --include 'fix/*' --include 'chore/*' \
83
+ --include 'docs/*' --include 'refactor/*' \
84
+ --current "$(git rev-parse --abbrev-ref HEAD)"
85
+ ```
86
+
87
+ This is the **safe subset** of the `/git-cleanup` phases: it fast-forwards the
88
+ base branch (`main`), prunes stale remote-tracking refs, and reaps every local
89
+ branch whose PR is **merged** and whose HEAD matches the merged `headRefOid`.
90
+ It **never** touches the stash stack, and its `evaluateProtection` partition
91
+ skips (never reaps) any candidate with unpushed work, a dirty worktree, or a
92
+ still-open parent ticket; `--current` always excludes the branch you are on.
93
+ The sweep is **silent on a no-op** — nothing merged, `main` already current →
94
+ one summary line (`[boot-sweep] reaped 0 local + 0 remote; protected 0.`). Its
95
+ exit code is always `0`; a failed sweep is reported in that summary, never
96
+ allowed to fail the delivery run.
97
+
98
+ ---
99
+
73
100
  ## Step 0 — Detect Git Setup & Resolve Level
74
101
 
75
102
  1. Resolve `[BASE_BRANCH]` from `--base` or `.agentrc.json` →
@@ -248,6 +275,15 @@ Do **not** poll CI. That is the `/deliver` Phase 7 job and is overkill for
248
275
  ad-hoc changes. The operator (or GitHub's email notification) is the next
249
276
  watcher.
250
277
 
278
+ > **Local ref left behind — reaped at the next boot.** At the **pr** level
279
+ > this command leaves a local feature branch behind after the PR merges; it
280
+ > does **not** reap it inline (the merge happens later, out of band). You do
281
+ > not need to run `/git-cleanup` by hand: the next time `/git-deliver` (or
282
+ > `/plan`) runs, its **Boot sweep** step reaps that merged ad-hoc branch and
283
+ > fast-forwards `main` automatically. The delivering flow owns tidying its own
284
+ > refs on the next boot — see
285
+ > [`.agents/rules/git-conventions.md` § Local checkout hygiene](../rules/git-conventions.md).
286
+
251
287
  ---
252
288
 
253
289
  ## Troubleshooting
@@ -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
@@ -495,20 +509,26 @@ node .agents/scripts/lifecycle-emit.js --epic <epicId> \
495
509
 
496
510
  If Phase 8.5 fell back to the operator-merges-button path (`gh pr
497
511
  merge --auto` was declined), the `epic.merge.armed` event never fires
498
- inside this run and Phase 9 will not run automatically. After the
499
- operator merges the PR, `epic/<epicId>` and each `story-<id>` ref can
500
- be reaped manually:
512
+ inside this run and Phase 9 will not run automatically. **Do not** hand-reap
513
+ the refs with a raw `git branch -D` sequence — drive the same
514
+ `BranchCleaner`-backed reap the auto-merge path uses by firing
515
+ `epic.merge.armed` after the operator merges the PR:
501
516
 
502
517
  ```bash
503
- git checkout main
504
- git pull --ff-only origin main
505
- git branch -D epic/<epicId>
506
- git branch -D story-<id1> story-<id2> ...
507
- git remote prune origin
518
+ node .agents/scripts/lifecycle-emit.js --epic <epicId> \
519
+ --event epic.merge.armed --pr-url <prUrl>
508
520
  ```
509
521
 
510
- Note that `git-cleanup.js` alone will not catch `story-<id>` refs in
511
- this case because the epic PR squash-merges break the `git branch
512
- --merged main` signal and the stories never had their own PRs. Wiring
513
- a CLI surface that drives the BranchCleaner listener for this
514
- fallback is tracked as follow-up to Story #2398.
522
+ That single emit reaps `epic/<epicId>` and every `story-<id>` ref from the
523
+ checkpoint, prunes stale tracking refs, and fast-forwards local `main` to
524
+ `origin/main` the whole Phase 9 reap, not a partial hand-roll. A plain
525
+ `git-cleanup.js` sweep alone will **not** catch the `story-<id>` refs here,
526
+ because the epic PR squash-merge breaks the `git branch --merged main` signal
527
+ and the stories never had their own PRs; the lifecycle-emit surface above is
528
+ the correct driver.
529
+
530
+ Re-running `/deliver <epicId>` reaches the same outcome without the manual
531
+ emit: the idempotent-resume auto-arm
532
+ (`detectMergedUncleanedEpic` → `armCleanupIfMerged` in
533
+ [`epic-cleanup.js`](../../scripts/lib/orchestration/epic-cleanup.js)) detects
534
+ the merged-but-uncleaned Epic and fires `epic.merge.armed` for you.
@@ -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,43 @@ 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.
736
+
737
+ **Blocked-path output (operator merges the button).** When arming is
738
+ declined, `epic.merge.armed` never fires inside this run, so Phase 9 does not
739
+ reap automatically. Surface the exact one-liner the operator runs **after**
740
+ they merge the PR by hand so local refs are reaped and `main` is
741
+ fast-forwarded (the idempotent-resume path below runs this automatically on
742
+ the next `/deliver <epicId>`):
743
+
744
+ ```bash
745
+ node .agents/scripts/lifecycle-emit.js --epic <epicId> \
746
+ --event epic.merge.armed --pr-url <prUrl>
747
+ ```
642
748
 
643
749
  Close the phase wrapper by emitting `epic.automerge.end` (records the arm
644
750
  outcome on the ledger; `merged: true` once GitHub completes the squash,
@@ -650,9 +756,10 @@ node .agents/scripts/lifecycle-emit.js --epic <epicId> \
650
756
  ```
651
757
 
652
758
  > **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
759
+ > predicate contract (the trailer read, the `delivery.ci.autoMerge` policy
760
+ > split, and the Story #4361 live `gh pr checks --required` probe that
761
+ > replaced the former CI-freshness skip) and the
762
+ > `epic-deliver-note-intervention.js` command + its trigger list, see
656
763
  > [`deliver-epic-reference.md` § Phase 8.5 — Auto-merge predicate detail](deliver-epic-reference.md#phase-85--auto-merge-predicate-detail).
657
764
 
658
765
  ---
@@ -689,6 +796,19 @@ via `helpers/epic-deliver-story`'s own checkpointing). The PR from Phase 7 is
689
796
  updated in place on subsequent runs. The authoritative live view is
690
797
  the `epic-run-progress` structured comment.
691
798
 
799
+ **Resume auto-arm for a merged-but-uncleaned Epic.** When `/deliver` resumes
800
+ against an Epic whose PR already merged (operator merged the button in a prior
801
+ session) but whose local `epic/<id>` / `story-<id>` refs still linger, the
802
+ resume path detects the merged-but-uncleaned state
803
+ (`detectMergedUncleanedEpic` in
804
+ [`epic-cleanup.js`](../../scripts/lib/orchestration/epic-cleanup.js)) and fires
805
+ `epic.merge.armed` automatically so Phase 9 reaps — no manual command. The
806
+ detection is idempotent: an already-reaped Epic (no local refs) is a clean
807
+ no-op, and an unmerged Epic never arms. It resolves the merged PR's URL for the
808
+ required `epic.merge.armed` payload and fails closed (does **not** arm) on any
809
+ indeterminate `gh` probe. The one-liner under Phase 8.5 / Phase 9 is the manual
810
+ equivalent for the case where the operator does not re-run `/deliver`.
811
+
692
812
  ---
693
813
 
694
814
  ## Constraints
@@ -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
@@ -318,11 +318,24 @@ Print a final run summary listing every delivered Story in completion order:
318
318
  All Stories delivered. PRs opened, auto-merge armed. CI will merge each
319
319
  PR when checks pass; each child then confirms the merge and flips its
320
320
  Story to `agent::done` (Story #3385 — until the merge confirms, a Story
321
- rests at `agent::closing` with its issue OPEN). Run
322
- `git-cleanup --fast-forward-main` after the last merge to bring local
323
- main up to date.
321
+ rests at `agent::closing` with its issue OPEN).
324
322
  ```
325
323
 
324
+ Then **fast-forward `main` yourself** — do not instruct the operator to run it
325
+ after the last merge. The delivering flow owns bringing the local base branch
326
+ up to whatever has merged so far:
327
+
328
+ ```bash
329
+ node .agents/scripts/git-cleanup.js --fast-forward-main --execute --yes
330
+ ```
331
+
332
+ This runs only the fast-forward-main phase (`git fetch origin main` →
333
+ `git merge --ff-only`); it is idempotent and a no-op when `main` is already
334
+ current, so it is safe to run even while some PRs are still queued in
335
+ auto-merge. Report its one-line result in the summary. Merged Story branches
336
+ themselves are reaped by the boot sweep at the next `/plan` / `/deliver` boot —
337
+ see [`.agents/rules/git-conventions.md` § Local checkout hygiene](../../rules/git-conventions.md).
338
+
326
339
  When some Stories are blocked or failed, list them explicitly with the
327
340
  `blockerCommentId` or failure detail so the operator knows where to look.
328
341
 
@@ -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
 
@@ -124,6 +124,30 @@ are operator-input waits, not validators. The two *named* HITL STOP gates the
124
124
  Story tracks (gate #1, gate #2) are the load-bearing pair; these additional
125
125
  waits are auto-proceeded for the same headless reason.
126
126
 
127
+ ## Boot sweep
128
+
129
+ Before anything else — ahead of the first-run preflight — run the **protected
130
+ boot sweep** so `/plan` opens against a tidy local checkout instead of one
131
+ still carrying the merged refs of the last delivered Epic or Story:
132
+
133
+ ```bash
134
+ node .agents/scripts/boot-sweep.js \
135
+ --include 'story-*' --include 'epic/*' \
136
+ --include 'feat/*' --include 'fix/*' --include 'chore/*'
137
+ ```
138
+
139
+ This is the **safe subset** of the `/git-cleanup` phases: it fast-forwards the
140
+ base branch (`main`), prunes stale remote-tracking refs, and reaps every local
141
+ branch whose PR is **merged** and whose HEAD matches the merged `headRefOid`.
142
+ It **never** touches the stash stack, and its `evaluateProtection` partition
143
+ skips (never reaps) any candidate with unpushed work, a dirty worktree, or a
144
+ still-open parent Story ticket — so it is safe to run unattended at the top of
145
+ a planning session. The sweep is **silent on a no-op**: with nothing to reap
146
+ and `main` already current it prints a single summary line
147
+ (`[boot-sweep] reaped 0 local + 0 remote; protected 0.`) and moves on. Its exit
148
+ code is always `0` — a failed sweep is swallowed and reported in the summary,
149
+ never allowed to fail the planning run.
150
+
127
151
  ## First-run preflight
128
152
 
129
153
  Before routing to a path helper, run a **first-run preflight** to catch
@@ -169,18 +193,22 @@ stubbed docs, or an unready doctor verdict).
169
193
  1. **Parse args.** Exactly one of `<epicId>`, `--idea`, `--from-notes`, or
170
194
  `--body` must be present; anything else is a usage error naming the four
171
195
  forms. A `--body` invocation routes to the story path (no triage).
172
- 2. **First-run preflight.** Run the preflight above. Skip when all signals
196
+ 2. **Boot sweep.** Run the protected boot sweep above
197
+ (`node .agents/scripts/boot-sweep.js …`) to fast-forward `main`, prune
198
+ stale remotes, and reap merged-PR branches. Silent on a no-op; never fails
199
+ the run.
200
+ 3. **First-run preflight.** Run the preflight above. Skip when all signals
173
201
  are clear (healthy project).
174
- 3. **Triage (idea path only).** Run the
202
+ 4. **Triage (idea path only).** Run the
175
203
  [`core/scope-triage`](../skills/core/scope-triage/SKILL.md) skill on the
176
204
  seed. Record the verdict in chat (one line).
177
- 4. **Delegate.** Read the selected path helper **in full** and execute it
205
+ 5. **Delegate.** Read the selected path helper **in full** and execute it
178
206
  from its entry phase, forwarding the absorbed flags (including `--yes`).
179
207
  The helper's phase numbering, HITL gates, and scripts are unchanged — this
180
208
  router adds no phase content. When `--yes` is present, the two HITL STOP
181
209
  gates auto-proceed per [Headless / non-interactive mode](#headless--non-interactive-mode---yes)
182
210
  above; every deterministic gate still runs.
183
- 5. **Internal returns.** When a path helper would historically have handed
211
+ 6. **Internal returns.** When a path helper would historically have handed
184
212
  off to the other planning command, switch helpers in-place and continue;
185
213
  surface the switch to the operator as a one-line note.
186
214
 
package/docs/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.86.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.85.0...mandrel-v1.86.0) (2026-07-07)
6
+
7
+
8
+ ### Added
9
+
10
+ * Epic [#4372](https://github.com/dsj1984/mandrel/issues/4372) ([#4376](https://github.com/dsj1984/mandrel/issues/4376)) ([98a82a4](https://github.com/dsj1984/mandrel/commit/98a82a4c27e5eafdfa2f1c54cfb42ed537a69a2e))
11
+
12
+
13
+ ### Fixed
14
+
15
+ * **baselines:** repair check-baselines auto-refresh + dedupe FF probes ([#4380](https://github.com/dsj1984/mandrel/issues/4380)) ([20a4ecd](https://github.com/dsj1984/mandrel/commit/20a4ecd97f92cb6a88be474c321b139fed95db56)), closes [#4379](https://github.com/dsj1984/mandrel/issues/4379)
16
+
17
+ ## [1.85.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.84.0...mandrel-v1.85.0) (2026-07-06)
18
+
19
+
20
+ ### Added
21
+
22
+ * **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))
23
+ * 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))
24
+ * **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))
25
+
5
26
  ## [1.84.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.83.0...mandrel-v1.84.0) (2026-07-05)
6
27
 
7
28
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mandrel",
3
- "version": "1.84.0",
3
+ "version": "1.86.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/",