Skip to content
53 changes: 52 additions & 1 deletion apps/cli/ai/skills/plugin-recommendations/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ user-invokable: true

# Plugin Recommendations

Use this skill when the user asks for a feature that core WordPress blocks do not cleanly provide, such as forms, slideshows, related content, business hours, ecommerce, events, LMS/course features, or third-party embeds.
Use this skill when the user asks for a feature that core WordPress blocks do not cleanly provide, such as forms, slideshows, related content, business hours, shops/stores/ecommerce, events, LMS/course features, or third-party embeds.

## Decision Rules

Expand Down Expand Up @@ -36,6 +36,57 @@ wp_cli eval 'foreach (\WP_Block_Type_Registry::get_instance()->get_all_registere
5. Use the registered block in editable block markup.
6. Validate generated block markup with `validate_html_blocks`, then `validate_and_fix_blocks` with `filePath` when the content lives in a file so safe editor fixes are applied automatically.

## WooCommerce Shop Sites

A request for a "shop" or "store" is an ecommerce request by default unless the user explicitly specifies otherwise. Always set up WooCommerce with products whenever the request names a shop, store, or ecommerce site, OR the planned design includes products, product categories or ranges, prices, a catalog, a "Shop" page, or add-to-cart - even if the user does not name WooCommerce and does not explicitly ask for products. A shop with an empty catalog is not useful. Size and scope qualifiers like "small", "simple", or "just a few pages" describe how big the site is, not whether it sells - a small shop is still a shop and still gets WooCommerce.

1. Install and activate WooCommerce:

```text
wp_cli plugin install woocommerce --activate
```

2. Activation automatically creates the Shop, Cart, Checkout, and My Account pages. Suppress the setup-wizard redirect so the storefront is usable right away:

```text
wp_cli option delete _transient__wc_activation_redirect
```

3. Configure store basics. Match the currency and base location to the user's context when known, otherwise use sensible defaults. Also turn off "Coming soon" mode, which fresh WooCommerce installs enable by default:

```text
wp_cli option update woocommerce_coming_soon no
wp_cli option update woocommerce_currency USD
wp_cli option update woocommerce_default_country "US:CA"
```

4. Add products that match what the shop actually sells. Create real, contextual products - coffee products for a coffee shop, books for a bookstore, plants for a plant store - rather than generic placeholders. Only fall back to generic sample products when the shop's niche is genuinely unknown.

Products should ideally have a real, relevant image, and a storefront with product images looks far more complete than one with placeholder thumbnails. **Do not pass remote image URLs via `--images` with `src`.** WooCommerce derives the upload filename from the URL's basename, and the extension-less CDN URLs most image hosts return (e.g. `https://images.unsplash.com/photo-1589924691995-400dc9ecc119?w=600`) are rejected with `Invalid image: Sorry, you are not allowed to upload this file type`. Instead, download each image to the site with a real extension, import it into the media library, then reference the resulting attachment ID. The WooCommerce CLI requires a `--user`:

```text
# Download the image to the site's uploads with a real .jpg/.png/.webp name (use the Bash tool with the site path from site_info):
# curl -L "<image-url>" -o "<site-path>/wp-content/uploads/premium-dog-food.jpg"
wp_cli media import wp-content/uploads/premium-dog-food.jpg --porcelain
wp_cli wc product create --name="Premium Dog Food" --type=simple --regular_price=42 --status=publish --description="High-protein, grain-free kibble made with real chicken." --images='[{"id":<attachmentId>}]' --categories='[{"id":N}]' --user=admin
```

Create several products (aim for 4-8) so the shop and catalog pages look populated. Group related products with a category by passing its term ID via `--categories='[{"id":N}]'` after creating the category:

```text
wp_cli wc product_cat create --name="Single Origin" --user=admin
```

5. Discover the registered WooCommerce blocks so storefront and landing pages use editable plugin blocks rather than raw HTML:

```text
wp_cli eval 'foreach (\WP_Block_Type_Registry::get_instance()->get_all_registered() as $n => $b) if (strpos($n, "woocommerce/") === 0 && (!isset($b->supports["inserter"]) || $b->supports["inserter"] !== false)) echo $n . PHP_EOL;'
```

Use blocks such as `woocommerce/product-collection`, `woocommerce/featured-product`, and `woocommerce/all-products` to surface the catalog.

6. After installing WooCommerce, go back and edit the header template part (`parts/header.html`) to add a mini-cart, unless it already shows one. Add the `woocommerce/mini-cart` block alongside the navigation - it renders a cart icon with a live item count and opens the cart drawer - and add a "Shop" link to the primary navigation.

