Skip to main content
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 code diff flows into Refactron, through the syntax, imports, and test gates in an isolated shadow tree, and out to a SAFE, UNSAFE, or UNPROVEN verdict

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.
Coverage is Python-only (via coverage.py). A TypeScript or mixed-language diff can never earn SAFE today; it returns UNPROVEN with the reason “coverage of the changed code could not be determined.” The gates still run; only the coverage half is Python-only.

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:
In CI, skip the browser and export a token instead:
An unauthenticated run exits 7.

Run it

repoRoot defaults to .. The --diff file is a standard unified (git) diff.
Output is the verdict, its reason, and (for 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 exit 2 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 (a tests/ 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:
This is a note, not a verdict change. A green verdict on a diff that also weakens its own tests is still green; the note is there so you look before you trust it.

The JSON report

--json emits a reproducible record of the verdict: the same structure the MCP tool returns:
  • gates: each gate’s passed flag and wall-clock. A failed gate also carries a blockingReason.
  • testFilesChanged: changed files matching test conventions. A note that the diff touched tests, never a verdict input.
  • coverage.tool: coverage.py when coverage was assessed, none when it couldn’t be (non-Python diff, or no coverage.py installed).
  • missingTests: present on UNPROVEN-by-coverage, a concrete hint per uncovered line.

Verify in CI

Run verify-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.
A pull request diff running through Refactron as a CI gate, blocking the merge on an UNSAFE verdict

verify-diff as a merge gate: UNSAFE fails the job, SAFE and UNPROVEN pass.

Unlike 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.