Spirolo

How far to trust a finding

How rules are measured, the four tiers, and what can fail your build.

No rule is proven yet. Every rule still runs on every check, but the default view only shows the rules whose measurements say they are usually right.

How a rule is measured

Each rule runs on a corpus of open-source projects. A sample of its findings is read against the source, one by one, and each is decided true or false. The counts are recorded in a status table that is built into the binary, so spirolo explain <rule> and spirolo rule-status print what your copy carries.

Each rule has a verdict:

VerdictMeaning
PASSProven: cleared the bar below. No rule has yet.
UNADJUDICATEDFindings were read and counted, without the adjudication a verdict needs.
INSUFFICIENTToo few decided findings in the corpus to call it.
SKIPPEDThe measurement skipped the rule; no precision claim.
UNMEASUREDNo row in the table.
FAILPrecision measured below the bar.
DROPPEDRetired by its review: never shown by default, never gates.

A rule is proven when the lower bound of its 95% Wilson interval reaches 0.9 on the pooled sample, which means at least 35 decided findings with none false, and it then holds on a fresh held-out check.

The four tiers

TierWhich rulesDefault viewWithout an override
provenverdict PASSshownthe rule's own severity
advisorynot FAIL or DROPPED, at least 15 decided, at least 80% trueshowninfo
experimentalevery other rule that is not FAIL or DROPPED--all onlyinfo
hiddenverdict FAIL or DROPPED--all onlyinfo, always

The tier is computed from the table alone, so a rule moves when its numbers change, with no code change. 16 true out of 20 is advisory; 11 out of 15 is not.

Every rule today

RuleFamilyTierTrue / decided
absence-anchorWeak testsadvisory16 / 20
fail-closed-setupMerge gateadvisory19 / 19
dead-skipMerge gateadvisory18 / 18
duplicate-testUnused testsadvisory15 / 18
gate-truthMerge gateexperimental12 / 18
unused-mockUnused testsexperimentalnone
flake-candidateFlakyexperimental3 / 6
untested-hotspotHealthexperimental5 / 16
weak-assertionWeak testshidden12 / 34
handler-driftWeak testshiddennone
data-layer-mockWeak testshiddennone
generator-walkWeak testshiddennone
redundant-testUnused testshidden0 / 12
orphan-testUnused testshidden4 / 14
stale-snapshotUnused testshiddennone
flake-observedFlakyhidden39 / 52

What can fail your build

Nothing, until you say so. An unproven rule reports at info and leaves the exit code at 0. To make a rule gate, give it warn or error in .spirolorc.jsonc:

{ "rules": { "dead-skip": { "severity": "warn" } } }
  • On an advisory or experimental rule, the override applies: its rows show by default at that severity, and a warn or error row exits 1.
  • On a FAIL or DROPPED rule, the override is not applied. The report says so: note config severity for orphan-test not applied: rule is DROPPED.
  • An info override is accepted but changes nothing.
  • The Unused tests rules accept warn at most. error on one of them is a config error, and the command exits 2.

When you upgrade, a rule that drops below a floor leaves the default view but keeps gating if you opted it in. A rule that becomes FAIL or DROPPED stops gating, with the note above.

On this page