Community生產力與協作github.com

Moksa1123/wporg-plugin-submit

Portable Claude Agent Skill to get any WordPress plugin through the wordpress.org automated scan + manual review — references, build/preflight scripts, code→fix map.

相容平台Claude Code~Codex CLI~Cursor
npx skills add Moksa1123/wporg-plugin-submit

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

說明文件

WordPress.org Plugin Submission Skill

Get any plugin through the wp.org automated scan and manual review without re-uploading five times. Distilled from real review cycles (the kind where a stray .gitkeep fails the whole scan).

Maintainer: moksa · moksaweb.com · License: MIT

When to use

Trigger this skill when the user asks to:

  • Package / build a clean install zip for wordpress.org
  • Submit or resubmit a plugin to the wp.org directory
  • Fix wp.org review-team feedback (the 4 classic categories)
  • Diagnose an automated-scan rejection (hidden files, prefixes, DB calls…)
  • Bump a plugin version consistently (header + readme + constants)
  • Pre-flight a plugin before upload

Do NOT use for:

  • Non-WordPress projects
  • Private/internal plugins never going to wp.org (review rules don't apply, though the security practices still help)

The two gates

  1. Automated scan (Plugin Check, run on upload) — hard blocks. Must pass to even reach a human. See references/automated-scan.md.
  2. Manual review (a human, after scan passes) — emails you issues, you fix + reply. Almost always one of 4 code categories (references/review-categories.md) plus policy guidelines no linter catches — licensing, remote code, tracking, admin nags, trademarks, external-service disclosure (references/guidelines.md).

Reference map

TopicFile
Plugin Check (PCP) runbook — UI/CLI, all 31 checks → fix map, errors vs warningsreferences/plugin-check.md
Automated-scan hard blocks (hidden files, DB, disallowed calls)references/automated-scan.md
The 4 manual-review categories + reply phrasingreferences/review-categories.md
Real-review lessons (sample→sweep, 3-case nonce, phpcs:ignore≠escape, cast≠sanitize, paths, fork prefixes)references/real-review-lessons.md
Prefixing every global (functions/options/hooks/handles/CPT/meta…)references/prefixing.md
Sanitize · validate · escape + $wpdb->prepare + webhook signaturesreferences/sanitization-escaping.md
Enqueue JS/CSS (deps/version, inline, gating, blocks, no raw tags)references/enqueue.md
i18n (textdomain==slug, literal strings, translators comments, JS)references/i18n.md
Capabilities & permission callbacks (authz, REST, priv/nopriv)references/capabilities.md
Policy guidelines (license, remote code, tracking, nags, trademarks, disclosure)references/guidelines.md
readme.txt format spec (headers, tags≤5, short desc≤150, screenshots, External services)references/readme-txt.md
Abilities API · AI Client · MCP (per-ability authz, data boundary, destructive confirm, provider disclosure)references/abilities-ai-mcp.md
Security extras (files/WP_Filesystem, uploads, SSRF, disallowed/obfuscation calls)references/security-extras.md
Uninstall & lifecycle (data cleanup, don't delete user content, multisite)references/uninstall.md
Packaging the zip + version/header/readme consistency + .moreferences/packaging.md
Scan/PHPCS code → fix lookupdata/plugin-check-errors.csv

Quick pre-upload checklist (copy-paste verify)

AUTOMATED SCAN (hard blocks)
[ ] No hidden files in the zip: `find . -name '.*' -not -name '.' -not -name '..'` is EMPTY
    (.gitkeep / .gitignore / .DS_Store / .editorconfig all fail "hidden_files")
[ ] No node_modules/, .git/, tests/, scripts/, phpcs.xml, webpack.config.js in the zip
[ ] All plugin-owned globals use a unique >=4-char prefix (no wp_ / __ / single _ )
[ ] No direct $wpdb queries without prepare(); no remote code execution; no phone-home

MANUAL REVIEW (the 4 categories)
[ ] Enqueue: no raw echo '<script>'/'<style>'; wp_enqueue_* + wp_add_inline_*
[ ] Nonce + current_user_can on every state-changing AJAX/admin_post; webhooks verify
    provider signature with hash_equals before use (no WP nonce possible)
[ ] Every $_POST/$_GET sanitized (wp_unslash + sanitize_*); json_decode -> map_deep;
    output escaped late (esc_html/esc_attr/esc_url/wp_kses)
[ ] textdomain == slug; __() args are literal strings; /* translators: */ before %s

POLICY (manual review, no linter catches these — references/guidelines.md)
[ ] GPL-compatible license stated + LICENSE file; every bundled lib/asset GPL-compatible
[ ] No remote code execution / no loading executable JS from a CDN; bundle assets locally
[ ] Any third-party API call disclosed in readme "== External services ==" (what data, when, URLs)
[ ] No data collection/tracking without explicit opt-in (default OFF); no front-end credit
    link unless opt-in; admin notices dismissible + screen-scoped (no nag spam)
[ ] Slug/name doesn't lead with a trademark you don't own (woocommerce-… / google-…)

README.TXT (references/readme-txt.md — validate at the wp.org readme validator)
[ ] Header parses: Contributors=wp.org usernames, Tags<=5, Stable tag = real shipped version
[ ] Short description <=150 chars, plain text; Changelog has an entry for Stable tag
[ ] Screenshots/banner/icon go in SVN /assets/, NOT in the zip

AI / ABILITIES / MCP (if the plugin registers abilities, calls an LLM, or exposes MCP)
[ ] Every wp_register_ability has a real permission_callback (object-level cap), not __return_true
[ ] Data boundary: an ability/MCP tool returns only what the current user's caps allow
[ ] Model/agent input treated as untrusted; destructive abilities gated behind confirmation
[ ] LLM/AI SDK bundled (GPL); provider disclosed in == External services ==; keys never logged
[ ] MCP endpoint authenticates (App Password/OAuth) AND runs each ability's permission_callback

EXTRA SECURITY + LIFECYCLE
[ ] No eval/shell/obfuscation/extract; no var_dump/print_r/error_log left in
[ ] Files via WP_Filesystem to uploads only; uploads via wp_handle_upload + MIME whitelist
[ ] Remote calls via wp_safe_remote_* (SSRF: block internal ranges) + timeout + response check
[ ] uninstall.php guarded by WP_UNINSTALL_PLUGIN; removes only your prefixed data; never user content

PACKAGE + METADATA
[ ] Version synced: main-file header Version, version constant, readme Stable tag, Changelog
[ ] Requires headers match in BOTH main file AND readme.txt (WP / PHP / WC) — and any
    runtime MIN_* constant that gates activation
[ ] vendor/ is `composer install --no-dev` (no squizlabs/phpunit/phpcompatibility)
[ ] zip root folder name == plugin slug

Workflow

  1. Read the rules for the task (see the Reference map above) — start with references/automated-scan.md + references/review-categories.md, then the deep-dive that matches the finding (prefixing.md / sanitization-escaping.md / enqueue.md / i18n.md) and references/packaging.md for the build.
  2. Build the clean zip — run scripts/build-zip.sh (handles git archive, strips dev files + ALL dotfiles, installs vendor/ --no-dev, zips with slug as root). Never hand-zip the working tree; that's how dev junk and dotfiles leak in.
  3. Preflight — run scripts/preflight.sh <path-to-zip-or-dir>: scans for hidden files, checks version sync + header/readme consistency, runs PHPCS if configured. Fix until clean.
  4. Run Plugin Check (PCP) — the official tool that runs the directory's own automated checks locally. wp plugin check <slug> --require=./wp-content/plugins/plugin-check/cli.php (or Tools → Plugin Check). Fix every ERROR, review every WARNING. See references/plugin-check.md for all 31 checks, flags, and which skill chapter fixes each.
  5. Upload. If the automated scan rejects → read the exact code, map it via data/plugin-check-errors.csv, fix, rebuild, re-upload.
  6. Manual-review reply — when the reviewer emails issues, fix each everywhere (the examples are a sample — grep the whole tree, run Plugin Check + PHPCS over the entire plugin; the same category recurring = instant re-rejection). Then reply point-by-point referencing what changed. See references/review-categories.md for canonical fixes + reply phrasing and references/real-review-lessons.md for the per-category sweep commands and the traps that survive a first fix.

Hard-won gotchas (the expensive ones)

  • .gitkeep fails the automated scan. It's a hidden file. Strip every dotfile from the package; remove .gitkeep from git so git archive never re-adds it.
  • Requires-version drift. wp.org reads the main-file header AND readme.txt; a runtime MIN_WC/MIN_PHP constant can gate activation separately. All three must agree, or a user on a too-low version installs successfully then hits broken features.
  • Webhooks can't use WP nonces. Reviewers flag "no nonce" on IPN handlers — that's a false positive; the correct answer is provider-signature verification with hash_equals(). Document it with a per-line phpcs:ignore + reason.
  • json_decode is not sanitization. Always map_deep(sanitize_text_field) the result.
  • composer install (without --no-dev) leaks phpcs/phpunit into vendor/ → bloated, flagged package. Always --no-dev for the shipped zip.

相關技能