Jun 17, 2026 · v0.2.4

Tier Taxonomy & Five Real Fixes

Findings now split by debt, modernization, and style. Five real fixes land, and the license moves from MIT to Apache 2.0. No new transforms, no API breakage.

Added

  • Tier taxonomy. Every transform is classified debt, modernization, or style. analyze 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. New fields in analyze --json output, with the invariant debt + modernization + style === totalMinutes.
  • BY TIER. A new section in the boxed TUI analyze output.

Changed

  • License: MIT to Apache 2.0. The same permissive freedoms, plus 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. The CLI now imports TRANSFORM_ORDER directly, and drift is pinned by a test. (#48, PR #49)
  • --files with a glob was ignored on --apply. The glob only narrowed the dry-run preview; the apply path silently rewrote every matching finding. The 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 __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, and callback_to_async_await now emit a precondition record on every refusal: no more detected-but-nothing-changed mysteries.
  • manual_typecheck_to_hints was the silent sidecar the sweep missed. Every refusal path now records why, gating prevents noise from unrelated siblings, and the nested-def scan stops at function boundaries. On Ansible: 16 silent files down to 0, and 4 records up to 87 covering all 20 affected files. (#57, PR #58)

Known follow-ups

  • manual_typecheck_to_hints still rewrites 0 of 20 Ansible files. It now records refusals, but expanding the rewriter to handle docstring plus body and dispatcher plus fallthrough shapes is tracked as #59.
  • Eight new transform candidates derived from a deeper Ansible scan are filed as #62 to #69 for v0.3 and v0.4 prioritisation.