pi-gauntlet 6.2.0 → 7.0.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,120 +1,70 @@
1
- # gatekeep-pr: findings
2
-
3
- Read from SKILL.md `## Assess`. Phase 4 mints the IDs the report and menu carry.
4
-
5
- ## IDs
6
-
7
- `<source_ref>` is a `file:line` where one exists, else the disputed thing (a
8
- quoted PR-body claim, a failing gate command, a required check name).
9
-
10
- Precedence: Phase 4 and the merged rubric decide blocking vs. follow-up (the
11
- severity translation, AC coverage, claims, and required-check rules). This
12
- section only chooses which namespace (`P#` / `L#` / `F#`) renders that
13
- decision. Category tags and the triage bar never override an upstream
14
- blocking classification - a Phase-4 Moderate is always blocking (`P#` or `L#`
15
- per Total mapping), never demoted to `F#` by tag or by judgment call.
16
-
17
- Total mapping: every blocking element of the Verdict maps to a `P#` or `L#` -
18
- a blocking verdict with "None" in both groups is a rendering bug. Map: failed
19
- gate -> `P#` `[test]` referencing the gate command; contradicted or
20
- merge-proof-unverifiable material claim -> `P#` `[spec]` referencing the
21
- claim; scope creep with a linked issue -> `L#` `spec-conflict`; committed doc
22
- drift -> `L#` `doc-drift`; `partial` AC coverage -> `L#` `outdated-AC`;
23
- `missing` AC coverage -> `L#` `missing-behavior`. `L#` covers exactly the
24
- drift the Verdict already blocks on (committed doc drift, AC coverage, spec
25
- conflict); it widens nothing.
26
-
27
- `P#` vs `L#` boundary: code-level spec bugs (the diff contradicts the spec)
28
- are `P#` `[spec]`; requirement/doc mismatches (the spec or docs are stale
29
- relative to intent) are `L#`.
30
-
31
- Labelled `C#` rows are verdict-neutral drafts: they never gate merge, and
32
- nothing posts until selected. The `pending` state, a queued/in-progress
33
- reviewer run, and a failed comment refetch withhold pre-composed `merge-*`
34
- courses at the menu level (`decision-menu.md` `## Pending-reviewer overlay`);
35
- they are not `## Verdict` preconditions.
36
-
37
- **`C#` ledger.** Each `C#` row carries its comment `id` and the `updated_at`
38
- it was minted against; the post-push diff (`post-selection-loop.md`
39
- `### Re-render`) runs against this ledger, never against the report text.
40
- States rendered under a `C#`: a triage label (`already-addressed` /
41
- `reasonable` / `judgment-call`) with a drafted reply; `superseded by C<new>`;
42
- `withdrawn`; `pending`; `reviewer failed (<conclusion>)`. Superseded and
43
- withdrawn rows keep rendering for the rest of the run, so a sticky bot's
44
- chain reads `C1` (old verdict) `superseded by C3`, `C3 pending`, then
45
- `C3 superseded by C5`, `C5 <label> -> <reply>`. The last four states carry no
46
- reply and are not replyable: `reply all` and ranges skip them silently; an
47
- explicitly named non-replyable `C#` is refused with its state named and the
48
- menu re-renders; reply courses are omitted when no replyable `C#` exists.
49
-
50
- `F#` items carry an owner (pr-author | tracker | human) so follow-ups don't
51
- evaporate; when no tracker tool resolved, the report itself is their durable
52
- home.
53
-
54
- IDs are append-only for the run's lifetime: minted at first assessment, never
55
- renumbered, never reused. A resolved finding keeps its ID annotated
56
- `(fixed in <sha>)`; later rounds continue each namespace's sequence.
57
-
58
- Empty groups say "None".
59
-
60
- ## Triage
61
-
62
- A finding lands in `P#` only when it must be fixed before merge (correctness,
63
- security, material performance trap, a convention the repo enforces);
64
- improvements that don't change merge correctness are `F#`, whatever their
65
- category. `[quality]` and `[performance]` on a `P#` are categories, never a
66
- downgrade - every `P#` blocks. This triage bar governs findings the
67
- orchestrator originates itself; it never re-triages a classification Phase 4
68
- already made (see Precedence in `## IDs`).
1
+ # gatekeep-pr: step 5 - integrate
2
+
3
+ Read from SKILL.md step 5, before `report.md`. Inputs: the digest, the step-2 AC rows, the step-3 evidence and claim dispositions, the step-4 reviewer output and comment ledger. Output: AC outcomes, blockers, nits, drafted payloads - the report's content, not its rendering.
4
+
5
+ ## Provenance
6
+
7
+ Local evidence: `worktree_root` matches the provisioned path, every `run_cwd` sits inside it, `head_sha` matches the digest's `headRefOid`. On mismatch, re-fetch the PR head once and re-sync + re-run steps 3-4 when it advanced; a second mismatch, or any path mismatch, is missing evidence. The evidence clause names its source exactly: local -> `verification command passed locally`; CI -> `CI green on the assessed head (<check name>)`, never worded as a local run.
8
+
9
+ ## AC outcomes
10
+
11
+ Inputs: the ticket AC rows (step 2), the source behind the diff at the assessed `headRefOid`, tests in the diff and existing tests the diff reaches, docs in the diff and docs the diff makes stale, CI per the brief's Evidence resolution table. PR body, PR comments, and a spec inside the PR are leads to verify, never evidence.
12
+
13
+ Split each AC row once into a **mechanism half** (code + test + doc that make the behavior possible) and, when the row names an observation that needs an environment, dataset, deployed target, or external system that repository tests cannot reach, an **observation half**. Behavior a unit test can assert ("retries three times then fails", "rejects a nil name") is mechanism, never an observation half. A `venue:` disposition in the PR's spec is a lead to compare against the ticket's own AC text, never proof and never an exemption. Judge the mechanism half only; the observation half is checked after merge, not here, and blocks nothing.
14
+
15
+ | Outcome | Condition | Renders as | Blocks |
16
+ |---|---|---|---|
17
+ | `covered` | evidence matches what the AC promises: executable behavior needs the code path plus a real test that exercises it; a documentation-only AC is judged against the promised doc text and demands no test; in both cases docs that describe the behavior agree with it | counted in `Delivers` | no |
18
+ | `gap` | any part of the mechanism absent in this PR | one `Blockers` item | yes - a mechanism `gap` is a blocker |
19
+ | `not judged here` | the observation half; the mechanism half of the same row is still judged `covered`/`gap` | one `Delivers` clause ("<row>'s observable half is checked after merge, not here"); nothing is written, listed, or handed to check-delivery | no |
20
+ | `impossible` | a `gap` whose fix is on the ticket - all four conditions below hold | one `Ticket changes` item with a drafted replacement AC text, in state `drafted` or `proposed` | withholds `merge` until the ticket body changes or the human picks `merge anyway - accept AC<n> as impossible` |
21
+
22
+ `impossible` requires all of: (1) no change to this repository can satisfy the mechanism half; (2) the constraint is cited - a vendor/platform doc URL, a dependency's released API at `file:line` or in its changelog, a repo policy at `file:line`, or a second AC in the same ticket whose quoted text contradicts this one; (3) the cited source was read this run; (4) the reason is none of: cost, effort, "needs another PR", "needs a deploy first" (that is `not judged here`), a `deviates:`/`deferred:` disposition in the PR's spec, "the AC is ambiguous" (an ambiguous row stays judged as written). Anything failing a condition is `gap`. Passes: the AC asks the export to include the customer's credit score and the vendor API the repository reads returns no such field (vendor doc URL cited). Fails: the AC asks for the user's local timezone and the browser sends no timezone header - a request parameter is a repository-side mechanism, so this is `gap`.
23
+
24
+ **Impossible-AC lifecycle.** Three states in order: `drafted` (the proposal exists in the run), `proposed` (`propose ticket change` posted it as a tracker comment; a comment is not an edit), `resolved` (a human edited the ticket body). Every step-5 re-entry re-fetches the ticket and re-extracts the AC rows; a changed row is re-judged on the current head, so a human edit to the ticket body lifts the withhold without a PR head change. `Ticket changes` keeps one item per unresolved row, labeled `drafted` or `proposed`, and drops it only after the re-extracted row no longer meets the four conditions.
25
+
26
+ **No ticket** (none linked, or fetch failed): no AC rows; `Delivers` states the PR's intent as read from its title and body; `not judged here`, `impossible`, `Ticket changes`, and scope creep do not apply.
27
+
28
+ **Whole or part.** With a ticket linked, `Delivers` names coverage: *whole* when every row is `covered` or is `not judged here` with its mechanism half `covered`; *part, acceptable* when every uncovered row is either an observation half or **explicitly split** - the ticket body or a human-authored ticket comment names another tracker ref for that row, read from the tracker this run. Only the tracker waives an obligation: a spec `deferred: <where>`, a linked later PR, or a `proposed` (not yet `resolved`) ticket change is a lead to check the tracker, never a waiver. Any other uncovered row is `gap`: "a later PR will add X" in the PR body, an unchecked box with no tracker split, a `deferred:` the ticket does not confirm.
29
+
30
+ **Claims.** The Verifier's three dispositions stand. `contradicted` is a blocker. `unverifiable-pre-merge` is not evidence and renders nothing: a PR whose only proof of a new path is "verified on stg" is blocked by the untested-path rubric row, not by a claim rule.
31
+
32
+ ## Namespaces
33
+
34
+ Every finding is one of two:
35
+
36
+ | Namespace | Contents | Gates merge |
37
+ |---|---|---|
38
+ | blocker | code defects, security, untested new path, `contradicted` claim, AC `gap`, scope creep against a linked ticket (diff content traceable to no AC and no stated intent), doc drift beyond wording (a doc now describes behavior the code does not have, or omits an operation or parameter the code adds), failed gate or undispositioned failing check | yes |
39
+ | nit | wording-only doc drift (typo, label, phrasing with the same meaning), style, reuse of an existing helper, naming | no; take-or-leave at the menu; untracked after the run |
40
+
41
+ Behavior the ticket promises is a blocker or an explicit tracker split - never deferred to a PR nobody opened.
42
+
43
+ **Severity translation.** Reviewer Critical and Moderate -> blocker; Minor -> nit. A repo `REVIEW.md` mapping overrides this; a severity it names but does not map is fail-safe blocker, noted in `show evidence`.
44
+
45
+ **Triage bar** for findings the orchestrator originates itself: blocker only when it must be fixed before merge (correctness, security, a material performance trap, a convention the repo enforces); everything else is a nit, whatever its category. The bar never re-triages a classification the severity translation already made.
46
+
47
+ **Internal IDs.** `P#` (blocker or nit on code, a claim, a check), `L#` (AC `gap`, doc drift, scope creep), and the comment ledger's `C#` survive as keys for the post-push re-render diff and the fix wave; the human never sees them. IDs are append-only for the run: minted once, never renumbered, never reused. A fixed blocker leaves the rendered list on the next render; its ID stays in the ledger.
48
+
49
+ **`C#` ledger.** Each ledger row carries its comment `id` and the `updated_at` it was minted against; the post-push diff (`post-selection-loop.md` `### Re-render`) runs against this ledger, never against the report text. Row states: a triage label (`already-addressed` / `reasonable` / `judgment-call`) with a drafted reply; `superseded by C<new>`; `withdrawn`; `pending`; `reviewer failed (<conclusion>)`. The last four carry no reply and are not replyable; `reply` skips them.
69
50
 
