Skip to content

Commit 902f20a

Browse files
themohitkhareclaude
andcommitted
docs: add README, AGENTS.md, SKILL.md, CI/CD, PyPI metadata
- Full README with install, config, rules reference, API docs - AGENTS.md for AI coding agents (Claude Code, Cursor, etc.) - Claude Code skill in skills/python-doctor/SKILL.md - GitHub Actions CI (test matrix 3.10-3.13 + lint + dogfood) - GitHub Actions publish workflow (trusted PyPI publishing) - PyPI classifiers, keywords, and project URLs for uvx discovery - MIT LICENSE - Minor import cleanup across test files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 52495bd commit 902f20a

18 files changed

Lines changed: 408 additions & 13 deletions

File tree

.github/workflows/ci.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python-version: ["3.10", "3.11", "3.12", "3.13"]
15+
16+
steps:
17+
- uses: actions/checkout@v5
18+
- uses: astral-sh/setup-uv@v4
19+
20+
- name: Set up Python ${{ matrix.python-version }}
21+
run: uv python install ${{ matrix.python-version }}
22+
23+
- name: Install dependencies
24+
run: uv sync --all-extras
25+
26+
- name: Run tests
27+
run: uv run pytest --tb=short -q
28+
29+
- name: Run python-doctor on itself
30+
run: uv run python-doctor . --fail-on error
31+
32+
lint:
33+
runs-on: ubuntu-latest
34+
steps:
35+
- uses: actions/checkout@v5
36+
- uses: astral-sh/setup-uv@v4
37+
38+
- name: Install dependencies
39+
run: uv sync --all-extras
40+
41+
- name: Ruff check
42+
run: uv run ruff check .
43+
44+
- name: Ruff format check
45+
run: uv run ruff format --check .

.github/workflows/publish.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Publish to PyPI
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
permissions:
8+
id-token: write
9+
10+
jobs:
11+
publish:
12+
runs-on: ubuntu-latest
13+
environment:
14+
name: pypi
15+
url: https://pypi.org/project/python-doctor/
16+
17+
steps:
18+
- uses: actions/checkout@v5
19+
- uses: astral-sh/setup-uv@v4
20+
21+
- name: Build
22+
run: uv build
23+
24+
- name: Publish to PyPI
25+
uses: pypa/gh-action-pypi-publish@release/v1

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,6 @@ venv/
1212
.ruff_cache/
1313
htmlcov/
1414
.coverage
15+
*.so
16+
.env
17+
.DS_Store

AGENTS.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
## General Rules
2+
3+
- MUST: Use `uv` for all Python operations. `uv run` to execute, `uv sync` to install.
4+
- MUST: Follow existing code patterns — AST-based rule checks extending `BaseRules`.
5+
- MUST: Keep all types in `src/python_doctor/types.py`.
6+
- MUST: Use dataclasses (frozen) for data containers.
7+
- MUST: Never comment unless absolutely necessary.
8+
- If the code is a hack, prefix with `# HACK: reason`
9+
- MUST: Use snake_case for files and variables.
10+
- MUST: Use descriptive names (avoid shorthands or 1-2 character names).
11+
- MUST: Do not use `type: ignore` unless absolutely necessary.
12+
- MUST: Remove unused code and don't repeat yourself.
13+
- MUST: Put all magic numbers in `constants.py` using `SCREAMING_SNAKE_CASE`.
14+
- MUST: Put small, focused utility functions in `utils/` with one utility per file.
15+
- MUST: Use early returns and guard clauses to reduce nesting depth below 5.
16+
- MUST: Keep functions under 50 lines.
17+
18+
## Adding Rules
19+
20+
1. Create a new file in `src/python_doctor/rules/` extending `BaseRules`
21+
2. Implement `check(self, source: str, filename: str) -> list[Diagnostic]`
22+
3. Register in `src/python_doctor/rules/__init__.py`
23+
4. Add tests in `tests/rules/`
24+
25+
## Testing
26+
27+
Run checks before committing:
28+
29+
```bash
30+
uv run pytest # runs all tests
31+
uv run python-doctor . -v # dogfood on ourselves
32+
```
33+
34+
## Architecture
35+
36+
```
37+
src/python_doctor/
38+
cli.py — Click CLI entry point
39+
api.py — Programmatic API (diagnose function)
40+
scan.py — Orchestrator: parallel lint + dead code
41+
score.py — Score calculation from diagnostics
42+
config.py — Config loading (python-doctor.toml / pyproject.toml)
43+
discover.py — Project detection (framework, package manager, etc.)
44+
output.py — Rich terminal output
45+
types.py — All data types (Diagnostic, Score, etc.)
46+
constants.py — Magic numbers and thresholds
47+
rules/
48+
base.py — Abstract BaseRules with AST parsing
49+
security.py — eval, exec, pickle, yaml, secrets, hashes
50+
performance.py — string concat, imports in functions, star imports
51+
architecture.py — giant modules, nesting, god functions, too many args
52+
correctness.py — mutable defaults, bare except, assert, return in init
53+
django.py — raw SQL, DEBUG, SECRET_KEY, N+1
54+
fastapi.py — sync endpoints, missing response_model
55+
flask.py — secret key, debug mode, SQL f-strings
56+
dead_code.py — Vulture integration
57+
utils/
58+
file_discovery.py — Python file discovery (git + fallback)
59+
ast_helpers.py — Common AST utilities
60+
diff.py — Git diff file resolution
61+
```

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Mohit Khare
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 197 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,199 @@
11
# python-doctor
22

