npx claudepluginhub parhumm/jaan-to --plugin jaan-toThis skill is limited to using the following tools:
> Review WordPress plugin pull requests for security, performance, standards, and compatibility.
Creates isolated Git worktrees for feature branches with prioritized directory selection, gitignore safety checks, auto project setup for Node/Python/Rust/Go, and baseline verification.
Executes implementation plans in current session by dispatching fresh subagents per independent task, with two-stage reviews: spec compliance then code quality.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
Review WordPress plugin pull requests for security, performance, standards, and compatibility.
$JAAN_LEARN_DIR/jaan-to-wp-pr-review.learn.md - Past lessons (loaded in Pre-Execution)$JAAN_TEMPLATES_DIR/jaan-to-wp-pr-review.template.md - Report output template$JAAN_OUTPUTS_DIR/research/67-wp-pr-review.md - Full research document${CLAUDE_PLUGIN_ROOT}/docs/extending/language-protocol.md - Language resolution protocolReference files (loaded on demand during review):
references/security-checklist.md - Sanitization, escaping, nonce, capability, DB securityreferences/performance-checklist.md - N+1 queries, autoload, unbounded queries, asset loadingreferences/standards-checklist.md - WPCS naming, Yoda, i18n, prefix, WP API usagereferences/vulnerability-patterns.md - CVE patterns, grep commands, dangerous functionsreferences/addon-ecosystem.md - Hook contracts, API stability, schema changesOutput path: $JAAN_OUTPUTS_DIR/wp/pr/ — ID-based folder pattern.
Arguments: $ARGUMENTS
Input modes:
https://github.com/owner/repo/pull/123 or https://gitlab.com/owner/repo/-/merge_requests/123owner/repo#123 (GitHub) or owner/repo!123 (GitLab)local or empty — uses git diff main...HEAD on current repoMANDATORY — Read and execute ALL steps in: ${CLAUDE_PLUGIN_ROOT}/docs/extending/pre-execution-protocol.md
Skill name: wp-pr-review
Execute: Step 0 (Init Guard) → A (Load Lessons) → B (Resolve Template) → C (Offer Template Seeding)
Read and apply language protocol: ${CLAUDE_PLUGIN_ROOT}/docs/extending/language-protocol.md
Override field for this skill: language_wp-pr-review
Classify $ARGUMENTS:
| Pattern | Mode | Action |
|---|---|---|
https://github.com/.../pull/N | GitHub URL | Extract owner, repo, PR number |
https://gitlab.com/.../-/merge_requests/N | GitLab URL | Extract owner, repo, MR number |
owner/repo#N | GitHub shorthand | Extract owner, repo, PR number |
owner/repo!N | GitLab shorthand | Extract owner, repo, MR number |
local or empty | Local diff | Use current repo, git diff main...HEAD |
Confirm to user:
"Review mode: {mode} | Target: {owner}/{repo} #{number}"
Read project configuration files to understand the plugin under review. For remote PRs, first fetch the PR metadata to understand the repo structure, then read available config files.
composer.json (if exists) — PHP version constraints, dependencies, autoload configphpcs.xml.dist or .phpcs.xml (if exists) — WPCS config: text domain, prefix, minimum WP versionphpstan.neon or phpstan.neon.dist (if exists) — static analysis levelPlugin Name: header. Extract: Requires at least, Requires PHP, Text Domain, Version, Requires PluginsStore and display gathered context:
PROJECT CONTEXT
───────────────
Plugin Name: {name}
Text Domain: {text-domain}
Prefix: {prefix}
Min WP: {version}
Min PHP: {version}
PHPCS Standard: {standard} (or "none detected")
PHPStan Level: {level} (or "none detected")
Based on input mode, fetch the diff using a fallback chain.
GitHub:
gh pr view {number} --repo {owner}/{repo} --json files,additions,deletions,title,body
gh pr diff {number} --repo {owner}/{repo}
GitLab:
glab mr diff {number} --repo {owner}/{repo}
Local:
git diff main...HEAD
git log main..HEAD --oneline
If gh pr diff succeeds, proceed to 2.3.
If gh pr diff fails (HTTP 406 or diff too large), use the REST API:
gh api repos/{owner}/{repo}/pulls/{number}/files --paginate --jq '.[].filename'
This returns filenames only. For each changed .php file, retrieve its patch:
gh api repos/{owner}/{repo}/pulls/{number}/files --paginate \
--jq '.[] | select(.filename == "{file}") | .patch'
If individual patches are unavailable for a file, note it as "patch unavailable — grep-only review" and rely on Step 3 grep patterns against the file content.
Large PR batching: When a PR has more than 50 changed PHP files, process files in batches of 30 to reduce per-call context size. Complete analysis of each batch before loading the next.
Parse the diff (or file list from fallback) to identify:
.php files (primary review targets).js, .css, .json — note but don't deep-review)vendor/, node_modules/, .git/Show summary:
"Diff acquired: {N} PHP files changed (+{additions} / -{deletions} lines)" If fallback was used: "Note: Used paginated API fallback — {N} files retrieved via REST endpoint" If batching active: "Large PR detected ({N} PHP files) — processing in batches of 30"
Read references/vulnerability-patterns.md for the grep pattern catalog.
Run grep patterns against changed PHP files ONLY. This is the high-signal, low-noise first pass.
Batching for large diffs: If the PR has more than 50 changed PHP files, split the file list into batches of 30 and run each grep pattern set per batch. Complete one batch fully before moving to the next. This prevents excessively large grep output from triggering connection resets.
CRITICAL patterns — run all of these:
$_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE$wpdb->query, $wpdb->get_results, $wpdb->get_var, $wpdb->get_row, $wpdb->get_col — grep and check if prepare() is usedunserialize, eval(, assert(, create_function, extract(, shell_exec, exec(, system(, passthru, popenregister_rest_route — check for permission_callbackpermission_callback.*__return_truewp_ajax_ — check for nonce and capabilityWARNING patterns — run these next:
posts_per_page.*-1, nopaging.*truewp_options, wp_posts, etc. instead of $wpdb->prefixwp_redirect without wp_safe_redirect, json_encode without wp_json_encode, file_get_contents, curl_initheader( with LocationStore all grep matches with file paths and line numbers for contextual analysis in Step 4.
For each grep match from Step 3, read the surrounding code context (5-10 lines before and after) and determine:
$_POST access actually sanitized on the same or next line?$wpdb->get_results() call using prepare() in the same statement?echo output properly escaped with esc_html(), esc_attr(), or wp_kses()?file_get_contents() for a local file (acceptable) or remote URL (not acceptable)?is_admin() used alongside current_user_can() (acceptable) or alone as auth (not acceptable)?Read references/standards-checklist.md on demand. Check changed code for:
Check changed code for:
Read references/performance-checklist.md on demand. Check for:
update_option() without explicit autoload parameter for large dataRead references/addon-ecosystem.md on demand. If the PR modifies hooks, public methods, database schema, or option keys:
do_action_deprecated() / apply_filters_deprecated()?Severity classification:
| Condition | Severity |
|---|---|
| Security vulnerability | CRITICAL |
| Data loss possible | CRITICAL |
| PHP fatal error | CRITICAL |
| Broken access control | CRITICAL |
| Significant performance degradation | WARNING |
| Standards violation with functional impact | WARNING |
| Backward compatibility break | WARNING |
| Missing i18n for user strings | WARNING |
| Style/formatting issue only | INFO |
| Improvement suggestion | INFO |
Confidence scoring (0-100):
$wpdb->query() with direct $_POST concatenation)Only include findings with confidence >= 80.
Present the review summary:
PR REVIEW ANALYSIS COMPLETE
────────────────────────────────────
PR: {title} (#{number})
Repository: {owner}/{repo}
Files reviewed: {count} PHP files (+{additions} / -{deletions})
FINDINGS SUMMARY
────────────────
CRITICAL: {count} issues
WARNING: {count} issues
INFO: {count} issues
Filtered: {count} findings below confidence threshold
VERDICT: {APPROVE | REQUEST_CHANGES | COMMENT}
TOP FINDINGS (Preview)
──────────────────────
1. [{severity}] {title} — {file}:{line} (confidence: {score})
2. [{severity}] {title} — {file}:{line} (confidence: {score})
3. [{severity}] {title} — {file}:{line} (confidence: {score})
...
OUTPUT WILL CREATE
──────────────────
- $JAAN_OUTPUTS_DIR/wp/pr/{id}-{slug}/{id}-pr-review-{slug}.md
- Update $JAAN_OUTPUTS_DIR/wp/pr/README.md index
Verdict logic:
REQUEST_CHANGESCOMMENTAPPROVE"Generate full review report? [y/n]"
Do NOT proceed to Phase 2 without explicit approval.
source "${CLAUDE_PLUGIN_ROOT}/scripts/lib/id-generator.sh"
SUBDOMAIN_DIR="$JAAN_OUTPUTS_DIR/wp/pr"
mkdir -p "$SUBDOMAIN_DIR"
NEXT_ID=$(generate_next_id "$SUBDOMAIN_DIR")
Generate slug from PR: {pr-number}-{slugified-pr-title} (max 50 chars, lowercase, hyphens).
OUTPUT_FOLDER="${SUBDOMAIN_DIR}/${NEXT_ID}-${slug}"
MAIN_FILE="${OUTPUT_FOLDER}/${NEXT_ID}-pr-review-${slug}.md"
Preview:
Output Configuration
- ID: {NEXT_ID}
- Folder:
$JAAN_OUTPUTS_DIR/wp/pr/{NEXT_ID}-{slug}/- Main file:
{NEXT_ID}-pr-review-{slug}.md
Read template from $JAAN_TEMPLATES_DIR/jaan-to-wp-pr-review.template.md (if exists) or use the skill's built-in template.md.
Fill all sections:
Before showing to user, verify:
vendor/ or node_modules/ directoriesIf any check fails, fix the report before preview.
Show the complete review report to user.
"Write review report? [y/n]"
If approved:
Create output folder:
mkdir -p "$OUTPUT_FOLDER"
Write main output file to $MAIN_FILE
Update subdomain index:
source "${CLAUDE_PLUGIN_ROOT}/scripts/lib/index-updater.sh"
add_to_index \
"$SUBDOMAIN_DIR/README.md" \
"$NEXT_ID" \
"${NEXT_ID}-${slug}" \
"PR Review: {pr_title}" \
"{executive_summary_one_line}"
Confirm:
"Report written to:
$JAAN_OUTPUTS_DIR/wp/pr/{NEXT_ID}-{slug}/{NEXT_ID}-pr-review-{slug}.md" "Index updated:$JAAN_OUTPUTS_DIR/wp/pr/README.md"
"Would you like to post this review as a comment on the PR?"
This will post a public comment visible to all PR participants.
[1] Post full review as PR comment [2] Post summary only (findings list without code snippets) [3] Skip — do not post
Do NOT post without explicit approval.
If user chooses option 1 or 2, format the review for the PR platform:
GitHub:
gh pr comment {number} --repo {owner}/{repo} --body "{formatted_review}"
GitLab:
glab mr comment {number} --repo {owner}/{repo} --message "{formatted_review}"
Confirm:
"Review posted as comment on {platform} PR #{number}."
"Any feedback on the review? [y/n]"
If yes, invoke /jaan-to:learn-add wp-pr-review "{feedback}" to capture the lesson.
$JAAN_OUTPUTS_DIR path$JAAN_OUTPUTS_DIR/wp/pr/