The writing rules in one file

Voice, register by surface, the words and patterns this project never ships, and the tests every piece of copy passes first. The source of truth for how the system reads, copied verbatim from the repo on every build.

Overview

Every word this project ships should be consistent, human, clear, and neutral.

  • Consistent: the same voice, spelling, and register rules on every surface, so a reader moving from the homepage to a component page to the journal never feels the author change.
  • Human: specific, committed, rhythmically uneven prose. Text that could only have been written by someone who knows this project.
  • Clear: plain verbs, one idea per sentence. A reader who has never seen the repo can follow it.
  • Neutral: matter-of-fact, never promotional. State what a thing is and does, and let the specifics carry the weight. Nothing here needs selling.

This document governs all shipped prose: website page copy and metadata, journal entries, component descriptions, README and Storybook copy, release notes, commit bodies, and UI microcopy. It sits beside its siblings with a clean split: design.md owns how things look, CLAUDE.md owns where facts live (one authoritative home per fact, point instead of enumerating, no counts outside registries), and this file owns how sentences read.

Deliberately out of scope: the agent-facing markdown references. CLAUDE.md, design.md, SECURITY.md, skill instruction bodies, and this file itself are written for AI agents to parse, and their format optimises for that job: dense sections, bold markers, tables, and em dashes as structural separators. Those are formatting tools there, not voice, and this guide does not restyle them. The published ones appear on /blueprints as artefacts, shown deliberately as they are. What IS in scope on those pages is the shell copy around them (titles, taglines, intros, metadata), which is shipped prose like any other.

One deliberate irony to note up front: the ## headings in this file are Title Case because that is the file convention shared with design.md, while the rule for shipped copy is sentence case. The convention applies to the markdown spec files; the rule applies to what users read on the site.

A word on the origin of this document. The prose in this project is written by AI agents, and unedited AI prose has recognisable habits: hedged claims, uniform sentence lengths, promotional adjectives, and stock phrases that could sit in any project's docs without changing a word. This guide exists to strip those habits out. The test of success is not an AI detector score (see The Detector Question below); it is whether the copy reads like it was written by one careful person who knows this system inside out.


Voice

The system is the subject. System documentation, the journal, and component pages use no first person. Write "The registry drives the sidebar", never "we built the registry to drive the sidebar". The exception is genuine instructions, where "you" is correct because the reader is doing something ("Install the package, then import the stylesheet once").

British spelling in prose, American in code. Colour, behaviour, organising, centred. Code identifiers and tokens keep their American spellings (--color-action-primary-bg, the color CSS property), and prose never respells them. When a sentence names a token, the token wins.

Sentence case everywhere. Headings, buttons, nav labels, card titles: "Work experience", not "Work Experience". Title Case is reserved for proper nouns (Storybook, Material Symbols Rounded, Nunito Sans).