3-
Diagnose your Python project's health. Get a score, find issues, fix them.
3+
[![PyPI version](https://img.shields.io/pypi/v/python-doctor?style=flat&colorA=000000&colorB=000000)](https://pypi.org/project/python-doctor/)
4+
[![Downloads](https://img.shields.io/pypi/dm/python-doctor?style=flat&colorA=000000&colorB=000000)](https://pypi.org/project/python-doctor/)
5+
6+
Diagnose your Python project's health. One command scans your codebase for security, performance, correctness, and architecture issues, then outputs a **0-100 score** with actionable diagnostics.
7+
8+
Inspired by [react-doctor](https://github.com/millionco/react-doctor).
9+
10+
## How it works
11+
12+
Python Doctor detects your framework (Django, FastAPI, Flask), Python version, package manager (uv, poetry, pip), and test framework, then runs two analysis passes **in parallel**:
13+
14+
1. **Lint**: Checks 30+ rules across security, performance, architecture, correctness, and framework-specific categories. Rules are toggled automatically based on your project setup.
15+
2. **Dead code**: Detects unused functions, classes, imports, and variables via [Vulture](https://github.com/jendrikseipp/vulture).
16+
17+
Diagnostics are filtered through your config, then scored by severity (errors weigh more than warnings) to produce a **0-100 health score** (75+ Great, 50-74 Needs Work, <50 Critical).
18+
19+
## Install
20+
21+
Run instantly with uvx (no install needed):
22+
23+
```bash
24+
uvx python-doctor .
25+
```
26+
27+
Or install globally:
28+
29+
```bash
30+
uv tool install python-doctor
31+
# or
32+
pip install python-doctor
33+
```
34+
35+
Use `--verbose` to see affected files and line numbers:
36+
37+
```bash
38+
python-doctor . --verbose
39+
```
40+
41+
## Install for your coding agent
42+
43+
Add the skill to your Claude Code, Cursor, or other AI coding agent:
44+
45+
```bash
46+
# Claude Code
47+
cp skills/python-doctor/SKILL.md .claude/skills/python-doctor.md
48+
```
49+
50+
Or reference the AGENTS.md in your project root — it's automatically picked up by Claude Code, Cursor, Windsurf, and others.
51+
52+
## GitHub Actions
53+
54+
```yaml
55+
- uses: actions/checkout@v5
56+
with:
57+
fetch-depth: 0 # required for --diff
58+
- uses: actions/setup-python@v5
59+
with:
60+
python-version: "3.12"
61+
- name: Run Python Doctor
62+
run: |
63+
pip install python-doctor
64+
python-doctor . --verbose --diff main --fail-on error
65+
```
66+
67+
## Options
68+
69+
```
70+
Usage: python-doctor [OPTIONS] [DIRECTORY]
71+
72+
Options:
73+
-v, --version Show the version and exit.
74+
--lint / --no-lint Enable/disable lint checks.
75+
--dead-code / --no-dead-code Enable/disable dead code detection.
76+
--verbose Show file details per rule.
77+
--score Output only the numeric score.
78+
--diff TEXT Scan only files changed vs base branch.
79+
--fail-on [error|warning|none] Exit with code 1 on this severity level.
80+
-h, --help Show this message and exit.
81+
```
82+
83+
## Configuration
84+
85+
Create a `python-doctor.toml` in your project root:
86+
87+
```toml
88+
[options]
89+
lint = true
90+
dead_code = true
91+
verbose = false
92+
fail_on = "none"
93+
94+
[ignore]
95+
rules = ["no-import-in-function", "dead-code"]
96+
files = ["tests/fixtures/**", "migrations/**"]
97+
```
98+
99+
Or use `pyproject.toml`:
100+
101+
```toml
102+
[tool.python-doctor]
103+
lint = true
104+
dead_code = true
105+
106+
[tool.python-doctor.ignore]
107+
rules = ["no-import-in-function"]
108+
files = ["tests/fixtures/**"]
109+
```
110+
111+
If both exist, `python-doctor.toml` takes precedence. CLI flags always override config values.
112+
113+
## Rules
114+
115+
### Security
116+
| Rule | Severity | Description |
117+
|------|----------|-------------|
118+
| `no-eval` | Error | `eval()` executes arbitrary code |
119+
| `no-exec` | Error | `exec()` executes arbitrary code |
120+
| `no-pickle-load` | Error | `pickle.load()` can execute arbitrary code |
121+
| `no-unsafe-yaml-load` | Error | `yaml.load()` without Loader is unsafe |
122+
| `no-hardcoded-secret` | Error | Hardcoded secrets in source code |
123+
| `no-weak-hash` | Warning | MD5/SHA1 are cryptographically weak |
124+
125+
### Performance
126+
| Rule | Severity | Description |
127+
|------|----------|-------------|
128+
| `no-string-concat-in-loop` | Warning | O(n^2) string concatenation |
129+
| `no-import-in-function` | Warning | Import re-executed on every call |
130+
| `no-star-import` | Warning | Pollutes namespace |
131+
132+
### Architecture
133+
| Rule | Severity | Description |
134+
|------|----------|-------------|
135+
| `no-giant-module` | Warning | Module exceeds 500 lines |
136+
| `no-deep-nesting` | Warning | Nesting depth exceeds 5 |
137+
| `no-god-function` | Warning | Function exceeds 50 lines |
138+
| `no-too-many-args` | Warning | Function has more than 7 arguments |
139+
140+
### Correctness
141+
| Rule | Severity | Description |
142+
|------|----------|-------------|
143+
| `no-mutable-default` | Error | Mutable default argument shared across calls |
144+
| `no-bare-except` | Warning | Catches SystemExit and KeyboardInterrupt |
145+
| `no-broad-except` | Warning | Catches overly broad Exception |
146+
| `no-assert-in-production` | Warning | Assert statements stripped with -O |
147+
| `no-return-in-init` | Warning | Return value in `__init__` |
148+
149+
### Django
150+
| Rule | Severity | Description |
151+
|------|----------|-------------|
152+
| `no-raw-sql-injection` | Error | SQL built with string concatenation |
153+
| `no-debug-true` | Error | DEBUG = True hardcoded in settings |
154+
| `no-secret-key-in-source` | Error | SECRET_KEY hardcoded |
155+
| `no-n-plus-one-query` | Warning | Related object access in loop |
156+
157+
### FastAPI
158+
| Rule | Severity | Description |
159+
|------|----------|-------------|
160+
| `no-sync-endpoint` | Warning | Sync def blocks the event loop |
161+
| `no-missing-response-model` | Warning | Endpoint missing response_model |
162+
163+
### Flask
164+
| Rule | Severity | Description |
165+
|------|----------|-------------|
166+
| `no-flask-secret-in-source` | Error | Secret key hardcoded |
167+
| `no-flask-debug-mode` | Error | Debug mode in production |
168+
| `no-sql-string-format` | Error | SQL built with f-strings |
169+
170+
### Dead Code
171+
| Rule | Severity | Description |
172+
|------|----------|-------------|
173+
| `dead-code` | Warning | Unused function, class, import, or variable |
174+
175+
## Python API
176+
177+
```python
178+
from python_doctor.api import diagnose
179+
180+
result = diagnose("./path/to/your/project")
181+
182+
print(result.score) # Score(value=82, label="Great")
183+
print(result.diagnostics) # List of Diagnostic objects
184+
print(result.project) # Detected framework, Python version, etc.
185+
```
186+
187+
## Contributing
188+
189+
```bash
190+
git clone https://github.com/themohitkhare/pythondoctor
191+
cd pythondoctor
192+
uv sync --all-extras
193+
uv run pytest
194+
uv run python-doctor . # dogfood it
195+
```
196+
197+
## License
198+
199+
MIT

pyproject.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,32 @@ readme = "README.md"
66
requires-python = ">=3.10"
77
license = "MIT"
88
authors = [{ name = "Mohit Khare" }]
9+
keywords = ["linter", "health", "diagnostics", "security", "architecture", "dead-code", "python"]
10+
classifiers = [
11+
"Development Status :: 4 - Beta",
12+
"Environment :: Console",
13+
"Intended Audience :: Developers",
14+
"License :: OSI Approved :: MIT License",
15+
"Programming Language :: Python :: 3",
16+
"Programming Language :: Python :: 3.10",
17+
"Programming Language :: Python :: 3.11",
18+
"Programming Language :: Python :: 3.12",
19+
"Programming Language :: Python :: 3.13",
20+
"Topic :: Software Development :: Quality Assurance",
21+
"Topic :: Software Development :: Testing",
22+
"Typing :: Typed",
23+
]
924
dependencies = [
1025
"click>=8.1",
1126
"rich>=13.0",
1227
"vulture>=2.11",
1328
]
1429

30+
[project.urls]
31+
Homepage = "https://github.com/themohitkhare/pythondoctor"
32+
Repository = "https://github.com/themohitkhare/pythondoctor"
33+
Issues = "https://github.com/themohitkhare/pythondoctor/issues"
34+
1535
[project.optional-dependencies]
1636
dev = [
1737
"pytest>=8.0",

0 commit comments

Comments
 (0)