Skip to main content

pyclichecker

CI PyPI License: MIT

High-signal behavioral linting for Python.

pyclichecker is a read-only Python linter for high-signal defects and maintainability smells that often appear in rushed or generated code. It parses source with Python's AST and token APIs and has no runtime dependencies.

It is a code-quality tool, not an AI-authorship detector. The same finding can occur in human-written code, and every finding should be judged in context.

Where it fits

pyclichecker belongs alongside general-purpose formatters, linters, type checkers, and tests. It does not replace them.

Tool Primary role Relationship to pyclichecker
Ruff Broad Python linting and formatting Use Ruff for style, imports, modernization, common bug patterns, and formatting. pyclichecker concentrates on a smaller set of behavioral and path-sensitive risks.
Pyright Static type checking Use Pyright to validate type compatibility and typed interfaces. pyclichecker can report selected runtime-quality risks even when a project has few annotations.
ty Static type checking and language services Use ty for type analysis. Its role overlaps with Pyright rather than with most pyclichecker rules.
Black Deterministic Python formatting Use Black when it is the project's formatter. pyclichecker never rewrites formatting.
pyclichecker Focused AST and control-flow checks Use it for swallowed failures, unchecked external operations, missing postconditions, fragile constructor state, assertion-free tests, and related generated or rushed-code risks.

A practical stack is one formatter, Ruff, one type checker, meaningful tests, and pyclichecker. The differential corpus records examples that distinguish the tools and examples where their findings overlap.

Quick start

The project requires Python 3.14. Run it directly without installing:

uvx pyclichecker .

For a persistent command, install it with uv:

uv tool install pyclichecker
pyclichecker .

It can lint one file, consume standard input, emit JSON for an agent, or emit GitHub workflow annotations:

pyclichecker app.py
printf 'def unfinished():\n    pass\n' | pyclichecker -
pyclichecker . --format json
pyclichecker . --format github

Pin a release when reproducibility matters:

uvx pyclichecker@2.5.0 .

Reading a result

Text diagnostics use the conventional path:line:column: code message shape:

app.py:8:1: SLP001 `load_config` is a concrete placeholder implementation
Found 1 issue(s) in 1 file(s).

A practical review loop is:

  1. Open the reported file and line.
  2. Decide whether the behavior is intentional.
  3. Fix the implementation, error handling, or structure.
  4. Run the same command again.
  5. Suppress only the specific rule when the code is intentionally exceptional.

JSON findings include a confidence level, evidence statements, related source locations, and a stable fingerprint:

{
  "code": "SLP009",
  "confidence": "high",
  "evidence": [
    "resolved call is `subprocess.run`",
    "no return-code check or deliberate result delegation covers every continuation"
  ],
  "related_locations": [],
  "fingerprint": "v1:..."
}

Confidence describes how strongly the analyzer established the reported code pattern. It does not prove that the behavior is unintended.

Rules

pyclichecker --list-rules reports these rules:

Code Severity Check Typical correction
SLP000 error Invalid Python syntax Correct the reported syntax before trusting the rest of the scan.
SLP001 error Placeholder implementation Implement the function, remove it, or make the contract explicitly abstract.
SLP002 error Silently swallowed exception Handle, record, or re-raise the failure.
SLP003 warning Broad exception converted to fallback behavior Catch the failures you expect and preserve unexpected ones.
SLP004 warning Async function with no async behavior Make it synchronous or perform the intended awaited operation.
SLP005 warning Duplicate implementation in the same file Extract shared behavior so copies cannot drift.
SLP006 error Obvious placeholder in configuration Require a real configured value instead of shipping a dummy fallback.
SLP007 warning Cluster of narrating comments Remove narration or replace it with the reason behind non-obvious code.
SLP008 warning Oversized function Split distinct responsibilities and test them independently.
SLP009 warning Unchecked subprocess.run result Use check=True, validate returncode before reading output, or deliberately return the complete result.
SLP010 warning Synchronous network call omits a timeout or sets it to None Pass an explicit timeout appropriate for the operation.
SLP011 warning HTTP response consumed without a success check Call raise_for_status() or validate the status on every path before using the body.
SLP012 warning Path tied to one user's home directory Use Path.home(), a project-relative path, or configuration.
SLP013 warning Known blocking API called inside async code Use an async API or move the blocking call to a worker thread.
SLP014 warning Test has no explicit result or failure oracle Use an assertion, a recognized test-framework oracle, or an expected-failure declaration. Arbitrary assert_* helper names are not trusted automatically.
SLP015 warning Overridable method called before constructor state is initialized Initialize state before dispatch, or make the hook private or final.
SLP016 warning Instance state initialized on only some constructor paths Initialize the attribute unconditionally before other methods can read it.
SLP017 warning Shared mutable class state changed through an instance in production code Initialize it per instance or mark intentional shared state as ClassVar.
SLP018 warning Return status from os.system or subprocess.call is discarded Inspect or return the status, or use a raising subprocess API.
SLP019 warning Insecure temporary file creation via tempfile.mktemp Use NamedTemporaryFile or TemporaryDirectory instead of deprecated mktemp.

Rule selection accepts exact codes or prefixes:

pyclichecker . --select SLP001,SLP002
pyclichecker . --ignore SLP004,SLP008

Thresholds for function size, comment clusters, and duplicate bodies are exposed as command-line options. Run pyclichecker --help for their names and defaults.

For a first pass, fix error findings before reviewing warning findings. Warnings are prompts for engineering judgment, not proof that the code is wrong.

