agentleFS
dennisrongodennisrongo/claude-skills/skills/safe-refactor/SKILL.md

safe-refactor

Skill1 starsChanged 3 months agoMITView on GitHub

Execute a behavior-preserving refactor with a proof of preservation — establishes a safety net first (existing tests or new characterization tests over every touched path), locks a baseline green run, then moves in small always-green steps where each step is one mechanical transformation, and treats any needed assertion change as a smuggled behavior change to surface, not fix. Use this skill whenever the user says "refactor this", "clean this up without changing behavior", "extract this into", "restructure this module", "rename this across the codebase", "inline this", "split this function/class", or "/safe-refactor" — even if they don't explicitly say "refactoring skill". Do not use for choosing WHAT to refactor (use improve-codebase-architecture) or for changes that are supposed to alter behavior (use plan-and-build).

  • Deletes or force-pushes

26 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use verify-and-stop, investigate-first and 10 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • JuliusBrusseeSkill

    verify-and-stop

    Prove existing work meets acceptance conditions without expanding scope. Use for validation-only tasks, completion checks, focused gate runs, and last-mile proof.

    Also in 8 projects that use this

  • JuliusBrusseeSkill

    investigate-first

    Diagnose ambiguous failures before editing. Use for unknown causes, intermittent behavior, performance regressions, or investigations needing evidence-ranked hypotheses.

    Also in 7 projects that use this

  • +10 more from JuliusBrussee

The file

We split this skill into the rules it gives an agent

1 rule. 0 are common, 1 is distinctive, and 0 are very specific to this project.

  • 0

    Common

    Most of the 24 edited versions we read keep these.

  • 1

    Distinctive

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    If the safety-net phase reveals the code is nearly untestable, that finding may reorder the plan — sometimes the first refactor step must …

    line 71

“Common” is measured: kept by at least 6 in 10 of the 24 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Safe Refactor

Execute a refactor as a sequence of proofs, not a rewrite. "Behavior-preserving" is a falsifiable claim: the observable behavior before and after must be identical, and this skill's job is to make that claim checkable at every step instead of asserting it once at the end. The completion of the chain: improve-codebase-architecture names the target, write-tests builds the net, this skill makes the move.

When to use this skill

  • The user says "refactor this", "clean this up", "extract X into Y", "restructure without changing behavior", "rename across the codebase", "split this up", "inline this", "/safe-refactor".
  • improve-codebase-architecture produced an approved deepening candidate and the user says "do it".
  • A feature task requires restructuring existing code before the new behavior can land (do the refactor as its own phase, this skill governs that phase only).

Do not auto-trigger for changes meant to alter behavior — bug fixes, feature work, "improve this error message" — those are plan-and-build / task-executor territory. If the user's "refactor" turns out to include behavior changes, split the work: refactor first under this skill, behavior change after, never interleaved.

Workflow

  1. Write the behavior contract. One short list: the observable surface of the code being moved — public functions/endpoints and their outputs, side effects (writes, emits, logs that anything consumes), error types thrown, and performance characteristics if anything depends on them. This list is what "preserved" means; everything not on it is fair game. If you can't write the list, you haven't read enough code yet — a claim about code you haven't opened is a hypothesis.
  2. Audit the safety net. For each item on the contract, find the test that would fail if it broke — grep the test tree, cite file:line. Before writing any tests or touching any file, run git status: if the working tree is not clean, stop and ask the user to commit or stash the existing changes first — this skill's reverts must never be able to touch work that isn't yours. Contract items with no covering test get characterization tests before any restructuring begins (per write-tests: pin actual behavior, prove each test can go red). No net, no refactor — this gate is not skippable, and "the change is simple" is not an exemption; simple changes with no net are how behavior drifts silently.
  3. Lock the baseline. Run the full suite (single-run mode, never watch mode) and build; quote both summary lines. A result you didn't observe is "not run", never "passed". If anything is red before you start, stop and surface it — you cannot distinguish your breakage from pre-existing breakage on a red baseline.
  4. Plan the move as mechanical steps. Each step is ONE named transformation — extract function, move file, rename symbol, inline variable, introduce parameter, replace conditional with polymorphism — small enough that if the suite goes red, the cause is unambiguous. Order steps so each leaves the code compiling and the suite green. Present the step list and stop — do not begin step 5 until the user approves it; ask the commit question ("commit at each rollback point?") in the same message so both answers arrive in one round-trip.
  5. Execute step → verify → step. After every step: build + run the affected tests (full suite at least at every commit point), quote the result. Affected tests = the covering tests cited in step 2 for the contract items this step touches; if you cannot name that set for a step, run the full suite instead. Green → proceed. Red → the step is wrong; revert or fix the step, never the test. Revert means undoing only the files this step edited — targeted git restore <file>... if the last good state is committed, manual re-edit otherwise (git restore rewinds a file past your earlier uncommitted steps too); never git reset --hard or git checkout -- .. A red step gets one bounded fix attempt: read the actual error, change exactly one thing, retry once — a second red on the same step means revert the step and surface it; never keep patching until something goes green. Each step (or small coherent group) is a rollback point — ask once, up front, whether to commit at these points; committing without that yes is not authorized by this skill.
  6. Renames are grep-verified, not assumed. After renaming or moving a symbol, grep for the old name repo-wide — including strings, configs, reflection sites, docs, and serialized names (JSON keys, DB column mappings, API routes). Zero hits or each remaining hit justified in one line. Dynamic-dispatch and reflection usages don't break the build; the grep is the only net they have. Run it repo-wide, no path or extension filters: rg -n --hidden --glob '!.git' '<old-name>' (no ripgrep: grep -rn --exclude-dir=.git '<old-name>' .), from the repo root.
    • ❌ "Renamed getUser to fetchUser; build is green, done." — the build can't see the "getUser" string in the route table.
    • ✅ "Renamed; repo grep for getUser returns 2 hits: a CHANGELOG entry (historical, left) and routes.json:14 (updated)."
  7. The assertion-change tripwire. If any step makes an existing test fail and the "fix" would be changing that test's assertion, expected value, or setup — stop. That is the definition of a behavior change wearing a refactor's clothes. Surface it: quote the old and new behavior, ask whether the change is intended. Only test changes allowed without asking: mechanical renames mirroring step 6, and import/path updates.
    • ❌ Silently updating expect(total).toBe(107.10) to 109.31 mid-refactor because "the new rounding is more correct."
    • ✅ "Step 4 changes the rounding point, which alters totals on multi-line invoices (old: 107.10, new: 109.31). That's a behavior change, not a refactor — proceed with old behavior preserved, or split this into a follow-up?"
  8. Final proof. Full suite + build, summaries quoted. Then re-read the complete diff once, adversarially, hunting for the behavior change you smuggled in without noticing — reordered side effects, a dropped await, a widened catch, changed default parameter, iteration-order dependence, log lines something might parse. Report using this template — every line carries a tag: verified (observed this session), inferred (deduced from something observed) or assumed (neither):

    ## Refactor proof
    Contract: <item> → <test file:line> — [verified|assumed]
    Baseline: <quoted suite summary> / <quoted build result>
    Steps: <n>. <transformation> → <quoted result>
    Rename greps: <old-name> → <hit count>, <justification per remaining hit>
    Left un-refactored: <item> — <one-line why>
    Assumptions: <what breaks if wrong>

Examples

Example 1: extract a module

User: "Pull the pricing logic out of OrderService into its own PricingEngine."

Claude: Writes the contract (3 public methods, the discount side effect, 2 error types), finds tests covering 2 of 3 methods, writes characterization tests for the third and proves them red-capable, locks baseline (112 passed, build green), then: extract class → move methods one at a time → re-point callers → delete originals, suite quoted green after each, old-name grep clean, final adversarial diff read confirms no reordered side effects.

Example 2: the smuggled behavior change

User: "Refactor this date-handling mess to use one formatter."

Claude: Mid-sequence, unifying formatters makes a test expecting "3/5/2026" fail because the shared formatter emits "03/05/2026". Stops at the tripwire, shows both outputs, asks: preserve the legacy format via a format parameter (pure refactor), or adopt the new format (behavior change, separate commit, may affect consumers)?

Anti-patterns

  • ❌ Refactoring untested code because "it's a small change" — the net gate (step 2) exists precisely for the changes that feel too small to break anything.
  • ❌ The big-bang rewrite: restructure everything, then see what's red. When five things break you can't attribute any of them.
  • ❌ Fixing a red step by editing the test — the tripwire (step 7) exists because this is the single most common way behavior changes ship inside "refactors".
  • ❌ Interleaving "while I'm in here" behavior improvements with the restructuring — one diff, two intents, zero reviewability. Queue them for after.
  • ❌ Trusting the compiler to catch all breakage from a rename — strings, reflection, routes, and serialization don't compile-check.
  • ❌ Reporting the refactor complete without quoting the final suite run — "not run" is the only honest label for an unobserved result.
  • ❌ Refactoring toward a pattern the repo doesn't use because it's considered best practice — match the codebase's existing idiom; consistency is a feature.
  • ✅ Contract → net → baseline → small proven steps → grep-verified renames → adversarial final read.

Notes

  • Commit hygiene: keep refactor commits free of behavior changes so git bisect stays useful and reviewers can verify "no behavior change" structurally. Match the project's commit convention first (project CLAUDE.md, then the last ~20 git log --oneline subjects); only if no convention exists and conventional-commits is installed, use its refactor: type.
  • If the safety-net phase reveals the code is nearly untestable, that finding may reorder the plan — sometimes the first refactor step must be the minimal seam that makes testing possible (extract the untestable I/O, pin the rest). Say so rather than silently expanding scope.
  • Apply think-like-fable throughout: the contract is the decomposition into independently checkable pieces, every green claim is re-derived by running (never recognized), and the final adversarial read is attacking your own conclusion before handing it over.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.