{"slug":"refactor-clean","title":"refactor-clean","summary":"Refactor cleanly instead of layering sediment. Use for rename requests, stale or misleading names, legacy leftovers, changed scope, or when a change reveals duplicated concepts, local adapters, obsolete owners, redundant inputs or guards, compatibility wrappers, parallel abstract","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-10-14T21:49:26.188994Z","repo":{"url":"https://github.com/dzhng/skills","stars":1333,"forks":81,"license":"MIT","updatedAt":"2026-10-14T20:39:18Z"},"bodyHtml":"<hr>\n<h2>name: refactor-clean\ndescription: Refactor cleanly instead of layering sediment. Use for rename requests, stale or misleading names, legacy leftovers, changed scope, or when a change reveals duplicated concepts, local adapters, obsolete owners, redundant inputs or guards, compatibility wrappers, parallel abstractions, an over-large module that has accreted many responsibilities, or \"just tack this on\" pressure in any code area.</h2>\n<h1>Clean Refactoring</h1>\n<p>Replace the old shape with the simpler shape the codebase would want if it were\ndesigned today. Refactoring is not adding a compatibility layer beside the problem;\nit is moving ownership until every concept has exactly one clear home. That cuts\nboth ways: merging N duplicated owners into one, and splitting one over-loaded\nmodule into the several owners it was hiding.</p>\n<h2>Workflow</h2>\n<ol>\n<li>Sweep names in every touched path, even when no structural refactor is needed.\nAsk whether each name describes the concept's current scope. Rename misleading\nfiles, symbols, imported bindings and shared exports immediately; update every\nconsumer, test, doc and agent instruction in the same pass. Keep scenario names\nonly for scenario-specific code, then search again for stale references.</li>\n<li>Name the concept that lacks one clear owner — duplicated across several owners,\nor several concepts fused into one over-loaded module. Identify the thing(s)\nthat should each have one owner: environment, pricing rule, geometry source,\nstate machine, data contract, renderer phase, API shape, UI state, or test\noracle.</li>\n<li>Find every current owner and consumer. Treat wrappers, aliases, pass-local\nconstants, copied structs, migration comments and \"temporary\" branches as\nsediment until proven otherwise.</li>\n<li>Run a deletion pass before relaxing checks or making inputs optional. For each\ninput, flag, guard, fallback, and adapter in the affected path, ask what required\nbehavior breaks if it disappears. Trace its writers, transport, readers,\nvalidators, and consumers; compare them with the authoritative owner. Making a\nredundant input optional is not completion. If removing it preserves required\nbehavior, delete the mechanism and its call-site, test, and documentation residue.</li>\n<li>Promote any remaining concept to its natural home. Pick the module that would\nown it from scratch, then make old call sites consume that owner directly.\nDelete or collapse the stale path in the same pass.</li>\n<li>Retain compatibility only for an identified consumer and a concrete behavior\nthat removal would break. Old inputs that can safely be ignored need no parser,\nvalidator, or adapter just because they may still arrive. If a bridge is needed,\nkeep it tiny and give it a removal condition; hypothetical callers do not count.</li>\n<li>Verify behavior through consumers, including affected failure and retry paths.\nSearch again for the removed mechanism across code, tests, docs, and agent\ninstructions. Finish only when each retained mechanism in the affected path\nhas a named consumer and behavior it preserves; report retained bridges and\ntheir removal conditions.</li>\n</ol>\n<h2>Rules</h2>\n<ul>\n<li><p><strong>Every line is debt — the best mechanism is the one you don't write.</strong>\nBefore building any guard, validator, convergence protocol, or cleanup\npath, two checks gate it:</p>\n<ol>\n<li><strong>Does the platform already do this?</strong> Read the component/framework you\nare working around before coding around it — its crons, retries,\ndefaults, and lifecycles. A hand-built janitor beside a component that\nalready vacuums itself is pure debt, and it will be debugged by someone\nwho doesn't know the component made it unnecessary.</li>\n<li><strong>Does the failure it guards change any real outcome?</strong> Trace the\nguarded condition to its consumer. If the consumer is indifferent — an\nordering check feeding a consumer that doesn't care about order, a\nvalidation on data only our own code produces — the guard tests nothing\nand must not be written. A common shape: an invariant re-checked at read\ntime that the single write path already establishes, so the branch can\nonly fire on hand-corrupted rows. Ask which caller could produce the bad\nstate; if the honest answer is \"none, short of someone editing the\ndatabase\", delete the branch. \"It could be inconsistent\" is not a reason;\n\"the consumer would then do the wrong thing\" is.\nThe bar is not \"is this correct?\" — defensive code is usually correct.\nThe bar is \"what breaks, for whom, if this line doesn't exist?\" No\nconcrete answer → no line.</li>\n</ol>\n</li>\n<li><p><strong>Measure the change, and treat a bad size-to-behaviour ratio as a shape\ndefect.</strong> Before accepting a change, count what it actually cost — production\ncode apart from tests and docs, and comments apart from logic, because a\ndiff dominated by explanation is not the same as one dominated by machinery:</p>\n<pre><code>git diff --numstat &lt;base&gt; -- ':!specs' ':!*.test.*' ':!**/test-harness'\n</code></pre>\n<p>Then read the added lines, not just the total. <strong>A small behavioural change\nthat costs a large number of lines is a red flag</strong> — it is the most reliable\nsignal that the fix is fighting the existing shape rather than fitting it:\na special case layered where the general case belongs, a second owner\nintroduced beside the real one, or a wrapper bridging two things that should\nhave been merged. The correct response is to rework it from the shape the\ncode would want, <strong>not</strong> to commit it with a paragraph explaining why it had\nto be big; that explanation is the smell, not the mitigation.</p>\n<p>Two honesty rules, or the number means nothing: formatter churn in files the\nchange did not otherwise touch is not part of the change and must stay out of\nthe commit; and a net count near zero can still hide real weight — a new\ncron, table column, index, endpoint, dependency, or config flag is a surface\nsomeone now owns and maintains, so name those separately from the count.</p>\n</li>\n<li><p><strong>Do not over-weigh the sunk cost of the existing architecture.</strong> \"It already\nexists and works\" is not an argument for keeping a shape — coding agents make\nlarge architecture switches cheap, so size a refactor by the quality of the end\nstate, not by the volume of code it replaces. When behavior must survive, pin it\nwith tests at the consumer surface and swap the architecture underneath — though\nmost of the time even the old seams shouldn't survive verbatim: a big refactor is\nthe chance to redraw them into the shape the codebase would want today, not to\nfaithfully rebuild the old interfaces on a new foundation.</p>\n</li>\n<li><p>Prefer one shared primitive over N adapters. An adapter is acceptable only at an\nexternal boundary or as a short-lived migration seam.</p>\n</li>\n<li><p><strong>Split a module that owns too many concepts — decomposition is refactoring\ntoo.</strong> The dual of merging duplicates: when one file or function has accreted\nunrelated responsibilities, divide it along ownership seams so each piece owns a\ncoherent slice a reader can name and the original thins to composition. <strong>Past\n~1000 lines a file is a smell, not a verdict</strong> — some modules earn their size (a\ncohesive state machine, a generated table, one algorithm with tight internal\ncoupling), but a god-file that registers dozens of routes or handles many domains\ninline is accreted responsibility, not cohesion. The split is right only when\neach new module owns a nameable responsibility and import pressure drops because\ndependencies moved with it; it's wrong when it's line-count relief that scatters\none concept across files a reader must reassemble.</p>\n</li>\n<li><p><strong>Know what already exists before you build something new.</strong> Before writing a\nnew mechanism — a shape, computation, asset, state machine, data contract —\nsearch the codebase for one that already does this, or something close enough\nto share. Only once you know what's there can you make the real decision:\nreuse it, consolidate two near-duplicates, or extract the shared core into an\nindependent module both call — and that decision belongs <em>before</em> you start,\nnot bolted on after. Reuse is not automatically the answer; the existing thing\nmay be wrong, or genuinely different, and then you build new deliberately. The\nfailure this prevents is building in ignorance of what's already there: two\nimplementations that must agree then silently drift, each re-deriving details\nthe other already settled (orientation, units, edge cases, ordering).</p>\n</li>\n<li><p>Do not preserve dev-only compatibility by default. Unshipped scaffolding should\nmove to the clean contract immediately.</p>\n</li>\n<li><p><strong>Zero lineage signaling: name things for what they are, never for where they\ncame from.</strong> A name that encodes history — a slice file prefixed with the\nmega-file it was split from, <code>foo-v2</code>/<code>foo-new</code>/<code>foo-legacy</code>, a module named\nafter the experiment that produced it, a wrapper named after the API it\nreplaced — carries no information to a reader who wasn't there, and actively\nmisleads the one who was once the old thing is gone. The test: would someone\nwho joined today, knowing nothing of the history, choose this name? If the\nname only makes sense with the backstory, rename it; history lives in git,\nnot in identifiers. The same rule kills lineage comments (\"previously this\nwas...\", \"moved from X\") — they describe the diff, not the code, and rot the\nmoment the referent disappears.\nRenaming is routine maintenance, not a separate project or a reason to ask for\npermission. Do it proactively at whatever scale the consumers require; a large\nmechanical diff is not a reason to retain a misleading name. Distinguish internal\nidentifiers from persisted keys and external contracts: retain the latter only\nfor an identified consumer, under the compatibility rule above. Do not leave\ninternal aliases that preserve the old vocabulary.</p>\n</li>\n<li><p><strong>Prefer the idempotent contract over the refusal.</strong> When an operation can be\nasked for twice — a retry after a lost response, a user clicking the same\nbutton again, a replayed webhook — reaching the requested end state should\nsucceed, not error. \"Install X\" where X is already installed at the place it\nbelongs is a success: return the thing. Refusal is correct only when the\nsecond request means something genuinely different from the first — a\nDIFFERENT thing already occupies the name, so honoring the request would\ndestroy or shadow it. The tell that you have it backwards: a caller has to\nspecial-case your error code to recover normal behavior, or a retry path\nneeds a pre-flight \"does it already exist?\" read that races. Applies beyond\nwrites: deletes of absent things, unsubscribes, and \"mark complete\" toggles\nare all idempotent by nature, and making them 404 or 409 pushes bookkeeping\nonto every caller.</p>\n</li>\n<li><p><strong>Constrain the model to what production actually writes.</strong> A schema that\npermits more than any writer produces — a list where every flow stores one\nelement, a state nothing reaches — taxes every consumer with the general\ncase. The tell: a consumer asking \"but which one?\" when the data can only\ncontain one. Enforce the constraint at the write path; widen when a real\nuse arrives.</p>\n</li>\n<li><p>Make ownership visible in stats, tests, or debug output when divergence was the\nbug class.</p>\n</li>\n<li><p><strong>A check that RE-DERIVES a value the code already computes will drift from\nit.</strong> A test or second consumer that recomputes geometry, state, or a derived\nquantity independently can disagree with the code over a difference invisible\non paper — an operation-order or rounding subtlety — and fire false verdicts.\nExport the owner's computed value and have the check consume that, so it\nenforces exactly what the code produced: one owner for the computation, not two\nthat happen to mostly agree.</p>\n</li>\n<li><p><strong>A symmetric or featureless placeholder can hide an orientation or coordinate\nbug in the thing it stands in for.</strong> A symmetric stand-in renders the same\nwhether or not the coordinate frame is flipped, so the defect stays invisible\nuntil a real asymmetric asset exposes it. When you swap a placeholder for the\nreal asset, re-verify orientation and framing, not just that it renders.</p>\n</li>\n<li><p>Update the spec or handoff with the new invariant, not the mechanical file list.</p>\n</li>\n<li><p>If the refactor starts widening into unrelated behavior, slice it: land the shared\ncontract first, then port consumers in reviewable passes.</p>\n</li>\n</ul>\n","files":[{"path":"SKILL.md","sizeBytes":12505,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-10-14T21:49:49.885549Z","sha256":"4B24C9E795EC065527E8301FF1578BC206E690B4B05474CFDFB2DF6F39688E1E","sizeBytes":5586},"review":null,"source":{"repositoryUrl":"https://github.com/dzhng/skills","path":"skills/engineering/refactor-clean","license":"MIT","commit":"f2fbf92f131fb08bbf3e400a1ef99b7be85bb2bc","subtreeSha":"C4306F1B89D17C8DBD0FBB67EEE7E3866540741C47BBE02E2E7D8D7ADBDEF9B2","lastSyncedAt":"2026-10-14T21:49:23.275319Z"},"reviewedAt":"2026-10-14T21:50:41.275047Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/dzhng/skills/tree/main/skills/engineering/refactor-clean"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install dzhng-skills@llmmart"},{"target":"git","command":"git clone https://github.com/dzhng/skills.git"}]}