Refactron’s moat is what happens before a write, not how clever the rewrite is. Every refactor plan passes through three verification gates and a single atomic batch-write step. If any gate rejects, your working tree is untouched.Documentation Index
Fetch the complete documentation index at: https://docs.refactron.dev/llms.txt
Use this file to discover all available pages before exploring further.
Inviolable Rule #4: Verification runs before every write. Atomic write protocol always.
The 3 gates
Each gate operates on the proposed-new-content string for everyFileChange in the plan. Nothing in the gate path reads or writes your real working tree until the final atomic step.
Gate 1 — Syntax
Re-parse the new content for eachFileChange in the plan.
- Python: LibCST parses the proposed source. A failure means a malformed transformation slipped past the refactorer (a bug we want to catch).
- TypeScript: ts-morph collects diagnostics on the proposed source. Compiler diagnostics with category
Errorreject the file.
blockingReason naming the offending file. The plan is dropped; nothing else runs.
Typical wall-clock: ~50ms for a small batch.
Gate 2 — Imports
Build a dependency graph from the proposed-new-content trees and resolve every import.- Python: collect
import Xandfrom X import Ystatements; resolve againstsys.pathplus the project tree. - TypeScript: ts-morph
getImportDeclarations()plusgetModuleSpecifierSourceFile()for each import.
- A newly-introduced import in the new content does not resolve.
- An import that previously resolved in a changed file now fails (e.g. transform deleted a re-exported symbol).
Gate 3 — Tests
The most expensive gate, and the only one that runs your code.- Build a shadow tree — a temp directory copy of the project root. Unchanged files are hardlinked (cheap, instant). Files in the plan are written from the proposed new content.
- Detect the test runner by looking for a config file in the project root:
vitest.config.{ts,js,mjs}→vitest runjest.config.{ts,js,cjs,json}→jestpyproject.tomlwith[tool.pytest.*]orpytest.ini→pytest- User can override via
.refactronrc.jsontestCmd: "<command>".
- Run the baseline first — execute the runner against the unchanged copy. If the baseline already fails, abort with
your tests don't pass before refactoringrather than blame the refactor for pre-existing failures. - Run the mutated — same runner, same temp tree, with the proposed changes in place. Any non-zero exit rejects the plan.
Atomic batch write protocol
Once all three gates pass, the verifier hands a list of(path, newContent) pairs to writeBatchAtomic. Each write uses write-file-atomic:
- Write the new content to a sibling temp file (
<path>.refactron-tmp-<rand>). fsyncthe temp file.- POSIX
rename(MoveFileExWon Windows) the temp over the real path. This rename is atomic at the filesystem layer.
Cross-file preconditions
Some transforms need to know whether other files in the project depend on the surface they’re about to mutate. These transforms walk the import graph for the target file and skip themselves rather than break a cross-file caller:callback_to_async_await— skips when an external file imports the function and passes a callback at the call site.deprecated_api_requests_to_httpx— skips when an external file references<module>.requests(includingunittest.mock.patchstring targets).
Citations
The three-gate model traces back to Bill Opdyke’s 1992 PhD thesis on behaviour-preserving refactoring at UIUC — the original formal treatment of preconditions before automated source transformation.- Opdyke, William F. Refactoring Object-Oriented Frameworks. PhD thesis, University of Illinois at Urbana-Champaign, 1992. PDF