Code Documentation Skill
This skill governs how to write, improve, and maintain code documentation. Help the reader understand
what something does and how to use it, not how it is implemented.
Document at the element level. Every exposed element — type, function, target, value, field —
documents itself directly; never describe elements in an aggregate block above them. A section header
listing its targets, a type doc enumerating its variants, a struct doc itemizing its fields all put the
doc in the wrong place: the reader looks at the element and finds no comment, while the description sits
paragraphs above with all the others. This applies everywhere — pnpm scripts, enum values, interface
methods, struct fields.
What to Document
Document these file types:
- Language source files:
.go, .ts, .svelte, .sql, .sh, .bash, .proto, etc.
- General configuration files:
.yaml, .toml, .json (when they encode project logic, not tool config)
Do not document these:
- Tool-specific configuration files:
.golangci.yaml, .eslintrc, .prettierrc, renovate.json, etc.
Their documentation belongs to the tool, not the codebase.
- Auto-generated files (protobuf output in
protogen/, mocks, migrations generated by tooling).
For .proto files, the comment conventions on service, rpc, message, field, and enum values
belong to the write-proto skill: apply this skill's general principles (accuracy, no redundancy,
intent over mechanics) and follow write-proto for proto-specific comment style and placement.
For openapi.yaml, field descriptions, examples, and spec-level rules belong to the write-openapi
skill: apply this skill to prose quality, write-openapi to every spec-structural decision (status
codes, schema shape, breaking changes).
What deserves a comment at all
Most code deserves none. A comment must clear one of two bars:
- It describes the repo's own logic — type and method docs, package roles, domain
invariants (the "Documentation Priorities" below).
- It explains an unusual behavior specific to this repo — a constraint, workaround, or
ordering the reader cannot extract from the code in front of them.
Orientation is part of the first bar. In a long function or file, one line naming each logical
movement lets a contributor grasp the shape before reading it. Pace those deliberately: one orienting
line per movement, nothing inside it unless a line is genuinely surprising. Clustered or line-by-line
comments bury the signal as badly as none at all.
Everything else is noise. In particular:
- Platform-wide conventions don't get repeated per repo. A behavior that is unusual but holds
across the organization (e.g., compose files never declare sibling services because the tooling
resolves them) belongs ONCE in the tool that implements it or in the org-level documentation; copies
in each repo drift. If a reader could be surprised, fix the central doc, not the local file.
- The global picture belongs to the project docs. The architecture, the layer split, the reason
the system is shaped this way — README and CONTRIBUTING own that (see
write-project-docs); a code
comment re-explaining it duplicates a doc that then drifts. Code comments carry the local detail
those files can't: the invariant at the line that enforces it, why this call precedes that one.
- Nothing the code already says. If reading the surrounding lines answers the question, the
comment restates; delete it.
depends_on already shows ordering — only the why of a
surprising dependency is comment-worthy.
- No external dependencies. A comment must stay meaningful from the git history alone. Never
reference a PR, review thread, or temporary plan document. The one exception: linking an issue
for a patch or temporary workaround, where the issue is the tracking artifact. Write it as the
full URL —
https://github.com/<org>/<repo>/issues/<n> — and keep the marker short, ending it
with Tracked in <url>. In source a bare #N is not a hyperlink and names no repo, while a full
URL pastes straight from the editor into a browser. (On GitHub itself #N auto-links, so this
applies to code.)
The test, with "document the code as it is" below: a new contributor with only this repo checked out
today — does the comment teach them something true, local, and not already on their screen?
Prose economy
Maximize meaning per word. Every sentence carries an idea the reader needs — rationale first,
mechanics only when surprising. Exhaustive and terse are not opposites: cover everything that
matters, in as few words as it takes, and stop.
- Lead with why; the code already shows what.
- Cut hedges, preambles, and ceremony ("note that", "in order to", "it is important to").
- Stay sharp. Cut the sugar. Softening costs words and buys nothing: a comment is read by someone
who needs the fact, not reassurance. Drop the throat-clearing ("on purpose", "deliberately", "it is
worth noting", "the case that matters"), the emphasis that adds no information (
MUST, NEVER, and
the rest of the shouted words), and the build-up that delays the point ("two is nobody's intended
answer"). Assert the fact and move on.
- One precise sentence beats three approximate ones. If a comment reads well aloud, it ships.
- Default to plain words. Prefer the common word and the short one. Reach for an advanced or
technical word only when it earns its place — when it replaces a whole clause or removes a real
ambiguity.
- Write plain sentences, not labels. State the point as a subject-verb-object sentence and keep
that order; a fronted object or a cleft makes the reader unpack it first. A rhetorical label
("The reason:", "Note:", "Why this matters:") is ceremony — delete it and let the sentence carry
the idea: "This runs first because the cache is cold", not "Why this runs first: the cache."
- Give every layer one job. A heading makes the task easy to find, prose supplies context or a
decision rule, code carries the mechanics, and a comment records a hidden constraint. Do not restate
one fact across those layers or present a value derived from itself as verification. After
## Check the account and tool account, "This prints the account" adds nothing; say "Continue only
if it names the intended account." Delete any sentence, comment, or check whose removal loses no fact
or decision.
- Name a thing once, and keep the name. Use one term for one concept. Do not alternate synonyms
("the cache," then "the store") for the same thing, or the reader stops to check whether they differ.
An abstract label earns its place only when tied to something the reader can hold: "the intention" is
vague until it is "the specification behind your idea" — name it that, then carry "specification"
through. Where the domain already has a precise term, use it and resist paraphrasing it for warmth,
because a paraphrase can carry a connotation the precise term rules out: a "convoy" may lose
stragglers and still be one, where an "atomic landing" is all-or-none by name. When a name doubles as
a common word, name its category on first use — "a dedicated Tracking status" reads as a place on the
board where "Tracking" alone reads as an activity — and use it bare afterwards. When one concept spans
several concrete forms, name the umbrella too, and never reuse that word in its ordinary sense a line
later.
- Make every reference land on something the reader already holds. Keep a pronoun beside its one
possible antecedent: "comprehension and judgment are the whole job, so spend your attention there"
beats "so spend them there", where "them" reaches back past "the whole job" for its noun. Name earlier
content by a plain description rather than an abstract handle the reader has to decode; a handle earns
its place only once you have attached it to the thing.
- Concision is careful word choice, not dropped grammar. Keep sentences fully formed — subject and
verb — rather than clipping them to fragments. A doc that is short but cryptic has failed.
- Write the choice, not the rejected alternative. State what holds and stop. The contrast form
("X is this, not that", "we use A rather than B") spends a clause on something the reader was never
going to do, and leaves the rejected idea sitting in their head next to the real one. Write "Values
are stored as JSONB", not "Values are stored as JSONB, never EAV". Keep a counter-example only where
the wrong path is the one a reader would otherwise take; then give it once, plainly.
- A counterfactual is that same defect in other grammar. A sentence describing what would happen
under an implementation nobody wrote defends the code instead of describing it: "a fixed counter
would repeat them", "a nested transaction would inherit the caller's isolation", "a redacted
placeholder would still leak the length". State the property that holds — "Both must differ", "A
redaction still carries its length" — and drop the rest. Three uses of "would" survive this rule.
A previewed action the code shows without taking: "renderPruneImpact names the rulesets a plan's
prune op would delete" documents what a dry run displays. A condition that genuinely occurs:
"git refuses when incoming commits would clobber a locally-modified file" is git's real behavior and
the code branches on it. And the wrong-path counter-example above: "depend on the typed structs
declared here rather than reading os.Getenv directly" names the call a reader would otherwise reach
for. When the conditional describes what the reader is shown or a condition that occurs, keep it.
- Prefer affirmative sentences to clause chains. Give a rule its own sentence rather than trailing
it off another as "…, so X must Y", and split a chain of subordinate clauses into plain statements.
"…the MAC and ENC keys of an AES-CBC-HMAC key, so they must differ" becomes "…the MAC and ENC keys
of an AES-CBC-HMAC key. Both must differ." This is not license to shred prose into fragments — the
aim is statements a reader takes in one at a time, and the fully-formed-sentence rule above still
holds.
This applies to every prose surface we write — doc comments, package docs, README sections, PR
descriptions (see open-pull-request), and planning issues (see plan-feature). Load this section
before writing prose on any of them, including surfaces this skill does not otherwise own.
Documentation Priorities
1. Exported / public symbols (highest priority)
Write with the end-user in mind: the reader is using this API, not maintaining it.
- Describe what the symbol does and how to use it. Keep it relevant and direct.
- Do not explain internal implementation details.
- Do not restate the name or the obvious (e.g.,
// Foo returns foo. is useless).
- Every exported type needs a type-level doc, including parameter and option structs
(
FooRequest, FooOptions, etc.) — even when every field already has its own comment.
Field docs explain the fields; the type doc explains the purpose of the whole.
2. Unexported / internal symbols
Write with the maintainer in mind: the reader must grasp intent and usage well enough to modify it
safely.
- Same principles: what it does, how it's used — not how it works internally.
- Less critical than exported symbols; be more concise.
3. Inline technical comments
Only add inline comments when the logic is genuinely non-obvious. Ask: would a competent developer in
this language understand the line without one? If yes, skip it. Standard language constructs, external
library behavior, and restatements of the code are all noise — see "What to Avoid".
4. Interface vs. implementation
When a type has both an interface and a concrete implementation, document the interface thoroughly —
that is what users read. The implementation doc serves maintainers: keep it terse, or omit it when the
interface doc already covers everything. Never duplicate the same explanation across both.
5. Package-level documentation
When a package's purpose isn't self-evident, add a package-level doc comment explaining its
architectural role: where it sits in the codebase, what layer it belongs to, and how it relates to
adjacent packages. Avoid listing features — those change; the role usually doesn't.
Understand the package's place in the overall architecture before writing its doc. If you're uncertain
(is this a utility layer, a business-logic layer, something else?), ask the developer rather than
guessing. A wrong package doc is worse than none.
Each language has a canonical place for the package doc — always use it:
- Go: a dedicated
doc.go file at the package root — see the Go conventions below.
- Shell / other imperative files: the top-level comment block in the entry file (after the shebang).
Imperative Files (Scripts, Main Entrypoints)
For files that describe a sequence of actions (shell scripts, main.go, job runners, etc.):
Top-level package comment: explain what the file does as a whole.
- In Go: a package-level doc comment placed before
package main (or package foo), never a
comment on func main() — Go tooling and doc generators read that as a function doc, not the
package description.
- In shell: place it after the shebang (
#!/...) line.
- Keep it brief — one to three sentences. For
cmd/ packages with multiple entry points
(e.g., grpc vs. rest, dev vs. prod), always explain which role this particular command serves
and cross-reference the others.
Step comments: when the file has distinct logical phases, add a short comment before each to
orient the reader. If a phase hides a non-obvious sub-step (a multi-step dependency wiring chain,
say), add one line on that sub-group's purpose, not its mechanics.
Section separators: use comment blocks (e.g., # ---- Section Name ----) to visually separate
major logical steps when the file is long enough to benefit from it.
Language-Specific Conventions
Go
- Exported symbols: doc comment directly above the declaration, with the symbol name near the start —
as the plain subject (
// Foo holds...) or with a natural article (// A Foo holds...,
// The Foo interface...). Readers scanning go doc output orient on it, so never omit the name:
// Holds... and // This holds... are wrong.
- Package doc: library packages, always a dedicated
doc.go at the package root — never inline in a
source file, not the first file alphabetically, not a random one. Command packages (package main),
directly before package main in main.go — the idiomatic location; a doc.go is unusual there.
- Errors: always document exported error variables (
ErrFoo). Callers check them by identity, so say
what condition causes the error to be returned rather than restating the name. Say whether the error
is returned or only logged — the distinction matters to callers.
- Enums (typed string/int constants): the type doc says what the enum conceptually represents and how
it's used; never describe the values there. Document each constant individually, explaining what that
specific value means or selects.
- Interfaces: document the interface type and its methods. Implementations can reference the interface
instead of duplicating doc.
- Cross-references: use
[Symbol], [Type.Field], or [package.Symbol] godoc link syntax. Never link
to an unexported symbol from outside its package, or to any symbol in an internal package the
current package cannot import — neither resolves, and both signal inaccurate coupling. Describe the
relationship in prose instead.
- Language: Go returns errors — never write "throws", "raises", or "panics" when the code just returns an error.
Shell (bash/sh)
- Shebang first, then a top-level comment block explaining the script's purpose and usage.
- Use
# ---- Section ---- style separators for distinct phases.
- Comment variables when their purpose or expected format isn't obvious.
YAML (config files)
- Add inline comments to explain non-obvious fields, valid values, or important constraints.
- Do not comment fields that are self-explanatory from the key name.
TypeScript / Svelte
- JSDoc (
/** ... */) for exported functions, types, and components.
- For Svelte components: briefly describe the component's role at the top if it's not obvious.
Domain Concepts and Specialized Knowledge
Public APIs often work with domain concepts, serialization formats, or industry standards a competent
developer may not know. Explain the concept rather than assume familiarity, so a reader can use the API
without looking up external specs first.
Where and how to introduce concepts
Introduce a concept once, at its first appearance in the public API — typically the package doc
or the first exported type that depends on it. Later uses take the term for granted.
An introduction answers: what is it, and what does the caller do with it? One or two sentences are
usually enough. Name the standard (RFC, spec) that governs the format; the reader can look up the rest.
Serialized and encoded values
When a string, []byte, or other primitive field holds a structured or encoded value, document:
- What it contains semantically (e.g., "a signed JWT token")
- What format it uses (e.g., "compact JWT — base64url-encoded header.payload.signature")
- If non-obvious, how to produce or consume that value (e.g., "returned by the SignToken RPC")
Token string documented as // The JWT token. tells the caller nothing actionable;
// Token is the signed compact JWT (base64url header.payload.signature) returned by Sign. does.
Generic type parameters
When a type parameter must satisfy a domain contract, state that contract in terms the caller can act
on, not just "must match X". Common patterns:
- Serialization contract: "C must be JSON-serializable and match the struct embedded at signing time"
- Protocol contract: "T must implement proto.Message"
- Schema contract: "Row must have fields matching the SQL columns selected by this query"
Trust and security boundaries
When an API has security implications — what data leaves the process, which side holds secrets, what
operations happen locally vs. remotely — document them in the package doc or at the primary entry
point. Callers need this to reason about correctness and trust:
- "Signing is performed server-side; private keys never leave the service."
- "Verification is done locally using cached public keys — no network call per token."
- "The private key is generated and stored in memory only; it is never persisted."
Document the code as it is, not the change that produced it
A comment describes the code in its current form — never the edit, migration, or plan that created it.
The reader has no access to what the code used to be, and references to that past rot the moment the
change merges.
The test: if this were the first commit introducing the file — not a modification of something
already there — would you still write this comment? If not, delete it or rewrite it in the
present tense. This rules out:
- Change narrative: "now that X is removed", "the new model", "predates the redesign", "was
previously", "had to go", "dropped in the rewrite". State what the code does, not what it
stopped doing.
- Comparisons to deleted code: "matches the old bash script", "the Go port of
foo.sh", "same
default the script used". The thing compared against is gone; the comparison points at nothing.
- Pointers that send the reader outside the code: "per spec §6.4", "see PLAN.md", "see #50", "the
Stage-2 gate", "Gate-2 override". Transient ones rot when the branch merges; durable ones (an issue
or epic link) persist but still force the reader out of the file to reconstruct the rationale, and a
planning label like "Stage 2" goes meaningless once the work ships. Fold the rationale into the
comment itself and write it as if the plan that motivated the code never existed.
- Removal notes: "placeholder removed — see above", or any comment whose only content is that
something used to be here. Absence needs no monument.
Keep the rationale, drop the history. "Uses --no-deps to avoid podman-compose's broken
depends_on wait" is durable; "mirrors the lessons from the old CLI's --no-deps handling" carries
the same fact but only parses for someone who remembers the old CLI. The prior bar still applies:
comment only when the code doesn't supply the context — a durable comment that restates the code is
still noise.
A placeholder pending planned work is the one case where the future is worth naming — as a
self-contained TODO/FIXME on the provisional line, describing what is missing in its own terms
("TODO: always returns success; compute the real result"), not a prose comment that only parses with
the roadmap open. The surrounding doc still describes what the code does today, cleanly, as if the
placeholder were the intended design.
Sweeping comments across a codebase
A cleanup pass over existing comments — one repo or many — behaves differently from documenting new
code, and fails in its own ways.
Scope the check to the tree, not to your diff. Searching your own diff answers "did I change this
correctly?" It cannot answer "did I find everything?" The moment the bar tightens mid-sweep — a rule
added after reviewing the first pass — every comment the earlier pass deliberately left alone is
reclassified, and only a full re-scan surfaces them. Re-scan the whole tree whenever the rules change,
and measure the tree you are sweeping: a grep -r over a sibling checkout reports whatever
branch that checkout happens to be parked on, not your baseline.
A pattern search locates candidates; reading classifies them. Every mechanical scan is a proxy —
would, rather than, , not — and the worst findings match none of them. Change narrative
("the defect lived on the read path", "fixed in v1.4.5"), aggregate blocks describing the elements
below them, and docs that contradict the code are all found by reading the block the grep dragged into
view, never by the grep itself.
Expect to find comments that are wrong. Trimming forces someone to read the code underneath, which
is why a verbosity sweep routinely turns up inverted parameter descriptions, references to helpers
that no longer exist, and doc blocks attached to the wrong function. Those are the valuable output.
Fix the comment, leave the code, and call them out separately from the prose changes — a reviewer
skims rewording and scrutinizes a corrected claim.
Comments in a generating source must be regenerated. Editing a .proto comment changes the
generated Go, and the generated-go job fails on the drift. Run the repo's generate script and commit
the output as its own chore(gen) commit — git-conventions forbids mixing types, and a docs
commit carrying regenerated files hides the reason they changed.
A comment-only diff is not always a comment-only diff. Removing a trailing comment lets gofmt
re-align a struct or const block, and removing the comment above a single-element var (…) group
makes gofumpt collapse it. Both are the formatter asserting itself rather than an edit to the code, and
both belong in the PR description — an unexplained non-comment hunk costs a reviewer more than it saves.
Consistency and Quality
Wrong documentation is a liability. Every time you write or touch it:
- Verify accuracy: re-read the code and confirm the doc matches what it actually does. Watch
initialization claims ("pre-fetches", "caches on creation") — check the constructor to see whether
the behavior is eager or lazy.
- Check consistency: if a concept is described elsewhere (another file, the interface), use the
same terminology.
- Rephrase freely: fix awkward, redundant, or misleading doc rather than preserving it out of
caution. Rewrite entire sections if that's what it takes to make them clear.
- Respect scope: asked to document one function, don't refactor the whole file. Document what was
asked, fix obvious inconsistencies nearby, and stop there.
What to Avoid
- Redundant preambles:
// This function... — the symbol name already acts as the subject. Same for
field docs: // Tell the service to X when the field already implies a configuration value — state
the behavior directly.
- Doc comments that omit the symbol name entirely — see the Go conventions above.
- Sentence fragments in doc comments — every top-level doc (type, func, var) should be a complete
sentence. Field docs may be shorter, but avoid opening with a bare adjective like "Optional ..." or
"Required ..."; a noun phrase ("Validation overrides to apply") is clearer and still concise.
- Explaining how the language works (readers know Go, shell, etc.).
- Paraphrasing external APIs: the reader can look those up.
- Documenting obvious setters/getters unless there's a real invariant to explain.
- Noise comments that just make the file longer without adding information.
- Copying the same description from an interface down to the struct method — reference or omit instead.
- Tying a reusable component's docs to one caller's wiring: an action input, exported function, or
library parameter documents the contract — the capability it needs — not how one consumer supplies
it. Bad: "the AGENT_BOT_CLIENT_ID secret". Good: "a GitHub App with write access to Checks".
Naming a specific secret or env var couples the general component to one deployment and misleads the
next caller.
- Enumerating anything in a list — fields, parameters, behaviors, checks, steps, etc. Lists go stale
as soon as an item changes, and they push implementation details into docs that should describe intent.
Bad: "It validates signature, expiry, issuer, audience, and subject against the config."
Good: "It validates all token claims against the configuration registered for the given usage."
When an example helps, give one representative item, not a complete inventory.
- Section-level enumerations: describing elements in a block header above them — document each
element directly, as stated at the top of this skill.
- British English spelling: use American English throughout ("initialize" not "initialise",
"behavior" not "behaviour").
- Incorrect acronym casing: write acronyms as the language ecosystem conventionally styles them
(
JSON not Json, DAO not dao, gRPC not grpc, URL not url).
- Referencing inaccessible symbols in cross-package links — see the Go conventions above.
- All-caps emphasis words: avoid
MUST, SHOULD, OPTIONAL, NOT, etc. in prose documentation.
These RFC-style terms feel out of place in code docs; plain prose ("must", "only", "nil for...")
reads better.
- Contrastive framing where the positive alone would do:
// Stored, not derived,
// We use X rather than Y, // This is not a cache. State the choice instead:
// Stored at publish time. — see "Write the choice, not the rejected alternative" above.
- Counterfactuals defending the implementation:
// A fixed counter would repeat them,
// a nested transaction would inherit the caller's isolation. State the property that holds — see
"A counterfactual is that same defect in other grammar" above, including the three cases where a
conditional stays.
- A rule trailing off another sentence:
// …, so they must differ. Give it its own sentence —
see "Prefer affirmative sentences to clause chains" above.
- Rhetorical labels standing in for a sentence:
// Why this matters: ..., // Note: ...,
// The reason: ... — state the point plainly, as under "Write plain sentences, not labels" above.
- Change narrative and references to deleted code or transient docs — see "Document the code as
it is, not the change that produced it" above.
Process
- Read the file(s) fully before writing anything.
- Understand the context: for package-level doc, also look at adjacent packages (what layer is
this? how does it relate to its neighbors?). If the architectural role is unclear, ask the
developer before writing anything.
- Identify gaps: missing doc on exported symbols, stale/incorrect comments, package doc absent.
- Check existing doc: is it accurate? Consistent? Could it be said more clearly?
- Write or improve following the priorities above.
- Re-read the result: does every comment add value? Is it consistent with the rest of the file?
1---2name: document-code3description: Write and improve code documentation. Use whenever asked to document code, add comments, write a doc comment, annotate a function, document a package, explain a file's purpose, or clean up unclear or outdated comments — however phrased ("doc this"). Applies to Go, Bash/shell, YAML, Svelte, SQL, TypeScript, and other source or config files. Invoke before writing any documentation, even for one function.4---56# Code Documentation Skill78This skill governs how to write, improve, and maintain code documentation. Help the reader understand9**what** something does and **how to use it**, not how it is implemented.1011**Document at the element level.** Every exposed element — type, function, target, value, field —12documents itself directly; never describe elements in an aggregate block above them. A section header13listing its targets, a type doc enumerating its variants, a struct doc itemizing its fields all put the14doc in the wrong place: the reader looks at the element and finds no comment, while the description sits15paragraphs above with all the others. This applies everywhere — pnpm scripts, enum values, interface16methods, struct fields.1718---1920## What to Document2122Document these file types:2324- **Language source files**: `.go`, `.ts`, `.svelte`, `.sql`, `.sh`, `.bash`, `.proto`, etc.25- **General configuration files**: `.yaml`, `.toml`, `.json` (when they encode project logic, not tool config)2627Do **not** document these:2829- Tool-specific configuration files: `.golangci.yaml`, `.eslintrc`, `.prettierrc`, `renovate.json`, etc.30 Their documentation belongs to the tool, not the codebase.31- Auto-generated files (protobuf output in `protogen/`, mocks, migrations generated by tooling).3233For `.proto` files, the comment conventions on `service`, `rpc`, `message`, `field`, and `enum` values34belong to the **`write-proto` skill**: apply this skill's general principles (accuracy, no redundancy,35intent over mechanics) and follow write-proto for proto-specific comment style and placement.3637For `openapi.yaml`, field descriptions, examples, and spec-level rules belong to the **`write-openapi`38skill**: apply this skill to prose quality, write-openapi to every spec-structural decision (status39codes, schema shape, breaking changes).4041---4243## What deserves a comment at all4445Most code deserves none. A comment must clear one of two bars:46471. **It describes the repo's own logic** — type and method docs, package roles, domain48 invariants (the "Documentation Priorities" below).492. **It explains an unusual behavior specific to this repo** — a constraint, workaround, or50 ordering the reader cannot extract from the code in front of them.5152Orientation is part of the first bar. In a long function or file, one line naming each logical53movement lets a contributor grasp the shape before reading it. Pace those deliberately: one orienting54line per movement, nothing inside it unless a line is genuinely surprising. Clustered or line-by-line55comments bury the signal as badly as none at all.5657Everything else is noise. In particular:5859- **Platform-wide conventions don't get repeated per repo.** A behavior that is unusual but holds60 across the organization (e.g., compose files never declare sibling services because the tooling61 resolves them) belongs ONCE in the tool that implements it or in the org-level documentation; copies62 in each repo drift. If a reader could be surprised, fix the central doc, not the local file.63- **The global picture belongs to the project docs.** The architecture, the layer split, the reason64 the system is shaped this way — README and CONTRIBUTING own that (see `write-project-docs`); a code65 comment re-explaining it duplicates a doc that then drifts. Code comments carry the _local_ detail66 those files can't: the invariant at the line that enforces it, why this call precedes that one.67- **Nothing the code already says.** If reading the surrounding lines answers the question, the68 comment restates; delete it. `depends_on` already shows ordering — only the _why_ of a69 surprising dependency is comment-worthy.70- **No external dependencies.** A comment must stay meaningful from the git history alone. Never71 reference a PR, review thread, or temporary plan document. The one exception: linking an issue72 for a patch or temporary workaround, where the issue _is_ the tracking artifact. Write it as the73 full URL — `https://github.com/<org>/<repo>/issues/<n>` — and keep the marker short, ending it74 with `Tracked in <url>`. In source a bare `#N` is not a hyperlink and names no repo, while a full75 URL pastes straight from the editor into a browser. (On GitHub itself `#N` auto-links, so this76 applies to code.)7778The test, with "document the code as it is" below: a new contributor with only this repo checked out79today — does the comment teach them something true, local, and not already on their screen?8081---8283## Prose economy8485Maximize meaning per word. Every sentence carries an idea the reader needs — rationale first,86mechanics only when surprising. Exhaustive and terse are not opposites: cover everything that87matters, in as few words as it takes, and stop.8889- Lead with _why_; the code already shows _what_.90- Cut hedges, preambles, and ceremony ("note that", "in order to", "it is important to").91- **Stay sharp. Cut the sugar.** Softening costs words and buys nothing: a comment is read by someone92 who needs the fact, not reassurance. Drop the throat-clearing ("on purpose", "deliberately", "it is93 worth noting", "the case that matters"), the emphasis that adds no information (`MUST`, `NEVER`, and94 the rest of the shouted words), and the build-up that delays the point ("two is nobody's intended95 answer"). Assert the fact and move on.96- One precise sentence beats three approximate ones. If a comment reads well aloud, it ships.97- **Default to plain words.** Prefer the common word and the short one. Reach for an advanced or98 technical word only when it earns its place — when it replaces a whole clause or removes a real99 ambiguity.100- **Write plain sentences, not labels.** State the point as a subject-verb-object sentence and keep101 that order; a fronted object or a cleft makes the reader unpack it first. A rhetorical label102 ("The reason:", "Note:", "Why this matters:") is ceremony — delete it and let the sentence carry103 the idea: "This runs first because the cache is cold", not "Why this runs first: the cache."104- **Give every layer one job.** A heading makes the task easy to find, prose supplies context or a105 decision rule, code carries the mechanics, and a comment records a hidden constraint. Do not restate106 one fact across those layers or present a value derived from itself as verification. After107 `## Check the account` and `tool account`, "This prints the account" adds nothing; say "Continue only108 if it names the intended account." Delete any sentence, comment, or check whose removal loses no fact109 or decision.110- **Name a thing once, and keep the name.** Use one term for one concept. Do not alternate synonyms111 ("the cache," then "the store") for the same thing, or the reader stops to check whether they differ.112 An abstract label earns its place only when tied to something the reader can hold: "the intention" is113 vague until it is "the specification behind your idea" — name it that, then carry "specification"114 through. Where the domain already has a precise term, use it and resist paraphrasing it for warmth,115 because a paraphrase can carry a connotation the precise term rules out: a "convoy" may lose116 stragglers and still be one, where an "atomic landing" is all-or-none by name. When a name doubles as117 a common word, name its category on first use — "a dedicated Tracking status" reads as a place on the118 board where "Tracking" alone reads as an activity — and use it bare afterwards. When one concept spans119 several concrete forms, name the umbrella too, and never reuse that word in its ordinary sense a line120 later.121- **Make every reference land on something the reader already holds.** Keep a pronoun beside its one122 possible antecedent: "comprehension and judgment are the whole job, so spend your attention there"123 beats "so spend them there", where "them" reaches back past "the whole job" for its noun. Name earlier124 content by a plain description rather than an abstract handle the reader has to decode; a handle earns125 its place only once you have attached it to the thing.126- **Concision is careful word choice, not dropped grammar.** Keep sentences fully formed — subject and127 verb — rather than clipping them to fragments. A doc that is short but cryptic has failed.128- **Write the choice, not the rejected alternative.** State what holds and stop. The contrast form129 ("X is this, not that", "we use A rather than B") spends a clause on something the reader was never130 going to do, and leaves the rejected idea sitting in their head next to the real one. Write "Values131 are stored as JSONB", not "Values are stored as JSONB, never EAV". Keep a counter-example only where132 the wrong path is the one a reader would otherwise take; then give it once, plainly.133- **A counterfactual is that same defect in other grammar.** A sentence describing what _would_ happen134 under an implementation nobody wrote defends the code instead of describing it: "a fixed counter135 would repeat them", "a nested transaction would inherit the caller's isolation", "a redacted136 placeholder would still leak the length". State the property that holds — "Both must differ", "A137 redaction still carries its length" — and drop the rest. Three uses of "would" survive this rule.138 A **previewed action** the code shows without taking: "renderPruneImpact names the rulesets a plan's139 prune op would delete" documents what a dry run displays. A **condition that genuinely occurs**:140 "git refuses when incoming commits would clobber a locally-modified file" is git's real behavior and141 the code branches on it. And the wrong-path counter-example above: "depend on the typed structs142 declared here rather than reading os.Getenv directly" names the call a reader would otherwise reach143 for. When the conditional describes what the reader is shown or a condition that occurs, keep it.144- **Prefer affirmative sentences to clause chains.** Give a rule its own sentence rather than trailing145 it off another as "…, so X must Y", and split a chain of subordinate clauses into plain statements.146 "…the MAC and ENC keys of an AES-CBC-HMAC key, so they must differ" becomes "…the MAC and ENC keys147 of an AES-CBC-HMAC key. Both must differ." This is not license to shred prose into fragments — the148 aim is statements a reader takes in one at a time, and the fully-formed-sentence rule above still149 holds.150151This applies to every prose surface we write — doc comments, package docs, README sections, PR152descriptions (see `open-pull-request`), and planning issues (see `plan-feature`). Load this section153before writing prose on any of them, including surfaces this skill does not otherwise own.154155---156157## Documentation Priorities158159### 1. Exported / public symbols (highest priority)160161Write with the **end-user in mind**: the reader is using this API, not maintaining it.162163- Describe what the symbol does and how to use it. Keep it relevant and direct.164- Do not explain internal implementation details.165- Do not restate the name or the obvious (e.g., `// Foo returns foo.` is useless).166- **Every exported type needs a type-level doc**, including parameter and option structs167 (`FooRequest`, `FooOptions`, etc.) — even when every field already has its own comment.168 Field docs explain the fields; the type doc explains the purpose of the whole.169170### 2. Unexported / internal symbols171172Write with the **maintainer in mind**: the reader must grasp intent and usage well enough to modify it173safely.174175- Same principles: what it does, how it's used — not how it works internally.176- Less critical than exported symbols; be more concise.177178### 3. Inline technical comments179180Only add inline comments when the logic is genuinely non-obvious. Ask: would a competent developer in181this language understand the line without one? If yes, skip it. Standard language constructs, external182library behavior, and restatements of the code are all noise — see "What to Avoid".183184### 4. Interface vs. implementation185186When a type has both an interface and a concrete implementation, **document the interface thoroughly** —187that is what users read. The implementation doc serves maintainers: keep it terse, or omit it when the188interface doc already covers everything. Never duplicate the same explanation across both.189190### 5. Package-level documentation191192When a package's purpose isn't self-evident, add a package-level doc comment explaining its193**architectural role**: where it sits in the codebase, what layer it belongs to, and how it relates to194adjacent packages. Avoid listing features — those change; the role usually doesn't.195196Understand the package's place in the overall architecture before writing its doc. If you're uncertain197(is this a utility layer, a business-logic layer, something else?), **ask the developer** rather than198guessing. A wrong package doc is worse than none.199200Each language has a canonical place for the package doc — always use it:201202- **Go**: a dedicated `doc.go` file at the package root — see the Go conventions below.203- **Shell / other imperative files**: the top-level comment block in the entry file (after the shebang).204205---206207## Imperative Files (Scripts, Main Entrypoints)208209For files that describe a sequence of actions (shell scripts, `main.go`, job runners, etc.):2102111. **Top-level package comment**: explain what the file does as a whole.212 - In Go: a **package-level doc comment** placed before `package main` (or `package foo`), never a213 comment on `func main()` — Go tooling and doc generators read that as a function doc, not the214 package description.215 - In shell: place it after the shebang (`#!/...`) line.216 - Keep it brief — one to three sentences. For `cmd/` packages with multiple entry points217 (e.g., grpc vs. rest, dev vs. prod), always explain which role this particular command serves218 and cross-reference the others.2192202. **Step comments**: when the file has distinct logical phases, add a short comment before each to221 orient the reader. If a phase hides a non-obvious sub-step (a multi-step dependency wiring chain,222 say), add one line on that sub-group's purpose, not its mechanics.2232243. **Section separators**: use comment blocks (e.g., `# ---- Section Name ----`) to visually separate225 major logical steps when the file is long enough to benefit from it.226227---228229## Language-Specific Conventions230231### Go232233- Exported symbols: doc comment directly above the declaration, with the symbol name near the start —234 as the plain subject (`// Foo holds...`) or with a natural article (`// A Foo holds...`,235 `// The Foo interface...`). Readers scanning `go doc` output orient on it, so never omit the name:236 `// Holds...` and `// This holds...` are wrong.237- Package doc: library packages, always a dedicated `doc.go` at the package root — never inline in a238 source file, not the first file alphabetically, not a random one. Command packages (`package main`),239 directly before `package main` in `main.go` — the idiomatic location; a `doc.go` is unusual there.240- Errors: always document exported error variables (`ErrFoo`). Callers check them by identity, so say241 what condition causes the error to be returned rather than restating the name. Say whether the error242 is returned or only logged — the distinction matters to callers.243- Enums (typed string/int constants): the type doc says what the enum conceptually represents and how244 it's used; never describe the values there. Document each constant individually, explaining what that245 specific value means or selects.246- Interfaces: document the interface type and its methods. Implementations can reference the interface247 instead of duplicating doc.248- Cross-references: use `[Symbol]`, `[Type.Field]`, or `[package.Symbol]` godoc link syntax. Never link249 to an unexported symbol from outside its package, or to any symbol in an `internal` package the250 current package cannot import — neither resolves, and both signal inaccurate coupling. Describe the251 relationship in prose instead.252- Language: Go returns errors — never write "throws", "raises", or "panics" when the code just returns an error.253254### Shell (bash/sh)255256- Shebang first, then a top-level comment block explaining the script's purpose and usage.257- Use `# ---- Section ----` style separators for distinct phases.258- Comment variables when their purpose or expected format isn't obvious.259260### YAML (config files)261262- Add inline comments to explain non-obvious fields, valid values, or important constraints.263- Do not comment fields that are self-explanatory from the key name.264265### TypeScript / Svelte266267- JSDoc (`/** ... */`) for exported functions, types, and components.268- For Svelte components: briefly describe the component's role at the top if it's not obvious.269270---271272## Domain Concepts and Specialized Knowledge273274Public APIs often work with domain concepts, serialization formats, or industry standards a competent275developer may not know. Explain the concept rather than assume familiarity, so a reader can use the API276without looking up external specs first.277278### Where and how to introduce concepts279280Introduce a concept **once**, at its first appearance in the public API — typically the package doc281or the first exported type that depends on it. Later uses take the term for granted.282283An introduction answers: _what is it, and what does the caller do with it?_ One or two sentences are284usually enough. Name the standard (RFC, spec) that governs the format; the reader can look up the rest.285286### Serialized and encoded values287288When a `string`, `[]byte`, or other primitive field holds a structured or encoded value, document:289290- What it contains semantically (e.g., "a signed JWT token")291- What format it uses (e.g., "compact JWT — base64url-encoded header.payload.signature")292- If non-obvious, how to produce or consume that value (e.g., "returned by the SignToken RPC")293294`Token string` documented as `// The JWT token.` tells the caller nothing actionable;295`// Token is the signed compact JWT (base64url header.payload.signature) returned by Sign.` does.296297### Generic type parameters298299When a type parameter must satisfy a domain contract, state that contract in terms the caller can act300on, not just "must match X". Common patterns:301302- Serialization contract: "C must be JSON-serializable and match the struct embedded at signing time"303- Protocol contract: "T must implement proto.Message"304- Schema contract: "Row must have fields matching the SQL columns selected by this query"305306### Trust and security boundaries307308When an API has security implications — what data leaves the process, which side holds secrets, what309operations happen locally vs. remotely — document them in the package doc or at the primary entry310point. Callers need this to reason about correctness and trust:311312- "Signing is performed server-side; private keys never leave the service."313- "Verification is done locally using cached public keys — no network call per token."314- "The private key is generated and stored in memory only; it is never persisted."315316---317318## Document the code as it is, not the change that produced it319320A comment describes the code in its current form — never the edit, migration, or plan that created it.321The reader has no access to what the code used to be, and references to that past rot the moment the322change merges.323324The test: **if this were the first commit introducing the file — not a modification of something325already there — would you still write this comment?** If not, delete it or rewrite it in the326present tense. This rules out:327328- **Change narrative**: "now that X is removed", "the new model", "predates the redesign", "was329 previously", "had to go", "dropped in the rewrite". State what the code does, not what it330 stopped doing.331- **Comparisons to deleted code**: "matches the old bash script", "the Go port of `foo.sh`", "same332 default the script used". The thing compared against is gone; the comparison points at nothing.333- **Pointers that send the reader outside the code**: "per spec §6.4", "see PLAN.md", "see #50", "the334 Stage-2 gate", "Gate-2 override". Transient ones rot when the branch merges; durable ones (an issue335 or epic link) persist but still force the reader out of the file to reconstruct the rationale, and a336 planning label like "Stage 2" goes meaningless once the work ships. Fold the rationale into the337 comment itself and write it as if the plan that motivated the code never existed.338- **Removal notes**: "placeholder removed — see above", or any comment whose only content is that339 something used to be here. Absence needs no monument.340341Keep the rationale, drop the history. _"Uses `--no-deps` to avoid podman-compose's broken342`depends_on` wait"_ is durable; _"mirrors the lessons from the old CLI's `--no-deps` handling"_ carries343the same fact but only parses for someone who remembers the old CLI. The prior bar still applies:344comment only when the code doesn't supply the context — a durable comment that restates the code is345still noise.346347A **placeholder** pending planned work is the one case where the future is worth naming — as a348self-contained `TODO`/`FIXME` on the provisional line, describing what is missing in its own terms349(_"TODO: always returns success; compute the real result"_), not a prose comment that only parses with350the roadmap open. The surrounding doc still describes what the code does _today_, cleanly, as if the351placeholder were the intended design.352353---354355## Sweeping comments across a codebase356357A cleanup pass over existing comments — one repo or many — behaves differently from documenting new358code, and fails in its own ways.359360**Scope the check to the tree, not to your diff.** Searching your own diff answers "did I change this361correctly?" It cannot answer "did I find everything?" The moment the bar tightens mid-sweep — a rule362added after reviewing the first pass — every comment the earlier pass deliberately left alone is363reclassified, and only a full re-scan surfaces them. Re-scan the whole tree whenever the rules change,364and measure the tree you are sweeping: a `grep -r` over a sibling checkout reports whatever365branch that checkout happens to be parked on, not your baseline.366367**A pattern search locates candidates; reading classifies them.** Every mechanical scan is a proxy —368`would`, `rather than`, `, not ` — and the worst findings match none of them. Change narrative369("the defect lived on the read path", "fixed in v1.4.5"), aggregate blocks describing the elements370below them, and docs that contradict the code are all found by reading the block the grep dragged into371view, never by the grep itself.372373**Expect to find comments that are wrong.** Trimming forces someone to read the code underneath, which374is why a verbosity sweep routinely turns up inverted parameter descriptions, references to helpers375that no longer exist, and doc blocks attached to the wrong function. Those are the valuable output.376Fix the comment, leave the code, and call them out separately from the prose changes — a reviewer377skims rewording and scrutinizes a corrected claim.378379**Comments in a generating source must be regenerated.** Editing a `.proto` comment changes the380generated Go, and the `generated-go` job fails on the drift. Run the repo's generate script and commit381the output as its own `chore(gen)` commit — `git-conventions` forbids mixing types, and a `docs`382commit carrying regenerated files hides the reason they changed.383384**A comment-only diff is not always a comment-only diff.** Removing a trailing comment lets gofmt385re-align a struct or const block, and removing the comment above a single-element `var (…)` group386makes gofumpt collapse it. Both are the formatter asserting itself rather than an edit to the code, and387both belong in the PR description — an unexplained non-comment hunk costs a reviewer more than it saves.388389---390391## Consistency and Quality392393Wrong documentation is a liability. Every time you write or touch it:394395- **Verify accuracy**: re-read the code and confirm the doc matches what it actually does. Watch396 initialization claims ("pre-fetches", "caches on creation") — check the constructor to see whether397 the behavior is eager or lazy.398- **Check consistency**: if a concept is described elsewhere (another file, the interface), use the399 same terminology.400- **Rephrase freely**: fix awkward, redundant, or misleading doc rather than preserving it out of401 caution. Rewrite entire sections if that's what it takes to make them clear.402- **Respect scope**: asked to document one function, don't refactor the whole file. Document what was403 asked, fix obvious inconsistencies nearby, and stop there.404405---406407## What to Avoid408409- Redundant preambles: `// This function...` — the symbol name already acts as the subject. Same for410 field docs: `// Tell the service to X` when the field already implies a configuration value — state411 the behavior directly.412- Doc comments that omit the symbol name entirely — see the Go conventions above.413- Sentence fragments in doc comments — every top-level doc (type, func, var) should be a complete414 sentence. Field docs may be shorter, but avoid opening with a bare adjective like "Optional ..." or415 "Required ..."; a noun phrase ("Validation overrides to apply") is clearer and still concise.416- Explaining how the language works (readers know Go, shell, etc.).417- Paraphrasing external APIs: the reader can look those up.418- Documenting obvious setters/getters unless there's a real invariant to explain.419- Noise comments that just make the file longer without adding information.420- Copying the same description from an interface down to the struct method — reference or omit instead.421- **Tying a reusable component's docs to one caller's wiring**: an action input, exported function, or422 library parameter documents the _contract_ — the capability it needs — not how one consumer supplies423 it. _Bad_: _"the AGENT_BOT_CLIENT_ID secret"_. _Good_: _"a GitHub App with write access to Checks"_.424 Naming a specific secret or env var couples the general component to one deployment and misleads the425 next caller.426- **Enumerating anything in a list** — fields, parameters, behaviors, checks, steps, etc. Lists go stale427 as soon as an item changes, and they push implementation details into docs that should describe intent.428 _Bad_: _"It validates signature, expiry, issuer, audience, and subject against the config."_429 _Good_: _"It validates all token claims against the configuration registered for the given usage."_430 When an example helps, give one representative item, not a complete inventory.431- **Section-level enumerations**: describing elements in a block header above them — document each432 element directly, as stated at the top of this skill.433- **British English spelling**: use American English throughout ("initialize" not "initialise",434 "behavior" not "behaviour").435- **Incorrect acronym casing**: write acronyms as the language ecosystem conventionally styles them436 (`JSON` not `Json`, `DAO` not `dao`, `gRPC` not `grpc`, `URL` not `url`).437- **Referencing inaccessible symbols in cross-package links** — see the Go conventions above.438- **All-caps emphasis words**: avoid `MUST`, `SHOULD`, `OPTIONAL`, `NOT`, etc. in prose documentation.439 These RFC-style terms feel out of place in code docs; plain prose ("must", "only", "nil for...")440 reads better.441- **Contrastive framing where the positive alone would do**: `// Stored, not derived`,442 `// We use X rather than Y`, `// This is not a cache`. State the choice instead:443 `// Stored at publish time.` — see "Write the choice, not the rejected alternative" above.444- **Counterfactuals defending the implementation**: `// A fixed counter would repeat them`,445 `// a nested transaction would inherit the caller's isolation`. State the property that holds — see446 "A counterfactual is that same defect in other grammar" above, including the three cases where a447 conditional stays.448- **A rule trailing off another sentence**: `// …, so they must differ`. Give it its own sentence —449 see "Prefer affirmative sentences to clause chains" above.450- **Rhetorical labels standing in for a sentence**: `// Why this matters: ...`, `// Note: ...`,451 `// The reason: ...` — state the point plainly, as under "Write plain sentences, not labels" above.452- **Change narrative and references to deleted code or transient docs** — see "Document the code as453 it is, not the change that produced it" above.454455---456457## Process4584591. **Read the file(s)** fully before writing anything.4602. **Understand the context**: for package-level doc, also look at adjacent packages (what layer is461 this? how does it relate to its neighbors?). If the architectural role is unclear, **ask the462 developer** before writing anything.4633. **Identify gaps**: missing doc on exported symbols, stale/incorrect comments, package doc absent.4644. **Check existing doc**: is it accurate? Consistent? Could it be said more clearly?4655. **Write or improve** following the priorities above.4666. **Re-read the result**: does every comment add value? Is it consistent with the rest of the file?