express-intent-in-code
Installation
SKILL.md
- Treat every comment, boolean, nullable return, boolean argument, and inline judgment in the supplied code as a candidate for expressing its intent in code, and decide each in this order: delete it when the code already says the same thing; move it into the code by renaming, choosing a result type, introducing a sum type or named constant, extracting a function, or adding an assertion or test, then delete it; keep it only when it states something the code cannot express, such as an external specification, a workaround for a defect elsewhere, or a deliberate trade-off chosen against the obvious alternative; a comment that ties this code to another place it must stay consistent with is such a constraint until the tie becomes a shared symbol or a test, so move it into a test or keep it. Before keeping a comment as a trade-off or an external fact, try a name that states the property the rejected alternative lacks (a helper named for constant-time comparison) or a type alias that states the unit or contract (a
UnixSecondsalias on a field whose wire key cannot change); keep the comment only when no name, alias, or test can hold it. - Choose the type that carries a result by what the caller does with it: a boolean when yes or no is the whole value and nothing downstream re-derives it; a type predicate when the caller narrows on the same value;
T | nullwhen exactly one absent or invalid state exists; a discriminated union when there are three or more outcomes, outcomes carry different data, or onenullorundefinedstands for two meanings that a caller distinguishes or reconstructs, and put the data the caller was reconstructing (the unknown identifier, the failing input) on the branch that owns it; two causes that every caller treats the same stay one value. - Replace booleans or optionals that are read together with a discriminated union so impossible combinations cannot be constructed. Replace a boolean argument that selects between distinct behaviors with a named union value or two functions whose return types differ, and a boolean positioned between arguments of the same type with a named field; update every caller in the same change.
- Introduce a branded or nominal type for a value whose "already done" state the code cannot otherwise tell only when the constructor lives in one place and a caller's duplicate application or re-check disappears; a brand whose proof nothing downstream consumes is a name, not type safety, so do not add it.
- Make a domain function total: replace a
throw, non-null assertion, cast, or "cannot happen" branch with a value in the return type when the caller can act on it; the boundary that receives partial input stays the last place that catches. - Extract a judgment out of a function that performs I/O (awaits a store or request, reads a clock or random source) into a pure function or predicate only when the extraction deletes something that exists now: a fake, layer, or injected dependency an existing test needs; a second copy of the same judgment; or a clock or random read inside the function. A test that a new function could gain, or a name a one-line condition could carry, does not count when the existing tests already cover the judgment through a trivial stub. When nothing is deleted, leave the lines where they are, even a one-line predicate; a comment that explained such a condition moves into a test name or assertion, not into a new function; and report the candidate with what would have had to disappear. Keep I/O order that carries business meaning, such as a re-check inside a transaction, in the calling code. Inside code that is already pure, a helper whose name carries what a comment said is a valid move and needs no deletion test.
- Apply the same order to unclear names and responsibilities even where no comment exists: perform the smallest restructuring and renaming that makes the intent readable from the code; do not stop at suggesting names or structure. Leave names that are already clear unchanged.
- When a repository rule, ADR, or house idiom conflicts with one of these moves, the repository wins: leave the move unapplied and report the conflict.
- Do not add comments during the cleanup, including for a constraint that the request or the edit reveals and no existing comment states: pin it with a test or assertion, and if neither can hold it, report it instead of writing it.
- Preserve supplied inputs, outputs, and externally observed behavior. Keep feature and policy changes outside this cleanup, including changes a comment or TODO asks for, and leave any restructuring that requires an unauthorized target unapplied.
- Distinguish a write submitted from a change verified by its response, readback, or observed diff; never infer success from submission alone.
- After verified edits, run fresh lint and the supplied behavior checks, including every identified boundary case, and compare observed outputs with the pre-change evidence.
- Remove a lint suppression only when the fresh lint result proves it unnecessary; a suppression that remains keeps the one-line reason the code still does not express.
- Limit edits to authorized code and corresponding verification files.
- Report the verified diff, each kept comment with the reason the code cannot express it, each extraction or brand left unapplied with the reason, lint and behavior-check results, out-of-scope restructuring, failed or unverified writes, and any behavior that remains unverified.