pi-gauntlet 7.3.2 → 7.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## v7.5.0 - 2026-10-06
4
+
5
+ - `gatekeep-pr` classifies every pending check as binding or not from GitHub's viewer-relative `mergeStateStatus`, refreshed before every menu (`### Pre-menu refresh`): a pending commit status or review requirement that does not bind the gating actor no longer withholds `merge` or recommends `wait`; a `BLOCKED`, `UNKNOWN`, or unreadable merge state, and a check whose Actions run is still live, still withhold. No new settings or overrides key. New `eval/gatekeep-pr-merge-state/` target with six digest fixtures and a recorded baseline/candidate run.
6
+
7
+ ## v7.4.0 - 2026-10-06
8
+
9
+ ### Added
10
+
11
+ - **gatekeep-pr `--rebase [base]`.** An opt-in step 2b rebases the PR head onto the fetched base before verification, hands each rebase stop to a fresh implementer, has a fresh reviewer gate every rewritten head, lease-pushes with `--force-with-lease=<head_ref>:<pre_head>`, and verifies the pushed head. Another author's branch asks before the rewrite; a non-pushable head, a missing base, a harness without helpers, or a moved remote head stops the run; without the flag nothing changes. New `eval/gatekeep-pr/` target with seven samples and two recorded runs. (#58)
12
+
3
13
  ## v7.3.2 - 2026-10-06
4
14
 
5
15
  ### Changed
package/README.md CHANGED
@@ -69,7 +69,7 @@ Everything between gate 1 and gate 2 - task breakdown, implementation, both revi
69
69
 
70
70
  pi-gauntlet ships three kinds of pieces, layered on top of pi-cohort's dispatch:
71
71
 
