WP Audit Standards
This skill defines the audit criteria, severity levels, report format, and quality thresholds used by all wp-audit agents.
Severity Levels
| Severity | Meaning | Action |
|---|---|---|
| CRITICAL | Security vulnerability, complete accessibility failure, broken core functionality | Fix immediately |
| WARNING | Best-practice violation, degraded UX, performance issue | Fix before delivery |
| INFO | Optimization opportunity, minor improvement | Fix when convenient |
Report JSON Schema
All audit agents MUST output findings in this format:
{
"category": "security|seo|a11y|performance|best-practices",
"tier": 1,
"issues": [
{
"severity": "critical|warning|info",
"code": "SEC-001",
"message": "Human-readable description",
"file": "path/to/file.php",
"line": 42,
"auto_fixable": true,
"fix_method": "Description of how to fix"
}
],
"summary": {
"total": 0,
"critical": 0,
"warning": 0,
"info": 0,
"auto_fixable": 0
}
}
Field Requirements
category— one of:security,seo,a11y,performance,best-practicestier— integer 1–3 indicating which audit tier produced the findingissues— array of issue objects (may be empty)severity— one of:critical,warning,infocode— prefixed issue code (see Issue Code Prefixes below)message— human-readable description of the problemfile— relative path to the affected fileline— line number (0 if not applicable)auto_fixable— boolean indicating whether the agent can safely fix thisfix_method— description of the fix approachsummary— counts aggregated from the issues array
Issue Code Prefixes
| Prefix | Domain |
|---|---|
SEC-xxx |
Security |
SEO-xxx |
SEO |
A11Y-xxx |
Accessibility |
PERF-xxx |
Performance |
WP-xxx |
Best Practices |
Audit Tiers
Tier 1 — Code-only (always available)
File scanning via Read, Grep, Glob. No runtime environment required.
Tier 2 — + WP-CLI runtime (when .wp-create.json exists)
Plugin management, option reading, database queries. Requires a working WordPress installation with WP-CLI access.
Tier 3 — + External skills (when web-quality-skills installed)
Lighthouse-style browser audits. Requires the web-quality-skills package for browser-based testing.
Performance Budgets
| Resource | Budget |
|---|---|
| CSS compressed | <100KB |
| JS compressed | <300KB |
| Total page weight | <1.5MB |
| Images above-fold | <500KB |
| Fonts total | <100KB |
Accessibility Thresholds
| Requirement | Minimum |
|---|---|
| Normal text contrast | 4.5:1 |
| Large text contrast (>=18px or >=14px bold) | 3:1 |
| UI component contrast | 3:1 |
| Touch target size | 44x44 CSS pixels |
| Minimum gray on white passing 4.5:1 | #767676 |
Core Web Vitals Targets
| Metric | Good | Needs Work | Poor |
|---|---|---|---|
| LCP | ≤2.5s | 2.5s–4s | >4s |
| INP | ≤200ms | 200ms–500ms | >500ms |
| CLS | ≤0.1 | 0.1–0.25 | >0.25 |
Performance — Hard-won Lessons (Tier 3 / Lighthouse)
Interpretation traps and fixes that actually move production bytes. Consult before filing or fixing a PERF-xxx finding from a Lighthouse run.
Read observed metrics before chasing a bad simulated score
Lighthouse's default (throttlingMethod: "simulate") reports Lantern-simulated LCP/FCP/TTI on a modeled slow-4G + 4× CPU. On a dev server this is dominated by local TTFB + the throttle model and can read 6–7 s while the page is actually instant. Before treating a high LCP as real, open the full JSON and compare:
audits.metrics.details.items[0].largestContentfulPaint(simulated) vs...observedLargestContentfulPaint(real paint).- If observed is ~200–900 ms and simulated is multi-second, the number is a simulation/dev-server artifact — the score barely moves regardless of theme changes, and production (with page cache + real CDN/TTFB) differs. Say so in the finding instead of burning effort chasing it. Real byte reductions (WebP, right-sizing) still help production and still shrink the LCP resource, so do those — just set score expectations.
image-delivery-insight,render-blocking-insight,lcp-discovery-insightetc. are weight-0 in the Performance score (informative). Only the 5 metric audits (FCP/LCP/TBT/CLS/SI) carry weight. Fixing a weight-0 insight is a production win, not a score win — label it accordingly.
WebP: image_editor_output_format only covers NEW, attachment-pipeline images
add_filter('image_editor_output_format', ...) converts uploads to WebP only on new uploads, and only images that flow through WP's attachment functions (wp_get_attachment_image, the_post_thumbnail). Themes that print raw SCF/ACF field URLs (echo $field['url']) or CSS background-image: url(<field>) bypass it entirely, so existing hero/about/neighborhood/banner images stay JPEG/PNG.
To serve WebP for existing + SCF-driven images without touching every template:
- Pre-generate
.webpsiblings for existing uploads (one-time):find uploads -iname '*.jpg' -o -iname '*.png'→magick "$f" -quality 82 "${f%.*}.webp"(hero/LCP images can go lower, ~q68, since they sit behind scrims or are video-replaced). - Add a front-end output-buffer that rewrites finished HTML — covers
src,srcset, and inlinebackground-imagein one pass:
add_action( 'template_redirect', function () {
if ( is_admin() || is_feed() || is_robots() ) return;
$u = wp_get_upload_dir(); $base = $u['baseurl']; $dir = $u['basedir'];
ob_start( function ( $html ) use ( $base, $dir ) {
$pat = '#' . preg_quote( $base, '#' ) . '/[^"'\)\s]+?\.(?:jpe?g|png)#i';
return preg_replace_callback( $pat, function ( $m ) use ( $base, $dir ) {
$webp = preg_replace( '/\.(?:jpe?g|png)$/i', '.webp', $m[0] );
return file_exists( $dir . substr( $webp, strlen( $base ) ) ) ? $webp : $m[0];
}, $html );
} );
} );
template_redirect is front-end-only, and with a page cache (WP Super Cache) the buffer runs once per cache build. WebP is universally supported by target browsers — matching the theme's existing unconditional image_editor_output_format policy — so no Accept-header branching is needed.
Right-size — never print $field['url'] for a fixed slot
Lighthouse "responsive-size" waste = serving a 2200px original in a 400px slot. Raw SCF field URLs ($image['url']) always emit the full original. Use the attachment ID so the browser gets a srcset:
echo wp_get_attachment_image( (int) $image['id'], 'large', false, array(
'class' => 'about__bg', 'alt' => $heading, 'loading' => 'lazy',
'sizes' => '(max-width: 899px) 100vw, 136vw', // match the real CSS display width
) );
Pick sizes from the element's actual rendered width (full-bleed → 100vw; a 136%-wide bg → 136vw; a 136px logo → 136px). This composes with the WebP buffer above (the srcset .jpg URLs are rewritten to .webp).
Hero LCP pattern (poster-first, video deferred)
Make the LCP element a small poster image, not the video: <img fetchpriority="high" width/height> + a <link rel="preload" as="image"> for it in wp_head; lazy-load the background <video> via JS on desktop only (min-width:1024px), after the poster paints (requestIdleCallback), and never on mobile / navigator.connection.saveData. Preload the poster's WebP so the preload and the rendered src match (else the preload is wasted).
Render-blocking CSS
Dequeue the near-empty theme style.css on the front end (it usually carries only the WP header comment; runtime styles live in the compiled bundle). Async-load below-the-fold plugin CSS (e.g. Contact Form 7) via style_loader_tag → media='print'>.
AIOS × Lighthouse gotcha (Tier 2 ↔ Tier 3 interaction)
If the security agent sets AIOS aiowps_disallow_unauthorized_rest_requests = 1, it returns 403 on CF7's REST endpoints (/wp-json/contact-form-7/v1/...). Lighthouse then hangs up to 45 s on those pending requests and logs console errors → Best-Practices drops (often 100 → 96) and metrics inflate. If a CF7 form is present, leave that AIOS setting off (or whitelist the CF7 REST namespace). Symptom in the JSON: errors-in-console / pending Fetch requests to contact-form-7 with statusCode: 403.
Auto-fixable
Agent can safely fix without risk of breaking the site:
- Add missing attributes (e.g.,
alt,aria-label,loading) - Append CSS rules
- Set wp-config constants
- Install/configure plugins via WP-CLI
- Add missing escaping wrappers
- Add missing nonce checks to simple forms
Manual-only
Requires human judgment:
- Restructuring templates
- Changing application logic
- Removing code
- Design decisions
- Complex refactoring
Deduplication Rules
When multiple agents find the same issue:
- Security agent owns vulnerability-class checks (XSS, SQLi, CSRF)
- Practices agent owns coding-standards checks (escaping for theme review compliance)
- SEO agent owns heading hierarchy for search ranking context
- A11y agent owns heading hierarchy for screen reader navigation context
- Command deduplicates identical file:line findings, keeping the highest severity
Agent Interaction Model
All plugin interaction via WP-CLI options/meta, never PHP APIs:
| Operation | Command |
|---|---|
| Read plugin config | $WP option get <option_name> |
| Write plugin config | $WP option update/patch |
| Seed data | $WP post meta update |
| Complex operations | $WP eval "php code" |
| Install plugins | $WP plugin install --activate |
| Theme modifications | Direct file Edit/Write |
| wp-config changes | $WP config set |