Skip to main content
Refactron follows Semantic Versioning. The full, unabridged log lives in CHANGELOG.md.
August 20, 2026
Two fixes to what the verified test suite can reach.Neither is exploitable without already being able to hand Refactron a diff or a test command, which is the normal mode of use. That is the point: the CI gate this tool is built for verifies untrusted pull requests, so “the attacker controls the input” is the design assumption.The verified suite no longer inherits your credentials. Refactron runs your repository’s own test suite, and the diff under verification defines that suite. It was handed the full parent environment. Reproduced: a test read REFACTRON_TOKEN, GITHUB_TOKEN, NPM_TOKEN and AWS_SECRET_ACCESS_KEY in plaintext. This matters most in the deployment Refactron is built for, a CI gate holding the credentials of the repository being protected.Credentials are now removed from the environment of every spawn that executes your suite — the common names, plus anything ending in _TOKEN, _SECRET, _API_KEY, _PASSWORD or _CREDENTIALS. PATH, HOME, VIRTUAL_ENV and the rest of your toolchain are untouched.This is redaction, not a sandbox. Running verify-diff still runs your tests, exactly as running them yourself does. SECURITY.md now says so plainly.A diff can no longer name a file outside the repository. The path came from the diff’s own +++ header and was read with no containment check, so a diff naming ../../../.ssh/id_rsa caused Refactron to open it. The shadow tree blocked the resulting write, but the read had already happened, and whether the patch applied was an oracle for the file’s contents. Containment now runs at intake, before the first read, and resolves symlinks — a link planted inside the repository defeated a purely lexical check.Both fixes shipped with a bypass of themselves, found in review of the release and fixed in it. The coverage probe still ran unredacted, and it executes a coverage.py planted at your repository root. Recording that because the pattern is the lesson: a redaction covering two of three spawns is not a redaction.SECURITY.md was also rewritten. The previous version described the refactoring product removed in 0.4.0, and it now documents what actually ships, including a section on what Refactron explicitly does not defend.
August 19, 2026
Security: Refactron could write to your working tree. Update immediately.The shadow tree was populated with hardlinks, so every file your diff did not change shared an inode with your real file. The tests gate runs the suite as the diff defines it — and a diff may edit conftest.py, a fixture, or any test file — so any in-place write from that suite went straight into your repository. The verdict said SAFE while it happened.No attacker required. Any suite with a snapshot updater, a golden-file regenerator, or a test that writes a fixture could silently modify the repository being verified.Affected: every published version, 0.1.0-beta.2 through 0.4.1, on npm and PyPI. Reachable from the CLI and from the MCP verify_change tool, which applies no authentication. It contradicts the guarantee the README, the docs and SECURITY.md all state — “your working tree is never touched” — which was false for the entire life of the product.Fixed by copying instead of hardlinking, with copy-on-write where the filesystem supports it. Two related fixes ship with it: shadow-tree containment could be escaped by a repository symlink pointing outside itself, and a rejected change left a full copy of your source in the temp directory. Both now have tests; neither did before, which is how this survived four minor releases.Also fixed: four more false SAFE verdicts in the 0.4.1 narrowing check, found by an adversarial review of that release. The serious one is that the command scanner stopped at the first flag it did not recognise and discarded any filter after it — so pytest -q --durations-min=0.5 tests/test_a.py, using a stock pytest flag, returned SAFE. Also unittest discover -s tests/unit, pytest --cov --collect-only, and a bare runtests.py being granted the classifier’s strongest claim about a file it never opened.The docs no longer state the narrowing check as an absolute: it is a strong check on the runners and flags Refactron knows, not a guarantee. Run the bare command if you need certainty.
August 19, 2026
Five false SAFE verdicts fixed, and SAFE now means something narrower. The report’s shape did not change, so the version number alone will not tell you that — read this entry.Each of these was reproduced before being fixed. In every case the suite passed and coverage was measured, and the verdict was still wrong:Two rules changed to close them. A test command that names a subset of your suite now caps the verdict at UNPROVEN, and SAFE requires every changed statement a test could reach to have run, rather than one per file. Statements coverage.py excludes (# pragma: no cover, if TYPE_CHECKING:) are subtracted rather than held against you.Every verdict that moves, moves from SAFE toward UNPROVEN. Nothing that was UNSAFE or UNPROVEN can become SAFE. Exit codes are unchanged, so no CI pipeline breaks — pipelines that read the verdict string will see more UNPROVEN.If you acted on a SAFE from 0.4.0 or earlier for a change verified with a narrowed test command, or one whose coverage.changedStatements showed covered < total, that verdict claimed more than it proved. Re-verify.Two new report fields: testScope (was the command full, narrowed or unknown) and engineVersion (which rules produced this verdict — reportVersion only tells you which shape you hold). unittest joins pytest, vitest and jest as a recognised runner. Also clears two high security advisories, lockfile-only.
August 6, 2026
Breaking. Refactron is now only a verification layer. analyze, run, document, rollback, preflight, init, the interactive TUI and the 20 AST transforms have been removed from this package, along with blast-radius scoring and the tier taxonomy. They were the demo of the verification engine, not the product.If you use any of them, pin refactron@0.3.1. The code is archived with its full history and is not currently published under any name.verify-diff and the MCP verify_change tool are unchanged. Bare refactron now prints help and exits 2 instead of opening the TUI, refactron login is a real command for the first time, and import { verifyDiff } from 'refactron' resolves for the first time. Full detail in CHANGELOG.md.
August 6, 2026
Two false SAFE verdicts, found and fixed. Both had the same shape: coverage measured a different program than the tests gate ran, then reported the changed lines as covered. A false SAFE is the one defect this product cannot have, so upgrade rather than pin: npm install -g refactron@0.3.1.A testCmd carrying a leading PYTHONPATH= no longer silently disables coverage measurement, and a console entry point is now resolved the way the shell resolves it, or declined. A testCmd naming an entry point that cannot be resolved reports UNPROVEN rather than SAFE, which is a verdict change in the safe direction. Full detail in CHANGELOG.md.
August 2, 2026
The verification layer ships. Verify any diff (an AI agent’s, a codemod’s, or your own) for a SAFE / UNSAFE / UNPROVEN verdict, and expose the same gate to agents over MCP. Purely additive: nothing was renamed or removed, and the transform CLI behaves exactly as it did in 0.2.4. Install with npm install -g refactron@0.3.0.Added
  • verify-diff command: verify an arbitrary unified diff end to end and print [SAFE|UNSAFE|UNPROVEN] <reason>. Applies the change in an isolated shadow tree, runs the syntax / imports / tests gates, and fuses changed-line coverage into the verdict. Read-only: your working tree is never mutated. (PR #75)
  • refactron-mcp, an MCP server exposing verify_change: a stdio server an AI agent calls before it lands a change; accepts full-file edits or a unifiedDiff and returns the same JSON verdict report. Ships as a second bin in the same package. (PR #75)
  • Three-way verdict fusion: SAFE (gates pass and the changed lines are exercised), UNSAFE (a gate failed), UNPROVEN (tests pass but the change isn’t proven, or coverage couldn’t be measured). Coverage is Python-only via coverage.py; a non-Python or mixed diff returns UNPROVEN, never a false SAFE. (PR #75)
  • preflight command: a coverage-aware SQLAlchemy 1.x to 2.0 migration safety report that classifies each Model.query site as safe-to-automate, unproven, or needs-review; --fail-on-unproven gates CI. (PR #74)
  • A versioned report shape: reportVersion, plus changedStatements, filesWithUncovered, testFilesChanged, and flakyTests, so the evidence behind a verdict is legible in --json and over MCP rather than implied. (PRs #79, #83, #86)
FixedEach item is a false-verdict class the shipped release does not have. None reached a published build, since verify-diff is new here; they are listed because what a verification tool refuses to claim is the product.
  • A diff that deleted a file could read SAFE. Unmodelable operations were silently skipped, so a diff removing a module plus one benign edit passed the gates. Deletions, renames, copies, and binary changes are now refused with exit 2. So are anchorless hunks that misrepresent a live file as new, submodule pointer bumps, and non-UTF-8 bases. (PRs #79, #82)
  • A changed blank line could vouch for a function that never ran. Coverage attribution walked back to the nearest preceding statement start, so an executed def header covered a body that was never called. Attribution is now exact line-to-statement containment from the Python AST, and a line carrying no code token never marks a file exercised. (PR #86)
  • A flaky suite became a false UNSAFE rate. The tests gate now computes new failures against the baseline set and retries once on a fresh shadow tree, so a timing flake heals but an idempotency break does not. Flakiness floors the verdict at UNPROVEN: a green that needed a retry is not proof. (PR #83)
  • The imports gate failed modern Python. if TYPE_CHECKING: imports, platform-conditional imports, and the repo’s pre-existing breakage were all blamed on the change. The gate is now delta-aware and TYPE_CHECKING-safe, failing only on imports the change introduced or newly broke. (PR #78)
  • Coverage reported fake zeros. Script-form test commands, quoted arguments, a directory named coverage/ on sys.path, dynamically compiled code, and pip-installed projects each produced an empty covered set that read as “not exercised”. Each now reports honestly, and an unmeasurable run says coverage could not be determined instead of guessing. (PRs #78, #80, #84)
  • Removal-only diffs read like a coverage miss. A diff that only deletes lines now says so: there are no added lines for coverage to attest. The verdict stays a conservative UNPROVEN. (PR #81)
  • CRLF diffs could produce a false SAFE. Changed-line derivation now normalizes CRLF before the diff, so a change that only differs in line endings no longer mismatches coverage and slips through as covered. (PR #75)
  • Coverage was unavailable in CI. coverage.py is now installed into the same python3 the test suite spawns, so the coverage-based verdict is exercised in the CI runner instead of degrading to UNPROVEN. (PR #74)
Security
  • Dependency advisories cleared: npm audit reports 0, down from 6 (4 high). brace-expansion and fast-uri moved to patched versions in the lockfile, with no change to any declared range.
  • The PyPI wrapper no longer runs an unpinned global npm install. pip install refactron used to shell out to npm install -g refactron on first use, fetching whatever was latest regardless of the version you pinned. It now detects the CLI and, if missing, prints the exact matching command and exits non-zero. The wrapper is also relicensed to Apache-2.0 to match the rest of the project.
June 17, 2026
Reliability and observability release. Five real fixes, one feature (tier taxonomy), one license change. No new transforms, no API breakage; every existing call site keeps working.Added
  • Tier taxonomy on every transform (debt / modernization / style). analyze output groups findings and remediation minutes by tier: the headline “N findings” splits into “57 debt, 102 modernization, 2,569 style” instead of one undifferentiated count.
  • byTier and minutesByTier fields in analyze --json output, with the invariant debt + modernization + style === totalMinutes.
  • BY TIER section in the boxed TUI analyze output.
Changed
  • License: MIT → Apache 2.0. Same permissive freedoms; adds an explicit patent grant from contributors. See LICENSE, NOTICE, and the FAQ.
Fixed
  • run --transforms=all silently dropped 8 transforms. The CLI’s local list had drifted out of sync with the engine’s canonical order when the v0.2.3 catalog expansion landed. CLI now imports TRANSFORM_ORDER directly; drift is pinned by a test. (#48, PR #49)
  • --files=<glob> was ignored on --apply. The glob only narrowed the dry-run preview; the apply path silently rewrote every matching finding. Filter is now applied to plan.changes before the split, so both paths honour it. (#50, PR #52)
  • Documenter broke files with multi-line return-type signatures. On def f() -> type[Union[…]]: shapes that span multiple lines, the inserter latched onto the first inner line of the type subscript as if it were the body. The walker now tracks bracket depth and skips inline Protocol stubs. (#51, PR #52)
  • apply and rollback dropped POSIX file modes. Both paths now round-trip mode bits.
  • class_to_dataclass injected imports before from __future__. New imports now land after the __future__ block, preserving PEP 236 ordering.
  • Silent refusals in four transform sidecars. pep585_generics, pep604_optional_union, datetime_utc_alias, callback_to_async_await now emit a precondition record on every refusal: no more “detected, but nothing changed” with no explanation.
  • manual_typecheck_to_hints was the silent sidecar Bug #3 missed. Every refusal path now records why; gating prevents noise from unrelated siblings; the nested-def scan stops at function boundaries. On Ansible: 16 silent files → 0; 4 records → 87 covering all 20 affected files. (#57, PR #58)
Known follow-ups
  • manual_typecheck_to_hints now records refusals but on Ansible still rewrites 0 of 20 files. Expanding the rewriter to handle docstring + body and dispatcher + fallthrough is tracked as #59.
  • Eight new transform candidates derived from a deeper Ansible scan are filed as #62 to #69 for v0.3 / v0.4 prioritisation.
May 27, 2026
Ten new deterministic transforms (six for Python, four for TypeScript) roughly doubling Refactron’s transform coverage. Adds the pythonVersion config key so version-gated rewrites can be opted in safely.Added: Python
  • super_no_args: super(ClassName, self).method(...)super().method(...). Refuses sibling/parent class names and nested-class shadows to preserve MRO.
  • lru_cache_to_cache: @functools.lru_cache(maxsize=None)@functools.cache (≥ 3.9); also rewrites the from functools import … line.
  • pep585_generics: typing.List / Dict / Tuple / Iterable / … → list / dict / tuple / collections.abc.Iterable / … (≥ 3.9, or from __future__ import annotations). Refuses files with Pydantic v1 or get_type_hints to avoid runtime-eval crashes.
  • pep604_optional_union: Optional[X]X | None; Union[A, B]A | B (≥ 3.10, or from __future__ import annotations).
  • datetime_utc_alias: datetime.timezone.utcdatetime.UTC (≥ 3.11). No __future__ override: UTC is a runtime attribute.
  • yield_from_for_loop: for x in y: yield xyield from y. Refuses inside async def (a CPython compile-stage SyntaxError LibCST’s parser does not catch).
Added: TypeScript
  • indexof_to_includes: arr.indexOf(x) !== -1 and friends → arr.includes(x). Type-aware via ts-morph (String / Array / ReadonlyArray receivers). Gated on tsconfig target ≥ ES2016.
  • object_assign_to_spread: Object.assign({}, a, b){ ...a, ...b }. First arg must be an object literal; refuses spread-element sources. Gated on tsconfig target ≥ ES2018.
  • string_concat_to_template_literal: "…" + x + "…"`…${x}…`. Refuses any / unknown / non-primitive operands. Gated on tsconfig target ≥ ES2015.
  • vue_set_delete_to_assignment: Vue.set / this.$set → direct assignment; Vue.delete / this.$deletedelete obj.k. .js / .ts only; .vue SFC parsing is deferred to v0.4. Refuses delete in expression context (return-value semantics differ). On Vue 2 codebases this is a semantic change (Vue.set is required for new reactive keys); caveat ships in the suggestion text.
Added: Configuration
  • pythonVersion: pin the Python target version ("3.9", "3.11", …) for the four version-gated Python transforms. Auto-detected from pyproject.toml’s requires-python when unset; falls back to refusing version-gated transforms rather than guessing.
Changed
  • Engine composition: multi-transform composition is now order-stable: when several transforms touch the same file, each emits its own FileChange carrying the cumulative content, and the last one per path is what’s written to disk. Fixes a silent-data-loss bug where only the LAST transform’s rewrite survived under run --apply (PR #38).
May 18, 2026
Quality-of-life release for the analyze → run → document pipeline: boxed CLI output, a real rollback command, and a much more efficient document.Added
  • Bordered table output: analyze renders one box per file plus boxed TRANSFORMS / BY TRANSFORM / SUMMARY blocks; run --dry-run matches, with a CHANGES table and a four-sided diff box per file.
  • rollback command: undo an applied refactor or document run; journal-based LIFO undo, drift-safe, --all / --force / --dry-run.
  • run --apply live progress: gate-by-gate status and per-file verify/apply detail; batch-first with a per-file fallback when the batch fails.
  • run --apply short-circuit: exits early when no test runner is detected, instead of silently skipping the test gate.
  • Full report saved to disk: analyze / run --dry-run write the complete report to .refactron/reports/.
  • document enrichments: inline comments, a per-run modernization report under docs/refactron/, and a post-apply syntax re-check.
Changed
  • document is far more efficient: docstring requests are batched with bounded concurrency and token-aware rate limiting; the LLM call count is now O(source tokens / batch budget), not O(symbols).
Fixed
  • document produced zero docstrings on large files: batches were sized by input tokens only, so the combined response overran the completion cap and truncated. Batches are now also capped by response size, and a truncated reply is salvaged entry-by-entry.
  • document six-quote docstring bug (""""""…""""""); rate-limited runs that ground on for minutes; report / CHANGELOG paths normalized to forward slashes on Windows.
  • analyze: old-string-format findings now anchor on the operator, not the opening quote; manual_typecheck_to_hints no longer flags already-annotated parameters; the misleading “Fixable N/N” became an honest auto-fix-candidate count.
  • deprecated_api_requests_to_httpx no longer emits runtime-broken code: it refuses files using requests API that is not a safe httpx drop-in.
May 16, 2026
Patch release: a large-file crash fix and two transform-coverage improvements.Fixed
  • analyze crashed on files larger than ~32 KB: tree-sitter’s native binding rejects oversized string input. Parsing now uses the streaming callback-input form; a single unparseable file is skipped rather than aborting the run.
  • var_to_const_let dropped whole files: reassignment checks matched identifiers by text across the entire file. Reference resolution is now scope-correct, and for-loop var i initializers are covered.
Changed
  • format_to_fstring now converts the full printf grammar: %d, %.2f, %x, %o, %e, %g, width/precision specifiers, and %%. Mapping %(name)s, non-literal targets, and dynamic * widths are still conservatively skipped.
May 15, 2026
First public release of the v2.0 deterministic-refactoring rebuild.Added
  • Engine: 10 deterministic AST transforms (5 Python via LibCST, 5 TypeScript via ts-morph) with cross-file preconditions.
  • 3-gate verification: syntax + imports + tests on a shadow tree, with atomic batch write or rollback.
  • Documentation engine: the only LLM-touching component, running only on already-verified diffs; 5 providers (Ollama, Groq, OpenAI, Anthropic, managed backend).
  • .refactronrc.json config: cosmiconfig + Ajv schema validation.
  • Authentication: OAuth device flow with REFACTRON_TOKEN support and long-lived API keys.
  • A Mintlify documentation site and reproducible performance benchmarks under bench/.