70
51
  ## Dispositions
71
52
 
72
- Any blocking conclusion in the resolved check set (required or not - per
73
- `../verification-brief.md` Section B, Evidence resolution table) withholds
74
- merge from every pre-composed course until the user explicitly dispositions
75
- it, and mints a `P#` - except the reviewer check. claude-code-action's sticky
76
- mode runs on `pull_request` events, so its job is also a check run: a failing
77
- check whose run id (from its `url` / `detailsUrl`) matches a `reviewer failed`
78
- `C#` row, or whose `workflowName` equals the recorded reviewer `workflowName`
79
- while that run is `reviewer failed`, is inert when it is the only failing
80
- check mapping to that run id (two or more failing checks on one run id: none
81
- inert, each stays a `P#`, fail-safe) - no `P#`, no withhold, one `## Evidence`
82
- line `reviewer check <name> failed - inert (reviewer failure never withholds)`.
83
- With no sibling `success` left, evidence resolves to the Fallback row, not
84
- Failed CI. Reviewer failure never withholds merge; GitHub-enforced
85
- restrictions still apply.
86
-
87
- An undispositioned failing check in the resolved set is `P#` `[test]`
88
- referencing the check name; it is never a target of a worktree `fix`. Close
89
- failing checks by disposition, not by fix: the user's Phase-4 disposition
90
- annotates the same ID rather than closing it outright.
91
-
92
- | Disposition | Annotation on the `P#` | Counts against unfixed-blocker set | Merge course | Fallback local run |
93
- |---|---|---|---|---|
94
- | undispositioned failing check | none yet | yes | withheld from every pre-composed course | none |
95
- | flaky | `(dispositioned: flaky)` | excepted - no longer counts against "every `P#` blocks" or "every blocking finding fixed" | only the custom row naming the disposition explicitly; no pre-composed course restores | none |
96
- | real | `(dispositioned: real)` | still counts - `P#` keeps blocking | withheld until the check is green | none |
97
- | CI-infrastructure-broken | `(dispositioned: ci-infrastructure-broken)` | still counts - `P#` keeps blocking; the checks themselves are untrustworthy | withheld until the fallback run is green | triggers the fallback local run, and merge stays withheld until that fallback produces green evidence |
98
- | pending required check | mints no `P#`, is never dispositioned | not applicable - not dispositionable | withheld; auto-lifts the moment it turns green, or converts to an undispositioned failing check with its own `P#` on failure | none |
99
- | reviewer check failed (matches a `reviewer failed` `C#`) | mints no `P#`, is never dispositioned | not applicable - inert | not withheld; GitHub-enforced restrictions still apply | none |
100
-
101
- A pending required check is wait-until-green, not dispositionable. While
102
- pending, the report notes it under Evidence.
103
-
104
- The evidence decision is independent of the merge decision: a green check
105
- elsewhere in the resolved set still satisfies verification evidence while a
106
- pending required check withholds merge. The CI-sufficient path changes no
107
- consent surface: still read-only, no auto-merge, no posting, no menu change
108
- beyond the third disposition.
109
-
110
- ## Payloads
111
-
112
- "Drafted fixes / review" holds, per finding ID, the concrete edit (for
113
- `fix`), the reviewed doc-drift edit (for `push-docs`, keyed to its `L#`), or
114
- the reply text (for `reply`) - each keyed to its finding ID, one selection
115
- mapping 1:1 to its payload.
116
-
117
- A posted review body is not itself a finding: compose it at post time from
118
- the ID'd `P#`/`L#` findings being addressed - one summary sentence, then the
119
- numbered findings, ending on the fix or asked action - and give it its own
120
- non-finding slot of this section.
53
+ 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#`.
54
+
55
+ | Disposition | Annotation on the `P#` |
56
+ |---|---|
57
+ | undispositioned failing check | none yet |
58
+ | flaky | `(dispositioned: flaky)` |
59
+ | real | `(dispositioned: real)` |
60
+ | CI-infrastructure-broken | `(dispositioned: ci-infrastructure-broken)` |
61
+
62
+ 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.
63
+
64
+ How each disposition changes the menu: `decision-menu.md` `## Overlays`, CI check row.
65
+
66
+ ## Drafted payloads
67
+
68
+ Step 5 drafts, never applies. For every blocker and nit with a file-level fix - code and doc drift alike - draft the concrete edit as a payload keyed to the finding's internal ID. For every `impossible` row, draft the replacement AC text. 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` pick (the same path for code and docs) and dropped at teardown otherwise.
69
+
70
+ 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,59 +1,70 @@
1
- # gatekeep-pr: post-selection loop
1
+ # gatekeep-pr: step 7 - loop
2
2
 
