Audit pages from the audit queue. Use AFTER discover-site has completed. Claims pages from the audit queue (not discovery queue), runs compliance checks with automatic pacing, saves results, and keeps running until the audit queue is empty. Every check produces a detailed, well-justified finding.
This skill processes pages from the audit queue (queue/audit/), running compliance checks across viewports with automatic pacing. It claims one page at a time, audits it, writes results, and loops until the queue is empty.
Important: This skill reads from the AUDIT queue, not the discovery queue. Pages reach the audit queue only after discover-site has extracted their links and promoted them.
NEVER call init.sh from this skill. Read the audit directory from the pointer:
AUDIT_DIR=$(bash "${CLAUDE_PLUGIN_ROOT}/scripts/queue.sh" get-audit-dir)
MAX_PAGES=$(python3 -c "import json; print(json.load(open('${AUDIT_DIR}/state.json'))['config']['max_pages'])")
echo "Auditing from: $AUDIT_DIR | Budget: $MAX_PAGES pages"
If get-audit-dir fails, stop and report to the orchestrator — do not create a new directory.
WHILE true:
→ Check pacing
→ Claim next page from audit queue
→ Open page, extract new links
→ Run checks (full or light based on pacing)
→ Save results
→ Mark complete in audit queue
→ Print progress
→ Loop (do NOT stop between pages)
MODE=$(bash "${CLAUDE_PLUGIN_ROOT}/scripts/queue.sh" pacing "<audit_dir>" "<max_pages>")
| Result | Action |
|---|---|
full | Full audit: 3 viewports, all 27 checks |
light | Light audit: desktop only, 7 essential checks |
done | Audit queue empty — tell user to run generate-report |
budget_exhausted | MAX_PAGES reached — tell orchestrator to HTTP-verify remainder |
If done or budget_exhausted, stop and output:
All audit queue pages processed. Run generate-report to create the final report.
URL=$(bash "${CLAUDE_PLUGIN_ROOT}/scripts/queue.sh" claim "<audit_dir>" audit "<agent_id>")
If returns EMPTY, the queue is done — stop.
playwright-cli open "$URL"
playwright-cli eval "await new Promise(r => setTimeout(r, 2000))"
Re-extract links to catch JS-rendered content missed during discovery:
playwright-cli eval "JSON.stringify([...document.querySelectorAll('a[href]')].map(a=>({href:a.href,text:a.textContent.trim()})))"
Why re-extract during audit: Discovery runs playwright-cli eval which may miss links that only appear after full page render (e.g., React hydration, lazy-loaded sections, client-side routing). The 2-second wait in the audit phase gives JS more time to execute. Any new internal links go to the DISCOVERY queue (not audit) so they get properly crawled first:
bash "${CLAUDE_PLUGIN_ROOT}/scripts/queue.sh" add-discovery "<audit_dir>" "<new_url>" <depth> "<current_url>"
Full mode: desktop (1920×1080), tablet (768×1024), mobile (375×812) Light mode: desktop (1920×1080) only
Why three viewports matter: Portugal has high mobile usage (~65% of web traffic). A site that works on desktop but breaks on mobile fails a majority of its users. Tablet testing catches layout breakpoints that neither desktop nor mobile reveal (e.g., navigation that collapses too early or too late). EU accessibility regulations (EAA, in force since June 2025) require content to be usable across device types.
For each viewport:
playwright-cli resize <width> <height>
# Screenshot (ALWAYS use the helper — NEVER use > redirect)
bash "${CLAUDE_PLUGIN_ROOT}/scripts/screenshot.sh" "<target_path>.png"
# Snapshot (text output — > redirect IS correct for snapshots)
playwright-cli snapshot > "<audit_dir>/snapshots/<slug>_<viewport>.md"
Every check must produce a finding with: status (pass/warn/fail), description, evidence, and justification for the rating.
1. Cookie Consent & Tracking (GDPR/ePrivacy)
# Before any consent interaction:
playwright-cli eval "JSON.stringify({cookies: document.cookie, ls: Object.keys(localStorage)})"
playwright-cli network | grep -iE "hubspot|analytics|gtm|facebook|track|pixel|hotjar"
What to check:
Why this is critical: Under Portuguese GDPR enforcement (CNPD), setting tracking cookies before consent is a fineable offense. The CNPD has been increasingly active since 2023, issuing fines for exactly this pattern. A missing cookie banner on a commercial Portuguese website is a near-certain compliance gap.
Justification format: "FAIL: 3 tracking cookies (Google Analytics, HubSpot, Facebook) set before any consent interaction. This violates Art. 5(3) of the ePrivacy Directive as transposed into Portuguese law. Evidence: [screenshot reference]"
2. Meta Tags & SEO
playwright-cli eval "JSON.stringify({title:document.title, desc:document.querySelector('meta[name=description]')?.content, canonical:document.querySelector('link[rel=canonical]')?.href, ogTitle:document.querySelector('meta[property=\"og:title\"]')?.content, ogDesc:document.querySelector('meta[property=\"og:description\"]')?.content, ogImage:document.querySelector('meta[property=\"og:image\"]')?.content, lang:document.documentElement.lang, viewport:document.querySelector('meta[name=viewport]')?.content})"
What to check:
<html lang="pt"> or appropriate variantwidth=device-width (required for mobile rendering)Why this matters: Missing meta tags directly impact SEO visibility. For Portuguese businesses, the lang attribute is especially important because it signals to Google which language results to show the page in. Missing OG tags mean shared links appear as blank cards on social media — a significant UX issue for marketing-driven sites.
Justification format: "WARN: Meta description is 42 characters ('Bem-vindo ao nosso site'). Best practice is 120-160 characters to maximize search result click-through. This description is too short to convey the page's value proposition in search results."
3. Console Errors
playwright-cli console warning
What to check:
Why this matters: Console errors on a production site indicate either broken functionality that affects users or security issues (mixed content). They're also a signal of code quality — a site with zero console errors demonstrates professional development practices.
Justification format: "FAIL: 2 JavaScript errors found. TypeError at main.js:142 breaks the contact form submission handler. Mixed content warning for http://cdn.example.pt/logo.png — loading HTTP resources on HTTPS page creates a security vulnerability and triggers browser warnings."
4. Link Validation
playwright-cli eval "JSON.stringify([...document.querySelectorAll('a[href]')].map(a=>({href:a.href,text:a.textContent.trim(),target:a.target})))"
What to check:
rel="noopener noreferrer" when target="_blank"? (Security best practice)<a href="#"> or <a href=""> (usability issue)Why this matters: Broken links directly impact user experience and SEO. Google penalizes pages with many broken links. For accessibility (WCAG 2.2 AA), links must have descriptive text — "click here" fails SC 2.4.4. External links without noopener on target="_blank" create a security vulnerability (reverse tabnapping).
5. Basic Accessibility
playwright-cli snapshot # Check heading structure, ARIA, labels
playwright-cli eval "JSON.stringify({headings:[...document.querySelectorAll('h1,h2,h3,h4,h5,h6')].map(h=>({tag:h.tagName,text:h.textContent.trim()})), images_no_alt:[...document.querySelectorAll('img:not([alt])')].length, inputs_no_label:[...document.querySelectorAll('input:not([aria-label]):not([id])')].length, lang:document.documentElement.lang})"
What to check:
<html lang="pt"> must be present (WCAG 2.2 SC 3.1.1)Why this matters: The European Accessibility Act (EAA, EU 2019/882) became enforceable in June 2025. Commercial websites serving EU consumers must meet WCAG 2.2 AA. Non-compliance exposes the business to legal action. Portugal's national accessibility body (AMA) has been conducting audits of public and commercial sites.
Justification format: "FAIL: 5 images missing alt text. Under WCAG 2.2 SC 1.1.1 (Non-text Content) and the European Accessibility Act (in force since June 2025), all meaningful images must have descriptive alt text. This is a legal compliance gap for any commercial site serving EU consumers."
6. Desktop Screenshots
Handled in Step 4. Evidence for all other checks.
7. Link Extraction
Handled in Step 3. New links go to discovery queue.
8. Privacy Policy Check
What to check:
Why this matters: GDPR Art. 13-14 require specific information to be provided to data subjects. A missing or incomplete privacy policy is one of the most common GDPR violations cited by the CNPD.
9. Legal Information Check
What to check:
Why this matters: Portuguese Decree-Law 7/2004 (transposing EU E-Commerce Directive) requires all commercial websites to display company identification. Missing NIPC or registered address is a legal violation.
10. Livro de Reclamações Online
What to check:
Why this matters: Portuguese Decree-Law 156/2005 (updated by DL 74/2017) REQUIRES all businesses providing goods or services to Portuguese consumers to display a link to the electronic complaints book. This is a legal obligation, not a best practice. Non-compliance can result in fines from ASAE (food/economic authority).
11. E-Commerce Checks (if applicable)
What to check:
12. Content Quality Score
Score 7 dimensions (1-10 each) with detailed justification:
| Dimension | What it measures | Why it matters |
|---|---|---|
| Originality | Is content unique or generic? | Google's Helpful Content Update penalizes thin, duplicated content. AI-generated text without human editing reads as low-quality. |
| Relevance | Does content match the page's purpose? | A services page with only generic marketing copy fails to serve user intent. |
| Depth | Is the topic covered thoroughly? | Superficial content ("We offer the best services") provides no value and hurts credibility. |
| Accuracy | Are claims factual and verifiable? | Inaccurate claims damage trust and can create legal liability. |
| Readability | Is the text clear and well-structured? | Portuguese web readers scan content — poor structure means content goes unread. |
| Tone | Is the tone appropriate for the audience? | A law firm using casual language, or a startup using overly formal language, creates dissonance. |
| SEO | Are keywords naturally integrated? | Keyword stuffing hurts rankings; missing keywords means no organic traffic. |
Justification format: "Originality: 4/10 — The services page contains generic descriptions that could apply to any agency ('We deliver creative solutions for your brand'). No case studies, specific methodologies, or unique value propositions are mentioned."
13. Spelling & Grammar
Check content in the page's language (Portuguese and/or English). Note: Portuguese spelling follows the 2009 Acordo Ortográfico — both pre- and post-agreement spellings may be valid.
14. Image Quality Check
What to check:
loading="lazy") for below-fold content15. Form Testing
What to check:
16-17. Navigation & Keyboard Accessibility
Tab through the page. Every interactive element must be reachable and show a visible focus indicator. The navigation must work on all viewports.
18-19. Network Tracking & Console Deep Dive
Deeper analysis of third-party scripts, tracking pixels, and error patterns across viewports.
20. Heuristic Evaluation (Nielsen's 10)
Score each heuristic 0-4 with specific evidence:
Calculate weighted average (0–40 total, express as 0–10). Note the weakest heuristic with evidence.
21. Visual Design Scoring
Rate 5 dimensions (1-10 each) with specific evidence from the screenshots:
| Dimension | What to assess |
|---|---|
| Typography | Font choices, hierarchy, readability, line-height, contrast |
| Color & Branding | Consistent palette, brand recognition, sufficient contrast ratios |
| Layout & Spacing | Visual balance, whitespace use, grid consistency, alignment |
| Imagery | Quality, relevance, compression artifacts, professional feel |
| Call-to-Action Clarity | Are CTAs visible, distinguishable, and logically placed? |
Calculate average (overall visual design score). Include a written summary: what visual impression does the site give a first-time visitor?
22. First Impression Test
Simulate a user landing on this page for the first time (5-second rule). Answer all five questions with evidence:
Overall first impression score = average of the 5 ratings.
24. AI Content Detection
Assess whether the page content shows signs of being AI-generated without meaningful human editing. Look for the following signals:
playwright-cli eval "JSON.stringify({h1:document.querySelector('h1')?.textContent?.trim(), bodyText:document.body.innerText.substring(0,3000)})"
AI content signals to check:
Output: Rate AI risk as low / medium / high with a confidence level and specific evidence strings quoted from the page. A high rating means the page likely contains unedited AI output.
Justification format: "AI Risk: HIGH (confidence: 85%) — The services page uses 4 of 7 AI signals. Example: 'We leverage cutting-edge technology to deliver seamless solutions for your business needs.' No specific services, tools, case studies, or client industries are mentioned. The same phrase structure ('We + verb + adjective + noun') repeats 8 times in the first paragraph."
25. Content Coherence Check
Verify that the page's content is internally consistent and matches its stated purpose:
playwright-cli eval "JSON.stringify({title:document.title, h1:document.querySelector('h1')?.textContent?.trim(), metaDesc:document.querySelector('meta[name=description]')?.content, h2s:[...document.querySelectorAll('h2')].map(h=>h.textContent.trim()), ogTitle:document.querySelector('meta[property=\"og:title\"]')?.content})"
What to check:
Output: Pass / Warn / Fail with specific evidence for each sub-check.
Justification format: "FAIL — Title is 'Serviços de Marketing Digital' but H1 is 'Transforme o Seu Negócio'. The H1 gives no indication this is a marketing services page. Meta description says 'Especialistas em SEO, PPC e Social Media' but the page body only mentions 'marketing solutions' with no specific services listed — creating a mismatch between what search engines promise and what users find."
26. Web Vitals & Performance (full mode only)
Measure Core Web Vitals and page load timings. Run this after the page has been open for at least 3 seconds so LCP and CLS have time to finalize:
# Give LCP and CLS time to finalize
playwright-cli eval "await new Promise(r => setTimeout(r, 3000))"
# Collect all performance metrics in one eval
playwright-cli eval "JSON.stringify((() => {
const nav = performance.getEntriesByType('navigation')[0];
const paint = performance.getEntriesByType('paint');
const fcp = paint.find(e => e.name === 'first-contentful-paint');
const lcpEntries = performance.getEntriesByType('largest-contentful-paint');
const lcp = lcpEntries.length ? lcpEntries[lcpEntries.length - 1] : null;
let cls = 0;
performance.getEntriesByType('layout-shift').forEach(e => { if (!e.hadRecentInput) cls += e.value; });
const longTasks = performance.getEntriesByType('longtask');
const tbt = longTasks.reduce((s, t) => s + Math.max(0, t.duration - 50), 0);
const resources = performance.getEntriesByType('resource');
return {
ttfb: nav ? Math.round(nav.responseStart - nav.requestStart) : null,
fcp: fcp ? Math.round(fcp.startTime) : null,
lcp: lcp ? Math.round(lcp.startTime) : null,
cls: Math.round(cls * 1000) / 1000,
tbt: Math.round(tbt),
dcl: nav ? Math.round(nav.domContentLoadedEventEnd - nav.startTime) : null,
load: nav ? Math.round(nav.loadEventEnd - nav.startTime) : null,
lcp_el: lcp ? lcp.element?.tagName : null,
resources: resources.length,
transfer_kb: nav ? Math.round(nav.transferSize / 1024) : null,
slow_resources: resources.filter(r => r.duration > 1000).map(r => ({url: r.name.split('/').pop(), ms: Math.round(r.duration)}))
};
})())"
Core Web Vitals thresholds (Google):
| Metric | Good ✅ | Needs Improvement ⚠️ | Poor ❌ | What it measures |
|---|---|---|---|---|
| TTFB | ≤800ms | ≤1800ms | >1800ms | Server response time |
| FCP | ≤1800ms | ≤3000ms | >3000ms | First visible content |
| LCP | ≤2500ms | ≤4000ms | >4000ms | Largest visible element |
| CLS | ≤0.1 | ≤0.25 | >0.25 | Layout shift stability |
| TBT | ≤200ms | ≤600ms | >600ms | Main thread blocking |
Pass/warn/fail mapping: Good → pass · Needs Improvement → warn · Poor → fail
What to note beyond thresholds:
lcp_el: what element triggered LCP? An IMG is good; DIV with background-image is often a CLS risk. H1 means the hero image may not be loading.slow_resources: any individual resource taking >1000ms is a red flag (unoptimized image, slow third-party script)transfer_kb: total page weight. Over 3MB is a fail for mobile users; over 1MB is a warn.width/height cause CLS — cross-reference with image_quality_checkWhy this matters for Portuguese/EU sites: Google's CWV directly impact search rankings (Core Web Vitals became a ranking signal in 2021). Sites with poor LCP or CLS rank lower in Portuguese search results. The EAA (June 2025) also implies performance obligations — a page that takes 5s to load on mobile fails users with lower-end devices, disproportionately affecting accessibility.
Justification format: "LCP: FAIL (4200ms) — Page takes 4.2 seconds for the largest element (hero IMG) to render. Google's threshold is 2.5s for a 'Good' rating. This directly impacts search ranking. The hero image (1.2MB) is not optimised — consider WebP format and lazy loading for below-fold images. CLS: WARN (0.18) — Layout shifts likely caused by images without explicit width/height attributes (see image_quality_check findings). TBT: FAIL (780ms) — Two third-party scripts (HubSpot, Google Tag Manager) block the main thread for 780ms total, degrading interactivity."
27. Language & Tone Audit
(Handled by the language-tone skill — run separately or as part of the full audit workflow)
28. Secrets Scan
(Handled by the scan-secrets skill — run separately or as part of the full audit workflow)
23. Issue Evidence
For every 🔴 Critical and 🟠 High issue, capture a targeted screenshot of the specific element:
bash "${CLAUDE_PLUGIN_ROOT}/scripts/screenshot.sh" "<audit_dir>/screenshots/${AID}_${SLUG}_issue_<N>.png" "<element_ref>"
Write results to results/page_<slug>.json:
{
"task_id": "page_<slug>",
"url": "<url>",
"audit_mode": "full|light",
"agent_id": "<agent_id>",
"completed_at": "<ISO-8601>",
"viewports_tested": ["desktop", "tablet", "mobile"],
"checks": { ... },
"issues": [ ... ],
"scores": { ... }
}
Every issue MUST include:
severity: 🔴 🟠 🟡 🔵category: which check found itdescription: clear, specific description of the problemjustification: WHY this is an issue — reference the specific regulation, standard, or best practicerecommendation: specific action to fix itevidence_screenshot: filename of the screenshot showing the issuebash "${CLAUDE_PLUGIN_ROOT}/scripts/queue.sh" complete "<audit_dir>" audit "<slug>"
bash "${CLAUDE_PLUGIN_ROOT}/scripts/queue.sh" status "<audit_dir>" both
Then immediately go back to Step 1. Do NOT stop between pages. Do NOT ask the user for permission to continue.
screenshot.sh — NEVER playwright-cli screenshot > file.png. The redirect saves a text log, not a PNG image.