Baselines: adopt now, review the backlog deliberately #
A baseline is the reviewed starting set of findings already present when a repository adopts LintLang. It records where the repository was at introduction, acknowledges that existing backlog explicitly, and distinguishes new or changed findings without treating the pre-existing backlog as newly introduced.
Use one when enabling a gate on an existing repository whose known findings
cannot all be resolved at once. It is not a rule-wide ignore or a declaration
that the acknowledged instructions are correct. The default scanner behavior is
unchanged until you pass --baseline.
Availability: baseline support is included in released LintLang 0.6.0 and later. Install the release:
python -m pip install lintlang==0.7.1
Create and review the starting set #
Run from your project's Git root. Replace AGENTS.md with the actual file or
supported directory your team wants to gate:
lintlang scan AGENTS.md --write-baseline .lintlang-baseline.json
This prints the complete scan report and writes a new baseline. Review the findings themselves before committing the file: confirm the intended scan scope, identify false positives or accepted backlog, and decide what should instead be fixed now. Hashes in the baseline are not a substitute for reading the report.
Creation is explicit. It never overwrites an existing path, including a symlink,
and writes nothing if any input fails or no input is scanned. A successfully
scanned file with zero findings can produce a valid empty baseline; scanning zero
files cannot. The destination's parent directory must already exist. Omit
--fail-on and --fail-under for the initial inventory: those gates can make a
completed inventory return nonzero because of findings or quality scores.
Apply it locally #
After review, commit the baseline with the gate configuration:
lintlang scan AGENTS.md --baseline .lintlang-baseline.json --fail-on review
--fail-on review blocks remaining MEDIUM, HIGH, or CRITICAL findings.
--fail-on fail blocks remaining HIGH or CRITICAL findings. An unchanged scan
with all findings acknowledged passes either structural gate. A newly added
file receives no allowance from another file's entries.
Use the same scan paths, --patterns, --min-severity, and directory exclusions
when creating and applying a baseline. Filters run before baseline matching.
Expanding the selected rules or severity range can reveal findings that were
never acknowledged. Directory scans exclude the active baseline file itself;
this does not exclude every JSON report or historical baseline in that directory.
Apply it in GitHub CI #
Add the optional baseline input to your existing LintLang Action step:
- uses: hermes-labs-ai/lintlang@175a9a19414aff9a1752d3b9850d6cf58d59fb32 # v0.7.0
with:
path: AGENTS.md
baseline: .lintlang-baseline.json
fail-on: review
The Action installs the scanner from its own checked-out source. For the complete workflow, immutable pins, and permissions, use the GitHub guide rather than maintaining another workflow here.
The same input works with sarif-file. Only remaining findings are emitted as
SARIF results; the run's properties.lintlangBaseline contains the suppressed
count and verdict scope. The Action rejects a SARIF destination that resolves
to the baseline, including symlink aliases. See
Code Scanning permission and upload guidance.
CI only reads the committed baseline. It never creates or updates one. Review baseline edits as carefully as changes to the CI gate itself: anyone who can change the baseline can acknowledge findings.
Exact, count-limited matching #
Each entry identifies a repository-relative POSIX path, a SHA-256 finding fingerprint, and an occurrence count. The fingerprint includes the diagnostic code, severity, diagnostic location, description, and evidence. Matching is exact: there are no wildcards or rule-wide exemptions.
A changed code, severity, location, message, or evidence can reopen a finding, including after a detector upgrade. Suggestions, the rule's display name, and the separate source-region field are not identity. Python source-path prefixes in diagnostic locations are normalized to the repository-relative path; line information encoded in the location remains part of the identity.
Additional occurrences beyond the recorded count remain visible. Repeating the
same file through multiple CLI paths does not enlarge its allowance. HERM
scores, quality thresholds, and source input errors are unchanged. Terminal and
Markdown reports state that their verdict covers remaining findings; JSON adds
baseline.suppressed to each scanned result.
Paths and invocation roots #
Inside a Git worktree, matching uses the nearest repository root. In a non-Git directory, the invocation directory is the root. Source paths are resolved before matching; inputs outside that root are rejected, including symlink escapes.
The --baseline filename and ordinary scan arguments are resolved from the
invocation directory, not automatically from the Git root. Run from the same
project root locally and in CI so both the selected files and baseline path are
unambiguous. Absolute versus relative Python invocation paths do not by themselves
change a normalized fingerprint; moving or renaming the source can.
Invalid inputs and privacy #
Missing, malformed, or unsupported baseline inputs and failed baseline writes return nonzero even without a severity gate. The loader rejects unknown fields and schema versions, duplicate entries or JSON keys, noncanonical paths, invalid hashes, and invalid counts. Source input errors also remain nonzero and cannot be acknowledged away. See the exact schema in the technical reference.
The baseline stores paths and hashes, not raw prompts or finding evidence. Hashes are not encryption: review filenames and the sensitivity of your inputs before publishing a baseline. Ordinary scan reports may still contain evidence.
Refresh and shrink the backlog #
After fixing findings, create a candidate at a new path and review it before replacing the committed baseline:
lintlang scan AGENTS.md --write-baseline .lintlang-baseline.candidate.json
git diff --no-index .lintlang-baseline.json .lintlang-baseline.candidate.json
# After review:
mv .lintlang-baseline.candidate.json .lintlang-baseline.json
git diff --no-index exits 1 when the files differ; that is expected. Compare
the complete scan report as well as the hash changes. Check that removed entries
represent resolved or intentionally out-of-scope findings, and review any new
allowances explicitly. Never refresh automatically after a failed gate: doing
so can accept the very changes the gate was meant to surface.
Unused entries are allowed, so removing a finding does not itself break CI. Prune them through the reviewed refresh above. An identical finding reintroduced at the same identity can still match an old entry until it is removed. A baseline records acknowledged identities, not the history of when each defect was fixed. It does not assess runtime behavior or establish safety.
Related: GitHub CI, integrations, technical reference, README.