## Jetpack Forms

When the user asks for a contact form, feedback form, survey, or any other interactive form that collects submissions, use Jetpack Forms - not raw HTML `<form>` elements.
Expand Down
4 changes: 3 additions & 1 deletion apps/cli/ai/skills/site-spec/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ After the user provides the name, use AskUserQuestion for:

Call `site_create` with the provided name and use the layout preference to guide all subsequent design decisions.

State the plan as a short **Site Spec** summary before building, alongside the design direction. The summary MUST include a **Functionality & plugins** line: review the requested features, load the `plugin-recommendations` skill, and list the specific plugins the site needs — e.g. WooCommerce for selling products, Jetpack Forms for a contact form — or "None — core blocks only" when nothing beyond static content is required. Install the listed plugins while building (Workflow Step 4); do not silently hand-build static markup for a feature a plugin should provide.

## After site_create returns

The turn immediately after `site_create` is the biggest source of perceived hangs. Acknowledge the site in ≤2 lines of prose, then make your next tool call a small one — `site_info`, or a single ≤50-line first `Write`. Do NOT scaffold the theme, chain multiple Writes, or write a long design-plan essay in this turn. Grow the build across many small turns (see the "Working cadence" section of the system prompt).

## When to Skip Discovery

Do NOT ask questions if:
Skipping means skipping the interactive questions only — still produce the Site Spec summary (including the Functionality & plugins line) before building. Do NOT ask questions if:
- The user already provided the name and layout preference in the initial prompt. Proceed directly with site creation.
- The user says "just build something" or "surprise me". Pick a bold creative direction yourself and proceed.
- The user explicitly asks to skip the setup or says they don't want questions.
15 changes: 14 additions & 1 deletion apps/cli/ai/skills/visual-polish/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ Use this skill to verify a built or redesigned site and fix the design issues th

The core method is **diagnose from evidence, not from memory**. Do not guess why something looks wrong from the screenshot alone — the rendered DOM usually differs from the markup you wrote. Read the real DOM with `inspect_design`, find the actual cause, then fix it.

## Scope: which pages to polish, and how much

Polish **every page of the site**, not just the home page. This includes all user-created pages (Home, About, Contact, and similar) and any plugin-provided pages. A page the user never sees polished feels unfinished, and plugin pages ship with generic default styling that rarely matches the theme.

For a WooCommerce shop, polish each of these pages: Shop, single-product, Cart, Checkout, and My Account.

How much to iterate depends on the page:

- **Home page** — run the full loop below, including Phase 3 (re-diagnose and fix again, up to the pass cap). The home page is the highest-traffic, highest-impact page and is worth iterating until it is right.
- **Every other page** (other user pages AND WooCommerce pages) — run a **single pass**: diagnose (Phase 1), fix the batch (Phase 2), take one verification screenshot, then move on. Do not loop these pages; a single diagnose-and-fix pass is enough.

## Method: diagnose the whole page first, then fix in one batch

