plugins/dev/skills/pr-review/SKILL.md
Intent-aware map-reduce PR review with CI/CD support, confidence gating, and intelligent comment deduplication.
npx skillsauth add rp1-run/rp1 pr-reviewInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
§ROLE: Map-reduce PR review orchestrator. 6 phases, local + CI modes, comment deduplication.
§CTX: Use the pre-resolved projectRoot, kbRoot, and workRoot values from the generated Workflow Bootstrap section. Do not hardcode .rp1/work/ or .rp1/context/ paths.
§GUARDRAILS
.rp1/work/ or anywhere inside the target project's .rp1/ directory.stateDiagram-v2
[*] --> reviewing
reviewing --> posting : analysis_complete
posting --> [*] : done
On each phase transition, report via:
rp1 agent-tools emit \
--workflow pr-review \
--type status_change \
--run-id {RUN_ID} \
--step {CURRENT_STATE} \
--data '{"status": "running"}'
RUN_ID comes from the generated Workflow Bootstrap sectionRUN_NAME from the resolved PR context: use "PR #{pr_number}" when a PR number is available, otherwise use "PR: {branch_name}" as fallback--name "{RUN_NAME}" to label the runOn session start, emit the status change:
rp1 agent-tools emit \
--workflow pr-review \
--type status_change \
--run-id {RUN_ID} \
--name "{RUN_NAME}" \
--step reviewing \
--data '{"status": "running"}'
State Progression Protocol:
--step with --data '{"status": "running"}' when you enter that state→ [*] transitions): report with --data '{"status": "completed"}' and --close-run when the step's work finishesState mapping:
reviewing covers: P-1 (config), P0 (input resolution), P0.5 (visual gen), P1 (splitting), P2 (sub-reviewers), P3 (synthesis)posting covers: P4 (reporting), P5 (comment posting)Example sequence:
--workflow pr-review --step reviewing --name "PR #42" --data '{"status": "running"}' # first emit includes --name
--workflow pr-review --step posting --data '{"status": "running"}' # analysis done, entering posting phase
--workflow pr-review --step posting --data '{"status": "completed"}' --close-run # posting done, workflow complete
§ARCH
P-1 (seq): Config Load -> CI Detection -> Early Exit Check
P0 (seq): Input Resolution -> Intent Model
P0.5 (bg): Visual Gen (conditional, parallel w/ P1)
P1 (seq): Splitter -> ReviewUnit[]
P2 (par): N x Sub-Reviewers -> Findings + Summaries
P3 (seq): Synthesizer -> Cross-File Issues + Judgment
P4 (seq): Reporter -> Markdown Report + Structured Data
P5 (seq): Comment Posting (CI only) -> GitHub Review
§PROC
Execute immediately. No approval prompts.
Load config: Read {projectRoot}/.rp1/config/pr-review.yaml if exists:
enabled: boolean # default: false
review_drafts: boolean # default: true
ai_harness: string # "claude-code" | "opencode", default: "claude-code"
add_comments: boolean # default: true
collapse_summary: boolean # default: false
verdict: string # "approve" | "request_changes" | "comment" | "auto", default: "auto"
max_comments: integer # default: 25
bot_marker: string # default: "<!-- rp1-review -->"
visualize: boolean # default: true
ci_platform: string # "github" | "buildkite" | "gitlab", default: "github"
Missing file -> use defaults.
Detect CI: CI=true env -> CI_MODE=true, else CI_MODE=false
Platform: CI_PLATFORM = config.ci_platform (default: github)
Env overrides:
| Env Var | Overrides | |---------|-----------| | RP1_PR_REVIEW_ENABLED | enabled | | RP1_PR_REVIEW_VERDICT | verdict | | RP1_PR_REVIEW_ADD_COMMENTS | add_comments | | RP1_PR_REVIEW_VISUALIZE | visualize |
Early exit (CI only):
if CI_MODE AND NOT config.enabled -> Output config instructions, EXIT
Build CI_CONTEXT (GitHub only):
GITHUB_EVENT_PATH -> pr_number, action, is_draftGITHUB_REPOSITORY -> owner, repoGITHUB_TOKEN, GITHUB_HEAD_REF, GITHUB_BASE_REFStore: {"pr_number": N, "owner": "...", "repo": "...", "token": "...", "head_ref": "...", "base_ref": "...", "is_draft": false}
Draft check (CI only):
if CI_MODE AND CI_CONTEXT.is_draft AND NOT config.review_drafts -> Skip, EXIT
Skip if CI_MODE=true.
git status --porcelainrp1 agent-tools emit \
--workflow pr-review \
--type waiting_for_user \
--run-id {RUN_ID} \
--step reviewing \
--data '{"prompt": "Stash local changes and continue, or abort the review?", "context": "PR review needs a clean local git worktree before analysis"}'
{% ask_user "Stash and continue?", options: "Stash and continue", "Abort" %}git stash push -m "rp1-pr-review-auto-stash", set STASHED=true
Abort:
rp1 agent-tools emit end-run \
--run-id {RUN_ID} \
--outcome cancelled \
--reason "User aborted review instead of stashing local changes"
Exit "Review cancelled."pr_branch, base_branch (BASE_BRANCH if provided), pr_numbergh pr view {{pr_number}} --json title,body,url 2>/dev/null -> PR_METADATAREVIEWED_PR_URL = PR_METADATA.url when present, else REVIEWED_PR_URL=""problem_statement, body -> expected_changes, acceptance_criteria
Parse fails -> mode="ci_minimal", problem_statement="Review PR #{{pr_number}}"; keep REVIEWED_PR_URL if url was parsedSet REVIEWED_PR_URL=""
Resolve target:
| Input | Detection | Resolution |
|-------|-----------|------------|
| Empty | No TARGET | git branch --show-current |
| PR# | Numeric | gh pr view {{target}} --json headRefName,baseRefName,title,body,url and set REVIEWED_PR_URL from url when present |
| PR URL | /pull/ | Extract #, fetch above and set REVIEWED_PR_URL from url when present |
| Branch | Non-numeric | Use directly |
gh pr view {{branch}} --json title,body,headRefName,baseRefName,url 2>/dev/null
REVIEWED_PR_URL from url when presentREVIEWED_PR_URL="" and continue in branch-only or git-only mode3a. Get repo URL: gh repo view --json url --jq '.url' 2>/dev/null -> GITHUB_URL (empty if fails)
Build Intent Model:
full): title -> problem_statement, parse body, fetch linked issuesuser_provided): use descriptionbranch_only): problem_statement="Review changes on {{branch}}"Add: git log {{base}}..{{branch}} --oneline --no-decorate -> commit_summaries
Base: PR metadata > BASE_BRANCH > 'main'
Intent Model: {"mode": "...", "problem_statement": "", "expected_changes": "", "should_not_change": "", "acceptance_criteria": [], "commit_summaries": []}
Skip conditions (any true -> skip):
SKIP_VISUAL == trueconfig.visualize == falseSpawn (if not skipped): Background mode: local=true, CI=false
First, register visual generation as a subflow under reviewing and mark it running:
rp1 agent-tools emit \
--workflow pr-review \
--type subflow_registered \
--run-id {RUN_ID} \
--step reviewing \
--data '{"parentStepId":"reviewing","subflowName":"visual-generation","diagram":"flowchart TD\n A[Start] --> B[Generate Visuals]\n B --> C[Return Markdown]"}'
rp1 agent-tools emit \
--workflow pr-review \
--type status_change \
--run-id {RUN_ID} \
--step reviewing \
--unit visual-generation \
--data '{"status":"running"}'
{% dispatch_agent "rp1-dev:pr-visualizer" %} Generate PR visualization. PR_BRANCH: {{pr_branch}} BASE_BRANCH: {{base_branch}} REVIEW_DEPTH: quick STANDALONE: false KB_ROOT: {kbRoot} WORK_ROOT: {workRoot} {% enddispatch_agent %}
Capture VISUAL_CONTENT (raw markdown with Mermaid diagrams).
If the visualizer fails or returns unusable output:
reviewing / visual-generation with {"status":"failed"}VISUAL_CONTENT=""After the visualizer returns, mark the subflow unit completed:
rp1 agent-tools emit \
--workflow pr-review \
--type status_change \
--run-id {RUN_ID} \
--step reviewing \
--unit visual-generation \
--data '{"status":"completed"}'
{% dispatch_agent "rp1-dev:pr-review-splitter" %} Split PR diff into review units. PR_BRANCH: {{pr_branch}} BASE_BRANCH: {{base_branch}} THRESHOLD: 100 Return JSON with units array. {% enddispatch_agent %}
Parse units, store counts. Fail -> Abort w/ error.
CRITICAL: Spawn ALL sub-reviewers in SINGLE message.
For each unit: git diff {{base}}..{{branch}} -- {{unit.path}}
Build file_list
Spawn N sub-reviewers (one msg):
{% dispatch_agent "rp1-dev:pr-sub-reviewer" %} Analyze review unit across 5 dimensions. UNIT_JSON: {{stringify(unit_with_diff)}} KB_ROOT: {kbRoot} INTENT_JSON: {{stringify(intent_model)}} PR_FILES: {{stringify(file_list)}} Return JSON with findings and summary. {% enddispatch_agent %}
Aggregate findings + summaries
<50% fail -> continue | >=50% fail -> abort
Prep summary: {"critical": N, "high": N, "medium": N, "low": N, "needs_human_review": N, "details": [...]}
Spawn:
{% dispatch_agent "rp1-dev:pr-review-synthesizer" %} Perform holistic verification. INTENT_JSON: {{stringify(intent_model)}} KB_ROOT: {kbRoot} FILE_LIST: {{stringify(file_list)}} SUMMARIES_JSON: {{stringify(all_summaries)}} FINDINGS_SUMMARY: {{stringify(findings_summary)}} Return JSON with intent_achieved, cross_file_findings, judgment, rationale. {% enddispatch_agent %}
Extract: intent_achieved, intent_gap, cross_file_findings, judgment, rationale
Fail -> findings-only judgment: Critical->block, High->request_changes, else->approve
Merge findings: unit + cross_file, dedupe by (path, lines, dimension), keep highest severity
Stats: {critical: N, high: N, medium: N, low: N}
Review ID: PR# -> pr-{{number}} | else -> sanitized branch
git rev-parse {{branch}} -> HEAD_SHA
Spawn:
{% dispatch_agent "rp1-dev:pr-review-reporter" %} Generate markdown report. PR_INFO: {% raw %}{{stringify({branch, title, base, github_url: GITHUB_URL, head_sha: HEAD_SHA, reviewed_pr_url: REVIEWED_PR_URL})}}{% endraw %} INTENT_JSON: {{stringify(intent_model)}} JUDGMENT_JSON: {% raw %}{{stringify({judgment, rationale, intent_achieved, intent_gap})}}{% endraw %} FINDINGS_JSON: {{stringify(merged_findings)}} CROSS_FILE_JSON: {{stringify(cross_file_findings)}} STATS_JSON: {{stringify(stats)}} VISUAL_CONTENT: {{VISUAL_CONTENT or ""}} OUTPUT_DIR: {workRoot}/pr-reviews REVIEW_ID: {{review_id}} Return JSON with path. {% enddispatch_agent %}
Fail -> output findings inline
Store REPORTER_FINDINGS for P5 (CI mode)
Register the report artifact after the reporter creates it:
rp1 agent-tools emit \
--workflow pr-review \
--type artifact_registered \
--run-id {RUN_ID} \
--step posting \
--data '{"path": "{REPORT_PATH}", "feature": "{review_id}", "storageRoot": "absolute", "format": "markdown"}'
Skip if CI_MODE=false.
Fetch existing:
echo '{"owner":"{{CI_CONTEXT.owner}}","repo":"{{CI_CONTEXT.repo}}","pr_number":{{CI_CONTEXT.pr_number}}}' | \
rp1 agent-tools github-pr fetch-comments
Parse -> existing_bot_comments, existing_human_comments
Transform findings to deduplicator fmt:
[{"id": "f1", "path": "...", "line": N, "line_end": N, "body": "...", "severity": "...", "dimension": "..."}]
Deduplicate:
{% dispatch_agent "rp1-dev:pr-comment-deduplicator" %} Deduplicate PR comments. NEW_COMMENTS: {{stringify(new_comments)}} EXISTING_BOT_COMMENTS: {{stringify(existing_bot_comments)}} EXISTING_HUMAN_COMMENTS: {{stringify(existing_human_comments)}} BOT_MARKER: {{config.bot_marker}} Return JSON with to_post, to_react, to_augment, duplicates_skipped. {% enddispatch_agent %}
Post:
{% dispatch_agent "rp1-dev:pr-comment-poster" %} Post PR review to GitHub. OWNER: {{CI_CONTEXT.owner}} REPO: {{CI_CONTEXT.repo}} PR_NUMBER: {{CI_CONTEXT.pr_number}} DEDUP_OUTPUT: {{stringify(dedup_output)}} CONFIG: {% raw %}{{stringify({verdict: config.verdict, bot_marker: config.bot_marker, max_comments: config.max_comments, add_comments: config.add_comments})}}{% endraw %} VISUAL_CONTENT: {{VISUAL_CONTENT or ""}} FINDINGS_SUMMARY: {% raw %}{{stringify({critical: N, high: N, medium: N, low: N, total: N})}}{% endraw %} Return JSON with success, review, reactions, replies, summary, errors. {% enddispatch_agent %}
Parse: REVIEW_URL, COMMENTS_POSTED, REACTIONS_ADDED, POSTING_ERRORS
Use this insertable block in any orchestrator that has a curated external URL and a report artifact containing the matching External Links row. Replace every placeholder with workflow-specific values before use:
| Placeholder | Replace with |
|-------------|--------------|
| {WORKFLOW_NAME} | Workflow name passed to --workflow |
| {RUN_ID} | Current tracked run ID |
| {STEP_NAME} | Completion or artifact-registration step for the workflow |
| {LINK_URL} | Canonical http or https URL from structured workflow state |
| {LINK_LABEL} | Human label shown in artifact lists and reports |
| {LINK_RELATIONSHIP} | Stable relationship key such as reviewed_pr |
| {SOURCE_CONTEXT} | Short description of how the workflow resolved the link |
| {SOURCE_ARTIFACT_PATH} | Report path containing the matching External Links row |
| {FEATURE_OR_UNIT_ID} | Workflow-specific artifact grouping value |
rp1 agent-tools emit \
--workflow {WORKFLOW_NAME} \
--type artifact_registered \
--run-id {RUN_ID} \
--step {STEP_NAME} \
--data '{"locationKind":"url","type":"link","storageRoot":"work_dir","url":"{LINK_URL}","label":"{LINK_LABEL}","relationship":"{LINK_RELATIONSHIP}","sourceContext":"{SOURCE_CONTEXT}","sourceArtifactPath":"{SOURCE_ARTIFACT_PATH}","feature":"{FEATURE_OR_UNIT_ID}"}'
{LINK_URL} is empty.PR review is the first concrete use of the reusable block. Apply exactly these substitutions:
| Generic placeholder | PR review value |
|---------------------|-----------------|
| {WORKFLOW_NAME} | pr-review |
| {RUN_ID} | {RUN_ID} |
| {STEP_NAME} | posting |
| {LINK_URL} | {REVIEWED_PR_URL} |
| {LINK_LABEL} | Reviewed PR |
| {LINK_RELATIONSHIP} | reviewed_pr |
| {SOURCE_CONTEXT} | PR review input resolution |
| {SOURCE_ARTIFACT_PATH} | {REPORT_PATH} |
| {FEATURE_OR_UNIT_ID} | {review_id} |
After markdown report registration and after CI comment posting is complete or skipped, register the reviewed PR URL as a non-blocking external link artifact when REVIEWED_PR_URL is known:
rp1 agent-tools emit \
--workflow pr-review \
--type artifact_registered \
--run-id {RUN_ID} \
--step posting \
--data '{"locationKind":"url","type":"link","storageRoot":"work_dir","url":"{REVIEWED_PR_URL}","label":"Reviewed PR","relationship":"reviewed_pr","sourceContext":"PR review input resolution","sourceArtifactPath":"{REPORT_PATH}","feature":"{review_id}"}'
REVIEWED_PR_URL is empty.If STASHED=true: git stash pop
CI Mode:
{{EMOJI}} PR Review Complete (CI Mode)
Judgment: {{JUDGMENT}}
{{RATIONALE}}
Findings: Critical={{critical}}, High={{high}}, Medium={{medium}}, Low={{low}}
{{IF REVIEWED_PR_URL}}Reviewed PR: {{REVIEWED_PR_URL}}{{/IF}}
GitHub Review: {{REVIEW_URL}}
Comments Posted: {{COMMENTS_POSTED}}
Duplicates Skipped: {{dedup_output.duplicates_skipped}}
Reactions Added: {{REACTIONS_ADDED}}
{{IF POSTING_ERRORS}}Errors: {{POSTING_ERRORS}}{{/IF}}
Local Report: {{REPORT_PATH}}
Local Mode:
{{EMOJI}} PR Review Complete
Judgment: {{JUDGMENT}}
{{RATIONALE}}
Findings: Critical={{critical}}, High={{high}}, Medium={{medium}}, Low={{low}}
{{IF REVIEWED_PR_URL}}Reviewed PR: {{REVIEWED_PR_URL}}{{/IF}}
Report: {{REPORT_PATH}}
{{IF STASHED}}Restored stashed changes{{/IF}}
Emoji: approve->check, request_changes->warning, block->stop
§ERR
| Error | Action | |-------|--------| | CI not enabled | Exit w/ config instructions | | Draft PR skipped | Exit w/ message | | Dirty git (local) | Prompt stash/abort | | Unknown branch | Ask user (local) or fail (CI) | | gh unavailable | git-only mode | | Visual fails | Continue w/o (non-blocking) | | Splitter fails | Abort w/ error | | >50% reviewers fail | Abort | | Synthesizer fails | Findings-only judgment | | Reporter fails | Inline output | | fetch-comments fails | Skip P5, warn | | Deduplicator fails | Post all (no dedup) | | Poster fails | Output error, report still generated |
§OUT
<thinking> tagstools
Plan and execute splitting a large PR or branch into a reviewable stacked PR sequence.
documentation
Ask about rp1 capabilities, discover skills, and get workflow guidance.
tools
Generate an evidence-grounded markdown walkthrough for a pull request.
development
Run a bounded, evidence-driven two-agent debate into a separate rp1 debate artifact with backend locks only.