Bug Review v2 Multi-pass PR review agent with 5 parallel review passes, majority voting, independent Opus validation, and resolution rate learning. Posts inline PR comments and optionally generates autofix commits. Tracks whether findings get resolved at merge time and uses that signal to improve future reviews. When to Apply - User asks to review a pull request for bugs or correctness issues - User runs - User runs to classify resolutions after merge - User runs for resolution rate statistics - User asks for code review focused on logic errors, edge cases, or security - User wants to find bu…

\\n'\"$FUNCS\"\n fi\ndone \u003c\u003c\u003c \"$CHANGED_FILES\"\n\nMODIFIED_FUNCTIONS=$(echo \"$MODIFIED_FUNCTIONS\" | sort -u | grep -v '^

Bug Review v2 Multi-pass PR review agent with 5 parallel review passes, majority voting, independent Opus validation, and resolution rate learning. Posts inline PR comments and optionally generates autofix commits. Tracks whether findings get resolved at merge time and uses that signal to improve future reviews. When to Apply - User asks to review a pull request for bugs or correctness issues - User runs - User runs to classify resolutions after merge - User runs for resolution rate statistics - User asks for code review focused on logic errors, edge cases, or security - User wants to find bu…

|| true)\n\n# --- Priority 2: Find callers of modified functions (up to 5) ---\nif [[ -n \"$MODIFIED_FUNCTIONS\" && $BUDGET -gt 0 ]]; then\n CALLER_LIMIT=$((BUDGET \u003c 5 ? BUDGET : 5))\n\n while IFS= read -r func; do\n [[ $CALLER_COUNT -ge $CALLER_LIMIT ]] && break\n [[ -z \"$func\" ]] && continue\n\n # Search for callers, excluding changed files themselves and skip patterns\n CALLERS=$(grep -rlE \"\\b${func}\\b\" --include=\"*.ts\" --include=\"*.js\" --include=\"*.tsx\" --include=\"*.jsx\" --include=\"*.py\" --include=\"*.go\" --include=\"*.rs\" --include=\"*.java\" . 2>/dev/null | \\\n grep -vE \"$SKIP_PATTERN\" | \\\n while IFS= read -r caller; do\n # Exclude the changed files themselves\n IS_CHANGED=false\n while IFS= read -r cf; do\n [[ \"$caller\" == \"./$cf\" || \"$caller\" == \"$cf\" ]] && IS_CHANGED=true\n done \u003c\u003c\u003c \"$CHANGED_FILES\"\n $IS_CHANGED || echo \"$caller\"\n done | head -\"$CALLER_LIMIT\" || true)\n\n while IFS= read -r caller; do\n [[ -z \"$caller\" ]] && continue\n [[ $CALLER_COUNT -ge $CALLER_LIMIT ]] && break\n\n CONTEXT_FILES=$(echo \"$CONTEXT_FILES\" | jq --arg path \"$caller\" --arg reason \"Calls $func\" \\\n '. + [{\"path\": $path, \"relevance\": \"caller\", \"reason\": $reason}]')\n ((CALLER_COUNT++))\n ((BUDGET--))\n done \u003c\u003c\u003c \"$CALLERS\"\n done \u003c\u003c\u003c \"$MODIFIED_FUNCTIONS\"\nfi\n\n# --- Priority 3: Find type definitions imported by changed files (up to 3) ---\nif [[ $BUDGET -gt 0 ]]; then\n TYPE_LIMIT=$((BUDGET \u003c 3 ? BUDGET : 3))\n\n while IFS= read -r file; do\n [[ $TYPE_COUNT -ge $TYPE_LIMIT ]] && break\n [[ -f \"$file\" ]] || continue\n\n # Extract import paths (handles: import {X} from './path', from path import X)\n IMPORTS=$(grep -oE \"(from\\s+['\\\"]\\.?\\.?/[^'\\\"]+['\\\"]|import\\s+['\\\"]\\.?\\.?/[^'\\\"]+['\\\"])\" \"$file\" 2>/dev/null | \\\n sed -E \"s/(from|import)\\s+['\\\"]//; s/['\\\"]$//\" | \\\n grep -vE \"$SKIP_PATTERN\" || true)\n\n while IFS= read -r imp; do\n [[ -z \"$imp\" ]] && continue\n [[ $TYPE_COUNT -ge $TYPE_LIMIT ]] && break\n\n # Resolve relative import to a file path\n DIR=$(dirname \"$file\")\n for ext in \"\" \".ts\" \".tsx\" \".js\" \".jsx\" \"/index.ts\" \"/index.js\"; do\n RESOLVED=\"${DIR}/${imp}${ext}\"\n if [[ -f \"$RESOLVED\" ]]; then\n # Check if it contains type definitions\n if grep -qE \"(interface\\s|type\\s|enum\\s|class\\s)\" \"$RESOLVED\" 2>/dev/null; then\n CONTEXT_FILES=$(echo \"$CONTEXT_FILES\" | jq --arg path \"$RESOLVED\" --arg reason \"Types imported by $file\" \\\n '. + [{\"path\": $path, \"relevance\": \"type-definition\", \"reason\": $reason}]')\n ((TYPE_COUNT++))\n ((BUDGET--))\n fi\n break\n fi\n done\n done \u003c\u003c\u003c \"$IMPORTS\"\n done \u003c\u003c\u003c \"$CHANGED_FILES\"\nfi\n\n# --- Priority 4: Find test files for changed modules (up to 3) ---\nif [[ $BUDGET -gt 0 ]]; then\n TEST_LIMIT=$((BUDGET \u003c 3 ? BUDGET : 3))\n\n while IFS= read -r file; do\n [[ $TEST_COUNT -ge $TEST_LIMIT ]] && break\n [[ -f \"$file\" ]] || continue\n\n BASENAME=$(basename \"$file\" | sed -E 's/\\.[^.]+$//')\n DIR=$(dirname \"$file\")\n\n # Search for test files matching the changed file name\n for pattern in \"${DIR}/${BASENAME}.test.\"* \"${DIR}/${BASENAME}.spec.\"* \"${DIR}/__tests__/${BASENAME}.\"* \"${DIR}/${BASENAME}_test.\"*; do\n [[ $TEST_COUNT -ge $TEST_LIMIT ]] && break\n\n for testfile in $pattern; do\n [[ -f \"$testfile\" ]] || continue\n CONTEXT_FILES=$(echo \"$CONTEXT_FILES\" | jq --arg path \"$testfile\" --arg reason \"Tests for $file\" \\\n '. + [{\"path\": $path, \"relevance\": \"test\", \"reason\": $reason}]')\n ((TEST_COUNT++))\n ((BUDGET--))\n break\n done\n done\n done \u003c\u003c\u003c \"$CHANGED_FILES\"\nfi\n\n# --- Priority 5: Check for .bug-review.md ---\nif [[ -f \".bug-review.md\" && $BUDGET -gt 0 ]]; then\n CONTEXT_FILES=$(echo \"$CONTEXT_FILES\" | jq \\\n '. + [{\"path\": \".bug-review.md\", \"relevance\": \"repo-rules\", \"reason\": \"Repository-specific review rules\"}]')\n ((BUDGET--))\nfi\n\n# Deduplicate by path\nCONTEXT_FILES=$(echo \"$CONTEXT_FILES\" | jq 'unique_by(.path)')\n\nCHANGED_COUNT=$(echo \"$CHANGED_FILES\" | wc -l | tr -d ' ')\nTOTAL=$(echo \"$CONTEXT_FILES\" | jq 'length')\n\n# Output final JSON\njq -n \\\n --argjson files \"$CONTEXT_FILES\" \\\n --arg changed \"$CHANGED_COUNT\" \\\n --arg callers \"$CALLER_COUNT\" \\\n --arg types \"$TYPE_COUNT\" \\\n --arg tests \"$TEST_COUNT\" \\\n --argjson total \"$TOTAL\" \\\n '{\n files: $files,\n stats: {\n changed_files: ($changed | tonumber),\n callers_found: ($callers | tonumber),\n type_defs_found: ($types | tonumber),\n test_files_found: ($tests | tonumber),\n total_context_files: $total\n }\n }'\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":6877,"content_sha256":"03bd964edf02be1b4eda6cc51742ce9f989054b8518bf5d2ed8b6f48d05a8106"},{"filename":"scripts/post-review.sh","content":"#!/usr/bin/env bash\n# post-review.sh — Post findings as a GitHub PR review with inline comments\n# Part of: bug-review\n# Exit codes: 0 = review posted, 1 = error, 2 = no findings to post (skipped)\nset -euo pipefail\n\nif [[ $# -lt 2 ]]; then\n echo \"Usage: $0 \u003cpr-number> \u003cfindings-json-file>\" >&2\n echo \" findings-json-file: path to JSON file with array of findings\" >&2\n exit 1\nfi\n\nPR_NUMBER=\"$1\"\nFINDINGS_FILE=\"$2\"\n\nif [[ ! -f \"$FINDINGS_FILE\" ]]; then\n echo \"Error: Findings file not found: $FINDINGS_FILE\" >&2\n exit 1\nfi\n\nFINDINGS_COUNT=$(jq 'length' \"$FINDINGS_FILE\")\n\nif [[ \"$FINDINGS_COUNT\" -eq 0 ]]; then\n echo \"No findings to post. Skipping review.\"\n exit 2\nfi\n\n# Get the latest commit SHA on the PR (required for creating review)\nCOMMIT_SHA=$(gh pr view \"$PR_NUMBER\" --json headRefOid --jq '.headRefOid')\n\n# Build severity summary\nCRITICAL_COUNT=$(jq '[.[] | select(.severity == \"CRITICAL\")] | length' \"$FINDINGS_FILE\")\nHIGH_COUNT=$(jq '[.[] | select(.severity == \"HIGH\")] | length' \"$FINDINGS_FILE\")\nMEDIUM_COUNT=$(jq '[.[] | select(.severity == \"MEDIUM\")] | length' \"$FINDINGS_FILE\")\nLOW_COUNT=$(jq '[.[] | select(.severity == \"LOW\")] | length' \"$FINDINGS_FILE\")\n\nREVIEW_BODY=\"## Bug Review Summary\n\nFound **${FINDINGS_COUNT}** issue(s): ${CRITICAL_COUNT} critical, ${HIGH_COUNT} high, ${MEDIUM_COUNT} medium, ${LOW_COUNT} low.\n\n\u003c!-- [bug-review] automated review -->\"\n\n# Build inline comments from findings\nCOMMENTS=$(jq -c '[.[] | {\n path: .file,\n line: .line,\n body: (\n \"**\" + .severity + \"**: \" + .title + \"\\n\\n\" +\n .description + \"\\n\\n\" +\n \"**Trigger scenario**: \" + .triggerScenario + \"\\n\\n\" +\n (if .suggestedFix then (\"**Suggested fix**: \" + .suggestedFix + \"\\n\\n\") else \"\" end) +\n \"\u003c!-- [bug-review] -->\"\n )\n}]' \"$FINDINGS_FILE\")\n\n# Create the review via GitHub API\nPAYLOAD=$(jq -n \\\n --arg body \"$REVIEW_BODY\" \\\n --arg sha \"$COMMIT_SHA\" \\\n --argjson comments \"$COMMENTS\" \\\n '{\n commit_id: $sha,\n body: $body,\n event: \"COMMENT\",\n comments: $comments\n }')\n\necho \"$PAYLOAD\" | gh api \"repos/{owner}/{repo}/pulls/$PR_NUMBER/reviews\" \\\n --input - \\\n --method POST >/dev/null 2>&1 || {\n echo \"Error: Failed to post review on PR #$PR_NUMBER\" >&2\n echo \"Hint: Check that your gh token has write permission on the repository\" >&2\n exit 1\n}\n\necho \"Posted review with $FINDINGS_COUNT finding(s) on PR #$PR_NUMBER\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":2371,"content_sha256":"193e7b841869b06b42a72c030bdcc82e190a9febfe5a3cae649cca4291caa2fa"},{"filename":"scripts/resolution-report.sh","content":"#!/usr/bin/env bash\n# resolution-report.sh — Aggregate resolution rate statistics\n# Part of: bug-review\n# Purpose: Generates resolution rate report across all tracked PRs.\n# Shows overall rate, by severity, by category, and trends.\n#\n# Usage: $0 [--json]\n# --json: output JSON instead of markdown\n# Exit codes: 0 = success, 1 = error, 2 = no data\nset -euo pipefail\n\nFORMAT=\"markdown\"\n[[ \"${1:-}\" == \"--json\" ]] && FORMAT=\"json\"\n\nSTORE_DIR=\"${CLAUDE_PLUGIN_DATA:-$HOME/.claude/plugin-data}/bug-review/findings\"\n\nif [[ ! -d \"$STORE_DIR\" ]]; then\n echo \"No bug-review data found. Run /bug-review on a PR first.\" >&2\n exit 2\nfi\n\n# Collect all PR files that have resolutions\nRESOLVED_FILES=$(find \"$STORE_DIR\" -name \"pr-*.json\" -exec grep -l '\"resolutions\"' {} + 2>/dev/null || true)\n\nif [[ -z \"$RESOLVED_FILES\" ]]; then\n echo \"No resolution data found. Run /bug-review:resolve on a merged PR first.\" >&2\n exit 2\nfi\n\n# Aggregate all resolution data\nTOTAL_FINDINGS=0\nTOTAL_RESOLVED=0\nTOTAL_UNRESOLVED=0\nTOTAL_INCONCLUSIVE=0\nPRS_ANALYZED=0\n\n# Per-severity and per-category accumulators (stored as temp files)\nTMPDIR=$(mktemp -d)\ntrap 'rm -rf \"$TMPDIR\"' EXIT\n\nwhile IFS= read -r file; do\n [[ -f \"$file\" ]] || continue\n\n HAS_RESOLUTIONS=$(jq 'has(\"resolutions\") and .resolutions != null and .resolutions.results != null' \"$file\" 2>/dev/null)\n [[ \"$HAS_RESOLUTIONS\" == \"true\" ]] || continue\n\n ((PRS_ANALYZED++))\n\n # Extract findings with their resolutions\n jq -c '.findings as $findings | .resolutions.results[] as $res |\n ($findings | to_entries[] | select(\n (.value.id // (\"F\" + (.key | tostring))) == $res.id\n ) | .value) as $finding |\n {\n severity: ($finding.severity // \"UNKNOWN\"),\n category: ($finding.category // \"unknown\"),\n status: $res.status\n }' \"$file\" 2>/dev/null >> \"$TMPDIR/all_resolutions.jsonl\" || true\n\n # Count per-status\n R=$(jq '.resolutions.summary.resolved // 0' \"$file\")\n U=$(jq '.resolutions.summary.unresolved // 0' \"$file\")\n I=$(jq '.resolutions.summary.inconclusive // 0' \"$file\")\n T=$(jq '.resolutions.summary.total // 0' \"$file\")\n\n TOTAL_FINDINGS=$((TOTAL_FINDINGS + T))\n TOTAL_RESOLVED=$((TOTAL_RESOLVED + R))\n TOTAL_UNRESOLVED=$((TOTAL_UNRESOLVED + U))\n TOTAL_INCONCLUSIVE=$((TOTAL_INCONCLUSIVE + I))\ndone \u003c\u003c\u003c \"$RESOLVED_FILES\"\n\n# Calculate overall rate\nif [[ $TOTAL_FINDINGS -gt 0 ]]; then\n OVERALL_RATE=$(echo \"scale=1; $TOTAL_RESOLVED * 100 / $TOTAL_FINDINGS\" | bc)\nelse\n OVERALL_RATE=\"0.0\"\nfi\n\n# Calculate per-severity rates\nSEVERITY_REPORT=\"[]\"\nfor severity in CRITICAL HIGH MEDIUM LOW; do\n SEV_TOTAL=$(grep -c \"\\\"severity\\\":\\\"$severity\\\"\" \"$TMPDIR/all_resolutions.jsonl\" 2>/dev/null || echo \"0\")\n SEV_RESOLVED=$(grep \"\\\"severity\\\":\\\"$severity\\\"\" \"$TMPDIR/all_resolutions.jsonl\" 2>/dev/null | grep -c '\"status\":\"RESOLVED\"' || echo \"0\")\n\n if [[ \"$SEV_TOTAL\" -gt 0 ]]; then\n SEV_RATE=$(echo \"scale=1; $SEV_RESOLVED * 100 / $SEV_TOTAL\" | bc)\n else\n SEV_RATE=\"0.0\"\n fi\n\n SEVERITY_REPORT=$(echo \"$SEVERITY_REPORT\" | jq \\\n --arg sev \"$severity\" --arg total \"$SEV_TOTAL\" \\\n --arg resolved \"$SEV_RESOLVED\" --arg rate \"$SEV_RATE\" \\\n '. + [{\"severity\": $sev, \"total\": ($total|tonumber), \"resolved\": ($resolved|tonumber), \"rate\": ($rate|tonumber)}]')\ndone\n\n# Calculate per-category rates\nCATEGORY_REPORT=\"[]\"\nif [[ -f \"$TMPDIR/all_resolutions.jsonl\" ]]; then\n CATEGORIES=$(jq -r '.category' \"$TMPDIR/all_resolutions.jsonl\" 2>/dev/null | sort -u || true)\n\n while IFS= read -r cat; do\n [[ -z \"$cat\" ]] && continue\n CAT_TOTAL=$(grep -c \"\\\"category\\\":\\\"$cat\\\"\" \"$TMPDIR/all_resolutions.jsonl\" 2>/dev/null || echo \"0\")\n CAT_RESOLVED=$(grep \"\\\"category\\\":\\\"$cat\\\"\" \"$TMPDIR/all_resolutions.jsonl\" 2>/dev/null | grep -c '\"status\":\"RESOLVED\"' || echo \"0\")\n\n if [[ \"$CAT_TOTAL\" -gt 0 ]]; then\n CAT_RATE=$(echo \"scale=1; $CAT_RESOLVED * 100 / $CAT_TOTAL\" | bc)\n else\n CAT_RATE=\"0.0\"\n fi\n\n CATEGORY_REPORT=$(echo \"$CATEGORY_REPORT\" | jq \\\n --arg cat \"$cat\" --arg total \"$CAT_TOTAL\" \\\n --arg resolved \"$CAT_RESOLVED\" --arg rate \"$CAT_RATE\" \\\n '. + [{\"category\": $cat, \"total\": ($total|tonumber), \"resolved\": ($resolved|tonumber), \"rate\": ($rate|tonumber)}]')\n done \u003c\u003c\u003c \"$CATEGORIES\"\nfi\n\n# Sort categories by rate (ascending — worst performers first)\nCATEGORY_REPORT=$(echo \"$CATEGORY_REPORT\" | jq 'sort_by(.rate)')\n\nif [[ \"$FORMAT\" == \"json\" ]]; then\n jq -n \\\n --arg prs \"$PRS_ANALYZED\" \\\n --arg total \"$TOTAL_FINDINGS\" \\\n --arg resolved \"$TOTAL_RESOLVED\" \\\n --arg unresolved \"$TOTAL_UNRESOLVED\" \\\n --arg inconclusive \"$TOTAL_INCONCLUSIVE\" \\\n --arg rate \"$OVERALL_RATE\" \\\n --argjson severity \"$SEVERITY_REPORT\" \\\n --argjson category \"$CATEGORY_REPORT\" \\\n '{\n prs_analyzed: ($prs|tonumber),\n overall: {\n total: ($total|tonumber),\n resolved: ($resolved|tonumber),\n unresolved: ($unresolved|tonumber),\n inconclusive: ($inconclusive|tonumber),\n resolution_rate: ($rate|tonumber)\n },\n by_severity: $severity,\n by_category: $category\n }'\nelse\n echo \"# Bug Review Resolution Report\"\n echo \"\"\n echo \"**PRs analyzed:** $PRS_ANALYZED\"\n echo \"**Overall resolution rate:** ${OVERALL_RATE}%\"\n echo \"\"\n echo \"| Metric | Count |\"\n echo \"|--------|-------|\"\n echo \"| Total findings | $TOTAL_FINDINGS |\"\n echo \"| Resolved | $TOTAL_RESOLVED |\"\n echo \"| Unresolved | $TOTAL_UNRESOLVED |\"\n echo \"| Inconclusive | $TOTAL_INCONCLUSIVE |\"\n echo \"\"\n echo \"## By Severity\"\n echo \"\"\n echo \"| Severity | Total | Resolved | Rate |\"\n echo \"|----------|-------|----------|------|\"\n echo \"$SEVERITY_REPORT\" | jq -r '.[] | \"| \\(.severity) | \\(.total) | \\(.resolved) | \\(.rate)% |\"'\n echo \"\"\n echo \"## By Category\"\n echo \"\"\n echo \"| Category | Total | Resolved | Rate |\"\n echo \"|----------|-------|----------|------|\"\n echo \"$CATEGORY_REPORT\" | jq -r '.[] | \"| \\(.category) | \\(.total) | \\(.resolved) | \\(.rate)% |\"'\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":5943,"content_sha256":"662fcb66b920d78a745e40b36a62bf4d483286527ff9cf28661412c79602f34c"},{"filename":"scripts/shuffle-diff.sh","content":"#!/usr/bin/env bash\n# shuffle-diff.sh — Randomize file ordering in a unified diff\n# Part of: bug-review\n# Purpose: Each review pass gets a different file ordering to create\n# attention diversity. Earlier files in a diff get more careful\n# review, so shuffling forces different bugs to the \"top.\"\n#\n# Usage: $0 \u003cseed> \u003c input.diff > shuffled.diff\n# Exit codes: 0 = success, 1 = error, 2 = empty diff\nset -euo pipefail\n\nif [[ $# -lt 1 ]]; then\n echo \"Usage: $0 \u003cseed>\" >&2\n echo \" Reads unified diff from stdin, outputs shuffled diff to stdout\" >&2\n echo \" seed: integer seed for deterministic shuffling (use pass number)\" >&2\n exit 1\nfi\n\nSEED=\"$1\"\n\n# Read entire diff from stdin\nDIFF=$(cat)\n\nif [[ -z \"$DIFF\" ]]; then\n echo \"Error: Empty diff on stdin\" >&2\n exit 2\nfi\n\n# Split diff into per-file chunks using \"diff --git\" as delimiter\n# Store each chunk in a temp directory as a numbered file\nTMPDIR=$(mktemp -d)\ntrap 'rm -rf \"$TMPDIR\"' EXIT\n\nCHUNK_NUM=0\nCURRENT_CHUNK=\"\"\n\nwhile IFS= read -r line; do\n if [[ \"$line\" =~ ^diff\\ --git ]]; then\n # Save previous chunk if exists\n if [[ -n \"$CURRENT_CHUNK\" ]]; then\n printf '%s\\n' \"$CURRENT_CHUNK\" > \"$TMPDIR/chunk_$(printf '%04d' $CHUNK_NUM)\"\n ((CHUNK_NUM++))\n fi\n CURRENT_CHUNK=\"$line\"\n else\n if [[ -n \"$CURRENT_CHUNK\" ]]; then\n CURRENT_CHUNK=\"$CURRENT_CHUNK\"

