Inviolable rule: verification runs against a copy, never your working tree. Refactron never writes to it.
The shadow tree
Nothing in the gate path reads or writes your real working tree. Refactron builds a shadow tree first: a temp-directory copy of the project root where unchanged files are hardlinked (cheap, instant) and the changed files are written from the proposed new content.The change is applied to an isolated copy. Your working tree is never touched.
verify-diff and the MCP verify_change tool, this is the whole story: the shadow tree is built, the gates run, the verdict is returned, and the copy is cleaned up. Your repo is never mutated. Landing the change is the caller’s decision.
The three gates
Gate 1: Syntax
Re-parse the new content for each changed file.- Python: LibCST parses the proposed source. A failure means a malformed change slipped through.
- TypeScript: ts-morph collects diagnostics; any
Error-category diagnostic rejects the file.
blockingReason naming the offending file. Typical wall-clock: ~50ms for a small change.
Gate 2: Imports
Resolve every import in the changed files, and reject only the ones the change is actually responsible for.- Python: collect
import Xandfrom X import Yand resolve each top-level module againstsys.pathplus the project tree. Imports guarded byif TYPE_CHECKING:are skipped, because they never run at runtime; a type-only dependency that is not installed is not a runtime break. Relative imports are left to the tests gate. - TypeScript: resolve every import specifier against the project’s
tsconfig.json. Node builtins always count as resolved.
- The change adds an import that does not resolve.
- The change breaks an import that resolved before (for example, it deleted a re-exported symbol a changed file relied on).
import msvcrt, which exists only on Windows) can still fail when the gate runs on another OS. The gate does not evaluate platform conditions, and stays conservative there.
This catches the most common breakage: the change is locally valid but quietly orphans a downstream module, without blaming your edit for imports the repo could never resolve in the first place. Typical wall-clock: ~50ms.
Gate 3: Tests
The most expensive gate, and the only one that runs your code.- Detect the test runner by config-file presence in the project root (no file contents are parsed):
vitest.config.tsorvitest.config.js→vitest runjest.config.jsorjest.config.ts→jest- any of
pyproject.toml,pytest.ini, orsetup.cfgpresent →pytest - Override with
--test-cmd(CLI) or thetestCmdargument (MCP).
- Run the baseline first: the runner against the unchanged copy. If the baseline already fails, Refactron won’t blame the change: the tests gate reports an already-red baseline, and the verdict becomes
UNPROVEN, notUNSAFE. - Run the mutated tree: same runner, with the change in place. A non-zero exit fails the gate.
verify-diff and MCP path applies the flat default.)
Coverage fusion → the verdict
Passing the gates is necessary but not sufficient. A green suite says nothing about lines your tests never run. So when the gates pass, Refactron measures changed-line coverage and fuses the two signals into one verdict:
Coverage is measured with
coverage.py, so it is Python-only today. A non-Python or mixed diff passes the gates but returns UNPROVEN (“coverage could not be determined”); Refactron never reports an unmeasured change as SAFE. The full rules, including the missingTests hints, are on the Verdicts page.
Atomic batch write (landing)
verify-diff never writes, and as of 0.4.0 nothing in Refactron does. Earlier versions had a migration mode that wrote on green, handing a list of (path, newContent) pairs to an atomic batch writer so a partial failure could never leave a half-written tree. That path was removed with the transforms; the guarantee it protected is now unconditional.
Citations
The gate-before-transform 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