PyCodeGate
Trust, but verify. The quality gate for AI-generated Python code.
Why PyCodeGate?
AI coding agents ship code fast — but fast doesn't mean safe. Every eval() an LLM drops in, every mutable default it forgets, every circular import it creates is a landmine waiting to go off. PyCodeGate is the trust layer between AI-generated code and your production codebase: one command, one score, zero ambiguity.
How it works
PyCodeGate detects your project's context: framework (Django, FastAPI, Flask), Python version, package manager (uv, poetry, pip), and test framework. That context drives which rules are active — Django projects get SQL injection checks, FastAPI projects get async-correctness checks, and so on.
It then runs two analysis passes in parallel: a lint pass that evaluates 40+ rules across 8 categories (Security, Correctness, Complexity, Architecture, Performance, Structure, Imports, Dead Code), and a dead-code pass powered by Vulture that finds unused functions, classes, imports, and variables.
Findings are filtered through your configuration and scored using a weighted category-budget system. Each category has a maximum deduction budget proportional to its weight. Within a category the top 3 findings apply at full cost; additional findings apply diminishing returns (10% each), so fixing the worst issues always moves the needle. The final result is a 0–100 health score with a label: Excellent (90+), Great (75–89), Needs work (50–74), or Critical (<50).
Install
Run instantly with uvx — no install needed:
uvx pycodegate .
Install globally with pipx or uv:
pipx install pycodegate
# or
uv tool install pycodegate
# or
pip install pycodegate
Quick Start
# Basic scan — score + summary
pycodegate .
# Show file paths and line numbers for every finding
pycodegate . --verbose
# Machine-readable output for AI agents and CI
pycodegate . --json
# Auto-fix ruff-fixable issues, then scan
pycodegate . --fix
# Output only the numeric score (useful in scripts)
pycodegate . --score
# Scan only files changed vs a base branch
pycodegate . --diff main
JSON Output
Pass --json to get structured output that AI agents and CI pipelines can parse:
pycodegate . --json
{
"version": "0.1.0",
"path": ".",
"score": 87,
"label": "Great",
"errors": 1,
"warnings": 4,
"elapsed_ms": 212,
"project": {
"framework": "fastapi",
"python_version": "3.12",
"package_manager": "uv",
"test_framework": "pytest"
},
"diagnostics": [
{
"rule": "no-mutable-default",
"severity": "error",
"category": "Correctness",
"message": "Mutable default argument `[]` is shared across all calls",
"file_path": "src/api/routes.py",
"line": 34
},
{
"rule": "high-complexity",
"severity": "warning",
"category": "Complexity",
"message": "Function 'process_order' has cyclomatic complexity 17 (max 15)",
"file_path": "src/api/orders.py",
"line": 88
}
]
}
Agent Integration
PyCodeGate is designed to be used by AI coding agents. Add it to your agent's context so it runs after every Python change.
Claude Code
Add the skill to your project:
mkdir -p .claude/skills
curl -fsSL https://raw.githubusercontent.com/themohitkhare/pycodegate/main/skills/pycodegate/SKILL.md \
-o .claude/skills/pycodegate.md
Or copy AGENTS.md to your project root — Claude Code picks it up automatically.
Cursor
Add to .cursor/rules/pycodegate.mdc:
After modifying Python files, run `uvx pycodegate . --json` and fix all findings
with severity "error" before marking the task complete. Target score: 80+.
Windsurf
Add to .windsurfrules:
After modifying Python files, run: uvx pycodegate . --json
Fix all "error" severity findings. Re-run to verify the score improved.
Codex
Add to your system prompt:
After modifying Python files, run `uvx pycodegate . --json` to check code quality.
Fix errors first. Target score: 80+.
Aider
aider --read AGENTS.md
GitHub Actions
name: Quality Gate
on: [push, pull_request]
jobs:
pycodegate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # required for --diff
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Run PyCodeGate
run: |
pip install pycodegate
pycodegate . --verbose --diff main --fail-on error
CLI Reference
| Flag | Default | Description |
|---|---|---|
[DIRECTORY] |
. |
Path to the Python project to scan |
--lint / --no-lint |
on | Enable or disable lint checks |
--dead-code / --no-dead-code |
on | Enable or disable dead code detection |
--verbose |
off | Show file path and line number per finding |
--score |
off | Print only the numeric score and exit |
--json |
off | Emit structured JSON (for agents and CI) |
--fix |
off | Run ruff --fix before scanning |
--diff TEXT |
— | Scan only files changed vs this base branch |
--fail-on [error|warning|none] |
none |
Exit code 1 when findings at this level exist |
-v, --version |
— | Show version and exit |
-h, --help |
— | Show help and exit |
What It Checks
| Category | Weight | Max Deduction | What it catches |
|---|---|---|---|
| Security | 5 | ~24 pts | eval, exec, pickle.load, unsafe YAML, hardcoded secrets, weak hashes |
| Correctness | 4 | ~19 pts | Mutable defaults, bare/broad except, assert in production, bad __init__ return |
| Complexity | 3 | ~14 pts | Cyclomatic complexity > 15 (warning) or > 25 (error) |
| Architecture | 3 | ~14 pts | Giant modules (>500 lines), deep nesting (>5), god functions (>50 lines), too many args (>7) |
| Performance | 2 | ~10 pts | String concat in loops, imports inside functions, star imports |
| Structure | 2 | ~10 pts | Missing __init__.py, missing tests directory, no type hints |
| Imports | 1 | ~5 pts | Circular imports, wildcard imports, import order issues |
| Dead Code | 1 | ~5 pts | Unused functions, classes, variables, and imports via Vulture |
Framework-specific rules (Django, FastAPI, Flask) are mapped into the Security or Correctness budget.
Scoring
PyCodeGate uses a weighted category-budget system:
- Each category has a weight (see table above). Weights are normalized to sum to 100 points of total deduction budget.
- Within a category, findings are sorted by cost (errors cost more than warnings). The top 3 findings apply at full cost; every additional finding applies diminishing returns (10% of its cost).
- A category's deduction is capped at its budget, so a single broken category can never zero out an otherwise healthy project.
- Final score =
100 - sum(capped category deductions), floored at 0.
| Score | Label | Meaning |
|---|---|---|
| 90–100 | Excellent | Production-ready |
| 75–89 | Great | Minor issues to address |
| 50–74 | Needs work | Significant issues present |
| 0–49 | Critical | Blocking issues, do not ship |
Configuration
Create pycodegate.toml in your project root:
[options]
lint = true
dead_code = true
verbose = false
fail_on = "none"
[ignore]
rules = ["dead-code", "no-import-in-function"]
files = ["tests/fixtures/**", "migrations/**", "scripts/**"]
[per-file-ignores]
"src/legacy/*.py" = ["high-complexity", "no-god-function"]
[scoring]
max-deduction.Security = 20
max-deduction.Dead Code = 0 # disable dead-code penalty entirely
Or use pyproject.toml:
[tool.pycodegate]
lint = true
dead_code = true
fail_on = "error"
[tool.pycodegate.ignore]
rules = ["no-import-in-function"]
files = ["tests/fixtures/**"]
[tool.pycodegate.per-file-ignores]
"src/legacy/*.py" = ["high-complexity"]
[tool.pycodegate.scoring]
"max-deduction" = { Security = 20, "Dead Code" = 0 }
If both files exist, pycodegate.toml takes precedence. CLI flags always override config values.
Profiles
PyCodeGate auto-detects a project profile and adjusts rule weights accordingly. You can also set a profile explicitly in config (profile = "library").
| Profile | Auto-detected when | Adjustments |
|---|---|---|
cli |
[project.scripts] in pyproject.toml |
Architecture rules weighted up; dead-code weighted down |
web |
Django / FastAPI / Flask detected | Security and correctness weighted up; framework rules active |
library |
No scripts, no framework, has py.typed |
Public API checks active; dead-code weighted up |
script |
Single-file project or scripts/ directory |
Architecture rules relaxed; complexity thresholds raised |
Pre-commit Hook
Use --pre-commit to run PyCodeGate as a pre-commit hook. It automatically scans only the staged files:
pycodegate . --pre-commit --fail-on error
Add to .pre-commit-config.yaml:
repos:
- repo: local
hooks:
- id: pycodegate
name: PyCodeGate quality check
entry: pycodegate . --pre-commit --fail-on error
language: system
types: [python]
pass_filenames: false
Contributing
git clone https://github.com/themohitkhare/pycodegate
cd pycodegate
uv sync --all-extras
uv run pytest -q
uv run pycodegate . --verbose # dogfood it
To add a new rule:
- Create a file in
src/pycodegate/rules/extendingBaseRules - Implement
check(self, source: str, filename: str) -> list[Diagnostic] - Register it in
src/pycodegate/rules/__init__.py - Add tests in
tests/rules/
License
MIT
Metadata
Release files for pycodegate 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pycodegate-0.3.0.tar.gz | 98.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pycodegate-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 158.5 kB
Release files / pycodegate-0.3.0.tar.gz
| Download URL | pycodegate-0.3.0.tar.gz |
|---|---|
| Size | 98.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5e05ba013d86362f1833778229bdd74a073ad0f61860e50e33fad4e4f8354e56
|
|
BLAKE2b-256 checksum How to use checksums |
ecdea84585343f9e6bc1b5a9919a118ca790c554c3600f163b989ff8eae9e153
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 22, 2026.
Transparency logRelease files / pycodegate-0.3.0-py3-none-any.whl
| Download URL | pycodegate-0.3.0-py3-none-any.whl |
|---|---|
| Size | 59.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2ad9916cd3fbbbd8d0f0b520537322de04d51bc5d4e476fee28ab451ff22da7f
|
|
BLAKE2b-256 checksum How to use checksums |
1ddff08e68efe07f159ea0e9362d94e5a022b1977fcd003ff20d9f1ef51b9071
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 22, 2026.
Transparency log