wordpress-agent-kit 0.3.2 → 0.5.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 (135) hide show
  1. package/.agents/skills/blueprint/SKILL.md +418 -0
  2. package/.agents/skills/wordpress-router/SKILL.md +52 -0
  3. package/.agents/skills/wordpress-router/references/decision-tree.md +55 -0
  4. package/.agents/skills/wp-abilities-api/SKILL.md +108 -0
  5. package/.agents/skills/wp-abilities-api/references/delegate-helper-pattern.md +241 -0
  6. package/.agents/skills/wp-abilities-api/references/domain-vs-projection.md +113 -0
  7. package/.agents/skills/wp-abilities-api/references/error-code-vocabulary.md +123 -0
  8. package/.agents/skills/wp-abilities-api/references/grouping-heuristic.md +89 -0
  9. package/.agents/skills/wp-abilities-api/references/input-schema-gotchas.md +265 -0
  10. package/.agents/skills/wp-abilities-api/references/php-registration.md +94 -0
  11. package/.agents/skills/wp-abilities-api/references/plugin-family-patterns.md +233 -0
  12. package/.agents/skills/wp-abilities-api/references/rest-api.md +13 -0
  13. package/.agents/skills/wp-abilities-api/references/shared-core-service.md +184 -0
  14. package/.agents/skills/wp-abilities-audit/SKILL.md +199 -0
  15. package/.agents/skills/wp-abilities-audit/references/audit-schema.md +300 -0
  16. package/.agents/skills/wp-abilities-audit/references/capability-gate-tracing.md +197 -0
  17. package/.agents/skills/wp-abilities-audit/references/controller-enumeration.md +116 -0
  18. package/.agents/skills/wp-abilities-verify/SKILL.md +215 -0
  19. package/.agents/skills/wp-abilities-verify/references/annotation-correctness.md +154 -0
  20. package/.agents/skills/wp-abilities-verify/references/audit-schema-validation.md +131 -0
  21. package/.agents/skills/wp-abilities-verify/references/permission-roundtrip.md +190 -0
  22. package/.agents/skills/wp-abilities-verify/references/runtime-harness.md +462 -0
  23. package/.agents/skills/wp-abilities-verify/references/schema-lints.md +118 -0
  24. package/.agents/skills/wp-abilities-verify/references/static-enumeration.md +126 -0
  25. package/.agents/skills/wp-block-development/SKILL.md +175 -0
  26. package/.agents/skills/wp-block-development/references/attributes-and-serialization.md +22 -0
  27. package/.agents/skills/wp-block-development/references/block-json.md +49 -0
  28. package/.agents/skills/wp-block-development/references/creating-new-blocks.md +46 -0
  29. package/.agents/skills/wp-block-development/references/debugging.md +36 -0
  30. package/.agents/skills/wp-block-development/references/deprecations.md +24 -0
  31. package/.agents/skills/wp-block-development/references/dynamic-rendering.md +23 -0
  32. package/.agents/skills/wp-block-development/references/inner-blocks.md +25 -0
  33. package/.agents/skills/wp-block-development/references/registration.md +30 -0
  34. package/.agents/skills/wp-block-development/references/supports-and-wrappers.md +18 -0
  35. package/.agents/skills/wp-block-development/references/tooling-and-testing.md +21 -0
  36. package/.agents/skills/wp-block-development/scripts/list_blocks.mjs +121 -0
  37. package/.agents/skills/wp-block-themes/SKILL.md +117 -0
  38. package/.agents/skills/wp-block-themes/references/creating-new-block-theme.md +37 -0
  39. package/.agents/skills/wp-block-themes/references/debugging.md +24 -0
  40. package/.agents/skills/wp-block-themes/references/patterns.md +18 -0
  41. package/.agents/skills/wp-block-themes/references/style-variations.md +14 -0
  42. package/.agents/skills/wp-block-themes/references/templates-and-parts.md +16 -0
  43. package/.agents/skills/wp-block-themes/references/theme-json.md +59 -0
  44. package/.agents/skills/wp-block-themes/scripts/detect_block_themes.mjs +117 -0
  45. package/.agents/skills/wp-interactivity-api/SKILL.md +180 -0
  46. package/.agents/skills/wp-interactivity-api/references/debugging.md +29 -0
  47. package/.agents/skills/wp-interactivity-api/references/directives-quickref.md +30 -0
  48. package/.agents/skills/wp-interactivity-api/references/server-side-rendering.md +310 -0
  49. package/.agents/skills/wp-performance/SKILL.md +147 -0
  50. package/.agents/skills/wp-performance/references/autoload-options.md +24 -0
  51. package/.agents/skills/wp-performance/references/cron.md +20 -0
  52. package/.agents/skills/wp-performance/references/database.md +20 -0
  53. package/.agents/skills/wp-performance/references/http-api.md +15 -0
  54. package/.agents/skills/wp-performance/references/measurement.md +21 -0
  55. package/.agents/skills/wp-performance/references/object-cache.md +24 -0
  56. package/.agents/skills/wp-performance/references/query-monitor-headless.md +38 -0
  57. package/.agents/skills/wp-performance/references/server-timing.md +22 -0
  58. package/.agents/skills/wp-performance/references/wp-cli-doctor.md +24 -0
  59. package/.agents/skills/wp-performance/references/wp-cli-profile.md +32 -0
  60. package/.agents/skills/wp-performance/scripts/perf_inspect.mjs +128 -0
  61. package/.agents/skills/wp-phpstan/SKILL.md +98 -0
  62. package/.agents/skills/wp-phpstan/references/configuration.md +52 -0
  63. package/.agents/skills/wp-phpstan/references/third-party-classes.md +76 -0
  64. package/.agents/skills/wp-phpstan/references/wordpress-annotations.md +124 -0
  65. package/.agents/skills/wp-phpstan/scripts/phpstan_inspect.mjs +263 -0
  66. package/.agents/skills/wp-playground/SKILL.md +233 -0
  67. package/.agents/skills/wp-playground/references/blueprints.md +36 -0
  68. package/.agents/skills/wp-playground/references/cli-commands.md +39 -0
  69. package/.agents/skills/wp-playground/references/debugging.md +16 -0
  70. package/.agents/skills/wp-playground/references/e2e-playwright.md +115 -0
  71. package/.agents/skills/wp-plugin-development/SKILL.md +113 -0
  72. package/.agents/skills/wp-plugin-development/references/data-and-cron.md +19 -0
  73. package/.agents/skills/wp-plugin-development/references/debugging.md +19 -0
  74. package/.agents/skills/wp-plugin-development/references/lifecycle.md +33 -0
  75. package/.agents/skills/wp-plugin-development/references/security.md +29 -0
  76. package/.agents/skills/wp-plugin-development/references/settings-api.md +22 -0
  77. package/.agents/skills/wp-plugin-development/references/structure.md +16 -0
  78. package/.agents/skills/wp-plugin-development/scripts/detect_plugins.mjs +122 -0
  79. package/.agents/skills/wp-plugin-directory-guidelines/SKILL.md +133 -0
  80. package/.agents/skills/wp-plugin-directory-guidelines/references/gpl-compliance.md +217 -0
  81. package/.agents/skills/wp-plugin-directory-guidelines/references/guideline-review-checklist.md +592 -0
  82. package/.agents/skills/wp-plugin-directory-guidelines/references/naming-rules.md +121 -0
  83. package/.agents/skills/wp-project-triage/SKILL.md +39 -0
  84. package/.agents/skills/wp-project-triage/references/triage.schema.json +143 -0
  85. package/.agents/skills/wp-project-triage/scripts/detect_wp_project.mjs +610 -0
  86. package/.agents/skills/wp-rest-api/SKILL.md +115 -0
  87. package/.agents/skills/wp-rest-api/references/authentication.md +18 -0
  88. package/.agents/skills/wp-rest-api/references/custom-content-types.md +20 -0
  89. package/.agents/skills/wp-rest-api/references/discovery-and-params.md +20 -0
  90. package/.agents/skills/wp-rest-api/references/responses-and-fields.md +30 -0
  91. package/.agents/skills/wp-rest-api/references/routes-and-endpoints.md +36 -0
  92. package/.agents/skills/wp-rest-api/references/schema.md +22 -0
  93. package/.agents/skills/wp-wpcli-and-ops/SKILL.md +126 -0
  94. package/.agents/skills/wp-wpcli-and-ops/references/automation.md +30 -0
  95. package/.agents/skills/wp-wpcli-and-ops/references/cron-and-cache.md +23 -0
  96. package/.agents/skills/wp-wpcli-and-ops/references/debugging.md +17 -0
  97. package/.agents/skills/wp-wpcli-and-ops/references/multisite.md +22 -0
  98. package/.agents/skills/wp-wpcli-and-ops/references/packages-and-updates.md +22 -0
  99. package/.agents/skills/wp-wpcli-and-ops/references/safety.md +30 -0
  100. package/.agents/skills/wp-wpcli-and-ops/references/search-replace.md +40 -0
  101. package/.agents/skills/wp-wpcli-and-ops/scripts/wpcli_inspect.mjs +90 -0
  102. package/.agents/skills/wp-wpengine/SKILL.md +398 -0
  103. package/.agents/skills/wp-wpengine/references/ci-gate.md +469 -0
  104. package/.agents/skills/wp-wpengine/references/github-actions-deploy.md +736 -0
  105. package/.agents/skills/wp-wpengine/scripts/ci-gate.sh +118 -0
  106. package/.agents/skills/wp-wpengine/scripts/wpe-check.sh +89 -0
  107. package/.agents/skills/wp-wpengine/scripts/wpe-preflight.sh +104 -0
  108. package/.agents/skills/wpds/SKILL.md +59 -0
  109. package/.github/agents/wp-architect.agent.md +1 -2
  110. package/.github/copilot-instructions.md +1 -1
  111. package/.github/instructions/wordpress-workflow.instructions.md +3 -3
  112. package/.github/skills/wp-playground/SKILL.md +132 -1
  113. package/.github/skills/wp-playground/references/e2e-playwright.md +115 -0
  114. package/.github/skills/wp-wpengine/SKILL.md +127 -0
  115. package/AGENTS.md +22 -10
  116. package/AGENTS.template.md +20 -10
  117. package/README.md +93 -86
  118. package/dist/cli.js +5 -1
  119. package/dist/commands/clean-skills.js +64 -0
  120. package/dist/commands/setup.js +6 -2
  121. package/dist/commands/sync-skills.js +3 -0
  122. package/dist/lib/api.js +176 -4
  123. package/dist/lib/installer.js +166 -2
  124. package/extensions/wp-agent-kit/index.ts +185 -10
  125. package/package.json +10 -14
  126. package/skills-custom/wp-wpengine/SKILL.md +398 -0
  127. package/skills-custom/wp-wpengine/references/ci-gate.md +469 -0
  128. package/skills-custom/wp-wpengine/references/github-actions-deploy.md +736 -0
  129. package/skills-custom/wp-wpengine/scripts/ci-gate.sh +118 -0
  130. package/skills-custom/wp-wpengine/scripts/wpe-check.sh +89 -0
  131. package/skills-custom/wp-wpengine/scripts/wpe-preflight.sh +104 -0
  132. package/.github/workflows/ci.yml +0 -44
  133. package/.husky/pre-commit +0 -7
  134. package/CLI_REVIEW.md +0 -250
  135. package/biome.json +0 -39
