From prr
Start an AI-powered PR review with parallel multi-agent review and arbiter synthesis. Takes a PR URL or owner/repo#N as argument.
How this skill is triggered — by the user, by Claude, or both
Slash command
/prr:prr-start <pr-url-or-ref><pr-url-or-ref>This skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
You are orchestrating a multi-agent PR review. Follow each phase sequentially. Use task tracking to report progress.
You are orchestrating a multi-agent PR review. Follow each phase sequentially. Use task tracking to report progress.
The PR reference is available as $ARGUMENTS. The PRR binary is at ${CLAUDE_PLUGIN_ROOT}/bin/prr-darwin-universal.
Throughout this skill, use these shorthands:
${CLAUDE_PLUGIN_ROOT}/bin/prr-darwin-universal$ARGUMENTSBefore anything else, check if ~/.prr/config.yml exists by reading it.
If it exists: proceed to Phase 0.
If it does not exist: tell the user:
"PRR has not been set up yet. Run
/prr:setupfirst to configure your workspace, GitHub username, and Jira credentials. Config will be saved to~/.prr/config.yml."
Then stop — do not proceed with the review.
Create task tracking items so the user can see progress through the review pipeline:
Mark each task in_progress when you start it and completed when you finish it.
Phases 7 and 8 (line comment review, posting to GitHub) are interactive flows that do not use task tracking.
Update task 1 to in_progress.
~/.prr/config.yml to get the workspace_path value.
~/.prr/workspace${CLAUDE_PLUGIN_ROOT}/bin/prr-darwin-universal cleanup --workspace <workspace_path>
Update task 1 to completed.
Update task 2 to in_progress.
${CLAUDE_PLUGIN_ROOT}/bin/prr-darwin-universal context "$ARGUMENTS" --workspace <workspace_path>
--ticket flag in their original message, pass it: --ticket <TICKET_ID>ROUND_DIR for all subsequent phases.
/Users/me/.prr/workspace/owner-repo-pr-42/r1<ROUND_DIR>/context-manifest.md and store its contents for the next phase.Update task 2 to completed.
Update task 3 to in_progress.
Present the full context manifest to the user. Include every row from the manifest table exactly as generated (including the PRR version row). Format it clearly.
Ask the user:
Context gathered. You can:
- Say "go" to start the review
- Provide review tasks (specific things you want reviewers to focus on)
- Add extra context (links, notes, things to watch for)
What would you like to do?
Wait for user input via AskUserQuestion.
Handle the response:
["Check auth flow", "Verify the migration is reversible"].REVIEW_TASKS_JSON for Phase 4.Update task 3 to completed.
Update task 4 to in_progress.
Read ~/.prr/config.yml and extract the agents list. This determines which agents to dispatch. Possible values: claude, codex, gemini, opencode — in any combination.
Also read gemini_model (default: gemini-2.5-flash), google_cloud_project (default: fuga-prod), google_cloud_location (default: europe-west4), and arbiter_rounds (default: 3) for later use.
Before building the prompt or dispatching agents, verify that each external CLI agent is actually working. Run a quick smoke test for each non-claude agent in the list. Run all checks in parallel (single message, multiple Bash calls).
echo "Say hello" | timeout 30 codex -a never exec -s read-only --ephemeral --color never -p "Reply with exactly: HELLO" 2>&1 | head -5
export GOOGLE_CLOUD_PROJECT="<GOOGLE_CLOUD_PROJECT>" GOOGLE_CLOUD_LOCATION="<GOOGLE_CLOUD_LOCATION>" && echo "Reply with exactly: HELLO" | timeout 30 gemini -p "" -m "<GEMINI_MODEL>" -o text --approval-mode yolo 2>&1 | head -5
printf 'Reply with exactly: HELLO\n' | timeout 30 opencode run --model openai/gpt-5.5 --format json 2>&1 | jq -r 'select(.type == "text") | .part.text' | head -5
If opencode fails, also check whether OPENAI_API_KEY is set in the environment. If it is missing, tell the user:
opencode requires
OPENAI_API_KEYto be exported in your shell (e.g., addexport OPENAI_API_KEY=sk-...to~/.zshrc), or runopencode authto authenticate. Skipping opencode for this review.
Claude does not need a health check — it runs as a native Claude Code sub-agent and is always available.
After all checks complete:
Agent health check:
- claude: ok (native)
- gemini: FAILED —
<first line of error>- codex: ok
- opencode: ok
Skipping gemini for this review.
Run:
${CLAUDE_PLUGIN_ROOT}/bin/prr-darwin-universal prompt --review <ROUND_DIR>
If the user provided review tasks in Phase 3, include them:
${CLAUDE_PLUGIN_ROOT}/bin/prr-darwin-universal prompt --review <ROUND_DIR> --tasks '<REVIEW_TASKS_JSON>'
The command writes the prompt to <ROUND_DIR>/results/review-prompt.md and prints its path to stdout.
Set these path variables:
PROMPT_PATH = <ROUND_DIR>/results/review-prompt.mdREPO_PATH = <ROUND_DIR>/repoRESULTS_PATH = <ROUND_DIR>/resultsFor each active agent, dispatch a sub-agent using the Agent tool. Dispatch ALL agents in a single message so they run in parallel.
Dispatch with agent definition: claude-reviewer
Instructions to pass:
Review this PR. Here are your paths:
- Review prompt: <PROMPT_PATH>
- Cloned repo: <REPO_PATH>
- Write your review to: <RESULTS_PATH>/claude-review.md
Read the review prompt first, then explore the repo as needed. Write your complete review to the output path.
Dispatch with agent definition: codex-reviewer
Instructions to pass:
Run the Codex CLI to review this PR. Here are your paths:
- Review prompt: <PROMPT_PATH>
- Cloned repo: <REPO_PATH>
- Results directory: <RESULTS_PATH>
- Write output to: <RESULTS_PATH>/codex-review.md
Run this exact command:
cat "<PROMPT_PATH>" | codex -a never exec -C "<REPO_PATH>" -s workspace-write --add-dir "<RESULTS_PATH>" --ephemeral --color never --output-last-message "<RESULTS_PATH>/codex-review.md" -
Dispatch with agent definition: gemini-reviewer
Instructions to pass:
Run the Gemini CLI to review this PR. Here are your paths:
- Review prompt: <PROMPT_PATH>
- Cloned repo: <REPO_PATH>
- Gemini model: <GEMINI_MODEL>
- Google Cloud Project: <GOOGLE_CLOUD_PROJECT>
- Google Cloud Location: <GOOGLE_CLOUD_LOCATION>
- Write output to: <RESULTS_PATH>/gemini-review.md
Run this exact command:
export GOOGLE_CLOUD_PROJECT="<GOOGLE_CLOUD_PROJECT>" GOOGLE_CLOUD_LOCATION="<GOOGLE_CLOUD_LOCATION>" && cat "<PROMPT_PATH>" | gemini -p "" -m "<GEMINI_MODEL>" -o text --approval-mode yolo --include-directories "<REPO_PATH>" > "<RESULTS_PATH>/gemini-review.md"
Dispatch with agent definition: opencode-reviewer
Instructions to pass:
Run the opencode CLI to review this PR. Here are your paths:
- Review prompt: <PROMPT_PATH>
- Cloned repo: <REPO_PATH>
- Results directory: <RESULTS_PATH>
- Write output to: <RESULTS_PATH>/opencode-review.md
Run this exact command:
cat "<PROMPT_PATH>" | opencode run --model openai/gpt-5.5 --dir "<REPO_PATH>" --format json --dangerously-skip-permissions | jq -r 'select(.type == "text") | .part.text' > "<RESULTS_PATH>/opencode-review.md"
After all agents complete, verify each expected output file exists and is non-empty:
<RESULTS_PATH>/claude-review.md (if claude was dispatched)<RESULTS_PATH>/codex-review.md (if codex was dispatched)<RESULTS_PATH>/gemini-review.md (if gemini was dispatched)<RESULTS_PATH>/opencode-review.md (if opencode was dispatched)If any agent failed (empty or missing output), report the failure to the user but continue with whatever reviews succeeded. At least one review must succeed to proceed.
Tell the user which agents completed successfully.
Update task 4 to completed.
Update task 5 to in_progress.
This phase runs in a loop. The arbiter may ask questions of the agents (up to arbiter_rounds rounds, default 3), or it may produce a final report immediately.
Run:
${CLAUDE_PLUGIN_ROOT}/bin/prr-darwin-universal prompt --arbiter <ROUND_DIR>
This reads all *-review.md files from results, plus any Q&A history from arbiter-log.md, and writes <ROUND_DIR>/results/arbiter-prompt.md.
Dispatch with agent definition: arbiter
Instructions to pass:
Synthesize the agent reviews for this PR. Here are your paths:
- Arbiter prompt: <ROUND_DIR>/results/arbiter-prompt.md
- Write your output to: <ROUND_DIR>/results/arbiter-output.md
Read the arbiter prompt and follow its instructions exactly. Either produce a JSON questions block or the final report.
After the arbiter completes, read <ROUND_DIR>/results/arbiter-output.md.
Determine if it contains questions or a final report:
Search the output for a fenced JSON code block (```json) whose content is an object with agent name keys (e.g., "claude", "codex", "gemini", "opencode"). Each key maps to an array of question strings.
If questions are found:
Parse the JSON questions object. Example:
{
"claude": ["What about the race condition on line 42?"],
"codex": [],
"gemini": ["Did you verify the SQL injection fix?"],
"opencode": []
}
For each agent with a non-empty questions array, build a question prompt:
${CLAUDE_PLUGIN_ROOT}/bin/prr-darwin-universal prompt --question <ROUND_DIR> --agent <AGENT_NAME> --questions '<QUESTIONS_JSON>'
Where <QUESTIONS_JSON> is the full JSON object (not just that agent's array).
The command writes the question prompt to <ROUND_DIR>/results/round-<N>-<agent>-question.md.
Dispatch the appropriate agent to answer:
claude-reviewer with instructions to read the question prompt and write the answer to <ROUND_DIR>/results/round-<N>-claude-answer.mdcodex-reviewer with instructions to run:
cat "<question_prompt_path>" | codex -a never exec -C "<REPO_PATH>" -s read-only --add-dir "<RESULTS_PATH>" --ephemeral --color never --output-last-message "<answer_path>" -
Note: use -s read-only for Q&A (not workspace-write).gemini-reviewer with the question prompt piped to gemini, output to the answer path.opencode-reviewer with instructions to run:
cat "<question_prompt_path>" | opencode run --model openai/gpt-5.5 --dir "<REPO_PATH>" --format json --dangerously-skip-permissions | jq -r 'select(.type == "text") | .part.text' > "<answer_path>"
Dispatch all agent answers in parallel.
After all answers are collected, append the Q&A round to <ROUND_DIR>/results/arbiter-log.md:
## Round <N>
### <Agent> Questions
<questions>
### <Agent> Answers
<answers>
---
Increment the round counter. If rounds < arbiter_rounds (from config), go back to Step 5a (rebuild arbiter prompt with updated history). Otherwise, force the arbiter to produce a final report by telling it this is the last round.
If NO questions (final report):
<ROUND_DIR>/results/final-report.md.Update task 5 to completed.
Update task 6 to in_progress.
<ROUND_DIR>/results/final-report.md.---
## Review Summary
**Verdict:** `[<VERDICT>]` | **Confidence:** <CONFIDENCE> | **Line comments:** N (severity breakdown)
## Key Findings
- finding 1
- finding 2
- ...
## Low-Severity Items
1. item 1
2. item 2
3. ...
---
Use these exact strings for the verdict (no emojis — they don't render in the terminal):
[APPROVE][REQUEST_CHANGES][COMMENT]Tell the user:
Review complete. You can:
- Ask questions about any finding
- Ask me to read code, check git blame, run tests in the repo at
<REPO_PATH>- Say "re-review" to run another round with additional guidance
- Say "continue" or "comments" to proceed to line comment review
Enter an interactive loop using AskUserQuestion:
<REPO_PATH>. Read files, run git commands, grep for patterns, etc. Present findings and ask if they have more questions.
git -C <REPO_PATH> <command> instead of cd <REPO_PATH> && git <command> to avoid security prompts.prr context "$ARGUMENTS" --workspace <workspace_path> again (this creates rN+1), then re-run Phases 4-5 with the new round dir. Include any guidance the user provides.Update task 6 to completed.
Run:
${CLAUDE_PLUGIN_ROOT}/bin/prr-darwin-universal parse-report <ROUND_DIR>/results/final-report.md --diff <ROUND_DIR>/results/diff.txt
This outputs JSON to stdout. Parse it. The structure is:
{
"verdict": "REQUEST_CHANGES",
"confidence": "HIGH",
"findings": [
{
"id": "F-01",
"title": "...",
"trigger": "Acceptance Criteria",
"severity": "HIGH",
"anchor": "diff",
"location": "path/to/file:line",
"path": "path/to/file",
"line": 42,
"start_line": null,
"why_it_matters": "...",
"suggested_comment": "...",
"suggested_fix": "..."
}
],
"line_comments": [ /* derived: diff-anchored findings only */ ],
"review_action": "Request Changes",
"review_body": "..."
}
anchor is one of "diff", "reference", "none". location, path, line, start_line are null when the anchor is "none". The parser may have emitted stderr warnings (e.g., downgraded mislabels, malformed findings, unreadable diff) — surface those to the user before the interactive review.
Maintain an in-memory list of CommentState entries, one per Finding:
CommentState {
finding: Finding // straight from the JSON
status: Pending | Accepted | Rejected | Edited
overridden_body: string | null // populated when status == Edited
}
Initialize every entry with status = Pending. New findings the user adds in Step 7d are appended with status = Accepted. The list survives across 7b / 7c / 7d and is consumed by Phase 8.
Walk findings where anchor == "diff" strictly one at a time. Present one finding, then stop and resolve it before touching the next.
Hard rule — never batch. Each iteration is exactly one rich-text block followed by exactly one AskUserQuestion call containing exactly one question about that single finding. Do NOT print multiple findings' context up front, and do NOT put more than one finding into a single AskUserQuestion call (AskUserQuestion accepts up to four questions — do not use that here). Batching forces the user to scroll and auto-advances their answers; that is the regression this rule exists to prevent.
For each finding, present in two parts: rich context as regular text, then a minimal AskUserQuestion.
## Comment N/M — <Trigger> — <title> (<Severity>)
📄 [path#L<line>](url) (lines start–end)
<code context in a language-specific fenced block; target line marked with # <-->
**Why this matters:** <why_it_matters>
**Suggested comment:**
> <suggested_comment>
**Suggested fix:** <suggested_fix>
N/M counts only diff-anchored findings. Code context is read from <ROUND_DIR>/repo/<path>, ~5 lines before/after the target line, language-hinted fence (e.g., ```ruby), target line marked with a trailing # <-- comment. The clickable link uses the url field if present, else plain text path#L<line>.
Comment N/M — <one-line summary>
Options:
Free-text input is a clarification or edit, not a signal to advance. Stay on the same finding: incorporate the input (rewrite the comment, answer the question, or discuss), show the result, and ask again with a fresh single-question AskUserQuestion for the same finding. Only move to the next finding once the user picks Accept, Reject, or Edit (or issues a special command). The user may go several rounds on one comment — never auto-advance after recording a free-text reply.
Special commands: add/new/+ switches to Step 7d; done/stop/enough exits Phase 7.
status = Acceptedstatus = Rejectedstatus = Edited, overridden_body = <new text>After diff-anchored findings, walk findings where anchor is "reference" or "none" strictly one at a time, under the same hard rule as 7b: one finding's rich-text block, then exactly one AskUserQuestion call with exactly one question — never batch multiple findings, and stay on the same finding for free-text follow-up until the user picks Accept/Reject/Edit. These cannot be posted as inline comments but must still be reviewed for inclusion in the regenerated review body.
## Report-Only Finding N/M — <Trigger> — <title> (<Severity>)
(Report-only — won't be posted as an inline comment; will be summarized in the review body.)
📄 <path:line if anchor == reference; else "(no anchor line)">
**Why this matters:** <why_it_matters>
**Suggested comment:**
> <suggested_comment>
**Suggested fix:** <suggested_fix>
For anchor: reference, render the code context the same way as 7b but note "(unchanged code — for reference only)" above the fence.
Report-Only Finding N/M — <one-line summary>
Options:
CommentState updates as in 7b.
Triggered by add/new/+ at any point in 7b or 7c. Collect:
diff / reference / none.none: ask for path:line. Validate against <ROUND_DIR>/results/diff.txt — if the user picked diff but the line isn't in the diff, ask whether to downgrade to reference.Why this matters, Suggested comment, Suggested fix with the user; confirm.status = Accepted and continue the review.Show the final list (accepted + edited entries from both 7b and 7c):
---
## Final Findings (N total)
**Inline comments (P):**
1. `path:line` — <Trigger> — <one-line summary>
...
**Report-only findings (Q):**
1. `path:line` (or no anchor) — <Trigger> — <one-line summary>
...
---
Confirm: "Ready to post? [yes / edit more]"
Do NOT use the arbiter's original review_body directly. Regenerate based on the CommentState list from Phase 7.
Partition Accepted + Edited entries into two groups:
inline = findings with anchor == "diff"other = findings with anchor == "reference" or "none"Build the body:
<one opening sentence with overall assessment>
**Inline comments (P):**
- `path:line` — <Trigger> — <one-line summary from suggested_comment or overridden_body>
- ...
**Other findings (Q):**
- `path:line` (or "(no anchor)") — <Trigger> — <one-line summary>
- ...
Omit either section if its list is empty. If both lists are empty, use a short body appropriate to the action (e.g., "LGTM" for APPROVE, "No actionable findings." for COMMENT).
Save as REVIEW_BODY.
The GitHub payload (Step 8e) builds comments[] from the inline group only — the other group is captured in the review body and never sent as inline comments.
Present the review body and action as rich text output, then use a two-step AskUserQuestion flow: first pick the action, then optionally edit the body.
Rich text output:
## Post Review
**Suggested action:** <review_action>
**Review body:**
> <REVIEW_BODY>
AskUserQuestion (minimal):
Post review?
Provide these options:
Handle the response:
EVENT to APPROVE, proceed to Step 2EVENT to COMMENT, proceed to Step 2EVENT to REQUEST_CHANGES, proceed to Step 2Do NOT accept free-text input here. If the user types something other than picking an option, re-ask the question. Body editing happens in Step 2, not here.
Only run this step if the user picked Approve, Comment, or Request Changes in Step 1.
AskUserQuestion (minimal):
Use this body, or edit it first?
Provide these options:
Handle the response:
Post as-is (a, as-is, keep, ok, option 1): keep REVIEW_BODY unchanged, proceed to Step 8c
Edit body (e, edit, option 2): prompt the user with:
Paste or type the new review body. The original is shown above for reference.
Take the user's next message verbatim as the new REVIEW_BODY. Then show it once for confirmation as rich text:
## Updated Review Body
> <new REVIEW_BODY>
**Action:** <EVENT>
Posting now...
Then proceed to Step 8c.
Do not accept free-text input on the menu itself — the user must pick Edit body explicitly to enter edit mode.
Parse the PR reference from $ARGUMENTS to extract owner, repo, and number.
Run:
gh pr view <NUMBER> --repo <OWNER>/<REPO> --json headRefOid --jq .headRefOid
Save the result as COMMIT_SHA.
Run:
gh pr view <NUMBER> --repo <OWNER>/<REPO> --json files --jq '.files[].path'
Save the result as the list of PR file paths.
For each line comment, resolve its path against the PR file list:
Build a JSON payload:
{
"commit_id": "<COMMIT_SHA>",
"event": "<EVENT>",
"body": "<REVIEW_BODY>",
"comments": [
{
"path": "<resolved_path>",
"line": <line_number>,
"start_line": <start_line_or_omit>,
"side": "RIGHT",
"body": "<comment_body>"
}
]
}
For each comment: if start_line is present in the parsed JSON, include it in the payload (GitHub highlights the range). If start_line is absent, omit it (single-line comment).
If there are no comments, omit the `comments` array (just post the review body with the event).
Write the JSON to a temp file and post:
```bash
TMPFILE=$(mktemp)
cat > "$TMPFILE" << 'PAYLOAD'
<json payload>
PAYLOAD
gh api repos/<OWNER>/<REPO>/pulls/<NUMBER>/reviews --input "$TMPFILE"
rm "$TMPFILE"
If the API call succeeds, report:
Review posted successfully!
Action: <EVENT>
Comments: <N>
PR: https://github.com/<OWNER>/<REPO>/pull/<NUMBER>
If it fails, show the error and suggest the user check:
Throughout all phases:
context command prints the round directory path as the last line of stdout. Capture it accurately.prompt commands print the output file path to stdout. You can use these to verify the files were written.arbiter_rounds iterations. Track the count.-s read-only (not workspace-write).parse-report command outputs JSON to stdout. Parse it directly.Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub cfactolerin/prr --plugin prr