plugins/frontend-toolkit/skills/code-refactoring/SKILL.md
Refactor TypeScript/React code for readability and maintainability — remove `any`, name compound conditions, apply guard clauses, extract magic literals to constants, deduplicate shared logic. Use during PR review, before a feature touches a complex area, or on a weekly cadence. Not for component-level extraction (use component-quality) or cross-file/module restructuring (use architecture-improvement).
npx skillsauth add jaykim88/claude-ai-engineering code-refactoringInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Improve readability and maintainability without changing behavior. Each refactor pass targets a small, scoped area and is verifiable by tests.
Universal — the refactoring principles (named conditions, guard clauses, magic literals → constants, extraction, immutability, parameter limits) are language-agnostic. The grep patterns and linter commands differ per stack.
Precondition — establish a safety net. Refactoring preserves behavior only if behavior is observable. Check test coverage on the target area first; if it's low, add characterization tests before refactoring (see test-strategy). "Tests pass" proves nothing when there are no tests on the code you're changing.
Eliminate type escapes
@ts-ignore / @ts-expect-error), as any / <any> casts, and any hidden in generics (Record<string, any>). Suppression comments are the most common real-world escape hatch — audit them first.unknown-equivalent + narrowing, or a generic. When a suppression is unavoidable, prefer @ts-expect-error (with a reason) over @ts-ignore — it self-removes when the underlying error is fixed.Name compound conditions
if with 2+ boolean expressions → extract to a named const before the if (examples in Anti-patterns)Apply guard clauses (early returns)
Extract magic literals to named constants
const MAX_RETRY_COUNT = 3 not if (retries === 3)SCREAMING_SNAKE_CASEcamelCaseExtract duplicated logic
lib/ (utilities) or components/ (shared UI)component-quality skill's extraction criteria (① props-expressible ② readability improves)Limit function parameters to ≤ 2
fn({ strict: true, async: false })createUser(name, email, true, false, null) — unreadable at call sitePrefer immutability
readonly where the value is set onceas const for inferring narrow types (const config = { mode: 'strict' } as const)ReadonlyArray<T>, Readonly<T> in type signaturesSplit long functions/components
Remove what-comments, keep why-comments
// increment counter — delete (the code says this)// off-by-one because the API is 1-indexed — keep (explains a non-obvious why)Verify (validation loop)
tsc --noEmit; if errors, fix them and re-run until clean before proceedingeslint . --max-warnings 0; if violations, fix and re-run until cleanany that remain must have justification comments — if not, add or remove| ❌ Anti-pattern | ✅ Correct |
|---|---|
| if (user.role === 'admin' && !user.suspended && order.total > 0) | Extract to named const: const canApprove = ...; if (canApprove) |
| if (a) { if (b) { if (c) { return doIt() } } } | Guard clauses, one per condition (keeps the failing reason visible): if (!a) return; if (!b) return; if (!c) return; return doIt() |
| if (retries === 3) | const MAX_RETRY = 3; if (retries === MAX_RETRY) |
| function fetchUser(id: any) | Use precise type or unknown + narrowing |
| // increments counter (what-comment) | Delete — code says this; only why-comments survive |
| fn(true, false, null) (boolean trap) | Options object: fn({ strict: true, async: false }) |
| Tier | Examples | Action SLA |
|---|---|---|
| Critical | any in security-critical paths (auth, payment, RLS); silent catch swallowing security errors | Fix immediately |
| Major | Nesting depth > 3; functions > 50 LOC with multiple responsibilities; magic literals in business logic | Fix this sprint |
| Minor | Stylistic naming inconsistencies; let where const would do | Schedule within 2 sprints |
tsc --noEmit cleanany have justification commentsgrep -r '<symbol>' first; the user confirms it's actually deadrefactor(<scope>): <subject>- tsc --noEmit: 0 errors
- eslint .: 0 warnings
- tests: N passing, 0 regressions
- any usage: N (all justified with comments)
grep -rEn ': any($|[^a-zA-Z])|as any|<any>|@ts-(ignore|expect-error)' src/npx tsc --noEmit — if errors, fix and re-run until cleannpx eslint . --max-warnings 0 — if violations, fix and re-runts-morph, jscodeshift) or an autofixable ESLint rule (eslint --fix) rather than manual edits — safer, faster, and reviewable as one mechanical diffreadonly modifier, as const, ReadonlyArray<T>, Readonly<T>useState derivation chainstsc --checkJs; the rest applies as-is# type: ignore; type check mypy . or pyright; immutability via Final[], frozen=True dataclasses, MappingProxyTypeinterface{} / any; go vet ./... + staticcheck; immutability via unexported fields + accessor methodsunsafe; immutability is default (let is immutable, opt-in mut)component-quality — for component-level extraction and cva/hook patternsarchitecture-improvement — when refactoring crosses file/module boundariesif is a missing named const. This is more readable AND easier to test than nested ternaries or chained &&.development
Design webhooks correctly on both sides — sending (HMAC signing, retries with backoff, at-least-once) and receiving (verify signature on raw body, enqueue + 200 fast, dedupe on event id). Use when adding webhook delivery or consuming a provider's webhooks. Not for internal service-to-service events (use async-messaging) or general outbound-call retry policy (use resilience-patterns).
testing
Use transactions and isolation levels correctly — keep them short, no network calls inside, explicit isolation, retry on serialization conflicts, and choose optimistic vs pessimistic locking. Use when a write spans multiple tables, when concurrent updates corrupt data, or when designing money/inventory flows. Not for cross-service event delivery (use async-messaging Outbox) or schema-level constraints (use schema-design).
development
Backend testing pyramid — unit for pure logic, integration against a real DB (Testcontainers), and consumer-driven contract testing (Pact) for service boundaries. Use before a feature, after a bug fix, or when services break each other on deploy. Not for load testing (use performance-profiling) or security testing (use backend-security-audit).
data-ai
Design a relational schema — normalize to 3NF then denormalize with justification, choose the right Postgres index type per data shape, enforce constraints at the DB. Use when modeling a new domain, when queries are slow, or before a migration. Not for diagnosing slow queries (use query-optimization) or shipping the change without downtime (use migration-strategy).