claude-plugin-wordpress-manager 1.5.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/CHANGELOG.md +97 -0
  3. package/README.md +27 -13
  4. package/agents/wp-accessibility-auditor.md +206 -0
  5. package/agents/wp-content-strategist.md +18 -0
  6. package/agents/wp-deployment-engineer.md +34 -2
  7. package/agents/wp-performance-optimizer.md +12 -0
  8. package/agents/wp-security-auditor.md +20 -0
  9. package/agents/wp-security-hardener.md +266 -0
  10. package/agents/wp-site-manager.md +14 -0
  11. package/agents/wp-test-engineer.md +207 -0
  12. package/docs/guides/INDEX.md +46 -0
  13. package/docs/guides/wp-blog.md +590 -0
  14. package/docs/guides/wp-design-system.md +976 -0
  15. package/docs/guides/wp-ecommerce.md +786 -0
  16. package/docs/guides/wp-landing-page.md +762 -0
  17. package/docs/guides/wp-portfolio.md +713 -0
  18. package/docs/plans/2026-02-27-design-system-guide-design.md +30 -0
  19. package/docs/plans/2026-02-27-site-type-guides-design.md +44 -0
  20. package/package.json +2 -2
  21. package/skills/wordpress-router/references/decision-tree.md +12 -2
  22. package/skills/wp-accessibility/SKILL.md +170 -0
  23. package/skills/wp-accessibility/references/a11y-audit-tools.md +248 -0
  24. package/skills/wp-accessibility/references/a11y-testing.md +222 -0
  25. package/skills/wp-accessibility/references/block-a11y.md +247 -0
  26. package/skills/wp-accessibility/references/interactive-a11y.md +272 -0
  27. package/skills/wp-accessibility/references/media-a11y.md +254 -0
  28. package/skills/wp-accessibility/references/theme-a11y.md +309 -0
  29. package/skills/wp-audit/SKILL.md +4 -0
  30. package/skills/wp-block-development/SKILL.md +5 -0
  31. package/skills/wp-block-themes/SKILL.md +4 -0
  32. package/skills/wp-e2e-testing/SKILL.md +186 -0
  33. package/skills/wp-e2e-testing/references/ci-integration.md +174 -0
  34. package/skills/wp-e2e-testing/references/jest-wordpress.md +114 -0
  35. package/skills/wp-e2e-testing/references/phpunit-wordpress.md +141 -0
  36. package/skills/wp-e2e-testing/references/playwright-wordpress.md +108 -0
  37. package/skills/wp-e2e-testing/references/test-data-generation.md +127 -0
  38. package/skills/wp-e2e-testing/references/visual-regression.md +107 -0
  39. package/skills/wp-e2e-testing/references/wp-env-setup.md +97 -0
  40. package/skills/wp-e2e-testing/scripts/test_inspect.mjs +375 -0
  41. package/skills/wp-headless/SKILL.md +168 -0
  42. package/skills/wp-headless/references/api-layer-choice.md +160 -0
  43. package/skills/wp-headless/references/cors-config.md +245 -0
  44. package/skills/wp-headless/references/frontend-integration.md +331 -0
  45. package/skills/wp-headless/references/headless-auth.md +286 -0
  46. package/skills/wp-headless/references/webhooks.md +277 -0
  47. package/skills/wp-headless/references/wpgraphql.md +331 -0
  48. package/skills/wp-headless/scripts/headless_inspect.mjs +321 -0
  49. package/skills/wp-i18n/SKILL.md +170 -0
  50. package/skills/wp-i18n/references/js-i18n.md +201 -0
  51. package/skills/wp-i18n/references/multilingual-setup.md +219 -0
  52. package/skills/wp-i18n/references/php-i18n.md +196 -0
  53. package/skills/wp-i18n/references/rtl-support.md +206 -0
  54. package/skills/wp-i18n/references/translation-workflow.md +178 -0
  55. package/skills/wp-i18n/references/wpcli-i18n.md +177 -0
  56. package/skills/wp-i18n/scripts/i18n_inspect.mjs +330 -0
  57. package/skills/wp-interactivity-api/SKILL.md +4 -0
  58. package/skills/wp-plugin-development/SKILL.md +6 -0
  59. package/skills/wp-rest-api/SKILL.md +4 -0
  60. package/skills/wp-security/SKILL.md +179 -0
  61. package/skills/wp-security/references/api-restriction.md +147 -0
  62. package/skills/wp-security/references/authentication-hardening.md +105 -0
  63. package/skills/wp-security/references/filesystem-hardening.md +105 -0
  64. package/skills/wp-security/references/http-headers.md +105 -0
  65. package/skills/wp-security/references/incident-response.md +144 -0
  66. package/skills/wp-security/references/user-capabilities.md +115 -0
  67. package/skills/wp-security/references/wp-config-security.md +129 -0
  68. package/skills/wp-security/scripts/security_inspect.mjs +393 -0
