lint
Deterministic, differential hygiene gate. Runs static-analysis / style / spelling tools over the files a change touches and reports ONLY newly-introduced findings, so pre-existing debt never blocks. R
SKILL.md.claude/skills/lint/SKILL.md
GitHublint
A cheap, deterministic pass that catches the mechanical defect class model reviewers systematically miss (formatting, dead code, obvious cppcheck-class bugs, malformed markdown, comment typos, trailing whitespace). Run it before the expensive Opus-tier reviewers so their attention goes to what only they can judge — ABI footguns, physics parity — not style nits.
It is advisory, not a merge gate: nothing in CI or pr-reviewer blocks on
it. It is a local tool you (or an implementer agent) run before opening a PR.
What it does
scripts/lint.py check finds the files your change touches (vs origin/main by
default), runs each applicable tool over them, and — the important part — runs
the same tools over the same files at the base ref (checked out into a throwaway
detached git worktree in the system temp dir) and subtracts the base
findings. You see only what your change newly introduced. Legacy debt in the
C++ core therefore never drowns the signal.
# lint everything changed on this branch vs origin/main (the default)
python .claude/skills/lint/scripts/lint.py check
# diff against a specific base
python .claude/skills/lint/scripts/lint.py check --base main
# lint specific files regardless of git state
python .claude/skills/lint/scripts/lint.py check --paths src/foo.cpp include/bar.h
# scan the whole tracked tree (non-differential — noisy, for audits only)
python .claude/skills/lint/scripts/lint.py check --all
# make a missing linter a hard failure instead of a warning
python .claude/skills/lint/scripts/lint.py check --require-coverage
Tools in the registry
| Tool | Files | Notes |
|---|---|---|
ruff |
.py |
default ruff ruleset (pyflakes + pycodestyle core) |
cppcheck |
.cpp .cc .cxx .hpp .hh .h |
.cu/.cuh excluded — they need nvcc flags |
clang-format |
C/C++/CUDA | only runs if a root .clang-format exists |
markdownlint |
.md |
markdownlint-cli2 |
codespell |
code + docs | spelling in identifiers/comments/prose |
shellcheck |
.sh .bash |
JSON output |
git-diff-check |
any | trailing whitespace / conflict markers (built-in) |
Non-negotiable invariants
- Never installs anything. A missing tool is reported
unavailablewith an install hint and (if--require-coverage) a coverage gap — it is never counted as a pass. - A crashing tool is an
error, never a pass. - Nothing is auto-fixed. No
--fix, no--write-changes. It reports; you fix.
Exit codes
0 clean · 1 new findings · 2 a tool errored · 3 coverage gap
(only with --require-coverage). Precedence when several apply:
error(2) > coverage-gap(3) > findings(1).
Enabling full coverage on a machine (per-machine, one time)
The script and this skill sync via OneDrive, but the linters themselves are
per-machine binaries that live outside the repo — they are not synced. On a
fresh machine the gate still runs and still refuses to lie (missing tools show
unavailable), but to get real coverage install the tools once:
python -m pip install ruff codespell # Python lint + spelling
npm install -g markdownlint-cli2 # Markdown
choco install llvm shellcheck # clang-format + shellcheck
choco install cppcheck # C++
clang-formatstays dormant until a root.clang-formatexists — without a project style it would emit noise against LLVM defaults. Add one deliberately if/when the team wants C++ formatting enforced.
When to invoke
- An implementer runs
/lintafter finishing a package and before opening the PR, so mechanical findings are fixed beforecpp-abi-guard/cycles-parity-reviewer/pr-reviewerever look at the diff. - Any time you want a fast, deterministic read on "did I add anything sloppy?" without waiting on a model review.
Anti-patterns
- Treating
unavailableas "clean" — it means unchecked. Install the tool or accept the gap knowingly (--require-coveragemakes the gap loud). - Running
--alland trying to fix every pre-existing finding — that's the debt the differential mode exists to keep out of your way. Fix what your change adds. - Wiring this into CI or
pr-revieweras a blocking gate without a tuning pass first — cppcheck throws false positives on template-heavy path-tracer code, so it earns trust before it earns a veto.