AI-assisted code refactoring using the T-S-V-E (Types, Specs, Verification, Evolution) workflow with Code Hollowing. Use when refactoring complex logic, rewriting legacy code, restructuring modules, or when AI-generated refactors produce patch-on-patch results instead of clean redesigns. Activates for requests like "refactor this class", "rewrite this module", "clean up this spaghetti code", or "redesign this API".
Resources
1Install
npx skillscat add pipi-1997/agent-skills/tsve-refactoring Install via the SkillsCat registry.
T-S-V-E Refactoring
Break the "local optimum trap" in AI refactoring. Instead of patching legacy code, hollow it out and rebuild from type contracts.
Why This Exists
When refactoring complex code, AI tends to patch the existing mess — adding more if-else branches, wrapping with adapters — because old tokens dominate the context window. The result: code worse than before. T-S-V-E fixes this by removing old implementations entirely and letting AI derive fresh solutions from constraints alone.
When to Use
| Scenario | Approach |
|---|---|
| Simple rename / move | Skip T-S-V-E, use standard tools |
| Logic change in clean code | Start at Phase S |
| Legacy spaghetti rewrite | Full T-S-V-E |
| Cross-module restructure | T-S-V-E per module, integrate after E |
Workflow
Execute phases strictly in order: T → S → V → E
T — Types: Define the Algebra
Preserve all type signatures and interfaces. Delete every implementation body.
TypeScript:
// KEEP: the full type surface
interface PaymentGateway {
charge(amount: Money, method: PaymentMethod): Promise<Result<Receipt, PaymentError>>;
refund(receiptId: string, reason: RefundReason): Promise<Result<Refund, RefundError>>;
}
// DELETE: the entire class body
class StripeGateway implements PaymentGateway {
async charge(amount: Money, method: PaymentMethod): Promise<Result<Receipt, PaymentError>> {
// ← delete everything here
}
async refund(receiptId: string, reason: RefundReason): Promise<Result<Refund, RefundError>> {
// ← delete everything here
}
}Python:
# KEEP: signatures + type hints
class OrderProcessor:
def process(self, order: Order) -> Result[Receipt, OrderError]: ...
def validate(self, order: Order) -> list[ValidationError]: ...
# DELETE: all implementation bodiesGo:
// KEEP: interface definition
type Repository interface {
FindByID(ctx context.Context, id string) (*Entity, error)
Save(ctx context.Context, entity *Entity) error
}
// DELETE: method bodies, keep signatures
func (r *pgRepository) FindByID(ctx context.Context, id string) (*Entity, error) {
// ← delete everything here
}Rules:
- Keep generics, unions, return types, error types — the full contract
- Delete ALL implementation lines, not just "the complex parts"
- If types are missing or weak (
any,object), strengthen them first
S — Specs: Build the Shell
Add invariants and contracts as docstrings or JSDoc. Describe what must hold, never how.
interface PaymentGateway {
/**
* Charge a payment method for the given amount.
*
* Preconditions:
* - amount.cents > 0
* - method must be previously validated via validateMethod()
*
* Postconditions:
* - On success: receipt.amount === amount, receipt.status === 'completed'
* - On failure: no side effects, original payment method unchanged
*
* Invariants:
* - Idempotent: same idempotency_key always returns same receipt
* - Timeout: must resolve within 30s or return TimeoutError
*/
charge(amount: Money, method: PaymentMethod): Promise<Result<Receipt, PaymentError>>;
}Focus on:
- Preconditions (what must be true before calling)
- Postconditions (what must be true after)
- Invariants (what must always hold)
- Edge cases (nulls, empty inputs, concurrency, timeouts)
- Performance constraints (if any)
V — Verification: Tests Before Code
Write tests that define what "correct" means before generating any implementation.
describe("StripeGateway.charge", () => {
it("returns receipt for valid charge", async () => {
const result = await gateway.charge(usd(1000), validCard);
expect(result.isOk()).toBe(true);
expect(result.value.amount).toEqual(usd(1000));
});
it("rejects zero amount", async () => {
const result = await gateway.charge(usd(0), validCard);
expect(result.isErr()).toBe(true);
expect(result.error).toBeInstanceOf(InvalidAmountError);
});
it("is idempotent with same key", async () => {
const r1 = await gateway.charge(usd(500), validCard, { idempotencyKey: "abc" });
const r2 = await gateway.charge(usd(500), validCard, { idempotencyKey: "abc" });
expect(r1).toEqual(r2);
});
it("returns TimeoutError after 30s", async () => {
// simulate slow provider
const result = await gateway.charge(usd(100), slowCard);
expect(result.error).toBeInstanceOf(TimeoutError);
});
});Cover:
- Happy path (at least one)
- Boundary conditions (zero, max, empty)
- Error cases (invalid input, external failures)
- Concurrency / idempotency (if specified in Specs)
Run the tests — they must fail (confirming the hollow state).
E — Evolution: Zero-Origin Generation
Now generate implementation inside the empty bodies. With types, specs, and tests as constraints, AI operates as an architect, not a patch-worker.
Instructions for this phase:
- Fill each hollowed function/method one at a time
- Run the test suite after each fill
- If tests fail, share only the failure output — never re-inject old implementations
- Iterate until all tests pass
- Run a final review for readability and edge cases
Critical Rules
- Never leave old code in comments. AI will latch onto commented-out implementations. Delete completely.
- Never re-inject old code when stuck. If generation fails, add more specs or tests — not old implementations.
- Never hollow without types. Deleting everything including signatures leaves no constraints. Always preserve the type shell.
- Never skip V. Without tests, there's no way to verify the new implementation matches intent.
- One function at a time in Phase E. Fill, test, fix, then move to the next. Don't generate everything at once.
Progress Checklist
T-S-V-E Progress:
- [ ] T: Type signatures and interfaces defined/refined
- [ ] T: All implementation bodies deleted (Code Hollowing)
- [ ] S: Preconditions and postconditions documented
- [ ] S: Edge cases and invariants specified
- [ ] V: Happy path tests written
- [ ] V: Boundary and error tests written
- [ ] V: Tests run and fail (confirming hollow state)
- [ ] E: Implementation generated within type shell
- [ ] E: All tests pass
- [ ] E: Final readability review doneFurther Reading
See references/concept.md for the theoretical foundation behind Code Hollowing and the local optimum trap.