wordpress-agent-kit 0.4.0 → 0.6.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 (153) hide show
  1. package/.agents/skills/wp-bootstrap/SKILL.md +314 -0
  2. package/.agents/skills/wp-bootstrap/references/composer-setup.md +275 -0
  3. package/.agents/skills/wp-bootstrap/references/monorepo-patterns.md +184 -0
  4. package/.agents/skills/wp-bootstrap/scripts/bootstrap.sh +151 -0
  5. package/.agents/skills/wp-bootstrap/scripts/detect-structure.mjs +466 -0
  6. package/.agents/skills/wp-bootstrap/scripts/package-wp.sh +173 -0
  7. package/.agents/skills/wp-bootstrap/scripts/playground-start.sh +148 -0
  8. package/.agents/skills/wp-bootstrap/scripts/playground-verify.sh +165 -0
  9. package/.agents/skills/wp-bootstrap/scripts/setup-github.sh +417 -0
  10. package/{.github → .agents}/skills/wp-wpcli-and-ops/SKILL.md +11 -9
  11. package/.agents/skills/wp-wpengine/SKILL.md +462 -0
  12. package/.agents/skills/wp-wpengine/references/ci-gate.md +469 -0
  13. package/.agents/skills/wp-wpengine/references/github-actions-deploy.md +743 -0
  14. package/.agents/skills/wp-wpengine/scripts/ci-gate.sh +118 -0
  15. package/.agents/skills/wp-wpengine/scripts/wpe-check.sh +89 -0
  16. package/.agents/skills/wp-wpengine/scripts/wpe-preflight.sh +104 -0
  17. package/.github/agents/wp-architect.agent.md +1 -2
  18. package/.github/copilot-instructions.md +1 -1
  19. package/.github/instructions/wordpress-workflow.instructions.md +3 -3
  20. package/AGENTS.md +22 -10
  21. package/AGENTS.template.md +20 -10
  22. package/README.md +89 -85
  23. package/dist/cli.js +7 -1
  24. package/dist/commands/bootstrap.js +105 -0
  25. package/dist/commands/clean-skills.js +64 -0
  26. package/dist/commands/setup.js +6 -2
  27. package/dist/commands/sync-skills.js +3 -0
  28. package/dist/lib/api.js +165 -5
  29. package/dist/lib/bootstrap.js +352 -0
  30. package/dist/lib/installer.js +166 -2
  31. package/extensions/wp-agent-kit/index.ts +325 -10
  32. package/package.json +10 -14
  33. package/skills-custom/wp-bootstrap/SKILL.md +314 -0
  34. package/skills-custom/wp-bootstrap/references/composer-setup.md +275 -0
  35. package/skills-custom/wp-bootstrap/references/monorepo-patterns.md +184 -0
  36. package/skills-custom/wp-bootstrap/scripts/bootstrap.sh +151 -0
  37. package/skills-custom/wp-bootstrap/scripts/detect-structure.mjs +466 -0
  38. package/skills-custom/wp-bootstrap/scripts/package-wp.sh +173 -0
  39. package/skills-custom/wp-bootstrap/scripts/playground-start.sh +148 -0
  40. package/skills-custom/wp-bootstrap/scripts/playground-verify.sh +165 -0
  41. package/skills-custom/wp-bootstrap/scripts/setup-github.sh +417 -0
  42. package/skills-custom/wp-wpengine/SKILL.md +362 -27
  43. package/skills-custom/wp-wpengine/references/ci-gate.md +469 -0
  44. package/skills-custom/wp-wpengine/references/github-actions-deploy.md +743 -0
  45. package/skills-custom/wp-wpengine/scripts/ci-gate.sh +118 -0
  46. package/skills-custom/wp-wpengine/scripts/wpe-check.sh +89 -0
  47. package/skills-custom/wp-wpengine/scripts/wpe-preflight.sh +104 -0
  48. package/.github/skills/wp-wpengine/SKILL.md +0 -127
  49. package/.github/workflows/ci.yml +0 -44
  50. package/.husky/pre-commit +0 -7
  51. package/CLI_REVIEW.md +0 -250
  52. package/biome.json +0 -39
  53. /package/{.github → .agents}/skills/blueprint/SKILL.md +0 -0
  54. /package/{.github → .agents}/skills/wordpress-router/SKILL.md +0 -0
  55. /package/{.github → .agents}/skills/wordpress-router/references/decision-tree.md +0 -0
  56. /package/{.github → .agents}/skills/wp-abilities-api/SKILL.md +0 -0
  57. /package/{.github → .agents}/skills/wp-abilities-api/references/delegate-helper-pattern.md +0 -0
  58. /package/{.github → .agents}/skills/wp-abilities-api/references/domain-vs-projection.md +0 -0
  59. /package/{.github → .agents}/skills/wp-abilities-api/references/error-code-vocabulary.md +0 -0
  60. /package/{.github → .agents}/skills/wp-abilities-api/references/grouping-heuristic.md +0 -0
  61. /package/{.github → .agents}/skills/wp-abilities-api/references/input-schema-gotchas.md +0 -0
  62. /package/{.github → .agents}/skills/wp-abilities-api/references/php-registration.md +0 -0
  63. /package/{.github → .agents}/skills/wp-abilities-api/references/plugin-family-patterns.md +0 -0
  64. /package/{.github → .agents}/skills/wp-abilities-api/references/rest-api.md +0 -0
  65. /package/{.github → .agents}/skills/wp-abilities-api/references/shared-core-service.md +0 -0
  66. /package/{.github → .agents}/skills/wp-abilities-audit/SKILL.md +0 -0
  67. /package/{.github → .agents}/skills/wp-abilities-audit/references/audit-schema.md +0 -0
  68. /package/{.github → .agents}/skills/wp-abilities-audit/references/capability-gate-tracing.md +0 -0
  69. /package/{.github → .agents}/skills/wp-abilities-audit/references/controller-enumeration.md +0 -0
  70. /package/{.github → .agents}/skills/wp-abilities-verify/SKILL.md +0 -0
  71. /package/{.github → .agents}/skills/wp-abilities-verify/references/annotation-correctness.md +0 -0
  72. /package/{.github → .agents}/skills/wp-abilities-verify/references/audit-schema-validation.md +0 -0
  73. /package/{.github → .agents}/skills/wp-abilities-verify/references/permission-roundtrip.md +0 -0
  74. /package/{.github → .agents}/skills/wp-abilities-verify/references/runtime-harness.md +0 -0
  75. /package/{.github → .agents}/skills/wp-abilities-verify/references/schema-lints.md +0 -0
  76. /package/{.github → .agents}/skills/wp-abilities-verify/references/static-enumeration.md +0 -0
  77. /package/{.github → .agents}/skills/wp-block-development/SKILL.md +0 -0
  78. /package/{.github → .agents}/skills/wp-block-development/references/attributes-and-serialization.md +0 -0
  79. /package/{.github → .agents}/skills/wp-block-development/references/block-json.md +0 -0
  80. /package/{.github → .agents}/skills/wp-block-development/references/creating-new-blocks.md +0 -0
  81. /package/{.github → .agents}/skills/wp-block-development/references/debugging.md +0 -0
  82. /package/{.github → .agents}/skills/wp-block-development/references/deprecations.md +0 -0
  83. /package/{.github → .agents}/skills/wp-block-development/references/dynamic-rendering.md +0 -0
  84. /package/{.github → .agents}/skills/wp-block-development/references/inner-blocks.md +0 -0
  85. /package/{.github → .agents}/skills/wp-block-development/references/registration.md +0 -0
  86. /package/{.github → .agents}/skills/wp-block-development/references/supports-and-wrappers.md +0 -0
  87. /package/{.github → .agents}/skills/wp-block-development/references/tooling-and-testing.md +0 -0
  88. /package/{.github → .agents}/skills/wp-block-development/scripts/list_blocks.mjs +0 -0
  89. /package/{.github → .agents}/skills/wp-block-themes/SKILL.md +0 -0
  90. /package/{.github → .agents}/skills/wp-block-themes/references/creating-new-block-theme.md +0 -0
  91. /package/{.github → .agents}/skills/wp-block-themes/references/debugging.md +0 -0
  92. /package/{.github → .agents}/skills/wp-block-themes/references/patterns.md +0 -0
  93. /package/{.github → .agents}/skills/wp-block-themes/references/style-variations.md +0 -0
  94. /package/{.github → .agents}/skills/wp-block-themes/references/templates-and-parts.md +0 -0
  95. /package/{.github → .agents}/skills/wp-block-themes/references/theme-json.md +0 -0
  96. /package/{.github → .agents}/skills/wp-block-themes/scripts/detect_block_themes.mjs +0 -0
  97. /package/{.github → .agents}/skills/wp-interactivity-api/SKILL.md +0 -0
  98. /package/{.github → .agents}/skills/wp-interactivity-api/references/debugging.md +0 -0
  99. /package/{.github → .agents}/skills/wp-interactivity-api/references/directives-quickref.md +0 -0
  100. /package/{.github → .agents}/skills/wp-interactivity-api/references/server-side-rendering.md +0 -0
  101. /package/{.github → .agents}/skills/wp-performance/SKILL.md +0 -0
  102. /package/{.github → .agents}/skills/wp-performance/references/autoload-options.md +0 -0
  103. /package/{.github → .agents}/skills/wp-performance/references/cron.md +0 -0
  104. /package/{.github → .agents}/skills/wp-performance/references/database.md +0 -0
  105. /package/{.github → .agents}/skills/wp-performance/references/http-api.md +0 -0
  106. /package/{.github → .agents}/skills/wp-performance/references/measurement.md +0 -0
  107. /package/{.github → .agents}/skills/wp-performance/references/object-cache.md +0 -0
  108. /package/{.github → .agents}/skills/wp-performance/references/query-monitor-headless.md +0 -0
  109. /package/{.github → .agents}/skills/wp-performance/references/server-timing.md +0 -0
  110. /package/{.github → .agents}/skills/wp-performance/references/wp-cli-doctor.md +0 -0
  111. /package/{.github → .agents}/skills/wp-performance/references/wp-cli-profile.md +0 -0
  112. /package/{.github → .agents}/skills/wp-performance/scripts/perf_inspect.mjs +0 -0
  113. /package/{.github → .agents}/skills/wp-phpstan/SKILL.md +0 -0
  114. /package/{.github → .agents}/skills/wp-phpstan/references/configuration.md +0 -0
  115. /package/{.github → .agents}/skills/wp-phpstan/references/third-party-classes.md +0 -0
  116. /package/{.github → .agents}/skills/wp-phpstan/references/wordpress-annotations.md +0 -0
  117. /package/{.github → .agents}/skills/wp-phpstan/scripts/phpstan_inspect.mjs +0 -0
  118. /package/{.github → .agents}/skills/wp-playground/SKILL.md +0 -0
  119. /package/{.github → .agents}/skills/wp-playground/references/blueprints.md +0 -0
  120. /package/{.github → .agents}/skills/wp-playground/references/cli-commands.md +0 -0
  121. /package/{.github → .agents}/skills/wp-playground/references/debugging.md +0 -0
  122. /package/{.github → .agents}/skills/wp-playground/references/e2e-playwright.md +0 -0
  123. /package/{.github → .agents}/skills/wp-plugin-development/SKILL.md +0 -0
  124. /package/{.github → .agents}/skills/wp-plugin-development/references/data-and-cron.md +0 -0
  125. /package/{.github → .agents}/skills/wp-plugin-development/references/debugging.md +0 -0
  126. /package/{.github → .agents}/skills/wp-plugin-development/references/lifecycle.md +0 -0
  127. /package/{.github → .agents}/skills/wp-plugin-development/references/security.md +0 -0
  128. /package/{.github → .agents}/skills/wp-plugin-development/references/settings-api.md +0 -0
  129. /package/{.github → .agents}/skills/wp-plugin-development/references/structure.md +0 -0
  130. /package/{.github → .agents}/skills/wp-plugin-development/scripts/detect_plugins.mjs +0 -0
  131. /package/{.github → .agents}/skills/wp-plugin-directory-guidelines/SKILL.md +0 -0
  132. /package/{.github → .agents}/skills/wp-plugin-directory-guidelines/references/gpl-compliance.md +0 -0
  133. /package/{.github → .agents}/skills/wp-plugin-directory-guidelines/references/guideline-review-checklist.md +0 -0
  134. /package/{.github → .agents}/skills/wp-plugin-directory-guidelines/references/naming-rules.md +0 -0
  135. /package/{.github → .agents}/skills/wp-project-triage/SKILL.md +0 -0
  136. /package/{.github → .agents}/skills/wp-project-triage/references/triage.schema.json +0 -0
  137. /package/{.github → .agents}/skills/wp-project-triage/scripts/detect_wp_project.mjs +0 -0
  138. /package/{.github → .agents}/skills/wp-rest-api/SKILL.md +0 -0
  139. /package/{.github → .agents}/skills/wp-rest-api/references/authentication.md +0 -0
  140. /package/{.github → .agents}/skills/wp-rest-api/references/custom-content-types.md +0 -0
  141. /package/{.github → .agents}/skills/wp-rest-api/references/discovery-and-params.md +0 -0
  142. /package/{.github → .agents}/skills/wp-rest-api/references/responses-and-fields.md +0 -0
  143. /package/{.github → .agents}/skills/wp-rest-api/references/routes-and-endpoints.md +0 -0
  144. /package/{.github → .agents}/skills/wp-rest-api/references/schema.md +0 -0
  145. /package/{.github → .agents}/skills/wp-wpcli-and-ops/references/automation.md +0 -0
  146. /package/{.github → .agents}/skills/wp-wpcli-and-ops/references/cron-and-cache.md +0 -0
  147. /package/{.github → .agents}/skills/wp-wpcli-and-ops/references/debugging.md +0 -0
  148. /package/{.github → .agents}/skills/wp-wpcli-and-ops/references/multisite.md +0 -0
  149. /package/{.github → .agents}/skills/wp-wpcli-and-ops/references/packages-and-updates.md +0 -0
  150. /package/{.github → .agents}/skills/wp-wpcli-and-ops/references/safety.md +0 -0
  151. /package/{.github → .agents}/skills/wp-wpcli-and-ops/references/search-replace.md +0 -0
  152. /package/{.github → .agents}/skills/wp-wpcli-and-ops/scripts/wpcli_inspect.mjs +0 -0
  153. /package/{.github → .agents}/skills/wpds/SKILL.md +0 -0
