tend-prose · diff
git:20261002.b41571c to git:20261013.4e5c041
18 added, 15 removed. Audit B to B.
---
description: >-
Review the prose you added in recent work — code comments, docstrings, and
Markdown — against four equal defects: **existence** (it should not have been
written at all), **durability** / **narration** (it narrates the change
instead of the code's lasting contract), **tightness** / **bloat** (it spends
more words than it informs), and **negation** / **polar bear** (it denies a
thing the change removed, so the mention survives as its own denial). Rewrites,
moves and cuts in place. Invoke as: /tend-prose [lens] [optional focus
guidance] — naming one of the four, by either of its names, runs only that
lens.
---
You are reviewing prose _you_ recently added — code comments, docstrings, and
Markdown (skill bodies, `docs/`, READMEs) — for four defects that carry
**equal weight**:
- **T**ightness — text that spends more words than it informs: it
restates what the function name, signature, and types already convey, or
stretches a real point past the length it needs. CLAUDE.md's docstring rule:
document only the non-obvious contract (side effects, runtime constraints,
cross-boundary coupling, don't-change-this traps).
- **E**xistence — text that should not have been written: a description of how
the code works rather than a constraint on it, something a reader recovers from
the code itself, or a real constraint filed where nothing will load it.
- **N**egation — text whose subject is a thing the change removed, surviving as
its own denial instead of being removed altogether. It is written in clean
present tense, so it reads as a constraint and no change verb fires. The idiom
is *don't think about the polar bear*.
- **D**urability — text that describes the change that produced the code rather
than the code's durable behaviour, whether it narrates against the state before
the branch or against an earlier step inside it. CLAUDE.md: "Comments describe
the code's lasting contract, not the change that produced it."
They are listed in the order that spells **TEND**, which is a memory aid only.
Step 2 has no order — it applies all four to each line in one pass — and Step 3
fixes in the opposite direction from this list: deletions before rewrites before
trims, so you never shorten a sentence you are about to cut.
The output is **edits, not a report**. Fix the clear cases in place; only ask
about genuinely ambiguous ones.
This skill is the long version of CLAUDE.md § "Writing things down", and the only
home for these rules.
## Single-lens mode
The argument may **name one lens**, optionally followed by focus guidance:
`/tend-prose durability`, `/tend-prose tightness src/db`, `/tend-prose polar
bear`.
- | Lens | Primary | Alias |
- | ---- | ------------ | ------------- |
- | 1 | `existence` | — |
- | 2 | `durability` | `narration` |
- | 3 | `tightness` | `bloat` |
- | 4 | `negation` | `polar bear` |
+ 1. `existence`
+ 2. `durability`, alias `narration`
+ 3. `tightness`, alias `bloat`
+ 4. `negation`, alias `polar bear`
Then run **only** that lens: skip the other three in Step 2 and apply only its
fixes in Step 3. This is the mode for "I just saw narration in there", and for a
bare `polar bear here` on a PR comment — a targeted pass, not the full sweep.
With no lens named, all four run.
## When to use
- After an autonomous run (several commits since the operator last engaged),
before handing back — all four defects accumulate step-to-step.
- When the scoped work **removed** a module, file, field or concept that prose
described. That is the condition lens 4 exists for, and the only one that puts
a defect in prose nobody rewrote.
- Any time you notice a comment reads like a diff note, or a docstring restates
its own signature. If that is the whole complaint, name the lens.
- Skip if recent work added no prose (pure logic/test edits with no new comments).
## Step 1: Scope to what you added since the operator last engaged
In order:
1. **Uncommitted changes**: `git diff HEAD` + untracked files (`git status --short`).
2. **Commits since upstream / since the operator's last turn**: `git log --oneline @{u}..HEAD`, or the SHA range the session summary names. `git diff <range>` is the scope.
3. **Nothing matches** → tell the user there's nothing to review and stop.
Only the **added/changed** prose lines are in scope for lenses 1–3 — don't
rewrite pre-existing comments you didn't touch. Lens 4 is the exception, and
reads the other side of the same diff:
**Collect the removed nouns** once the range is fixed. From the removed lines
(`git diff <range>`) and the deleted files (`git diff --diff-filter=D
--name-only <range>`), list what the change took away: identifiers, file paths,
field and type names, and named concepts. Grep each against the post-change
tree. **A name that survives only inside prose is a lens 4 candidate** — the
code no longer has it, so any sentence still about it is about nothing.
**Skip this sub-step when the diff removes nothing** — most additive work — so
the ordinary pass pays nothing for the lens.
## Step 2: Read each added prose line and ask all four questions
A line can fail any of the four, or several. Work through the diff **once**,
applying all four lenses to each comment — not one traversal per lens. (In
single-lens mode, only the named one.) Lens 4 also ranges over prose the diff
did not touch, since a sentence that survived a removal is exactly the one
nobody rewrote.
### Lens 1 — should it exist at all?
The default is not to write it. Prose costs context on every session that loads
it, and it goes stale invisibly — a constraint survives a refactor, a description
of how the constraint works does not.
Keep it only when **all three** hold:
1. It is a **constraint or an accepted cost**, not a description of how the code
works.
2. It is **not recoverable** from the code and its docstrings by someone reading
them — _or_ recovering it means taking in more modules and flows at once than
anyone holds in their head, where a paragraph gets there faster. Even then, the
candidate is a **docstring at the topmost point of the code**, not a separate
file.
3. **Getting it wrong breaks something** — you can name the specific way a future
editor goes astray without it.
Then check it is in the right home, and **move it if it isn't** — a rule that only
one call site can violate is a comment; a page nobody's glob reaches is nothing.
The homes, in order of preference — take the first that fits:
- | # | Home | What lives there |
- | --- | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
- | 1 | **A comment at the call site** | A trap that lives at one line |
- | 2 | **A docstring at the topmost point of the code it describes** | How a flow works, when following it means holding more modules in your head than fit at once |
- | 3 | **The skill or hook whose run it governs** | A step, or a rule that only binds while that process runs |
- | 4 | **The directory's own `CLAUDE.md`** | An obligation every edit in that directory's files must respect, loaded on the first read there |
- | 5 | **`.claude/rules/<area>.md`** (frontmatter `paths:`) | The same, for files no single directory bounds — `'**/*.test.ts'`, several scattered paths |
- | 6 | **A colocated `README.md`** | The big picture, for when not having it is itself what blocks the edit |
- | 7 | **A runbook page** (`docs/runbook/*.md`, or the project's equivalent) | A procedure a human executes |
+ 1. **A comment at the call site** — a trap that lives at one line.
+ 2. **A docstring at the topmost point of the code it describes** — how a flow
+ works, when following it means holding more modules in your head than fit at
+ once.
+ 3. **The skill or hook whose run it governs** — a step, or a rule that only
+ binds while that process runs.
+ 4. **The directory's own `CLAUDE.md`** — an obligation every edit in that
+ directory's files must respect, loaded on the first read there.
+ 5. **`.claude/rules/<area>.md`** (frontmatter `paths:`) — the same, for files no
+ single directory bounds — `'**/*.test.ts'`, several scattered paths.
+ 6. **A colocated `README.md`** — the big picture, for when not having it is
+ itself what blocks the edit.
+ 7. **A runbook page** (`docs/runbook/*.md`, or the project's equivalent) — a
+ procedure a human executes.
**A line in the root `CLAUDE.md` answers one question before any of these**:
CLAUDE.md § "About this file"'s test, since that file loads on every turn. A line
that fails it moves to the first home above that loads when its process runs.
**The alternatives already live in the PR or issue thread, and any of these
homes can cite it.** Add a `#1234` beside the line where a reader would otherwise
stop and wonder "hmm, why this and not the obvious thing?"; leave it off where
nobody would ask, because a citation on an unsurprising line is one more thing to
chase.
**Rule or README?** A rule — homes 4 and 5 alike — is what every edit in the
files it covers must respect _whether or not the editor went looking_ — an obligation you can violate without
noticing. It loads automatically, so it must stay short and always-relevant. A
README carries the part the code cannot: **why** the arrangement is this one,
where reading the modules end to end still leaves the reason obscure. It is
opened on purpose, by someone who has already noticed they are lost, and it runs
as long as that explanation takes. **The default is a rule**; reach for a README
when the obscure _why_ is the thing worth writing, and spending every matched
edit's context on it would be the wrong trade.
**Never create a new top-level doc without asking the operator** — and when you
ask, argue the "don't" side. A top-level doc is the one home nothing scopes, so
every later session pays for it; an operator waving it through is easy to get and
is not the same as it being right. If nothing in the list fits, that is usually
the signal it does not need writing at all.
**Touching documented code is not a reason to edit the doc.** Edit a rule when
the _constraint_ changed — not when code near it moved, was renamed, or grew a
feature. A rule that names a function, module path or column is already suspect:
name it only when the rule is _about_ that name.
### Lens 2 — is it narration?
A line is narration when it only makes sense to a reader who knows an earlier
state of the code — the state before the branch, or a step inside it. Tells:
- Change verbs anchored to the past: **"no longer"**, **"now also"**, **"used to"**,
**"previously"**, **"migrated from"**, **"this used to…"**, **"as of this change"**, **"renamed from"**.
- **"for now" / "temporarily" / "once X lands"** — situational hedges that go stale.
- References to a prior step, ticket, or version as if the reader is watching the diff.
The test: **would this sentence still be true and useful to someone reading the
final code a year from now, with no memory of the edit?** If it only informs
"what changed", it's narration.
**The special case is narration against an intermediate step inside the branch.**
In shipped prose it is the same defect as the rest of the lens. It earns a name
because it is the half that survives in the documents Step 4 excludes: a commit
body is *meant* to narrate the branch against the state before it, and still has
no business reporting what a draft became on the way there
(`@.claude/skills/squash-message/SKILL.md` owns that pass).
### Lens 3 — is it bloat?
A line is bloat when it costs a reader more than it tells them — either they
already had the fact from the name, signature, and types, or the fact is real
but buried in twice the prose it needs. Tells:
- **Restates the signature** — names the params or return type, or paraphrases the
function name (`/** Fetches the user by id. */` over `fetchUserById(id: string)`).
- **Narrates the next lines of code** — "Loop over the items and set the flag",
"If the list is empty we skip".
- **Restates a type's or enum's members** in prose sitting next to the declaration.
- **Ceremony** — "This function is responsible for…", "Helper that…", "Note that…"
wrapped around one actual fact.
- **A docstring block on something that crosses no boundary** — the contract is
already obvious from name + types, so the block is decoration.
- **Length**: **> 4 lines** is almost certainly too much; find the one or two facts
that aren't self-evident and keep only those. Even exactly 4 is too much unless
it's a proper docstring block on a function/class/etc. **A good inline clarification
fits ~2 lines.** The point is often real and the prose still too long:
- "We await this before returning, because otherwise the transaction may still be
open by the time the caller commits, which can deadlock under concurrent writes."
→ "Await before returning — an open transaction here deadlocks concurrent writes."
- "It's worth noting that this cache can only be used on the server, since the key
includes the workspace id and the browser bundle has no access to it."
→ "Server-only: the cache key needs the workspace id."
- **Markdown**: an added paragraph that re-explains what the adjacent bullet or code
block already shows; a "Note:" restating the rule directly above it.
### Lens 4 — is it about a thing that isn't there?
When a change removes something the prose described, the reflex is to **negate
the sentence in place** rather than delete it. The mention survives as its own
denial, and every later reader pays for a thing that is not there. Tells:
- **A subject from Step 1's removed-noun list.** The name survives only inside
prose; grep says the tree no longer has it.
- **A negated predicate in clean present tense** — "X is not a Y", "nothing
generates X", "X is not a segment". No change verb, which is exactly why lens
2 waves it through. Step 3's negator sweep is its search.
The test: **with the sentence gone, would its subject have crossed the reader's
mind at all?** If the sentence is the only reason they are thinking about the
thing, the sentence is what put the bear there — which is Dostoevsky's point in
*Winter Notes on Summer Impressions*: set yourself the task of not thinking
about a polar bear, and the cursed thing comes to mind every minute. Denying a
removed thing sets the reader that task.
**The discriminator — constraint or residue.** This is where the lens
over-deletes if you run it on the tells alone, because a codebase's prose is
properly full of negative rules. Apply the test and the two separate cleanly: a
constraint's subject is something the reader reaches for **anyway**, so it
crosses their mind with or without the sentence.
- *"Dev artifacts go under gitignored `tmp/`, not as new `.gitignore` entries."*
— a **constraint**. The next editor reaches for a `.gitignore` line whether or
not anything was ever removed. Keep it.
- *"Copy is not a segment — every string sits in the module that renders it."* —
**residue**, if the copy catalogue was just deleted. Nobody reading the current
tree would propose a catalogue; the sentence exists to answer the draft that
had one. Cut it.
Same grammar, opposite verdicts: residue guards against something a previous
draft did, which no longer threatens anyone.
## Step 3: Fix
**Existence:**
- **Delete** when the line describes how the code works, or restates something a
reader recovers from the code — that is the most common outcome and it is a
clean one.
- **Move** when the constraint is real but mis-homed: a rule only one call site
can violate becomes a comment there; a paragraph of orientation in a rule
becomes a colocated README; a procedure becomes a runbook page. State it once in
its new home rather than in both.
- **Keep** a named trap beside the thing it traps, and a decision's alternatives
in the PR thread with a `#1234` from the docstring that needs them.
**Narration:**
- **Rephrase to a present-tense property** when the underlying fact is durable:
"The emoji is no longer parsed" → "Nothing parses the emoji." "Now also sets
`x`" → "Sets `x`."
- **Delete** when the note carried no lasting information beyond the change
itself ("Migrated from the old helper", "Renamed for clarity").
- **Keep** genuine forward-references to still-pending work (e.g. "§F2 is
deferred") and durable rationale phrased in the present ("kept in sync by
hand because…"). These are contracts, not changelog.
**Bloat:**
- **Cut to the non-obvious facts** — the side effect, the runtime constraint, the
coupling, the trap. Drop every clause the name and types already carry.
- **Downgrade a docstring block to a one-line inline comment inside the function**
when its only real purpose is a don't-break-this warning — CLAUDE.md says the
dev editing the code will see it there, and that's enough.
- **Delete entirely.** This is a normal outcome and often the right one; a comment
the code already makes true earns nothing by being shortened.
- If you can't say it in ~2 lines, question whether the **code** should be clearer
rather than the comment longer.
**Negation:**
- **Delete.** A sentence whose whole content is that something is absent has
nothing to restate positively, so this lens cuts where the others rewrite.
- **Keep the fact, drop the denial** where the mention carries a live fact
alongside it. Rephrase as what *is* there: "The mark is not generated —
`public/aeapp-mark.svg` is committed" → "**One committed
`public/aeapp-mark.svg` serves every consumer**", the page, the favicon and the
Open Graph card alike.
- **Keep** a negative sentence that passes the constraint-vs-residue
discriminator. A rule against a temptation the reader has anyway is a
constraint, and cutting it is the false positive this lens must not produce.
**Resolve in this order** — existence, negation, durability, tightness (1, 4, 2,
3): the two deletion verdicts first, so no rewrite or trim is ever spent on a
line that is about to go.
**Then sweep the result for negators.** The order above runs lens 4 before the
lens that manufactures bears: durability's own fix turns "the emoji is no
longer parsed" into "nothing parses the emoji". So once every fix is applied, grep
the **resulting** text of each prose line in scope — every added line, plus
whatever lens 4 read outside the diff:
```bash
git diff -U0 <range-start> -- <files> | grep '^+' \
| grep -niE "\b(not|no|nothing|none|neither|nor|never|nobody|without|cannot)\b|n't\b"
```
`<range-start>` against the working tree, so the uncommitted fixes are what gets
read. Only comments and Markdown prose count: a flag (`--no-sandbox`), a string
literal or a code span is code, and code is never a bear.
**The grep proposes; the discriminator decides.** Most negative sentences in a
codebase are constraints, so every hit goes through the constraint-vs-residue
test above, and only residue is fixed, by the negation fixes. A `no longer` hit is a durability miss: rephrase it
there, then put the rephrasing through the same test.
The sweep belongs to lens 4 and runs wherever it does. It also runs after a
`durability` single-lens pass, over the lines that pass rewrote.
## Step 4: Do NOT touch
- **Commit messages and PR bodies** — narrating the branch is their job.
- **`docs/plans/*.md`** — transient by nature, swept at finalize.
- **`docs/remove-before-merging/squash-message.md`** — a commit body, and transient
by nature.
- **CHANGELOG / release-notes files** — a dated record of changes is the point.
- **Deliberately thorough reference prose** — length carrying rationale the
reader can't get elsewhere is not bloat; length carrying nothing is. But that
describes a colocated `README.md`, opened on purpose, not a directory's
`CLAUDE.md` or a `.claude/rules/*.md` file: **a rule's length is a budget**,
because it loads in full every time a file it covers is read. Paragraphs of
rationale in a rule are a lens
1 finding — the orientation belongs in a README the rule links to.
## Step 5: Commit
Commit the edits, with the fixes and their reasons in the body.