Python code smell detector -- 82 refactoring patterns, 55 AST checks, zero dependencies
Project description
smellcheck
Python Code Smell Detector & Refactoring Guide
82 refactoring patterns · 55 automated AST checks · zero dependencies
smellcheck is a Python code smell detector and refactoring catalog. It works as a pip-installable CLI, GitHub Action, pre-commit hook, or Agent Skills plugin for AI coding assistants.
No dependencies. Pure Python stdlib (ast, pathlib, json). Runs anywhere Python 3.10+ runs.
Installation
pip
pip install smellcheck
smellcheck src/
smellcheck myfile.py --format json
smellcheck src/ --min-severity warning --fail-on warning
GitHub Action
- uses: cheickmec/smellcheck@v1
with:
paths: 'src/'
fail-on: 'error' # exit 1 on error-level findings (default)
min-severity: 'info' # display all findings (default)
format: 'github' # GitHub annotations (default)
pre-commit
repos:
- repo: https://github.com/cheickmec/smellcheck
rev: v0.2.0
hooks:
- id: smellcheck
args: ['--fail-on', 'warning']
Agent Skills (Claude Code, Codex CLI, Cursor, Copilot, Roo Code, Gemini CLI)
Any tool supporting the Agent Skills standard can install directly:
# Claude Code
/plugin marketplace add cheickmec/smellcheck
/plugin install python-refactoring@smellcheck
# OpenAI Codex CLI
$skill-installer install cheickmec/smellcheck
# Cursor
# Import from GitHub URL in skills settings
# Or install from a local clone
git clone https://github.com/cheickmec/smellcheck.git
# Point your tool to plugins/python-refactoring/skills/python-refactoring/
Manual setup for other tools
For tools without Agent Skills support (Aider, Windsurf, Continue.dev, Amazon Q), copy the relevant files into your project's instruction directory:
- Copy
SKILL.mdcontent into your tool's instruction file (.cursorrules,CONVENTIONS.md,.windsurf/rules/, etc.) - Install with
pip install smellcheckand run thesmellcheckCLI
Usage
# Scan a directory
smellcheck src/
# Scan multiple files
smellcheck file1.py file2.py
# JSON output
smellcheck src/ --format json
# GitHub Actions annotations
smellcheck src/ --format github
# Filter by severity
smellcheck src/ --min-severity warning
# Control exit code
smellcheck src/ --fail-on warning # exit 1 on warning or error
smellcheck src/ --fail-on info # exit 1 on any finding
# Run only specific checks
smellcheck src/ --select 001,057,CC
# Skip specific checks
smellcheck src/ --ignore 003,006
# Module execution
python -m smellcheck src/
Configuration
smellcheck reads [tool.smellcheck] from the nearest pyproject.toml:
[tool.smellcheck]
select = ["001", "002", "057"] # only run these checks (default: all)
ignore = ["003", "006"] # skip these checks
per-file-ignores = {"tests/*" = ["002", "034"]} # per-path overrides
fail-on = "warning" # override default fail-on
format = "text" # override default format
CLI flags override config values.
Inline Suppression
Add # noqa: SC057 to a line to suppress pattern #057 on that line:
def foo(x=[]): # noqa: SC057
return x
Use # noqa (no codes) to suppress all findings on that line. Multiple codes: # noqa: SC003,SC006
Features
- 55 automated smell checks -- per-file AST analysis, cross-file dependency analysis, and OO metrics
- 82 refactoring patterns -- numbered catalog with before/after examples, trade-offs, and severity levels
- Zero dependencies -- stdlib-only, runs on any Python 3.10+ installation
- Multiple output formats -- text (terminal), JSON (machine-readable), GitHub annotations (CI)
- Configurable -- pyproject.toml config, inline suppression, CLI overrides
- Four distribution channels -- pip, GitHub Action, pre-commit, Agent Skills
Detected Patterns
Per-File (40 checks)
| # | Pattern | Severity |
|---|---|---|
| 001 | Setters (half-built objects) | warning |
| 002 | Long functions (>20 lines) | warning |
| 003 | Magic numbers | info |
| 004 | Bare except / unused exception variable | warning |
| 006 | Generic names (data, result, tmp) | info |
| 007 | Extract class (too many methods) | info |
| 008 | UPPER_CASE without Final | info |
| 009 | Unprotected public attributes | info |
| 014 | isinstance chains | warning |
| 016 | Half-built objects (init assigns None) | warning |
| 017 | Boolean flag parameters | info |
| 018 | Singleton pattern | warning |
| 021 | Dead code after return | warning |
| 024 | Global mutable state | warning |
| 026 | input() in business logic | warning |
| 028 | Sequential IDs | info |
| 029 | Functions returning None or list | info |
| 033 | Excessive decorators (>3) | info |
| 034 | Too many parameters (>5) | warning |
| 036 | String concatenation for multiline | info |
| 039 | Deep nesting (>4 levels) | warning |
| 040 | Loop + append pattern | info |
| 041 | CQS violation (query + modify) | warning |
| 042 | Complex boolean expressions | warning |
| 051 | Error codes instead of exceptions | warning |
| 054 | Law of Demeter violation | info |
| 055 | Boolean control flag in loop | info |
| 057 | Mutable default arguments | error |
| 058 | open() without context manager | warning |
| 061 | Dataclass candidate | info |
| 062 | Sequential tuple indexing | info |
| 063 | contextlib candidate | info |
| CC | Cyclomatic complexity (>10) | warning |
| 064 | Unused function parameters | warning |
| 065 | Empty catch block | warning |
| 066 | Long lambda (>60 chars) | info |
| 067 | Complex comprehension (>2 generators) | warning |
| 068 | Missing default else branch | info |
| 069 | Lazy class (<2 methods) | info |
| 070 | Temporary fields | warning |
Cross-File (10 checks)
| # | Pattern | Description |
|---|---|---|
| 013 | Duplicate functions | AST-normalized hashing across files |
| CYC | Cyclic imports | DFS cycle detection |
| GOD | God modules | >500 lines or >30 top-level definitions |
| FE | Feature envy | Function accesses external attributes more than own |
| SHO | Shotgun surgery | Function called from >5 different files |
| DIT | Deep inheritance | Inheritance depth >4 |
| WHI | Wide hierarchy | >5 direct subclasses |
| INT | Inappropriate intimacy | >3 bidirectional class references between files |
| SPG | Speculative generality | Abstract class with no concrete subclasses |
| UDE | Unstable dependency | Stable module depends on unstable module |
OO Metrics (5 checks)
| # | Metric | Threshold |
|---|---|---|
| LCOM | Lack of Cohesion of Methods | >0.8 |
| CBO | Coupling Between Objects | >8 |
| FIO | Excessive Fan-Out | >15 |
| RFC | Response for a Class | >20 |
| MID | Middle Man (delegation ratio) | >50% |
Refactoring Reference Files
Each pattern includes a description, before/after code examples, and trade-offs:
| File | Patterns |
|---|---|
state.md |
Immutability, setters, attributes (001, 008, 009, 016, 017, 030) |
functions.md |
Extraction, naming, parameters, CQS (002, 010, 020, 026, 027, 034, 037, 041, 050, 052, 064, 066) |
types.md |
Classes, reification, polymorphism, nulls (007, 012, 014, 015, 019, 022, 023, 029, 038, 044, 048, 069, 070, DIT, WHI) |
control.md |
Guards, pipelines, conditionals, phases (039-043, 046, 047, 049, 053, 055, 056, 067, 068) |
architecture.md |
DI, singletons, exceptions, delegates (018, 024, 028, 035, 045, 051, 054, SHO, INT, SPG, UDE) |
hygiene.md |
Constants, dead code, comments, style (003, 004, 011, 013, 021, 025, 031-033, 036, 065) |
idioms.md |
Context managers, generators, unpacking (057-063) |
metrics.md |
OO metrics: cohesion, coupling, fan-out, response, delegation (LCOM, CBO, FIO, RFC, MID) |
Compatibility
| Tool | Install Method | Status |
|---|---|---|
| pip | pip install smellcheck |
Native support |
| GitHub Actions | uses: cheickmec/smellcheck@v1 |
Native support |
| pre-commit | .pre-commit-config.yaml |
Native support |
| Claude Code | /plugin install |
Native support |
| OpenAI Codex CLI | $skill-installer |
Native support |
| Cursor | GitHub import / .cursor/skills/ |
Native support |
| GitHub Copilot | MCP gallery | Native support |
| Roo Code | .roo/ directory |
Native support |
| Gemini CLI | Agent Skills | Native support |
| Windsurf | Copy to .windsurf/rules/ |
Manual |
| Aider | --read CONVENTIONS.md |
Manual |
| Continue.dev | .continue/rules/ |
Manual |
| Amazon Q | .amazonq/rules/ |
Manual |
How It Compares
| Feature | smellcheck | PyExamine | SMART-Dal | Pyscent |
|---|---|---|---|---|
| Automated detections | 55 | 49 | 31 | 11 |
| Refactoring guidance | 82 patterns | None | None | None |
| Dependencies | 0 (stdlib) | pylint, radon | DesigniteJava | pylint, radon, cohesion |
| Python-specific idioms | Yes | No | No | No |
| Cross-file analysis | Yes | Limited | Yes | No |
| OO metrics | 6 | 19 | 0 | 1 |
| Distribution channels | 4 (pip, GHA, pre-commit, Agent Skills) | 1 | 1 | 1 |
Contributing
Contributions welcome. The core detector is src/smellcheck/detector.py -- add new checks by extending the SmellDetector AST visitor class and adding a cross-file analysis function if needed.
# Development setup
git clone https://github.com/cheickmec/smellcheck.git
cd smellcheck
pip install -e .
pip install pytest
# Run tests
pytest tests/ -v
# Self-check
smellcheck src/smellcheck/
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file smellcheck-0.2.4.tar.gz.
File metadata
- Download URL: smellcheck-0.2.4.tar.gz
- Upload date:
- Size: 788.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2bcfc2a186e6336a7900596a7adcaf02a7b2431c9f1d4050201bba7bf6bff167
|
|
| MD5 |
a9aa5ae371af461db9bc59c53af07685
|
|
| BLAKE2b-256 |
c39cf80879a96a5ad942bb988654546402ef6a8aee7817f4d0e6b3a087485b1b
|
Provenance
The following attestation bundles were made for smellcheck-0.2.4.tar.gz:
Publisher:
release-please.yml on cheickmec/smellcheck
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
smellcheck-0.2.4.tar.gz -
Subject digest:
2bcfc2a186e6336a7900596a7adcaf02a7b2431c9f1d4050201bba7bf6bff167 - Sigstore transparency entry: 940678530
- Sigstore integration time:
-
Permalink:
cheickmec/smellcheck@48d634f785397ba4b96dbc364a8b9d0342f659dd -
Branch / Tag:
refs/heads/main - Owner: https://github.com/cheickmec
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-please.yml@48d634f785397ba4b96dbc364a8b9d0342f659dd -
Trigger Event:
push
-
Statement type:
File details
Details for the file smellcheck-0.2.4-py3-none-any.whl.
File metadata
- Download URL: smellcheck-0.2.4-py3-none-any.whl
- Upload date:
- Size: 28.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8ce6efbb6555b4f127b5893dc40448be67569bdef22c7f8d9e2e17ce8abc94cb
|
|
| MD5 |
e53b2eb8fd4da223ce20899ed4fbcdf6
|
|
| BLAKE2b-256 |
f55b3cea5c5b32793e15aceb98e0de607209b61c5a894c67e22ea319bf9626f4
|
Provenance
The following attestation bundles were made for smellcheck-0.2.4-py3-none-any.whl:
Publisher:
release-please.yml on cheickmec/smellcheck
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
smellcheck-0.2.4-py3-none-any.whl -
Subject digest:
8ce6efbb6555b4f127b5893dc40448be67569bdef22c7f8d9e2e17ce8abc94cb - Sigstore transparency entry: 940678544
- Sigstore integration time:
-
Permalink:
cheickmec/smellcheck@48d634f785397ba4b96dbc364a8b9d0342f659dd -
Branch / Tag:
refs/heads/main - Owner: https://github.com/cheickmec
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-please.yml@48d634f785397ba4b96dbc364a8b9d0342f659dd -
Trigger Event:
push
-
Statement type: