pi-gauntlet 5.16.0 → 5.16.2

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,13 @@
1
1
  # Changelog
2
2
 
3
+ ## v5.16.2 - 2026-09-22
4
+
5
+ - gatekeep-pr: `SKILL.md` is a flow-ordered body under 250 lines; the assessment phases, finding IDs and dispositions, consent table and courses, and the post-selection loop move to `skills/gatekeep-pr/reference/{assessment,findings,decision-menu,post-selection-loop}.md`, each rule owned once. The CI telemetry-salvage probe reads `reference/post-selection-loop.md`. (#43)
6
+
7
+ ## v5.16.1 - 2026-09-20
8
+
9
+ - gauntlet-handoff follows the shipped pi-cohort 7.1.0 `handoff` skill: a skill cannot expand `/skill:handoff`, so step 2 reads cohort's `skills/handoff/SKILL.md` from the session skill list and follows its procedure; the brief path comes from cohort's `Handoff written:` report line (`Handoff not written:` is a STOP) instead of being recomputed; the minimum is stated as pi-cohort >= 7.1.0 in README and the brief contract. (#40)
10
+
3
11
  ## v5.16.0 - 2026-09-20
4
12
 
5
13
  - Skills never name a provider or model: `subagent-driven-development` drops its `## Model Selection` tier table for a one-place `## Model` rule (a dispatch's `model:` is omitted, or carries a `gauntlet_setting` value, a user-named model, or the main loop's own string); `doc/configuration.md` gains `### Dispatch model precedence`; `scripts/model-literal-lint.mjs` bans provider/model literals in `skills/`, `agents/`, `extensions/` from `scripts/ci.mjs`. `writing-skills` widens its trigger to personas and prompt templates and adds `## Authoring rules` (imperative voice, low conditionality, minimal diff, oversized-skill extraction); AGENTS core `v6` makes it binding for every skill, persona, and prompt edit. `shape-ticket` and `conformance-check.md` name the main-loop-string provenance explicitly. (#42)
package/README.md CHANGED
@@ -79,11 +79,11 @@ pi-gauntlet is **opinionated**: every non-trivial change is *meant* to ride this
79
79
 
80
80
  ## Handoff and resume
81
81
 
82
- `/skill:gauntlet-handoff` and `/skill:gauntlet-resume` are a pair: cohort's `handoff` skill writes the six flow-agnostic headings, gauntlet-handoff appends `## Process state`, and gauntlet-resume reads the whole brief back. Both gauntlet skills read one grammar file, `skills/gauntlet-resume/reference/brief-contract.md`; the repo validator (`scripts/ci.mjs`) fails if a grammar line appears anywhere else under `skills/`. Requires the pi-cohort release that ships [pi-cohort #18](https://github.com/jjuraszek/pi-cohort/issues/18) (the `handoff` skill); on an older pi-cohort, gauntlet-handoff stops before writing.
82
+ `/skill:gauntlet-handoff` and `/skill:gauntlet-resume` are a pair: cohort's `handoff` skill writes the six flow-agnostic headings, gauntlet-handoff appends `## Process state`, and gauntlet-resume reads the whole brief back. Both gauntlet skills read one grammar file, `skills/gauntlet-resume/reference/brief-contract.md`; the repo validator (`scripts/ci.mjs`) fails if a grammar line appears anywhere else under `skills/`. Requires pi-cohort >= 7.1.0 (the `handoff` skill, [pi-cohort #18](https://github.com/jjuraszek/pi-cohort/issues/18)); on an older pi-cohort, gauntlet-handoff stops before writing.
83
83
 
84
84
  ### Smoke walkthrough (release-gated)
85
85
 
86
- Run by a human against the pi-cohort release that ships #18, before a pi-gauntlet release claims the pair works; record the outcome in the release commit body.
86
+ Run by a human against pi-cohort >= 7.1.0, before a pi-gauntlet release claims the pair works; record the outcome in the release commit body.
87
87
 
88
88
  1. Implement phase with tasks `complete`/`in_progress`/`pending` in a `.worktrees/<branch>` flow, session in the primary checkout: `/skill:gauntlet-handoff` writes `<tmpdir>/pi-handoff/<branch>.md` with the six core headings then `## Process state` last; a fresh session running `/skill:gauntlet-resume <path>` restores implement with the three statuses and ends `Gate history not restored; re-validating <task> before any stage advance`.
89
89
  2. Plan phase, `No plan active.`: the brief keeps that line and `Active task: none`; resume restores phase-only with no `plan_tracker init`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-gauntlet",
3
- "version": "5.16.0",
3
+ "version": "5.16.2",
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",
@@ -9,19 +9,19 @@ argument-hint: "<pr> [issue-ref] (e.g. 123, or 123 gh-45)"
9
9
 
10
10
  Verify, don't trust. A PR description is a claim, not proof: over-claimed coverage,
11
11
  hallucinated references, and "tests pass" that were never rerun are the normal case,
12
- not the exception - especially on generated code. This skill gathers evidence, accepts green CI on the exact assessed head as
13
- verification evidence (running the project's own verification command only as
14
- the fallback), reviews the diff against a rubric, and
15
- presents a deterministic, authorship-aware menu. Authorship sets which row carries
12
+ not the exception - especially on generated code. Gather evidence, accept green CI on
13
+ the exact assessed head as verification evidence (run the project's own verification
14
+ command only as the fallback), review the diff against a rubric, and present a
15
+ deterministic, authorship-aware menu. Authorship sets which row carries
16
16
  `[recommended]`; it never changes which rows are offered.
17
17
 
18
- **Consent gate.** The only actions this skill performs before you pick a menu row are:
18
+ **Consent gate.** The only actions performed before a menu row is picked are:
19
19
  read-only gathering, provisioning the worktree, and applying uncommitted, worktree-local
20
20
  doc-drift fixes discovered as a blocking finding. Every other action - code fixes,
21
- pushes, reviews, comments, merges - happens only on your explicit selection.
21
+ pushes, reviews, comments, merges - happens only on explicit selection.
22
22
 
23
23
  **Residual risk.** Running the verification command executes PR code with the
24
- operator's ambient credentials. There is no sandbox. Only run this skill against PRs
24
+ operator's ambient credentials. There is no sandbox. Run this skill only against PRs
25
25
  you are willing to execute.
26
26
 
27
27
  ## Arguments
@@ -34,10 +34,10 @@ you are willing to execute.
34
34
 
35
35
  ## Configuration resolution
36
36
 
37
- Applied per concern, first match wins, evaluated unconditionally - never delegated to
37
+ Apply per concern, first match wins, evaluated unconditionally - never delegated to
38
38
  a wrapper skill:
39
39
 
40
- 1. **Repo root `REVIEW.md`** (rubric concerns only). Always wins over the shipped
40
+ 1. **Repo root `REVIEW.md`** (rubric concerns only). Wins over the shipped
41
41
  baseline and reviewer-persona defaults on any conflict.
42
42
  2. **Gauntlet overrides file** (3-location discovery, first found wins): the
43
43
  `## PR gate` section (verification command, `timeout minutes`, `requires credentials`, issue
@@ -46,8 +46,8 @@ a wrapper skill:
46
46
  3. **Repo documentation** - an explicitly documented command or tool (e.g. `AGENTS.md`'s
47
47
  canonical test entrypoint, a documented worktree wrapper, a documented tracker CLI,
48
48
  or documented merge policy/branch rules). Reading documentation is not inference.
49
- Discovery-only: consumers are never told to add gatekeep-pr configuration here.
50
- 4. **Ask the user.** Never guessed from lockfiles, file heuristics, or vibes.
49
+ Discovery-only: never tell consumers to add gatekeep-pr configuration here.
50
+ 4. **Ask the user.** Never guess from lockfiles, file heuristics, or vibes.
51
51
 
52
52
  The `## PR gate` overrides schema (all keys optional except the verification command,
53
53
  which is required unless documented elsewhere):
@@ -70,7 +70,7 @@ in the repo's `REVIEW.md` (rubric) and the gauntlet overrides file's `## PR gate
70
70
  section (everything else); anything a wrapper carries beyond trigger phrases is
71
71
  misplaced and belongs in one of those two homes instead.
72
72
 
73
- All of the above is read from the **merge-base of the PR's base branch**, never from
73
+ Read the ladder from the **merge-base of the PR's base branch**, never from
74
74
  the PR's head tree - a PR cannot weaken its own rubric or swap the command that will
75
75
  gate it. Recipe: `MB=$(git merge-base origin/<baseRefName> <headRefOid>)`, then for
76
76
  each ladder source `git show "$MB:<path>"` (e.g. `git show "$MB:REVIEW.md"`,
@@ -89,154 +89,17 @@ Use `plan_tracker`, never `phase_tracker`. Init with the first four stages:
89
89
  Verifier enumerates them and record each verdict: a matched claim ->
90
90
  `complete`, a contradicted claim -> `failed` (shown crossed, error color).
91
91
  Once every claim is terminal and `claim-check` is closed, `add` `review` and
92
- `consent menu` and continue. Stages are never inited ahead of the claims:
93
- the tracker rejects a verdict recorded behind a still-pending stage. A
92
+ `consent menu` and continue. Never init stages ahead of the claims: the
93
+ tracker rejects a verdict recorded behind a still-pending stage. A
94
94
  failed stage or claim stays `failed` while the skill stops at the menu -
95
95
  never marked complete to move on. On a harness without the `plan_tracker`
96
96
  tool: fall back to a plain checklist (or skip if none is available);
97
97
  functionality is unchanged either way.
98
98
 
99
- ## Assessment
100
-
101
- Four phases, run in order, read-only through Phase 3:
102
-
103
- **Phase 1 - Gather.** Run verification-brief.md Section A in full: the fixed `gh`
104
- command set (`gh pr view`, `gh api user`, `gh pr diff`, both paginated comment
105
- endpoints, review threads, issue fetch, `git worktree list --porcelain` for
106
- discovery only), producing the normative gather digest.
107
-
108
- **Phase 2 - Provision worktree** (the orchestrator's mutation - a state machine):
109
-
110
- - A worktree already exists on the expected branch (`headRefName` for in-repo PRs,
111
- a fork-local `pr-<N>` branch for fork PRs), at any path -> reuse it unconditionally.
112
- In-repo PRs: `git fetch origin` + `git pull --ff-only` (the local branch tracks
113
- `origin/<headRefName>`). Fork PRs: the local `pr-<N>` branch has no upstream, so
114
- sync with `git fetch origin pull/<N>/head` + `git merge --ff-only FETCH_HEAD`
115
- instead. Either way, on divergence, dirt, or local-only commits -> STOP and surface.
116
- Never force, never create a duplicate.
117
- - The default path `.worktrees/pr-<N>` exists but holds a different branch -> STOP
118
- and surface; never repurpose.
119
- - Nothing exists -> create at `.worktrees/pr-<N>` (an overrides worktree wrapper may
120
- relocate it), following `using-git-worktrees` conventions (gitignore-first). In-repo
121
- PRs: `git fetch origin` + `git worktree add .worktrees/pr-<N> <headRefName>`. Fork
122
- PRs: `git fetch origin pull/<N>/head:pr-<N>` first, then add on that local branch.
123
- Verify post-checkout that HEAD == the digest's `headRefOid`.
124
-
125
- Record create-vs-reuse; it drives the non-merge teardown rule below.
126
-
127
- After provisioning, re-poll `mergeable` once (`gh pr view --json mergeable`) if Section
128
- A reported `UNKNOWN` - still `UNKNOWN` after this single re-poll is treated as not
129
- merge-ready and surfaced (see the merge preconditions below).
130
-
131
- **Phase 3 - Verify, then Review** (sequential, same worktree - deliberate: the
132
- verification command may write to the tree while the Reviewer reads it):
133
-
134
- - Run verification-brief.md Section B: resolve the verification evidence per its
135
- Evidence resolution table (green exact-head CI is the default evidence); run
136
- the resolved verification command only when the table selects a fallback or
137
- opt-out row, under its safety contract - self-contained and non-interactive (no prompts; run under a
138
- non-interactive environment), bounded by a timeout (default 15 minutes, `timeout
139
- minutes` override) via the first available mechanism: the harness's own bash
140
- timeout parameter, else the `timeout`/`gtimeout` CLI when installed, else a
141
- background-and-kill fallback - then material-claim checking against the PR body.
142
- After a local run, the orchestrator asserts tracked-only cleanliness (`git status --porcelain
143
- --untracked-files=no` empty, equivalently `git diff --quiet && git diff --cached
144
- --quiet`; HEAD unmoved) - untracked gate artifacts, including the Verifier's
145
- `log_path`, are expected and do not fail this check as long as `log_path` sits
146
- under a gitignored path inside the worktree. Any tracked change invalidates the
147
- run - re-provision and re-run once.
148
- - Run verification-brief.md Section C: review the source behind the diff against the
149
- merged rubric (shipped `review-baseline.md` overlaid by base-branch `REVIEW.md`),
150
- triage existing comments. The Reviewer emits its native output format only - AC
151
- coverage is not part of its contract.
152
-
153
- **Phase 4 - Integrate** (orchestrator):
154
-
155
- - **Provenance:** `worktree_root` matches the provisioned path, every `run_cwd` is
156
- inside it, `head_sha` matches the digest's `headRefOid`. On mismatch, re-fetch the
157
- PR head once and re-sync + re-run Phase 3 if it advanced; a second mismatch, or any
158
- path mismatch, is treated as missing evidence - not merge-ready. The claim stated in output names its source. Local path: precisely "reproduced
159
- locally under the project's documented verification command" - nothing
160
- stronger; never worded to imply a deployed, staging, or CI environment.
161
- CI path (`source: ci`): precisely
162
- `verified by CI: <check name(s)> succeeded on <sha> (run <url>)`
163
- - never phrased as local reproduction, never implying the local command ran;
164
- `<sha>` is the assessed `headRefOid`, `<url>` degrades to `unavailable` when
165
- absent. Provenance checks on `worktree_root`/`run_cwd` bind only to the local
166
- path.
167
- - **Telemetry record:** run `node <bin>/gauntlet-telemetry-salvage.mjs --worktree
168
- <provisioned path> --base origin/<baseRefName> --check` (`<bin>` = `<directory of
169
- this skill's SKILL.md>/../../bin`). Detect-only: it never mutates. `present` / `no
170
- telemetry run` / `never written` land in `## Evidence` as one line each. `stripped
171
- <path> in <sha>` mints a blocking `P#` (`source_ref`:
172
- `gauntlet-telemetry-salvage`) whose drafted fix is "run the salvage without
173
- `--check`" - the spec and its telemetry record are deliverables that ship in the
174
- squash. On a cell with no push row (fork overlay, report-only states) the same
175
- finding is a non-blocking follow-up instead: the record stays recoverable from the
176
- PR head ref after merge, and blocking would stop a ship the gate cannot repair.
177
- `unfinished <path>` (record still `in_progress` with no ship phase) also lands in `## Evidence` as one line and is non-blocking: pre-landing `in_progress` is normal, and the merge course's salvage run stamps it.
178
- - **Evidence:** On the CI path, list each satisfying check's name, conclusion, assessed SHA, and run URL - there is no command or raw_tail to paste. On the local path, paste each run's `command` and `raw_tail` verbatim, fenced - never
179
- paraphrased. Any authored summary is labeled as a summary and never substitutes for
180
- `raw_tail`.
181
- - **Severity translation:** Critical -> blocking, Moderate -> blocking, Minor ->
182
- non-blocking follow-up. A repo `REVIEW.md` severity mapping overrides this; any
183
- severity it names but does not map is fail-safe **blocking**, noted in the output.
184
- - **AC coverage:** the orchestrator computes `met` / `partial` / `missing` per
185
- acceptance criterion from the issue's ACs, the diff, and the Reviewer's findings -
186
- it is an integration product, not raw persona output. Only `met` is merge-ready;
187
- `partial` or `missing` is blocking. Skipped entirely when no issue is linked.
188
- - **Claims:** a failed local gate is a hard merge failure. A `contradicted` material
189
- claim is a blocking finding. An `unverifiable-pre-merge` claim used as merge proof
190
- (appears in the PR body's evidence/result/test-plan content) is blocking; stated as
191
- an explicit post-merge observation instead, it is a non-blocking follow-up.
192
- - **CI checks:** any blocking conclusion in the resolved check set (required or
193
- not - see the brief's Evidence resolution table) withholds merge from every
194
- pre-composed course until the user explicitly dispositions it, and mints a
195
- `P#`. Three dispositions: **flaky** (proceed via the custom row), **real** (it
196
- blocks until green), **CI-infrastructure-broken** (the checks themselves are
197
- untrustworthy: triggers the fallback local run, and merge stays withheld until
198
- that fallback produces green evidence). A **pending** required check (still
199
- running - the normal case, not a defect) is **wait-until-green, not
200
- dispositionable**: it mints no `P#`, is never dispositioned, and the withhold
201
- auto-lifts the moment it turns green - or, if it instead fails, converts into
202
- an undispositioned failing check with its own `P#` at that point. While
203
- pending, the report notes it under Evidence and every merge course simply does
204
- not render (a pending-only PR is not a blocking verdict - findings groups can
205
- all read "None" - the recommended course falls to `stop` or `review-comment`,
206
- never a merge course, until it resolves). The evidence decision is
207
- independent: a green check elsewhere in the resolved set still satisfies
208
- verification evidence while a pending required check withholds merge. The
209
- CI-sufficient path changes no consent surface: still read-only, no auto-merge,
210
- no posting, no menu change beyond the third disposition.
211
- - **Doc drift:** when the review finds committed doc drift as a **blocking** finding,
212
- the orchestrator applies the doc fixes itself, in the provisioned worktree (created
213
- or reused), as part of assessment - real edits, uncommitted, worktree-local. The
214
- result is presented in `## Findings`, the edits themselves under
215
- `## Drafted fixes / review`. Pushing them is a separate, later menu selection.
216
- Follow-ups alone never trigger doc fixes - only blocking drift does.
217
-
218
- ## Inline-first execution
219
-
220
- > This section is an optional optimization. Delete it and the rest of the skill still
221
- > works: the orchestrator can run every phase above itself, inline, with no subagent
222
- > system.
223
-
224
- The inline path is primary: the orchestrator runs the brief's sections itself, in
225
- order, self-contained. When pi-cohort is available, delegation is an optimization
226
- layered on top, never a hard dependency:
227
-
228
- - **Gatherer** -> `scout` builtin, as a prior sync run producing the gather digest.
229
- - **Verifier** -> `worker` builtin, dispatched with the report-only constraint
230
- prepended to its task ("report only - do not edit, fix, or commit anything").
231
- - **Reviewer** -> the existing `code-reviewer` agent, emitting its native output
232
- format (never overridden at call time).
233
-
234
- Verifier and Reviewer share the provisioned worktree via `cwd`, dispatched
235
- **sequentially** (Verify before Review, per Phase 3) - never `worktree: true`, which
236
- would provision a separate isolated worktree and break the shared-tree contract this
237
- skill depends on. A subagent that fails, or violates its section's output schema, is
238
- re-dispatched once demanding the schema; a second failure means that section runs
239
- inline instead.
99
+ ## Assess
100
+
101
+ Run four phases in order - gather, provision worktree, verify then review, integrate.
102
+ Read `reference/assessment.md` and `reference/findings.md` now.
240
103
 
241
104
  ## Verdict
242
105
 
@@ -245,42 +108,23 @@ merge-proof unverifiable claim, `partial`/`missing` AC coverage, scope creep whe
245
108
  issue is linked, committed doc drift, anything the merged rubric maps to blocking),
246
109
  **follow-ups only** (never gate merge), or **clean**.
247
110
 
248
- **Merge preconditions** (all must hold): gate green with every blocking finding fixed,
249
- not deferred; verification evidence present per the brief's Evidence resolution table (a CI claim or a green local run - a table-sanctioned CI skip is evidence, not missing "not run" evidence; result: not run blocks only when the table required a fallback run that didn't happen); `mergeable == MERGEABLE` (`UNKNOWN` after the one post-provision
250
- re-poll withholds merge, same as `CONFLICTING`); no undispositioned failing check in
251
- the resolved set, no pending required check; evidence pasted with clean provenance; worktree clean and synced with the remote
252
- head (fixes pushed first); explicit selection with a head compare-and-swap that
253
- passes. A merge selection while any precondition fails is refused, naming the failing
254
- precondition, and the menu re-renders - never a dead end, never a silent merge. Merge
255
- always executes as `gh pr merge --match-head-commit <assessed-sha>`; push and merge
256
- are never bundled into one selection, with one scoped exception: the selected merge
257
- course first runs `node <bin>/gauntlet-telemetry-salvage.mjs --worktree <provisioned
258
- path> --base origin/<baseRefName>` (no `--check`). `present` -> merge as-is. `restored
259
- <path> from <sha>` -> push that single `telemetry: restore` commit as part of this
260
- course, re-fetch `headRefOid`, and pass the new SHA to `--match-head-commit`. A line ending `(marked shipped)` (`present` or `restored`) is handled the same way: push that single `telemetry:` commit, re-fetch `headRefOid`, pass the new SHA. `restore
261
- failed` -> merge proceeds, the reason is printed, and the follow-up names recovery
262
- from the PR head ref.
263
-
264
- **Consent menu** (deterministic - this table is the golden-scenario oracle):
265
-
266
- | Author | State | Offered rows (first = `[recommended]`) |
267
- |---|---|---|
268
- | you | clean / follow-ups only | merge (squash); merge (merge-commit); do not merge (leave it); post no-blockers comment |
269
- | you | blocking | apply code fixes (named finding subset): skill edits in worktree, commits, re-runs gate, pushes - then merge re-offered; push applied doc fixes; do not act; post review-comment of findings |
270
- | someone else | clean / follow-ups only | approve; merge (squash, offered-unrecommended); post no-blockers comment |
271
- | someone else | blocking | post request-changes review; apply fixes on their branch (courtesy option 2); reply to existing threads; post comment |
272
- | bot author | any | someone-else's rows for the same state, review actions recommended |
273
- | fork (any) | any | post review (request-changes / comment / approve per state) - push and merge rows absent |
274
- | any | draft PR | assessment rows only; merge and approve rows absent until ready-for-review |
275
- | any | merged / closed | report-only; no mutation rows |
276
-
277
- The consent table above remains the single oracle for what may be offered; the
278
- Decision rendering section defines how its rows render as actions and numbered
279
- courses. Rows GitHub would refuse (branch protection, missing permissions,
280
- `viewerPermission` too low) are listed as unavailable with the reason. Approving
281
- your own PR is not offered. Nothing executes until explicit selection.
282
-
283
- ## Output
111
+ **Merge preconditions** (all must hold):
112
+
113
+ - gate green with every blocking finding fixed, not deferred
114
+ - verification evidence present per the brief's Evidence resolution table (a CI claim
115
+ or a green local run - a table-sanctioned CI skip is evidence, not missing;
116
+ "not run" blocks only when the table required a fallback run that didn't happen)
117
+ - `mergeable == MERGEABLE` (`UNKNOWN` after the one post-provision re-poll withholds
118
+ merge, same as `CONFLICTING`)
119
+ - no undispositioned failing check in the resolved set, no pending required check
120
+ - evidence pasted with clean provenance
121
+ - worktree clean and synced with the remote head (fixes pushed first)
122
+ - explicit selection with a head compare-and-swap that passes
123
+
124
+ Define `<bin>` = `<directory of this skill's SKILL.md>/../../bin`. Merge execution
125
+ per `reference/post-selection-loop.md` `### Merge course`.
126
+
127
+ ## Report
284
128
 
285
129
  The rendered report is terse by design: deciding factor, evidence lines, ID'd
286
130
  findings, menu. No restating diffs, no narration, no recap prose.
@@ -305,238 +149,23 @@ Requirement/doc drift (linked issue, committed doc drift, or spec conflict):
305
149
  F1. **<source_ref>** - <action>. Owner: <pr-author | tracker | human>
306
150
 
307
151
  ## Decision
308
- <action vocabulary + numbered courses - see Decision rendering>
152
+ <action vocabulary + numbered courses>
309
153
 
310
154
  ## Drafted fixes / review
311
155
  <payloads, each keyed by its finding ID>
312
156
  ```
313
157
 
314
- **ID rules:**
315
-
316
- - `<source_ref>` is a `file:line` where one exists, else the disputed thing (a
317
- quoted PR-body claim, a failing gate command, a required check name).
318
- - **Precedence:** Phase 4 and the merged rubric decide blocking vs. follow-up (the
319
- severity translation, AC coverage, claims, and required-check rules above); this
320
- section only chooses **which namespace** (`P#` / `L#` / `F#`) renders that
321
- decision. Category tags and the triage bar below never override an upstream
322
- blocking classification - a Phase-4 Moderate is always blocking (`P#` or `L#`
323
- per Total mapping), never demoted to `F#` by tag or by judgment call.
324
- - **Total mapping:** every blocking element of the Verdict maps to a `P#` or `L#` -
325
- a blocking verdict with "None" in both groups is a rendering bug. Concretely:
326
- failed gate -> `P#` `[test]` referencing the gate command; contradicted or
327
- merge-proof-unverifiable material claim -> `P#` `[spec]` referencing the claim;
328
- scope creep with a linked issue -> `L#` `spec-conflict`; committed doc drift ->
329
- `L#` `doc-drift`; `partial` AC coverage -> `L#` `outdated-AC`; `missing` AC
330
- coverage -> `L#` `missing-behavior`. `L#` covers exactly the drift the Verdict
331
- already blocks on (committed doc drift, AC coverage, spec conflict); it widens
332
- nothing. Code-level spec bugs (the diff contradicts the spec) are `P#` `[spec]`;
333
- requirement/doc mismatches (the spec or docs are stale relative to intent) are
334
- `L#`.
335
- - **Failing checks close by disposition, not by fix:** an undispositioned failing check in the resolved set
336
- is `P#` `[test]` referencing the check name; it is never a target
337
- of a worktree `fix`. The user's Phase-4 disposition annotates the same ID rather
338
- than closing it outright: dispositioned **flaky** -> annotate
339
- `(dispositioned: flaky)`; this annotation excepts the `P#` from the unfixed-blocker
340
- set - it no longer counts against "every `P#` blocks" or the merge precondition
341
- "every blocking finding fixed", and the merge path is Phase 4's explicit flaky
342
- disposition via the custom row. Dispositioned **real** -> annotate
343
- `(dispositioned: real)` and the `P#` keeps blocking until the check is green.
344
- Dispositioned **CI-infrastructure-broken** -> annotate
345
- `(dispositioned: ci-infrastructure-broken)`; the fallback local run executes,
346
- and the `P#` keeps blocking until that fallback is green.
347
- - **Severity is decided at triage, not by the category tag:** a finding lands in
348
- `P#` only when it must be fixed before merge (correctness, security, material
349
- performance trap, a convention the repo enforces); improvements that don't
350
- change merge correctness are `F#`, whatever their category. `[quality]` on a
351
- `P#` is a category, never a downgrade - every `P#` blocks. This triage bar governs
352
- findings the orchestrator originates itself; it never re-triages a classification
353
- Phase 4 already made (see Precedence above).
354
- - `C#` replies are verdict-neutral drafts: they never block and never gate merge;
355
- nothing posts until selected.
356
- - `F#` items carry an owner so follow-ups don't evaporate; when no tracker tool
357
- resolved, the report itself is their durable home.
358
- - **IDs are append-only for the run's lifetime:** minted at first assessment,
359
- never renumbered, never reused. A resolved finding keeps its ID annotated
360
- `(fixed in <sha>)`; later rounds continue each namespace's sequence.
361
- - Empty groups say "None".
362
-
363
- "Drafted fixes / review" holds, per finding ID, the concrete edit (for `fix`), the
364
- reviewed doc-drift edit (for `push-docs`, keyed to its `L#`), or the reply text
365
- (for `reply`) - each keyed to its finding ID, one selection mapping 1:1 to its
366
- payload. A posted review body is not itself a finding: it is composed at post time
367
- from the ID'd `P#`/`L#` findings being addressed - one summary sentence, then the
368
- numbered findings, ending on the fix or asked action - and occupies its own
369
- non-finding slot of this section.
370
-
371
- ## Decision rendering
372
-
373
- `## Decision` has two parts: the action vocabulary, then the numbered courses.
374
-
375
- **Action vocabulary** (bare verbs; availability constraints inline):
376
-
377
- ```markdown
378
- Actions (compose freely in the custom row):
379
- fix <P#s|all> apply blocking fixes in worktree, re-run gate, push (in-repo PRs only)
380
- push-docs push already-applied doc-drift edits (only when uncommitted
381
- reviewed doc edits exist
382
- in the worktree)
383
- merge-squash | merge-commit (preconditions per Verdict;
384
- never bundled with a push,
385
- except the telemetry: restore commit)
386
- request-changes | review-comment | approve (approve: never own PR)
387
- reply <C#s> post drafted thread replies
388
- tracker <act> tracker action (only when a tracker tool resolved)
389
- stop leave the PR as-is / report-only exit
390
- ```
391
-
392
- A `+ tracker <act>` suffix is available on any mutation course when a tracker tool
393
- resolved.
394
-
395
- **Selection grammar:** ID sets accept `all`, ranges (`P1-P4`), comma lists
396
- (`P1,P3`), and exclusions (`all but P2`).
397
-
398
- **Numbered courses** - a normative rendering of the consent table (never a second
399
- offer source): per author x state cell, exactly one `[recommended]` course first,
400
- the custom row always last. Courses are **atomic across pushes**: no course,
401
- pre-composed or custom, bundles a push-producing action (`fix`, `push-docs`) with
402
- `merge-*`; after a fix wave the menu re-renders with merge as row 1.
403
-
404
- | Author | State | Courses (first = `[recommended]`) |
405
- |---|---|---|
406
- | you | clean / follow-ups only | 1. merge-squash; 2. merge-commit; 3. stop; 4. review-comment (post no-blockers note) |
407
- | you | blocking | 1. fix (worktree-fixable P#s only - `all` covers only those) [+ push-docs when uncommitted doc edits exist]; 2. push-docs (alone, when doc edits exist); 3. stop; 4. review-comment (post findings). When no P# is worktree-fixable (blocking is failing-check-only or L#-only), course 1 (fix) is not rendered: push-docs becomes first when doc edits exist, else stop is first |
408
- | you | blocking, post-fix re-render (gate green, preconditions hold) | 1. merge-squash; 2. merge-commit; 3. stop; 4. review-comment |
409
- | someone else | clean / follow-ups only | 1. approve; 2. merge-squash (offered-unrecommended); 3. review-comment (no-blockers note) |
410
- | someone else | blocking | 1. request-changes; 2. fix all (courtesy, their branch - omitted when nothing is worktree-fixable); 3. reply <C#s> (omitted when the `C#` group is None); 4. review-comment |
411
- | bot author | any | someone-else's rows for the same state; review actions recommended |
412
- | any | draft | 1. request-changes / review-comment / reply <C#s> (omit the reply course when the `C#` group is None) / stop - `[recommended]` follows the same authorship rule as the non-draft cells, **except** on your own draft PR `request-changes` is never recommended (you cannot request changes on your own PR any more than you can approve it); the fallback recommendation there is `review-comment` when findings exist, else `stop`. Custom present but cannot compose `merge-*`/`approve`/`fix`/`push-docs` until ready-for-review |
413
- | any | merged / closed | 1. stop; report-only, no other mutation courses at all; Custom present but cannot compose `merge-*`/`approve`/`fix`/`push-docs`/`request-changes`/`review-comment`/`reply`/`tracker` - nothing remains actionable |
414
-
415
- **Fork overlay:** the consent-table fork row renders as an overlay on the authorship
416
- cells (push/merge/fix absent; approve also dropped when the viewer authored the PR) -
417
- it is not a distinct authorship cell. It overlays whichever authorship row above
418
- applies (you vs. someone else), removing `fix`, `push-docs`, and `merge-*` (never
419
- available on a fork). When you authored the fork PR, `approve` is
420
- also dropped (never offered on your own PR) - fork|you|clean renders
421
- `review-comment`/`stop` only; fork|you|blocking renders
422
- `request-changes`/`review-comment`/`stop` (the someone-else courtesy fix-on-their-
423
- branch course is also absent, since it is your own PR). A fork PR authored by someone
424
- else uses the someone-else cells above with `fix`/`push-docs`/`merge-*` removed.
425
-
426
- **CI-check gate on merge courses:** an undispositioned failing check in the resolved
427
- set, or a pending **required** check, withholds every pre-composed course containing `merge-*` (per the
428
- Verdict merge preconditions) - none render, whatever the author/state cell says. A
429
- pending check mints no `P#` and is wait-until-green, not dispositionable (see Phase
430
- 4); a failing one mints a `P#` and takes a disposition (flaky / real / CI-infrastructure-broken). A **flaky** disposition does
431
- not restore merge to a pre-composed course; merge proceeds only via the custom row
432
- naming the disposition explicitly. A **real** disposition, or an unresolved pending
433
- check, keeps every merge course withheld until the check is green; a **CI-infrastructure-broken** disposition keeps them withheld until the triggered fallback run is green - a pending-only
434
- render is not itself a blocking verdict (findings groups may all read "None"); the
435
- recommended course falls to `stop` or `review-comment` in the meantime. This never
436
- falls through to the clean cell's recommended `merge-squash` - a failing resolved-set
437
- check or a pending required check means the PR is not in the clean state to begin with.
438
-
439
- Rows a cell offers but GitHub would refuse (branch protection, missing permission)
440
- render listed-but-unavailable with the reason. Zero mutation courses is a legal
441
- render (merged/closed) - the menu still appears, carrying findings and `stop`.
442
-
443
- Example render (golden fixture 1 - own PR, blocking findings including committed doc
444
- drift, so uncommitted reviewed doc edits exist):
158
+ Consent table and courses per `reference/decision-menu.md`.
445
159
 
446
- ```markdown
447
- Pick one:
448
- 1. fix all (P1-P10) + push-docs [recommended]
449
- 2. push-docs (docs only, hold code fixes)
450
- 3. stop (leave as-is)
451
- 4. review-comment (post findings, act later)
452
- 5. Custom - compose: e.g. "fix P1-P8,P10 + push-docs" or "reply C1 + tracker comment"
453
- ```
160
+ ## Decide
454
161
 
455
- Golden fixture 2 - the post-fix re-render after course 1's gate re-run passes:
162
+ Render `## Decision` from the consent table for the PR's author and state. Read
163
+ `reference/decision-menu.md` now.
456
164
 
457
- ```markdown
458
- Pick one:
459
- 1. merge-squash [recommended]
460
- 2. merge-commit
461
- 3. stop (leave as-is)
462
- 4. review-comment
463
- 5. Custom
464
- ```
165
+ ## Act
465
166
 
466
- ## Post-selection loop
467
-
468
- The menu is a state machine, not a one-shot report:
469
-
470
- 1. **Compare-and-swap before every external write:** re-fetch `headRefOid`, `state`,
471
- `mergeable`. Any change since assessment invalidates the current state - re-sync
472
- the worktree, re-run Phase 3, re-render the menu. **Exception:** a course's own
473
- push updates the assessed head to the pushed SHA as part of that course's
474
- execution - this self-inflicted head move does not invalidate the course; the
475
- next CAS check runs against the new head on the next external write.
476
- 2. Execute only the selected course. **Fix wave** (`fix <set>`): first filter the
477
- selected set to worktree-fixable `P#`s - drop any `P#` closed by disposition
478
- (an undispositioned failing-check `P#` is never a `fix` target; a **flaky**
479
- disposition already excepts it) - and route file-less `P#`s (a claim or a gate
480
- command as `source_ref`, no draft touching a file) to run inline/sequentially,
481
- never as part of a parallel file-batch.
482
-
483
- For the remaining worktree-fixable set, batch by the **union of files each
484
- finding's drafted edit in `## Drafted fixes / review` touches** (fallback to
485
- the `source_ref` file only when a finding has no draft) - findings whose
486
- drafts share a file share a batch.
487
-
488
- **Child contract (same for 1 batch or many):** children are `implementer`
489
- dispatches - `subagent({ agent: "implementer", context: "fresh", cwd: <PR
490
- worktree> })`, matching Inline-first execution's persona-naming style; this is
491
- the pi-cohort optimization path, inline is always valid per that section. The
492
- orchestrator owns commit, gate, and push - never a child. Every dispatched
493
- child gets `cwd` = the PR worktree, **`worktree: true` forbidden** (a separate
494
- isolated worktree breaks the shared-tree contract - see Inline-first
495
- execution); is **edit-only, no git commands, no verification runs**, and must
496
- never delete `.pi/gauntlet/telemetry/**` or anything under the configured
497
- telemetry dir (a fix that "cleans up" the run's telemetry record is a defect in
498
- the fix - the record is a deliverable); and its task is that batch's `P#` lines
499
- **plus the drafted edit already keyed to each
500
- ID** in `## Drafted fixes / review` - the child applies the consented payload,
501
- it does not re-solve the finding. Below the cutoff (<= 2 worktree-fixable findings), the orchestrator
502
- applies inline instead of dispatching - the no-cohort path stays available at
503
- any batch count per Inline-first execution. Above the cutoff, when more than
504
- one batch results, dispatch the batches' children **in parallel**, all under
505
- the same contract.
506
-
507
- Once every dispatched/inline batch returns, the orchestrator commits the golden
508
- course as one local commit set - the code fixes plus any already-applied
509
- reviewed doc edits selected alongside them (one commit, or one per batch
510
- sequentially; subjects name the fixes) - then re-resolves the evidence for the new head **once** (the brief's stale-head row: prior evidence is stale; the local command executes only on a fallback/opt-out resolution). Before the push, run
511
- `node <bin>/gauntlet-telemetry-salvage.mjs --worktree <provisioned path> --base
512
- origin/<baseRefName>` (no `--check`); a `restored` or `(marked shipped)` commit rides the wave's single
513
- push and the pushed SHA becomes the assessed head under the course's-own-push rule
514
- in step 1. Print its stdout in the re-rendered report's `## Evidence`. **On
515
- green**, push **once**; gate and push are per-wave invariants, never per-fix or
516
- per-batch. **On red**, do not push: leave the commit(s) local, re-render with
517
- the unresolved `P#`s still open, and warn that unpushed fix commits sit in the
518
- worktree exactly like unpushed doc edits (see Teardown). Doc fixes (`push-docs`
519
- alone) follow the same green-gate-then-push rule: stage + commit (subject names
520
- what is documented), re-run the gate, push only on green. Reviews, replies, and
521
- tracker actions -> `gh pr review` / `gh api` / the tracker tool, non-interactive,
522
- with the drafted payload for the selected IDs.
523
- 3. After any mutation that can change readiness (fix wave pushed, docs pushed, PR
524
- head moved), re-run the claim-check and Review on the synced worktree: claims
525
- are re-checked against the new head and findings are re-rendered, but
526
- the verification command itself is **not** re-executed here - step 2's evidence re-resolution
527
- already was the wave's one and only gate pass. Re-render the report:
528
- each selected `P#`/`L#` confirmed resolved is annotated `(fixed in <sha>)`
529
- under its original ID; unresolved ones stay open unchanged; new findings
530
- continue the sequence. Merge, if now available, renders as row 1.
531
- 4. Loop until the user selects merge or an explicit stop/no-action row.
532
-
533
- **Teardown:** merge success -> tear down the worktree, whether it was reused or
534
- created (the sync precondition guarantees no local-only work is stranded, and the
535
- branch is gone remotely). A non-merge stop: offer teardown of a **created** worktree
536
- (never autonomous; warn if unpushed doc edits would be discarded); a **reused**
537
- worktree is left as found - if unpushed doc edits **or unpushed fix commits from a
538
- red-gate hold** remain in it, say so explicitly and let the user choose
539
- leave-or-discard.
167
+ The menu is a state machine that loops until merge or an explicit stop. Read
168
+ `reference/post-selection-loop.md` now.
540
169
 
541
170
  ## Output done-check
542
171
 
@@ -556,33 +185,21 @@ overrides file - see Project overrides.
556
185
 
557
186
  ## Red flags - STOP
558
187
 
559
- - Approving your own PR
560
- - Any mutation (fix, push, review, merge) without an explicit menu selection
561
- - Pasting paraphrased evidence instead of verbatim `raw_tail`
562
- - A provenance mismatch (worktree, `run_cwd`, or `head_sha`) noticed and ignored
563
- - Merging around an undispositioned blocking finding or failing-check `P#`
564
- - Reading configuration (rubric, verification command, or ladder sources) from the
565
- PR's head instead of the base branch's merge-base
566
- - Renumbering or reusing a finding ID between menu rounds
567
- - Presenting findings without IDs, a blocking verdict with no `P#`/`L#`, or a
568
- `## Decision` rendered without its action vocabulary
569
- - Treating `[quality]` or `[performance]` as a downgrade signal on a `P#` - only
570
- an explicit Phase-4 flaky disposition excepts a failing-check `P#` from the
571
- unfixed-blocker set, never a category tag
572
- - A course (pre-composed or custom) bundling a push-producing action with
573
- `merge-*`
574
- - A pre-composed course, or a custom row, composing an action the overlay or the
575
- cell lists as unavailable
576
- - Batching a file-less `P#` (a claim or a gate command as `source_ref`, no draft touching a file)
577
- into a parallel dispatch - a claim `P#` with a drafted file edit is
578
- worktree-fixable and may batch - dispatching parallel implementers over
579
- batches that share a file, or letting a fix-wave child run git commands or a
580
- verification pass in the shared worktree, or dispatching a fix-wave child with
581
- `worktree: true`
582
- - A second execution of the verification command, a second push, or pushing fix
583
- commits after a red gate, within one fix wave - re-running Verify/Review to
584
- re-confirm claims and annotate IDs (step 3) is not a second gate execution
585
- - Posting or committing an external payload without the output done-check
188
+ - Approving your own PR - owner: `reference/decision-menu.md` `## Consent table`
189
+ - Any mutation (fix, push, review, merge) without an explicit menu selection - owner: **Consent gate** (intro)
190
+ - Pasting paraphrased evidence instead of verbatim `raw_tail` - owner: `reference/assessment.md` `## Phase 4 - Integrate`
191
+ - A provenance mismatch (worktree, `run_cwd`, or `head_sha`) noticed and ignored - owner: `reference/assessment.md` `## Phase 4 - Integrate`
192
+ - Merging around an undispositioned blocking finding or failing-check `P#` - owner: `reference/findings.md` `## Dispositions`
193
+ - Reading configuration (rubric, verification command, or ladder sources) from the PR's head instead of the base branch's merge-base - owner: `## Configuration resolution`
194
+ - Renumbering or reusing a finding ID between menu rounds - owner: `reference/findings.md` `## IDs`
195
+ - Presenting findings without IDs, or a blocking verdict with no `P#`/`L#` - owner: `reference/findings.md` `## IDs`
196
+ - A `## Decision` rendered without its action vocabulary - owner: `reference/decision-menu.md` `## Actions`
197
+ - Treating `[quality]` or `[performance]` as a downgrade signal on a `P#` - only an explicit Phase-4 flaky disposition excepts a failing-check `P#` from the unfixed-blocker set, never a category tag - owner: `reference/findings.md` `## Triage`
198
+ - A course (pre-composed or custom) bundling a push-producing action with `merge-*` - owner: `reference/decision-menu.md` `## Courses`
199
+ - A pre-composed course, or a custom row, composing an action the overlay or the cell lists as unavailable - owner: `reference/decision-menu.md` `## Fork overlay`
200
+ - Batching a file-less `P#` (a claim or a gate command as `source_ref`, no draft touching a file) into a parallel dispatch - a claim `P#` with a drafted file edit is worktree-fixable and batches - dispatching parallel implementers over batches that share a file, or letting a fix-wave child run git commands or a verification pass in the shared worktree, or dispatching a fix-wave child with `worktree: true` - owner: `reference/post-selection-loop.md` `### Fix wave`
201
+ - A second execution of the verification command, a second push, or pushing fix commits after a red gate, within one fix wave - re-running Verify/Review to re-confirm claims and annotate IDs is not a second gate execution - owner: `reference/post-selection-loop.md` `### Fix wave`
202
+ - Posting or committing an external payload without the output done-check - owner: `## Output done-check`
586
203
 
587
204
  ## Project overrides
588
205
 
@@ -0,0 +1,129 @@
1
+ # gatekeep-pr: assessment
2
+
3
+ Read from SKILL.md `## Assess`. Four phases run in order; Phases 1-3 are read-only.
4
+
5
+ ## Phase 1 - Gather
6
+
7
+ Run `../verification-brief.md` Section A in full: the fixed `gh` command set
8
+ (`gh pr view`, `gh api user`, `gh pr diff`, both paginated comment endpoints,
9
+ review threads, issue fetch, `git worktree list --porcelain` for discovery
10
+ only). It produces the normative gather digest.
11
+
12
+ ## Phase 2 - Provision worktree
13
+
14
+ The orchestrator's mutation - a state machine.
15
+
16
+ | State | In-repo PR | Fork PR | On divergence |
17
+ |---|---|---|---|
18
+ | A worktree exists on the expected branch (`headRefName` for in-repo PRs, a fork-local `pr-<N>` branch for fork PRs), at any path | Reuse unconditionally; sync with `git fetch origin` + `git pull --ff-only` (the local branch tracks `origin/<headRefName>`) | Reuse unconditionally; the local `pr-<N>` branch has no upstream - sync with `git fetch origin pull/<N>/head` + `git merge --ff-only FETCH_HEAD` | STOP and surface on divergence, dirt, or local-only commits; never force, never create a duplicate |
19
+ | The default path `.worktrees/pr-<N>` exists but holds a different branch | STOP and surface; never repurpose | STOP and surface; never repurpose | - |
20
+ | Nothing exists (an overrides worktree wrapper can relocate it, following `using-git-worktrees` conventions, gitignore-first) | Create at `.worktrees/pr-<N>`; `git fetch origin` + `git worktree add .worktrees/pr-<N> <headRefName>`; verify post-checkout that HEAD == the digest's `headRefOid` | Create at `.worktrees/pr-<N>`; `git fetch origin pull/<N>/head:pr-<N>` first, then add on that local branch; verify post-checkout that HEAD == the digest's `headRefOid` | - |
21
+
22
+ Record create-vs-reuse; it drives the non-merge teardown rule in `post-selection-loop.md`
23
+ `### Teardown`.
24
+
25
+ After provisioning, re-poll `mergeable` once (`gh pr view --json mergeable`) when
26
+ Section A reported `UNKNOWN`. Still `UNKNOWN` after this single re-poll is treated as
27
+ not merge-ready and surfaced (merge preconditions per SKILL.md `## Verdict`).
28
+
29
+ ## Phase 3 - Verify, then review
30
+
31
+ Sequential, same worktree - deliberate: the verification command can write to the tree
32
+ while the Reviewer reads it.
33
+
34
+ Run `../verification-brief.md` Section B: resolve the verification evidence per its
35
+ Evidence resolution table (green exact-head CI is the default evidence). Run the
36
+ resolved verification command only when the table selects a fallback or opt-out row,
37
+ under its safety contract: self-contained and non-interactive (no prompts, under a
38
+ non-interactive environment), bounded by a timeout (default 15 minutes, `timeout
39
+ minutes` override) via the first available mechanism - the harness's own bash timeout
40
+ parameter, else the `timeout`/`gtimeout` CLI when installed, else a background-and-kill
41
+ fallback. Then check material claims against the PR body.
42
+
43
+ After a local run, assert tracked-only cleanliness: `git status --porcelain
44
+ --untracked-files=no` empty, equivalently `git diff --quiet && git diff --cached
45
+ --quiet`; HEAD unmoved. Untracked gate artifacts, including the Verifier's `log_path`,
46
+ are expected and do not fail this check as long as `log_path` sits under a gitignored
47
+ path inside the worktree. Any tracked change invalidates the run: re-provision and
48
+ re-run once.
49
+
50
+ Run `../verification-brief.md` Section C: review the source behind the diff against
51
+ the merged rubric - shipped `../review-baseline.md` overlaid by base-branch
52
+ `REVIEW.md` - and triage existing comments. The Reviewer emits its native output
53
+ format only; AC coverage is not part of its contract.
54
+
55
+ ## Phase 4 - Integrate
56
+
57
+ Orchestrator step.
58
+
59
+ - **Provenance:** `worktree_root` matches the provisioned path, every `run_cwd` sits
60
+ inside it, `head_sha` matches the digest's `headRefOid`. On mismatch, re-fetch the
61
+ PR head once and re-sync + re-run Phase 3 when it advanced; a second mismatch, or
62
+ any path mismatch, counts as missing evidence - not merge-ready. The claim stated
63
+ in output names its source. Local path: precisely "reproduced locally under the
64
+ project's documented verification command" - nothing stronger; never worded to
65
+ imply a deployed, staging, or CI environment. CI path (`source: ci`): precisely
66
+ `verified by CI: <check name(s)> succeeded on <sha> (run <url>)` - never phrased as
67
+ local reproduction, never implying the local command ran; `<sha>` is the assessed
68
+ `headRefOid`, `<url>` degrades to `unavailable` when absent. Provenance checks on
69
+ `worktree_root`/`run_cwd` bind only to the local path.
70
+ - **Telemetry record:** run
71
+ `node <bin>/gauntlet-telemetry-salvage.mjs --worktree <provisioned path> --base
72
+ origin/<baseRefName> --check`. The `--check` flag makes the probe detect-only: it
73
+ never mutates. `present` / `no telemetry run` / `never written` land in
74
+ `## Evidence` as one line each. `stripped <path> in <sha>` mints a blocking `P#`
75
+ (`source_ref`: `gauntlet-telemetry-salvage`) whose drafted fix is "run the salvage
76
+ without `--check`" - the spec and its telemetry record are deliverables that ship
77
+ in the squash. On a cell with no push row (fork overlay, report-only states) the
78
+ same finding is a non-blocking follow-up instead: the record stays recoverable
79
+ from the PR head ref after merge, and blocking would stop a ship the gate cannot
80
+ repair. `unfinished <path>` (record still `in_progress` with no ship phase) also
81
+ lands in `## Evidence` as one line and is non-blocking: pre-landing `in_progress`
82
+ is normal, and the merge course's salvage run stamps it.
83
+ - **Evidence:** on the CI path, list each satisfying check's name, conclusion,
84
+ assessed SHA, and run URL - there is no command or `raw_tail` to paste. On the
85
+ local path, paste each run's `command` and `raw_tail` verbatim, fenced - never
86
+ paraphrased. Any authored summary is labeled as a summary and never substitutes for
87
+ `raw_tail`.
88
+ - **Severity translation:** Critical -> blocking, Moderate -> blocking, Minor ->
89
+ non-blocking follow-up. A repo `REVIEW.md` severity mapping overrides this; any
90
+ severity it names but does not map is fail-safe **blocking**, noted in the output.
91
+ - **AC coverage:** the orchestrator computes `met` / `partial` / `missing` per
92
+ acceptance criterion from the issue's ACs, the diff, and the Reviewer's findings -
93
+ an integration product, not raw persona output. Only `met` is merge-ready;
94
+ `partial` or `missing` is blocking. Skip entirely when no issue is linked.
95
+ - **Claims:** a failed local gate is a hard merge failure. A `contradicted` material
96
+ claim is a blocking finding. An `unverifiable-pre-merge` claim used as merge proof
97
+ (appears in the PR body's evidence/result/test-plan content) is blocking; stated as
98
+ an explicit post-merge observation instead, it is a non-blocking follow-up.
99
+ - **CI checks:** disposition a failing or pending check per `findings.md`
100
+ `## Dispositions`.
101
+ - **Doc drift:** when the review finds committed doc drift as a **blocking** finding,
102
+ apply the doc fixes in the provisioned worktree (created or reused), as part of
103
+ assessment - real edits, uncommitted, worktree-local. Present the result in
104
+ `## Findings`, the edits themselves under `## Drafted fixes / review`. Pushing them
105
+ is a separate, later menu selection. Follow-ups alone never trigger doc fixes; only
106
+ blocking drift does.
107
+
108
+ ## Inline-first execution
109
+
110
+ > This section is an optional optimization. Delete it and the rest of the skill still
111
+ > works: the orchestrator can run every phase in this file itself, inline, with no
112
+ > subagent system.
113
+
114
+ The inline path is primary: the orchestrator runs the brief's sections itself, in
115
+ order, self-contained. When pi-cohort is available, delegation is an optimization
116
+ layered on top, never a hard dependency.
117
+
118
+ | Phase role | Persona | Detail |
119
+ |---|---|---|
120
+ | Gatherer | `scout` builtin | dispatched as a prior sync run producing the gather digest |
121
+ | Verifier | `worker` builtin | dispatched with the report-only constraint prepended to its task ("report only - do not edit, fix, or commit anything") |
122
+ | Reviewer | the existing `code-reviewer` agent | emits its native output format (never overridden at call time) |
123
+
124
+ Verifier and Reviewer share the provisioned worktree via `cwd`, dispatched
125
+ **sequentially** (Verify before Review, per `## Phase 3 - Verify, then review`) - never `worktree: true`,
126
+ which would provision a separate isolated worktree and break the shared-tree contract
127
+ this skill depends on. A subagent that fails, or violates its section's output
128
+ schema, is re-dispatched once demanding the schema; a second failure means that
129
+ section runs inline instead.
@@ -0,0 +1,127 @@
1
+ # gatekeep-pr: decision menu
2
+
3
+ Read from SKILL.md `## Decide`. `## Decision` in the report has two parts: the
4
+ action vocabulary, then the numbered courses.
5
+
6
+ ## Actions
7
+
8
+ ```markdown
9
+ Actions (compose freely in the custom row):
10
+ fix <P#s|all> apply blocking fixes in worktree, re-run gate, push (in-repo PRs only)
11
+ push-docs push already-applied doc-drift edits (only when uncommitted
12
+ reviewed doc edits exist
13
+ in the worktree)
14
+ merge-squash | merge-commit (preconditions per Verdict;
15
+ never bundled with a push,
16
+ except the telemetry: restore commit)
17
+ request-changes | review-comment | approve (approve: never own PR)
18
+ reply <C#s> post drafted thread replies
19
+ tracker <act> tracker action (only when a tracker tool resolved)
20
+ stop leave the PR as-is / report-only exit
21
+ ```
22
+
23
+ A `+ tracker <act>` suffix is available on any mutation course when a tracker tool
24
+ resolved.
25
+
26
+ Selection grammar: ID sets accept `all`, ranges (`P1-P4`), comma lists
27
+ (`P1,P3`), and exclusions (`all but P2`).
28
+
29
+ ## Consent table
30
+
31
+ Deterministic - this table is the golden-scenario oracle.
32
+
33
+ | Author | State | Offered rows (first = `[recommended]`) |
34
+ |---|---|---|
35
+ | you | clean / follow-ups only | merge (squash); merge (merge-commit); do not merge (leave it); post no-blockers comment |
36
+ | you | blocking | apply code fixes (named finding subset): skill edits in worktree, commits, re-runs gate, pushes - then merge re-offered; push applied doc fixes; do not act; post review-comment of findings |
37
+ | someone else | clean / follow-ups only | approve; merge (squash, offered-unrecommended); post no-blockers comment |
38
+ | someone else | blocking | post request-changes review; apply fixes on their branch (courtesy option 2); reply to existing threads; post comment |
39
+ | bot author | any | someone-else's rows for the same state, review actions recommended |
40
+ | fork (any) | any | post review (request-changes / comment / approve per state) - push and merge rows absent |
41
+ | any | draft PR | assessment rows only; merge and approve rows absent until ready-for-review |
42
+ | any | merged / closed | report-only; no mutation rows |
43
+
44
+ This table is the single oracle for what is offered; `## Courses` renders its
45
+ rows as actions and numbered courses. Rows GitHub would refuse (branch protection,
46
+ missing permissions, `viewerPermission` too low) render listed-but-unavailable with
47
+ the reason. Approving your own PR is never offered. Nothing executes until explicit
48
+ selection.
49
+
50
+ ## Courses
51
+
52
+ A normative rendering of the consent table (never a second offer source): per
53
+ author x state cell, exactly one `[recommended]` course renders first, the custom
54
+ row renders last. Courses are atomic across pushes: no course, pre-composed or
55
+ custom, bundles a push-producing action (`fix`, `push-docs`) with `merge-*`; after
56
+ a fix wave the menu re-renders with merge as row 1.
57
+
58
+ | Author | State | Courses (first = `[recommended]`) |
59
+ |---|---|---|
60
+ | you | clean / follow-ups only | 1. merge-squash; 2. merge-commit; 3. stop; 4. review-comment (post no-blockers note) |
61
+ | you | blocking | 1. fix (worktree-fixable P#s only - `all` covers only those) [+ push-docs when uncommitted doc edits exist]; 2. push-docs (alone, when doc edits exist); 3. stop; 4. review-comment (post findings). When no P# is worktree-fixable (blocking is failing-check-only or L#-only), course 1 (fix) does not render: push-docs becomes first when doc edits exist, else stop is first |
62
+ | you | blocking, post-fix re-render (gate green, preconditions hold) | 1. merge-squash; 2. merge-commit; 3. stop; 4. review-comment |
63
+ | someone else | clean / follow-ups only | 1. approve; 2. merge-squash (offered-unrecommended); 3. review-comment (no-blockers note) |
64
+ | someone else | blocking | 1. request-changes; 2. fix all (courtesy, their branch - omitted when nothing is worktree-fixable); 3. reply <C#s> (omitted when the `C#` group is None); 4. review-comment |
65
+ | bot author | any | someone-else's rows for the same state; review actions recommended |
66
+ | any | draft | 1. request-changes / review-comment / reply <C#s> (omit the reply course when the `C#` group is None) / stop - `[recommended]` follows the same authorship rule as the non-draft cells, except on your own draft PR `request-changes` is never recommended (you cannot request changes on your own PR any more than you can approve it); the fallback recommendation there is `review-comment` when findings exist, else `stop`. Custom present but cannot compose `merge-*`/`approve`/`fix`/`push-docs` until ready-for-review |
67
+ | any | merged / closed | 1. stop; report-only, no other mutation courses at all; Custom present but cannot compose `merge-*`/`approve`/`fix`/`push-docs`/`request-changes`/`review-comment`/`reply`/`tracker` - nothing remains actionable |
68
+
69
+ ## Fork overlay
70
+
71
+ The consent-table fork row renders as an overlay on the authorship cells
72
+ (push/merge/fix absent; approve also dropped when the viewer authored the PR) - it
73
+ is not a distinct authorship cell. It overlays the applicable authorship cell (you
74
+ or someone else), removing `fix`, `push-docs`, and `merge-*` (never available on a
75
+ fork). When you authored the fork PR, `approve` is also dropped (never offered on
76
+ your own PR) - fork|you|clean renders `review-comment`/`stop` only; fork|you|blocking
77
+ renders `request-changes`/`review-comment`/`stop` (the someone-else courtesy
78
+ fix-on-their-branch course is also absent, since it is your own PR). A fork PR
79
+ authored by someone else uses the someone-else cells with `fix`/`push-docs`/
80
+ `merge-*` removed.
81
+
82
+ ## CI-check gate
83
+
84
+ An undispositioned failing check in the resolved set, or a pending required check,
85
+ withholds every pre-composed course containing `merge-*` (merge preconditions per
86
+ SKILL.md `## Verdict`) - none render, whatever the author/state cell says.
87
+ Disposition and pending-check definitions per `findings.md` `## Dispositions`.
88
+
89
+ | Disposition | Merge courses |
90
+ |---|---|
91
+ | Flaky | Not restored to a pre-composed course; merge proceeds only via the custom row naming the disposition explicitly |
92
+ | Real, or an unresolved pending check | Withheld until the check is green |
93
+ | CI-infrastructure-broken | Withheld until the triggered fallback run is green |
94
+
95
+ A pending-only render is not itself a blocking verdict (findings groups can all
96
+ read "None"); the recommended course falls to `stop` or `review-comment` in the
97
+ meantime. This never falls through to the clean cell's recommended `merge-squash` -
98
+ a failing resolved-set check or a pending required check means the PR is not in the
99
+ clean state to begin with.
100
+
101
+ ## Fixtures
102
+
103
+ Refused rows render per `## Consent table`. Zero mutation courses is a legal
104
+ render (merged/closed) - the menu still appears, carrying findings and `stop`.
105
+
106
+ Example render (golden fixture 1 - own PR, blocking findings including committed
107
+ doc drift, so uncommitted reviewed doc edits exist):
108
+
109
+ ```markdown
110
+ Pick one:
111
+ 1. fix all (P1-P10) + push-docs [recommended]
112
+ 2. push-docs (docs only, hold code fixes)
113
+ 3. stop (leave as-is)
114
+ 4. review-comment (post findings, act later)
115
+ 5. Custom - compose: e.g. "fix P1-P8,P10 + push-docs" or "reply C1 + tracker comment"
116
+ ```
117
+
118
+ Golden fixture 2 - the post-fix re-render after course 1's gate re-run passes:
119
+
120
+ ```markdown
121
+ Pick one:
122
+ 1. merge-squash [recommended]
123
+ 2. merge-commit
124
+ 3. stop (leave as-is)
125
+ 4. review-comment
126
+ 5. Custom
127
+ ```
@@ -0,0 +1,93 @@
1
+ # gatekeep-pr: findings
2
+
3
+ Read from SKILL.md `## Assess`. Phase 4 mints the IDs the report and menu carry.
4
+
5
+ ## IDs
6
+
7
+ `<source_ref>` is a `file:line` where one exists, else the disputed thing (a
8
+ quoted PR-body claim, a failing gate command, a required check name).
9
+
10
+ Precedence: Phase 4 and the merged rubric decide blocking vs. follow-up (the
11
+ severity translation, AC coverage, claims, and required-check rules). This
12
+ section only chooses which namespace (`P#` / `L#` / `F#`) renders that
13
+ decision. Category tags and the triage bar never override an upstream
14
+ blocking classification - a Phase-4 Moderate is always blocking (`P#` or `L#`
15
+ per Total mapping), never demoted to `F#` by tag or by judgment call.
16
+
17
+ Total mapping: every blocking element of the Verdict maps to a `P#` or `L#` -
18
+ a blocking verdict with "None" in both groups is a rendering bug. Map: failed
19
+ gate -> `P#` `[test]` referencing the gate command; contradicted or
20
+ merge-proof-unverifiable material claim -> `P#` `[spec]` referencing the
21
+ claim; scope creep with a linked issue -> `L#` `spec-conflict`; committed doc
22
+ drift -> `L#` `doc-drift`; `partial` AC coverage -> `L#` `outdated-AC`;
23
+ `missing` AC coverage -> `L#` `missing-behavior`. `L#` covers exactly the
24
+ drift the Verdict already blocks on (committed doc drift, AC coverage, spec
25
+ conflict); it widens nothing.
26
+
27
+ `P#` vs `L#` boundary: code-level spec bugs (the diff contradicts the spec)
28
+ are `P#` `[spec]`; requirement/doc mismatches (the spec or docs are stale
29
+ relative to intent) are `L#`.
30
+
31
+ `C#` replies are verdict-neutral drafts: they never block and never gate
32
+ merge; nothing posts until selected.
33
+
34
+ `F#` items carry an owner (pr-author | tracker | human) so follow-ups don't
35
+ evaporate; when no tracker tool resolved, the report itself is their durable
36
+ home.
37
+
38
+ IDs are append-only for the run's lifetime: minted at first assessment, never
39
+ renumbered, never reused. A resolved finding keeps its ID annotated
40
+ `(fixed in <sha>)`; later rounds continue each namespace's sequence.
41
+
42
+ Empty groups say "None".
43
+
44
+ ## Triage
45
+
46
+ A finding lands in `P#` only when it must be fixed before merge (correctness,
47
+ security, material performance trap, a convention the repo enforces);
48
+ improvements that don't change merge correctness are `F#`, whatever their
49
+ category. `[quality]` and `[performance]` on a `P#` are categories, never a
50
+ downgrade - every `P#` blocks. This triage bar governs findings the
51
+ orchestrator originates itself; it never re-triages a classification Phase 4
52
+ already made (see Precedence in `## IDs`).
53
+
54
+ ## Dispositions
55
+
56
+ Any blocking conclusion in the resolved check set (required or not - per
57
+ `../verification-brief.md` Section B, Evidence resolution table) withholds
58
+ merge from every pre-composed course until the user explicitly dispositions
59
+ it, and mints a `P#`.
60
+
61
+ An undispositioned failing check in the resolved set is `P#` `[test]`
62
+ referencing the check name; it is never a target of a worktree `fix`. Close
63
+ failing checks by disposition, not by fix: the user's Phase-4 disposition
64
+ annotates the same ID rather than closing it outright.
65
+
66
+ | Disposition | Annotation on the `P#` | Counts against unfixed-blocker set | Merge course | Fallback local run |
67
+ |---|---|---|---|---|
68
+ | undispositioned failing check | none yet | yes | withheld from every pre-composed course | none |
69
+ | flaky | `(dispositioned: flaky)` | excepted - no longer counts against "every `P#` blocks" or "every blocking finding fixed" | only the custom row naming the disposition explicitly; no pre-composed course restores | none |
70
+ | real | `(dispositioned: real)` | still counts - `P#` keeps blocking | withheld until the check is green | none |
71
+ | CI-infrastructure-broken | `(dispositioned: ci-infrastructure-broken)` | still counts - `P#` keeps blocking; the checks themselves are untrustworthy | withheld until the fallback run is green | triggers the fallback local run, and merge stays withheld until that fallback produces green evidence |
72
+ | pending required check | mints no `P#`, is never dispositioned | not applicable - not dispositionable | withheld; auto-lifts the moment it turns green, or converts to an undispositioned failing check with its own `P#` on failure | none |
73
+
74
+ A pending required check is wait-until-green, not dispositionable. While
75
+ pending, the report notes it under Evidence.
76
+
77
+ The evidence decision is independent of the merge decision: a green check
78
+ elsewhere in the resolved set still satisfies verification evidence while a
79
+ pending required check withholds merge. The CI-sufficient path changes no
80
+ consent surface: still read-only, no auto-merge, no posting, no menu change
81
+ beyond the third disposition.
82
+
83
+ ## Payloads
84
+
85
+ "Drafted fixes / review" holds, per finding ID, the concrete edit (for
86
+ `fix`), the reviewed doc-drift edit (for `push-docs`, keyed to its `L#`), or
87
+ the reply text (for `reply`) - each keyed to its finding ID, one selection
88
+ mapping 1:1 to its payload.
89
+
90
+ A posted review body is not itself a finding: compose it at post time from
91
+ the ID'd `P#`/`L#` findings being addressed - one summary sentence, then the
92
+ numbered findings, ending on the fix or asked action - and give it its own
93
+ non-finding slot of this section.
@@ -0,0 +1,38 @@
1
+ # gatekeep-pr: post-selection loop
2
+
3
+ ## Post-selection loop
4
+
5
+ Read from SKILL.md `## Act`. Treat the menu as a state machine: execute only the selected course, re-render, and loop until the user selects merge or an explicit stop/no-action row.
6
+
7
+ ### Compare-and-swap
8
+
9
+ Before every external write, re-fetch `headRefOid`, `state`, and `mergeable`. Any change since assessment invalidates the current state - re-sync the worktree, re-run Phase 3 per `assessment.md` `## Phase 3 - Verify, then review`, and re-render the menu. Exception: a course's own push updates the assessed head to the pushed SHA as part of that course's execution - this self-inflicted head move does not invalidate the course; the next compare-and-swap check runs against the new head on the next external write.
10
+
11
+ ### Fix wave
12
+
13
+ For a `fix <set>` selection, filter the selected set to worktree-fixable `P#`s first: drop any `P#` closed by disposition (per `findings.md` `## Dispositions`). Route file-less `P#`s (a claim or a gate command as `source_ref`, no draft touching a file) to run inline and sequentially, never as part of a parallel batch. A claim `P#` with a drafted file edit is worktree-fixable and batches.
14
+
15
+ Batch the remaining worktree-fixable set by the union of files each finding's drafted edit in `## Drafted fixes / review` touches - fall back to the `source_ref` file only when a finding has no draft. Findings whose drafts share a file share a batch.
16
+
17
+ Use one child contract for every batch, whether there is one or many: dispatch `implementer` children with `subagent({ agent: "implementer", context: "fresh", cwd: <PR worktree> })`. The orchestrator owns commit, gate, and push - never a child. Every dispatched child gets `cwd` = the PR worktree; `worktree: true` is forbidden per `assessment.md` `## Inline-first execution`. Each child is edit-only - no git commands, no verification runs - and must never delete `.pi/gauntlet/telemetry/**` or anything under the configured telemetry dir (a fix that "cleans up" the run's telemetry record is a defect in the fix, not a cleanup). Each child's task is that batch's `P#` lines plus the drafted edit already keyed to each ID in `## Drafted fixes / review` - the child applies the consented payload, it does not re-solve the finding.
18
+
19
+ At a cutoff of 2 worktree-fixable findings or fewer, the orchestrator applies the fix inline instead of dispatching - the no-cohort path stays available at any batch count per `assessment.md` `## Inline-first execution`. Past that cutoff, when more than one batch results, dispatch the batches' children in parallel, all under the same contract.
20
+
21
+ Once every dispatched or inline batch returns, commit the golden course as one local commit set - the code fixes plus any already-applied reviewed doc edits selected alongside them (one commit, or one per batch sequentially; subjects name the fixes). Re-resolve the evidence for the new head once (the brief's stale-head row: prior evidence is stale; the local command executes only on a fallback/opt-out resolution). Before the push, run `node <bin>/gauntlet-telemetry-salvage.mjs --worktree <provisioned path> --base origin/<baseRefName>` (no `--check`); a `restored` or `(marked shipped)` commit rides the wave's single push and the pushed SHA becomes the assessed head under the course's-own-push rule in `### Compare-and-swap`. Print its stdout in the re-rendered report's `## Evidence`. On green, push once - gate and push are per-wave invariants, never per-fix or per-batch. On red, do not push: leave the commit(s) local, re-render with the unresolved `P#`s still open, and warn that unpushed fix commits sit in the worktree exactly like unpushed doc edits (per `### Teardown`).
22
+
23
+ Doc fixes (`push-docs` alone) follow the same rule: stage and commit (subject names what is documented), re-run the gate, and push only on green. Execute reviews, replies, and tracker actions via `gh pr review` / `gh api` / the tracker tool, non-interactively, with the drafted payload for the selected IDs.
24
+
25
+ ### Merge course
26
+
27
+ Merge always executes as `gh pr merge --match-head-commit <assessed-sha>`. Push and merge are never bundled into one selection, with one scoped exception: the selected merge course first runs the salvage (no `--check`) via `node <bin>/gauntlet-telemetry-salvage.mjs --worktree <provisioned path> --base origin/<baseRefName>`. `present` -> merge as-is. `restored <path> from <sha>` -> push that single `telemetry: restore` commit as part of this course, re-fetch `headRefOid`, and pass the new SHA to `--match-head-commit`. A line ending `(marked shipped)` (`present` or `restored`) is handled the same way: push that single `telemetry:` commit, re-fetch `headRefOid`, and pass the new SHA. `restore failed` -> merge proceeds, the reason is printed, and the follow-up names recovery from the PR head ref. A merge selection while any precondition (SKILL.md `## Verdict`) fails is refused, naming the failing precondition, and the menu re-renders - never a dead end, never a silent merge.
28
+
29
+ ### Re-render
30
+
31
+ After any mutation that can change readiness (fix wave pushed, docs pushed, PR head moved), re-run the claim-check and Review on the synced worktree: claims are re-checked against the new head and findings are re-rendered, but do not re-execute the verification command here - the fix wave's evidence re-resolution already was the wave's one gate pass. Annotate each selected `P#`/`L#` confirmed resolved as `(fixed in <sha>)` under its original ID; unresolved ones stay open unchanged; new findings continue the sequence. Merge, if now available, renders as row 1.
32
+
33
+ ### Teardown
34
+
35
+ | | Created worktree | Reused worktree |
36
+ |---|---|---|
37
+ | Merge success | tear down | tear down (the sync precondition guarantees no local-only work is stranded, and the branch is gone remotely) |
38
+ | Non-merge stop | offer teardown, never autonomous; warn if unpushed doc edits would be discarded | leave as found; say so explicitly when unpushed doc edits or red-gate fix commits remain, and let the user choose leave-or-discard |
@@ -55,7 +55,7 @@ invent ACs.
55
55
 
56
56
  `mergeable` is reported as-is, including `UNKNOWN` - the Gatherer runs before
57
57
  provisioning, so it never re-polls; the orchestrator re-polls once after
58
- provisioning the worktree (see SKILL.md Phase 2) and treats a still-`UNKNOWN`
58
+ provisioning the worktree (per `reference/assessment.md` `## Phase 2 - Provision worktree`) and treats a still-`UNKNOWN`
59
59
  result as not merge-ready. Bot author noted
60
60
  (`author_is_bot`). Capture each status check's `isRequired` where exposed (digest field: `required`).
61
61
 
@@ -81,7 +81,7 @@ conclusion, with `ERROR` blocking and `PENDING` pending. Missing `required` is
81
81
  treated as non-required. `ci checks:` matches check name, workflow name, or
82
82
  status context, trimmed, case-insensitive. What checks mean for verification evidence is owned by
83
83
  Section B's Evidence resolution table; what they mean for merge is owned by the
84
- orchestrator's required-check rule (SKILL.md Phase 4) - two independent
84
+ orchestrator's required-check rule (`reference/findings.md` `## Dispositions`) - two independent
85
85
  consumers of the same data.
86
86
 
87
87
  ## Section B - Verifier
@@ -34,7 +34,7 @@ loop: tracker state is extension state no subagent can read.
34
34
  - `--out` and `--key` both given -> STOP: they are exclusive in cohort's grammar.
35
35
  - `--out <path>` -> resolve to an absolute path against the session cwd
36
36
  (`node -p "require('path').resolve(process.argv[1])" -- "<path>"`); that is the
37
- expected path and the form passed to cohort.
37
+ form passed to cohort.
38
38
  - Otherwise the key is `--key <name>` when given, else derived from the **run
39
39
  worktree**: the path a flow skill reported as `Worktree ready at <path>` in this
40
40
  transcript (the same evidence cohort's snapshot uses):
@@ -54,19 +54,22 @@ loop: tracker state is extension state no subagent can read.
54
54
  `--out` (a default-branch key would collide across flows).
55
55
  - A derived key has every `/` replaced by `-` (`hotfix/x` -> `hotfix-x`) so the file
56
56
  lands flat in the default directory. A human-given `--key` is passed verbatim.
57
- - Expected path = `<tmpdir>/pi-handoff/<key>.md`, `<tmpdir>` from
58
- `node -p "require('os').tmpdir()"`. This is the one place this package restates
59
- cohort's default-path rule; step 3 verifies it.
60
- 2. **Invoke cohort.** `/skill:handoff --out <absolute path>` or `/skill:handoff --key <key>`,
61
- exactly as resolved in step 1. The skill must be present in the session skill list
62
- under `name: handoff`. Absent -> STOP: "cohort `handoff` skill not in the session
63
- skill list - install or upgrade pi-cohort to the release that ships pi-cohort #18".
64
- No version lookup, no fallback to the `/handoff` prompt.
65
- 3. **Verify the file.** Expected path missing, or line 1 not starting `# Handoff:` ->
66
- STOP with the expected path.
57
+ - With `--key`, cohort writes `<tmpdir>/pi-handoff/<key>.md`, `<tmpdir>` from
58
+ `node -p "require('os').tmpdir()"`; the authoritative path is the one cohort
59
+ reports in step 2.
60
+ 2. **Run cohort's handoff procedure.** A skill cannot expand `/skill:handoff` (pi expands
61
+ skill commands on typed input only). Find `handoff` in the session skill list, `Read`
62
+ its `SKILL.md` at the location listed there, and follow its procedure with the output
63
+ option resolved in step 1 - the brief's core (six headings, ending at `## Skills loaded`)
64
+ is cohort's to write, by cohort's rules. Absent from the skill list -> STOP: "cohort
65
+ `handoff` skill not in the session skill list - install or upgrade to pi-cohort >= 7.1.0".
66
+ No fallback to the `/handoff` prompt.
67
+ 3. **Take the path from the report.** Cohort's procedure ends with `Handoff written: <abs
68
+ path>` or `Handoff not written: <reason>`. `Handoff not written` -> STOP with that reason.
69
+ Otherwise that path is the brief; line 1 not starting `# Handoff:` -> STOP with the path.
67
70
  4. **Refuse a double section.** The file already has a line matching
68
71
  `^## Process state\s*$` -> STOP: "the installed pi-cohort still writes process state
69
- itself - upgrade to the release that ships #18".
72
+ itself - upgrade to pi-cohort >= 7.1.0".
70
73
  5. **Hotfix exclusion.** `skills/chase-bug/hotfix.md` is in this session's context ->
71
74
  append nothing; report a plain hotfix handoff and go to step 7. Checked before the
72
75
  trackers: chase-bug never touches tracker state, and a stale armed flow appended here
@@ -78,7 +81,7 @@ loop: tracker state is extension state no subagent can read.
78
81
  `reference/brief-contract.md` lays it out: both status outputs verbatim
79
82
  (`No plan active.` verbatim when there is no plan), the active-task line naming
80
83
  the first `→` task else `none`, the gate-history line. Append with a single
81
- `cat >> "<expected path>" <<'EOF' ... EOF` whose body is that block.
84
+ `cat >> "<brief path>" <<'EOF' ... EOF` whose body is that block.
82
85
  - Re-read the file tail and confirm it ends with the gate-history line from the
83
86
  contract and nothing after.
84
87
  7. **Report.** Print the path and `/skill:gauntlet-resume <path>`. When a section was
@@ -91,8 +94,9 @@ loop: tracker state is extension state no subagent can read.
91
94
 
92
95
  ## Red flags - STOP
93
96
 
94
- - Writing the brief yourself instead of invoking `/skill:handoff` (the core headings are
95
- cohort's; this skill appends one section).
97
+ - Writing the brief's core yourself instead of following cohort's `handoff` procedure (the
98
+ six core headings are cohort's; this skill appends one section).
99
+ - Recomputing the brief path instead of taking it from `Handoff written:`.
96
100
  - Restating the section layout here instead of reading `reference/brief-contract.md`.
97
101
  - Appending when step 4 or step 5 fired.
98
102
  - Deriving the key from the primary checkout's branch while a run worktree exists.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Consumed by `gauntlet-resume/SKILL.md` (consumer) and `gauntlet-handoff/SKILL.md`
4
4
  (producer). Grammar lives only here; both skills cite this file and inline none of it
5
- (`scripts/ci.mjs` drift lint). Coupled to pi-cohort's `handoff` skill (pi-cohort #18)
5
+ (`scripts/ci.mjs` drift lint). Coupled to pi-cohort's `handoff` skill (pi-cohort >= 7.1.0, #18)
6
6
  for exactly six headings - `# Handoff:`, `## Intent`, `## Repo state`, `## Decisions`,
7
7
  `## Open questions`, `## Skills loaded` - and the `## Repo state` fields. `## Process
8
8
  state` grammar and its consumer rules are owned here. Cohort drift is reconciled here