wordpress-agent-kit 0.5.1 → 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 (132) 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/.agents/skills/wp-wpengine/SKILL.md +76 -12
  11. package/.agents/skills/wp-wpengine/references/github-actions-deploy.md +16 -9
  12. package/README.md +1 -1
  13. package/dist/cli.js +2 -0
  14. package/dist/commands/bootstrap.js +105 -0
  15. package/dist/lib/api.js +1 -0
  16. package/dist/lib/bootstrap.js +352 -0
  17. package/extensions/wp-agent-kit/index.ts +143 -3
  18. package/package.json +1 -1
  19. package/skills-custom/wp-bootstrap/SKILL.md +314 -0
  20. package/skills-custom/wp-bootstrap/references/composer-setup.md +275 -0
  21. package/skills-custom/wp-bootstrap/references/monorepo-patterns.md +184 -0
  22. package/skills-custom/wp-bootstrap/scripts/bootstrap.sh +151 -0
  23. package/skills-custom/wp-bootstrap/scripts/detect-structure.mjs +466 -0
  24. package/skills-custom/wp-bootstrap/scripts/package-wp.sh +173 -0
  25. package/skills-custom/wp-bootstrap/scripts/playground-start.sh +148 -0
  26. package/skills-custom/wp-bootstrap/scripts/playground-verify.sh +165 -0
  27. package/skills-custom/wp-bootstrap/scripts/setup-github.sh +417 -0
  28. package/skills-custom/wp-wpengine/SKILL.md +76 -12
  29. package/skills-custom/wp-wpengine/references/github-actions-deploy.md +16 -9
  30. package/.github/skills/blueprint/SKILL.md +0 -418
  31. package/.github/skills/wordpress-router/SKILL.md +0 -52
  32. package/.github/skills/wordpress-router/references/decision-tree.md +0 -55
  33. package/.github/skills/wp-abilities-api/SKILL.md +0 -108
  34. package/.github/skills/wp-abilities-api/references/delegate-helper-pattern.md +0 -241
  35. package/.github/skills/wp-abilities-api/references/domain-vs-projection.md +0 -113
  36. package/.github/skills/wp-abilities-api/references/error-code-vocabulary.md +0 -123
  37. package/.github/skills/wp-abilities-api/references/grouping-heuristic.md +0 -89
  38. package/.github/skills/wp-abilities-api/references/input-schema-gotchas.md +0 -265
  39. package/.github/skills/wp-abilities-api/references/php-registration.md +0 -94
  40. package/.github/skills/wp-abilities-api/references/plugin-family-patterns.md +0 -233
  41. package/.github/skills/wp-abilities-api/references/rest-api.md +0 -13
  42. package/.github/skills/wp-abilities-api/references/shared-core-service.md +0 -184
  43. package/.github/skills/wp-abilities-audit/SKILL.md +0 -199
  44. package/.github/skills/wp-abilities-audit/references/audit-schema.md +0 -300
  45. package/.github/skills/wp-abilities-audit/references/capability-gate-tracing.md +0 -197
  46. package/.github/skills/wp-abilities-audit/references/controller-enumeration.md +0 -116
  47. package/.github/skills/wp-abilities-verify/SKILL.md +0 -215
  48. package/.github/skills/wp-abilities-verify/references/annotation-correctness.md +0 -154
  49. package/.github/skills/wp-abilities-verify/references/audit-schema-validation.md +0 -131
  50. package/.github/skills/wp-abilities-verify/references/permission-roundtrip.md +0 -190
  51. package/.github/skills/wp-abilities-verify/references/runtime-harness.md +0 -462
  52. package/.github/skills/wp-abilities-verify/references/schema-lints.md +0 -118
  53. package/.github/skills/wp-abilities-verify/references/static-enumeration.md +0 -126
  54. package/.github/skills/wp-block-development/SKILL.md +0 -175
  55. package/.github/skills/wp-block-development/references/attributes-and-serialization.md +0 -22
  56. package/.github/skills/wp-block-development/references/block-json.md +0 -49
  57. package/.github/skills/wp-block-development/references/creating-new-blocks.md +0 -46
  58. package/.github/skills/wp-block-development/references/debugging.md +0 -36
  59. package/.github/skills/wp-block-development/references/deprecations.md +0 -24
  60. package/.github/skills/wp-block-development/references/dynamic-rendering.md +0 -23
  61. package/.github/skills/wp-block-development/references/inner-blocks.md +0 -25
  62. package/.github/skills/wp-block-development/references/registration.md +0 -30
  63. package/.github/skills/wp-block-development/references/supports-and-wrappers.md +0 -18
  64. package/.github/skills/wp-block-development/references/tooling-and-testing.md +0 -21
  65. package/.github/skills/wp-block-development/scripts/list_blocks.mjs +0 -121
  66. package/.github/skills/wp-block-themes/SKILL.md +0 -117
  67. package/.github/skills/wp-block-themes/references/creating-new-block-theme.md +0 -37
  68. package/.github/skills/wp-block-themes/references/debugging.md +0 -24
  69. package/.github/skills/wp-block-themes/references/patterns.md +0 -18
  70. package/.github/skills/wp-block-themes/references/style-variations.md +0 -14
  71. package/.github/skills/wp-block-themes/references/templates-and-parts.md +0 -16
  72. package/.github/skills/wp-block-themes/references/theme-json.md +0 -59
  73. package/.github/skills/wp-block-themes/scripts/detect_block_themes.mjs +0 -117
  74. package/.github/skills/wp-interactivity-api/SKILL.md +0 -180
  75. package/.github/skills/wp-interactivity-api/references/debugging.md +0 -29
  76. package/.github/skills/wp-interactivity-api/references/directives-quickref.md +0 -30
  77. package/.github/skills/wp-interactivity-api/references/server-side-rendering.md +0 -310
  78. package/.github/skills/wp-performance/SKILL.md +0 -147
  79. package/.github/skills/wp-performance/references/autoload-options.md +0 -24
  80. package/.github/skills/wp-performance/references/cron.md +0 -20
  81. package/.github/skills/wp-performance/references/database.md +0 -20
  82. package/.github/skills/wp-performance/references/http-api.md +0 -15
  83. package/.github/skills/wp-performance/references/measurement.md +0 -21
  84. package/.github/skills/wp-performance/references/object-cache.md +0 -24
  85. package/.github/skills/wp-performance/references/query-monitor-headless.md +0 -38
  86. package/.github/skills/wp-performance/references/server-timing.md +0 -22
  87. package/.github/skills/wp-performance/references/wp-cli-doctor.md +0 -24
  88. package/.github/skills/wp-performance/references/wp-cli-profile.md +0 -32
  89. package/.github/skills/wp-performance/scripts/perf_inspect.mjs +0 -128
  90. package/.github/skills/wp-phpstan/SKILL.md +0 -98
  91. package/.github/skills/wp-phpstan/references/configuration.md +0 -52
  92. package/.github/skills/wp-phpstan/references/third-party-classes.md +0 -76
  93. package/.github/skills/wp-phpstan/references/wordpress-annotations.md +0 -124
  94. package/.github/skills/wp-phpstan/scripts/phpstan_inspect.mjs +0 -263
  95. package/.github/skills/wp-playground/SKILL.md +0 -233
  96. package/.github/skills/wp-playground/references/blueprints.md +0 -36
  97. package/.github/skills/wp-playground/references/cli-commands.md +0 -39
  98. package/.github/skills/wp-playground/references/debugging.md +0 -16
  99. package/.github/skills/wp-playground/references/e2e-playwright.md +0 -115
  100. package/.github/skills/wp-plugin-development/SKILL.md +0 -113
  101. package/.github/skills/wp-plugin-development/references/data-and-cron.md +0 -19
  102. package/.github/skills/wp-plugin-development/references/debugging.md +0 -19
  103. package/.github/skills/wp-plugin-development/references/lifecycle.md +0 -33
  104. package/.github/skills/wp-plugin-development/references/security.md +0 -29
  105. package/.github/skills/wp-plugin-development/references/settings-api.md +0 -22
  106. package/.github/skills/wp-plugin-development/references/structure.md +0 -16
  107. package/.github/skills/wp-plugin-development/scripts/detect_plugins.mjs +0 -122
  108. package/.github/skills/wp-plugin-directory-guidelines/SKILL.md +0 -133
  109. package/.github/skills/wp-plugin-directory-guidelines/references/gpl-compliance.md +0 -217
  110. package/.github/skills/wp-plugin-directory-guidelines/references/guideline-review-checklist.md +0 -592
  111. package/.github/skills/wp-plugin-directory-guidelines/references/naming-rules.md +0 -121
  112. package/.github/skills/wp-project-triage/SKILL.md +0 -39
  113. package/.github/skills/wp-project-triage/references/triage.schema.json +0 -143
  114. package/.github/skills/wp-project-triage/scripts/detect_wp_project.mjs +0 -610
  115. package/.github/skills/wp-rest-api/SKILL.md +0 -115
  116. package/.github/skills/wp-rest-api/references/authentication.md +0 -18
  117. package/.github/skills/wp-rest-api/references/custom-content-types.md +0 -20
  118. package/.github/skills/wp-rest-api/references/discovery-and-params.md +0 -20
  119. package/.github/skills/wp-rest-api/references/responses-and-fields.md +0 -30
  120. package/.github/skills/wp-rest-api/references/routes-and-endpoints.md +0 -36
  121. package/.github/skills/wp-rest-api/references/schema.md +0 -22
  122. package/.github/skills/wp-wpcli-and-ops/SKILL.md +0 -124
  123. package/.github/skills/wp-wpcli-and-ops/references/automation.md +0 -30
  124. package/.github/skills/wp-wpcli-and-ops/references/cron-and-cache.md +0 -23
  125. package/.github/skills/wp-wpcli-and-ops/references/debugging.md +0 -17
  126. package/.github/skills/wp-wpcli-and-ops/references/multisite.md +0 -22
  127. package/.github/skills/wp-wpcli-and-ops/references/packages-and-updates.md +0 -22
  128. package/.github/skills/wp-wpcli-and-ops/references/safety.md +0 -30
  129. package/.github/skills/wp-wpcli-and-ops/references/search-replace.md +0 -40
  130. package/.github/skills/wp-wpcli-and-ops/scripts/wpcli_inspect.mjs +0 -90
  131. package/.github/skills/wp-wpengine/SKILL.md +0 -127
  132. package/.github/skills/wpds/SKILL.md +0 -59
