slopcheck-lint 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. slopcheck_lint-0.1.0/.agents/skills/slopcheck/SKILL.md +1 -0
  2. slopcheck_lint-0.1.0/.claude/skills/slopcheck/SKILL.md +1 -0
  3. slopcheck_lint-0.1.0/.cursor/skills/slopcheck/SKILL.md +1 -0
  4. slopcheck_lint-0.1.0/.github/ISSUE_TEMPLATE/bug.yml +34 -0
  5. slopcheck_lint-0.1.0/.github/ISSUE_TEMPLATE/config.yml +1 -0
  6. slopcheck_lint-0.1.0/.github/ISSUE_TEMPLATE/rule.yml +29 -0
  7. slopcheck_lint-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +9 -0
  8. slopcheck_lint-0.1.0/.github/workflows/ci.yml +24 -0
  9. slopcheck_lint-0.1.0/.github/workflows/release.yml +44 -0
  10. slopcheck_lint-0.1.0/.gitignore +18 -0
  11. slopcheck_lint-0.1.0/.pre-commit-hooks.yaml +9 -0
  12. slopcheck_lint-0.1.0/CHANGELOG.md +12 -0
  13. slopcheck_lint-0.1.0/CONTRIBUTING.md +48 -0
  14. slopcheck_lint-0.1.0/LICENSE +21 -0
  15. slopcheck_lint-0.1.0/PKG-INFO +250 -0
  16. slopcheck_lint-0.1.0/README.md +218 -0
  17. slopcheck_lint-0.1.0/README.zh-CN.md +218 -0
  18. slopcheck_lint-0.1.0/action.yml +73 -0
  19. slopcheck_lint-0.1.0/demo/taskflow/__init__.py +1 -0
  20. slopcheck_lint-0.1.0/demo/taskflow/client.py +53 -0
  21. slopcheck_lint-0.1.0/demo/taskflow/imports_hallucinated.py +9 -0
  22. slopcheck_lint-0.1.0/demo/taskflow/ok.py +41 -0
  23. slopcheck_lint-0.1.0/demo/taskflow/utils.py +87 -0
  24. slopcheck_lint-0.1.0/docs/before-after.png +0 -0
  25. slopcheck_lint-0.1.0/docs/demo-report.html +181 -0
  26. slopcheck_lint-0.1.0/docs/demo.gif +0 -0
  27. slopcheck_lint-0.1.0/docs/demo.svg +281 -0
  28. slopcheck_lint-0.1.0/docs/demo.txt +65 -0
  29. slopcheck_lint-0.1.0/docs/hero-dark.png +0 -0
  30. slopcheck_lint-0.1.0/docs/hero-light.png +0 -0
  31. slopcheck_lint-0.1.0/docs/report.png +0 -0
  32. slopcheck_lint-0.1.0/docs/slop-score.svg +12 -0
  33. slopcheck_lint-0.1.0/docs/social-preview.png +0 -0
  34. slopcheck_lint-0.1.0/examples/hooks/claude-code.settings.json +16 -0
  35. slopcheck_lint-0.1.0/examples/hooks/slopcheck.sh +35 -0
  36. slopcheck_lint-0.1.0/examples/pre-commit-config.yaml +5 -0
  37. slopcheck_lint-0.1.0/examples/workflows/slopcheck.yml +19 -0
  38. slopcheck_lint-0.1.0/pyproject.toml +76 -0
  39. slopcheck_lint-0.1.0/scripts/render_assets.py +114 -0
  40. slopcheck_lint-0.1.0/scripts/render_launch.py +682 -0
  41. slopcheck_lint-0.1.0/skills/slopcheck/SKILL.md +78 -0
  42. slopcheck_lint-0.1.0/src/slopcheck/__init__.py +7 -0
  43. slopcheck_lint-0.1.0/src/slopcheck/__main__.py +6 -0
  44. slopcheck_lint-0.1.0/src/slopcheck/cli.py +306 -0
  45. slopcheck_lint-0.1.0/src/slopcheck/config.py +189 -0
  46. slopcheck_lint-0.1.0/src/slopcheck/discover.py +172 -0
  47. slopcheck_lint-0.1.0/src/slopcheck/engine.py +188 -0
  48. slopcheck_lint-0.1.0/src/slopcheck/formatters/__init__.py +9 -0
  49. slopcheck_lint-0.1.0/src/slopcheck/formatters/badge.py +47 -0
  50. slopcheck_lint-0.1.0/src/slopcheck/formatters/html.py +211 -0
  51. slopcheck_lint-0.1.0/src/slopcheck/formatters/jsonfmt.py +62 -0
  52. slopcheck_lint-0.1.0/src/slopcheck/formatters/prompt.py +60 -0
  53. slopcheck_lint-0.1.0/src/slopcheck/formatters/sarif.py +83 -0
  54. slopcheck_lint-0.1.0/src/slopcheck/formatters/text.py +135 -0
  55. slopcheck_lint-0.1.0/src/slopcheck/globs.py +42 -0
  56. slopcheck_lint-0.1.0/src/slopcheck/ignore.py +113 -0
  57. slopcheck_lint-0.1.0/src/slopcheck/models.py +60 -0
  58. slopcheck_lint-0.1.0/src/slopcheck/parse.py +82 -0
  59. slopcheck_lint-0.1.0/src/slopcheck/resolver.py +134 -0
  60. slopcheck_lint-0.1.0/src/slopcheck/rules/__init__.py +43 -0
  61. slopcheck_lint-0.1.0/src/slopcheck/rules/comments.py +228 -0
  62. slopcheck_lint-0.1.0/src/slopcheck/rules/defensive.py +282 -0
  63. slopcheck_lint-0.1.0/src/slopcheck/rules/exceptions.py +262 -0
  64. slopcheck_lint-0.1.0/src/slopcheck/rules/hygiene.py +514 -0
  65. slopcheck_lint-0.1.0/src/slopcheck/rules/imports.py +135 -0
  66. slopcheck_lint-0.1.0/src/slopcheck/rules/support.py +244 -0
  67. slopcheck_lint-0.1.0/src/slopcheck/score.py +55 -0
  68. slopcheck_lint-0.1.0/src/slopcheck/version.py +3 -0
  69. slopcheck_lint-0.1.0/tests/__init__.py +1 -0
  70. slopcheck_lint-0.1.0/tests/conftest.py +20 -0
  71. slopcheck_lint-0.1.0/tests/fixtures/clean/ok.py +41 -0
  72. slopcheck_lint-0.1.0/tests/fixtures/ignored/bare_ok.py +5 -0
  73. slopcheck_lint-0.1.0/tests/fixtures/sloppy/SLP001_bare_except.py +5 -0
  74. slopcheck_lint-0.1.0/tests/fixtures/sloppy/SLP002_broad_except.py +5 -0
  75. slopcheck_lint-0.1.0/tests/fixtures/sloppy/SLP003_fake_fallback.py +5 -0
  76. slopcheck_lint-0.1.0/tests/fixtures/sloppy/SLP004_narrating_comment.py +4 -0
  77. slopcheck_lint-0.1.0/tests/fixtures/sloppy/SLP005_debug_print.py +5 -0
  78. slopcheck_lint-0.1.0/tests/fixtures/sloppy/SLP006_unused_private.py +6 -0
  79. slopcheck_lint-0.1.0/tests/fixtures/sloppy/SLP007_near_duplicate.py +38 -0
  80. slopcheck_lint-0.1.0/tests/fixtures/sloppy/SLP008_over_defensive.py +5 -0
  81. slopcheck_lint-0.1.0/tests/fixtures/sloppy/SLP009_placeholder.py +3 -0
  82. slopcheck_lint-0.1.0/tests/fixtures/sloppy/SLP010_hallucinated_import.py +3 -0
  83. slopcheck_lint-0.1.0/tests/fixtures/sloppy/SLP011_try_nesting.py +11 -0
  84. slopcheck_lint-0.1.0/tests/fixtures/sloppy/SLP012_verbose_docstring.py +13 -0
  85. slopcheck_lint-0.1.0/tests/fixtures/sloppy/SLP013_versioned_name.py +2 -0
  86. slopcheck_lint-0.1.0/tests/fixtures/sloppy/SLP014_commented_code.py +4 -0
  87. slopcheck_lint-0.1.0/tests/fixtures/sloppy/SLP015_error_string.py +5 -0
  88. slopcheck_lint-0.1.0/tests/test_cli.py +109 -0
  89. slopcheck_lint-0.1.0/tests/test_config.py +73 -0
  90. slopcheck_lint-0.1.0/tests/test_demo.py +18 -0
  91. slopcheck_lint-0.1.0/tests/test_discover.py +51 -0
  92. slopcheck_lint-0.1.0/tests/test_formatters.py +56 -0
  93. slopcheck_lint-0.1.0/tests/test_ignore.py +80 -0
  94. slopcheck_lint-0.1.0/tests/test_readme.py +87 -0
  95. slopcheck_lint-0.1.0/tests/test_rules.py +236 -0
  96. slopcheck_lint-0.1.0/tests/test_score.py +48 -0
