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,466 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * detect-structure.mjs — probe a repo and emit a JSON structure report.
4
+ *
5
+ * Answers: Is this a monorepo? Where are the WP packages? Where is the WP
6
+ * root (if any)? What tooling exists? What WP Engine remotes are configured?
7
+ *
8
+ * Usage:
9
+ * node detect-structure.mjs [dir] # probe dir (default: cwd)
10
+ * node detect-structure.mjs --json [dir] # always emit raw JSON (default)
11
+ * node detect-structure.mjs --pretty [dir] # human-readable summary
12
+ *
13
+ * Exit code: 0 always (probe never fails; missing fields are null/[]).
14
+ */
15
+
16
+ import { execSync } from 'node:child_process';
17
+ import fs from 'node:fs';
18
+ import path from 'node:path';
19
+
20
+ const args = process.argv.slice(2);
21
+ const pretty = args.includes('--pretty');
22
+ const dirArg = args.find(a => !a.startsWith('--')) ?? '.';
23
+ const root = path.resolve(dirArg);
24
+
25
+ // ── Helpers ──────────────────────────────────────────────────────────────────
26
+
27
+ const exists = (...parts) => fs.existsSync(path.join(root, ...parts));
28
+ const read = (...parts) => {
29
+ try { return fs.readFileSync(path.join(root, ...parts), 'utf-8'); }
30
+ catch { return null; }
31
+ };
32
+ const readJson = (...parts) => {
33
+ try { return JSON.parse(read(...parts) ?? 'null'); }
34
+ catch { return null; }
35
+ };
36
+ const run = (cmd, cwd = root) => {
37
+ try { return execSync(cmd, { cwd, encoding: 'utf-8', stdio: ['pipe','pipe','pipe'] }).trim(); }
38
+ catch { return null; }
39
+ };
40
+
41
+ // Recursively find files matching predicate up to maxDepth
42
+ function findFiles(dir, predicate, maxDepth = 3, _depth = 0) {
43
+ const results = [];
44
+ if (_depth > maxDepth) return results;
45
+ let entries;
46
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
47
+ catch { return results; }
48
+ for (const e of entries) {
49
+ const full = path.join(dir, e.name);
50
+ if (e.isDirectory()) {
51
+ if (['node_modules','vendor','.git','dist','build','.next','coverage'].includes(e.name)) continue;
52
+ results.push(...findFiles(full, predicate, maxDepth, _depth + 1));
53
+ } else if (predicate(e.name, full)) {
54
+ results.push(full);
55
+ }
56
+ }
57
+ return results;
58
+ }
59
+
60
+ // ── 1. WordPress package detection ───────────────────────────────────────────
61
+
62
+ function parsePluginHeader(content) {
63
+ const get = (key) => {
64
+ const m = content.match(new RegExp(`\\*\\s+${key}:\\s*(.+)`, 'i'));
65
+ return m ? m[1].trim() : null;
66
+ };
67
+ return { name: get('Plugin Name'), version: get('Version'), textDomain: get('Text Domain') };
68
+ }
69
+
70
+ function parseThemeHeader(content) {
71
+ const get = (key) => {
72
+ const m = content.match(new RegExp(`\\*\\s+${key}:\\s*(.+)`, 'i'));
73
+ return m ? m[1].trim() : null;
74
+ };
75
+ return { name: get('Theme Name'), version: get('Version'), textDomain: get('Text Domain') };
76
+ }
77
+
78
+ function detectWpPackages() {
79
+ const packages = [];
80
+
81
+ // Find all PHP files that could be plugin/theme headers (shallow)
82
+ const phpFiles = findFiles(root, (name) => name.endsWith('.php'), 2);
83
+ const checkedDirs = new Set();
84
+
85
+ for (const phpFile of phpFiles) {
86
+ const dir = path.dirname(phpFile);
87
+ const relDir = path.relative(root, dir);
88
+ if (checkedDirs.has(relDir)) continue;
89
+
90
+ const content = fs.readFileSync(phpFile, 'utf-8');
91
+
92
+ if (/Plugin Name:/i.test(content)) {
93
+ const h = parsePluginHeader(content);
94
+ const composerJson = readJson(...relDir.split('/'), 'composer.json') ?? readJson('composer.json');
95
+ checkedDirs.add(relDir);
96
+ packages.push({
97
+ type: 'plugin',
98
+ path: relDir || '.',
99
+ name: h.name,
100
+ version: h.version,
101
+ slug: h.textDomain ?? path.basename(dir),
102
+ mainFile: path.relative(dir, phpFile),
103
+ hasComposer: exists(...relDir.split('/'), 'composer.json'),
104
+ hasTests: exists(...relDir.split('/'), 'tests') || exists(...relDir.split('/'), 'test'),
105
+ hasPest: (readJson(...relDir.split('/'), 'composer.json') ?? {})?.['require-dev']?.['pestphp/pest'] != null,
106
+ });
107
+ } else if (/Theme Name:/i.test(content)) {
108
+ const h = parseThemeHeader(content);
109
+ checkedDirs.add(relDir);
110
+ packages.push({
111
+ type: 'theme',
112
+ path: relDir || '.',
113
+ name: h.name,
114
+ version: h.version,
115
+ slug: h.textDomain ?? path.basename(dir),
116
+ hasComposer: exists(...relDir.split('/'), 'composer.json'),
117
+ styleSheet: path.relative(dir, phpFile),
118
+ });
119
+ }
120
+ }
121
+
122
+ // Also detect by style.css (theme) in case main file is not PHP
123
+ const styleCss = findFiles(root, (name) => name === 'style.css', 2);
124
+ for (const cssFile of styleCss) {
125
+ const dir = path.dirname(cssFile);
126
+ const relDir = path.relative(root, dir);
127
+ if (checkedDirs.has(relDir)) continue;
128
+ const content = fs.readFileSync(cssFile, 'utf-8');
129
+ if (/Theme Name:/i.test(content)) {
130
+ const h = parseThemeHeader(content);
131
+ checkedDirs.add(relDir);
132
+ packages.push({
133
+ type: 'theme',
134
+ path: relDir || '.',
135
+ name: h.name,
136
+ version: h.version,
137
+ slug: h.textDomain ?? path.basename(dir),
138
+ hasComposer: exists(...relDir.split('/'), 'composer.json'),
139
+ styleSheet: path.relative(dir, cssFile),
140
+ });
141
+ }
142
+ }
143
+
144
+ return packages;
145
+ }
146
+
147
+ // ── 2. WP root detection ─────────────────────────────────────────────────────
148
+
149
+ function detectWpRoot() {
150
+ // Look for wp-config.php or wp-blog-header.php
151
+ const candidates = ['.', 'web', 'public', 'wordpress', 'wp', 'src/wordpress'];
152
+ for (const c of candidates) {
153
+ if (exists(c, 'wp-config.php') || exists(c, 'wp-blog-header.php')) {
154
+ return c === '.' ? '.' : c;
155
+ }
156
+ }
157
+ // Look up to 2 levels deep
158
+ const configs = findFiles(root, (name) => name === 'wp-config.php', 2);
159
+ if (configs.length > 0) {
160
+ return path.relative(root, path.dirname(configs[0]));
161
+ }
162
+ return null; // Playground-only / external WP
163
+ }
164
+
165
+ // ── 3. WP Engine remotes ──────────────────────────────────────────────────────
166
+
167
+ function detectWpeRemotes() {
168
+ const output = run('git remote -v');
169
+ if (!output) return [];
170
+ const remotes = [];
171
+ const seen = new Set();
172
+ for (const line of output.split('\n')) {
173
+ const m = line.match(/^(\S+)\s+(git@git\.wpengine\.com:[^\s]+)/);
174
+ if (m && !seen.has(m[1])) {
175
+ seen.add(m[1]);
176
+ const urlParts = m[2].match(/git@git\.wpengine\.com:(?:(production|staging|development)\/)?([^.]+)\.git/);
177
+ remotes.push({
178
+ name: m[1],
179
+ url: m[2],
180
+ environment: urlParts?.[1] ?? null,
181
+ install: urlParts?.[2] ?? null,
182
+ });
183
+ }
184
+ }
185
+ return remotes;
186
+ }
187
+
188
+ // ── 4. Tooling detection ──────────────────────────────────────────────────────
189
+
190
+ function detectPackageManager() {
191
+ if (exists('pnpm-lock.yaml') || exists('pnpm-workspace.yaml')) return 'pnpm';
192
+ if (exists('yarn.lock')) return 'yarn';
193
+ if (exists('bun.lockb')) return 'bun';
194
+ if (exists('package-lock.json')) return 'npm';
195
+ if (exists('package.json')) return 'npm';
196
+ return null;
197
+ }
198
+
199
+ function detectJsWorkspaces() {
200
+ const ws = [];
201
+ // pnpm workspace
202
+ const pnpmWs = read('pnpm-workspace.yaml');
203
+ if (pnpmWs) {
204
+ const pkgs = [...pnpmWs.matchAll(/[-]\s+['"]?([^'"#\n]+)['"]?/g)].map(m => m[1].trim());
205
+ ws.push(...pkgs);
206
+ }
207
+ // npm/yarn workspaces
208
+ const rootPkg = readJson('package.json');
209
+ if (rootPkg?.workspaces) {
210
+ ws.push(...(Array.isArray(rootPkg.workspaces) ? rootPkg.workspaces : rootPkg.workspaces.packages ?? []));
211
+ }
212
+ return ws;
213
+ }
214
+
215
+ function detectPhpTooling() {
216
+ const composerJson = readJson('composer.json');
217
+ const devDeps = { ...(composerJson?.require ?? {}), ...(composerJson?.['require-dev'] ?? {}) };
218
+ return {
219
+ hasComposer: exists('composer.json'),
220
+ hasPhpcs: exists('vendor/bin/phpcs') || 'squizlabs/php_codesniffer' in devDeps,
221
+ hasWpcs: 'wp-coding-standards/wpcs' in devDeps,
222
+ hasPhpstan: exists('vendor/bin/phpstan') || 'phpstan/phpstan' in devDeps,
223
+ hasPhpstanWp: 'szepeviktor/phpstan-wordpress' in devDeps,
224
+ hasPest: 'pestphp/pest' in devDeps,
225
+ phpcsConfig: exists('phpcs.xml.dist') ? 'phpcs.xml.dist' : exists('phpcs.xml') ? 'phpcs.xml' : null,
226
+ phpstanConfig: exists('phpstan.neon.dist') ? 'phpstan.neon.dist' : exists('phpstan.neon') ? 'phpstan.neon' : null,
227
+ composerScripts: Object.keys(composerJson?.scripts ?? {}),
228
+ };
229
+ }
230
+
231
+ function detectJsTooling() {
232
+ const rootPkg = readJson('package.json');
233
+ const biomeJson = readJson('biome.json');
234
+ const devDeps = { ...(rootPkg?.devDependencies ?? {}), ...(rootPkg?.dependencies ?? {}) };
235
+ return {
236
+ hasBiome: !!biomeJson || '@biomejs/biome' in devDeps,
237
+ biomeVersion: biomeJson?.['$schema']?.match(/schemas\/(\d+\.\d+\.\d+)/)?.[1] ?? (devDeps['@biomejs/biome']?.replace(/^\^|~/, '') ?? null),
238
+ hasEslint: 'eslint' in devDeps || exists('eslint.config.mjs') || exists('.eslintrc.js'),
239
+ hasPrettier: 'prettier' in devDeps || exists('.prettierrc'),
240
+ hasVitest: 'vitest' in devDeps,
241
+ hasJest: 'jest' in devDeps,
242
+ hasPlaywright: '@playwright/test' in devDeps || exists('playwright.config.ts') || exists('playwright.config.js'),
243
+ hasWpScripts: '@wordpress/scripts' in devDeps,
244
+ rootScripts: Object.keys(rootPkg?.scripts ?? {}),
245
+ };
246
+ }
247
+
248
+ function detectGitHooks() {
249
+ if (exists('.githooks')) return '.githooks';
250
+ if (exists('.husky')) return '.husky';
251
+ return null;
252
+ }
253
+
254
+ function detectPlayground() {
255
+ const blueprints = findFiles(root, (name) => name.endsWith('-blueprint.json') || name === 'blueprint.json', 3);
256
+ const runScript = findFiles(root, (name) => name.startsWith('run-') && name.endsWith('.sh'), 3)
257
+ .filter(f => fs.readFileSync(f,'utf-8').includes('@wp-playground'));
258
+ return {
259
+ hasPlayground: blueprints.length > 0 || runScript.length > 0,
260
+ blueprints: blueprints.map(f => path.relative(root, f)),
261
+ scripts: runScript.map(f => path.relative(root, f)),
262
+ hasWpEnv: exists('.wp-env.json'),
263
+ };
264
+ }
265
+
266
+ function detectSatispress() {
267
+ const composerJson = readJson('composer.json');
268
+ const authJson = readJson('auth.json');
269
+ const repos = Object.values(composerJson?.repositories ?? {});
270
+ const satisRepo = repos.find(r => typeof r === 'object' && r.url?.includes('satispress'));
271
+ return {
272
+ configured: !!satisRepo,
273
+ url: satisRepo?.url ?? null,
274
+ hasAuthJson: !!authJson,
275
+ };
276
+ }
277
+
278
+ function detectWpackagist() {
279
+ const composerJson = readJson('composer.json');
280
+ const repos = Object.values(composerJson?.repositories ?? {});
281
+ return repos.some(r => typeof r === 'object' && r.url?.includes('wpackagist.org'));
282
+ }
283
+
284
+ // ── 5. GitHub CLI detection ─────────────────────────────────────────────────
285
+
286
+ function detectGithub() {
287
+ // Check if gh is installed
288
+ const ghInstalled = !!run('command -v gh 2>/dev/null || which gh 2>/dev/null');
289
+ if (!ghInstalled) {
290
+ return { ghInstalled: false, authenticated: false, account: null,
291
+ repoOwner: null, repoName: null, repoUrl: null, visibility: null,
292
+ defaultBranch: null, existingSecrets: [], missingSecrets: [],
293
+ branchProtection: {}, error: 'gh CLI not installed' };
294
+ }
295
+
296
+ // Parse owner/repo from git remote origin
297
+ const originUrl = run('git remote get-url origin 2>/dev/null');
298
+ let repoOwner = null, repoName = null;
299
+ if (originUrl) {
300
+ const sshMatch = originUrl.match(/git@github\.com[:/]([^/]+)\/([^.]+)(?:\.git)?$/);
301
+ const httpsMatch = originUrl.match(/https?:\/\/github\.com\/([^/]+)\/([^/.]+)/);
302
+ const m = sshMatch ?? httpsMatch;
303
+ if (m) { repoOwner = m[1]; repoName = m[2]; }
304
+ }
305
+
306
+ // Check authentication
307
+ const authOut = run('gh auth status 2>&1');
308
+ const authenticated = authOut ? /Logged in to github\.com/.test(authOut) : false;
309
+ const accountMatch = authOut?.match(/account (\S+)/);
310
+ const account = accountMatch?.[1] ?? null;
311
+
312
+ if (!authenticated || !repoOwner || !repoName) {
313
+ return { ghInstalled: true, authenticated, account, repoOwner, repoName,
314
+ repoUrl: repoOwner && repoName ? `https://github.com/${repoOwner}/${repoName}` : null,
315
+ visibility: null, defaultBranch: null, existingSecrets: [], missingSecrets: [],
316
+ branchProtection: {}, error: authenticated ? 'no GitHub remote found' : 'not authenticated' };
317
+ }
318
+
319
+ // Repo info
320
+ let repoInfo = null;
321
+ try {
322
+ const info = run(`gh repo view ${repoOwner}/${repoName} --json name,owner,url,defaultBranchRef,visibility 2>/dev/null`);
323
+ if (info) repoInfo = JSON.parse(info);
324
+ } catch {}
325
+
326
+ const defaultBranch = repoInfo?.defaultBranchRef?.name ?? 'main';
327
+ const visibility = repoInfo?.visibility ?? null;
328
+ const repoUrl = repoInfo?.url ?? `https://github.com/${repoOwner}/${repoName}`;
329
+
330
+ // Existing secrets
331
+ const secretsOut = run(`gh secret list --repo ${repoOwner}/${repoName} --json name 2>/dev/null`);
332
+ let existingSecrets = [];
333
+ try { existingSecrets = secretsOut ? JSON.parse(secretsOut).map(s => s.name) : []; } catch {}
334
+
335
+ // Required secrets for WP Engine deploy
336
+ const REQUIRED_SECRETS = [
337
+ 'WPE_SSH_KEY', 'WPE_SSH_KNOWN_HOSTS',
338
+ 'WPE_PROD_INSTALL', 'WPE_PROD_GIT_URL',
339
+ 'WPE_STAGING_INSTALL', 'WPE_STAGING_GIT_URL',
340
+ 'WPE_DEV_INSTALL', 'WPE_DEV_GIT_URL',
341
+ 'WPE_API_USER', 'WPE_API_PASSWORD',
342
+ ];
343
+ const existingSet = new Set(existingSecrets);
344
+ const missingSecrets = REQUIRED_SECRETS.filter(s => !existingSet.has(s));
345
+
346
+ // Branch protection check
347
+ const branchProtection = {};
348
+ for (const branch of [defaultBranch, 'staging', 'develop']) {
349
+ const protOut = run(`gh api repos/${repoOwner}/${repoName}/branches/${branch}/protection 2>/dev/null`);
350
+ try {
351
+ const prot = protOut ? JSON.parse(protOut) : null;
352
+ branchProtection[branch] = prot && !prot.message ? {
353
+ protected: true,
354
+ requiredChecks: prot.required_status_checks?.contexts ?? [],
355
+ requiredReviewers: prot.required_pull_request_reviews?.required_approving_review_count ?? 0,
356
+ enforceAdmins: prot.enforce_admins?.enabled ?? false,
357
+ allowForcePushes: prot.allow_force_pushes?.enabled ?? true,
358
+ } : { protected: false };
359
+ } catch {
360
+ branchProtection[branch] = { protected: false };
361
+ }
362
+ }
363
+
364
+ return {
365
+ ghInstalled: true, authenticated, account,
366
+ repoOwner, repoName, repoUrl, visibility, defaultBranch,
367
+ existingSecrets, missingSecrets, branchProtection,
368
+ error: null,
369
+ };
370
+ }
371
+
372
+ // ── 6. Assemble result ────────────────────────────────────────────────────────
373
+
374
+ const wpPackages = detectWpPackages();
375
+ const wpRoot = detectWpRoot();
376
+ const wpeRemotes = detectWpeRemotes();
377
+ const pm = detectPackageManager();
378
+ const jsWorkspaces = detectJsWorkspaces();
379
+ const php = detectPhpTooling();
380
+ const js = detectJsTooling();
381
+ const playground = detectPlayground();
382
+ const gitHooks = detectGitHooks();
383
+
384
+ const result = {
385
+ repoRoot: root,
386
+ isMonorepo: wpPackages.length > 1 || jsWorkspaces.length > 0,
387
+ packageManager: pm,
388
+ jsWorkspaces,
389
+
390
+ wpPackages,
391
+ wpRoot,
392
+ wpRootExists: wpRoot !== null,
393
+ playgroundOnly: wpRoot === null,
394
+
395
+ wpeRemotes,
396
+ hasWpeRemote: wpeRemotes.length > 0,
397
+
398
+ php,
399
+ js,
400
+ playground,
401
+
402
+ satispress: detectSatispress(),
403
+ wpackagist: detectWpackagist(),
404
+
405
+ gitHooks,
406
+ hasAgentKit: exists('.wp-agent-kit-manifest.github.json') ||
407
+ exists('.wp-agent-kit-manifest.pi.json') ||
408
+ exists('.agents', 'skills'),
409
+ hasAgentsDir: exists('.agents'),
410
+ hasAgentsMd: exists('AGENTS.md'),
411
+
412
+ gitBranch: run('git rev-parse --abbrev-ref HEAD'),
413
+ gitRemotes: (() => {
414
+ const out = run('git remote -v');
415
+ if (!out) return [];
416
+ const seen = new Set();
417
+ return out.split('\n')
418
+ .map(l => l.match(/^(\S+)\s+(\S+)\s+\(fetch\)/))
419
+ .filter(Boolean)
420
+ .filter(m => !seen.has(m[1]) && seen.add(m[1]))
421
+ .map(m => ({ name: m[1], url: m[2] }));
422
+ })(),
423
+
424
+ github: detectGithub(),
425
+ };
426
+
427
+ // ── 6. Output ─────────────────────────────────────────────────────────────────
428
+
429
+ if (pretty) {
430
+ const pkg = (p) => ` ${p.type === 'plugin' ? '🔌' : '🎨'} ${p.name ?? p.slug} (${p.path}) v${p.version ?? '?'}`;
431
+ console.log(`\n── WordPress Project Structure ─────────────────────────────`);
432
+ console.log(` Repo root: ${root}`);
433
+ console.log(` Monorepo: ${result.isMonorepo ? 'yes' : 'no'}`);
434
+ console.log(` Package mgr: ${pm ?? 'none detected'}`);
435
+ console.log(` WP root: ${wpRoot ?? 'none (Playground-only)'}`);
436
+ console.log(`\n WP packages (${wpPackages.length}):`);
437
+ wpPackages.forEach(p => console.log(pkg(p)));
438
+ console.log(`\n WP Engine remotes (${wpeRemotes.length}):`);
439
+ wpeRemotes.forEach(r => console.log(` → ${r.name}: ${r.url} (install: ${r.install})`));
440
+ console.log(`\n PHP tooling: PHPCS=${php.hasPhpcs} PHPSTAN=${php.hasPhpstan} Pest=${php.hasPest}`);
441
+ console.log(` JS tooling: Biome=${js.hasBiome} Vitest=${js.hasVitest} Playwright=${js.hasPlaywright}`);
442
+ console.log(` Playground: ${playground.hasPlayground ? `yes (${playground.blueprints.length} blueprints)` : 'no'}`);
443
+ console.log(` SatisPress: ${result.satispress.configured ? result.satispress.url : 'not configured'}`);
444
+ console.log(` WPackagist: ${result.wpackagist ? 'yes' : 'no'}`);
445
+ console.log(` Agent kit: ${result.hasAgentKit ? 'installed' : 'not installed'}`);
446
+ console.log(` Git hooks: ${gitHooks ?? 'none'}`);
447
+
448
+ // GitHub section
449
+ const gh = result.github;
450
+ console.log(`\n GitHub CLI: ${gh.ghInstalled ? `installed (${gh.authenticated ? `✓ ${gh.account}` : '✗ not authenticated'})` : '✗ not installed'}`);
451
+ if (gh.ghInstalled && gh.authenticated && gh.repoOwner) {
452
+ console.log(` GitHub repo: ${gh.repoUrl} (${gh.visibility?.toLowerCase()})`);
453
+ console.log(` Secrets: ${gh.existingSecrets.length} set, ${gh.missingSecrets.length} missing`);
454
+ if (gh.missingSecrets.length > 0) {
455
+ console.log(` Missing: ${gh.missingSecrets.join(', ')}`);
456
+ }
457
+ const mainBranch = gh.defaultBranch ?? 'main';
458
+ const prot = gh.branchProtection[mainBranch];
459
+ console.log(` Branch prot: ${mainBranch}=${prot?.protected ? '✓ protected' : '✗ unprotected'}`);
460
+ } else if (gh.error) {
461
+ console.log(` GitHub: ${gh.error}`);
462
+ }
463
+ console.log();
464
+ } else {
465
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
466
+ }
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env bash
2
+ # package-wp.sh — build and zip WordPress plugins/themes for upload to a live site.
3
+ #
4
+ # Generalized from the wp-agent-os tools/package-plugins.sh pattern.
5
+ # Reads wp-bootstrap.config.json for package list, or auto-detects from detect-structure.mjs.
6
+ #
7
+ # Usage:
8
+ # bash .agents/skills/wp-bootstrap/scripts/package-wp.sh [OPTIONS]
9
+ # bash .agents/skills/wp-bootstrap/scripts/package-wp.sh --package=wpaos
10
+ # bash .agents/skills/wp-bootstrap/scripts/package-wp.sh --out=./release
11
+ #
12
+ # Options:
13
+ # --package=<dir> Package only this directory (default: all WP packages)
14
+ # --out=<dir> Output directory (default: dist-plugins/)
15
+ # --no-build Skip build step before packaging
16
+ # --version=<ver> Override version string in zip filename
17
+ # --dry-run Show what would be packaged without doing it
18
+ #
19
+ # Excludes dev-only files from the zip:
20
+ # bin/ tests/ AGENTS.md .pi/ blueprint.json *.md (except readme.txt)
21
+ # composer.json (dev-only — plugins should have no runtime Composer deps)
22
+ # node_modules/ vendor/ .DS_Store
23
+ #
24
+ # After packaging: upload both zips to wp-admin → Plugins → Add New → Upload.
25
+ # Order: wpaos-blocks (or generated plugin) first, then the companion plugin.
26
+
27
+ set -euo pipefail
28
+ root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
29
+ cd "$root"
30
+
31
+ PACKAGE_FILTER=""
32
+ OUT_DIR="dist-plugins"
33
+ NO_BUILD=false
34
+ DRY_RUN=false
35
+ VERSION_OVERRIDE=""
36
+
37
+ for arg in "$@"; do
38
+ case "$arg" in
39
+ --package=*) PACKAGE_FILTER="${arg#--package=}" ;;
40
+ --out=*) OUT_DIR="${arg#--out=}" ;;
41
+ --no-build) NO_BUILD=true ;;
42
+ --version=*) VERSION_OVERRIDE="${arg#--version=}" ;;
43
+ --dry-run) DRY_RUN=true ;;
44
+ esac
45
+ done
46
+
47
+ # ── Detect WP packages ────────────────────────────────────────────────────────
48
+ DETECT_SCRIPT="$(dirname "$0")/detect-structure.mjs"
49
+ PACKAGES=()
50
+
51
+ if command -v node >/dev/null 2>&1 && [ -f "$DETECT_SCRIPT" ]; then
52
+ while IFS= read -r line; do
53
+ PACKAGES+=("$line")
54
+ done < <(node "$DETECT_SCRIPT" "$root" | node -e "
55
+ const chunks = [];
56
+ process.stdin.on('data', c => chunks.push(c));
57
+ process.stdin.on('end', () => {
58
+ const r = JSON.parse(chunks.join(''));
59
+ r.wpPackages.forEach(p => console.log(p.path));
60
+ });
61
+ " 2>/dev/null)
62
+ else
63
+ # Fallback: look for directories with Plugin Name: or Theme Name: in a PHP file
64
+ while IFS= read -r dir; do
65
+ [ -n "$dir" ] && PACKAGES+=("$dir")
66
+ done < <(find . -maxdepth 2 -name "*.php" -not -path "*/vendor/*" -not -path "*/node_modules/*" \
67
+ -exec grep -l "Plugin Name:\|Theme Name:" {} \; 2>/dev/null \
68
+ | xargs -I{} dirname {} | sort -u | sed 's|^\./||')
69
+ fi
70
+
71
+ [ "${#PACKAGES[@]}" -eq 0 ] && { echo "❌ No WordPress packages found. Run from repo root or specify --package=<dir>."; exit 1; }
72
+
73
+ # Filter if --package specified
74
+ if [ -n "$PACKAGE_FILTER" ]; then
75
+ PACKAGES=("$PACKAGE_FILTER")
76
+ fi
77
+
78
+ # ── Run build step first ──────────────────────────────────────────────────────
79
+ if ! $NO_BUILD && ! $DRY_RUN; then
80
+ echo "▶ Running build before packaging..."
81
+ CONFIG_FILE="$root/wp-bootstrap.config.json"
82
+ if [ -f "$CONFIG_FILE" ] && command -v node >/dev/null 2>&1; then
83
+ BUILD_CMD=$(node -e "process.stdout.write(require('$CONFIG_FILE').buildCommand ?? '')" 2>/dev/null)
84
+ fi
85
+ BUILD_CMD="${BUILD_CMD:-}"
86
+
87
+ if [ -n "$BUILD_CMD" ]; then
88
+ echo " $BUILD_CMD"
89
+ eval "$BUILD_CMD"
90
+ elif [ -f "package.json" ]; then
91
+ if grep -q '"build"' package.json; then
92
+ PKG_MGR="npm"
93
+ [ -f "pnpm-lock.yaml" ] && PKG_MGR="pnpm"
94
+ $PKG_MGR run build 2>/dev/null || echo " ⚠ build step failed — packaging current files"
95
+ fi
96
+ fi
97
+ fi
98
+
99
+ # ── Package each WP plugin/theme ──────────────────────────────────────────────
100
+ [ -d "$OUT_DIR" ] || mkdir -p "$OUT_DIR"
101
+
102
+ EXCLUDE=(
103
+ -x '*/bin/*'
104
+ -x '*/tests/*'
105
+ -x '*/test/*'
106
+ -x '*/AGENTS.md'
107
+ -x '*/.pi/*'
108
+ -x '*/blueprint.json'
109
+ -x '*/.DS_Store'
110
+ -x '*/node_modules/*'
111
+ -x '*/vendor/*'
112
+ -x '*/.git/*'
113
+ -x '*/coverage/*'
114
+ -x '*/.env'
115
+ -x '*/phpunit.xml'
116
+ -x '*/phpcs.xml*'
117
+ -x '*/phpstan.neon*'
118
+ -x '*/biome.json'
119
+ -x '*/tsconfig.json'
120
+ -x '*/vitest.config*'
121
+ -x '*/package-lock.json'
122
+ -x '*/pnpm-lock.yaml'
123
+ )
124
+
125
+ PACKAGED=()
126
+
127
+ for pkg_dir in "${PACKAGES[@]}"; do
128
+ [ ! -d "$root/$pkg_dir" ] && { echo " ⚠ $pkg_dir not found — skipping"; continue; }
129
+
130
+ # Get version from Plugin Name / Theme file
131
+ VERSION="$VERSION_OVERRIDE"
132
+ if [ -z "$VERSION" ]; then
133
+ # Try plugin header
134
+ MAIN_PHP=$(find "$root/$pkg_dir" -maxdepth 1 -name "*.php" | head -1)
135
+ if [ -n "$MAIN_PHP" ]; then
136
+ VERSION=$(grep -i "Version:" "$MAIN_PHP" | head -1 | sed 's/.*Version: *//' | tr -d '[:space:]') || true
137
+ fi
138
+ # Try style.css (theme)
139
+ if [ -z "$VERSION" ] && [ -f "$root/$pkg_dir/style.css" ]; then
140
+ VERSION=$(grep -i "Version:" "$root/$pkg_dir/style.css" | head -1 | sed 's/.*Version: *//' | tr -d '[:space:]') || true
141
+ fi
142
+ # Try package.json
143
+ if [ -z "$VERSION" ] && [ -f "$root/$pkg_dir/package.json" ] && command -v node >/dev/null 2>&1; then
144
+ VERSION=$(node -e "process.stdout.write(require('$root/$pkg_dir/package.json').version ?? '')" 2>/dev/null) || true
145
+ fi
146
+ VERSION="${VERSION:-unknown}"
147
+ fi
148
+
149
+ SLUG="$(basename "$pkg_dir")"
150
+ ZIP_FILE="$OUT_DIR/${SLUG}-${VERSION}.zip"
151
+
152
+ if $DRY_RUN; then
153
+ echo " [dry-run] Would package: $pkg_dir → $ZIP_FILE"
154
+ else
155
+ echo "▶ Packaging $pkg_dir (v$VERSION) → $ZIP_FILE"
156
+ ( cd "$root" && zip -rq "$ZIP_FILE" "$pkg_dir" "${EXCLUDE[@]}" )
157
+ SIZE=$(du -sh "$ZIP_FILE" | cut -f1)
158
+ echo " ✓ $ZIP_FILE ($SIZE)"
159
+ PACKAGED+=("$ZIP_FILE")
160
+ fi
161
+ done
162
+
163
+ echo ""
164
+ if $DRY_RUN; then
165
+ echo "Dry run complete. No files created."
166
+ else
167
+ echo "Packaged ${#PACKAGED[@]} artifact(s) in $OUT_DIR/:"
168
+ ls -lh "$OUT_DIR/"
169
+ echo ""
170
+ echo "Upload order: generated/blocks plugin first, then companion plugin."
171
+ echo "wp-admin → Plugins → Add New → Upload Plugin"
172
+ echo "Or with WP-CLI: wp plugin install <file.zip> --activate"
173
+ fi