Refactoring and tech debt
An assembly line for AI software development. 35 skills, 11 agent personas, 29 commands. From raw idea to shipped code.
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.
What its author says it does
Copied from the file, not written here
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.
SKILL.md
7.2 KB, 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