@@ -111,8 +111,12 @@ export const setupCommand = new Command('setup')
111
111
  let detectedTech = [];
112
112
  let detectedPackageManager = 'npm/pnpm';
113
113
  const triageScriptPaths = [
114
+ // Canonical location (AgentSkills.io convention)
115
+ path.join(targetDir, '.agents', 'skills/wp-project-triage/scripts/detect_wp_project.mjs'),
116
+ // Legacy platform-specific location
114
117
  path.join(targetDir, platformFolder, 'skills/wp-project-triage/scripts/detect_wp_project.mjs'),
115
- path.join(PACKAGE_ROOT, 'vendor/wp-agent-skills/skills/wp-project-triage/scripts/detect_wp_project.mjs'),
118
+ // Source repo
119
+ path.join(PACKAGE_ROOT, '.agents', 'skills/wp-project-triage/scripts/detect_wp_project.mjs'),
116
120
  ];
117
121
  const triageScriptPath = triageScriptPaths.find((p) => fs.existsSync(p));
118
122
  if (triageScriptPath) {
@@ -291,6 +295,6 @@ export const setupCommand = new Command('setup')
291
295
  console.log('\nNext steps:');
292
296
  console.log(` 1. Review ${path.join(targetDir, 'AGENTS.md')}`);
293
297
  console.log(` 2. Customize ${path.join(targetDir, platformFolder, 'prompts/')}`);
294
- console.log(` 3. Run triage: node ${path.join(targetDir, platformFolder, 'skills/wp-project-triage/scripts/detect_wp_project.mjs')}`);
298
+ console.log(` 3. Run triage: node ${path.join(targetDir, '.agents', 'skills/wp-project-triage/scripts/detect_wp_project.mjs')}`);
295
299
  process.exit(0);
296
300
  });
