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,137 @@
|
||||
---
|
||||
name: wordpress-plugin-rebrand
|
||||
description: Use when forking and rebranding an existing WordPress plugin under a new name/prefix (e.g. for a white-label or iWP-branded resale product). Covers identifier renaming, repo flattening, updater bundling, and license-field wiring.
|
||||
---
|
||||
|
||||
# WordPress Plugin Rebrand
|
||||
|
||||
Process for taking an existing WordPress plugin and rebranding it under a
|
||||
new name/prefix, without breaking anything or missing a spot. Validated
|
||||
live across 5 real plugins in one session (WooList, WooBC, WooSol,
|
||||
SlickWoo, MetAI → iWP-branded forks), each independently re-verified
|
||||
end-to-end afterward with zero defects found.
|
||||
|
||||
## Inputs you need before starting
|
||||
|
||||
- Source repo (clone URL + credentials).
|
||||
- New brand prefix (e.g. `iWP`), new product slug (e.g. `iwp-woolist`) —
|
||||
**get the exact slug from wherever the catalog/product list already
|
||||
defines it, never invent your own.**
|
||||
- The updater/license-client file to bundle, if the target platform has
|
||||
one (e.g. a shared `IWP_Updater` class), and its exact integration
|
||||
snippet (api_url, plugin_slug, license_option names).
|
||||
|
||||
## Step 1 — Learn the actual existing convention, don't assume
|
||||
|
||||
Read the plugin's main file first. Every plugin has its own prefix
|
||||
scheme — don't assume it matches a previous rebrand. Identify:
|
||||
- Constant prefix (`PLUGINNAME_...`)
|
||||
- Class prefix (`PluginName_...`)
|
||||
- Function prefix (`pluginname_...`)
|
||||
- Text domain string (usually lowercase-hyphenated, e.g. `pluginname`)
|
||||
- Whether the plugin lives at the repo root or in a subdirectory
|
||||
|
||||
## Step 2 — Flatten to repo root
|
||||
|
||||
If the plugin's files live in a subdirectory (not the repo root), move
|
||||
everything up to root. Reason: many git hosts' repo-archive endpoint
|
||||
(`/archive/{tag}.zip`-style) names the zip's top-level folder after the
|
||||
**repo**, so a nested subdirectory inside the repo produces a
|
||||
double-nested folder in the resulting distributable zip
|
||||
(`reponame/plugin-subdir/...` instead of `reponame/...`). Flattening
|
||||
avoids this categorically rather than requiring special-case handling
|
||||
downstream.
|
||||
|
||||
## Step 3 — Rename, in this order
|
||||
|
||||
1. **Text-domain / hyphenated contexts first**: every `oldname-` (CSS
|
||||
classes, script/style handles, asset file paths, admin page slugs) →
|
||||
`newprefix-oldname-`. A safe global find/replace, catches the vast
|
||||
majority of hyphenated identifiers correctly in one pass.
|
||||
2. **Constants**: `OLDNAME_` → `NEWPREFIX_OLDNAME_`.
|
||||
3. **Classes**: `OldName_` → `NewPrefix_OldName_`.
|
||||
4. **Functions / hooks / underscore contexts**: `oldname_` →
|
||||
`newprefix_oldname_`.
|
||||
5. **camelCase-after-underscore contexts** (JS localize-object names are
|
||||
the common case: `oldnameData` used as the 2nd arg to
|
||||
`wp_localize_script()`) → `newprefix_oldnameData`. **Grep the JS files
|
||||
for the exact existing spelling before finalizing — don't guess the
|
||||
casing, the PHP `wp_localize_script()` call must match byte-for-byte
|
||||
or the JS breaks silently at runtime.**
|
||||
6. **Remaining bare prose occurrences of the product name** (page titles,
|
||||
UI labels, doc-comments) → the new display name (e.g. `MetAI` →
|
||||
`iWP MetAI`). Do this LAST, after class renaming, or you'll double-
|
||||
prefix the class name.
|
||||
7. **File/directory renames** to match: `class-oldname-admin.php` →
|
||||
`class-newprefix-oldname-admin.php`, etc.
|
||||
8. Update the plugin header block: `Plugin Name`, `Author`, `Author URI`,
|
||||
`Plugin URI`, `Text Domain` (always hyphenated, never underscored —
|
||||
WordPress text domains are always hyphen-form even when everything
|
||||
else in that context uses underscores).
|
||||
|
||||
## Step 4 — Cross-check enqueue calls against actual filenames
|
||||
|
||||
Every `wp_enqueue_script()`, `wp_enqueue_style()`, `wp_localize_script()`
|
||||
handle/path argument must match the ACTUAL renamed filenames on disk
|
||||
exactly, or the admin UI silently fails to load its JS/CSS with no error
|
||||
message anywhere obvious. Check this explicitly, don't assume the
|
||||
renames were consistent.
|
||||
|
||||
## Step 5 — Bundle the updater / license client (if applicable)
|
||||
|
||||
Copy the shared updater class unchanged into the plugin's `includes/`
|
||||
directory. Wire it in the main file (near the top, after constants):
|
||||
|
||||
```php
|
||||
require_once <PATH_CONSTANT> . 'includes/class-<updater-file>.php';
|
||||
add_action( 'plugins_loaded', function () {
|
||||
new <UpdaterClass>( [
|
||||
'api_url' => '<the licensing server's API base>',
|
||||
'plugin_file' => __FILE__,
|
||||
'plugin_slug' => '<the confirmed catalog slug>',
|
||||
'version' => <VERSION_CONSTANT>,
|
||||
'license_option' => '<newprefix>_<name>_license_key',
|
||||
] );
|
||||
}, 5 );
|
||||
```
|
||||
|
||||
Then add ONE settings field for the license key (option name matching
|
||||
`license_option` above) into wherever the plugin already has an admin
|
||||
settings UI — follow that file's existing code style/pattern for how
|
||||
other fields are registered/rendered, don't introduce a different
|
||||
pattern. If the plugin has no settings UI at all, add a minimal one
|
||||
rather than skipping this step — license-gated updates need somewhere
|
||||
for the customer to enter the key.
|
||||
|
||||
## Step 6 — Verify before pushing
|
||||
|
||||
1. `php -l` every `.php` file. Zero tolerance for syntax errors.
|
||||
2. Case-insensitive grep the whole tree for the bare old name:
|
||||
`grep -rniE '(^|[^a-z_-])oldname' --include='*.php' --include='*.js' --include='*.css' .`
|
||||
— every hit must be part of a correctly-prefixed token
|
||||
(`newprefix-oldname`, `newprefix_oldname`, `NewPrefix_OldName`, or the
|
||||
new display name). Zero unprefixed exceptions, except possibly an
|
||||
original-authorship credit comment if one genuinely exists.
|
||||
3. Sanity-check for double-prefixing bugs (`iWP iWP`, `IWP_IWP`,
|
||||
`iwp-iwp`, `iwp_iwp` or equivalent for your prefix) — a real mistake
|
||||
that happens when step 3.6 runs before 3.3, or runs twice.
|
||||
|
||||
## Step 7 — Ship
|
||||
|
||||
Commit, push (force-push is fine for a freshly-created fork with no
|
||||
other history depending on it — confirm that's actually the case before
|
||||
force-pushing anything else). Tag a release. See the
|
||||
`gitea-release-workflow` skill for the release/verification steps if the
|
||||
git host is Gitea.
|
||||
|
||||
## Common failure modes (all hit at least once during validation)
|
||||
|
||||
- **Forgetting the flatten-to-root step** → double-nested zip.
|
||||
- **JS localize-object name mismatch** (hyphen where an underscore is
|
||||
required for a valid JS identifier) → admin UI JS silently breaks with
|
||||
no visible error.
|
||||
- **Trusting the delegate's self-report instead of re-verifying** — every
|
||||
single delegate run in the validating session initially reported
|
||||
success; independent re-verification (re-running `php -l` yourself, a
|
||||
fresh grep, an actual functional test through the real serving
|
||||
pipeline) is what actually confirms it, not the report.
|
||||
Reference in New Issue
Block a user