anti-slop-python 0.1.0__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.
Files changed (63) hide show
  1. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/.github/workflows/publish.yml +10 -3
  2. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/PKG-INFO +247 -6
  3. anti_slop_python-0.2.0/README.md +565 -0
  4. anti_slop_python-0.2.0/examples/basic_project/README.md +94 -0
  5. anti_slop_python-0.2.0/examples/basic_project/REFACTOR_TRIAL.md +89 -0
  6. anti_slop_python-0.2.0/examples/basic_project/src/example_project/annotation_violations.py +40 -0
  7. anti_slop_python-0.2.0/examples/basic_project/src/example_project/explicit_exports.py +5 -0
  8. anti_slop_python-0.2.0/examples/basic_project/src/example_project/order_report.py +554 -0
  9. anti_slop_python-0.2.0/examples/basic_project/src/example_project/order_report_refactored/demo.py +53 -0
  10. anti_slop_python-0.2.0/examples/basic_project/src/example_project/order_report_refactored/exports.py +83 -0
  11. anti_slop_python-0.2.0/examples/basic_project/src/example_project/order_report_refactored/inventory.py +23 -0
  12. anti_slop_python-0.2.0/examples/basic_project/src/example_project/order_report_refactored/models.py +73 -0
  13. anti_slop_python-0.2.0/examples/basic_project/src/example_project/order_report_refactored/order_report.py +155 -0
  14. anti_slop_python-0.2.0/examples/basic_project/src/example_project/order_report_refactored/parsing.py +129 -0
  15. anti_slop_python-0.2.0/examples/basic_project/src/example_project/order_report_refactored/presentation.py +71 -0
  16. anti_slop_python-0.2.0/examples/basic_project/src/example_project/order_report_refactored/pricing.py +70 -0
  17. anti_slop_python-0.2.0/examples/basic_project/src/example_project/order_report_refactored/reporting.py +65 -0
  18. anti_slop_python-0.2.0/examples/basic_project/src/example_project/wildcard_violations.py +5 -0
  19. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/pyproject.toml +5 -1
  20. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/src/anti_slop_python/checker.py +20 -6
  21. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/src/anti_slop_python/cli.py +14 -2
  22. anti_slop_python-0.2.0/src/anti_slop_python/configuration.py +115 -0
  23. anti_slop_python-0.2.0/src/anti_slop_python/diagnostics.py +94 -0
  24. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/src/anti_slop_python/ruff_integration.py +32 -11
  25. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/src/anti_slop_python/ruff_policy.py +17 -1
  26. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/src/anti_slop_python/rules/__init__.py +2 -0
  27. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/src/anti_slop_python/rules/base.py +3 -0
  28. anti_slop_python-0.2.0/src/anti_slop_python/rules/too_many_module_lines.py +42 -0
  29. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/tests/test_cli.py +48 -6
  30. anti_slop_python-0.2.0/tests/test_diagnostics.py +41 -0
  31. anti_slop_python-0.2.0/tests/test_examples.py +78 -0
  32. anti_slop_python-0.2.0/tests/test_module_size_configuration.py +176 -0
  33. anti_slop_python-0.2.0/tests/test_order_report_refactoring.py +235 -0
  34. anti_slop_python-0.2.0/tests/test_release_version.py +70 -0
  35. anti_slop_python-0.2.0/tests/test_ruff_annotations.py +86 -0
  36. anti_slop_python-0.2.0/tests/test_ruff_exports.py +74 -0
  37. anti_slop_python-0.2.0/tests/test_ruff_project_root.py +53 -0
  38. anti_slop_python-0.2.0/tests/test_too_many_module_lines.py +80 -0
  39. anti_slop_python-0.1.0/README.md +0 -328
  40. anti_slop_python-0.1.0/examples/basic_project/README.md +0 -34
  41. anti_slop_python-0.1.0/src/anti_slop_python/diagnostics.py +0 -18
  42. anti_slop_python-0.1.0/tests/test_examples.py +0 -31
  43. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/.github/workflows/checks.yml +0 -0
  44. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/.gitignore +0 -0
  45. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/.pre-commit-config.yaml +0 -0
  46. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/.pre-commit-hooks.yaml +0 -0
  47. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/AGENTS.md +0 -0
  48. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/LICENSE +0 -0
  49. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/examples/basic_project/pyproject.toml +0 -0
  50. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/examples/basic_project/src/example_project/__init__.py +0 -0
  51. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/examples/basic_project/src/example_project/preferred.py +0 -0
  52. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/examples/basic_project/src/example_project/violations.py +0 -0
  53. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/skills/install-anti-slop-python/SKILL.md +0 -0
  54. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/skills/install-anti-slop-python/agents/openai.yaml +0 -0
  55. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/src/anti_slop_python/__init__.py +0 -0
  56. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/src/anti_slop_python/__main__.py +0 -0
  57. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/src/anti_slop_python/rules/no_any_containers.py +0 -0
  58. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/src/anti_slop_python/rules/no_dynamic_attribute_access.py +0 -0
  59. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/tests/test_checker.py +0 -0
  60. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/tests/test_no_any_containers.py +0 -0
  61. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/tests/test_no_dynamic_attribute_access.py +0 -0
  62. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/tests/test_ruff_integration.py +0 -0
  63. {anti_slop_python-0.1.0 → anti_slop_python-0.2.0}/uv.lock +0 -0
