Initial skill library: 9 skills for delegate briefs
WordPress plugin rebrand/conventions/remote-CLI patterns, Gitea release workflow, bastille jail provisioning, remote shell quoting safety, server fleet map, delegate brief writing, and verification discipline -- all derived from real incidents this session, plus two skills adapted (MIT license, attributed) from obra/superpowers and andrej-karpathy-skills.
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
---
|
||||
name: wordpress-plugin-conventions
|
||||
description: Use when writing or modifying a WordPress plugin's PHP code. Baseline structural/security conventions to follow without being told each time.
|
||||
---
|
||||
|
||||
# WordPress Plugin Conventions
|
||||
|
||||
Baseline conventions for first-party WordPress plugin code. Follow these
|
||||
without needing to be told in every brief.
|
||||
|
||||
## File structure
|
||||
|
||||
```
|
||||
plugin-slug/
|
||||
plugin-slug.php — main file: header, constants, bootstrap class
|
||||
includes/
|
||||
class-<prefix>-*.php — one class per concern, class-based not procedural
|
||||
admin/ — admin-only UI (settings pages, dashboards)
|
||||
assets/{css,js}/
|
||||
languages/ — .pot/.po/.mo if the plugin is translatable
|
||||
```
|
||||
|
||||
## Main file skeleton
|
||||
|
||||
```php
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: ...
|
||||
* Plugin URI: ...
|
||||
* Description: ...
|
||||
* Version: 1.0.0
|
||||
* Author: ...
|
||||
* Author URI: ...
|
||||
* License: GPL v2 or later
|
||||
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||
* Text Domain: plugin-slug
|
||||
* Requires PHP: 7.4
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
define('PREFIX_VERSION', '1.0.0');
|
||||
define('PREFIX_PATH', plugin_dir_path(__FILE__));
|
||||
define('PREFIX_URL', plugin_dir_url(__FILE__));
|
||||
|
||||
require_once PREFIX_PATH . 'includes/class-prefix-thing.php';
|
||||
|
||||
class Prefix_Main {
|
||||
private static $instance = null;
|
||||
public static function instance() {
|
||||
if (null === self::$instance) { self::$instance = new self(); }
|
||||
return self::$instance;
|
||||
}
|
||||
private function __construct() { /* hook registration only */ }
|
||||
public static function activate() { /* create tables/options/dirs */ }
|
||||
public static function deactivate() { /* reverse activate() side effects */ }
|
||||
}
|
||||
register_activation_hook(__FILE__, ['Prefix_Main', 'activate']);
|
||||
register_deactivation_hook(__FILE__, ['Prefix_Main', 'deactivate']);
|
||||
add_action('plugins_loaded', ['Prefix_Main', 'instance']);
|
||||
```
|
||||
|
||||
Every included file starts with `if (!defined('ABSPATH')) exit;` — never
|
||||
`define('ABSPATH', ...) &&` or any variant, exactly the guard-and-exit
|
||||
form, so the file can never be requested directly over HTTP.
|
||||
|
||||
## Every file must pass `php -l` before you consider a task done
|
||||
|
||||
Not optional, not "probably fine" — actually run it, on every changed
|
||||
file, every time. This is the cheapest possible check and catches a
|
||||
meaningful fraction of real mistakes (typos, mismatched braces from a
|
||||
find/replace, etc.) before they ever reach a live site.
|
||||
|
||||
## Security baseline
|
||||
|
||||
- Nonces on every form/AJAX action: `wp_nonce_field()` /
|
||||
`check_admin_referer()` / `check_ajax_referer()`.
|
||||
- Capability checks before any privileged action:
|
||||
`current_user_can('manage_options')` (or the narrowest capability that
|
||||
actually applies — don't default to `manage_options` for things a
|
||||
lower-privileged role should legitimately be able to do).
|
||||
- Escape on output, every time, using the context-correct function:
|
||||
`esc_html()`, `esc_attr()`, `esc_url()`, `esc_js()` — never raw-echo
|
||||
anything that traces back to user input or the database without one of
|
||||
these.
|
||||
- Sanitize on input: `sanitize_text_field()`, `absint()`,
|
||||
`sanitize_email()`, etc. — appropriate to the expected shape of the
|
||||
data, applied at the point the `$_POST`/`$_GET` value is first read.
|
||||
- `$wpdb->prepare()` for every query with a variable in it — no string-
|
||||
interpolated SQL, ever, no exceptions.
|
||||
|
||||
## Database tables
|
||||
|
||||
If a plugin needs its own tables (not just options), use `dbDelta()`:
|
||||
```php
|
||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||
$charset = $wpdb->get_charset_collate();
|
||||
$sql = "CREATE TABLE {$wpdb->prefix}prefix_thing (...) {$charset};";
|
||||
dbDelta($sql);
|
||||
```
|
||||
Track a `DB_VERSION` constant + a stored option so future plugin updates
|
||||
can detect and re-run `dbDelta()` for schema changes.
|
||||
|
||||
## PHP 7.4 compatibility
|
||||
|
||||
Unless a specific brief says otherwise, target PHP 7.4+ (a large fraction
|
||||
of real WordPress hosting is still on 7.4). Avoid PHP 8-only syntax
|
||||
(named arguments, enums, readonly properties, `match`). If a codebase
|
||||
needs `str_contains`/`str_starts_with`/`str_ends_with` (PHP 8.0+) on an
|
||||
environment that might run 7.4, guard with `function_exists()`
|
||||
polyfills rather than assuming they exist.
|
||||
|
||||
## Don't build what WordPress core already gives you
|
||||
|
||||
Before writing custom code for: cron scheduling (`wp_schedule_event`),
|
||||
REST endpoints (`register_rest_route`), settings UI
|
||||
(`register_setting`/`add_settings_field`/`woocommerce_admin_fields` if
|
||||
WooCommerce context), object caching (`wp_cache_*` functions), HTTP
|
||||
requests (`wp_remote_get`/`wp_remote_post`, not raw `curl`) — check
|
||||
whether a core API already does it. Reinventing these is both wasted
|
||||
effort and a common source of subtle bugs core already solved correctly.
|
||||
Reference in New Issue
Block a user