| name | review-trivial |
| description | The lighter, reduced-prompt fail-closed verify gate for a trivially-classified diff (ADR 0120 §2). A trivial PR (small, single-concern, no new surface, not control-plane — as the trivial-diff classifier established) routes here instead of the full review-code/review-doc fan-out: a tight, scoped checklist over a tiny diff, run by an independent reviewer, landing a SHA-bound PASS/FAIL verdict in the existing review-code/review-doc/review-skill namespace. Only the gate's prompt cost is reduced — never its authority to FAIL. Trigger on "review the trivial PR #N", "run the lighter gate on #N", "review-trivial #N", or when the executor's trivial tier routes a PR here. It is fail-closed: any ambiguity, surprise, or out-of-scope signal FAILs and falls back to the full path. It never merges, never skips, never auto-passes, and never reviews the author's own PR. |
review-trivial
You are the lighter gate — the reduced-prompt fail-closed verify path ADR
0120
§2 authorizes. write-code opened a PR whose diff a deterministic, fail-closed classifier
(the trivial-diff classifier, ADR 0120 §1) already established is trivial: small,
single-concern, no new code-path / surface / dep / migration, and not control-plane.
Your job is to gate that PR with a tighter, scoped checklist over the small diff — far
cheaper than the full review-code / review-doc fan-out — and land a clear pass-or-fail
verdict on it.
"Lighter" reduces the gate's prompt cost, never its authority. This is a real,
independent, fail-closed gate. It can and does FAIL a bad trivial change: a wrong
one-liner, a leaked secret, a leaked machine-local path. It is not option (b) of ADR
0120 — the gate-skip the ADR explicitly rejected. You verify; you do not rubber-stamp.
You come to it fresh — the split-role firewall is unchanged (ADR 0052)
You are an independent reviewer, never the author of the diff. The whole point of the
pipeline's split-role review is that the agent that wrote the change is the worst judge of
whether it's correct — it knows what it meant. That firewall is structural, the same
as for review-code: this gate is run by a separate reviewer agent, never by
write-code on the PR it just opened. The reduced prompt does not relax this — a lighter
gate the author runs on itself is no gate at all. If you are the agent that authored this
PR, stop: you are not its reviewer.
Authority limit: you never merge
You do not merge — not on a PASS, not ever, not on your own authority. Your output is a
verdict: a merge-ready signal (ship-it is the one stage granted merge authority) or a
FAIL listing what's wrong. Conflating "verified" with "merged" is the self-grading collapse
this stage exists to prevent. You signal; ship-it asserts your SHA-bound PASS + green CI
and squash-merges.
All GitHub ops via gh api REST — never GraphQL
The kamp-us org runs a legacy Projects-classic integration that breaks GraphQL issue/PR
queries; every read and write goes through gh api. Resolve the target repo once, up front
(this skill is repo-agnostic — every call targets $REPO), per the shared contract's
Target repo resolution (../gh-issue-intake-formats.md,
ADR 0062 §1):
REPO="${CLAUDE_PIPELINE_REPO:-$(gh repo view --json nameWithOwner -q .nameWithOwner)}"
Read-only on git working state
You never mutate the git working tree of the checkout you run in — the canonical rule
lives in ../gh-issue-intake-formats.md §RO; cite it, don't
restate the prohibition. Step 1's head-read mechanism enforces it by construction (the head
reaches a per-run ref; your session tree is never switched, reset, or checked out).
Status: built dormant — not yet wired (ADR 0120 §2, issue #1558). This gate exists and
is correct, but nothing invokes it yet. The executor tier branch that routes a
trivially-classified PR to this gate (instead of the full review-code / review-doc
fan-out) is sibling issue #1559's job — it wires the branch + the fail-closed fallback into
.claude/workflows/drive-issue.js. The trivial-diff classifier (the predicate that
decides "is this diff trivial?") is sibling #1557. Adopting the lighter path at all is gated
behind the ADR 0112 two-axis measurement of sibling #1560 (a measured token win and held
gate-accuracy, with a quality regression vetoing the lever). Until those land, this skill is
reachable only by an explicit operator invocation — the build is intentionally ahead of its
wiring.
Step 0 — Refuse a PR that is not actually trivial (fail-closed re-affirm)
The classifier upstream decided this diff is trivial; you re-affirm that independently and
fail-closed. The lighter checklist is sound only because the diff is bounded — small,
single-concern, no new surface, not control-plane. If that premise does not hold under your
own eyes, the lighter gate is the wrong gate: FAIL and route to the full path rather than
under-gate a non-trivial change (ADR 0120 §3, default-deny). This is the safety hinge — never
relax it.
Pull the file set and confirm the bound. Classify §CP through the shared cp-classify verb,
which re-resolves the live boundary from origin/main at run time — never a stale snapshot (the
#981 mis-classification class) — and covers the second §CP source: a guard-touching
.decisions/** ADR is §CP by content (ADR 0164) with zero path matches, and an ADR is a doc by
path, so a path-only test here would ride it straight onto the lighter gate (#4161). See
../gh-issue-intake-formats.md §CP, the single source; cite it,
don't re-hard-code the list:
PCLI="${CLAUDE_PLUGIN_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null)/claude-plugins/kampus-pipeline}/bin/pipeline-cli"
PR=<pr number>
if ! cp_changed_files "$REPO" "$PR"; then
echo "review-trivial: not-trivial — changed-file list unreadable (§CP UNKNOWN, never 'no §CP path'); route to full path"; exit 0
fi
FILES="$CP_FILES"; NFILES="$CP_FILES_N"
ADD=$(gh api repos/$REPO/pulls/$PR --jq '.additions'); DEL=$(gh api repos/$REPO/pulls/$PR --jq '.deletions')
CP_STATE="$(printf '%s\n' "$FILES" | "$PCLI" cp-classify classify --repo "$REPO")"
Refuse the lighter gate (FAIL → full path) on any of:
control-plane — a path matched the live boundary (.claude/**, .github/**, a
gate-critical skill, the enforcement-guard packages). A control-plane diff is never trivial;
it takes the full path and the §CP approve-then-enqueue gate — a @kamp-us/control-plane
approval at head before ship-it enqueues it (ADR 0135; ADR 0053 / 0065 / 0100). It must never
have routed here.
content-undetermined whose ADR probe comes back guard-touching — a .decisions/** ADR
that is §CP by content (ADR 0164). Probe each touched ADR at head with the shared
guard-content-probe verb before you may treat the diff as ordinary; any guard-touching ⇒
route to the full path.
unknown — the classification could not be made (an unresolvable or uncompilable
boundary, an empty file set). With no classification you cannot prove the diff is
non-control-plane, so you must not treat it as trivial (mirrors the gates'
CONTROL_PLANE_RE='.' flag-everything posture). An unreadable answer is not a "no".
- The diff is not actually small / single-concern — many files, a large hunk count, or
visibly more than one logical concern. "Trivial" is a tiny, reviewable change; if it isn't,
the bound that licenses the lighter checklist is gone.
- A new surface, control-flow change, dep, schema/migration, or config key is visible —
the change adds executable behavior rather than correcting existing prose or a single trivial
line. Any new surface needs the full gate.
- The body's
## Deviations section is anything other than None. — the author disclosed a
departure from the issue, an acceptance criterion, a reviewer's guidance, or a governing ADR
(../gh-issue-intake-formats.md §DEV). A disclosed deviation is
evidence the diff is not trivial: it is a judgment call that needs the full gate's three
questions (authorized? needs an ADR? needs a follow-up issue?), and the reduced fan-out has no
slot for them. You do not grade the deviation here — the lighter path's premise is gone, so
you decline to be this PR's gate.
- The body carries no
## Deviations heading at all — with no section there is no None. claim,
so the triviality premise is unprovable and you route to the full path. Note what this bounce does
not decide: whether the absence is a defect is §DEV's Who owes the section question, and the
full gate answers it — [FAIL] on a PR that owed the section, [N/A] on one that never did (a
bot-opened bump, the ADR 0184/0075 issueless lane). So this stays default-deny — it costs a
not-owed PR a full review, never a dead-end, which is why it does not need the scoping itself.
On any refusal, emit a review-trivial: not-trivial — route to full path note (a plain note,
not a verdict marker — you are declining to be this PR's gate) and stop. The executor's
fail-closed fallback (#1559) re-routes it to the full review-code / review-doc fan-out;
the worst case of a miss is paying the full (correct) cost, never an under-gated merge.
PCLI="${CLAUDE_PLUGIN_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null)/claude-plugins/kampus-pipeline}/bin/pipeline-cli"
if [ "$CP_STATE" != "not-control-plane" ]; then
if [ "$CP_STATE" = "content-undetermined" ]; then
cp_head_sha "$REPO" "$PR"; HEAD_SHA="$CP_HEAD_SHA"
if [ -z "$HEAD_SHA" ]; then
echo "review-trivial: not-trivial — head SHA unreadable, ADR content unprobeable (§CP UNKNOWN); route to full path"; exit 0
fi
GUARD_TOUCHING="$(printf '%s\n' "$FILES" | grep -E '^\.decisions/.*\.md$' | while IFS= read -r adr; do
# Capture and CHECK, never a straight pipe: gh writes its error document to STDOUT, so a pipe
# hands the probe an ERROR BODY as the ADR body (§CPREAD #2). Unreadable ⇒ §CP, fail-closed.
adr_body="$(gh api "repos/$REPO/contents/$adr?ref=$HEAD_SHA" -H 'Accept: application/vnd.github.raw' 2>/dev/null)" || adr_body=""
if [ -z "$adr_body" ]; then echo "$adr(body-unreadable⇒§CP)"
else
GC_STATE="$(printf '%s' "$adr_body" | "$PCLI" guard-content-probe classify --path "$adr" 2>/dev/null)"
case "$GC_STATE" in
not-guard-touching) : ;;
guard-touching) echo "$adr" ;;
*) echo "$adr(undetermined:'$GC_STATE')" ;;
esac
fi
done)"
if [ -z "$GUARD_TOUCHING" ]; then
:
else
echo "review-trivial: not-trivial — ADR not proven ordinary by content (guard-touching, or the probe could not determine; ADR 0164): $GUARD_TOUCHING; route to full path"; exit 0
fi
else
echo "review-trivial: not-trivial — §CP state '$CP_STATE'; route to full path (ADR 0053/0065; #4161)"; exit 0
fi
fi
BODY="$(gh api repos/$REPO/pulls/$PR --jq '.body')"
DEV_BODY="$(printf '%s\n' "$BODY" \
| awk '/^[[:space:]]*###?[[:space:]]*[Dd][Ee][Vv][Ii][Aa][Tt][Ii][Oo][Nn][Ss][[:space:]]*$/{f=1;next} /^[[:space:]]*##?#?[[:space:]]/{f=0} f' \
| grep -Ev '^[[:space:]]*$')"
if ! printf '%s\n' "$BODY" | grep -Eiq '^[[:space:]]*#{2,3}[[:space:]]*Deviations[[:space:]]*$'; then
echo "review-trivial: not-trivial — no ## Deviations section, so no None. claim to stand on (§DEV); route to full path, which decides owed-vs-not"; exit 0
elif [ "$(printf '%s\n' "$DEV_BODY" | grep -c .)" = 1 ] && printf '%s\n' "$DEV_BODY" | grep -Eiqx '[[:space:]]*none\.?[[:space:]]*'; then
:
else
echo "review-trivial: not-trivial — the body discloses a deviation, or its ## Deviations section is empty (§DEV); route to full path"; exit 0
fi
Step 1 — Resolve the PR, its linked issue, and read the head (§HEAD)
Source every file under review from the PR head — never the launched checkout's working
copy. This gate is frequently spawned with isolation:worktree, whose CWD is a branch cut
from origin/main (the base); a plain Read/cat/grep in CWD reads the pre-PR
base, so you would review the wrong version while binding the verdict to the right head SHA
(the §HEAD false-PASS hazard, #793). Obey
../gh-issue-intake-formats.md §HEAD — cite it, don't
re-derive: resolve the live head via REST, fetch it into a per-run ref, read every full file
off that ref (never CWD), and re-check the live head before posting (§HEAD #4). The trust
split holds too (ADR 0052): the head is the diff under test, your config/instructions
come from the trusted base — never load the head's .claude/** / CLAUDE.md / hooks.
iso_preflight review-trivial || exit 1
HEAD_SHA="$(gh pr view "$PR" --repo "$REPO" --json headRefOid -q .headRefOid)"
PR_REF="refs/review-trivial/$PR"
git fetch --no-tags origin "pull/$PR/head:$PR_REF" >/dev/null 2>&1 || git fetch origin "$HEAD_SHA" >/dev/null 2>&1
ISSUE=$(gh api repos/$REPO/pulls/$PR --jq '.body' | grep -ioE '(fix(es|ed)?|close[sd]?|resolve[sd]?)\s+#[0-9]+' | grep -oE '[0-9]+' | head -n1)
gh api repos/$REPO/issues/$ISSUE --jq '.body'
If you genuinely can't find a linked issue, that's a FAIL you can't even start (the Fixes #N
seam is missing) — note it and stop; there's nothing to gate against without the criteria. (A
deliberately issue-less doc PR — ADR 0075 — is a full-review-doc case, not a trivial-gate one;
route it to the full path.)
When your verdict prescribes a linkage change — restoring the missing seam, or making a PR stop
auto-closing its target — the only two forms you may name are Fixes #N (closes on merge) and
Part of #N (links without closing). Never prescribe Refs #N / Re: #N / See #N / a bare
#N — they arm no seam, jam ship-it Step 1, and brick the lane the verdict was gating (#4047).
Rule and rationale: ../gh-issue-intake-formats.md §9
(Part of #N).
Step 2 — The scoped checklist (the reduced fan-out)
This is where the cost is reduced. The full gate runs a per-criterion table plus the ADR
0079 specialist fan-out (claim-vs-ground-truth, dangling-reference, omitted-case, …) to surface
in-scope defects the AC never named. The lighter gate does not run that fan-out — and that
is sound precisely because the diff is bounded-trivial (Step 0 re-affirmed it): a one-line,
no-new-surface change has a vanishingly small surface for a hidden in-scope defect, so a tight
scoped checklist over the tiny diff catches the failure classes a one-liner can actually carry.
This is the reduced fan-out: fewer dimensions, smaller prompt — not a lowered bar.
Verify all of the following over the head diff. Each is conjunctive; one miss → FAIL.
-
Right one-liner (correctness vs the AC). The change does what the linked issue's
### Acceptance criteria ask — not something adjacent, not the inverse, not a no-op. Read
the AC, read the diff at head, and confirm the diff actually satisfies each box. A wrong
one-liner — a change that lands but doesn't do what was asked, or does the opposite — is the
first failure class ADR 0120 §2 names. Evidence is the diff line vs the criterion, not the
author's say-so.
-
No leaked secret. No credential, token, API key, password, private key, connection
string, or other secret material in the added lines. A one-line change is a classic vector
for a pasted secret. Scan the added hunks:
git show "$PR_REF" | grep -nE '^\+' | grep -niE 'api[_-]?key|secret|token|password|passwd|BEGIN [A-Z ]*PRIVATE KEY|AKIA[0-9A-Z]{16}|ghp_[0-9A-Za-z]{30,}|xox[baprs]-|-----BEGIN' || echo "no secret-shaped added lines"
Treat any hit as a finding to confirm by eye (a variable named token referencing a binding
is fine; a literal secret value is a FAIL).
-
No leaked machine-local / home / absolute / sibling-repo path. No ~/, /Users/…,
/home/…, a vault path, an absolute machine path, or a sibling-clone path in the added lines
— committed files, like PR bodies and comments, cite repo-relative paths only (the
standing no-local-paths invariant). Scan the added hunks:
git show "$PR_REF" | grep -nE '^\+' | grep -nE '(~/|/Users/|/home/|/private/var/folders/|[A-Za-z]:\\\\)' || echo "no local/home/absolute paths in added lines"
A repo-relative path (apps/web/…, .decisions/…) is fine; a machine-local/home/absolute/
sibling-repo path is a FAIL.
A clean pass on all three (plus the Step 0 triviality re-affirm) is a PASS. Any miss, or
any ambiguity you can't resolve from the diff, is a FAIL — default-deny, never an
"it's-probably-fine" pass. When in doubt, FAIL and let it take the full path.
Step 3 — Land the SHA-bound verdict in the existing namespace (ADR 0058)
The lighter gate reuses the SHA-bound verdict contract unchanged — it emits exactly the
marker shape ship-it and write-code-repair already consume, so the lighter path needs no
change to ship-it (that, plus the executor wiring, is #1559's lane, not yours). The marker
namespace is the artifact class of the trivial diff, resolved via the §DOC / §CP single-
source probes in ../gh-issue-intake-formats.md — cite them,
don't re-derive:
- doc-class diff (the §DOC surface —
.decisions/**, .patterns/**, docs/**, or a
root/top-level prose *.md) → emit a review-doc: marker.
- skills/ diff (a non-control-plane skill — control-plane skills were already refused in
Step 0) → emit a
review-skill: marker.
- code-class diff (everything else —
apps/**, packages/**, .glossary/**, a code-root
*.md) → emit a review-code: marker.
Whichever namespace, the verdict obeys the §5/§6/§6.5 matcher contract: the first line is
the bare, canonical, SHA-bound marker, with @ <sha> immediately after the PASS/FAIL
polarity and before the — merge-ready / — not merge-ready tail (token order is fixed;
a trailing @ <sha> captures sha=null and ship-it refuses a correct PASS as unverified,
#625). The @ <sha> is load-bearing: ship-it and write-code-repair refuse any verdict
not bound to the PR's current head, and refuse a SHA-less marker outright (ADR 0058, #258).
Re-check the live head before posting (§HEAD #4): if the head moved while you reviewed, do
not post a verdict bound to a SHA you no longer reviewed — re-resolve and re-review, or
abort. Then resolve the head SHA and upsert (one verdict per (PR, namespace) — PATCH your
own prior marker if present, else POST; ADR 0058 rule 2). The post is a comment, never a
native APPROVE (a native review can't carry the @ <sha> this contract controls; ADR 0058
rule 4):
NS=review-code
HEAD_NOW="$(gh api repos/$REPO/pulls/$PR --jq .head.sha)"
[ "$HEAD_NOW" = "$HEAD_SHA" ] || { echo "head moved under the verdict — re-review the new head or abort (ADR 0058)"; exit 1; }
VERDICT_FILE="$(mktemp /tmp/review-trivial-verdict.XXXXXX)"
BODY="$(cat "$VERDICT_FILE")"
ME="$(gh api user --jq .login)"
MINE=$(gh api "repos/$REPO/issues/$PR/comments?per_page=100" \
| jq -r --arg me "$ME" --arg ns "$NS" 'map(select(.user.login==$me
and (.body | test("^\\s*\\**\\s*" + $ns + ":"; "i")))) | last | .id // empty')
if [ -n "$MINE" ]; then
gh api -X PATCH "repos/$REPO/issues/comments/$MINE" -f body="$BODY"
else
gh api -X POST "repos/$REPO/issues/$PR/comments" -f body="$BODY"
fi
Verdict body shape
The first line is the canonical bare marker (the matchers tolerate an optional leading **,
but emit bare); the body carries the scoped evidence. A PASS:
review-code: PASS @ <HEAD_SHA> — merge-ready
Lighter gate (ADR 0120 §2) — trivial diff verified against #<ISSUE> with the scoped checklist:
- [PASS] Triviality re-affirmed — <N> file(s), +<add>/-<del>, §CP `not-control-plane` (cp-classify: path + ADR-0164 content, live from origin/main), no new surface
- [PASS] Right one-liner — diff satisfies the AC: <criterion> — <evidence: file:line>
- [PASS] No leaked secret — added lines scanned, clean
- [PASS] No leaked local/home/absolute/sibling-repo path — added lines scanned, clean
Read the PR head (§HEAD): all files sourced from `<HEAD_SHA>` via `git show "$PR_REF:<path>"`,
never the launched checkout's working copy.
Lighter gate, full authority: only the prompt cost is reduced. This PR is merge-ready.
**review-trivial does not merge** — `ship-it` is the authorized merge step; merging auto-closes
#<ISSUE> via `Fixes #<ISSUE>`.
A FAIL names the failed check and the diff site, so write-code-repair drains it the same way
it drains a full-gate FAIL:
review-code: FAIL @ <HEAD_SHA> — not merge-ready
Lighter gate (ADR 0120 §2) — trivial diff FAILS the scoped checklist:
- [FAIL] Right one-liner — diff at <file:line> does not satisfy AC "<criterion>": <what's wrong>
- [PASS] No leaked secret — clean
- [PASS] No leaked local/home/absolute/sibling-repo path — clean
Fix on the same branch and push; an independent re-review re-gates (the lighter gate is
stateless). **review-trivial does not merge.**
The verdict is conjunctive: every checklist item must PASS for a PASS; one miss → FAIL.
This is the fail-closed floor — the lighter gate produces a real PASS/FAIL exactly like the
full gate, only over a smaller prompt.
Why this stays fail-closed (the one-paragraph invariant)
"Lighter" touches only the prompt cost — fewer fan-out dimensions over a provably tiny
diff. Three things keep the safety floor intact: (1) Step 0 re-affirms triviality
independently and fail-closed — any control-plane file, unreadable boundary, multi-concern
diff, or new surface routes to the full path, so the lighter checklist only ever runs where its
bound holds; (2) the scoped checklist still catches every failure class a one-liner can carry —
a wrong one-liner, a leaked secret, a leaked machine-local path (ADR 0120 §2) — and is
conjunctive + default-deny, so any miss or ambiguity is a FAIL, never an
"it's-probably-fine" pass; (3) the verdict is an independent reviewer's SHA-bound PASS/FAIL
in the existing namespace (ADR 0058 + the split-role firewall, ADR 0052), so the lighter gate
can and does FAIL a bad trivial change and a stale verdict can never authorize a merge. The
gate is never skipped and never auto-passes — only made cheaper.