@@ -12,10 +12,78 @@ export const PLATFORM_FOLDERS = {
12
12
  agent: '.agent',
13
13
  pi: '.pi/agent',
14
14
  };
15
+ /**
16
+ * Copy universal skills from .agents/skills/ to the target directory.
17
+ * Uses the AgentSkills.io convention (.agents/skills/) which is discovered
18
+ * automatically by Pi, GitHub Copilot, and other agents.
19
+ */
20
+ function copyUniversalSkills(targetDir) {
21
+ const sourceSkillsDir = path.join(PACKAGE_ROOT, '.agents', 'skills');
22
+ const targetSkillsDir = path.join(targetDir, '.agents', 'skills');
23
+ const copied = [];
24
+ const skipped = [];
25
+ if (!fs.existsSync(sourceSkillsDir)) {
26
+ return { copied, skipped };
27
+ }
28
+ if (!fs.existsSync(targetSkillsDir)) {
29
+ fs.mkdirSync(targetSkillsDir, { recursive: true });
30
+ }
31
+ for (const skillName of fs.readdirSync(sourceSkillsDir)) {
32
+ const src = path.join(sourceSkillsDir, skillName);
33
+ if (!fs.statSync(src).isDirectory())
34
+ continue;
35
+ const dest = path.join(targetSkillsDir, skillName);
36
+ if (fs.existsSync(dest)) {
37
+ skipped.push(skillName);
38
+ }
39
+ else {
40
+ fs.cpSync(src, dest, { recursive: true });
41
+ copied.push(skillName);
42
+ }
43
+ }
44
+ return { copied, skipped };
45
+ }
46
+ /**
47
+ * Merge custom skills from skills-custom/ into the target's universal skills directory.
48
+ * Only copies skills that don't already exist in the target (avoids overwriting
49
+ * upstream versions that may have been user-modified).
50
+ * Returns arrays of skill names that were merged or skipped.
51
+ */
52
+ function mergeCustomSkills(targetDir) {
53
+ const customSkillsDir = path.join(PACKAGE_ROOT, 'skills-custom');
54
+ if (!fs.existsSync(customSkillsDir)) {
55
+ return { merged: [], skipped: [] };
56
+ }
57
+ const targetSkills = path.join(targetDir, '.agents', 'skills');
58
+ const merged = [];
59
+ const skipped = [];
60
+ for (const skillName of fs.readdirSync(customSkillsDir)) {
61
+ const src = path.join(customSkillsDir, skillName);
62
+ if (!fs.statSync(src).isDirectory())
63
+ continue;
64
+ const dest = path.join(targetSkills, skillName);
65
+ if (fs.existsSync(dest)) {
66
+ // Don't overwrite — upstream or user version takes priority
67
+ skipped.push(skillName);
68
+ }
69
+ else {
70
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
71
+ fs.cpSync(src, dest, { recursive: true });
72
+ merged.push(skillName);
73
+ }
74
+ }
75
+ return { merged, skipped };
76
+ }
15
77
  /**
16
78
  * Installs the WordPress Agent Kit into the specified directory for a given platform.
17
79
  * If the kit is already installed, uses safe update logic to preserve user modifications.
18
80
  *
81
+ * Installation layout:
82
+ * - .agents/skills/ — Universal skills (AgentSkills.io convention)
83
+ * - .github/ (or .pi/agent/, etc.) — Platform-specific agents, instructions, prompts
84
+ * - AGENTS.md — Project-level agent instructions
85
+ * - AGENTS.template.md — Template reference for future updates
86
+ *
19
87
  * @param targetDir - The directory where the kit should be installed.
20
88
  * @param platform - The target platform (github, cursor, claude, agent, pi)
21
89
  * @returns InstallKitResult with details of what was created/skipped
@@ -31,6 +99,49 @@ export function installKit(targetDir, platform = 'github', options = {}) {
31
99
  // Fresh install or fallback to full replacement
32
100
  return fullInstall(targetDir, platform, force);
33
101
  }
102
+ /**
103
+ * Migrate legacy skill directories to the universal .agents/skills/ convention.
104
+ * Detects skills in platform-specific directories (e.g., .github/skills/, .pi/agent/skills/)
105
+ * and moves them to .agents/skills/ if they don't already exist there.
106
+ * Returns lists of migrated and removed skill names.
107
+ */
108
+ function migrateLegacySkills(targetDir, platform) {
109
+ const platformFolder = PLATFORM_FOLDERS[platform];
110
+ const legacySkillsDir = path.join(targetDir, platformFolder, 'skills');
111
+ const universalSkillsDir = path.join(targetDir, '.agents', 'skills');
112
+ const migrated = [];
113
+ const removed = [];
114
+ if (!fs.existsSync(legacySkillsDir)) {
115
+ return { migrated, removed };
116
+ }
117
+ // Ensure universal directory exists
118
+ if (!fs.existsSync(universalSkillsDir)) {
119
+ fs.mkdirSync(universalSkillsDir, { recursive: true });
120
+ }
121
+ for (const skillName of fs.readdirSync(legacySkillsDir)) {
122
+ const srcPath = path.join(legacySkillsDir, skillName);
123
+ const destPath = path.join(universalSkillsDir, skillName);
124
+ if (!fs.statSync(srcPath).isDirectory())
125
+ continue;
126
+ if (!fs.existsSync(destPath)) {
127
+ // Migrate: copy to universal location
128
+ fs.cpSync(srcPath, destPath, { recursive: true });
129
+ migrated.push(skillName);
130
+ }
131
+ else {
132
+ // Already exists in universal location — just remove from legacy
133
+ removed.push(skillName);
134
+ }
135
+ // Remove from legacy location
136
+ fs.rmSync(srcPath, { recursive: true, force: true });
137
+ }
138
+ // Remove the legacy skills directory if empty
139
+ const remaining = fs.readdirSync(legacySkillsDir);
140
+ if (remaining.length === 0) {
141
+ fs.rmSync(legacySkillsDir, { recursive: true, force: true });
142
+ }
143
+ return { migrated, removed };
144
+ }
34
145
  /**
35
146
  * Check if kit is already installed for a platform.
36
147
  */
