Release
Refactron v0.2.0 Is Here: Deterministic Refactoring with a Three-Gate Safety Net
Two numbers shaped how we built Refactron. NYU researchers studied 1,692 programs generated with GitHub Copilot and found that roughly 40% contained exploitable vulnerabilities. A separate study presented at ACM AST 2024 found that 92.45% of Copilot-generated tests fail or are otherwise broken when there is no existing test suite to anchor against. These are not edge cases. They are the baseline behavior of statistical code generation when no one is checking the output.
Refactron v0.2.0 is our response. The refactoring engine is fully deterministic — every transform is an AST rewrite with a defined input shape and a defined output shape. The LLM is confined to documentation generation, and only after a refactor has already passed verification. Every change is checked by three gates before any byte hits your disk, and the whole batch either commits atomically or rolls back. This is the first public release of the v2.0 rebuild that replaced our Python prototype.
What shipped
Ten transforms in this release. Five for Python, parsed and rewritten through LibCST. Five for TypeScript, parsed and rewritten through ts-morph and the TypeScript Compiler API.
- callback_to_async_await (Python) — rewrites a function with a trailing callback parameter into an async def that returns the result directly.
- format_to_fstring (Python) — converts percent-style formatting and .format() calls into f-strings.
- manual_typecheck_to_hints (Python) — collapses an isinstance chain that dispatches on a single parameter into a Union annotation.
- deprecated_api_requests_to_httpx (Python) — rewrites requests imports and call sites to use httpx, refusing files where the API is not a safe drop-in replacement.
- class_to_dataclass (Python) — converts pure init-assignment classes into dataclasses with the @dataclass decorator.
- var_to_const_let (TypeScript) — rewrites each var binding to const, or to let if the binding is reassigned.
- promise_chains_to_async (TypeScript) — flattens .then(...).then(...) chains into async functions using await.
- implicit_any (TypeScript) — adds explicit parameter types when every call site passes the same primitive.
- commonjs_to_esm (TypeScript) — converts require and module.exports to ES module import and export.
- promise_constructor_to_async (TypeScript) — rewrites new Promise((resolve) => resolve(x)) into an async function that returns x.
Each transform has a defined refusal set. When the AST shape does not match the contract, the transform skips the file and says why. There is no fallback to a probabilistic rewrite.
The three gates
Every refactor passes through three gates in sequence: syntax, imports, tests. The gates run on a shadow copy of your project. Your real tree is not touched until the last gate passes. Failure at any gate aborts the entire refactor and leaves your working directory exactly as it was.
The syntax gate re-parses every modified file using the language's own AST library — LibCST for Python, the TypeScript Compiler API for .ts and .tsx. This catches the cheapest failures first.
The imports gate walks the project's import graph, including reverse imports, and verifies that every import statement still resolves. This catches the cross-file damage the syntax gate cannot see.
The tests gate auto-detects the project's test runner — pytest, vitest, or jest — and runs the suite against the shadow tree. If no runner is detected, the gate short-circuits with a clear message rather than silently skipping.
The order matters. Cheap checks fail fast. Expensive checks only run when they have to.
Atomic write or rollback
When all three gates pass, the refactor writes its files using write-file-atomic and a two-phase rename. Each file is written to a temporary path and then renamed into place. Every file in the batch commits, or none does. There is no partial state where some files have the new code and others have the old.
If a single file in the batch fails to write — disk full, permission denied, locked by another process — the entire refactor reverts. You do not end up with a half-applied refactor that you have to untangle by hand.
What does not happen
The LLM does not write your code. The refactoring engine is one hundred percent deterministic AST rewrites. There is no model temperature, no statistical inference, no creative rewrite of your business logic. The LLM is invoked only by the document step, and only against a diff that has already passed all three gates. The code the LLM sees is code that has already been verified to behave the same as before.
Getting started
Install the CLI, log in once to register your local environment, then analyze, dry-run, and apply.
npm install -g refactron@0.2.3 cd your-project && refactron login refactron analyze . refactron run --dry-run . refactron run --apply .
The analyze step is read-only. The dry-run step shows the diff without writing. The apply step runs the three gates and atomically commits if they all pass.
What is next
The patch series — v0.2.1, v0.2.2, and v0.2.3 — added bordered output, a real rollback command, a much faster document step, and ten more transforms. The deeper engineering writeups that follow this post cover the three-gate model, the rollback journal, and the engine composition bug we fixed in v0.2.3.