Skip to content
Back to skills

Content Audit

ASecurity

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 against the content guide.

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 6, 2026
developmentrailsawsgitapi

Works with

  • terminal
  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned November 11, 2026

npx -y skills add robritacca-dotcom/rift-ds --skill content-audit --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Content Audit?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Content Audit
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/robritacca-dotcom-content-audit/badge)](https://www.skillsdirectory.com/skills/robritacca-dotcom-content-audit)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: content-audit
description: 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 against the content guide.
icon: spellcheck
displayDescription: "Scans shipped prose against content-design.md: banned words, em dashes, promotional register, first person where the system should be the subject, and rhythm problems no word list catches. Reports each finding with its location, the offending text, and a suggested rewrite. Accepts a page, a data file, or a whole surface as scope."
invoke: ["content audit","audit the copy on [page]","check this prose against the content guide","any AI slop in [file]"]
---

# content-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

1. **Determine scope.** Accept one of:
   - A specific file path (a page, a data file, a markdown doc)
   - `release-log` → `website/src/data/release-log.json` (titles + story bodies, plus the predecessor versions' one-line summaries)
   - `registry` → the `description` fields in `src/components/registry.json`, and the chat widget's `description` and per-subpath strings in `src/chat/registry.json` (rendered on its component page and served by MCP)
   - `package-meta` → the npm package description: `PACKAGE_DESCRIPTION` in `scripts/package-manifest.mjs`, mirrored into the root `package.json` (renders on the npmjs.com package page — its register row calls it production copy at the README's bar; no validator judges its prose, and the `readme` scope never reaches it)
   - `corpus-prose` → the hand-written connective paragraphs inside `scripts/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.md` prose plus `src/stories/Configure.mdx`. README's marked regions (the doc block of `scripts/generate-readme-content.mjs` owns the list) are **generated** by `scripts/generate-readme-content.mjs` (the component regions from `src/components/registry.json`, the rest from `scripts/brand.mjs`) — a rewrite inside them silently reverts on the next build, so fix the source instead (component copy through the `registry` scope); the `/blueprints` page copies under `website/public/` are generated too (synced from the root specs by `scripts/sync-blueprints.mjs` on every build), so edit the root file, never the copy
   - `skills` → the `displayDescription` frontmatter strings and the `invoke` phrase lists across `.claude/skills/` (both render on /skills — the descriptions as card copy, the invoke phrases as visible chips — so both are published copy; skill instruction *bodies* are out of scope)
   - `website` → user-visible strings in `website/src/app/**` page files; a page slug (e.g. `overview`) scopes to that page folder
   - `chat` → the chat's shipped prose, in two layers. The package's (`src/chat/`, shipped to every consumer): the greeting fragments in `greeting.ts`, the default tagline, disclaimer, locked-model line, the composer's placeholder while a question set holds it (`questionsPlaceholder`) and the Chat position menu's label inline in `ChatWidget.tsx`, that menu's row labels in `placement.ts`, the placeholder replies in `useChat.ts`, the transport's failure lines in `transport.ts`, the package's fallback follow-up pool in `followups.ts`, the handlers' default notices, the follow-ups prompt, the `ask_user` tool's description and its error line, and the questions intro line in `server.ts`, the `ask_user` schema's field descriptions in `questions.ts` (read by the model, so the tool-copy register applies), QuestionCard's default labels in `src/components/QuestionCard/QuestionCard.tsx`, ActionCard's default labels in `src/components/ActionCard/ActionCard.tsx`, the starters prompt (`startersSystemPrompt` in `server.ts`), the model-facing lines `skippedText` and `confirmationLine` write in `actions.ts` and `selectionLines` writes in `selectionContext.ts` (the tool-copy register applies), the guardrail notices in `upstash.ts`, and the demo's staged copy — the turns, chip labels and question prompts and options in `demo-script.ts`, the fixtures in `demo-fixtures.ts`, the welcome fillings in `DemoWelcome.tsx`, the agent panes in `demo-agent-panes.tsx` and their empty-state lines in `demo-agent.tsx`, the artifact panel's empty state in `ChatArtifactPane.tsx` and the demo's two artifacts in `demo-artifacts.tsx` (the framed page's own copy included), and the scripted transport's own fallback replies in `sim.ts`. The site's (its configuration of the package): the component-page and fallback starter pools in `website/src/components/SiteChat/starters.ts` (each static page's own pool lives in `page-summaries.json`, the `page-summaries` scope) and the written follow-up fallbacks in `website/src/lib/chat-followups.ts` (chip length is build-covered by `scripts/validate-chat-starters.mjs`; register, voice and banned words stay this scope's), the site's tagline, disclaimer and placeholder in `website/src/components/SiteChat/useSiteWidgetProps.tsx`, the highlight notes in `website/src/components/SiteChat/siteFocus.tsx`, the merchant sales assistant's persona and tool copy in `website/src/app/api/merchant-chat/route.ts`, the model names and one-line descriptions in `website/src/lib/chat-model.ts` (rendered in the composer's picker; unscanned by any validator), the persona strings in `website/src/app/api/chat/persona.ts`, the tool descriptions, trace points and notice copy in `website/src/app/api/chat/route.ts`, the guardrail notice copy in `website/src/app/api/chat/guardrails.ts` (all visitor-visible; the guardrail notices' register row is content-design.md's — say what happened and what to do next, never blame the visitor), the event-rail copy in `website/src/app/playground/ChatDirector.tsx`, and the view modules under `website/src/app/playground/views/` (TypeView's tier notes and labels). The em-dash half is split: `scripts/validate-shipped-prose.mjs` build-scans the story and handler modules' string literals (`STORY_MODULES` is its list), so this scope's em-dash job there is done by the build — but the widget's inline strings and the persona stay unscanned, and every judgement-level rule (register, voice, banned words) stays this scope's across all of it
   - `footer` → the sitemap footer's shipped copy: the column titles and the bottom row's copyright and author credit in `website/src/components/SiteFooter/SiteFooter.tsx`, and the link labels in `website/src/config/social.ts` (the credited name and its URL are `AUTHOR_NAME`/`AUTHOR_URL` in `scripts/brand.mjs` — audit the sentence around them, never the values) (visible on every non-chromeless page, but outside `app/**`, so the `website` scope misses them — the same gap the `chat` scope 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 in `website/src/components/SitePalette/` (reachable from every non-chromeless page, but outside `app/**`, so the `website` scope misses it — the same gap the `chat` and `footer` scopes 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 <ASSISTANT_NAME>" — `ASSISTANT_NAME` in `scripts/brand.mjs` owns the name) is authored copy in scope; the empty state is unreachable (the ask row matches every query), so there is none to audit
   - `backgrounds` → the header's background menu copy: each row's `label` and `description` in `website/src/data/site-backgrounds.json`, rendered as the menu's title and subtitle on every non-chromeless page (a data file outside `app/**`, so the `website` scope misses it, the same gap the `nav` and `footer` scopes close for theirs; `scripts/validate-site-backgrounds.mjs` build-covers the em-dash ban and the lengths, and register, voice and banned words stay this scope's)
   - `nav` → the navigation config's shipped copy: the section and link descriptions, group labels and the mega showcase card's overline, title and description in `website/src/config/navigation.ts` (rendered in the mega panel, the sidebars, the footer's derived columns and the home and DS-landing cards — outside `app/**`, so the `website` scope misses it, the same gap the `chat`, `footer` and `palette` scopes close for theirs; no validator scans it)
   - `agent-skill` → the consumer agent skills' instructional prose, the manifest's one-line summaries and the agent starter's prose (`assembleAgentStarter()`, published as `website/public/AGENTS.md`), all hand-written inside `scripts/generate-agent-skill.mjs` (their register row is in the guide's table). The published folders under `website/public/skill/` (`AGENT_SKILLS` in `scripts/brand.mjs` owns the names) are generated and byte-compared, so fix the generator, never the files; `scripts/validate-agent-skill.mjs` build-covers the em-dash ban there, and register, voice and banned words stay this scope's. The reference tables (the component catalogue, the token reference) derive from registry data and are not prose to audit, and the sibling surface, the per-component `.md` pages, deliberately has no scope for the same reason
   - `theme-export` → the playground theme export's shipped copy: the themed AGENTS.md's theme section and the THEME.md and agent prompt templates in `website/src/lib/theme/theme-export.ts` (the AGENTS.md's other sections are the agent starter, the `agent-skill` scope's), the From the source section's fit lines and traits (the DESIGN.md match report's row notes and the trait texts in `website/src/lib/theme/design-md.ts`, which ship in the export whenever the theme came from a file), the export dialog's description in `website/src/app/playground/ExportThemeDialog.tsx`, and the lever words in `LEVER_VOCABULARY` in `website/src/lib/theme/theme-overrides.ts`, which the rail prints beside each slider and THEME.md uses to describe the look (a visitor carries the exported files into their own repo, but all of it lives in `lib/` modules and a component file, so the `website` scope's page scan never reaches it, and /playground is chromeless and outside the corpus, so no corpus check does either). `scripts/validate-theme-export.mjs` build-covers the token names the templates use, the band ranges, the colours the prose names (held to the ones the exported CSS draws) and the From the source section over a fixture, `scripts/validate-design-md-import.mjs` holds every report note and trait line free of em dashes, and `scripts/validate-shipped-prose.mjs`'s module scan build-covers the em-dash ban in all three files; register, voice and banned words stay this scope's. Their register row is the guide's Playground theme export entry: imperative and addressed to the reader's coding agent in the templates, declarative in the dialog description, and one plain word per band in the vocabulary
   - `cli` → the package init bin's terminal output: the usage block, error lines, the deprecation line and success lines in `src/cli/init.mjs` (prints in a consumer's terminal, so it is shipped copy; the em-dash half is build-covered through `validate-shipped-prose.mjs`'s module scan, and its register row in the guide holds errors to the Microcopy standard — what happened, then what to do next), plus the scaffold templates the `init` subcommands write into a consumer's project, every folder under `src/cli/templates/` (`init chat`'s, `init app`'s and `init block`'s; the block comments at the top of each file and the widget's placeholder title, tagline and disclaimer; read as instructions in a stranger's repository, so every path and env name is checked against the files that exist — `scripts/validate-chat-widget.mjs` holds the chat scaffold's file and env names to the docs page and every template's imports to the package, `scripts/validate-blocks.mjs` the blocks' files to their registry, nothing holds the comments — and no validator scans them for em dashes)
   - `page-summaries` → the FAB panel's per-page TLDR copy: the `title`, `text`, chip labels and `starters` labels in `website/src/data/page-summaries.json` (`routes` and `essays` maps — the `essays` map is empty by design, since this site has no essays feature — rendered to every visitor on the chat FAB's summary panel, but a data file outside `app/**`, so the `website` scope misses it, the same gap the `footer` scope closes for its copy). `scripts/validate-page-summaries.mjs` build-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
   - `loops` → the loops registry's shipped copy: the `description`, `cadence`, `trigger`, `stages` and `guardrails` strings in `website/src/data/loops.json` (rendered verbatim as the /loops cards and embedded in the chat corpus, but a data file outside `app/**`, so the `website` scope misses it — the same gap the `page-summaries` scope closes for its copy). `scripts/validate-loops.mjs` build-covers structure and `scripts/validate-shipped-prose.mjs` the em-dash ban; register, voice and banned words stay this scope's — its register row lives in the guide's table
   - `schemas` → the hand-written prose in the meta-schemas at the top of `website/public/schema/` (`title`, `description`, `$comment`) and the `$description` strings `scripts/generate-token-values.mjs` writes into the DTCG files (content-design.md's Schema prose row owns the register)
   - `mcp` → the agent-facing routes' shipped copy: the tool blurbs and example prompts in `website/src/lib/mcp-tools.ts`, the connect snippets in `website/src/lib/mcp-clients.ts`, the server instructions and browser landing page in `website/src/app/api/mcp/route.ts`, the shared tool implementations' error and hint strings in `website/src/lib/site-tools.ts` (read by agents over MCP and repeated by the chat model to visitors), the `CHAT_TOOLS` descriptions in `website/src/app/api/chat/route.ts` (read by the model choosing a tool), and the `/llms.txt` section intros in `website/src/app/llms.txt/route.ts` (route handlers and `lib/` modules, not page files, so the `website` scope misses all of them — `scripts/validate-mcp-tools.mjs` holds the roster to the registered tools but judges no prose; their register rows live in the guide's table)
   - `stage` → the immersive stages' floating chrome: the toolbar labels and tooltips in `website/src/components/StageToolbar/`, the control-bar labels in `website/src/components/StageControlBar/`, inspect mode's labels and pin/release states in `website/src/components/InspectMode/`, the playground's theme-source import (the dropzone and source cards in `website/src/app/playground/` and the DESIGN.md match report's row notes and the trait list under it in `website/src/lib/theme/design-md.ts`, all four in `validate-shipped-prose.mjs`'s module scan), and the graph instrument's panel and caption copy in `website/src/components/SystemGraph/` — whose column labels ship from `scripts/generate-dependency-graph.mjs`, so fix the generator's strings, never `dependency-graph.generated.ts` (all rendered on the chromeless stage routes, so the `chat`/`footer`/`palette` scopes never reach them, and outside `app/**`, so the `website` scope misses them too — the same gap those scopes close for theirs; `scripts/validate-shipped-prose.mjs` scans the two SystemGraph modules for the em-dash ban, and otherwise no validator scans any of them). Their register row is the guide's Immersive stage copy entry, with the Microcopy section as the label-style standard

2. **Read `content-design.md` first — 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 in `content-design.md` (per its Iteration Guide), never a rule added here.

3. **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.mjs` already 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

4. **Never flag:**
   - `content-design.md` itself, and quoted examples anywhere (a rule must be able to name what it bans)
   - Skill instruction bodies, `design.md`, `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 (external-skill copies keep their upstream voice)
   - The labs rebuilds under `website/src/app/labs/**` and the template screens under `website/src/components/templates/**` (served under `/templates/<slug>`, whether or not they began in labs) — their copy is fictional demo data staging a product screen, excluded from the chat corpus for the same reason, so register and voice rules do not apply to it. The `/templates` index page's own prose is ordinary page copy and stays in scope

5. **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"`

6. **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

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in development →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.