Skip to content

How a score is built

Every score is reconstructible from the report. There is no hidden judgment, and no step you have to take on trust.

The arithmetic

Findings are weighted by severity, divided by the lines their category could apply to, and put through a curve:

weighted_density = sum(severity weight) / (relevant_lines / 1000)
score            = 100 / (1 + weighted_density / half_density)

Severity weights are geometric: low 1, medium 3, high 9. Geometric spacing guarantees a high-severity finding always outranks any realistic pile of low-severity ones, rather than being outvoted by volume.

half_density is the density at which a category scores exactly 50, and the report prints it beside the density it measured. One division reproduces the number.

Why this curve

Score at multiples of the half density

0100
1x50
2x33
4x20
10x9

Hyperbolic decay rather than exponential, on purpose. Both are bounded and monotone, but exponential collapses to near zero a short way past the halfway point: at four times the half density it scores 6, and at ten times it scores 0.1. Both read as zero.

That matters for the case where a score is most useful. A team cleaning up a genuinely bad codebase would move from 0.1 to 3 and see nothing happen. This curve keeps resolution across the whole bad end of the range, so improvement is visible.

The trade is that it is more forgiving at the bottom. Grade banding is what turns a number into a judgment, and 20 lands in the worst band either way.

Linear deduction was rejected outright: it is not size-normalized, and it floors at zero on any real codebase, losing all information exactly when it is needed.

Size normalization

relevant_lines counts only the lines a category's detectors could apply to, so two codebases with the same defect rate score the same whatever their size. A large clean codebase is not punished for being large.

The overall score

A weighted arithmetic mean of the categories that applied.

A geometric mean was considered, since it stops one terrible category hiding behind three good ones: 95, 95, 95 and 5 give 72 arithmetically and 46 geometrically. It was rejected because explainability is the entire thesis here. An arithmetic mean is what a reader assumes a weighted average to be, and the per-category breakdown is always shown, so a category scoring 5 is visible on its face.

What a score refuses to say

A category with nothing to inspect is reported as not applicable, never as 100. Nothing was checked, so nothing is known.

A category with fewer relevant lines than the configured minimum is reported as low confidence and given no score, because a rate over too few lines is noise.

When there is no overall score

The overall figure is the weighted mean of the categories that scored, and it is withheld whenever any applicable category could not be measured. An overall claims to summarize the whole project, so computing it from the categories that happened to work would present a partial summary as a complete one.

Every category that did score is still shown, and every one that did not says why. A reader loses only a number that would have been wrong.

Dual licensed under MIT or Apache-2.0.