72
- - **20 skills** - the workflow logic. Twelve activate automatically when pi sees the matching kind of task, and the workflow skills chain through their gates: `writing-plans`, `roasting-the-spec`, `test-driven-development`, `subagent-driven-development`, `dispatching-parallel-agents`, `verification-before-completion`, `requesting-code-review`, `receiving-code-review`, `using-git-worktrees`, `finishing-a-development-branch`, `forge-skill`, `linear` (reads/searches/comments on/manages Linear tickets via the `linearis` CLI; owns all linearis mechanics and the `## Issue tracker` overrides schema; tracker-facing skills route to it). Eight more require explicit entry (`disable-model-invocation: true`): `brainstorming` starts the gated workflow on explicit consent - use `/skill:brainstorming` for reliable discovery, or opt in through reachable explicit prose or a human-selected handoff; after spec approval, downstream planning and implementation still chain automatically. `shape-ticket` creates or repairs one tracker issue per run against a Context/Problem/Idea/Acceptance-Criteria template, gated by an AC integrity check, a cheap council roast, and a single human-confirmed write - run it with `/skill:shape-ticket`. `gatekeep-pr` is consent-gated pre-merge verification of a PR against its issue's acceptance criteria - read-only gathering, verification evidence resolved CI-first (green checks on the exact assessed head count as evidence; the project's verification command runs only as fallback), a rubric-based review, a compact bottom-up report (what the PR delivers, ticket changes, PR comments, nits, blockers, then a `mergeable` / `fixable` verdict) and a verb menu whose rows derive from the actor's permissions on the PR; nothing mutates (fixes, pushes, reviews, merges, tracker comments) until you pick a row - run it with `/skill:gatekeep-pr <pr>`. `check-delivery` is a post-merge detective control: proves an issue actually shipped (default-branch landing, delivery target, per-AC evidence) before its tracker status advances; it never writes a terminal status - run it with `/skill:check-delivery <ref>`. `chase-bug` is human-only bug triage: read-only root-cause discovery to an evidenced verdict menu (real bug -> ticket/brainstorm/hotfix/respond; five negative verdicts), then a gated response to the reporter for addressable origins (GitHub issue / tracker ticket) and a rendered verdict summary otherwise - it never fixes during triage; the hotfix row hands off to `skills/chase-bug/hotfix.md` after the menu - run it with `/skill:chase-bug`. `gauntlet-performance` reads the committed run telemetry (current repo by default, `--dir <path>` adds others) through the parse-only `gauntlet-performance` CLI and answers with one example-led recommendation, 3-5 cornerstone numbers, a council assessment when available, and a menu of at most three actions (render a report file, open the recommendation as a ticket via `shape-ticket`, drill into one run); it writes nothing unless you pick render - run it with `/skill:gauntlet-performance [--dir <path>]... [--since <version>]`. `gauntlet-handoff` ends a session whose flow a fresh session will continue: it invokes pi-cohort's `handoff` skill (`--out <path>` or `--key <name>`, default key = the run worktree's branch with `/` flattened to `-`, default file `<tmpdir>/pi-handoff/<key>.md`), then appends the gauntlet process-state section (phase/plan tracker status) per the shared contract `skills/gauntlet-resume/reference/brief-contract.md` - run it with `/skill:gauntlet-handoff [--out <path> | --key <name>]`. `gauntlet-resume` is the only way back into an interrupted flow from a fresh session: it takes a `gauntlet-handoff` brief (a file path, pasted text, or - with no arguments - a pick from the briefs under `<tmpdir>/pi-handoff/`), a bare worktree that already holds a spec, or a spec tracked on main under a configured spec dir (a spec seed: it creates or reuses the worktree through `using-git-worktrees` and pins the spec), restores phase/plan tracker state through the legal arming sequence (`start brainstorm`, `skip` with `resume:` reasons, `plan_check` before implement-or-later), and never infers approval from artifacts - run it with `/skill:gauntlet-resume [<brief-file> | <spec>.md | <worktree-name-or-path>]`.
72
+ - **20 skills** - the workflow logic. Twelve activate automatically when pi sees the matching kind of task, and the workflow skills chain through their gates: `writing-plans`, `roasting-the-spec`, `test-driven-development`, `subagent-driven-development`, `dispatching-parallel-agents`, `verification-before-completion`, `requesting-code-review`, `receiving-code-review`, `using-git-worktrees`, `finishing-a-development-branch`, `forge-skill`, `linear` (reads/searches/comments on/manages Linear tickets via the `linearis` CLI; owns all linearis mechanics and the `## Issue tracker` overrides schema; tracker-facing skills route to it). Eight more require explicit entry (`disable-model-invocation: true`): `brainstorming` starts the gated workflow on explicit consent - use `/skill:brainstorming` for reliable discovery, or opt in through reachable explicit prose or a human-selected handoff; after spec approval, downstream planning and implementation still chain automatically. `shape-ticket` creates or repairs one tracker issue per run against a Context/Problem/Idea/Acceptance-Criteria template, gated by an AC integrity check, a cheap council roast, and a single human-confirmed write - run it with `/skill:shape-ticket`. `gatekeep-pr` is consent-gated pre-merge verification of a PR against its issue's acceptance criteria - read-only gathering, verification evidence resolved CI-first (green checks on the exact assessed head count as evidence; a pending commit status that GitHub does not report as blocking the gating actor (`mergeStateStatus` not `BLOCKED`) is not waited for - external-CI commit statuses included - while a running Actions check, an unknown merge state, and every other merge precondition still withhold; the project's verification command runs only as fallback), a rubric-based review, a compact bottom-up report (what the PR delivers, ticket changes, PR comments, nits, blockers, then a `mergeable` / `fixable` verdict) and a verb menu whose rows derive from the actor's permissions on the PR; nothing mutates (fixes, pushes, reviews, merges, tracker comments) until you pick a row, except the opt-in `--rebase [base]` sync that rebases, reviews, and lease-pushes the head before verification - run it with `/skill:gatekeep-pr <pr> [--rebase [base]]`. `check-delivery` is a post-merge detective control: proves an issue actually shipped (default-branch landing, delivery target, per-AC evidence) before its tracker status advances; it never writes a terminal status - run it with `/skill:check-delivery <ref>`. `chase-bug` is human-only bug triage: read-only root-cause discovery to an evidenced verdict menu (real bug -> ticket/brainstorm/hotfix/respond; five negative verdicts), then a gated response to the reporter for addressable origins (GitHub issue / tracker ticket) and a rendered verdict summary otherwise - it never fixes during triage; the hotfix row hands off to `skills/chase-bug/hotfix.md` after the menu - run it with `/skill:chase-bug`. `gauntlet-performance` reads the committed run telemetry (current repo by default, `--dir <path>` adds others) through the parse-only `gauntlet-performance` CLI and answers with one example-led recommendation, 3-5 cornerstone numbers, a council assessment when available, and a menu of at most three actions (render a report file, open the recommendation as a ticket via `shape-ticket`, drill into one run); it writes nothing unless you pick render - run it with `/skill:gauntlet-performance [--dir <path>]... [--since <version>]`. `gauntlet-handoff` ends a session whose flow a fresh session will continue: it invokes pi-cohort's `handoff` skill (`--out <path>` or `--key <name>`, default key = the run worktree's branch with `/` flattened to `-`, default file `<tmpdir>/pi-handoff/<key>.md`), then appends the gauntlet process-state section (phase/plan tracker status) per the shared contract `skills/gauntlet-resume/reference/brief-contract.md` - run it with `/skill:gauntlet-handoff [--out <path> | --key <name>]`. `gauntlet-resume` is the only way back into an interrupted flow from a fresh session: it takes a `gauntlet-handoff` brief (a file path, pasted text, or - with no arguments - a pick from the briefs under `<tmpdir>/pi-handoff/`), a bare worktree that already holds a spec, or a spec tracked on main under a configured spec dir (a spec seed: it creates or reuses the worktree through `using-git-worktrees` and pins the spec), restores phase/plan tracker state through the legal arming sequence (`start brainstorm`, `skip` with `resume:` reasons, `plan_check` before implement-or-later), and never infers approval from artifacts - run it with `/skill:gauntlet-resume [<brief-file> | <spec>.md | <worktree-name-or-path>]`.
73
73
  - **7 subagent personas** - the specialized child agents the skills dispatch via pi-cohort: `implementer`, `code-reviewer`, `spec-reviewer`, `conformance-reviewer`, `spec-summarizer`, `spec-council-member`, `spec-council-synthesizer`. See [doc/personas.md](./doc/personas.md) for what each one does and why its permissions are scoped the way they are.
74
74
  - **4 runtime extensions** - the enforcement layer. `plan-tracker` and `phase-tracker` are tools skills call to track progress (with a TUI widget); `verify-before-ship` is a hook that warns if you push or open a PR without a passing test run since your last edit; a phase-tracker flow guard reminds on implement-phase commits missing spec/code review. In a brainstorming-entered flow, phase-tracker rejects `implement` or `verify` completion while tracker tasks remain pending or in progress; see [its configuration reference](./doc/configuration.md#phase-tracker). phase-tracker also registers `plan_check`, which verifies a plan against its spec and against the grammar in [skills/writing-plans/reference/plan-contract.md](./skills/writing-plans/reference/plan-contract.md), including that each task's `Tests:` commands are selective and never the full suite; a pass stamps the plan for implementation. `telemetry` records one YAML record per gauntlet run (phase timing, models, personas, gate/fix rounds, diff at ship) that ships in the squash beside the spec - it is written only inside a brainstorming-entered flow, kept out of history until `finishing-a-development-branch` seals it once with `gauntlet-telemetry-seal` (a `telemetry:` commit under git; the working-copy snapshot in a plain jj workspace) - and blocks a brainstorm `write` into an already-shipped spec; see [its configuration reference](./doc/configuration.md#telemetry). See [doc/configuration.md](./doc/configuration.md) for the settings each one reads.
75
75
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-gauntlet",
3
- "version": "7.3.2",
3
+ "version": "7.5.0",
4
4
  "description": "Opinionated, gated workflow skills, subagent personas, and runtime extensions for the pi coding agent.",
5
5
  "author": "Jacek Juraszek",
6
6
  "type": "module",
@@ -1,18 +1,19 @@
1
1
  ---
2
2
  name: gatekeep-pr
3
- description: Use when gating a pull request before it merges - whether you authored it or are reviewing someone else's work. Judges whether the diff delivers the mechanism each acceptance criterion needs (code, tests, docs) and conforms to the review rubric; what can be seen only after merge belongs to /skill:check-delivery. Consent-gated - every mutation (fixes, pushes, reviews, merges, tracker comments) waits for an explicit menu pick.
3
+ description: Use when gating a pull request before it merges - whether you authored it or are reviewing someone else's work. Judges whether the diff delivers the mechanism each acceptance criterion needs (code, tests, docs) and conforms to the review rubric; what can be seen only after merge belongs to /skill:check-delivery. Consent-gated - every mutation (fixes, pushes, reviews, merges, tracker comments) waits for an explicit menu pick, except the sync `--rebase` asks for.
4
4
  disable-model-invocation: true
5
- argument-hint: "<pr> [issue-ref] (e.g. 123, or 123 gh-45)"
5
+ argument-hint: "<pr> [issue-ref] [--rebase [base]] (e.g. 123, 123 gh-45, 123 --rebase, 123 gh-45 --rebase release/2)"
6
6
  ---
7
7
 
8
8
  # gatekeep-pr
9
9
 
10
- Verify, don't trust: a PR description is a claim, and over-claimed coverage, hallucinated references, and "tests pass" that never ran are the normal case on generated code. Judge the mechanism only - whether code, tests, and docs at the assessed head make each promised behavior possible - and leave what can be seen only after merge (a staging run, production data, an external system) to `/skill:check-delivery`. Nothing mutates before a menu pick: steps 1-5 read, provision a worktree, and draft; fixes, pushes, reviews, comments, merges, and tracker writes happen only on explicit selection. Running the verification command executes PR code with your ambient credentials and no sandbox - gate only PRs you are willing to execute.
10
+ Verify, don't trust: a PR description is a claim, and over-claimed coverage, hallucinated references, and "tests pass" that never ran are the normal case on generated code. Judge the mechanism only - whether code, tests, and docs at the assessed head make each promised behavior possible - and leave what can be seen only after merge (a staging run, production data, an external system) to `/skill:check-delivery`. Nothing mutates before a menu pick except the `--rebase` sync: steps 1-5 read, provision a worktree, run the `--rebase` sync when asked, and draft; fixes, pushes, reviews, comments, merges, and tracker writes happen only on explicit selection or, for the sync's rebase and lease push, on `--rebase`. Running the verification command executes PR code with your ambient credentials and no sandbox - gate only PRs you are willing to execute.
11
11
 
12
12
  ## Arguments
13
13
 
14
14
  - PR number or URL; omitted -> resolved from the current branch per `verification-brief.md` Section A.
15
15
  - Optional issue reference; omitted -> resolved in step 1 (`verification-brief.md` Section A) and fetched in step 2; none found -> judge the PR's stated intent only, never invent acceptance criteria.
16
+ - Optional `--rebase [base]`, parsed after `<pr> [issue-ref]`: the token after it, when present, is the base branch name (`123 --rebase gh-45` means base `gh-45`, not an issue ref); absent, the base is the digest's `baseRefName`. Step 2b fetches `origin/<base>` and validates it: a missing ref stops with a one-line error before any rebase or push. Without `--rebase` the run performs no additional fetch, rebase, or push before a menu pick and renders no sync line.
16
17
 
17
18
  ## Steps
18
19
 
@@ -22,21 +23,22 @@ Verify (step 3) and Review (step 4) each run as a fresh helper with `cwd` the PR
22
23
  |---|---|---|---|
23
24
  | 1 Gather | `verification-brief.md` Section A | PR digest: metadata, diff, checks, comments, worktree discovery | `gather` |
24
25
  | 2 Provision + configure | `reference/assessment.md` | provisioned worktree; config resolved from the merge-base; ticket fetched; AC rows extracted per `../brainstorming/reference/ticket-acceptance.md` | `provision` |
26
+ | 2b Sync (only with `--rebase`) | `reference/sync.md` | the PR worktree on the rebased, pushed head with a refreshed digest, or a `sync` record and the unsynced head | `sync` |
25
27
  | 3 Verify | `verification-brief.md` Section B | evidence record (CI-first) and one disposition per material claim | `verify` |
26
28
  | 4 Review | `verification-brief.md` Section C | reviewer findings and the internal comment ledger | `review` |
27
- | 5 Integrate + report | `reference/findings.md`, then `reference/report.md` | AC outcomes, blockers, nits, drafted payloads, the rendered report | `report` |
29
+ | 5 Integrate + report | `reference/post-selection-loop.md` `### Pre-menu refresh`, then `reference/findings.md`, then `reference/report.md` | AC outcomes, blockers, nits, drafted payloads, the rendered report | `report` |
28
30
  | 6 Menu | `reference/decision-menu.md` | the menu under the verdict line, exactly one `[recommended]` | `menu` |
29
31
  | 7 Loop | `reference/post-selection-loop.md` (a `fix` pick continues in `reference/fix-wave.md`) | the executed pick; re-entry until `merge` or `stop` | (re-opens the re-entered stage) |
30
32
 
31
- Re-entry: a fix wave's own push re-enters step 4 through the own-push sequence (`reference/fix-wave.md` `## Evidence after push`, then `reference/post-selection-loop.md` `### Re-render`); any other head move re-enters step 3; an unchanged head re-enters step 5.
33
+ Re-entry: a fix wave's own push re-enters step 4 through the own-push sequence (`reference/fix-wave.md` `## Evidence after push`, then `reference/post-selection-loop.md` `### Re-render`); any other head move re-enters step 3; an unchanged head re-enters step 5 through `reference/post-selection-loop.md` `### Pre-menu refresh`. A loop re-entry never re-runs step 2b; the sync runs once per invocation.
32
34
 
33
35
  ## Progress tracking
34
36
 
35
- Use `plan_tracker`, never `phase_tracker`. `init` with the first three stages of the table. In step 3 `add` one task per material claim and record each verdict: `complete` matched, `failed` contradicted, `skipped` unverifiable-pre-merge. Once every claim is terminal and `verify` is closed, `add` the remaining three stages - the tracker rejects a verdict recorded behind a still-pending stage. A failed stage or claim stays `failed` while the skill stops at the menu. Without a `plan_tracker` tool, keep a plain checklist; behavior is unchanged.
37
+ Use `plan_tracker`, never `phase_tracker`. `init` with the first three stages of the table, four when `--rebase` is present (`gather, provision, sync, verify, ...`); `reference/sync.md` closes `sync` before step 3 starts - `complete` after a push or a `no-op`, `skipped` when step 3 runs unsynced, `failed` on a terminal stop. In step 3 `add` one task per material claim and record each verdict: `complete` matched, `failed` contradicted, `skipped` unverifiable-pre-merge. Once every claim is terminal and `verify` is closed, `add` the remaining three stages - the tracker rejects a verdict recorded behind a still-pending stage. A failed stage or claim stays `failed` while the skill stops at the menu. Without a `plan_tracker` tool, keep a plain checklist; behavior is unchanged.
36
38
 
37
39
  ## Harness notes
38
40
 
39
- Every helper is a fresh context with `cwd` the PR worktree; its task names its output path and ends with the output contract. Every dispatch on either harness gets its own output path from `mktemp "${TMPDIR:-/tmp}/gatekeep-<role>.XXXXXX"` outside the worktree. On pi: `subagent({ agent: <persona>, context: "fresh", async: false, cwd: <worktree> })` with `worker` for Verify, `code-reviewer` for Review and the pre-push review, `implementer` for payloads; `gauntlet_setting({ key: "closureReview" })` supplies the fix-round cap. On Claude Code: the harness's subagent dispatch tool with the duty text as the prompt - Verify: `verification-brief.md` Section B; Review, pre-push review, and comment-delta review: Section C plus `review-baseline.md` (the pre-push review adds the closure-line contract); implementer: the payload task of `reference/fix-wave.md` `## Wave`; the cap is 3. A Claude Code helper inherits the session's directory, so its prompt opens with the absolute worktree path and the rule that every command runs as `git -C <worktree> ...` or `(cd <worktree> && ...)`, checks that `git -C <worktree> rev-parse HEAD` equals the head its task names (the assessed head for Verify, Review, and the comment-delta reviewer; the current wave HEAD for wave helpers) before working, and names the output path; the orchestrator awaits the tool result before reading the file. A harness with neither facility is unsupported: stop at step 3 and say so.
41
+ Every helper is a fresh context with `cwd` the PR worktree; its task names its output path and ends with the output contract. Every dispatch on either harness gets its own output path from `mktemp "${TMPDIR:-/tmp}/gatekeep-<role>.XXXXXX"` outside the worktree. On pi: `subagent({ agent: <persona>, context: "fresh", async: false, cwd: <worktree> })` with `worker` for Verify, `code-reviewer` for Review and the pre-push review, `implementer` for payloads; `gauntlet_setting({ key: "closureReview" })` supplies the fix-round cap. On Claude Code: the harness's subagent dispatch tool with the duty text as the prompt - Verify: `verification-brief.md` Section B; Review, pre-push review, and comment-delta review: Section C plus `review-baseline.md` (the pre-push review adds the closure-line contract); implementer: the payload task of `reference/fix-wave.md` `## Wave`; sync conflict stop: the task `reference/sync.md` `## Conflict stop` builds; sync review: Section C plus `review-baseline.md` with the `## Review before push` subject, no closure lines; the cap is 3. A Claude Code helper inherits the session's directory, so its prompt opens with the absolute worktree path and the rule that every command runs as `git -C <worktree> ...` or `(cd <worktree> && ...)`, checks that `git -C <worktree> rev-parse HEAD` equals the head its task names (the assessed head for Verify, Review, and the comment-delta reviewer; the current wave HEAD for wave helpers) before working, and names the output path; the orchestrator awaits the tool result before reading the file. A harness with neither facility is unsupported: stop at step 3 and say so - at step 2b, before any sync write, when `--rebase` was passed (`reference/sync.md`).
40
42
 
41
43
  ## Wording rules
42
44
 
@@ -55,19 +57,21 @@ These bind every rendered report, menu, and external payload (review bodies, rep
55
57
 
56
58
  ## Red flags - STOP
57
59
 
58
- - Any mutation (fix, push, review, comment, merge, tracker write) without an explicit menu pick - owner: the intro.
60
+ - Any mutation (fix, push, review, comment, merge, tracker write) without an explicit menu pick or the `--rebase` argument - owner: the intro.
59
61
  - A code or doc edit sitting in the worktree when a menu renders - except helper residue that `reference/fix-wave.md` `## Wave` names in the re-rendered menu - owner: `reference/findings.md` `## Drafted payloads`.
60
62
  - Blocking on an AC's observation half, or on a PR-body claim the gate cannot check - owner: `reference/findings.md` `## AC outcomes`.
61
63
  - `impossible` without all four conditions and a source read this run - owner: `reference/findings.md` `## AC outcomes`.
62
64
  - Deferring behavior the ticket promises to a later PR: it is a blocker or an explicit tracker split, nothing else - owner: `reference/findings.md` `## Namespaces`.
63
65
  - Reading the rubric, the verification command, or any ladder source from the PR's head instead of the base branch's merge-base - owner: `reference/assessment.md` `## Configuration`.
64
66
  - Raw command output, drafted payloads, or internal IDs in the rendered report - owner: `reference/report.md`.
65
- - An open-PR menu, other than the conflict menu, without `fix` while a helper facility exists and the fix-round cap is neither `0` nor reached (a merged or closed PR offers `show evidence` and `stop` only); any menu without `show evidence`, with two `[recommended]`, or with `stop` not last - owner: `reference/decision-menu.md`.
66
- - A local verification run while CI is pending, or a second push inside one round - owner: `reference/fix-wave.md` `## Evidence after push`.
67
+ - An open-PR menu, other than the conflict menu or a `--rebase` terminal stop (`stop` and `show evidence` only, `reference/sync.md`), without `fix` while a helper facility exists and the fix-round cap is neither `0` nor reached (a merged or closed PR offers `show evidence` and `stop` only); any menu without `show evidence`, with two `[recommended]`, or with `stop` not last - owner: `reference/decision-menu.md`.
68
+ - A local verification run while a binding check is pending, or a second push inside one round - owner: `reference/fix-wave.md` `## Evidence after push`.
67
69
  - The orchestrator resolving a merge conflict itself, or editing or committing a tracked file during a fix wave - owner: `reference/fix-wave.md` `## Conflicts` and `## Wave`.
68
70
  - A helper's duty run in the orchestrator's own context - owner: `## Harness notes`.
69
71
  - Approving your own PR - owner: `reference/decision-menu.md` `## Consent table`.
70
- - Merging around an undispositioned failing check or a pending required check - owner: `reference/findings.md` `## Dispositions`.
72
+ - Merging around an undispositioned failing check or a binding pending check, or a `BLOCKED`/`UNKNOWN`/`unreadable` merge state - owner: `reference/findings.md` `## Dispositions`.
73
+ - A menu rendered without the pre-menu refresh - owner: `reference/post-selection-loop.md` `### Pre-menu refresh`.
74
+ - Reading `mergeStateStatus` from anything but a `gh pr view` invocation, attributing a `BLOCKED` to a named rule, or treating a non-`BLOCKED` value as a merge verdict - owner: `verification-brief.md` Section A.
71
75
  - A claim verdict recorded behind a pending tracker stage - owner: `## Progress tracking`.
72
76
 
73
77
  ## Project overrides
@@ -1,6 +1,6 @@
1
1
  # gatekeep-pr: step 2 - provision and configure
2
2
 
3
- Read from SKILL.md step 2. Input: the step-1 digest. Output: a provisioned worktree, the resolved configuration, and the ticket's AC rows. This step is the orchestrator's only mutation before the menu.
3
+ Read from SKILL.md step 2. Input: the step-1 digest. Output: a provisioned worktree, the resolved configuration, and the ticket's AC rows. This step is the orchestrator's only mutation before the menu, besides the `--rebase` sync of step 2b (`sync.md`).
4
4
 
5
5
  ## Provision the worktree
6
6
 
@@ -14,7 +14,7 @@ Record create-vs-reuse; it drives teardown (`post-selection-loop.md` `### Teardo
14
14
 
15
15
  Then set the digest's `permissions.head_pushable` (`../verification-brief.md` Section A): `git -C <worktree> push --dry-run --no-verify --no-follow-tags <head_url> HEAD:refs/heads/<head_ref>` exit 0 is `true`, a non-zero exit is `false`, when `head_url` is `unreadable`, `head_pushable` is `unreadable` and the probe does not run. The probe records observed authorization; a real push the server rejects (branch protection) is reported as that push's failure.
16
16
 
17
- When the digest reported `mergeable: UNKNOWN`, re-poll once (`gh pr view --json mergeable`). Still `UNKNOWN` is not merge-ready; the loop's merge preconditions treat it like `CONFLICTING`.
17
+ When the digest reported `mergeable: UNKNOWN` or `mergeStateStatus: UNKNOWN`, re-poll once (`gh pr view --json mergeable,mergeStateStatus`) and write both values back to the digest (`mergeStateStatus` also to `permissions.merge_state_status`). Still `UNKNOWN` is not merge-ready: the loop's merge preconditions treat `mergeable: UNKNOWN` like `CONFLICTING`, and `merge_state_status: UNKNOWN` makes every pending check binding (`../verification-brief.md` Section B, binding classification) and withholds `merge` as `merge state unknown`.
18
18
 
19
19
  ## Configuration
20
20
 
@@ -20,7 +20,7 @@ Rows are `<n>. <verb> - <consequence>`; every row, `stop` included, carries its
20
20
  - `reply` - post the drafted replies under `PR comments` (`reply <n>` for one)
21
21
  - `propose ticket change` - show the drafted AC text verbatim; post it as a tracker comment on an explicit confirmation reply, via the resolved tracker verb (`gh issue comment`, the overrides' tracker mapping, or `/skill:linear`); re-fetch the ticket first and re-render instead of posting when the AC row text drifted; no write path -> copy-paste block
22
22
  - `show evidence` - print the evidence record, claim and AC outcomes, dispositions, drafted payloads and replies (`report.md` `## What stays out`)
23
- - `wait` - poll the reviewer run, every pending check in the resolved set (required or not), and the comment set, then re-render
23
+ - `wait` - poll the reviewer run, every **binding** pending check in the resolved set (required or not), `mergeStateStatus` while it is `BLOCKED`, `UNKNOWN`, or `unreadable`, and the comment set, then re-render
24
24
  - `stop` - leave the PR as-is
25
25
 
26
26
  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: no `Nits` section, no `fix nits`; 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` and `merge anyway - accept AC<n> as impossible` render as their own rows when `## Availability` allows them, so the rendered-row rule does not apply to them. A composed line never bundles a push-producing verb (`fix`, `push`) 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`).
@@ -45,7 +45,7 @@ Rows derive from the digest's `permissions` block (`../verification-brief.md` Se
45
45
 
46
46
  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]`.
47
47
 
48
- 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 `wait` under the verification-evidence-pending, pending-reviewer, or pending-required-check overlay, else to `propose ticket change` while an `impossible` row is `drafted`, else to `stop`.
48
+ 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 `propose ticket change` while an `impossible` row is `drafted`, else to `stop`.
49
49
 
50
50
  | Author | Verdict | Rows |
51
51
  |---|---|---|
@@ -56,6 +56,14 @@ A `fixable` verdict with zero blockers (a withhold reason) uses the mergeable ro
56
56
 
57
57
  `propose ticket change` joins any row set after the `fix` rows whenever an `impossible` row is `drafted`. `push` joins any row set after `fix` when unpushed fix commits sit in the worktree.
58
58
 
59
+ ## Withhold reason resolver
60
+
61
+ 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:
62
+
63
+ 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.
64
+ 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`.
65
+ 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`.
66
+
59
67
  ## Overlays
60
68
 
61
69
  Overlays modify the consent row; they are never a second offer source. When two apply, the one higher in this table wins the `merge` reason and `[recommended]`.
@@ -65,8 +73,9 @@ Overlays modify the consent row; they are never a second offer source. When two
65
73
  | merged or closed PR | `state` is not `OPEN` | the report renders; the menu is `show evidence` and `stop` only |
66
74
  | 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 and `fix` on their mergeable draft; on your own, `fix` when a blocker exists, else `stop` |
67
75
  | 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 |
68
- | 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 (`Not offered: merge (verification evidence pending)`); consent rows otherwise unchanged |
69
- | CI check | an undispositioned failing check in the resolved set, or a pending required check (`findings.md` `## Dispositions`); a held run is not a pending required 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 pending required check renders one `PR comments` line (`A required check is still pending, so merge waits. (<check name>)`), puts `wait` as row 1 and, when `Blockers` is empty, `[recommended]`, and omits `merge` (`Not offered: merge (required check pending)`); it mints no disposition hint (not dispositionable) |
76
+ | 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 |
77
+ | 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 |
78
+ | 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) |
70
79
  | 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 |
71
80
  | 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`, `show evidence`, `stop` stay; `stop` is `[recommended]` |
72
81
  | impossible AC | an `impossible` row in `drafted` or `proposed` while every other merge prerequisite holds | `merge` is omitted (`Not offered: merge (ticket change pending on AC<n>)`); `merge anyway - accept AC<n> as impossible` renders as its own row when `## Availability` allows it and lifts exactly that withhold - blockers, an unreviewed delta, pending checks, and forge-refused states stay in force |
@@ -52,7 +52,7 @@ Behavior the ticket promises is a blocker or an explicit tracker split - never d
52
52
 
53
53
  ## Dispositions
54
54
 
55
- Any blocking conclusion in the resolved check set (required or not - `../verification-brief.md` Section B, Evidence resolution table) withholds `merge` until the user dispositions it, and mints a `P#` - except the reviewer check, whose exception the brief's Section B defines (one `show evidence` line `reviewer check <name> failed - inert`). An undispositioned failing check is never a target of `fix`; close it by disposition. A pending required check mints nothing and is not dispositionable: merge waits until it turns green, or it converts to a failing check with its own `P#`.
55
+ Any blocking conclusion in the resolved check set (required or not - `../verification-brief.md` Section B, Evidence resolution table) withholds `merge` until the user dispositions it, and mints a `P#` - except the reviewer check, whose exception the brief's Section B defines (one `show evidence` line `reviewer check <name> failed - inert`). An undispositioned failing check is never a target of `fix`; close it by disposition. A **binding** pending check (`../verification-brief.md` Section B, binding classification) mints nothing and is not dispositionable: merge waits until it turns green, or `merge_state_status` leaves `BLOCKED`, or it converts to a failing check with its own `P#`. A not-binding pending check withholds nothing, never renders under `PR comments`, and prints under `show evidence` only, as `<name> pending - not binding this viewer (merge_state_status <value>)`; the `required` field stays gathered and no longer withholds on its own.
56
56
 
57
57
  | Disposition | Annotation on the `P#` |
58
58
  |---|---|
@@ -49,8 +49,8 @@ Mark a finding resolved only from a `resolved` closure line; the orchestrator ne
49
49
 
50
50
  Run `## Conflicts` once more, then push to the recorded destination: `git -C <worktree> push --no-follow-tags <head_url> HEAD:refs/heads/<head_ref>`. Report a rejected push verbatim and re-render the menu with the commits unpushed.
51
51
 
52
- Poll the pushed head only, every 30 seconds for up to `timeout minutes` (default 15): `gh pr view <N> --json headRefOid,statusCheckRollup` and `gh run list -R <base-owner>/<base-repo> -c <head> --json databaseId,status,conclusion`. A moved `headRefOid` stops the poll and takes the head-move rule (re-enter step 3, `post-selection-loop.md` intro). Normalize first: a `CheckRun` is terminal at `status: COMPLETED` with its `conclusion`; a `StatusContext` maps `state` `SUCCESS` to success, `FAILURE` and `ERROR` to blocking, `PENDING` and `EXPECTED` to pending; apply the `ci checks` filter and the reviewer-check exception (`../verification-brief.md` Section B) before the table.
52
+ Poll the pushed head only, every 30 seconds for up to `timeout minutes` (default 15): `gh pr view <N> --json headRefOid,statusCheckRollup,mergeStateStatus` and `gh run list -R <base-owner>/<base-repo> -c <head> --json databaseId,status,conclusion`. A moved `headRefOid` stops the poll and takes the head-move rule (re-enter step 3, `post-selection-loop.md` intro). Normalize first: a `CheckRun` is terminal at `status: COMPLETED` with its `conclusion`; a `StatusContext` maps `state` `SUCCESS` to success, `FAILURE` and `ERROR` to blocking, `PENDING` and `EXPECTED` to pending; apply the `ci checks` filter and the reviewer-check exception (`../verification-brief.md` Section B) before the table, then the binding classification of every pending check on the pushed head's `mergeStateStatus`.
53
53
 
54
54
  Resolve the normalized set through `../verification-brief.md` `## Section B - Verifier` (Evidence resolution) - the same table the first pass uses. When the re-resolve moves the evidence from `source: pending` to CI-sufficient (an approved held run went green, or a `wait` ended), 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-polls. The evidence block records the outcome and, 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`.
55
55
 
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.
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. 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.
@@ -12,14 +12,15 @@ All must hold before `merge` executes; a `merge` pick while one fails is refused
12
12
  - evidence provenance clean (`findings.md` `## Provenance`): `worktree_root`, every `run_cwd`, and `head_sha` match on the local path
13
13
  - `mergeable == MERGEABLE` (`UNKNOWN` after step 2's single re-poll withholds like `CONFLICTING`)
14
14
  - `merge_state_status` is not `BEHIND`; while it is, the menu renders `update branch` (`fix-wave.md` `## Conflicts` row 1, same precondition) beside the consent rows
15
- - no undispositioned failing check in the resolved set, no pending required check
15
+ - no undispositioned failing check in the resolved set, no **binding** pending check (`../verification-brief.md` Section B, binding classification)
16
+ - `merge_state_status` is not `BLOCKED`, `UNKNOWN`, or `unreadable`, and the pre-menu refresh succeeded (`### Pre-menu refresh`); a non-`BLOCKED` value is never a verdict - it only leaves this row satisfied, and a `gh pr merge` refusal is reported verbatim
16
17
  - no retained, unreviewed comment delta (`### Re-render` step 4)
17
18
  - worktree tracked-clean and synced with the remote head (fixes pushed first)
18
19
  - the compare-and-swap below passes
19
20
 
20
21
  ### Compare-and-swap
21
22
 
22
- Before every external write, re-fetch `headRefOid`, `state`, and `mergeable`. Any change since assessment invalidates the current state - re-sync the worktree, re-enter step 3, and re-render the menu. Exception: a pick's own push updates the assessed head to the pushed SHA as part of that pick's execution - this self-inflicted head move does not invalidate the pick; the next compare-and-swap runs against the new head on the next external write. Before a merge executes (plain or `anyway`), run the full comment refetch and reconciliation (`### Re-render` steps 1-5), not just placeholder detection. If the head changed, re-enter step 3 instead. A new or changed-body delta since the consent render aborts the selected merge, plain or `anyway`: show the reconciled report and request a fresh pick even when no blocker resulted. A newly `pending` row, a queued/in-progress reviewer run, or a failed refetch (`comments not refreshed (<reason>)`) refuses a plain merge and re-renders; `anyway` overrides only those existing pending/refetch-failure overlays and prints what it overrode, never a new blocker or unreviewed delta.
23
+ Before every external write, re-fetch `headRefOid`, `state`, `mergeable`, and `mergeStateStatus`. A head or state change since assessment invalidates the current state - re-sync the worktree, re-enter step 3, and re-render the menu; a `mergeStateStatus` change alone (head unchanged) invalidates a `merge` or `merge anyway` pick and re-enters step 5 through `### Pre-menu refresh`; `reply`, `review`, `approve`, and `propose ticket change` proceed, and the next menu renders from the refreshed value. Exception: a pick's own push updates the assessed head to the pushed SHA as part of that pick's execution - this self-inflicted head move does not invalidate the pick; the next compare-and-swap runs against the new head on the next external write. Before a merge executes (plain or `anyway`), run the full comment refetch and reconciliation (`### Re-render` steps 1-5), not just placeholder detection. If the head changed, re-enter step 3 instead. A new or changed-body delta since the consent render aborts the selected merge, plain or `anyway`: show the reconciled report and request a fresh pick even when no blocker resulted. A newly `pending` row, a queued/in-progress reviewer run, or a failed refetch (`comments not refreshed (<reason>)`) refuses a plain merge and re-renders; `anyway` overrides only those existing pending/refetch-failure overlays and prints what it overrode, never a new blocker or unreviewed delta.
23
24
 
24
25
  ### Fix wave
25
26
 
@@ -37,7 +38,7 @@ Apply per `fix-wave.md`: the local conflict check, one fresh implementer helper
37
38
 
38
39
  After every push or any mutation that can change readiness (fix wave pushed, PR head moved), re-sync the worktree. On an own push, after `fix-wave.md` `## Evidence after push`, run steps 1-5 below and render the menu from the pre-push reviewer's closure lines for the exact pushed SHA, where a blocker whose closure line reads `resolved` leaves the rendered list while its internal ID stays in the ledger and new findings continue each sequence; no claim re-check and no whole-wave review runs after an own push. On every other head move, re-enter step 3, then run steps 1-5 below - the last read before the menu renders:
39
40
 
40
- 1. Re-run the Section A comment fetches (`../verification-brief.md`: both `--paginate` calls, plus the GraphQL `reviewThreads` query when the initial gather used it), one `gh run view` per placeholder row, and the reviewer-run `gh run list` when a reviewer workflow is known. Read-only.
41
+ 1. Run the pre-menu refresh (`### Pre-menu refresh`), then re-run the Section A comment fetches (`../verification-brief.md`: both `--paginate` calls, plus the GraphQL `reviewThreads` query when the initial gather used it), one `gh run view` per placeholder row, and the reviewer-run `gh run list` when a reviewer workflow is known. Read-only.
41
42
  2. Diff the fresh set against the `C#` ledger (`findings.md` `## Namespaces`) by `id` and `updated_at`:
42
43
  - same `id`, same `updated_at` -> unchanged: keep the existing `C#`.
43
44
  - same `id`, different `updated_at` -> edited: mint a new `C#`; the old one is `superseded by C<new>`. An `updated_at` change with an identical body (reaction, revert) still counts as edited.
@@ -50,11 +51,15 @@ After every push or any mutation that can change readiness (fix wave pushed, PR
50
51
 
51
52
  **Refetch failure** (`gh` non-zero, network, pagination incomplete): re-render with the ledger's prior states, one `PR comments` line `Comments could not be refreshed, so merge waits. (<reason>)`, and the pending-reviewer overlay withholding `merge`; `wait` is recommended; a composed `merge anyway` remains available when `decision-menu.md` `## Availability` allows it.
52
53
 
54
+ ### Pre-menu refresh
55
+
56
+ The first read of step 5, before the report and the menu render from one state - the first pass included (a long local run or review can stale the gather), every `### Re-render` entry, the post-`wait` re-fetch, `update branch`, a `merge base` round, and a fix-wave push (`fix-wave.md` `## Evidence after push`). Run `gh pr view <N> --json headRefOid,state,mergeable,mergeStateStatus,reviewDecision,statusCheckRollup` and `gh run list -R <owner>/<repo> -c <headRefOid> --json databaseId,status,conclusion,workflowName,url`. Head moved -> the head-move rule (re-enter step 3). `state` or `mergeable` changed (head unchanged) -> route through `### Compare-and-swap` re-assessment. Head unchanged with `state` and `mergeable` unchanged -> replace `permissions.merge_state_status`, `pr.reviewDecision`, `status_checks[]`, and `actions_runs[]` in the digest together, re-normalize and re-classify every pending check (`../verification-brief.md` Section B, binding classification), and re-resolve the Evidence resolution table on the fresh set, so a check whose run completed since the last menu is never paired with its cached run status; when that re-resolve moves `source: pending` to CI-sufficient, dispatch the Section B helper in claim-check-only mode (`../verification-brief.md` Section B) before rendering. A `gh` failure keeps the prior values, classifies every pending check as binding (`../verification-brief.md` Section B, binding classification), and renders the `merge state` overlay (`decision-menu.md` `## Overlays`) through `## Withhold reason resolver` reason 2 (locator `merge state not refreshed (<reason>)`). The refresh is read-only and never mints `C#` rows - comment reconciliation stays in `### Re-render`.
57
+
53
58
  ### Wait course
54
59
 
55
- For a `wait` pick, run a sequence of short bounded calls - never one long bash call. Each iteration: `gh run view -R <repo> <run-id> --json status,conclusion` for every tracked run (placeholder-linked and head-listed), and `gh pr view <N> -R <repo> --json statusCheckRollup` while any check in the resolved set is pending, required or not; when any row has no parsable URL, or its run is completed while the prefix persists, one comment refetch as well; then sleep 30 s. Use each polling refetch only to observe run/placeholder state; leave the digest and `C#` ledger untouched until the completion/timeout reconciliation below. Check the deadline between iterations: the resolved `timeout minutes` (default 15, the same knob as the local verification run). Stop when every tracked run is completed and no check in the resolved set is pending and no row is in the "completed `success`, prefix persists" state, or the deadline passes.
60
+ For a `wait` pick, run a sequence of short bounded calls - never one long bash call. Each iteration: `gh run view -R <repo> <run-id> --json status,conclusion` for every tracked run (placeholder-linked and head-listed), and `gh pr view <N> -R <repo> --json statusCheckRollup,mergeStateStatus` while any pending check in the resolved set is binding when classified against the polled rollup and the polled `mergeStateStatus`, or the polled `mergeStateStatus` is `BLOCKED`, `UNKNOWN`, or `unreadable`; when any row has no parsable URL, or its run is completed while the prefix persists, one comment refetch as well; then sleep 30 s. Use each polling refetch to classify binding checks and compare the loop's stop condition against the polled values; leave the digest and `C#` ledger untouched until the completion/timeout reconciliation below - this preserves persisted digest state, not the loop's comparison values. Check the deadline between iterations: the resolved `timeout minutes` (default 15, the same knob as the local verification run). Stop when every tracked run is completed and no pending check is binding against the polled values and the polled `mergeStateStatus` is none of `BLOCKED`, `UNKNOWN`, `unreadable` and no row is in the "completed `success`, prefix persists" state, or the deadline passes.
56
61
 
57
- Then re-fetch `statusCheckRollup`, `headRefOid`, `state`, `mergeable`. If the head advanced or state/mergeability changed, route through compare-and-swap re-assessment before reusing evidence or reviewing comments; otherwise re-resolve the brief's Evidence table on the fresh rollup; dispatch a fresh Section B helper for a local run only when the re-resolved table selects a local-run row (Fallback, Held run not approvable, or Opt-out - `../verification-brief.md` Evidence resolution) and no evidence exists yet for this head - otherwise the existing evidence stands; when the re-resolve reaches CI-sufficient from a `pending` result, dispatch a fresh Section B helper in claim-check-only mode (`../verification-brief.md` Section B) before re-rendering. On completion or timeout, run the refetch (steps 1-5 above) and re-render; do not re-run claim-check or whole-diff review solely because the head did not move beyond the claim-check-only dispatch above. On timeout: rows in the completed-`success`/prefix-persists state become `reviewer failed (stale placeholder)` (brief Section C placeholder table); every other `pending` row stays `pending`, merge stays withheld, `wait` renders as row 1 again.
62
+ Then run `### Pre-menu refresh` (it re-fetches `statusCheckRollup`, `headRefOid`, `state`, `mergeable`, `mergeStateStatus`). If the head advanced or state/mergeability changed, route through compare-and-swap re-assessment before reusing evidence or reviewing comments; otherwise re-resolve the brief's Evidence table on the fresh rollup; dispatch a fresh Section B helper for a local run only when the re-resolved table selects a local-run row (Fallback, Held run not approvable, or Opt-out - `../verification-brief.md` Evidence resolution) and no evidence exists yet for this head - otherwise the existing evidence stands. On completion or timeout, run steps 2-5 of `### Re-render` plus step 1's comment and reviewer-run reads - the pre-menu refresh already ran - and re-render; do not re-run claim-check or whole-diff review solely because the head did not move. On timeout: rows in the completed-`success`/prefix-persists state become `reviewer failed (stale placeholder)` (brief Section C placeholder table); every other `pending` row stays `pending`, merge stays withheld, `wait` renders as row 1 again.
58
63
 
59
64
  ### Output done-check
60
65
 
@@ -4,7 +4,7 @@ Read from SKILL.md step 5, after `findings.md`. Input: the integration output. O
4
4
 
5
5
  ## Order
6
6
 
7
- Render bottom-up for a terminal: least important first, verdict and menu last. Empty sections are omitted - never "None". A failed comment refresh, an incomplete comment source review (`post-selection-loop.md` `### Re-render`), or a pending required check renders its one line under `PR comments` as well.
7
+ Render bottom-up for a terminal: least important first, verdict and menu last. Empty sections are omitted - never "None". A failed comment refresh, an incomplete comment source review (`post-selection-loop.md` `### Re-render`), a blocked or unknown merge state, or a binding pending check renders its one line under `PR comments` as well.
8
8
 
9
9
  Section order is fixed:
10
10
 
@@ -18,10 +18,10 @@ Delivers: <one sentence: what the PR does. With a ticket, a second sentence buil
18
18
  Ticket changes: (one item per unresolved `impossible` row)
19
19
  - AC<n> asks for <X>, and <cited constraint>. Proposed wording: "<new AC text>". (drafted | proposed <comment url>; <source>)
20
20
 
21
- PR comments: (replyable review comments, a pending reviewer run or required check, a failed refresh, or an incomplete source review - see the paragraph above)
21
+ PR comments: (replyable review comments, a pending reviewer run, a blocked or unknown merge state, a binding pending check, a failed refresh, or an incomplete source review - see the paragraph above)
22
22
  - <reviewer>'s comment on <topic> <verdict clause>, so the drafted reply <what it says>. (<locator>)
23
23
  - The reviewer run is still in progress, so merge waits. (<run url>)
24
- - A required check is still pending, so merge waits. (<check name>)
24
+ - <the line from decision-menu.md `## Withhold reason resolver`>
25
25
 
26
26
  Nits:
27
27
  - <1-2 sentences>. (<file:line>)
@@ -29,14 +29,18 @@ Nits:
29
29
  Blockers: (numbered; menu picks reference these numbers)
30
30
  1. <1-2 sentences: what is wrong, why it matters>. (<file:line> | <check name> | <doc path>)
31
31
 
32
+ Sync: <one whole sentence from the digest's sync field; only when --rebase was passed>
33
+
32
34
  Verdict: mergeable - <evidence clause> | fixable - <N> blockers | fixable - <withhold reason>
33
35
 
34
36
  <menu>
35
37
  ```
36
38
 
39
+ The `Sync:` line renders only when `--rebase` was passed, as one whole sentence built from the digest's `sync` field (for example `Sync: rebased a1b2c3d..e4f5a6b onto origin/main, one stop resolved in src/parser.ts and src/parser.test.ts.`). SKILL.md wording rule 5 (passing facts only under `show evidence`) does not apply to it: it names the resolved files even when the sync succeeded.
40
+
37
41
  ## Verdict
38
42
 
39
- Two states: `mergeable` or `fixable`. `mergeable` carries the evidence clause from `findings.md` `## Provenance`: `CI green on the assessed head (<check name>)` or `verification command passed locally`. `fixable - <N> blockers` when `Blockers` is non-empty; `fixable - <withhold reason>` when it is empty but a merge prerequisite is unmet: `ticket change pending on AC<n>` (for example `ticket change pending on AC4`), `reviewer run in progress`, `required check pending`, `failed gate` (listed for spec parity; a red gate mints a blocker per `findings.md` `## Namespaces`, so `Blockers` is non-empty), `comments not refreshed`, `comment source review incomplete`, `verification not run` (the command needed credentials or no command resolved, with nothing else blocking), `mergeable unknown`, `behind base`, `merge conflict` (for `mergeable == CONFLICTING`), `verification evidence pending` (the Pending row: >=1 check pending, none blocking).
43
+ Two states: `mergeable` or `fixable`. `mergeable` carries the evidence clause from `findings.md` `## Provenance`: `CI green on the assessed head (<check name>)` or `verification command passed locally`. `fixable - <N> blockers` when `Blockers` is non-empty; `fixable - <withhold reason>` when it is empty but a merge prerequisite is unmet: `ticket change pending on AC<n>` (for example `ticket change pending on AC4`), `reviewer run in progress`, `binding check pending`, `blocked by GitHub`, `merge state unknown`, `failed gate` (listed for spec parity; a red gate mints a blocker per `findings.md` `## Namespaces`, so `Blockers` is non-empty), `comments not refreshed`, `comment source review incomplete`, `verification not run` (the command needed credentials or no command resolved, with nothing else blocking), `mergeable unknown`, `behind base`, `merge conflict` (for `mergeable == CONFLICTING`).
40
44
 
41
45
  `<N> blockers` reads `1 blocker` for one.
42
46
 
@@ -0,0 +1,61 @@
1
+ # gatekeep-pr: step 2b - sync (`--rebase [base]`)
2
+
3
+ Read from SKILL.md step 2b, only when `--rebase` was passed. Input: the step-1 digest, the provisioned worktree, and the base name (`<base>` = the argument's value, else `baseRefName`; `--rebase release-1.x` rebases onto `origin/release-1.x`). Output: the worktree on the rebased, pushed head with a refreshed digest, or a `sync` record and the unsynced head. The sync runs once per invocation; a loop re-entry never returns here. This file is self-contained: `fix-wave.md` is the post-menu contract and is not loaded here; `assessment.md` owns step 2.
4
+
5
+ ## Permitted orchestrator git writes
6
+
7
+ `git fetch origin <base>`, `git rebase origin/<base>`, `GIT_EDITOR=true git rebase --continue`, `git rebase --abort` (only while a rebase this step started is in progress), `git reset --hard <pre_head>` (only after that rebase completed, to restore the recorded SHA), and the lease push below. No `add`, no `commit`, no `rebase --skip`, no edit to a tracked file, and no `--continue` while `git diff --name-only --diff-filter=U` is non-empty. Every command runs as `git -C <worktree> ...`.
8
+
9
+ ## Preconditions
10
+
11
+ Check in order after step 2. A terminal stop - here, at `## Baseline`, or at `## Push` - closes the `sync` stage `failed`, prints the reason, offers `stop` and `show evidence` only, and runs no step 3; on such a stop `show evidence` prints the digest's head-dependent fields and the `sync` record.
12
+
13
+ 1. The harness has a helper facility (SKILL.md `## Harness notes`); none -> terminal stop before any write: `sync: stopped - no helper facility for conflict resolution and review`.
14
+ 2. `permissions.head_pushable` is `true`; `false` or `unreadable` -> terminal stop naming the reason; no rebase, no push.
15
+ 3. `origin/<base>` exists after `git fetch origin <base>`; missing -> terminal stop with one line naming the missing ref.
16
+ 4. `git merge-base --is-ancestor origin/<base> HEAD` exits 1 (the head is behind); 0 -> `sync: no-op - head already contains origin/<base>`, stage `complete`, continue to step 3 with no push and no review; any other exit -> terminal stop quoting it.
17
+ 5. `git rev-list --merges origin/<base>..HEAD` prints nothing; a head holding merge commits is not rewritten -> `sync: skipped - head contains merge commits`, stage `skipped`, continue to step 3 unsynced.
18
+
19
+ The fetch in precondition 3 is the only git write that may precede the confirmation below.
20
+
21
+ A reused worktree holding local-only commits or a foreign in-progress operation never reaches this step: step 2's provisioning stop (`assessment.md`) fires first.
22
+
23
+ ## Baseline
24
+
25
+ Refresh `headRefOid` with `gh pr view <N> --json headRefOid` and set `<pre_head>` = `git -C <worktree> rev-parse HEAD`. The two must agree; a mismatch is a terminal stop (stage `failed`) naming both SHAs (the remote moved during provisioning).
26
+
27
+ ## Other-author confirmation
28
+
29
+ When `pr.author.login` differs from `gh api user --jq .login`, or that login is `unreadable`, print one explanation before any rewrite: the rebase replays the author's commits under new SHAs (authorship stays; the committer becomes this checkout's local git identity), review comments anchored on the old commits may render as outdated on the forge, and branch protection may dismiss existing approvals. Then ask for a yes. Any other reply -> `sync: declined - other author's branch`, stage `skipped`, step 3 runs unsynced. A yes covers the whole sync (rebase, helper commits, review, lease push, and any restore). Same-author PRs run on `--rebase` alone.
30
+
31
+ ## Rebase
32
+
33
+ `git -C <worktree> rebase origin/<base>`. A non-zero exit with no unmerged paths (`git diff --name-only --diff-filter=U` empty) takes `## Failure path`. A non-zero exit with unmerged paths is a conflict stop.
34
+
35
+ ## Conflict stop
36
+
37
+ One fresh `implementer` per stop, sequential, `cwd` the PR worktree, dispatched per SKILL.md `## Harness notes` (pi: the `implementer` persona; Claude Code: the sync conflict-stop duty). Before dispatch, record the stop's `git status --porcelain` (the clean replays git already staged belong to it) and the conflicted file list. Resolve `SCOPED_TEST_COMMANDS` yourself: the resolved `verification command` from the configuration ladder (`assessment.md` `## Configuration`) followed by the paths of the test files among the stopped commit's changed files when the ladder documents a file-scoped form, otherwise `none`. The task carries the conflicted file list, the stopped commit (`git rebase --show-current-patch`), the base being rebased onto, `SCOPED_TEST_COMMANDS`, and this rule verbatim: "resolve every listed file so both sides' intent survives, run exactly the supplied scoped test commands and report each result (or report that none were supplied), `git add` each resolved file, return `done` with the list of files you changed; never run `git rebase --continue`, `git rebase --abort`, `git rebase --skip`, or `git commit`; never edit a file outside the list".
38
+
39
+ Continue (`GIT_EDITOR=true git rebase --continue`) only when the helper returned `done` with the scoped results, `--diff-filter=U` is empty, every listed file is fully staged, and `git status --porcelain` differs from the recorded snapshot only in the listed files; a non-zero `--continue` with unmerged paths is the next conflict stop; without unmerged paths it takes `## Failure path`. Anything else (`open`, residue outside the listed files, unmerged paths left) takes `## Failure path`. One attempt per stop, no round counter; the fix-round cap (`closureReview.maxFixRounds`) is untouched. Resolved file names accumulate across stops.
40
+
41
+ ## Review before push
42
+
43
+ Runs on every rewritten head, with or without a stop; only a `no-op` skips it. One fresh `code-reviewer`, `cwd` the PR worktree, read-only, dispatched per SKILL.md `## Harness notes` (Claude Code: the sync review duty), over `git range-diff origin/<base> <pre_head> HEAD` and, when stops were resolved, `git diff <pre_head> HEAD -- <resolved files>`; the task asks for the reviewer's native findings report with "rebase replay and conflict resolutions" as the review subject and no closure lines. A Critical or Moderate finding takes `## Failure path`; no second helper.
44
+
45
+ ## Push
46
+
47
+ `git -C <worktree> push --no-follow-tags --force-with-lease=<head_ref>:<pre_head> <head_url> HEAD:refs/heads/<head_ref>`. The destination is the digest's `head_url` and `head_ref` (the same destination `fix-wave.md` pushes to), never `origin`.
48
+
49
+ A refusal whose message names the lease (`stale info`, the remote head moved since `## Baseline`): restore `<pre_head>` with `git reset --hard <pre_head>`, assert `HEAD == <pre_head>` and a clean `git status --porcelain`, then terminal stop (stage `failed`, menu `stop` and `show evidence` only) with the reason and both SHAs. No retry. Any other rejection (server policy, branch protection) takes `## Failure path` with `push rejected: <first server line>`.
50
+
51
+ ## Re-gather
52
+
53
+ Re-run every step-1 forge read for the PR (`../verification-brief.md` Section A fixed command set): `headRefOid`, `mergeable`, `mergeStateStatus`, `reviewDecision`, status checks and `actions_runs`, commits, changed files and diff, comments and reviews; the step-1 ticket reads are not repeated. Unreadable fields fail closed as in step 1. Re-resolve the configuration ladder (`assessment.md` `## Configuration`) at `git merge-base origin/<baseRefName> HEAD` - `baseRefName` stays the trust source even when `<base>` names another branch. Run no CI poll here: step 3's first pass is the single poll window (every 30 s for up to `timeout minutes`; a binding check still pending at the limit renders the `verification evidence pending` overlay (`../verification-brief.md` Section B, binding classification), never green, and a head with no check, or only not-binding pending checks, takes the `Fallback` row) and step 3 resolves the pushed head as any assessed head - the sync push is not an own push for `### Re-render` and the `Fix wave` row of Section B does not apply; a head that moves during that poll takes the existing head-move rule (`post-selection-loop.md` `### Re-render`), with step 2's divergence stop as the backstop. Red CI on the pushed head triggers no automatic fix: step 3 records the failing checks and the menu offers `fix` - `fix [recommended]` only when `decision-menu.md` and `../verification-brief.md` Section B make it `[recommended]`. Set the digest's `sync` field (`../verification-brief.md` Section A), close the `sync` stage `complete`, and enter step 3 with the refreshed digest.
54
+
55
+ ## Failure path
56
+
57
+ Triggers: an unresolved stop, `open`, residue, a non-zero `rebase` or `--continue` without unmerged paths, a reviewer Critical or Moderate finding, a push rejected by server policy. Restore `<pre_head>`: `git rebase --abort` while the rebase is in progress, `git reset --hard <pre_head>` after it completed; assert `HEAD == <pre_head>` and a clean `git status --porcelain` - a mismatch is a hard stop with the error, never a silent continue. Record `sync: failed - <reason>` with reason one of `unresolved stop in <commit7>`, `implementer returned open`, `residue outside conflicted files`, `rebase failed: <first line>`, `reviewer: <top finding>`, `push rejected: <first server line>`. Close the `sync` stage `skipped` and continue to step 3 on the unsynced head; step 5's conflict rows (`update branch`, `merge base`, `reply`, `stop`) carry what remains.
58
+
59
+ ## Sync record
60
+
61
+ The digest's `sync` field is one of: `rebased <pre_head7>..<new_head7> onto origin/<base>, <n> stop(s) resolved in <files>, <m> commit(s) dropped as already applied`, `no-op - ...` (short form `sync: no-op`), `skipped - ...` (`sync: skipped`), `declined - ...` (`sync: declined`), `failed - ...` (for example `sync: failed - reviewer: ...` or `sync: failed - push rejected: <first server line>`), `stopped - ...`. `report.md` renders it as the `Sync:` line immediately before `Verdict:`. `<m>` is `git rev-list --count origin/<base>..<pre_head>` minus `git rev-list --count origin/<base>..HEAD`; omit the stops clause when no stop occurred.
@@ -63,13 +63,14 @@ provisioning, so it never re-polls; step 2 re-polls once after provisioning (`re
63
63
  - pr: { number, title, body, author, author_is_bot, state, isDraft, headRefName, baseRefName,
64
64
  isCrossRepository, mergeable, mergeStateStatus, headRefOid, headRepository, maintainerCanModify, files, additions, deletions, reviewDecision }
65
65
  - viewer: { login, is_author }
66
- - permissions: { viewer_permission, is_cross_repository, maintainer_can_modify, merge_state_status, can_update_branch, head_url, head_ref, head_pushable } # head_pushable is set in step 2 after provisioning (reference/assessment.md); any field that cannot be read is the literal `unreadable`; menu availability derives from this block (reference/decision-menu.md ## Availability)
67
- - status_checks: [ { name, status, conclusion, required, url, workflowName } ] # evidence semantics: Section B Evidence resolution; workflowName from the CheckRun rollup entry (absent on StatusContext)
66
+ - permissions: { viewer_permission, is_cross_repository, maintainer_can_modify, merge_state_status, can_update_branch, head_url, head_ref, head_pushable } # head_pushable is set in step 2 after provisioning (reference/assessment.md); merge_state_status is `BLOCKED`, `BEHIND`, `UNKNOWN`, `unreadable` (null), or any other GitHub value (`CLEAN`, `UNSTABLE`, `HAS_HOOKS`, `DIRTY`) and is written only from a `gh pr view` invocation that names `mergeStateStatus` (this call, the step-2 re-poll, the step-2b re-gather (`reference/sync.md`), the pre-menu refresh, compare-and-swap, the wait and fix-wave polls), never from an ad hoc GraphQL query or `gh pr list`, and never from a reply carrying `errors`; any field that cannot be read is the literal `unreadable`; menu availability derives from this block (reference/decision-menu.md ## Availability)
67
+ - status_checks: [ { name, kind, status, conclusion, required, url, workflowName } ] # kind: check_run | status_context | unreadable, from the rollup entry's __typename; evidence semantics: Section B Evidence resolution; workflowName from the CheckRun rollup entry (absent on StatusContext)
68
68
  - actions_runs: [ { databaseId, status, conclusion, workflowName, url } ] # from the fixed gh run list call on the assessed head
69
69
  - comments: { inline[ { id, updated_at, user_type, body, ... } ], top_level[ { id, updated_at, user_type, body, ... } ], review_threads[]? } # retain REST body for source-review deltas; C# identity diffs on id/updated_at, Section C gates placeholder detection on user_type
70
70
  - issue_ref: <ref> | null
71
71
  - issue: null # shape and fill: step 2, reference/assessment.md ## Fetch the ticket
72
72
  - worktree_discovery: { expected_path, exists, branch, dirty, ahead, behind }
73
+ - sync: <record> # only with --rebase; grammar: reference/sync.md ## Sync record
73
74
  - truncation_notes: []
74
75
  ```
75
76
 
@@ -98,6 +99,8 @@ a null `headRepository` (deleted fork) gives `unreadable`;
98
99
  `viewerPermission` (null when authenticated as an App -> `unreadable`);
99
100
  `can_update_branch` is `viewerCanUpdateBranch`.
100
101
 
102
+ That `mergeStateStatus` reflects the viewer's own standing is an observation (an exempt viewer reads `UNSTABLE` on a review-required PR), not a documented guarantee; GitHub documents `BLOCKED` only as "the merge is blocked" with no per-rule attribution, so the gate never names which rule blocks. A non-`BLOCKED` value never approves a merge, it only leaves the `merge` row available to the preconditions (`reference/post-selection-loop.md`).
103
+
101
104
  ## Section B - Verifier
102
105
 
103
106
  Resolves the verification evidence per the table below - green exact-head CI is
@@ -118,6 +121,7 @@ command runs. Inputs come from Section A's fixed `gh pr view` and `gh run list`
118
121
  completed check with a missing/unreadable conclusion cannot satisfy
119
122
  (fail-safe). A StatusContext's `state` maps `SUCCESS` to success,
120
123
  `FAILURE`/`ERROR` to blocking, `PENDING`/`EXPECTED` to pending.
124
+ - **Binding classification**: every pending check in the resolved set is classified once, here, as `binding` or `not binding`. `binding` when `merge_state_status` is `BLOCKED`, `UNKNOWN` (after the step-2 re-poll), or `unreadable`, or the pre-menu refresh failed (`reference/post-selection-loop.md` `### Pre-menu refresh` - the cached value is stale, so every pending check binds until a refresh succeeds); or when `kind` is `unreadable`; or when `kind` is `check_run` and its `actions_runs[]` entry (matched by the run id parsed from its `detailsUrl`, as the Held run row does) has `status` other than `completed` - `queued`, `in_progress`, `waiting`, `requested`, `pending`, or unreadable - or has no matching entry (an external CheckRun app, or an unparsable `detailsUrl`: no completed-run evidence exists, so it fails closed). `not binding` otherwise: a pending `status_context`, or a pending `check_run` whose matched run is `completed`, while `merge_state_status` is any other value. Every consumer below reads "pending" as "pending and binding"; a not-binding pending check withholds nothing and prints only under `show evidence` as `<name> pending - not binding this viewer (merge_state_status <value>)`. Trade-off, stated: a pending commit status posted by an external CI (Buildkite, Jenkins, CircleCI) is a `status_context` with no run linkage, so for a viewer it does not bind it is not waited for; a consumer whose external CI matters makes it a required check, which binds through `BLOCKED`.
121
125
  - **Reviewer-check exception**: claude-code-action's sticky mode runs on
122
126
  `pull_request` events, so its job is also a check run on the head. A
123
127
  resolved-set check whose run id (from its `url` / `detailsUrl`) equals a
@@ -141,14 +145,14 @@ On the first pass the orchestrator re-runs Section A's fixed `gh pr view` and `g
141
145
  | Opt-out | `local verification: always` in `## PR gate` | Run local command unconditionally; a Failed-CI block below still applies independently | Local; `local run: local verification: always` |
142
146
  | Failed CI | Any blocking conclusion in the resolved set | Blocks: mints a `P#` (any resolved-set failure, required or not) and names the check in the evidence block; the menu re-renders with `fix` recommended only when a drafted payload exists for that failure, else per the overlays. A green local run never overrides it. Only the human `ci-infrastructure-broken` disposition - a pick, never a trigger - runs the fallback local run; merge stays withheld until that run is green | The disposition; plus the fallback run's result when the disposition triggered one |
143
147
  | Held run | an `action_required` rollup CheckRun whose run matches an `actions_runs` entry, or an `actions_runs` entry with `action_required` and no rollup entry (a rollup CheckRun matches the `actions_runs` entry whose `databaseId` equals the run id parsed from the CheckRun's `detailsUrl`) | Pending: render `approve workflow run` when available - `viewer_permission` in `WRITE`/`MAINTAIN`/`ADMIN`, the rule `reference/decision-menu.md` `## Availability` owns; when the row is not available, or it was picked and the API returned 403 or 404, run the local command at once; the fresh Section B helper receives the resolved row `held run not approvable` in its task; an approved run that completes is re-resolved per `reference/fix-wave.md` `## Evidence after push`, which dispatches the claim-check-only helper | `local run: held run not approvable` when it fired, else n/a (waiting) |
144
- | Pending | >=1 pending check, no blocking conclusion, and the poll window (first pass or after a push) elapsed | the `verification evidence pending` overlay renders (`reference/decision-menu.md` `## Overlays`); `wait` polls another `timeout minutes`; never a local run, never an evidence-less merge; when a later re-resolve moves this head to CI-sufficient, the orchestrator dispatches a fresh Section B helper in claim-check-only mode (no local run) so every material claim gets a disposition | n/a (waiting) |
145
- | CI-sufficient | >=1 `success`, zero blocking, zero pending | Skip local run | CI claim: check name(s), conclusion, assessed SHA, run URL |
146
- | Fallback | No conclusive check: no checks on the assessed head, or only inert / fail-safe conclusions, after `timeout minutes` | Run local command (protocol below) | Local command + `log_path`; `local run: no conclusive check` |
147
- | Fix wave | A push by this gate | Poll the pushed head per `reference/fix-wave.md` `## Evidence after push`, then resolve this table on the normalized set | The resolved row's evidence for the pushed SHA |
148
+ | Pending | >=1 **binding** pending check, no blocking conclusion, and the poll window (first pass or after a push) elapsed | the `verification evidence pending` overlay renders (`reference/decision-menu.md` `## Overlays`); `wait` polls another `timeout minutes`; never a local run, never an evidence-less merge; when a later re-resolve moves this head to CI-sufficient, the orchestrator dispatches a fresh Section B helper in claim-check-only mode (no local run) so every material claim gets a disposition | n/a (waiting) |
149
+ | CI-sufficient | >=1 `success`, zero blocking, zero binding pending | Skip local run | CI claim: check name(s), conclusion, assessed SHA, run URL |
150
+ | Fallback | No conclusive check: no checks on the assessed head, or only inert / fail-safe conclusions or only not-binding pending checks, after `timeout minutes` | Run local command (protocol below) | Local command + `log_path`; `local run: no conclusive check` |
151
+ | Fix wave | A fix-wave push by this gate (not the step-2b sync push) | Poll the pushed head per `reference/fix-wave.md` `## Evidence after push`, then resolve this table on the normalized set | The resolved row's evidence for the pushed SHA |
148
152
  | Stale head | Head advances since evidence was resolved (another actor's push); fires across runs - a single gather is same-head by construction | All prior evidence (CI or local) is stale; re-resolve this table for the new head before merge is offered | Fresh evidence for the new head |
149
153
 
150
154
  CI-sufficient predicate, stated once (the rows implement exactly this): **>=1
151
- completed `success`, zero blocking conclusions, zero pending checks, no
155
+ completed `success`, zero blocking conclusions, zero **binding** pending checks, no
152
156
  opt-out.** The evidence decision ("run local?") and the merge decision ("can
153
157
  this merge?") are separate consumers of the same `status_checks` data. A local
154
158
  run fires on exactly three triggers, each recorded in the evidence block: