claude-plugin-wordpress-manager 1.4.0

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 (142) hide show
  1. package/.claude-plugin/plugin.json +19 -0
  2. package/.mcp.json +19 -0
  3. package/CHANGELOG.md +62 -0
  4. package/LICENSE +69 -0
  5. package/README.md +213 -0
  6. package/agents/wp-content-strategist.md +148 -0
  7. package/agents/wp-deployment-engineer.md +93 -0
  8. package/agents/wp-performance-optimizer.md +198 -0
  9. package/agents/wp-security-auditor.md +161 -0
  10. package/agents/wp-site-manager.md +109 -0
  11. package/commands/wp-audit.md +37 -0
  12. package/commands/wp-backup.md +45 -0
  13. package/commands/wp-deploy.md +38 -0
  14. package/commands/wp-setup.md +64 -0
  15. package/commands/wp-status.md +53 -0
  16. package/docs/GUIDE.md +1190 -0
  17. package/hooks/hooks.json +57 -0
  18. package/hooks/scripts/backup-reminder.sh +29 -0
  19. package/hooks/scripts/pre-deploy-check.sh +49 -0
  20. package/package.json +46 -0
  21. package/scripts/health-check.sh +110 -0
  22. package/scripts/validate-wp-operation.sh +115 -0
  23. package/servers/wp-rest-bridge/build/server.d.ts +2 -0
  24. package/servers/wp-rest-bridge/build/server.js +74 -0
  25. package/servers/wp-rest-bridge/build/tools/comments.d.ts +227 -0
  26. package/servers/wp-rest-bridge/build/tools/comments.js +192 -0
  27. package/servers/wp-rest-bridge/build/tools/index.d.ts +919 -0
  28. package/servers/wp-rest-bridge/build/tools/index.js +30 -0
  29. package/servers/wp-rest-bridge/build/tools/media.d.ts +174 -0
  30. package/servers/wp-rest-bridge/build/tools/media.js +247 -0
  31. package/servers/wp-rest-bridge/build/tools/plugin-repository.d.ts +62 -0
  32. package/servers/wp-rest-bridge/build/tools/plugin-repository.js +149 -0
  33. package/servers/wp-rest-bridge/build/tools/plugins.d.ts +153 -0
  34. package/servers/wp-rest-bridge/build/tools/plugins.js +175 -0
  35. package/servers/wp-rest-bridge/build/tools/search.d.ts +44 -0
  36. package/servers/wp-rest-bridge/build/tools/search.js +44 -0
  37. package/servers/wp-rest-bridge/build/tools/unified-content.d.ts +328 -0
  38. package/servers/wp-rest-bridge/build/tools/unified-content.js +628 -0
  39. package/servers/wp-rest-bridge/build/tools/unified-taxonomies.d.ts +244 -0
  40. package/servers/wp-rest-bridge/build/tools/unified-taxonomies.js +492 -0
  41. package/servers/wp-rest-bridge/build/tools/users.d.ts +269 -0
  42. package/servers/wp-rest-bridge/build/tools/users.js +226 -0
  43. package/servers/wp-rest-bridge/build/types.d.ts +151 -0
  44. package/servers/wp-rest-bridge/build/types.js +2 -0
  45. package/servers/wp-rest-bridge/build/wordpress.d.ts +48 -0
  46. package/servers/wp-rest-bridge/build/wordpress.js +305 -0
  47. package/servers/wp-rest-bridge/package.json +27 -0
  48. package/skills/wordpress-router/SKILL.md +78 -0
  49. package/skills/wordpress-router/references/decision-tree.md +88 -0
  50. package/skills/wp-abilities-api/SKILL.md +97 -0
  51. package/skills/wp-abilities-api/references/php-registration.md +67 -0
  52. package/skills/wp-abilities-api/references/rest-api.md +13 -0
  53. package/skills/wp-audit/SKILL.md +114 -0
  54. package/skills/wp-audit/references/performance-checklist.md +113 -0
  55. package/skills/wp-audit/references/security-checklist.md +95 -0
  56. package/skills/wp-audit/references/seo-checklist.md +128 -0
  57. package/skills/wp-backup/SKILL.md +87 -0
  58. package/skills/wp-backup/references/backup-strategies.md +116 -0
  59. package/skills/wp-backup/references/restore-procedures.md +129 -0
  60. package/skills/wp-block-development/SKILL.md +176 -0
  61. package/skills/wp-block-development/references/attributes-and-serialization.md +22 -0
  62. package/skills/wp-block-development/references/block-json.md +49 -0
  63. package/skills/wp-block-development/references/creating-new-blocks.md +46 -0
  64. package/skills/wp-block-development/references/debugging.md +36 -0
  65. package/skills/wp-block-development/references/deprecations.md +24 -0
  66. package/skills/wp-block-development/references/dynamic-rendering.md +23 -0
  67. package/skills/wp-block-development/references/inner-blocks.md +25 -0
  68. package/skills/wp-block-development/references/registration.md +30 -0
  69. package/skills/wp-block-development/references/supports-and-wrappers.md +18 -0
  70. package/skills/wp-block-development/references/tooling-and-testing.md +21 -0
  71. package/skills/wp-block-development/scripts/list_blocks.mjs +121 -0
  72. package/skills/wp-block-themes/SKILL.md +118 -0
  73. package/skills/wp-block-themes/references/creating-new-block-theme.md +37 -0
  74. package/skills/wp-block-themes/references/debugging.md +24 -0
  75. package/skills/wp-block-themes/references/patterns.md +18 -0
  76. package/skills/wp-block-themes/references/style-variations.md +14 -0
  77. package/skills/wp-block-themes/references/templates-and-parts.md +16 -0
  78. package/skills/wp-block-themes/references/theme-json.md +59 -0
  79. package/skills/wp-block-themes/scripts/detect_block_themes.mjs +117 -0
  80. package/skills/wp-content/SKILL.md +103 -0
  81. package/skills/wp-content/references/content-templates.md +230 -0
  82. package/skills/wp-content/references/seo-optimization.md +169 -0
  83. package/skills/wp-deploy/SKILL.md +52 -0
  84. package/skills/wp-deploy/references/hostinger-deploy.md +51 -0
  85. package/skills/wp-deploy/references/ssh-deploy.md +63 -0
  86. package/skills/wp-interactivity-api/SKILL.md +181 -0
  87. package/skills/wp-interactivity-api/references/debugging.md +29 -0
  88. package/skills/wp-interactivity-api/references/directives-quickref.md +30 -0
  89. package/skills/wp-interactivity-api/references/server-side-rendering.md +310 -0
  90. package/skills/wp-migrate/SKILL.md +100 -0
  91. package/skills/wp-migrate/references/cross-platform.md +104 -0
  92. package/skills/wp-migrate/references/hostinger-migration.md +86 -0
  93. package/skills/wp-performance/SKILL.md +148 -0
  94. package/skills/wp-performance/references/autoload-options.md +24 -0
  95. package/skills/wp-performance/references/cron.md +20 -0
  96. package/skills/wp-performance/references/database.md +20 -0
  97. package/skills/wp-performance/references/http-api.md +15 -0
  98. package/skills/wp-performance/references/measurement.md +21 -0
  99. package/skills/wp-performance/references/object-cache.md +24 -0
  100. package/skills/wp-performance/references/query-monitor-headless.md +38 -0
  101. package/skills/wp-performance/references/server-timing.md +22 -0
  102. package/skills/wp-performance/references/wp-cli-doctor.md +24 -0
  103. package/skills/wp-performance/references/wp-cli-profile.md +32 -0
  104. package/skills/wp-performance/scripts/perf_inspect.mjs +128 -0
  105. package/skills/wp-phpstan/SKILL.md +99 -0
  106. package/skills/wp-phpstan/references/configuration.md +52 -0
  107. package/skills/wp-phpstan/references/third-party-classes.md +76 -0
  108. package/skills/wp-phpstan/references/wordpress-annotations.md +124 -0
  109. package/skills/wp-phpstan/scripts/phpstan_inspect.mjs +263 -0
  110. package/skills/wp-playground/SKILL.md +103 -0
  111. package/skills/wp-playground/references/blueprints.md +36 -0
  112. package/skills/wp-playground/references/cli-commands.md +39 -0
  113. package/skills/wp-playground/references/debugging.md +16 -0
  114. package/skills/wp-plugin-development/SKILL.md +114 -0
  115. package/skills/wp-plugin-development/references/data-and-cron.md +19 -0
  116. package/skills/wp-plugin-development/references/debugging.md +19 -0
  117. package/skills/wp-plugin-development/references/lifecycle.md +33 -0
  118. package/skills/wp-plugin-development/references/security.md +29 -0
  119. package/skills/wp-plugin-development/references/settings-api.md +22 -0
  120. package/skills/wp-plugin-development/references/structure.md +16 -0
  121. package/skills/wp-plugin-development/scripts/detect_plugins.mjs +122 -0
  122. package/skills/wp-project-triage/SKILL.md +40 -0
  123. package/skills/wp-project-triage/references/triage.schema.json +143 -0
  124. package/skills/wp-project-triage/scripts/detect_wp_project.mjs +592 -0
  125. package/skills/wp-rest-api/SKILL.md +116 -0
  126. package/skills/wp-rest-api/references/authentication.md +18 -0
  127. package/skills/wp-rest-api/references/custom-content-types.md +20 -0
  128. package/skills/wp-rest-api/references/discovery-and-params.md +20 -0
  129. package/skills/wp-rest-api/references/responses-and-fields.md +30 -0
  130. package/skills/wp-rest-api/references/routes-and-endpoints.md +36 -0
  131. package/skills/wp-rest-api/references/schema.md +22 -0
  132. package/skills/wp-wpcli-and-ops/SKILL.md +125 -0
  133. package/skills/wp-wpcli-and-ops/references/automation.md +30 -0
  134. package/skills/wp-wpcli-and-ops/references/cron-and-cache.md +23 -0
  135. package/skills/wp-wpcli-and-ops/references/debugging.md +17 -0
  136. package/skills/wp-wpcli-and-ops/references/multisite.md +22 -0
  137. package/skills/wp-wpcli-and-ops/references/packages-and-updates.md +22 -0
  138. package/skills/wp-wpcli-and-ops/references/safety.md +30 -0
  139. package/skills/wp-wpcli-and-ops/references/search-replace.md +40 -0
  140. package/skills/wp-wpcli-and-ops/scripts/wpcli_inspect.mjs +90 -0
  141. package/skills/wpds/SKILL.md +60 -0
  142. package/skills/wpds/references/wpds-mcp-setup.md +59 -0
