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.
- 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/testing-standards.md +14 -0
- package/.agents/schemas/agentrc.schema.json +29 -6
- package/.agents/schemas/lifecycle/epic.watch.end.schema.json +2 -1
- 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/finalize/open-or-locate-pr.js +65 -0
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +401 -84
- 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/ticket-validator-sizing.js +17 -16
- 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/skills/core/epic-plan-decompose-author/SKILL.md +32 -1
- package/.agents/skills/core/scope-triage/SKILL.md +5 -4
- package/.agents/workflows/helpers/code-review.md +70 -5
- package/.agents/workflows/helpers/deliver-epic-reference.md +22 -8
- package/.agents/workflows/helpers/deliver-epic.md +123 -28
- package/.agents/workflows/helpers/deliver-stories.md +2 -2
- package/.agents/workflows/helpers/single-story-deliver-reference.md +3 -3
- package/.agents/workflows/helpers/single-story-deliver.md +56 -19
- package/docs/CHANGELOG.md +16 -0
- 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
|
|
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
|
|
@@ -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,31 @@ 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.
|
|
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
|
-
>
|
|
654
|
-
>
|
|
655
|
-
>
|
|
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 (`
|
|
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
|
|
@@ -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
|
|
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