pr-review
Use when reviewing, inspecting, or commenting on a GitHub pull request or GitLab merge request by number, URL, or branch, including a second review after an automated reviewer. Use review-agent-feedba
SKILL.mdskills/pr-review/SKILL.md
GitHubPR Review
Activation and Scope
Review a GitHub PR or GitLab MR by number, URL, or branch, not uncommitted diffs.
Ask for a missing target; do not invent one. Existing bot-thread replies belong
to review-agent-feedback when available, not this review/posting workflow.
Detect the provider from origin: GitLab uses glab, GitHub uses gh.
This body owns the actor contract, including isolated/body-only use. Resolve
<skill> and optional references from the real, symlink-resolved skill directory,
not cwd. No sibling skill or evaluator bundle is required. Missing executable
assets, provider access, or host capabilities are explicit blockers/degradation,
not permission to fabricate a clean review or install unrelated tooling.
Every defect needs opened definitions, an upstream validation hop, a downstream
consumer hop, and a concrete failure on the cumulative diff. Only deterministic
gate measurements are exempt. max adds blind refutation and reproduction;
cheaper levels retain evidence requirements and disclose the missing adversarial stage.
Effort levels
--level on preflight picks how much is spent. Only max spawns agents. Every level below
it runs the same lenses inline in your own context — no finder sub-agents, no skeptics — which
is why its cost does not scale with the number of units. The default is high.
| level | gate | find | verify | candidates per lens |
|---|---|---|---|---|
low |
생략 | logic+boundary만, 팩 1패스 |
prescreen | 렌즈당 4 |
medium |
✅ | 적용 렌즈 전부, 인라인 순차 (정밀도 편향) | prescreen | 렌즈당 3 |
high (기본) |
✅ | 적용 렌즈 전부, 인라인 순차 (재현율 편향) | prescreen | 렌즈당 4 |
xhigh |
✅ | 전부 + sweep 패스 | prescreen | 렌즈당 6 |
max |
✅ | workflow.js 팬아웃 | prescreen + blind tracer + reproducer | 규모 기반 (기존) |
summary.md prints the resolved plan as a level= line; context.json → level drives the
disclosure post_review.py puts in the posted body. Candidate confidence uses
the screening scale (≥80 strong, 60–79 summary, <60 dropped); posting uses the
severity-weighted bars in step 4. Do not impose a final finding-count cap:
--max-inline limits inline placement, not the findings disclosed.
Pick max when the review is the gate — a release branch, a change to money/auth/data, or
any MR whose findings you intend to state as verified. Pick high (or below) for a running
review of ordinary work. Say which level ran when you report in chat; never describe an inline
level's output as verified.
0. Preflight
python3 <skill>/scripts/mr_context.py --mr <ref> --repo-dir <repo> --worktree --history 5 --level high
Read <out_dir>/summary.md. Stop and say so when eligible=false (closed/merged/draft, or a
review by this skill already exists for this head) unless the user explicitly asked to review it
anyway. When large=true (> 40 files or > 2000 added lines), tell the user the scale and
ask whether to narrow (directories or lenses) before spending agents.
--worktree guarantees safe isolation: reuse a clean primary/linked checkout
at head_sha (context.checkout, worktree=null); only a dirty or different-head
checkout needs <out_dir>/worktree. Never create a redundant detached worktree.
workflow_args.json already contains the finder units, the checkout path, the candidate caps,
the hunk ranges and prior lessons for the prescreen — never hand-build it. A unit is one
lens bundle × one shard of the changed files (bundles: behavior = logic/boundary/data/async,
contract = security/contract/rules, intent = tests/scope/intent); each unit has one
self-contained pack (pack/<unit>.md: that slice's cumulative diff, the definitions and
one-hop neighbours of the symbols it defines, matching rules, threads and prior lessons). One
finder applies its bundle's lenses separately over one pack read. Shards keep
packs under ~150 KB of diff; defs.md and hunks/<file>.patch serve skeptics.
1. Gate
python3 <skill>/scripts/quality_gate.py --context <out_dir>/context.json
Run except at low: lint, scoped typecheck, targeted tests, LOC, longest touched
function, approximate complexity and test/source ratio against base_sha.
Only gate.json → candidates (G1..; failure 95, new breach 90, pre-existing 60,
unanchorable 50) are hop-exempt. All review numbers come from the gate, never estimates.
2. Find — inline (low / medium / high / xhigh)
Do not dispatch sub-agents at these levels. You are the finder. Work through the units in
units.json yourself, in this context, in sequence:
- Read
pack/<unit>.mdwhole, in one call — it already holds that slice's diff, the definitions and one-hop neighbours, matching rules, threads and prior lessons. Do not grep around it; that is what the pack exists to prevent. - Apply each of the unit's lenses to that pack separately, and tag every candidate with its
lens. Do not let one lens's conclusion suppress another's. - Emit the finder schema below, at most
perLensCapcandidates and 3verified_okentries per lens. Attest every assigned file exactly once inreviewed_files. xhighonly: after every unit, take one more pass over the diff as a fresh reviewer holding the current list, looking only for defects not already on it — moved/extracted code that dropped a guard, setup/teardown asymmetry in tests, config defaults flipped. Add nothing you already have; return nothing rather than padding.
State missing hops and cap confidence at 50. Low/medium favor actionable precision; high/xhigh favor recall with a nameable failure scenario, then confidence screening.
Finder contract (all levels)
Apply only the generated unit's effort-selected lenses independently; never share finder conclusions.
Low is limited to logic and boundary. The five-lens base below applies to other
levels when selected by the generator; topic matches do not add undisclosed lenses.
logic covers control/data flow; boundary covers changed exports, callers and
callee shapes; tests covers real regression assertions; rules requires explicit
rule text, matching glob, changed line, and cited rule; scope compares description
with cumulative diff. intent, when selected, applies with linked issues:
map each claim, criterion, checklist/table row to code/tests and quote unmet issue lines.
security applies to security/controller/gateway/DTO/ops or auth/token/secret/
permission/env changes; data to DB/migration/query/repository/transaction changes;
async to async/gateway/queue/stream/retry/timeout; contract to DTO/controller/
gateway/generated changes. Check authorization/injection, partial writes/indexes/
migration symmetry, durability/idempotency/cancellation, and public/generated
API shape/error documentation respectively.
{"lens":"<id>","reviewed_files":["<each assigned path once>"],"inspected":["<opened files/symbols>"],
"candidates":[{"path":"...","new_line":1,"end_line":null,"severity":"critical|high|medium|low",
"category":"bug|security|performance|business-logic|data|api-contract|test|rule|scope",
"title":"<Korean>","what":"<Korean>","why":"<Korean failure scenario>","how":"<Korean fix>",
"evidence":["path:line — proof"],"upstream":"<observed>","downstream":"<observed>",
"suggestion":null,"rule":null,"confidence":80,"newly_reachable":false}],
"verified_ok":[{"concern":"...","why_ok":"<evidence file:line>","loc":"file:line","thread":null}]}
Severity: critical = loss/corruption, auth bypass, secrets, payment/quota error;
high = reachable wrong behavior, partial write or crash; medium = degradation,
missing timeout/cleanup or consumer-visible drift; low = rare but concrete risk.
Exclude style/naming, speculative refactors, unsupported "consider adding", and
linter/typechecker/CI findings (the gate owns those). A call site is not a definition.
Require suggestion for API-contract and one-line decorator/description/config fixes;
apply and check it before shipping, or strip the code while retaining the finding.
2b. Screen — inline levels
Write the candidates to <out_dir>/candidates.json and run the deterministic stage:
python3 <skill>/scripts/prescreen.py --args <out_dir>/workflow_args.json \
--candidates <out_dir>/candidates.json --out <out_dir>/prescreened.json
Merge prescreened.json → candidates. Dedup, off-hunk checks and committed memory
match max; security/data retain their exemption. Dropped reasons go in chat, not the MR.
2 (max). Find + Verify — fan-out
Claude Code: Workflow({scriptPath: "<skill>/references/workflow.js", args: <contents of workflow_args.json>}). Omo native: do not execute workflow.js through the current Omo
session. Run the pinned adapter instead:
python3 <skill>/scripts/omo_driver.py --args <out_dir>/workflow_args.json \
--profile omo-flash --provider zai --model glm-5.3-flash
The Omo adapter pins zai/glm-5.3-flash, checks --no-model-fallback, and
sets SENPI_NO_FALLBACK=1; never silently switch providers. It owns rate-limit
backoff: 429 is not a format error/provider switch. omo-flash uses 24 finder
turns, 18 skeptic turns, cap floor 40, concurrency 10, high thinking;
standard uses Workflow budgets. Finder/tracer permissions are read-only;
only the reproducer may create throwaway tests. The adapter supplies temporary
read-only inputs and schema-constrained submit_pr_review_* output, enforces
budgets, recovers validated submissions from empty final replies, and retries
format failures with original input and only the submit tool enabled.
Remove the temporary input directory after all phases; retain diagnostic artifacts.
Retain raw stdout/stderr, exit, timeout, denial, parse/schema diagnostics and
failure_counts (parse_failure, schema_failure, timeout, rate_limited,
low_confidence_abstain, coverage_gap). Missing/duplicate reviewed_files,
failed children, or abstention mean non-zero/degraded, never a clean review.
Other hosts use current sub-agent tools with workflow.js's finder/skeptic
prompts (or the contracts in this body when the asset is absent). Budget:
one finder per unit, then tracer and only if needed reproducer per candidate;
10 finder messages and 8 skeptic messages, batched reads. Use the session model
unless args.models = {finder, tracer, reproducer} overrides it.
Save <out_dir>/workflow-result.json before reading; quote its cost block
(agents, prescreened, reproducers skipped, output tokens) in chat.
Max verdict contract
Prescreen deduplicates, rejects non-changed files/off-hunk lines unless proven newly reachable, and matches prior lessons/refutation memory at similarity ≥0.5; security/data never lose the learned-suppression exemption, including after dedup. Skeptics start by reading candidate hunks and relevant defs together. The tracer sees no finder evidence/upstream/downstream and independently opens the validation and consumer hops, including linked intent; the reproducer sees the whole candidate and tracer verdict, attempts a throwaway test, targeted check or executable scenario, and records command/output, then deletes throwaway files.
Prescreen refusal or refuted=true at confidence ≥70 kills; a tracer kill skips
reproduction. Inability to verify is not refutation: return refuted=false,
confidence ≤40, reason prefixed 미확인:. Exclude those verdicts; fewer than two
usable verdicts leaves an abstain ≤50, summary-only. Otherwise both must fail to
refute; final confidence is the minimum of finder and non-refuting skeptics.
Partial refutations with a standing skeptic ≥70 become open questions.
Severity changes require evidence and rationale, at most one step; use corrected
line/suggestion values. Scores: 0–25 inferred/unverified, 50 definition-proven rare
case, 75 real reachable path, 90–100 reproduction or definitions with no escape.
No reproduction and no proof from definitions can refute a scenario; tool inability
alone cannot. Intent alone is not correctness.
Missing finders or incomplete/duplicate coverage block verify. Recover only failed units:
python3 <skill>/scripts/omo_driver.py --args <out_dir>/workflow_args.json \
--phase verify --retry-failed-units
This repairs failed-unit results/diagnostics, rebuilds whole-stage dedup/prescreen/
coverage and persists find-stage.json. No tracers until coverage is complete;
failure exits non-zero. Never combine with skeptic-abstention retry below.
To rerun only candidates that were kept as skeptics unavailable (abstain) in a degraded run:
python3 <skill>/scripts/omo_driver.py --args <out_dir>/workflow_args.json \
--phase verify --retry-degraded-from <out_dir>/workflow-result.json
This leaves the original result untouched and writes <out_dir>/workflow-retry-result.json.
3. Merge (you are the moderator)
Write <out_dir>/findings.json (schema: post_review.py docstring):
- Start from
gate.json → candidates(keep theirsource/metrics/pre_existing/minorfields), then add the findings with idsF1..— atmaxfromworkflow-result.json(keepskeptics_passed,upstream,downstream); at the inline levels fromprescreened.json → candidates(keepupstream/downstream; there is noskeptics_passed, and never invent one —post_review.pyreads it as "both skeptics failed to refute this" and lowers the inline bar to 50 on the strength of it). - The workflow's
partiallist (refuted candidates where a skeptic still stood at ≥ 70) becomesopen_questions: one line each — the residual risk in the standing skeptic's words, the location, and what the author should confirm. Inline levels have nopartial; put the hops you could not complete there instead. verified_okentries stay as{concern, why_ok, loc, thread}objects; pick at most 8, preferring ones that contradict an existing bot thread (threadset).- Drop a finding whose line already has a bot/human thread unless it contradicts that thread
(then say so in
what). Never repeat a claim inprior_review_lessons. - Re-review of a moved head:
context.json → prior_review_threadslists earlier threads this skill posted (either marker); resolved ones go toverified_okas "이전 지적 Fn 해결 확인", unresolved ones are referenced, not re-posted. verified_ok= the risky-looking things inspectors traced and cleared, each with evidence.rule_candidates= each refutation that hinged on a project fact, as one Korean sentence naming the fact and the proving file.- Verdict:
request_changesif any critical/high;approveif nothing ≥ medium and the tests lens found the changed behavior covered; elsecomment. - Korean prose; identifiers, paths, commands verbatim. Never quote secret values.
- Before posting, use
fluent-koreanwhen installed and polish every Korean prose field the agents produced —summary,verified_ok,open_questions,rule_candidates, and each finding'stitle/what/why/how. Sub-agent prose field. The polish pass is required even in an isolated copy: write complete, direct Korean sentences, remove filler/translationese, keep terminology stable, and preserve facts, evidence lines, identifiers, and commands exactly.
4. Post
python3 <skill>/scripts/post_review.py --context <out_dir>/context.json --findings <out_dir>/findings.json --gate <out_dir>/gate.json # dry run
python3 <skill>/scripts/post_review.py ... --post # only when asked
Inline bar is severity-weighted: critical/high ≥ 50, medium ≥ 65, low ≥ 80, and any
finding both skeptics failed to refute (skeptics_passed) qualifies at ≥ 50. At most 8
inline, agent findings first; deterministic gate findings get one inline slot (the worst by
complexity) and the rest a table. Medium+ findings under the bar are shown in "저자 확인
요청" with their what, never folded away. Pre-existing and minor (≤ 10 LOC over, complexity
≤ 3) breaches are table-only.
Findings on non-diff lines are demoted to the summary with a warning (--strict to fail
instead). Secrets are masked. GitHub approve posts as COMMENT unless --allow-approve.
The script refuses a moved head, a closed MR, and a second post for the same head. Report
the read-back table it prints, not the POST responses.
When the user did not ask to post, the deliverable in chat is: the level that ran, verdict, the
"지적" table, "저자 확인 요청", the 자동 검사 table, "검토했으나 문제 없음", rule proposals, and one
line per refuted candidate (title + winning skeptic reason; at the inline levels the prescreen
reason from prescreened.json). Do not paste the inline bodies. The
summary's first paragraph must name only defects that appear in the tables or in "저자 확인
요청" — never mention a finding the reader cannot find below.
4b. Remember refutations (team memory)
python3 <skill>/scripts/record_refuted.py --result <out_dir>/workflow-result.json --context <out_dir>/context.json
Appends evidence-backed refutations (skeptic confidence ≥ 80) to <repo>/.issueops/pr-review/refuted.jsonl
— commit it with the repo. The next run's prescreen drops a same-file candidate whose title/what overlap
≥ 0.5 with a recorded refutation; security/data candidates are never suppressed. Prescreen kills are
not recorded (they carry no evidence).
4c. Re-review of a moved head
Run preflight with --incremental: when <out_dir> already holds workflow-result.json and
context.prev.json from the previous head, only units whose files changed since that head are
re-inspected and findings on untouched files are carried (carried_from). Findings on changed
files are dropped and re-found or not. post_review.py still validates every line against the
new diff.
5. Clean up
Read context.json before cleanup. Remove a worktree only when its non-null worktree
field resolves exactly to <out_dir>/worktree; a null field means preflight reused the
caller's checkout and it must never be removed. Remove an isolated review worktree with
git worktree remove --force <out_dir>/worktree on every exit path, including
eligible=false and validation failures. Delete throwaway specs the reproducer left.
Scope and Disclosure
Only changed/newly reachable lines qualify; pre-existing debt needs explicit evidence that this change worsened it. Name unread units and unverified ("probably/may/could") claims rather than presenting a complete/verified review. Never approve through another path or post twice for one head.
Stop and Optional Background
Done after the requested chat report or authorized post readback, plus cleanup.
No findings is valid with traced verified_ok; never pad. Missing evidence,
failed agents, or incomplete coverage must remain visible. Do not resolve
threads, merge, silently upgrade effort, or add a posting approval the user did
not give. In an IssueOps cycle return evidence to its owning stage for authenticated
recording and preserve its lifecycle/actor/generation/cwd and publication gates.
Lenses and verification provide optional detail; this body's effort, output, and posting rules govern.