maintainability-agent docs
Reference

Migrating from 1.x to 2.0

Genre: reference. What changes the number after 1.10.1, what does not, and what you can ignore.

One thing changes a score, and it changes every score. The reference corpus — the set of repositories whose medians every reported multiple is a multiple of — went from 40 repositories in three languages to 112 across all eight the scanner parses. CALIBRATION_C and the per-dimension references move with it, so a repository whose code did not change will still grade differently.

Nothing else in this release touches a score. The report schema does not break, baselines remain valid, finding identity is unchanged, and no threshold moved.

# 1. Same command. Scores move because the scale moved, not because
#    anything about your repository did.
maintainability-agent --root . --config maintainability-agent.json

# 2. Regenerate any baseline you gate on, so "new findings" stays a
#    statement about your code rather than about the anchor.
maintainability-agent --config maintainability-agent.json \
  --write-baseline maintainability-baseline.json

# 3. Do not compare a 1.x number to a 2.0 number as if they were the
#    same measurement. They are readings from two different instruments.

Why the anchor changed

For five releases the tool parsed eight languages and calibrated against three. Java, C, C++, C# and Fortran were scored against medians measured on none of their code — and the effect was not theoretical. Audited against the old anchor, LAPACK reported declarations at 7.18x the median and fortran-lang/stdlib at 1.10x. The first figure is a true statement about LAPACK relative to mature open-source web code. It is not a statement about typical Fortran, because no typical Fortran was in the comparison set.

The uniform rubric was never the problem — that is the promise (P2), and it is intact. The corpus was the problem, and the fix is to extend it rather than to soften the disclosure or to introduce per-language references, which would trade a stated limit for a silent breach of the one-rubric promise.

What was held constant, deliberately

The original 40 rows are unchanged and still pinned to the commits they were measured at. Adding languages is the only variable, so whatever the constants did, they did it because of the languages and not because the corpus was re-rolled underneath them.

Selection is the same mechanical query — created before 2021, pushed since 2026, under the size cap — with one stated exception. Fortran entered at a 500-star threshold rather than 3,000, because the entire Fortran ecosystem has zero repositories above the bar the other seven use; its most-starred serious projects sit between roughly 900 and 1,900 stars (LAPACK, WRF, Elmer, stdlib, CP2K). Holding one number across ecosystems would have measured popularity rather than maturity and excluded the language outright. The per-language thresholds are recorded in tools/calibration/corpus.json under selection.min_stars.

One member changed for a reason worth stating plainly: CPlusPlusThings is a C++ teaching repository, and the marker list gained "things" so that category is excluded mechanically rather than by veto. It was found by its measurements rather than by its name, which is the outcome-triggered discovery a mechanical corpus exists to prevent — the same disclosure 33-js-concepts received. Re-running selection with the new marker changed exactly one member; googletest took its place.

What moved

The constants below are derived by tools/calibration/measure.py from the pinned corpus and are reproducible from it.

Dimension 1.x 2.0 3.0 4.0 5.0
file_size 0.0858 0.0952 0.0938 0.0871 0.0871
declarations 0.1005 0.0908 0.0962 0.0929 0.0944
duplication 0.28 0.3222 0.3144 0.2973 0.2973
risk 0.0737 0.0826 0.0341 0.0249 0.0249
gates 0.05 0.05 0.05 0.05 0.05
CALIBRATION_C 5.8843 8.7161 4.2565 4.3092 4.35

5.0 re-pins the analyzer pool (2026-10-26). Every row was re-measured on the refreshed pins, with lizard held at 1.24.0 because 1.24.3 fails to parse JavaScript that 1.24.0 reads. It is also the first measurement in which pylint and cohesion contribute, which they should have done since 4.3.0: they read Python declarations as denser. Only declarations moved. 14 of 201 repositories move at the published decimal and 2 change grade — go C→B, manim B→C — with the grade counts unchanged. Published grades move, so it is a major version, on the same reasoning as 3.0 and 4.0.

4.0 anchors Kotlin and Shell (2026-10-02). The corpus grew from 180 repositories to 201 — fifteen Kotlin, six Shell — and every row was re-measured on the re-pinned analyzer pool. Separated on the 180 shared repositories: the new pins alone would change 4 grades; adding the two languages changes 9 of 180 — 8 B→C and 1 C→B — and moves 52 at the published decimal. Published grades move, so it is a major version, on the same reasoning as 3.0. Shell entered thin: ten of sixteen candidates were collections or too small to be codebases, and six cleared verification.

3.0 is the first of these that re-grades repositories. 33 of the 112 repositories present in both the 2.0 and 3.0 corpora change grade — 15 B→C, 7 C→B, 5 A→B, 4 C→D and 2 B→A. The corpus gains its first D grades. A 2.11.x re-measure taken a day earlier moved nothing at all, which is why only this one is a major version.

Three changes land together and cannot be separated, because each moves scanner_fingerprint and invalidates every stored measurement: the corpus grew from 112 repositories to 180 and from eight anchored languages to thirteen; the analyzer tier's criterion set is now completed from the built-in tier per concept, taking analyzer-driven declarations from 6 rows of 180 to 173; and D137 fixed the criterion check's scope.

risk more than halving is the largest single move and the languages explain it. Risk patterns are configured policy and the defaults were written against Python and C. As a multiple of the new reference the added languages read Swift 0.00×, PHP 0.01×, Ruby 0.04×, Go 0.10× and Rust 0.26×, against C 5.82×, C++ 4.95× and Python 4.24×. The old reference was measuring the pattern set rather than the code.

CALIBRATION_C is fitted by bisection so the corpus median rolls up to 4.0 through the full rubric. The direction a given repository moves depends on its own mix, because four of the five references moved as well — a repository heavy on duplication and light on declarations does not move the same way as its opposite.

declarations fell, which the motivating evidence did not predict, and the per-language readings explain why: C and Fortran are heavy, Java, C# and C++ are light, and there are more of the latter. The full breakdown, method and limits are in the calibration study.

What did not change

If you pin the anchor rather than the tool

Nothing forces you onto the new scale. CALIBRATION_C and the references live in src/maintainability_audit/_calibration.py with their previous values recorded beside them, so an organization that has built thresholds on the 1.x scale can hold that scale by pinning the package version. The honest framing is that you are then measuring against an anchor that omits five of the languages you may be scoring, which is the limit this release exists to remove.