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.
- package/.agents/skills/wp-bootstrap/SKILL.md +314 -0
- package/.agents/skills/wp-bootstrap/references/composer-setup.md +275 -0
- package/.agents/skills/wp-bootstrap/references/monorepo-patterns.md +184 -0
- package/.agents/skills/wp-bootstrap/scripts/bootstrap.sh +151 -0
- package/.agents/skills/wp-bootstrap/scripts/detect-structure.mjs +466 -0
- package/.agents/skills/wp-bootstrap/scripts/package-wp.sh +173 -0
- package/.agents/skills/wp-bootstrap/scripts/playground-start.sh +148 -0
- package/.agents/skills/wp-bootstrap/scripts/playground-verify.sh +165 -0
- package/.agents/skills/wp-bootstrap/scripts/setup-github.sh +417 -0
- package/{.github → .agents}/skills/wp-wpcli-and-ops/SKILL.md +11 -9
- package/.agents/skills/wp-wpengine/SKILL.md +462 -0
- package/.agents/skills/wp-wpengine/references/ci-gate.md +469 -0
- package/.agents/skills/wp-wpengine/references/github-actions-deploy.md +743 -0
- package/.agents/skills/wp-wpengine/scripts/ci-gate.sh +118 -0
- package/.agents/skills/wp-wpengine/scripts/wpe-check.sh +89 -0
- package/.agents/skills/wp-wpengine/scripts/wpe-preflight.sh +104 -0
- package/.github/agents/wp-architect.agent.md +1 -2
- package/.github/copilot-instructions.md +1 -1
- package/.github/instructions/wordpress-workflow.instructions.md +3 -3
- package/AGENTS.md +22 -10
- package/AGENTS.template.md +20 -10
- package/README.md +89 -85
- package/dist/cli.js +7 -1
- package/dist/commands/bootstrap.js +105 -0
- package/dist/commands/clean-skills.js +64 -0
- package/dist/commands/setup.js +6 -2
- package/dist/commands/sync-skills.js +3 -0
- package/dist/lib/api.js +165 -5
- package/dist/lib/bootstrap.js +352 -0
- package/dist/lib/installer.js +166 -2
- package/extensions/wp-agent-kit/index.ts +325 -10
- package/package.json +10 -14
- package/skills-custom/wp-bootstrap/SKILL.md +314 -0
- package/skills-custom/wp-bootstrap/references/composer-setup.md +275 -0
- package/skills-custom/wp-bootstrap/references/monorepo-patterns.md +184 -0
- package/skills-custom/wp-bootstrap/scripts/bootstrap.sh +151 -0
- package/skills-custom/wp-bootstrap/scripts/detect-structure.mjs +466 -0
- package/skills-custom/wp-bootstrap/scripts/package-wp.sh +173 -0
- package/skills-custom/wp-bootstrap/scripts/playground-start.sh +148 -0
- package/skills-custom/wp-bootstrap/scripts/playground-verify.sh +165 -0
- package/skills-custom/wp-bootstrap/scripts/setup-github.sh +417 -0
- package/skills-custom/wp-wpengine/SKILL.md +362 -27
- package/skills-custom/wp-wpengine/references/ci-gate.md +469 -0
- package/skills-custom/wp-wpengine/references/github-actions-deploy.md +743 -0
- package/skills-custom/wp-wpengine/scripts/ci-gate.sh +118 -0
- package/skills-custom/wp-wpengine/scripts/wpe-check.sh +89 -0
- package/skills-custom/wp-wpengine/scripts/wpe-preflight.sh +104 -0
- package/.github/skills/wp-wpengine/SKILL.md +0 -127
- package/.github/workflows/ci.yml +0 -44
- package/.husky/pre-commit +0 -7
- package/CLI_REVIEW.md +0 -250
- package/biome.json +0 -39
- /package/{.github → .agents}/skills/blueprint/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wordpress-router/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wordpress-router/references/decision-tree.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-api/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-api/references/delegate-helper-pattern.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-api/references/domain-vs-projection.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-api/references/error-code-vocabulary.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-api/references/grouping-heuristic.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-api/references/input-schema-gotchas.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-api/references/php-registration.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-api/references/plugin-family-patterns.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-api/references/rest-api.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-api/references/shared-core-service.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-audit/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-audit/references/audit-schema.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-audit/references/capability-gate-tracing.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-audit/references/controller-enumeration.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-verify/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-verify/references/annotation-correctness.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-verify/references/audit-schema-validation.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-verify/references/permission-roundtrip.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-verify/references/runtime-harness.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-verify/references/schema-lints.md +0 -0
- /package/{.github → .agents}/skills/wp-abilities-verify/references/static-enumeration.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/references/attributes-and-serialization.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/references/block-json.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/references/creating-new-blocks.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/references/debugging.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/references/deprecations.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/references/dynamic-rendering.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/references/inner-blocks.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/references/registration.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/references/supports-and-wrappers.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/references/tooling-and-testing.md +0 -0
- /package/{.github → .agents}/skills/wp-block-development/scripts/list_blocks.mjs +0 -0
- /package/{.github → .agents}/skills/wp-block-themes/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-block-themes/references/creating-new-block-theme.md +0 -0
- /package/{.github → .agents}/skills/wp-block-themes/references/debugging.md +0 -0
- /package/{.github → .agents}/skills/wp-block-themes/references/patterns.md +0 -0
- /package/{.github → .agents}/skills/wp-block-themes/references/style-variations.md +0 -0
- /package/{.github → .agents}/skills/wp-block-themes/references/templates-and-parts.md +0 -0
- /package/{.github → .agents}/skills/wp-block-themes/references/theme-json.md +0 -0
- /package/{.github → .agents}/skills/wp-block-themes/scripts/detect_block_themes.mjs +0 -0
- /package/{.github → .agents}/skills/wp-interactivity-api/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-interactivity-api/references/debugging.md +0 -0
- /package/{.github → .agents}/skills/wp-interactivity-api/references/directives-quickref.md +0 -0
- /package/{.github → .agents}/skills/wp-interactivity-api/references/server-side-rendering.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/references/autoload-options.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/references/cron.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/references/database.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/references/http-api.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/references/measurement.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/references/object-cache.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/references/query-monitor-headless.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/references/server-timing.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/references/wp-cli-doctor.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/references/wp-cli-profile.md +0 -0
- /package/{.github → .agents}/skills/wp-performance/scripts/perf_inspect.mjs +0 -0
- /package/{.github → .agents}/skills/wp-phpstan/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-phpstan/references/configuration.md +0 -0
- /package/{.github → .agents}/skills/wp-phpstan/references/third-party-classes.md +0 -0
- /package/{.github → .agents}/skills/wp-phpstan/references/wordpress-annotations.md +0 -0
- /package/{.github → .agents}/skills/wp-phpstan/scripts/phpstan_inspect.mjs +0 -0
- /package/{.github → .agents}/skills/wp-playground/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-playground/references/blueprints.md +0 -0
- /package/{.github → .agents}/skills/wp-playground/references/cli-commands.md +0 -0
- /package/{.github → .agents}/skills/wp-playground/references/debugging.md +0 -0
- /package/{.github → .agents}/skills/wp-playground/references/e2e-playwright.md +0 -0
- /package/{.github → .agents}/skills/wp-plugin-development/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-plugin-development/references/data-and-cron.md +0 -0
- /package/{.github → .agents}/skills/wp-plugin-development/references/debugging.md +0 -0
- /package/{.github → .agents}/skills/wp-plugin-development/references/lifecycle.md +0 -0
- /package/{.github → .agents}/skills/wp-plugin-development/references/security.md +0 -0
- /package/{.github → .agents}/skills/wp-plugin-development/references/settings-api.md +0 -0
- /package/{.github → .agents}/skills/wp-plugin-development/references/structure.md +0 -0
- /package/{.github → .agents}/skills/wp-plugin-development/scripts/detect_plugins.mjs +0 -0
- /package/{.github → .agents}/skills/wp-plugin-directory-guidelines/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-plugin-directory-guidelines/references/gpl-compliance.md +0 -0
- /package/{.github → .agents}/skills/wp-plugin-directory-guidelines/references/guideline-review-checklist.md +0 -0
- /package/{.github → .agents}/skills/wp-plugin-directory-guidelines/references/naming-rules.md +0 -0
- /package/{.github → .agents}/skills/wp-project-triage/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-project-triage/references/triage.schema.json +0 -0
- /package/{.github → .agents}/skills/wp-project-triage/scripts/detect_wp_project.mjs +0 -0
- /package/{.github → .agents}/skills/wp-rest-api/SKILL.md +0 -0
- /package/{.github → .agents}/skills/wp-rest-api/references/authentication.md +0 -0
- /package/{.github → .agents}/skills/wp-rest-api/references/custom-content-types.md +0 -0
- /package/{.github → .agents}/skills/wp-rest-api/references/discovery-and-params.md +0 -0
- /package/{.github → .agents}/skills/wp-rest-api/references/responses-and-fields.md +0 -0
- /package/{.github → .agents}/skills/wp-rest-api/references/routes-and-endpoints.md +0 -0
- /package/{.github → .agents}/skills/wp-rest-api/references/schema.md +0 -0
- /package/{.github → .agents}/skills/wp-wpcli-and-ops/references/automation.md +0 -0
- /package/{.github → .agents}/skills/wp-wpcli-and-ops/references/cron-and-cache.md +0 -0
- /package/{.github → .agents}/skills/wp-wpcli-and-ops/references/debugging.md +0 -0
- /package/{.github → .agents}/skills/wp-wpcli-and-ops/references/multisite.md +0 -0
- /package/{.github → .agents}/skills/wp-wpcli-and-ops/references/packages-and-updates.md +0 -0
- /package/{.github → .agents}/skills/wp-wpcli-and-ops/references/safety.md +0 -0
- /package/{.github → .agents}/skills/wp-wpcli-and-ops/references/search-replace.md +0 -0
- /package/{.github → .agents}/skills/wp-wpcli-and-ops/scripts/wpcli_inspect.mjs +0 -0
- /package/{.github → .agents}/skills/wpds/SKILL.md +0 -0
package/dist/lib/api.js
CHANGED
|
@@ -267,21 +267,35 @@ export async function syncSkillsApi(options = {}) {
|
|
|
267
267
|
// Merge custom skills (skills-custom/) — these are not part of the upstream
|
|
268
268
|
// WordPress/agent-skills repo and survive upstream syncs.
|
|
269
269
|
const customSkillsDir = path.join(PACKAGE_ROOT, 'skills-custom');
|
|
270
|
+
let customMerged = 0;
|
|
270
271
|
if (fs.existsSync(customSkillsDir)) {
|
|
271
272
|
for (const skillName of fs.readdirSync(customSkillsDir)) {
|
|
272
273
|
const src = path.join(customSkillsDir, skillName);
|
|
273
274
|
const dest = path.join(targetSkills, skillName);
|
|
274
275
|
if (fs.statSync(src).isDirectory()) {
|
|
276
|
+
const isNew = !fs.existsSync(dest);
|
|
275
277
|
fs.cpSync(src, dest, { recursive: true });
|
|
276
|
-
|
|
278
|
+
if (isNew) {
|
|
279
|
+
skillsSynced++;
|
|
280
|
+
}
|
|
281
|
+
customMerged++;
|
|
277
282
|
}
|
|
278
283
|
}
|
|
279
284
|
}
|
|
280
|
-
|
|
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 };
|
|
281
294
|
});
|
|
282
295
|
return formatter.success({
|
|
283
296
|
targetDir,
|
|
284
297
|
skillsSynced: result.skillsSynced,
|
|
298
|
+
customMerged: result.customMerged,
|
|
285
299
|
sourceUrl: 'https://github.com/WordPress/agent-skills.git',
|
|
286
300
|
ref,
|
|
287
301
|
durationMs: Date.now() - startTime,
|
|
@@ -334,7 +348,13 @@ function dryRunSyncSkills(targetDir, ref) {
|
|
|
334
348
|
actions.push({
|
|
335
349
|
type: 'create',
|
|
336
350
|
target: targetSkills,
|
|
337
|
-
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)',
|
|
338
358
|
});
|
|
339
359
|
return new OutputFormatter('json', 'sync-skills', '0.0.0').success({
|
|
340
360
|
wouldExecute: true,
|
|
@@ -342,6 +362,7 @@ function dryRunSyncSkills(targetDir, ref) {
|
|
|
342
362
|
summary: {
|
|
343
363
|
targetDir,
|
|
344
364
|
skillsSynced: 0,
|
|
365
|
+
customMerged: 0,
|
|
345
366
|
sourceUrl: 'https://github.com/WordPress/agent-skills.git',
|
|
346
367
|
ref,
|
|
347
368
|
durationMs: 0,
|
|
@@ -356,9 +377,13 @@ export async function runTriageApi(options) {
|
|
|
356
377
|
const formatter = new OutputFormatter('json', 'triage', '0.0.0');
|
|
357
378
|
const { targetDir, platform = 'github' } = options;
|
|
358
379
|
try {
|
|
359
|
-
const platformFolder = getPlatformFolder(platform);
|
|
360
380
|
const triageScriptPaths = [
|
|
361
|
-
|
|
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'),
|
|
362
387
|
path.join(PACKAGE_ROOT, 'vendor/wp-agent-skills/skills/wp-project-triage/scripts/detect_wp_project.mjs'),
|
|
363
388
|
];
|
|
364
389
|
const triageScriptPath = triageScriptPaths.find((p) => fs.existsSync(p));
|
|
@@ -530,6 +555,141 @@ function getInstalledSummary(targetDir, platform) {
|
|
|
530
555
|
}
|
|
531
556
|
return summary;
|
|
532
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
|
+
}
|
|
533
692
|
export { ExitCode } from '../utils/exit-codes.js';
|
|
534
693
|
export { OutputFormatter, createFormatter, parseOutputFormat } from '../utils/output.js';
|
|
535
694
|
export { computeChanges, isKitInstalled, loadManifest, updateKit } from './updater.js';
|
|
695
|
+
export { bootstrapApi } from './bootstrap.js';
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootstrap API — detects WordPress project structure and scaffolds
|
|
3
|
+
* the full wp-agent-kit system (agent kit, Composer, WP-CLI, Playground,
|
|
4
|
+
* WP Engine CI/CD, git hooks). Supports single plugins/themes and monorepos.
|
|
5
|
+
*/
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { ExitCode } from '../utils/exit-codes.js';
|
|
10
|
+
import { OutputFormatter } from '../utils/output.js';
|
|
11
|
+
import { PACKAGE_ROOT } from '../utils/paths.js';
|
|
12
|
+
import { installKit } from './installer.js';
|
|
13
|
+
// ── Detection ─────────────────────────────────────────────────────────────
|
|
14
|
+
/**
|
|
15
|
+
* Run the detect-structure.mjs probe script and return the parsed result.
|
|
16
|
+
*/
|
|
17
|
+
export function detectStructure(targetDir) {
|
|
18
|
+
const detectScript = path.join(PACKAGE_ROOT, '.agents', 'skills', 'wp-bootstrap', 'scripts', 'detect-structure.mjs');
|
|
19
|
+
if (!fs.existsSync(detectScript)) {
|
|
20
|
+
// Fallback: minimal detection without the full script
|
|
21
|
+
return minimalDetect(targetDir);
|
|
22
|
+
}
|
|
23
|
+
const result = spawnSync('node', [detectScript, targetDir], {
|
|
24
|
+
encoding: 'utf-8',
|
|
25
|
+
cwd: targetDir,
|
|
26
|
+
});
|
|
27
|
+
if (result.status !== 0) {
|
|
28
|
+
return minimalDetect(targetDir);
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(result.stdout.trim());
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return minimalDetect(targetDir);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Minimal structure detection without the full probe script */
|
|
38
|
+
function minimalDetect(targetDir) {
|
|
39
|
+
const exists = (...parts) => fs.existsSync(path.join(targetDir, ...parts));
|
|
40
|
+
const pm = exists('pnpm-lock.yaml')
|
|
41
|
+
? 'pnpm'
|
|
42
|
+
: exists('yarn.lock')
|
|
43
|
+
? 'yarn'
|
|
44
|
+
: exists('package.json')
|
|
45
|
+
? 'npm'
|
|
46
|
+
: null;
|
|
47
|
+
return {
|
|
48
|
+
repoRoot: targetDir,
|
|
49
|
+
isMonorepo: false,
|
|
50
|
+
packageManager: pm,
|
|
51
|
+
jsWorkspaces: [],
|
|
52
|
+
wpPackages: [],
|
|
53
|
+
wpRoot: exists('wp-config.php') ? '.' : null,
|
|
54
|
+
wpRootExists: exists('wp-config.php'),
|
|
55
|
+
playgroundOnly: !exists('wp-config.php'),
|
|
56
|
+
wpeRemotes: [],
|
|
57
|
+
hasWpeRemote: false,
|
|
58
|
+
php: {
|
|
59
|
+
hasComposer: exists('composer.json'),
|
|
60
|
+
hasPhpcs: exists('vendor/bin/phpcs'),
|
|
61
|
+
hasWpcs: false,
|
|
62
|
+
hasPhpstan: exists('vendor/bin/phpstan'),
|
|
63
|
+
hasPhpstanWp: false,
|
|
64
|
+
hasPest: false,
|
|
65
|
+
phpcsConfig: exists('phpcs.xml.dist') ? 'phpcs.xml.dist' : null,
|
|
66
|
+
phpstanConfig: exists('phpstan.neon.dist') ? 'phpstan.neon.dist' : null,
|
|
67
|
+
composerScripts: [],
|
|
68
|
+
},
|
|
69
|
+
js: {
|
|
70
|
+
hasBiome: exists('biome.json'),
|
|
71
|
+
biomeVersion: null,
|
|
72
|
+
hasEslint: exists('eslint.config.mjs') || exists('.eslintrc.js'),
|
|
73
|
+
hasPrettier: exists('.prettierrc'),
|
|
74
|
+
hasVitest: false,
|
|
75
|
+
hasJest: false,
|
|
76
|
+
hasPlaywright: exists('playwright.config.ts') || exists('playwright.config.js'),
|
|
77
|
+
hasWpScripts: false,
|
|
78
|
+
rootScripts: [],
|
|
79
|
+
},
|
|
80
|
+
playground: {
|
|
81
|
+
hasPlayground: exists('tools/playground') || exists('playground'),
|
|
82
|
+
blueprints: [],
|
|
83
|
+
scripts: [],
|
|
84
|
+
hasWpEnv: exists('.wp-env.json'),
|
|
85
|
+
},
|
|
86
|
+
satispress: { configured: false, url: null, hasAuthJson: false },
|
|
87
|
+
wpackagist: false,
|
|
88
|
+
gitHooks: exists('.githooks') ? '.githooks' : exists('.husky') ? '.husky' : null,
|
|
89
|
+
hasAgentKit: exists('.wp-agent-kit-manifest.github.json') || exists('.agents', 'skills'),
|
|
90
|
+
hasAgentsDir: exists('.agents'),
|
|
91
|
+
hasAgentsMd: exists('AGENTS.md'),
|
|
92
|
+
gitBranch: null,
|
|
93
|
+
gitRemotes: [],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
// ── Scaffolding helpers ────────────────────────────────────────────────────
|
|
97
|
+
function writeIfMissing(filePath, content, dryRun) {
|
|
98
|
+
if (fs.existsSync(filePath))
|
|
99
|
+
return false;
|
|
100
|
+
if (!dryRun) {
|
|
101
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
102
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
function scaffoldWpCliYml(targetDir, structure, wpeEnvironments, dryRun) {
|
|
107
|
+
const wpCliPath = path.join(targetDir, 'wp-cli.yml');
|
|
108
|
+
if (fs.existsSync(wpCliPath))
|
|
109
|
+
return [];
|
|
110
|
+
const localPath = structure.wpRoot && structure.wpRoot !== '.' ? `path: ./${structure.wpRoot}\n\n` : '';
|
|
111
|
+
const prod = wpeEnvironments?.production;
|
|
112
|
+
const staging = wpeEnvironments?.staging;
|
|
113
|
+
const dev = wpeEnvironments?.development;
|
|
114
|
+
const content = [
|
|
115
|
+
'# wp-cli.yml — WP-CLI targeting. Commit to repo.',
|
|
116
|
+
'# Get exact git push URLs from: https://my.wpengine.com/installs/<ENV>/git_push',
|
|
117
|
+
'',
|
|
118
|
+
localPath,
|
|
119
|
+
prod
|
|
120
|
+
? `@production:\n ssh: ${prod}@${prod}.ssh.wpengine.net\n path: /home/wpe-user/sites/${prod}\n`
|
|
121
|
+
: '',
|
|
122
|
+
staging
|
|
123
|
+
? `\n@staging:\n ssh: ${staging}@${staging}.ssh.wpengine.net\n path: /home/wpe-user/sites/${staging}\n`
|
|
124
|
+
: '',
|
|
125
|
+
dev
|
|
126
|
+
? `\n@development:\n ssh: ${dev}@${dev}.ssh.wpengine.net\n path: /home/wpe-user/sites/${dev}\n`
|
|
127
|
+
: '',
|
|
128
|
+
].join('');
|
|
129
|
+
if (writeIfMissing(wpCliPath, content, dryRun)) {
|
|
130
|
+
return ['wp-cli.yml'];
|
|
131
|
+
}
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
function scaffoldGitHooks(targetDir, dryRun) {
|
|
135
|
+
const hooksDir = path.join(targetDir, '.githooks');
|
|
136
|
+
const prePush = path.join(hooksDir, 'pre-push');
|
|
137
|
+
const readme = path.join(hooksDir, 'README.md');
|
|
138
|
+
const created = [];
|
|
139
|
+
const prePushContent = `#!/usr/bin/env bash
|
|
140
|
+
# .githooks/pre-push — CI gate before every push.
|
|
141
|
+
# Activate once: git config core.hooksPath .githooks
|
|
142
|
+
# Generated by wp-agent-kit bootstrap.
|
|
143
|
+
set -uo pipefail
|
|
144
|
+
root="$(git rev-parse --show-toplevel)"
|
|
145
|
+
cd "$root"
|
|
146
|
+
|
|
147
|
+
# Run the CI gate (PHP + JS/TS)
|
|
148
|
+
GATE_SCRIPT=".agents/skills/wp-bootstrap/scripts/ci-gate.sh"
|
|
149
|
+
if [ -f "$GATE_SCRIPT" ]; then
|
|
150
|
+
bash "$GATE_SCRIPT" || exit 1
|
|
151
|
+
else
|
|
152
|
+
echo "⚠ CI gate script not found at $GATE_SCRIPT" >&2
|
|
153
|
+
echo " Run wp-agent-kit install to set up .agents/skills/" >&2
|
|
154
|
+
fi
|
|
155
|
+
`;
|
|
156
|
+
const readmeContent = `# Git hooks (versioned)
|
|
157
|
+
|
|
158
|
+
Activate once per clone:
|
|
159
|
+
\`\`\`bash
|
|
160
|
+
git config core.hooksPath .githooks
|
|
161
|
+
chmod +x .githooks/pre-push
|
|
162
|
+
\`\`\`
|
|
163
|
+
Or run: \`pnpm run setup\` / \`bash tools/setup.sh\` if that exists.
|
|
164
|
+
|
|
165
|
+
## What runs
|
|
166
|
+
|
|
167
|
+
- **pre-push**: PHP gate (php -l + phpcs + phpstan) + JS gate (biome check).
|
|
168
|
+
Fix failures with \`composer fix && composer lint\` (PHP) or \`npx biome check --write .\` (JS).
|
|
169
|
+
--no-verify is not allowed on deploy branches; CI will catch it anyway.
|
|
170
|
+
`;
|
|
171
|
+
if (!fs.existsSync(hooksDir) && !dryRun) {
|
|
172
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
173
|
+
}
|
|
174
|
+
if (writeIfMissing(prePush, prePushContent, dryRun)) {
|
|
175
|
+
if (!dryRun)
|
|
176
|
+
fs.chmodSync(prePush, 0o755);
|
|
177
|
+
created.push('.githooks/pre-push');
|
|
178
|
+
}
|
|
179
|
+
if (writeIfMissing(readme, readmeContent, dryRun)) {
|
|
180
|
+
created.push('.githooks/README.md');
|
|
181
|
+
}
|
|
182
|
+
return created;
|
|
183
|
+
}
|
|
184
|
+
function scaffoldSetupSh(targetDir, dryRun) {
|
|
185
|
+
const toolsDir = path.join(targetDir, 'tools');
|
|
186
|
+
const setupSh = path.join(toolsDir, 'setup.sh');
|
|
187
|
+
if (fs.existsSync(setupSh))
|
|
188
|
+
return [];
|
|
189
|
+
const content = `#!/usr/bin/env bash
|
|
190
|
+
# tools/setup.sh — one-command dev setup. Idempotent: safe to re-run.
|
|
191
|
+
# Generated by wp-agent-kit bootstrap.
|
|
192
|
+
set -uo pipefail
|
|
193
|
+
root="$(cd "$(dirname "$0")/.." && pwd)"
|
|
194
|
+
bash "$root/.agents/skills/wp-bootstrap/scripts/bootstrap.sh" "$@"
|
|
195
|
+
`;
|
|
196
|
+
if (writeIfMissing(setupSh, content, dryRun)) {
|
|
197
|
+
if (!dryRun)
|
|
198
|
+
fs.chmodSync(setupSh, 0o755);
|
|
199
|
+
return ['tools/setup.sh'];
|
|
200
|
+
}
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
function scaffoldBootstrapConfig(targetDir, structure, options, dryRun) {
|
|
204
|
+
const configPath = path.join(targetDir, 'wp-bootstrap.config.json');
|
|
205
|
+
if (fs.existsSync(configPath))
|
|
206
|
+
return [];
|
|
207
|
+
const config = {
|
|
208
|
+
packageManager: options.packageManager ?? structure.packageManager ?? 'npm',
|
|
209
|
+
jsWorkspaces: structure.jsWorkspaces,
|
|
210
|
+
phpDirs: ['.', ...structure.wpPackages.filter((p) => p.hasComposer).map((p) => p.path)],
|
|
211
|
+
hooksDir: '.githooks',
|
|
212
|
+
wpPackages: structure.wpPackages.map((p) => ({
|
|
213
|
+
path: p.path,
|
|
214
|
+
type: p.type,
|
|
215
|
+
slug: p.slug,
|
|
216
|
+
...(p.mainFile ? { mainFile: p.mainFile } : {}),
|
|
217
|
+
})),
|
|
218
|
+
...(structure.wpeRemotes.length > 0 || options.wpeEnvironments
|
|
219
|
+
? {
|
|
220
|
+
wpeEnvironments: {
|
|
221
|
+
...(options.wpeEnvironments?.production
|
|
222
|
+
? {
|
|
223
|
+
production: { install: options.wpeEnvironments.production },
|
|
224
|
+
}
|
|
225
|
+
: {}),
|
|
226
|
+
...(options.wpeEnvironments?.staging
|
|
227
|
+
? {
|
|
228
|
+
staging: { install: options.wpeEnvironments.staging },
|
|
229
|
+
}
|
|
230
|
+
: {}),
|
|
231
|
+
...(options.wpeEnvironments?.development
|
|
232
|
+
? {
|
|
233
|
+
development: { install: options.wpeEnvironments.development },
|
|
234
|
+
}
|
|
235
|
+
: {}),
|
|
236
|
+
},
|
|
237
|
+
}
|
|
238
|
+
: {}),
|
|
239
|
+
};
|
|
240
|
+
if (writeIfMissing(configPath, `${JSON.stringify(config, null, 2)}
|
|
241
|
+
`, dryRun)) {
|
|
242
|
+
return ['wp-bootstrap.config.json'];
|
|
243
|
+
}
|
|
244
|
+
return [];
|
|
245
|
+
}
|
|
246
|
+
// ── Main API ───────────────────────────────────────────────────────────────
|
|
247
|
+
export async function bootstrapApi(options) {
|
|
248
|
+
const formatter = new OutputFormatter('json', 'bootstrap', '0.0.0');
|
|
249
|
+
const { targetDir, platform = 'github', detectOnly = false, dryRun = false, skipInstall = false, } = options;
|
|
250
|
+
try {
|
|
251
|
+
// 1. Detect structure
|
|
252
|
+
const structure = detectStructure(targetDir);
|
|
253
|
+
if (!structure) {
|
|
254
|
+
return formatter.fail({
|
|
255
|
+
code: 'DETECT_FAILED',
|
|
256
|
+
message: 'Failed to detect project structure',
|
|
257
|
+
exitCode: ExitCode.ERROR,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
// Apply overrides
|
|
261
|
+
if (options.wpRoot)
|
|
262
|
+
structure.wpRoot = options.wpRoot;
|
|
263
|
+
if (options.packageManager)
|
|
264
|
+
structure.packageManager = options.packageManager;
|
|
265
|
+
if (detectOnly) {
|
|
266
|
+
return formatter.success({
|
|
267
|
+
structure,
|
|
268
|
+
detectOnly: true,
|
|
269
|
+
actions: [],
|
|
270
|
+
filesCreated: [],
|
|
271
|
+
dryRun,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
const actions = [];
|
|
275
|
+
const filesCreated = [];
|
|
276
|
+
// 2. Install agent kit
|
|
277
|
+
if (!skipInstall && !structure.hasAgentKit) {
|
|
278
|
+
if (!dryRun) {
|
|
279
|
+
installKit(targetDir, platform, { safe: true, backup: false });
|
|
280
|
+
}
|
|
281
|
+
actions.push(`✅ wp-agent-kit installed (platform: ${platform})`);
|
|
282
|
+
filesCreated.push('.agents/skills/', `${platform === 'github' ? '.github' : `.${platform}`}/`);
|
|
283
|
+
}
|
|
284
|
+
else if (structure.hasAgentKit) {
|
|
285
|
+
actions.push('✓ wp-agent-kit already installed');
|
|
286
|
+
}
|
|
287
|
+
// 3. Generate wp-cli.yml
|
|
288
|
+
const wpeEnvs = options.wpeEnvironments;
|
|
289
|
+
const hasWpeConfig = wpeEnvs?.production || wpeEnvs?.staging || wpeEnvs?.development;
|
|
290
|
+
if (hasWpeConfig || structure.wpRoot) {
|
|
291
|
+
const created = scaffoldWpCliYml(targetDir, structure, wpeEnvs, dryRun);
|
|
292
|
+
if (created.length > 0) {
|
|
293
|
+
actions.push('✅ wp-cli.yml created');
|
|
294
|
+
filesCreated.push(...created);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
actions.push('✓ wp-cli.yml already exists');
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// 4. Git hooks
|
|
301
|
+
const hookFiles = scaffoldGitHooks(targetDir, dryRun);
|
|
302
|
+
if (hookFiles.length > 0) {
|
|
303
|
+
actions.push('✅ .githooks/pre-push created');
|
|
304
|
+
filesCreated.push(...hookFiles);
|
|
305
|
+
if (!dryRun) {
|
|
306
|
+
// Activate hooks
|
|
307
|
+
spawnSync('git', ['config', 'core.hooksPath', '.githooks'], { cwd: targetDir });
|
|
308
|
+
}
|
|
309
|
+
actions.push('✅ git config core.hooksPath .githooks');
|
|
310
|
+
}
|
|
311
|
+
// 5. tools/setup.sh
|
|
312
|
+
const setupFiles = scaffoldSetupSh(targetDir, dryRun);
|
|
313
|
+
if (setupFiles.length > 0) {
|
|
314
|
+
actions.push('✅ tools/setup.sh created');
|
|
315
|
+
filesCreated.push(...setupFiles);
|
|
316
|
+
}
|
|
317
|
+
// 6. wp-bootstrap.config.json
|
|
318
|
+
const configFiles = scaffoldBootstrapConfig(targetDir, structure, options, dryRun);
|
|
319
|
+
if (configFiles.length > 0) {
|
|
320
|
+
actions.push('✅ wp-bootstrap.config.json created');
|
|
321
|
+
filesCreated.push(...configFiles);
|
|
322
|
+
}
|
|
323
|
+
// 7. Report what still needs manual setup
|
|
324
|
+
if (!structure.php.hasComposer) {
|
|
325
|
+
actions.push('⚠ composer.json missing — run: composer init (see wp-bootstrap skill references/composer-setup.md)');
|
|
326
|
+
}
|
|
327
|
+
if (!structure.js.hasBiome) {
|
|
328
|
+
actions.push('⚠ biome.json missing — run: npm install --save-dev @biomejs/biome && npx biome init');
|
|
329
|
+
}
|
|
330
|
+
if (!structure.playground.hasPlayground) {
|
|
331
|
+
actions.push('⚠ Playground not configured — run playground-start.sh to get started');
|
|
332
|
+
}
|
|
333
|
+
if (!structure.hasWpeRemote && !hasWpeConfig) {
|
|
334
|
+
actions.push('⚠ No WP Engine remotes — add --wpe-prod/--wpe-staging/--wpe-dev or configure manually');
|
|
335
|
+
}
|
|
336
|
+
return formatter.success({
|
|
337
|
+
structure,
|
|
338
|
+
detectOnly: false,
|
|
339
|
+
actions,
|
|
340
|
+
filesCreated,
|
|
341
|
+
dryRun,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
const err = error;
|
|
346
|
+
return formatter.fail({
|
|
347
|
+
code: 'BOOTSTRAP_FAILED',
|
|
348
|
+
message: err.message || 'Bootstrap failed',
|
|
349
|
+
exitCode: err.exitCode ?? ExitCode.ERROR,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|