Documentation is the work everybody agrees matters and nobody has time for, which makes it the most obvious thing to generate.
It is also where generated content does the most lasting damage, for a reason worth stating up front: a wrong README is not corrected by anything. A wrong commit message is noticed during archaeology. Wrong code fails a test. A wrong README is read by a new engineer who believes it, wastes an afternoon, and does not know enough to file a correction.
What to generate, and what not to
Section titled “What to generate, and what not to”The distinction that determines whether this is useful: is the source of truth in the repository?
| Document | Derivable from code | Generate |
|---|---|---|
| API reference | Yes — signatures, types, annotations | Yes, and regenerate |
| Module overview | Mostly — structure and imports | Draft, then edit |
| Setup instructions | Partly — manifests, Dockerfiles, CI config | Draft, then verify by running |
| Architecture description | Partly — imports and boundaries | Draft, then correct |
| Onboarding guide | Partly | Draft; the useful parts are human |
| Why decisions were made | No — unless history records it | Do not generate |
| Operational runbooks | No — depends on your systems | Do not generate |
| Security guidance | No | Do not generate |
The bottom three are where hallucinated documentation is most damaging, because they are read under pressure by somebody who does not know the answer.
Deriving from actual state
Section titled “Deriving from actual state”The techniques that keep generated docs true, in order of how much they help.
Give it the real files. Obvious and skipped. A README generated from a directory listing describes
a project shaped like yours; one generated from package.json, the entry point and the CI workflow
describes yours.
{/* What a setup section is actually derived from */}cat package.jsoncat .github/workflows/ci.ymlcat Dockerfile 2>/dev/nullExtract structure mechanically.
git ls-filesWhat it doesLists tracked files, excluding anything Git ignores.
Why we run itA generated repository map should reflect what is committed, not whatever build artefacts happen to be on your disk.
Expected resultOne path per line.
{/* Where the code actually lives, by volume */}git ls-files | awk -F/ '{print $1}' | sort | uniq -c | sort -rn | headPrefer generated-from-source API docs. Where your language has a documentation generator that reads annotations, use it. It cannot hallucinate a parameter, and it regenerates when the code changes. AI’s role there is writing the prose around it, not the reference itself.
Ask for citations. The same discipline as history analysis: “for each claim about how this works, name the file it comes from.” A claim with a file reference is checkable in seconds.
The setup section, specifically
Section titled “The setup section, specifically”The most-read and most-often-wrong part of any README, and the one with a definitive test.
A generated setup section will confidently produce:
## Getting started
1. Clone the repository2. Run `npm install`3. Copy `.env.example` to `.env` and fill in your values4. Run `npm run dev`Every line of that is plausible. Whether it is true depends on facts a model infers rather than knows:
whether the package manager is npm, whether .env.example exists, whether dev is a real script,
whether there is a database that must be running first.
The test is not review. It is execution.
- Clone into a fresh directory — not the one you have been working in for a year.
- Follow the instructions exactly, without using anything you know.
- Note every point where you had to know something the document did not say.
- Those gaps are the document.
Step 3 is where real onboarding documentation comes from, and it is the one thing on this page that cannot be generated — because the gaps are invisible to anybody who already knows the answers, and invisible to a model that inferred them.
Architecture and the boundary of inference
Section titled “Architecture and the boundary of inference”A model reading a codebase can describe its structure accurately: what modules exist, what imports what, where the boundaries are. That is genuinely useful for onboarding and for your own understanding of an unfamiliar area.
What it cannot do is explain why the structure is that way — and architecture documentation that omits the why is a map without a legend.
The failure is specific: asked why a system is designed a certain way, a model produces a reasonable architectural rationale. Reasonable, well-written, and unconnected to the actual reason, which was probably a constraint from a system that no longer exists.
The instruction that keeps this honest:
Describe the structure from the code. Where you are inferring intent rather than reading it from a comment, a commit message or a document, say so explicitly.
Then, for anything that matters, do the archaeology and write the reason down as a decision record — attributed to the reconstruction rather than to the original authors.
Diagrams
Section titled “Diagrams”Structural diagrams generate well because the structure is in the code.
graph TD API[api/handlers] --> SVC[services/orders] SVC --> REPO[repositories/orders] REPO --> DB[(PostgreSQL)] SVC --> PAY[clients/payments](placed in a mermaid fenced block in your Markdown)
Two properties make this worth doing. It is text in the repository, so it diffs and reviews like code. And it is checkable — every arrow should correspond to an actual import, and asking “for each edge, name the file containing the import” verifies it.
The limitation is the same as everywhere else: a diagram showing how data should flow, or how the system is meant to be layered, is a claim about intent. If the code violates the intended layering — which is the interesting case — a diagram generated from the code shows the violation, and a diagram generated from the intent hides it. Both are useful; they are not the same document, and they should be labelled.
Code comments
Section titled “Code comments”A category with different economics from documents, and one where AI is over-applied.
Generated comments explaining what code does are usually noise. // increment the counter above
counter++ adds length and no information, and a codebase full of them is harder to read rather than
easier.
Comments explaining why are valuable and cannot be generated. // the provider rejects batches over 500, undocumented is exactly the comment worth having, and it comes from you.
Doc comments on public interfaces are the useful middle. Parameter descriptions, return semantics, thrown errors — derivable from signatures and surrounding code, feed a documentation generator, and worth drafting.
The instruction that produces useful output:
Add doc comments to the exported functions in this file. Describe parameters, return values and error conditions. Do not add inline comments explaining what statements do.
Two specific risks in generated comments. A comment asserting behaviour the code does not have is worse than none, because readers trust comments over code until proven otherwise. And a comment that was true when generated becomes a lie when the code changes — comments are documentation with the same staleness problem and no review process at all.
Onboarding guides
Section titled “Onboarding guides”The document with the highest value and the largest generated-versus-real gap.
A generated onboarding guide describes: cloning, installing, running tests, the directory layout. All true, all discoverable in twenty minutes by anybody competent.
What a new engineer actually needs is none of that:
- Which three files to read first, and in what order
- What the codebase calls things, versus what the domain calls them
- Which parts are load-bearing and which are vestigial
- The one non-obvious thing about local setup that costs everybody an afternoon
- Who to ask about what
- Which tests are meaningful and which are flaky
None of that is in the code. It is in the heads of people who have onboarded, which is why the best onboarding documentation is written by the most recently onboarded person while they still remember what was confusing.
The useful role for generation here is narrow and real: produce the structural scaffolding — the directory tour, the setup steps, the test commands — so that the human effort goes entirely into the list above. That is a genuine saving, and it is the opposite of generating the whole document.
Keeping it true
Section titled “Keeping it true”Generation makes documentation cheap to produce, which makes it cheap to produce a lot of, which makes staleness worse rather than better. A repository with forty generated documents has forty things that can drift.
Three practical controls.
Generate less. A short accurate README beats a comprehensive stale one. The question before generating a document is who reads it and when.
Put it next to what it describes. Documentation in the same directory as the code changes in the same pull request. Documentation in a separate repository does not.
Check it in CI where you can. Not everything is checkable, but more is than people try:
- name: Fail if documented commands do not exist run: | for cmd in $(grep -oP 'npm run \K[\w:-]+' README.md | sort -u); do npm run "${cmd}" --dry-run >/dev/null 2>&1 \ || { echo "::error::README references missing script: ${cmd}"; exit 1; } doneThat is a small check catching a specific, common decay: a README referencing a script somebody renamed. Link checking, example compilation and “does the file this references still exist” are all in the same category — cheap, mechanical, and they catch the drift that matters.
Date what cannot be checked. A document that says when it was last verified, and against what, lets a reader calibrate. An undated architecture document is either current or three years old, and nothing in it says which.
Updating stale documentation
Section titled “Updating stale documentation”Most repositories do not need documentation written. They need documentation corrected, which is a different and better-suited task.
The reason it suits AI well: finding the mismatch between a document and the code is a comparison task, and comparison is something a systematic reader does better than a human skimming.
{/* What changed since the doc was last touched? */}git log --oneline --since="$(git log -1 --format=%cd --date=short -- README.md)" -- src/Then ask a narrow question:
Here is the README and here are the commits touching
src/since it was last updated. Which statements in the README are now wrong or incomplete?
That is checkable output — each claimed mismatch names a document statement and a commit, and both can be verified. It is much more reliable than asking for a rewrite, which discards whatever was right.
Update rather than regenerate. This is the important habit. Regeneration produces a clean document and silently discards every correction, caveat and hard-won clarification somebody added over two years. The diff of a regenerated README is unreviewable — everything changed — so nobody reviews it, and the losses are invisible.
An update produces a small diff a reviewer can read, which is the whole point.
Find the stale documents first:
{/* Documents not touched in a year, in a repository that has moved on */}git ls-files '*.md' | while read -r f; do printf '%s %s\n' "$(git log -1 --format=%cd --date=short -- "$f")" "$f"done | sort | head -20That list, sorted oldest first, is your documentation debt. Most of it is probably fine; the entries describing code that has changed substantially are the ones to look at.
Documentation for AI, not just about the codebase
Section titled “Documentation for AI, not just about the codebase”A repository increasingly carries two kinds of documentation with different audiences.
Human documentation — README, architecture notes, onboarding. Written for people, structured for reading.
Agent instructions — .github/copilot-instructions.md, AGENTS.md, skills. Written for models,
structured for context efficiency, and covered in
Repository Instructions.
They overlap and should not be merged. Human docs explain and give context; instructions are terse directives that cost tokens on every request. A README pasted into an instructions file wastes context on prose a model does not need; instructions pasted into a README produce a document nobody enjoys reading.
The useful relationship is that both derive from the same understanding, and updating one is a prompt to check the other.
Who the document is for
Section titled “Who the document is for”The question that decides everything else, and the one generation cannot answer.
A README is for somebody deciding whether to use or contribute to this project. It should answer what it is, whether it is maintained, how to run it, and where to go next — in that order, in under a screen. Most generated READMEs fail on length: comprehensive is the wrong target.
Architecture notes are for somebody about to change the system. They need the boundaries, the invariants, and the reasons — and the reasons are the part that is not derivable.
API documentation is for somebody integrating. Completeness matters here in a way it does not elsewhere, which is why generated-from-source reference docs are the right tool.
Runbooks are for somebody at 03:00 who is under pressure and not thinking clearly. They must be exactly correct, tested, and written by whoever operates the system. This is the category where generated content is most dangerous — it reads authoritatively and is followed without scepticism.
Decision records are for somebody in two years asking why. Short, dated, and written at the time the decision was made. AI can format one; it cannot supply the content, and a reconstructed decision record should say that it is reconstructed.
Matching the register to the reader is the editorial work. A generated document defaults to a uniform, comprehensive, mildly enthusiastic voice regardless of who is reading — which is exactly wrong for at least three of the five above.
Common mistakes
Section titled “Common mistakes”Generating setup instructions without running them. The single most common source of wrong documentation, and the one with a definitive test.
Accepting an architectural rationale. The structure is in the code; the reasons usually are not.
Generating comprehensively. More documents means more drift. Fewer, truer ones are better.
Documentation far from the code. If it does not change in the same pull request, it will not.
No verification date on unverifiable claims. A reader cannot calibrate an undated document.
Merging human docs and agent instructions. Different audiences, different constraints.
Regenerating rather than updating. Regeneration discards the human corrections somebody made last time, silently.
Trusting a generated diagram’s edges. Check each one against an actual import.
A workable process
Section titled “A workable process”Putting the page together into something a team can actually adopt.
-
Decide what documents you need. Fewer than you have, probably. Each one is a maintenance liability.
-
Generate drafts from real files, not from structure alone, with citations requested.
-
Verify anything executable by executing it — in a clean clone, following the instructions literally.
-
Write the parts that are not derivable yourself. Reasons, gotchas, who to ask, what to read first. This is where the value is and it is unavoidably human.
-
Put each document next to what it describes, so it changes in the same pull request.
-
Add the checks that are cheap — referenced scripts exist, links resolve, examples compile.
-
Date what cannot be checked, with what it was verified against.
-
Update rather than regenerate, so corrections survive and diffs stay reviewable.
Steps 3 and 4 are the ones that make the difference, and they are the two that generation does not help with at all. That is the honest summary of this lesson: AI removes most of the typing and none of the verification, and documentation is a domain where the verification was always the work.
Mental model
Section titled “Mental model”Documentation is a claim about the repository. AI writes claims fluently and cannot check them, so the value of generated documentation is entirely determined by what you derived it from and what you verified afterwards.
What you learned
Section titled “What you learned”- Documentation is where wrong AI output survives longest, because nothing corrects it
- Generate what is derivable from code; do not generate reasons, runbooks or security guidance
- Setup instructions are tested by executing them in a clean clone, not by reading them
- The gaps you hit doing that are the actual documentation
- A model can describe structure accurately and will invent architectural rationale
- Ask for file citations, and for explicit marking of inference versus reading
- Mermaid diagrams are checkable — every edge should be a real import
- Some documentation decay is catchable in CI: missing scripts, dead links, broken examples
- Human documentation and agent instructions are different documents for different readers
Exercise
Section titled “Exercise”Use a repository you know well.
-
Ask for a README from the directory listing alone. Predict: how much of the setup section is inferred?
-
Regenerate giving it the manifest, the entry point and the CI workflow. Compare specificity.
-
Clone into a fresh directory and follow the setup instructions exactly. Predict: how far do you get?
-
Note every step where you used knowledge the document did not contain. That list is the gap.
-
Ask for an architecture description, then ask it to mark which statements are read from the code and which are inferred. Predict: what proportion is inference?
-
Generate a Mermaid diagram and verify three edges against actual imports.
-
Add the CI check for README-referenced scripts and deliberately rename one. Confirm it fails.