verify-diff takes a change (your AI agent’s, a codemod’s, or your own) and answers one question: did it preserve behavior, and can you prove it?
Refactron applies the diff in an isolated shadow tree, runs the syntax, imports, and test gates against it, checks whether your tests actually exercise the changed lines, and returns one of three verdicts. Your working tree is never touched.
A diff enters, the gates run in an isolated shadow tree, and one of three verdicts comes out.
The verdict, in one line
UNPROVEN is the honest verdict. “Tests pass” is not the same as “this change is proven safe”: if nothing runs the lines you changed, a green suite proves nothing about them. Refactron says so out loud and tells you which test to add. See Verdicts for the full model.
Install (from source)
verify-diff and the MCP server are unreleased; they are not in refactron@0.2.4 on npm.
Install from source until the next release, which ships the refactron and refactron-mcp bins.Authenticate
verify-diff is auth-gated. Log in once on this machine:
7.
Run it
repoRoot defaults to .. The --diff file is a standard unified (git) diff.
UNPROVEN by lack of coverage) the uncovered lines:
Flags
Exit codes
Unsupported operations
v1 verifies content edits. A diff that deletes, renames, or copies a file, or changes a binary file, is refused with exit2 rather than partially verified. This is deliberate: a diff that deleted a module while also making one innocuous edit once verified SAFE, yet applying it broke every import in the package. A partial verdict on the verifiable half must never read as a verdict on the whole diff.
Detection is belt and braces: both the parsed diff and a raw scan of the diff text are checked, so a pure rename or deletion that the diff parser drops entirely is still caught. Full deletion and rename support is planned; until then, verify those changes manually.
Test files touched
When a diff changes files that look like tests (atests/ or test/ path segment, test_*.py, *_test.py, conftest.py, *.test.ts, or *.spec.ts), the human output prints one advisory line and the JSON report carries the list under testFilesChanged:
The JSON report
--json emits a reproducible record of the verdict: the same structure the MCP tool returns:
gates: each gate’spassedflag and wall-clock. A failed gate also carries ablockingReason.testFilesChanged: changed files matching test conventions. A note that the diff touched tests, never a verdict input.coverage.tool:coverage.pywhen coverage was assessed,nonewhen it couldn’t be (non-Python diff, or nocoverage.pyinstalled).missingTests: present onUNPROVEN-by-coverage, a concrete hint per uncovered line.
Verify in CI
Runverify-diff on a pull request’s diff and let the exit code gate the merge. UNSAFE returns 1 and fails the job; SAFE and UNPROVEN return 0.
verify-diff as a merge gate: UNSAFE fails the job, SAFE and UNPROVEN pass.
preflight, verify-diff has no --fail-on-unproven flag. To fail CI on UNPROVEN, read the verdict field from --json output and set the exit code yourself; the process exits 0 for UNPROVEN by design, so a green suite on untested lines never silently blocks a merge unless you choose to.
Next
Verdicts
The three verdicts, coverage fusion, and the Python-only limitation in depth.
MCP server
Give your AI agent the same gate as a
verify_change tool call.Safety model
How the shadow tree and the three gates work.
Preflight
A coverage-aware safety report for the SQLAlchemy 1.x to 2.0 migration.