Bug Review v2 Multi-pass PR review agent with 5 parallel review passes, majority voting, independent Opus validation, and resolution rate learning. Posts inline PR comments and optionally generates autofix commits. Tracks whether findings get resolved at merge time and uses that signal to improve future reviews. When to Apply - User asks to review a pull request for bugs or correctness issues - User runs - User runs to classify resolutions after merge - User runs for resolution rate statistics - User asks for code review focused on logic errors, edge cases, or security - User wants to find bu…

\\n'\"$line\"\n fi\n fi\ndone \u003c\u003c\u003c \"$DIFF\"\n\n# Save last chunk\nif [[ -n \"$CURRENT_CHUNK\" ]]; then\n printf '%s\\n' \"$CURRENT_CHUNK\" > \"$TMPDIR/chunk_$(printf '%04d' $CHUNK_NUM)\"\n ((CHUNK_NUM++))\nfi\n\nif [[ $CHUNK_NUM -eq 0 ]]; then\n # Not a git diff format — try splitting on \"---\" lines (plain unified diff)\n echo \"$DIFF\"\n exit 0\nfi\n\n# Generate a shuffled order using the seed\n# Use awk with seeded random to create a permutation\nls \"$TMPDIR\"/chunk_* 2>/dev/null | awk -v seed=\"$SEED\" '\n BEGIN { srand(seed) }\n { print rand() \"\\t\" $0 }\n' | sort -k1,1n | cut -f2- | while IFS= read -r chunk_file; do\n cat \"$chunk_file\"\ndone\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1993,"content_sha256":"00cc5b7149c81d115286c9478a7658bface89b26b5643c00ea039a0c5d7ac138"},{"filename":"scripts/store-findings.sh","content":"#!/usr/bin/env bash\n# store-findings.sh — Persist findings to durable storage for resolution tracking\n# Part of: bug-review\n# Purpose: Save review findings so we can later measure whether they were\n# resolved at merge time (the resolution rate feedback loop).\n#\n# Usage: $0 \u003cpr-number> \u003cfindings-json-file> \u003creview-commit>\n# Exit codes: 0 = success, 1 = error\nset -euo pipefail\n\nif [[ $# -lt 3 ]]; then\n echo \"Usage: $0 \u003cpr-number> \u003cfindings-json-file> \u003creview-commit>\" >&2\n echo \" Stores findings to \\${CLAUDE_PLUGIN_DATA}/bug-review/findings/\" >&2\n exit 1\nfi\n\nPR_NUMBER=\"$1\"\nFINDINGS_FILE=\"$2\"\nREVIEW_COMMIT=\"$3\"\n\nif [[ ! -f \"$FINDINGS_FILE\" ]]; then\n echo \"Error: Findings file not found: $FINDINGS_FILE\" >&2\n exit 1\nfi\n\n# Determine storage directory\nSTORE_DIR=\"${CLAUDE_PLUGIN_DATA:-$HOME/.claude/plugin-data}/bug-review/findings\"\nmkdir -p \"$STORE_DIR\"\n\nTIMESTAMP=$(date -u +\"%Y-%m-%dT%H:%M:%SZ\")\n\n# Get repo info for context\nREPO=$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null || echo \"unknown\")\n\n# Read findings and wrap with metadata\nFINDINGS=$(jq '\n if type == \"array\" then .\n elif .findings then .findings\n else []\n end\n' \"$FINDINGS_FILE\")\n\nFINDINGS_COUNT=$(echo \"$FINDINGS\" | jq 'length')\n\n# Build the stored record\njq -n \\\n --arg pr \"$PR_NUMBER\" \\\n --arg repo \"$REPO\" \\\n --arg commit \"$REVIEW_COMMIT\" \\\n --arg timestamp \"$TIMESTAMP\" \\\n --argjson count \"$FINDINGS_COUNT\" \\\n --argjson findings \"$FINDINGS\" \\\n '{\n pr: ($pr | tonumber),\n repo: $repo,\n reviewCommit: $commit,\n postedAt: $timestamp,\n findingsCount: $count,\n findings: $findings,\n resolutions: null\n }' > \"$STORE_DIR/pr-${PR_NUMBER}.json\"\n\necho \"Stored $FINDINGS_COUNT findings for PR #$PR_NUMBER at $STORE_DIR/pr-${PR_NUMBER}.json\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1777,"content_sha256":"d3cbc26be5d3a0bee2f86c4e0033b8d0d0861cef4a08f1e6987e3c89b2bbbcbe"},{"filename":"scripts/update-weights.sh","content":"#!/usr/bin/env bash\n# update-weights.sh — Learn category weights from resolution rate data\n# Part of: bug-review\n# Purpose: The optimization loop. Categories with low resolution rates\n# (findings that developers don't fix) are likely false-positive-heavy.\n# This script adjusts category_weights in config.json based on actual\n# resolution data, so future reviews deprioritize noisy categories.\n#\n# Usage: $0 [--dry-run] [config-path]\n# --dry-run: show the proposed weight changes without modifying config.json\n# config-path: path to config.json (default: auto-detect from skill dir)\n# Exit codes: 0 = success, 1 = error, 2 = insufficient data\nset -euo pipefail\n\nSCRIPT_DIR=$(dirname \"$0\")\nDRY_RUN=0\nCONFIG_PATH=\"\"\nfor arg in \"$@\"; do\n case \"$arg\" in\n --dry-run) DRY_RUN=1 ;;\n *) CONFIG_PATH=\"$arg\" ;;\n esac\ndone\nCONFIG_PATH=\"${CONFIG_PATH:-$SCRIPT_DIR/../config.json}\"\n\nif [[ ! -f \"$CONFIG_PATH\" ]]; then\n echo \"Error: config.json not found at $CONFIG_PATH\" >&2\n exit 1\nfi\n\n# Get resolution report as JSON\nREPORT=$(bash \"$SCRIPT_DIR/resolution-report.sh\" --json 2>/dev/null) || {\n echo \"Error: Could not generate resolution report. Need at least 1 resolved PR.\" >&2\n exit 2\n}\n\nTOTAL_FINDINGS=$(echo \"$REPORT\" | jq '.overall.total')\nPRS_ANALYZED=$(echo \"$REPORT\" | jq '.prs_analyzed')\n\n# Require minimum data before adjusting weights\nMIN_FINDINGS=10\nMIN_PRS=3\n\nif [[ \"$TOTAL_FINDINGS\" -lt \"$MIN_FINDINGS\" || \"$PRS_ANALYZED\" -lt \"$MIN_PRS\" ]]; then\n echo \"Insufficient data for weight learning (need $MIN_FINDINGS+ findings across $MIN_PRS+ PRs).\" >&2\n echo \" Current: $TOTAL_FINDINGS findings across $PRS_ANALYZED PRs\" >&2\n exit 2\nfi\n\n# Compute weights from category resolution rates\n# Weight = resolution_rate / 100, clamped to [0.1, 1.0]\n# Categories below 30% resolution rate get minimum weight (0.1)\nCATEGORY_WEIGHTS=$(echo \"$REPORT\" | jq '\n .by_category | map({\n key: .category,\n value: (\n if .total \u003c 3 then 1.0 # Not enough data, keep default\n elif .rate \u003c 30 then 0.1 # Likely false-positive-heavy, suppress\n else (.rate / 100) # Scale 0-100% to 0-1.0\n end\n )\n }) | from_entries\n')\n\necho \"Category weights computed from $TOTAL_FINDINGS findings across $PRS_ANALYZED PRs:\"\necho \"$CATEGORY_WEIGHTS\" | jq -r 'to_entries[] | \" \\(.key): \\(.value)\"'\n\n# Show the change (current -> proposed) so the effect is reviewable before applying.\necho \"\"\necho \"Proposed category_weights change (current -> new):\"\njq -n \\\n --argjson cur \"$(jq '.category_weights // {}' \"$CONFIG_PATH\")\" \\\n --argjson new \"$CATEGORY_WEIGHTS\" '\n (($cur + $new) | keys_unsorted)\n | map({ key: ., value: { old: ($cur[.] // \"default\"), new: ($new[.] // \"unchanged\") } })\n | from_entries' \\\n | jq -r 'to_entries[] | \" \\(.key): \\(.value.old) -> \\(.value.new)\"'\n\nif [[ \"$DRY_RUN\" -eq 1 ]]; then\n echo \"\"\n echo \"Dry run — no changes written. Re-run without --dry-run to apply.\"\n exit 0\nfi\n\n# Back up before overwriting, then update config.json with new weights.\ncp \"$CONFIG_PATH\" \"${CONFIG_PATH}.bak\"\njq --argjson weights \"$CATEGORY_WEIGHTS\" '.category_weights = $weights' \"$CONFIG_PATH\" > \"${CONFIG_PATH}.tmp\" \\\n && mv \"${CONFIG_PATH}.tmp\" \"$CONFIG_PATH\"\n\necho \"\"\necho \"Updated $CONFIG_PATH with learned category weights (backup: ${CONFIG_PATH}.bak).\"\n\n# Flag suppressed categories\nSUPPRESSED=$(echo \"$CATEGORY_WEIGHTS\" | jq -r 'to_entries[] | select(.value \u003c= 0.1) | .key')\nif [[ -n \"$SUPPRESSED\" ]]; then\n echo \"\"\n echo \"WARNING: These categories have been suppressed (resolution rate \u003c 30%):\"\n while IFS= read -r cat; do\n RATE=$(echo \"$REPORT\" | jq -r --arg c \"$cat\" '.by_category[] | select(.category == $c) | .rate')\n echo \" - $cat (${RATE}% resolution rate)\"\n done \u003c\u003c\u003c \"$SUPPRESSED\"\n echo \" They will have minimal impact on future review scoring.\"\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":3838,"content_sha256":"c89d7fca542102e83240abbccefe4afb9c70efce99e1ab0c8742d927907cf3d7"},{"filename":"scripts/verify.sh","content":"#!/usr/bin/env bash\n# verify.sh — Verify bug review was posted successfully\n# Part of: bug-review\nset -euo pipefail\n\nif [[ $# -lt 1 ]]; then\n echo \"Usage: $0 \u003cpr-number>\" >&2\n exit 1\nfi\n\nPR_NUMBER=\"$1\"\n\nPASS=0\nFAIL=0\n\nassert_true() {\n local label=\"$1\" condition=\"$2\"\n if [[ \"$condition\" == \"true\" ]]; then\n echo \" PASS: $label\"\n ((PASS++))\n else\n echo \" FAIL: $label\"\n ((FAIL++))\n fi\n}\n\n# Check that at least one [bug-review] review exists on the PR\nREVIEWS_RAW=$(gh api \"repos/{owner}/{repo}/pulls/$PR_NUMBER/reviews\" --paginate 2>&1) || {\n echo \" ERROR: Could not fetch reviews — API call failed: $REVIEWS_RAW\" >&2\n exit 1\n}\nREVIEW_COUNT=$(echo \"$REVIEWS_RAW\" | jq '[.[] | select(.body | contains(\"[bug-review]\"))] | length')\n\nassert_true \"At least one [bug-review] review exists\" \\\n \"$([ \"$REVIEW_COUNT\" -gt 0 ] && echo true || echo false)\"\n\n# Check that review comments exist\nCOMMENTS_RAW=$(gh api \"repos/{owner}/{repo}/pulls/$PR_NUMBER/comments\" --paginate 2>&1) || {\n echo \" ERROR: Could not fetch comments — API call failed: $COMMENTS_RAW\" >&2\n exit 1\n}\nCOMMENT_COUNT=$(echo \"$COMMENTS_RAW\" | jq '[.[] | select(.body | contains(\"[bug-review]\"))] | length')\n\nassert_true \"Review has inline comments\" \\\n \"$([ \"$COMMENT_COUNT\" -gt 0 ] && echo true || echo false)\"\n\necho \"\"\necho \"Results: $PASS passed, $FAIL failed\"\necho \"Reviews: $REVIEW_COUNT, Inline comments: $COMMENT_COUNT\"\n[[ $FAIL -eq 0 ]] || exit 1\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1445,"content_sha256":"71994eb05df12ab02d68139d57d2b9a8e9b86b5541371dcd7ba1148a48731225"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Bug Review v2","type":"text"}]},{"type":"paragraph","content":[{"text":"Multi-pass PR review agent with 5 parallel review passes, majority voting, independent Opus validation, and resolution rate learning. Posts inline PR comments and optionally generates autofix commits. Tracks whether findings get resolved at merge time and uses that signal to improve future reviews.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to Apply","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User asks to review a pull request for bugs or correctness issues","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User runs ","type":"text"},{"text":"/bug-review \u003cPR-number-or-URL>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User runs ","type":"text"},{"text":"/bug-review:resolve \u003cPR>","type":"text","marks":[{"type":"code_inline"}]},{"text":" to classify resolutions after merge","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User runs ","type":"text"},{"text":"/bug-review:report","type":"text","marks":[{"type":"code_inline"}]},{"text":" for resolution rate statistics","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User asks for code review focused on logic errors, edge cases, or security","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User wants to find bugs in a diff or set of changes","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Setup","type":"text"}]},{"type":"paragraph","content":[{"text":"On first run, verify:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"gh","type":"text","marks":[{"type":"code_inline"}]},{"text":" CLI is installed and authenticated (","type":"text"},{"text":"gh auth status","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Current directory is a git repo with a GitHub remote","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"jq","type":"text","marks":[{"type":"code_inline"}]},{"text":" is installed (for JSON processing)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"bc","type":"text","marks":[{"type":"code_inline"}]},{"text":" is installed (for resolution rate calculations; pre-installed on most systems)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Read ","type":"text"},{"text":"config.json","type":"text","marks":[{"type":"link","attrs":{"href":"config.json","title":null}}]},{"text":" for configuration (passes, vote threshold, models, category weights).","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Workflow Overview","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"/bug-review \u003cPR>\n |\n v\nFetch PR context + gather-context.sh\n |\n v\n5 parallel passes (shuffled diffs, Sonnet) --> Aggregate & vote (3/5 majority)\n |\n v\nIndependent Opus validator --> Dedup --> Present findings --> Post + store\n |\n (later, after merge)\n v\n/bug-review:resolve \u003cPR> --> Classify resolutions --> Update category weights","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Command: /bug-review \u003cPR>","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 1: Parse Input & Fetch Context","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Parse the PR identifier (number, URL, or branch name)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check cache","type":"text","marks":[{"type":"strong"}]},{"text":": Look for ","type":"text"},{"text":"${CLAUDE_PLUGIN_DATA}/bug-review/cache/pr-{N}/","type":"text","marks":[{"type":"code_inline"}]},{"text":" — if cache exists for the same head commit, offer to resume from the last checkpoint","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"scripts/fetch-pr.sh \u003cpr-identifier>","type":"text","marks":[{"type":"code_inline"}]},{"text":" to get PR diff + metadata as JSON","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Save the diff to a temp file for shuffling","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"scripts/gather-context.sh \u003cchanged-files-json>","type":"text","marks":[{"type":"code_inline"}]},{"text":" to get prioritized context (callers, types, tests, repo rules)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read ","type":"text"},{"text":".bug-review.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" from repo root if it exists","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Save checkpoint","type":"text","marks":[{"type":"strong"}]},{"text":": Write context to ","type":"text"},{"text":"${CLAUDE_PLUGIN_DATA}/bug-review/cache/pr-{N}/context.json","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 2: Run 5 Parallel Review Passes","type":"text"}]},{"type":"paragraph","content":[{"text":"For each pass (1-5), prepare a shuffled diff:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"scripts/shuffle-diff.sh \u003cpass-number> \u003c pr.diff > pass-\u003cN>.diff","type":"text"}]},{"type":"paragraph","content":[{"text":"Launch ","type":"text"},{"text":"5 Agent subprocesses in parallel","type":"text","marks":[{"type":"strong"}]},{"text":". Read ","type":"text"},{"text":"review-passes.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/review-passes.md","title":null}}]},{"text":" for the exact prompt for each pass.","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Pass 1","type":"text","marks":[{"type":"strong"}]},{"text":": Logic & Edge Cases (seed 1)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Pass 2","type":"text","marks":[{"type":"strong"}]},{"text":": Security & Data Integrity (seed 2)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Pass 3","type":"text","marks":[{"type":"strong"}]},{"text":": Error Handling & API Contracts (seed 3)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Pass 4","type":"text","marks":[{"type":"strong"}]},{"text":": Concurrency & State (seed 4)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Pass 5","type":"text","marks":[{"type":"strong"}]},{"text":": Data Flow & Contracts (seed 5)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"model","type":"text","marks":[{"type":"code_inline"}]},{"text":" from config.json ","type":"text"},{"text":"agent_model","type":"text","marks":[{"type":"code_inline"}]},{"text":" (default: ","type":"text"},{"text":"\"sonnet\"","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]},{"type":"paragraph","content":[{"text":"Each agent returns a JSON array of findings.","type":"text"}]},{"type":"paragraph","content":[{"text":"Save checkpoint","type":"text","marks":[{"type":"strong"}]},{"text":": Write all pass results to ","type":"text"},{"text":"${CLAUDE_PLUGIN_DATA}/bug-review/cache/pr-{N}/pass-results.json","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 3: Aggregate & Vote","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Collect findings from all 5 passes","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Group findings by similarity: same file + line within +/-5 + same or related category","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Count votes per group","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Keep only findings with 3+ votes","type":"text","marks":[{"type":"strong"}]},{"text":" (majority of 5, configurable via ","type":"text"},{"text":"vote_threshold","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Apply ","type":"text"},{"text":"category weights","type":"text","marks":[{"type":"strong"}]},{"text":" from config.json: ","type":"text"},{"text":"final_score = votes × severity_weight × category_weight","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Categories with weight \u003c 0.1 are suppressed entirely","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Rank by final_score descending","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"If only 1-2 passes found bugs and the others found none, present findings but note they lack consensus.","type":"text"}]},{"type":"paragraph","content":[{"text":"Save checkpoint","type":"text","marks":[{"type":"strong"}]},{"text":": Write voted findings to cache.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 4: Independent Validation (Opus)","type":"text"}]},{"type":"paragraph","content":[{"text":"Launch a ","type":"text"},{"text":"separate Agent","type":"text","marks":[{"type":"strong"}]},{"text":" using ","type":"text"},{"text":"validator_model","type":"text","marks":[{"type":"code_inline"}]},{"text":" from config.json (default: ","type":"text"},{"text":"\"opus\"","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]},{"type":"paragraph","content":[{"text":"This agent has NOT seen the review passes. It receives only the voted findings and the original code. Read the Validator section in ","type":"text"},{"text":"review-passes.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/review-passes.md","title":null}}]},{"text":" for the prompt.","type":"text"}]},{"type":"paragraph","content":[{"text":"For each finding, the validator outputs: ","type":"text"},{"text":"{id, verdict: \"KEEP\"|\"DISCARD\", confidence, reasoning}","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"Remove DISCARDed findings. Multiply each finding's score by the validator's confidence.","type":"text"}]},{"type":"paragraph","content":[{"text":"Compute each finding's final ","type":"text"},{"text":"confidence","type":"text","marks":[{"type":"code_inline"}]},{"text":" field:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"confidence = (votes / total_passes) × validator_confidence","type":"text"}]},{"type":"paragraph","content":[{"text":"Findings with confidence \u003c 0.5 are shown with a \"low confidence\" warning.","type":"text"}]},{"type":"paragraph","content":[{"text":"Save checkpoint","type":"text","marks":[{"type":"strong"}]},{"text":": Write validated findings to cache.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 5: Dedup Against Prior Reviews","type":"text"}]},{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"scripts/dedup.sh \u003cpr-number>","type":"text","marks":[{"type":"code_inline"}]},{"text":" to get existing ","type":"text"},{"text":"[bug-review]","type":"text","marks":[{"type":"code_inline"}]},{"text":" comments. Match by location proximity (file + line within +/-10) and category — not text similarity.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 6: Present Findings to User","type":"text"}]},{"type":"paragraph","content":[{"text":"Display a table:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"#","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Severity","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Confidence","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"File","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Line","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Title","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Votes","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"For each finding, show full description, trigger scenario, suggested fix, and validator reasoning.","type":"text"}]},{"type":"paragraph","content":[{"text":"Ask the user (using AskUserQuestion with multiSelect):","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Which findings to ","type":"text"},{"text":"post as PR comments","type":"text","marks":[{"type":"strong"}]},{"text":" (default: all)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Which findings to ","type":"text"},{"text":"autofix","type":"text","marks":[{"type":"strong"}]},{"text":" (default: none)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"If no findings survived voting + validation: \"No bugs found across 5 review passes. The changes look clean.\"","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 7a: Post PR Review","type":"text"}]},{"type":"paragraph","content":[{"text":"Write approved findings to a temporary JSON file, then run:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"scripts/post-review.sh \u003cpr-number> \u003cfindings-json-file>","type":"text"}]},{"type":"paragraph","content":[{"text":"Then persist findings for resolution tracking:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"scripts/store-findings.sh \u003cpr-number> \u003cfindings-json-file> \u003chead-commit-sha>","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 7b: Autofix (User-Selected Findings)","type":"text"}]},{"type":"paragraph","content":[{"text":"For each finding selected for autofix:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read the file and understand surrounding context","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Generate a ","type":"text"},{"text":"minimal fix","type":"text","marks":[{"type":"strong"}]},{"text":" (smallest possible change)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Apply the fix using the Edit tool","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Scope check","type":"text","marks":[{"type":"strong"}]},{"text":": Run ","type":"text"},{"text":"git diff --stat","type":"text","marks":[{"type":"code_inline"}]},{"text":" — verify only the finding's file was modified and diff is under 20 lines. If exceeded, revert and warn.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run existing tests if available (","type":"text"},{"text":"npm test","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"go test ./...","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"pytest","type":"text","marks":[{"type":"code_inline"}]},{"text":", etc.)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If tests pass: commit with ","type":"text"},{"text":"fix: {title} [bug-review]","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If tests fail: revert the fix (","type":"text"},{"text":"git checkout -- \u003cfile>","type":"text","marks":[{"type":"code_inline"}]},{"text":") and report to user","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"After all fixes: push to the PR branch","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Safety: one commit per fix, run tests between fixes, never force-push, scope-validate every fix.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Command: /bug-review:resolve \u003cPR>","type":"text"}]},{"type":"paragraph","content":[{"text":"Run after a PR is merged to classify whether findings were resolved.","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"scripts/classify-resolutions.sh \u003cpr-number>","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Loads stored findings from ","type":"text"},{"text":"${CLAUDE_PLUGIN_DATA}/bug-review/findings/pr-{N}.json","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Checks if PR is merged","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For each finding: diffs code between review commit and merge commit","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Classifies each as RESOLVED, UNRESOLVED, or INCONCLUSIVE","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Updates the stored findings file with resolution data","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Display resolution summary to user","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If enough data accumulated (10+ findings, 3+ PRs): run ","type":"text"},{"text":"scripts/update-weights.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" to adjust category weights","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Command: /bug-review:report","type":"text"}]},{"type":"paragraph","content":[{"text":"Display resolution rate statistics across all tracked PRs.","type":"text"}]},{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"scripts/resolution-report.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" which outputs:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Overall resolution rate","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Resolution rate by severity","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Resolution rate by category (sorted worst-first to highlight noisy categories)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Suppressed categories (weight \u003c 0.1)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Repo-Specific Rules (.bug-review.md)","type":"text"}]},{"type":"paragraph","content":[{"text":"Teams can create ","type":"text"},{"text":".bug-review.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" at their repo root:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"markdown"},"content":[{"text":"## Focus Areas\n- Pay special attention to authentication flows\n- Check all database queries for SQL injection\n\n## Ignore\n- Don't flag issues in generated files (*.generated.ts)\n- Ignore style-only concerns\n\n## Invariants\n- All API endpoints must check req.user before accessing user data\n- Database migrations must be reversible\n\n## Severity Overrides\n- Treat any auth bypass as CRITICAL regardless of category default","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"How to Use","type":"text"}]},{"type":"paragraph","content":[{"text":"Read ","type":"text"},{"text":"workflow.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/workflow.md","title":null}}]},{"text":" for detailed step-by-step with error handling. Read ","type":"text"},{"text":"review-passes.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/review-passes.md","title":null}}]},{"text":" for all 5 review pass prompts and the validator. Read ","type":"text"},{"text":"categories.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/categories.md","title":null}}]},{"text":" for bug categories and learned weights.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Related Skills","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Consider creating a ","type":"text"},{"text":"Runbook","type":"text","marks":[{"type":"strong"}]},{"text":" skill for investigating bugs found by this review","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Consider creating a ","type":"text"},{"text":"CI/CD","type":"text","marks":[{"type":"strong"}]},{"text":" skill to run this review automatically on PR open","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"bug-review","author":"@skillopedia","source":{"stars":153,"repo_name":"dot-skills","origin_url":"https://github.com/pproenca/dot-skills/blob/HEAD/skills/.experimental/bug-review/SKILL.md","repo_owner":"pproenca","body_sha256":"6c64e8ed972f66e727732093b43f59bc68de998df7254070bdc36cbc55a58557","cluster_key":"819a4af9a05bbe715b5c2a2c8b86dd4eaf83f62c26359635ead332cf782fc88d","clean_bundle":{"format":"clean-skill-bundle-v1","source":"pproenca/dot-skills/skills/.experimental/bug-review/SKILL.md","attachments":[{"id":"dfe37280-20c7-5d9f-a54d-2262c5113a04","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/dfe37280-20c7-5d9f-a54d-2262c5113a04/attachment.yml","path":".github/workflows/bug-review-resolve.yml","size":2791,"sha256":"804f1661e99bc221c3e3de06a90825946554d89c5133b51e1aee0d29aee8dadb","contentType":"application/yaml; charset=utf-8"},{"id":"9800f806-1ca4-56f8-b36b-4ec05bd7840d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9800f806-1ca4-56f8-b36b-4ec05bd7840d/attachment.json","path":"config.json","size":1192,"sha256":"9b5b958c9d8256e060bfa6d8be3e7424a075e0e2f66052119c0bc0afe67d8d79","contentType":"application/json; charset=utf-8"},{"id":"e576fa3f-fae5-587c-afc0-a6d97302e215","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e576fa3f-fae5-587c-afc0-a6d97302e215/attachment.md","path":"gotchas.md","size":106,"sha256":"c69c2e2780cb0e1d4bd1ad054d981985a3fb0ed197f96a4590bb666876184cb4","contentType":"text/markdown; charset=utf-8"},{"id":"30f4c231-b4bf-524b-a6dc-f4b5b818227f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/30f4c231-b4bf-524b-a6dc-f4b5b818227f/attachment.json","path":"hooks/hooks.json","size":1029,"sha256":"0cd34bba5ed5623fae601208ee010ae0283e47764278170e44f7b0e16dfae3e6","contentType":"application/json; charset=utf-8"},{"id":"4468c9c8-3d6a-55ec-b3a4-f8a85612c006","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4468c9c8-3d6a-55ec-b3a4-f8a85612c006/attachment.json","path":"metadata.json","size":766,"sha256":"3e8d5743d9d5ac2ce48048a2cf77c5bd53d06c795600344d91b29e8af469e586","contentType":"application/json; charset=utf-8"},{"id":"7b63166d-d6d8-5594-8143-f99b8ec5ead7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7b63166d-d6d8-5594-8143-f99b8ec5ead7/attachment.md","path":"references/categories.md","size":6207,"sha256":"2ff7eb71e9ae3c5815615225a319779c1bc357de4c064c049a629982bceaa4b1","contentType":"text/markdown; charset=utf-8"},{"id":"9f9c5b5f-22cc-5713-8bad-a0dee6e2f1d7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9f9c5b5f-22cc-5713-8bad-a0dee6e2f1d7/attachment.md","path":"references/review-passes.md","size":11945,"sha256":"76d3deb415080715e6688ac9bee049040c1be920395a494b6741825afe7ef995","contentType":"text/markdown; charset=utf-8"},{"id":"d82feba7-2f1e-54dd-bc81-73c734745d7a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d82feba7-2f1e-54dd-bc81-73c734745d7a/attachment.md","path":"references/workflow.md","size":4780,"sha256":"0b992d4eefbed2fdb6ce5b38f4a4c5008a082a71a60e46357572a594d5027d70","contentType":"text/markdown; charset=utf-8"},{"id":"c2b6c522-41c1-518b-9cb0-b582efc1dc0f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c2b6c522-41c1-518b-9cb0-b582efc1dc0f/attachment.sh","path":"scripts/classify-resolutions.sh","size":5392,"sha256":"0fc7397e072fe0632277aac0ff6db6e49798ad0d7d22da00eef9177a0d400958","contentType":"application/x-sh; charset=utf-8"},{"id":"91765728-f126-5091-82a8-80e74add8bcb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/91765728-f126-5091-82a8-80e74add8bcb/attachment.sh","path":"scripts/dedup.sh","size":1253,"sha256":"b139a8c323d119aad0d75a60a3974c6a5f38c27bb66e8df09944b7553bed1613","contentType":"application/x-sh; charset=utf-8"},{"id":"c68fe4e5-df27-52f9-8a57-0b796075d15a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c68fe4e5-df27-52f9-8a57-0b796075d15a/attachment.sh","path":"scripts/fetch-pr.sh","size":1734,"sha256":"21f2d8715ad5e03c4d79d6912c7f57d0e3275b6d280ee4aa9ebf5abbe210b90b","contentType":"application/x-sh; charset=utf-8"},{"id":"03fcf1cc-bde2-51a0-bacc-044ff649b8b1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/03fcf1cc-bde2-51a0-bacc-044ff649b8b1/attachment.sh","path":"scripts/gather-context.sh","size":6877,"sha256":"03bd964edf02be1b4eda6cc51742ce9f989054b8518bf5d2ed8b6f48d05a8106","contentType":"application/x-sh; charset=utf-8"},{"id":"38767391-a73b-5664-b74f-2d003684153d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/38767391-a73b-5664-b74f-2d003684153d/attachment.sh","path":"scripts/post-review.sh","size":2371,"sha256":"193e7b841869b06b42a72c030bdcc82e190a9febfe5a3cae649cca4291caa2fa","contentType":"application/x-sh; charset=utf-8"},{"id":"574d535f-a2fd-5152-91da-3d355b081d4c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/574d535f-a2fd-5152-91da-3d355b081d4c/attachment.sh","path":"scripts/resolution-report.sh","size":5943,"sha256":"662fcb66b920d78a745e40b36a62bf4d483286527ff9cf28661412c79602f34c","contentType":"application/x-sh; charset=utf-8"},{"id":"d021030c-4f59-522f-916e-c59240da8c12","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d021030c-4f59-522f-916e-c59240da8c12/attachment.sh","path":"scripts/shuffle-diff.sh","size":1993,"sha256":"00cc5b7149c81d115286c9478a7658bface89b26b5643c00ea039a0c5d7ac138","contentType":"application/x-sh; charset=utf-8"},{"id":"dbf9e920-f264-5713-b7c1-bec778649fe9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/dbf9e920-f264-5713-b7c1-bec778649fe9/attachment.sh","path":"scripts/store-findings.sh","size":1777,"sha256":"d3cbc26be5d3a0bee2f86c4e0033b8d0d0861cef4a08f1e6987e3c89b2bbbcbe","contentType":"application/x-sh; charset=utf-8"},{"id":"6d10d297-c540-5eb3-8994-a8d550c0dce7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6d10d297-c540-5eb3-8994-a8d550c0dce7/attachment.sh","path":"scripts/update-weights.sh","size":3838,"sha256":"c89d7fca542102e83240abbccefe4afb9c70efce99e1ab0c8742d927907cf3d7","contentType":"application/x-sh; charset=utf-8"},{"id":"d17bb1c7-5d65-5288-8aed-0aabb3047a45","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d17bb1c7-5d65-5288-8aed-0aabb3047a45/attachment.sh","path":"scripts/verify.sh","size":1445,"sha256":"71994eb05df12ab02d68139d57d2b9a8e9b86b5541371dcd7ba1148a48731225","contentType":"application/x-sh; charset=utf-8"}],"bundle_sha256":"d20c284a4473f4e90f57491fde192f05a594c39fc6e22461338700a8c55fbb52","attachment_count":18,"text_attachments":18,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":2,"skill_md_path":"skills/.experimental/bug-review/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"software-engineering","category_label":"Engineering"},"exact_dupes_collapsed_into_this":1},"version":"v1","category":"software-engineering","import_tag":"clean-skills-v1","description":"Multi-pass PR bug review — 5 parallel passes, majority voting, independent Opus validation, and resolution rate tracking. Trigger on PR review, bug finding, code review, \"review this PR\", \"check for bugs\", \"find issues in this PR\", or /bug-review. Also trigger on /bug-review:resolve to classify whether findings were fixed at merge time, and /bug-review:report for resolution rate stats. Even if the user just says \"review this\" while on a PR branch, trigger this skill."}},"renderedAt":1782980517007}

Bug Review v2 Multi-pass PR review agent with 5 parallel review passes, majority voting, independent Opus validation, and resolution rate learning. Posts inline PR comments and optionally generates autofix commits. Tracks whether findings get resolved at merge time and uses that signal to improve future reviews. When to Apply - User asks to review a pull request for bugs or correctness issues - User runs - User runs to classify resolutions after merge - User runs for resolution rate statistics - User asks for code review focused on logic errors, edge cases, or security - User wants to find bu…