Skip to content
Back to skills

Writing Docs

ASecurity

Write or restructure documentation agents and people actually read: which of the four directories it belongs in (how-to, reference, decisions, exec-plans), what belongs in a failure message instead of a file, and keeping the routing table honest. Use for any doc, runbook, README, ADR or plan; when docs are stale, contradictory or ignored; when deciding where a convention lives; when moving knowledge out of CLAUDE.md or agent memory.

  • 5 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 2, 2026
developmentpythonrustgokubernetesgitapidocumentation

Works with

  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 20, 2026

npx -y skills add WangChangxin0809/cc-repo-harness --skill writing-docs --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Writing Docs?

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

Security grade badge for Writing Docs
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/wangchangxin0809-writing-docs/badge)](https://www.skillsdirectory.com/skills/wangchangxin0809-writing-docs)

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: writing-docs
description: Write or restructure documentation agents and people actually read: which of the four directories it belongs in (how-to, reference, decisions, exec-plans), what belongs in a failure message instead of a file, and keeping the routing table honest. Use for any doc, runbook, README, ADR or plan; when docs are stale, contradictory or ignored; when deciding where a convention lives; when moving knowledge out of CLAUDE.md or agent memory.
---

# Writing docs an agent will actually use

Governs: shared/scripts/context/before_write.py

A document is read because something happened. That event is its **reading
trigger**, and it decides everything: where the file goes, how long it may be,
and what shape it takes. Documents written without one become reference material
nobody references.

## Fix the top level; leave the interior free

| Directory | Trigger | What it holds |
|---|---|---|
| `how-to/` | I am about to do a thing | Ordered steps, each ending in an observable criterion |
| `reference/` | I need to look up a fact | Facts keyed for lookup, carried by working examples |
| `decisions/` | Why is it like this? | Numbered from the PR, dated, superseded rather than edited |
| `exec-plans/` | What are we in the middle of? | Goal · steps with state · what would abort it |

Four directories, fixed. **Inside each one, organise however suits the
material.** Both halves are evidence, not taste: a mandated shallow top level
with a free interior is the arrangement that survived a decade across hundreds
of OpenStack repositories, while two controlled studies — one on 65 people, one
on 1,650 agent sessions — each found *no* effect from documentation shape. So
the constraint sits where something was measured and stays off where nothing
was. Templates in `references/kinds.md` are advice.

Additions to the top level are fine once `docs/index.md` routes them. A
directory that forks a required name (`adr/`, `howto/`, `plans/`) is an error
either way — that fork is how one bucket silently becomes two.

A file that does not fit any of the four is usually two files.

## The one shape rule that is a requirement

Three parts per step, all three present:

```markdown
### 3. Rebuild the index

    python3 scripts/index/build.py

Criterion: `scripts/index/query.py --stats` reports a symbol count within 5% of
`git grep -c 'def \|class '`. A wildly lower count means the parser silently
skipped a language.
```

This one is required while the rest of the shape guidance is advice, because it
is **content rather than form**. Of five context signals measured against
SWE-bench Verified, reproduction instructions were worth +56.3% — three times
edit location and more than everything else combined. Steps without a criterion
are read as gestures and executed as gestures.

**Write the positive path.** The space of wrong ways is unbounded; the right way
is one path. And a prohibition has no reading trigger — nobody opens a document
to find out what they were about to do wrong. They find out by doing it.

So a prohibition worth keeping belongs where it fires. **This is also where
troubleshooting content goes**, and it is why there is no `troubleshooting/`
directory: agents open such documents 0.4% of the time and consult documentation
after a failure 7.5% of the time, but structured guidance delivered *at* the
error produced over 85% recovery against 17%.

| The thing you want to forbid, or the symptom you want to explain | Where it belongs |
|---|---|
| An action that destroys work | A guard, with the reason in the block message |
| A state the repo must not reach | A gate, with the fix in the failure output |
| A symptom with a known cause | The failure output of whatever detects it |
| A pattern that is wrong only here | That subtree's `CLAUDE.md` |
| A road already tried and abandoned | A decision record — that is what they are for |

Failure output is the one place a negative is guaranteed to be read, because the
reader is stuck. Make it carry the remedy and a path: *"blocked: `git restore`
discards uncommitted work in the same file. Back up with `cp` first — see
docs/how-to/reverting-safely.md."*

## Decision records

Immutable. When a decision changes you write a new one with `Supersedes: 0004`,
and edit the old one only to add `Superseded by: 0019`. Editing the original
destroys the only artifact that records what you used to believe and why — which
is the part that stops the same idea being re-litigated every six months.

Record what you rejected. A decision that lists only the winner reads as
inevitable, and the next person re-proposes the alternative you already killed.

**The number comes from the pull request, not a counter, and numbers are not
continuous.** Write the file as `0000-<slug>.md` and rename it when the PR
opens. A counter looks tidier and breaks under concurrent contribution: two
people both take the next number, both are right when they write it, and both
land — Open edX's decision directory carries four collided numbers from exactly
that. Every scheme that survived at scale numbers from an identifier that
already exists (Rust from the PR; Go and Kubernetes from the issue).

Two honest notes. These are for people, not agents — in observed sessions agents
opened decision and architecture documents 4.0% of the time, and no measured
evidence says they help an agent at all. And the genre usually does not survive:
about half of all repositories with decision records have five or fewer.

## Exec plans

Multi-session work needs a file, because context does not survive the session
and the plan is the only thing that does. Past a few steps it needs a folder —
`docs/exec-plans/<name>/` with a `README.md` and a `steps/` directory — because
the plan's state must be readable at a glance while one step may carry pages of
decisions.

- **`README.md` owns state**: goal, steps each marked `todo | doing | done |
  dropped`, and the condition that would abort the whole plan. Step files never
  restate status; nobody reopens a finished step to change `doing` to `done`.
- **A step earns a file** when it has decisions to record or is worth handing to
  a subagent — otherwise it is a line in the README, and the numbering gap
  (`01, 03`) is how "no file needed" stays distinguishable from "file missing".
- **A step file is written when the step is entered**, not upfront. Written in
  advance it is fiction, and fiction in a plan is indistinguishable from a
  decision that was actually made.
- **Every step file carries `## Consulted`** — existing skills (`find-skill`),
  prior art in other people's code, research. It may say "none, because this
  step only executes what 01 decided"; it may not be absent. Same shape as
  `<!-- unrouted: reason -->`: an exemption states its reason or becomes
  blanket. Prior art means their **code**, not their documentation, and a
  decision a paper drives is checked against that paper's implementation.
- `docs/exec-plans/tech-debt-tracker.md` is permanent. Anything found in passing
  goes here with the reading that revealed it and the blast radius — never fixed
  inline, because a batch that grows while you work is a batch that never lands.
- On completion the folder is deleted and a decision record replaces it, if
  anything was decided. Finished plans left in place are read as active work,
  and deleting the folder takes the step files with it — so anything worth
  keeping is promoted into the record, not left in `steps/`.

`docs/index.md` routes the `README.md` only; `check_docs_index.py` reaches the
steps through the links the README already has.

## Reference, and the generated property

Reference is retrieval infrastructure, and **its value is carried by working
examples rather than by the table**. Removing code examples from API
documentation dropped answer accuracy from 0.66–0.82 to 0.22–0.39 in the one
ablation that measured the components separately. A page that lists parameters
and shows no working call has given away most of what it was worth. Write
reference for what the model has no prior about — your APIs, your formats,
uncommon libraries; restating what is widely known costs context and returns
nothing.

A glossary belongs here and is worth more than it looks: it pins the vocabulary
every search depends on, and when it drifts, searches silently return less.

**Generated is a property, not a directory.** A generated file lives wherever
its content belongs and declares itself in its own first line — source, command,
and the gate. Regenerate, then `git diff --exit-code`. The gate keys on the
declaration rather than on the location, which is the dominant idiom in the
wild; without it these files are hand-edited within a month and then lie with
the authority of something that looks machine-produced.

## Keep the routing table honest

`docs/index.md` maps *task → read this → then edit this*. It is the only
document allowed to be about other documents, and it holds no knowledge of its
own — detail written back into it is paid by every reader who did not need it.

One gate keeps it from rotting, `scripts/gates/check_docs_index.py`, and it
checks both directions: every file under `docs/` appears in the table, and
every path the table names exists. Ten lines of checking, and it catches
drift the week it happens rather than the quarter.

## Scope every document

Open with what it covers and what it does not. Two lines. It tells the next
writer where new material goes, which is the difference between a document that
stays focused and one that becomes the place things get appended to.

## Give the document a reading trigger: `Governs:`

Every kind above is defined by *why someone opened it*, and one trigger has no
natural home: **"I am about to change code this document describes."** Nobody
opens a document for that, because knowing to open it requires already knowing
it exists.

One line in the document's first 60 lines fixes it:

```markdown
# How billing works

Governs: src/billing/, src/payments/gateway.py

...
```

Plain text at the start of a line — despite being described elsewhere as
frontmatter, it needs no `---` fence and works anywhere in the head of the file.
Comma- or space-separated.

**Its justification is freshness, not navigation, and that changed on evidence.**
Navigation is unsupported: in a preregistered ablation the agent never loaded
the catalog at all — it inferred the path from the question and read the file
directly. Freshness is a different matter. Drift is not an occasional lapse but
the normal state (of 3,000+ repositories surveyed, most carried a reference to a
code element that no longer existed at some point in their history), and stale
prose is measurably harmful rather than merely useless — misleading natural
language around code degraded reasoning by 23.2%. Since a purely descriptive
document's measured benefit is near zero, a *stale* one has negative expected
value.

A declared pair is the only thing that makes that mechanically detectable: this
document claims to describe that path, so when the path moves and the document
does not, something can say so. It buys two things:

- **Delivery at the one instant it matters.** A `PreToolUse` hook
  (`scripts/context/before_write.py`) says *"docs/billing.md governs this path"*
  **before** the write, alongside any `.claude/rules/` scoped to it. Nothing
  else in the harness can do that — and two corrections are owed here, both
  measured. It ran on `PostToolUse` and delivered *after* the file was already
  written, which is the wrong half of the moment: creating a file is when a
  convention is worth most. And it printed to stdout, which on every event
  except `UserPromptSubmit`, `UserPromptExpansion` and `SessionStart` goes to
  the debug log — so for its whole life it delivered nothing to anybody. The
  channel is `hookSpecificOutput.additionalContext`.

  It advises; it does not prevent. Measured, the first write lands wrong and
  the agent corrects on the retry. A rule that must not be violated belongs in
  a guard, which can refuse.
- **A checkable claim.** *This document describes that code* is a pair a
  reader can compare, and the plugin's `/assess` reads the pairs whose code
  moved after the document did. Nothing in this repository runs that pass;
  it is an instrument, and instruments stay in the plugin.

Be honest about the standing of this: **no measured work exists on declared
doc-to-code relationships at all.** Of 70 major repositories surveyed, 8
declared anything comparable and several of those left the declaration empty or
scoped to everything; none verified it in CI. This is untested ground we chose
deliberately, not a practice we adopted.

Three rules that are easy to get wrong:

1. **Targets match by path segment, not by prefix.** `Governs: src/bill`
   covers `src/bill` and everything under `src/bill/`, and does *not* reach
   `src/billing_old/`. A trailing slash is allowed and changes nothing. This was
   once plain prefix matching, and the trailing slash was load-bearing; both
   readers now agree and an index selftest case holds them there.
2. **A target that resolves to nothing is drift, and is reported.** It lands in
   the generated index report as a dangling target. This is the one signal
   that catches a document still describing a path that was deleted — invisible
   from the document's side and from the code's side, both.
3. **Govern exactly what you describe.** The line is a claim that this
   document explains how that code is supposed to work. Pointing it at a whole
   `src/` makes every edit deliver a document that answers nothing, and then the
   hook's output stops being read.


## References

| File | Read when |
|---|---|
| `references/kinds.md` | Templates for the four directories, and what is not a directory |

Related skills: `writing-checks` (the gates named above),
`consolidating-notes` (merging accumulated notes into these kinds).

Files in this skill

  • SKILL.md15.7 KB
  • references/kinds.md9.5 KB

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.

Comments

Loading comments…

Newsletter

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