Run the full per-PR verification loop (adherence + review + review-pr + simplify), then gate through /verify-gate. Bounces the PR for at most one retry. Does not merge — the merge decision belongs to the caller.
Installs into .claude/skills of the current project.
Are you the author of Gaze?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/minhhaduong-gaze)
---
name: gaze
description: "Run the full per-PR verification loop (adherence + review + review-pr + simplify), then gate through /verify-gate. Bounces the PR for at most one retry. Does not merge — the merge decision belongs to the caller."
disable-model-invocation: false
user-invocable: true
argument-hint: "<pr-number>"
context: fork
# Background by intent: /gaze is long-running, and a raid wave gates several PRs
# at once — backgrounding the orchestrator is what lets those run concurrently.
# This matches the Claude Code 2.1.218 default; pinned explicitly so a future
# default flip cannot serialize a wave silently. Its own sub-skills and reviewer
# agents are waited for by polling (see "Fork execution contract" below) — the
# two are different axes: parallelism comes from concurrent tool calls inside one
# message, never from backgrounding a phase this skill must wait on.
background: true
---
# Gaze — verify PR $ARGUMENTS, six-phase loop with anti-rubber-stamp gate
For helper commands, set `IDH_ROOT="$(cd -P "$(dirname "<loaded-SKILL.md>")/../.." && pwd -P)"` in the same shell call. Replace `<loaded-SKILL.md>` with the absolute path the runtime supplied for this skill. This follows a projected skill symlink to the canonical checkout; do not derive the helper root from the project cwd.
> **TASK DIRECTIVE — execute now.** You are running `/gaze` on PR `$ARGUMENTS`.
> This file is your operating procedure, not reference documentation: begin at
> phase 1 immediately. If `$ARGUMENTS` does not contain a PR number, STOP and
> return ESCALATE with reason "no PR argument" — do NOT infer a task from the
> environment (worktree name, git status snapshot, ticket files, or the shared
> task list).
**Caller responsibility (before this fork is ever spawned).** The rule above
binds the fork, which starts with no cwd and no conversation (ticket 0193 —
a bare fork once guessed its target from ambient state and pushed a rogue
PR). It does not mean the user must supply a bare number by hand every time.
The invoking session has a cwd and a conversation — when the user types
`/gaze` with no argument, resolve the PR number *before* invoking this skill:
query the forge for the PR associated with the current branch, or reuse a PR
number already established earlier in the conversation. Invoke with that
number filled in. Only ask the user for it when no such source exists or two
candidates conflict.
<!-- harness-extension-point: on GitHub, `gh pr view --json number` resolves
the current branch's PR number. -->
One skill, one PR, one decision: APPROVED / REROLL / ESCALATE. **Does not
merge** — the merge decision belongs to the caller (the human or the raid).
Only one live `/gaze` may run on a PR at a time; the caller keeps to that
(`rules/workflow.md` § Delegation). A past verdict is evidence, not authority
to overwrite or call it fabricated: inspect its gate identity and ruled tip.
## When to use
- Raid Phase 6 (per-ticket verification before merge).
- Any time an author wants a full-depth check on a single PR before asking for merge.
- **Do NOT** use for quick sanity — use `/review` or `/review-pr` directly.
## Invariants
- Runs on exactly one PR.
- Two gate rounds maximum. Third round is forbidden — escalate instead.
- Does not merge. The verdict is structured output; the merge decision belongs to the caller.
- The fix loop between rounds makes commits on the PR branch; no changes to other branches.
- `--force-approve` is supported for explicit human override; it is logged loudly in the
PR comments and the skill transcript.
- **Cross-repo prerequisite**: the caller must ensure cwd is the target project before
invoking `/gaze`. The skill and its sub-skills (`/verify-gate`, `/simplify`, etc.)
use `gh` and `git` commands that resolve against cwd.
- **Branch-currency prerequisite**: the caller rebases the PR branch onto current
`origin/main` and force-pushes with lease before invoking (`rules/git.md`
§ Merging) — a gate validates the *combination* branch ⊕ base, so a verdict
computed on a stale base is partially void. `/gaze` does not rebase the
branch itself; that is a caller-side step, not a phase here.
- **Quiet-branch prerequisite**: never start `/gaze` while that branch's coding
lead is still live. The gate reviews a head the lead may move under it, and
the gate's fix loop would then commit on top of work in flight.
## Fork execution contract
`/gaze` runs as a `context: fork` (see frontmatter). A fork's turn ends the
instant it stops calling tools, and **delegated subagents always run in the
background and notify the session, not this fork.** There is no foreground
launch parameter to pass; earlier revisions of this contract named one, and a
lever that does not exist cannot be the thing holding a phase together.
What is true is the first sentence: the fork survives only while it keeps
calling tools. So every agent this skill spawns — the phase 2–4 reviewers, the
phase 6 gate, the REROLL fix agent — is launched in the background and then
**waited for by polling an artifact it writes**, in one bounded loop. The wait
itself is the tool call that keeps the fork alive.
Each spawned agent is therefore told, in its prompt, to write its result to a
named path under `<worktree>/.panel/<pr-number>/` — writing to `<name>.md.part`
and renaming to `<name>.md`, so a half-written file never carries the final
name — and the roster of expected names is written down *before* launching. A
waiter that just gathers whatever files appear cannot tell an agent that
finished silently from one that never started; comparing against a roster
written in advance can.
Without this, the failure is silent and total: the fork ends its turn at the
launch, the completions re-invoke the **MAIN loop**, and the fork's last message
is a fan-out narration ("reviewers are running in parallel…") instead of a
verdict, with phases 5–6 never running. This orphaned two real gate runs, each forcing the caller to relaunch a
duplicate reviewer battery, and did it again on two consecutive `/review-pr`
rounds where ten reviewers returned real
verdicts and the merge request carried none of them.
**Bound every wait, and record what did not arrive.** An agent missing at the
deadline is missing, not clear: name it in the verdict and treat its phase as
unresolved. Never extend a deadline to avoid recording a gap.
**The contract applies recursively.** Any nested fan-out performed on `/gaze`'s
behalf — a reviewer Agent (e.g. Agent C) that itself spins a panel of
perspective agents — inherits this same rule: the inner launch must be waited for by
polling an artifact too, or the orphan failure simply moves one layer down. A launch
site is bound by the fork contract whether it is this skill's own or a
sub-agent's.
**Caller-side recovery.** If `/gaze <pr>` ever returns a bare fan-out
narration with no `## /verify-gate verdict` block (APPROVED/REROLL/ESCALATE),
treat it as a non-result: do **not** relaunch the reviewer battery. Wait for
the background reviewer notifications, then run `/verify-gate <pr>
worktree=$primary_root/.claude/worktrees/review-<pr-number>` directly to produce the verdict from their outputs.
**Fork liveness.** Once the phase 2–4 review comment has posted on the PR, the
caller must see the phase-6 verdict (returned or posted as a PR comment), or
the orchestrator's posed `reroll_bump` line on the PR branch, within
`fork_liveness_seconds` (`skills/gaze/telemetry.yml`, env override
`GAZE_LIVENESS_WINDOW_S`; default 1200s / ~20 min) — same knob pattern as the
wall/token thresholds in § Telemetry. Two independent stalls landed in exactly
this window, both silent: 2026-07-11 (memory
`feedback_agent_stall_watchdog_recovery`) and 2026-07-13 (raid 291-245, PR
#551, where the review comment posted, then ~30 min of quiet: no simplify
commit, no verdict, review worktree mtime frozen).
**On window expiry, do not re-run phases 2–5.** Check three completion markers:
(1) a posted verdict comment, (2) branch-tip motion on the PR branch, (3) the
review worktree's file mtime. All stale/absent → invoke `/verify-gate`
directly, with the same invocation form as **Caller-side recovery** above
(one recipe, stated once), and continue the normal round
flow from its verdict. On REROLL, the caller poses the returned `reroll_bump`
using the rule in phase 6: commit it onto the PR branch with the fix, or defer
to merge time; never run `erg log` against main mid-wave. The returned verdict
itself is liveness evidence even when posing is deferred. The fallback skips only
the redundant phase 2–5 re-execution — verify-gate runs at full rigor and the
two-round cap is unaffected.
## Phases
### 1. Setup
Always invoke `/gaze` from within a conversation worktree, never from the main repo
root. The worktree is the isolation boundary; no main-repo checkout is ever needed.
**Isolation setup:**
```bash
# Step 1 — Resolve PR number to branch name (forge-specific step)
PR_BRANCH=<resolved-branch-name>
# Step 2 — Resolve the primary repo root, then create the worktree under its
# `.claude/worktrees/` namespace (ticket 0300), where the runtime's worktree
# isolation covers it like every other worktree; never under /tmp.
primary_root=$(git rev-parse --show-toplevel)
primary_root="${primary_root%%/.claude/worktrees/*}" # strip if we run from a session worktree
review_tree="$primary_root/.claude/worktrees/review-<pr-number>"
# A fresh id for this run; the phase 6 gate names it in its verdict comment.
gate_session_id=$(python3 -c 'import uuid; print(uuid.uuid4())')
git fetch origin "$PR_BRANCH" || exit 1
if ! git worktree add "$review_tree" origin/"$PR_BRANCH" || ! test -d "$review_tree"; then
echo "gaze: cannot create isolated review worktree for PR <pr-number>; refusing to fall back into the invoking tree" >&2
exit 1
fi
# The reviewer agents (review worktree via git -C) and the REROLL fix agent run inside
# $primary_root/.claude/worktrees/review-<pr-number>; the main repo is never
# switched, never dirtied. (Exception: phase 5 /simplify is still a direct
# invocation and runs from the fork's own cwd, not review-<pr> — see its note below.)
```
## Review scratch cleanup
On every exit path (APPROVED, REROLL-escalated, ESCALATE, circuit-breaker
abort), after Agent C and the gate have consumed their artifacts, remove this
run's review scratch before removing the worktree.
The embedded `/review-pr` Agent C leaves the panel for this step. Do not run
this while perspective agents are still writing: cancel or wait for them first.
If the review worktree was never created, there is nothing to clean up. The
exit preflight must pass after scratch removal: if it reports other WIP, leave
the review worktree registered and surface those file names as ESCALATE to the
caller. Never force-remove a tree with real uncommitted files. A failed
preflight or normal worktree removal is a cleanup failure, not a successful
terminal verdict.
```bash
review_tree="$primary_root/.claude/worktrees/review-<pr-number>"
if [ -d "$review_tree" ]; then
# Read verdicts before deleting panel files. Citation or ancestry uncertainty
# keeps both the candidate .tmp and its review evidence in place.
if [ -e "$review_tree/.tmp" ] || [ -L "$review_tree/.tmp" ]; then
python3 "$IDH_ROOT/scripts/worktree-tmp-guard.py" removal \
"$primary_root" "$review_tree" --branch "$PR_BRANCH" || exit 1
fi
tracked_panel=$(git -C "$review_tree" ls-files -- ".panel/<pr-number>") || exit 2
tracked_base=$(git -C "$review_tree" ls-files -- build/panel-head) || exit 2
[ -n "$tracked_panel" ] || rm -rf -- "$review_tree/.panel/<pr-number>"
if [ ! -L "$review_tree/build" ] && [ -z "$tracked_base" ]; then
rm -rf -- "$review_tree/build/panel-head"
fi
rmdir -- "$review_tree/.panel" 2>/dev/null || true
# Detached review trees without provable branch ancestry retain .tmp.
if ! "$IDH_ROOT/scripts/worktree-exit-preflight.sh" --removal --branch "$PR_BRANCH" "$review_tree"; then
echo "/gaze cleanup blocked: real WIP remains in $review_tree" >&2
exit 1
fi
git -C "$primary_root" worktree remove "$review_tree" || exit 1
fi
```
- Abort if not mergeable or if there are open merge conflicts.
- Collect:
- The ticket file(s) referenced by PR-body close claims (`tickets/*.erg`);
include any title/body reference when no close claim exists. No close
claim and no title/body reference → proceed with `ticket: none`; the gate
reviews against the PR body (`/verify-gate` § Circuit breakers).
- PR body, full diff, all existing review comments, all inline comments, all commit
messages on the branch.
- Check CI status for the merge request if the forge exposes it. If the forge CLI or API is unavailable, skip gracefully — CI status is informational only. If checks are configured and any are failing, note this in the setup summary; do not block on it (reviewer decides).
- First run `python3 "$IDH_ROOT/scripts/review-pr-anchor.py" <pr-number> --worktree "$primary_root/.claude/worktrees/review-<pr-number>"`. A nonzero `REVIEW-ANCHOR:` result stops the battery: do not classify an empty or wrong-tree diff as tiny or approve it. Carry its HEAD and changed-file roster into every review prompt.
- Compute PR size rename-aware: `pr_lines` = total insertions + deletions from `git diff origin/main...HEAD --stat`; `pr_files` = the CONTENT-BEARING entry count from `git diff --name-status -M origin/main...HEAD` — added, modified, and deleted each count 1; cross-reference rename entries with `git diff --numstat -M origin/main...HEAD`: a rename counts 1 iff its additions + deletions > 0, otherwise 0 (a genuinely content-free pure rename has zero line change in rename-aware numstat; R100 alone is insufficient because the similarity index ignores line order) — pure renames add no review load, and counting them via `--no-renames` inflates consolidation diffs past the breaker on arithmetic (ticket 1026, observed on PRs #1178/#1179). Classify the battery **tier**:
Before classifying, check the PR label, PR body, and linked ticket body for
`review:standard`. An explicit request selects the **full** tier even when
the size thresholds below would select tiny or small; it also directs Agent C
to run all five perspectives.
- **tiny** — `pr_lines ≤ 20` and `pr_files ≤ 2` and this is round 1.
- **small** — `pr_lines ≤ 150` and `pr_files ≤ 5` and this is round 1 (and not already tiny).
- **full** — everything else, and any round ≥ 2. The **tiny** and **small** tiers are round-1 classifications only; a round ≥ 2 is never tiny or small.
The tier selects which reviewer agents run (see §§ 2–4, 5); phase 6 (`/verify-gate`) is **invariant** — it runs at every tier, never reduced. Per-tier battery:
- **tiny** → Agent A + Agent C + phase 6 gate. Skip Agent B (`/review`) and phase 5 (`/simplify`); Agent C still supplies a review seat.
- **small** → Agent A + Agent B + Agent C + phase 5 (`/simplify`) + phase 6 gate.
- **full**, round 1 → the complete battery below, unchanged; an explicit
`review:standard` request runs all five Agent C perspectives.
- **full**, round ≥ 2 (i.e. the merge request already carries a prior Agent C review) → Agent A + Agent B + phase 5 (`/simplify`) + phase 6 gate; Agent C is scoped per § Round scoping below.
Carry the resolved `tier` into the telemetry footer and the output-shape template (see § Telemetry, § Output shape).
Size and risk are two axes, never collapsed
into one. The 15+ files un-reviewable breaker pre-empts the band — its
ESCALATE fires before any battery spawns, so a band never sets a battery
tier on an un-reviewable PR. Classify the changed-file
roster with a third, cwd-anchored call — it reads no disk (pure
path-pattern match), so the missing-path refusal does not apply, and
deleted or renamed paths still get a band:
`cd $primary_root/.claude/worktrees/review-<pr-number> && python3 "$IDH_ROOT/scripts/prose_predicate.py" --risk <changed files>` — it prints
the path-risk **band**: `high` if any file matches a pipeline or guard path
(`scripts/**`, `skills/**`, `rules/**`, `hooks/**`, `settings*.json`, and
the rest of the `panel-width.json` registry); `low` only if every file
matches `docs/**`, `*.bib`, or `**/fixtures/**`; `normal` otherwise.
Combine band and tier on the round-1 size classification only — a round ≥ 2
stays **full**, and an explicit `review:standard` pre-empts both:
- band `high` → raise one rung: tiny → small, small → full — clamped at
`full`.
- band `low` → lower one rung: full → small, small → tiny — floored at
`tiny`; the battery never drops below Agent A + Agent C + the phase-6
gate, at every tier and every band.
- band `normal` → unchanged.
A tiny → small raise arms Agent B and phase 5 (`/simplify`); the
internal battery follows the tier. Optional external review follows
§ External reviewer panel. What this closes is the battery
tier, not panel width: the 0392 width gate already reads `pipeline_paths`.
- **Preflight before spawning phases 2–5.** Count distinct ticket close-claims
from the PR **body**, using the same line grammar as `erg-pr-merge`: bold or
bare `Ticket: tickets/NNNN...` lines count; the title, `Ticket-ref:` lines,
`Ticket: none`, and repeated claims for one ID do not. For example, extract
IDs with `grep -oiP '^\*{0,2}ticket:?\*{0,2}:?\s*tickets/\K\d+' | sort -u`.
If the count is two or more, record `multi_ticket: <IDs>` as a **non-blocking**
finding for phase 6 to disposition. Below the size breaker, continue the normal review;
genuinely bundled tickets are permitted by `rules/git.md` § Merging.
- If `pr_files >= 15`, the PR exceeds the **15+ content-bearing files** monster threshold
borrowed from `rules/workflow.md` § Ticket discipline for multi-PR work. Return ESCALATE
immediately, before spawning any phase 2–5 reviewer battery or phase 6 gate,
with `circuit_breaker: un-reviewable` and reason
`un-reviewable: <pr_files> content-bearing files / <pr_lines> lines — split before review`.
When `multi_ticket` is present, add that the combined tickets are a
decomposition signal. Post the normal two-section verdict comment with the
breaker and `gate: skipped (un-reviewable)` visible. In that comment's
`/verify-gate verdict` section, mark the direct setup ESCALATE and its
unrun criterion checks explicitly; do not imply phase 6 produced a verdict.
Cleanup and containment
still run. An explicit `--force-approve <reason>` overrides this breaker
loudly under the existing override contract; record the waived breaker and
size in its PR comment.
- ESCALATE with a clear message, and do not proceed, only if a close claim names a nonexistent ticket file or the PR body, diff or comments cannot be located. A missing ticket with no claim and no reference is not an escalation.
#### Round scoping
**Supersedes the #562 clause.** A round ≥ 2 still resolves to the **full**
tier, but its Agent C no longer re-runs the whole five-perspective panel by
default. Agent C re-runs the perspectives that objected in the previous round
plus one regression check over the cleared ones, per § Round scoping in
`skills/review-pr/SKILL.md` (ticket 0377). #562's lesson was that a round-2
battery was needed on an unchanged diff. That lesson is preserved by keeping
Agents A and B, phase 5, and the phase 6 gate at full strength every round.
What it does *not* justify is re-paying for perspectives that had nothing to
say.
**Reachable trigger.** The only entry into this branch is a *caller-level*
repeat `/gaze` on a merge request that already carries a prior Agent C
review: that invocation re-enters phase 1, reclassifies the tier, and finds a
review history. gaze's internal REROLL re-entry never reaches here — it
re-runs the **gate only** (§ Branch on verdict) and never returns to phase 1,
so round scoping neither widens nor narrows it. Two different counters share
the word "round", and they never interact: the REROLL round-1/round-2 cap
counts gaze's own internal fix-and-regate attempts *within* one invocation,
while the round scoped here counts the Agent C reviews posted on the merge
request *across* invocations.
**gaze does not compute the round.** It reads only whether a prior Agent C
review exists — a boolean from the merge request's review history, enough to
select the scoped branch — and passes no round number to Agent C. Agent C's
embedded procedure derives the round itself, exactly as § Round scoping in
`skills/review-pr/SKILL.md` prescribes: count the reviews this skill
previously posted on the merge request, identifiable by the verdict roster
those reviews always carry; the round is that count plus one.
An unchanged diff does **not** reset scoping. Reset happens only on a
substantial rewrite: the round's diff touches files the last review did not
cover, or changes more than roughly half of its lines. Then Agent C runs the
full panel for this round — scoping is ignored. The derived round number
itself is never mutated; only the scoping decision changes.
### 2–4. Read-only review fan-out (parallel)
These phases run as **Agent-spawned sub-agents, not `context: fork`
invocations** (ticket 0216). A fork does not inherit this skill's cwd or
conversation, so it lands in the session worktree on whatever branch is
checked out there — that is how a drifted fork pushed a stray branch and
opened rogue PR #243 (ticket 0193). Spawning an Agent fixes this
deterministically: each reviewer is a **read-only** Agent that
operates on the existing review worktree
`$primary_root/.claude/worktrees/review-<pr-number>`
(created in phase 1) via `git -C` — never assume the spawned agent's
own cwd is the review tree; how a runtime sets a spawned agent's cwd
is not portable, `git -C` is (ticket 0853). Waiting on a written artifact, not on a return value, is
load-bearing, not incidental — see **Fork execution contract** below: this
skill runs as a `context: fork`, and a fork cannot wait on background
agents.
If the battery cannot be launched at all — the runtime refuses the
spawn, the agent connector is absent, or the spawn-depth limit is hit — first
attempt the detached-seat substitution documented in
`skills/review-pr/SKILL.md`; if that also cannot run, emit a one-line
`battery: NOT-RUN — cause: <guard|no-agent-tool|depth>` in the verdict
comment. The boundary with the seat-level signal: no seat launched at all is
a battery NOT-RUN; seats launched but reports missing stays seat-level
`PANEL-INTEGRITY:` (whose name is unchanged). The NOT-RUN line names a launch
failure, not a review finding: never fabricate findings — the gate reports
the criteria unverified, and the verdict is capped at ESCALATE, never
APPROVED. When the phase-6 gate itself cannot launch — under the same
causes: the runtime refuses the spawn, the agent connector is
absent, or the spawn-depth limit is hit — the gaze orchestrator emits the
capped verdict directly with `gate: skipped (gate launch failure — see
battery NOT-RUN)` and the criteria reported unverified — never fabricated.
Every reviewer agent's prompt:
- opens with `TASK DIRECTIVE — execute now`, naming the single sub-skill
procedure it runs and the PR;
- forbids commits, pushes, new branches, new PRs, and any write to
`tickets/*.erg` (read-only role);
- runs every git read as
`git -C "$primary_root/.claude/worktrees/review-<pr-number>" ...` —
it never assumes its own cwd is the review tree (portable across
runtimes);
- bans working-tree overwrites outright: no `git checkout <ref> -- `,
no `git restore .`, no `git reset --hard`; inspect another version
with `git show <ref>:<path>` (rules/git.md);
- refuses and reports NOT-RUN if the review worktree is unreachable, or
its HEAD differs from the anchor HEAD carried in the prompt — it never
falls back to the session cwd;
- imperatively embeds the sub-skill's operating procedure (the steps below),
except Agent A, which invokes the live adherence contract as specified below;
- ends by **returning a single structured block as its final message**, which
the orchestrator parses to branch.
Spawn the applicable agents **in a single message, as parallel background
Agent calls**, each writing its result to the phase artifact path — the single message runs them
concurrently, and the bounded poll on those artifacts is what makes this fork wait
before it proceeds. Keep polling until every artifact lands: a fork's
turn ends the moment it stops calling tools, and a background completion
re-invokes the MAIN loop, not the fork, so a fork that stops polling returns at
launch and orphans its reviewers (ticket 0250; see **Fork execution
contract**). Once all return, collect their structured outputs. Choose the model of each launched worker per launch, from the `route` skill grid
(`skills/route/SKILL.md`); seats that must be independent follow
`skills/route/references/decorrelation.md` ("The launch line"): scale to the risk; a high-risk PR needs one
cross-family detached seat, else `no report` and `PANEL-INTEGRITY: DEGRADED`.
Seat defaults are guidance, not pins; raise a seat when the change warrants it:
| Seat | Default worker class | When to raise |
|---|---|---|
| adherence, scope, docs-propagation, consistency | cheap cluster | a rule is subtle or the diff is large |
| correctness, gate | mid-size model | high-risk or cross-cutting change |
| red team | mid-size model | high-risk change: another family (`decorrelation.md`) |
**Agent A — adherence** (`/verify-adherence <branch> worktree=<absolute-review-worktree>`).
**Label-skip:** if the PR carries the
`verify:adherence-passed` label (set by `/hunt`'s pre-PR gate, see PR #40),
do **not** spawn this agent — the adherence check already ran clean before the
PR was opened. Otherwise the launch is **the adherence-seat profile**
(`agents/adherence-seat.md`), whose contract is
`profiles/adherence-seat/PROFILE.md`: spawn it on the review worktree
`$primary_root/.claude/worktrees/review-<pr-number>`, the branch and the
worktree path riding in the launch prompt — the seat's rails (read-only,
`git -C` discipline, artifact protocol) are the contract's and are not
restated in this launch block. Its FIRST action is to invoke the live contract:
<!-- harness-extension-point: runtime skill-loader invocation. -->
```
Skill(skill: "verify-adherence", args: "<branch> worktree=$primary_root/.claude/worktrees/review-<pr-number>")
```
Substitute the resolved branch and absolute review-worktree path in the args.
Pass through `trace=<path>` when the caller supplied it. Do not embed or
reimplement the verification steps here: the skill loader supplies the current
procedure, including project-runner discovery, reference resolution, and trace
checks. Wait for completion and return its structured verdict unchanged:
`adherence: PASS|FAIL`, plus `mechanical_failures`, `semantic_findings`
(each with `severity: blocking|nit`), and `untested_rules`. The orchestrator's
early-exit reads `adherence` and the count of `blocking` findings. Propagate
ESCALATE, a timeout, or an unparseable result as an infrastructure failure;
never replace a missing verdict with PASS. Write the result to Agent A's
phase artifact using the same completion protocol as the other reviewers.
**Agent B — built-in review** (`/review`). **Tier-skip:** skip this agent when
the tier is **tiny** and log `review: skipped (tier: tiny)` in the setup
summary; it runs on the **small** and **full** tiers. This is a built-in slash command
whose procedure cannot be embedded as text, so it is **Agent-WRAPped, not
embedded**: spawn a read-only Agent on the review worktree `$primary_root/.claude/worktrees/review-<pr-number>`
(all git via `git -C`), same containment rails, whose prompt first runs `python3 "$IDH_ROOT/scripts/review-pr-anchor.py" <pr-number> --worktree "$primary_root/.claude/worktrees/review-<pr-number>"`, then invokes `/review` on the PR, then runs the same anchor command again. Any nonzero result is `review: FAILED — REVIEW-ANCHOR`, never a clean verdict. Supply the first anchor's HEAD and changed-file roster to `/review` and require it to examine the explicit `git -C "$primary_root/.claude/worktrees/review-<pr-number>" diff origin/<base>...HEAD`. Compare its result to the roster: an empty-diff answer, a different branch or HEAD, or a result with no concrete analysis of the listed changed files is `review: FAILED — wrong or unverified diff`. Record the named reason in Agent B's phase artifact and the final verdict; never convert this failure into an approval. **Hand it the resolved axes; it guesses without
them.** `/review` checks prose against a house rulebook, and told nothing it
picks one by inference — on a manuscript merge request it read
`rules/doctype/book.md` where the project manifest declares `techreport`
(audit of 2026-08-17, MR 136). Before spawning, run
`cd $primary_root/.claude/worktrees/review-<pr-number> && python3 "$IDH_ROOT/scripts/prose_predicate.py" --axes <changed files>`
and carry its lines verbatim into the wrap prompt. Same cwd anchoring and same
refusal semantics as the routing call below — this reads the disk too. Note
what the two calls settle: routing picks *which panel* reviews the diff, the
axes tell the reviewer *which rulebook* to hold it to. Agent B keeps running on
prose either way; the audit found it earning its seat there — a rebuilt PDF
with its link integrity checked on MR 136, and a catch the prose panel missed
on MR 138. What it lacked was the rulebook, not the mandate. Phase 5 uses the native `/simplify` command when available, or the portable
procedure below when that command is absent. The native route remains a direct
invocation (ticket 0349); both routes target the explicit review worktree and
PR branch, including when the invoking session lives in a sibling worktree.
**Agent C — PR review** (`/review-pr <pr-number> worktree=$primary_root/.claude/worktrees/review-<pr-number>`
or `/review-pr-prose <pr-number> worktree=$primary_root/.claude/worktrees/review-<pr-number>`).
Launch Agent C with `subagent_type: gaze-pr-review` from
`agents/gaze-pr-review.md`. Its explicit `tools: Agent, ...` grant is required:
the inner perspective fan-out cannot be inferred from the outer Agent's tools.
If Agent C itself cannot launch, record the same panel integrity failure in
the /gaze verdict comment, mark Agent C unresolved, and do not claim a completed
review round.
Before launching perspectives, Agent C checks that the `Agent` tool is in its
own available toolset and that a spawn call succeeds. A missing tool, depth
limit, or denied spawn is a panel integrity failure, not permission to replace
independent perspectives with sequential self-review. Write the selected roster
to the manifest and mark each unlaunched perspective `no report`. Post the
review on the PR with `PANEL-INTEGRITY: DEGRADED — Agent tool unavailable or
spawn failed; selected perspectives not run` and `dissent: unavailable`;
return those same lines in Agent C's structured block. The /gaze orchestrator
must carry the exact `PANEL-INTEGRITY:` line into its final verdict comment
and use `panel integrity: DEGRADED` in the actions section. Never write
`dissent: none` for this path. When Agent C's own toolset lacks the
`Agent` tool, apply the detached-seat substitution in
skills/review-pr/SKILL.md instead of degrading.
If a launched perspective is still missing at the bounded collection deadline,
name it as `no report`, post and return `PANEL-INTEGRITY: DEGRADED — missing
perspective report: <name>`, and use `dissent: unavailable` there too.
**Width-gate:** run Agent C at every tier. For round-1 code diffs, read
`$IDH_ROOT/skills/review-pr/panel-width.json` and apply its
`max_lines`, `max_files`, and `pipeline_paths` to `git diff --numstat
--no-renames origin/<base>...HEAD` in the review worktree. A diff within both
limits and with no pipeline path gets Correctness only. A binary count, missing
base, or unreadable config selects the proportional panel, with at least
Correctness + Consistency even if the risk description is trivial. `review:standard`
in the PR label/body or linked ticket body selects all five code perspectives
even for a tiny diff. A Correctness reviewer concern or low confidence adds
the other four perspectives before synthesis. The gaze battery tier controls
Agents A/B and simplify, but never overrides this Agent C width decision;
when the merge request already carries a prior Agent C
review (the caller-level repeat `/gaze` case), run the panel scoped per
§ Round scoping — only the perspectives whose previous verdict was
comment/request-changes (prose: minor/major), plus one regression agent
running a single regression check over all the cleared ones, unless the diff
was substantially rewritten, in which case the full panel runs for this round
and scoping is ignored. Do **not** state a round number — gaze computes none.
Instead, have the embedded procedure instruct the spawned agent to derive the
round itself (count the reviews this skill previously posted on the merge
request, identifiable by the verdict roster they always carry; the round is
that count plus one), read the previous round's verdict roster, build the
scoped perspective list from it, and record the reason for each inclusion.
Spell that derivation out: the spawned agent does not read this file or
`skills/review-pr/SKILL.md`, so an unstated rule does not reach it.
Otherwise route by the shared prose predicate, anchored in the review
worktree — the predicate reads the disk (manifest walk-up, `\documentclass`
sniff), so a parked cwd would return a plausible, wrong verdict, the failure
mode this ticket exists to close. Run
`cd $primary_root/.claude/worktrees/review-<pr-number> && python3 "$IDH_ROOT/scripts/prose_predicate.py" <changed files>`
— it prints `prose` when any changed file resolves to a `doctype` (from the
project's `.claude/rules-map.toml` manifest, else the `\documentclass`
sniff; one manuscript flips a mixed diff), `code` otherwise. It refuses any
path that does not exist under that cwd (exit 2, no verdict) — treat a
refusal as a routing error to fix, never as `code`. Process prose — notes,
tickets — carries no doctype and stays on the code panel, which is the panel
the audit measured as correct for it (ticket 0550). Spawn a read-only Agent on the review worktree `$primary_root/.claude/worktrees/review-<pr-number>`
(all git via `git -C`), whose embedded procedure is: read the linked ticket's exit criteria and the
diff, assess risk, apply the Width-gate for round-1 code, and run the selected
perspective set in parallel —
**code:** correctness, consistency, scope, red-team, doc-propagation (trivial →
correctness only when Width-gate qualifies; otherwise at least +consistency;
scripts or other pipeline paths → +doc-propagation;
substantial → all five); **prose:** discipline panel sized to the change,
always including an adversarial referee and an AI-tells auditor that scans the
full text against `config/ai-tells.yml`. Each perspective reports
confidence and a verdict (approve / comment / request-changes for code; accept
/ minor / major for prose).
If the round-1 code panel was reduced to Correctness only and that reviewer
raises a concern or low confidence, add the other four perspectives and
collect their reports before synthesis. An explicit `review:standard` request
also forces all five code perspectives. Synthesize: preserve dissent verbatim,
dedupe, and run the project's declared gate. With `.idh-checks.json`, use
`python3 "$IDH_ROOT/scripts/scoped-check.py"` and carry `selected:` and `skipped:`
into the review; without a map, run `make check`. A scoped pass is not a full
gate claim. Every non-blocker (minor) finding **must** carry exactly one tag
prefix — `verifiable:` (a reproducible failing assertion is attached),
`consider:` (hypothesis, no enforcement), or `nofollow:` (noted, not pursued);
hedged "might break X" phrasing is forbidden — produce the assertion or
downgrade to `consider:`. Blockers (request-changes / major) are untagged.
Post the single review on the PR and return the synthesized findings (blockers
+ tagged minors) as the structured block. This inner panel is itself a fan-out:
Agent C must launch its perspective agents in one message, each writing its
own artifact, and poll those artifacts until every one has landed
returns before it synthesizes — the fork contract applies recursively (see
**Fork execution contract**; ticket 0263, `/gaze 479`, 2026-07-11). Embed in
its prompt the 30-second live-state polling rule above; if the PR stops,
cancel its perspective agents and return the phase/state line without posting
a review.
Wait for all spawned agents to complete. Collect their structured outputs.
**Early-exit check**: if the adherence agent returned any `blocking` violations, skip phase 5 (simplify). Blocking adherence guarantees a REROLL; simplify tokens would be wasted. Log `simplify: skipped (adherence blocking)` in the telemetry phase line.
### 5. Simplify (sequential)
**Tier-skip:** when the tier is **tiny**, skip this phase and log
`simplify: skipped (tier: tiny)` in the telemetry phase line; it runs on the
**small** and **full** tiers.
**Prose-skip:** when the diff routed prose (the same
`"$IDH_ROOT/scripts/prose_predicate.py"` verdict the phase 2–4 panel choice used), skip
this phase and log `simplify: skipped (prose workpackage)` in the telemetry
phase line. Motive: this phase may commit to the PR branch, and `rules/git.md`
§ Prose workpackages forbids autonomous edits to a manuscript — an autonomous
change to prose goes through its own branch + merge request for the author to
arbitrate, never through a review-phase auto-fix (ticket 0550: two runs on the
same file decided this in opposite directions the same day; the rule now
exists). Otherwise, after 2–4 land their comments (and the early-exit check passes),
perform one of these routes. Record `simplify route: native|portable`.
- **Native command available:** run `/simplify <pr-number> worktree=$primary_root/.claude/worktrees/review-<pr-number>`.
- **Native command absent:** launch a separate reviewer through the active
runtime (model chosen per launch from the `route` skill), using the phase artifact
and bounded-wait contract above. Give it the explicit PR number, review tree,
base ref, anchor HEAD and changed-file roster. It runs the review-anchor check
before and after examining `git -C <review-tree> diff origin/<base>...HEAD`
and the relevant surrounding code or instructions. It must look for
unnecessary complexity, duplication, opportunities to reuse existing
mechanisms, and safe simplifications that preserve behavior and ticket
intent. This is a distinct simplification pass, not reuse of a general
review's approval. It returns the reviewed SHA, paths examined, and actionable
findings with severity, location, proposed change and rationale; or a CLEAN
verdict explaining the concrete candidates considered and why they need no
change. An absent reviewer, timeout, anchor mismatch, wrong diff or missing
evidence means this phase is unresolved and triggers the existing breaker;
absence of the slash command alone does not.
For either route, the orchestrator applies bounded fixes only to the PR branch
in the explicit review tree, or records an evidence-backed disposition for each
finding. Run affected checks for changes, commit and push fixes, and wait for
completion before phase 6 reads state. After any fixes, anchor the final HEAD
again and have the simplification reviewer confirm the dispositions against
that head; update the artifact with the final reviewed SHA. A moved head without
this confirmation leaves the pass unresolved. Feed all findings and dispositions
(or the evidenced CLEAN result), route, and reviewed SHA to verify-gate. The
same must-fix rules apply to both routes; no portable route silently skips or
weakens the phase. Existing tiny/prose/adherence skips above remain unchanged.
### 6. Gate (the non-rubber-stamp step)
Pass this run's `gate_session_id` and `review_tree` to the gate agent. It must
read the tip SHA from the review worktree immediately before ruling and
put all three values in its PR verdict comment. If the worktree is unavailable,
its HEAD cannot be read, or its HEAD differs from the anchor HEAD carried in
the prompt, the gate agent refuses and reports NOT-RUN — it never falls back
to the session cwd — and the orchestrator ESCALATEs without posting an approval.
The gate also runs as an **Agent-spawned sub-agent, not a `context: fork`**
(ticket 0216) — same rationale as phases 2–4. The launch is **the gate-seat
profile** (`agents/gate-seat.md`), whose contract is
`profiles/gate-seat/PROFILE.md` — spawn one seat (waited for by polling its
written verdict artifact), with its model chosen per launch from the `route` skill, operating on the review worktree
`$primary_root/.claude/worktrees/review-<pr-number>` (the equivalent fork call is
`/verify-gate <pr-number> worktree=$primary_root/.claude/worktrees/review-<pr-number>`);
the PR number, this run's `gate_session_id`, and the review worktree path ride
in the launch prompt — the seat's containment rails (read-only except the
REROLL bump, `git -C` discipline with no `cd` into or out of another tree, no
commits/pushes/branches/PRs, banned working-tree overwrites, the
`tickets/*.erg` write ban, the NOT-RUN refusal anchored on the review
worktree) are the contract's and are not restated in this launch block. The
gate's only write-shaped output is the verdict itself: the reroll-bump line
travels in the verdict's `reroll_bump:` field (and its PR verdict comment);
the orchestrator poses the line, the gate never runs `erg log` (ticket 0879).
The agent's
prompt embeds the gate procedure
imperatively and returns the YAML verdict block below as its **final message**;
the orchestrator parses `verdict` to branch.
Embedded gate procedure: for **every** ticket exit criterion, emit
ADDRESSED/MISSING with concrete evidence — a commit SHA + file:line that
touched the cited file, a matching test_id, or a posted rationale; "CI ran" /
"tests pass" is **not** evidence. For **every** review comment (from the phase
2–4 agents or a human), mark ADDRESSED (a post-comment commit changed the cited
file, OR the comment is resolved, OR a follow-up ticket is referenced) or
UNRESOLVED. Tagged minors are triaged by tag, not severity: `verifiable:`
unresolved → blocker-adjacent (REROLL); `consider:` informational; `nofollow:`
muted; untagged minors → `malformed_minors` (round 2 → ESCALATE). Every
must-fix simplify finding is APPLIED (diff shows it) or has a validated
rationale. Any `blocking` adherence violation → REROLL. Run `git log
origin/main..origin/<branch> --stat` (two-dot) and report files not traceable
to an exit criterion as `scope_overflow` (report only — never rebase/amend to
excise). At verdict time, re-check the content-bearing file count (the phase-1
rule: `git diff --name-status -M` for the roster, cross-referenced with
`git diff --numstat -M`: A/M/D count 1; renames count 1 iff additions +
deletions > 0, otherwise 0; R100 alone is insufficient because similarity
ignores line order) against the
breaker (`pr_files >= 15`, the phase-1 threshold) — a round-2 fix can grow a
14-file PR past it after the phase-1 check has passed; if the diff against
main now meets the breaker, report `circuit_breaker: un-reviewable` in the
verdict comment and rule ESCALATE, never APPROVED. Decision: any MISSING criterion, any unresolved human comment, any
unresolved `verifiable:` minor, any unapplied must-fix, any blocking adherence,
or any `scope_overflow` with disposition ESCALATE → REROLL (round 1) / ESCALATE
(round 2); all lists empty and all criteria ADDRESSED → APPROVED. Round 3 is
forbidden. On REROLL include `reroll_bump:` in the verdict — value `note
verify-reroll — round {n}: {top unresolved criterion}`, never `none` — only
after the live `verdict` state check; never run `erg log` (the gate writes no
ticket file); then post the PR verdict comment. Return:
```yaml
verdict: APPROVED | REROLL | ESCALATE
per_exit_criterion: [...]
unresolved_review_comments: [...]
unresolved_simplify_findings: [...]
unresolved_adherence_violations: [...]
multi_ticket: <distinct close-claim IDs and non-blocking disposition> | none
reroll_bump: <the exact line to pose, "note verify-reroll — round {n}: {top unresolved criterion}"> | none (REROLL only — the gate writes nothing)
rationale: <paragraph>
round: 1 | 2
gate_session_id: <this run's id>
review_worktree_path: <absolute path>
ruled_tip_sha: <full SHA read from review worktree at verdict time>
```
The orchestrator poses the `reroll_bump` line — it never runs `erg log`
against `main` mid-wave, where it would collide at the insertion point with
the branch's own ticket commits (ticket 0879, the 1016 collision class):
commit it onto the PR branch together with the REROLL fix, or defer to merge
time.
If phase 1 reported `multi_ticket`, carry it into the gate's findings and
disposition each close-claimed ticket's exit criteria. The finding alone is
non-blocking; it never changes APPROVED to REROLL or ESCALATE below the size
breaker.
## Branch on verdict
- **APPROVED** → post a "verify: approved" comment on the PR summarising the evidence. End
the skill. The caller merges.
- **REROLL, round 1** → spawn a fix subagent that creates and locks its own worktree on the PR branch (`git worktree add <path> <PR_BRANCH>`, then `git worktree lock` as in hunt step 3) —
the **coder profile** (`agents/coder.md`, whose contract is
`profiles/coder/PROFILE.md`; the model is chosen per launch from the
`route` skill, no frontmatter pin) —
waited for by polling
the artifact it writes on completion (so this fork survives until it pushes — see
**Fork execution contract**), feeding it the unresolved lists as input. Fix agent gets ≤10 min. On push, **re-enter phase 6 by
re-spawning the read-only gate Agent** (review worktree `$primary_root/.claude/worktrees/review-<pr-number>`, as in
phase 6) with `round=2` — not a fork invocation.
- **REROLL, round 2** → upgrade to ESCALATE (no third round). Post a PR comment with the
still-unresolved items and the gate's rationale. End the skill.
- **ESCALATE** → post a PR comment tagged `/gaze stopped:` listing what needs human
judgment. End the skill.
## Containment postcondition
On **every** exit path (APPROVED, REROLL-escalated, ESCALATE, circuit-breaker
abort), after removing the review worktree and before returning control to
the caller — the caller must see this report before any merge step:
```bash
git rev-parse --show-toplevel # FIRST: confirm cwd is the session
# worktree — the forks just returned,
# so a drifted cwd would make the two
# reads below report a foreign tree's
# state (rules/git.md § anchor)
git status --porcelain # invoking session worktree
git branch --show-current # must equal the branch on entry
git -C <primary-repo-root> status --porcelain
```
- **Foreign files** (anything this run did not deliberately create —
especially stray `tickets/*.erg`) → remove or restore them, and flag the
contamination in the verdict comment. Do not leave them for a later
`git add` to sweep.
- **Unexpected branch** → switch back to the entry branch and flag it. Never
end the skill with the session worktree on a different branch than it was
on at phase 1. This runs right after the review forks return, so the shell
cwd may sit in a foreign worktree: confirm the tree first
(`git rev-parse --show-toplevel` should equal the session worktree), then
anchor the corrective switch — `git -C <session-worktree> switch <entry-branch>`,
not a bare `git switch` against an assumed cwd (rules/git.md § anchor across
a forked-skill boundary).
- Anything that cannot be restored cleanly → downgrade the verdict to
ESCALATE; a contaminated workspace must not feed a merge.
## Fix-agent contract
The subagent spawned on REROLL receives:
- Worktree path (PR branch already checked out).
- Unresolved lists from the gate verdict (review comments, simplify findings, adherence
violations, per-exit-criterion gaps).
- Strict rule: **only** the listed items. No scope creep. No "while I'm here" edits.
- TDD discipline still applies: add a failing test for any behavioural fix before coding.
- Test-run budget: `make check-fast` plus the tests implicated by the unresolved-items list during the fix; one declared project gate before the final push — not per fix. A mapped doc-only fix uses `python3 "$IDH_ROOT/scripts/scoped-check.py"` and quotes selected and skipped targets; Python code and unmapped changes use the full `make check`.
Push commits to the PR branch; do not open new PRs. Trigger re-entry into phase 6.
## Circuit breakers
- Close claim names a ticket file that does not exist → ESCALATE. A PR with
no ticket at all is not a breaker: the gate reviews it against the PR body
(`/verify-gate` § Circuit breakers).
- `pr_files >= 15` (content-bearing files, phase-1 rule) → ESCALATE as
`un-reviewable` before the reviewer battery;
this is the `rules/workflow.md` 15+ file monster threshold. Name the breaker
in the verdict comment and mention `multi_ticket` when multiple close claims
add a decomposition signal. `--force-approve` can override it loudly.
- Any of phases 2–5 errors or times out → ESCALATE (do not silently skip). Exception: phase 5 (simplify) intentionally skipped when adherence is blocking — this is not an error.
- Fix agent timeout (10 min) → ESCALATE.
- Gate disagrees with phase 2–5 on a must-fix finding → ESCALATE (no silent resolution).
- Two REROLL rounds reached → ESCALATE.
- Telemetry thresholds (see `## Telemetry`).
On **every** circuit-breaker exit (not only ESCALATE): run the
`## Review scratch cleanup` procedure before returning so the main repo is
never left in a partial state.
## Telemetry
### Per-phase timing (stderr only)
Each phase emits start/end lines: `[verify] phase=<name> start=<ISO> / end=<ISO> elapsed=<s>s`
### Verdict footer (PR comment)
Appended to verdict comment: `telemetry: tier=<tiny|small|full> risk_band=<high|low|normal> wall=<s>s agents=<n> tokens=<in+out> cost~=$<usd>`
Fields: `tier` (battery tier from phase 1 — `tiny|small|full`), `risk_band`
(path-risk band from the phase-1 `--risk` call — `high|low|normal`), `wall`
(phase-1 to verdict), `agents` (sub-agent count), `tokens` (sum, use `na` for
missing), `cost~=` (best-effort USD, `na` if incomplete).
### Thresholds
Read from `skills/gaze/telemetry.yml`; env vars override. Defaults:
wall warn=15min escalate=30min; tokens warn=500k escalate=1M; fork liveness
window=20min (monitored by the caller, not checked at internal phase
boundaries).
On warn: post `/gaze: slow run` comment, continue. On escalate: stop, post
`/gaze stopped:` with measured value. Escalate > warn. Check at phase boundaries
only — except fork liveness, which a silent fork cannot self-check, so the
caller monitors it (§ Fork execution contract).
## `--force-approve`
Explicit human override. Usage: `/gaze <pr-number> --force-approve <reason>`.
- Skips phase 6 gate.
- Posts a loud PR comment: `/gaze: force-approved — reason: <reason>`. Includes the
outputs of phases 2–5 so reviewers see what was waived.
- Logs the override in the skill transcript.
- Still does not merge.
## Not in scope
- **Wave-level integration review.** Verify one PR at a time. Post-merge
integration review of a batch is `/roar` step 8 — child diffs re-read and the
full suite run once every child has merged.
- **Merging.** Ever. That is the caller's job.
## External reviewer panel
External, decorrelated review is optional evidence routed by the active
runtime. When useful or requested, describe the expertise, capability and
independence needs through `skills/reviewers/SKILL.md` and read its live-route
reference. The runtime discovers available reviewers and chooses an appropriate
route; no fixed roster, provider or gateway is required. The internal battery
and phase-6 gate retain their existing tier and round requirements.
**Scope and collection.** Optional external code review applies to small/full
CODE reviews, using the same `"$IDH_ROOT/scripts/prose_predicate.py"` verdict as
phases 2–4. Skip these code-review routes on tiny tiers and prose panels.
If selected, route reviews concurrently with phases 2–4 and collect completed
evidence before phase 6 within the run's deadline. Record the actual reviewer,
route, independence basis, exact reviewed revisions and raw report. A dispatch
is not completion; a changed head requires current review evidence.
**Disposition.** Pass `verifiable:` / `consider:` findings and their supporting
evidence to phase 6 as panel comments. The gate dispositions external findings
identically to internal ones: only verifiable-class findings may bounce;
suggestions are noted-not-blocking. Optional participation does not waive a
returned finding.
**Panel integrity.** No suitable reviewer, a timeout or a failed route is a
visible limitation. WARN and continue the required internal battery and gate;
optional external review is fail-open. Preserve `SEAT-FAILED:` /
`SEAT-MISSING:` records and the `PANEL-INTEGRITY:` headline verbatim in the
`/gaze` comment's panel section. These are coverage records, not findings to
disposition. Distinguish a completed clean review from absent evidence; an
unavailable route must never be reported as independent agreement. Record
external review as not selected when none was requested or attempted.
**Forge automated reviewer** (ticket 0206): the gate's comment-validation
step includes the forge's automated reviewer (e.g. a requested Copilot
review), when present. Its findings are dispositioned like any panel
comment: correctness-class only may bounce, style is noted-not-blocking.
The seat is **on-demand**: a PR nobody requested it on is simply a PR
without that seat, with no warning and no wait. Only when a request WAS
made (the bot appears in the PR's requested or completed reviewers) and
its review is still pending while everything else is ready: bounded wait
of a few minutes, then proceed with a logged WARN — fail-open.
<!-- harness-extension-point: requested-reviewer detection is
`gh pr view <pr> --json reviewRequests,reviews` on GitHub. -->
## Output shape
Post a single top-level PR comment at end of skill. Two sections,
always both present. No interim "started"/"finished" chatter — the
final report is the signal.
```
## /gaze actions
round: <n>
tier: tiny|small|full
risk_band: high|low|normal
circuit_breaker: un-reviewable | none
multi_ticket: <distinct close-claim IDs> | none (non-blocking below breaker)
adherence: PASS|FAIL — <n_blocking> blocking | skipped (un-reviewable) | skipped (battery NOT-RUN)
review: <n_comments_posted> | skipped (tier: tiny) | skipped (un-reviewable) | skipped (battery NOT-RUN)
review-pr: <n_comments_posted> | skipped (tier: tiny) | skipped (adherence blocking) | skipped (un-reviewable) | skipped (battery NOT-RUN)
review-pr scope: one seat | proportional panel | five perspectives | scoped: <objecting perspectives> + regression (omit if Agent C did not run)
panel integrity: COMPLETE|DEGRADED — <PANEL-INTEGRITY: line if degraded>
battery: ran | NOT-RUN — cause: <guard|no-agent-tool|depth> | skipped (un-reviewable)
simplify: <n_fixes_applied> | skipped (tier: tiny) | skipped (adherence blocking) | skipped (prose workpackage) | skipped (un-reviewable)
fix agent: <n_commits> commits (round 2 only, omit if round 1)
gate: ran | skipped (un-reviewable) | skipped (--force-approve) | skipped (gate launch failure — see battery NOT-RUN)
## /verify-gate verdict
verdict: APPROVED|REROLL|ESCALATE (direct setup ESCALATE when un-reviewable)
gate_session_id: <this run's id>
review_worktree_path: <absolute review tree path>
ruled_tip_sha: <full SHA read from review tree at verdict time>
reroll_bump: <exact returned note verify-reroll line; include on REROLL, omit otherwise>
Exit criteria:
- <criterion 1>: ADDRESSED — <evidence>
- <criterion 2>: MISSING — <gap>
Unresolved review comments: [list or "none"]
Unresolved simplify: [list or "none"]
Adherence: PASS | FAIL (<count>)
Rationale:
<paragraph>
telemetry: tier=<tiny|small|full> risk_band=<high|low|normal> wall=<seconds>s agents=<n> tokens=<in+out> cost~=$<usd>
```
On `--force-approve`, Part A is annotated `FORCE-APPROVED by <reason>`
and Part B shows the gate's would-have-been verdict before override.