python-doctor 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_doctor-0.1.0/.github/workflows/publish-pypi.yml +30 -0
- python_doctor-0.1.0/.github/workflows/test.yml +30 -0
- python_doctor-0.1.0/.gitignore +9 -0
- python_doctor-0.1.0/CHANGELOG.md +23 -0
- python_doctor-0.1.0/CONTRIBUTING.md +48 -0
- python_doctor-0.1.0/LICENSE +21 -0
- python_doctor-0.1.0/PKG-INFO +191 -0
- python_doctor-0.1.0/README.md +159 -0
- python_doctor-0.1.0/pyproject.toml +60 -0
- python_doctor-0.1.0/python_doctor/__init__.py +3 -0
- python_doctor-0.1.0/python_doctor/analyzers/__init__.py +1 -0
- python_doctor-0.1.0/python_doctor/analyzers/bandit_analyzer.py +62 -0
- python_doctor-0.1.0/python_doctor/analyzers/complexity.py +49 -0
- python_doctor-0.1.0/python_doctor/analyzers/dependency_analyzer.py +87 -0
- python_doctor-0.1.0/python_doctor/analyzers/docstring_analyzer.py +91 -0
- python_doctor-0.1.0/python_doctor/analyzers/exceptions_analyzer.py +72 -0
- python_doctor-0.1.0/python_doctor/analyzers/imports_analyzer.py +104 -0
- python_doctor-0.1.0/python_doctor/analyzers/ruff_analyzer.py +53 -0
- python_doctor-0.1.0/python_doctor/analyzers/structure.py +269 -0
- python_doctor-0.1.0/python_doctor/analyzers/vulture_analyzer.py +41 -0
- python_doctor-0.1.0/python_doctor/cli.py +149 -0
- python_doctor-0.1.0/python_doctor/py.typed +0 -0
- python_doctor-0.1.0/python_doctor/rules.py +76 -0
- python_doctor-0.1.0/python_doctor/scorer.py +27 -0
- python_doctor-0.1.0/tests/__init__.py +0 -0
- python_doctor-0.1.0/tests/test_docstring_analyzer.py +33 -0
- python_doctor-0.1.0/tests/test_exceptions_analyzer.py +29 -0
- python_doctor-0.1.0/tests/test_imports_analyzer.py +26 -0
- python_doctor-0.1.0/tests/test_rules.py +29 -0
- python_doctor-0.1.0/tests/test_scorer.py +52 -0
- python_doctor-0.1.0/uv.lock +360 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
id-token: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
publish:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
environment: pypi
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.12"
|
|
22
|
+
|
|
23
|
+
- name: Install build tools
|
|
24
|
+
run: pip install build
|
|
25
|
+
|
|
26
|
+
- name: Build package
|
|
27
|
+
run: python -m build
|
|
28
|
+
|
|
29
|
+
- name: Publish to PyPI
|
|
30
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Tests
|
|
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@v4
|
|
18
|
+
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
|
|
23
|
+
- name: Install uv
|
|
24
|
+
uses: astral-sh/setup-uv@v4
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: uv sync --extra dev
|
|
28
|
+
|
|
29
|
+
- name: Run tests
|
|
30
|
+
run: uv run pytest tests/ -v
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 (2026-02-18)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- 4 new analyzers: dependencies, docstrings, imports, exceptions
|
|
7
|
+
- Enhanced structure checks: test-to-source ratio, README, LICENSE, .gitignore, linter config, type checker config, py.typed
|
|
8
|
+
- Bandit B101 (assert) auto-filtered in test files
|
|
9
|
+
- Tests (22 tests via pytest)
|
|
10
|
+
- CI: health check on every push, tests across Python 3.10-3.13
|
|
11
|
+
- PyPI publishing via Trusted Publishers
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- Score is now `max(0, 100 - total_deductions)` — categories keep their own maxes
|
|
15
|
+
- All high-complexity functions refactored (CC 27 → <10)
|
|
16
|
+
- Docstrings added to all public functions/classes
|
|
17
|
+
|
|
18
|
+
## 0.1.0 (2026-02-18)
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- Initial release with 5 analyzers: security (Bandit), lint (Ruff), dead code (Vulture), complexity (Radon), structure
|
|
22
|
+
- CLI with `--verbose`, `--score`, `--json`, `--fix` flags
|
|
23
|
+
- Exit code 1 if score < 50
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in python-doctor!
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone https://github.com/saikatkumardey/python-doctor.git
|
|
9
|
+
cd python-doctor
|
|
10
|
+
uv sync --extra dev
|
|
11
|
+
uv run pytest tests/ -v
|
|
12
|
+
uv run python-doctor .
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Adding a New Analyzer
|
|
16
|
+
|
|
17
|
+
1. Create `python_doctor/analyzers/your_analyzer.py`
|
|
18
|
+
2. Implement `analyze(path: str, **kw) -> AnalyzerResult`
|
|
19
|
+
3. Add the category to `rules.py` `CATEGORIES` dict
|
|
20
|
+
4. Register it in `cli.py` `ANALYZERS` list
|
|
21
|
+
5. Add tests in `tests/test_your_analyzer.py`
|
|
22
|
+
6. Run `python-doctor .` — the score should still be 80+
|
|
23
|
+
|
|
24
|
+
Every analyzer follows the same pattern: scan the codebase, produce `Finding` objects, cap the deduction at the category max.
|
|
25
|
+
|
|
26
|
+
## Running Tests
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
uv run pytest tests/ -v
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Code Quality
|
|
33
|
+
|
|
34
|
+
We dogfood python-doctor on itself. The CI runs it on every push and fails if the score drops below 50.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
uv run python-doctor . --verbose
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Releases
|
|
41
|
+
|
|
42
|
+
Tags trigger PyPI + ClawHub publishing via CI. Don't publish manually.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Bump version in pyproject.toml and __init__.py
|
|
46
|
+
git tag v0.3.0
|
|
47
|
+
git push origin v0.3.0
|
|
48
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Saikat Kumar Dey
|
|
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.
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-doctor
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: One command. One score. Built for AI agents. Scans Python codebases and returns a 0-100 health score.
|
|
5
|
+
Project-URL: Homepage, https://github.com/saikatkumardey/python-doctor
|
|
6
|
+
Project-URL: Repository, https://github.com/saikatkumardey/python-doctor
|
|
7
|
+
Project-URL: Issues, https://github.com/saikatkumardey/python-doctor/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/saikatkumardey/python-doctor/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Saikat Kumar Dey <deysaikatkumar@gmail.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: ai-agents,code-quality,developer-tools,linting,python,static-analysis
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
23
|
+
Classifier: Topic :: Software Development :: Testing
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: bandit>=1.7.0
|
|
26
|
+
Requires-Dist: radon>=6.0.0
|
|
27
|
+
Requires-Dist: ruff>=0.4.0
|
|
28
|
+
Requires-Dist: vulture>=2.11
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# Python Doctor 🐍
|
|
34
|
+
|
|
35
|
+
[](https://github.com/saikatkumardey/python-doctor/actions/workflows/test.yml)
|
|
36
|
+
[](https://pypi.org/project/python-doctor/)
|
|
37
|
+
[](https://pypi.org/project/python-doctor/)
|
|
38
|
+
[](LICENSE)
|
|
39
|
+
|
|
40
|
+
**One command. One score. Built for AI agents.**
|
|
41
|
+
|
|
42
|
+
Python Doctor scans a Python codebase and returns a 0-100 health score with structured, actionable output. It's designed so an AI agent can run it, read the results, fix the issues, and verify the fix — in a loop, without human intervention.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
python-doctor .
|
|
46
|
+
# 📊 Score: 98/100 (Excellent)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Why?
|
|
50
|
+
|
|
51
|
+
Setting up linting, security scanning, dead code detection, and complexity analysis means configuring 5+ tools, reading 5 different output formats, and deciding what matters. Python Doctor wraps them all into a single command with a single score.
|
|
52
|
+
|
|
53
|
+
An agent doesn't need to know what Bandit is. It just needs to know the score dropped and which lines to fix.
|
|
54
|
+
|
|
55
|
+
## Install the CLI
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Using uv (recommended)
|
|
59
|
+
uv tool install git+https://github.com/saikatkumardey/python-doctor
|
|
60
|
+
|
|
61
|
+
# Using pip
|
|
62
|
+
pip install git+https://github.com/saikatkumardey/python-doctor
|
|
63
|
+
|
|
64
|
+
# Or clone and run directly
|
|
65
|
+
git clone https://github.com/saikatkumardey/python-doctor.git
|
|
66
|
+
cd python-doctor
|
|
67
|
+
uv run python-doctor /path/to/project
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Add to Your Coding Agent
|
|
71
|
+
|
|
72
|
+
Python Doctor works with any agent that can run shell commands. Install the CLI (above), then add the rule to your agent:
|
|
73
|
+
|
|
74
|
+
### Claude Code
|
|
75
|
+
|
|
76
|
+
Add to your `CLAUDE.md`:
|
|
77
|
+
|
|
78
|
+
```markdown
|
|
79
|
+
## Python Health Check
|
|
80
|
+
|
|
81
|
+
Before finishing work on Python files, run:
|
|
82
|
+
python-doctor . --json
|
|
83
|
+
|
|
84
|
+
Fix any findings with severity "error". Target score: 80+.
|
|
85
|
+
If score drops below 50, do not commit — fix the issues first.
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Cursor
|
|
89
|
+
|
|
90
|
+
Add to `.cursor/rules/python-doctor.mdc`:
|
|
91
|
+
|
|
92
|
+
```markdown
|
|
93
|
+
---
|
|
94
|
+
description: Python codebase health check
|
|
95
|
+
globs: "**/*.py"
|
|
96
|
+
alwaysApply: false
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
Run `python-doctor . --json` after modifying Python files.
|
|
100
|
+
Fix findings. Target score: 80+. Do not commit below 50.
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### OpenAI Codex
|
|
104
|
+
|
|
105
|
+
Add to `AGENTS.md`:
|
|
106
|
+
|
|
107
|
+
```markdown
|
|
108
|
+
## Python Health Check
|
|
109
|
+
|
|
110
|
+
After modifying Python files, run `python-doctor . --json` to check codebase health.
|
|
111
|
+
Fix any findings. Target score: 80+. Exit code 1 means score < 50 — fix before committing.
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Windsurf / Cline / Aider
|
|
115
|
+
|
|
116
|
+
Add to your project rules or system prompt:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
After modifying Python files, run: python-doctor . --json
|
|
120
|
+
Read the output. Fix findings with severity "error" first, then warnings.
|
|
121
|
+
Re-run to verify the score improved. Target: 80+.
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### OpenClaw
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
clawhub install python-doctor
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### GitHub Actions (CI)
|
|
131
|
+
|
|
132
|
+
```yaml
|
|
133
|
+
- name: Health Check
|
|
134
|
+
run: |
|
|
135
|
+
uv tool install git+https://github.com/saikatkumardey/python-doctor
|
|
136
|
+
python-doctor . --verbose
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Exits with code 1 if score < 50.
|
|
140
|
+
|
|
141
|
+
## Usage
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# Scan current directory
|
|
145
|
+
python-doctor .
|
|
146
|
+
|
|
147
|
+
# Verbose — show all findings with line numbers
|
|
148
|
+
python-doctor . --verbose
|
|
149
|
+
|
|
150
|
+
# Just the score (for CI or quick checks)
|
|
151
|
+
python-doctor . --score
|
|
152
|
+
|
|
153
|
+
# Structured JSON for agents
|
|
154
|
+
python-doctor . --json
|
|
155
|
+
|
|
156
|
+
# Auto-fix what Ruff can handle, then report the rest
|
|
157
|
+
python-doctor . --fix
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## What It Checks
|
|
161
|
+
|
|
162
|
+
9 categories, 5 external tools + 4 custom AST analyzers:
|
|
163
|
+
|
|
164
|
+
| Category | Max | What |
|
|
165
|
+
|----------|-----|------|
|
|
166
|
+
| 🔒 Security | -30 | Bandit (SQLi, hardcoded secrets, unsafe calls). Auto-skips `assert` in test files. |
|
|
167
|
+
| 🧹 Lint | -25 | Ruff (unused imports, undefined names, style) |
|
|
168
|
+
| 💀 Dead Code | -15 | Vulture (unused functions, variables, imports) |
|
|
169
|
+
| 🔄 Complexity | -15 | Radon (cyclomatic complexity > 10) |
|
|
170
|
+
| 🏗 Structure | -15 | File sizes, test ratio, type hints, README, LICENSE, linter/type-checker config |
|
|
171
|
+
| 📦 Dependencies | -15 | Build file exists, no mixed systems, pip-audit vulnerabilities |
|
|
172
|
+
| 📝 Docstrings | -10 | Public function/class docstring coverage |
|
|
173
|
+
| 🔗 Imports | -10 | Star imports, circular import detection |
|
|
174
|
+
| ⚡ Exceptions | -10 | Bare `except:`, silently swallowed exceptions |
|
|
175
|
+
|
|
176
|
+
Score = `max(0, 100 - total_deductions)`. Each category is capped at its max.
|
|
177
|
+
|
|
178
|
+
## The Loop
|
|
179
|
+
|
|
180
|
+
This is how an agent uses it:
|
|
181
|
+
|
|
182
|
+
1. `python-doctor . --json` → read the report
|
|
183
|
+
2. Fix the findings (auto-fix with `--fix`, manual fixes for the rest)
|
|
184
|
+
3. `python-doctor . --score` → verify improvement
|
|
185
|
+
4. Repeat until score target met
|
|
186
|
+
|
|
187
|
+
We built Python Doctor, then ran it on itself. Score: 47. Fixed everything it flagged. Score: 98. The tool eats its own dogfood.
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT — Saikat Kumar Dey, 2026
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Python Doctor 🐍
|
|
2
|
+
|
|
3
|
+
[](https://github.com/saikatkumardey/python-doctor/actions/workflows/test.yml)
|
|
4
|
+
[](https://pypi.org/project/python-doctor/)
|
|
5
|
+
[](https://pypi.org/project/python-doctor/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
**One command. One score. Built for AI agents.**
|
|
9
|
+
|
|
10
|
+
Python Doctor scans a Python codebase and returns a 0-100 health score with structured, actionable output. It's designed so an AI agent can run it, read the results, fix the issues, and verify the fix — in a loop, without human intervention.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
python-doctor .
|
|
14
|
+
# 📊 Score: 98/100 (Excellent)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Why?
|
|
18
|
+
|
|
19
|
+
Setting up linting, security scanning, dead code detection, and complexity analysis means configuring 5+ tools, reading 5 different output formats, and deciding what matters. Python Doctor wraps them all into a single command with a single score.
|
|
20
|
+
|
|
21
|
+
An agent doesn't need to know what Bandit is. It just needs to know the score dropped and which lines to fix.
|
|
22
|
+
|
|
23
|
+
## Install the CLI
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Using uv (recommended)
|
|
27
|
+
uv tool install git+https://github.com/saikatkumardey/python-doctor
|
|
28
|
+
|
|
29
|
+
# Using pip
|
|
30
|
+
pip install git+https://github.com/saikatkumardey/python-doctor
|
|
31
|
+
|
|
32
|
+
# Or clone and run directly
|
|
33
|
+
git clone https://github.com/saikatkumardey/python-doctor.git
|
|
34
|
+
cd python-doctor
|
|
35
|
+
uv run python-doctor /path/to/project
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Add to Your Coding Agent
|
|
39
|
+
|
|
40
|
+
Python Doctor works with any agent that can run shell commands. Install the CLI (above), then add the rule to your agent:
|
|
41
|
+
|
|
42
|
+
### Claude Code
|
|
43
|
+
|
|
44
|
+
Add to your `CLAUDE.md`:
|
|
45
|
+
|
|
46
|
+
```markdown
|
|
47
|
+
## Python Health Check
|
|
48
|
+
|
|
49
|
+
Before finishing work on Python files, run:
|
|
50
|
+
python-doctor . --json
|
|
51
|
+
|
|
52
|
+
Fix any findings with severity "error". Target score: 80+.
|
|
53
|
+
If score drops below 50, do not commit — fix the issues first.
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Cursor
|
|
57
|
+
|
|
58
|
+
Add to `.cursor/rules/python-doctor.mdc`:
|
|
59
|
+
|
|
60
|
+
```markdown
|
|
61
|
+
---
|
|
62
|
+
description: Python codebase health check
|
|
63
|
+
globs: "**/*.py"
|
|
64
|
+
alwaysApply: false
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
Run `python-doctor . --json` after modifying Python files.
|
|
68
|
+
Fix findings. Target score: 80+. Do not commit below 50.
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### OpenAI Codex
|
|
72
|
+
|
|
73
|
+
Add to `AGENTS.md`:
|
|
74
|
+
|
|
75
|
+
```markdown
|
|
76
|
+
## Python Health Check
|
|
77
|
+
|
|
78
|
+
After modifying Python files, run `python-doctor . --json` to check codebase health.
|
|
79
|
+
Fix any findings. Target score: 80+. Exit code 1 means score < 50 — fix before committing.
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Windsurf / Cline / Aider
|
|
83
|
+
|
|
84
|
+
Add to your project rules or system prompt:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
After modifying Python files, run: python-doctor . --json
|
|
88
|
+
Read the output. Fix findings with severity "error" first, then warnings.
|
|
89
|
+
Re-run to verify the score improved. Target: 80+.
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### OpenClaw
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
clawhub install python-doctor
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### GitHub Actions (CI)
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
- name: Health Check
|
|
102
|
+
run: |
|
|
103
|
+
uv tool install git+https://github.com/saikatkumardey/python-doctor
|
|
104
|
+
python-doctor . --verbose
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Exits with code 1 if score < 50.
|
|
108
|
+
|
|
109
|
+
## Usage
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# Scan current directory
|
|
113
|
+
python-doctor .
|
|
114
|
+
|
|
115
|
+
# Verbose — show all findings with line numbers
|
|
116
|
+
python-doctor . --verbose
|
|
117
|
+
|
|
118
|
+
# Just the score (for CI or quick checks)
|
|
119
|
+
python-doctor . --score
|
|
120
|
+
|
|
121
|
+
# Structured JSON for agents
|
|
122
|
+
python-doctor . --json
|
|
123
|
+
|
|
124
|
+
# Auto-fix what Ruff can handle, then report the rest
|
|
125
|
+
python-doctor . --fix
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## What It Checks
|
|
129
|
+
|
|
130
|
+
9 categories, 5 external tools + 4 custom AST analyzers:
|
|
131
|
+
|
|
132
|
+
| Category | Max | What |
|
|
133
|
+
|----------|-----|------|
|
|
134
|
+
| 🔒 Security | -30 | Bandit (SQLi, hardcoded secrets, unsafe calls). Auto-skips `assert` in test files. |
|
|
135
|
+
| 🧹 Lint | -25 | Ruff (unused imports, undefined names, style) |
|
|
136
|
+
| 💀 Dead Code | -15 | Vulture (unused functions, variables, imports) |
|
|
137
|
+
| 🔄 Complexity | -15 | Radon (cyclomatic complexity > 10) |
|
|
138
|
+
| 🏗 Structure | -15 | File sizes, test ratio, type hints, README, LICENSE, linter/type-checker config |
|
|
139
|
+
| 📦 Dependencies | -15 | Build file exists, no mixed systems, pip-audit vulnerabilities |
|
|
140
|
+
| 📝 Docstrings | -10 | Public function/class docstring coverage |
|
|
141
|
+
| 🔗 Imports | -10 | Star imports, circular import detection |
|
|
142
|
+
| ⚡ Exceptions | -10 | Bare `except:`, silently swallowed exceptions |
|
|
143
|
+
|
|
144
|
+
Score = `max(0, 100 - total_deductions)`. Each category is capped at its max.
|
|
145
|
+
|
|
146
|
+
## The Loop
|
|
147
|
+
|
|
148
|
+
This is how an agent uses it:
|
|
149
|
+
|
|
150
|
+
1. `python-doctor . --json` → read the report
|
|
151
|
+
2. Fix the findings (auto-fix with `--fix`, manual fixes for the rest)
|
|
152
|
+
3. `python-doctor . --score` → verify improvement
|
|
153
|
+
4. Repeat until score target met
|
|
154
|
+
|
|
155
|
+
We built Python Doctor, then ran it on itself. Score: 47. Fixed everything it flagged. Score: 98. The tool eats its own dogfood.
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT — Saikat Kumar Dey, 2026
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "python-doctor"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "One command. One score. Built for AI agents. Scans Python codebases and returns a 0-100 health score."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = {text = "MIT"}
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
authors = [
|
|
9
|
+
{name = "Saikat Kumar Dey", email = "deysaikatkumar@gmail.com"},
|
|
10
|
+
]
|
|
11
|
+
keywords = ["python", "linting", "code-quality", "ai-agents", "static-analysis", "developer-tools"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Environment :: Console",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
23
|
+
"Topic :: Software Development :: Testing",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"ruff>=0.4.0",
|
|
27
|
+
"bandit>=1.7.0",
|
|
28
|
+
"vulture>=2.11",
|
|
29
|
+
"radon>=6.0.0",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
python-doctor = "python_doctor.cli:main"
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/saikatkumardey/python-doctor"
|
|
37
|
+
Repository = "https://github.com/saikatkumardey/python-doctor"
|
|
38
|
+
Issues = "https://github.com/saikatkumardey/python-doctor/issues"
|
|
39
|
+
Changelog = "https://github.com/saikatkumardey/python-doctor/blob/main/CHANGELOG.md"
|
|
40
|
+
|
|
41
|
+
[project.optional-dependencies]
|
|
42
|
+
dev = ["pytest>=7.0"]
|
|
43
|
+
|
|
44
|
+
[build-system]
|
|
45
|
+
requires = ["hatchling"]
|
|
46
|
+
build-backend = "hatchling.build"
|
|
47
|
+
|
|
48
|
+
[tool.ruff]
|
|
49
|
+
target-version = "py310"
|
|
50
|
+
line-length = 120
|
|
51
|
+
exclude = [".venv", "venv", "node_modules", "__pycache__", ".git", ".tox"]
|
|
52
|
+
|
|
53
|
+
[tool.ruff.lint]
|
|
54
|
+
select = ["E", "F", "W", "I"]
|
|
55
|
+
|
|
56
|
+
[tool.mypy]
|
|
57
|
+
python_version = "3.10"
|
|
58
|
+
warn_return_any = true
|
|
59
|
+
warn_unused_configs = true
|
|
60
|
+
ignore_missing_imports = true
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Analyzers for Python Doctor."""
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Bandit security analyzer."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import subprocess # nosec B404 — required for running CLI tools
|
|
6
|
+
|
|
7
|
+
from ..rules import BANDIT_SEVERITY_COST, CATEGORIES, AnalyzerResult, Finding
|
|
8
|
+
|
|
9
|
+
_EXCLUDE_DIRS = [".venv", "venv", "node_modules", "__pycache__", ".git", ".tox", ".mypy_cache", ".ruff_cache"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _is_test_file(filepath: str) -> bool:
|
|
13
|
+
"""Check if a file is a test file (in test dir or test-named)."""
|
|
14
|
+
parts = os.path.normpath(filepath).split(os.sep)
|
|
15
|
+
if any(p in ("tests", "test") for p in parts):
|
|
16
|
+
return True
|
|
17
|
+
basename = os.path.basename(filepath)
|
|
18
|
+
return basename.startswith("test_") or basename.endswith("_test.py")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def analyze(path: str, **_kw) -> AnalyzerResult:
|
|
22
|
+
"""Run bandit security analysis on the project."""
|
|
23
|
+
result = AnalyzerResult(category="security")
|
|
24
|
+
max_ded = CATEGORIES["security"]["max_deduction"]
|
|
25
|
+
abs_path = os.path.abspath(path)
|
|
26
|
+
excludes = ",".join(os.path.join(abs_path, d) for d in _EXCLUDE_DIRS)
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
proc = subprocess.run( # nosec B603 B607 — intentional subprocess call to bandit CLI tool
|
|
30
|
+
["bandit", "-r", "-f", "json", "-q",
|
|
31
|
+
"--exclude", excludes,
|
|
32
|
+
abs_path],
|
|
33
|
+
capture_output=True, text=True, timeout=120
|
|
34
|
+
)
|
|
35
|
+
data = json.loads(proc.stdout) if proc.stdout.strip() else {}
|
|
36
|
+
items = data.get("results", [])
|
|
37
|
+
except FileNotFoundError:
|
|
38
|
+
result.error = "bandit not found (skipped)"
|
|
39
|
+
return result
|
|
40
|
+
except Exception as e:
|
|
41
|
+
result.error = str(e)
|
|
42
|
+
return result
|
|
43
|
+
|
|
44
|
+
for item in items:
|
|
45
|
+
sev = item.get("issue_severity", "LOW").upper()
|
|
46
|
+
cost = BANDIT_SEVERITY_COST.get(sev, 1)
|
|
47
|
+
test_id = item.get("test_id", "?")
|
|
48
|
+
msg = item.get("issue_text", "")
|
|
49
|
+
filename = item.get("filename", "")
|
|
50
|
+
line = item.get("line_number", 0)
|
|
51
|
+
|
|
52
|
+
# Skip B101 (assert) in test files
|
|
53
|
+
if test_id == "B101" and _is_test_file(filename):
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
result.findings.append(Finding(
|
|
57
|
+
category="security", rule=f"bandit/{test_id}", message=msg,
|
|
58
|
+
file=filename, line=line, severity=sev.lower(), cost=cost,
|
|
59
|
+
))
|
|
60
|
+
|
|
61
|
+
result.deduction = min(sum(f.cost for f in result.findings), max_ded)
|
|
62
|
+
return result
|