comment-quality · v1.0.1 · 2026-09-28 · sha256 ee5b0a7cc6def7e5
comment-quality v1.0.1A
Immutable. This exact content never changes and is served at /api/v1/blob/ee5b0a7cc6def7e5.
---
name: comment-quality
description: "Use when comments or docstrings drift from the code: stale, retracted, or lacking WHY."
version: 1.0.1
author: code-hygiene contributors
license: MIT
platforms: [linux, macos, windows]
metadata:
hermes:
tags: [stale, why, useless, misleading, retracted, docstring, removed]
related_skills: [code-structure, changelog-quality, upstream-hygiene]
---
# Comment Quality
<!-- GC-20 --> Comments explain WHY code does this, not WHAT the next line does.
## Rules
### GC-20 — WHY not WHAT, density
- "/* increment counter */" → delete (code says WHAT, comment must say WHY).
- Multi-paragraph essay / numbered plan comment → compress to 2-8 line WHY block. Link to issue/pr for long history, not in comment.
- Doc scaffolding `/** @param` on private/internal helper → downgrade to plain `/*` WHY block or delete. Reserve full doc for exported public APIs. Grep for `name - summary` + `@param:` to catch — scaffolding appears under both markers.
- 50-word para cap aim (see changelog GC-12) applies to comments too.
### GC-21 — Placement, one source of truth at definition
- Doc comment lives at definition, not prototype/header when they differ. Header can have brief one-liner or none — deep WHY at def.
- Helper inserted? Doc left describing wrong function after insert still compiles — read for it.
### GC-22 — Subtle logic MUST have WHY
Genuinely subtle logic with no WHY comment is a bug. Check:
- Locking / ordering / lifetime / invariant / barriering / ownership transfer.
- Security / validation decision: why safe here but not elsewhere.
- Concurrency: who serializes, what can race, what barrier pairs where.
If you can answer "why not simpler?" you need a comment.
### GC-23 — Comment contradicted by a code change → rewrite in the same diff
Stale comment is worse than none, and a stale DOCSTRING is worse than a stale
comment: a comment narrates, a docstring makes a contract — one naming a
parameter, return key or field the code no longer has reads as an invitation to
call something that is not there. Rank the drift by what the text claims, not by
how close it sits to the edit.
- **Radius is the identifier, not the neighbourhood.** Removing a key, field,
flag, function or config name is a rename in reverse: grep the WHOLE tree for
that name — code, tests, docs, docstrings, comments — and settle every hit in
the same commit (`change-splitting` GC-36 owns the completion evidence). The
survivors are the ones no compiler reports: a docstring in the file you edited,
or a comment in ANOTHER module that justified its own field by the removed thing.
- **Write what is true now, not what was true.** The replacement says why the code
does what it does today; the retraction is history and belongs in the changelog,
where a reader looking for it goes. GC-24 already bans provenance in shipped
comments — a retraction note is provenance. A comment whose only justification
was the removed thing is rewritten to that thing's own contract, or deleted.
### GC-24 — Public-only context
Per `upstream-hygiene`: no private infra, no internal mount paths, no "this is like our fix in X" cross-project lore. If comment needs external pairing, name only upstream public symbols, e.g. "Pair with update_cache() — must hold lock per contract in cache.c".
### GC-25 — Comment removal is cleanup, not fix — separate drive-by
Removing a long-wrong comment mixed with logic change inflates diff. Do: separate commit "Remove stale comment — no behavior change" or fold only when that stale comment explains logic you are changing now.
## Anti-patterns
| Bad | What to do |
|-----|-----------|
| `i++; // increment i` | delete |
| 20-line plan comment explaining iteration history | compress to invariant + Link to pr/issue |
| `/** This helper does X @param y */` on static private | `/* X because Y */` or delete if obvious |
| Comment says "see jira IN-123" | public link or gist: "See #123" |
| Comment describes other project's fix | rewrite for this project's invariants |
## Positive framing for comments too
Applies from generic-principles "Positive framing". Comment that says "This is not kernel code" or "This is not X" is defining by negation — rewrite to what it IS and its contract. Provenance ("was previously ...") belongs in commit message or docs/references, not in code comment that ships.
## Verification
Use placeholder tokens for verification examples (self-dogfoods upstream-hygiene).
```bash
# Stale pattern: probe nearby file for contradictory TODO
git diff --unified=0 | grep -A2 -B2 "TODO\|XXX\|HACK"
# Private leakage — real scanner lives in private overlay; skeleton here uses placeholder marker
git diff HEAD 2>/dev/null | grep -E "^\+\s*(//|#|/\*|\*)" | grep -i "example\.invalid" && echo "REVIEW: placeholder marker still present — strip or rewrite" || echo OK
```
## Related
- `code-structure` owns helper extraction — this skill owns what comment moves with it.
- `upstream-hygiene` owns banned classes — this skill references it for examples.
- `generic-principles` Positive framing — where tagline rule encoded.
- `project-discovery` — sample the changesets of the files you touch for the project's comment style, terminology, and length.