python-code-quality 0.1.8__tar.gz → 0.1.10__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_code_quality-0.1.8 → python_code_quality-0.1.10}/PKG-INFO +81 -51
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/README.md +75 -45
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/pyproject.toml +5 -5
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/cli.py +54 -5
- python_code_quality-0.1.8/src/py_cq/config/tools.yaml → python_code_quality-0.1.10/src/py_cq/config/config.yaml +28 -37
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/execution_engine.py +2 -2
- python_code_quality-0.1.10/src/py_cq/language_detector.py +29 -0
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/llm_formatter.py +2 -1
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/localtypes.py +5 -1
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/parsers/banditparser.py +2 -2
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/parsers/common.py +31 -0
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/parsers/compileparser.py +2 -2
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/parsers/coverageparser.py +1 -1
- python_code_quality-0.1.10/src/py_cq/parsers/exitcodeparser.py +16 -0
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/parsers/halsteadparser.py +1 -1
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/parsers/interrogateparser.py +1 -1
- python_code_quality-0.1.10/src/py_cq/parsers/linecountparser.py +26 -0
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/parsers/pytestparser.py +53 -10
- python_code_quality-0.1.10/src/py_cq/parsers/regexcountparser.py +35 -0
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/parsers/ruffparser.py +2 -2
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/parsers/typarser.py +2 -2
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/parsers/vultureparser.py +2 -2
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/tool_registry.py +5 -4
- python_code_quality-0.1.8/.github/workflows/python-publish.yml +0 -70
- python_code_quality-0.1.8/.gitignore +0 -17
- python_code_quality-0.1.8/.python-version +0 -1
- python_code_quality-0.1.8/CLAUDE.md +0 -54
- python_code_quality-0.1.8/LICENSE +0 -21
- python_code_quality-0.1.8/data/problems/travelling_salesman/ts_bad.py +0 -65
- python_code_quality-0.1.8/data/problems/travelling_salesman/ts_good.py +0 -61
- python_code_quality-0.1.8/tests/conftest.py +0 -7
- python_code_quality-0.1.8/tests/test_cli.py +0 -228
- python_code_quality-0.1.8/tests/test_common.py +0 -73
- python_code_quality-0.1.8/tests/test_config.py +0 -84
- python_code_quality-0.1.8/tests/test_context_hash.py +0 -81
- python_code_quality-0.1.8/tests/test_execution_engine.py +0 -291
- python_code_quality-0.1.8/tests/test_llm_formatter.py +0 -237
- python_code_quality-0.1.8/tests/test_localtypes.py +0 -74
- python_code_quality-0.1.8/tests/test_parser_bandit.py +0 -66
- python_code_quality-0.1.8/tests/test_parser_compile.py +0 -61
- python_code_quality-0.1.8/tests/test_parser_complexity.py +0 -76
- python_code_quality-0.1.8/tests/test_parser_coverage.py +0 -59
- python_code_quality-0.1.8/tests/test_parser_halstead.py +0 -116
- python_code_quality-0.1.8/tests/test_parser_interrogate.py +0 -72
- python_code_quality-0.1.8/tests/test_parser_maintainability.py +0 -74
- python_code_quality-0.1.8/tests/test_parser_pytest.py +0 -49
- python_code_quality-0.1.8/tests/test_parser_ruff.py +0 -33
- python_code_quality-0.1.8/tests/test_parser_ty.py +0 -33
- python_code_quality-0.1.8/tests/test_parser_vulture.py +0 -51
- python_code_quality-0.1.8/uv.lock +0 -519
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/__init__.py +0 -0
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/config/__init__.py +0 -0
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/context_hash.py +0 -0
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/main.py +0 -0
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/metric_aggregator.py +0 -0
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/parsers/__init__.py +0 -0
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/parsers/complexityparser.py +0 -0
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/parsers/maintainabilityparser.py +0 -0
- {python_code_quality-0.1.8 → python_code_quality-0.1.10}/src/py_cq/py.typed +0 -0
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-code-quality
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.10
|
|
4
4
|
Summary: Python Code Quality Analysis Tool - feed the results from 11 CQ tools straight into an LLM. Minimal tokens.
|
|
5
|
-
|
|
6
|
-
Project-URL: Repository, https://github.com/rhiza-fr/py-cq
|
|
5
|
+
Author: Chris Kilner
|
|
7
6
|
Author-email: Chris Kilner <chris@rhiza.fr>
|
|
8
7
|
License-Expression: MIT
|
|
9
|
-
License-File: LICENSE
|
|
10
|
-
Requires-Python: >=3.12
|
|
11
8
|
Requires-Dist: bandit>=1.8.0
|
|
12
9
|
Requires-Dist: coverage>=7.8.2
|
|
13
10
|
Requires-Dist: diskcache>=5.6.3
|
|
14
11
|
Requires-Dist: interrogate>=1.7.0
|
|
12
|
+
Requires-Dist: pytest>=8.4.0
|
|
15
13
|
Requires-Dist: pytest-cov>=6.1.1
|
|
16
14
|
Requires-Dist: pytest-json-report>=1.5.0
|
|
17
|
-
Requires-Dist: pytest>=8.4.0
|
|
18
15
|
Requires-Dist: pyyaml>=6.0.2
|
|
19
16
|
Requires-Dist: radon>=6.0.1
|
|
20
17
|
Requires-Dist: rich>=14.0.0
|
|
@@ -22,26 +19,25 @@ Requires-Dist: ruff>=0.14.1
|
|
|
22
19
|
Requires-Dist: ty>=0.0.17
|
|
23
20
|
Requires-Dist: typer>=0.16.0
|
|
24
21
|
Requires-Dist: vulture>=2.14
|
|
22
|
+
Requires-Python: >=3.12
|
|
23
|
+
Project-URL: Homepage, https://github.com/rhiza-fr/py-cq
|
|
24
|
+
Project-URL: Repository, https://github.com/rhiza-fr/py-cq
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
|
|
27
27
|
# CQ - Python Code Quality Analysis Tool
|
|
28
28
|
|
|
29
29
|
Feed the results from 11+ code quality tools to an LLM. Minimal tokens.
|
|
30
30
|
|
|
31
|
+
Why? It removes the mental burden of understanding all these tools and parsing their results.
|
|
32
|
+
|
|
31
33
|
The primary workflow is:
|
|
32
34
|
|
|
33
35
|
```bash
|
|
34
36
|
# get the single most critical defect as markdown
|
|
35
37
|
cq check . -o llm
|
|
36
38
|
```
|
|
37
|
-
Selects the single most critical defect using this priority order:
|
|
38
|
-
|
|
39
|
-
1. **Severity** — tools with score below `error_threshold` come before those only below `warning_threshold`
|
|
40
|
-
2. **Order** — among tools at the same severity, lower-order tools win (compile before lint before style)
|
|
41
|
-
3. **Score** — among ties, the lower score wins
|
|
42
39
|
|
|
43
|
-
|
|
44
|
-
```md
|
|
40
|
+
```python
|
|
45
41
|
`data/problems/travelling_salesman/ts_bad.py:21` — **F841**: Local variable `unused_variable` is assigned to but never used
|
|
46
42
|
|
|
47
43
|
18: min_dist = float("inf")
|
|
@@ -60,9 +56,10 @@ Feed to an LLM with edit tools and repeat until there are no issues, e.g.
|
|
|
60
56
|
```python
|
|
61
57
|
cq check . -o llm | claude -p "fix this"
|
|
62
58
|
# or
|
|
63
|
-
cq check . -o llm | ollama gpt-oss:20b "Explain how to fix this"
|
|
59
|
+
cq check . -o llm | ollama run gpt-oss:20b "Explain how to fix this"
|
|
64
60
|
```
|
|
65
61
|
|
|
62
|
+
|
|
66
63
|
## Install
|
|
67
64
|
|
|
68
65
|
```bash
|
|
@@ -70,21 +67,22 @@ cq check . -o llm | ollama gpt-oss:20b "Explain how to fix this"
|
|
|
70
67
|
uv tool install python-code-quality
|
|
71
68
|
|
|
72
69
|
# or, clone it then install
|
|
73
|
-
git
|
|
70
|
+
git clone https://github.com/rhiza-fr/py-cq.git
|
|
74
71
|
cd py-cq
|
|
75
72
|
uv tool install .
|
|
76
73
|
```
|
|
77
74
|
|
|
78
75
|
## Tools
|
|
79
76
|
|
|
80
|
-
These tools are run in **parallel** except
|
|
77
|
+
These tools are run in **parallel** except:
|
|
78
|
+
When running '-o llm', we run sequentially and exit early at the first error.
|
|
81
79
|
|
|
82
80
|
| Order | Tool | Measures |
|
|
83
81
|
|----------|------|----------|
|
|
84
82
|
| 1 | compileall | Syntax errors |
|
|
85
|
-
| 2 |
|
|
86
|
-
| 3 |
|
|
87
|
-
| 4 |
|
|
83
|
+
| 2 | ruff | Lint / style |
|
|
84
|
+
| 3 | ty | Type errors |
|
|
85
|
+
| 4 | bandit | Security vulnerabilities |
|
|
88
86
|
| 5 | pytest | Test pass rate |
|
|
89
87
|
| 6 | coverage | Test coverage |
|
|
90
88
|
| 7 | radon cc | Cyclomatic complexity |
|
|
@@ -93,14 +91,14 @@ These tools are run in **parallel** except when looking for the first error in -
|
|
|
93
91
|
| 10 | vulture | Dead code |
|
|
94
92
|
| 11 | interrogate | Docstring coverage |
|
|
95
93
|
|
|
96
|
-
Diskcache is used to cache tool output for lightning fast re-runs. Sane defaults: <100 Mb, <5 days, No pickle
|
|
94
|
+
Diskcache is used to cache tool output for lightning fast re-runs. Sane defaults: <100 Mb, <5 days, No pickle risk.
|
|
97
95
|
|
|
98
96
|
|
|
99
97
|
## Usage
|
|
100
98
|
|
|
101
99
|
```bash
|
|
102
100
|
cq check . # Table overview of scores for humans
|
|
103
|
-
cq check -o llm
|
|
101
|
+
cq check . -o llm # Top defect as markdown for LLMs
|
|
104
102
|
cq check . -o score # Numeric score only for CI
|
|
105
103
|
cq check . -o json # Detailed parsed JSON output for jq
|
|
106
104
|
cq check . -o raw # Raw tool output for debug
|
|
@@ -110,6 +108,46 @@ cq check . --clear-cache # Clear cached results before running (rarely needed)
|
|
|
110
108
|
cq config path/to/project/ # Show effective tool configuration
|
|
111
109
|
```
|
|
112
110
|
|
|
111
|
+
**Exit codes:** `cq check` exits with code `1` if any tool metric falls below its `error_threshold`, making it suitable as a CI gate:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
cq check . && deploy # block deploy on errors
|
|
115
|
+
cq check . -o score # print score, exit 1 on errors
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Claude Code Integration
|
|
119
|
+
|
|
120
|
+
Add a stop hook to your project's `.claude/settings.json` so Claude automatically checks quality after each session and loops until clean:
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"hooks": {
|
|
125
|
+
"Stop": [{
|
|
126
|
+
"matcher": "",
|
|
127
|
+
"hooks": [{"type": "command", "command": "cq check . -o score && echo 'CQ: all clear' || cq check . -o llm"}]
|
|
128
|
+
}]
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
When the score passes, Claude sees `CQ: all clear` (~5 tokens). When it fails, Claude receives the targeted fix prompt and continues working. This automates the `cq check . -o llm | claude -p "fix this"` loop.
|
|
134
|
+
|
|
135
|
+
> **Note:** Use project-level `.claude/settings.json`, not global settings — this hook only makes sense in Python projects.
|
|
136
|
+
|
|
137
|
+
### As a slash command (skill)
|
|
138
|
+
|
|
139
|
+
For manual invocation, create `.claude/commands/cq-fix.md`:
|
|
140
|
+
|
|
141
|
+
```markdown
|
|
142
|
+
$(cq check . -o llm)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Then invoke it with `/cq-fix` in Claude Code. The `$(...)` embeds the live `cq` output directly into the prompt before Claude starts, so it sees the issue immediately without an extra tool call.
|
|
146
|
+
|
|
147
|
+
**Hook vs skill:**
|
|
148
|
+
- **Stop hook** — automatic, runs after every session, best for unattended loops
|
|
149
|
+
- **Skill** — manual `/cq-fix`, gives you explicit control over when to check
|
|
150
|
+
|
|
113
151
|
## Table output
|
|
114
152
|
|
|
115
153
|
```bash
|
|
@@ -121,9 +159,9 @@ cq config path/to/project/ # Show effective tool configuration
|
|
|
121
159
|
┃ Tool ┃ Time ┃ Metric ┃ Score ┃ Status ┃
|
|
122
160
|
┡━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━┩
|
|
123
161
|
│ compile │ 0.42s │ compile │ 1.000 │ OK │
|
|
124
|
-
│ bandit │ 0.56s │ security │ 1.000 │ OK │
|
|
125
162
|
│ ruff │ 0.17s │ lint │ 1.000 │ OK │
|
|
126
163
|
│ ty │ 0.33s │ type_check │ 1.000 │ OK │
|
|
164
|
+
│ bandit │ 0.56s │ security │ 1.000 │ OK │
|
|
127
165
|
│ pytest │ 0.91s │ tests │ 1.000 │ OK │
|
|
128
166
|
│ coverage │ 1.26s │ coverage │ 0.910 │ OK │
|
|
129
167
|
│ radon cc │ 0.32s │ simplicity │ 0.982 │ OK │
|
|
@@ -167,7 +205,7 @@ cq config path/to/project/ # Show effective tool configuration
|
|
|
167
205
|
|
|
168
206
|
## Raw output
|
|
169
207
|
```bash
|
|
170
|
-
> cq check -o raw
|
|
208
|
+
> cq check . -o raw
|
|
171
209
|
```
|
|
172
210
|
|
|
173
211
|
```json
|
|
@@ -193,66 +231,63 @@ Add a `[tool.cq]` section to your project's `pyproject.toml`:
|
|
|
193
231
|
# Skip tools that are slow or not relevant to your project
|
|
194
232
|
disable = ["coverage", "interrogate"]
|
|
195
233
|
|
|
234
|
+
# Lines of source context shown around each defect in LLM output (default: 15)
|
|
235
|
+
context_lines = 15
|
|
236
|
+
|
|
196
237
|
# Override warning/error thresholds per tool
|
|
197
238
|
[tool.cq.thresholds.coverage]
|
|
198
239
|
warning = 0.9
|
|
199
240
|
error = 0.7
|
|
200
241
|
```
|
|
201
242
|
|
|
202
|
-
Tool IDs match the keys in `config/
|
|
243
|
+
Tool IDs match the keys in `config/config.yaml`: `compile`, `ruff`, `ty`, `bandit`, `pytest`, `coverage`, `radon-cc`, `radon-mi`, `radon-hal`, `vulture`, `interrogate`.
|
|
203
244
|
|
|
204
245
|
|
|
205
246
|
### Default config
|
|
206
247
|
|
|
207
248
|
```yaml
|
|
208
|
-
|
|
249
|
+
python:
|
|
209
250
|
|
|
210
|
-
|
|
211
|
-
name: "compile"
|
|
251
|
+
compile:
|
|
212
252
|
command: "{python} -m compileall -r 10 -j 8 {context_path} -x .*venv"
|
|
213
253
|
parser: "CompileParser"
|
|
214
254
|
order: 1
|
|
215
255
|
warning_threshold: 0.9999
|
|
216
256
|
error_threshold: 0.9999
|
|
217
257
|
|
|
218
|
-
bandit:
|
|
219
|
-
name: "bandit"
|
|
220
|
-
command: "{python} -m bandit -r {context_path} -f json -q -s B101 --severity-level medium --exclude {input_path_posix}/.venv,{input_path_posix}/tests"
|
|
221
|
-
parser: "BanditParser"
|
|
222
|
-
order: 2
|
|
223
|
-
warning_threshold: 0.9999
|
|
224
|
-
error_threshold: 0.8
|
|
225
|
-
|
|
226
258
|
ruff:
|
|
227
|
-
name: "ruff"
|
|
228
259
|
command: "{python} -m ruff check --output-format concise --no-cache {context_path}"
|
|
229
260
|
parser: "RuffParser"
|
|
230
|
-
order:
|
|
261
|
+
order: 2
|
|
231
262
|
warning_threshold: 0.9999
|
|
232
263
|
error_threshold: 0.9
|
|
233
264
|
|
|
234
265
|
ty:
|
|
235
|
-
name: "ty"
|
|
236
266
|
command: "{python} -m ty check --output-format concise --color never {context_path}"
|
|
237
267
|
parser: "TyParser"
|
|
238
|
-
order:
|
|
268
|
+
order: 3
|
|
239
269
|
warning_threshold: 0.9999
|
|
240
270
|
error_threshold: 0.8
|
|
241
271
|
run_in_target_env: true
|
|
242
272
|
extra_deps:
|
|
243
273
|
- ty
|
|
244
274
|
|
|
275
|
+
bandit:
|
|
276
|
+
command: "{python} -m bandit -r {context_path} -f json -q -s B101 --severity-level medium --exclude {input_path_posix}/.venv,{input_path_posix}/tests"
|
|
277
|
+
parser: "BanditParser"
|
|
278
|
+
order: 4
|
|
279
|
+
warning_threshold: 0.9999
|
|
280
|
+
error_threshold: 0.8
|
|
281
|
+
|
|
245
282
|
pytest:
|
|
246
|
-
name: "pytest"
|
|
247
283
|
command: "{python} -m pytest -v {context_path}"
|
|
248
284
|
parser: "PytestParser"
|
|
249
285
|
order: 5
|
|
250
|
-
warning_threshold: 0
|
|
251
|
-
error_threshold: 0
|
|
286
|
+
warning_threshold: 1.0
|
|
287
|
+
error_threshold: 1.0
|
|
252
288
|
run_in_target_env: true
|
|
253
289
|
|
|
254
290
|
coverage:
|
|
255
|
-
name: "coverage"
|
|
256
291
|
command: "{python} -m coverage run --omit=*/tests/*,*/test_*.py -m pytest {context_path} && {python} -m coverage report --omit=*/tests/*,*/test_*.py"
|
|
257
292
|
parser: "CoverageParser"
|
|
258
293
|
order: 6
|
|
@@ -263,24 +298,21 @@ tools:
|
|
|
263
298
|
- coverage
|
|
264
299
|
- pytest
|
|
265
300
|
|
|
266
|
-
|
|
267
|
-
name: "radon cc"
|
|
301
|
+
radon-cc:
|
|
268
302
|
command: "{python} -m radon cc --json {context_path}"
|
|
269
303
|
parser: "ComplexityParser"
|
|
270
304
|
order: 7
|
|
271
305
|
warning_threshold: 0.6
|
|
272
306
|
error_threshold: 0.4
|
|
273
307
|
|
|
274
|
-
|
|
275
|
-
name: "radon mi"
|
|
308
|
+
radon-mi:
|
|
276
309
|
command: "{python} -m radon mi -s --json {context_path}"
|
|
277
310
|
parser: "MaintainabilityParser"
|
|
278
311
|
order: 8
|
|
279
312
|
warning_threshold: 0.6
|
|
280
313
|
error_threshold: 0.4
|
|
281
314
|
|
|
282
|
-
|
|
283
|
-
name: "radon hal"
|
|
315
|
+
radon-hal:
|
|
284
316
|
command: "{python} -m radon hal -f --json {context_path}"
|
|
285
317
|
parser: "HalsteadParser"
|
|
286
318
|
order: 9
|
|
@@ -288,7 +320,6 @@ tools:
|
|
|
288
320
|
error_threshold: 0.3
|
|
289
321
|
|
|
290
322
|
vulture:
|
|
291
|
-
name: "vulture"
|
|
292
323
|
command: "{python} -m vulture {context_path} --min-confidence 80 --exclude .venv,dist,.*_cache,docs,.git"
|
|
293
324
|
parser: "VultureParser"
|
|
294
325
|
order: 10
|
|
@@ -296,7 +327,6 @@ tools:
|
|
|
296
327
|
error_threshold: 0.8
|
|
297
328
|
|
|
298
329
|
interrogate:
|
|
299
|
-
name: "interrogate"
|
|
300
330
|
command: "{python} -m interrogate {context_path} -v --fail-under 0"
|
|
301
331
|
parser: "InterrogateParser"
|
|
302
332
|
order: 11
|
|
@@ -2,20 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
Feed the results from 11+ code quality tools to an LLM. Minimal tokens.
|
|
4
4
|
|
|
5
|
+
Why? It removes the mental burden of understanding all these tools and parsing their results.
|
|
6
|
+
|
|
5
7
|
The primary workflow is:
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
10
|
# get the single most critical defect as markdown
|
|
9
11
|
cq check . -o llm
|
|
10
12
|
```
|
|
11
|
-
Selects the single most critical defect using this priority order:
|
|
12
|
-
|
|
13
|
-
1. **Severity** — tools with score below `error_threshold` come before those only below `warning_threshold`
|
|
14
|
-
2. **Order** — among tools at the same severity, lower-order tools win (compile before lint before style)
|
|
15
|
-
3. **Score** — among ties, the lower score wins
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
```md
|
|
14
|
+
```python
|
|
19
15
|
`data/problems/travelling_salesman/ts_bad.py:21` — **F841**: Local variable `unused_variable` is assigned to but never used
|
|
20
16
|
|
|
21
17
|
18: min_dist = float("inf")
|
|
@@ -34,9 +30,10 @@ Feed to an LLM with edit tools and repeat until there are no issues, e.g.
|
|
|
34
30
|
```python
|
|
35
31
|
cq check . -o llm | claude -p "fix this"
|
|
36
32
|
# or
|
|
37
|
-
cq check . -o llm | ollama gpt-oss:20b "Explain how to fix this"
|
|
33
|
+
cq check . -o llm | ollama run gpt-oss:20b "Explain how to fix this"
|
|
38
34
|
```
|
|
39
35
|
|
|
36
|
+
|
|
40
37
|
## Install
|
|
41
38
|
|
|
42
39
|
```bash
|
|
@@ -44,21 +41,22 @@ cq check . -o llm | ollama gpt-oss:20b "Explain how to fix this"
|
|
|
44
41
|
uv tool install python-code-quality
|
|
45
42
|
|
|
46
43
|
# or, clone it then install
|
|
47
|
-
git
|
|
44
|
+
git clone https://github.com/rhiza-fr/py-cq.git
|
|
48
45
|
cd py-cq
|
|
49
46
|
uv tool install .
|
|
50
47
|
```
|
|
51
48
|
|
|
52
49
|
## Tools
|
|
53
50
|
|
|
54
|
-
These tools are run in **parallel** except
|
|
51
|
+
These tools are run in **parallel** except:
|
|
52
|
+
When running '-o llm', we run sequentially and exit early at the first error.
|
|
55
53
|
|
|
56
54
|
| Order | Tool | Measures |
|
|
57
55
|
|----------|------|----------|
|
|
58
56
|
| 1 | compileall | Syntax errors |
|
|
59
|
-
| 2 |
|
|
60
|
-
| 3 |
|
|
61
|
-
| 4 |
|
|
57
|
+
| 2 | ruff | Lint / style |
|
|
58
|
+
| 3 | ty | Type errors |
|
|
59
|
+
| 4 | bandit | Security vulnerabilities |
|
|
62
60
|
| 5 | pytest | Test pass rate |
|
|
63
61
|
| 6 | coverage | Test coverage |
|
|
64
62
|
| 7 | radon cc | Cyclomatic complexity |
|
|
@@ -67,14 +65,14 @@ These tools are run in **parallel** except when looking for the first error in -
|
|
|
67
65
|
| 10 | vulture | Dead code |
|
|
68
66
|
| 11 | interrogate | Docstring coverage |
|
|
69
67
|
|
|
70
|
-
Diskcache is used to cache tool output for lightning fast re-runs. Sane defaults: <100 Mb, <5 days, No pickle
|
|
68
|
+
Diskcache is used to cache tool output for lightning fast re-runs. Sane defaults: <100 Mb, <5 days, No pickle risk.
|
|
71
69
|
|
|
72
70
|
|
|
73
71
|
## Usage
|
|
74
72
|
|
|
75
73
|
```bash
|
|
76
74
|
cq check . # Table overview of scores for humans
|
|
77
|
-
cq check -o llm
|
|
75
|
+
cq check . -o llm # Top defect as markdown for LLMs
|
|
78
76
|
cq check . -o score # Numeric score only for CI
|
|
79
77
|
cq check . -o json # Detailed parsed JSON output for jq
|
|
80
78
|
cq check . -o raw # Raw tool output for debug
|
|
@@ -84,6 +82,46 @@ cq check . --clear-cache # Clear cached results before running (rarely needed)
|
|
|
84
82
|
cq config path/to/project/ # Show effective tool configuration
|
|
85
83
|
```
|
|
86
84
|
|
|
85
|
+
**Exit codes:** `cq check` exits with code `1` if any tool metric falls below its `error_threshold`, making it suitable as a CI gate:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
cq check . && deploy # block deploy on errors
|
|
89
|
+
cq check . -o score # print score, exit 1 on errors
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Claude Code Integration
|
|
93
|
+
|
|
94
|
+
Add a stop hook to your project's `.claude/settings.json` so Claude automatically checks quality after each session and loops until clean:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"hooks": {
|
|
99
|
+
"Stop": [{
|
|
100
|
+
"matcher": "",
|
|
101
|
+
"hooks": [{"type": "command", "command": "cq check . -o score && echo 'CQ: all clear' || cq check . -o llm"}]
|
|
102
|
+
}]
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
When the score passes, Claude sees `CQ: all clear` (~5 tokens). When it fails, Claude receives the targeted fix prompt and continues working. This automates the `cq check . -o llm | claude -p "fix this"` loop.
|
|
108
|
+
|
|
109
|
+
> **Note:** Use project-level `.claude/settings.json`, not global settings — this hook only makes sense in Python projects.
|
|
110
|
+
|
|
111
|
+
### As a slash command (skill)
|
|
112
|
+
|
|
113
|
+
For manual invocation, create `.claude/commands/cq-fix.md`:
|
|
114
|
+
|
|
115
|
+
```markdown
|
|
116
|
+
$(cq check . -o llm)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Then invoke it with `/cq-fix` in Claude Code. The `$(...)` embeds the live `cq` output directly into the prompt before Claude starts, so it sees the issue immediately without an extra tool call.
|
|
120
|
+
|
|
121
|
+
**Hook vs skill:**
|
|
122
|
+
- **Stop hook** — automatic, runs after every session, best for unattended loops
|
|
123
|
+
- **Skill** — manual `/cq-fix`, gives you explicit control over when to check
|
|
124
|
+
|
|
87
125
|
## Table output
|
|
88
126
|
|
|
89
127
|
```bash
|
|
@@ -95,9 +133,9 @@ cq config path/to/project/ # Show effective tool configuration
|
|
|
95
133
|
┃ Tool ┃ Time ┃ Metric ┃ Score ┃ Status ┃
|
|
96
134
|
┡━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━┩
|
|
97
135
|
│ compile │ 0.42s │ compile │ 1.000 │ OK │
|
|
98
|
-
│ bandit │ 0.56s │ security │ 1.000 │ OK │
|
|
99
136
|
│ ruff │ 0.17s │ lint │ 1.000 │ OK │
|
|
100
137
|
│ ty │ 0.33s │ type_check │ 1.000 │ OK │
|
|
138
|
+
│ bandit │ 0.56s │ security │ 1.000 │ OK │
|
|
101
139
|
│ pytest │ 0.91s │ tests │ 1.000 │ OK │
|
|
102
140
|
│ coverage │ 1.26s │ coverage │ 0.910 │ OK │
|
|
103
141
|
│ radon cc │ 0.32s │ simplicity │ 0.982 │ OK │
|
|
@@ -141,7 +179,7 @@ cq config path/to/project/ # Show effective tool configuration
|
|
|
141
179
|
|
|
142
180
|
## Raw output
|
|
143
181
|
```bash
|
|
144
|
-
> cq check -o raw
|
|
182
|
+
> cq check . -o raw
|
|
145
183
|
```
|
|
146
184
|
|
|
147
185
|
```json
|
|
@@ -167,66 +205,63 @@ Add a `[tool.cq]` section to your project's `pyproject.toml`:
|
|
|
167
205
|
# Skip tools that are slow or not relevant to your project
|
|
168
206
|
disable = ["coverage", "interrogate"]
|
|
169
207
|
|
|
208
|
+
# Lines of source context shown around each defect in LLM output (default: 15)
|
|
209
|
+
context_lines = 15
|
|
210
|
+
|
|
170
211
|
# Override warning/error thresholds per tool
|
|
171
212
|
[tool.cq.thresholds.coverage]
|
|
172
213
|
warning = 0.9
|
|
173
214
|
error = 0.7
|
|
174
215
|
```
|
|
175
216
|
|
|
176
|
-
Tool IDs match the keys in `config/
|
|
217
|
+
Tool IDs match the keys in `config/config.yaml`: `compile`, `ruff`, `ty`, `bandit`, `pytest`, `coverage`, `radon-cc`, `radon-mi`, `radon-hal`, `vulture`, `interrogate`.
|
|
177
218
|
|
|
178
219
|
|
|
179
220
|
### Default config
|
|
180
221
|
|
|
181
222
|
```yaml
|
|
182
|
-
|
|
223
|
+
python:
|
|
183
224
|
|
|
184
|
-
|
|
185
|
-
name: "compile"
|
|
225
|
+
compile:
|
|
186
226
|
command: "{python} -m compileall -r 10 -j 8 {context_path} -x .*venv"
|
|
187
227
|
parser: "CompileParser"
|
|
188
228
|
order: 1
|
|
189
229
|
warning_threshold: 0.9999
|
|
190
230
|
error_threshold: 0.9999
|
|
191
231
|
|
|
192
|
-
bandit:
|
|
193
|
-
name: "bandit"
|
|
194
|
-
command: "{python} -m bandit -r {context_path} -f json -q -s B101 --severity-level medium --exclude {input_path_posix}/.venv,{input_path_posix}/tests"
|
|
195
|
-
parser: "BanditParser"
|
|
196
|
-
order: 2
|
|
197
|
-
warning_threshold: 0.9999
|
|
198
|
-
error_threshold: 0.8
|
|
199
|
-
|
|
200
232
|
ruff:
|
|
201
|
-
name: "ruff"
|
|
202
233
|
command: "{python} -m ruff check --output-format concise --no-cache {context_path}"
|
|
203
234
|
parser: "RuffParser"
|
|
204
|
-
order:
|
|
235
|
+
order: 2
|
|
205
236
|
warning_threshold: 0.9999
|
|
206
237
|
error_threshold: 0.9
|
|
207
238
|
|
|
208
239
|
ty:
|
|
209
|
-
name: "ty"
|
|
210
240
|
command: "{python} -m ty check --output-format concise --color never {context_path}"
|
|
211
241
|
parser: "TyParser"
|
|
212
|
-
order:
|
|
242
|
+
order: 3
|
|
213
243
|
warning_threshold: 0.9999
|
|
214
244
|
error_threshold: 0.8
|
|
215
245
|
run_in_target_env: true
|
|
216
246
|
extra_deps:
|
|
217
247
|
- ty
|
|
218
248
|
|
|
249
|
+
bandit:
|
|
250
|
+
command: "{python} -m bandit -r {context_path} -f json -q -s B101 --severity-level medium --exclude {input_path_posix}/.venv,{input_path_posix}/tests"
|
|
251
|
+
parser: "BanditParser"
|
|
252
|
+
order: 4
|
|
253
|
+
warning_threshold: 0.9999
|
|
254
|
+
error_threshold: 0.8
|
|
255
|
+
|
|
219
256
|
pytest:
|
|
220
|
-
name: "pytest"
|
|
221
257
|
command: "{python} -m pytest -v {context_path}"
|
|
222
258
|
parser: "PytestParser"
|
|
223
259
|
order: 5
|
|
224
|
-
warning_threshold: 0
|
|
225
|
-
error_threshold: 0
|
|
260
|
+
warning_threshold: 1.0
|
|
261
|
+
error_threshold: 1.0
|
|
226
262
|
run_in_target_env: true
|
|
227
263
|
|
|
228
264
|
coverage:
|
|
229
|
-
name: "coverage"
|
|
230
265
|
command: "{python} -m coverage run --omit=*/tests/*,*/test_*.py -m pytest {context_path} && {python} -m coverage report --omit=*/tests/*,*/test_*.py"
|
|
231
266
|
parser: "CoverageParser"
|
|
232
267
|
order: 6
|
|
@@ -237,24 +272,21 @@ tools:
|
|
|
237
272
|
- coverage
|
|
238
273
|
- pytest
|
|
239
274
|
|
|
240
|
-
|
|
241
|
-
name: "radon cc"
|
|
275
|
+
radon-cc:
|
|
242
276
|
command: "{python} -m radon cc --json {context_path}"
|
|
243
277
|
parser: "ComplexityParser"
|
|
244
278
|
order: 7
|
|
245
279
|
warning_threshold: 0.6
|
|
246
280
|
error_threshold: 0.4
|
|
247
281
|
|
|
248
|
-
|
|
249
|
-
name: "radon mi"
|
|
282
|
+
radon-mi:
|
|
250
283
|
command: "{python} -m radon mi -s --json {context_path}"
|
|
251
284
|
parser: "MaintainabilityParser"
|
|
252
285
|
order: 8
|
|
253
286
|
warning_threshold: 0.6
|
|
254
287
|
error_threshold: 0.4
|
|
255
288
|
|
|
256
|
-
|
|
257
|
-
name: "radon hal"
|
|
289
|
+
radon-hal:
|
|
258
290
|
command: "{python} -m radon hal -f --json {context_path}"
|
|
259
291
|
parser: "HalsteadParser"
|
|
260
292
|
order: 9
|
|
@@ -262,7 +294,6 @@ tools:
|
|
|
262
294
|
error_threshold: 0.3
|
|
263
295
|
|
|
264
296
|
vulture:
|
|
265
|
-
name: "vulture"
|
|
266
297
|
command: "{python} -m vulture {context_path} --min-confidence 80 --exclude .venv,dist,.*_cache,docs,.git"
|
|
267
298
|
parser: "VultureParser"
|
|
268
299
|
order: 10
|
|
@@ -270,7 +301,6 @@ tools:
|
|
|
270
301
|
error_threshold: 0.8
|
|
271
302
|
|
|
272
303
|
interrogate:
|
|
273
|
-
name: "interrogate"
|
|
274
304
|
command: "{python} -m interrogate {context_path} -v --fail-under 0"
|
|
275
305
|
parser: "InterrogateParser"
|
|
276
306
|
order: 11
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-code-quality"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.10"
|
|
4
4
|
description = "Python Code Quality Analysis Tool - feed the results from 11 CQ tools straight into an LLM. Minimal tokens."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -29,11 +29,11 @@ Homepage = "https://github.com/rhiza-fr/py-cq"
|
|
|
29
29
|
Repository = "https://github.com/rhiza-fr/py-cq"
|
|
30
30
|
|
|
31
31
|
[build-system]
|
|
32
|
-
requires = ["
|
|
33
|
-
build-backend = "
|
|
32
|
+
requires = ["uv_build"]
|
|
33
|
+
build-backend = "uv_build"
|
|
34
34
|
|
|
35
|
-
[tool.
|
|
36
|
-
|
|
35
|
+
[tool.uv.build-backend]
|
|
36
|
+
module-name = "py_cq"
|
|
37
37
|
|
|
38
38
|
[project.scripts]
|
|
39
39
|
cq = "py_cq.main:main"
|