refactor
Use when a named refactor, rename, move or internal API reshape must keep behavior unchanged, or a shim or compatibility re-export tempts. Not for finding what to refactor, a behavior change, which bu
SKILL.mdskills/refactor/SKILL.md
GitHubRefactor
Change structure with behavior pinned; leave less code than you found. The enemy is the half-finished move: old API kept beside the new, a shim forwarding to it, or a reshape trusted because it type-checks. The overcorrection is a refactor growing layers for needs nobody has written yet.
When to use
- Named rename, move, extraction, split, flattening or reshape of an internal API whose results must not change.
- Pull to keep the old signature, accept both shapes, or leave a re-export "for compatibility".
- Not for a behavior change:
buildowns it; mixed request → refactor first, then the change. - Not for persisted data, a stored format or a consumer outside this repository:
buildkeeps those compatible on purpose via../build/references/data-migration.md. - Not for a failure with unproven cause:
find-causeowns it.
The loop
- Pin behavior before the first move. Capture current outputs in a test, snapshot or before/after script over the inputs that matter; type check and lint pin nothing, both pass on a wrong result.
- Name the target shape. One sentence: module layout and call graph as if built today; reshape removes branches, layers or states, never adds indirection.
- Subtract first. Delete dead code, one-caller wrappers and orphaned references before building the new shape.
- Migrate every caller, then delete the old API, in the same change. No shim, deprecated wrapper, both-shapes signature or re-export stays, because nothing then forces the last caller over. Search strings, docs, scripts and tests too; renames miss usages there.
- Build no seam for a future caller. No base class, registry, factory or flag with one implementation; seam arrives with its second adapter; unused sketch code is dropped, not finished.
- Prove equality on the real code. Rerun the pin against old and new versions, quote the comparison; "it compiles" or a green suite that never covered the moved code proves nothing.
- Keep it only if reading got easier. Revert a step that lowers no reader load (fewer files to trace, less state to hold).
- Report. The report rule in
../route-skills/references/closing.md; equality evidence, line counts and reverted changes go behind the pointer.
Red flags
| The excuse | What holds |
|---|---|
| "Accept both shapes for now; callers move when someone touches them." | Nobody touches them; dual path becomes permanent. Migrate now; say what the line cap or open PR costs. |
| "Keep the re-export so nothing breaks." | Inside one repository a search proves no caller is left; a kept re-export invites new ones. |
| "Types and lint pass, so the results held." | Types check shapes, not values; only a before/after run on the same inputs shows equality. |
| "No time for tests on a mechanical split." | A throwaway comparison script takes minutes, no committed test needed. |
| "Leave it extensible for the variants coming later." | One-implementation seam = indirection; the next variant adds its seam when it lands. |
References
| File | Read it when |
|---|---|
references/behavior-pin.md |
Step 1 when no test covers the code, and step 6 for the comparison. |
references/legacy-api.md |
Step 4, before keeping any old path, and when a caller belongs to another team's open work. |
../route-skills/references/closing.md |
Before the final message. |
Judgment
- Pin outranks target shape: a step that breaks it is reverted, never the pin loosened.
- Delete-old-API rule covers only APIs whose every caller is in this repository; anything outside follows
../build/references/data-migration.md. - Deletion outranks addition when both reach the target shape.
- Explicit user request to keep the old API outranks step 4; state its cost once, keep it with a removal date.