Fast, deterministic linter for AI 'slop' in Python — runs right after Ruff.
Project description
sloplint
A fast, deterministic, no-LLM linter that counters AI slop in Python — a deliberately nitpicking, opinionated layer that runs right after Ruff in the same CI job. Ruff handles standard linting; sloplint adds the strict, slop-specific judgments Ruff intentionally won't ship, and never re-checks anything Ruff already covers.
Written in Rust, reusing Ruff's own parser crates for a full-fidelity AST + token stream.
Features
Rules that flag slop patterns no mainstream linter covers today. Stable rules run by
default; preview rules are heuristic — enable them with --preview.
| Rule | Stability | What it flags |
|---|---|---|
SLP010 |
stable | Comments — banned by default (relax per-path in sloplint.toml) |
SLP020 |
stable | Cross-file duplicate / near-duplicate functions — copy-paste and "same logic, slightly different" |
SLP030 |
stable | Overly defensive try/except |
SLP050 |
stable | Non-ASCII source (e.g. emoji) |
SLP080 |
stable | Oversized files (default: > 400 lines, configurable via file_max_lines) |
SLP082 |
stable | Deep control-flow nesting inside a function (default: > 4 levels, via nesting_max_depth) |
SLP090 |
stable | Flat-directory fanout — too many .py modules in one directory (default: > 15, via dir_max_modules) |
SLP001 |
preview | Redundant "what" comments that just restate the code |
SLP002 |
preview | Redundant docstrings that just restate the code |
SLP040 |
preview | Redundant type hints |
SLP060 |
preview | Verbose, mechanical identifier naming |
SLP084 |
preview | Deeply nested data-structure literals (a dict-of-lists-of-dicts blob past a depth — model it with a named type) |
SLP120 |
preview | Low-cohesion "god classes" via LCOM4 (methods that split into unrelated groups) |
SLP180 |
preview | Undeclared third-party imports — a module imported but missing from the project's pyproject.toml/requirements*.txt (broken on a clean install) |
Plus software-quality metrics (cyclomatic + cognitive complexity, LCOM4 cohesion) with
McCabe risk tiers, shields badges, and a per-PR summary — and package/module architecture
metrics over the import graph (dependency cycles, coupling/instability, propagation cost,
modularity) — via the metrics command and the GitHub Action.
Installation
sloplint ships on PyPI as sloplintpy (the wheel bundles the native binary — no Rust
toolchain needed). The installed command is sloplint.
Run it directly with uvx (the package and command differ, so use
--from):
uvx --from sloplintpy sloplint check # Lint all files in the current directory.
uvx --from sloplintpy sloplint metrics # Report software-quality metrics.
Or install sloplintpy with uv (recommended), pip, or pipx — then run sloplint:
# With uv.
uv tool install sloplintpy@latest # Install the `sloplint` command globally.
uv add --dev sloplintpy # Or add it to your project.
# With pip.
pip install sloplintpy
# With pipx.
pipx install sloplintpy
Usage
Once installed, sloplint is a native binary on your PATH:
sloplint check path/to/code # lint (exit 1 on findings)
sloplint check src --format sarif # SARIF / json / github / text
sloplint metrics src # software-quality metrics table
sloplint metrics src --format github # PR-summary markdown (CC risk tiers)
sloplint metrics src --format packages # per-package feed: coupling, cycles, abstractness (JSONL)
sloplint metrics src --max-cyclomatic 10 # CI gate: exit 1 over McCabe's ceiling
sloplint metrics src --badges badges/ # emit SVG + shields-endpoint badges
sloplint init # wire sloplint into your AI coding tool (see below)
sloplint parse file.py # dump AST + tokens (debug aid)
From a clone, run it through cargo instead (cargo run -p sloplint -- check path/to/code), or
build a wheel locally with maturin (maturin build --release).
Comments are banned by default; relax per-path (see Configuration). Preview
rules need --preview.
Agent-loop integration
sloplint is fast, deterministic and reproducible — so instead of only catching slop in CI, after the code has landed, you can run it inside your AI coding tool's edit loop. The tool fires a hook after every file edit, sloplint checks the just-edited file, and any findings go straight back to the agent so it self-corrects in the same turn — a guardrail, not just a gate.
sloplint init # detect the tools in this repo and wire them up
sloplint init --tool claude # or target one: claude | cursor | aider | all
sloplint init --dry-run # preview the config changes without writing
init writes (merging into any existing config, never clobbering it):
| Tool | Config | Mechanism |
|---|---|---|
| Claude Code | .claude/settings.json |
PostToolUse hook → sloplint check --hook --format agent |
| Cursor | .cursor/hooks.json |
afterFileEdit hook → sloplint check --hook --format agent |
| Aider | .aider.conf.yml |
lint-cmd: "python: sloplint check --format agent" |
The Claude Code and Cursor hooks pass the edited path as JSON on stdin; check --hook reads it
(no jq needed), lints just that file with the fast per-file rules, prints any findings to
stderr in the terse path:line:col: CODE message agent format, and exits 2 so the agent sees
them. A clean edit exits 0 silently. Whole-project rules (clone detection, dir fanout,
undeclared imports) still belong in the CI run — they need the whole tree, not one edit.
You can use the agent format anywhere, not just in hooks: sloplint check src --format agent.
Configuration
sloplint reads sloplint.toml, discovered from the working directory upward (or pass
--config <path>). Every key is optional — the defaults are shown below.
ignore = ["SLP040"] # turn specific rules/prefixes off
select = [] # force-enable rules/prefixes
preview = false # enable preview rules (same as --preview)
[limits] # thresholds for the size/structure rules
file_max_lines = 400 # SLP080
nesting_max_depth = 4 # SLP082
data_nesting_max_depth = 3 # SLP084
max_identifier_words = 4 # SLP060
dir_max_modules = 15 # SLP090
lcom4_max_components = 1 # SLP120 — flag a class that splits into > 1 cohesion group
lcom4_min_methods = 3 # SLP120 — skip classes smaller than this
[clone] # SLP020 near-duplicate detection
min_statements = 3 # ignore tiny functions
similarity = 0.85 # Jaccard similarity at/above which a pair is reported
[imports] # SLP180 undeclared third-party import
extra = [] # extra distribution names to treat as declared (suppress FPs)
[badges] # which `metrics --badges` files to emit (see Metrics & badges)
# include = ["cyclomatic-risk"] # per-metric badges; omit = all, [] = none
summary = [] # metrics to fold into one combined `sloplint` badge
[[overrides]] # relax rules for matching paths (gitignore-style globs)
path = "tests/**"
ignore = ["SLP010"]
allow_comments = true # permit comments here (otherwise banned)
Metrics & badges
Beyond the lint rules, sloplint metrics reports software-quality metrics — cyclomatic and
cognitive complexity (with McCabe risk tiers), average function length, max nesting, and comment
density. These are measured, not linted, so they never duplicate Ruff. Gate them in CI by
exit code (each names the offending functions and exits 1):
sloplint metrics src --max-cyclomatic 10 # fail if any function's cyclomatic complexity > 10
sloplint metrics src --max-cognitive 15 # ditto for SonarSource cognitive complexity
--badges badges/ writes an SVG + a shields.io endpoint JSON for
each metric (cyclomatic-risk, max-cognitive, avg-function-loc, max-nesting,
comment-density, …) — for example:
Choose which badges via [badges] in sloplint.toml: include picks the per-metric badges
(omit the key for all, [] for none), and summary folds a list of metrics into one combined
sloplint badge colored by the worst tier — e.g. include = [] + summary = [...] emits only:
Commit the SVGs, or host the *.json and point a shields URL at it for a badge that updates
itself. The GitHub Action writes them when you set its badges-dir input.
Package & module architecture metrics
sloplint metrics also analyzes the project's first-party import graph — the metrics the
literature ties most directly to architectural decay, and the ones AI-generated codebases tend to
do worst (circular imports, god-modules, flat dumping-grounds, hidden coupling). All deterministic
and reproducible — no LLM, no randomness. Two feeds:
-
--format packages— one JSONL row per package (directory):modules,loc, efferent / afferent coupling (ce/ca) and Martininstability,abstractness+distancefrom the main sequence, whether it sits in a dependency cycle (in_cycle), and the first-party packages itimports/ isimported_by. The per-package discovery feed, mirroring--format functions/--format classes. -
--format json— a per-projectpackagesrollup alongside the complexity figures:"packages": { "modules": 412, "packages": 37, "module_edges": 689, "package_edges": 81, "cycles": { // cyclic dependency tangles (Tarjan SCC) — 2–11× defect density "tangles": 3, "largest_tangle": 9, "modules_in_cycles": 21, "pct_modules_in_cycles": 0.051, "runtime_tangles": 2, // dropping `if TYPE_CHECKING:`-only edges (benign at runtime) "members": [["pkg.a", "pkg.b", "pkg.c"]] }, "propagation_cost": 0.18, // how far a change ripples (DSM transitive-closure density) "modularity": { // Newman–Girvan Q: declared packages vs. detected communities "q_declared": 0.41, "communities_declared": 37, "q_detected": 0.55, "communities_detected": 29, "gap": 0.14 // large positive gap ⇒ "packages in name only" } }
These are research-backed structural signals (Martin's package metrics; MacCormack's propagation cost; Newman–Girvan modularity; Melton & Tempero on cyclic dependencies) — descriptive measures for tracking a repo over time or comparing across codebases, not pass/fail gates. (Published clean-vs-slop reference distributions are the job of the benchmark harness, #55.)
GitHub Action
Run sloplint on every PR — it uploads SARIF (inline annotations), posts a findings summary comment, and can emit metric badges:
permissions:
contents: read
security-events: write
pull-requests: write
jobs:
sloplint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: galthran-wq/sloplint@main
with:
paths: src
badges-dir: .sloplint-badges # optional
Intended to run after Ruff in the same job. See action.yml for all inputs.
Required permissions: security-events: write (SARIF upload) and pull-requests: write
(PR comment) — both shown above. Without them the action degrades gracefully (it warns
rather than failing).
By default the action downloads a prebuilt binary for the runner (set version: to a
release tag like v0.2.0, or latest); if none is available it builds from source. Cut a
release to publish binaries:
git tag v0.2.0 && git push origin v0.2.0 # triggers .github/workflows/release.yml
Layout
| Crate | Role |
|---|---|
sloplint |
CLI binary |
sloplint_linter |
all rules + core run logic (cf. ruff_linter) |
sloplint_python |
parser seam over the pinned ruff_* crates |
sloplint_diagnostics |
rule-independent diagnostic model |
sloplint_clone |
near-duplicate function detection |
sloplint_metrics |
quality metrics, import-graph architecture metrics, badges |
sloplint_report |
output formatters (text/JSON/SARIF/markdown) |
sloplint_dev |
development utilities (cf. ruff_dev) |
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sloplintpy-0.3.0-py3-none-win_amd64.whl.
File metadata
- Download URL: sloplintpy-0.3.0-py3-none-win_amd64.whl
- Upload date:
- Size: 2.3 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2bed6041f4288a9e587855b7177bbfbda4e7c669d5673c5dc6285271054f2503
|
|
| MD5 |
2a4ca5f3ac4b270e58e4ba9ae72f8270
|
|
| BLAKE2b-256 |
21934508844d8ec736ba71a69e74817224538d9a39b0d18c7ca0070c810fd95d
|
File details
Details for the file sloplintpy-0.3.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: sloplintpy-0.3.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 2.4 MB
- Tags: Python 3, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
58ef83f8855c2d2334737c6544f5c283e926c51bd79591d44f1dcbc0689f0eb3
|
|
| MD5 |
be82f61702925ee35648b36afc781426
|
|
| BLAKE2b-256 |
f4e72b2e880c16ce889dbebb346fde064968e7d1bbf816a4afa5e2fe2155d559
|
File details
Details for the file sloplintpy-0.3.0-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: sloplintpy-0.3.0-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 2.2 MB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2d8b1e134aa1a1396e7a1a72825743f019a8bb86997b96be4d89cfdb440adff6
|
|
| MD5 |
a1665d05428b9f06c8ab31c180d80b81
|
|
| BLAKE2b-256 |
3533d1671b1c0e8a01a34f82744fd33b5510a7d466c784d9380ea2e4f79f4693
|
File details
Details for the file sloplintpy-0.3.0-py3-none-macosx_10_12_x86_64.whl.
File metadata
- Download URL: sloplintpy-0.3.0-py3-none-macosx_10_12_x86_64.whl
- Upload date:
- Size: 2.3 MB
- Tags: Python 3, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a910b732e3bd6ea31f190c2829da1c8a4afd96847b28267984cd6cdd214ac4c7
|
|
| MD5 |
fb8a25c81edfd6f8cbbb70aafa20d1ef
|
|
| BLAKE2b-256 |
fdb3b4cf88ae2288c7a513676b97e97e267a4255b72c3daab5c97151c3ea7d46
|