WordPress.org プラグイン提出スキル
どのプラグインでも wp.org の 自動スキャン と 手動レビュー を
再アップロード無しで通過させます。実レビューサイクル(.gitkeep 一つで全体が落ちるような経験)から抽出した知見です。
メンテナー: moksa · moksaweb.com · ライセンス: MIT
使用するタイミング
以下のようなユーザー要求があった場合にこのスキルを起動します:
- wordpress.org 提出用のクリーンなインストール zip をパッケージング/ビルドする
- プラグインを wp.org ディレクトリに提出/再提出する
- wp.org レビューチームのフィードバックを修正する(4つの定番カテゴリ)
- 自動スキャン拒否(隠しファイル、プレフィックス、DB 呼び出しなど)の診断
- プラグインバージョンの一貫した更新(ヘッダー + readme + 定数)
- アップロード前の事前チェック
以下の場合は使用しないでください:
- WordPress 以外のプロジェクト
- wp.org に提出しないプライベート/内部プラグイン(レビュールールは適用されませんが、セキュリティ慣行は有用です)
2つの関門
- 自動スキャン(アップロード時に実行される Plugin Check)— ハードブロック。人間のレビューに到達するためには必ず通過する必要があります。詳細は
references/automated-scan.mdを参照。 - 手動レビュー(スキャン通過後の人間による確認)— 問題をメールで通知 → 修正後返信。ほぼ常に4つのコードカテゴリ (
references/review-categories.md) プラス リンターでは捕捉できないポリシーガイドライン(ライセンス、リモートコード、トラッキング、管理画面のナグ、商標、外部サービス開示)が含まれます。詳細はreferences/guidelines.mdを参照。
リファレンスマップ
| トピック | ファイル |
|---|---|
| Plugin Check (PCP) 実行手順 — UI/CLI、全31チェックと修正マップ、エラー vs 警告 | references/plugin-check.md |
| 自動スキャンのハードブロック(隠しファイル、DB、許可されない呼び出し) | references/automated-scan.md |
| 手動レビューの4カテゴリと返信の定型文 | references/review-categories.md |
| 実際のレビュー教訓(サンプル→一括修正、3ケースの nonce、phpcs:ignore≠エスケープ、キャスト≠サニタイズ、パス、フォークプレフィックス) | references/real-review-lessons.md |
| すべてのグローバル(関数/オプション/フック/ハンドル/カスタム投稿タイプ/メタ…)のプレフィックス | references/prefixing.md |
サニタイズ・検証・エスケープ + $wpdb->prepare + Webhook 署名 | references/sanitization-escaping.md |
| JS/CSS のエンキュー(依存関係/バージョン、インライン、ゲーティング、ブロック、生タグ禁止) | references/enqueue.md |
| 国際化(textdomain==スラッグ、リテラル文字列、翻訳者コメント、JS) | references/i18n.md |
| 権限とパーミッションコールバック(認可、REST、priv/nopriv) | references/capabilities.md |
| ポリシーガイドライン(ライセンス、リモートコード、トラッキング、ナグ、商標、開示) | references/guidelines.md |
| readme.txt フォーマット仕様(ヘッダー、タグ≤5、短い説明≤150字、スクリーンショット、外部サービス) | references/readme-txt.md |
| Abilities API・AI Client・MCP(能力ごとの認可、データ境界、破壊的操作確認、プロバイダ開示) | references/abilities-ai-mcp.md |
| セキュリティ補足(ファイル/WP_Filesystem、アップロード、SSRF、許可されない/難読化呼び出し) | references/security-extras.md |
| アンインストールとライフサイクル(データクリーンアップ、ユーザーコンテンツ削除禁止、マルチサイト) | references/uninstall.md |
| zip パッケージング + バージョン/ヘッダー/readme 一貫性 + .mo | references/packaging.md |
| スキャン/PHPCS コード → 修正ルックアップ | data/plugin-check-errors.csv |
アップロード前クイックチェックリスト(コピペして確認)
自動スキャン(ハードブロック)
[ ] zip 内に隠しファイルがない: `find . -name '.*' -not -name '.' -not -name '..'` が空
(.gitkeep / .gitignore / .DS_Store / .editorconfig はすべて "hidden_files" で不合格)
[ ] zip 内に node_modules/、.git/、tests/、scripts/、phpcs.xml、webpack.config.js がない
[ ] プラグインが所有するすべてのグローバルが一意の4文字以上のプレフィックスを使用している(wp_ / __ / 単一 _ は不可)
[ ] prepare() なしの直接 $wpdb クエリがない、リモートコード実行がない、ホームへの通信がない
手動レビュー(4カテゴリ)
[ ] エンキュー: 生の echo '<script>'/'<style>' がない;wp_enqueue_* + wp_add_inline_*
[ ] 状態変更を行うすべての AJAX/admin_post に nonce + current_user_can;Webhook はプロバイダ署名を
hash_equals で検証してから使用(WP nonce は不可)
[ ] すべての $_POST/$_GET をサニタイズ(wp_unslash + sanitize_*);json_decode は map_deep;
出力は遅延エスケープ(esc_html/esc_attr/esc_url/wp_kses)
[ ] textdomain == スラッグ;__() の引数はリテラル文字列;/* translators: */ は %s の前
ポリシー(手動レビュー、リンターでは捕捉不可 — references/guidelines.md)
[ ] GPL 互換ライセンスの明記 + LICENSE ファイル;同梱のライブラリ/アセットはすべて GPL 互換
[ ] リモートコード実行なし / CDN から実行可能 JS をロードしない;アセットはローカルにバンドル
[ ] サードパーティ API 呼び出しは readme の "== External services ==" で開示(何のデータ、いつ、URL)
[ ] 明示的なオプトインなしでのデータ収集/トラッキングなし(デフォルト OFF);フロントエンドの
クレジットリンクはオプトイン時のみ;管理画面通知は非表示可能かつ画面スコープ(ナグスパム禁止)
[ ] スラッグ/名前が所有していない商標で始まらない(woocommerce-… / google-… など)
README.TXT(references/readme-txt.md — wp.org readme バリデータで検証)
[ ] ヘッダーが解析可能: Contributors=wp.org ユーザー名、Tags≤5、Stable tag = 実際に出荷するバージョン
[ ] 短い説明≤150文字、プレーンテキスト;Changelog に Stable tag のエントリがある
[ ] スクリーンショット/バナー/アイコンは SVN /assets/ に配置し、zip には含めない
AI / ABILITIES / MCP(プラグインが abilities を登録、LLM を呼び出し、または MCP を公開する場合)
[ ] すべての wp_register_ability に実際の permission_callback(オブジェクトレベルの cap)、__return_true は不可
[ ] データ境界: ability/MCP ツールは現在のユーザーの権限で許可されたデータのみを返す
[ ] モデル/エージェントの入力は信頼できないものとして扱う;破壊的 ability は確認を必須
[ ] LLM/AI SDK は同梱(GPL);プロバイダは == External services == で開示;キーはログに記録しない
[ ] MCP エンドポイントは認証(App Password/OAuth)を行い、各 ability の permission_callback を実行
追加セキュリティ + ライフサイクル
[ ] eval/shell/難読化/extract なし;var_dump/print_r/error_log を残さない
[ ] ファイルは WP_Filesystem 経由でアップロードディレクトリのみ;アップロードは wp_handle_upload + MIME ホワイトリスト
[ ] リモート呼び出しは wp_safe_remote_*(SSRF: 内部範囲をブロック)+ タイムアウト + レスポンスチェック
[ ] uninstall.php は WP_UNINSTALL_PLUGIN でガード;プレフィックス付きデータのみ削除;ユーザーコンテンツは削除しない
パッケージ + メタデータ
[ ] バージョンの同期: メインファイルのヘッダー Version、バージョン定数、readme Stable tag、Changelog
[ ] Requires ヘッダーがメインファイルと readme.txt の両方で一致(WP / PHP / WC)— および
ランタイムの MIN_* 定数(アクティベーションをゲートするもの)
[ ] vendor/ は `composer install --no-dev`(squizlabs/phpunit/phpcompatibility を含まない)
[ ] zip のルートフォルダ名 == プラグインスラッグ
ワークフロー
- タスクに関連するルールを読む(上記のリファレンスマップを参照)—
references/automated-scan.md+references/review-categories.mdから始め、発見内容に合った詳細ガイド(prefixing.md/sanitization-escaping.md/enqueue.md/i18n.md)およびビルド用のreferences/packaging.mdを参照。 - クリーンな zip をビルドする —
scripts/build-zip.shを実行(git archive を処理、開発ファイル + すべてのドットファイルを除去、vendor/を--no-devでインストール、スラッグをルートとして zip 化)。作業ツリーを手動で zip 化しないでください — 開発ゴミやドットファイルが混入する原因です。 - 事前チェック —
scripts/preflight.sh <zip-またはディレクトリへのパス>を実行:隠しファイルをスキャン、バージョン同期とヘッダー/readme の一貫性をチェック、設定されていれば PHPCS を実行。クリーンになるまで修正。 - Plugin Check (PCP) を実行 — 公式ツールでディレクトリ自身の自動チェックをローカルで実行。
wp plugin check <slug> --require=./wp-content/plugins/plugin-check/cli.php(または ツール → Plugin Check)。すべての ERROR を修正、すべての WARNING を確認。全31チェック、フラグ、および各チェックに対応するスキル章についてはreferences/plugin-check.mdを参照。 - アップロード。 自動スキャンで拒否された場合 → 正確なコードを読み、
data/plugin-check-errors.csvでマッピングし、修正、再ビルド、再アップロード。 - 手動レビューへの返信 — レビュアーが問題をメールで通知した場合、それぞれをすべての場所で修正(例示されたものはサンプルです — ツリー全体を grep し、プラグイン全体で Plugin Check + PHPCS を実行;同じカテゴリが再発すると即座に再拒否されます)。その後、変更内容を参照しながら箇条書きで返信。正規の修正方法と返信の定型文については
references/review-categories.mdを、カテゴリごとの一括修正コマンドと初回修正をすり抜けるトラップについてはreferences/real-review-lessons.mdを参照。
苦労して得た落とし穴(高くつくもの)
.gitkeepが自動スキャンを失敗させる。 これは隠しファイルです。パッケージからすべてのドットファイルを削除してください;.gitkeepを git から削除し、git archiveで再追加されないようにします。- Requires-version の乖離。 wp.org はメインファイルのヘッダーと readme.txt の両方を読み取ります;ランタイムの
MIN_WC/MIN_PHP定数でアクティベーションを別途ゲートできます。この3つがすべて一致していないと、バージョンが低すぎるユーザーがインストールに成功した後に機能が壊れている状況に遭遇します。 - Webhook は WP nonce を使用できない。 レビュアーは IPN ハンドラの "no nonce" を指摘します — これは誤検出です;正しい対応は
hash_equals()によるプロバイダ署名の検証です。行ごとのphpcs:ignore+ 理由で文書化してください。 json_decodeはサニタイズではない。 結果には常にmap_deep(sanitize_text_field)を適用してください。composer install(--no-devなし) は phpcs/phpunit を vendor/ に漏洩させ、肥大化したフラグ付きパッケージになります。出荷用 zip では常に--no-devを使用してください。