pi-gauntlet 7.29.0 → 7.31.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,22 @@
1
1
  # Changelog
2
2
 
3
+ ## v7.31.0 - 2026-10-23
4
+
5
+ ### Added
6
+
7
+ - **`gatekeep-pr` accepts a set of PRs in pi**, for example `/skill:gatekeep-pr 12 15 18 --rebase main`. One fresh `pr-gatekeeper` orchestrator and worktree per PR keep nested Verify/Review dispatch and consent isolated; the parent shows a summary and ordered per-PR menus, then runs queued picks in parallel. Sets are bounded by pi-cohort's `parallel.maxTasks` (default `8`), with one eligible-other-author rebase confirmation per set. Single-PR behavior is unchanged; multi-PR mode is pi-only. Requires pi-cohort >= 7.4.5 (a `subagent`-enabled persona could not start with inherited extensions before, [pi-cohort #26](https://github.com/jjuraszek/pi-cohort/issues/26)). (#79)
8
+
9
+ ## v7.30.0 - 2026-10-23
10
+
11
+ ### Added
12
+
13
+ - **Brainstorming accepts an open PR as its subject.** `/skill:brainstorming <ref>` resolves `#N`, `owner/repo#N`, or a PR URL to a pull request first and gathers from its title, body, diff, unresolved review threads, and linked ticket; constraints are read from the base branch's HEAD docs, not the fork point, and the spec carries a `## Rework record` naming what the rework keeps and changes, proposing a repair of the original over a rewrite where one satisfies the current rules (`skills/brainstorming/reference/pr-subject.md`). `using-git-worktrees` checks out the PR's head branch at the PR head SHA (`reference/pr-head.md`; a branch already checked out, an unreachable head, or a fork head stops before any write), spec and implementation commits append to the PR branch, and `finishing-a-development-branch` offers "push to the PR branch": it saves the pre-rework head under `refs/gauntlet/pre-rework/pr-<N>`, pushes without force, appends a `## Rework` block to the PR description with one line per review thread, and stops at the menu when the push would need force. `gauntlet-handoff` and `gauntlet-resume` carry the PR reference and pre-rework head so a fresh session resumes on the PR branch in the same worktree. Ticket and free-text subjects are unchanged. (#78)
14
+ - Eval samples `pr-subject-resolution`, `pr-subject-repair-over-rewrite`, `pr-subject-rule-added-later`, and `pr-subject-weakened-rule` under `eval/brainstorming/`, `pr-head` under `eval/using-git-worktrees/`, `pr-subject-threads` under `eval/writing-plans/`, `pr-subject-push` under `eval/finishing-a-development-branch/`, and `pr-subject-brief` under `eval/gauntlet-resume/`. (#78)
15
+
16
+ ### Changed
17
+
18
+ - The withheld-push finishing menu reads a bare numeric reply by its displayed label (2-6), so `1` is refused while the push row is withheld. (#78)
19
+
3
20
  ## v7.29.0 - 2026-10-23
4
21
 
5
22
  ### Added
package/README.md CHANGED
@@ -36,13 +36,13 @@ pi-gauntlet's only hard dependency is pi-cohort - every gate that dispatches a r
36
36
  Concretely, one change through the gauntlet:
37
37
 
38
38
  0. *(Optional)* Before there's even a spec, `/skill:shape-ticket` can create one tracker issue or repair a comma/whitespace list of issue refs - shaping each into a Context/Problem/Idea/Acceptance Criteria ticket, grounded by a repository audit, an AC integrity check, and a cheap council roast. Repair finishes preparation for all tickets before presenting exact-diff confirmations sequentially in ref order; each approval writes that ticket before the next gate, and may include one optional gated comment for a Reporter note or overlap evidence. The audit checks factual claims, Idea fit, AC mechanisms against cited repo guidance, and already-stated observable legs in configured sibling checkouts. It reports in-motion work and attempts a best-effort scenario through authorized current-system reads, without promising reproduction or a fix; missing access or nothing to observe is reported, not a new approval gate. Implementer detail moves into the gated Reporter note; brainstorming reads that note with the ticket so the spec retains the detail. A failed roast is retried once, then surfaced inline at the gate if it fails again. It's a tool, not a phase: no worktree, no plan/phase tracker, runs from any repo state. The agent may start it on its own when the ask is "file a ticket" or another skill routes there; the write still waits for your yes on the presented diff. Similarly, `/skill:chase-bug` triages a raw bug report into an evidenced verdict - and can hand off to shape-ticket, brainstorming, or a bounded hotfix - before any spec exists.
39
- 1. Opt in with `/skill:brainstorming` and describe the change. The command is the reliable discovery-and-entry path; explicit prose requesting brainstorming/the gauntlet also supplies consent when the skill is already loaded or an available instruction or route identifies it. An ordinary implementation request or an agent recommendation is not consent. **`brainstorming`** sets up an isolated worktree, explores the codebase, and turns your description into a written spec - when its recon finds a cited case that the ask is the wrong change (misdiagnosed, wrong-sized, already solved, a band-aid, or cutting across a sound pattern - including a repo convention that is itself debt) and can name the alternative, question one is a framing question (`as framed`, `pivot: <alternative>`, or `smaller or none`); otherwise it opens with one sentence naming what it checked, and every approach it proposes ends with a `Pattern:` line naming what it follows or cuts across; when the run starts from a ticket, the spec carries the ticket's acceptance criteria verbatim, each with a disposition (`in-scope`, `deviates:`, `deferred:`, `venue:`, `elsewhere:` for a row another repo's part delivers), scope cuts come only from your answer to a cut question and are listed at the gate, the conformance gate checks the in-scope ones, and `/skill:check-delivery` verifies `venue:` rows before the prod deploy. A multi-model critique runs on it automatically. If the spec replaces a known prior spec, brainstorming marks the predecessor with a `> **Superseded by:**` banner under its title (default format, syntax overridable via `.pi/gauntlet-overrides.md`; candidates come from brainstorming's scout recon, never a mechanical sweep). **You read and approve the spec - human gate 1.** No implementation code exists yet.
39
+ 1. Opt in with `/skill:brainstorming` and describe the change. The command is the reliable discovery-and-entry path; explicit prose requesting brainstorming/the gauntlet also supplies consent when the skill is already loaded or an available instruction or route identifies it. An ordinary implementation request or an agent recommendation is not consent. A leading GitHub reference (`#N`, `owner/repo#N`, or a `/pull/N` or `/issues/N` URL) is resolved as a PR first: an open PR, including a draft, starts rework on its existing in-repo branch; a non-PR follows the ticket path. Trailing text carries intent; mid-sentence references and other ticket/free-text subjects keep their existing paths. The PR diff is the starting state, judged against constraints from the fetched current base HEAD, not the fork point or the PR's proposed policy edits; see the [PR-subject contract](skills/brainstorming/reference/pr-subject.md) and [PR-head worktree owner](skills/using-git-worktrees/reference/pr-head.md). **`brainstorming`** sets up an isolated worktree (on the existing branch for PR rework), explores the codebase, and turns the subject into a written spec - when its recon finds a cited case that the ask is the wrong change (misdiagnosed, wrong-sized, already solved, a band-aid, or cutting across a sound pattern - including a repo convention that is itself debt) and can name the alternative, question one is a framing question (`as framed`, `pivot: <alternative>`, or `smaller or none`); otherwise it opens with one sentence naming what it checked, and every approach it proposes ends with a `Pattern:` line naming what it follows or cuts across; when the run starts from a ticket, the spec carries the ticket's acceptance criteria verbatim, each with a disposition (`in-scope`, `deviates:`, `deferred:`, `venue:`, `elsewhere:` for a row another repo's part delivers), scope cuts come only from your answer to a cut question and are listed at the gate, the conformance gate checks the in-scope ones, and `/skill:check-delivery` verifies `venue:` rows before the prod deploy. A multi-model critique runs on it automatically. If the spec replaces a known prior spec, brainstorming marks the predecessor with a `> **Superseded by:**` banner under its title (default format, syntax overridable via `.pi/gauntlet-overrides.md`; candidates come from brainstorming's scout recon, never a mechanical sweep). **You read and approve the spec - human gate 1.** Before approval, PR rework permits only the base merge, conflict resolution, and scoped conflict tests outside the spec directories; other subjects have no implementation code yet.
40
40
  2. By default, **`writing-plans`** decomposes the approved spec into atomic, independently-verifiable tasks, grouped into parallel waves where they don't touch the same files. Experimental approval options 4/5 instead select **`loop-implementation`**: the plan phase is skipped and a stamped acceptance-criteria ledger replaces the task plan. Option 4 retains amendment review; option 5 grants auto-apply amends, with redraws still stopping. See [Loop engine operations](doc/configuration.md#loop-engine-operations).
41
41
  3. **`subagent-driven-development`** executes the plan one task at a time, each in a fresh subagent, behind spec-compliance review then code-quality review. TDD-locked: red, green, refactor. Independent implementation, review, and council batches remain parallel, but their dispatches are foreground: the orchestrator waits for terminal results before accepting work or advancing a phase.
42
42
  The loop alternative to step 3 runs one fresh `loop-implementer` per seed or row iteration, without implement-phase reviewers. It stops for a human decision on stagnation or a failed exit verification; passing full verification with every row closed enters the same verify gates.
43
43
 
44
44
  4. **verify**: the parent first runs the plan's full verification command set (the ledger's `verification:` command for loop); only a passing result permits whole-diff code review, then the **conformance gate**. A review fix invalidates that result, so the parent reruns full verification before the next review or conformance gate. When the overrides file declares a happy path for the touched paths (see [Project-specific overrides](#project-specific-overrides)), the parent runs it once here, between code review and the conformance gate, and the reviewer reads its transcript as runtime evidence - a hang or a failed request shows up in the audit with the transcript quoted - as a gap when an origin clause ties to it, otherwise as a `happy-path: failed - unattributable` line - never as a silent green. The conformance subagent reads the finished code and docs against your *original words* from step 1, not the plan, and reports per-requirement: delivered, partial, missing, drifted, or unauthorized. Unauthorized rows - shipped surface no human input asked for, whether it crept in or was laundered through the spec - follow their recommendation like every other row: a contained removal auto-runs, anything another requirement leans on is deferred with a plain-language "I'd cut it / I'd keep it" recommendation. Inside a brainstorming-entered flow this gate is machine-blocked from being skipped. Compatible executable recommendations auto-run through an isolated fix-and-re-audit loop with no prompt; anything still open surfaces as a numbered decision brief - one item per decision, the problem in plain words first, lettered options each with a one-line consequence, and `Recommended: <letter> - <reason>` or `No recommendation - pick one`. Reply with one line per item (`1. B`), or bare `1` to take every recommendation (no-recommendation items are re-asked by number); a current `CONFORMS` / no-concerns result goes straight to the branch options with no extra conformance sign-off.
45
- 5. **`finishing-a-development-branch`**: PR, draft PR, squash, keep, or discard. Once a PR exists, run `/skill:gatekeep-pr <pr>` to verify it before merging - it judges the diff against the spec at the PR's head (the acceptance-criteria dispositions and Design you approved; the PR description when no spec applies) - rows the spec defers or deviates are settled, every other row needs its mechanism (code, tests, docs) - and against the rubric; whatever can be seen only after merge is left to step 6. **Human gate 2** - the only other decision you make.
45
+ 5. **`finishing-a-development-branch`**: PR, draft PR, squash, keep, or discard; PR rework also offers a non-force push to the existing PR branch. That push preserves history and appends a `## Rework` block while retaining the original description bytes. Its `Before:` handle includes the full portable pre-rework SHA and the local `refs/gauntlet/pre-rework/pr-<N>` ref in the rework checkout. The [finishing owner](skills/finishing-a-development-branch/SKILL.md#pr-subject-row-1-push-to-the-pr-branch) defines the landing and body-update contract. Once a PR exists, run `/skill:gatekeep-pr <pr>` to verify it before merging - it judges the diff against the spec at the PR's head (the acceptance-criteria dispositions and Design you approved; the PR description when no spec applies) - rows the spec defers or deviates are settled, every other row needs its mechanism (code, tests, docs) - and against the rubric; whatever can be seen only after merge is left to step 6. **Human gate 2** - the only other decision you make.
46
46
  6. *(Optional)* Once the merge lands, `/skill:check-delivery <ref>` can prove delivery - default-branch landing, delivery target, per-AC evidence - before the tracker status advances. Explicit ask only, no auto-chain: deploys commonly lag merges by minutes to hours, so an auto-run would routinely check too early.
47
47
 
48
48
  Only the machine-owned `plan -> implement` and `verify -> ship` handoffs receive a branch-local one-shot nudge after an unexpected settled stop; it is fire-and-forget, does not bypass either human gate, and older Pi hosts without `agent_settled` retain existing behavior.
@@ -73,14 +73,14 @@ Everything between gate 1 and gate 2 - task breakdown, implementation, both revi
73
73
 
74
74
  ## Architecture
75
75
 
76
- `gatekeep-pr` retains explicit, action-scoped `fix` and `merge base` grants for one run so covered repairs can continue to a fresh final menu. Bare `fix` covers source-verified blockers within the agreed scope; named fixes retain only their selected IDs for later reassessments. Nits are one-shot. Final merge, approval, external posts, workflow approval, check dispositions, and contract changes require separate decisions. Exact PR-description corrections are `fix` targets that require exact-text selection and readback plus a narrow recheck; they cannot clear independent test or scope blockers. See the [next-action and consent protocol](skills/gatekeep-pr/reference/post-selection-loop.md).
76
+ `gatekeep-pr` retains explicit, action-scoped `fix` and `merge base` grants for one run so covered repairs can continue to a fresh final menu. Bare `fix` covers source-verified blockers within the agreed scope; named fixes retain only their selected IDs for later reassessments. Nits are one-shot. Final merge, approval, external posts, workflow approval, check dispositions, and contract changes require separate decisions. Exact PR-description corrections are `fix` targets that require exact-text selection and readback plus a narrow recheck; they cannot clear independent test or scope blockers. See the [next-action and consent protocol](skills/gatekeep-pr/reference/post-selection-loop.md). In pi, `/skill:gatekeep-pr 12 15 18 [--rebase [base]]` gates a set of PR numbers or same-repository URLs, with one fresh `pr-gatekeeper` child and one worktree per PR. The parent shows a summary and unchanged per-PR menus in input order, queues every pick until the last menu, then executes the queued work in parallel. The set must fit pi-cohort's `parallel.maxTasks` (default `8`); it is never chunked. With `--rebase`, the parent asks once per set for eligible other-author or unreadable-author heads, excluding no-op heads and heads with merge commits. Multi-PR mode rejects positional issue references and is pi-only; single-PR behavior on Claude Code is unchanged. See the [parent-child contract](skills/gatekeep-pr/reference/batch.md).
77
77
 
78
78
  The run's grants, target/scope baseline, repair usage, and poll deadline live in one OS temporary continuation record, whose path appears in the tracker or checklist and `show evidence`. Missing or invalid state pauses authority without resetting the budget. Terminal exit deletes the record; OS cleanup can remove it earlier, and a new session never discovers authority automatically. This is a skill protocol, not hard runtime enforcement.
79
79
 
80
80
  pi-gauntlet ships three kinds of pieces, layered on top of pi-cohort's dispatch:
81
81
 
82
82
  - **21 skills** - the workflow logic. Sixteen activate automatically when pi sees the matching kind of task, and the workflow skills chain through their gates: `shape-ticket` (creates one tracker issue or repairs a comma/whitespace list of refs against a Context/Problem/Idea/Acceptance-Criteria template, with a grounded repo audit, an AC integrity check, a cheap council roast, and sequential per-ticket exact-diff write confirmations; model-invocable so other skills and commands can route "file a ticket" to it, the gate still needs your yes), `gatekeep-pr` (model-invocable on an explicit ask that names the PR and the verb; the gates still need your selection - is consent-gated pre-merge verification of a PR against the scope contract at its head - the approved spec's acceptance-criteria dispositions and Design when the PR carries one, else the PR's stated intent; the ticket is cross-checked, never judged here (`check-delivery` owns it) - 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, including rows deferred or deviating per spec, 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, or holds the rebased head locally when a conflict stop's scoped tests fail, so the normal `fix` row resolves it before the lease push; after your own merge two opt-in rows post the `AC coverage` block to the ticket or the PR; every terminal menu offers a recommended `cleanup` row that tears down the gate's worktree and branch and runs the overrides file's `## cleanup` steps - run it with `/skill:gatekeep-pr <pr> [--rebase [base]]`), `check-delivery` (model-invocable on an explicit ask that names the ticket and the verb; the gates still need your selection - 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>`), `writing-plans`, `roasting-the-spec`, `test-driven-development`, `subagent-driven-development`, `loop-implementation` (the plan-free implement driver that iterates a fresh `loop-implementer` over the spec's acceptance-criteria ledger when approval picks the loop engine), `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). Five 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. `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>]`.
83
- - **10 subagent personas** - the specialized child agents the skills dispatch via pi-cohort: `implementer`, `loop-implementer`, `code-reviewer`, `spec-reviewer`, `conformance-reviewer`, `spec-summarizer`, `plan-writer`, `spec-council-member`, `spec-council-synthesizer`, `spec-council-applier`. See [doc/personas.md](./doc/personas.md) for what each one does and why its permissions are scoped the way they are.
83
+ - **11 subagent personas** - the specialized child agents the skills dispatch via pi-cohort: `implementer`, `loop-implementer`, `code-reviewer`, `spec-reviewer`, `conformance-reviewer`, `spec-summarizer`, `plan-writer`, `spec-council-member`, `spec-council-synthesizer`, `spec-council-applier`, `pr-gatekeeper`. See [doc/personas.md](./doc/personas.md) for what each one does and why its permissions are scoped the way they are.
84
84
  - **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, and that every test command carries a passing base-commit result; 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.
85
85
 
86
86
  pi-gauntlet is **opinionated**: it recommends this pipeline for non-trivial changes, but starts `brainstorming` only after explicit user consent. Enforcement is opt-in by entry, not ambient: once brainstorming starts a flow, the phase-tracker extension mechanically blocks a phase from closing before its gate runs, and warns once if the main loop writes code during implement (subagents own implement-phase edits). A change made *without* entering the flow (a typo, a formatting run, a dependency bump - see "When to use / when NOT to use") is not gated; the discipline of routing real work through the pipeline is a convention the tooling supports, not a trap it springs on every edit.
@@ -124,7 +124,7 @@ Run by a human against pi-cohort >= 7.1.0, before a pi-gauntlet release claims t
124
124
  ## Requirements
125
125
 
126
126
  - [pi-coding-agent](https://github.com/earendil-works/pi) ≥ 0.85.1 - tested minimum for sequential `phase_tracker` tool execution, which persists a tracker update before a later tool call in the same model message.
127
- - [pi-cohort](https://github.com/jjuraszek/pi-cohort) >= 7.4.5 - required peer package, including binary-safe worktree patches. Skills that dispatch agents (`requesting-code-review`, `subagent-driven-development`, `dispatching-parallel-agents`, `writing-plans`, `forge-skill`, `shape-ticket`, `roasting-the-spec`) call `subagent({})`, which pi-cohort provides. pi-gauntlet does not vendor the dispatch tool; without pi-cohort those skills have nothing to call. Gauntlet execution dispatches explicitly use `async: false`; do not enable pi-cohort's incompatible `forceTopLevelAsync` setting. See pi-cohort's [dispatch configuration](https://github.com/jjuraszek/pi-cohort/blob/main/doc/configuration.md) for that setting's owner and semantics. A wave of N >= 1 tasks dispatches its implementers and first spec reviews as one chain call. `subagent-driven-development` stops before dispatch on unsupported installs; there is no older-runtime fallback.
127
+ - [pi-cohort](https://github.com/jjuraszek/pi-cohort) >= 7.4.5 - required peer package, including binary-safe worktree patches; it also lets a persona with `subagent` in `tools:` start with inherited extensions ([pi-cohort #26](https://github.com/jjuraszek/pi-cohort/issues/26)), which multi-PR `gatekeep-pr` needs. Skills that dispatch agents (`requesting-code-review`, `subagent-driven-development`, `dispatching-parallel-agents`, `writing-plans`, `forge-skill`, `shape-ticket`, `roasting-the-spec`) call `subagent({})`, which pi-cohort provides. pi-gauntlet does not vendor the dispatch tool; without pi-cohort those skills have nothing to call. Gauntlet execution dispatches explicitly use `async: false`; do not enable pi-cohort's incompatible `forceTopLevelAsync` setting. See pi-cohort's [dispatch configuration](https://github.com/jjuraszek/pi-cohort/blob/main/doc/configuration.md) for that setting's owner and semantics. A wave of N >= 1 tasks dispatches its implementers and first spec reviews as one chain call. `subagent-driven-development` stops before dispatch on unsupported installs; there is no older-runtime fallback.
128
128
 
129
129
  Both packages must be listed in your `.pi/settings.json#packages` array (pi adds them automatically when you `pi install`). pi-gauntlet and pi-cohort are versioned independently but release together whenever dispatch semantics change - pin compatible versions of both. At each pi-gauntlet release, set the pi-cohort peer minimum and README floor together to the newest released pi-cohort version; `/skill:release` checks the registry before proceeding.
130
130
 
@@ -0,0 +1,39 @@
1
+ ---
2
+ name: pr-gatekeeper
3
+ description: "runs one PR of a gatekeep-pr multi-PR set to its report or executes one queued pick. Not for direct dispatch."
4
+ tools: read, grep, find, ls, bash, edit, write, subagent, gauntlet_setting, plan_tracker
5
+ defaultContext: fresh
6
+ inheritProjectContext: true
7
+ inheritSkills: false
8
+ systemPromptMode: replace
9
+ completionGuard: false
10
+ ---
11
+
12
+ Run one supplied PR to its report or execute one queued pick. Keep each PR isolated so helpers, evidence, and authority never cross into a sibling. Return one result line, never a human menu or a question.
13
+
14
+ Use the supplied record and report paths only. Never re-initialize a supplied record or mint a second record/report. Validate repository/number identity, worktree, and HEAD before work. Treat the injected skill and batch contract as the procedure; use the supplied mode, PR, base, rebase flag, confirmation, optional answer, and paths. Read the injected procedure's named dependencies using the caller-supplied absolute procedure root or absolute paths. Do not discover unrelated skills or procedure files.
15
+
16
+ ## Method
17
+
18
+ - In `assess`, run steps 1-5 in the provisioned worktree. Skip the already completed provisioning fetch/gitignore/add only; verify HEAD against the supplied headRefOid and preserve sync's safety fetch. Keep trusted merge-base configuration and assessed-head binding. Dispatch Verify then Review sequentially as fresh helpers with cwd the same worktree, never new helper worktrees.
19
+ - In `execute`, refresh before writing. If the head moved or the picked row disappeared, return a fresh report without executing the stale pick. Otherwise run step 7 and its next-action/fix-wave loop. Keep retained grants, round caps, deadline, ledger, helper paths, and no-progress decisions PR-local. Never reset consent or counters on re-entry.
20
+ - Substitute supplied rebase confirmation at the existing ask only: yes proceeds; no records `sync: declined - other author's branch`, stage skipped, then assesses unsynced. Use supplied `verification command: <command>` or `spec: <path>` at that ask only, re-assessing from the start with the same record. Never treat answers as broader authority.
21
+ - Track single-PR stages with session-local plan_tracker or a checklist. Delegate tracked source edits and commits to implementer; perform only orchestrator writes authorized by the injected procedure. Never push, edit PR metadata, comment, review, or write to a tracker before direct human authority, except the requested sync push. Never choose a sibling's actions.
22
+
23
+ ## Recovery and output
24
+
25
+ Apply the injected batch-only outcome adapter without changing single-PR behavior. On sync failure first run the existing recovery to restore pre_head and assert exact HEAD and a clean tree. Never retry unsafe checkout or continue after a failed recovery assertion.
26
+
27
+ | Event | Result |
28
+ |---|---|
29
+ | Unresolved stop, implementer open, residue, or sync reviewer finding | After recovery record sync failure; no rendered report; CONFLICTING |
30
+ | Rebase command or server push rejection | After recovery FAILED with sync reason |
31
+ | Terminal sync precondition, baseline mismatch, lease refusal | FAILED with sync reason, never conflict |
32
+ | Without rebase, false/unreadable pushability | FAILED before Verify, naming head destination |
33
+ | Required fallback verification unavailable | FAILED with no-verification-evidence reason |
34
+ | Empty, off-contract, non-zero, or async helper output | FAILED naming helper role and quoted failure |
35
+ | Other steps 1-5 and 7 outcomes | Write supplied report and return REPORT |
36
+
37
+ Return only the enumerated pre-report asks: `STOPPED - verification command - <subproject dirs seen>` or `STOPPED - spec candidate - <paths>`. Stop without further writes at the ask. UNKNOWN merge state after the single re-poll returns a report with merge withheld. Return reports for step-7 contract, no-progress, and budget menus. Leave all menus, confirmations, answers, cleanup, stop consequences, and retirement to the parent; never tear down a live child's tree.
38
+
39
+ Write the rendered report only to the supplied path. For CONFLICTING leave that report unrendered and include the sync record. Make exactly one of these the last line, with nothing after it: `REPORT <report path>`, `CONFLICTING <report path> - <sync record>`, `FAILED - <reason>`, `STOPPED - <ask> - <what the user must supply>`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-gauntlet",
3
- "version": "7.29.0",
3
+ "version": "7.31.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",
@@ -18,6 +18,8 @@ You **may** read code/docs, run the existing system to observe current behavior,
18
18
 
19
19
  You may **not** write outside the resolved spec dirs; build, deploy, validate, or exercise the proposed change; run implementation skills; commit the spec on `main`; or start `/skill:writing-plans`.
20
20
 
21
+ For a PR subject, the base merge in `using-git-worktrees/reference/pr-head.md` - its merge commit, its conflict helper's edits to the conflicted files, and its scoped conflict tests - is the one pre-approval write and run outside the spec dirs; nothing else moves.
22
+
21
23
  The line: current-system observation is research; exercising the proposed change waits for approval.
22
24
 
23
25
  ## Foreground dispatch policy
@@ -42,7 +44,7 @@ Work through the items below **in order**. After a user answer or completed side
42
44
  phase_tracker({ action: "start", phase: "brainstorm" })
43
45
  ```
44
46
 
45
- 2. **Set up the worktree** - see [Worktree First](#worktree-first).
47
+ 2. **Set up the worktree** - resolve a tracker reference first per `reference/pr-subject.md`; pass a PR subject's head to `/skill:using-git-worktrees`' PR-head path. Then see [Worktree First](#worktree-first).
46
48
  3. **Gather context** - read [Ticket Handling](#ticket-handling), then follow [`gatherer.md`](gatherer.md) without a human stop.
47
49
  4. **Understand the idea against the draft** - if `reference/ticket-acceptance.md` is not already in context this session (restored entry, or a redraw after a fresh start), read [Ticket Handling](#ticket-handling) before the rest of this step; then see [Understand the idea](#3-understand-the-idea).
48
50
  5. **Propose 2-3 approaches** - see [Explore approaches](#4-explore-approaches).
@@ -63,7 +65,7 @@ Detection order: (1) infer from ticket labels, description, or paths; (2) use th
63
65
 
64
66
  ## Worktree First
65
67
 
66
- The spec is the first commit in a dedicated worktree, never a separate commit on `main`.
68
+ The spec is the first commit in a dedicated worktree, never a separate commit on `main`. For a PR subject, the worktree is the PR's branch and the spec commit is appended to it.
67
69
 
68
70
  Invoke `/skill:using-git-worktrees`; use `.worktrees/` or the project-native location. Carry its `Worktree ready at <full-path>` value: spec path `<full-path>/<spec dir>/<filename>.md`; every dispatch uses that `cwd`; git uses `git -C <full-path>`. Keep the process cwd unchanged.
69
71
 
@@ -87,7 +89,7 @@ If any test fails, offer no split. Never split by service, package, repo, layer,
87
89
 
88
90
  ### 3. Understand the idea
89
91
 
90
- Clarify freely.
92
+ Clarify freely. For a PR subject, run the framing check against the PR's diff as the ask (`reference/pr-subject.md`).
91
93
 
92
94
  `Read` the gathered draft unconditionally before question one. Treat it as a helper, not a fence: verify load-bearing claims from primary code, and confirm whether the codebase or ecosystem already solves the problem.
93
95
 
@@ -140,7 +142,7 @@ Propose 2-3 genuinely distinct approaches and name what separates them (the axis
140
142
 
141
143
  ### 5. Design for clarity and isolation
142
144
 
143
- Prefer clear testable boundaries, YAGNI, existing conventions when they are sound - when the convention is the debt, say so and do not extend it - the owning schema/contract rather than parallel state, and explicit errors and edge cases.
145
+ Prefer clear testable boundaries, YAGNI, existing conventions when they are sound - when the convention is the debt, say so and do not extend it - the owning schema/contract rather than parallel state, and explicit errors and edge cases. Retain every existing user-approved design detail in approaches and design, including when drafting or replacing the spec.
144
146
 
145
147
  ### 6. Present the design in two rounds
146
148
 
@@ -162,6 +164,8 @@ Inside an opted-in flow, shared schemas, contracts, and invariants require a spe
162
164
 
163
165
  Write under the routed spec dir: pick the routing prose's dir (overrides file, `AGENTS.md`) when it is one of the resolved dirs, else the one resolved dir from `gauntlet_setting({ key: "flowGuards" })` that already exists in the repo, else the first resolved dir (`doc/specs` under the default). Name ticketed files `YYYY-MM-DD-<ticket-id>-<topic>.md`, otherwise `YYYY-MM-DD-<topic>.md`. Use a filename-safe tracker slug (`E-12345`, `gh-123`), but native references in plan headers and commits. `<topic>` is 3-6 kebab-case words without `-design` or another suffix.
164
166
 
167
+ For a PR subject, apply `reference/pr-subject.md`: keep the bound-ticket filename; name an unbound spec `YYYY-MM-DD-pr-<N>-<topic>.md` from the PR title.
168
+
165
169
  Mint the slug once during gather and reuse it. If questionary invalidates it, write to the new path and delete the uncommitted draft.
166
170
 
167
171
  ## Spec Self-Review (Before User Review Gate)
@@ -70,7 +70,8 @@ Scout (always dispatched):
70
70
  > independently of context-builder, in pages of 50 until a short page. Match sections
71
71
  > anywhere in each comment regardless of author; accept either ordering with
72
72
  > `Overlap evidence:` and exclude that evidence part. Include every matched section's
73
- > terms in the query. Run
73
+ > terms in the query, keeping literal file paths intact rather than shortening them
74
+ > to stems. Run
74
75
  > `node <SPEC_INDEX> --query '<keywords>' --limit 10` from the worktree root,
75
76
  > keeping the keywords inside single quotes, and treat its rows as the candidate
76
77
  > list; zero rows means the index found no evidence, not that no predecessor exists -
@@ -124,6 +125,29 @@ Scout (always dispatched):
124
125
  > "Open questions that matter for the spec"
125
126
  > section. Compact handoff, not a dump.
126
127
 
128
+ For a PR subject, supply this additional scout block as values from
129
+ `reference/pr-subject.md` (keep scout unconditional):
130
+
131
+ > PR title and body verbatim: `<title>`, `<body>`.
132
+ > PR-carried specs: `<paths and stated contracts>`.
133
+ > Changed paths: `<git diff --name-only <BASE_SHA>...<PRE_REWORK_HEAD> output>`.
134
+ > Read the diff itself from the worktree. Unresolved threads: `<full comment threads
135
+ > in order, with ids and locations from using-git-worktrees/reference/pr-head.md,
136
+ > or threads: not fetched (<reason>)>`.
137
+ > Merge result: `<merge line, contract exceptions, scoped test results>`.
138
+ > Authoritative base-HEAD constraints: `<git show <BASE_SHA>:<path> texts for
139
+ > AGENTS.md, first-found overrides, REVIEW.md, and all cited constraint documents;
140
+ > explicit missing-document results>`. These govern on every merge outcome,
141
+ > including clean; worktree policy edits are proposals under `changes:` or
142
+ > `dropped claims:`, never constraints.
143
+ > The PR is the starting state, not the target. Judge every PR-stated claim against
144
+ > the diff and supplied base rules. Name the smallest repair satisfying them;
145
+ > propose a rewrite only where no repair can. Render `## PR under rework` with
146
+ > `keeps:`, `changes:`, `dropped claims:`, `constraints added since fork:` (one line
147
+ > per base rule absent at the fork point), and one proposed disposition per
148
+ > unresolved thread: `fix | keep open | decline: <reason>`. Carry merge contract
149
+ > exceptions into `changes:`.
150
+
127
151
  Context-builder (conditional):
128
152
 
129
153
  > Extract external context for an upcoming design discussion. The request:
@@ -156,7 +180,10 @@ Context-builder (conditional):
156
180
 
157
181
  ## Context-builder trigger rule
158
182
 
159
- Dispatch context-builder when the initial prompt (or a file it explicitly references)
183
+ For a resolved PR subject, dispatch context-builder only for the bound ticket
184
+ using `reference/pr-subject.md`; an unbound PR supplies intent, not ticket ACs.
185
+ Title wins a title/body-first-line ticket conflict; carry the other reference into
186
+ `## Problem` as context. For other subjects, dispatch context-builder when the initial prompt (or a file it explicitly references)
160
187
  contains any of:
161
188
 
162
189
  - an `http(s)://` URL;
@@ -196,6 +223,10 @@ convention; slug minted from the initial prompt):
196
223
  ## Codebase recon
197
224
  <scout output, or the degraded text>
198
225
 
226
+ ## PR under rework
227
+ <PR subject only: scout rework section and supplied PR/base/merge values per
228
+ reference/pr-subject.md; retain these values even when scout failed>
229
+
199
230
  ## External context
200
231
  <context-builder output, degraded text, or unfetched-ref list; omit this section
201
232
  only when the trigger rule never fired>
@@ -134,7 +134,7 @@ Details
134
134
 
135
135
  Letter options in fixed order: `A) accept`, then `B) alt-1`, `C) alt-2` in index order, only for genuine alternatives. Give each option its one-line effect; tie the recommendation's reason to the end goal or a development practice. Record a letter as its shown option's token. Keep custom unlettered as shown. Keep an unavailable option visible and unlettered: `Not available: <option> - <missing prerequisite>`. Use exactly `Recommended: <letter> - <reason>` or `No recommendation - pick one`; the prepared-item schema requires a recommendation, so this amendment menu never renders the second form.
136
136
 
137
- Reply grammar: one line per item, `<n>. <letter or free text>`; bare `1` applies every recommendation and re-asks any item without one by number, never re-rendering the menu (vacuous here: the prepared-item schema always supplies a recommendation); an omitted item keeps its recommendation; free text is `custom(<text>)` and may redirect anywhere ("keep the spec, fix the parser") - a redirect drops the item (still recorded in the batch commit body as `custom(<effect>)`) and returns the finding to its calling loop; a letter always picks its shown option, even when free text would spell its name. A legacy `2: <handle>=<choice>` reply is accepted silently as the same picks; the form is never rendered in a prompt. Invalid number or letter -> reprompt for that item only, keep every valid pick, never reopen the gate; a bare digit other than `1` is an invalid reply and never a grant (reprompt the same way). A bare letter or bare `1` mixed with numbered lines is invalid; free text matching an option's name remains `custom(<text>)`. Resolve every item before applying; retain original numbers during reprompts. Take no action before the reply. After the reply lands and the batch applies, print one confirmation line `Recorded: <handle>=<choice>, ...` covering the batch's decided items (a custom choice keeps the reply's free text verbatim; grant-applied items record their recommended choice); this is the only place `<handle>=<choice>` appears to the human.
137
+ Reply grammar: one line per item, `<n>. <letter or free text>`; bare `1` applies every recommendation and re-asks any item without one by number, never re-rendering the menu (vacuous here: the prepared-item schema always supplies a recommendation); an omitted item keeps its recommendation; free text is `custom(<text>)` and may redirect anywhere ("keep the spec, fix the parser") - a redirect drops the item (still recorded in the batch commit body as `custom(<effect>)`) and returns the finding to its calling loop; a letter always picks its shown option, even when free text would spell its name. A legacy `2: <handle>=<choice>` reply is accepted silently as the same picks; the form is never rendered in a prompt. Invalid number or letter -> reprompt for that item only, keep every valid pick, never reopen the gate; a bare digit other than `1` is an invalid reply and never a grant (reprompt the same way). A bare letter or bare `1` mixed with numbered lines is invalid; free text matching an option's name remains `custom(<text>)`. Execute a custom instruction as given: apply its spec-edit portion, including documentation of a retained value. Treat it as a redirect only when the instruction directs work elsewhere, never merely because the reply is free text. Resolve every item before applying; retain original numbers during reprompts. Take no action before the reply. After the reply lands and the batch applies, print one confirmation line `Recorded: <handle>=<choice>, ...` covering the batch's decided items (print each custom choice as `<handle>=custom(<text>)`, retaining the literal `custom(...)` wrapper with the reply's free text verbatim inside it; grant-applied items record their recommended choice); this is the only place `<handle>=<choice>` appears to the human.
138
138
 
139
139
  ## 5. Apply, aftermath, commit
140
140
 
@@ -0,0 +1,51 @@
1
+ # PR subject
2
+
3
+ ## Resolve before provisioning
4
+
5
+ Match only a trimmed leading GitHub reference: `#N`, `owner/repo#N`, or a GitHub `/pull/N` or `/issues/N` URL, optionally followed by free text carried as intent. Keep mid-sentence mentions on the existing free-text/context-builder path. Keep Linear/Jira and other free-text subjects on their existing paths.
6
+
7
+ Complete classification in order, never in parallel: `gh issue view` can return a PR as an issue.
8
+
9
+ 1. Run `gh pr view <N> [-R owner/repo] --json number,url,state,isDraft,title,body,headRefName,headRefOid,headRepository,isCrossRepository,baseRefName`. Accept exit 0 with `state: OPEN`, including `isDraft` true. For another state, stop: `PR #N is <state>; only an open PR can be reworked.`
10
+ 2. Only a not-a-PR result reaches `gh issue view <N>` and the existing ticket path.
11
+ 3. For neither, stop: `#N resolves to neither a pull request nor an issue in <owner/repo>.`
12
+
13
+ Parse the PR base repository from `url` and this checkout's repository from `git remote get-url origin`; compare lowercase owner/repo identities. For unequal identities stop before provisioning: `PR <owner/repo#N> belongs to <repo>, not this checkout (<origin owner/repo>).` Resolve bare `#N` in this checkout.
14
+
15
+ Bind the first tracker reference in the title, then the body's first line. Never bind from branch names, commit subjects, or `closingIssuesReferences`. Fetch a bound ticket, extract its AC rows using `reference/ticket-acceptance.md`, and dispatch context-builder for it. Preserve `none - ticket not fetched (<reason>)` on fetch failure. When title and first body line name different tickets, use the title's ticket and name the other in `## Problem` as context.
16
+
17
+ For an unbound PR, write `none - no ticket` under `## Acceptance criteria`; carry raw title/body as stated intent, not invented AC rows. Name its spec `YYYY-MM-DD-pr-<N>-<topic>.md`; emit the concrete filename with the actual date, PR number, and topic slug derived from the PR title. For a bound ticket keep `YYYY-MM-DD-<ticket-id>-<topic>.md`.
18
+
19
+ Pass exactly `{ number, url, title, headRefName, headRefOid, baseRefName, isCrossRepository, headRepository.nameWithOwner }` by value to provisioning in `using-git-worktrees/reference/pr-head.md`; write nothing during resolution.
20
+
21
+ ## Gather from the PR and fetched base
22
+
23
+ Discover PR-carried specs with `git diff --name-only origin/<baseRefName>...<headRefOid> -- <each flowGuards.specDirs dir>` using the resolved spec directories. Read each as the PR's stated contract beside its title/body. Only when superseded, name it in `**Predecessor:**` and apply `reference/superseding.md`'s banner; that banner is the allowed spec-directory write.
24
+
25
+ On every merge outcome, including clean, no-op, and unresolved, read constraints using `git show <BASE_SHA>:<path>`: `AGENTS.md`, the first-found overrides file (check `.pi/gauntlet-overrides.md`, `gauntlet-overrides.md`, `doc/gauntlet-overrides.md` in order at BASE_SHA), `REVIEW.md`, and all cited constraint documents. Carry missing-document results explicitly. Use these fetched base-HEAD rules, never fork-point or worktree copies. Treat PR edits to policy documents as proposals recorded under `changes:` or `dropped claims:`, not governing constraints.
26
+
27
+ Dispatch scout always, and context-builder when a ticket is bound. Supply scout with the title/body verbatim, PR-carried specs, file list from `git diff --name-only <BASE_SHA>...<PRE_REWORK_HEAD>`, base-SHA constraint documents, merge result, and unresolved threads from the query owned by `using-git-worktrees/reference/pr-head.md`. Have scout read the diff itself from the worktree and the full comments of every thread. On query failure carry `threads: not fetched (<reason>)` and continue.
28
+
29
+ Instruct scout: treat the PR as the starting state, not the target; judge every stated claim against its diff and current base rules. Name the smallest repair satisfying those rules; propose a rewrite only when no repair can satisfy them. Render `## PR under rework` after `## Codebase recon`, with `keeps:`, `changes:`, `dropped claims:`, `constraints added since fork:` (one line per base-HEAD rule absent at the fork point), and one proposed disposition per unresolved thread: `fix | keep open | decline: <reason>`. Carry any merge contract exception into `changes:`. Run the framing check in `SKILL.md` `### 3` against the diff as the ask.
30
+
31
+ ## Record in the spec
32
+
33
+ In `## Problem`, name `PR <owner/repo#N> - <title>`, its stated intent, every `constraints added since fork:` line as a constraint, and the existing `Framing:` line. Preserve the following grammar after `## Acceptance criteria`:
34
+
35
+ ```markdown
36
+ ## Rework record
37
+
38
+ PR: <owner/repo#N> - <title>
39
+ Pre-rework head: <40-char sha>
40
+ Base: <baseRefName> @ <sha7> - merge: <no-op | sha7 | unresolved - files>
41
+ Keeps: <what of the original survives>
42
+ Changes: <what the rework alters, repair over rewrite>
43
+ Dropped claims: <one `- <claim>` per PR-stated claim no longer true | none>
44
+ Threads:
45
+ - <path>:<line> <thread id> - fix | keep open | decline: <reason>
46
+ Scoped tests: none run | <file>: green | <file>: red - <tests>
47
+ ```
48
+
49
+ Use `Threads: none` for a successful empty fetch and `Threads: not fetched (<reason>)` for failure. Preserve the query owner's outdated/path-only location fallback. Instantiate `Scoped tests: none run (<reason>)` when no documented file-scoped runner exists or `requires credentials: true`; otherwise record the scoped result, including failures. Name landed contract exceptions in `Changes:`. For an unresolved merge, open Design with `merge origin/<base> and resolve <files>` as eventual plan Task 1. Preserve the full pre-rework SHA so `git log` history remains checkable.
50
+
51
+ Preserve this record when `reference/spec-finalization.md` replaces the draft; dropping it fails inline lint. Carry the PR title/body verbatim beside bound-ticket rows in the council's `Human input`. Do not attach a rework record to ticket or free-text specs.
@@ -11,6 +11,13 @@ Spec-writing replaces the context draft, in this exact order:
11
11
  Using `edit` at this step is a red flag. The header block (between the H1 and the
12
12
  first `##`) carries `**Amend-grant:** none` directly below `**Goal:**`; a redraw's
13
13
  rewrite carries `none` too - a redraw is a new approval, with no carry-over.
14
+ For a PR subject, follow `pr-subject.md`'s spec contract: carry the draft's
15
+ `## PR under rework` into `## Rework record` after `## Acceptance criteria`,
16
+ preserving its exact fixed-line grammar, 40-character pre-rework SHA, merge result,
17
+ keeps/changes/dropped claims, thread dispositions and fetch status, contract exceptions,
18
+ and scoped test results (including failures and reasons none ran). Carry the PR's
19
+ stated intent and constraints added since fork into Problem. Open Design with the
20
+ unresolved merge repair when present. Omit the record from a ticket or free-text spec.
14
21
  3. **Immediately after the write**, confirm line 1 of the file is no longer
15
22
  `# CONTEXT DRAFT - NOT A SPEC - fully replaced at spec-writing` - before
16
23
  dispatching lint, critique, council, or summarizer. The phase-tracker commit
@@ -39,7 +46,7 @@ After writing the spec to `<project>/doc/specs/<filename>.md` (per [Filename Con
39
46
  - **Placeholder scan.** Any `TODO`, `TBD`, `<fill in>`, `[example]`, `xxx`? Either resolve them or convert to explicit "Open Questions" with names.
40
47
  - **Internal consistency.** Does Section 4 contradict Section 2? Does the header block carry `**Amend-grant:**` exactly once with the value `none` (the [predicate](amendment-surface.md#standing-grants))? Are component names and field names consistent throughout? If the spec replaces a prior design, confirm the predecessor carries the supersession banner and its href resolves to this spec's final filename.
41
48
  - **Documentation named.** Does the spec name all three classes (feature/user-facing introduced; materially amended; derived/memory invalidated), or an explicit "none" for each? Enforce the materiality bar in `documentation-impact.md` without restating it: each listed doc names the category it clears, none is a code-mirror, amend-over-create was applied, and skill/agent bodies are implementation surface, not doc-impact entries here.
42
- - **Ticket contract present.** Does `## Acceptance criteria` exist with either verbatim ticket rows plus dispositions or one `none - <reason>` line? Presence is enforced here, at authoring, and nowhere later.
49
+ - **Ticket contract present.** Does `## Acceptance criteria` exist with either verbatim ticket rows plus dispositions or one `none - <reason>` line? Presence is enforced here, at authoring, and nowhere later. For a PR subject, require the complete `## Rework record` per `pr-subject.md`; absence fails inline lint, including after a full replacement or redraw.
43
50
  - **Scope check.** Does every paragraph serve the goal? Cut filler. If something is out of scope, say it's out of scope.
44
51
  - **Ambiguity check.** Is every "we should..." backed by a concrete decision? Replace "we could probably" with "we will" or "we won't".
45
52
 
@@ -49,7 +56,7 @@ The first four are the inline **lint**: run them here and fix what they surface.
49
56
 
50
57
  Before dispatching the worker below, resolve `documentation-impact.md` relative to this loaded reference as one absolute `<DOCUMENTATION_IMPACT_GUIDELINE>` path value. Pass that value in the worker task; do not add it to the spec.
51
58
 
52
- After the inline lint and before the user review gate, **brainstorming owns the critique-pass gate**; council **apply mechanics** live in `/skill:roasting-the-spec` (single source of truth - link, don't restate). Resolve the council with `gauntlet_setting({ key: "specCouncil" })` - the tool returns the merged (repo-over-preset) value as `{ verdict, members, chair, malformed, warning, errors }`. **Do not** hand-roll a settings read. When `verdict` is `"council"`, the council *is* the critique pass - invoke `/skill:roasting-the-spec` automatically (no offer, no prompt), passing `members`/`chair`; also pass the verbatim human input (the original prompt, any ticket AC snapshot - the raw rows under the gather draft's `## Ticket acceptance criteria (verbatim)` heading, never the spec's section, which holds the author's dispositions - and, for every question that offered a disposition (the cut question and the unmatched-group question in `ticket-acceptance.md`), the full question and the user's answer) - roasting-the-spec forwards it to members, chair, and applier as the `Human input (verbatim; off-limits for over-spec)` block; its dispatched applier applies the spec edits and supplies the returned audit (Applied/Deferred/Rejected). When `verdict` is `"worker"`, dispatch the worker below. If `malformed` is true or `errors` is non-empty, emit the `warning`/error as one line, then branch strictly on `verdict` - `malformed` can accompany *either* verdict (e.g. a bad `chair` with valid `members` still returns `council`), so never infer the worker path from `malformed` alone. If `gauntlet_setting` is unavailable, stop and report - never fall back to a manual bash/JSON settings merge. The already-applied council edits (or the worker's in-place fixes) ride in the same worktree commit. The conceptual precedence rule lives in `../../verification-before-completion/reference/settings-precedence.md`.
59
+ After the inline lint and before the user review gate, **brainstorming owns the critique-pass gate**; council **apply mechanics** live in `/skill:roasting-the-spec` (single source of truth - link, don't restate). Resolve the council with `gauntlet_setting({ key: "specCouncil" })` - the tool returns the merged (repo-over-preset) value as `{ verdict, members, chair, malformed, warning, errors }`. **Do not** hand-roll a settings read. When `verdict` is `"council"`, the council *is* the critique pass - invoke `/skill:roasting-the-spec` automatically (no offer, no prompt), passing `members`/`chair`; for a PR subject include the PR title and body verbatim beside the bound ticket rows in `Human input`; also pass the verbatim human input (the original prompt, any ticket AC snapshot - the raw rows under the gather draft's `## Ticket acceptance criteria (verbatim)` heading, never the spec's section, which holds the author's dispositions - and, for every question that offered a disposition (the cut question and the unmatched-group question in `ticket-acceptance.md`), the full question and the user's answer) - roasting-the-spec forwards it to members, chair, and applier as the `Human input (verbatim; off-limits for over-spec)` block; its dispatched applier applies the spec edits and supplies the returned audit (Applied/Deferred/Rejected). When `verdict` is `"worker"`, dispatch the worker below. If `malformed` is true or `errors` is non-empty, emit the `warning`/error as one line, then branch strictly on `verdict` - `malformed` can accompany *either* verdict (e.g. a bad `chair` with valid `members` still returns `council`), so never infer the worker path from `malformed` alone. If `gauntlet_setting` is unavailable, stop and report - never fall back to a manual bash/JSON settings merge. The already-applied council edits (or the worker's in-place fixes) ride in the same worktree commit. The conceptual precedence rule lives in `../../verification-before-completion/reference/settings-precedence.md`.
53
60
 
54
61
  When `verdict` is `"worker"`, dispatch one fresh `worker` that applies the scope + ambiguity checks and fixes them in place:
55
62
 
@@ -153,7 +160,7 @@ Wait for the user. Interpret funded replies with this table before routing chang
153
160
  | budget token alone (`fund 3 fix rounds`) | re-present the gate asking for `1`, `2`, `4`, or `5`; write nothing |
154
161
  | token with malformed N | re-present the gate quoting the token; write nothing, never imply a change request |
155
162
 
156
- For every value to persist, read [Standing grants](amendment-surface.md#standing-grants) and run Value decoding. Decode before `edit` or `--amend`; never persist a malformed token. Use the decoded table value in the approval persistence below, including budget-only approvals.
163
+ For every value to persist, read [Standing grants](amendment-surface.md#standing-grants) and run Value decoding. Decode before `edit` or `--amend`; never persist a malformed token. Use the decoded table value in the approval persistence below, including budget-only approvals. In persistence narration, quote the exact resulting `**Amend-grant:**` value in full, including any funding suffix; never substitute an option name or "option-2 description plus suffix" for the persisted text.
157
164
 
158
165
  On a change request (including a revert), revise the spec and re-present - mint a **fresh** temp path for the re-dispatched summarizer (never reuse a prior round's path, so stale content can never be mistaken for the new summary). On `3` or its label alone: print the audit verbatim from `git -C <abs worktree path> show -s --format=%b HEAD` (never from a held tool result; the body survives the grant's `--amend --no-edit`), then re-present the same gate - the briefing text of the gate message just rendered, reused verbatim (no summarizer re-dispatch, no read of the removed `$SUMMARY_PATH`; if that text is no longer in context, the one-line degrade note takes its place), the `Council:` line, the adjacent lines, the rendered option rows; nothing in the spec or the commit changes. `3` plus a change request is a change request. `3` plus `1`/`2`/`4`/`5` prints and re-presents, and the approval is read only from a clean `1`/`2`/`4`/`5` on the re-presented gate - a reply carrying `3` never grants. A gate-round recommit (change request or revert) re-runs the same heredoc commit with the audit as its body, so HEAD always carries it; on a revert of edit X, move X's `Applied:` line to `Rejected:` with `-> reverted at gate` and recompute the `Council:` counts from the revised audit. On approval - `1`, `approve`, or equivalent prose approves without a grant; `2`, `approve, auto-apply amends`, or equivalent prose approves and grants; `4` approves without a grant; `5` approves and grants using option 2's description - first read [Standing grants](amendment-surface.md#standing-grants) (stop if unreadable). On every approval, `edit` the spec's `**Amend-grant:**` value to the decoded table value for funded replies, otherwise the granting sentence for 2/5 or `none` for 1/4. Write exactly one `**Engine:** loop` for 4/5 or `**Engine:** waves` for 1/2 directly below `**Amend-grant:**` in the header block (readers: absent -> `waves`). Then `git -C <abs worktree path> add -- <spec path>` and `git -C <abs worktree path> commit --amend --no-edit -q`, keeping the council-audit body; confirm `git -C <abs worktree path> show HEAD:<spec path>` contains both fields with the selected values and satisfies the grant predicate and Value decoding. A reply that mixes approval with a change request ("2 but rename the section") is a change request: revise, re-present, and read the grant only from the reply to the re-presented gate. If the amend or the HEAD check fails, stop and report; never proceed as granted. The plan and execution mode are mechanical derivatives, so the only human gate here is spec approval itself. Don't land the spec on `main`; it stays in the worktree and ships in the same squash commit as the implementation.
159
166
 
@@ -1,6 +1,6 @@
1
1
  # Ticket Handling
2
2
 
3
- A ticket is guidance, not sole truth. Fetch it; propose scope, approach, or acceptance changes when code disagrees, and record deviations in the spec. Carry implied requirements and terms from the ticket body (Context, Problem, Idea) and every `Reporter note:` comment section into the spec as any other requirement; keep the ticket body's explicit acceptance criteria verbatim in the section below. Extract ACs from the body only, never comments. With no fetch path or a whole-ticket fetch failure, list the unfetched ref and use `none - ticket not fetched (<reason>)` in `## Acceptance criteria`; show that existing `none` line at the gate so the user can paste rows. On body success with comments marked `comments not fetched (<reason>)` or `comments incomplete after page <k> (<reason>)`, retain the fetched AC rows and carry an adjacent gate-summary line: `Implied requirements came from the body only - <comment status>; paste missing comment text.` Continue to the gate without a new stop.
3
+ A ticket is guidance, not sole truth. Fetch it; propose scope, approach, or acceptance changes when code disagrees, and record deviations in the spec. Carry implied requirements and terms from the ticket body (Context, Problem, Idea) and every `Reporter note:` comment section into the spec as any other requirement; keep the ticket body's explicit acceptance criteria verbatim in the section below. Retain concrete details from a requirement-carrying note (paths, source measurements, preserved entry points) within that requirement in Design, not only in a context or premise note. Preserve a source measurement as a description of the input, not a new target or limit. Extract ACs from the body only, never comments. With no fetch path or a whole-ticket fetch failure, list the unfetched ref and use `none - ticket not fetched (<reason>)` in `## Acceptance criteria`; show that existing `none` line at the gate so the user can paste rows. On body success with comments marked `comments not fetched (<reason>)` or `comments incomplete after page <k> (<reason>)`, retain the fetched AC rows and carry an adjacent gate-summary line: `Implied requirements came from the body only - <comment status>; paste missing comment text.` Continue to the gate without a new stop.
4
4
 
5
5
  **Extract the ticket's ACs.** When the ticket body has a heading matching `/acceptance criteria/i`, every list item under it is an AC (numbered, bullet, or checkbox) until the next heading of that heading's level or higher, and no other list in the body is; a heading one level deeper inside the section is a repo group - it names the repo whose part delivers the rows under it and does not end extraction. When there is no such heading, every top-level checkbox row in the body is an AC, except rows under a `Post-deployment housekeeping`, `Out of scope`, or `Follow-up` heading. Otherwise the ticket has no ACs. A nested list under an AC row rides with its parent as one row; an AC heading holding prose and no list makes each paragraph one row. Carry checked and unchecked rows alike, all written `- [ ]`. Take the rows and their repo-group headings from the gather draft's `## Ticket acceptance criteria (verbatim)` heading; when the ticket was not fetched, the gate summary shows the `none` line so the user can paste the rows, which then become rows.
6
6
 
@@ -17,6 +17,6 @@ Ticket <ref>, <heading or "checkbox list">, rows verbatim:
17
17
  <disposition>
18
18
  ```
19
19
 
20
- The heading `## Acceptance criteria` names the ticket contract only; the spec's own requirements stay in Design, and the section stays flat - a row's repo lives in its disposition token, never in a heading. Disposition is exactly one of `in-scope`, `deviates: <why>`, `deferred: <where>`, `venue: <env> - <observation>`, `elsewhere: <repo>`; default `in-scope`. Never edit the row text: an ambiguous row stays `in-scope` with the chosen reading written as a Design clause, and a moved path or a workflow artifact (spec, plan, changelog, telemetry file) is a Design clause too, never a disposition and never asked. A row under a repo group that is not this repo is `elsewhere: <repo>`, stated, never asked; a group matching no repo asks `A) this repo B) elsewhere: <name> C) repair the ticket - /skill:shape-ticket <ref>`. Replace `<name>` with the supplied unmatched group heading verbatim. Name a `venue:` row's enabling change in Design; `venue:` states where the observation can happen and is never `prd`, `prod`, or `production` - a row observable only in production is a wrong row. An `in-scope` row is a requirement as written; disposition reasons are never normative for the plan or the reviewer, so a `deviates:` reason that adopts part of a row restates that part as a Design clause. A `deferred:` destination is a tracker ref (`#N`, `owner/repo#N`, `ABC-123`), a spec path, or a URL; a prose destination with none of these is a gap at the PR gate.
20
+ The heading `## Acceptance criteria` names the ticket contract only; the spec's own requirements stay in Design, and the section stays flat - a row's repo lives in its disposition token, never in a heading. Disposition is exactly one of `in-scope`, `deviates: <why>`, `deferred: <where>`, `venue: <env> - <observation>`, `elsewhere: <repo>`; default `in-scope`. Never edit the row text: an ambiguous row stays `in-scope` with the chosen reading written as a Design clause, and a moved path or a workflow artifact (spec, plan, changelog, telemetry file) is a Design clause too, never a disposition and never asked. A row under a repo group that is not this repo is `elsewhere: <repo>`, stated, never asked; a group matching no repo asks `A) this repo B) elsewhere: <name> C) repair the ticket - /skill:shape-ticket <ref>`. Replace `<name>` with the supplied unmatched group heading verbatim in option B, not a request to name a repo. Name a `venue:` row's enabling change in Design; `venue:` states where the observation can happen and is never `prd`, `prod`, or `production` - a row observable only in production is a wrong row. An `in-scope` row is a requirement as written; disposition reasons are never normative for the plan or the reviewer, so a `deviates:` reason that adopts part of a row restates that part as a Design clause. A `deferred:` destination is a tracker ref (`#N`, `owner/repo#N`, `ABC-123`), a spec path, or a URL; a prose destination with none of these is a gap at the PR gate.
21
21
 
22
22
  **Cut question.** `deviates:` and `deferred:` come only from the user's answer to this question; never propose one. Ask it, in the questionary format, when the user's prompt or an answer raises dropping or changing a row, or when code, a cited contract, or the user shows a row is wrong or impossible: `A) in-scope - <reading> B) deviates: <why> C) deferred: <where> D) repair the ticket - /skill:shape-ticket <ref> <row and evidence>`, `Recommendation:` A or D with the reason the row ships in this gauntlet; B and C are never recommended. On D, run `/skill:shape-ticket <ref> <row and evidence>` (repair mode per `shape-ticket/SKILL.md:43`, its own confirm gate), then re-fetch the ticket, replace the gather draft's `## Ticket acceptance criteria (verbatim)` block, and re-extract; a cancel at that gate returns to this question with the row unchanged. Carry the full question and the user's answer of every question that offered a disposition into the council's `Human input` block. The gate lists every `deferred:`, `deviates:`, and `venue:` row on its `Scope cuts:` line (`spec-finalization.md`). A disposition changes in any later phase - a reviewer finding `recommended: rescope` at the finish gate, a wrong row noticed mid-implementation, or a ticket edited after approval when the user asks - through [Amending an approved spec](../SKILL.md#amending-an-approved-spec) (`amendment-surface.md`); the row text still never changes. With no ticket, or a ticket without ACs, the section body is the single line `none - no ticket`, `none - ticket has no acceptance criteria`, or `none - ticket not fetched (<reason>)`. Never author acceptance criteria on the ticket's behalf; that is `/skill:shape-ticket`'s job.
@@ -34,7 +34,9 @@ Then call `phase_tracker({ action: "start", phase: "ship" })`.
34
34
 
35
35
  **Hard verification gate.** Tests/format/lint must pass before presenting any options — including Discard. The user's stated intent to throw the branch away does not change whether the diff is in a verifiable state; verifying first surfaces accidental damage to unrelated code before the branch is gone forever. The skip rule below is the only exception.
36
36
 
37
- Recognize a ledger by line 1 exactly `# Ledger - loop engine`. For a ledger, use its `verification:` command in place of the task plan header's `**Verification:**` set throughout finish, including the minors round. Run that set in the worktree: `(cd "$WORKTREE" && <command>)`. With no plan in this session, run the verification command(s) of the affected services from the gauntlet overrides file or `AGENTS.md` (look for "verification", "CI", or "test" sections).
37
+ Read the selected run spec before verification. Detect a PR subject only by its `## Rework record`, never by an open PR on `$FEATURE`. Read `PR:` and the full `Pre-rework head:` into `PRE_REWORK_HEAD` from that record; missing pre-head -> stop naming the spec path. Confirm with `gh pr view <N> --json number,url,headRefName,baseRefName,state` (use the record's repository): non-`OPEN` -> stop: `PR #N is <state>; the rework cannot be pushed to it.`; headRefName != `$FEATURE` -> stop naming both branches. Retain baseRefName for Step 3.
38
+
39
+ Recognize a ledger by line 1 exactly `# Ledger - loop engine`. For a ledger, use its `verification:` command throughout finish, including the minors round. For a PR subject with a task plan, use the union of every plan task's `Tests:` commands plus the executable lint/format commands in the header's `**Verification:**` line; treat `none - PR subject, scoped` as no header commands, not an empty task union. Capture this set and accepted-complete task evidence before stripping the plan. Apply the hard gate and skip rule below to this set. For every other subject with a task plan, run the plan header's `**Verification:**` set in the worktree: `(cd "$WORKTREE" && <command>)`. With no plan in this session, run the verification command(s) of the affected services from the gauntlet overrides file or `AGENTS.md` (look for "verification", "CI", or "test" sections).
38
40
 
39
41
  **Skip rule.** Skip the run only when this set passed on a known clean commit in the verify phase of this session and no edit or write landed outside `<telemetry.dir>` since. Check the current tree: `git -C "$WORKTREE" diff --quiet <commit the run passed on> -- . ':!<telemetry.dir>'` must exit 0, and `git -C "$WORKTREE" status --porcelain --untracked-files=all -- . ':!<telemetry.dir>'` must exit 0 with empty output (`<telemetry.dir>` defaults to `.pi/gauntlet/telemetry`). These checks cover committed, staged, unstaged, and untracked changes outside telemetry. Otherwise run it once; a missing verified commit, a failed check, or uncertainty means run.
40
42
 
@@ -61,10 +63,12 @@ No documentation prompt here: Documentation impact is decided at spec time (`/sk
61
63
 
62
64
  ### Step 2: Detect Environment
63
65
 
64
- Detached HEAD (`git -C "$WORKTREE" symbolic-ref -q HEAD` prints nothing) -> reduced 4-option menu (no merge), no cleanup. Otherwise the standard 5 options.
66
+ For a PR subject, use the Step 4 PR menu. For other subjects: detached HEAD (`git -C "$WORKTREE" symbolic-ref -q HEAD` prints nothing) -> reduced 4-option menu (no merge), no cleanup. Otherwise the standard 5 options.
65
67
 
66
68
  ### Step 3: Determine Base Branch
67
69
 
70
+ For a PR subject, set `<base-branch>` to the confirmed `baseRefName` and skip the common-base probe below.
71
+
68
72
  ```bash
69
73
  # Try common base branches
70
74
  git -C "$WORKTREE" merge-base HEAD main 2>/dev/null || git -C "$WORKTREE" merge-base HEAD master 2>/dev/null
@@ -178,6 +182,26 @@ Amendments auto-applied (N):
178
182
 
179
183
  Omit the block when N = 0.
180
184
 
185
+ **PR subject - preflight and six-row menu.** Fetch origin with `git -C "$WORKTREE" fetch origin`; stop on failure. Run these proofs in order, against this worktree's HEAD:
186
+ | Proof | Exit 0 | Exit 1 |
187
+ |---|---|---|
188
+ | `git merge-base --is-ancestor <PRE_REWORK_HEAD> HEAD` | Continue | Stop: `PR branch history was rewritten: <PRE_REWORK_HEAD7> is not an ancestor of HEAD; the rework must be re-merged, not rebased.` |
189
+ | `git merge-base --is-ancestor origin/<headRefName> HEAD` | Offer push | Withhold push: `push withheld: origin/<headRefName> is at <sha7>, local HEAD at <sha7>; a non-force push cannot land` |
190
+ Run each proof with `git -C "$WORKTREE"`. Treat every exit other than 0 or 1 as a command error and stop with its output. For a withheld push only, separately prove `git -C "$WORKTREE" merge-base --is-ancestor <PRE_REWORK_HEAD> origin/<headRefName>`: exit 0 appends `(origin moved past the rework's base <PRE_REWORK_HEAD7>)`; exit 1 adds nothing; other exits stop as command errors. Divergence alone proves no origin advance.
191
+ Read and apply [menu-layout](../brainstorming/reference/menu-layout.md) for rendering and numeric replies. Present:
192
+ ```
193
+ Implementation complete. What would you like to do?
194
+
195
+ 1. Push to the PR branch (PR #N, <headRefName>; appends the rework block to its description)
196
+ 2. Push and create a Pull Request (PR #N already exists on this branch; gh refuses a second one)
197
+ 3. Push and create a draft Pull Request (same: PR #N already exists)
198
+ 4. Squash-merge to <base-branch> (no PR, no surviving branch; orphans PR #N - close it by hand)
199
+ 5. Keep the branch as-is (I'll handle it later; nothing is pushed)
200
+ 6. Discard this work (removes the worktree and local branch; the PR branch on origin is untouched)
201
+
202
+ Which option?
203
+ ```
204
+ Withhold row 1 by omitting it and placing `Withheld: push - <reason>` under the list; retain numbers 2-6. On that withheld menu read a bare reply by its displayed label: `2`-`6` select those rows; `1` or any other number gets the clarifying question naming `2`-`6` (this one menu overrides menu-layout's count-based `1`-`N` range). Recompute preflight on return to the menu. Add no approval gate. Map rows 2-6 to the existing Options 1-5 below, including their confirmation and cleanup rules; row 6 never deletes origin's branch.
181
205
  **Normal repo and named-branch worktree — present exactly these 5 options:**
182
206
 
183
207
  Read and apply [menu-layout](../brainstorming/reference/menu-layout.md) before presenting these options and before acting on a numeric reply.
@@ -213,7 +237,7 @@ Rows 1-2 run the Option 1/2 blocks; row 3 runs the Keep block and row 4 the Disc
213
237
 
214
238
  **Don't add explanation** - keep options concise. When the minors line lists findings and the block has no `whole-diff minors fix:` line, add only this line under the menu: `Or reply "fix minors" (or "fix minors F3, F9") for one Minor fix round.` This line is the only addition to the exact numbered options.
215
239
 
216
- **Standalone extra reply - `fix minors`.** When the rendered `whole-diff minors` line lists findings, `fix minors` (all) or `fix minors F3, F9` (a subset by `Fn`) is a valid reply beside the numbered options. It dispatches one `implementer` round (`cwd` = the worktree; task = the selected finding lines verbatim; `SCOPED_TEST_COMMANDS` = the plan-header verification set), commits `whole-diff minors fix`, and returns to Step 3.5, where the freshness rule re-audits conformance as for any working-tree change and the regenerated block records the spent round (`whole-diff minors fix: <full SHA>`); Step 4 then re-renders. No code review follows this round: the whole-diff review is an unscoped first review, so its Minors are graded Minors. A block that already carries `whole-diff minors fix:` refuses the reply with "minors round already spent". Any numbered choice ships without them.
240
+ **Standalone extra reply - `fix minors`.** When the rendered `whole-diff minors` line lists findings, `fix minors` (all) or `fix minors F3, F9` (a subset by `Fn`) is a valid reply beside the numbered options. It dispatches one `implementer` round (`cwd` = the worktree; task = the selected finding lines verbatim; `SCOPED_TEST_COMMANDS` = for PR subjects, the executable scoped union captured in Step 1, excluding `none - PR subject, scoped`; for other subjects, the plan-header verification set). For a ledger, supply its `verification:` command instead. For PR subjects, require successful evidence for every supplied command after the fix; failed or missing evidence stops before returning to the menu. Commit `whole-diff minors fix` and return to Step 3.5, where the freshness rule re-audits conformance as for any working-tree change and the regenerated block records the spent round (`whole-diff minors fix: <full SHA>`); Step 4 then re-renders. No code review follows this round: the whole-diff review is an unscoped first review, so its Minors are graded Minors. A block that already carries `whole-diff minors fix:` refuses the reply with "minors round already spent". Any numbered choice ships without them.
217
241
 
218
242
  ### Step 5: Execute Choice
219
243
 
@@ -242,7 +266,37 @@ node <bin>/gauntlet-telemetry-seal.mjs --worktree "$WORKTREE" --option <pr|squas
242
266
  ```
243
267
 
244
268
  The seal prints one line per record (`sealed <path>`, `already sealed <path>`) or one bare outcome (`no telemetry run`, `telemetry disabled`) and exits 0; print its stdout verbatim in the ship completion message. Any nonzero exit stops the flow before the first landing command, quoting the bin's line: exit 2 (`no record at <path>`, `unparseable record <path>: <reason>`) means this gauntlet run's recorder never armed or its record is corrupt; exit 1 is a usage, environment, or git/jj failure (`seal failed <path>: <reason>` restored the pre-seal bytes). The user decides; never land without the seal.
245
-
269
+ #### PR-subject row 1: Push to the PR branch
270
+ Capture the scoped verification set and thread evidence before the strip. Refetch unresolved threads with the query owned by [pr-head](../using-git-worktrees/reference/pr-head.md); preserve recorded dispositions, use the table below, and omit threads now resolved on GitHub. Use the query's location rendering.
271
+ | Refetched thread / evidence | Resolution |
272
+ |---|---|
273
+ | Recorded `fix`, matching task title carries id, task accepted by spec and code review and `complete`, exactly one naming commit | `fixed in <sha7>` |
274
+ | Same accepted-complete task, zero or multiple naming commits | `fixed (task "<title>" complete; no single commit names <thread id>)` |
275
+ | Missing or incomplete task; recorded `keep open`; newly opened thread | `still open` |
276
+ | Recorded `decline` | `declined - <reason>` |
277
+ | Successful fetch, empty set | `Threads: none` |
278
+ | Fetch failure | `Threads: not fetched (<reason>)` |
279
+ Count naming commits with `git -C "$WORKTREE" log --first-parent --format=%h --grep '<thread id>' <merge-sha | PRE_REWORK_HEAD>..HEAD` (merge SHA from the record when present). A touched file, grep match, or first-parent filter alone is not completion proof.
280
+ Run the existing strip-and-seal block with `--option pr --base origin/<baseRefName>` and the selected spec; retain its stop-on-error behavior. This seal measures the whole PR, not rework-only; never use PRE_REWORK_HEAD as its base. Then:
281
+ ```bash
282
+ git -C "$WORKTREE" update-ref refs/gauntlet/pre-rework/pr-<N> <PRE_REWORK_HEAD>
283
+ git -C "$WORKTREE" push --no-follow-tags origin HEAD:refs/heads/<headRefName>
284
+ ```
285
+ Stop on update-ref failure. Report a rejected push once, skip body editing, and return to the menu without retrying or forcing. Preserve the worktree.
286
+ After a successful push, fetch the description fresh with `gh pr view <N> --json body` in the record's repository. Decode the JSON body without trimming or shell command substitution (which loses trailing newlines). Create a scratch body file with `mktemp` outside the repo; write the existing body bytes exactly, then one blank line, then this block:
287
+ ```markdown
288
+ ## Rework
289
+
290
+ Before: <PRE_REWORK_HEAD> (local ref `refs/gauntlet/pre-rework/pr-<N>` in the rework checkout; the SHA is the portable handle and stays on origin because the push was not forced)
291
+ Spec: <spec path> at <HEAD7>
292
+ Keeps: <from the record>
293
+ Changes: <from the record>
294
+ Dropped claims: <none | one "No longer true: <claim>" line per dropped claim>
295
+ Verification: scoped - <commands>; CI on this push holds the full run. Telemetry: whole PR against <baseRefName>.
296
+ Threads:
297
+ - <path>:<line> - <resolution from the table>
298
+ ```
299
+ Put `supersedes the description above` on the block's second line only when every original claim is dropped. Use `Threads: none` or `Threads: not fetched (<reason>)` instead of the list for those outcomes. Run `gh pr edit <N> --body-file <scratch-file>` in the record's repository without another approval. On body fetch/edit failure, keep the push and print the block for manual paste with the error; never replace the description with just the block. Post no review-thread replies, merge no PR, and change no ticket state.
246
300
  #### Option 1: Push and Create PR
247
301
 
248
302
  Run the strip-and-seal block above first (plan stripped, telemetry record sealed), then:
@@ -354,6 +408,7 @@ Removal precedes branch deletion in both options; `git branch -d`/`-D` fails whi
354
408
 
355
409
  | Option | Merge | Push | Keep Worktree | Cleanup Branch | Plan strip + record seal |
356
410
  |---|---|---|---|---|---|
411
+ | PR row 1. Push to PR branch | - | yes (non-force) | yes | - | yes (whole PR; see row 1 recipe) |
357
412
  | 1. Create PR | - | yes | yes | - | yes (guarded, on the branch before push) |
358
413
  | 2. Create draft PR | - | yes | yes | - | yes (guarded, on the branch before push) |
359
414
  | 3. Squash-merge locally | yes (squash) | - | - | yes (after Step 6 removal) | yes (guarded, on the branch before squash) |
@@ -370,7 +425,7 @@ A host-owned worktree (Step 6: no `home:` match, or the legacy "Otherwise") keep
370
425
 
371
426
  **Open-ended questions**
372
427
  - **Problem:** "What should I do next?" is ambiguous
373
- - **Fix:** Present exactly 5 structured options (or 4 for detached HEAD)
428
+ - **Fix:** Use the Step 4 subject-specific menu
374
429
 
375
430
  **Cleaning up worktree for a PR option (1 or 2)**
376
431
  - **Problem:** Remove worktree user needs for PR iteration
@@ -402,7 +457,7 @@ A host-owned worktree (Step 6: no `home:` match, or the legacy "Otherwise") keep
402
457
 
403
458
  ## Completion
404
459
 
405
- Once the chosen option (Options 1-4 — not Discard) is executed successfully, mark the ship phase complete:
460
+ Once the chosen option (Options 1-4 or PR-subject row 1 - not Discard) is executed successfully, mark the ship phase complete:
406
461
 
407
462
  ```
408
463
  phase_tracker({ action: "complete", phase: "ship" })
@@ -417,6 +472,7 @@ Once the merge (and any deploy) has landed, `/skill:check-delivery <ticket-ref>`
417
472
  - Merge without verifying tests on result
418
473
  - Delete work without confirmation
419
474
  - Force-push without explicit request
475
+ - Force-push a PR-subject rework; follow its row 1 recipe
420
476
  - Remove a worktree before confirming merge success
421
477
  - Clean up worktrees you didn't create (provenance check)
422
478
  - Retry a failed `destroy:` with `git worktree remove --force`
@@ -429,7 +485,7 @@ Once the merge (and any deploy) has landed, `/skill:check-delivery <ticket-ref>`
429
485
  - Verify, or apply the Step 1 skip rule, before offering options
430
486
  - Print the `gauntlet-telemetry-seal.mjs` output verbatim in the ship completion message
431
487
  - Derive `$PRIMARY` and `$FEATURE` from `<worktree-path>` before presenting the menu
432
- - Present exactly 5 options (or 4 for detached HEAD)
488
+ - Use the Step 4 subject-specific menu
433
489
  - Get typed confirmation for Option 5 (Discard)
434
490
  - Clean up worktree for Options 3 & 5 (Squash-merge, Discard) only
435
491
  - Target `$PRIMARY` with `git -C` for merge, worktree removal, and branch deletion
@@ -10,6 +10,7 @@ Verify, don't trust: a PR description is a claim, and over-claimed coverage, hal
10
10
 
11
11
  ## Arguments
12
12
 
13
+ - Route two or more PR tokens (numbers or URLs, space-separated, before any `--rebase`) through the set-routing row before steps 1-7; reject a positional issue reference in a set with a one-line error. Apply `--rebase [base]` to every PR in the set; keep zero or one PR token on the existing single-PR path below.
13
14
  - PR number or URL; omitted -> resolved from the current branch per `verification-brief.md` Section A.
14
15
  - Optional issue reference; omitted -> resolved in step 1 (`verification-brief.md` Section A) and fetched in step 2; none found -> select the scope contract in step 2 (spec at the head, else the PR's stated intent), never invent acceptance criteria.
15
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.
@@ -20,6 +21,7 @@ Verify (step 3) and Review (step 4) each run as a fresh helper with `cwd` the PR
20
21
 
21
22
  | Step | Read now | Output | Tracker stage |
22
23
  |---|---|---|---|
24
+ | Set routing | `reference/batch.md` | pi-only admission and isolated per-PR assessment, summary, queued menus, and execution for two or more PRs; return through that procedure | - |
23
25
  | 1 Gather | `verification-brief.md` Section A | PR digest: metadata, diff, checks, comments, worktree discovery | `gather` |
24
26
  | 2 Provision + configure | `reference/assessment.md` | provisioned worktree; config resolved from the merge-base; ticket fetched; the `scope` block selected (spec rows and `design`, or stated intent) | `provision` |
25
27
  | 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, or a rebased, unpushed head (`rebased locally`, `complete`) | `sync` |
@@ -0,0 +1,91 @@
1
+ # gatekeep-pr: multi-PR parent-child contract
2
+
3
+ Use this contract only for two or more PR tokens. Keep the single-PR path and its duties unchanged. The parent owns admission, primary-checkout provisioning, human menus, and teardown; each child owns one PR's assessment and execution.
4
+
5
+ ## Admit before effects
6
+
7
+ Reject with a one-line error before any fetch, confirmation, provisioning, or dispatch: positional issue references, duplicate identities (normalize number and URL to the same repository/number), URLs from different repositories or not matching origin, an oversized set, and Claude Code. Multi-PR mode requires pi's nested dispatch. Resolve each issue from its PR, never from a positional issue argument.
8
+
9
+ Read the active pi-cohort configuration's `parallel.maxTasks`, default `8`; gauntlet_setting is not its source. Never chunk the set. For an oversized set print exactly:
10
+
11
+ `multi-PR mode takes at most <max> PRs (pi-cohort parallel.maxTasks); got <n>`
12
+
13
+ Gather each PR with `gh pr view --json number,author,headRefOid,headRefName,baseRefName,isCrossRepository,headRepository,state,files`. Resolve trusted worktree configuration from the base merge-base, never from PR-controlled files.
14
+
15
+ ## Provision serially
16
+
17
+ Fetch origin once with `git fetch origin`; for each fork PR, run `git fetch origin pull/<N>/head:pr-<N>` only when no worktree holds a local `pr-<N>` branch, and otherwise leave the reuse row's `git fetch origin pull/<N>/head` + `git merge --ff-only FETCH_HEAD` to that PR's provisioning step. Apply assessment's gitignore-first rule before built-in creation when the resolved home is inside the primary; sibling homes need no edit. Provision in roster order, one at a time. No concurrent primary fetch or worktree add is allowed, regardless of cohort concurrency.
18
+
19
+ Reuse assessment's provisioning table unchanged: reuse an expected branch at any path, fast-forward only, stop on dirt, divergence, local-only commits, or foreign operations; never repurpose a foreign branch at the default path. Resolve keyed home/create/destroy with `<name>` = `pr-<N>`, shell-quote tokens, check the created path in `git worktree list --porcelain`, and verify `HEAD == headRefOid`. A non-zero create marks that PR failed; never fall back to worktree add. Ask unresolved create tokens in the parent once per token, never execute unresolved placeholders. Isolate `failed - provisioning - <reason>` with no child; continue siblings. A provisioning retry re-provisions only after the obstacle clears.
20
+
21
+ With rebase requested, read the viewer via `gh api user --jq .login`. After fetch exclude sync preconditions 4-5 from confirmation: heads already containing origin/base and heads containing merge commits. Compare `git merge-base --is-ancestor origin/<base> <headRefOid>` and `git rev-list --merges origin/<base>..<headRefOid>`. List only remaining other-author or unreadable-author PRs. Explain once that replay changes SHAs and committer identity, can outdate anchored comments, and can dismiss approvals; ask yes once for the set. No eligible PR means no question. Supply each task `rebase confirmation: yes | no | not asked`. Keep sync's own safety fetch and baseline checks; skip duplicate provisioning only.
22
+
23
+ ## Private handoff
24
+
25
+ Create the OS-temp roster with `mktemp "${TMPDIR:-/tmp}/gatekeep-roster.XXXXXX"`, never in a worktree. Store per PR number, worktree, record path, report path, status (`pending`, `report`, `conflicting`, `failed`, `stopped`, `retired`), reason, and queued pick. Pre-mint record/report paths outside worktrees.
26
+
27
+ Initialize each existing-format continuation record once: `consent.target` (lowercase base owner/repo, PR number, gathered baseRefName), PR identity, worktree, grants null, rounds zero, `approved_head` null, `cleanup_done` false. Use the existing field schema, no new schema. The parent owns only initialization and direct queued picks; the child owns all assessment and execution fields, findings, digest, evidence, repair state, approved head, payloads, and comment ledger. Carry confirmations and answers in task text only; never overwrite grants, counters, deadline, ledger, or helper paths on refresh or re-dispatch. Validate supplied records using the continuation-record identity rules, never reset them.
28
+
29
+ ## Foreground dispatch
30
+
31
+ Dispatch one foreground fresh `subagent` tasks batch, one `pr-gatekeeper` task per provisioned PR. Use `async: false`, `context: "fresh"`, each PR's worktree as cwd, and `skill: "gatekeep-pr"` for explicit injection. Use per-task `acceptance: { level: "none", reason: "the gatekeep child result line is the handoff contract" }`; the persona's completionGuard pin is separate. Let cohort concurrency bound running tasks, not alter membership.
32
+
33
+ Supply mode `assess` or `execute`, PR identity, rebase flag/base, confirmation, optional answer, record/report paths, queued pick for execute, and this statement:
34
+
35
+ `worktree provisioned at <path> on <headRefOid>; fetch and gitignore done - skip the fetch, run no worktree add, verify HEAD`
36
+
37
+ Supply the skill and this batch contract explicitly; do not depend on child discovery. Derive the installed procedure root from the selected skill's absolute `filePath` metadata (the path returned by the runtime's skill resolution), taking the directory containing `SKILL.md`. Supply `procedure root: <absolute installed skill directory>` in every assess and execute task. Resolve all named procedure dependencies relative to this root, never the PR worktree or cwd; this includes sibling briefs and baseline files as well as files under `reference/`. Assess runs steps 1-5 with sequential fresh Verify then Review in the supplied worktree, never separate helper worktrees. Preserve trusted configuration, digest/head binding, and PR-local tracking. Delegate tracked source edits and commits to implementer. Execute refreshes first, then runs step 7 using the existing next-action/fix-wave rules. Refresh before any execute write: if HEAD moved or the row is unavailable, return a fresh report instead of doing the stale action. Retained fix/merge-base grants, caps, deadlines, and no-progress decisions stay PR-local; never borrow sibling consent. Parallel waits are independent bounded watches.
38
+
39
+ Write only the supplied report, never a second record/report. End with exactly one closing result line:
40
+
41
+ | Result | Closing line |
42
+ |---|---|
43
+ | Report | `REPORT <report path>` |
44
+ | Conflict after recovery, no rendered report | `CONFLICTING <report path> - <sync record>` |
45
+ | Failure | `FAILED - <reason>` |
46
+ | Pre-report ask | `STOPPED - <ask> - <what the user must supply>` |
47
+
48
+ Await all children before rendering or running consequences; never overlap batches. Classify non-zero, timeout, absent or off-contract result as `failed - <first error line>`, using the first error, not an invented success. Isolate failures. All terminal PRs get summary and terminal menus with no helper dispatch.
49
+
50
+ ## Recovery-first outcome adapter
51
+
52
+ Apply only in multi-PR mode at existing decision points. Leave single-PR owners and reference contracts unchanged. Never retry an unsafe checkout. For sync failures first restore/assert `<pre_head>` and a clean tree using the existing abort/reset recovery; a failed assertion is a hard failure, not permission to continue.
53
+
54
+ | Event | Child result after required recovery |
55
+ |---|---|
56
+ | `unresolved stop in <commit7>`, `implementer returned open`, `residue outside conflicted files`, `reviewer: <top finding>` | Record `sync: failed - <reason>`; no rendered report; `CONFLICTING <report path> - <sync record>` |
57
+ | `rebase failed: <first line>` or `push rejected: <first server line>` | `FAILED - sync: <reason>` |
58
+ | Sync terminal preconditions, baseline mismatch, lease refusal | `FAILED - sync: <reason>`; never conflict rows |
59
+ | Without rebase, head pushability false or unreadable | `FAILED - head not pushable: <head_url>:<head_ref>` before Verify |
60
+ | Required fallback cannot run: credentials, unresolved verification command after answer handling, or refused run | `FAILED - no verification evidence: <reason>`; never fabricate evidence |
61
+ | Helper output empty, off-contract, non-zero, or async | `FAILED - helper <role>: <quoted failure>` |
62
+ | All other steps 1-5 or 7 outcomes | `REPORT <report path>` |
63
+
64
+ Use only these pre-report asks; stop at the ask without further writes:
65
+
66
+ | Ask | Closing line | Re-dispatched assess task answer |
67
+ |---|---|---|
68
+ | Verification configuration rung 4 | `STOPPED - verification command - <subproject dirs seen>` | `verification command: <command>` |
69
+ | Multiple spec candidates | `STOPPED - spec candidate - <paths>` | `spec: <path>` |
70
+
71
+ Re-assess from steps 1-5 with the same record; substitute the answer at that ask only. Use `yes` confirmation as typed authority; `no` records `sync: declined - other author's branch`, stage skipped, then assesses unsynced. `not asked` covers same-author/no-op/skipped cases. Never ask the human from a child. UNKNOWN merge state after the single re-poll produces a report with merge withheld, not an ask. Step-7 contract, no-progress, and budget decisions return reports for parent menus.
72
+
73
+ ## Summary and menu round
74
+
75
+ The parent alone renders menus, prints reports verbatim, gathers answers, and runs cleanup/stop consequences. Render in roster order:
76
+
77
+ `#<N> <verdict line> | evidence: <ci | local | pending | unknown> | blockers: <count | unknown>`
78
+
79
+ Use report Verdict text, `conflicting - <sync record>`, `failed - <reason>`, or `stopped - <ask>`. Evidence is the record's resolved source; use unknown before Verify. Count open P# findings; use unknown without review. Follow with `Shared files:` and every changed path shared by two or more PRs, `<path> - #<N>, #<M>`. Include `#<N> - files unknown` for missing gathered files; print `none` when no path overlaps. Never schedule merges or choose landing order.
80
+
81
+ Render unchanged per-PR menus in roster order: report plus existing rows, conflicts with existing conflict rows and evidence, failures with retry/evidence/stop, STOPPED with the recorded ask and stop last. For retry use exactly `retry - re-dispatch this PR's assessment with the same record`, recommended only after clean recovery and no rebase. Resolve the actual gitdir with git (a worktree .git can be a file); require empty `git -C <worktree> status --porcelain` and neither rebase-merge nor rebase-apply there. Otherwise omit the row and print `Not offered: retry (worktree dirty or mid-rebase: <state>)`.
82
+
83
+ Show evidence only in-place from record/helper paths, then re-ask. Queue all other picks and answers until the final menu. Record direct picks with existing grant grammar; do not turn answers into grants. For terminal overlays, the parent handles `post coverage to ticket`, `post coverage to PR`, and `show evidence` from the primary checkout, even after cleanup removes the PR worktree; assign the parent the `merge queued` overlay's `wait` pick after all picks are collected: re-run `post-selection-loop.md` `### Merge course`'s post-merge read (`gh pr view <N> --json state,mergeCommit`), on `MERGED` persist `pr.state` and `pr.merge_commit` and render coverage/menu under `own merge`, otherwise re-render `merge queued` (`wait`/`show evidence`/`stop`), with no helper, child, or CI watch. Follow `decision-menu.md`'s terminal rows and `post-selection-loop.md` `### Merge course` and `### Output done-check`: execute the direct selected post only, with no compare-and-swap or helper dispatch; preserve successful-post suppression, failed-post rows, and terminal re-rendering. Queue terminal posts with all other external writes until every PR's pick is collected; keep evidence in-place from the retained record/helper paths. Exclude terminal picks from child dispatch; never use a removed worktree as cwd. Next batch mixes retry/answered-ask `assess` with ordinary nonterminal `execute` tasks, always same record.
84
+
85
+ ## Between batches and retirement
86
+
87
+ Run parent cleanup/stop only after all children finish, never on a live child's tree. For cleanup apply cleanup guards and ordered overrides. Keep the PR and post rows in the roster, set cleanup_done, and re-render with cleanup gone. Cleanup does not retire.
88
+
89
+ For stop on rebased-local heads reset to `<pre_head>` and assert exact HEAD and clean tree. For ordinary open PRs offer human-selected teardown naming unpushed fix commits that would be discarded, never auto-destroy. Under terminal overlays leave checkout as found and print leftover path/branch. Delete only that PR's record, helper outputs, and log_path files under existing teardown rules, then retire it. Await all next-batch results and re-render every nonretired PR; repeat until all retire.
90
+
91
+ Initialize parent tracker tasks `pr-<N> state: <record path>`; mark in_progress on dispatch, complete on retirement, failed on unretried failure. Child trackers are session-local stages, not parent roster tasks. Use a plain checklist if plan_tracker is unavailable.
@@ -83,7 +83,7 @@ loop: tracker state is extension state no subagent can read.
83
83
  - A phase `in_progress` (verify or ship included - resume stops at verify on its own)
84
84
  -> append the process-state section exactly as `## Producers` in
85
85
  `reference/brief-contract.md` lays it out: both status outputs verbatim
86
- (`No plan active.` verbatim when there is no plan), the contract's artifact-specific fields, and the gate-history line. Recognize the loop alternative by ledger line 1; read the ledger from the run worktree for its durable last decision. Use only the contract's grammar; write no second state file. Append with a single
86
+ (`No plan active.` verbatim when there is no plan), the contract's artifact-specific fields, and the gate-history line. Recognize the loop alternative by ledger line 1; read the ledger from the run worktree for its durable last decision. Read the run worktree's spec; when it carries `## Rework record`, copy its PR identity (without the title) and full pre-rework head into the optional fields in the contract's producer layout. Without that record, use the ordinary layout. Use only the contract's grammar; write no second state file. Append with a single
87
87
  `cat >> "<brief path>" <<'EOF' ... EOF` whose body is that block.
88
88
  - Re-read the file tail and confirm it ends with the gate-history line from the
89
89
  contract and nothing after.
@@ -27,7 +27,7 @@ Headings, fixed order. A consumer keys on headings, never prose.
27
27
  | `## Repo state` | one field per line: `toplevel`, `worktree: yes <path>` or `worktree: no`, `branch` (`detached` when none), `HEAD`, `base` (`unknown` when no remote resolves), `dirty: <porcelain>` or `dirty: clean`, `diff-stat` (`unavailable` when base unknown), `test cmd`; any field `unavailable` when its command failed; heading `## Repo state: not a git repo` outside git | always |
28
28
  | `## Decisions` | bullets; rejected alternatives marked `rejected:` | always |
29
29
  | `## Skills loaded` | frontmatter `name`s; `## Skills loaded: none` when none | always |
30
- | `## Process state` | `phase_tracker status` then `plan_tracker status` verbatim; `Active task: <name|none>`; the line `Gate history not restored - re-validate before advancing.` | only when a phase is `in_progress` and no hotfix flow is in context |
30
+ | `## Process state` | `phase_tracker status` then `plan_tracker status` verbatim; `Active task: <name|none>`; optional `PR: <owner/repo#N>` and `Pre-rework head: <40-char sha>` lines, present together only for a PR subject; the line `Gate history not restored - re-validate before advancing.` | only when a phase is `in_progress` and no hotfix flow is in context |
31
31
 
32
32
  For a loop ledger (line 1 exactly `# Ledger - loop engine`), replace `Active task` with these two fields, retaining both tracker status outputs and the gate-history line:
33
33
 
@@ -51,8 +51,10 @@ inline. `## Repo state` is matched by prefix: `## Repo state` or
51
51
  cohort's `handoff` skill wrote, which ends at `## Skills loaded`. Layout, byte-exact:
52
52
  one blank line, the heading on its own line, a blank line, the `phase_tracker status`
53
53
  output verbatim, a blank line, the `plan_tracker status` output verbatim, a blank line,
54
- `Active task: <name|none>`, then the gate-history line - unfenced, nothing after.
55
- For a task plan, set `Active task` to the first `→` task name, else `none` (also for `No plan active.`). For a line-1 ledger, read its path and last decision and use the loop alternative above.
54
+ `Active task: <name|none>`, then, only for a PR subject, exactly two lines:
55
+ `PR: <owner/repo#N>` and `Pre-rework head: <40-char sha>`, then the gate-history line -
56
+ unfenced, nothing after.
57
+ For a task plan, set `Active task` to the first `→` task name, else `none` (also for `No plan active.`). For a line-1 ledger, read its path and last decision and use the loop alternative above, retaining the optional PR pair before the gate-history line.
56
58
 
57
59
  Example appended block (the fence is documentation; the brief carries no fence):
58
60
 
@@ -112,6 +114,15 @@ Parse stops (print the offending line, make no tracker call):
112
114
 
113
115
  ## Process-state restore
114
116
 
117
+ Before any tracker write, read the optional PR identity and fetch its branch with
118
+ `gh pr view <N> --repo <owner/repo> --json headRefName`. Compare `headRefName` with
119
+ `git -C <worktree> branch --show-current`. A mismatch stops with
120
+ `brief names PR #N on <headRefName>, worktree is on <branch>`, substituting the observed PR number and both actual branch names, never literal placeholders; a failed fetch stops
121
+ with its error. Carry the paired full pre-rework head into reconstruction's
122
+ candidate selection. Without the optional pair, retain ordinary restoration.
123
+ Preserve the entry checks, standing-grant validation, plan repair, and task snapshot;
124
+ PR identity implies no approval or gate evidence.
125
+
115
126
  Facts that fix the order (from the extensions):
116
127
 
117
128
  - Arming happens only on `start brainstorm`; `reset` disarms; `skip` and `complete`
@@ -129,7 +140,7 @@ Per-stage call table, keyed by the brief's active phase (`→`). Use
129
140
  `R = resume: <brief file or "pasted brief">` for plan/implement/verify skips and
130
141
  `S = resume: <spec path>` for brainstorm skips. Resolve `<spec path>` as
131
142
  `reconstruction.md` "Candidates" does, to an absolute path under the worktree: the single
132
- `*.md` spec added after base under `flowGuards.specDirs`, or a spec tracked from base when
143
+ `*.md` spec added after the candidate base under `flowGuards.specDirs`, or a spec tracked from that base when
133
144
  a plan added after base pairs with it by basename. Zero specs -> stop; more than one ->
134
145
  human picks. The consumer carries this path to its entry check 6 and its post-restore
135
146
  announcement without re-resolving it.
@@ -147,7 +158,7 @@ For a ledger brief, pair by the ledger's path-only `**Spec:**` header, not basen
147
158
 
148
159
  `planPath` (task-plan implement onward): the brief carries none. Resolve as reconstruction does
149
160
  (`reconstruction.md`, "Candidates", including its `flowGuards.specDirs` resolution): the
150
- single plan added after base, paired with `<specDir>/<same basename>` whether that
161
+ single plan added after the candidate base, paired with `<specDir>/<same basename>` whether that
151
162
  spec was added after base or tracked from base (`reconstruction.md` "Candidates");
152
163
  resolve the selected plan to an absolute path under the worktree before `plan_check`.
153
164
  Zero pairs -> stop; more than one -> human picks.
@@ -176,7 +187,7 @@ End a turn awaiting selection, restoration, or answers with phase `plan` retaine
176
187
 
177
188
  ### Citation repair
178
189
 
179
- Record the plan's state before any write: tracked and clean when `git -C <worktree> ls-files --error-unmatch -- <plan path>` exits 0 and `git -C <worktree> status --porcelain -- <plan path>` prints nothing (index and working copy both match HEAD; a staged edit alone makes it dirty); otherwise copy the file to a temp path outside the worktree.
190
+ Record the plan's state before any write: tracked and clean when `git -C <worktree> ls-files --error-unmatch -- <plan path>` exits 0 and `git -C <worktree> status --porcelain -- <plan path>` prints nothing (index and working copy both match HEAD; a staged edit alone makes it dirty); otherwise copy the file to a temp path outside the repository before citation repair writes anything. Any non-empty porcelain output, including staged-only `M <plan path>`, takes this copy path even when tracked and the working copy equals HEAD. Never substitute `git checkout` for the dirty or untracked plan's snapshot or saved-copy restore.
180
191
 
181
192
  Classify every finding against the table by its `check` and `reason`, gathering the row's evidence first. One finding with no row -> stop (below) before any write.
182
193
 
@@ -25,6 +25,12 @@ for a base ref before reading any artifact.
25
25
 
26
26
  A pinned spec (seed route, `seed.md`) skips this section.
27
27
 
28
+ When the brief carries `Pre-rework head:`, use that full SHA as `<base>` for spec
29
+ and plan candidate scans instead of the merge-base from "Base"; the PR's earlier
30
+ spec remains input, not an added candidate, so it does not trigger a multi-spec pick.
31
+ Keep pairing, untracked candidates, task evidence, and human confirmation below.
32
+ Without the optional pair, including a bare worktree, use the ordinary base.
33
+
28
34
  Resolve with `gauntlet_setting({ key: "flowGuards" })`; `<dirs>` below is its `specDirs` plus `planDirs` (each spec dir's sibling `plans` dir), space-separated.
29
35
 
30
36
  ```bash
@@ -17,7 +17,7 @@ Git worktrees create isolated workspaces sharing the same repository, allowing w
17
17
 
18
18
  ## Step 0 — Detect Existing Isolation (REQUIRED)
19
19
 
20
- Before doing anything else, check whether you are **already** inside an isolated worktree. Creating a worktree inside another worktree, or inside a submodule, produces silent corruption.
20
+ For a caller passing resolved PR values, read [reference/pr-head.md](reference/pr-head.md) now and follow it instead of Steps 0 and 1, before generic linked-worktree reuse. For every other caller, check whether you are **already** inside an isolated worktree. Creating a worktree inside another worktree, or inside a submodule, produces silent corruption.
21
21
 
22
22
  ```bash
23
23
  read -r GIT_DIR GIT_COMMON <<<"$(git rev-parse --path-format=absolute --git-dir --git-common-dir | tr '\n' ' ')"
@@ -44,7 +44,7 @@ Use `$WORKTREE`, report it in the Step 4 shape (`Worktree ready at <that path>`)
44
44
 
45
45
  ## Step 1 — Announce, Don't Ask (skill-driven work defaults to a worktree)
46
46
 
47
- For gauntlet-driven work — brainstorming, plans, implementation — the worktree is the default, not a question. Announce and proceed:
47
+ For a PR head, use [reference/pr-head.md](reference/pr-head.md) instead of this step. For gauntlet-driven work — brainstorming, plans, implementation — the worktree is the default, not a question. Announce and proceed:
48
48
 
49
49
  > "Setting up an isolated worktree at `<path>` on branch `<branch>` for this work."
50
50
 
@@ -177,7 +177,7 @@ If `git symbolic-ref -q HEAD` returns nothing, you're on a detached HEAD. Do not
177
177
 
178
178
  ## Keeping a Worktree Current
179
179
 
180
- For longer-running work the base branch advances:
180
+ For a PR-subject branch, follow [reference/pr-head.md](reference/pr-head.md): merge `origin/<baseRefName>`; never rebase it. For other longer-running work the base branch advances:
181
181
 
182
182
  ```bash
183
183
  git -C "$WORKTREE" fetch origin
@@ -0,0 +1,74 @@
1
+ # PR-head worktree
2
+
3
+ ## Select home and stop before writes
4
+
5
+ For a caller passing PR values, follow this reference instead of `using-git-worktrees/SKILL.md` Steps 0 and 1. Derive ROOT from the absolute git common directory as in Step 0, applying its submodule guard. Resolve the worktree home through the existing overrides/native-tool/wrapper contract; default to `$ROOT/.worktrees/<name>`. Set `<name>` to `headRefName` with every `/` replaced by `-`. Never adopt the session's linked tree unless it is the selected home on `headRefName`.
6
+
7
+ Run `git -C "$ROOT" fetch origin`, then check these stops in order before creating or modifying any file in the primary or a worktree, or running `git worktree add`.
8
+
9
+ | Observable case | Exact stop |
10
+ |---|---|
11
+ | `isCrossRepository` true | `PR #N's head lives on fork <headRepository.nameWithOwner>; rework needs an in-repo branch.` |
12
+ | `headRefName` listed by `git -C "$ROOT" worktree list --porcelain` at another path, including `.worktrees/pr-<N>` | `branch <headRefName> is checked out at <path>; finish or remove that worktree first.` |
13
+ | `git -C "$ROOT" rev-parse --verify -q <headRefOid>^{commit}` fails after fetch, or `origin/<headRefName>` differs from `headRefOid` | `PR #N head <headRefOid7> is not reachable (origin/<headRefName> is at <sha7>); the branch moved.` |
14
+ | Local branch exists and `git -C "$ROOT" merge-base --is-ancestor <headRefName> origin/<headRefName>` exits 1 | `local branch <headRefName> is at <sha7>, not an ancestor of origin/<headRefName> (<sha7>); rework needs the PR head.` |
15
+
16
+ ## Provision the exact head
17
+
18
+ Apply the first matching row after all stops pass. Keep the process cwd unchanged and carry WORKTREE as a value.
19
+
20
+ | State | Action |
21
+ |---|---|
22
+ | Selected home is already on `headRefName` | Run Step 3's dirty-tree ask first; do not stash or clean automatically. Then run `git -C "$WORKTREE" merge --ff-only origin/<headRefName>` and require HEAD equal to `headRefOid`; otherwise stop naming both SHAs. |
23
+ | No worktree, local branch exists | Only after ancestry proof, run `git -C "$ROOT" update-ref refs/heads/<headRefName> origin/<headRefName>`, then `git -C "$ROOT" worktree add "<home>" <headRefName>`. Never use `git -C "$ROOT" branch -f`. |
24
+ | No worktree, no local branch | Run `git -C "$ROOT" worktree add "<home>" <headRefName>`. Git's unique-remote DWIM resolves exactly one matching remote-tracking branch as `--track -b <headRefName> "<home>" origin/<headRefName>`; do not substitute the generic new-branch `-b` creation path. |
25
+
26
+ For fresh creation, apply Step 3's source-clean ask and the existing home safety checks after the ordered stops. Respect the resolved home and native tool/wrapper, but use an overrides `create:` only when it takes `<branch>` as an existing branch and contains no `-b`/`-B`. Otherwise run the built-in checkout above and add `create: override skipped (contains -b)` to the report. Resolve tokens and surface wrapper failures through the existing contract.
27
+
28
+ Verify `git -C "$WORKTREE" rev-parse HEAD` equals `headRefOid`. On a post-create head race, leave the tree intact and stop naming its path and both SHAs. Capture `PRE_REWORK_HEAD=<headRefOid>` and `BASE_SHA=$(git -C "$ROOT" rev-parse origin/<baseRefName>)` as full SHAs. In the Step 4 report use `Base: <headRefName> @ <sha7>` and add immediately after it:
29
+
30
+ ```text
31
+ PR: <owner/repo#N>
32
+ Pre-rework head: <sha>
33
+ Base SHA: <sha7>
34
+ ```
35
+
36
+ ## Preserve pre-rework history
37
+
38
+ Advance by merge: preserve every commit reachable from PRE_REWORK_HEAD. Never `git rebase`, or use `reset` or `commit --amend` to replace a commit at or below that boundary. Keep the flow's appended-spec approval `commit --amend --no-edit` in `brainstorming/reference/spec-finalization.md` and appended-wave fold `reset --soft "$WAVE_BASE_SHA"` in `subagent-driven-development/SKILL.md` unchanged; require WAVE_BASE_SHA to descend from PRE_REWORK_HEAD.
39
+
40
+ ## Merge the fetched base
41
+
42
+ Run `git -C "$WORKTREE" merge --no-ff --no-edit origin/<baseRefName>` and record the matching outcome:
43
+
44
+ | Outcome | Record and continuation |
45
+ |---|---|
46
+ | Base already an ancestor | `merge: no-op - head contains origin/<base>`; no commit. |
47
+ | Clean merge | `merge: <sha7>`; this merge is the first rework commit. |
48
+ | Conflicts | Follow the helper recipe below. |
49
+
50
+ Snapshot `git -C "$WORKTREE" status --porcelain` before helper dispatch. Get conflicted paths with `git -C "$WORKTREE" diff --name-only --diff-filter=U`. Dispatch one fresh foreground `implementer`, `cwd` the worktree, `async: false`, with that exact file list, fetched base, and SCOPED_TEST_COMMANDS. Require resolution of every listed file preserving both sides' intent, exactly the supplied scoped commands, and `git -C "$WORKTREE" add` for every resolved file. Require return `done`, or `done - contract exception: <why>` when resolution changes a PR or bound-ticket obligation. Forbid helper commits, aborts, and edits outside the list.
51
+
52
+ Read the first-found overrides file from BASE_SHA, not the conflicted tree: `git -C "$ROOT" show <BASE_SHA>:<overrides path>`. Use SCOPED_TEST_COMMANDS only from a documented file-scoped runner under `## PR gate` or `## verification-before-completion` with `requires credentials` not `true`. Missing runner or `requires credentials: true` means `none`, with the reason recorded as `none run (<reason>)`. Never infer file scope from a full-suite script such as `npm test`.
53
+
54
+ Continue only with an empty unmerged index (`git -C "$WORKTREE" diff --name-only --diff-filter=U`), every listed file staged (`git -C "$WORKTREE" diff --cached --name-only`), and the `git -C "$WORKTREE" status --porcelain` delta contained in the listed paths. Then complete with `git -C "$WORKTREE" commit --no-edit` and record `merge: <sha7>, <n> file(s) resolved, scoped tests <green | red in <file>: <tests> | none>`. Land a reported contract exception and name it in the spec's `## Rework record` `Changes:`.
55
+
56
+ For any failed helper/completion checks, run `git -C "$WORKTREE" merge --abort`, record `merge: unresolved - <files>`, and continue on the original unmerged head. Open the spec's Design with `merge origin/<base> and resolve <files>` as eventual Task 1. On every outcome, require the base-SHA constraint reads owned by `brainstorming/reference/pr-subject.md`.
57
+
58
+ Treat this merge, helper edits to the conflict list, and scoped conflict tests as the sole preapproval write/run exception outside spec directories. Run no other brainstorming tests, dispatch no reviewer, and push nothing; leave review of the merge to end-of-flow code review.
59
+
60
+ ## Fetch unresolved review threads
61
+
62
+ Run this query; callers cite this owner rather than copying it:
63
+
64
+ ```
65
+ gh api graphql --paginate -f query='query($o:String!,$r:String!,$n:Int!,$endCursor:String){repository(owner:$o,name:$r){pullRequest(number:$n){reviewThreads(first:100,after:$endCursor){pageInfo{hasNextPage endCursor} nodes{id isResolved isOutdated path line originalLine comments(first:100){pageInfo{hasNextPage endCursor} totalCount nodes{url body author{login} createdAt}}}}}}}' -F o=<owner> -F r=<repo> -F n=<N>
66
+ ```
67
+
68
+ Merge every reviewThreads page and filter to `isResolved == false`. For each thread whose comments report `hasNextPage: true` (with `totalCount` exceeding the first page), fetch the remaining pages by thread id with a separate comment cursor:
69
+
70
+ ```
71
+ gh api graphql --paginate -f query='query($id:ID!,$endCursor:String){node(id:$id){... on PullRequestReviewThread{comments(first:100,after:$endCursor){pageInfo{hasNextPage endCursor} nodes{url body author{login} createdAt}}}}}' -F id=<thread id>
72
+ ```
73
+
74
+ Start that thread's follow-up query at its first comment page, then replace its initial comments with the complete paginated connection to avoid duplicates. Keep each thread's comment cursor separate from the reviewThreads cursor and fetch until its last page. Carry all comments in order: read the first request and every reply because replies can change the request. Render location as `<path>:<line>`, else `<path>:<originalLine> (outdated)`, else `<path>`. On fetch failure carry `threads: not fetched (<reason>)` to the gather draft and `Threads: not fetched (<reason>)` to the record; continue. On successful empty fetch record `Threads: none`.
@@ -46,7 +46,7 @@ BEFORE claiming any status or expressing satisfaction:
46
46
  Skip any step = lying, not verifying
47
47
  ```
48
48
 
49
- Evidence is proportionate to the claim: a task or wave claim is proven by the commands the plan declares for it — a scoped run is **complete** evidence for a scoped claim. Phase-completion and ship claims require the project's full verification entrypoint (tests + style + format), run once at verify before the conformance gate.
49
+ Evidence is proportionate to the claim: a task or wave claim is proven by the commands the plan declares for it - a scoped run is **complete** evidence for a scoped claim. For phase-completion and ship claims, use the project's full verification entrypoint (tests + style + format), run once at verify before the conformance gate. For an approved spec carrying `## Rework record`, instead prove the scoped claim with the union of every plan task's `Tests:` commands plus the header's documented lint and format commands; treat `none - PR subject, scoped` as no header command, not an executable command. Keep freshness, conformance and phase gates intact; an open PR without that spec record does not select this policy.
50
50
 
51
51
  ## Common Failures
52
52
 
@@ -160,6 +160,8 @@ Defaults recognise `make ci`, `make test`, `npm test`, `pnpm test`, `yarn test`,
160
160
 
161
161
  Before running the verification gate, call `phase_tracker({ action: "start", phase: "verify" })`.
162
162
 
163
+ Run the plan header's full verification command set at verify. For an approved spec carrying `## Rework record`, instead run the union of every task's `Tests:` commands plus the header's documented lint and format commands, omitting task `none:` entries and the non-executable header sentinel `none - PR subject, scoped`. Read all task blocks, including the merge task; the lint/format-only header alone is not the verification set. Do not run the full test entrypoint on this PR-subject path or copy task commands into the header. Keep ordinary ticket/free-text verification unchanged and pass through the same freshness, conformance and phase gates; change no settings, runner or implement-phase gate.
164
+
163
165
  When all verification passes, finish and await the required `plan_tracker` updates before marking verify complete. Reconcile accepted work on its existing indices; never clear or reinitialize to obtain closure. In a direct verification path, complete only after those updates have actually returned.
164
166
 
165
167
  ```
@@ -178,12 +178,16 @@ Each step is **one action, 2-5 minutes**:
178
178
  ---
179
179
  ```
180
180
 
181
+ **PR-subject header.** Detect a PR subject only from `## Rework record` in the approved spec. For that subject, replace the header's full verification command set with only the overrides' documented lint and format commands. With neither documented, write `**Verification:** none - PR subject, scoped`; this sentinel is not a command. Keep every task's `Tests:` command out of the header: copying it there makes it a full-suite duplicate rejected by `plan_check`. Keep the original spelling; resolve duplication by separating the task tests from header lint/format, not by disguising commands. Read the PR specialization in [reference/plan-contract.md](reference/plan-contract.md#pr-subject-verification).
182
+
181
183
  The full verification entrypoint appears only on the `**Verification:**` line — see [reference/plan-contract.md § Header-only entrypoint](reference/plan-contract.md). The verify phase reads it from the plan; execution runs `Tests:` commands only. Execution runs it in the worktree via the subshell form `(cd "<abs worktree path>" && <command>)`; the process cwd stays in the primary checkout, and dispatch `cwd` is the worktree path. The `**Happy path:**` line is optional: present only when File Structure selected a row, with the row label, the row's command in backticks, and the row's `Timeout` as `(~<duration>)` (omit the suffix when the cell is absent or malformed). Like `Verification`, the happy-path command appears only on this header line - never in a task's `Tests:` or `Run:` block and never as free text in wave scope; `plan_check` enforces both.
182
184
 
183
185
  ## Task Structure
184
186
 
185
187
  Each task uses `- [ ]` checkbox steps so execution tools (and humans) can track progress.
186
188
 
189
+ **PR-subject tasks.** Read the approved spec's `## Rework record`. When its base merge is `unresolved`, make merging the named base and resolving the listed files Task 1, before thread fixes; preserve the original commits. Make each `fix` thread exactly one task whose title carries that thread id. Name the thread's request in its `Tests:` block through the scoped command and `via:` seam that exercises it. Keep `keep open` and `decline: <reason>` as recorded dispositions, not repair tasks. Apply the existing file-disjoint wave, spec-anchor, baseline and checker rules to these tasks without changing executor or spec-commit mechanics.
190
+
187
191
  ```markdown
188
192
  ### Task N: [Component Name]
189
193
 
@@ -276,7 +280,7 @@ If a decision is genuinely open, put it in an explicit **Open Questions** sectio
276
280
  The plan-writer runs Baseline, the deterministic checker, then the judgment checks before replying.
277
281
 
278
282
  - **Baseline.** Execute the numbered procedure below for task `Tests:` commands only; leave header `**Verification:**` to verify. Never author baseline evidence without execution.
279
- 1. Read provenance: `BASE=$(git -C "<abs worktree path>" rev-parse --short HEAD)` and `git -C "<abs worktree path>" status --porcelain --untracked-files=all`. Match every `XY <path>` entry (including `??`) against the worktree-relative plan and spec paths, plus only the parent-supplied `In-flight:` paths on an `Amend:` re-dispatch; never infer that set. Also allow untracked paths already recorded in existing baseline bullets' ` - artifacts:` suffixes in the plan at the plan path, excluding every plan `Create:` or `Test:` path. Keep those suffixes on retained bullets. Any other entry blocks all commands: collect `- Baseline: worktree dirty on <BASE>: <paths>`. Allowed in-flight paths outside the spec and plan directories are source edits: collect `- Baseline: in-flight source edits on <BASE>: <paths>` and run nothing. Allow doc-only workflow edits. For plain jj (no `.git`, VCS kind `jj`), use `BASE=$(jj -R "<abs worktree path>" log -r @- --no-graph -T 'commit_id.short()')` and `jj -R "<abs worktree path>" diff --summary`; classify one-letter status and relative paths identically (`A` includes new files). Never revert or clean anything.
283
+ 1. Read provenance: `BASE=$(git -C "<abs worktree path>" rev-parse --short HEAD)` and `git -C "<abs worktree path>" status --porcelain --untracked-files=all`. Match every `XY <path>` entry (including `??`) against the worktree-relative plan and spec paths, plus only the parent-supplied `In-flight:` paths on an `Amend:` re-dispatch; never infer that set. Also allow untracked paths already recorded in existing baseline bullets' ` - artifacts:` suffixes in the plan at the plan path, excluding every plan `Create:` or `Test:` path. Keep those suffixes on retained bullets. Any other entry blocks all commands: collect `- Baseline: worktree dirty on <BASE>: <paths>`. Allowed in-flight paths outside the spec and plan directories are source edits: collect `- Baseline: in-flight source edits on <BASE>: <paths>` and run nothing. Allow doc-only workflow edits. For plain jj (no `.git`, VCS kind `jj`), use `BASE=$(jj -R "<abs worktree path>" log -r @- --no-graph -T 'commit_id.short()')` and `jj -R "<abs worktree path>" diff --summary`; classify one-letter status and relative paths identically (`A` includes new files). Never revert or clean anything. Record a short baseline provenance note with the initial per-file inventory, BASE, and which exact paths were admitted before running commands; retain it even when a later run is contaminated or blocked.
280
284
  2. On Findings/Amend, retain a bullet only for unchanged command text on the same base; rerun commands with missing/invalid bullets, changed text, or different recorded SHA. `Findings: rerun baseline` reruns all commands. Never rewrite recorded SHAs without execution. For each command, split on the contract's separators (`&&`, `||`, `;`, `|`) and classify anchor tokens (a `Test:` path alone or with `::`/`#`/`:` filters) and selectors independently:
281
285
 
282
286
  | Command anchors/selectors | Baseline action |
@@ -47,6 +47,10 @@ The header value's backtick spans (else the raw value) are split on `&&`, `||`,
47
47
 
48
48
  The optional `**Happy path:**` line's backticked command is a second header entrypoint under the same rule: its segments join the header segment set, and its whole command may not appear in wave prose.
49
49
 
50
+ ## PR-subject verification
51
+
52
+ Detect this specialization only from `## Rework record` in the approved spec. Put only the overrides' documented lint and format commands in `**Verification:**`, or the literal `none - PR subject, scoped` when neither is documented. Treat the sentinel as no header command. Keep scoped tests in task `Tests:` blocks, never copy them into the header or change their spelling to evade `header-entrypoint` or `tests-block` rejection. Retain baseline evidence and all existing checker grammar. At verify, run the union of every task's `Tests:` commands plus the header's lint/format commands; omit `none:` entries and the sentinel from executable commands. This policy specializes the command set, not freshness, conformance or phase gates. Without the record, keep the ordinary full verification entrypoint.
53
+
50
54
  ## Happy path line (`header-happy-path`)
51
55
 
52
56
  Optional. When present, the header line reads `**Happy path:** <label> - \`<command>\`` with an optional ` (~<duration>)` suffix, `<duration>` matching `\d+(s|m|h)`. A present line that does not parse is a finding; absence is never a finding. The label names the row of the overrides file's `## Happy path` table the plan selected; the verify phase reads the line from the plan and re-derives the row from the real diff.