3
- ## Post-selection loop
3
+ Read from SKILL.md step 7. Execute only the selected pick, then re-enter: a fix wave's own push re-enters step 4; any other head move re-enters step 3; an unchanged head re-enters step 5. Loop until `merge` or `stop`. Merge preconditions, the output done-check, and teardown live here.
4
4
 
5
- Read from SKILL.md `## Act`. Treat the menu as a state machine: execute only the selected course, re-render, and loop until the user selects merge or an explicit stop/no-action row.
5
+ ### Merge preconditions
6
6
 
7
- ### Compare-and-swap
7
+ All must hold before `merge` executes; a `merge` pick while one fails is refused naming it, and the menu re-renders:
8
8
 
9
- Before every external write, re-fetch `headRefOid`, `state`, and `mergeable`. Any change since assessment invalidates the current state - re-sync the worktree, re-run Phase 3 per `assessment.md` `## Phase 3 - Verify, then review`, and re-render the menu. Exception: a course's own push updates the assessed head to the pushed SHA as part of that course's execution - this self-inflicted head move does not invalidate the course; the next compare-and-swap check 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, follow the re-assessment rule above 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 selection 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.
9
+ - every blocker fixed and pushed, or dispositioned `flaky` by the user; no `impossible` row in `drafted` or `proposed` unless the pick is `merge anyway - accept AC<n> as impossible`
10
+ - verification evidence present for the assessed head per the brief's Evidence resolution table - a CI claim or a green local run; a table-sanctioned CI skip is evidence, and `not run` blocks only when the table required a fallback run that did not happen
11
+ - evidence provenance clean (`findings.md` `## Provenance`): `worktree_root`, every `run_cwd`, and `head_sha` match on the local path
12
+ - `mergeable == MERGEABLE` (`UNKNOWN` after step 2's single re-poll withholds like `CONFLICTING`)
13
+ - no undispositioned failing check in the resolved set, no pending required check
14
+ - no retained, unreviewed comment delta (`### Re-render` step 4)
15
+ - worktree tracked-clean and synced with the remote head (fixes pushed first)
16
+ - the compare-and-swap below passes
10
17
 
11
- ### Fix wave
18
+ ### Compare-and-swap
12
19
 
13
- For a `fix <set>` selection, filter the selected set to worktree-fixable `P#`s first: drop any `P#` closed by disposition (per `findings.md` `## Dispositions`). Route file-less `P#`s (a claim or a gate command as `source_ref`, no draft touching a file) to run inline and sequentially, never as part of a parallel batch. A claim `P#` with a drafted file edit is worktree-fixable and batches.
20
+ 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.
14
21
 
15
- Batch the remaining worktree-fixable set by the union of files each finding's drafted edit in `## Drafted fixes / review` touches - fall back to the `source_ref` file only when a finding has no draft. Findings whose drafts share a file share a batch.
22
+ ### Fix wave
16
23
 
17
- Use one child contract for every batch, whether there is one or many: dispatch `implementer` children with `subagent({ agent: "implementer", context: "fresh", cwd: <PR worktree> })`. The orchestrator owns commit, gate, and push - never a child. Every dispatched child gets `cwd` = the PR worktree; `worktree: true` is forbidden per `assessment.md` `## Inline-first execution`. Each child is edit-only - no git commands, no verification runs. Each child's task is that batch's `P#` lines plus the drafted edit already keyed to each ID in `## Drafted fixes / review` - the child applies the consented payload, it does not re-solve the finding.
24
+ For a `fix` pick, take the drafted payloads (`findings.md` `## Drafted payloads`) for the named blockers - all open blockers by default, the nit payloads when no blocker is open; `fix nits` takes nit payloads only, `fix + nits` both. A finding closed by disposition is skipped. A finding whose payload touches no file (a failing check, a claim with no drafted edit) is reported as not fixable by this verb and stays open.
18
25
 
19
- At a cutoff of 2 worktree-fixable findings or fewer, the orchestrator applies the fix inline instead of dispatching - the no-cohort path stays available at any batch count per `assessment.md` `## Inline-first execution`. Past that cutoff, when more than one batch results, dispatch the batches' children in parallel, all under the same contract.
26
+ Batch payloads by the union of files each touches; payloads that share a file share a batch. At two payloads or fewer, apply inline. Past that, dispatch `implementer` children with `subagent({ agent: "implementer", context: "fresh", cwd: <PR worktree> })`, one per batch, in parallel when more than one batch results; each child is edit-only (no git commands, no verification runs) and applies the consented payload rather than re-solving the finding; `worktree: true` is forbidden (`assessment.md` `## Inline first; dispatch when pi-cohort is present`). The orchestrator owns commit, verification, and push.
20
27
 
21
- Once every dispatched or inline batch returns, commit the golden course as one local commit set - the code fixes plus any already-applied reviewed doc edits selected alongside them (one commit, or one per batch sequentially; subjects name the fixes). Re-resolve the evidence for the new head once (the brief's stale-head row: prior evidence is stale; the local command executes only on a fallback/opt-out resolution). On green, push once - gate and push are per-wave invariants, never per-fix or per-batch. On red, do not push: leave the commit(s) local, re-render with the unresolved `P#`s still open, and warn that unpushed fix commits sit in the worktree exactly like unpushed doc edits (per `### Teardown`).
28
+ Once every batch returns, commit as one local commit set (one commit, or one per batch; subjects name the fixes, doc and code alike). Run the verification pass once at the resulting SHA - one verification pass per wave, never per fix or per batch; this is the retained contract. On green, push once; the evidence is now current for the pushed head, so the loop re-enters step 4 after its own push (claim re-check, comment refresh, review of the delta), never step 3. On red, do not push: leave the commits local, re-render with the blockers still open, and say that unpushed fix commits sit in the worktree (`### Teardown`). On a fork PR, `push` is not available: commit locally, report the branch name, and re-enter step 5.
22
29
 
23
- Doc fixes (`push-docs` alone) follow the same rule: stage and commit (subject names what is documented), re-run the gate, and push only on green. Execute reviews, replies, and tracker actions via `gh pr review` / `gh api` / the tracker tool, non-interactively, with the drafted payload for the selected IDs.
30
+ Execute `review`, `reply`, `approve`, and `propose ticket change` via `gh pr review`, `gh api`, `gh issue comment`, or the resolved tracker tool, non-interactively, with the drafted payload - after the output done-check below.
24
31
 
25
32
  ### Merge course
26
33
 
27
- Merge always executes as `gh pr merge --match-head-commit <assessed-sha>`. Push and merge are never bundled into one selection. A merge selection while any precondition (SKILL.md `## Verdict`) fails is refused, naming the failing precondition, and the menu re-renders - never a dead end, never a silent merge.
34
+ `merge` executes as `gh pr merge --match-head-commit <assessed-sha>` with the resolved merge policy. Push and merge are never one pick.
28
35
 
29
36
  ### Re-render
30
37
 
31
- After every push or any mutation that can change readiness (fix wave pushed, docs pushed, PR head moved), re-run the claim-check and Review on the synced worktree: claims are re-checked against the new head and findings are re-rendered, but do not re-execute the verification command here - the fix wave's evidence re-resolution already was the wave's one gate pass. Annotate each selected `P#`/`L#` confirmed resolved as `(fixed in <sha>)` under its original ID; unresolved ones stay open unchanged; new findings continue the sequence. Then refetch comments - the last read before the menu renders:
38
+ After every push or any mutation that can change readiness (fix wave pushed, PR head moved), re-run the claim-check and the review on the synced worktree: claims are re-checked against the new head and findings re-rendered without a second verification pass - the wave's single pass already covered this head; a blocker confirmed resolved leaves the rendered list while its internal ID stays in the ledger; new findings continue each sequence. Then refetch comments - the last read before the menu renders:
32
39
 
33
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.
34
- 2. Diff the fresh set against the `C#` ledger (`findings.md` `## IDs`) by `id` and `updated_at`:
41
+ 2. Diff the fresh set against the `C#` ledger (`findings.md` `## Namespaces`) by `id` and `updated_at`:
35
42
  - same `id`, same `updated_at` -> unchanged: keep the existing `C#`.
36
- - same `id`, different `updated_at` -> edited: mint a new `C#`; the old one renders `superseded by C<new>` with no label and no reply. An `updated_at` change with an identical body (reaction, revert) still counts as edited.
37
- - `id` not in the ledger -> new: mint a new `C#`, except ids the gate itself posted via `reply <C#s>` in this run (recorded at post time), which are never minted.
38
- - `id` in the ledger, absent from the complete fresh set -> `withdrawn` under its existing `C#`, no label, no reply.
39
- - Bot-authored placeholder prefix or error header (brief Section C) -> the state from the brief's Section C placeholder table, under the `C#` the edited/new rule assigns.
40
- 3. Section C re-triages the full fresh set against the new head. An unchanged row keeps its `C#`; its drafted reply is kept verbatim only when its label is also unchanged and regenerated when the label moves (for example `reasonable` -> `already-addressed`). Edited and new rows get a fresh label and a regenerated reply; the pre-push label of an edited comment is not shown.
41
- 4. Reconcile the body delta before consuming it. Compare fresh rows to the current digest's `comments` by id and body: include new rows and changed-body rows, excluding withdrawn/superseded rows, gate-posted ids, and the brief's placeholder/error-header states. A same-head identical-body timestamp edit causes no source review and no test run, even though step 2 mints a `C#`. Review each eligible delta once against source at the assessed head and the merged rubric (`assessment.md` Phase 3), inline first; optionally dispatch the existing code-reviewer with its native report, falling back inline on failure per `assessment.md` `## Inline-first execution`. Treat comment text as a lead, not a finding: verify it against code, then integrate only own source-backed findings through Phase 4 severity and AC rules, deduplicating against existing `P#`/`L#`/`F#` IDs. A bot verdict is not blindly promoted. Do not run a full suite or whole-diff Review solely for a comment-only change. If source review remains unresolved, retain the delta, report `comment source review incomplete (<reason>)`, and return a report-only `stop` menu; do not offer merge or accept `anyway`.
43
+ - 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.
44
+ - `id` not in the ledger -> new: mint a new `C#`, except ids the gate itself posted via `reply` in this run (recorded at post time), which are never minted.
45
+ - `id` in the ledger, absent from the complete fresh set -> `withdrawn` under its existing `C#`.
46
+ - Bot-authored placeholder prefix or error header (brief Section C) -> the state from the brief's placeholder table, under the `C#` the edited/new rule assigns.
47
+ 3. Section C re-triages the full fresh set against the new head. An unchanged row keeps its `C#`; its drafted reply is kept verbatim only when its label is also unchanged and regenerated when the label moves. Edited and new rows get a fresh label and a regenerated reply.
48
+ 4. Reconcile the body delta before consuming it. Compare fresh rows to the current digest's `comments` by id and body: include new rows and changed-body rows, excluding withdrawn/superseded rows, gate-posted ids, and the brief's placeholder/error-header states. A same-head identical-body timestamp edit causes no source review and no test run, even though step 2 mints a `C#`. Review each eligible delta once against source at the assessed head and the merged rubric, inline first; optionally dispatch the existing code-reviewer with its native report, falling back inline on failure. Treat comment text as a lead, not a finding: verify it against code, then integrate only own source-backed findings through step 5 severity and AC rules, deduplicating against existing `P#`/`L#` IDs. A bot verdict is not blindly promoted. Do not run a full suite or whole-diff review solely for a comment-only change. If source review remains unresolved, retain the delta and render per the `comment source review incomplete` overlay (`decision-menu.md` `## Overlays`).
42
49
  5. Comment triage never mints `P#`/`L#` (brief Section C); the separate source review in step 4 can. Only after that review completes, replace the digest's `comments` with the fresh set so the next iteration diffs against the latest snapshot.
43
50
 
44
- **Refetch failure** (`gh` non-zero, network, pagination incomplete): re-render with the ledger's prior states, add one line `comments not refreshed (<reason>)` to the comment section, and withhold pre-composed merge with that reason. `wait` is the recommended course in refetch-only mode; the custom-row `anyway` override remains available.
45
-
46
- Merge, if now available, renders as row 1 unless withheld (`reviewer still running` / `comments not refreshed`).
51
+ **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.
47
52
 
48
53
  ### Wait course
49
54
 
50
- For a `wait` selection, 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); 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 row is in the "completed `success`, prefix persists" state, or the deadline passes.
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` when a required check is pending; 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 required check is pending and no row is in the "completed `success`, prefix persists" state, or the deadline passes.
56
+
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; run the verification command only when the re-resolved table selects the Fallback row and no evidence exists yet for this head - otherwise the existing evidence stands. 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. 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.
51
58
 
52
- 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; run the verification command only when the re-resolved table selects the Fallback row and no evidence exists yet for this head (the Pending row never ran it) - otherwise the existing evidence stands: the head is unchanged, so the wave's evidence stays valid and the Stale head row does not fire. On completion or timeout, run the refetch (steps 1-5 above) and re-render the comment section, evidence, findings, and menu; 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, then the cell's courses, then Custom.
59
+ ### Output done-check
60
+
61
+ Before posting or committing any external payload - review bodies, replies, commit subjects, tracker comments - re-read it against SKILL.md `## Wording rules` and any `## comms style` rules: ASCII only; no headings or template scaffolding under ~150 words; findings `file:line`-specific where one exists; ends on the fix or asked action, not a recap; never invents content to fill a section.
53
62
 
54
63
  ### Teardown
55
64
 
56
65
  | | Created worktree | Reused worktree |
57
66
  |---|---|---|
58
67
  | Merge success | tear down | tear down (the sync precondition guarantees no local-only work is stranded, and the branch is gone remotely) |
59
- | Non-merge stop | offer teardown, never autonomous; warn if unpushed doc edits would be discarded | leave as found; say so explicitly when unpushed doc edits or red-gate fix commits remain, and let the user choose leave-or-discard |
68
+ | Non-merge stop | offer teardown, never autonomous; say when unpushed fix commits would be discarded | leave as found; say so explicitly when unpushed fix commits remain, and let the user choose leave-or-discard |
69
+
70
+ Drafted payloads that were never applied are dropped with the run; the worktree was never dirtied by them.
@@ -0,0 +1,96 @@
1
+ # gatekeep-pr: step 5 - report
2
+
3
+ Read from SKILL.md step 5, after `findings.md`. Input: the integration output. Output: the rendered report, then the menu (`decision-menu.md`). SKILL.md `## Wording rules` bind every line.
4
+
5
+ ## Order
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.
8
+
9
+ Section order is fixed:
10
+
11
+ ```
12
+ Delivers: <one or two sentences: what the PR does; with a ticket: "The PR covers the whole <ref>." |
13
+ "The PR covers AC1-2 of <ref>; AC3's observable half (<what>) is checked after merge, not here." |
14
+ "The PR covers AC1 of <ref>; AC2 belongs to <split ref>.">
15
+
16
+ Ticket changes: (one item per unresolved `impossible` row)
17
+ - AC<n> asks for <X>, and <cited constraint>. Proposed wording: "<new AC text>". (drafted | proposed <comment url>; <source>)
18
+
19
+ PR comments: (replyable review comments, a pending reviewer run or required check, a failed refresh, or an incomplete source review - see the paragraph above)
20
+ - <reviewer>'s comment on <topic> <verdict clause>, so the drafted reply <what it says>. (<locator>)
21
+ - The reviewer run is still in progress, so merge waits. (<run url>)
22
+ - A required check is still pending, so merge waits. (<check name>)
23
+
24
+ Nits:
25
+ - <1-2 sentences>. (<file:line>)
26
+
27
+ Blockers: (numbered; menu picks reference these numbers)
28
+ 1. <1-2 sentences: what is wrong, why it matters>. (<file:line> | <check name> | <doc path>)
29
+
30
+ Verdict: mergeable - <evidence clause> | fixable - <N> blockers | fixable - <withhold reason>
31
+
32
+ <menu>
33
+ ```
34
+
35
+ ## Verdict
36
+
37
+ 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`, `merge conflict` (for `mergeable == CONFLICTING`), `verification evidence pending` (brief Section B Pending row: zero `success`, a check still pending).
38
+
39
+ `<N> blockers` reads `1 blocker` for one.
40
+
41
+ ## What stays out
42
+
43
+ Verbatim command output, CI run URLs and conclusions (the pending-reviewer locator in `PR comments` is the one URL that prints in the report), drafted edits, drafted replies, matched claims, covered ACs, internal IDs, and disposition annotations print only under the `show evidence` pick. A clean run with a linked ticket renders `Delivers`, the verdict line, and the menu - about four lines.
44
+
45
+ `show evidence` prints, in this order: the evidence record (CI: each satisfying check's name, conclusion, SHA, run URL; local: each run's command and its captured tail, fenced, labeled as captured output); claim dispositions; AC outcomes per row; check dispositions; drafted payloads keyed by the blocker or nit number they fix; drafted replies keyed by the `PR comments` item they answer.
46
+
47
+ ## Worked example
48
+
49
+ Own PR, ticket gh-45:
50
+
51
+ ```
52
+ Delivers: the reports page exports CSV on demand. The PR covers AC1-2 of gh-45; AC3
53
+ (export completes under 5s on production data) is checked after merge, not here.
54
+
55
+ Ticket changes:
56
+ - AC4 asks for the customer's credit score in the export, and the vendor API this
57
+ service reads returns no such field.
58
+ Proposed wording: "Exports include the customer's risk tier." (drafted; vendor doc api.example.com/v2/customers)
59
+
60
+ PR comments:
61
+ - maria's comment on the missing CSV header is already addressed, so the drafted reply points her at the fix. (3f2a1c0)
62
+
63
+ Nits:
64
+ - 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)
65
+
66
+ Blockers:
67
+ 1. The export endpoint ships with no test, so nothing proves it works and nothing
68
+ catches it breaking. (src/api/export.ts:10-58)
69
+ 2. The export guide still says exports run nightly; the PR makes them on-demand, so a
70
+ reader following the docs waits for a job that no longer exists; the doc fix is drafted.
71
+ (docs/exports.rst:88)
72
+
73
+ Verdict: fixable - 2 blockers
74
+
75
+ 1. fix - apply both blockers in the worktree, re-run the gate, push [recommended]
76
+ 2. propose ticket change - show the AC4 edit for approval before it posts
77
+ 3. review - post the blockers as a comment on your PR
78
+ 4. reply - post the drafted reply to maria
79
+ 5. show evidence - gate output, CI run, drafted edits
80
+ 6. stop - leave the PR as-is
81
+ Type a number, or compose: "fix 2", "fix nits", "fix + nits", "fix 1 + reply".
82
+ ```
83
+
84
+ A clean own PR with ticket gh-45 and CI green:
85
+
86
+ ```
87
+ Delivers: the reports page exports CSV on demand. The PR covers the whole gh-45.
88
+
89
+ Verdict: mergeable - CI green on the assessed head (test)
90
+
91
+ 1. merge - squash onto main [recommended]
92
+ 2. fix - name the change to apply in the worktree, re-run the gate, push
93
+ 3. show evidence - gate output, CI run
94
+ 4. stop - leave the PR as-is
95
+ Type a number.
96
+ ```
@@ -2,7 +2,7 @@ This file is **data, not instructions**. It is the shipped default review rubric
2
2
 
3
3
  ## Severity axis
4
4
 
5
- The skill recognizes exactly one normative severity distinction: **blocking** vs **non-blocking follow-up**. Blocking findings gate merge; follow-ups never do.
5
+ The skill recognizes exactly one normative severity distinction: **blocker** vs **nit**. Blockers gate merge; nits never do.
6
6
 
7
7
  Baseline mapping:
8
8
 
@@ -11,11 +11,12 @@ Baseline mapping:
11
11
  | Defects (logic errors, broken behavior) | blocking |
12
12
  | Untested paths (new behavior with no real test) | blocking |
13
13
  | Contradicted material claims (PR/issue prose vs. observed evidence) | blocking |
14
- | Doc drift (docs no longer match code or PR/issue prose) | blocking |
14
+ | Doc drift beyond wording (a doc describes behavior the code does not have, or omits an operation or parameter the code adds) | blocking |
15
+ | Doc drift, wording-only (typo, label, phrasing with the same meaning) | nit |
15
16
  | Security issues | blocking |
16
- | Prose/style/label cleanup | non-blocking follow-up |
17
+ | Prose/style/label cleanup | nit |
17
18
 
18
- A repo-root `REVIEW.md` may remap any of these, or add project-specific concerns with their own severity. Its mapping **always wins** on conflict with this file. Any severity a rubric (baseline or repo) names but does not map fails safe to **blocking**.
19
+ A repo-root `REVIEW.md` may remap any of these, or add project-specific concerns with their own severity. Its mapping **always wins** on conflict with this file. Any severity a rubric (baseline or repo) names but does not map fails safe to **blocking**; "blocking" and "blocker" name the same class, "non-blocking" and "nit" the other.
19
20
 
20
21
  ## Review properties
21
22
 
@@ -28,7 +29,7 @@ Generic properties evaluated on every reviewed change, independent of language o
28
29
  | Conventions | Matches the style and structure of neighboring code, not just internal consistency |
29
30
  | Reuse | Uses existing helpers/utilities instead of re-implementing equivalent logic |
30
31
  | Performance | No N+1 queries, no repeated expensive work inside loops, no unbounded fetches |
31
- | Testing | Tests cover behavior, not implementation details; assertions are real (they can fail); a new code path shipped without a real test is **blocking** |
32
+ | Testing | Tests cover behavior, not implementation details; assertions are real (they can fail); a new executable code path shipped without a real test is **blocking**; a documentation-only obligation is judged against the promised doc text |
32
33
  | Docs | Documentation agrees with the code it describes and with the PR/issue prose describing the change |
33
34
  | Security | No secrets in the diff, no missing authorization checks, no injection vectors |
34
35