Baselines for established repositories

Create a baseline from a complete JSON run:

pyclichecker . --format json --fail-on never > .pyclichecker-baseline.json

Confirm that command exits 0, then fail only on findings that are not present in the baseline:

pyclichecker . --baseline .pyclichecker-baseline.json

AST-backed fingerprints use the rule, normalized path, enclosing scope, and normalized AST content rather than line numbers. Syntax-error fingerprints use the parser message and offending source line. Moving unchanged code within a file therefore does not make an existing finding new. Duplicate fingerprints are counted, so adding another copy of an existing defect is still reported.

Review coverage and performance

pyclichecker complements, rather than replaces, general-purpose quality tools. This repository also gates changes with Ruff, strict mypy, Bandit, pip-audit, unit tests, and branch coverage. See Review coverage and limits for the mapping to the Real Python review and agentic-engineering workflows. See Flow analysis for the implementation model behind the path-sensitive rules.

Performance coverage is deliberately narrow. SLP013 catches selected blocking calls in async code, and Ruff's PERF rules catch known local performance anti-patterns. pyclichecker does not infer Big-O complexity, detect general N+1 behavior, or replace benchmarks and profiling.

Suppressions

Inline suppression requires an explicit pyclichecker rule code:

def intentional_stub():  # noqa: SLP001
    pass


def another_stub():  # slop: ignore [SLP001]
    pass

Bare # noqa and unrelated codes such as # noqa: F401 do not suppress pyclichecker. A code-specific directive may include a trailing reason and may follow a # type: ignore[...] directive on the same line. Directive-like prose and text inside a string are ignored.

To suppress an entire file, place this real comment within its first five lines:

# slop: ignore-file

Exit codes

  • 0: no finding met --fail-on, and the run had no operational error.
  • 1: at least one finding met the configured failure severity.
  • 2: the scan could not run completely, including missing paths, unreadable files, unsupported inputs, or no discovered Python files.

--fail-on warning is the default. --fail-on error reports warnings without failing, and --fail-on never reports all findings without failing.

Agent use

The repository includes a reusable pyclichecker Agent Skill. Skill-aware agents can load that folder and run the linter through uvx without permanently installing the package.

Agents should use the pinned release and JSON output for stable results:

uvx pyclichecker@2.5.0 changed_file.py --format json

JSON output contains the package version, number of files checked, findings, baseline metadata, and operational errors. Each finding includes path, line, column, code, severity, confidence, message, evidence, related locations, and a stable fingerprint.

Treat exit 1 as work to review and exit 2 as a broken or incomplete scan. Fix findings before adding suppressions, and keep every suppression scoped to one explicit rule.

Treat findings or review claims from another agent as hypotheses until source inspection, reproduction, tests, or another independent check verifies them.

An agent should finish only after the same command returns 0, or after it records why each remaining finding is intentional. It should never treat exit 2 as a clean result.

For agents that do not load skills, add this portable contract to the project's AGENTS.md:

## Python quality gate

After creating or changing Python code:

1. Run pyclichecker on every changed Python file:
   `uvx pyclichecker@2.5.0 changed_file.py --format json`
2. Treat exit 1 as findings to fix and exit 2 as an incomplete scan.
3. Fix findings and rerun relevant tests. Do not add broad suppressions.
4. Run the final repository gate with the same command, replacing
   `changed_file.py` with `.`, and finish only when it exits 0.

Development

The locked development environment contains Ruff and uses the standard-library unittest runner. It also includes strict typing, branch coverage, security, and dependency-audit gates:

uv sync --locked
uv run python -m unittest discover -v
uv run python -m tests.corpus_runner
uv run coverage erase
uv run coverage run -m unittest discover -v
uv run coverage report
uv run ruff check .
uv run ruff format --check .
uv run mypy
uv run bandit -q -r src
uv export --quiet --locked --all-groups --no-emit-project --format requirements.txt --output-file .audit-requirements.txt
uv run pip-audit --strict --requirement .audit-requirements.txt
uv run pyclichecker src tests
uv build
uvx --from . pyclichecker --version

See CONTRIBUTING.md for rule and pull-request requirements.

The GitHub Actions workflow runs the complete validation suite with CPython 3.14 on Linux, macOS, and Windows.

License

Released under the MIT License.

Metadata

Release files for pyclichecker 2.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyclichecker 2.5.0
File Size Uploaded
pyclichecker-2.5.0.tar.gz 170.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pyclichecker 2.5.0
File Interpreter ABI Platform
pyclichecker-2.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 234.5 kB

Release files / pyclichecker-2.5.0.tar.gz

Download URL pyclichecker-2.5.0.tar.gz
Size 170.3 kB
Tags Source
SHA-256 checksum
How to use checksums
80ea0882724fd9aa3e2b01aff739c12aef9a61c9ae3746a51be0135bf9bda843
BLAKE2b-256 checksum
How to use checksums
9b26c89a16d7b65f6a3ef3fc1341183990fed325e39123708174b4bbe926e010
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / pyclichecker-2.5.0-py3-none-any.whl

Download URL pyclichecker-2.5.0-py3-none-any.whl
Size 64.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9185c91f1221700cb9dbd04c9da7e6216ea106e8aa30900a4c938c9ed04fa55c
BLAKE2b-256 checksum
How to use checksums
3546568a0e29fb1c8910ae15f3a0949382c0c10ee32b5be5baa608a9d217e0c3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

2.5.0 This release

2 release files

2.4.3

2 release files

2.4.2

2 release files

2.4.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page