@@ -0,0 +1,305 @@
1
+ // src/wordpress.ts - Multi-site WordPress REST API client
2
+ import axios from 'axios';
3
+ // ── Concurrency Limiter ──────────────────────────────────────────────
4
+ // Simple FIFO semaphore to cap concurrent requests per site
5
+ class ConcurrencyLimiter {
6
+ maxConcurrent;
7
+ running = 0;
8
+ queue = [];
9
+ constructor(maxConcurrent) {
10
+ this.maxConcurrent = maxConcurrent;
11
+ }
12
+ async acquire() {
13
+ if (this.running < this.maxConcurrent) {
14
+ this.running++;
15
+ return;
16
+ }
17
+ return new Promise((resolve) => {
18
+ this.queue.push(() => {
19
+ this.running++;
20
+ resolve();
21
+ });
22
+ });
23
+ }
24
+ release() {
25
+ this.running--;
26
+ const next = this.queue.shift();
27
+ if (next)
28
+ next();
29
+ }
30
+ }
31
+ // ── Module State ─────────────────────────────────────────────────────
32
+ const siteClients = new Map();
33
+ const siteLimiters = new Map();
34
+ let activeSiteId = '';
35
+ const MAX_CONCURRENT_PER_SITE = 5;
36
+ const DEFAULT_TIMEOUT_MS = parseInt(process.env.WP_REQUEST_TIMEOUT_MS || '30000', 10);
37
+ const MAX_RETRIES = 3;
38
+ // HTTP status codes that are safe to retry
39
+ const RETRYABLE_STATUS_CODES = new Set([408, 429, 500, 502, 503, 504]);
40
+ // Network error codes that are safe to retry
41
+ const RETRYABLE_ERROR_CODES = new Set(['ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND']);
42
+ // ── Initialization ───────────────────────────────────────────────────
43
+ /**
44
+ * Parse WP_SITES_CONFIG JSON and initialize all site clients
45
+ */
46
+ export async function initWordPress() {
47
+ const sitesJson = process.env.WP_SITES_CONFIG;
48
+ const defaultSite = process.env.WP_DEFAULT_SITE;
49
+ if (!sitesJson) {
50
+ // Fallback to legacy single-site env vars
51
+ const url = process.env.WORDPRESS_API_URL;
52
+ const username = process.env.WORDPRESS_USERNAME;
53
+ const password = process.env.WORDPRESS_PASSWORD;
54
+ if (!url) {
55
+ throw new Error('No WordPress site configured. Set WP_SITES_CONFIG or WORDPRESS_API_URL.');
56
+ }
57
+ const siteId = 'default';
58
+ await initSiteClient(siteId, url, username || '', password || '');
59
+ activeSiteId = siteId;
60
+ logToStderr(`Initialized single site: ${url}`);
61
+ return;
62
+ }
63
+ let sites;
64
+ try {
65
+ sites = JSON.parse(sitesJson);
66
+ }
67
+ catch {
68
+ throw new Error('Invalid WP_SITES_CONFIG JSON format');
69
+ }
70
+ if (sites.length === 0) {
71
+ throw new Error('WP_SITES_CONFIG contains no sites');
72
+ }
73
+ for (const site of sites) {
74
+ await initSiteClient(site.id, site.url, site.username, site.password);
75
+ logToStderr(`Initialized site: ${site.id} (${site.url})`);
76
+ }
77
+ activeSiteId = defaultSite || sites[0].id;
78
+ logToStderr(`Active site: ${activeSiteId}`);
79
+ }
80
+ /**
81
+ * Initialize a single site's Axios client.
82
+ * baseURL now points to {site}/wp-json/ so that tools can use any namespace.
83
+ */
84
+ async function initSiteClient(id, url, username, password) {
85
+ let baseURL = url.endsWith('/') ? url : `${url}/`;
86
+ // Strip existing wp-json path variants so we normalize to just /wp-json/
87
+ const wpJsonIdx = baseURL.indexOf('/wp-json');
88
+ if (wpJsonIdx !== -1) {
89
+ baseURL = baseURL.substring(0, wpJsonIdx + 1);
90
+ }
91
+ baseURL = baseURL + 'wp-json/';
92
+ const config = {
93
+ baseURL,
94
+ headers: { 'Content-Type': 'application/json' },
95
+ timeout: DEFAULT_TIMEOUT_MS,
96
+ };
97
+ if (username && password) {
98
+ const auth = Buffer.from(`${username}:${password}`).toString('base64');
99
+ config.headers = {
100
+ ...config.headers,
101
+ 'Authorization': `Basic ${auth}`,
102
+ };
103
+ }
104
+ const client = axios.create(config);
105
+ // Verify connection (using wp/v2 namespace)
106
+ try {
107
+ await client.get('wp/v2');
108
+ }
109
+ catch (error) {
110
+ logToStderr(`Warning: Could not verify connection to ${url}: ${error.message}`);
111
+ // Don't throw - the site might become available later
112
+ }
113
+ siteClients.set(id, client);
114
+ siteLimiters.set(id, new ConcurrencyLimiter(MAX_CONCURRENT_PER_SITE));
115
+ }
116
+ // ── Site Management ──────────────────────────────────────────────────
117
+ /**
118
+ * Get the active site's client, or a specific site's client
119
+ */
120
+ function getClient(siteId) {
121
+ const id = siteId || activeSiteId;
122
+ const client = siteClients.get(id);
123
+ if (!client) {
124
+ const available = Array.from(siteClients.keys()).join(', ');
125
+ throw new Error(`Site "${id}" not found. Available sites: ${available}`);
126
+ }
127
+ return client;
128
+ }
129
+ function getLimiter(siteId) {
130
+ const id = siteId || activeSiteId;
131
+ let limiter = siteLimiters.get(id);
132
+ if (!limiter) {
133
+ limiter = new ConcurrencyLimiter(MAX_CONCURRENT_PER_SITE);
134
+ siteLimiters.set(id, limiter);
135
+ }
136
+ return limiter;
137
+ }
138
+ /**
139
+ * Switch the active site
140
+ */
141
+ export function switchSite(siteId) {
142
+ if (!siteClients.has(siteId)) {
143
+ const available = Array.from(siteClients.keys()).join(', ');
144
+ throw new Error(`Site "${siteId}" not found. Available: ${available}`);
145
+ }
146
+ activeSiteId = siteId;
147
+ return activeSiteId;
148
+ }
149
+ /**
150
+ * List all configured sites
151
+ */
152
+ export function listSites() {
153
+ return Array.from(siteClients.keys());
154
+ }
155
+ /**
156
+ * Get the active site ID
157
+ */
158
+ export function getActiveSite() {
159
+ return activeSiteId;
160
+ }
161
+ // ── Logging ──────────────────────────────────────────────────────────
162
+ /**
163
+ * Log to stderr (safe for MCP stdio transport)
164
+ */
165
+ export function logToStderr(message) {
166
+ const timestamp = new Date().toISOString();
167
+ process.stderr.write(`[${timestamp}] ${message}\n`);
168
+ }
169
+ // Keep backward-compatible alias
170
+ export const logToFile = logToStderr;
171
+ // ── Retry Logic ──────────────────────────────────────────────────────
172
+ function isRetryableError(error, method) {
173
+ // Network errors are always retryable
174
+ if (error.code && RETRYABLE_ERROR_CODES.has(error.code)) {
175
+ return true;
176
+ }
177
+ const status = error.response?.status;
178
+ if (!status)
179
+ return false;
180
+ // For non-GET (mutating) methods, only retry on 429 (rate limit) — not 5xx
181
+ // to avoid duplicate side-effects
182
+ if (method !== 'GET') {
183
+ return status === 429;
184
+ }
185
+ return RETRYABLE_STATUS_CODES.has(status);
186
+ }
187
+ function getRetryDelay(attempt, error) {
188
+ // Respect Retry-After header on 429
189
+ const retryAfter = error.response?.headers?.['retry-after'];
190
+ if (retryAfter) {
191
+ const seconds = parseInt(retryAfter, 10);
192
+ if (!isNaN(seconds))
193
+ return seconds * 1000;
194
+ }
195
+ // Exponential backoff: 1s, 2s, 4s + jitter (0–500ms)
196
+ const base = Math.pow(2, attempt) * 1000;
197
+ const jitter = Math.random() * 500;
198
+ return base + jitter;
199
+ }
200
+ /**
201
+ * Make a request to the WordPress API (active site or specific site).
202
+ * Includes automatic retry with exponential backoff and concurrency limiting.
203
+ */
204
+ export async function makeWordPressRequest(method, endpoint, data, options) {
205
+ const siteId = options?.siteId || activeSiteId;
206
+ const client = getClient(siteId);
207
+ const limiter = getLimiter(siteId);
208
+ const namespace = options?.namespace || 'wp/v2';
209
+ // Build the full path: {namespace}/{endpoint}
210
+ const cleanEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;
211
+ const path = `${namespace}/${cleanEndpoint}`;
212
+ await limiter.acquire();
213
+ try {
214
+ return await executeWithRetry(client, method, path, data, siteId, options);
215
+ }
216
+ finally {
217
+ limiter.release();
218
+ }
219
+ }
220
+ async function executeWithRetry(client, method, path, data, siteId, options) {
221
+ let lastError;
222
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
223
+ try {
224
+ const requestConfig = {
225
+ method,
226
+ url: path,
227
+ headers: options?.headers || {},
228
+ };
229
+ if (options?.timeout) {
230
+ requestConfig.timeout = options.timeout;
231
+ }
232
+ if (method === 'GET') {
233
+ requestConfig.params = data;
234
+ }
235
+ else if (options?.isFormData) {
236
+ requestConfig.data = data;
237
+ }
238
+ else if (data) {
239
+ requestConfig.data = data;
240
+ }
241
+ const response = await client.request(requestConfig);
242
+ // Handle pagination metadata extraction
243
+ if (options?.includePagination && method === 'GET') {
244
+ const total = parseInt(response.headers['x-wp-total'] || '0', 10);
245
+ const totalPages = parseInt(response.headers['x-wp-totalpages'] || '0', 10);
246
+ const page = data?.page || 1;
247
+ const perPage = data?.per_page || 10;
248
+ return {
249
+ items: response.data,
250
+ pagination: { total, totalPages, page, perPage },
251
+ };
252
+ }
253
+ return options?.rawResponse ? response : response.data;
254
+ }
255
+ catch (error) {
256
+ lastError = error;
257
+ if (attempt < MAX_RETRIES && isRetryableError(error, method)) {
258
+ const delay = getRetryDelay(attempt, error);
259
+ const status = error.response?.status || error.code || 'unknown';
260
+ logToStderr(`[${siteId}] Retry ${attempt + 1}/${MAX_RETRIES} for ${method} ${path} (${status}), waiting ${Math.round(delay)}ms`);
261
+ await sleep(delay);
262
+ continue;
263
+ }
264
+ break;
265
+ }
266
+ }
267
+ // All retries exhausted or non-retryable error
268
+ const errorMessage = lastError.response?.data?.message || lastError.message;
269
+ logToStderr(`[${siteId}] Error ${method} ${path}: ${errorMessage}`);
270
+ throw lastError;
271
+ }
272
+ function sleep(ms) {
273
+ return new Promise((resolve) => setTimeout(resolve, ms));
274
+ }
275
+ // ── Plugin Repository (External API) ────────────────────────────────
276
+ /**
277
+ * Search the WordPress.org Plugin Repository
278
+ */
279
+ export async function searchWordPressPluginRepository(searchQuery, page = 1, perPage = 10) {
280
+ const apiUrl = 'https://api.wordpress.org/plugins/info/1.2/';
281
+ const requestData = {
282
+ action: 'query_plugins',
283
+ request: {
284
+ search: searchQuery,
285
+ page,
286
+ per_page: perPage,
287
+ fields: {
288
+ description: true,
289
+ sections: false,
290
+ tested: true,
291
+ requires: true,
292
+ rating: true,
293
+ downloaded: true,
294
+ downloadlink: true,
295
+ last_updated: true,
296
+ homepage: true,
297
+ tags: true,
298
+ },
299
+ },
300
+ };
301
+ const response = await axios.post(apiUrl, requestData, {
302
+ headers: { 'Content-Type': 'application/json' },
303
+ });
304
+ return response.data;
305
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "wp-rest-bridge",
3
+ "version": "1.1.0",
4
+ "description": "Lightweight MCP server bridging WordPress REST API for multi-site management",
5
+ "type": "module",
6
+ "main": "./build/server.js",
7
+ "scripts": {
8
+ "build": "tsc --project tsconfig.json",
9
+ "start": "node ./build/server.js",
10
+ "dev": "tsx watch src/server.ts",
11
+ "clean": "rm -rf build"
12
+ },
13
+ "engines": {
14
+ "node": ">=18.0.0"
15
+ },
16
+ "dependencies": {
17
+ "@modelcontextprotocol/sdk": "^1.27.1",
18
+ "axios": "^1.7.9",
19
+ "form-data": "^4.0.1",
20
+ "zod": "^3.24.2"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^22.10.0",
24
+ "tsx": "^4.19.0",
25
+ "typescript": "^5.7.0"
26
+ }
27
+ }
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: wordpress-router
3
+ description: “Use when the user asks about WordPress — whether development (plugins, themes,
4
+ blocks, REST API) or operations (deploy, audit, backup, migrate, content management).
5
+ Classifies the task and routes to the correct development or operational skill/agent.”
6
+ compatibility: “Targets WordPress 6.9+ (PHP 7.2.24+). Filesystem-based agent with bash + node. Some workflows require WP-CLI.”
7
+ version: 1.0.0
8
+ source: “WordPress/agent-skills (GPL-2.0-or-later) + wordpress-manager extensions”
9
+ ---
10
+
11
+ # WordPress Router
12
+
13
+ ## When to use
14
+
15
+ Use this skill at the start of most WordPress tasks to:
16
+
17
+ - identify what kind of WordPress task this is (development vs operations),
18
+ - for development: classify the repo (plugin vs theme vs block theme vs WP core),
19
+ - for operations: identify the operation type (deploy, audit, backup, migrate, content),
20
+ - route to the most relevant skill(s) and/or agent(s).
21
+
22
+ ## Inputs required
23
+
24
+ - Repo root (for development tasks) or site identifier (for operational tasks).
25
+ - The user’s intent and any constraints.
26
+
27
+ ## Procedure
28
+
29
+ ### 1) Determine task category
30
+
31
+ **Development tasks** (code changes to WordPress projects):
32
+ - Creating/modifying blocks, themes, plugins, REST endpoints
33
+ - Building UI with WordPress Design System (WPDS)
34
+ - Static analysis, build tooling, testing
35
+ - Sandbox testing via WordPress Playground
36
+ - Route via project triage → development skills
37
+
38
+ **Operational tasks** (managing live WordPress sites):
39
+ - Deploying, auditing, backing up, migrating, managing content
40
+ - Route directly → operational skills and agents
41
+
42
+ ### 2) For Development tasks
43
+
44
+ 1. Run the project triage script:
45
+ - `node skills/wp-project-triage/scripts/detect_wp_project.mjs`
46
+ 2. Classify from triage output.
47
+ 3. Route to development skills — see `references/decision-tree.md`.
48
+
49
+ ### 3) For Operational tasks
50
+
51
+ Route by intent keywords:
52
+
53
+ - **Deploy / push / production** → `wp-deploy` skill + `wp-deployment-engineer` agent
54
+ - **Audit / security / health check / SEO** → `wp-audit` skill + `wp-security-auditor` agent
55
+ - **Backup / restore / snapshot** → `wp-backup` skill
56
+ - **Migrate / move / transfer / clone** → `wp-migrate` skill
57
+ - **Content / blog post / pages / categories** → `wp-content` skill + `wp-content-strategist` agent
58
+ - **Performance / slow / PageSpeed / optimize** → `wp-audit` skill + `wp-performance-optimizer` agent
59
+ - **Site status / plugins / users / multi-site** → `wp-site-manager` agent
60
+
61
+ ### 4) Apply guardrails
62
+
63
+ - For development: prefer repo’s existing tooling and conventions.
64
+ - For operations: confirm target site, verify backups, get user confirmation for destructive ops.
65
+
66
+ ## Verification
67
+
68
+ - Re-run triage after structural changes (development).
69
+ - Verify site connectivity before operations.
70
+
71
+ ## Failure modes / debugging
72
+
73
+ - Development: If triage reports `kind: unknown`, inspect root files.
74
+ - Operations: If site unreachable, check `WP_SITES_CONFIG` and credentials.
75
+
76
+ ## Escalation
77
+
78
+ - If routing is ambiguous, ask: “Are you looking to develop/modify WordPress code, or manage/operate a live WordPress site?”
@@ -0,0 +1,88 @@
1
+ # Router decision tree (v2 — unified development + operations)
2
+
3
+ This routing guide covers both WordPress **development** and **operations** workflows.
4
+
5
+ ## Step 0: determine task category
6
+
7
+ Before repo triage, classify the user’s intent:
8
+
9
+ - **Development** (modifying code) → proceed to Step 1
10
+ - **Operations** (managing live sites) → skip to Step 2b
11
+
12
+ Keywords that indicate **operations**:
13
+ deploy, push to production, audit, security check, backup, restore, migrate, move site, create post, manage content, site status, check plugins, performance check, SEO audit
14
+
15
+ Keywords that indicate **development**:
16
+ create block, block.json, theme.json, register_rest_route, plugin development, hooks, PHPStan, build, test, scaffold
17
+
18
+ ## Step 1: classify repo kind (from triage — development only)
19
+
20
+ Run `wp-project-triage` first, then use `triage.project.kind`:
21
+
22
+ - `wp-core` → WordPress core checkout work.
23
+ - `wp-site` → full site repo (wp-content present).
24
+ - `wp-block-theme` → theme.json/templates/patterns workflows.
25
+ - `wp-theme` → classic theme workflows.
26
+ - `wp-block-plugin` → Gutenberg block development in a plugin.
27
+ - `wp-plugin` / `wp-mu-plugin` → plugin workflows.
28
+ - `gutenberg` → Gutenberg monorepo workflows.
29
+
30
+ Priority: `gutenberg` > `wp-core` > `wp-site` > `wp-block-theme` > `wp-block-plugin` > `wp-theme` > `wp-plugin`.
31
+
32
+ ## Step 2a: route by development intent (keywords)
33
+
34
+ - **Interactivity API / data-wp-* / @wordpress/interactivity / viewScriptModule**
35
+ → `wp-interactivity-api`
36
+ - **Abilities API / wp_register_ability / wp-abilities/v1**
37
+ → `wp-abilities-api`
38
+ - **Blocks / block.json / registerBlockType / attributes / save serialization**
39
+ → `wp-block-development`
40
+ - **theme.json / Global Styles / templates/*.html / patterns/**
41
+ → `wp-block-themes`
42
+ - **Plugin architecture / hooks / activation / Settings API / admin pages**
43
+ → `wp-plugin-development`
44
+ - **REST endpoint / register_rest_route / permission_callback**
45
+ → `wp-rest-api`
46
+ - **WP-CLI / wp-cli.yml / commands**
47
+ → `wp-wpcli-and-ops`
48
+ - **PHPStan / static analysis / phpstan.neon**
49
+ → `wp-phpstan`
50
+ - **Performance profiling / query optimization / editor slowness**
51
+ → `wp-performance` (backend profiling with WP-CLI doctor/profile)
52
+ - **UI components / design tokens / @wordpress/components / @wordpress/ui / WPDS**
53
+ → `wpds` (WordPress Design System)
54
+ - **Playground / disposable WP / blueprint / sandbox / test environment / version switching**
55
+ → `wp-playground`
56
+
57
+ ## Step 2b: route by operational intent (keywords)
58
+
59
+ - **Deploy / push / production / Hostinger**
60
+ → `wp-deploy` skill + `wp-deployment-engineer` agent
61
+ - **Audit / security check / vulnerability / hacked / health check**
62
+ → `wp-audit` skill + `wp-security-auditor` agent
63
+ - **Backup / snapshot / disaster recovery / restore**
64
+ → `wp-backup` skill
65
+ - **Migrate / move / transfer / clone site / change hosting**
66
+ → `wp-migrate` skill
67
+ - **Create post / write content / manage pages / categories / SEO content**
68
+ → `wp-content` skill + `wp-content-strategist` agent
69
+ - **Slow site / PageSpeed / Core Web Vitals / optimize performance**
70
+ → `wp-audit` skill (performance scope) + `wp-performance-optimizer` agent
71
+ - **Site status / list plugins / manage users / multi-site coordination**
72
+ → `wp-site-manager` agent
73
+ - **DNS / domain / SSL / hosting configuration**
74
+ → `wp-site-manager` agent (Hostinger MCP tools)
75
+
76
+ ## Step 3: guardrails checklist (always)
77
+
78
+ ### Development guardrails
79
+ - Verify detected tooling before suggesting commands (Composer vs npm/yarn/pnpm).
80
+ - Prefer existing lint/test scripts if present.
81
+ - If version constraints aren’t detectable, ask for target WP core and PHP versions.
82
+
83
+ ### Operations guardrails
84
+ - Confirm target site before any operation.
85
+ - Verify backups exist before destructive operations (deploy, migrate, restore).
86
+ - Get explicit user confirmation before publishing, deleting, or modifying live content.
87
+ - Never deactivate plugins or delete content without listing dependencies first.
88
+ - For multi-site operations, announce which site you’re operating on.
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: wp-abilities-api
3
+ description: "Use when working with the WordPress Abilities API (wp_register_ability, wp_register_ability_category, /wp-json/wp-abilities/v1/*, @wordpress/abilities) including defining abilities, categories, meta, REST exposure, and permissions checks for clients."
4
+ compatibility: "Targets WordPress 6.9+ (PHP 7.2.24+). Filesystem-based agent with bash + node. Some workflows require WP-CLI."
5
+ version: 1.0.0
6
+ source: "WordPress/agent-skills (GPL-2.0-or-later)"
7
+ ---
8
+
9
+ # WP Abilities API
10
+
11
+ ## When to use
12
+
13
+ Use this skill when the task involves:
14
+
15
+ - registering abilities or ability categories in PHP,
16
+ - exposing abilities to clients via REST (`wp-abilities/v1`),
17
+ - consuming abilities in JS (notably `@wordpress/abilities`),
18
+ - diagnosing “ability doesn’t show up” / “client can’t see ability” / “REST returns empty”.
19
+
20
+ ## Inputs required
21
+
22
+ - Repo root (run `wp-project-triage` first if you haven’t).
23
+ - Target WordPress version(s) and whether this is WP core or a plugin/theme.
24
+ - Where the change should live (plugin vs theme vs mu-plugin).
25
+
26
+ ## Procedure
27
+
28
+ ### 1) Confirm availability and version constraints
29
+
30
+ - If this is WP core work, check `signals.isWpCoreCheckout` and `versions.wordpress.core`.
31
+ - If the project targets WP < 6.9, you may need the Abilities API plugin/package rather than relying on core.
32
+
33
+ ### 2) Find existing Abilities usage
34
+
35
+ Search for these in the repo:
36
+
37
+ - `wp_register_ability(`
38
+ - `wp_register_ability_category(`
39
+ - `wp_abilities_api_init`
40
+ - `wp_abilities_api_categories_init`
41
+ - `wp-abilities/v1`
42
+ - `@wordpress/abilities`
43
+
44
+ If none exist, decide whether you’re introducing Abilities API fresh (new registrations + client consumption) or only consuming.
45
+
46
+ ### 3) Register categories (optional)
47
+
48
+ If you need a logical grouping, register an ability category early (see `references/php-registration.md`).
49
+
50
+ ### 4) Register abilities (PHP)
51
+
52
+ Implement the ability in PHP registration with:
53
+
54
+ - stable `id` (namespaced),
55
+ - `label`/`description`,
56
+ - `category`,
57
+ - `meta`:
58
+ - add `readonly: true` when the ability is informational,
59
+ - set `show_in_rest: true` for abilities you want visible to clients.
60
+
61
+ Use the documented init hooks for Abilities API registration so they load at the right time (see `references/php-registration.md`).
62
+
63
+ ### 5) Confirm REST exposure
64
+
65
+ - Verify the REST endpoints exist and return expected results (see `references/rest-api.md`).
66
+ - If the client still can’t see the ability, confirm `meta.show_in_rest` is enabled and you’re querying the right endpoint.
67
+
68
+ ### 6) Consume from JS (if needed)
69
+
70
+ - Prefer `@wordpress/abilities` APIs for client-side access and checks.
71
+ - Ensure build tooling includes the dependency and the project’s build pipeline bundles it.
72
+
73
+ ## Verification
74
+
75
+ - `wp-project-triage` indicates `signals.usesAbilitiesApi: true` after your change (if applicable).
76
+ - REST check (in a WP environment): endpoints under `wp-abilities/v1` return your ability and category when expected.
77
+ - If the repo has tests, add/update coverage near:
78
+ - PHP: ability registration and meta exposure
79
+ - JS: ability consumption and UI gating
80
+
81
+ ## Failure modes / debugging
82
+
83
+ - Ability never appears:
84
+ - registration code not running (wrong hook / file not loaded),
85
+ - missing `meta.show_in_rest`,
86
+ - incorrect category/ID mismatch.
87
+ - REST shows ability but JS doesn’t:
88
+ - wrong REST base/namespace,
89
+ - JS dependency not bundled,
90
+ - caching (object/page caches) masking changes.
91
+
92
+ ## Escalation
93
+
94
+ - If you’re uncertain about version support, confirm target WP core versions and whether Abilities API is expected from core or as a plugin.
95
+ - For canonical details, consult:
96
+ - `references/rest-api.md`
97
+ - `references/php-registration.md`
@@ -0,0 +1,67 @@
1
+ # PHP registration quick guide
2
+
3
+ Key concepts and entrypoints for the WordPress Abilities API:
4
+
5
+ - Register ability categories and abilities in PHP.
6
+ - Use the Abilities API init hooks to ensure registration occurs at the right lifecycle time.
7
+
8
+ ## Hook order (critical)
9
+
10
+ **Categories must be registered before abilities.** Use the correct hooks:
11
+
12
+ 1. `wp_abilities_api_categories_init` — Register categories here first.
13
+ 2. `wp_abilities_api_init` — Register abilities here (after categories exist).
14
+
15
+ **Warning:** Registering abilities outside `wp_abilities_api_init` triggers `_doing_it_wrong()` and the registration will fail.
16
+
17
+ ```php
18
+ // 1. Register category first
19
+ add_action( 'wp_abilities_api_categories_init', function() {
20
+ wp_register_ability_category( 'my-plugin', [
21
+ 'label' => __( 'My Plugin', 'my-plugin' ),
22
+ ] );
23
+ } );
24
+
25
+ // 2. Then register abilities
26
+ add_action( 'wp_abilities_api_init', function() {
27
+ wp_register_ability( 'my-plugin/get-info', [
28
+ 'label' => __( 'Get Site Info', 'my-plugin' ),
29
+ 'description' => __( 'Returns basic site information.', 'my-plugin' ),
30
+ 'category' => 'my-plugin',
31
+ 'callback' => 'my_plugin_get_info_callback',
32
+ 'meta' => [ 'show_in_rest' => true ],
33
+ ] );
34
+ } );
35
+ ```
36
+
37
+ ## Common primitives
38
+
39
+ - `wp_register_ability_category( $category_id, $args )`
40
+ - `wp_register_ability( $ability_id, $args )`
41
+
42
+ ## Key arguments for `wp_register_ability()`
43
+
44
+ | Argument | Description |
45
+ |----------|-------------|
46
+ | `label` | Human-readable name for UI (e.g., command palette) |
47
+ | `description` | What the ability does |
48
+ | `category` | Category ID (must be registered first) |
49
+ | `callback` | Function that executes the ability |
50
+ | `input_schema` | JSON Schema for expected input (enables validation) |
51
+ | `output_schema` | JSON Schema for returned output |
52
+ | `permission_callback` | Optional function to check if current user can execute |
53
+ | `meta.show_in_rest` | Set `true` to expose via REST API |
54
+ | `meta.readonly` | Set `true` if ability is informational only |
55
+
56
+ ## Recommended patterns
57
+
58
+ - Namespace IDs (e.g. `my-plugin:feature.edit`).
59
+ - Treat IDs as stable API; changing IDs is a breaking change.
60
+ - Use `input_schema` and `output_schema` for validation and to help AI agents understand usage.
61
+ - Always include a `permission_callback` for abilities that modify data.
62
+
63
+ ## References
64
+
65
+ - Abilities API handbook: https://developer.wordpress.org/apis/abilities-api/
66
+ - Dev note: https://make.wordpress.org/core/2025/11/10/abilities-api-in-wordpress-6-9/
67
+
@@ -0,0 +1,13 @@
1
+ # REST API quick guide (`wp-abilities/v1`)
2
+
3
+ The Abilities API exposes endpoints under the REST namespace:
4
+
5
+ - `wp-abilities/v1/abilities`
6
+ - `wp-abilities/v1/categories`
7
+
8
+ Debug checklist:
9
+
10
+ - Confirm the route exists under `wp-json/wp-abilities/v1/...`.
11
+ - Verify the ability/category shows in REST responses.
12
+ - If missing, confirm `meta.show_in_rest` is enabled for that ability.
13
+