@@ -0,0 +1,105 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { Command } from 'commander';
5
+ import { bootstrapApi } from '../lib/api.js';
6
+ import { OutputFormatter, createFormatter } from '../utils/output.js';
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ /**
10
+ * Bootstrap command — probe, configure, and wire up a WordPress project.
11
+ * Supports single plugins/themes and monorepos (multiple WP packages).
12
+ * Supports --detect-only, --auto (non-interactive), --dry-run, --json.
13
+ */
14
+ export const bootstrapCommand = new Command('bootstrap')
15
+ .description('Bootstrap a WordPress project: detect structure, install agent kit, scaffold tooling')
16
+ .argument('[dir]', 'Target directory (defaults to cwd)', process.cwd())
17
+ .option('--detect-only', 'Only run structure detection and output result', false)
18
+ .option('--auto', 'Non-interactive: use detected values without prompting', false)
19
+ .option('--platform <platform>', 'Agent kit platform (github, pi, cursor, claude)', 'github')
20
+ .option('--wp-root <path>', 'WordPress root path relative to repo root (overrides detection)')
21
+ .option('--wp-packages <dirs>', 'Comma-separated list of WP package dirs (overrides detection)')
22
+ .option('--package-manager <pm>', 'Package manager: npm, pnpm, yarn (overrides detection)')
23
+ .option('--wpe-prod <slug>', 'WP Engine production install slug')
24
+ .option('--wpe-staging <slug>', 'WP Engine staging install slug')
25
+ .option('--wpe-dev <slug>', 'WP Engine development install slug')
26
+ .option('--with-satispress <url>', 'Add SatisPress repository URL to composer.json')
27
+ .option('--with-wpackagist', 'Add WPackagist repository to composer.json', false)
28
+ .option('--skip-install', 'Skip running wp-agent-kit install after scaffolding', false)
29
+ .action(async (dir, options, command) => {
30
+ const globalOpts = command.parent?.opts() || {};
31
+ const targetDir = path.resolve(dir);
32
+ const formatter = createFormatter(globalOpts, 'bootstrap', '0.0.0');
33
+ if (!fs.existsSync(targetDir)) {
34
+ const result = formatter.fail({
35
+ code: 'NOT_FOUND',
36
+ message: `Directory does not exist: ${targetDir}`,
37
+ exitCode: 3,
38
+ });
39
+ process.exit(OutputFormatter.getExitCode(result));
40
+ }
41
+ // ── 1. Structure detection ───────────────────────────────────────────
42
+ if (!globalOpts.json && !globalOpts.quiet) {
43
+ console.log(`\n▶ Detecting project structure in ${targetDir}...`);
44
+ }
45
+ const result = await bootstrapApi({
46
+ targetDir,
47
+ platform: options.platform,
48
+ detectOnly: options.detectOnly,
49
+ auto: options.auto,
50
+ dryRun: globalOpts.dryRun ?? false,
51
+ wpRoot: options.wpRoot,
52
+ wpPackages: options.wpPackages?.split(',').map((s) => s.trim()),
53
+ packageManager: options.packageManager,
54
+ wpeEnvironments: {
55
+ production: options.wpeProd,
56
+ staging: options.wpeStaging,
57
+ development: options.wpeDev,
58
+ },
59
+ withSatispress: options.withSatispress,
60
+ withWpackagist: options.withWpackagist,
61
+ skipInstall: options.skipInstall,
62
+ });
63
+ if (globalOpts.json || globalOpts.quiet) {
64
+ process.exit(OutputFormatter.getExitCode(result));
65
+ }
66
+ if (!result.success) {
67
+ console.error(`\n✗ Bootstrap failed: ${result.error?.message}`);
68
+ process.exit(1);
69
+ }
70
+ const data = result.data;
71
+ if (data.detectOnly) {
72
+ // Pretty-print structure
73
+ const s = data.structure;
74
+ console.log('\n── Detected structure ──────────────────────────────────────');
75
+ console.log(` Monorepo: ${s.isMonorepo ? 'yes' : 'no'}`);
76
+ console.log(` Pkg mgr: ${s.packageManager ?? 'none'}`);
77
+ console.log(` WP root: ${s.wpRoot ?? 'none (Playground-only)'}`);
78
+ console.log(` WP packages: ${s.wpPackages.length}`);
79
+ for (const p of s.wpPackages) {
80
+ console.log(` ${p.type === 'plugin' ? '🔌' : '🎨'} ${p.name ?? p.path} (${p.path}) v${p.version ?? '?'}`);
81
+ }
82
+ console.log(` WPE remotes: ${s.wpeRemotes.length}`);
83
+ for (const r of s.wpeRemotes) {
84
+ console.log(` → ${r.name} (${r.install})`);
85
+ }
86
+ console.log(` PHP: PHPCS=${s.php.hasPhpcs} PHPStan=${s.php.hasPhpstan}`);
87
+ console.log(` JS: Biome=${s.js.hasBiome} Vitest=${s.js.hasVitest}`);
88
+ console.log(` Playground: ${s.playground.hasPlayground ? 'configured' : 'not configured'}`);
89
+ console.log(` Agent kit: ${s.hasAgentKit ? 'installed' : 'not installed'}`);
90
+ console.log('');
91
+ }
92
+ else {
93
+ console.log('\n── Bootstrap complete ──────────────────────────────────────');
94
+ for (const action of data.actions) {
95
+ console.log(` ${action}`);
96
+ }
97
+ console.log('\nNext steps:');
98
+ console.log(' 1. Run: bash tools/setup.sh (install dev deps + activate hooks)');
99
+ console.log(' 2. Run: bash tools/playground/run-playground.sh (start local WP)');
100
+ console.log(' 3. Review AGENTS.md and the generated wp-cli.yml');
101
+ console.log(' 4. Add WP Engine secrets to GitHub (see .agents/skills/wp-wpengine/SKILL.md)');
102
+ console.log('');
103
+ }
104
+ process.exit(0);
105
+ });
package/dist/lib/api.js CHANGED
@@ -692,3 +692,4 @@ export async function cleanSkillsApi(options) {
692
692
  export { ExitCode } from '../utils/exit-codes.js';
693
693
  export { OutputFormatter, createFormatter, parseOutputFormat } from '../utils/output.js';
694
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
+ }
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'node:url';
7
7
  * Provides Pi Coding Agent with WordPress development tools:
8
8
  * - 18 WordPress agent skills (17 upstream + wp-wpengine custom) at .agents/skills/ (AgentSkills.io convention)
9
9
  * - Project triage detection
10
- * - Skill installation, syncing, upgrade, and orphan cleanup
10
+ * - Skill installation, syncing, upgrade, orphan cleanup, and project bootstrapping
11
11
  *
12
12
  * Follows Pi Coding Agent SDK conventions (extensions.md, packages.md, skills.md).
13
13
  */
@@ -26,8 +26,15 @@ try {
26
26
  apiModule = (await import('../../src/lib/api.js')) as typeof import('../../dist/lib/api.js');
27
27
  }
28
28
 
29
- const { installKitApi, syncSkillsApi, runTriageApi, cleanSkillsApi, isKitInstalled, loadManifest } =
30
- apiModule;
29
+ const {
30
+ installKitApi,
31
+ syncSkillsApi,
32
+ runTriageApi,
33
+ cleanSkillsApi,
34
+ bootstrapApi,
35
+ isKitInstalled,
36
+ loadManifest,
37
+ } = apiModule;
31
38
 
32
39
  export default function (pi: ExtensionAPI) {
33
40
  // =========================================================================
@@ -530,6 +537,113 @@ export default function (pi: ExtensionAPI) {
530
537
  },
531
538
  });
532
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
+
533
647
  // =========================================================================
534
648
  // Commands
535
649
  // =========================================================================
@@ -624,4 +738,30 @@ export default function (pi: ExtensionAPI) {
624
738
  ctx.ui.setStatus('wp-clean', total === 0 ? 'clean' : `${total} issues`);
625
739
  },
626
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
+ });
627
767
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wordpress-agent-kit",
3
- "version": "0.5.1",
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)",