@@ -0,0 +1 @@
1
+ ../../../skills/slopcheck/SKILL.md
@@ -0,0 +1 @@
1
+ ../../../skills/slopcheck/SKILL.md
@@ -0,0 +1 @@
1
+ ../../../skills/slopcheck/SKILL.md
@@ -0,0 +1,34 @@
1
+ name: Bug
2
+ description: A rule misfired, a score looks wrong, or a command failed
3
+ title: "SLPxxx: "
4
+ labels: ["bug"]
5
+ body:
6
+ - type: textarea
7
+ id: what
8
+ attributes:
9
+ label: What happened
10
+ description: Include the rule id, the file, and the score if you have one.
11
+ validations:
12
+ required: true
13
+ - type: textarea
14
+ id: code
15
+ attributes:
16
+ label: Smallest Python snippet
17
+ description: Ten lines that reproduce it. Strip secrets.
18
+ render: python
19
+ validations:
20
+ required: true
21
+ - type: input
22
+ id: version
23
+ attributes:
24
+ label: slopcheck version
25
+ placeholder: "0.1.0"
26
+ validations:
27
+ required: true
28
+ - type: input
29
+ id: python
30
+ attributes:
31
+ label: Python version
32
+ placeholder: "3.12"
33
+ validations:
34
+ required: true
@@ -0,0 +1 @@
1
+ blank_issues_enabled: false
@@ -0,0 +1,29 @@
1
+ name: Rule idea
2
+ description: A pattern agents keep leaving that slopcheck should flag
3
+ title: "Rule: "
4
+ labels: ["rule"]
5
+ body:
6
+ - type: textarea
7
+ id: pattern
8
+ attributes:
9
+ label: The pattern
10
+ description: Show the messy version and the version you want to keep.
11
+ render: python
12
+ validations:
13
+ required: true
14
+ - type: textarea
15
+ id: why
16
+ attributes:
17
+ label: Why Ruff or Pylint does not already cover it
18
+ validations:
19
+ required: true
20
+ - type: dropdown
21
+ id: severity
22
+ attributes:
23
+ label: Suggested severity
24
+ options:
25
+ - error
26
+ - warning
27
+ - note
28
+ validations:
29
+ required: true
@@ -0,0 +1,9 @@
1
+ ## What
2
+
3
+ ## Why
4
+
5
+ ## Check
6
+
7
+ - [ ] `pytest`
8
+ - [ ] `slopcheck .` still scores 100
9
+ - [ ] README rule table updated if a rule id changed
@@ -0,0 +1,24 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+ strategy:
11
+ fail-fast: false
12
+ matrix:
13
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ - name: Install
20
+ run: python -m pip install -e ".[dev]"
21
+ - name: Test
22
+ run: pytest
23
+ - name: Dogfood
24
+ run: slopcheck .
@@ -0,0 +1,44 @@
1
+ name: release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ pypi:
13
+ runs-on: ubuntu-latest
14
+ environment:
15
+ name: pypi
16
+ url: https://pypi.org/p/slopcheck-lint
17
+ permissions:
18
+ id-token: write
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ - uses: actions/setup-python@v5
22
+ with:
23
+ python-version: "3.12"
24
+ - name: Check tag matches package version
25
+ run: |
26
+ python - << 'PY'
27
+ import os
28
+ import pathlib
29
+ import re
30
+ import sys
31
+
32
+ text = pathlib.Path("src/slopcheck/version.py").read_text(encoding="utf-8")
33
+ match = re.search(r'__version__ = "([^"]+)"', text)
34
+ version = match.group(1) if match else ""
35
+ tag = os.environ["GITHUB_REF_NAME"].removeprefix("v")
36
+ if version != tag:
37
+ sys.exit(f"tag {tag} does not match package {version}")
38
+ PY
39
+ - name: Build
40
+ run: |
41
+ python -m pip install build
42
+ python -m build
43
+ - name: Publish
44
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,18 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .eggs/
5
+ .venv/
6
+ venv/
7
+ env/
8
+ build/
9
+ dist/
10
+ .pytest_cache/
11
+ .mypy_cache/
12
+ .ruff_cache/
13
+ .coverage
14
+ htmlcov/
15
+ .tox/
16
+ .nox/
17
+ *.log
18
+ .DS_Store
@@ -0,0 +1,9 @@
1
+ - id: slopcheck
2
+ name: slopcheck
3
+ description: Flag AI-agent slop in Python
4
+ entry: slopcheck
5
+ language: python
6
+ language_version: python3
7
+ types_or: [python, pyi]
8
+ pass_filenames: true
9
+ minimum_pre_commit_version: "3.2.0"
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 — 2026-10-26
4
+
5
+ First release.
6
+
7
+ - Published on PyPI as `slopcheck-lint`, because `slopcheck` on PyPI belongs to another project. The CLI command and the import package are `slopcheck`: `pip install slopcheck-lint`, then run `slopcheck`.
8
+ - Fifteen rules, `SLP001` through `SLP015`, and a Slop Score from 0 to 100.
9
+ - Terminal, JSON, SARIF, HTML, SVG badge, and an agent fix prompt.
10
+ - Config in `[tool.slopcheck]`, inline `# slopcheck: ignore`.
11
+ - pre-commit hook, GitHub Action, and a skill for Claude Code, Codex, and Cursor.
12
+ - Python 3.10 through 3.13.
@@ -0,0 +1,48 @@
1
+ # Contributing
2
+
3
+ Issues and pull requests go to [yungcs/slopcheck](https://github.com/yungcs/slopcheck).
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ pip install -e ".[dev]"
9
+ pytest
10
+ slopcheck .
11
+ ```
12
+
13
+ `slopcheck .` has to score 100. The demo and the test fixtures are excluded on purpose. Scan them by passing the path: `slopcheck demo`.
14
+
15
+ Python 3.10 through 3.13. CI runs that matrix.
16
+
17
+ ## A new rule
18
+
19
+ 1. Add `SLP0xx` in `src/slopcheck/rules/`, and register it in `src/slopcheck/rules/__init__.py`.
20
+ 2. Add `tests/fixtures/sloppy/SLP0xx_*.py` that the rule flags, and a case in `tests/test_rules.py` for something it must keep.
21
+ 3. Put the id in both `README.md` and `README.zh-CN.md`.
22
+ 4. Keep the tool's own tree clean. Do not silence a rule to hide a hit in `src/`.
23
+
24
+ Rule ids are stable. Do not renumber one that has shipped.
25
+
26
+ ## Pictures
27
+
28
+ ```bash
29
+ python scripts/render_assets.py # SVG terminal, badge, HTML sample
30
+ python scripts/render_launch.py # hero, GIF, report shot, before/after, social card
31
+ ```
32
+
33
+ `render_launch.py` needs Pillow (installed with `.[dev]`) and ffmpeg. The GIF is built from a real `slopcheck demo` run (score 29) and a fixed tree tuned to score 94. Headless Chrome takes `docs/report.png`. If Chrome stays open after writing the PNG, the script stops it.
34
+
35
+ `docs/social-preview.png` is 1280×640 for the GitHub social preview setting. It is not embedded in the README.
36
+
37
+ ## Releasing
38
+
39
+ Tag `vX.Y.Z` only when `src/slopcheck/version.py` has the same version. [`.github/workflows/release.yml`](.github/workflows/release.yml) publishes to PyPI with trusted publishing.
40
+
41
+ The PyPI distribution is `slopcheck-lint` (the CLI command and import name stay `slopcheck`). On PyPI, add this GitHub repo as a trusted publisher for the `slopcheck-lint` project:
42
+
43
+ - Owner: `yungcs`
44
+ - Repository: `slopcheck`
45
+ - Workflow: `release.yml`
46
+ - Environment: `pypi`
47
+
48
+ The GitHub environment named `pypi` needs the `id-token: write` permission the workflow already requests.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 slopcheck contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,250 @@
1
+ Metadata-Version: 2.5
2
+ Name: slopcheck-lint
3
+ Version: 0.1.0
4
+ Summary: A linter for the mess AI coding agents leave in Python.
5
+ Project-URL: Homepage, https://github.com/yungcs/slopcheck
6
+ Project-URL: Repository, https://github.com/yungcs/slopcheck
7
+ Project-URL: Issues, https://github.com/yungcs/slopcheck/issues
8
+ Project-URL: Changelog, https://github.com/yungcs/slopcheck/blob/main/CHANGELOG.md
9
+ Author: slopcheck contributors
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: agents,ai,claude,cli,codex,cursor,linter,python,sarif,slop,static-analysis
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Software Development :: Quality Assurance
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: rich>=13.7
26
+ Requires-Dist: tomli>=2.0.1; python_version < '3.11'
27
+ Requires-Dist: typer>=0.12
28
+ Provides-Extra: dev
29
+ Requires-Dist: pillow>=10; extra == 'dev'
30
+ Requires-Dist: pytest>=8.0; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ <p align="center"><strong>English</strong> · <a href="README.zh-CN.md">简体中文</a></p>
34
+
35
+ <p align="center">
36
+ <picture>
37
+ <source media="(prefers-color-scheme: dark)" srcset="docs/hero-dark.png">
38
+ <img src="docs/hero-light.png" alt="slopcheck — A linter for the mess AI agents leave in Python" width="820">
39
+ </picture>
40
+ </p>
41
+
42
+ <p align="center">
43
+ <a href="https://pypi.org/project/slopcheck-lint/"><img alt="PyPI" src="https://img.shields.io/pypi/v/slopcheck-lint?label=pypi"></a>
44
+ <a href="https://github.com/yungcs/slopcheck/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/yungcs/slopcheck/actions/workflows/ci.yml/badge.svg"></a>
45
+ <a href="LICENSE"><img alt="MIT license" src="https://img.shields.io/badge/license-MIT-blue"></a>
46
+ <img alt="Python 3.10 through 3.13" src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-3776AB">
47
+ <img alt="Slop Score 100" src="docs/slop-score.svg">
48
+ </p>
49
+
50
+ <p align="center"><strong>One command. A score from 0 to 100. Higher is cleaner.</strong></p>
51
+
52
+ <p align="center">
53
+ <img src="docs/demo.gif" alt="uvx --from slopcheck-lint slopcheck demo scores 29, slopcheck prompt, then a fixed tree scores 94" width="720">
54
+ </p>
55
+
56
+ Ruff and Pylint will not catch this. Agents still ship it: a bare `except`, `import requets`, a `_v2` twin, a `breakpoint()` left in the fetch path. slopcheck flags that residue and hands the findings to Claude Code, Codex, or Cursor.
57
+
58
+ ## Install in 5 seconds
59
+
60
+ ```bash
61
+ uvx --from slopcheck-lint slopcheck .
62
+ ```
63
+
64
+ ```bash
65
+ pipx install slopcheck-lint
66
+ ```
67
+
68
+ ```bash
69
+ pip install slopcheck-lint
70
+ ```
71
+
72
+ The PyPI package is `slopcheck-lint`; the command it installs is `slopcheck`, and the import name stays `slopcheck`.
73
+
74
+ Python 3.10 or newer. From a checkout of this repo: `pip install -e ".[dev]"`.
75
+
76
+ ## Quick start
77
+
78
+ ```bash
79
+ slopcheck . # terminal report and a Slop Score
80
+ slopcheck . --format sarif -o slopcheck.sarif
81
+ slopcheck prompt . > fix.md # paste into Claude Code, Codex, or Cursor
82
+ slopcheck . --fix-with-agent
83
+ ```
84
+
85
+ The bundled demo is the messy tree in the GIF. It ends at `20 findings · score 29 · fail`. The plain transcript is [`docs/demo.txt`](docs/demo.txt). `demo/taskflow/ok.py`, scanned alone, scores 100.
86
+
87
+ An error weighs 8, a warning 4, a note 2. `budget = lines / 5`, and the score is `round(100 * budget / (budget + raw))`. No findings is 100. A 200-line project with five errors scores 50.
88
+
89
+ | Score | Label |
90
+ | --- | --- |
91
+ | 90–100 | clean |
92
+ | 75–89 | light residue |
93
+ | 50–74 | needs a wash |
94
+ | 25–49 | sloppy |
95
+ | 0–24 | hazardous sludge |
96
+
97
+ Exit `0` when nothing error-or-warning remains and the score clears `--min-score`. Exit `1` for findings, parse errors, or a score under the minimum. Exit `2` for bad arguments. Notes alone do not fail the process unless `min-score` says they do. `--exit-zero` always exits 0.
98
+
99
+ ## Why
100
+
101
+ Agents write code that looks finished and fails quietly. Ruff is the fast linter (it will flag a bare `except` as E722). Pylint has its own score. Neither one is looking for the residue around that `except`.
102
+
103
+ | Pattern | Ruff / Pylint | slopcheck |
104
+ | --- | --- | --- |
105
+ | `except KeyError: return {}` | no | SLP003 |
106
+ | `import requets` | no | SLP010 |
107
+ | `summarize_tasks_v2` next to `summarize_tasks` | no | SLP007, SLP013 |
108
+ | `# increment index` above `index += 1` | no | SLP004 |
109
+ | `print(f"{tasks=}")` and `breakpoint()` | not by default | SLP005 |
110
+
111
+ Run both. Ruff formats and finds bugs. slopcheck is the pass after the agent.
112
+
113
+ <p align="center">
114
+ <img src="docs/before-after.png" alt="Before and after for a bare except, a hallucinated import, and a _v2 function" width="820">
115
+ </p>
116
+
117
+ ## Rules
118
+
119
+ | ID | Sev | What a tiny example looks like |
120
+ | --- | --- | --- |
121
+ | SLP001 | error | `except:` then `pass` |
122
+ | SLP002 | error | `except Exception: return None` |
123
+ | SLP003 | error | `except KeyError: return {}` |
124
+ | SLP004 | note | `# increment index` |
125
+ | SLP005 | warning | `print(f"{tasks=}")`, `breakpoint()` |
126
+ | SLP006 | warning | `def _helper(): ...` and nothing calls it |
127
+ | SLP007 | warning | a second function with the same shape |
128
+ | SLP008 | note | `name = "ada"` then `if name is not None` |
129
+ | SLP009 | warning | `def export_tasks(): pass` |
130
+ | SLP010 | error | `import requets` |
131
+ | SLP011 | warning | `try` inside `try` inside `try` |
132
+ | SLP012 | note | an essay docstring on `return len(tasks)` |
133
+ | SLP013 | warning | `def summarize_tasks_v2` |
134
+ | SLP014 | note | `# status = "archived"` |
135
+ | SLP015 | warning | `return "Error: failed to fetch title"` |
136
+
137
+ `slopcheck rules` prints the hint that ships with each finding.
138
+
139
+ Left alone on purpose: a comment that says why, `print(f"tasks={tasks}")` (the flagged form is `f"{tasks=}"`), imports inside `except ImportError`, abstract methods, `Protocol`, `.pyi` files, and `return False` from `__exit__`.
140
+
141
+ Silence a line with `# slopcheck: ignore[SLP003]`. A comment-only line applies to the next line of code. `# slopcheck: disable` covers the rest of the file until `# slopcheck: enable`.
142
+
143
+ ```toml
144
+ [tool.slopcheck]
145
+ exclude = ["demo/**", "tests/fixtures/**"]
146
+ ignore = ["SLP004"]
147
+ min-score = 90
148
+
149
+ [tool.slopcheck.rules.SLP010]
150
+ allowed_modules = ["pytest"]
151
+ ```
152
+
153
+ `--select` replaces the config selection. `--ignore` is added on top. This repo sets `min-score = 100` and excludes the demo, so `slopcheck .` fails on any finding. `slopcheck demo` opts that path back in.
154
+
155
+ <p align="center">
156
+ <img src="docs/report.png" alt="HTML report for the demo, score 29, with filters and the first findings" width="760">
157
+ </p>
158
+
159
+ `slopcheck . --format html -o report.html` writes one file. No network requests. The same run as JSON, SARIF 2.1.0, or an SVG badge: `slopcheck badge . -o slop-score.svg`.
160
+
161
+ ## Use with Claude Code, Codex, and Cursor
162
+
163
+ ```bash
164
+ slopcheck prompt . > fix.md
165
+ ```
166
+
167
+ The prompt says: keep behavior, raise instead of returning a dummy, do not silence rules to inflate the score, then run slopcheck again.
168
+
169
+ The skill is [`skills/slopcheck/SKILL.md`](skills/slopcheck/SKILL.md). The same file is linked from `.claude/skills`, `.cursor/skills`, and `.agents/skills`.
170
+
171
+ Claude Code can lint after each edit. Copy [`examples/hooks/slopcheck.sh`](examples/hooks/slopcheck.sh) and the matcher in [`examples/hooks/claude-code.settings.json`](examples/hooks/claude-code.settings.json).
172
+
173
+ ## CI, pre-commit, GitHub Action
174
+
175
+ pre-commit, once `v0.1.0` is tagged:
176
+
177
+ ```yaml
178
+ repos:
179
+ - repo: https://github.com/yungcs/slopcheck
180
+ rev: v0.1.0
181
+ hooks:
182
+ - id: slopcheck
183
+ ```
184
+
185
+ GitHub Action, with SARIF upload for code scanning:
186
+
187
+ ```yaml
188
+ - uses: yungcs/slopcheck@v0.1.0
189
+ with:
190
+ path: .
191
+ upload-sarif: "true"
192
+ ```
193
+
194
+ The action is [`action.yml`](action.yml). A full workflow is [`examples/workflows/slopcheck.yml`](examples/workflows/slopcheck.yml). CI for this repo runs pytest and `slopcheck .` on Python 3.10–3.13.
195
+
196
+ ## Comparison
197
+
198
+ | | slopcheck | Ruff | Pylint | eslint-plugin-slop |
199
+ | --- | --- | --- | --- | --- |
200
+ | Language | Python | Python | Python | TypeScript |
201
+ | Fake fallback `return {}` | yes | no | no | no |
202
+ | Hallucinated import | yes | no | no | no |
203
+ | Near-duplicate `_v2` | yes | no | no | no |
204
+ | Narrating comments | yes | no | no | no |
205
+ | Score meant for agents | 0–100 | no | 0–10, different job | no |
206
+ | Fix prompt | yes | no | no | no |
207
+ | SARIF | yes | yes | no | via ESLint |
208
+ | Replaces the others | no | no | no | different language |
209
+
210
+ [eslint-plugin-slop](https://github.com/antfu/eslint-plugin-slop) is the TypeScript cousin. [no-ai-slop](https://github.com/petergyang/no-ai-slop) edits prose. [hallmark](https://github.com/Nutlope/hallmark) lints interface. This one is for Python.
211
+
212
+ ## FAQ
213
+
214
+ **Does this replace Ruff?** No. Turn Ruff on. Add slopcheck for the residue Ruff is not built to name.
215
+
216
+ **Will it call my code AI-written?** No. It flags patterns. Humans write `except: pass` too.
217
+
218
+ **A note failed my build.** Notes weigh 2 points. They fail the process only when the score drops under `--min-score`. This repo uses 100, so a note fails `slopcheck .`.
219
+
220
+ **How do I keep a false positive?** `# slopcheck: ignore[SLP010]` on the line, or `allowed_modules` for an extra that is not installed here.
221
+
222
+ **Which Python?** 3.10, 3.11, 3.12, and 3.13.
223
+
224
+ ## Roadmap
225
+
226
+ - Safe edits for narrating comments, commented-out code, and debug leftovers.
227
+ - A baseline file, so a dirty tree can ratchet forward.
228
+ - An LSP, so the score shows up while the agent is still typing.
229
+ - Hallucinated keyword arguments.
230
+ - A plugin API for rules that only make sense in one codebase.
231
+
232
+ ## Contributing
233
+
234
+ See [CONTRIBUTING.md](CONTRIBUTING.md). `pytest` and `slopcheck .` should both be clean before you open a PR. The changelog is [CHANGELOG.md](CHANGELOG.md).
235
+
236
+ ## Star history
237
+
238
+ <p align="center">
239
+ <a href="https://star-history.com/#yungcs/slopcheck&Date">
240
+ <picture>
241
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=yungcs/slopcheck&type=Date&theme=dark">
242
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=yungcs/slopcheck&type=Date">
243
+ <img alt="Star history of yungcs/slopcheck" src="https://api.star-history.com/svg?repos=yungcs/slopcheck&type=Date">
244
+ </picture>
245
+ </a>
246
+ </p>
247
+
248
+ ## License
249
+
250
+ [MIT](LICENSE)