pi-gauntlet 7.32.1 → 7.34.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.
@@ -1,175 +1,53 @@
1
1
  # gatekeep-pr: step 6 - menu
2
2
 
3
- Read from SKILL.md step 6. Input: the report's verdict and the PR state. Output: the menu appended under the verdict line. Execute a pick or validated retained action only through `post-selection-loop.md` `### Next action`; the menu grants no authority by recommendation.
3
+ Read from SKILL.md step 6 with `menus.md`. Execute through `post-selection-loop.md` `### Next action`; recommendations confer no authority.
4
4
 
5
5
  ## Verbs
6
6
 
7
- `accept contract change`, `preserve contract`, `fix`, `fix nits`, `push`, `review`, `approve`, `approve workflow run`, `update branch`, `merge base`, `merge`, `merge anyway`, `reply`, `post coverage to ticket`, `post coverage to PR`, `cleanup`, `show evidence`, `wait`, `stop`.
8
-
9
- Render fix consequences with the applicable lifetime from `post-selection-loop.md` `### Continuation record`. For named fixes, state that authority retains only the selected stable IDs for later independent reassessments, with the same lifetime as broad fixes; link active pre-push retries to `fix-wave.md` `## Review before push`. For clarification-only fixes, state one-shot authority for the exactly selected payload; keep nit consequences one-shot.
10
-
11
- Read and apply [menu-layout](../../brainstorming/reference/menu-layout.md) before presenting these options and before acting on a numeric reply.
12
-
13
- Rows are `<n>. <verb> - <consequence>`; every row, `stop` included, carries its consequence clause. `stop` is always last, before the compose hint. `show evidence` is always offered and never `[recommended]`. A row the actor cannot execute - by `## Availability` below, or because the forge would refuse it (branch protection, a withhold) - is omitted and named in the compose line's `Not offered:` sentence; it is never `[recommended]`. Exactly one row is `[recommended]`. A **terminal overlay** is `own merge`, `merge queued` (once its read returns `MERGED` it re-renders as `own merge`), `approved`, or `merged or closed PR`; every other menu is an ordinary open-PR menu. **Landed** means the PR's `state` is `MERGED`; under `approved` and under a closed-unmerged PR nothing has landed.
14
-
15
- - `accept contract change` - invoke the concrete proposal's owning amendment process through `post-selection-loop.md` `### Contract decision`; settle the baseline only after completion
16
- - `preserve contract` - reject the concrete proposal and resume only permitted scope-preserving repair through `post-selection-loop.md` `### Contract decision`; keep independent blockers open
17
- - `fix` - retain covered blocker repair during this run until readiness or a new decision boundary (`post-selection-loop.md`); apply description targets through the loop's Description repair and file payloads for the named blockers (open blockers by default) through fresh implementer helpers in the provisioned worktree, review the wave before any push, push when `push` is available (`fix-wave.md`); on a `rebased locally` head, lease-push once every carried blocker is resolved (`fix-wave.md` `## Evidence after push`); `fix + nits` composes this verb with `fix nits`
18
- - `fix nits` - apply one-shot nit payloads drafted at this pick (`findings.md` `## Drafted payloads`) through fresh implementer helpers in the provisioned worktree, review the wave before any push, push when `push` is available (`fix-wave.md`); `fix + nits` typed with zero open blockers runs as this verb
19
- - `push` - push local fix commits (renders only when unpushed commits exist)
20
- - `review` - post the blockers as a GitHub review: request-changes on someone else's PR, a comment on your own
21
- - `approve` - approve the PR (never your own)
22
- - `approve workflow run` - `gh api --method POST repos/<base-owner>/<base-repo>/actions/runs/<id>/approve` for a held run on the assessed head, then re-enter the refresh (`post-selection-loop.md` `### Pre-menu refresh`)
23
- - `update branch` - `gh pr update-branch <N>`, then fast-forward the worktree to the new head (`fix-wave.md` `## Conflicts`); renders on a conflict with no wave commit, or when `merge_state_status` is `BEHIND` at the merge preconditions
24
- - `merge base` - retain merging the recorded base and resolving conflicts through reviewed helper work and push during this run until readiness or a new decision boundary (`fix-wave.md`)
25
- - `merge` - `gh pr merge --match-head-commit <assessed-sha>` with the flag of the resolved `merge policy` (`squash` -> `--squash`, `merge-commit` -> `--merge`, `rebase` -> `--rebase`; default squash); preconditions per `post-selection-loop.md` `### Merge preconditions`, including the repo allowing that method
26
- - `merge anyway` - merge while the pending-reviewer overlay withholds it; prints what it overrode
27
- - `reply` - post the drafted replies under `PR comments` (`reply <n>` for one)
28
- - `post coverage to ticket` - post the rendered `AC coverage` block as a comment on the source ticket via the resolved tracker verb (`gh issue comment`, the overrides' tracker mapping, or `/skill:linear`); renders only under the `own merge` overlay
29
- - `post coverage to PR` - post the same block with `gh pr comment <N> --body-file`; renders only under the `own merge` overlay
30
- - `cleanup` - tear down the gate's checkout and local branch per `cleanup.md`, then run the repo's `## cleanup` steps in order; the consequence clause names each override step as `<label> (<resolved command>)` with placeholders already substituted, so the exact tracker comment or release invocation is visible before the pick; with no `## cleanup` section the clause reads `tear down the gate's checkout and local branch (untracked files there are deleted)`
31
- - `show evidence` - print the evidence record, claim and AC outcomes, dispositions, drafted payloads and replies (`report.md` `## What stays out`)
32
- - `wait` - run one bounded `gh pr checks --watch` on every **binding** pending check in the resolved set (required or not), then re-render
33
- - `stop` - leave the PR as-is; under a terminal overlay leave the checkout as found and print one line naming the leftover worktree path and local branch; on an ordinary open-PR menu with a `rebased locally` head run `git reset --hard <pre_head>` before `### Teardown`, discarding the rewrite and any unpushed fix commits, and assert `HEAD == <pre_head>` with a clean tree; a mismatch is the existing hard stop with the error, and the remote stays untouched
34
-
35
- Compose hint: `Type a number, or compose: "fix 2", "fix nits", "fix + nits", "fix 1 + reply". Not offered: push (needs push to <head_url>:<head_ref>), merge (needs WRITE on <owner>/<repo>).` The first sentence lists only compositions whose every verb is a rendered row: `fix nits` and `fix + nits` appear only when both the `fix` and `fix nits` rows render; no `reply` row, no `fix 1 + reply`; no composable row, `Type a number.` alone. The second sentence names every omitted row as `<verb> (<why it is omitted>)` and is dropped when nothing is omitted. `merge anyway` renders as its own row when `## Availability` allows it, so the rendered-row rule does not apply to it. Offer `revoke fix` and `revoke merge base` compose forms for held grants, persisting only the named revocation through the loop. A composed line never bundles a push-producing verb (`fix`, `fix nits`, `push`, `merge base`) with `merge`. When a failing check withholds `merge`, the hint line also offers `check <name> flaky | real | ci-broken`, which records the disposition on that check (`findings.md` `## Dispositions`): `flaky` lifts the blocker, `real` keeps it until green, `ci-broken` triggers the fallback local run (`ci-broken` records `ci-infrastructure-broken`).
36
-
37
- A composed line naming an omitted row is refused by name and the menu re-renders; the `merge anyway` row is the only override.
7
+ Read and apply [menu-layout](../../brainstorming/reference/menu-layout.md) before presenting rows and before numeric lookup. Render `<n>. <verb> - <consequence>` using the selected `menus.md` template. Keep `show evidence` hint-only and queued wait solely in the merge queued template.
8
+
9
+ - `assess` - post the current open Blockers with locators as one review through `review`; use `gh pr review --request-changes` for someone else's/bot PR and `--comment` on the human's own PR; with zero blockers post nothing, then render assess terminal. Perform no repair or sync. Keep C# thread replies behind reply.
10
+ - `deliver` - run the persistent delivery course through readiness, obstacle, or cap; at an obstacle continue under the cumulative accounting rules in `fix-wave.md` `## Wave`.
11
+ - `deliver with nits` - store deliver-nits and include every current and subsequently minted nit in delivery.
12
+ - `fix nits` - authorize exactly one delivery iteration without changing course; initialize missing accounting from `fix-wave.md` `## Wave` before work.
13
+ - `accept contract change` - invoke the proposal's owning amendment through `### Contract decision`; update the baseline only after completion.
14
+ - `preserve contract` - keep the baseline and independent blockers; resume scope-preserving course work without the rejected payload on that re-run.
15
+ - `approve` - approve someone else's non-draft PR with zero blockers; persist approved_head only after success.
16
+ - `approve workflow run` - execute `gh api --method POST repos/<base-owner>/<base-repo>/actions/runs/<id>/approve`, then refresh and watch under the course.
17
+ - `merge` - execute `gh pr merge --match-head-commit <assessed-sha>` with the resolved policy flag and all merge preconditions; never infer approval from a setting.
18
+ - `reply` - post drafted replyable C# replies; exact `reply <n>` selects one.
19
+ - `post coverage to ticket` - post the rendered AC coverage block through the resolved tracker verb; renders only under the `own merge` template with issue and rows.
20
+ - `post coverage to PR` - post the same block with `gh pr comment <N> --body-file`; renders only under the `own merge` template.
21
+ - `cleanup` - run `cleanup.md` behind a terminal pick; name each resolved cleanup override command in the consequence, or say "tear down the gate's checkout and local branch (untracked files there are deleted)" with no override.
22
+ - `stop` - run `### Teardown`; delegate non-final stop to `cleanup.md` `## Non-final stop`, keeping terminal behavior distinct.
38
23
 
39
24
  ## Availability
40
25
 
41
- Rows derive from the digest's `permissions` block (`../verification-brief.md` Section A; `head_pushable` from step 2, `assessment.md`). A field that reads `unreadable` fails closed: its rows are omitted.
26
+ Derive availability from the digest's permissions; `unreadable` fails closed. Omit unavailable rows and name concrete reasons in `Not offered:`; never render an unavailable row with a suffix.
42
27
 
43
28
  | Row | Available when |
44
29
  |---|---|
45
- | `accept contract change`, `preserve contract` | a recorded contract exception is pending and no terminal/sync stop applies; route numeric and typed selections through `post-selection-loop.md` `### Contract decision` |
46
- | `fix` | an open blocker or explicitly requested exact clarification payload exists (a failing-check `P#` included), a helper facility exists (`../SKILL.md` `## Harness notes`), and the fix-round cap is neither `0` nor reached (`fix-wave.md` `## Wave`) |
47
- | `fix nits` | a nit exists, a helper facility exists (`../SKILL.md` `## Harness notes`), and the fix-round cap is neither `0` nor reached (`fix-wave.md` `## Wave`); a `fix` with neither blocker nor requested clarification, or a `fix nits` with no nit, is dropped from the row set and never named in `Not offered:` |
48
- | `merge base` | open PR with `mergeable: CONFLICTING` or `mergeStateStatus: BEHIND`, even with zero code blockers; head_pushable true, helpers, remaining budget, and no terminal/sync stop; preserve rebased-locally lease-only restrictions |
49
- | `push` | `head_pushable` is true and unpushed wave commits exist and the last pre-push review's carried set (`fix-wave.md` `## Review before push`) held no Critical or Moderate |
50
- | `merge` | `viewer_permission` is `WRITE`, `MAINTAIN`, or `ADMIN`, and the merge preconditions hold (`post-selection-loop.md`) |
51
- | `merge anyway` | its own overlay's withhold is the only unmet merge prerequisite (`post-selection-loop.md` `### Merge preconditions`) and `viewer_permission` is `WRITE`, `MAINTAIN`, or `ADMIN`; otherwise omitted and named in `Not offered:` |
52
- | `approve workflow run` | `viewer_permission` is `WRITE`, `MAINTAIN`, or `ADMIN`, and a held run exists on the assessed head |
53
- | `update branch` | `can_update_branch` is true and no unpushed wave commit exists |
54
- | `update branch`, `review`, `merge`, `merge anyway`, `push`, `wait` | omitted while the sync record is `rebased locally`, named in `Not offered:` as `(head not pushed)`; the fix wave's lease push is the only write to the PR head |
55
- | `cleanup` | only under a terminal overlay, never on an ordinary open-PR menu; it leaves the menu after it runs, success or not (`cleanup.md`) |
56
- | `approve`, `review`, `reply`, `post coverage to ticket`, `post coverage to PR`, `show evidence`, `wait`, `stop` | per the consent table and overlays below |
57
-
58
- ## Consent table
59
-
60
- Authorship moves only `[recommended]`, never which rows are offered. The first rendered row is `[recommended]`; when every other row is `show evidence`, `stop` is `[recommended]`.
61
-
62
- A `fixable` verdict with zero blockers (a withhold reason) uses the mergeable rows with `merge` omitted; the reason stays in the verdict line (`report.md`) and in the `Not offered:` sentence. On such a withhold, `[recommended]` goes to `approve workflow run` under the held-run overlay, else to `approve` or `wait` under the merge state overlay, else to `wait` under the verification-evidence-pending, pending-reviewer, or binding-pending-check overlay, else to `stop`.
63
-
64
- | Author | Verdict | Rows |
65
- |---|---|---|
66
- | you | mergeable | `merge`; `fix nits`; `reply` (when a drafted reply exists); `show evidence`; `stop` |
67
- | you | fixable | `fix`; `fix nits`; `review`; `reply` (when a drafted reply exists); `show evidence`; `stop` |
68
- | someone else | mergeable | `approve`; `fix nits`; `merge`; `reply` (when a drafted reply exists); `show evidence`; `stop` |
69
- | someone else | fixable | `review`; `fix`; `fix nits`; `reply` (when a drafted reply exists); `show evidence`; `stop` |
70
-
71
- Add `fix` to mergeable rows only for an explicitly requested exact clarification; a clarification-only selection grants that payload and no ongoing blocker grant. Add available `merge base` to applicable main row sets without a fake blocker or empty fix. With no held strategy, prefer available `update branch`, otherwise recommend available `merge base`; higher safety overlays keep their recommendation. Preserve a held merge-base strategy, never replace it with update branch or rerun --rebase step 2b.
72
-
73
- `push` joins any row set after `fix` or `fix nits` when unpushed fix commits sit in the worktree.
30
+ | `assess` | initial or obstacle course selection |
31
+ | `deliver`, `deliver with nits` | helpers exist, maxFixRounds is positive, head_pushable is true; deliver with nits additionally needs nits; use the template's continuation rules at an obstacle |
32
+ | `fix nits` | final template, nits remain outside the course, helpers and head_pushable true, accounting permits one iteration |
33
+ | `accept contract change`, `preserve contract` | pending recorded contract exception |
34
+ | `merge` | non-draft, viewer_permission `WRITE`, `MAINTAIN`, or `ADMIN`, all `### Merge preconditions` hold |
35
+ | `approve` | viewer.is_author false, zero blockers, non-draft |
36
+ | `approve workflow run` | held run on assessed head and viewer_permission `WRITE`, `MAINTAIN`, or `ADMIN` |
37
+ | `reply` | drafted reply exists and source review complete |
38
+ | `cleanup` | terminal template and cleanup_done false |
39
+ | `post coverage to ticket`, `post coverage to PR` | own merge template, not previously successful; ticket also needs issue and rows |
40
+ | `stop` | every template |
74
41
 
75
42
  ## Withhold reason resolver
76
43
 
77
- The single source for the merge-withhold reason, the `PR comments` line, and the `Not offered: merge (...)` text, in this precedence; overlays above `merge state` (merged or closed PR, draft PR, held run) keep their own reasons, and under two firing overlays the higher one owns the reason and the single `PR comments` line - the first matching reason renders, and `report.md`'s `fixable - <withhold reason>` carries the same word:
78
-
79
- 1. `merge_state_status` is `BLOCKED` -> line `GitHub reports this actor's merge as blocked, so merge waits. (merge_state_status BLOCKED; reviewDecision <value>)`, `Not offered: merge (blocked by GitHub)`, reason `blocked by GitHub`. `reviewDecision` rides as a locator only - never as a claim about which rule blocks, and no rendered sentence says another developer's approval is required.
80
- 2. `merge_state_status` is `UNKNOWN` or `unreadable`, or the pre-menu refresh failed -> line `GitHub has not reported this actor's merge state, so merge waits. (merge_state_status <value>)` (on a failed refresh the locator is `merge state not refreshed (<reason>)`), `Not offered: merge (merge state unknown)`, reason `merge state unknown`.
81
- 3. a binding pending check (`../verification-brief.md` Section B, binding classification) -> line `A check that binds this actor is still pending, so merge waits. (<check name>)`, `Not offered: merge (<check name> pending)`, reason `binding check pending`.
82
-
83
- ## Overlays
84
-
85
- For a pending contract exception, print the concrete proposed contract change before the rows, using the recorded proposal; numeric and typed picks select these actual rows and route to `post-selection-loop.md` `### Contract decision`. Render the `preserve contract` consequence as "reject the proposal, keep <named requirement>, and resume authorized implementation repair within it". Existing terminal/sync stops take precedence; otherwise the contract-exception overlay replaces ordinary rows, and lower overlays cannot discard its decision rows.
86
-
87
- Apply overlays to the consent row; take additional rows only from a terminal overlay (`cleanup` and the post-coverage rows) or the pending `contract exception` (contract-decision rows). When two apply, the one higher in this table wins the `merge` reason and `[recommended]`.
88
-
89
- | Overlay | Trigger | Effect |
90
- |---|---|---|
91
- | own merge | this run's `merge` pick ran and the post-merge read (`post-selection-loop.md` `### Merge course`) returned `state: MERGED` | the menu is `cleanup` `[recommended]`, `post coverage to ticket` (when `issue` is non-null and `scope.rows` is non-empty), `post coverage to PR`, `show evidence`, `stop`; each post row renders once and leaves the menu after its post succeeds, and a successful post re-renders this menu - posting never tears down and never ends the run; a failed post keeps its row and prints the `gh` error; no compare-and-swap runs before a post |
92
- | merge queued | this run's `merge` pick ran and the post-merge read returned a state other than `MERGED` | the menu is `wait` (re-run the post-merge read, then re-render), `show evidence`, `stop`, with `wait` `[recommended]`; one `PR comments` line `The merge is queued, so coverage posts wait. (<state>)` |
93
- | approved | the continuation record's `approved_head` equals the assessed head (`post-selection-loop.md` sets it only when `gh pr review --approve` exits 0, before the re-render; a non-zero exit prints the `gh` error and the ordinary menu re-renders) | the menu is `cleanup` `[recommended]`, `show evidence`, `stop` |
94
- | merged or closed PR | `state` was not `OPEN` at step 1 | the report renders; the menu is `cleanup` `[recommended]`, `show evidence`, `stop` |
95
- | contract exception | a recorded contract exception is pending, with no terminal/sync stop | replace the ordinary menu with `accept contract change`, `preserve contract`, `show evidence`, `stop`; recommend `preserve contract`; use `post-selection-loop.md` `### Contract decision` for every selection |
96
- | draft PR | `isDraft` | `merge` and `approve` are omitted (`Not offered: merge (draft)`, plus `approve (draft)` when the consent row holds `approve`) and never carry `[recommended]`; `review` is recommended on someone else's fixable draft, `fix nits` on their mergeable draft when a nit exists, else `stop`; on your own, `fix` when a blocker exists, else `stop` |
97
- | held run | an `action_required` conclusion on an Actions run of the assessed head (`../verification-brief.md` Evidence resolution, Held run) with no `local run: held run not approvable` evidence yet for that head | `approve workflow run` renders as row 1 when available (`## Availability`) and is `[recommended]` when `Blockers` is empty, else `[recommended]` stays on the consent row's first row; `merge` is omitted (`Not offered: merge (workflow run awaiting approval)`); once the held-run-not-approvable local run exists, this overlay no longer applies and `merge` follows the preconditions |
98
- | merge state | `merge_state_status` is `BLOCKED`, `UNKNOWN`, or `unreadable`, or the pre-menu refresh failed (`post-selection-loop.md` `### Pre-menu refresh`) - independent of any pending check, so a green-CI state-only block fires it | one `PR comments` line from `## Withhold reason resolver` (reason 1 or 2); `wait` as row 1; `[recommended]` goes to `approve` when the consent row renders it (someone else's PR, resolver reason 1, `reviewDecision` `REVIEW_REQUIRED`) - recommending a row attributes nothing to a rule - else to `wait` when `Blockers` is empty; `merge` omitted with the resolver's `Not offered` text; no `anyway` row |
99
- | verification evidence pending | the Evidence resolution table resolved Pending for the assessed head (`../verification-brief.md` Section B) | puts `wait` as row 1 and, when `Blockers` is empty, `[recommended]`; `merge` is omitted with `## Withhold reason resolver` reason 3's text; consent rows otherwise unchanged |
100
- | CI check | an undispositioned failing check in the resolved set, or a binding pending check (`findings.md` `## Dispositions`); a held run is not a binding pending check here (the `held run` overlay owns it) | on a failing check, the hint line offers `check <name> flaky \| real \| ci-broken`; `merge` is omitted (`Not offered: merge (<check name> failing)`); `flaky` restores `merge` on the next render; `real` and `ci-infrastructure-broken` keep it omitted until green; a binding pending check renders one `PR comments` line and omits `merge` with the text of `## Withhold reason resolver` reason 3, and puts `wait` as row 1 and, when `Blockers` is empty, `[recommended]`; under both this and the `merge state` overlay the higher one owns the reason and the single `PR comments` line; it mints no disposition hint (not dispositionable) |
101
- | pending reviewer | a `pending` ledger row, a queued or in-progress reviewer run on the assessed head, or a failed comment refetch (`post-selection-loop.md` `### Re-render`) | puts `wait` as row 1 and, when `Blockers` is empty, `[recommended]`; `merge` is omitted (`Not offered: merge (reviewer still running)` or `merge (comments not refreshed)`); `merge anyway` renders as its own row when `## Availability` allows it, overrides only this overlay, and does not bypass an unreviewed delta or a blocker; a `reviewer failed (<conclusion>)` row changes nothing |
102
- | comment source review incomplete | the `### Re-render` step-4 source review of a comment delta did not finish (`post-selection-loop.md`) | one `PR comments` line `The comment source review did not finish, so merge waits. (<reason>)`; `merge` is omitted (`Not offered: merge (comment source review incomplete)`) with no `anyway` row; `reply` and `review` are dropped (the delta is unreviewed); `fix`, `fix nits`, `show evidence`, `stop` stay; `stop` is `[recommended]` |
103
- | head not pushable | `head_pushable` is false or `unreadable` (a fork head, a branch this actor cannot push, or an unreadable head) | `push` is omitted (`Not offered: push (needs push to <head_url>:<head_ref>)`); `fix` and `fix nits` stay and apply to the local worktree branch (`assessment.md` provisioning), their consequence clauses reading "open a PR from that branch, or hand the patch to the author"; `merge` follows `## Availability` |
104
- | bot author | `author_is_bot` | the someone-else rows |
105
-
106
- ## Fixtures
107
-
108
- Fixture 1 - own PR, fixable, one nit, one drafted reply: the menu in `report.md` `## Worked example`.
44
+ Resolve every actual unmet `### Merge preconditions` reason through `post-selection-loop.md` `### Exit check`. Keep repairable gaps in the course, not a false readiness caused by missing WRITE permission. Render one concrete reason and locator rather than spinning.
109
45
 
110
- Fixture 2 - the re-render after fixture 1's `fix` pushed; the reviewer bot is mid-run on the new head:
111
-
112
- ```
113
- Delivers: the reports page exports CSV on demand. ACs 1, 2, 4; 3 deferred per spec to acme/widgets#46; 4's observable half (export completes under 5s on production data) is checked after merge, not here.
114
-
115
- PR comments:
116
- - maria's comment on the missing CSV header is already addressed, so the drafted reply points her at the fix. (3f2a1c0)
117
- - The reviewer run is still in progress, so merge waits. (https://github.com/<owner>/<repo>/actions/runs/<run-id>)
118
-
119
- Nits:
120
- - The export pages results with its own loop instead of the shared paging helper, so a paging bug fixed once would need fixing twice. (src/api/export.ts:31, lib/page.ts)
121
- 1 nit open - fix nits to take them
122
-
123
- Verdict: fixable - reviewer run in progress
124
-
125
- 1. wait - run one bounded CI watch, then re-render [recommended]
126
- 2. fix nits - apply the one-shot nit payload in the worktree, review the wave, push
127
- 3. reply - post the drafted reply to maria
128
- 4. show evidence - gate output, CI run
129
- 5. stop - leave the PR as-is
130
- Type a number. Not offered: merge (reviewer still running).
131
- ```
132
-
133
- Fixture 3 - a different PR: your own, every spec row covered, no nits, no drafted replies; the reviewer bot's run was the only withhold and `wait` just returned.
134
-
135
- (a) The run concluded `failure`: the `PR comments` line disappears, the reviewer check is inert, the verdict is `mergeable - CI green on the assessed head (test)`, and the menu is `merge` [recommended], `show evidence`, `stop`.
136
-
137
- (b) The run concluded `success` and the verdict comment raises a retry bug; source review confirms it and mints a blocker:
138
-
139
- ```
140
- PR comments:
141
- - The reviewer bot's retry concern holds up against the source, so the drafted reply confirms it. (src/retry.ts)
142
-
143
- Blockers:
144
- 1. Retries never count up, so a failing call retries forever instead of giving up
145
- after the limit. (src/retry.ts:41)
146
-
147
- Verdict: fixable - 1 blocker
148
-
149
- 1. fix - retain in-scope blocker repair during this run until readiness or a new decision boundary; review before push [recommended]
150
- 2. review - post the blocker as a comment on your PR
151
- 3. reply - post the drafted reply
152
- 4. show evidence - gate output, CI run, drafted edit
153
- 5. stop - leave the PR as-is
154
- Type a number, or compose: "fix 1 + reply".
155
- ```
156
-
157
- When source review disproves the comment, no blocker is minted; the mergeable menu renders with `reply` for the drafted reply.
158
-
159
- Fixture 4 - the pre-merge refetch finds a new human comment after a `merge` pick: the merge aborts even when the concern is false, the comment is reviewed against source, and the menu re-renders for a fresh pick. A same-head identical-body timestamp edit mints a ledger row but triggers no source review or test run.
160
-
161
- Fixture 5 - this run's `merge` pick succeeded (`state: MERGED`):
162
-
163
- ```
164
- ## AC coverage
165
- Informational - the ticket's rows are unchanged. PR https://github.com/acme/widgets/pull/210, merged 9f1c2d3, spec <path>
166
- - [ ] The reports page offers an Export CSV button. - covered
167
- - [ ] Exports are emailed to the requesting user. - deferred per spec to acme/widgets#46
168
-
169
- 1. cleanup - remove .worktrees/pr-210 and branch csv-export, pull main, then close ticket (gh issue close 45 --comment "Delivered in https://github.com/acme/widgets/pull/210, merged 9f1c2d3e4b5a6978.") [recommended]
170
- 2. post coverage to ticket - comment the block above on acme/widgets#45
171
- 3. post coverage to PR - comment the block above on the PR
172
- 4. show evidence - gate output, CI run
173
- 5. stop - leave the PR as-is; .worktrees/pr-210 and branch csv-export stay
174
- Type a number.
175
- ```
46
+ | Observation | Render |
47
+ |---|---|
48
+ | BLOCKED with no local repair | Use findings.md `## Dispositions` for the actor-framed PR-comments line and obstacle verdict; render obstacle even with course unset, plus `Not offered: merge (blocked by GitHub)`; never attribute a named rule |
49
+ | UNKNOWN/unreadable after one re-poll | `stuck - merge state unknown`; `Not offered: merge (merge state unknown)` |
50
+ | binding pending check | name the check under PR comments, `Not offered: merge (<check name> pending)`; watch only at delivery step 4 |
51
+ | draft | `Not offered: merge (draft)` |
52
+ | below WRITE | `Not offered: merge (needs WRITE on <owner>/<repo>)` |
53
+ | other failed prerequisite | name its actual missing evidence/input or unsafe-state obstacle from Exit check |
@@ -27,7 +27,9 @@ The `design` section is always part of the judged contract: a Design requirement
27
27
 
28
28
  **Coverage.** With `source: spec`, `Delivers` lists the covered rows, then the `deferred per spec`, `deviates per spec`, and `elsewhere per spec` rows, then the observation halves (`report.md`); any other uncovered row is `gap`. The spec's disposition is the record: a tracker comment, a PR named in the body, or a later PR never widens or narrows it, and a bare `deferred:` is never confirmed by the ticket.
29
29
 
30
- **Claims.** Treat `contradicted` as a blocker and draft a scope-neutral exact description correction; when a correction would narrow either spec-owned or PR-owned scope, pause the correction and route to the explicit contract-decision course in `post-selection-loop.md` `### Continuation record`, retaining the independent mechanism blocker. Render `unverifiable-pre-merge` as optional non-blocking Description uncertainty naming what the gate cannot verify, not as a nit or proof. Assess supplied evidence; leave `matched`, explicitly pending observations, and qualitative prose alone. Never invent unperformed work or a future commitment. Draft requested factual clarification only after an explicit human request; distinguish author assertions from gate evidence. Use pending-work wording only with an explicit record in the assessed body, approved spec, linked ticket, or request; conflicting records remain uncertain.
30
+ **Claims.** Treat `contradicted` as a blocker and draft a scope-neutral exact description correction; when a correction would narrow either spec-owned or PR-owned scope, pause the correction and route to the explicit contract-decision course in `post-selection-loop.md` `### Continuation record`, retaining the independent mechanism blocker. Render `unverifiable-pre-merge` as optional non-blocking Description uncertainty naming what the gate cannot verify, not as a nit or proof. Assess supplied evidence; leave `matched`, explicitly pending observations, and qualitative prose alone. Never invent unperformed work or a future commitment. Draft requested factual clarification only after an explicit human request; preserve the assertion with the two-part replacement `Author's statement: <assertion> (not verified by this gate). Gate evidence: <evidence>`, rather than deleting it. Use pending-work wording only with an explicit record in the assessed body, approved spec, linked ticket, or request; conflicting records remain uncertain.
31
+
32
+ Treat a human body rewording dropping a mechanism sentence as a description defect; repair the description without amending untouched spec requirements. Keep spec-owned scope and independent mechanism blockers in force. Record a real proposed contract change and its paused continuation through `post-selection-loop.md` `### Contract decision`; limit it to the requested obligation, not omitted description mechanisms.
31
33
 
32
34
  ## Namespaces
33
35
 
@@ -63,14 +65,14 @@ Any blocking conclusion in the resolved check set (required or not - `../verific
63
65
 
64
66
  Only `flaky` lifts the blocker; an undispositioned check, `real`, and `ci-infrastructure-broken` keep it until the check - or, for `ci-infrastructure-broken`, the fallback local run (brief Section B, Failed CI row) - is green.
65
67
 
66
- How each disposition changes the menu: `decision-menu.md` `## Overlays`, CI check row.
68
+ Record dispositions only from direct human check commands. A flaky disposition followed by numeric deliver can resume; no helper can disposition a check. With BLOCKED and no dispositionable local repair, render the obstacle template from `menus.md`, even with course unset, and use verdict `GitHub reports BLOCKED, no local repair applies`. Render its PR-comments line as `GitHub reports this actor's merge as blocked, so merge waits. (merge_state_status BLOCKED; reviewDecision <value>)`; keep the locator and never attribute a named rule.
67
69
 
68
70
  ## Drafted payloads
69
71
 
70
- Step 5 drafts, never applies. For every blocker with a file-level fix - code and doc drift alike - draft the concrete edit as a payload keyed to the finding's internal ID. Draft nit payloads on a `fix nits` or `fix + nits` pick, which is the consent: print them in the `show evidence` payload shape and dispatch the wave in the same pick with no second confirmation; before that pick `show evidence` prints only the payloads that exist. For every replyable `C#`, draft the reply. The worktree stays tracked-clean (`git status --porcelain --untracked-files=no` empty) at every menu render; a payload is applied only on a `fix`, `fix nits`, or `fix + nits` pick or a validated retained grant (the same path for code and docs) and dropped at teardown otherwise. A `P#` carried by a `rebased locally` sync record is drafted like any blocker, from the helper's test output and `git show <stopped commit>`; a `P#` whose smallest fix needs another repo, infrastructure, or a credential stays open. The human sees the draft at the `fix` consent and decides whether code or test expectations change; the implementer never decides it.
72
+ Step 5 drafts, never applies. For every blocker with a file-level fix - code and doc drift alike - draft the concrete edit as a payload keyed to the finding's internal ID. Draft nit payloads under `deliver-nits` or a terminal `fix nits` pick: print them in the `show evidence` payload shape and dispatch the wave in the same pick with no second confirmation; before that pick `show evidence` prints only the payloads that exist. For every replyable `C#`, draft the reply. The worktree stays tracked-clean (`git status --porcelain --untracked-files=no` empty) at every menu render; a payload is applied only under a delivery course or a terminal `fix nits` pick (the same path for code and docs) and dropped at teardown otherwise. A `P#` carried by a `rebased locally` sync record is drafted like any blocker, from the helper's test output and `git show <stopped commit>`; a `P#` whose smallest fix needs another repo, infrastructure, or a credential stays open. Keep the stored scope baseline authoritative; an implementer needing a contract change returns open - contract exception without changing it.
71
73
 
72
74
  Extend each finding-keyed evidence pack with `target: file | description`. For file targets retain the concrete edit and scoped test files. For description targets store `{ finding_id, contributor_ids, assessed_head, original_body, old_text, new_text, replacement_body, source_evidence }` with exact old-to-new text and unrelated body text preserved. Keep independent contributions under all their IDs when one finding combines a claim and test/scope/check gap; close only source-backed resolved claim contributions, never the whole combined finding from a body edit. Judge source: pr gaps against original_intent, not the shortened body.
73
75
 
74
- A payload is the smallest in-repo edit that turns the finding green and keeps the behavior the PR promises; it never adds infrastructure, a dependency, or a new module. A failure caused outside the repo (image pull, toolchain, credentials, external service) gets the smallest in-repo workaround with its reason in one sentence; a skip never removes a test an AC row relies on. A finding whose smallest fix needs another repo, infrastructure, or a credential stays open with the implementer's `open - needs <what>` sentence. A `fix` pick or a validated retained fix grant applies a description payload in the same round as the file payloads through `post-selection-loop.md` `### Description repair`; show compact exact old-to-new text under the blocker and the full replacement body under `show evidence`. A requested factual clarification enables a clarification-only `fix` without minting a blocker or ongoing grant. Never draft an unsolicited style rewrite.
76
+ A payload is the smallest in-repo edit that turns the finding green and keeps the behavior the PR promises; it never adds infrastructure, a dependency, or a new module. A failure caused outside the repo (image pull, toolchain, credentials, external service) gets the smallest in-repo workaround with its reason in one sentence; a skip never removes a test an AC row relies on. A finding whose smallest fix needs another repo, infrastructure, or a credential stays open with the implementer's `open - needs <what>` sentence. A delivery course applies a description payload alongside the file payloads through `post-selection-loop.md` `### Description repair`; show compact exact old-to-new text under the blocker and the full replacement body under `show evidence`. Apply a requested factual clarification only within selected course work; mint no fake blocker. Never draft an unsolicited style rewrite.
75
77
 
76
78
  A posted review body is composed at post time from the blockers being addressed - one summary sentence, then the numbered items, ending on the fix or asked action.
@@ -1,56 +1,53 @@
1
1
  # gatekeep-pr: fix wave
2
2
 
3
- Read from `post-selection-loop.md` `### Fix wave` or `### Next action` for a selected or retained file/base repair. Input: the selected payloads (`findings.md` `## Drafted payloads`), the dispatched findings, and the digest. Output: pushed commits with current evidence, or unpushed commits with the menu re-rendered. The orchestrator edits no tracked file and runs no git write other than `fetch`, `merge --ff-only`, `push`, and `reset --hard <pre_head>` after a refused lease or a remote head move on a `rebased locally` head (`post-selection-loop.md` `### Compare-and-swap`); every edit and every commit belongs to a fresh helper (`../SKILL.md` `## Harness notes`).
3
+ Read from `post-selection-loop.md` for delivery work or a terminal nit iteration. Keep edits and commits helper-owned; the orchestrator never edits a tracked file or commits. Permit only fetch, merge --ff-only, ordinary push, and the exact rewrite/recovery operations owned below.
4
4
 
5
- ## Conflicts
5
+ ## Sync
6
6
 
7
- Record `pre_wave_head` as `git -C <worktree> rev-parse HEAD` before the first `## Conflicts` check.
7
+ Run only through `post-selection-loop.md` `### Loop sync`, never re-enter the once-per-invocation `sync.md`. Persist `repair.wave.sync.pre_head` = `git -C <worktree> rev-parse HEAD` as the fresh local recovery head after repair, and `repair.wave.sync.expected_remote_head` from the assessed/pushed remote SHA. Verify the remote expectation with a fresh remote-head read before sync and again before push. Preserve expected_remote_head through local repair and rebase; reject a mismatch without replacing it. For remote A, local repair B, then rebase C: lease A, restore B; a remote mismatch must never replace A. Keep this checkpoint through pre-push reads and local-head assessment; clear it only after successful push/rebinding or recorded recovery. A malformed checkpoint exits `stuck - run record unreadable`, never substitutes local HEAD. Record pre_wave_head separately before wave work. Fetch base and run `git -C <worktree> merge-tree --write-tree origin/<baseRefName> HEAD` before wave work and again before push. Exit 0 is conflict-free; 1 is a sync trigger; any other exit is an obstacle naming it. Sync only for mergeable CONFLICTING, merge_state_status BEHIND, or local merge-tree conflict. A current conflict-free head skips sync. Increment only repair.syncs_used once per sync and persist before it; spend no round for base-only conflict commits.
8
8
 
9
- Run before the first wave commit and again before the push:
9
+ Read `sync.md` `## Permitted orchestrator git writes`, `## Conflict stop`, and `## Review before push` before a rewrite. Use these operations and helper continue checks with this sync's fresh pre_head, never entry-time state:
10
10
 
11
- ```bash
12
- git -C <worktree> fetch origin <baseRefName>
13
- git -C <worktree> merge-tree --write-tree origin/<baseRefName> HEAD # read-only; exit 0 = clean, exit 1 = conflict; any other exit: report it verbatim and re-render the menu without fix (at the pre-push check: also without push, commits stay unpushed)
14
- ```
11
+ | Observed branch | Procedure |
12
+ |---|---|
13
+ | viewer.is_author true, author_is_bot false, `git rev-list --merges origin/<baseRefName>..HEAD` empty | Rebase onto origin/<baseRefName>. Permit fetch, rebase, `GIT_EDITOR=true git rebase --continue`, rebase --abort, reset --hard pre_head and lease push only; every command runs with git -C worktree. Dispatch one fresh conflict-stop implementer per stop using sync.md's exact task/scoped-test/continue checks; never continue with unmerged paths or residue. Never resolve conflicts in the orchestrator. |
14
+ | Someone else's or bot-authored head, or own head with merge commits | Dispatch one fresh implementer to merge freshly fetched origin/<baseRefName>, resolve conflicts in one commit, restore only its own edits on failure, and preserve earlier wave commits. Own merge-containing history says `sync: merged base - head contains merge commits`. Never rewrite a bot head; use ordinary push. |
15
15
 
16
- This local check stands in for the remote `mergeable` field inside the wave: the remote stays `CONFLICTING` until a resolution is pushed, so only the local tree can show that a `merge base` round resolved it. `gh pr view --json mergeable` keeps its place in the merge preconditions (`post-selection-loop.md`).
16
+ Pass stored scope baseline to each helper; require `open - contract exception: <why>` without a contract-changing commit, preserve independent blockers and pause through Contract decision. Apply Wave's unsafe-residue stop.
17
17
 
18
- For a selected or retained main-menu `merge base`, run row 2 after the local check even when the merge is clean and only BEHIND remains; review its delta before push. On a conflict without a covered strategy, the conflict menu replaces the step. Each row carries its precondition in its consequence clause; a row whose precondition fails is omitted and named in the compose line's `Not offered:` sentence (`decision-menu.md`):
18
+ Review rewritten replay with `git range-diff origin/<baseRefName> <pre_head> HEAD` plus `git diff <pre_head> HEAD -- <resolved files>`; retain wave P#/L# closure IDs. Record pushed_head_after_replay as the rewritten counterpart of the assessed pushed head during replay, distinct from later unpushed wave commits. For wave commits use `git diff <pushed_head_after_replay> HEAD` as the post-replay pushed-head boundary; before the single combined push review replay and wave deltas separately against their recorded boundaries. Review base merges with `git show --remerge-diff <merge-sha>`. Keep Critical/Moderate findings in the carried repair set before any push.
19
19
 
20
- 1. `update branch` - `gh pr update-branch <N>`; only when no wave commit exists and `can_update_branch` is true and the sync record is not `rebased locally`. Afterwards run `git -C <worktree> fetch origin refs/pull/<N>/head && git -C <worktree> merge --ff-only FETCH_HEAD`; the head-move rule then refreshes the digest and the ledger (re-enter step 3). Report an error from `gh pr update-branch` (a conflict the forge cannot merge, or a 403) verbatim and re-render the menu without that row.
21
- 2. `merge base` - one fresh implementer helper merges the just-fetched `origin/<baseRefName>` into the branch and resolves the conflicts in one commit; the wave then continues with the selected payloads, and that merge commit joins the round's single `## Review before push` - its delta is given under `## Fix delta` as `git show --remerge-diff <merge-sha>` instead of the range diff - and shares the initial pass increment; only when `head_pushable` is true. Pass the stored scope baseline to the helper; require `open - contract exception: <why>` without a commit when resolution changes an agreed obligation or mechanism. Require the helper to restore only its own edits and preserve earlier wave commits; pause for the loop's contract decision. Apply the existing unsafe-residue stop. When picked at the pre-push check, dispatch one fresh reviewer over `git show --remerge-diff <merge-sha>` alone, gate the push on it as in `## Review before push`, and spend another counted pass for a newly required post-review base resolution. When that reviewer reports Critical or Moderate, the commits stay unpushed and `push` is omitted as `push (reviewer blocked the wave)`.
22
- 3. `reply` - post the drafted text "conflicts with <baseRefName>, please update".
23
- 4. `stop` - as `decision-menu.md` `stop`.
24
-
25
- A worktree holding unpushed wave commits never shows `update branch`. The orchestrator resolving a conflict itself is a red flag (`../SKILL.md`).
20
+ On every rewritten-head push use the complete form `git push --no-follow-tags --force-with-lease=<head_ref>:<expected_remote_head> <head_url> HEAD:refs/heads/<head_ref>` (with git -C worktree). Restore on failure: rebase --abort while in progress, otherwise reset --hard pre_head; assert exact HEAD == pre_head and tracked-clean state. A failed assertion hard-stops. A refused lease or remote movement restores and exits `stuck - remote head moved`; other failures restore and exit `stuck - sync: <reason>`. Never retry a refused lease. On success write repair.wave.pushed_head and headRefOid, then Re-render rebinds evidence and carries dispositions. Spend only one push per iteration through Evidence after push.
26
21
 
27
22
  ## Wave
28
23
 
29
- `pre_wave_head` was recorded before the first `## Conflicts` check. A round is one pass over the open payloads plus one `## Review before push`. The round cap is `closureReview.maxFixRounds`, read with `gauntlet_setting({ key: "closureReview" })` when that tool exists, else 3; a cap of `0` omits `fix` and `fix nits` from the menu, named as `fix, fix nits (maxFixRounds is 0)` in the `Not offered:` sentence. The round count is per run; at the cap `fix` and `fix nits` are omitted, named `fix, fix nits (round cap reached)` in `Not offered:`.
24
+ At course pick resolve `gauntlet_setting({ key: "closureReview" })`. Initialize repair.rounds_cap and repair.syncs_cap to `2 x closureReview.maxFixRounds`; use no-tool fallback `2 x 5`. Initialize usage to zero once per run. Cap 0 or missing helpers omits delivery rows. On obstacle deliver add exactly `2 x maxFixRounds` to each cap, persist before work and never reset usage; timeout continuation instead opens one window without extending either cap. Keep closureReview.enforce and the verify-phase phase-tracker's `1 x maxFixRounds` accounting unchanged; refused dispatch exits `stuck - phase-tracker fix-round guard: <its reason>`.
30
25
 
31
- Increment `repair.rounds_used` before the first body write or helper dispatch and persist `repair.wave` with its current stage. Count file, description-only, and conflict-only attempts under the same cap; cap 0 disables every repair target. Initial combined base/file/body work shares one increment and one push. Post-review resolution or completed unsuccessful retry spends another pass; paused-action resumption, context restoration, refresh, and a watch or the `P#` it mints do not increment or reset usage. Stop automatic work at exhaustion; a grant never raises the cap. Update apply/review/push checkpoints and clear the wave only after reconciliation and settlement of any `post-selection-loop.md` `### No-progress obstacle`.
26
+ Record pre_wave_head before work. Initialize one per-pass checkpoint with round_start_head and counted false before payload dispatch, including each carried-set retry; preserve it on interrupted reconciliation, never recreate it for the same attempt. Count a round once only after at least one repair payload dispatch leaves a new source-changing commit, including a source-changing carried-set retry. Persist counted true and counted_head in repair.wave with stage apply/review/push before reconciliation can repeat it; a fresh carried pass keeps prior checkpoint history. Base-only conflict commits, description-only writes, watches, re-reviews, empty waves and restored failures cost no round. Keep usage cumulative across re-entry. At round cap reached or sync cap reached exit through the loop's ordered Exit check; retain commits and name any reviewer's top finding. Update stage and counted checkpoints without replay; clear wave only after reconciliation and settlement of No-progress obstacle. For `action: fix-nits`, retain repair.wave including its action and selected payloads through watch and own-push re-review; clear it only through `post-selection-loop.md` `### Next action`'s one-shot completion rule.
32
27
 
33
28
  For each file payload in review order, dispatch one fresh implementer helper with `cwd` the PR worktree. Its task holds:
34
29
 
35
- - the finding's evidence pack (`findings.md` `## Drafted payloads` shape) and the instruction to apply the consented payload - or, for a reviewer finding that has no drafted payload, the reviewer's finding text - never to re-solve the finding. A payload is the smallest in-repo edit that turns the finding green and keeps the behavior the PR promises; it never adds infrastructure, a dependency, or a new module. A failure caused outside the repo (image pull, toolchain, credentials, external service) gets the smallest in-repo workaround with its reason in one sentence; a skip never removes a test an AC row relies on.
36
- - `SCOPED_TEST_COMMANDS`, resolved once per wave from the merge-base copies of the overrides file, then `AGENTS.md` (`git show "$MB:<path>"`, the trust rule of `assessment.md` `## Configuration`): a scoped runner is a documented test command carrying a `<paths>` or `<files>` placeholder, or a command the doc labels scoped, in any section of either file (`## PR gate`, `## Project`, `## routing`, `## test-driven-development`, `## verification-before-completion` alike); keep its cwd and flags verbatim; with several runners take the one whose directory prefix matches the payload's test file; substitute the test files the payload touches or writes, and for a `P#` carried by a `rebased locally` sync record the failing test file named in that record; no runner or no test file -> `none`. Never the resolved `verification command`, whatever a consumer sentence says about fix waves - that command runs only where `../verification-brief.md` Section B selects a local-run row; `none` -> the helper reports `no scoped test` and the pre-push reviewer is its only gate;
30
+ - the finding's evidence pack (`findings.md` `## Drafted payloads` shape) and the instruction to apply the course-authorized payload - or, for a reviewer finding that has no drafted payload, the reviewer's finding text - never to re-solve the finding. A payload is the smallest in-repo edit that turns the finding green and keeps the behavior the PR promises; it never adds infrastructure, a dependency, or a new module. A failure caused outside the repo (image pull, toolchain, credentials, external service) gets the smallest in-repo workaround with its reason in one sentence; a skip never removes a test an AC row relies on.
31
+ - `SCOPED_TEST_COMMANDS`, resolved once per wave from the merge-base copies of the overrides file, then `AGENTS.md` (`git show "$MB:<path>"`, the trust rule of `assessment.md` `## Configuration`): a scoped runner is a documented test command carrying a `<paths>` or `<files>` placeholder, or a command the doc labels scoped, in any section of either file (`## PR gate`, `## Project`, `## routing`, `## test-driven-development`, `## verification-before-completion` alike); keep its cwd and flags verbatim; with several runners take the one whose directory prefix matches the payload's test file; substitute the test files the payload touches or writes; insert each path made only of `[A-Za-z0-9/._-]` bare; insert any other path single-quoted with embedded `'` escaped as `'\''`; keep each path relative to the runner's working directory (strip the `cd <dir> &&` prefix's directory from the path), and for a `P#` carried by a `rebased locally` sync record the failing test file named in that record; no runner or no test file -> `none`. Never the resolved `verification command`, whatever a consumer sentence says about fix waves - that command runs only where `../verification-brief.md` Section B selects a local-run row; `none` -> the helper reports `no scoped test` and the pre-push reviewer is its only gate;
37
32
  - the commit rule: commit on the PR branch only when the scoped test passes, or when `SCOPED_TEST_COMMANDS` is `none`, one commit per payload, subject naming the fix; otherwise discard its edits (`git checkout -- <its files>`, remove files it created) and return `open - <why>`;
38
33
  - the report shape: the scoped test's failing run before the edit and passing run after it; a doc-only payload with no test reports `no scoped test` and its diff, and the reviewer below is its only gate.
39
34
 
40
- After each helper returns, read `git -C <worktree> status --porcelain` and `git -C <worktree> rev-parse HEAD`. A `done` return shows exactly one new commit and a clean tree; an `open` return shows no new commit and a clean tree. Any other state - staged, unstaged, or untracked residue, or a moved HEAD on `open` - stops the round: dispatch no further implementer, and re-render the menu naming the files and the payload. An `open` payload is never reported as fixed, whatever the reviewer reports. When its scoped test fails and the helper restores its edits, persist the result and failed prerequisite through `post-selection-loop.md` `### No-progress obstacle` before any retry, including carried-set retries. The orchestrator never commits or checks out files, and resets only to `<pre_head>` after a refused lease or a remote head move (`## Evidence after push`, `post-selection-loop.md` `### Compare-and-swap`).
35
+ After each helper returns, read `git -C <worktree> status --porcelain` and `git -C <worktree> rev-parse HEAD`. A `done` return shows exactly one new commit and a clean tree; an `open` return shows no new commit and a clean tree. Any other state - staged, unstaged, or untracked residue, or a moved HEAD on `open` - stops the round: dispatch no further implementer, and re-render the menu naming the files and the payload. An `open` payload is never reported as fixed, whatever the reviewer reports. When its scoped test fails and the helper restores its edits, persist the result and failed prerequisite through `post-selection-loop.md` `### No-progress obstacle` before any retry, including carried-set retries. The orchestrator never commits or checks out files; restrict resets to Sync's recorded pre_head recovery and assert its exact-head/clean postconditions.
41
36
 
42
37
  ## Review before push
43
38
 
44
- When HEAD equals `pre_wave_head` after a round (every payload returned `open`), skip the review and the push; persist the completed empty-round result and failed prerequisites through `post-selection-loop.md` `### No-progress obstacle`, then re-render with the `open` reasons. Link a failed scoped-test return after restored edits to that same owner; do not automatically dispatch the unchanged payload again.
39
+ With no dispatched file payload and no new local commits, skip file review/push and continue to sync/watch; description-only success is not no progress. When HEAD equals `pre_wave_head` after a round with dispatched file payloads (every payload returned `open`), skip the review and the push; persist the completed empty-round result and failed prerequisites through `post-selection-loop.md` `### No-progress obstacle`, then re-render with the `open` reasons. Link a failed scoped-test return after restored edits to that same owner; do not automatically dispatch the unchanged payload again.
45
40
 
46
- After the round's payloads, dispatch one fresh reviewer helper, `cwd` the PR worktree, read-only, over `<pushed_head>..HEAD`, where `pushed_head` is the digest's `headRefOid` (the assessed head), so commits a previous round left unpushed are always inside the reviewed range; `pre_wave_head` serves only the empty-round test. Its task carries the findings whose payloads this wave dispatched, under their `P#`/`L#` ids, under `## Previous review report (re-review trigger)`, and the output of `git diff <pushed_head> HEAD` under `## Fix delta`, and asks for the reviewer's native report plus one closure line per listed finding, accepted anywhere after the findings list: `<finding id>: resolved` or `<finding id>: open - <why>`. When the round holds a `merge base` commit, `## Fix delta` is `git diff <pushed_head> <merge-sha>^1`, then `git show --remerge-diff <merge-sha>`, then `git diff <merge-sha> HEAD`. Ignore a `TRAJECTORY:` line in the report.
41
+ After the round's payloads, dispatch one fresh reviewer helper, `cwd` the PR worktree, read-only, over `<pushed_head>..HEAD`, where `pushed_head` is the digest's `headRefOid` (the assessed head), or Sync's recorded pushed_head_after_replay under a rewrite, so commits a previous round left unpushed are always inside the reviewed range; `pre_wave_head` serves only the empty-round test. Its task carries the findings whose payloads this wave dispatched, under their `P#`/`L#` ids, under `## Previous review report (re-review trigger)`, and the output of `git diff <pushed_head> HEAD` under `## Fix delta`, and asks for the reviewer's native report plus one closure line per listed finding, accepted anywhere after the findings list: `<finding id>: resolved` or `<finding id>: open - <why>`. When the round holds a base-merge sync commit, `## Fix delta` is `git diff <pushed_head> <merge-sha>^1`, then `git show --remerge-diff <merge-sha>`, then `git diff <merge-sha> HEAD`. Ignore a `TRAJECTORY:` line in the report.
47
42
 
48
- Mark a finding resolved only from a `resolved` closure line; the orchestrator never marks one. The carried set is every `open` closure line of this wave's ids, every new Critical or Moderate finding inside `## Fix delta`, and every new Critical finding outside it; a new finding entering the set is minted a `P#` before dispatch and listed under it in the next round's reviewer task. An in-delta Minor translates to a nit (`findings.md` `## Namespaces`); a `[Minor] (outside fix delta)` finding, or an outside-delta finding the report tags Moderate, prints under `show evidence` only. Push only when the carried set holds no Critical or Moderate. Within the active pre-push wave, retry every carried item even under a named grant; the named-ID limit owns later independent reassessments, not this safety course. Do not add carried IDs to `consent.fix` or start unrelated post-push work. For a blocked carried set, dispatch one fresh implementer, serially, per carried item - the payload for an `open` id, the reviewer's finding text for a new finding - one more round, up to the cap. At the cap, the commits stay unpushed on the PR branch; the menu re-renders with the reviewer's verdict line and the carried set's Critical and Moderate titles, and `push` is omitted, named as `push (reviewer blocked the wave)` in the `Not offered:` sentence.
43
+ Mark a finding resolved only from a `resolved` closure line; the orchestrator never marks one. The carried set is every `open` closure line of this wave's ids, every new Critical or Moderate finding inside `## Fix delta`, and every new Critical finding outside it; a new finding entering the set is minted a `P#` before dispatch and listed under it in the next round's reviewer task. An in-delta Minor translates to a nit (`findings.md` `## Namespaces`); a `[Minor] (outside fix delta)` finding, or an outside-delta finding the report tags Moderate, prints under `show evidence` only. Push only when the carried set holds no Critical or Moderate. Within the active pre-push wave, retry every carried item through a fresh implementer, serially, with its payload or new finding text. Charge a source-changing carried-set retry once through Wave. Return to pre-push review without a menu or push. At exhaustion keep commits unpushed, exit through the cap with the reviewer's top finding and the Critical/Moderate carried titles.
49
44
 
50
45
  ## Evidence after push
51
46
 
52
- Run `## Conflicts` once more, then push to the recorded destination: `git -C <worktree> push --no-follow-tags <head_url> HEAD:refs/heads/<head_ref>`. On a `rebased locally` head push only when every carried `P#` has a `resolved` closure line, with `--force-with-lease=<head_ref>:<pre_head>`; otherwise the commits stay unpushed and the menu re-renders with the open `P#`. A refusal naming the lease (`stale info`): `git reset --hard <pre_head>`, assert `HEAD == <pre_head>` and a clean tree; a mismatch is a hard stop with the error, record `sync: failed - push rejected: <first server line>`, and render `stop` and `show evidence` only - the fix commits are discarded with the rewrite, and the reason says so. After a successful lease push rewrite the sync record to the plain `rebased ...` form. Report any other rejected push verbatim and re-render the menu with the commits unpushed.
47
+ Run the local merge-tree check once more; trigger `## Sync` only for its defined failures. After review passes, push once to the recorded destination: `git -C <worktree> push --no-follow-tags <head_url> HEAD:refs/heads/<head_ref>`, using Sync's complete lease form for every rewritten head. On a locally rewritten head push only when every carried `P#` has a `resolved` closure line. For loop rewrites, freshly compare the remote to `repair.wave.sync.expected_remote_head` and lease that SHA; recover to `repair.wave.sync.pre_head` on refusal or movement through Sync's restore/assert obstacle. For an entry-time `rebased locally` record without a loop checkpoint, keep sync.md's own pre_head for both comparison/lease and recovery (`--force-with-lease=<head_ref>:<pre_head>`); its baseline requires local/remote equality. Other push failures exit with the actual reason and commits unpushed. Only for an entry-time `rebased locally` record, rewrite that entry sync record to plain rebased form after a successful lease push. For loop rewrites, settle only repair.wave.sync through Sync's successful push/rebinding rule; leave the entry sync record unchanged.
48
+
49
+ On a newly successful push persist repair.wave.pushed_head and digest headRefOid, and renew an existing poll deadline only for this gate's own push. The loop runs the pre-menu refresh as `post-selection-loop.md` `### Re-render` step 1, using the verifier's binding classification. No claim re-check and no whole-wave review runs after an own push except the affected-claims-only description recheck; preserve unrelated dispositions. Pending to CI-sufficient dispatches a fresh Section B helper in claim-check-only mode for pending claims, with a disposition for every material claim. Local-run rows dispatch the separate Section B helper and record `local run: no conclusive check`, `local run: held run not approvable`, or `local run: local verification: always`.
53
50
 
54
- On a newly successful push, persist `repair.wave.pushed_head`, run no poll (the one `gh pr view` and `gh run list` read belongs to the refresh), and re-enter step 5 through `post-selection-loop.md` `### Re-render` step 1 (the pre-menu refresh runs once; its watch guard decides, by the brief's binding classification, whether a watch follows). No claim re-check and no whole-wave review runs after an own push; the description recheck of `### Description repair` is the one exception. When the refresh moves the evidence from `source: pending` to CI-sufficient, dispatch a fresh Section B helper in claim-check-only mode (`../verification-brief.md` Section B) before rendering the menu. `approve workflow run` executes `gh api --method POST repos/<base-owner>/<base-repo>/actions/runs/<id>/approve`, then re-enters the refresh; an `action_required` run renders `approve workflow run` before any watch, and `### Re-render` reconciliation is unchanged. When a local-run row fires, dispatch a fresh Section B helper; the evidence block records which trigger fired: `local run: no conclusive check`, `local run: held run not approvable`, or `local run: local verification: always`.
51
+ For failing CI gather `gh run view <id> -R <repo> --log-failed`. With neither failed log nor source lead request one run re-run inside the wave. Compare repeated-red observations against pushed repair-wave source commits, not arbitrary SHA movement; the loop's red-twice obstacle owns the result. Helpers never disposition checks. `approve workflow run` executes `gh api --method POST repos/<base-owner>/<base-repo>/actions/runs/<id>/approve` only under the loop/terminal permission check, then refreshes and watches under the course.
55
52
 
56
- Then run the own-push sequence of `post-selection-loop.md` `### Re-render`: the comment refetch and reconciliation, a fresh reviewer helper for any eligible comment delta, and the menu rendered from the closure lines for the exact pushed SHA. No claim re-check and no whole-wave review runs after an own push except the affected-claims-only description recheck in `post-selection-loop.md` `### Description repair`; preserve unrelated dispositions. The sequence runs the pre-menu refresh as `post-selection-loop.md` `### Re-render` step 1 (`### Pre-menu refresh`) - an approval dismissed by the push flips a bound viewer to `BLOCKED`, and the menu says so.
53
+ Return through the fixed delivery body: bounded watch at step 4, own-push Re-render comment reconciliation and any eligible source review, then Exit check. Never run an independent watch or render a mid-course menu.
@@ -0,0 +1,80 @@
1
+ # gatekeep-pr: menu templates
2
+
3
+ Read with `decision-menu.md` at step 6. Select the template from this state map; terminal state takes precedence over a pending contract exception, which takes precedence over course execution.
4
+
5
+ | State | Template |
6
+ |---|---|
7
+ | this run's merge returned MERGED | own merge |
8
+ | this run's merge returned another state | merge queued |
9
+ | approved_head equals assessed head | approved |
10
+ | PR state no longer OPEN | closed PR |
11
+ | pending contract exception | contract exception |
12
+ | readiness exit, including first report ready with course unset | final |
13
+ | obstacle or cap exit, including course unset with undispositionable BLOCKED | obstacle |
14
+ | assess completed | assess terminal |
15
+ | first report not ready, course unset | initial |
16
+ | multi-PR parent round | batch summary |
17
+
18
+ Render every menu in full from its template: the verdict line, every available row, and the hint line. Never narrate or summarize a menu; do not substitute "the initial template applied; the human picked 2" for its rendering. Quote a numeric pick only after the rendered menu.
19
+
20
+ Render flat numbers through [menu-layout](../../brainstorming/reference/menu-layout.md); preserve its bare-numeric mapping. Put exactly one `[recommended]` on an available row and `stop` last. Put `show evidence` in every hint line, never in a row. Include every omission reason from `decision-menu.md`. A menu rendered under `deliver` before an exit condition, other than the contract-exception template, is a red flag; so is a menu not rendered from these templates, two recommendations, stop not last, or no evidence hint.
21
+
22
+ Render terminal cleanup consequences through `cleanup.md`; use `skipped - needs a landed change` for merge-SHA-dependent overrides in the row text and result block.
23
+
24
+ Use `viewer.is_author` (`viewer.login == pr.author.login`) and `author_is_bot` for defaults. Treat a bot-authored head as someone else's throughout. Keep `merge policy` (`squash | merge-commit | rebase`) solely as the `gh pr merge` flag selector, never approval or sync strategy. Treat `approve (round 1) / approve (round 2)` as spec approval history, not menu configuration.
25
+
26
+ ## initial
27
+
28
+ Offer `assess`, eligible `deliver`, eligible `deliver with nits`, then `stop`. Recommend deliver for the human's own PR, assess for someone else's; when delivery is omitted on an own PR, recommend assess. Omit deliver with nits with zero nits, and both delivery rows with cap zero, missing helpers, or non-pushable/unreadable head; name each reason, including `Not offered: deliver (needs push to <head_url>:<head_ref>)`. For someone else's/bot PR change the sync consequence to "merge main into the branch". Map `deliver with nits` to stored `deliver-nits`.
29
+
30
+ ```text
31
+ Verdict: not ready - 2 blockers, 1 nit
32
+
33
+ Pick one:
34
+ 1. assess - post the review to the PR; then approve or stop, no repairs.
35
+ 2. deliver - repair blockers, rebase on main, watch CI, repeat until merge is offered. [recommended]
36
+ 3. deliver with nits - deliver, and fold the 1 nit into the repairs.
37
+ 4. stop - remove the worktree, leave the PR as is.
38
+
39
+ Course holds until the final menu; `show evidence` prints the full record.
40
+ ```
41
+
42
+ ## final
43
+
44
+ Offer available `merge`, `approve`, `fix nits` when nits remain and the course excluded them, `reply` when drafted replies exist, then `stop`. Recommend merge for own PR; recommend approve for someone else's when available, else merge; with neither available recommend stop. Omit merge on a draft with `Not offered: merge (draft)` or below WRITE with `Not offered: merge (needs WRITE on <owner>/<repo>)`. Omit approve on drafts and own PRs. Authorize exactly one delivery iteration for fix nits, leaving course unchanged (`post-selection-loop.md` `### Next action`). Hint: `Type a number; show evidence prints the full record.` Print one compact `Evidence:` line above the `Delivers:` line naming the source (`CI (source: ci)` or the local run), the head SHA, each run and its checks with their conclusions, the binding-pending count, and `merge_state_status`; put the same record in the `show evidence` hint's parenthetical.
45
+
46
+ ## assess terminal
47
+
48
+ Offer available `deliver` (switch course), `approve` only on someone else's non-draft PR with zero blockers, `reply` for drafted thread replies, then `stop` `[recommended]`. Hint: `Type a number; show evidence prints the full record.`
49
+
50
+ ## obstacle
51
+
52
+ For undispositionable BLOCKED with no local repair, use this template even with course unset: keep stop last and `[recommended]`, with no wait row, no sync promise, and no approve row. Describe eligible deliver only as retrying after the failed prerequisite changes; promise no automatic sync. Reserve the course-unset initial assess/deliver/stop template for a pending-only report when its sole withhold is a not-yet-resolved binding CI check; source blockers still use initial under the state map.
53
+
54
+ Print the concrete obstacle and retained failed prerequisites. Keep the `Delivers:` line in the obstacle report. Offer eligible `deliver`, `approve workflow run` only for the held-run permission case, `assess` (switch course), then `stop` `[recommended]`. Feed the `Evidence:` line from the verifier record's resolved source, never from the raw check or run rollup - render a `source: pending` record as `Evidence: pending (<binding check> binding; head <sha>)`, and when `course` is unset render the deliver row as `deliver - set course to deliver; <one clause naming what retries>, no round charged` instead of `resume the retained course`. Render ordinary obstacle continuation as `deliver - resume the retained course; <one clause naming what retries>, no round charged`. For every non-timeout deliver selection, add exactly `2 x maxFixRounds` to both caps and persist before work, preserving usage. Append `add <n> to each cap` (`+2 x maxFixRounds`, with cumulative usage/caps) to the row even below both caps. On timeout describe exactly one additional watch window instead, with neither cap extended. An unchanged prerequisite re-exits even after extension. Print exactly one `Evidence:` line before the verdict, naming the resolved evidence source and assessed head per `report.md` (for example `Evidence: CI (run 7001, test_unit and build_image green on 9c1e4f2)`). Include `check <name> flaky | real | ci-broken` in the hint for failing checks; flaky followed by a numeric deliver selection can resume. Hint: `Type a number; show evidence prints the full record (<source summary>)`. Replace `<source summary>` with the same source summary used in `Evidence:`, including the CI run, check outcomes, and assessed head when CI is the source.
55
+
56
+ ## contract exception
57
+
58
+ Print the concrete proposed contract change before the rows. Offer `accept contract change`, `preserve contract` `[recommended]`, then `stop`. Render preserve's consequence as "reject the proposal, keep <named requirement>, and resume authorized implementation repair within it". Route through `post-selection-loop.md` `### Contract decision`; show evidence settles nothing. Hint: `Type a number; show evidence prints the full record.`
59
+
60
+ ## own merge
61
+
62
+ After this run's merge returns `MERGED`, offer `cleanup` `[recommended]` until attempted, `post coverage to ticket` when issue and scope rows exist, `post coverage to PR`, then `stop`. Suppress each successful post for the rest of the run; a failed post keeps its row and prints the error. A post re-renders this template, never tears down or ends the run, and runs no compare-and-swap. After cleanup removes its row recommend the first available post, else stop. Hint: `Type a number; show evidence prints the full record.`
63
+
64
+ ## merge queued
65
+
66
+ After this run's merge returns a state other than `MERGED`, offer `wait` - re-run `gh pr view <N> --json state,mergeCommit` `[recommended]`, then `stop`. Render `The merge is queued, so coverage posts wait. (<state>)`. A MERGED read records pr.state and pr.merge_commit and renders own merge; OPEN or another state keeps this template. Run no CI watch. Hint: `Type a number; show evidence prints the full record.`
67
+
68
+ ## approved
69
+
70
+ When `approved_head` equals the assessed head, offer `cleanup` `[recommended]` until attempted, then `stop`; recommend stop after cleanup. Hint: `Type a number; show evidence prints the full record.`
71
+
72
+ ## closed PR
73
+
74
+ When state is not OPEN at step 1 or mid-course, offer `cleanup` `[recommended]` until attempted, then `stop`; recommend stop after cleanup. Keep this distinct from this run's own merge. Hint: `Type a number; show evidence prints the full record.`
75
+
76
+ ## batch summary
77
+
78
+ For a recovered `CONFLICTING` child, offer only safe `retry` and `stop`, with `show evidence` hint-only. Render `retry - re-dispatch this PR's assessment with the same record` `[recommended]` only after `batch.md`'s clean-recovery and no-rebase checks pass; otherwise omit retry with its recorded reason and recommend stop. Keep stop last; offer no update branch or merge base rows. Hint: `Type a number; show evidence prints the full record.`
79
+
80
+ Let the `batch.md` parent render its current summary rows and each child's template from this file. Keep terminal posts and queued-merge wait parent-owned, including after worktree removal; suppress successful posts and retain failed posts. Preserve the parent's row consequences and admission/queue semantics. Put stop last and exactly one recommendation. Hint: `Type a number; show evidence prints the full record.`