Skip to content

Baseline mode

Turning a linter on for an established repo has a chicken-and-egg problem: the rules go red on thousands of pre-existing violations the current change never introduced. Baseline mode breaks it. You record today’s violations into a committed baseline file, and from then on alint check reports and gates on only the findings that are new relative to that snapshot. It is the ratchet: stop the bleeding now, pay the backlog down on your own schedule.

A baseline suppresses recorded findings, lets new ones fail, and prunes stale ones This run's findings are checked against the recorded baseline by fingerprint. A recorded finding is suppressed even after its line moved; a finding not in the baseline is new and fails the gate; a recorded finding gone from this run is stale and pruned. this run, checked against the baseline by fingerprint no-todo-commentsapi.ts (line moved)suppressed no-todo-commentsnew.tsnew: fails the gate lockfiles-only-onefixed sincestale: pruned the fingerprint keys on content, not the line number a finding that only moved lines stays suppressed

1. Record the baseline. Run once when you adopt alint (and again when you deliberately pay down or accept debt):

Terminal window
alint baseline

This runs the same whole-tree evaluation as check, then writes every current violation to .alint-baseline.json. Commit that file.

2. Enforce the delta. In CI and locally:

Terminal window
alint check --baseline .alint-baseline.json

Every violation whose fingerprint is in the baseline is suppressed (up to its recorded count); only new violations are reported, and only they drive the exit code. Persist the path in .alint.yml (baseline: .alint-baseline.json) so CI need not repeat the flag. A --baseline flag overrides the config key, and there is no silent auto-detect: suppression is always an explicit, committed decision.

The crux of a usable baseline is a stable identity for each violation. alint fingerprints a violation as a SHA-256 over its rule, its path, and a content discriminator, chosen in priority order: a rule may supply its own key (a structured-query rule keys on its query plus the operator and the value it matched, so two different bad values at one path like $.license stay distinct findings; a whole-file rule keys on the path alone), otherwise the offending line’s text is used, and a path-bearing finding with no line keys on (rule, path) with the message deliberately left out of the hash. The line number is never part of it.

So inserting or deleting unrelated lines never churns the baseline. Editing the offending line re-keys a line-anchored finding, so it counts as new and the gate catches it, but a structured-query or whole-file finding keeps its identity across unrelated edits. The baseline survives ordinary refactoring without stale-entry noise, and never masks a genuinely new problem.

.alint-baseline.json is JSON Lines: a header, then one sorted entry per grandfathered finding.

{"schema_version":1,"alint_version":"0.16.1"}
{"rule_id":"no-todo-comments","path":"src/legacy/api.ts","fingerprint":"<64-hex>","count":3,"message":"TODO without an owner"}
{"rule_id":"lockfiles-only-one","path":null,"fingerprint":"<64-hex>","count":1,"message":"Multiple lockfiles found"}

One entry per line (not a JSON array) is deliberately merge-friendly, and the sorted order makes an unchanged tree regenerate byte-for-byte. Only fingerprint and count are matched; rule_id, path, and message are advisory, there so a reviewer reading the diff can see what is being grandfathered. The count is a budget: identical findings collapse into one entry, and if the tree later holds more occurrences than recorded, the excess is reported as new; if fewer, the remainder is pruned as stale.

Re-running alint baseline on a repo that already has one will not silently grandfather new debt. If the re-run would add any new fingerprint (or a higher count), it refuses to write and tells you to fix them or opt in:

regenerating .alint-baseline.json would grandfather 2 new violation(s) (+2 / -1);
fix them, or pass --accept-new to accept them into the baseline

Pruning stale entries (findings you have since fixed) is always safe and happens without a flag, so a pure-cleanup re-run just rewrites the file. Accepting new debt is always explicit, with --accept-new. Separately, at enforcement time, alint check --baseline warns about stale entries by default, and --strict-baseline turns those warnings into a hard failure so a baseline cannot quietly rot.

Suppression marks violations rather than deleting them, so sarif emits suppressed results with baselineState: "unchanged" (keeping GitHub Code Scanning alerts open-but-dismissed instead of flapping) and json carries a summary.baselined_suppressed count. Only sarif and json are baseline-aware; the other formats receive the already-filtered live report. The --show-baselined flag lists the suppressed findings in any format, and the exit code is gated on the live (new) findings only, unless --strict-baseline also fails the run on stale entries.

Adopt alint on a repo with a backlog of no-todo-comments hits, then gate on the delta:

Terminal window
alint baseline # records today's violations, commit it
alint check --baseline .alint-baseline.json # CI: only NEW findings fail

A pull request that adds one fresh TODO, while the legacy ones stay suppressed, fails on exactly that one:

error no-todo-comments TODO without an owner