@@ -0,0 +1,177 @@
1
+ # WP-CLI i18n Commands
2
+
3
+ Use this file when working with WP-CLI's `i18n` command group for translation management.
4
+
5
+ ## Prerequisites
6
+
7
+ ```bash
8
+ # WP-CLI i18n is built-in since WP-CLI 2.0
9
+ wp i18n --help
10
+
11
+ # If missing, install the i18n command package
12
+ wp package install wp-cli/i18n-command
13
+ ```
14
+
15
+ ## make-pot — Generate POT template
16
+
17
+ ```bash
18
+ # Basic usage (from plugin/theme root)
19
+ wp i18n make-pot . languages/my-text-domain.pot
20
+
21
+ # Full options
22
+ wp i18n make-pot . languages/my-text-domain.pot \
23
+ --slug=my-plugin \
24
+ --domain=my-text-domain \
25
+ --include="src/,includes/,templates/" \
26
+ --exclude="node_modules/,vendor/,tests/,build/" \
27
+ --skip-js \
28
+ --skip-php \
29
+ --skip-block-json \
30
+ --skip-theme-json \
31
+ --skip-audit \
32
+ --headers='{"Report-Msgid-Bugs-To":"support@example.com","Last-Translator":"Dev Team"}' \
33
+ --file-comment="Copyright (c) 2026 My Company"
34
+ ```
35
+
36
+ ### Key options
37
+
38
+ | Option | Purpose |
39
+ |--------|---------|
40
+ | `--domain=<domain>` | Only extract strings with this text domain |
41
+ | `--include=<paths>` | Comma-separated list of directories to scan |
42
+ | `--exclude=<paths>` | Comma-separated list of directories to skip |
43
+ | `--skip-js` | Skip JavaScript file scanning |
44
+ | `--skip-php` | Skip PHP file scanning |
45
+ | `--skip-block-json` | Skip block.json translation extraction |
46
+ | `--skip-theme-json` | Skip theme.json translation extraction |
47
+ | `--skip-audit` | Skip string auditing (faster, no warnings) |
48
+ | `--headers` | JSON object of PO headers |
49
+
50
+ ### What it scans
51
+
52
+ - PHP: `__()`, `_e()`, `_x()`, `_ex()`, `_n()`, `_nx()`, `esc_html__()`, `esc_html_e()`, `esc_attr__()`, `esc_attr_e()`, `esc_html_x()`, `esc_attr_x()`
53
+ - JS: `@wordpress/i18n` functions via static analysis
54
+ - block.json: `title`, `description`, `keywords`, `styles[].label`, `variations[].title`
55
+ - theme.json: Custom template names, style variation names
56
+
57
+ ## update-po — Merge new strings into PO files
58
+
59
+ ```bash
60
+ # Update all PO files in the directory
61
+ wp i18n update-po languages/my-text-domain.pot languages/
62
+
63
+ # Update a specific PO file
64
+ wp i18n update-po languages/my-text-domain.pot languages/my-text-domain-it_IT.po
65
+ ```
66
+
67
+ This is equivalent to `msgmerge --update` but integrated into WP-CLI. Preserves existing translations and marks removed strings as obsolete.
68
+
69
+ ## make-mo — Compile PO to MO
70
+
71
+ ```bash
72
+ # Compile all PO files in directory
73
+ wp i18n make-mo languages/
74
+
75
+ # Compile a specific file
76
+ wp i18n make-mo languages/my-text-domain-it_IT.po
77
+ ```
78
+
79
+ ## make-json — Generate JS translation files
80
+
81
+ ```bash
82
+ # Generate JSON files for all PO files
83
+ wp i18n make-json languages/
84
+
85
+ # Keep JS strings in PO files (don't purge)
86
+ wp i18n make-json languages/ --no-purge
87
+
88
+ # Pretty-print JSON output
89
+ wp i18n make-json languages/ --pretty-print
90
+ ```
91
+
92
+ Output: `{text-domain}-{locale}-{md5}.json` where `{md5}` is the hash of the relative JS file path.
93
+
94
+ ### When to use `--no-purge`
95
+
96
+ - Use `--no-purge` if strings appear in both PHP and JS files
97
+ - Without it, JS-only strings are removed from PO, breaking PHP translations if shared
98
+
99
+ ## make-php — Generate PHP translation files (WP 6.5+)
100
+
101
+ ```bash
102
+ # Convert PO files to PHP format
103
+ wp i18n make-php languages/
104
+ ```
105
+
106
+ PHP translation files load faster than MO files. WordPress 6.5+ supports this format natively.
107
+
108
+ ## Complete workflow example
109
+
110
+ ```bash
111
+ # 1. Generate fresh POT
112
+ wp i18n make-pot . languages/my-text-domain.pot \
113
+ --exclude="node_modules/,vendor/"
114
+
115
+ # 2. Update existing translations
116
+ wp i18n update-po languages/my-text-domain.pot languages/
117
+
118
+ # 3. (Translate the PO files — manual or via Poedit)
119
+
120
+ # 4. Compile MO files
121
+ wp i18n make-mo languages/
122
+
123
+ # 5. Generate JSON for JavaScript
124
+ wp i18n make-json languages/ --no-purge
125
+
126
+ # 6. (Optional) Generate PHP translations
127
+ wp i18n make-php languages/
128
+ ```
129
+
130
+ ## Audit strings
131
+
132
+ The `make-pot` command includes a string auditor. Common warnings:
133
+
134
+ | Warning | Meaning |
135
+ |---------|---------|
136
+ | `Mismatched placeholders` | Printf placeholders differ between singular/plural |
137
+ | `Multiple text domains` | File mixes text domains |
138
+ | `Missing translator comment` | Placeholder string without `/* translators: */` |
139
+
140
+ Run the audit explicitly:
141
+ ```bash
142
+ wp i18n make-pot . /dev/null --skip-js
143
+ # Warnings are printed to stderr
144
+ ```
145
+
146
+ ## Language management
147
+
148
+ ```bash
149
+ # Install a language pack for core
150
+ wp language core install it_IT
151
+
152
+ # Set site language
153
+ wp site switch-language it_IT
154
+
155
+ # List installed languages
156
+ wp language core list --status=installed
157
+
158
+ # Install plugin language pack
159
+ wp language plugin install my-plugin it_IT
160
+
161
+ # Install theme language pack
162
+ wp language theme install my-theme it_IT
163
+ ```
164
+
165
+ ## Verification
166
+
167
+ ```bash
168
+ # Verify POT is up to date (compare counts)
169
+ wp i18n make-pot . /tmp/fresh.pot --quiet
170
+ diff <(grep -c "^msgid" languages/my-text-domain.pot) <(grep -c "^msgid" /tmp/fresh.pot)
171
+
172
+ # Check MO files are current (MO should be newer than PO)
173
+ find languages/ -name "*.po" -newer languages/*.mo
174
+
175
+ # Verify JSON files exist for JS translations
176
+ ls languages/*.json
177
+ ```
@@ -0,0 +1,330 @@
1
+ /**
2
+ * i18n_inspect.mjs — Detect internationalization setup in a WordPress project.
3
+ *
4
+ * Scans for text domain, .pot/.po/.mo files, i18n function usage,
5
+ * and WP-CLI i18n availability.
6
+ * Outputs a JSON report to stdout.
7
+ *
8
+ * Usage:
9
+ * node i18n_inspect.mjs [--cwd=/path/to/check]
10
+ *
11
+ * Exit codes:
12
+ * 0 — i18n setup detected
13
+ * 1 — no i18n setup detected
14
+ */
15
+
16
+ import fs from "node:fs";
17
+ import path from "node:path";
18
+ import process from "node:process";
19
+ import { execSync } from "node:child_process";
20
+
21
+ const TOOL_VERSION = "1.0.0";
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Helpers
25
+ // ---------------------------------------------------------------------------
26
+
27
+ function statSafe(p) {
28
+ try {
29
+ return fs.statSync(p);
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+
35
+ function readFileSafe(p) {
36
+ try {
37
+ return fs.readFileSync(p, "utf8");
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+
43
+ function readJsonSafe(p) {
44
+ const raw = readFileSafe(p);
45
+ if (!raw) return null;
46
+ try {
47
+ return JSON.parse(raw);
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
52
+
53
+ function execSafe(cmd, cwd, timeoutMs = 5000) {
54
+ try {
55
+ return execSync(cmd, { encoding: "utf8", timeout: timeoutMs, cwd, stdio: ["pipe", "pipe", "pipe"] }).trim();
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
60
+
61
+ function readdirSafe(dir) {
62
+ try {
63
+ return fs.readdirSync(dir);
64
+ } catch {
65
+ return [];
66
+ }
67
+ }
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // Parse --cwd argument
71
+ // ---------------------------------------------------------------------------
72
+
73
+ function parseCwd() {
74
+ const cwdArg = process.argv.find((a) => a.startsWith("--cwd="));
75
+ return cwdArg ? cwdArg.slice(6) : process.cwd();
76
+ }
77
+
78
+ // ---------------------------------------------------------------------------
79
+ // Detect text domain
80
+ // ---------------------------------------------------------------------------
81
+
82
+ function detectTextDomain(cwd) {
83
+ const result = { found: false, domain: null, source: null };
84
+
85
+ // Check plugin header
86
+ const phpFiles = readdirSafe(cwd).filter((f) => f.endsWith(".php"));
87
+ for (const file of phpFiles) {
88
+ const content = readFileSafe(path.join(cwd, file));
89
+ if (!content) continue;
90
+
91
+ const domainMatch = content.match(/Text\s*Domain:\s*(\S+)/i);
92
+ if (domainMatch) {
93
+ result.found = true;
94
+ result.domain = domainMatch[1];
95
+ result.source = file;
96
+ return result;
97
+ }
98
+ }
99
+
100
+ // Check style.css (theme)
101
+ const styleContent = readFileSafe(path.join(cwd, "style.css"));
102
+ if (styleContent) {
103
+ const domainMatch = styleContent.match(/Text\s*Domain:\s*(\S+)/i);
104
+ if (domainMatch) {
105
+ result.found = true;
106
+ result.domain = domainMatch[1];
107
+ result.source = "style.css";
108
+ return result;
109
+ }
110
+ }
111
+
112
+ // Check block.json files
113
+ const blockJson = readJsonSafe(path.join(cwd, "block.json"));
114
+ if (blockJson?.textdomain) {
115
+ result.found = true;
116
+ result.domain = blockJson.textdomain;
117
+ result.source = "block.json";
118
+ return result;
119
+ }
120
+
121
+ const srcBlockJson = readJsonSafe(path.join(cwd, "src", "block.json"));
122
+ if (srcBlockJson?.textdomain) {
123
+ result.found = true;
124
+ result.domain = srcBlockJson.textdomain;
125
+ result.source = "src/block.json";
126
+ return result;
127
+ }
128
+
129
+ return result;
130
+ }
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // Detect translation files
134
+ // ---------------------------------------------------------------------------
135
+
136
+ function detectTranslationFiles(cwd) {
137
+ const result = { languagesDir: null, pot: [], po: [], mo: [], json: [] };
138
+
139
+ const langDirs = ["languages", "lang", "i18n"];
140
+ for (const dir of langDirs) {
141
+ const full = path.join(cwd, dir);
142
+ if (statSafe(full)?.isDirectory()) {
143
+ result.languagesDir = dir;
144
+ break;
145
+ }
146
+ }
147
+
148
+ if (!result.languagesDir) return result;
149
+
150
+ const files = readdirSafe(path.join(cwd, result.languagesDir));
151
+ for (const file of files) {
152
+ if (file.endsWith(".pot")) result.pot.push(file);
153
+ else if (file.endsWith(".po")) result.po.push(file);
154
+ else if (file.endsWith(".mo")) result.mo.push(file);
155
+ else if (file.endsWith(".json") && !file.startsWith(".")) result.json.push(file);
156
+ }
157
+
158
+ return result;
159
+ }
160
+
161
+ // ---------------------------------------------------------------------------
162
+ // Detect i18n function usage
163
+ // ---------------------------------------------------------------------------
164
+
165
+ function detectI18nUsage(cwd) {
166
+ const result = { php: { detected: false, functions: {} }, js: { detected: false, functions: {} } };
167
+
168
+ // PHP i18n functions
169
+ const phpFunctions = ["__", "_e", "_x", "_ex", "_n", "_nx", "esc_html__", "esc_html_e", "esc_attr__", "esc_attr_e"];
170
+ const phpPattern = phpFunctions.map((f) => f.replace(/_/g, "_")).join("|");
171
+ const phpCount = execSafe(
172
+ `grep -rl --include="*.php" -E "(${phpPattern})\\s*\\(" . 2>/dev/null | wc -l`,
173
+ cwd
174
+ );
175
+
176
+ if (phpCount && parseInt(phpCount) > 0) {
177
+ result.php.detected = true;
178
+
179
+ // Count per function
180
+ for (const func of phpFunctions) {
181
+ const count = execSafe(
182
+ `grep -r --include="*.php" -c "${func}(" . 2>/dev/null | awk -F: '{s+=$2} END {print s}'`,
183
+ cwd
184
+ );
185
+ if (count && parseInt(count) > 0) {
186
+ result.php.functions[func] = parseInt(count);
187
+ }
188
+ }
189
+ }
190
+
191
+ // JS i18n functions (@wordpress/i18n)
192
+ const jsCount = execSafe(
193
+ `grep -rl --include="*.js" --include="*.jsx" --include="*.ts" --include="*.tsx" "@wordpress/i18n" . 2>/dev/null | wc -l`,
194
+ cwd
195
+ );
196
+
197
+ if (jsCount && parseInt(jsCount) > 0) {
198
+ result.js.detected = true;
199
+ }
200
+
201
+ // Also check for import
202
+ const jsImportCount = execSafe(
203
+ `grep -rl --include="*.js" --include="*.jsx" --include="*.ts" --include="*.tsx" "from '@wordpress/i18n'" . 2>/dev/null | wc -l`,
204
+ cwd
205
+ );
206
+ if (jsImportCount && parseInt(jsImportCount) > 0) {
207
+ result.js.detected = true;
208
+ }
209
+
210
+ return result;
211
+ }
212
+
213
+ // ---------------------------------------------------------------------------
214
+ // Detect Domain Path header
215
+ // ---------------------------------------------------------------------------
216
+
217
+ function detectDomainPath(cwd) {
218
+ const phpFiles = readdirSafe(cwd).filter((f) => f.endsWith(".php"));
219
+ for (const file of phpFiles) {
220
+ const content = readFileSafe(path.join(cwd, file));
221
+ if (!content) continue;
222
+ const match = content.match(/Domain\s*Path:\s*(\S+)/i);
223
+ if (match) return match[1];
224
+ }
225
+
226
+ const styleContent = readFileSafe(path.join(cwd, "style.css"));
227
+ if (styleContent) {
228
+ const match = styleContent.match(/Domain\s*Path:\s*(\S+)/i);
229
+ if (match) return match[1];
230
+ }
231
+
232
+ return null;
233
+ }
234
+
235
+ // ---------------------------------------------------------------------------
236
+ // Detect load_textdomain calls
237
+ // ---------------------------------------------------------------------------
238
+
239
+ function detectTextdomainLoading(cwd) {
240
+ const functions = [
241
+ "load_plugin_textdomain",
242
+ "load_theme_textdomain",
243
+ "load_child_theme_textdomain",
244
+ "wp_set_script_translations",
245
+ ];
246
+
247
+ const detected = [];
248
+ for (const func of functions) {
249
+ const found = execSafe(
250
+ `grep -rl --include="*.php" "${func}" . 2>/dev/null | head -1`,
251
+ cwd
252
+ );
253
+ if (found) detected.push(func);
254
+ }
255
+
256
+ return detected;
257
+ }
258
+
259
+ // ---------------------------------------------------------------------------
260
+ // Check WP-CLI i18n availability
261
+ // ---------------------------------------------------------------------------
262
+
263
+ function checkWpCliI18n(cwd) {
264
+ const wpCli = execSafe("command -v wp", cwd);
265
+ if (!wpCli) return { available: false };
266
+
267
+ const i18nHelp = execSafe("wp i18n --help 2>/dev/null", cwd);
268
+ return { available: !!i18nHelp };
269
+ }
270
+
271
+ // ---------------------------------------------------------------------------
272
+ // Main
273
+ // ---------------------------------------------------------------------------
274
+
275
+ function main() {
276
+ const cwd = parseCwd();
277
+
278
+ if (!statSafe(cwd)?.isDirectory()) {
279
+ console.error(`Error: directory not found: ${cwd}`);
280
+ process.exit(1);
281
+ }
282
+
283
+ const textDomain = detectTextDomain(cwd);
284
+ const translationFiles = detectTranslationFiles(cwd);
285
+ const i18nUsage = detectI18nUsage(cwd);
286
+ const domainPath = detectDomainPath(cwd);
287
+ const textdomainLoading = detectTextdomainLoading(cwd);
288
+ const wpCliI18n = checkWpCliI18n(cwd);
289
+
290
+ const detected = textDomain.found || i18nUsage.php.detected || i18nUsage.js.detected || translationFiles.pot.length > 0;
291
+
292
+ const report = {
293
+ tool: "i18n_inspect",
294
+ version: TOOL_VERSION,
295
+ cwd,
296
+ detected,
297
+ textDomain,
298
+ domainPath,
299
+ textdomainLoading,
300
+ translationFiles,
301
+ i18nUsage,
302
+ wpCliI18n,
303
+ recommendations: [],
304
+ };
305
+
306
+ // Recommendations
307
+ if (!textDomain.found && (i18nUsage.php.detected || i18nUsage.js.detected)) {
308
+ report.recommendations.push("i18n functions used but no Text Domain header found. Add 'Text Domain:' to plugin/theme header.");
309
+ }
310
+ if (textDomain.found && translationFiles.pot.length === 0) {
311
+ report.recommendations.push("Text domain set but no .pot file found. Run: wp i18n make-pot . languages/" + textDomain.domain + ".pot");
312
+ }
313
+ if (translationFiles.po.length > 0 && translationFiles.mo.length === 0) {
314
+ report.recommendations.push("PO files found but no compiled MO files. Run: wp i18n make-mo languages/");
315
+ }
316
+ if (i18nUsage.js.detected && translationFiles.json.length === 0) {
317
+ report.recommendations.push("JS i18n detected but no JSON translation files. Run: wp i18n make-json languages/ --no-purge");
318
+ }
319
+ if (textDomain.found && textdomainLoading.length === 0) {
320
+ report.recommendations.push("Text domain found but no load_plugin_textdomain/load_theme_textdomain call detected.");
321
+ }
322
+ if (!domainPath && textDomain.found) {
323
+ report.recommendations.push("Consider adding 'Domain Path: /languages' to the plugin/theme header.");
324
+ }
325
+
326
+ console.log(JSON.stringify(report, null, 2));
327
+ process.exit(detected ? 0 : 1);
328
+ }
329
+
330
+ main();
@@ -179,3 +179,7 @@ See `references/debugging.md`.
179
179
  - `references/server-side-rendering.md`
180
180
  - `references/directives-quickref.md`
181
181
  - `references/debugging.md`
182
+
183
+ ## Related skills
184
+
185
+ - `wp-accessibility` — ARIA for interactive components, keyboard navigation, focus management, screen reader testing
@@ -112,3 +112,9 @@ See:
112
112
  ## Escalation
113
113
 
114
114
  For canonical detail, consult the Plugin Handbook and security guidelines before inventing patterns.
115
+
116
+ ## Related skills
117
+
118
+ - `wp-e2e-testing` — PHPUnit for plugin unit tests, Playwright for E2E, CI integration
119
+ - `wp-i18n` — Text domain setup, translation workflow, WP-CLI i18n commands
120
+ - `wp-security` — Filesystem hardening, wp-config constants, authentication security
@@ -114,3 +114,7 @@ Read `references/discovery-and-params.md`.
114
114
  ## Escalation
115
115
 
116
116
  If version support or behavior is unclear, consult the REST API Handbook and core docs before inventing patterns.
117
+
118
+ ## Related skills
119
+
120
+ - `wp-headless` — Decoupled architecture, WPGraphQL, CORS configuration, frontend framework integration, webhooks