anti-slop-python 0.1.1__tar.gz → 0.2.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.
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/.github/workflows/publish.yml +4 -3
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/PKG-INFO +240 -6
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/README.md +239 -5
- anti_slop_python-0.2.0/examples/basic_project/README.md +94 -0
- anti_slop_python-0.2.0/examples/basic_project/REFACTOR_TRIAL.md +89 -0
- anti_slop_python-0.2.0/examples/basic_project/src/example_project/annotation_violations.py +40 -0
- anti_slop_python-0.2.0/examples/basic_project/src/example_project/explicit_exports.py +5 -0
- anti_slop_python-0.2.0/examples/basic_project/src/example_project/order_report.py +554 -0
- anti_slop_python-0.2.0/examples/basic_project/src/example_project/order_report_refactored/demo.py +53 -0
- anti_slop_python-0.2.0/examples/basic_project/src/example_project/order_report_refactored/exports.py +83 -0
- anti_slop_python-0.2.0/examples/basic_project/src/example_project/order_report_refactored/inventory.py +23 -0
- anti_slop_python-0.2.0/examples/basic_project/src/example_project/order_report_refactored/models.py +73 -0
- anti_slop_python-0.2.0/examples/basic_project/src/example_project/order_report_refactored/order_report.py +155 -0
- anti_slop_python-0.2.0/examples/basic_project/src/example_project/order_report_refactored/parsing.py +129 -0
- anti_slop_python-0.2.0/examples/basic_project/src/example_project/order_report_refactored/presentation.py +71 -0
- anti_slop_python-0.2.0/examples/basic_project/src/example_project/order_report_refactored/pricing.py +70 -0
- anti_slop_python-0.2.0/examples/basic_project/src/example_project/order_report_refactored/reporting.py +65 -0
- anti_slop_python-0.2.0/examples/basic_project/src/example_project/wildcard_violations.py +5 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/pyproject.toml +1 -1
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/src/anti_slop_python/checker.py +20 -6
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/src/anti_slop_python/cli.py +14 -2
- anti_slop_python-0.2.0/src/anti_slop_python/configuration.py +115 -0
- anti_slop_python-0.2.0/src/anti_slop_python/diagnostics.py +94 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/src/anti_slop_python/ruff_integration.py +32 -11
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/src/anti_slop_python/ruff_policy.py +17 -1
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/src/anti_slop_python/rules/__init__.py +2 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/src/anti_slop_python/rules/base.py +3 -0
- anti_slop_python-0.2.0/src/anti_slop_python/rules/too_many_module_lines.py +42 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/tests/test_cli.py +48 -6
- anti_slop_python-0.2.0/tests/test_diagnostics.py +41 -0
- anti_slop_python-0.2.0/tests/test_examples.py +78 -0
- anti_slop_python-0.2.0/tests/test_module_size_configuration.py +176 -0
- anti_slop_python-0.2.0/tests/test_order_report_refactoring.py +235 -0
- anti_slop_python-0.2.0/tests/test_release_version.py +70 -0
- anti_slop_python-0.2.0/tests/test_ruff_annotations.py +86 -0
- anti_slop_python-0.2.0/tests/test_ruff_exports.py +74 -0
- anti_slop_python-0.2.0/tests/test_ruff_project_root.py +53 -0
- anti_slop_python-0.2.0/tests/test_too_many_module_lines.py +80 -0
- anti_slop_python-0.1.1/examples/basic_project/README.md +0 -34
- anti_slop_python-0.1.1/src/anti_slop_python/diagnostics.py +0 -18
- anti_slop_python-0.1.1/tests/test_examples.py +0 -31
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/.github/workflows/checks.yml +0 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/.gitignore +0 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/.pre-commit-config.yaml +0 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/.pre-commit-hooks.yaml +0 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/AGENTS.md +0 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/LICENSE +0 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/examples/basic_project/pyproject.toml +0 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/examples/basic_project/src/example_project/__init__.py +0 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/examples/basic_project/src/example_project/preferred.py +0 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/examples/basic_project/src/example_project/violations.py +0 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/skills/install-anti-slop-python/SKILL.md +0 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/skills/install-anti-slop-python/agents/openai.yaml +0 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/src/anti_slop_python/__init__.py +0 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/src/anti_slop_python/__main__.py +0 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/src/anti_slop_python/rules/no_any_containers.py +0 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/src/anti_slop_python/rules/no_dynamic_attribute_access.py +0 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/tests/test_checker.py +0 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/tests/test_no_any_containers.py +0 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/tests/test_no_dynamic_attribute_access.py +0 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/tests/test_ruff_integration.py +0 -0
- {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/uv.lock +0 -0
|
@@ -79,15 +79,16 @@ jobs:
|
|
|
79
79
|
tags = [t.strip() for t in tags_proc.stdout.splitlines() if t.strip()]
|
|
80
80
|
|
|
81
81
|
if not tags:
|
|
82
|
-
next_tag = "v0.
|
|
82
|
+
next_tag = "v0.2.0"
|
|
83
83
|
else:
|
|
84
84
|
latest = tags[0]
|
|
85
85
|
match = re.match(r"^v(\d+)\.(\d+)\.(\d+)$", latest)
|
|
86
86
|
if match:
|
|
87
87
|
major, minor, patch = map(int, match.groups())
|
|
88
|
-
|
|
88
|
+
next_version = max((major, minor, patch + 1), (0, 2, 0))
|
|
89
|
+
next_tag = "v" + ".".join(map(str, next_version))
|
|
89
90
|
else:
|
|
90
|
-
next_tag = "v0.
|
|
91
|
+
next_tag = "v0.2.0"
|
|
91
92
|
|
|
92
93
|
print(f"tag={next_tag}")
|
|
93
94
|
print(f"version={next_tag.lstrip('v')}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: anti-slop-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: An opinionated architectural linter for Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/ruarfff/anti-slop-python
|
|
6
6
|
Project-URL: Repository, https://github.com/ruarfff/anti-slop-python
|
|
@@ -77,7 +77,7 @@ The repository includes hook metadata. Add this entry to
|
|
|
77
77
|
```yaml
|
|
78
78
|
repos:
|
|
79
79
|
- repo: https://github.com/ruarfff/anti-slop-python
|
|
80
|
-
rev: v0.
|
|
80
|
+
rev: v0.2.0
|
|
81
81
|
hooks:
|
|
82
82
|
- id: anti-slop-python
|
|
83
83
|
```
|
|
@@ -96,7 +96,7 @@ parts of a project:
|
|
|
96
96
|
```yaml
|
|
97
97
|
repos:
|
|
98
98
|
- repo: https://github.com/ruarfff/anti-slop-python
|
|
99
|
-
rev: v0.
|
|
99
|
+
rev: v0.2.0
|
|
100
100
|
hooks:
|
|
101
101
|
- id: anti-slop-python
|
|
102
102
|
files: ^(?:src/a-specific-module/|tests/tests-for-that-module/)
|
|
@@ -107,6 +107,40 @@ Useful if you want to gradually introduce `anti-slop-python` to a project.
|
|
|
107
107
|
Expand the `files` expression as more directories adopt the policy.
|
|
108
108
|
This does not override the project's Ruff configuration for files outside the selected directories.
|
|
109
109
|
|
|
110
|
+
#### Exclude specific files during adoption
|
|
111
|
+
|
|
112
|
+
To adopt the module-size limit incrementally, add a hook-level `exclude` in
|
|
113
|
+
`.pre-commit-config.yaml` for existing large files:
|
|
114
|
+
|
|
115
|
+
```yaml
|
|
116
|
+
repos:
|
|
117
|
+
- repo: https://github.com/ruarfff/anti-slop-python
|
|
118
|
+
rev: v0.2.0 # Replace with the release to use.
|
|
119
|
+
hooks:
|
|
120
|
+
- id: anti-slop-python
|
|
121
|
+
exclude: ^(?:src/legacy/reporting\.py|tests/test_legacy_reporting\.py)$
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The [pre-commit filter](https://pre-commit.com/#regular-expressions) is a regular
|
|
125
|
+
expression over repository-relative paths. The anchors and escaped dots make
|
|
126
|
+
this example match only the two named files. Remove each path as its file is
|
|
127
|
+
refactored to meet the limit. This filter also applies to
|
|
128
|
+
`pre-commit run anti-slop-python --all-files`.
|
|
129
|
+
|
|
130
|
+
This skips **all checks in this hook** for those files, including other native
|
|
131
|
+
rules and Ruff-backed checks. It does not disable only `SPY003`. A separate Ruff
|
|
132
|
+
hook or command can still check them.
|
|
133
|
+
|
|
134
|
+
Native rules do not currently support per-file rule ignores. Ruff's
|
|
135
|
+
`per-file-ignores`, Ruff exclusions, and `# noqa: SPY003` do not disable
|
|
136
|
+
`SPY003`. The pre-commit filter also does not apply to direct CLI runs such as
|
|
137
|
+
`anti-slop-python .`. For direct runs, pass only the files or directories ready
|
|
138
|
+
for enforcement, for example:
|
|
139
|
+
|
|
140
|
+
```console
|
|
141
|
+
uvx anti-slop-python src/new_package/ tests/test_new_package.py
|
|
142
|
+
```
|
|
143
|
+
|
|
110
144
|
## Rules
|
|
111
145
|
|
|
112
146
|
Native `anti-slop-python` rules cover some checks that Ruff does not provide:
|
|
@@ -115,6 +149,7 @@ Native `anti-slop-python` rules cover some checks that Ruff does not provide:
|
|
|
115
149
|
| --- | --- | --- |
|
|
116
150
|
| SPY001 | `no-any-containers` | `dict`, `list`, `set`, or `tuple` parameterized with `Any` (including their `typing` aliases) |
|
|
117
151
|
| SPY002 | `no-dynamic-attribute-access` | Calls to `getattr()`, `setattr()`, or `delattr()` |
|
|
152
|
+
| SPY003 | `too-many-module-lines` | Modules over 500 physical lines, or test modules over 1,500; both limits are configurable |
|
|
118
153
|
|
|
119
154
|
### Ruff-backed policy
|
|
120
155
|
|
|
@@ -123,6 +158,10 @@ policy by default:
|
|
|
123
158
|
|
|
124
159
|
| Rule | Default policy |
|
|
125
160
|
| --- | --- |
|
|
161
|
+
| [`ANN001`, `ANN002`, `ANN003`](https://docs.astral.sh/ruff/rules/#flake8-annotations-ann) | Require annotations on function parameters, including `*args` and `**kwargs` |
|
|
162
|
+
| [`ANN201`, `ANN202`, `ANN204`, `ANN205`, `ANN206`](https://docs.astral.sh/ruff/rules/#flake8-annotations-ann) | Require return annotations on public/private functions and special, static, and class methods |
|
|
163
|
+
| [`ANN401`](https://docs.astral.sh/ruff/rules/any-type/) | Reject `Any` on function arguments |
|
|
164
|
+
| [`F403`](https://docs.astral.sh/ruff/rules/undefined-local-with-import-star/) | Require named imports instead of wildcard imports |
|
|
126
165
|
| [`C901`](https://docs.astral.sh/ruff/rules/complex-structure/) | Cyclomatic complexity of at most 10 |
|
|
127
166
|
| [`PLR0915`](https://docs.astral.sh/ruff/rules/too-many-statements/) | At most 40 statements per function or method |
|
|
128
167
|
| [`TID251`](https://docs.astral.sh/ruff/rules/banned-api/) | Ban `unittest.mock.patch` and `mock.patch` |
|
|
@@ -134,7 +173,7 @@ equivalent to:
|
|
|
134
173
|
|
|
135
174
|
```toml
|
|
136
175
|
[tool.ruff.lint]
|
|
137
|
-
extend-select = ["BLE001", "C901", "E722", "PLR0915", "TID251"]
|
|
176
|
+
extend-select = ["ANN", "BLE001", "C901", "E722", "F403", "PLR0915", "TID251"]
|
|
138
177
|
|
|
139
178
|
[tool.ruff.lint.mccabe]
|
|
140
179
|
max-complexity = 10
|
|
@@ -161,6 +200,10 @@ max-statements = 50
|
|
|
161
200
|
`ignore` and `extend-ignore` disable default rules. Explicit thresholds,
|
|
162
201
|
exclusions, per-file ignores, and `noqa` comments also apply.
|
|
163
202
|
|
|
203
|
+
Relative Ruff paths, including source roots and exclusions, resolve from the
|
|
204
|
+
directory containing that project's Ruff configuration. Checking a project from
|
|
205
|
+
its parent directory uses the same settings as checking from the project itself.
|
|
206
|
+
|
|
164
207
|
If the project defines the `TID251` banned-API table, it replaces the default
|
|
165
208
|
provided by `anti-slop-python`.
|
|
166
209
|
|
|
@@ -170,7 +213,7 @@ source location. Ruff exclusions apply to Ruff-backed checks. Native SPY rules
|
|
|
170
213
|
still check every Python file in the paths passed to `anti-slop-python`; use the
|
|
171
214
|
command paths or pre-commit `files`/`exclude` settings to control that scope.
|
|
172
215
|
|
|
173
|
-
When
|
|
216
|
+
When a Ruff override is weaker than the default policy, `anti-slop-python` prints a
|
|
174
217
|
non-failing policy notice. Stricter settings do not produce a notice. The
|
|
175
218
|
defaults apply only to Ruff runs started by `anti-slop-python`; add the equivalent
|
|
176
219
|
configuration above if a separate `ruff check` command must enforce the same
|
|
@@ -235,6 +278,144 @@ attribute name is a constant string. They therefore allow calls such as
|
|
|
235
278
|
depend on runtime strings. `SPY002` rejects
|
|
236
279
|
the built-ins regardless of whether the name is constant.
|
|
237
280
|
|
|
281
|
+
### SPY003 — Limit module size
|
|
282
|
+
|
|
283
|
+
`SPY003` allows 500 physical lines per production module and 1,500 per test
|
|
284
|
+
module by default. It counts code, comments, blank lines, and docstrings. A
|
|
285
|
+
final newline terminates the last line; it does not add another line. The
|
|
286
|
+
diagnostic appears at line 1 and includes the actual count and active limit.
|
|
287
|
+
Files with syntax errors report the syntax error instead of native diagnostics.
|
|
288
|
+
Native rules do not support `noqa` suppression.
|
|
289
|
+
|
|
290
|
+
For incremental adoption, see [Exclude specific files during adoption](#exclude-specific-files-during-adoption).
|
|
291
|
+
|
|
292
|
+
A module can contain many small, simple functions and still become hard to
|
|
293
|
+
maintain. This check complements Ruff's function size and complexity limits by
|
|
294
|
+
catching growth across the whole file. The 500-line limit is an opinionated
|
|
295
|
+
review threshold, not proof that a module has poor design.
|
|
296
|
+
|
|
297
|
+
When it fails, look for distinct responsibilities and move cohesive code into
|
|
298
|
+
focused modules. Do not remove useful documentation, compress code, or split
|
|
299
|
+
files at arbitrary line boundaries just to pass the check.
|
|
300
|
+
|
|
301
|
+
The diagnostic includes refactoring guidance. Each extracted module should have
|
|
302
|
+
a clear purpose and interface. Keep closely related code together, minimize
|
|
303
|
+
shared state and cross-module calls, and avoid circular imports. Moving unrelated
|
|
304
|
+
code into a generic helpers module does not improve the design. Preserve public
|
|
305
|
+
APIs and verify behavior after changing the boundaries.
|
|
306
|
+
Check package imports, standalone imports where supported, and every entry point.
|
|
307
|
+
Each supported import mode must retain the full public API.
|
|
308
|
+
Preserve validation order, side effects, and type information when moving code.
|
|
309
|
+
|
|
310
|
+
The [agent comparison](examples/basic_project/REFACTOR_TRIAL.md) records a strong
|
|
311
|
+
prompt baseline, repeated linter-guided runs, withheld behavior checks and the
|
|
312
|
+
failures that led to improvements. A clean lint result does not establish API
|
|
313
|
+
compatibility or correct behavior.
|
|
314
|
+
|
|
315
|
+
#### Test modules and configuration
|
|
316
|
+
|
|
317
|
+
Test modules can contain many independent scenarios, fixtures, and deliberate
|
|
318
|
+
repetition. Their larger budget helps keep related cases together. Files named
|
|
319
|
+
`test_*.py`, `*_test.py`, or `conftest.py` use the test limit automatically,
|
|
320
|
+
at any directory depth. Names are case-sensitive. Importing `pytest` or using
|
|
321
|
+
assertions does not change a file's classification. A helper such as
|
|
322
|
+
`tests/helpers.py` uses the production limit unless a configured pattern matches.
|
|
323
|
+
|
|
324
|
+
Set the limits and add test-helper paths in `pyproject.toml`:
|
|
325
|
+
|
|
326
|
+
```toml
|
|
327
|
+
[tool.anti-slop-python]
|
|
328
|
+
max-module-lines = 500
|
|
329
|
+
max-test-module-lines = 1500
|
|
330
|
+
test-file-patterns = ["tests/**", "specs/**"]
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Limits must be positive integers. Patterns extend the automatic filename
|
|
334
|
+
conventions; an empty list keeps only those conventions. Patterns are matched
|
|
335
|
+
against the complete, case-sensitive path relative to this `pyproject.toml`,
|
|
336
|
+
using `/` separators. They use shell-style matching: `*` can span directories,
|
|
337
|
+
so `tests/**` includes both immediate and nested files. Absolute paths and `..`
|
|
338
|
+
segments are rejected. Patterns cannot classify files outside the configuration
|
|
339
|
+
directory as tests.
|
|
340
|
+
|
|
341
|
+
For each source file, the checker searches its directory and parents for the
|
|
342
|
+
nearest `[tool.anti-slop-python]` table. A nested `pyproject.toml` without this
|
|
343
|
+
table inherits the parent settings. A nearer table replaces the parent table;
|
|
344
|
+
omitted options use the defaults above. This works for direct file arguments,
|
|
345
|
+
directory scans, and pre-commit, regardless of the current working directory.
|
|
346
|
+
Invalid options stop the CLI with exit code 2.
|
|
347
|
+
|
|
348
|
+
Only the module-size budget changes for tests. Other native and Ruff-backed
|
|
349
|
+
rules still apply. Oversized tests receive guidance specific to test design:
|
|
350
|
+
|
|
351
|
+
```text
|
|
352
|
+
tests/test_orders.py:1:1 SPY003 Too many lines in test module (1620 > 1500)
|
|
353
|
+
Group tests by the behavior or component they verify.
|
|
354
|
+
Keep each scenario readable and preserve assertions and edge cases.
|
|
355
|
+
Do not remove coverage, compress cases, or hide setup in shared fixtures
|
|
356
|
+
merely to satisfy this limit.
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
For Python callers, `check_file()` discovers configuration automatically.
|
|
360
|
+
`check_source()` uses defaults without reading configuration files; pass a
|
|
361
|
+
`ModuleSizeSettings` instance from `anti_slop_python.configuration` with its
|
|
362
|
+
`settings=` argument to supply custom values. The `root` field sets the base
|
|
363
|
+
directory for additional test patterns. Invalid file configuration raises
|
|
364
|
+
`ConfigurationError` when calling `check_file()` directly.
|
|
365
|
+
|
|
366
|
+
### ANN — Keep function contracts explicit
|
|
367
|
+
|
|
368
|
+
The annotation rules require parameter and return types on functions and methods,
|
|
369
|
+
including private helpers, special methods, and variadic arguments. `ANN401`
|
|
370
|
+
also rejects `Any` on function arguments. Local variables can use type inference;
|
|
371
|
+
this policy does not require annotations on every assignment.
|
|
372
|
+
|
|
373
|
+
A refactoring agent can preserve runtime output while removing type information
|
|
374
|
+
that callers and type checkers need. These rules catch missing function
|
|
375
|
+
annotations even when the function moves to a different file. They check the
|
|
376
|
+
current source, not Git history, and do not establish that the annotations are
|
|
377
|
+
correct. Use a type checker to check their consistency with the implementation.
|
|
378
|
+
|
|
379
|
+
Keep the actual types when moving code. Do not replace them with `Any`, broad
|
|
380
|
+
`object` types, or casts merely to pass. Projects can adopt these Ruff-backed
|
|
381
|
+
rules incrementally without disabling other checks on a legacy file:
|
|
382
|
+
|
|
383
|
+
```toml
|
|
384
|
+
[tool.ruff.lint.per-file-ignores]
|
|
385
|
+
"src/legacy.py" = ["ANN"]
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
The native rules still run on that file, and ignored annotation rules produce
|
|
389
|
+
policy notices. This policy does not require explicit annotations on `self`
|
|
390
|
+
or `cls`. Ruff's annotation-specific settings remain project-controlled.
|
|
391
|
+
|
|
392
|
+
### F403 — Keep imports and public exports explicit
|
|
393
|
+
|
|
394
|
+
Wildcard imports hide which module provides a name. They can also hide unused
|
|
395
|
+
import findings during a refactor. Use named imports and preserve the intended
|
|
396
|
+
public API. For a facade that re-exports a name, declare it in `__all__`:
|
|
397
|
+
|
|
398
|
+
```python
|
|
399
|
+
from .pricing import calculate_tax
|
|
400
|
+
|
|
401
|
+
__all__ = ["calculate_tax"]
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
When Ruff reports `F401`, the output also explains how to preserve public
|
|
405
|
+
re-exports. Do not delete a public name or replace named imports with `import *`
|
|
406
|
+
merely to clear that finding. `F403` checks wildcard syntax; it does not verify
|
|
407
|
+
that imports resolve at runtime. Test imports and behavior separately.
|
|
408
|
+
|
|
409
|
+
For incremental adoption, retain a specific legacy facade while checking other
|
|
410
|
+
files and rules:
|
|
411
|
+
|
|
412
|
+
```toml
|
|
413
|
+
[tool.ruff.lint.per-file-ignores]
|
|
414
|
+
"src/legacy/__init__.py" = ["F403"]
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
This produces a policy notice. Native rules continue to run on the file.
|
|
418
|
+
|
|
238
419
|
### [`C901`](https://docs.astral.sh/ruff/rules/complex-structure/) — Limit decision complexity
|
|
239
420
|
|
|
240
421
|
`C901` measures the number of paths through a function. Anti-slop-python uses a
|
|
@@ -307,10 +488,48 @@ command exit with status 1:
|
|
|
307
488
|
|
|
308
489
|
```text
|
|
309
490
|
src/api/parser.py:41:12 SPY001 Avoid containers parameterized with Any
|
|
491
|
+
Describe the actual data with concrete types, TypedDict, or a dataclass.
|
|
492
|
+
Validate untrusted data at the boundary; narrow unknown values before use.
|
|
493
|
+
Do not remove annotations or hide Any behind aliases, casts, or bare containers.
|
|
310
494
|
src/api/parser.py:45:8 SPY002 Avoid dynamic attribute access
|
|
495
|
+
Use direct attribute access for a known interface.
|
|
496
|
+
For runtime choices, use an explicit mapping of supported operations.
|
|
497
|
+
Preserve missing-value behavior explicitly.
|
|
498
|
+
Do not replace this call with __dict__, vars(), or a reflection wrapper.
|
|
499
|
+
src/api/large_module.py:1:1 SPY003 Too many lines in module (642 > 500)
|
|
500
|
+
Separate distinct responsibilities into cohesive modules with clear interfaces.
|
|
501
|
+
Keep closely related code together and preserve public APIs and behavior.
|
|
502
|
+
Preserve validation order, side effects, and type information when moving code.
|
|
503
|
+
Verify package and standalone imports where supported, plus each entry point.
|
|
504
|
+
Check that every supported import mode exposes the full public API.
|
|
505
|
+
Do not compress code, remove useful comments, split at arbitrary line counts,
|
|
506
|
+
or move unrelated code into a generic helpers module to satisfy this limit.
|
|
311
507
|
src/orders/service.py:18:5 C901 `create_order` is too complex (14 > 10)
|
|
508
|
+
Simplify the decision model; extract cohesive operations with explicit inputs.
|
|
509
|
+
Use a lookup table only when the branches represent a data mapping.
|
|
510
|
+
Preserve edge cases; do not hide branches in lambdas or raise the limit.
|
|
312
511
|
```
|
|
313
512
|
|
|
513
|
+
Each native rule and recommended Ruff rule includes indented guidance after the
|
|
514
|
+
source line. It describes the intended design change and common shortcuts to
|
|
515
|
+
avoid. Other Ruff diagnostics retain their original output. Ruff's message,
|
|
516
|
+
including any project-defined banned-API explanation, remains on the first line.
|
|
517
|
+
When reading diagnostics through the Python API, `message` contains the summary;
|
|
518
|
+
`str(diagnostic)` includes the source location and guidance.
|
|
519
|
+
|
|
520
|
+
| Rule | Guidance |
|
|
521
|
+
| --- | --- |
|
|
522
|
+
| `ANN` | Declare real parameter and return types; preserve existing type information |
|
|
523
|
+
| `SPY001` | Describe and validate the actual data instead of hiding `Any` |
|
|
524
|
+
| `SPY002` | Use explicit interfaces and preserve missing-value behavior |
|
|
525
|
+
| `SPY003` | Separate production responsibilities or group tests by behavior; preserve related code and test coverage |
|
|
526
|
+
| `F401`, `F403` | Keep public exports through named imports and `__all__`; check every supported import mode |
|
|
527
|
+
| `C901` | Simplify decisions while preserving edge cases |
|
|
528
|
+
| `PLR0915` | Extract meaningful steps while preserving ordering and side effects |
|
|
529
|
+
| `TID251` | Follow the project's API policy; pass dependencies when test isolation is needed |
|
|
530
|
+
| `E722` | Catch specific recoverable errors and allow interrupts to propagate |
|
|
531
|
+
| `BLE001` | Handle known failures; do not add logging or defaults just to silence the rule |
|
|
532
|
+
|
|
314
533
|
Policy notices are written to standard error and do not change the exit code:
|
|
315
534
|
|
|
316
535
|
```text
|
|
@@ -344,13 +563,28 @@ Run the example explicitly to see its diagnostics:
|
|
|
344
563
|
uv run anti-slop-python examples/basic_project
|
|
345
564
|
```
|
|
346
565
|
|
|
566
|
+
The [agent comparison](examples/basic_project/REFACTOR_TRIAL.md) records nine
|
|
567
|
+
fresh refactoring runs, including a strong-prompt control and the failures
|
|
568
|
+
observed. It shows agents correcting specific lint findings, but does not
|
|
569
|
+
establish an overall refactor-correctness advantage.
|
|
570
|
+
|
|
347
571
|
## Scope and limitations
|
|
348
572
|
|
|
349
573
|
Native checks use Python's built-in `ast` and a pragmatic import alias map.
|
|
350
574
|
They do not perform scope-aware name resolution, type checking, cross-file
|
|
351
|
-
analysis,
|
|
575
|
+
analysis, suppressions, or autofixes. Native configuration currently covers only
|
|
576
|
+
module-size limits and additional test-file patterns. Ruff-backed checks use
|
|
352
577
|
the project's effective Ruff configuration and suppression behavior.
|
|
353
578
|
|
|
579
|
+
## Releases
|
|
580
|
+
|
|
581
|
+
Version `0.2.0` adds module-size enforcement, function annotation checks,
|
|
582
|
+
explicit-import checks, and refactoring guidance. Versions come from Git tags
|
|
583
|
+
through `hatch-vcs`.
|
|
584
|
+
The publishing workflow selects at least `v0.2.0` for the next untagged `main`
|
|
585
|
+
commit, then continues with patch increments. Opening a PR does not publish a
|
|
586
|
+
release; publishing runs after eligible changes reach `main`.
|
|
587
|
+
|
|
354
588
|
## License
|
|
355
589
|
|
|
356
590
|
[MIT](LICENSE)
|