Docs match code
Documentation that quotes the code is code: a column name, an env var, a command flag, a route, a
response shape. The difference is that nothing fails when it goes stale — it just quietly starts lying, and
the person it lies to is the one with the least context to notice.
The rule: every fact quoted from the code is verified against the code before merging.
1. What counts as a quoted fact
| In the docs |
Verify against |
| A table or column name, a type, a nullability |
the migration, the model |
| An environment variable |
the example env file, the config that reads it |
| A command and its flags |
the command's own signature/help |
| A route, verb or path |
the route list |
| A response shape or field |
the resource/serializer, or a real response |
| A file path |
the filesystem |
| A default value |
the code that defines it, not the docs that repeated it |
Prose about why is not a quoted fact and does not rot the same way. It is the concrete nouns that drift.
2. Verify, do not remember
# env vars named in the docs but absent from the example file
rg -o "[A-Z][A-Z0-9_]{3,}" README.md docs/ | sort -u > /tmp/doc-vars
rg -o "^[A-Z][A-Z0-9_]*" .env.example | sort -u > /tmp/real-vars
comm -23 /tmp/doc-vars /tmp/real-vars | head
# a column named in the docs, against the migrations
rg -n "column_name" database/migrations/ app/Models/
# a command's real signature
<your-cli> help <command> # then compare flag by flag
The check that matters is the boring one: open the file the doc is describing. Most drift is one
renamed thing that nobody grepped for.
3. Prefer a pointer or a generator to a copy
A snippet copied into a document is a fork of the truth, and forks diverge. In order of preference:
- Generate it. A section between markers, rebuilt from the source by a script, with CI failing when it
is stale. This is the only option that cannot rot.
- Point at it. "The full list is in
config/x.php" beats reproducing the list.
- Copy it, and mark it. If you must copy, say where it came from, so the next person knows what to
re-check.
The cost of (1) is one script; the cost of (3) is every reader who trusted the copy.
4. Onboarding instructions are executable claims
"Clone, copy the env file, run these three commands" is a promise that it works on a clean machine. It is
also the documentation that rots fastest, because the people who write it never run it again.
Test it the only way that counts: a fresh clone, an empty environment, and the steps exactly as written —
nothing from your shell history, no assumed global tool. Whatever you had to do that is not in the file is
the part that was missing.
5. Agent instruction files rot the same way, and cost more
CLAUDE.md, AGENTS.md, skill files and the like quote paths, commands and rules. When one goes stale the
agent does not warn you: it follows the stale instruction confidently, which is worse than having no
instruction. Treat them as documentation with a shorter fuse.
Two specific traps:
- A rule that names a file, a helper or a flag that has been renamed. The agent looks for it, does not
find it, and improvises.
- Documentation that teaches the pattern you just fixed. If you correct instances of an anti-pattern and
leave the example that taught it, the next change reintroduces it — you fixed the symptom and kept the
cause.
5b. State files rot by time, not by drift
A progress ledger, a continuity note, a lessons file: these do not quote the code, they describe a moment
— and the moment passes while the file stays. Four rules keep one true:
- Write memory as a dated historical observation, not as a present-tense fact. "On the tree was
untracked" stays true forever; "the tree is untracked" becomes false the day it is committed, and the file
now lies without anyone editing it.
- Record the state after the action, plus the next gate. A ledger committed saying "staged, not yet
committed" is stale in the same commit that carries it. Worse is one saying "amend this commit, then push":
once that commit is under review, following the instruction rewrites published history.
- Keep it collaborator-neutral and machine-neutral. No contributor name, no absolute path from the
authoring machine. Record the branch identity and the command that discovers the path, so the file works
for the next person and the next clone.
- Do not promise a tool the reader may not have. If a check is mandatory it lives in the repository or is
installed reproducibly; anything environment-owned is labelled supplementary.
The same applies to a lessons file itself: append a fix only once it is confirmed, with the test or rule that
prevents recurrence — otherwise it accumulates hypotheses that read like conclusions.
6. When the code changes, the docs are part of the change
Not a follow-up, not a ticket: the same commit. The reviewer who can tell whether the doc is now right is
the reviewer of that diff, and only for as long as the diff is open.
The question at the end of a change: did I rename, move, add or remove anything a document names?
Gotchas
- A doc test that only checks links is measuring nothing about content. Links resolving does not mean the
column exists.
- Copy-paste across projects carries the other project's truth. A README that started as another repo's
README describes that repo until every line is checked.
- The example that is "obviously" right is the one that is wrong, because nobody re-reads it.
- Generated sections still need the generator to run in CI. A
make docs that only humans run drifts the
same way, just more slowly.
- Screenshots are documentation too, and they are the hardest to keep true. Prefer describing the state
over showing a UI that will be restyled.
Checklist
Final report
Docs touched: <files>
Facts verified: <n> (columns, env vars, commands, routes, paths)
Drift found: none | <doc:line → real value>
Copies converted to pointer/generated: <n>
Onboarding steps run clean: yes | no | not applicable
1---2name: padosoft-docs-match-code3description: Use this skill when writing or reviewing documentation that quotes the code — a README, a CLAUDE.md or AGENTS.md, a SKILL.md, an ADR, an onboarding guide, an API doc — and whenever the user reports that a documented command, column, env var or flag does not exist, that setup instructions fail on a clean machine, that the docs describe an older behaviour, or asks to document a feature. It checks the quoted facts against migrations, models, config, command signatures and routes, and prefers a generated or pointed-to source over a copied snippet. Do not use it for writing style, for choosing a docs platform, or for translating existing documentation.4license: MIT5---67# Docs match code89Documentation that quotes the code **is code**: a column name, an env var, a command flag, a route, a10response shape. The difference is that nothing fails when it goes stale — it just quietly starts lying, and11the person it lies to is the one with the least context to notice.1213**The rule: every fact quoted from the code is verified against the code before merging.**1415---1617## 1. What counts as a quoted fact1819| In the docs | Verify against |20|---|---|21| A table or column name, a type, a nullability | the migration, the model |22| An environment variable | the example env file, the config that reads it |23| A command and its flags | the command's own signature/help |24| A route, verb or path | the route list |25| A response shape or field | the resource/serializer, or a real response |26| A file path | the filesystem |27| A default value | the code that defines it, not the docs that repeated it |2829Prose about *why* is not a quoted fact and does not rot the same way. It is the concrete nouns that drift.3031## 2. Verify, do not remember3233```bash34# env vars named in the docs but absent from the example file35rg -o "[A-Z][A-Z0-9_]{3,}" README.md docs/ | sort -u > /tmp/doc-vars36rg -o "^[A-Z][A-Z0-9_]*" .env.example | sort -u > /tmp/real-vars37comm -23 /tmp/doc-vars /tmp/real-vars | head3839# a column named in the docs, against the migrations40rg -n "column_name" database/migrations/ app/Models/4142# a command's real signature43<your-cli> help <command> # then compare flag by flag44```4546The check that matters is the boring one: **open the file the doc is describing**. Most drift is one47renamed thing that nobody grepped for.4849## 3. Prefer a pointer or a generator to a copy5051A snippet copied into a document is a fork of the truth, and forks diverge. In order of preference:52531. **Generate it.** A section between markers, rebuilt from the source by a script, with CI failing when it54 is stale. This is the only option that cannot rot.552. **Point at it.** "The full list is in `config/x.php`" beats reproducing the list.563. **Copy it, and mark it.** If you must copy, say where it came from, so the next person knows what to57 re-check.5859The cost of (1) is one script; the cost of (3) is every reader who trusted the copy.6061## 4. Onboarding instructions are executable claims6263"Clone, copy the env file, run these three commands" is a promise that it works on a clean machine. It is64also the documentation that rots fastest, because the people who write it never run it again.6566Test it the only way that counts: a fresh clone, an empty environment, and the steps exactly as written —67nothing from your shell history, no assumed global tool. Whatever you had to do that is not in the file is68the part that was missing.6970## 5. Agent instruction files rot the same way, and cost more7172`CLAUDE.md`, `AGENTS.md`, skill files and the like quote paths, commands and rules. When one goes stale the73agent does not warn you: it follows the stale instruction confidently, which is worse than having no74instruction. Treat them as documentation with a shorter fuse.7576Two specific traps:7778- **A rule that names a file, a helper or a flag that has been renamed.** The agent looks for it, does not79 find it, and improvises.80- **Documentation that teaches the pattern you just fixed.** If you correct instances of an anti-pattern and81 leave the example that taught it, the next change reintroduces it — you fixed the symptom and kept the82 cause.8384## 5b. State files rot by time, not by drift8586A progress ledger, a continuity note, a lessons file: these do not quote the code, they describe **a moment**87— and the moment passes while the file stays. Four rules keep one true:8889- **Write memory as a dated historical observation**, not as a present-tense fact. "On <date> the tree was90 untracked" stays true forever; "the tree is untracked" becomes false the day it is committed, and the file91 now lies without anyone editing it.92- **Record the state *after* the action, plus the next gate.** A ledger committed saying "staged, not yet93 committed" is stale in the same commit that carries it. Worse is one saying "amend this commit, then push":94 once that commit is under review, following the instruction rewrites published history.95- **Keep it collaborator-neutral and machine-neutral.** No contributor name, no absolute path from the96 authoring machine. Record the branch identity and the command that *discovers* the path, so the file works97 for the next person and the next clone.98- **Do not promise a tool the reader may not have.** If a check is mandatory it lives in the repository or is99 installed reproducibly; anything environment-owned is labelled supplementary.100101The same applies to a lessons file itself: append a fix only once it is confirmed, with the test or rule that102prevents recurrence — otherwise it accumulates hypotheses that read like conclusions.103104## 6. When the code changes, the docs are part of the change105106Not a follow-up, not a ticket: the same commit. The reviewer who can tell whether the doc is now right is107the reviewer of that diff, and only for as long as the diff is open.108109The question at the end of a change: *did I rename, move, add or remove anything a document names?*110111---112113## Gotchas114115- **A doc test that only checks links is measuring nothing about content.** Links resolving does not mean the116 column exists.117- **Copy-paste across projects carries the other project's truth.** A README that started as another repo's118 README describes that repo until every line is checked.119- **The example that is "obviously" right is the one that is wrong**, because nobody re-reads it.120- **Generated sections still need the generator to run in CI.** A `make docs` that only humans run drifts the121 same way, just more slowly.122- **Screenshots are documentation too**, and they are the hardest to keep true. Prefer describing the state123 over showing a UI that will be restyled.124125## Checklist126127- [ ] Every column, env var, command, flag, route and path in the diff's docs verified against the source128- [ ] Onboarding steps run from a clean clone, exactly as written129- [ ] Copied snippets replaced by a pointer or a generated section, or marked with their source130- [ ] Agent instruction files checked for renamed files, helpers and flags131- [ ] No example left teaching a pattern that was just fixed132- [ ] Docs changed in the same commit as the code they describe133134## Final report135136```137Docs touched: <files>138Facts verified: <n> (columns, env vars, commands, routes, paths)139Drift found: none | <doc:line → real value>140Copies converted to pointer/generated: <n>141Onboarding steps run clean: yes | no | not applicable142```