No em dashes. The character ( — ) is banned in shipped copy (the agent-facing markdown references are exempt; see Overview). The turns it used to carry survive by other means: a colon for "and here is the point", a comma or parentheses for an aside, or a full stop and a second sentence. Two short sentences are almost always stronger than one spliced long one. This is the one rule in this guide a script can settle, so a script does: scripts/validate-shipped-prose.mjs fails the build on one, and its doc block is authoritative for which surfaces it reads. A lone dash standing in for an absent value (the disabled Input's placeholder) is a glyph, not a spliced sentence, and is not a violation.

Concrete numbers over adjectives. "Both themes resolve from one token layer" beats "a powerful theming system". If a claim deserves emphasis, give it a number, a name, or a mechanism. If it has none of those, it is probably decoration; cut it.

No emoji in shipped copy. Icons are Material Symbols Rounded, chosen deliberately; emoji are neither.

Four sentence-level moves recur in the strongest existing copy. They are rationed, not encouraged (demoted 2026-09-19: a reply built on move 1 was identified as machine-written on sight — the shapes themselves have become recognisable AI rhetoric). The budget is one use per page across all four combined:

  1. Concession, then correction. "Telling people a design system is themeable is easy. Showing them is harder."
  2. Mechanism, then consequence. "Semantic tokens reference primitives, so overriding one primitive re-themes every component at once."
  3. Stakes as what breaks. "The validator fails the build, so a stale count never reaches the site."
  4. Negative definition. "Motion here is functional, not decorative."

Each earns its keep by carrying information, and a page that uses none of them loses nothing.


Register by Surface

Each surface has its own shape. The full standard for a surface lives in one place; this table characterises each register in a line and points home.

SurfacePersonShapeThe rule that mattersFull standard
Release log entries (website/src/data/release-log.json)NoneShort paragraphs per release: what shipped, and what it means for a consumerConcise and neutral, never commit digests; written for a consumer, not a maintainer; a plain descriptive title a non-technical reader can followThis file
Website page copy + metadataNoneShort paragraphs under sentence-case headingsThe system is the subject; specifics over adjectivesThis file
Component descriptions (src/components/registry.json)NoneOne verbless fragment, ≤160 chars, ends in a full stopOne authoritative home: sidebar, metadata, and README all derive from itThis file + registry validator
README + src/stories/Configure.mdx"You" for instructionsInstall and usage copyProduction copy: the README ships in the npm tarballCLAUDE.md (Registries section)
npm package description (PACKAGE_DESCRIPTION in scripts/package-manifest.mjs, mirrored in root package.json)NoneOne fragmentRenders on the npmjs.com package page: production copy, same bar as the READMEThis file
design.md spec sectionsNoneBold BEM class opener, then prose and tablesSpecs state rules, not sales pointsdesign.md
Skill displayDescription + invoke frontmatterNone1–3 factual sentences; invoke phrases are short imperative fragmentsBoth render on the public /skills page (descriptions as card copy, invoke phrases as chips); describe what it does, not how clever it isThis file
Release notes"You" allowedWhat's new, what breaks, how to installWritten for a consumer, not a maintainer.claude/skills/release/SKILL.md
Commit bodiesNone1–3 sentences of whyThe diff shows what; the body explains why.claude/skills/ship/SKILL.md
Audit and loop reportsNoneFindings in plain EnglishThe reader is a designer, not an analystThe invoking skill
UI microcopy (labels, empty states, errors)ImperativeA few wordsDescribe the next action, not the current stateMicrocopy section below
Site-chat answers (generated at runtime)The system as subject; the maintainer in third personShort paragraphs; markdown headings only in walkthroughsThe assistant is not the maintainer; site facts and general design knowledge stay visibly separatewebsite/src/app/api/chat/persona.ts
Chat suggestion chips — written starters (SiteChat/starters.ts) and generated follow-upsThe system as subjectOne plain question, at most SUGGESTION_MAX_CHARSA chip is a question, not a request: cut the polite run-up and the "in three points" trimmings. Over the budget it is dropped, never clippedwebsite/src/app/api/chat/followups/route.ts + chat-starters validator
Playground story chips, scripted turns, and staged-history copy (website/src/lib/chat-sim.ts; the starters, seed threads with their detail lines, project rows and thread-name pool in the playground's Chat view; the lifecycle demo's name pool on the thread-panel docs page)The staged product's user and assistant — a fictional consumer product, never the maintainer or the siteOne chip, at most SUGGESTION_MAX_CHARS; answers a short paragraph; thread titles one short task phrase; thread descriptions one short status fragment; project names a two-word workstream noun phraseA story chip may be a request ("Set it all up for me") — the chip is the message that routes the branching script, so imperatives are the point. Same budget, same drop-never-clip rule. Thread titles read like a generated chat name, descriptions like the session's last known state, projects like workstreams: what the fictional user was doing, never the maintainer's workchat-starters validator (its SOURCES list, plus the page-summaries validator for the summary panel's chips, are jointly authoritative for which files hold written chips)
Template screen copy (the fictional product screens in website/src/components/templates/, served under /templates/<slug>)The staged product's own users and assistant — a fictional product, never the maintainer or the siteFull-screen app copy plus canned assistant turns and chipsAll data is fictional and the /templates index says so; register and voice rules deliberately do not apply (the content-audit skill excludes these screens), but the mock assistants' chips share SUGGESTION_MAX_CHARS, same drop-never-clip rulechat-starters validator (SOURCES) + .claude/skills/content-audit/SKILL.md's exclusion list
Page summaries (website/src/data/page-summaries.json — the FAB panel's per-page TLDR and its chips)The system as subjectA title, one TLDR sentence at most 160 characters ending in a full stop, and 1–2 chips at most SUGGESTION_MAX_CHARSIt is the page in one line, not a pitch — the panel calls itself a TLDR, so a paragraph is a failure; chips are questions the chat can answer, same drop-never-clip ruleThis file + the page-summaries validator
Loop entries (website/src/data/loops.json — the /loops cards)"I" for the approval voice; the agent is the actorOne description paragraph per loop, stage chips as short fragments, guardrails one line eachPlain claims about what actually runs: cadence and trigger state what is true today, and every description ends where the loop does, with a human judging a branchThis file + the loops validator
Hand-written corpus prose (the connective paragraphs in scripts/generate-site-corpus.mjs)NoneShort orienting paragraphs between derived blocksThe chat model can repeat any of it verbatim to a visitor: production copy, same bar as page proseThis file + CLAUDE.md (corpus boundary rules)
/llms.txt section intros (website/src/app/llms.txt/route.ts)NoneOne line per sectionServed publicly to crawlers and agents; describe, never promoteThis file
MCP tool descriptions, server instructions, the browser landing page (website/src/app/api/mcp/route.ts), and the shared roster of blurbs, example prompts and connect snippets (website/src/lib/mcp-tools.ts, website/src/lib/mcp-clients.ts)None ("you" for the landing page's instructions)One or two lines per tool; one question per example prompt; one short page for a person who typed the URLRead by agents choosing a tool and by people checking what the URL is: state what it returns, never promote. A prompt is a question the tool can answer alone, never an imperative the server cannot fulfilThis file + the MCP tools validator
Consumer agent skill (website/public/skill/rift-design-system/, generated by scripts/generate-agent-skill.mjs)"You" for instructionsFrontmatter, short instructional sections, and a reference catalogueRead by a consumer's coding agent while it writes code: contracts and pointers, never promotion. Every fact derives from a registry, and the files stay in for-consumers-of-the-package territory, never this repo's own /skillsThis file + the agent-skill validator
Component markdown pages (website/public/components/*.md, generated by scripts/generate-component-md.mjs)NoneOne markdown contract per component: metadata lines, import lines, a props table per exportNo hand-written prose lives here: every sentence derives from the registry and the prop JSDoc, and the component-md validator byte-compares the files on every build. Change the source, never the fileThis file + the component-md validator
Package CLI output (the usage, error and success lines the init bin prints, src/cli/init.mjs)"You" for usage; errors imperativeA usage block, one-line errors, one-line success plus the connect linePrints in a consumer's terminal, so it is shipped copy: errors say what happened and what to do next (Microcopy: errors is the standard), success states what landed and where, nothing promotesThis file + validate-shipped-prose.mjs's module scan + the content-audit skill's cli scope
Site-chat widget copy (the welcome tagline, disclaimer and locked-model lines in website/src/components/SiteChat/SiteChat.tsx, and the time-of-day greeting fragments in website/src/components/SiteChat/greeting.ts)ImperativeOne line eachThe tagline names subjects, not the author; the disclaimer links to /privacy for the AI-use and logging disclosure and fits one line at the caption size; the locked-model line states what happened and when it lifts, never blame; the greeting is three fixed fragments, no punctuation, third person about nobodyThis file + the content-audit skill's chat scope
Chat model names and descriptions (website/src/lib/chat-model.ts)NoneA display name plus one fragment per model, ending in a full stopRendered in the composer's picker; each line says what the model is for, never a performance claim or a superlativeThis file + the content-audit skill's chat scope
Chat guardrail notices (website/src/app/api/chat/guardrails.ts)The assistantOne or two plain sentencesThey render as ordinary assistant messages, so they say what happened and what to do next, never blame the visitorThis file (Microcopy: errors)
Chat tool copy (the CHAT_TOOLS descriptions and trace-point lines in website/src/app/api/chat/route.ts, and the lookup error and hint strings in website/src/lib/site-tools.ts, shared with /api/mcp)NoneA sentence or two per tool description; trace points and errors one short fragment eachThe descriptions are read by the model choosing a tool: state what the lookup returns, never promote. Trace points render in the widget while a lookup runs, and an error string can be repeated verbatim to a visitor or an agent: say what was not found and where the full list isThis file + validate-shipped-prose.mjs's module scan + the content-audit skill's chat and mcp scopes
Footer copy (column titles in website/src/components/SiteFooter/SiteFooter.tsx, link labels in website/src/config/social.ts)NoneSentence-case fragmentsIdentical on every page, so a change is a site-wide change; labels name destinations, never actionsThis file + the content-audit skill's footer scope
Command palette copy (group labels, item descriptions, the ask row's trailing chip label and the placeholder in website/src/components/SitePalette/)NoneGroup labels and one-line fragments; the placeholder as microcopy; the trailing chip a two-to-three word destination name, dropping to icon-only below 480pxNavigation rows' descriptions name destinations; action rows (the Actions group, the ask-chat row) name what selecting them does. The ask row's own label is the visitor's typed query — deliberately unauthored, never restyled; its trailing chip is authored copy naming the surface it opens. The empty state is unreachable by construction (the ask row matches every query), so the palette ships noneThis file + the content-audit skill's palette scope
Nav config copy (the section and link descriptions and the mega showcase card's overline, title and description in website/src/config/navigation.ts)NoneSentence-case fragments; the showcase description one sentenceOne string renders in several places at once (the mega panel, the sidebars, the footer's derived columns, the home and DS-landing cards), so a change is a site-wide change; descriptions say what a page holds, never actions or promotionThis file + the content-audit skill's nav scope
Immersive stage copy (control labels on /playground; the explanatory notes in the playground's views, e.g. the Type view's tier notes; the graph instrument's panel copy on /graph, in website/src/components/SystemGraph/SystemGraph.tsx)Imperative; notes declarativeControl labels a few words; view notes a sentence or two, held to the same rules as website page copy (the corpus carries them to the chat)Teach the interaction the surface does not otherwise reveal, in the order a visitor tries it; never restate what a visible control already says; notes state what a view shows, never promoteThis file + validate-shipped-prose.mjs's module scan

Deliberately out of scope: the hidden /labs rebuilds are out of scope the same way as the template screens: their copy is fictional demo data redrawing a reference product, and the content-audit skill's exclusion list records it.


Writing Principles

Six principles. Strong copy visibly demonstrates at least three of them; no copy may violate any of them.

  1. Specific beats general. The test for every paragraph: could this have been written by someone who knows nothing about this project? If yes, it says nothing. The fix is always the same: add something only this project knows. A token name, a real count from a registry, the actual failure a validator prevents, the date something shipped. The strongest form is the incident: when a real failure exists, lead with the story ("two modal titles shipped in body text for weeks because CSS does not error on a missing variable") rather than the principle it proves. The story convinces where the abstraction merely claims.

  2. Commit. Say the thing. No both-sidesing, no hedging a claim until nothing is asserted. "The build fails when the registry drifts" is a sentence; "the build should generally fail in most cases where the registry may have drifted" is fog. If a claim is genuinely uncertain, state the uncertainty as a fact ("Figma-to-code sync is still a manual process") rather than diluting the verb.

  3. Stay neutral. Never promotional. No hype adjectives, no superlative without a number behind it, no exclamation marks doing an adjective's job. This project describes itself the way a good spec describes a component: what it is, what it does, what breaks if you misuse it. Readers trust the register precisely because it is not asking for trust.

  4. Plain words, one idea per sentence. Used, not utilized. Has, not boasts. Is, not serves as. Every sentence advances exactly one idea; every paragraph does one job. If a sentence needs two commas and a semicolon to hold together, it is two sentences.

  5. Explain before you name. Shorthand is compression, and compression is a tell: "making drift impossible" and "the machine-readable surface" are accurate, but only to a reader who already holds the concepts. Describe the thing in plain narration first ("you write a count into the README, add more components, forget to update it"); the short name is earned once the reader has seen what it means. If a term saves a sentence of explanation, that sentence was probably the useful part.

  6. Vary the rhythm. Human prose is uneven. Mix sentences under ten words with sentences over twenty. Never write three sentences of similar length in a row. Let a short sentence land. Uniform 15-to-20-word sentences in a steady drumbeat are the single most reliable machine tell, and no word list fixes them.


Words to Avoid

Density is the tell, not any single word. One "robust" in a technical claim is fine; three per page reads as filler. Two lists follow: hard bans, which have no legitimate use in this project's copy, and rationed words, which have a narrow literal use and are otherwise replaced.

Hard Bans

Never writeWrite instead
delve, dive into (metaphorical)dig into, look at, read
tapestry, symphony, beacon(name the actual things)
a testament to(state the evidence directly)
realm, landscape, ecosystem (metaphorical)(name the actual area: the token layer, the docs site)
journey (metaphorical)process, path, or the named steps
seamless, seamlessly(say what actually connects, or cut)
game-changer, cutting-edge, next-level(the claim, with a number)
unlock, unleash, empowerlet, allow, enable
elevate (marketing sense)improve, or the specific change
boasts, features (as a verb for "has")has
synergy, paradigm(say the actual relationship)
ever-evolving, fast-paced(cut; nothing here evolves by itself)
myriad, plethoramany, or the number
it's worth noting, it is important to note(just say the thing)
in conclusion, in summary(end when done)
whether you're a X or a Y(name the actual reader, or address no one)

Rationed

WordLegitimate useOtherwise
robusta specific resilience claim ("survives a missing peer dependency")say what it survives
leveragenever as a verb; the noun is rare but realuse
crucial, pivotal, vitalalmost never; one per document at mostimportant, or cut
comprehensivea checkable claim ("every component has a page")list what is covered
key (adjective)sparingly; "the key fact" once per pagemain, central
fosternever in system prosebuild, encourage
showcasethe literal noun ("the component showcase")show
underscore, highlight (verb)rarelyshow, make clear
streamlineneversimplify
utilize, facilitateneveruse; say what it does
delivershipping software, literallymake, provide, publish

Patterns to Avoid

Sentence- and structure-level tells. Each entry pairs the pattern with its repair.

Em dash splicing. Banned outright; see Voice. Repair with a colon, a comma, parentheses, or two sentences.

Copula avoidance. "The registry serves as the single source of truth" → "The registry is the single source of truth". Stands as, functions as, acts as, represents: all of these are "is" wearing a costume.

Negative parallelism as a hook. "It's not just a component library, it's a design language" → say what it is: "A component library and the design language behind it." The not-X-but-Y frame implies someone claimed X; nobody did.

Rule-of-three adjective stacks. "Fast, flexible, and scalable" → pick the one that matters and prove it: "Themeable at runtime by overriding one primitive." Three near-synonyms carry one word's worth of information.

Participial significance tails. "The tokens are generated from CSS, ensuring consistency and highlighting the system's rigour" → full stop after "CSS". If the consequence matters, give it its own sentence with its own evidence.

Bolded-label bullets. "Performance: the site is fast" is a table row pretending to be prose. Use a real table for enumerable facts, or write sentences.

Over-organised short prose. Problem/solution headings, parallel bullet openers, and "The fix:" colon scaffolds draped over a few paragraphs of content are structure tells: the content is being arranged rather than said. If prose shorter than a page needs internal scaffolding to hold together, remove the scaffold and write the sentences in order.

Boldface as seasoning. Bold marks the one load-bearing term in a section, not every noun that felt important while writing. More than two bolded phrases per paragraph means none of them stand out.

Title Case Headings. Shipped copy uses sentence case; see Voice.

Summary closers. A final paragraph that restates the page adds nothing; a reader who reached it just read the page. End on the last fact.

Throat-clearing openers. "Let's explore the token system" → "The token system has three tiers." Start with the fact the reader came for.

Macro-openers. "In today's component-driven development landscape..." could open any article ever written. Start with this project.

Fake specificity. Numbers with no source are worse than no numbers. A number that cannot be traced is deleted, not rounded; a count comes from its registry (CLAUDE.md's Registries section owns that rule) and a case-study statistic comes from the work itself.

Listicle filler. A bullet that restates its heading in new words is padding. Every bullet must add a fact absent from the heading.

Elegant variation. Calling the same thing "the library", "the toolkit", "the collection", and "the suite" across four sentences is not variety, it is confusion. One name per thing, everywhere. The component library is "the library" or "the design system"; pick per page and hold it.

Hedge stacking. "Can potentially help improve" → "improves", or delete the claim. One hedge is a judgment; two is an evasion.


Human Signals

Avoiding tells is half the work. The other half is the habits of human writing that machine prose lacks, and most of them are permissions rather than rules:

  • Plain verbs are allowed to be plain. Wrote, not authored. Used, not utilized. Died, not passed away. Formal synonyms are the machine register.
  • Definitive claims are allowed when true. "The first release shipped on 2026-07-26" and "this is the only surface that loads fonts at runtime" are human sentences. Machine prose hedges reflexively; a writer who knows the facts commits to them.
  • Natural hedges are allowed when honest. "Very", "fairly", "tends to", "probably" are how people actually qualify claims. The banned hedges are the ceremonial ones ("it is worth noting that it may potentially...").
  • A little slack is allowed. "In order to", "the fact that", "as a result of" are wordier than strictly necessary, and human. Prose optimised to maximum tightness reads machine-made. Do not pad deliberately; do stop sanding once a sentence sounds like speech.
  • Unevenness is the signature. Paragraph lengths should differ. Some ideas deserve four sentences, others deserve five words. Resist the pull toward three medium sentences per paragraph, every paragraph.

Just as important is what not to treat as a tell. Perfect grammar is not a machine sign; careful people exist. Formal register is not a machine sign; specs are formal. Transition words are not a machine sign in themselves; only the ceremonial chains (furthermore, moreover, additionally, in conclusion) are. This guide targets specific measurable habits, not a vibe, and it should never be used to accuse prose of being machine-written on style alone.


Microcopy

Rules for text inside the UI: labels, buttons, empty states, errors, tooltips. Component-specific applications live in that component's spec section in design.md; these are the general principles behind them.

  • Describe the next action, not the current state. "Add your first component", not "No components yet". An empty state is an invitation, not a shrug.
  • Buttons are verbs. "Save changes", "Copy token", "View source". A button labelled with a noun ("Settings") is navigation, not action; keep the distinction.
  • Taglines are not the section name. The breadcrumb already says where the reader is. A component tagline says what the thing is for: "The main action element", not "Components".
  • Errors say what happened and what to do. "The token name is already taken. Choose another." Never blame the user, never just state failure.
  • Sentence case, no terminal full stop on labels. Fragments under about five words take no full stop; complete sentences (empty-state bodies, error explanations) do.
  • No exclamation marks. The UI does not get excited.

The Detector Question

This guide began with a goal: copy that would pass an AI detector. The honest version of that goal needs stating, because detectors themselves are unreliable and the wrong lesson is easy to learn.

Detectors estimate two proxies. Perplexity: how predictable each word is given the words before it. Burstiness: how much sentence length and structure vary. Machine prose scores low on both because models pick likely words at a steady rhythm. But the proxies misfire constantly. OpenAI withdrew its own detector after it caught only 26% of AI text while flagging 9% of human text as machine-written. A Stanford study found detectors flagged 61% of essays by non-native English speakers as AI, because plain vocabulary in even rhythm looks machine-like regardless of who wrote it.

The consequence for this project: passing a detector is a lagging indicator of following this guide, never a goal in itself. Prose that is specific, committed, and rhythmically uneven scores human as a side effect, because those are the properties the proxies approximate. Anything done purely to move a detector score (synonym-swapping, deliberate typos, tool-assisted "humanising") is a trick, and tricks produce prose that is worse for actual readers. Write for the reader who has never seen the repo; the detector follows.


Self-Review Tests

Four tests before any prose ships. They take a minute and catch most failures.

  1. The stranger test. Could this paragraph have been written by someone who knows nothing about this project? If yes, it fails. Fix: add a specific only this project knows.
  2. The pub test. Read it aloud. Would you say this sentence to a colleague across a table? "This showcases our commitment to robust theming" fails the moment it leaves your mouth. Rewrite until it survives being spoken.
  3. The rhythm test. Scan sentence lengths in the paragraph. Three similar lengths in a row fails. Fix: cut one sentence to under ten words, or merge two.
  4. The again test. Simplify the paragraph one more time than feels necessary. If no fact fell out, ship the simpler version. Most prose survives at least one more pass than its author expects, and the pass that removes the cleverness is usually the one that makes it read human.

The on-demand audit for all of this is the content-audit skill (.claude/skills/content-audit/SKILL.md): it scans a page, a data file, or a whole surface against this document and reports violations with suggested rewrites.


Iteration Guide

  1. A new tell appears in the wild: add it to Words to Avoid or Patterns to Avoid with a replacement, in the same change that fixes the offending copy. A ban without a repair is not usable guidance.
  2. A rule fights good writing twice: weaken it or delete it. This guide serves the copy, not the reverse. Record the change so the reasoning is not relitigated.
  3. A new prose surface appears (a new page type, a new generated artefact): add a row to Register by Surface in the same change, with its authoritative home. A surface with no register drifts immediately.
  4. A rule here starts duplicating a skill's standard: cut the duplication and point at the skill. One authoritative home per rule, same as facts (CLAUDE.md owns that principle).
  5. Before shipping prose: run the Self-Review Tests, and run the content-audit skill over anything longer than a paragraph. Before shipping code that carries prose, npm run verify still applies.

Known Gaps

  • Alt text and accessibility copy: image alt, aria-label wording, and screen-reader-only text have no rules here yet. The a11y test suite enforces presence, not quality.
  • The word lists are seeded, not exhaustive: they cover the tells observed in AI prose as of mid-2026. Model habits shift; the Iteration Guide covers additions.
  • No localisation stance: the project ships in English only; nothing here addresses translation.
  • Enforcement is mostly by audit, not build: the em dash is the one rule a script can settle, and scripts/validate-shipped-prose.mjs settles it. Every other rule here belongs to the on-demand content-audit skill, deliberately: most style calls need a reader, and a regex that mangles good writing to appease itself would be worse than drift.