Refactoring and tech debt
Use when tracking tech debt across a codebase, when planning a refactor, when deciding whether to rewrite vs incrementally improve, when a workaround has appeared in three places, or when a refactor PR is about to be combined with feature work.From its SKILL.md
npx -y skills add aneja5/forge-skills --skill refactoring-and-tech-debtAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 3 stars3 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
7.2 KB, ~1.7k tokens by cl100k_base, as published. Nobody here has run it
Refactoring and Tech Debt
Overview
Make tech debt visible, accountable, and resolvable — before it compounds. Output is .forge/tech-debt-registry.md — every known debt item with location, type, cost-to-fix, cost-of-not-fixing, owner, and trigger. Sets the refactor patterns (extract, inline, rename, strangler-fig) and enforces the rule that refactor PRs are refactor-only. Pairs with code-review-and-quality (review enforces the registry) and planning-and-task-breakdown (debt items get scheduled like features).
When to Use
- A codebase is older than 6 months and the team can name "the bad part" but it's not tracked
- A refactor is being proposed and the team is debating "rewrite vs incremental"
- The same workaround (try/catch with comment, copy-pasted helper, manual cleanup script) appears in 3+ places
- A perf or security finding requires a refactor in addition to the immediate fix
- A team member is leaving and the bus-factor on a module is 1
When NOT to Use
- A greenfield project — no debt yet
- A trivial rename or extract that's part of a feature task (do it as part of the feature work, don't ceremony it)
- A speculative "this might be debt someday" — wait for the third occurrence
Common Rationalizations
| Thought | Reality |
|---|---|
| "We'll fix it later" | Later never comes without a ticket and a trigger. The debt registry is the ticket. |
| "Just rewrite it" | Rewrites without a strangler-fig plan create two broken systems running in parallel. The new system inherits the old system's bugs because the old system encoded the bugs as decisions. |
| "It works, don't touch it" | Working code that nobody understands has a bus factor of zero. The first incident on that code is also a knowledge incident. |
| "Refactoring is a waste of time" | The time you save skipping refactoring you spend debugging. Debt compounds; refactor pays it down. |
| "We'll refactor while we add this feature" | Mixed PRs are impossible to review and impossible to revert independently. The next bisect points at the refactor commit even when the feature caused the regression. |
| "Strangler-fig is overkill, just swap it" | Big-bang swaps fail. Strangler-fig keeps the system running while you migrate, one consumer at a time. |
Red Flags
- A "refactor" PR that adds, changes, or removes user-visible behavior
- A rewrite started without a strangler-fig migration plan
- The same workaround copy-pasted in 3+ places, no extraction
- A "we'll fix it later" comment with no linked ticket
- A debt registry that's been stale for >90 days
- Bus factor 1 on a critical module
- A perf or security fix that "needs a refactor first" but the refactor never starts
Core Process
Step 1: Audit the codebase for debt
Walk the codebase. Look for:
- Copy-pasted code blocks (3+ occurrences of a near-identical pattern → candidate for extract)
- Workaround comments (
// TODO,// HACK,// FIXME,// XXX,// temporary) → triage each - Modules with high churn but low test coverage (changes break things, tests don't catch it)
- Functions over 100 lines or files over 500 lines → split candidates
- Cyclic imports → architectural debt
- Dead code (no callers, no tests) → delete candidates
- "The bad part" everyone names — the legacy module, the auth flow, the billing code → entry candidates
Step 2: Classify each debt item
In .forge/tech-debt-registry.md, one entry per item:
## DEBT-042: auth/session.ts — manual cookie parsing
**Location:** src/auth/session.ts:14-87
**Type:** Workaround (predates the framework's session helper)
**Bus factor:** 1 (only the original author understands it)
**Cost to fix:** 2 engineer-days
**Cost of not fixing:** 1 incident every ~3 months; each incident ~6 engineer-hours
**Owner:** @assigned-engineer
**Trigger:** Third recurrence in 6 months, OR before adding multi-tenant auth, OR a security finding
**Pattern:** Replace with framework helper. Strangler-fig: new sessions use helper, old sessions migrate on next login.
**Tracking:** ISSUE-LINK
Types: workaround, duplicate-code, architectural (boundaries leaky), dependency (outdated, unmaintained), dead-code, test-gap, documentation-gap, bus-factor.
Step 3: Set refactor triggers
A debt item is picked up when one of its triggers fires:
- Third-occurrence rule — the same workaround appearing in 3+ places triggers extraction
- Adjacent-work rule — touching a debt-flagged module for any feature work triggers fixing the debt as a prior PR (not combined)
- Budget-breach rule — a performance or cost budget breach traceable to the debt
- Security/compliance rule — a finding requires the refactor as part of remediation
- Calendar rule — items with
cost-of-not-fixing > cost-to-fixover a year are scheduled, not waited on
Step 4: Choose the pattern per item
| Item shape | Pattern |
|---|---|
| Duplicate code, stable shape | Extract to shared module |
| One-line wrappers used everywhere | Inline; remove indirection |
| Old name, new meaning | Rename (with deprecation alias for the window) |
| Big module needs replacing while running | Strangler-fig (route new traffic to new module, migrate old traffic over time) |
| Cyclic imports | Introduce a third module that owns the shared types |
| Dead code | Delete; no aliases, no "just in case" |
| Test gap | Add characterization tests for the existing behavior before refactoring |
Step 5: Enforce refactor-only PRs
A refactor PR contains no behavior change. Tests that pass before pass after, unchanged. New tests added for newly-exposed seams are allowed; changes to existing test assertions are not. Cross-reference code-review-and-quality.
If a refactor needs a behavior change to land safely, split into:
- PR A: extract / restructure (no behavior change, all tests green)
- PR B: behavior change (now testable because of A)
Step 6: Strangler-fig template for rewrites
When a module is being replaced (not refactored in place):
- Build the new module behind a feature flag, with no callers
- Add a router: callers go to
newif flag on,oldif off - Migrate one caller at a time. Each migration is its own PR with its own tests.
- When all callers are migrated, remove the router and the old module
- Each step is independently revertible
Big-bang rewrites are forbidden. If a rewrite cannot be strangler-figged, write an ADR explaining why.
Verification
-
.forge/tech-debt-registry.mdexists and was updated in the last 90 days - Every debt item has owner, estimate, and trigger
- Every TODO/HACK/FIXME comment in the codebase is either resolved or linked to a registry entry
- No refactor PR in the last 30 days mixed behavior changes with refactoring
- Every rewrite in flight has a strangler-fig plan documented (or an ADR justifying why not)
- No copy-pasted code block appears 3+ times without an extraction ticket
- No critical module has a bus factor of 1 without a documented mitigation
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.
Gives 1 of the 12 instructions most refactoring skills give in ~1.7k tokens
Counted across 545 of the 587 authors here whose files we hold, read 2026-09-06
- Run tests after each changein 61 of 545, across 59 files
- Run tests before refactoringin 42 of 545
- Revert immediately if tests failin 31 of 545, across 28 files
- Perform refactoring in small stepsin 30 of 545, across 29 files
- Write characterization tests for untested codein 21 of 545, across 19 files
- Remove dead code and unused importsin 20 of 545
- Identify code smellsin 20 of 545
- Perform one refactoring at a timein 19 of 545
- Commit after each successful refactoringin 17 of 545, across 15 files
- Verify all tests pass after refactoringin 17 of 545
- Keep refactoring separate from behavior changeshere, and in 16 of 545, across 14 files
- Run the full test suitein 16 of 545
Said here and by no other author read
- audit codebase for debt items
- record debt in the tech debt registry
- assign an owner to every debt item
- define a trigger for every debt item
- enforce refactor-only pull requests
- update the registry at least every ninety days
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.