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.
Files changed (62) hide show
  1. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/.github/workflows/publish.yml +4 -3
  2. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/PKG-INFO +240 -6
  3. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/README.md +239 -5
  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.1 → anti_slop_python-0.2.0}/pyproject.toml +1 -1
  20. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/src/anti_slop_python/checker.py +20 -6
  21. {anti_slop_python-0.1.1 → 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.1 → anti_slop_python-0.2.0}/src/anti_slop_python/ruff_integration.py +32 -11
  25. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/src/anti_slop_python/ruff_policy.py +17 -1
  26. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/src/anti_slop_python/rules/__init__.py +2 -0
  27. {anti_slop_python-0.1.1 → 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.1 → 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.1/examples/basic_project/README.md +0 -34
  40. anti_slop_python-0.1.1/src/anti_slop_python/diagnostics.py +0 -18
  41. anti_slop_python-0.1.1/tests/test_examples.py +0 -31
  42. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/.github/workflows/checks.yml +0 -0
  43. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/.gitignore +0 -0
  44. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/.pre-commit-config.yaml +0 -0
  45. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/.pre-commit-hooks.yaml +0 -0
  46. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/AGENTS.md +0 -0
  47. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/LICENSE +0 -0
  48. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/examples/basic_project/pyproject.toml +0 -0
  49. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/examples/basic_project/src/example_project/__init__.py +0 -0
  50. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/examples/basic_project/src/example_project/preferred.py +0 -0
  51. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/examples/basic_project/src/example_project/violations.py +0 -0
  52. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/skills/install-anti-slop-python/SKILL.md +0 -0
  53. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/skills/install-anti-slop-python/agents/openai.yaml +0 -0
  54. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/src/anti_slop_python/__init__.py +0 -0
  55. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/src/anti_slop_python/__main__.py +0 -0
  56. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/src/anti_slop_python/rules/no_any_containers.py +0 -0
  57. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/src/anti_slop_python/rules/no_dynamic_attribute_access.py +0 -0
  58. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/tests/test_checker.py +0 -0
  59. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/tests/test_no_any_containers.py +0 -0
  60. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/tests/test_no_dynamic_attribute_access.py +0 -0
  61. {anti_slop_python-0.1.1 → anti_slop_python-0.2.0}/tests/test_ruff_integration.py +0 -0
  62. {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.1.0"
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
- 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))
89
90
  else:
90
- next_tag = "v0.1.0"
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.1.1
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.1.0
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.1.0
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 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
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, 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
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)