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.
- package/.agents/docs/agentrc-reference.json +8 -2
- package/.agents/docs/configuration.md +7 -2
- package/.agents/instructions.md +4 -0
- package/.agents/rules/ci-remediation.md +131 -0
- package/.agents/rules/git-conventions.md +33 -0
- package/.agents/schemas/agentrc.schema.json +29 -6
- package/.agents/schemas/lifecycle/epic.watch.end.schema.json +2 -1
- package/.agents/scripts/boot-sweep.js +183 -0
- package/.agents/scripts/epic-deliver-prepare.js +55 -0
- package/.agents/scripts/git-pr-quality-gate.js +7 -5
- package/.agents/scripts/lib/config/ci.js +24 -3
- package/.agents/scripts/lib/config/explain.js +11 -3
- package/.agents/scripts/lib/config/github.js +11 -7
- package/.agents/scripts/lib/config-settings-schema-delivery.js +21 -0
- package/.agents/scripts/lib/config-settings-schema.js +6 -6
- package/.agents/scripts/lib/orchestration/epic-cleanup.js +289 -1
- package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +65 -0
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/git-probes-ff.js +83 -30
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +401 -84
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +8 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +48 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +6 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +172 -58
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +19 -0
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +2 -0
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/gate-failure.js +54 -6
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +35 -4
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +17 -16
- package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +75 -0
- package/.agents/scripts/lib/single-story-sweep.js +181 -54
- package/.agents/scripts/lib/templates/decomposer-prompts.js +17 -3
- package/.agents/scripts/pr-watch-with-update.js +324 -37
- package/.agents/scripts/run-verify.js +18 -3
- package/.agents/scripts/single-story-confirm-merge.js +1 -1
- package/.agents/scripts/single-story-init.js +7 -51
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +32 -1
- package/.agents/skills/core/scope-triage/SKILL.md +5 -4
- package/.agents/workflows/git-deliver.md +36 -0
- package/.agents/workflows/helpers/deliver-epic-reference.md +41 -21
- package/.agents/workflows/helpers/deliver-epic.md +148 -28
- package/.agents/workflows/helpers/deliver-stories.md +18 -5
- package/.agents/workflows/helpers/single-story-deliver-reference.md +3 -3
- package/.agents/workflows/helpers/single-story-deliver.md +56 -19
- package/.agents/workflows/plan.md +32 -4
- package/docs/CHANGELOG.md +21 -0
- 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
|
|
346
|
-
`acceptance.reconcile.ok` the `Finalizer` listener
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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.
|
|
499
|
-
|
|
500
|
-
|
|
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
|
-
|
|
504
|
-
|
|
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
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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`),
|
|
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
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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
|
-
|
|
611
|
-
|
|
612
|
-
override
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
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`
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
the
|
|
640
|
-
|
|
641
|
-
|
|
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
|
-
>
|
|
654
|
-
>
|
|
655
|
-
>
|
|
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 (`
|
|
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 `
|
|
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).
|
|
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
|
-
`
|
|
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 `
|
|
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`)
|
|
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.** `
|
|
259
|
-
>
|
|
260
|
-
>
|
|
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
|
-
|
|
286
|
+
node <agentRoot>/scripts/pr-watch-with-update.js --pr <prNumber>
|
|
270
287
|
```
|
|
271
288
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
- **
|
|
279
|
-
`
|
|
280
|
-
|
|
281
|
-
|
|
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 `
|
|
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. **
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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