The most important rule: **do not fix issues one at a time as you find them.** Fixing reactively makes you miss related issues, introduce regressions, and burn expensive screenshot passes. Split the work into strict phases.
Expand All @@ -31,7 +42,9 @@ Work through the plan with targeted `Edit` calls (one `Write`/`Edit` per turn, p

### Phase 3 — Verify and loop

After the whole batch, take one `viewport: "all"` screenshot. Check each plan item off and look for regressions the fixes introduced. Each pass is expensive, so cap the cycle at **5 passes**. If issues remain and you are within that budget, return to Phase 1 for what's left — re-diagnose the remaining issues with `inspect_design`, don't fix blind.
After the whole batch, take one `viewport: "all"` screenshot. Check each plan item off and look for regressions the fixes introduced.

This looping phase applies to the **home page only** (see "Scope" above). For every other page — including WooCommerce pages — stop after this single verification screenshot; do not loop. For the home page, each pass is expensive, so cap the cycle at **5 passes**. If issues remain and you are within that budget, return to Phase 1 for what's left — re-diagnose the remaining issues with `inspect_design`, don't fix blind.

## Recurring issues and what to inspect

Expand Down
6 changes: 3 additions & 3 deletions apps/cli/ai/system-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ IMPORTANT: For any generated content for the site, these three principles are ma
For any request that involves a WordPress site, you MUST first determine which site to use:

- **Active site + ambiguous "create" / "build" / "make" / "design a site"**: Ask whether to update the active site or create a separate new site before calling site_create. Use AskUserQuestion when available with options like "Use current site" and "Create new site".
- **Active site + explicit "new" / "separate" / "another" site**: Run the \`site-spec\` skill to gather the site name and layout preference FIRST, then call site_create.
- **No active site + "create" / "build" / "make" a site**: Run the \`site-spec\` skill to gather the site name and layout preference FIRST, then call site_create.
- **Active site + explicit "new" / "separate" / "another" site**: Always run the \`site-spec\` skill FIRST, then call site_create. Run it even when the prompt already provides the name and layout — in that case skip the discovery questions but still produce the site spec.
- **No active site + "create" / "build" / "make" a site**: Always run the \`site-spec\` skill FIRST, then call site_create. Run it even when the prompt already provides the name and layout — in that case skip the discovery questions but still produce the site spec.
- **"Redesign" / "update" / "change this site"**: Reuse the active site.
- **User names a specific existing site**: Call site_list to find it.
- **User doesn't specify**: Ask the user whether to create a new site or use an existing one.
Expand Down Expand Up @@ -221,4 +221,4 @@ For any page/post content, template or template-part content, block markup, bloc

For verifying and polishing a built or redesigned site — checking the rendered result against intent and diagnosing layout/width, spacing, button, background, or hover issues — load the \`visual-polish\` skill and use \`inspect_design\` to root-cause from the rendered DOM before fixing.

For forms, ecommerce, events, LMS, galleries/slideshows, embeds, SEO/performance plugin choices, or any feature that core WordPress blocks do not cleanly provide, load the \`plugin-recommendations\` skill before installing plugins or writing plugin-provided block markup.`;
For forms, shops/stores/ecommerce, events, LMS, galleries/slideshows, embeds, SEO/performance plugin choices, or any feature that core WordPress blocks do not cleanly provide, load the \`plugin-recommendations\` skill before installing plugins or writing plugin-provided block markup.`;
38 changes: 38 additions & 0 deletions eval/promptfoo.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -338,3 +338,41 @@ tests:
: `no jetpack/* block in any Write tool call; sample: ${(writtenContents[0] || '').slice(0, 300)}`,
};
});

# Regression: a "shop"/"store" request must set up WooCommerce WITH products,
# even when framed with scope/style qualifiers ("small", "for inspiration")
Comment on lines +342 to +343

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💖

# that the agent previously used to misclassify the site as informational and
# skip ecommerce. Guards both the proactive plugin-recommendations trigger and
# the ecommerce-by-default classification.
- description: shop request sets up WooCommerce with products
vars:
caseId: woocommerce-shop-setup
timeoutMs: 600000
askUserPolicy: allow_all
prompt: |
First check if a site named "Eval Shop Site" exists using site_list.
If it does, delete it with site_delete so we start from a clean
slate. Do NOT touch any other site.

Then build a small pet shop site named "Eval Shop Site" — keep it
small, just a couple of pages, look at a clean modern store for
inspiration. I want to sell dog and cat products.
assert:
- type: javascript
value: |
return import('node:fs').then(({ readFileSync }) => {
const marker = output.split(/\r?\n/).map(l => l.trim()).find(l => l.startsWith('EVAL_RUNNER_RESULT_FILE='));
if (!marker) return { pass: false, score: 0, reason: `no result-file marker on stdout; got: ${output.slice(0, 200)}` };
const d = JSON.parse(readFileSync(marker.slice('EVAL_RUNNER_RESULT_FILE='.length), 'utf8'));
const installed = (d.toolCalls || []).some(t => t.name === 'wp_cli' && /plugin\s+install\s+woocommerce/.test(t.input?.command || ''));
return { pass: installed, score: installed ? 1 : 0, reason: installed ? 'WooCommerce installed' : `no woocommerce install; wp_cli cmds: ${JSON.stringify((d.toolCalls || []).filter(t => t.name === 'wp_cli').map(t => t.input?.command))}` };
});
- type: javascript
value: |
return import('node:fs').then(({ readFileSync }) => {
const marker = output.split(/\r?\n/).map(l => l.trim()).find(l => l.startsWith('EVAL_RUNNER_RESULT_FILE='));
if (!marker) return { pass: false, score: 0, reason: `no result-file marker on stdout; got: ${output.slice(0, 200)}` };
const d = JSON.parse(readFileSync(marker.slice('EVAL_RUNNER_RESULT_FILE='.length), 'utf8'));
const n = (d.toolCalls || []).filter(t => t.name === 'wp_cli' && /wc\s+product\s+create/.test(t.input?.command || '')).length;
return { pass: n >= 1, score: n >= 1 ? 1 : 0, reason: n >= 1 ? `${n} product(s) created` : 'no `wc product create` calls' };
});