agentsclimarketplace

Refactor

Skill jsolly/agent-skills/skills/refactor

TypeScript refactoring that provably preserves behavior. Use for "refactor this", "clean up this code/file", "this file is a mess", "extract / split / reorganize / DRY this out", or periodic tech-debt reduction ("what should we clean up here?"). Two modes: census (scan a module, emit a prioritized debt plan) and targeted (execute one named refactor through contract → atomic commits → equivalence evidence). NOT for bug fixes, behavior changes, or feature work — those are not refactors. Less drastic than a rewrite.From its SKILL.md

Install
npx -y skills add jsolly/agent-skills --skill refactor

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

11.0 KB, ~2.5k tokens by cl100k_base, as published. Nobody here has run it

Refactor

Author in a worktree. This skill edits repo files (atomic commits below) — work in a git worktree off main, never the primary checkout on the main branch (rules/worktree-authoring.md). Integrate with /ship.

A refactor can only fail two ways, and this skill exists to prevent both:

  1. Silent behavior change. Hallucinated equivalence (arr.filter().map()arr.reduce(), a moved await preserving order), a caller you never grepped, a narrowed type some caller violated at runtime. A green tsc is not proof — see the evidence gate.
  2. Scope sprawl. A "rename this type" becomes a 400-line rewrite mixing three concerns, unreviewable and unrevertible. Touching a file you didn't need to touch is a defect. Go deep on the chosen change (no compat shims, delete the old shape); never go wide beyond it.

The goal is a more maintainable codebase by this repo's definition (house rules below), with evidence — not a feeling from reading the diff — that behavior is unchanged. A refactor that silently alters behavior is net-negative maintainability: a hidden bug plus churn.

Run the tests — that's what the evidence is made of. This skill runs solo and infrequently: no other agent is sharing the local DB, the dev server, or any stateful fixture, so there is no contention reason to skip, sample, or defer a test. Prefer running the suite over reasoning about equivalence from reading the diff — a green run is the evidence; a plausible argument is not. Run tests eagerly and often: to establish the baseline, between every commit, and in full before you hand off. When a slower or DB-touching/E2E suite exists, running it is the safe default here, not a luxury — a db reset/seed or a suite that mutates local state is fine to run. Skipping a test you could have run is the evidence gap that lets a silent behavior change through.

Entry: census or targeted?

Census — the ask is vague ("clean this up", "reduce tech debt", "what's messy here?"): do a read-only scan first; make no edits. Look for: re-exports/barrels (grep both forms — see rules/code-style.md), types/constants inline instead of types.ts/constants.ts, single-file folders and one-function files, optional-field bags that should be discriminated unions, any/as density, dead exports (knip if present), god functions. Emit a prioritized plan — one line per atomic refactor, each with its importer fan-out, ordered by value/risk:

DEBT PLAN — src/lib/messaging
1. Remove re-export shim email/index.ts → repoint 4 importers        [fan-out: 4]
2. Extract inline types from send.ts → messaging/types.ts            [fan-out: 2]
3. Payment: optional-field bag → discriminated union                 [fan-out: 7]

Each line is a valid future invocation of this skill. Let the user pick; execute one line at a time through the gates below.

Targeted — the user named the change: go straight to Gate 1.


Gate 1 — STOP before the first edit

Print this block, filled in, before touching any file. Blanks demand real values — a command, a file list, a count — not intentions.

REFACTOR CONTRACT
change:    <one line — the single transform this session performs>
files:     <explicit set of files allowed to change>
oracle:    <the repo's own check+test commands — read its AGENTS.md/package.json; fallback: npm run prepush.
           MUST actually run tests, not just typecheck — a green tsc is not the oracle. Pick the
           broadest test command you can run per-commit without it being painfully slow; running
           solo, err toward more coverage.>
baseline:  <paste the oracle command output's final line — must be green BEFORE any edit>
importers: <for each exported symbol touched: grep count + list; name any NOT yet read>
  • No green oracle? That's the whole job until one exists. No tests on the target → build a throwaway pin of current behavior first: references/characterization-harness.md.
  • Baseline red? Stop — a refactor on top of pre-existing failures can't be distinguished from one that caused them.
  • Importer fan-out too large to keep atomic? Don't refactor — emit a decomposition plan (census format above) and hand it back.

Execute — one transform per commit

  • Each atomic transform (extract constant → move type to types.ts → repoint imports) is its own commit, oracle green between each. A failure then localizes to one move; a revert is one clean git revert.
  • Let the typechecker be your hands, not your report card. To remove a re-export or rename a symbol: make the breaking change, run tscthe errors are your worklist. Don't plan the full edit from static reading; induce the errors and let them enumerate the call sites.
  • A rename is a transposition: definition, every call site, tests, types — one pass. The typecheck must fail until it's uniform.
  • Pre-commit, every commit: run git diff --stat and compare against the contract's file set. Any file outside it: git checkout -- <file> — a revert you execute, not a temptation you resist. Then commit with the contract echoed: refactor(<scope>): <transform> [contract: <files>].
  • Suspect you broke something? Revert the commit and re-approach smaller. A clean revert beats a clever patch on a bad refactor.