@@ -30,6 +30,9 @@ export const syncSkillsCommand = new Command('sync-skills')
30
30
  const data = result.data;
31
31
  console.log(`✓ Synced ${data.skillsSynced} skills from WordPress/agent-skills@${ref}`);
32
32
  console.log(` Method: ${data.method}`);
33
+ if (data.customMerged > 0) {
34
+ console.log(` Custom skills merged: ${data.customMerged}`);
35
+ }
33
36
  console.log(` Duration: ${data.durationMs}ms`);
34
37
  }
35
38
  else if (isDryRunResult(result)) {
package/dist/lib/api.js CHANGED
@@ -264,11 +264,38 @@ export async function syncSkillsApi(options = {}) {
264
264
  fs.cpSync(sourceSkills, targetSkills, { recursive: true });
265
265
  skillsSynced = fs.readdirSync(targetSkills).length;
266
266
  }
267
- return { success: true, skillsSynced, method };
267
+ // Merge custom skills (skills-custom/) these are not part of the upstream
268
+ // WordPress/agent-skills repo and survive upstream syncs.
269
+ const customSkillsDir = path.join(PACKAGE_ROOT, 'skills-custom');
270
+ let customMerged = 0;
271
+ if (fs.existsSync(customSkillsDir)) {
272
+ for (const skillName of fs.readdirSync(customSkillsDir)) {
273
+ const src = path.join(customSkillsDir, skillName);
274
+ const dest = path.join(targetSkills, skillName);
275
+ if (fs.statSync(src).isDirectory()) {
276
+ const isNew = !fs.existsSync(dest);
277
+ fs.cpSync(src, dest, { recursive: true });
278
+ if (isNew) {
279
+ skillsSynced++;
280
+ }
281
+ customMerged++;
282
+ }
283
+ }
284
+ }
285
+ // Copy synced skills to the canonical .agents/skills/ directory
286
+ // (AgentSkills.io convention — the pi.skills path in package.json points here).
287
+ const canonicalSkills = path.join(PACKAGE_ROOT, '.agents', 'skills');
288
+ if (fs.existsSync(canonicalSkills)) {
289
+ fs.rmSync(canonicalSkills, { recursive: true, force: true });
290
+ }
291
+ fs.mkdirSync(path.join(PACKAGE_ROOT, '.agents'), { recursive: true });
292
+ fs.cpSync(targetSkills, canonicalSkills, { recursive: true });
293
+ return { success: true, skillsSynced, customMerged, method };
268
294
  });