@@ -4,6 +4,12 @@ on:
4
4
  push:
5
5
  branches:
6
6
  - main
7
+ paths-ignore:
8
+ - "**.md"
9
+ - "docs/**"
10
+ - "examples/**"
11
+ - ".gitignore"
12
+ - "LICENSE"
7
13
 
8
14
  concurrency:
9
15
  group: publish-main
@@ -73,15 +79,16 @@ jobs:
73
79
  tags = [t.strip() for t in tags_proc.stdout.splitlines() if t.strip()]
74
80
 
75
81
  if not tags:
76
- next_tag = "v0.1.0"
82
+ next_tag = "v0.2.0"
77
83
  else:
78
84
  latest = tags[0]
79
85
  match = re.match(r"^v(\d+)\.(\d+)\.(\d+)$", latest)
80
86
  if match:
81
87
  major, minor, patch = map(int, match.groups())
82
- next_tag = f"v{major}.{minor}.{patch + 1}"
88
+ next_version = max((major, minor, patch + 1), (0, 2, 0))
89
+ next_tag = "v" + ".".join(map(str, next_version))
83
90
  else:
84
- next_tag = "v0.1.0"
91
+ next_tag = "v0.2.0"
85
92
 
86
93
  print(f"tag={next_tag}")
87
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.1.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
@@ -14,6 +14,10 @@ Classifier: Environment :: Console
14
14
  Classifier: License :: OSI Approved :: MIT License
15
15
  Classifier: Programming Language :: Python :: 3
16
16
  Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
17
21
  Classifier: Topic :: Software Development :: Quality Assurance
18
22
  Requires-Python: >=3.11
19
23
  Requires-Dist: ruff<0.17,>=0.16.5
@@ -21,7 +25,10 @@ Description-Content-Type: text/markdown
21
25
 
22
26
  # anti-slop-python
23
27
 
