Trust, but verify. The quality gate for AI-generated Python code.
Project description
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
Project details
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 pycodegate-0.3.0.tar.gz.
File metadata
- Download URL: pycodegate-0.3.0.tar.gz
- Upload date:
- Size: 98.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5e05ba013d86362f1833778229bdd74a073ad0f61860e50e33fad4e4f8354e56
|
|
| MD5 |
fb966e0a46a4541f74ee5fa6d042fe42
|
|
| BLAKE2b-256 |
ecdea84585343f9e6bc1b5a9919a118ca790c554c3600f163b989ff8eae9e153
|
Provenance
The following attestation bundles were made for pycodegate-0.3.0.tar.gz:
Publisher:
publish.yml on themohitkhare/pycodegate
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pycodegate-0.3.0.tar.gz -
Subject digest:
5e05ba013d86362f1833778229bdd74a073ad0f61860e50e33fad4e4f8354e56 - Sigstore transparency entry: 1155261317
- Sigstore integration time:
-
Permalink:
themohitkhare/pycodegate@2272c7fd09e62a07b716f107de2b6152ea2f602e -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/themohitkhare
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2272c7fd09e62a07b716f107de2b6152ea2f602e -
Trigger Event:
release
-
Statement type:
File details
Details for the file pycodegate-0.3.0-py3-none-any.whl.
File metadata
- Download URL: pycodegate-0.3.0-py3-none-any.whl
- Upload date:
- Size: 59.5 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 |
2ad9916cd3fbbbd8d0f0b520537322de04d51bc5d4e476fee28ab451ff22da7f
|
|
| MD5 |
97bb98011decb09e51fe107309ac84a5
|
|
| BLAKE2b-256 |
1ddff08e68efe07f159ea0e9362d94e5a022b1977fcd003ff20d9f1ef51b9071
|
Provenance
The following attestation bundles were made for pycodegate-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on themohitkhare/pycodegate
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pycodegate-0.3.0-py3-none-any.whl -
Subject digest:
2ad9916cd3fbbbd8d0f0b520537322de04d51bc5d4e476fee28ab451ff22da7f - Sigstore transparency entry: 1155261318
- Sigstore integration time:
-
Permalink:
themohitkhare/pycodegate@2272c7fd09e62a07b716f107de2b6152ea2f602e -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/themohitkhare
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2272c7fd09e62a07b716f107de2b6152ea2f602e -
Trigger Event:
release
-
Statement type: