mcpbeat

Auditor

qinghonglin/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.

58k tokens
context cost
the whole folder, loaded on every use
7
files
ships runnable scripts
0
copies elsewhere
how many repositories repackaged it
149
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/QinghongLin/data2story-skill --skill auditor

The instruction itself

15 sections, as written by the author

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 argument
  • SKILL_DIR = the directory containing this SKILL.md (its capture script is at SKILL_DIR/scripts/render_capture.js)
  • Required: PROJECT_DIR/index.html; also read PROJECT_DIR/designer.json (asset intents), PROJECT_DIR/editor.md (narrative), and list PROJECT_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 now UNVERIFIED-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. Set auditor.json.render_audit.ran=false AND auditor.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. Its findings are 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/canvas inside [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. (Lazy img[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 chained prop: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-check validate.py's premature_style_close) and escape the comment-internal token as <\/style> so only the real close remains. Re-render to confirm visibleCssLeak is 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 under mobile:{} 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 as oversized_asset — not a layout fix. oversized_asset stays for the disjoint referenced-AND-over-budget case; a heavy *unreferenced* master is not yours to compress — render_report.performance.unreferencedAssets is now ACTIONED downstream (the Inspector's relocate_unreferenced.py moves it to provenance/ at Stage 7, and validate.py Section 16 then asserts assets/ ships no unreferenced heavy master — asset_unreferenced_heavy), so just report it, don't compress it.
  • console entries of type error / 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 groups lens_map maps to this lens (chart_quality + color), and read ../../dataviz-craft/references/axis_label_polish.json + ../../dataviz-craft/references/encoding_craft.json to detect misleading axes / wrong encodings.
  • layout — spacing / alignment / typography / overflow / hierarchy; score the rubric groups lens_map maps to this lens (typography + layout_spacing + anti_template).
  • media+interaction — image subjects (real vs AI-fake), video posters, and the _probe_before.png vs _probe_after.png pair (did the interactive actually change?); score the rubric groups lens_map maps 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 writes playtest_report.json with ran: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 in UNVERIFIED-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_table figure — 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 hard interaction_recompute_disagrees — the reader would produce a different number than the article claims.
  • degradation — a <noscript> + a if(!MODEL){…published;return} static path + prefers-reduced-motion still 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 hard interaction_no_degradation.
  • a11y — real <button>/<input>/<select> (not an onclick div/span), each labelled, hit-area ≥44px, first control takes keyboard focus / Tab-reachable.
  • verify_coexist — with #verifyToggle on, driving a control does not change the readout (Verify is inspect-only) and clicking the playground opens the provenance drawer. (NA when the page has no verify layer.)
  • Route the findings. Each playtest_report.json playgrounds].send_backs[] already carries its type (a canonical name in [references/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 your auditor.json.send_backscorrectness 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 an int_NN co-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 its needs_human_eyeball notes 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 trigger by reading the role JSONs / the shared topic_profile it names (editor.json lead ana_*, analyst.json calculation, interaction.json centerpiece and supporting[], designer.json chart/asset-array items, scout.json asset arrays, cinematographer.json meta.mode, ../references/topic_profile.json). The interactive_supporting_set entry reads interaction.json.supporting[] and the Step 0c audit/playtest_report.json (each supporting id must resolve to a wired element AND have passed the playtester — fires==PASS and recompute_oracle within 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 in auditor.json.send_backs with send_back_to = the entry's role and type = its report_type. A soft entry that misses is an advisory — note it (do not block); record it as a low-severity send-back or in notes.
  • Dedup with the pitfall walk + render metrics. Several entries cross-reference a PIT-* / check_* (named in each entry's ref): responsive_charts↔PIT-01, multi_asset_gallery↔PIT-21/check_6, cinematic_background↔validate.py §6. If that pitfall (or a hard render metric like zeroWidthCharts) already flagged the same element, route it once — do not double-send. Use the canonical type/report_type names from references/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.json says 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 attributesdata-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_backs with 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.horizontalOverflow like 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-embed to display: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_DIR at Stage 6.5, after you finish. The Critic reads the rendered index.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's critic.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 own severity/detail/evidence; dedup is on the target element + owning role.
  • The gates and verify.py fall between you and the Critic, and are orchestrator-owned. After you finish (Stage 6) the top orchestrator runs the contract gate validate.py and the Inspector team's verify.py (Stage 6.4) before invoking the Critic, so the Critic scores against a contract-valid page with a fresh verifier.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.

How to use it

Copy the folder

Take qinghonglin/auditor from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.

Install what it needs

The instructions reference npm. Without those the skill loads but fails at the first command.