@@ -53,18 +164,23 @@ function fullInstall(targetDir, platform, _force) {
53
164
  const sourceGithub = path.join(PACKAGE_ROOT, '.github');
54
165
  const filesCreated = [];
55
166
  const filesSkipped = [];
56
- // Copy platform-specific folder
167
+ // Copy platform-specific folder (agents, instructions — NOT skills)
57
168
  const targetPlatform = path.join(targetDir, platformFolder);
58
169
  if (fs.existsSync(targetPlatform)) {
59
170
  fs.rmSync(targetPlatform, { recursive: true, force: true });
60
171
  }
61
172
  if (fs.existsSync(sourceGithub)) {
62
173
  fs.cpSync(sourceGithub, targetPlatform, { recursive: true });
174
+ // Remove skills from platform dir — skills go to .agents/skills/ instead
175
+ const platformSkills = path.join(targetPlatform, 'skills');
176
+ if (fs.existsSync(platformSkills)) {
177
+ fs.rmSync(platformSkills, { recursive: true, force: true });
178
+ }
63
179
  }
64
180
  else {
65
181
  throw new Error('Could not find source .github directory.');
66
182
  }
67
- // Collect created files
183
+ // Collect created files (excluding skills which are in .agents/)
68
184
  const collectFiles = (dir, prefix) => {
69
185
  if (!fs.existsSync(dir))
70
186
  return;
@@ -81,6 +197,30 @@ function fullInstall(targetDir, platform, _force) {
81
197
  }
82
198
  };
83
199
  collectFiles(targetPlatform, platformFolder);
200
+ // Migrate legacy skills from platform-specific dir to .agents/skills/
201
+ const migrationResult = migrateLegacySkills(targetDir, platform);
202
+ for (const skill of migrationResult.migrated) {
203
+ filesCreated.push(`.agents/skills/${skill} (migrated from ${platformFolder}/skills/)`);
204
+ }
205
+ for (const skill of migrationResult.removed) {
206
+ filesSkipped.push(`.agents/skills/${skill} (already existed, removed legacy copy)`);
207
+ }
208
+ // Copy universal skills to .agents/skills/ (AgentSkills.io convention)
209
+ const universalResult = copyUniversalSkills(targetDir);
210
+ for (const skill of universalResult.copied) {
211
+ filesCreated.push(`.agents/skills/${skill}`);
212
+ }
213
+ for (const skill of universalResult.skipped) {
214
+ filesSkipped.push(`.agents/skills/${skill} (already exists, preserved)`);
215
+ }
216
+ // Merge custom skills from skills-custom/ (e.g., wp-wpengine)
217
+ const customResult = mergeCustomSkills(targetDir);
218
+ for (const skill of customResult.merged) {
219
+ filesCreated.push(`.agents/skills/${skill}`);
220
+ }
221
+ for (const skill of customResult.skipped) {
222
+ filesSkipped.push(`.agents/skills/${skill} (already exists, preserved)`);
223
+ }
84
224
  // Copy AGENTS.md template
85
225
  const targetAgentsTemplate = path.join(targetDir, 'AGENTS.template.md');
86
226
  if (fs.existsSync(templatePath)) {
@@ -140,6 +280,30 @@ function safeUpdateInstall(targetDir, platform, options) {
140
280
  else {
141
281
  filesSkipped.push('AGENTS.md (preserved)');
142
282
  }
283
+ // Migrate legacy skills from platform-specific dir to .agents/skills/
284
+ const migrationResult = migrateLegacySkills(targetDir, platform);
285
+ for (const skill of migrationResult.migrated) {
286
+ filesCreated.push(`.agents/skills/${skill} (migrated from ${PLATFORM_FOLDERS[platform]}/skills/)`);
287
+ }
288
+ for (const skill of migrationResult.removed) {
289
+ filesSkipped.push(`.agents/skills/${skill} (already existed, removed legacy copy)`);
290
+ }
291
+ // Copy universal skills to .agents/skills/ (AgentSkills.io convention)
292
+ const universalResult = copyUniversalSkills(targetDir);
293
+ for (const skill of universalResult.copied) {
294
+ filesCreated.push(`.agents/skills/${skill}`);
295
+ }
296
+ for (const skill of universalResult.skipped) {
297
+ filesSkipped.push(`.agents/skills/${skill} (already exists, preserved)`);
298
+ }
299
+ // Merge custom skills from skills-custom/ (e.g., wp-wpengine)
300
+ const customResult = mergeCustomSkills(targetDir);
301
+ for (const skill of customResult.merged) {
302
+ filesCreated.push(`.agents/skills/${skill}`);
303
+ }
304
+ for (const skill of customResult.skipped) {
305
+ filesSkipped.push(`.agents/skills/${skill} (already exists, preserved)`);
306
+ }
143
307
  return {
144
308
  targetDir,
145
309
  platform,
@@ -1,12 +1,13 @@
1
+ import fs from 'node:fs';
1
2
  import path from 'node:path';
2
3
  import { fileURLToPath } from 'node:url';
3
4
  /**
4
5
  * WordPress Agent Kit — Pi Extension
5
6
  *
6
7
  * Provides Pi Coding Agent with WordPress development tools:
7
- * - 17 WordPress agent skills (plugin/theme/block dev, REST API, WP-CLI, etc.)
8
+ * - 18 WordPress agent skills (17 upstream + wp-wpengine custom) at .agents/skills/ (AgentSkills.io convention)
8
9
  * - Project triage detection
9
- * - Skill installation, syncing, and upgrade management
10
+ * - Skill installation, syncing, upgrade, orphan cleanup, and project bootstrapping
10
11
  *
11
12
  * Follows Pi Coding Agent SDK conventions (extensions.md, packages.md, skills.md).
12
13
  */
@@ -25,17 +26,50 @@ try {
25
26
  apiModule = (await import('../../src/lib/api.js')) as typeof import('../../dist/lib/api.js');
26
27
  }
27
28
 
28
- const { installKitApi, syncSkillsApi, runTriageApi, isKitInstalled, loadManifest } = apiModule;
29
+ const {
30
+ installKitApi,
31
+ syncSkillsApi,
32
+ runTriageApi,
33
+ cleanSkillsApi,
34
+ bootstrapApi,
35
+ isKitInstalled,
36
+ loadManifest,
37
+ } = apiModule;
29
38
 
30
39
  export default function (pi: ExtensionAPI) {
31
40
  // =========================================================================
32
41
  // Skills Registration
33
42
  // =========================================================================
43
+ // Primary skills (.agents/skills/) are registered via `pi.skills` in package.json.
44
+ // Pi also auto-discovers from .agents/skills/ at the project level (AgentSkills.io convention).
45
+ // The resources_discover handler ONLY supplements skills that aren't already
46
+ // covered by the static manifest — specifically, custom skills from
47
+ // skills-custom/ that haven't been synced into .agents/skills/ yet.
48
+ // This avoids name collisions (Pi keeps first-loaded, warns on duplicates).
34
49
  pi.on('resources_discover', async (_event, _ctx) => {
35
- const skillsDir = path.join(PACKAGE_ROOT, '.github', 'skills');
50
+ const canonicalSkillsDir = path.join(PACKAGE_ROOT, '.agents', 'skills');
51
+ const customSkillsDir = path.join(PACKAGE_ROOT, 'skills-custom');
36
52
  const promptsDir = path.join(PACKAGE_ROOT, '.github', 'prompts');
53
+
54
+ // Only discover custom skills that aren't already in .agents/skills/
55
+ // (which is registered via pi.skills in package.json).
56
+ const skillPaths: string[] = [];
57
+ if (fs.existsSync(customSkillsDir)) {
58
+ const existingSkills = fs.existsSync(canonicalSkillsDir)
59
+ ? new Set(fs.readdirSync(canonicalSkillsDir))
60
+ : new Set<string>();
61
+ for (const entry of fs.readdirSync(customSkillsDir)) {
62
+ if (!existingSkills.has(entry)) {
63
+ const entryPath = path.join(customSkillsDir, entry);
64
+ if (fs.statSync(entryPath).isDirectory()) {
65
+ skillPaths.push(entryPath);
66
+ }
67
+ }
68
+ }
69
+ }
70
+
37
71
  return {
38
- skillPaths: [skillsDir],
72
+ skillPaths: skillPaths.length > 0 ? skillPaths : undefined,
39
73
  promptPaths: [promptsDir],
40
74
  };
41
75
  });
@@ -127,7 +161,7 @@ export default function (pi: ExtensionAPI) {
127
161
  name: 'wp_install_kit',
128
162
  label: 'WP Install Kit',
129
163
  description:
130
- 'Install WordPress Agent Kit into a project directory. Copies 17 WordPress development skills, agent definitions, workflow instructions, and an AGENTS.md template. Safe by default — preserves user modifications on re-run.',
164
+ 'Install WordPress Agent Kit into a project directory. Copies 18 WordPress skills to .agents/skills/ (AgentSkills.io convention), platform-specific agents/instructions/prompts, and an AGENTS.md template. Safe by default — preserves user modifications on re-run.',
131
165
  promptSnippet: 'Install WordPress AI agent skills and configuration into a project',
132
166
  promptGuidelines: [
133
167
  'Use wp_install_kit when setting up a new WordPress project for AI agent development.',
@@ -243,7 +277,7 @@ export default function (pi: ExtensionAPI) {
243
277
  name: 'wp_sync_skills',
244
278
  label: 'WP Sync Skills',
245
279
  description:
246
- 'Sync WordPress agent skills from the upstream WordPress/agent-skills repository. Fetches the latest 17 skill definitions and replaces the local skills directory.',
280
+ 'Sync WordPress agent skills from the upstream WordPress/agent-skills repository. Fetches the latest upstream skill definitions and merges custom skills from skills-custom/ — custom skills survive upstream syncs.',
247
281
  promptSnippet: 'Sync latest WordPress agent skills from upstream',
248
282
  promptGuidelines: [
249
283
  'Use wp_sync_skills to pull the latest WordPress development skills from WordPress/agent-skills.',
@@ -273,11 +307,14 @@ export default function (pi: ExtensionAPI) {
273
307
  }
274
308
 
275
309
  const data = result.data as { skillsSynced: number; method: string };
310
+ const customSkillNote = fs.existsSync(path.join(PACKAGE_ROOT, 'skills-custom'))
311
+ ? '\n**Custom skills**: Merged from skills-custom/'
312
+ : '';
276
313
  return {
277
314
  content: [
278
315
  {
279
316
  type: 'text',
280
- text: `# Skills Synced\n\n**Synced**: ${data.skillsSynced} skills\n**Method**: ${data.method}`,
317
+ text: `# Skills Synced\n\n**Synced**: ${data.skillsSynced} skills\n**Method**: ${data.method}${customSkillNote}`,
281
318
  },
282
319
  ],
283
320
  details: data,
@@ -315,7 +352,6 @@ export default function (pi: ExtensionAPI) {
315
352
  const currentVersion = manifest?.version || 'not installed';
316
353
  const latestVersion = (() => {
317
354
  try {
318
- const fs = require('node:fs');
319
355
  return JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf-8'))
320
356
  .version;
321
357
  } catch {
@@ -393,6 +429,221 @@ export default function (pi: ExtensionAPI) {
393
429
  },
394
430
  });
395
431
 
432
+ // --- wp_clean_skills ---
433
+ pi.registerTool({
434
+ name: 'wp_clean_skills',
435
+ label: 'WP Clean Skills',
436
+ description:
437
+ 'Detect and remove orphaned skills from a WordPress Agent Kit installation. Compares installed skills against the canonical set (upstream + custom) and reports or removes skills that are no longer part of the kit. Safe by default — use dryRun first to preview.',
438
+ promptSnippet: 'Clean up orphaned WordPress agent skills',
439
+ promptGuidelines: [
440
+ 'Use wp_clean_skills after upgrading to remove skills that are no longer part of the kit.',
441
+ 'Always run with dryRun: true first to preview what would be removed.',
442
+ 'This only removes skill directories — it does not modify AGENTS.md or other config files.',
443
+ ],
444
+ parameters: Type.Object({
445
+ targetDir: Type.Optional(
446
+ Type.String({
447
+ description: 'WordPress project directory (defaults to current working directory)',
448
+ })
449
+ ),
450
+ dryRun: Type.Optional(
451
+ Type.Boolean({
452
+ description: 'Preview changes without applying (default: true for safety)',
453
+ })
454
+ ),
455
+ remove: Type.Optional(
456
+ Type.Boolean({
457
+ description: 'Actually remove orphaned skills (default: false — report only)',
458
+ })
459
+ ),
460
+ }),
461
+ async execute(_callId, params, _signal, _onUpdate, _ctx) {
462
+ const targetDir = params.targetDir || process.cwd();
463
+ const dryRun = params.dryRun ?? true;
464
+ const remove = params.remove ?? false;
465
+
466
+ const result = await cleanSkillsApi({
467
+ targetDir,
468
+ platform: 'pi',
469
+ dryRun,
470
+ remove,
471
+ });
472
+
473
+ if (!result.success) {
474
+ return {
475
+ content: [
476
+ { type: 'text', text: `Clean failed: ${result.error?.message || 'Unknown error'}` },
477
+ ],
478
+ isError: true,
479
+ };
480
+ }
481
+
482
+ const data = result.data as {
483
+ orphanedSkills: string[];
484
+ removedSkills: string[];
485
+ legacySkillDirs: string[];
486
+ migratedSkills: string[];
487
+ dryRun: boolean;
488
+ };
489
+
490
+ const hasNoIssues = data.orphanedSkills.length === 0 && data.legacySkillDirs.length === 0;
491
+ if (hasNoIssues) {
492
+ return {
493
+ content: [
494
+ {
495
+ type: 'text',
496
+ text: '# All Clean\n\nAll installed skills match the canonical set. No orphaned or legacy skills found.',
497
+ },
498
+ ],
499
+ details: data,
500
+ };
501
+ }
502
+
503
+ const lines = [dryRun ? '# Skills Cleanup Preview (Dry Run)' : '# Skills Cleaned Up', ''];
504
+
505
+ if (data.orphanedSkills.length > 0) {
506
+ lines.push(`**Orphaned skills** (${data.orphanedSkills.length}):`);
507
+ for (const skill of data.orphanedSkills) {
508
+ lines.push(`- ${skill}`);
509
+ }
510
+ }
511
+
512
+ if (data.legacySkillDirs.length > 0) {
513
+ lines.push('', `**Legacy skill directories** (${data.legacySkillDirs.length}):`);
514
+ for (const dir of data.legacySkillDirs) {
515
+ lines.push(`- ${dir}`);
516
+ }
517
+ lines.push('These will be migrated to `.agents/skills/` and then removed.');
518
+ }
519
+
520
+ if (dryRun) {
521
+ lines.push('', 'Run with `remove: true` to clean up.');
522
+ } else {
523
+ if (data.removedSkills.length > 0) {
524
+ lines.push('', `Removed **${data.removedSkills.length}** orphaned skill(s).`);
525
+ }
526
+ if (data.migratedSkills.length > 0) {
527
+ lines.push(
528
+ `Migrated **${data.migratedSkills.length}** skill(s) from legacy directories.`
529
+ );
530
+ }
531
+ }
532
+
533
+ return {
534
+ content: [{ type: 'text', text: lines.join('\n') }],
535
+ details: data,
536
+ };
537
+ },
538
+ });
539
+
540
+ // --- wp_bootstrap ---
541
+ pi.registerTool({
542
+ name: 'wp_bootstrap',
543
+ label: 'WP Bootstrap',
544
+ description:
545
+ 'Bootstrap a WordPress project: detect monorepo structure, install agent kit, scaffold Composer/WPackagist/SatisPress, WP-CLI aliases, git hooks, Playground scripts, and WP Engine CI/CD. Supports single plugins/themes and monorepos (multiple WP packages + JS workspaces).',
546
+ promptSnippet: 'Detect WordPress project structure and bootstrap full tooling',
547
+ promptGuidelines: [
548
+ 'Always run wp_bootstrap with detectOnly: true first to understand the project structure.',
549
+ 'Use the structure report to identify monorepo patterns before scaffolding.',
550
+ 'For monorepos, confirm WP package paths before proceeding.',
551
+ ],
552
+ parameters: Type.Object({
553
+ targetDir: Type.Optional(
554
+ Type.String({
555
+ description: 'Project root directory (defaults to current working directory)',
556
+ })
557
+ ),
558
+ detectOnly: Type.Optional(
559
+ Type.Boolean({
560
+ description: 'Only detect structure, do not scaffold anything (default: false)',
561
+ })
562
+ ),
563
+ platform: Type.Optional(
564
+ Type.String({
565
+ description: 'Agent kit platform: github, pi, cursor, claude (default: github)',
566
+ })
567
+ ),
568
+ wpeProd: Type.Optional(Type.String({ description: 'WP Engine production install slug' })),
569
+ wpeStaging: Type.Optional(Type.String({ description: 'WP Engine staging install slug' })),
570
+ wpeDev: Type.Optional(Type.String({ description: 'WP Engine development install slug' })),
571
+ withWpackagist: Type.Optional(
572
+ Type.Boolean({ description: 'Add WPackagist to composer.json' })
573
+ ),
574
+ withSatispress: Type.Optional(
575
+ Type.String({ description: 'SatisPress URL to add to composer.json' })
576
+ ),
577
+ dryRun: Type.Optional(
578
+ Type.Boolean({ description: 'Preview without making changes (default: false)' })
579
+ ),
580
+ }),
581
+ async execute(_callId, params, _signal, onUpdate, _ctx) {
582
+ const targetDir = params.targetDir || process.cwd();
583
+
584
+ onUpdate?.({ content: [{ type: 'text', text: '▶ Detecting project structure...' }] });
585
+
586
+ const result = await bootstrapApi({
587
+ targetDir,
588
+ platform: (params.platform as 'github' | 'pi' | 'cursor' | 'claude') ?? 'github',
589
+ detectOnly: params.detectOnly ?? false,
590
+ dryRun: params.dryRun ?? false,
591
+ wpeEnvironments: {
592
+ production: params.wpeProd,
593
+ staging: params.wpeStaging,
594
+ development: params.wpeDev,
595
+ },
596
+ withWpackagist: params.withWpackagist,
597
+ withSatispress: params.withSatispress,
598
+ });
599
+
600
+ if (!result.success) {
601
+ return {
602
+ content: [
603
+ { type: 'text', text: `Bootstrap failed: ${result.error?.message || 'Unknown error'}` },
604
+ ],
605
+ isError: true,
606
+ };
607
+ }
608
+
609
+ const data = result.data as {
610
+ detectOnly: boolean;
611
+ structure: {
612
+ isMonorepo: boolean;
613
+ wpPackages: Array<{ type: string; name: string; path: string }>;
614
+ wpRoot: string | null;
615
+ packageManager: string;
616
+ wpeRemotes: Array<{ name: string; install: string }>;
617
+ };
618
+ actions: string[];
619
+ filesCreated: string[];
620
+ };
621
+
622
+ if (data.detectOnly) {
623
+ const s = data.structure;
624
+ const lines = [
625
+ '# Project Structure',
626
+ '',
627
+ `**Monorepo**: ${s.isMonorepo ? 'yes' : 'no'} | **Package manager**: ${s.packageManager ?? 'none'} | **WP root**: ${s.wpRoot ?? 'Playground-only'}`,
628
+ '',
629
+ `**WP packages** (${s.wpPackages.length}):`,
630
+ ...s.wpPackages.map(
631
+ (p) => `- ${p.type === 'plugin' ? '🔌' : '🎨'} ${p.name ?? p.path} (\`${p.path}\`)`
632
+ ),
633
+ '',
634
+ `**WP Engine remotes** (${s.wpeRemotes.length}):`,
635
+ ...s.wpeRemotes.map((r) => `- ${r.name} (${r.install})`),
636
+ ];
637
+ return { content: [{ type: 'text', text: lines.join('\n') }], details: data.structure };
638
+ }
639
+
640
+ return {
641
+ content: [{ type: 'text', text: ['# Bootstrap Complete', '', ...data.actions].join('\n') }],
642
+ details: data,
643
+ };
644
+ },
645
+ });
646
+
396
647
  // =========================================================================
397
648
  // Commands
398
649
  // =========================================================================
@@ -431,7 +682,15 @@ export default function (pi: ExtensionAPI) {
431
682
  `${data.isUpdate ? 'Updated' : 'Installed'} (${data.filesCreated.length} entries)`,
432
683
  'info'
433
684
  );
434
- ctx.ui.setStatus('wp-agent-kit', 'v0.3.0 installed');
685
+ const pkgVersion = (() => {
686
+ try {
687
+ return JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf-8'))
688
+ .version;
689
+ } catch {
690
+ return 'unknown';
691
+ }
692
+ })();
693
+ ctx.ui.setStatus('wp-agent-kit', `v${pkgVersion} installed`);
435
694
  },
436
695
  });
437
696
 
@@ -449,4 +708,60 @@ export default function (pi: ExtensionAPI) {
449
708
  ctx.ui.setStatus('wp-agent-kit', `${data.skillsSynced} skills`);
450
709
  },
451
710
  });
711
+
712
+ pi.registerCommand('wp-clean-skills', {
713
+ description: 'Detect and remove orphaned/legacy skills from WordPress Agent Kit',
714
+ handler: async (args, ctx) => {
715
+ const targetDir = args?.trim() || ctx.cwd;
716
+ ctx.ui.setStatus('wp-clean', 'Checking for orphaned and legacy skills...');
717
+ const result = await cleanSkillsApi({
718
+ targetDir,
719
+ platform: 'pi',
720
+ dryRun: true,
721
+ remove: false,
722
+ });
723
+ if (!result.success) {
724
+ ctx.ui.notify(`Clean failed: ${result.error?.message}`, 'error');
725
+ return;
726
+ }
727
+ const data = result.data as { orphanedSkills: string[]; legacySkillDirs: string[] };
728
+ const total = data.orphanedSkills.length + data.legacySkillDirs.length;
729
+ if (total === 0) {
730
+ ctx.ui.notify('No orphaned or legacy skills found', 'info');
731
+ } else {
732
+ const parts: string[] = [];
733
+ if (data.orphanedSkills.length > 0) parts.push(`${data.orphanedSkills.length} orphaned`);
734
+ if (data.legacySkillDirs.length > 0)
735
+ parts.push(`${data.legacySkillDirs.length} legacy dir(s)`);
736
+ ctx.ui.notify(`Found ${parts.join(', ')}`, 'info');
737
+ }
738
+ ctx.ui.setStatus('wp-clean', total === 0 ? 'clean' : `${total} issues`);
739
+ },
740
+ });
741
+
742
+ pi.registerCommand('wp-bootstrap', {
743
+ description: 'Detect WordPress project structure and bootstrap the full toolkit',
744
+ handler: async (args, ctx) => {
745
+ const targetDir = args?.trim() || ctx.cwd;
746
+ ctx.ui.setStatus('wp-bootstrap', 'Detecting...');
747
+ const result = await bootstrapApi({ targetDir, detectOnly: true });
748
+ if (!result.success) {
749
+ ctx.ui.notify(`Bootstrap failed: ${result.error?.message}`, 'error');
750
+ return;
751
+ }
752
+ const data = result.data as {
753
+ structure: {
754
+ isMonorepo: boolean;
755
+ wpPackages: Array<{ type: string; name: string; path: string }>;
756
+ };
757
+ };
758
+ const s = data.structure;
759
+ const pkgCount = s.wpPackages.length;
760
+ const label = s.isMonorepo
761
+ ? `monorepo (${pkgCount} packages)`
762
+ : `${s.wpPackages[0]?.type ?? 'unknown'} (${pkgCount} package)`;
763
+ ctx.ui.notify(`Detected: ${label}`, 'info');
764
+ ctx.ui.setStatus('wp-bootstrap', label);
765
+ },
766
+ });
452
767
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wordpress-agent-kit",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "WordPress-focused AGENTS.md + Agent Skills starter kit for AI coding agents.",
5
5
  "license": "GPL-2.0-or-later",
6
6
  "author": "Kyle Brodeur <kyle@brodeur.me> (https://brodeur.me)",
@@ -31,7 +31,7 @@
31
31
  "./extensions/wp-agent-kit"
32
32
  ],
33
33
  "skills": [
34
- "./.github/skills"
34
+ "./.agents/skills"
35
35
  ]
36
36
  },
37
37
  "dependencies": {
@@ -44,28 +44,20 @@
44
44
  },
45
45
  "devDependencies": {
46
46
  "@biomejs/biome": "1.9.4",
47
- "@eslint/js": "^9.17.0",
47
+ "@earendil-works/pi-coding-agent": "^0.78.1",
48
48
  "@types/node": "^22.10.1",
49
49
  "@vitest/coverage-v8": "^2.1.8",
50
- "eslint": "^9.16.0",
51
50
  "husky": "^9.1.6",
52
- "prettier": "^3.4.1",
53
51
  "ts-node": "^10.9.2",
54
52
  "tsx": "^4.19.2",
53
+ "typebox": "*",
55
54
  "typescript": "^5.7.2",
56
- "typescript-eslint": "^8.18.0",
57
- "vitest": "^2.1.8",
58
- "@earendil-works/pi-coding-agent": "^0.78.1",
59
- "typebox": "*"
55
+ "vitest": "^2.1.8"
60
56
  },
61
57
  "scripts": {
62
58
  "dev": "tsx src/cli.ts",
63
59
  "build": "tsc",
64
60
  "check": "tsc --noEmit",
65
- "lint": "eslint src/ tests/ --fix && biome check --write .",
66
- "lint:check": "eslint src/ tests/ && biome check .",
67
- "format": "prettier --write . && biome format --write .",
68
- "format:check": "prettier --check . && biome format --check .",
69
61
  "test": "vitest",
70
62
  "test:run": "vitest run",
71
63
  "test:coverage": "vitest run --coverage",
@@ -73,6 +65,10 @@
73
65
  "sync:skills": "tsx src/cli.ts sync-skills",
74
66
  "setup": "tsx src/cli.ts setup",
75
67
  "playground": "tsx src/cli.ts playground",
76
- "build:bundles": "tsx scripts/build-bundles.ts"
68
+ "build:bundles": "tsx scripts/build-bundles.ts",
69
+ "lint": "biome check --write .",
70
+ "lint:check": "biome check .",
71
+ "format": "biome format --write .",
72
+ "format:check": "biome format --check ."
77
73
  }
78
74
  }