Gate 2 — STOP before declaring done

Print this block, filled in. Every line must be an artifact you produced, not an affirmation.

EQUIVALENCE EVIDENCE
oracle before:   <paste baseline result line from the contract>
oracle after:    <paste the same command's result line now — must match>
tests edited:    <none | STOP: an edited test means behavior changed — split it into its own labeled commit>
API surface:     <public-surface change? emit .d.ts before AND after to temp dirs and diff —
                  tsc --emitDeclarationOnly --declaration --outDir <tmp> (the bare --noEmit the
                  repo runs won't emit; --declaration is required). The .d.ts diff must be empty.>
laundering:      <rule out EACH, by name, for THIS diff:
                  as-casts added: <…>  any added: <…>  dynamic key access: <…>
                  runtime validators still asserting old shape: <…>
                  type narrowing (T|undefined→T, string→brand): proven at every call site? <…>>
perf:            <complexity/allocations in hot paths unchanged? one line>
falsification:   <the ONE call site most likely to have changed behavior — and the argument why it didn't>
scope:           <paste git diff --stat — every file inside the contract set>

If you cannot write the falsification line — or the equivalence argument a reviewer would need to approve without running it — the refactor is too big or too clever. Shrink it.

Then hand off the judging: spawn code-quality-reviewer (plus bug-scanner for larger diffs) scoped to git diff --name-only <baseline>..HEAD, prompt: "This diff claims pure behavior-preserving refactoring — flag any re-export or inline type/constant that crept in, any as/any laundering, and any hunk whose equivalence you can't argue in one sentence." High-confidence findings re-enter the Execute loop; the refactor isn't done while one is open.

Before pushing: run the repo's full test battery, not just the oracle subset. The oracle is scoped for fast per-commit feedback; the pre-push run is the whole suite — unit, integration, and every DB-touching/E2E test the repo has. Per the run-the-tests principle above, running solo means the stateful ones are safe to run — so run them. A refactor pushed on the oracle alone when a fuller suite exists is an evidence gap, not a time save.


Worked trace (what compliant output looks like)

User: "clean up the discount logic in pricing.ts"

REFACTOR CONTRACT
change:    extract duplicated membership-rate logic into constants.ts Record map
files:     src/pricing/pricing.ts, src/pricing/constants.ts
oracle:    npm run check:ts && npm test -- pricing
baseline:  Tests: 14 passed, 14 total ✓ / tsc: 0 errors
importers: getDiscountRate exported? no — module-local only. 0 external importers.

[edit 1] add DISCOUNT_RATES: Record<Membership, number> to constants.ts → tsc green
→ commit: refactor(pricing): extract DISCOUNT_RATES to constants.ts [contract: pricing.ts, constants.ts]
[edit 2] replace the if-chains with `DISCOUNT_RATES[m]`. FIRST pin the old output for
         EVERY key: gold/silver hit an if; bronze fell through to the fn's explicit
         `return 0`, so bronze was already 0 — pinning bronze:0 reproduces it exactly.
         (Had the old fn returned undefined for bronze, pinning it to 0 would be a
         behavior change: a Record forcing a key is never license to invent a value the
         old code didn't produce — that goes in a separate, labeled commit.)
→ commit: refactor(pricing): replace duplicate rate chains with DISCOUNT_RATES lookup [contract: …]

EQUIVALENCE EVIDENCE
oracle before:   14 passed / 0 errors      oracle after: 14 passed / 0 errors
tests edited:    none
API surface:     no exported signature changed (.d.ts diff empty)
laundering:      as-casts: 0 added (grepped) · any: 0 · dynamic keys: none ·
                 validators: no schema touches Membership · narrowing: none performed
perf:            O(1) map lookup replaces O(1) if-chain — unchanged
falsification:   riskiest site = bronze: confirmed old fn's explicit `return 0` default
                 already yielded 0 (not undefined); DISCOUNT_RATES[m] returns that same 0.
scope:           2 files changed — both in contract set

→ code-quality-reviewer on the 2 files: no findings. Done.

House rules (what "maintainable" means here)

Canonical in rules/code-style.md and rules/typescript-strict.md — consult them, don't re-derive them. The refactor-time operationalizations:

  • Extractions have destinations: types → the module's types.ts; magic values and as const maps → constants.ts. Not inline, not mixed.
  • Moving a symbol = repointing every importer in the same commit. Never leave a forwarding export { x } from "./old" shim "for compatibility" — no re-exports, ever.
  • Leave no single-file folder or one-function file behind. A refactor that strands one isn't done — flatten or grow.
  • Delete the old shape in the same commit that introduces the new one. Git history is the archive.

Smell → fix patterns (branded types, discriminated unions, strategy maps, guard clauses, dead-code biopsy): grep references/smells.md.

What ships with it: 2 files

8.7 KB alongside SKILL.md

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.