28
+ [![PyPI - Version](https://img.shields.io/pypi/v/anti-slop-python)](https://pypi.org/project/anti-slop-python/)
29
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/anti-slop-python)](https://pypi.org/project/anti-slop-python/)
24
30
  [![skills.sh](https://skills.sh/b/ruarfff/anti-slop-python)](https://skills.sh/ruarfff/anti-slop-python)
31
+ [![License: MIT](https://img.shields.io/github/license/ruarfff/anti-slop-python)](LICENSE)
25
32
 
26
33
  `anti-slop-python` is a small, opinionated architectural
27
34
  linter for Python inspired by and largely copied from
@@ -70,7 +77,7 @@ The repository includes hook metadata. Add this entry to
70
77
  ```yaml
71
78
  repos:
72
79
  - repo: https://github.com/ruarfff/anti-slop-python
73
- rev: v0.1.0
80
+ rev: v0.2.0
74
81
  hooks:
75
82
  - id: anti-slop-python
76
83
  ```
@@ -89,7 +96,7 @@ parts of a project:
89
96
  ```yaml
90
97
  repos:
91
98
  - repo: https://github.com/ruarfff/anti-slop-python
92
- rev: v0.1.0
99
+ rev: v0.2.0
93
100
  hooks:
94
101
  - id: anti-slop-python
95
102
  files: ^(?:src/a-specific-module/|tests/tests-for-that-module/)
@@ -100,6 +107,40 @@ Useful if you want to gradually introduce `anti-slop-python` to a project.
100
107
  Expand the `files` expression as more directories adopt the policy.
101
108
  This does not override the project's Ruff configuration for files outside the selected directories.
102
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
+
103
144
  ## Rules
104
145
 
105
146
  Native `anti-slop-python` rules cover some checks that Ruff does not provide:
@@ -108,6 +149,7 @@ Native `anti-slop-python` rules cover some checks that Ruff does not provide:
108
149
  | --- | --- | --- |
109
150
  | SPY001 | `no-any-containers` | `dict`, `list`, `set`, or `tuple` parameterized with `Any` (including their `typing` aliases) |
110
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 |
111
153
 
112
154
  ### Ruff-backed policy
113
155
 
@@ -116,6 +158,10 @@ policy by default:
116
158
 
117
159
  | Rule | Default policy |
118
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 |
119
165
  | [`C901`](https://docs.astral.sh/ruff/rules/complex-structure/) | Cyclomatic complexity of at most 10 |
120
166
  | [`PLR0915`](https://docs.astral.sh/ruff/rules/too-many-statements/) | At most 40 statements per function or method |
121
167
  | [`TID251`](https://docs.astral.sh/ruff/rules/banned-api/) | Ban `unittest.mock.patch` and `mock.patch` |
@@ -127,7 +173,7 @@ equivalent to:
127
173
 
128
174
  ```toml
129
175
  [tool.ruff.lint]
130
- extend-select = ["BLE001", "C901", "E722", "PLR0915", "TID251"]
176
+ extend-select = ["ANN", "BLE001", "C901", "E722", "F403", "PLR0915", "TID251"]
131
177
 
132
178
  [tool.ruff.lint.mccabe]
133
179
  max-complexity = 10
@@ -154,6 +200,10 @@ max-statements = 50
154
200
  `ignore` and `extend-ignore` disable default rules. Explicit thresholds,
155
201
  exclusions, per-file ignores, and `noqa` comments also apply.
156
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
+
157
207
  If the project defines the `TID251` banned-API table, it replaces the default
158
208
  provided by `anti-slop-python`.
159
209
 
@@ -163,7 +213,7 @@ source location. Ruff exclusions apply to Ruff-backed checks. Native SPY rules
163
213
  still check every Python file in the paths passed to `anti-slop-python`; use the
164
214
  command paths or pre-commit `files`/`exclude` settings to control that scope.
165
215
 
166
- When an override is weaker than the default policy, `anti-slop-python` prints a
216
+ When a Ruff override is weaker than the default policy, `anti-slop-python` prints a
167
217
  non-failing policy notice. Stricter settings do not produce a notice. The
168
218
  defaults apply only to Ruff runs started by `anti-slop-python`; add the equivalent
169
219
  configuration above if a separate `ruff check` command must enforce the same
@@ -228,6 +278,144 @@ attribute name is a constant string. They therefore allow calls such as
228
278
  depend on runtime strings. `SPY002` rejects
229
279
  the built-ins regardless of whether the name is constant.
230
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
+
231
419
  ### [`C901`](https://docs.astral.sh/ruff/rules/complex-structure/) — Limit decision complexity
232
420
 
233
421
  `C901` measures the number of paths through a function. Anti-slop-python uses a
@@ -300,10 +488,48 @@ command exit with status 1:
300
488
 
301
489
  ```text
302
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.
303
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.
304
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.
305
511
  ```
306
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
+
307
533
  Policy notices are written to standard error and do not change the exit code:
308
534
 
309
535
  ```text
@@ -337,13 +563,28 @@ Run the example explicitly to see its diagnostics:
337
563
  uv run anti-slop-python examples/basic_project
338
564
  ```
339
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
+
340
571
  ## Scope and limitations
341
572
 
342
573
  Native checks use Python's built-in `ast` and a pragmatic import alias map.
343
574
  They do not perform scope-aware name resolution, type checking, cross-file
344
- analysis, configuration, suppressions, or autofixes. Ruff-backed checks use
575
+ analysis, suppressions, or autofixes. Native configuration currently covers only
576
+ module-size limits and additional test-file patterns. Ruff-backed checks use
345
577
  the project's effective Ruff configuration and suppression behavior.
346
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
+
347
588
  ## License
348
589
 
349
590
  [MIT](LICENSE)