269
295
  return formatter.success({
270
296
  targetDir,
271
297
  skillsSynced: result.skillsSynced,
298
+ customMerged: result.customMerged,
272
299
  sourceUrl: 'https://github.com/WordPress/agent-skills.git',
273
300
  ref,
274
301
  durationMs: Date.now() - startTime,
@@ -321,7 +348,13 @@ function dryRunSyncSkills(targetDir, ref) {
321
348
  actions.push({
322
349
  type: 'create',
323
350
  target: targetSkills,
324
- description: 'Install synced skills',
351
+ description: 'Install synced skills to .github/skills (sync buffer), then copy to .agents/skills (canonical)',
352
+ });
353
+ const canonicalSkills = path.join(targetDir, '.agents', 'skills');
354
+ actions.push({
355
+ type: 'create',
356
+ target: canonicalSkills,
357
+ description: 'Copy synced skills to .agents/skills (canonical, AgentSkills.io convention)',
325
358
  });
326
359
  return new OutputFormatter('json', 'sync-skills', '0.0.0').success({
327
360
  wouldExecute: true,
@@ -329,6 +362,7 @@ function dryRunSyncSkills(targetDir, ref) {
329
362
  summary: {
330
363
  targetDir,
331
364
  skillsSynced: 0,
365
+ customMerged: 0,
332
366
  sourceUrl: 'https://github.com/WordPress/agent-skills.git',
333
367
  ref,
334
368
  durationMs: 0,
@@ -343,9 +377,13 @@ export async function runTriageApi(options) {
343
377
  const formatter = new OutputFormatter('json', 'triage', '0.0.0');
344
378
  const { targetDir, platform = 'github' } = options;
345
379
  try {
346
- const platformFolder = getPlatformFolder(platform);
347
380
  const triageScriptPaths = [
348
- path.join(targetDir, platformFolder, 'skills/wp-project-triage/scripts/detect_wp_project.mjs'),
381
+ // Canonical location (AgentSkills.io convention)
382
+ path.join(targetDir, '.agents', 'skills/wp-project-triage/scripts/detect_wp_project.mjs'),
383
+ // Legacy platform-specific location
384
+ path.join(targetDir, getPlatformFolder(platform), 'skills/wp-project-triage/scripts/detect_wp_project.mjs'),
385
+ // Source repo
386
+ path.join(PACKAGE_ROOT, '.agents', 'skills/wp-project-triage/scripts/detect_wp_project.mjs'),
349
387
  path.join(PACKAGE_ROOT, 'vendor/wp-agent-skills/skills/wp-project-triage/scripts/detect_wp_project.mjs'),
350
388
  ];
351
389
  const triageScriptPath = triageScriptPaths.find((p) => fs.existsSync(p));
@@ -517,6 +555,140 @@ function getInstalledSummary(targetDir, platform) {
517
555
  }
518
556
  return summary;
519
557
  }
558
+ /**
559
+ * Detect and optionally remove orphaned skills from a target installation.
560
+ * Compares the skills in the target platform directory against the source kit
561
+ * (upstream .agents/skills + skills-custom/) and identifies skills that exist
562
+ * in the target but not in the source.
563
+ */
564
+ export async function cleanSkillsApi(options) {
565
+ const startTime = Date.now();
566
+ const formatter = new OutputFormatter('json', 'clean-skills', '0.0.0');
567
+ const { targetDir, platform, dryRun = false, remove = false } = options;
568
+ try {
569
+ // Canonical skills come from .agents/skills/ (AgentSkills.io convention)
570
+ // with skills-custom/ as supplemental source for custom skills.
571
+ const canonicalSkillsDir = path.join(PACKAGE_ROOT, '.agents', 'skills');
572
+ const customSkillsDir = path.join(PACKAGE_ROOT, 'skills-custom');
573
+ const targetSkillsDir = path.join(targetDir, '.agents', 'skills');
574
+ // Build set of canonical skill names (upstream + custom)
575
+ const canonicalSkills = new Set();
576
+ if (fs.existsSync(canonicalSkillsDir)) {
577
+ for (const entry of fs.readdirSync(canonicalSkillsDir)) {
578
+ const entryPath = path.join(canonicalSkillsDir, entry);
579
+ if (fs.statSync(entryPath).isDirectory()) {
580
+ canonicalSkills.add(entry);
581
+ }
582
+ }
583
+ }
584
+ if (fs.existsSync(customSkillsDir)) {
585
+ for (const entry of fs.readdirSync(customSkillsDir)) {
586
+ const entryPath = path.join(customSkillsDir, entry);
587
+ if (fs.statSync(entryPath).isDirectory()) {
588
+ canonicalSkills.add(entry);
589
+ }
590
+ }
591
+ }
592
+ // Find orphaned skills in target .agents/skills/
593
+ const orphanedSkills = [];
594
+ if (fs.existsSync(targetSkillsDir)) {
595
+ for (const entry of fs.readdirSync(targetSkillsDir)) {
596
+ const entryPath = path.join(targetSkillsDir, entry);
597
+ if (fs.statSync(entryPath).isDirectory() && !canonicalSkills.has(entry)) {
598
+ orphanedSkills.push(entry);
599
+ }
600
+ }
601
+ }
602
+ // Detect legacy skill directories (platform-specific skills/ dirs)
603
+ const platformFolder = getPlatformFolder(platform);
604
+ const legacySkillsDir = path.join(targetDir, platformFolder, 'skills');
605
+ const legacySkillDirs = [];
606
+ if (fs.existsSync(legacySkillsDir)) {
607
+ legacySkillDirs.push(legacySkillsDir);
608
+ }
609
+ // Also check .github/skills for github platform (common legacy location)
610
+ if (platform !== 'github') {
611
+ const githubSkills = path.join(targetDir, '.github', 'skills');
612
+ if (fs.existsSync(githubSkills)) {
613
+ legacySkillDirs.push(githubSkills);
614
+ }
615
+ }
616
+ // Migrate legacy skills to .agents/skills/ and remove legacy dirs
617
+ const migratedSkills = [];
618
+ if (remove && !dryRun && legacySkillDirs.length > 0) {
619
+ for (const legacyDir of legacySkillDirs) {
620
+ for (const entry of fs.readdirSync(legacyDir)) {
621
+ const entryPath = path.join(legacyDir, entry);
622
+ if (!fs.statSync(entryPath).isDirectory())
623
+ continue;
624
+ const destPath = path.join(targetSkillsDir, entry);
625
+ if (!fs.existsSync(destPath)) {
626
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
627
+ fs.cpSync(entryPath, destPath, { recursive: true });
628
+ migratedSkills.push(entry);
629
+ }
630
+ }
631
+ // Remove the legacy skills directory
632
+ fs.rmSync(legacyDir, { recursive: true, force: true });
633
+ }
634
+ }
635
+ // Remove orphans if requested
636
+ const removedSkills = [];
637
+ if (remove && !dryRun) {
638
+ for (const orphan of orphanedSkills) {
639
+ const orphanPath = path.join(targetSkillsDir, orphan);
640
+ fs.rmSync(orphanPath, { recursive: true, force: true });
641
+ removedSkills.push(orphan);
642
+ }
643
+ }
644
+ const _durationMs = Date.now() - startTime;
645
+ if (dryRun) {
646
+ const actions = orphanedSkills.map((s) => ({
647
+ type: 'delete',
648
+ target: path.join('.agents', 'skills', s),
649
+ description: `Remove orphaned skill: ${s}`,
650
+ }));
651
+ for (const legacyDir of legacySkillDirs) {
652
+ actions.push({
653
+ type: 'delete',
654
+ target: legacyDir,
655
+ description: `Migrate and remove legacy skill directory: ${path.relative(targetDir, legacyDir)}`,
656
+ });
657
+ }
658
+ return formatter.success({
659
+ wouldExecute: true,
660
+ actions,
661
+ summary: {
662
+ targetDir,
663
+ platform,
664
+ orphanedSkills,
665
+ removedSkills: [],
666
+ legacySkillDirs,
667
+ migratedSkills: [],
668
+ dryRun: true,
669
+ },
670
+ });
671
+ }
672
+ return formatter.success({
673
+ targetDir,
674
+ platform,
675
+ orphanedSkills,
676
+ removedSkills,
677
+ legacySkillDirs,
678
+ migratedSkills,
679
+ dryRun: false,
680
+ });
681
+ }
682
+ catch (error) {
683
+ const err = error;
684
+ return formatter.fail({
685
+ code: err.code || 'CLEAN_FAILED',
686
+ message: err.message || 'Clean failed',
687
+ exitCode: err.exitCode ?? ExitCode.ERROR,
688
+ details: { platform, targetDir },
689
+ });
690
+ }
691
+ }
520
692
  export { ExitCode } from '../utils/exit-codes.js';
521
693
  export { OutputFormatter, createFormatter, parseOutputFormat } from '../utils/output.js';
522
694
  export { computeChanges, isKitInstalled, loadManifest, updateKit } from './updater.js';
@@ -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,