content-audit
Audit prose against content-design.md for AI-writing tells, voice violations, and register mismatches. Use when asked to audit copy, check content quality, review prose for AI slop, or check writing a
SKILL.md.claude/skills/content-audit/SKILL.md
GitHubcontent-audit
Audit prose against content-design.md, and report violations with suggested rewrites.
When invoked
Use this skill when asked to audit copy, check prose quality, find AI-writing tells, or review text against the content guide — phrases like "content audit", "audit the copy on the homepage", "does this read like AI", "check this against content-design.md".
Instructions
Determine scope. Accept one of:
- A specific file path (a page, a data file, a markdown doc)
case-studies→ thetitleanddekfields inwebsite/src/data/case-studies.json(shipped copy on /work and the home page), plus the TLDR points inwebsite/src/data/case-study-tldrs.ts(the key-claims block atop each /work study — content-design.md's register row owns its rules;validate-shipped-prose.mjsbuild-covers the em dash, register, voice and banned words stay this scope's)corpus-prose→ the hand-written connective paragraphs insidescripts/generate-site-corpus.mjs(the chat model can repeat any of them verbatim to a visitor — its register row holds them to the page-prose bar; the corpus validators screen for leaks, never for voice, so the judgement rules stay this scope's. Fix the generator's strings, never the generated file)readme→README.mdprose. The repo is public, so this is the first thing a stranger readsskills→ thedisplayDescriptionfrontmatter strings and theinvokephrase lists across.claude/skills/(no page renders them any more; hold them to the guide anyway, since the frontmatter is still build-scanned for em dashes; skill instruction bodies are out of scope)website→ user-visible strings inwebsite/src/app/**page files; a page slug (e.g.about) scopes to that page folderchat→ the site chat's shipped prose: the starter pools inwebsite/src/components/SiteChat/starters.tsand the written follow-up fallbacks inwebsite/src/lib/chat-followups.ts(chip length is build-covered byscripts/validate-chat-starters.mjs; register, voice and banned words stay this scope's), the welcome tagline and the disclaimer line inline inwebsite/src/components/SiteChat/SiteChatMount.tsx(the time-of-day greeting and the locked-model line are the rift-ds chat widget's defaults, shipped in the package rather than written here), the model names and one-line descriptions inwebsite/src/lib/chat-model.ts(rendered in the composer's picker; unscanned by any validator), the persona and easter-egg strings inwebsite/src/app/api/chat/, the notice strings inwebsite/src/app/api/chat/route.ts, and the guardrail notices inwebsite/src/app/api/chat/guardrails.ts(all visitor-visible; the em-dash half is build-covered throughvalidate-shipped-prose.mjs's module scan, and the guardrail notices' register row is content-design.md's — say what happened and what to do next, never blame the visitor). Every judgement-level rule stays this scope'scovers→ the alt text in the two cover registries,website/src/data/cover-renders.json(the case-study covers) andwebsite/src/data/essay-covers.json(the essay illustrations). Alt text is read aloud and indexed, so it is shipped copy, and its rule in the guide is its own: describe what the picture shows, never restate the title. Neither file is a page, so thewebsitescope misses bothfooter→ the sitemap footer's shipped copy: the column titles and copyright inwebsite/src/components/SiteFooter/SiteFooter.tsx, and the link labels inwebsite/src/config/social.ts(visible on every non-chromeless page, but outsideapp/**, so thewebsitescope misses them — the same gap thechatscope closes for the panel)palette→ the command palette's shipped copy: the group labels, per-item descriptions, the ask-chat row's trailing chip label and the search placeholder inwebsite/src/components/SitePalette/(reachable from every non-chromeless page, but outsideapp/**, so thewebsitescope misses it — the same gap thechatandfooterscopes close for theirs; no validator scans it). The ask-chat row's label is the visitor's own typed query — unauthored by design, never a finding — but its trailing chip ("Ask robr0 AI") is authored copy in scope; the empty state is unreachable (the ask row matches every query), so there is none to auditnav→ the navigation config's shipped copy: the link labels and descriptions inwebsite/src/config/navigation.ts(rendered in the header, the sidebars and the footer's derived columns — outsideapp/**, so thewebsitescope misses it, the same gap thechat,footerandpalettescopes close for theirs; no validator scans it)page-summaries→ the FAB panel's per-page TLDR copy: thetitle,textand chip labels inwebsite/src/data/page-summaries.json(routesandessaysmaps — rendered to every visitor on the chat FAB's summary panel, but a data file outsideapp/**, so thewebsitescope misses it, the same gap thecoversscope closes for alt text).scripts/validate-page-summaries.mjsbuild-covers length, the full stop and the em-dash ban; register, voice and banned words stay this scope's — its register row lives in the guide's table
Read
content-design.mdfirst — it is the only rule source. The Words to Avoid and Patterns to Avoid tables, the Voice rules, the Register by Surface table, and the Microcopy section are the checklist. This skill deliberately maintains no word list and no pattern list of its own: when the guide changes, the audit changes with it. If a rule seems missing, the fix is an entry incontent-design.md(per its Iteration Guide), never a rule added here.Scan the scoped prose and classify every finding at one of three severities:
Banned (the guide allows no use in shipped copy):
- Hard-ban words and phrases from the Words to Avoid table
- Em dashes anywhere in shipped copy.
scripts/validate-shipped-prose.mjsalready fails the build on these, so a clean tree means the surfaces it reads are clear and you are checking the ones it cannot judge: the chat's persona and greeting strings, and any prose outside its scope (its doc block is authoritative). Report a hit there as Banned exactly as before - Title Case in shipped headings, buttons, or labels
- First person in surfaces whose register says "None" (check the Register by Surface table for the scoped surface)
- Emoji, exclamation marks in UI copy, unsourced statistics
Rationed (legitimate in a narrow sense; flag for a density check):
- Words from the Rationed table — flag every use, note which look literal, and count per page
- American spellings in prose (colour/color and friends) — never flag code identifiers, token names, CSS properties, or file paths
Judgment (needs a reader, not a regex — quote the passage and say why):
- Rhythm uniformity: three or more similar-length sentences in a row
- Rule-of-three adjective stacks, copula avoidance, participial tails, negative parallelism, hedge stacking, elegant variation, bolded-label bullets, summary closers, throat-clearing openers
- Register mismatches: promotional tone in a neutral surface, a tagline restating its section name, an empty state describing absence instead of the next action
Never flag:
content-design.mditself, and quoted examples anywhere (a rule must be able to name what it bans)- Skill instruction bodies and
CLAUDE.md— agent-facing references, out of scope by design (see the guide's Overview) - Code, identifiers, token names, class names, and anything inside backticks or code fences
- Text authored by third parties
For each finding, output:
- File path (repo-relative) and line number, or the entry label for JSON surfaces
- Severity, the offending text, and the guide rule it breaks
- A suggested rewrite that keeps the sentence's meaning and any links intact
Format:
website/src/app/example/page.tsx:42 — banned — "a seamless theming journey" → "theming by overriding one primitive"Summarise at the end:
- Counts per severity, then the strongest single finding
- If nothing is found: "No content violations found. Prose follows content-design.md."
- Run the guide's Self-Review Tests over the longest passage in scope and report the result, pass or fail