PyCodeGate is the quality gate for AI-generated Python code. It produces a 0–100 health score by detecting your framework, running parallel lint and dead-code analysis across 40+ rules, and reporting findings by severity — so you can trust what your AI agent just wrote.
uvx pycodegate . --jsonRun pycodegate . --json after modifying Python files. Fix findings with severity "error" first (security, correctness). Re-run to verify the score improved. Target: 80+.
The JSON output includes the score, label, per-diagnostic severity and file location, and detected project context (framework, Python version, package manager).
- MUST: Use
uvfor all Python operations.uv runto execute,uv syncto install. - MUST: Follow existing code patterns — AST-based rule checks extending
BaseRules. - MUST: Keep all types in
src/pycodegate/types.py. - MUST: Use dataclasses (frozen) for data containers.
- MUST: Never comment unless absolutely necessary.
- If the code is a hack, prefix with
# HACK: reason
- If the code is a hack, prefix with
- MUST: Use snake_case for files and variables.
- MUST: Use descriptive names (avoid shorthands or 1-2 character names).
- MUST: Do not use
type: ignoreunless absolutely necessary. - MUST: Remove unused code and don't repeat yourself.
- MUST: Put all magic numbers in
constants.pyusingSCREAMING_SNAKE_CASE. - MUST: Put small, focused utility functions in
utils/with one utility per file. - MUST: Use early returns and guard clauses to reduce nesting depth below 5.
- MUST: Keep functions under 50 lines.
uv run pytest -q # run all tests
uv run ruff check . && uv run ruff format --check . # lint + format check
uv run pycodegate . --verbose # dogfood on ourselves- Create a new file in
src/pycodegate/rules/extendingBaseRules - Implement
check(self, source: str, filename: str) -> list[Diagnostic] - Register in
src/pycodegate/rules/__init__.py - Add tests in
tests/rules/
src/pycodegate/
cli.py — Click CLI entry point
api.py — Programmatic API (diagnose function)
scan.py — Orchestrator: parallel lint + dead code
score.py — Score calculation from diagnostics
config.py — Config loading (pycodegate.toml / pyproject.toml)
discover.py — Project detection (framework, package manager, etc.)
output.py — Rich terminal output
types.py — All data types (Diagnostic, Score, etc.)
constants.py — Magic numbers and thresholds
rules/
base.py — Abstract BaseRules with AST parsing
security.py — eval, exec, pickle, yaml, secrets, hashes, os.system, shell
performance.py — string concat in loops, star imports
architecture.py — giant modules, nesting, god functions, too many args
complexity.py — cyclomatic complexity
correctness.py — mutable defaults, bare except, assert, return in init
imports.py — circular imports (project-level)
structure.py — tests/README/LICENSE/linter/type-checker, type coverage
dependencies.py — pip-audit vulnerability scan
dead_code.py — Vulture integration
django.py / fastapi.py / flask.py — web framework rules
pydantic.py / sqlalchemy.py / celery.py / requests_rules.py /
logging_rules.py / pandas_rules.py / pytest_rules.py / numpy_rules.py — library rules
utils/
file_discovery.py — Python file discovery (git + fallback), excludes non-source dirs
is_test_file.py — test-file detection (drives test-only rule suppression)
ast_helpers.py — Common AST utilities
diff.py — Git diff file resolution
PyCodeGate is a quality gate — false positives destroy its value. Be precision-first:
- A new rule MUST ship with a negative fixture/test proving it does NOT fire on the idiomatic, correct form of the pattern, not just a positive test.
- Rules that are idiomatic in tests (asserts, long fixtures) belong in
RULES_SUPPRESSED_IN_TESTSinconstants.py; suppression happens centrally inscan._suppress_test_noise, keyed on the project-relative path. - Validate rule changes against the corpus benchmark:
python benchmarks/run_corpus.py. A rule lighting up respected codebases needs to be narrowed, suppressed, or removed.