pyclichecker 2.4.1__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.
@@ -0,0 +1,52 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ concurrency:
11
+ group: ${{ github.workflow }}-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ jobs:
15
+ quality:
16
+ name: Python 3.14
17
+ runs-on: ubuntu-latest
18
+ timeout-minutes: 10
19
+
20
+ steps:
21
+ - name: Check out repository
22
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23
+
24
+ - name: Set up uv and Python
25
+ uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
26
+ with:
27
+ version: "0.12.5"
28
+ python-version: "3.14"
29
+ enable-cache: true
30
+
31
+ - name: Install locked development environment
32
+ run: uv sync --locked
33
+
34
+ - name: Run unit tests
35
+ run: uv run python -m unittest discover -v
36
+
37
+ - name: Run Ruff
38
+ run: |
39
+ uv run ruff check .
40
+ uv run ruff format --check .
41
+
42
+ - name: Run pyclichecker on itself
43
+ run: uv run pyclichecker src tests
44
+
45
+ - name: Build distributions
46
+ run: uv build
47
+
48
+ - name: Smoke-test built wheel
49
+ run: |
50
+ wheel="$(find dist -name '*.whl' -print -quit)"
51
+ uvx --from "$wheel" pyclichecker --version
52
+ uvx --from "$wheel" pyclichecker src tests
@@ -0,0 +1,80 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ concurrency:
11
+ group: publish-${{ github.event.release.tag_name }}
12
+ cancel-in-progress: false
13
+
14
+ jobs:
15
+ publish:
16
+ name: Build and publish
17
+ if: github.event.release.prerelease == false
18
+ runs-on: ubuntu-latest
19
+ timeout-minutes: 10
20
+ environment:
21
+ name: pypi
22
+ url: https://pypi.org/p/pyclichecker
23
+ permissions:
24
+ contents: read
25
+ id-token: write
26
+
27
+ steps:
28
+ - name: Check out repository
29
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
30
+
31
+ - name: Set up uv and Python
32
+ uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
33
+ with:
34
+ version: "0.12.5"
35
+ python-version: "3.14"
36
+ enable-cache: true
37
+
38
+ - name: Install locked development environment
39
+ run: uv sync --locked
40
+
41
+ - name: Verify release tag
42
+ env:
43
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
44
+ run: |
45
+ project_version="$(uv run python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
46
+ test "$RELEASE_TAG" = "v$project_version"
47
+
48
+ - name: Run quality gates
49
+ run: |
50
+ uv run python -m unittest discover -v
51
+ uv run ruff check .
52
+ uv run ruff format --check .
53
+ uv run pyclichecker src tests
54
+
55
+ - name: Build distributions
56
+ run: uv build
57
+
58
+ - name: Inspect archives
59
+ run: |
60
+ for archive in dist/*.whl; do
61
+ uv run python -m zipfile -l "$archive"
62
+ done
63
+ for archive in dist/*.tar.gz; do
64
+ tar -tzf "$archive"
65
+ done
66
+
67
+ - name: Smoke-test built wheel
68
+ run: |
69
+ repo_root="$PWD"
70
+ wheel="$repo_root/dist/pyclichecker-${RELEASE_TAG#v}-py3-none-any.whl"
71
+ scratch="$(mktemp -d)"
72
+ cd "$scratch"
73
+ uvx --from "$wheel" pyclichecker --version
74
+ uvx --from "$wheel" pyclichecker --list-rules
75
+ uvx --from "$wheel" pyclichecker "$repo_root/src" "$repo_root/tests"
76
+ env:
77
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
78
+
79
+ - name: Publish distributions
80
+ run: uv publish --trusted-publishing always
@@ -0,0 +1,120 @@
1
+ # AGENTS.md
2
+
3
+ ## Purpose
4
+
5
+ This repository builds the `pyclichecker` command, a read-only AST linter for
6
+ Python quality defects associated with rushed or generated code.
7
+
8
+ ## Runtime
9
+
10
+ - The project requires Python 3.14.
11
+ - Package and environment operations use `uv`.
12
+ - Runtime code has no third-party dependencies.
13
+ - MUST NOT add a runtime dependency when the standard library provides a clear implementation.
14
+ - MUST keep source under `src/pyclichecker/` and tests under `tests/`.
15
+
16
+ ## Rule changes
17
+
18
+ - MUST give each new rule a permanent `SLP` code, severity, title, and actionable description.
19
+ - MUST NOT reuse or silently change the meaning of an existing code.
20
+ - MUST favor behavioral defects and high-signal maintainability risks over style checks already handled by Ruff.
21
+ - MUST add a positive test, a negative test, and relevant false-positive tests for every rule.
22
+ - MUST keep output deterministic by sorting diagnostics by path, line, column, and code.
23
+ - SHOULD explain the concrete risk and a practical correction in each diagnostic.
24
+
25
+ ## Suppressions
26
+
27
+ - MUST parse suppression directives from comment tokens, not source substrings.
28
+ - MUST require an explicit `SLP` code for inline suppression.
29
+ - MUST NOT make bare `# noqa` suppress pyclichecker.
30
+ - SHOULD fix a finding before adding a suppression.
31
+
32
+ ## CLI compatibility
33
+
34
+ - Exit `0` means a complete run with no finding at the configured failure
35
+ threshold.
36
+ - Exit `1` means findings met the configured failure threshold.
37
+ - Exit `2` means the scan was incomplete or could not operate.
38
+ - MUST preserve text, JSON, and GitHub output modes.
39
+ - MUST treat operational errors as exit `2`, even when findings are also present.
40
+
41
+ ## Testing
42
+
43
+ Run every check from the repository root.
44
+
45
+ ### Unit tests
46
+
47
+ ```bash
48
+ uv run python -m unittest discover -v
49
+ ```
50
+
51
+ Every changed rule MUST have:
52
+
53
+ - A source example that reports the rule.
54
+ - A corrected example that does not report it.
55
+ - False-positive coverage for aliases, shadowing, delegation, test fixtures, or suppressions when relevant.
56
+
57
+ ### Static checks
58
+
59
+ ```bash
60
+ uv run ruff check .
61
+ uv run ruff format --check .
62
+ ```
63
+
64
+ ### Self-lint
65
+
66
+ ```bash
67
+ uv run pyclichecker src tests
68
+ ```
69
+
70
+ ### Package build
71
+
72
+ ```bash
73
+ uv build
74
+ ```
75
+
76
+ ### Built archive inspection
77
+
78
+ ```bash
79
+ for archive in dist/*.whl; do
80
+ uv run python -m zipfile -l "$archive"
81
+ done
82
+ for archive in dist/*.tar.gz; do
83
+ tar -tzf "$archive"
84
+ done
85
+ ```
86
+
87
+ ### Installed CLI smoke tests
88
+
89
+ ```bash
90
+ uvx --from . pyclichecker --version
91
+ uvx --from . pyclichecker --list-rules
92
+ uvx --from . pyclichecker src tests
93
+ ```
94
+
95
+ ### Fresh-directory package smoke test
96
+
97
+ Run the built wheel away from the repository so imports cannot accidentally resolve from the checkout:
98
+
99
+ ```bash
100
+ repo_root="$PWD"
101
+ version="$(uv run python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
102
+ wheel="$repo_root/dist/pyclichecker-$version-py3-none-any.whl"
103
+ scratch="$(mktemp -d)"
104
+ (
105
+ cd "$scratch"
106
+ uvx --from "$wheel" pyclichecker --version
107
+ uvx --from "$wheel" pyclichecker --list-rules
108
+ uvx --from "$wheel" pyclichecker "$repo_root/src" "$repo_root/tests"
109
+ )
110
+ ```
111
+
112
+ ## Release changes
113
+
114
+ - MUST keep the project version and `CHANGELOG.md` aligned.
115
+ - MUST inspect both wheel and source archive contents before release.
116
+ - MUST run the built wheel from a fresh directory using only documented commands.
117
+ - MUST preserve the approved MIT license in package metadata and release
118
+ archives.
119
+ - MUST scan for credentials, personal paths, and internal references before a
120
+ public push.
@@ -0,0 +1,63 @@
1
+ # Changelog
2
+
3
+ ## 2.4.1 - 2026-08-21
4
+
5
+ - Calibrated `SLP015` to report overridable constructor dispatch only while
6
+ definitely initialized instance state is still pending.
7
+ - Reduced `SLP016` false positives for `hasattr()` guards, test-and-set
8
+ initialization, and explicit `AttributeError` fallback.
9
+ - Exempted test modules from `SLP017`, where shared mutable containers are
10
+ commonly intentional recorders and fixtures.
11
+ - Added PyPI Trusted Publishing and documented the short
12
+ `uvx pyclichecker .` command.
13
+ - Updated the bundled Agent Skill to use the pinned PyPI release.
14
+
15
+ ## 2.4.0 - 2026-08-21
16
+
17
+ - Added `SLP015` for constructor calls that dispatch to overridable same-class
18
+ methods before initialization is complete.
19
+ - Added `SLP016` for instance attributes that are initialized on only some
20
+ successful constructor paths and later read without a local assignment.
21
+ - Added `SLP017` for instance methods that mutate mutable list, dictionary, or
22
+ set state inherited from the class.
23
+ - Added conservative exemptions for final and private methods, class fallbacks,
24
+ dynamic attributes, `ClassVar`, per-instance initialization, explicit class
25
+ mutation, and nested scopes.
26
+ - Added positive, corrected, false-positive, and inline-suppression coverage
27
+ for all three class-correctness rules.
28
+
29
+ ## 2.3.0 - 2026-08-21
30
+
31
+ - Added `SLP009` for unchecked `subprocess.run` outcomes.
32
+ - Added `SLP010` for synchronous network calls that omit a timeout or set it to
33
+ `None`.
34
+ - Added `SLP011` for HTTP responses consumed without a success check.
35
+ - Added `SLP012` for hardcoded paths tied to a user's home directory.
36
+ - Added `SLP014` for tests that can pass without an explicit result or
37
+ expected-failure oracle.
38
+ - Expanded `SLP006` to inspect environment defaults, mappings, keyword
39
+ arguments, and function defaults.
40
+ - Added import-alias, shadowing, delegation, suppression, and false-positive
41
+ coverage for the new checks.
42
+ - Added a version-pinned Agent Skill and portable `AGENTS.md` workflow for
43
+ running strict changed-file and repository gates through `uvx`.
44
+ - Added MIT licensing, public project metadata, contribution guidance, and
45
+ pinned GitHub Actions validation.
46
+
47
+ ## 2.2.0 - 2026-08-21
48
+
49
+ - Converted the standalone script into a Python 3.14 `uv` package with a
50
+ `pyclichecker` console command.
51
+ - Added locked development tooling, module and console entry points, structured
52
+ modules, and a 26-test suite.
53
+ - Preserved all ten existing rules, including `SLP013` diagnostics for known
54
+ blocking calls inside async functions.
55
+ - Made suppressions token-aware so directive-like strings no longer hide
56
+ findings.
57
+ - Required explicit `SLP` codes for inline `noqa` suppression.
58
+ - Made zero-file scans return operational exit code `2`.
59
+
60
+ ## 2.1.0
61
+
62
+ - This version number came from the original standalone script. No earlier
63
+ release notes were available in the source folder.
@@ -0,0 +1,58 @@
1
+ # Contributing
2
+
3
+ Contributions that improve signal, reduce false positives, or make the command
4
+ easier to use are welcome.
5
+
6
+ ## Development setup
7
+
8
+ The project requires Python 3.14 and [uv](https://docs.astral.sh/uv/).
9
+
10
+ ```bash
11
+ git clone https://github.com/ktreharrison/pyclichecker.git
12
+ cd pyclichecker
13
+ uv sync --locked
14
+ ```
15
+
16
+ ## Validation
17
+
18
+ Run every check before opening a pull request:
19
+
20
+ ```bash
21
+ uv run python -m unittest discover -v
22
+ uv run ruff check .
23
+ uv run ruff format --check .
24
+ uv run pyclichecker src tests
25
+ uv build
26
+ ```
27
+
28
+ ## Rule changes
29
+
30
+ Every new rule needs a permanent `SLP` code, an actionable diagnostic, and
31
+ tests for:
32
+
33
+ - Code that must produce the finding.
34
+ - Corrected code that must not produce the finding.
35
+ - Relevant false-positive cases, including aliases, shadowing, delegation,
36
+ fixtures, and suppressions.
37
+
38
+ Prefer behavioral defects and high-signal maintainability risks over formatting
39
+ or style checks already covered by Ruff.
40
+
41
+ ## Reporting a false positive
42
+
43
+ Open an issue with the rule code, a minimal source example, the actual result,
44
+ and the result you expected. Remove credentials and proprietary code before
45
+ posting.
46
+
47
+ ## Maintainer releases
48
+
49
+ 1. Update the version in `pyproject.toml`, refresh `uv.lock`, and add the
50
+ matching `CHANGELOG.md` entry.
51
+ 2. Run every validation and package inspection command in `AGENTS.md`.
52
+ 3. Merge the release commit to `main` and create a GitHub release tagged
53
+ `v<version>`.
54
+ 4. The `publish.yml` workflow verifies the tag, rebuilds and smoke-tests the
55
+ archives, then publishes them to PyPI through Trusted Publishing.
56
+
57
+ The publishing job uses GitHub's short-lived OIDC identity. Do not add a PyPI
58
+ API token to repository secrets.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ken Harrison
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9
+ of the Software, and to permit persons to whom the Software is furnished to do
10
+ 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,219 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyclichecker
3
+ Version: 2.4.1
4
+ Summary: A high-signal Python linter for defects common in rushed and AI-generated code
5
+ Keywords: ai,ast,code-quality,linter,python
6
+ Author: Ken Harrison
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Classifier: Topic :: Software Development :: Quality Assurance
15
+ Requires-Python: >=3.14
16
+ Project-URL: Homepage, https://github.com/ktreharrison/pyclichecker
17
+ Project-URL: Repository, https://github.com/ktreharrison/pyclichecker
18
+ Project-URL: Issues, https://github.com/ktreharrison/pyclichecker/issues
19
+ Project-URL: Changelog, https://github.com/ktreharrison/pyclichecker/blob/main/CHANGELOG.md
20
+ Description-Content-Type: text/markdown
21
+
22
+ # pyclichecker
23
+
24
+ [![CI](https://github.com/ktreharrison/pyclichecker/actions/workflows/ci.yml/badge.svg)](https://github.com/ktreharrison/pyclichecker/actions/workflows/ci.yml)
25
+ [![PyPI](https://img.shields.io/pypi/v/pyclichecker.svg)](https://pypi.org/project/pyclichecker/)
26
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
27
+
28
+ `pyclichecker` is a read-only Python linter for high-signal defects and
29
+ maintainability smells that often appear in rushed or generated code. It parses
30
+ source with Python's AST and token APIs and has no runtime dependencies.
31
+
32
+ It is a code-quality tool, not an AI-authorship detector. The same finding can
33
+ occur in human-written code, and every finding should be judged in context.
34
+
35
+ ## Quick start
36
+
37
+ The project requires Python 3.14. Run it directly without installing:
38
+
39
+ ```bash
40
+ uvx pyclichecker .
41
+ ```
42
+
43
+ For a persistent command, install it with `uv`:
44
+
45
+ ```bash
46
+ uv tool install pyclichecker
47
+ pyclichecker .
48
+ ```
49
+
50
+ It can lint one file, consume standard input, emit JSON for an agent, or emit
51
+ GitHub workflow annotations:
52
+
53
+ ```bash
54
+ pyclichecker app.py
55
+ printf 'def unfinished():\n pass\n' | pyclichecker -
56
+ pyclichecker . --format json
57
+ pyclichecker . --format github
58
+ ```
59
+
60
+ Pin a release when reproducibility matters:
61
+
62
+ ```bash
63
+ uvx pyclichecker@2.4.1 .
64
+ ```
65
+
66
+ ## Reading a result
67
+
68
+ Text diagnostics use the conventional `path:line:column: code message` shape:
69
+
70
+ ```text
71
+ app.py:8:1: SLP001 `load_config` is a concrete placeholder implementation
72
+ Found 1 issue(s) in 1 file(s).
73
+ ```
74
+
75
+ A practical review loop is:
76
+
77
+ 1. Open the reported file and line.
78
+ 2. Decide whether the behavior is intentional.
79
+ 3. Fix the implementation, error handling, or structure.
80
+ 4. Run the same command again.
81
+ 5. Suppress only the specific rule when the code is intentionally exceptional.
82
+
83
+ ## Rules
84
+
85
+ `pyclichecker --list-rules` reports these rules:
86
+
87
+ | Code | Severity | Check | Typical correction |
88
+ |---|---|---|---|
89
+ | `SLP000` | error | Invalid Python syntax | Correct the reported syntax before trusting the rest of the scan. |
90
+ | `SLP001` | error | Placeholder implementation | Implement the function, remove it, or make the contract explicitly abstract. |
91
+ | `SLP002` | error | Silently swallowed exception | Handle, record, or re-raise the failure. |
92
+ | `SLP003` | warning | Broad exception converted to fallback behavior | Catch the failures you expect and preserve unexpected ones. |
93
+ | `SLP004` | warning | Async function with no async behavior | Make it synchronous or perform the intended awaited operation. |
94
+ | `SLP005` | warning | Duplicate implementation in the same file | Extract shared behavior so copies cannot drift. |
95
+ | `SLP006` | error | Obvious placeholder in configuration | Require a real configured value instead of shipping a dummy fallback. |
96
+ | `SLP007` | warning | Cluster of narrating comments | Remove narration or replace it with the reason behind non-obvious code. |
97
+ | `SLP008` | warning | Oversized function | Split distinct responsibilities and test them independently. |
98
+ | `SLP009` | warning | Unchecked `subprocess.run` result | Use `check=True`, inspect `returncode`, or deliberately return the result. |
99
+ | `SLP010` | warning | Synchronous network call omits a timeout or sets it to `None` | Pass an explicit timeout appropriate for the operation. |
100
+ | `SLP011` | warning | HTTP response consumed without a success check | Call `raise_for_status()` or validate the status before using the body. |
101
+ | `SLP012` | warning | Path tied to one user's home directory | Use `Path.home()`, a project-relative path, or configuration. |
102
+ | `SLP013` | warning | Known blocking API called inside async code | Use an async API or move the blocking call to a worker thread. |
103
+ | `SLP014` | warning | Test has no explicit result or failure oracle | Assert an observable result or declare the expected exception or failure. |
104
+ | `SLP015` | warning | Overridable method called before constructor state is initialized | Initialize state before dispatch, or make the hook private or final. |
105
+ | `SLP016` | warning | Instance state initialized on only some constructor paths | Initialize the attribute unconditionally before other methods can read it. |
106
+ | `SLP017` | warning | Shared mutable class state changed through an instance in production code | Initialize it per instance or mark intentional shared state as `ClassVar`. |
107
+
108
+ Rule selection accepts exact codes or prefixes:
109
+
110
+ ```bash
111
+ pyclichecker . --select SLP001,SLP002
112
+ pyclichecker . --ignore SLP004,SLP008
113
+ ```
114
+
115
+ Thresholds for function size, comment clusters, and duplicate bodies are
116
+ exposed as command-line options. Run `pyclichecker --help` for their names and
117
+ defaults.
118
+
119
+ For a first pass, fix `error` findings before reviewing `warning` findings.
120
+ Warnings are prompts for engineering judgment, not proof that the code is
121
+ wrong.
122
+
123
+ ## Suppressions
124
+
125
+ Inline suppression requires an explicit pyclichecker rule code:
126
+
127
+ ```python
128
+ def intentional_stub(): # noqa: SLP001
129
+ pass
130
+
131
+
132
+ def another_stub(): # slop: ignore [SLP001]
133
+ pass
134
+ ```
135
+
136
+ Bare `# noqa` and unrelated codes such as `# noqa: F401` do not suppress
137
+ pyclichecker. Directive-like text inside a string is also ignored.
138
+
139
+ To suppress an entire file, place this real comment within its first five
140
+ lines:
141
+
142
+ ```python
143
+ # slop: ignore-file
144
+ ```
145
+
146
+ ## Exit codes
147
+
148
+ - `0`: no finding met `--fail-on`, and the run had no operational error.
149
+ - `1`: at least one finding met the configured failure severity.
150
+ - `2`: the scan could not run completely, including missing paths, unreadable
151
+ files, unsupported inputs, or no discovered Python files.
152
+
153
+ `--fail-on warning` is the default. `--fail-on error` reports warnings without
154
+ failing, and `--fail-on never` reports all findings without failing.
155
+
156
+ ## Agent use
157
+
158
+ The repository includes a reusable
159
+ [`pyclichecker` Agent Skill](skills/pyclichecker/SKILL.md). Skill-aware agents
160
+ can load that folder and run the linter through `uvx` without permanently
161
+ installing the package.
162
+
163
+ Agents should use the pinned release and JSON output for stable results:
164
+
165
+ ```bash
166
+ uvx pyclichecker@2.4.1 changed_file.py --format json
167
+ ```
168
+
169
+ JSON output contains the package version, number of files checked, findings,
170
+ and operational errors. Each finding includes path, line, column, code,
171
+ severity, and message.
172
+
173
+ Treat exit `1` as work to review and exit `2` as a broken or incomplete scan.
174
+ Fix findings before adding suppressions, and keep every suppression scoped to
175
+ one explicit rule.
176
+
177
+ An agent should finish only after the same command returns `0`, or after it
178
+ records why each remaining finding is intentional. It should never treat exit
179
+ `2` as a clean result.
180
+
181
+ For agents that do not load skills, add this portable contract to the
182
+ project's `AGENTS.md`:
183
+
184
+ ```markdown
185
+ ## Python quality gate
186
+
187
+ After creating or changing Python code:
188
+
189
+ 1. Run pyclichecker on every changed Python file:
190
+ `uvx pyclichecker@2.4.1 changed_file.py --format json`
191
+ 2. Treat exit 1 as findings to fix and exit 2 as an incomplete scan.
192
+ 3. Fix findings and rerun relevant tests. Do not add broad suppressions.
193
+ 4. Run the final repository gate with the same command, replacing
194
+ `changed_file.py` with `.`, and finish only when it exits 0.
195
+ ```
196
+
197
+ ## Development
198
+
199
+ The locked development environment contains Ruff and uses the standard-library
200
+ `unittest` runner:
201
+
202
+ ```bash
203
+ uv sync --locked
204
+ uv run python -m unittest discover -v
205
+ uv run ruff check .
206
+ uv run ruff format --check .
207
+ uv run pyclichecker src tests
208
+ uv build
209
+ uvx --from . pyclichecker --version
210
+ ```
211
+
212
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for rule and pull-request requirements.
213
+
214
+ The implementation has been exercised on macOS with CPython 3.14. The GitHub
215
+ Actions workflow is configured to run the complete validation suite on Linux.
216
+
217
+ ## License
218
+
219
+ Released under the [MIT License](LICENSE).