build-theme
Select, install, and activate an FSE block theme from WP.org based on a natural language site description - queries API with full-site-editing tag, evaluates by relevance/popularity/rating, falls back to curated list
No fetch-and-execute, destructive, credential, exfiltration or rules-override patterns in 1 files, checked on Oct 20, 2026. This is not a guarantee of safety. How the scan works.
npx degit Shaisolaris/solaris-dev-shop/employees/specialized/wordpress-master/diagnostics/build-theme .claude/skills/build-theme12 alternatives · Best design skills · Author? Prove this skill
SKILL.md
# Build Theme Skill
Selects, installs, and activates a Full Site Editing (FSE) block theme from WP.org based on the user's natural language site description. This skill queries the WP.org Themes API v1.2 for FSE themes, evaluates results against the NL prompt using the coding agent's in-context judgment, and installs the best match. A curated fallback list provides guaranteed theme selection when the API returns poor results or is unavailable.
**Critical sequencing:** This skill runs AFTER `build-mcp` Section 2 (MCP adapter activated, DB re-exported) and BEFORE `build-content` (content seeding requires the theme to be active for menu location discovery).
This skill expects the following variables to already be set by the calling command:
- `BUILD_DIR` - absolute path to the build directory (set by build-scaffold Section 2)
- `WP` - the WP-CLI command prefix (e.g. `wp --path=$BUILD_DIR` or the Docker equivalent, set by build-scaffold Section 4)
- `NL_PROMPT` - the user's natural language site description string
- `SITE_TITLE` - site title derived from the NL prompt (set by the calling command before this skill)
## Section 1: WP.org Themes API Query
Query the WP.org Themes API v1.2 for the FSE block theme pool. Use the `full-site-editing` tag to filter - this is the canonical public signal for FSE/block themes. Do NOT combine `search` and `tag` parameters: the API ignores tags when `search` is present. the coding agent evaluates theme relevance in-context after fetching the pool.
```bash
echo "[Build] Querying WP.org Themes API for FSE theme pool..."
# CRITICAL anti-pattern: Do NOT use ?action=query_themes&request[search]=...&request[tag][]=full-site-editing
# The API ignores tag[] when search is present. Use tag-only query; the coding agent evaluates relevance in-context.
FSE_THEMES_FILE="/tmp/fse_themes_$$.json"
curl -s --max-time 10 \
"https://api.wordpress.org/themes/info/1.2/?action=query_themes&request[tag][]=full-site-editing&request[browse]=popular&request[per_page]=50&request[fields][active_installs]=true&request[fields][rating]=true&request[fields][description]=true&request[fields][tags]=true" \
-o "$FSE_THEMES_FILE"
CURL_EXIT=$?
# Validate response - check exit code, non-empty file, and JSON structure
API_AVAILABLE=false
if [ $CURL_EXIT -eq 0 ] && [ -s "$FSE_THEMES_FILE" ]; then
# Count themes in response - need at least 3 for the coding agent to evaluate properly
THEME_COUNT=$(python3 -c "import json,sys; d=json.load(open(sys.argv[1])); print(len(d.get('themes', [])))" "$FSE_THEMES_FILE" 2>/dev/null || echo "0")
if [ "$THEME_COUNT" -ge 3 ]; then
API_AVAILABLE=true
echo "[Build] WP.org API returned $THEME_COUNT FSE themes for evaluation."
else
echo "[Build] WP.org API returned fewer than 3 themes ($THEME_COUNT). Falling back to curated list."
fi
else
echo "[Build] WP.org API unavailable - using curated theme list"
fi
```
## Section 2: Theme Evaluation and Selection
the coding agent reads the API JSON response and evaluates each theme against the `NL_PROMPT`. If the API returned a viable pool, the coding agent selects the best match using relevance, popularity, and rating. If the API failed or returned too few results, the coding agent selects from the curated fallback list based on the site category in `NL_PROMPT`.
**Scoring criteria (in priority order):**
1. Name, description, and tag relevance to the site type described in `NL_PROMPT`
2. `active_installs` - popularity signal (higher = better)
3. `rating` - quality signal (higher = better)
**Classic themes are never selected**, even if they would score higher on other metrics.
```bash
if [ "$API_AVAILABLE" = "true" ]; then
echo "[Build] Evaluating FSE themes against NL prompt: \"$NL_PROMPT\""
echo "[Build] Reading themes from: $FSE_THEMES_FILE"
# the coding agent reads the FSE themes JSON and evaluates each theme:
# - themes[].name: display name
# - themes[].slug: WP.org install slug
# - themes[].description: theme description
# - themes[].tags: theme feature/category tags
# - themes[].active_installs: popularity (number of active installations)
# - themes[].rating: quality rating (0-100 scale)
#
# the coding agent selects the theme with the best combination of:
# (1) relevance to NL_PROMPT site type
# (2) high active_installs
# (3) high rating
#
# No-match behavior: If no theme scores well against the NL_PROMPT site type,
# the coding agent presents the top 3 candidates to the user and asks them to choose.
# This breaks autonomous flow intentionally - satisfaction > automation.
#
# After selection, the coding agent sets:
THEME_SLUG="<slug selected by the coding agent from API results>"
THEME_NAME="<display name selected by the coding agent from API results>"
else
# Curated fallback list - maps site categories to known FSE-compatible theme slugs
# These themes are verified FSE block themes maintained on WP.org.
# Update this list if a theme is removed from WP.org.
#
# Category → Slug mappings (the coding agent extracts site category from NL_PROMPT):
#
# portfolio / photography / creative → flavor
# business / corporate / agency → flavor
# blog / magazine / news → flavor
# restaurant / food / cafe → flavor
# ecommerce / shop / store → flavor
# default (any other) → twentytwentyfour
#
# twentytwentyfour is the guaranteed last-resort fallback (maintained by WordPress.org core team).
#
# the coding agent reads NL_PROMPT, determines the site category, and sets:
THEME_SLUG="<slug selected by the coding agent from curated fallback list>"
THEME_NAME="<display name for the selected curated theme>"
fi
echo "[Build] Theme selected: $THEME_NAME ($THEME_SLUG)"
```
## Section 3: Theme Installation and Activation
Install and activate the selected theme using WP-CLI. If installation fails, attempt fallback candidates. Always validate FSE compatibility by checking for `theme.json` post-install. Use warn-and-continue - never abort the build for theme failures.
```bash
echo "[Build] Installing theme: $THEME_SLUG..."
install_theme() {
local slug="$1"
if $WP theme install "$slug" --activate 2>&1; then
echo "[Build] Theme installed and activated: $slug"
# FSE validation - check for theme.json presence
# the coding agent selected from the FSE pool, so this is a secondary confirmation.
# false negative possible if theme.json is in a subdirectory or named differently.
if [ -f "$BUILD_DIR/wp-content/themes/$slug/theme.json" ]; then
echo "[Build] FSE validated: theme.json found."
else
echo "[Build] WARNING: theme.json not found - theme may not be FSE-compatible."
echo "[Build] Proceeding - the coding agent selected this theme from the full-site-editing tag pool."
fi
# Get theme version
THEME_VERSION=$($WP theme get "$slug" --field=version 2>/dev/null || \
grep -m 1 "^Version:" "$BUILD_DIR/wp-content/themes/$slug/style.css" 2>/dev/null | \
sed 's/Version: //' | tr -d '[:space:]' || \
echo "unknown")
THEME_INSTALLED=true
echo "[Build] Theme installed and activated: $slug (v$THEME_VERSION)"
return 0
else
echo "[Build] WARNING: Theme installation failed for slug: $slug"
THEME_INSTALLED=false
return 1
fi
}
# Attempt 1: Install API-selected or curated theme
if ! install_theme "$THEME_SLUG"; then
# Attempt 2: If API-selected theme failed, try curated fallback
if [ "$API_AVAILABLE" = "true" ]; then
echo "[Build] Trying curated fallback theme for site category..."
# the coding agent determines the curated fallback slug from NL_PROMPT site category
# (see Section 2 curated list) and retries
FALLBACK_SLUG="twentytwentyfour" # the coding agent replaces this with category-specific slug if applicable
if ! install_theme "$FALLBACK_SLUG"; then
# Attempt 3: Last resort - twentytwentyfour (maintained by WordPress.org)
if [ "$FALLBACK_SLUG" != "twentytwentyfour" ]; then
echo "[Build] Trying last-resort fallback: twentytwentyfour..."
if ! install_theme "twentytwentyfour"; then
# Final fallback - log warning and continue with WP default theme
echo "[Build] WARNING: All theme installation attempts failed."
echo "[Build] WARNING: Build continues with WordPress default theme."
THEME_SLUG="default"
THEME_NAME="WordPress Default"
THEME_VERSION="unknown"
THEME_INSTALLED=false
fi
else
# twentytwentyfour already failed - continue with default
echo "[Build] WARNING: All theme installation attempts failed."
echo "[Build] WARNING: Build continues with WordPress default theme."
THEME_SLUG="default"
THEME_NAME="WordPress Default"
THEME_VERSION="unknown"
THEME_INSTALLED=false
fi
fi
else
# Curated theme failed - try twentytwentyfour as last resort
if [ "$THEME_SLUG" != "twentytwentyfour" ]; then
echo "[Build] Trying last-resort fallback: twentytwentyfour..."
if ! install_theme "twentytwentyfour"; then
echo "[Build] WARNING: All theme installation attempts failed."
echo "[Build] WARNING: Build continues with WordPress default theme."
THEME_SLUG="default"
THEME_NAME="WordPress Default"
THEME_VERSION="unknown"
THEME_INSTALLED=false
fi
else
echo "[Build] WARNING: All theme installation attempts failed."
echo "[Build] WARNING: Build continues with WordPress default theme."
THEME_SLUG="default"
THEME_NAME="WordPress Default"
THEME_VERSION="unknown"
THEME_INSTALLED=false
fi
fi
fi
# Clean up temp API response file
rm -f "$FSE_THEMES_FILE"
```
## Section 4: Site Title Update
Update the WordPress site title and tagline to match the site described in `NL_PROMPT`. The site title was already set during `build-scaffold` WP install (`--title="$SITE_TITLE"`), but this step allows the tagline to be set with a contextual description generated from the NL prompt.
```bash
echo "[Build] Setting site title and tagline..."
# Update site title (may already match from build-scaffold --title, but update to confirm)
$WP option update blogname "$SITE_TITLE" 2>&1
# the coding agent generates a brief tagline (one line, no quotes) from NL_PROMPT
# Examples:
# NL_PROMPT: "a portfolio site for a freelance photographer"
# → Tagline: "Capturing moments that matter"
#
# NL_PROMPT: "a restaurant website for an Italian bistro in Sydney"
# → Tagline: "Authentic Italian flavours in the heart of Sydney"
#
# the coding agent sets SITE_TAGLINE to the generated tagline, then:
SITE_TAGLINE="<the coding agent-generated tagline from NL_PROMPT>"
$WP option update blogdescription "$SITE_TAGLINE" 2>&1
echo "[Build] Site title set: $SITE_TITLE"
echo "[Build] Site tagline set: $SITE_TAGLINE"
```
## Implementation Notes
**Pipeline position:** This skill runs in the NL build pipeline strictly AFTER `build-mcp` Section 2 (MCP adapter activated, database re-exported) and BEFORE `build-content`. Theme must be active before content seeding because `build-content` uses `$WP menu location list` to discover the active theme's navigation locations - this requires the target theme to already be installed and active.
**Docker MySQL container:** The ephemeral Docker MySQL container from `build-scaffold` Section 3 must still be running when this skill executes. WP-CLI theme installation writes to the WordPress options table (active theme slug). The EXIT trap set in `build-scaffold` Section 3 remains active for the entire build session and fires only when the full command exits - not between skill invocations.
**Output variables:** The following variables are set by this skill and consumed by downstream skills (`build-content`, `build-setup`):
- `THEME_SLUG` - installed theme slug (e.g. `twentytwentyfour`, `flavor`)
- `THEME_NAME` - display name (e.g. `Twenty Twenty-Four`, `Flavor`)
- `THEME_VERSION` - installed version string (e.g. `1.3`, `unknown` if undetectable)
- `THEME_INSTALLED` - `true` if installation succeeded, `false` if all fallbacks failed
**Curated fallback list maintenance:** The curated list maps site categories to known FSE block themes on WP.org. If a curated theme slug is removed from WP.org (causing `wp theme install` to fail with "not found"), update this list with a current FSE-compatible replacement. `twentytwentyfour` is the guaranteed last-resort fallback - it is maintained by the WordPress.org core team and will not be removed.
**No post-install customization (locked decision):** Per user decision captured in CONTEXT.md, no theme.json overrides, child themes, or Global Styles modifications are applied after installation. The theme is installed and activated as-is from WP.org. Its built-in design stands as delivered.
**FSE validation reliability:** The `theme.json` check is a secondary confirmation - the coding agent already filtered themes by the `full-site-editing` tag in the API query. A "WARNING: theme.json not found" message does not indicate an error: the theme may store `theme.json` at a non-standard location, or the WP.org tag may have been set correctly but the file path check failed. Build continues regardless.
**References:**
- @references/wp-block-themes/SKILL.md - FSE theme structure, theme.json schema, block template patterns
- @references/wp-wpcli-and-ops/SKILL.md - WP-CLI theme command reference, --activate flag, --field outputShown under the source repo’s MIT license. Read the full folder on GitHub.