writing-style
GitHub规范技术文档写作风格,消除AI生成痕迹。指导撰写docstrings、README等文本时提供事实与机制,避免空洞形容词和冗余铺垫,确保内容对工程师有价值且准确。
Trigger Scenarios
Install
npx skills add pydantic/monty --skill writing-style -g -y
SKILL.md
Frontmatter
{
"name": "writing-style",
"description": "How to write prose that reads like human technical documentation rather than LLM output. Use whenever writing or editing docstrings, comments, limitations\/ docs, READMEs, commit messages or PR descriptions, and when prose reads as smooth, salesy or generic."
}
Writing style
Applies to prose written in this repo: docstrings, comments, limitations/,
READMEs, commit messages, PR descriptions, and warnings like the one on
MountDir.
The reader is an engineer looking for a fact. Give them the fact. You are not persuading or building to a conclusion: say what happens, when, and what it costs.
Tells to avoid
Significance instead of mechanism
The most common LLM tell. If a sentence would fit unchanged in any other project's docs, it carries no information.
-
✗ "This ensures the sandbox remains secure."
-
✓ "Every operation runs relative to a
Diropened at mount time, so..and symlinks cannot reach outside it." -
✗ "Resource limits provide robust protection against runaway code."
-
✓ "The VM polls allocator usage every 255 instructions; crossing the allocator's hard limit exits the worker with
OOM_EXIT_CODE."
Throat-clearing
Openers that delay the sentence: "It's worth noting that", "It's important to understand", "In essence", "Simply put", "At its core", "Let's take a look at". Delete them; the sentence underneath is the content.
- ✗ "It's worth noting that overlay writes are discarded when the feed ends."
- ✓ "Overlay writes are discarded when the feed ends."
The "not just X, but Y" reveal
Building to a payoff is an essay move. Docs do not need one.
- ✗ "
heap.rsisn't just another module — it's the foundation of the entire safety model." - ✓ "
heap.rscontains theunsafecode thatHeapReadersoundness depends on. Changes need explicit review."
Adjectives doing the work of facts
"Powerful", "seamless", "robust", "elegant", "blazing fast", "significantly", "dramatically". Replace with a number, a mechanism, or nothing.
- ✗ "Overlays are capped at a reasonable size."
- ✓ "
memory_usage_limitcaps retained overlay data at 100 MB by default; exceeding it raisesMemoryErrorin the sandbox."
Restating what the reader can see
A docstring that repeats the signature wastes the line it occupies. Say why it exists, what it costs, or where it bites.
- ✗ "Adds a mount to the mount table." (on
MountTable::mount) - ✓ "Opens the host directory once; later operations run against that descriptor, so renaming the path afterwards does not detach the mount."
Summarising yourself
Do not close a section by restating it, and do not announce what the next section will do.
- ✗ "In summary, mounts are confined structurally rather than by checking."
- ✓ (nothing, you already said it)
War stories
Provenance is worth a clause only when it changes what the reader does. How the bug was found usually does not.
- ✗ "This was demonstrated against a live deployment during Hack Monty, where
sandboxed code wrote
dataclasses.pyand the client executed it during ordinary result conversion." - ✓ "Sandboxed code can write
json.pyinto a read-write mount, or any module not yet imported, and have the host's nextimportrun it, including importspydantic_montymakes itself."
Smoothness
A different failure from the tells above. Those pad out empty content; this dresses up real content, which makes it harder to spot and easier to approve.
Sentences engineered for rhythm read as conclusions, so the prose sounds like it is arguing when it is only listing facts. Balanced clauses and a stressed last syllable make a sentence sound authoritative whatever it contains, so one fact wearing three clauses gets read as three facts. Reference prose usually ends flatly, on a qualifier or a noun phrase, because the writer stopped when the information ran out rather than when the cadence resolved.
Timing for suspense. Commas and subordinate clauses arranged to delay the point.
- ✗ "The sandbox cannot execute what it writes, but your machine will, later, with your privileges, and the path from one to the other is easy to miss."
- ✓ "Files written by sandboxed code stay on the host, where other programs may execute them."
Telling the reader how to feel. "you did not choose", "easy to miss", "without being asked", "often does". These supply a mood in place of a fact.
- ✗ "
sys.path[0]is a directory you did not choose." - ✓ "
sys.path[0]is the script's directory, or the cwd forpython -m,python -cand the REPL."
Triples and reversals. A three-item list where one item carries the fact,
then a but clause positioned as the payoff.
- ✗ "Sandboxed code reads, writes and deletes normally and sees its own changes, but nothing reaches your disk."
- ✓ "Writes are kept in memory and discarded when the feed ends. Sandboxed code still sees its own writes."
The quotable closer. A generalisation at the end of a section, memorable, carrying no new fact. Delete it. The section ends at its last fact.
- ✗ "Principles alone produce prose that follows the rules and still reads like an LLM."
- ✗ "Most drafts get better by deleting the first sentence and the last."
Symmetry for its own sake. Three-item lists where two items are real, paragraphs of matched length, every bullet opening with a bolded term. If the shape came first and the content was fitted to it, cut back to what is true.
Three checks:
- Does the sentence end on a beat? If the last three words could be duller without losing meaning, they were there for rhythm.
- Strip the rhythm and count the facts. One is the usual answer.
- Is the sentence about the system, or about how the reader should feel?
Industry metaphor
Software described as objects moving through space, or as people with intentions. It is the register of a startup design review, not of reference documentation; CPython's docs use plain verbs throughout ("raises", "returns", "is stored in", "propagates", "Changed in version 3.11").
The metaphor also deletes the mechanism. "The error surfaces" does not say whether it raises, returns or logs. "Wire the tracker through" does not say parameter, field or global. "It lands in 3.14" does not say merged or released.
Motion and logistics:
| Instead of | Write |
|---|---|
| lands, landing | merged, released in 3.14 |
| ship, shipping | release |
| spin up, stand up | start, launch |
| wire up, plumb through | pass, connect |
| thread X through | pass X as a parameter |
| bubble up | propagate, or name the caller |
| surface (verb) | raise, return, report, log |
| hand back, hand off | return, transfer |
| bake in, baked into | built in, compiled in |
| punt on | defer, skip, leave to |
Structure as furniture:
| Instead of | Write |
|---|---|
| seam | interface, boundary |
| surface, surface area | API, the public functions |
| escape hatch | override, opt-out |
| knobs, dials | options, settings |
| load-bearing | required, relied on by X |
| X-shaped | with the same interface as X |
| lives in | is defined in, is stored in |
| sits on top of | wraps |
| under the hood | internally |
Code with intentions:
| Instead of | Write |
|---|---|
| the checker is happy | the check passes |
| knows about, is aware of | reads, checks, has a field for |
| talks to | sends requests to |
| teach the parser to | add X to the parser |
| wants, expects (of code) | requires |
| reaches into | accesses, reads |
Also: "for free", "just works", "out of the box", "first-class", "table stakes", "opinionated", "non-trivial", "unlock", "blast radius", "paper over".
In prose:
-
✗ "Errors from the worker surface to the caller."
-
✓ "
Checkout::feedreturnsPoolError::Crashedwhen the worker exits without aFatalErrorevent." -
✗ "The tracker is threaded through the whole VM."
-
✓ "Every allocation path takes
&ResourceTrackeras a parameter." -
✗ "
WorkerTransportis theNativeSession-shaped seam." -
✓ "
WorkerTransporthas the same methods asNativeSession, sosession.tsdrives either one."
Terms of art stay, even though they began as metaphors: heap, stack, pointer,
hot path, propagate, boilerplate, tombstone, sandbox escape, attack surface.
CLAUDE.md also sanctions foot-gun, happy path and single source of truth. The
test is whether a plain verb would say more than the metaphor does.
Structure
- Lead with the fact, not the context. First line of a docstring says what the thing is.
- Prose for reasoning, bullets for actual lists. A bulleted paragraph is harder to read, not easier.
- One idea per sentence. Split anything over about 30 words.
- Comments and field docs: 1 line, 3 at most. Function and struct docstrings: 5 lines or fewer. If the docstring is longer than the code, something is wrong with one of them.
- Warnings: the command or condition first, then the consequence and the subtle path to it, then the safe alternative. No preamble. Put the warning before the thing it warns about, not after.
Markdown line wrapping
Hard-wrap markdown at 120 characters, one sentence per line where possible: each sentence starts a new line, and only a sentence longer than 120 characters wraps onto continuation lines. This keeps diffs one-sentence-sized — editing a sentence does not rewrap the paragraph around it.
Continuation lines of a list item are indented to the content column of the marker. Code blocks, tables and headings are never rewrapped; a table whose rows cannot fit 120 characters should become a list instead.
To reformat an existing file: python3 .claude/skills/writing-style/rewrap_md.py <file>...
(rewrites in place, skips fences/tables/headings/frontmatter).
Words and punctuation
| Instead of | Write |
|---|---|
| utilize, leverage | use |
| in order to | to |
| serves as, acts as, functions as | is |
| prior to, subsequent to | before, after |
| a variety of, a number of | several, or the number |
| allows you to, enables you to | you can, or the imperative |
| is responsible for handling | handles |
One term per concept. Choose the word and keep it. Alternating between
worker, child and subprocess for one thing makes the reader stop to check
whether they are the same thing. Consistency beats variety.
- ✗ "The checkout feeds the child, and the worker replies with events."
- ✓ "The checkout feeds the worker, and the worker replies with events."
Noun clusters: three words at most. Longer stacks make the reader guess which noun modifies which.
- ✗ "parent-side mount table memory usage limit"
- ✓ "the memory limit for a parent-side mount table"
Active voice and simple tenses. Use the passive only when the actor is unknown or irrelevant. Do not drop articles or verbs to save space, except in the Rust convention of a subjectless first line ("Returns the host path.").
- ✗ "A
PermissionErrorwill have been raised by the mount." - ✓ "The mount raises
PermissionError."
Em dashes should be avoided, or used very sparingly. But do not update code just to remove em dashes.
Contractions are fine. Second person for user-facing docs ("you can never see the host path"), imperative for instructions ("mount a dedicated directory"). Hedges ("generally", "typically", "may") are for genuine uncertainty; if the behaviour is defined, state it.
Checking a draft
- Could this sentence appear verbatim in another project's docs? Then it is empty.
- Can you point at the code each claim describes? If not, you are guessing.
- Delete the first sentence. Was anything lost?
- Read it aloud. Sentences that sound like a conference talk get cut.
- Strip a sentence's rhythm. How many facts are left?
- Would a reviewer learn anything they could not get from the signature?
Version History
- 026b383 Current 2026-08-20 09:39


