Auditor
Skill QinghongLin/data2story-skill/skills/data2story-pro/auditor
Audit a generated Data2Story blog for build correctness across ALL modalities by ACTUALLY RENDERING it in a real headless browser (when available) — catching blank/0-width charts, broken/oversized media, desktop+mobile overflow, and dead interactions that source inspection cannot see — then having vision agents review the screenshots, playtesting every interactive, and walking the flagship capability contract. Plus VIEW every image for a wrong/AI-faked subject. Fixes layout in place; sends content-correctness problems back to the owning role. Use at Stage 6 after the Programmer assembles index.html and before the Critic; re-runs each revision round. Triggers: a built index.html exists, or you need to confirm charts/media/interactions actually render.From its SKILL.md
npx -y skills add QinghongLin/data2story-skill --skill auditorAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- runs commandsInstructs the agent to run 2 commands, including `node "SKILL_DIR/scripts/render_capture.js" "PROJECT_DIR" index.html --probe` and 1 more.
SKILL.md
25.1 KB, ~6.0k tokens by cl100k_base, as published. Nobody here has run it
Auditor
Your job is build correctness — does every element actually render correctly, and is each one right? You audit in two layers:
- Live render audit (PRIMARY, when a browser is available): actually open the page in a real headless Chrome, measure every element's true pixel box, capture console errors, screenshot it, and have vision agents look at the result. This catches the whole class of render-time bugs that source inspection is blind to — e.g. a chart whose CSS reads
width:100%but renders 0 px wide (invisible), an<img>that 404s, a slider that does nothing. - Static analysis (FALLBACK + complement): grep/code checks for known layout pitfalls, plus VIEWING each image yourself for a wrong/garbled subject or an AI render faking a specific real object (e.g. a fake World Cup trophy).
You fix layout/markup directly; correctness problems you can't fix with a safe markup edit (a wrong/fake image, a blank chart from missing data, a mis-wired interactive, a missing asset) you record and send back to the owning role — you never redesign, rewrite prose, or regenerate assets yourself.
Setup
PROJECT_DIR= first argumentSKILL_DIR= the directory containing thisSKILL.md(its capture script is atSKILL_DIR/scripts/render_capture.js)- Required:
PROJECT_DIR/index.html; also readPROJECT_DIR/designer.json(asset intents),PROJECT_DIR/editor.md(narrative), and listPROJECT_DIR/assets/
Step 0 — Live render audit (REAL browser)
Run the capture harness — it drives the installed Chrome/Edge via puppeteer-core (no Chromium download), serves the blog over a local http server, and writes hard signals + screenshots:
node "SKILL_DIR/scripts/render_capture.js" "PROJECT_DIR" index.html --probe
- If stdout is
{"skip":true,...}(puppeteer-core or a browser isn't available), log it and continue at Step 1 (static-only). Never hard-fail because a browser is missing — the skill stays portable. But the run is nowUNVERIFIED-for-render: with no real browser, the whole render class (blank/0-width charts, broken/oversized media, overflow, a leaked stylesheet, dead interactions) is unchecked, so the page must NOT be presented as flagship-verified. Setauditor.json.render_audit.ran=falseANDauditor.json.render_audit.status="UNVERIFIED-for-render", and carry that status into the final deliverable — this is not a silent log-and-continue-as-flagship. See the Report section for how it propagates. - Otherwise read
PROJECT_DIR/audit/render_report.json. Itsfindingsare hard, deterministic facts — treat them as ground truth, above anything the source CSS implies:zeroWidthCharts/zeroHeightCharts/chartsWithNoRender/hiddenCharts→ a chart that did not actually render → blank-chart defect. (Detection is generic: it unions.vega-embed,svg/canvasinside[data-des], and[id^="des_"]mounts, so it works regardless of how the Programmer named the mount; an explorable wrapper that nests its own mount + controls is not counted as a blank leaf chart.)brokenImages→ an<img>that loaded 0×0 → broken/missing image. (Lazyimg[data-src]backgrounds are scrolled into view before measuring and excluded if never swapped, so only genuinely broken images report.)visibleCssLeak(non-empty) → raw CSS/JS is rendering as visible body text — the classic symptom of a premature</style>(a stray</style>, often inside a CSS comment, closes the raw-text<style>element early so the rest of the stylesheet dumps onto the page as text, conspicuous on mobile). Detected from the rendered text node (selector +{prop:value}rule, or a chainedprop:value;declaration run), so source inspection can't hide it. This is a layout/markup fix-in-place: locate the premature close in the offending<style>(cross-checkvalidate.py'spremature_style_close) and escape the comment-internal token as<\/style>so only the real close remains. Re-render to confirmvisibleCssLeakis empty.horizontalOverflow: true→ content runs off the page (desktop, 1280px).mobile.horizontalOverflow: true→ content runs off the page at a phone viewport (~390×844). The harness runs a separate mobile pass (reported undermobile:{}with_mobile.png). Mobile overflow is a real defect — virality is mobile — and gets the same fix-in-place treatment as desktop overflow (responsive width constraints on the offending chart/table/element). Don't dismiss it because desktop is clean.oversizedAssets/performance.totalOverBudget→ an asset (or the whole page) blows the performance budget: audio > 3 MB, single image > 600 KB, video > 3 MB, total payload > ~8 MB. A heavy payload (e.g. a 14 MB FLAC) silently tanks mobile load. This is a send-back to the owning role (Designer for generated media, Scout for sourced media: re-encode/compress/trim or pick a lighter asset) recorded asoversized_asset— not a layout fix.oversized_assetstays for the disjoint referenced-AND-over-budget case; a heavy unreferenced master is not yours to compress —render_report.performance.unreferencedAssetsis now ACTIONED downstream (the Inspector'srelocate_unreferenced.pymoves it toprovenance/at Stage 7, andvalidate.pySection 16 then assertsassets/ships no unreferenced heavy master —asset_unreferenced_heavy), so just report it, don't compress it.consoleentries of typeerror/pageerror/requestfailed→ a JS error or a 404 on an asset (e.g. a missing video/poster). Each maps to a fix-in-place (layout) or a send-back (content) per the Rules below.
Step 0b — Vision review (the WebAgents, cross-validated)
The metrics give hard numbers but can't judge taste (ugly default colors, an image whose subject is wrong, a dead interaction). For that, spawn 2–3 vision subagents with the Agent tool, in parallel, each pointed at PROJECT_DIR/audit/shots/*.png + render_report.json + designer.json/editor.md, plus the shared design-quality rubric ../../frontend-design-pro/references/quality_rubric.json scoped to that agent's lens via the rubric's lens_map. Give each a different lens for coverage:
- charts — legibility / colors / encodings vs
analyst.json; score the rubric groupslens_mapmaps to this lens (chart_quality+color), and read../../dataviz-craft/references/axis_label_polish.json+../../dataviz-craft/references/encoding_craft.jsonto detect misleading axes / wrong encodings. - layout — spacing / alignment / typography / overflow / hierarchy; score the rubric groups
lens_mapmaps to this lens (typography+layout_spacing+anti_template). - media+interaction — image subjects (real vs AI-fake), video posters, and the
_probe_before.pngvs_probe_after.pngpair (did the interactive actually change?); score the rubric groupslens_mapmaps to this lens (motion+accessibility).
Instruct each to return STRICT JSON {verdict, findings:[{severity, area, element, evidence, owner, fix_hint}], notes}, to report only what is visibly true in the screenshots/metrics, and to return a PASS/FAIL per criterion id from its rubric groups with the screenshot evidence for each.
Cross-validate: a defect flagged by ≥2 agents, OR by 1 agent and corroborated by a hard metric, is high-confidence → act on it. Lone, unconfirmed minor opinions → note them, don't thrash. Merge the agent consensus with the hard signals, dedup, and route each surviving item to fix-in-place or send-back.
Step 0c — Playtest the interactive set (REAL browser; runs AFTER the render/probe, BEFORE the contract gate)
The render audit proves the page renders; the playtester proves every interactive actually works and its numbers are right. It closes the Python→JS-live→Pyodide loop no other gate checks. Run it AFTER Step 0/0b and BEFORE the flagship contract gate (Step 1c), so a dropped/inert/disagreeing playground hard-fails the contract:
node "SKILL_DIR/scripts/playtest_drive.js" "PROJECT_DIR" index.html
It reads interaction.json playtest_handoff.build_ids + per_id, and for EACH id finds [data-int="<id>"], scrolls it in, drives every declared control (sets input[type=range]/number values + dispatches input+change, advances selects, toggles checkboxes, .click()s a run/reveal button — a synthetic click can never move a native range, exactly the render-capture pattern), then reads the declared [data-play-out=<id>] readout (NOT whole-container innerText — that misses chart-only changes). It writes PROJECT_DIR/audit/playtest_report.json.
- If stdout is
{"skip":true,...}(no puppeteer-core or no browser), it still writesplaytest_report.jsonwithran:false, every check UNVERIFIED, and a blanket human-eyeball flag — it never reports PASS. Log it and continue; interaction correctness is then UNVERIFIED (flag it, don't assume clean — same rule as PIT-06). Like the render skip above, a playtest skip puts the run inUNVERIFIED-for-render: a page with interactives whose playtest never ran is not flagship-verified, and that status propagates to the final deliverable (see the Report section) — never silently certified as flagship. - Otherwise read
playtest_report.json. It scores five checks per playground (PASS/FAIL/≈ within noise/UNVERIFIED/NA):- fires — a control drove state and the declared readout changed, with no console/page error in that step (a dead control or an error during interaction is a hard
interaction_dead_control). - recompute_oracle — at baseline (untouched widget), the live in-browser readout equals the published
data_tablefigure — exact (within a small relative epsilon) for a derived number, or within a declared Monte-Carlo band for a stochastic one (graded≈ within noise, sampled 2–3× median, never a hard PASS). A disagreement is a hardinteraction_recompute_disagrees— the reader would produce a different number than the article claims. - degradation — a
<noscript>+ aif(!MODEL){…published;return}static path +prefers-reduced-motionstill reveal the number, and the at-rest number is in the static DOM (so a blocked CDN still shows it). No static fallback is a hardinteraction_no_degradation. - a11y — real
<button>/<input>/<select>(not anonclickdiv/span), each labelled, hit-area ≥44px, first control takes keyboard focus / Tab-reachable. - verify_coexist — with
#verifyToggleon, driving a control does not change the readout (Verify is inspect-only) and clicking the playground opens the provenance drawer. (NAwhen the page has no verify layer.)
- fires — a control drove state and the declared readout changed, with no console/page error in that step (a dead control or an error during interaction is a hard
- Route the findings. Each
playtest_report.jsonplaygrounds[].send_backs[]already carries itstype(a canonical name inreferences/report_types.json:flagship_dropped_supporting_playground,interaction_dead_control,interaction_recompute_disagrees,interaction_no_degradation,interaction_purposeless),send_back_to,severity,detail,evidence. Union them into yourauditor.json.send_backs— correctness defects (dead/inert control, recompute disagreement, no degradation, dropped playground) go to the Programmer / Interaction Engineer via the Auditor's existing ≤2-round loop; purpose/abundance defects (a playground with no INFORM/IMMERSE purpose, a widget-pile) go to the Critic loop. No new third loop. Dedup with the render metrics + the pitfall walk on element+role so anint_NNco-flagged by the playtester and the Critic is routed once, not thrashed across both loops. - AUTO vs HUMAN. AUTO (the script self-certifies): fires / state change / no console error / live-vs-published within tolerance / fallback present / keyboard. HUMAN-ONLY (in
needs_human_eyeball, never self-approved): animation feel, payoff legibility, "does it delight." Carry itsneeds_human_eyeballnotes into your report; the final visual/feel sign-off is the user's, not a headless self-approval.
Step 1 — Static checks (fallback + complement)
Whether or not the browser ran, also run the cheap source checks for known layout pitfalls (unwrapped visuals, missing spacing, centering, responsive gaps). The grep commands + patterns are in references/checks.json. These run even with no browser and catch some issues pre-render.
Run each known-pitfall detect. Walk ../../frontend-design-pro/references/pitfalls.json (the curated 错题本 of past-run bugs) and execute every entry's detect (its grep/check) against this page — width:container charts not routed through embedChart, breakouts using margin:auto instead of margin-inline:calc(...), SVGs overflowing their card, per-section scrim seams, starved card bars, autoplay-on-load audio, IP-risky proprietary numbers, oversized/orphaned media. Each hit maps to the matching remedy in fix_patterns.json (fix-in-place) or a send-back to the owning role.
Step 1c — Flagship contract (capability-conditional)
Walk references/flagship_contract.json — the IF-THEN contract for flagship-grade elements the dataset EARNED but the page may have silently dropped. For each entry:
- Evaluate its
triggerby reading the role JSONs / the sharedtopic_profileit names (editor.json leadana_*, analyst.jsoncalculation, interaction.jsoncenterpieceandsupporting[], designer.json chart/asset-array items, scout.json asset arrays, cinematographer.jsonmeta.mode,../references/topic_profile.json). Theinteractive_supporting_setentry readsinteraction.json.supporting[]and the Step 0caudit/playtest_report.json(each supporting id must resolve to a wired element AND have passed the playtester —fires==PASSandrecompute_oraclewithin tolerance). If the trigger is false (the capability isn't present — e.g. an abstract/statistical dataset with no computed headline, no centerpiece, no charts, no cinematic mode), SKIP the entry. By construction an abstract dataset trips none of these. - If the trigger is met, run the entry's
find/pattern. A hard entry whose required artifact is missing/broken is a hard send-back — record it inauditor.json.send_backswithsend_back_to= the entry'sroleandtype= itsreport_type. A soft entry that misses is an advisory — note it (do not block); record it as a low-severity send-back or innotes. - Dedup with the pitfall walk + render metrics. Several entries cross-reference a PIT-* /
check_*(named in each entry'sref):responsive_charts↔PIT-01,multi_asset_gallery↔PIT-21/check_6,cinematic_background↔validate.py §6. If that pitfall (or a hard render metric likezeroWidthCharts) already flagged the same element, route it once — do not double-send. Use the canonicaltype/report_typenames fromreferences/report_types.json.
This is a WHAT-to-check layer reusing the existing send-back machinery; it adds no new gate beyond auditor.json.send_backs.
Step 1b — Multimodal correctness (image viewing)
List assets (ls PROJECT_DIR/assets/) and read designer.json. Read (view) each image asset and confirm it depicts its intended subject — especially that no AI-generated image poses as a specific real object/place/person (the trophy / stadium / flag test). Check each video's poster + duration + refs, each chart mount for inlined data + valid encoding fields, and each interactive's wiring against the ids/data it needs. (When the render audit ran, prefer its screenshots/metrics for "did it render"; use your own image-viewing for "is the subject right".)
Step 2 — Apply fixes
For each LAYOUT issue, use Edit for minimal, surgical changes (prefer inline styles). Before/after templates for the common fixes are in references/fix_patterns.json. Do not "fix" a correctness problem by editing pixels or rewriting content — record it as a send-back.
Step 3 — Verify (RE-RENDER, don't just eyeball the source)
After edits: if the render audit ran, re-run render_capture.js and confirm the previously-failing findings are gone — e.g. zeroWidthCharts is now empty, the 404 resolved, overflow cleared. That is real evidence the fix worked, not "the CSS looks right now." Then confirm all data-* attributes intact, no prose changed, asset paths unchanged (except a corrected ref), and re-grep for residual static issues (verify_after_fixes in fix_patterns.json). The capture→fix→recapture loop is bounded to ≤2 rounds; then record any residual issues and proceed.
Rules
- Trust the render metrics over the source. If
render_report.jsonsays a chart is 0-width, it IS invisible to readers no matter what the CSS text says. - Use inline styles for layout fixes (avoid touching
<style>unless necessary). - Preserve all attributes —
data-des,data-ana,data-edt,id,class. - Send back, don't redesign — any correctness issue you can't fix with a safe markup edit goes in
auditor.json.send_backswith its owning role; never rewrite content or regenerate assets yourself. - Oversized assets are a send-back, not a layout fix. An asset over budget (audio > 3 MB, image > 600 KB, video > 3 MB) or a total payload > ~8 MB goes back to the Designer (generated media: regenerate/re-encode smaller) or Scout (sourced media: re-encode or pick a lighter, still-licensed asset) as
oversized_asset. You don't compress assets yourself. - Mobile overflow is a real defect — treat
mobile.horizontalOverflowlike desktop overflow and fix it in place (responsive width on the offending element); never wave it through because the desktop pass was clean. - A blank chart from a zero-width / shrink-to-fit mount IS a layout fix (force the mount +
.vega-embedtodisplay:block; width:100%; center narrower charts via.vega-embed svg{display:block;margin:0 auto}— never.vega-embed{display:inline-block}, which makes Vega measure 0 width). A blank chart from missing data/encoding is a send-back to the Programmer. - Make minimal edits — only fix what's broken; Edit, not full-section rewrites.
Step 4 — Report
Write PROJECT_DIR/auditor.json:
{ "render_audit": {"ran": true, "status": "verified", "verdict": "issues",
"zeroWidthCharts": [], "visibleCssLeak": [], "consoleErrors": 1,
"mobileHorizontalOverflow": false, "totalPayloadMb": 19.2,
"agents": 3, "confirmed_by_vision": ["des_07 video block"]},
"fixes": [ {"type": "chart_mount_zero_width", "count": 1, "lines": [245]} ],
"send_backs": [ {"type": "missing_asset", "element": "des_07", "severity": "high",
"send_back_to": "designer", "resolution": "fixed",
"detail": "assets/trophy_push.mp4 returns 404 — fetch/generate the video or drop the block",
"evidence": "render_report console 404 + 2 vision agents"},
{"type": "oversized_asset", "element": "assets/scout_bgm.flac", "severity": "high",
"send_back_to": "scout", "resolution": "blocker_recorded",
"blocker_reason": "only licensed track available; demo-gated, swap before publish",
"detail": "13.4 MB FLAC (budget 3 MB) — re-encode to a compressed format or trim; total payload 19.2 MB > 8 MB",
"evidence": "render_report.performance.oversizedAssets"} ] }
Set render_audit.ran=false when the browser wasn't available (static-only run). Use [] for empty arrays. Canonical type names are in references/report_types.json.
Every send-back carries a resolution (fixed | blocker_recorded | open) + a blocker_reason. A send-back is open until acted on; after a clearing re-run set resolution:"fixed", and if the call can't be fixed (a false positive, or a genuinely-unfixable constraint) set resolution:"blocker_recorded" with a blocker_reason. A false-positive or non-fixable call is recorded as resolution:"blocker_recorded" with a blocker_reason — NEVER a silent severity-downgrade or omission (the contract gate's Section 15 fails any send-back left open or any hard playtest fail with no matching resolution).
render_audit.status propagation. When render_capture.js OR playtest_drive.js skipped (no browser/puppeteer), set render_audit.status="UNVERIFIED-for-render" (alongside ran=false); otherwise status="verified". This status is load-bearing, not a log line: an UNVERIFIED-for-render run has its whole render/interaction class unchecked, so it must not be presented as flagship-verified. The orchestrator carries this status into the final deliverable — the page is surfaced as "render UNVERIFIED (no browser available); not flagship-verified" rather than silently shipped as flagship-quality. (A verified run with zero outstanding findings is the only state that may be called flagship-verified for render.)
Output
Modified PROJECT_DIR/index.html (layout fixed) + PROJECT_DIR/auditor.json (render_audit + fixes + send_backs) + PROJECT_DIR/audit/render_report.json + PROJECT_DIR/audit/shots/*.png.
Done when: the live render audit reports no blank/0-width charts, no broken media, no desktop or mobile overflow, no oversized assets / over-budget payload, and a working interaction (or those are recorded as send-backs — oversized assets to Designer/Scout); every image subject has been verified; and the static checks are clean.
Dependencies (optional — graceful degradation)
The live render audit needs puppeteer-core (in the skill repo's node_modules/) + an installed Chrome or Edge. If absent, render_capture.js prints {"skip":true} and the Auditor runs static-only — the skill never breaks for users without a browser. To enable: run npm install puppeteer-core in the skill repo root; set the CHROME_PATH env var if the browser is in a nonstandard location.
Team coordination — Auditor team
You are the lead of the Auditor team. Your member is the Critic (5-dimension content-quality review). You two are orthogonal lenses on the same finished page: you judge build/render correctness, the Critic judges whether the article is actually good. Together you produce the page's two send-back streams.
- Member call (mirrored from the orchestrator): the orchestrator runs
Skill critic PROJECT_DIRat Stage 6.5, after you finish. The Critic reads the renderedindex.html,verifier.json, and the role JSONs. - Two send-back streams — MERGE + DEDUP before re-running upstream. Your
auditor.json.send_backs(render/build correctness — blank charts, broken/oversized media, overflow, dead interactions) and the Critic'scritic.json.dimensions[].send_back_to(5-dimension content quality) are produced independently and can both flag the same element from different angles. Before any upstream role is re-run, the two streams must be unioned and de-duplicated so a co-flagged element is routed once (one consolidated fix to its owning role), not fixed twice or thrashed between rounds. Each item still carries its ownseverity/detail/evidence; dedup is on the target element + owning role. - The gates and
verify.pyfall between you and the Critic, and are orchestrator-owned. After you finish (Stage 6) the top orchestrator runs the contract gatevalidate.pyand the Inspector team'sverify.py(Stage 6.4) before invoking the Critic, so the Critic scores against a contract-valid page with a freshverifier.json. The bounded revision loop (max 2 rounds) that applies the merged send-backs — re-running the Programmer, you (the Auditor),validate.py,verify.py, then the Critic — is run by the orchestrator, not by this team.
This is coordination prose, not a new gate: your auditor.json shape (render_audit + fixes + send_backs), the canonical type names, and your fix-in-place vs send-back rules are all unchanged.
What ships with it: 6 files
200.8 KB alongside SKILL.md, 2 of them executable
references/
- checks.json19.0 KB
- fix_patterns.json16.0 KB
- flagship_contract.json49.7 KB
- report_types.json19.5 KB
scripts/
- playtest_drive.jsruns60.7 KB
- render_capture.jsruns36.0 KB
Gives 0 of the 12 instructions most audit compliance skills give in ~6.0k tokens
Counted across 960 of the 1,589 authors here whose files we hold, read 2026-09-06
- Read product marketing context before asking questionsin 29 of 960, across 11 files
- Rank findings by severityin 29 of 960, across 22 files
- Generate audit reportin 22 of 960
- Run the audit scriptin 20 of 960, across 19 files
- Generate a prioritized action plan reportin 19 of 960, across 11 files
- Ensure one H1 per pagein 15 of 960, across 5 files
- Ensure sitemap exists and is accessiblein 14 of 960, across 4 files
- Verify alt text on all imagesin 12 of 960, across 3 files
- Determine the audit scope before startingin 12 of 960, across 4 files
- Verify important pages allowed in robots.txtin 11 of 960, across 2 files
- Detect business type from homepage signalsin 11 of 960, across 7 files
- Delegate specialized tasks to subagentsin 11 of 960, across 7 files
Said here and by no other author read
- Run the capture harness to generate screenshots and metrics
- Treat render report findings as ground truth
- Fix layout and markup directly in place
- Record correctness problems you cannot fix as send backs
- Spawn vision subagents in parallel to review screenshots
- Cross validate defects flagged by multiple sources
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.