python-code-quality 0.1.6__tar.gz → 0.1.8__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.
Files changed (59) hide show
  1. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/CLAUDE.md +5 -5
  2. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/PKG-INFO +189 -55
  3. python_code_quality-0.1.8/README.md +296 -0
  4. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/pyproject.toml +2 -2
  5. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/cli.py +16 -18
  6. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/config/__init__.py +1 -3
  7. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/config/tools.yaml +13 -13
  8. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/execution_engine.py +21 -9
  9. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/llm_formatter.py +1 -1
  10. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/localtypes.py +4 -4
  11. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/parsers/banditparser.py +2 -2
  12. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/parsers/common.py +12 -0
  13. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/parsers/compileparser.py +2 -8
  14. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/parsers/halsteadparser.py +1 -1
  15. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/parsers/pytestparser.py +8 -0
  16. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/parsers/ruffparser.py +2 -6
  17. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/parsers/typarser.py +2 -6
  18. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/parsers/vultureparser.py +2 -2
  19. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/tool_registry.py +1 -1
  20. python_code_quality-0.1.8/tests/test_cli.py +228 -0
  21. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/tests/test_common.py +18 -0
  22. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/tests/test_config.py +2 -2
  23. python_code_quality-0.1.8/tests/test_execution_engine.py +291 -0
  24. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/tests/test_llm_formatter.py +14 -14
  25. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/tests/test_localtypes.py +17 -2
  26. python_code_quality-0.1.8/tests/test_parser_halstead.py +116 -0
  27. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/tests/test_parser_pytest.py +14 -0
  28. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/uv.lock +1 -1
  29. python_code_quality-0.1.6/README.md +0 -162
  30. python_code_quality-0.1.6/src/py_cq/storage.py +0 -27
  31. python_code_quality-0.1.6/tests/test_execution_engine.py +0 -127
  32. python_code_quality-0.1.6/tests/test_storage.py +0 -41
  33. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/.github/workflows/python-publish.yml +0 -0
  34. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/.gitignore +0 -0
  35. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/.python-version +0 -0
  36. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/LICENSE +0 -0
  37. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/data/problems/travelling_salesman/ts_bad.py +0 -0
  38. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/data/problems/travelling_salesman/ts_good.py +0 -0
  39. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/__init__.py +0 -0
  40. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/context_hash.py +0 -0
  41. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/main.py +0 -0
  42. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/metric_aggregator.py +0 -0
  43. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/parsers/__init__.py +0 -0
  44. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/parsers/complexityparser.py +0 -0
  45. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/parsers/coverageparser.py +0 -0
  46. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/parsers/interrogateparser.py +0 -0
  47. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/parsers/maintainabilityparser.py +0 -0
  48. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/src/py_cq/py.typed +0 -0
  49. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/tests/conftest.py +0 -0
  50. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/tests/test_context_hash.py +0 -0
  51. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/tests/test_parser_bandit.py +0 -0
  52. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/tests/test_parser_compile.py +0 -0
  53. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/tests/test_parser_complexity.py +0 -0
  54. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/tests/test_parser_coverage.py +0 -0
  55. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/tests/test_parser_interrogate.py +0 -0
  56. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/tests/test_parser_maintainability.py +0 -0
  57. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/tests/test_parser_ruff.py +0 -0
  58. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/tests/test_parser_ty.py +0 -0
  59. {python_code_quality-0.1.6 → python_code_quality-0.1.8}/tests/test_parser_vulture.py +0 -0
@@ -11,7 +11,7 @@ cq check -o llm # returns the single most critical defect as markdown
11
11
  ```
12
12
 
13
13
  The LLM fixes it, the user re-runs, and repeats until all tools pass. CQ runs
14
- 11 static analysis tools in priority order (compile → security → lint → types →
14
+ 11 static analysis tools in execution order (compile → security → lint → types →
15
15
  tests → coverage → complexity → dead code → style) and aggregates results into
16
16
  a single score.
17
17
 
@@ -40,15 +40,15 @@ uv run ruff check src/
40
40
 
41
41
  - **`cli.py`**: Typer app with a single `check` command. Output mode selected via `--output`/`-o` enum (`table`, `score`, `json`, `llm`). Runs tools in parallel by default. Reads `[tool.cq]` from the target project's `pyproject.toml` and applies overrides before running.
42
42
  - **`tool_registry.py`**: Loads `src/cq/config/tools.yaml` at import time via `importlib.resources`, dynamically imports parser classes, builds a `dict[str, ToolConfig]` registry.
43
- - **`config/tools.yaml`** (at `src/cq/config/tools.yaml`): Declares each analysis tool: shell command template (with `{context_path}` placeholder), parser class name, priority, warning/error thresholds. Tools are listed and executed in priority order.
44
- - **`execution_engine.py`**: Runs shell commands via `subprocess.run`, caches results with `diskcache` using a content-based hash. Parallel execution via `ThreadPoolExecutor`; results are sorted by priority before returning.
43
+ - **`config/tools.yaml`** (at `src/cq/config/tools.yaml`): Declares each analysis tool: shell command template (with `{context_path}` placeholder), parser class name, order, warning/error thresholds. Tools are listed and executed in order.
44
+ - **`execution_engine.py`**: Runs shell commands via `subprocess.run`, caches results with `diskcache` using a content-based hash. Parallel execution via `ThreadPoolExecutor`; results are sorted by order before returning.
45
45
  - **`parsers/`**: Each parser subclasses `AbstractParser` (from `localtypes.py`), implementing `parse(RawResult) -> ToolResult` and optionally `format_llm_message(ToolResult) -> str`. Parser module names must match the lowercase parser class name (e.g., `PytestParser` → `pytestparser.py`).
46
46
  - **`localtypes.py`**: Core dataclasses — `ToolConfig`, `RawResult`, `ToolResult`, `CombinedToolResults`, and `AbstractParser` ABC.
47
47
  - **`metric_aggregator.py`**: Wraps results into `CombinedToolResults`, which computes an overall score as the average of per-tool mean metrics.
48
- - **`llm_formatter.py`**: Selects the worst-scoring tool by severity tier then priority, formats its top defect as markdown for LLM consumption.
48
+ - **`llm_formatter.py`**: Selects the worst-scoring tool by severity tier then order, formats its top defect as markdown for LLM consumption.
49
49
 
50
50
  ## Adding a New Analysis Tool
51
51
 
52
- 1. Add tool entry in `config/tools.yaml` with command template, parser name, priority, and thresholds.
52
+ 1. Add tool entry in `config/tools.yaml` with command template, parser name, order, and thresholds.
53
53
  2. Create `src/cq/parsers/<parsername>.py` with a class matching the `parser` field in YAML.
54
54
  3. The parser must subclass `AbstractParser` and implement `parse(RawResult) -> ToolResult`.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-code-quality
3
- Version: 0.1.6
4
- Summary: Python Code Quality Analysis Tool - feed the results from 11 CQ CQ straight into an LLM.
3
+ Version: 0.1.8
4
+ Summary: Python Code Quality Analysis Tool - feed the results from 11 CQ tools straight into an LLM. Minimal tokens.
5
5
  Project-URL: Homepage, https://github.com/rhiza-fr/py-cq
6
6
  Project-URL: Repository, https://github.com/rhiza-fr/py-cq
7
7
  Author-email: Chris Kilner <chris@rhiza.fr>
@@ -26,20 +26,50 @@ Description-Content-Type: text/markdown
26
26
 
27
27
  # CQ - Python Code Quality Analysis Tool
28
28
 
29
- Python Code Quality Analysis Tool - feed the results from 11 CQ tools straight into an LLM. The primary workflow is:
29
+ Feed the results from 11+ code quality tools to an LLM. Minimal tokens.
30
+
31
+ The primary workflow is:
30
32
 
31
33
  ```bash
32
- cq check -o llm # get the single most critical defect as markdown
34
+ # get the single most critical defect as markdown
35
+ cq check . -o llm
33
36
  ```
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
34
42
 
35
- Feed that output to an LLM, apply the fix, repeat until the score is clean.
43
+ The code context is expanded if available.
44
+ ```md
45
+ `data/problems/travelling_salesman/ts_bad.py:21` — **F841**: Local variable `unused_variable` is assigned to but never used
46
+
47
+ 18: min_dist = float("inf")
48
+ 19: nearest_city = None
49
+ 20: for city in cities:
50
+ 21: unused_variable = 67
51
+ 22: dist = calc_dist(current_city, city)
52
+ 23: if dist < min_dist:
53
+ 24: min_dist = dist
54
+ 25: nearest_city = city
55
+
56
+ Please fix only this issue. After fixing, run `cq check . -o llm` to verify.
57
+ ```
58
+ Feed to an LLM with edit tools and repeat until there are no issues, e.g.
59
+
60
+ ```python
61
+ cq check . -o llm | claude -p "fix this"
62
+ # or
63
+ cq check . -o llm | ollama gpt-oss:20b "Explain how to fix this"
64
+ ```
36
65
 
37
66
  ## Install
38
67
 
39
68
  ```bash
69
+ # install the `cq` command line tool from PyPi
40
70
  uv tool install python-code-quality
41
71
 
42
- # or
72
+ # or, clone it then install
43
73
  git pull https://github.com/rhiza-fr/py-cq.git
44
74
  cd py-cq
45
75
  uv tool install .
@@ -47,9 +77,9 @@ uv tool install .
47
77
 
48
78
  ## Tools
49
79
 
50
- CQ runs these tools in *parallel*:
80
+ These tools are run in **parallel** except when looking for the first error in -o llm mode:
51
81
 
52
- | Priority | Tool | Measures |
82
+ | Order | Tool | Measures |
53
83
  |----------|------|----------|
54
84
  | 1 | compileall | Syntax errors |
55
85
  | 2 | bandit | Security vulnerabilities |
@@ -63,40 +93,24 @@ CQ runs these tools in *parallel*:
63
93
  | 10 | vulture | Dead code |
64
94
  | 11 | interrogate | Docstring coverage |
65
95
 
66
- ## Usage
67
-
68
- ```bash
69
- # LLM workflow: get the top defect as markdown (primary use case)
70
- cq check -o llm
71
-
72
- # Rich table with all metrics (default, also saves .cq.json)
73
- cq check
96
+ Diskcache is used to cache tool output for lightning fast re-runs. Sane defaults: <100 Mb, <5 days, No pickle
74
97
 
75
- # Numeric score only — useful in CI or scripts
76
- cq check -o score
77
98
 
78
- # Full JSON output
79
- cq check -o json
80
-
81
- # Explicit path (defaults to current directory)
82
- cq check path/to/project/
83
- cq check path/to/file.py
84
-
85
- # Run sequentially (1 worker) instead of in parallel
86
- cq check --workers 1
87
-
88
- # Clear cached results before running
89
- cq check --clear-cache
90
-
91
- # Save table output to a custom file
92
- cq check --out-file custom_results.json
99
+ ## Usage
93
100
 
94
- # Show effective tool configuration (thresholds, enabled/disabled status)
95
- cq config
96
- cq config path/to/project/
101
+ ```bash
102
+ cq check . # Table overview of scores for humans
103
+ cq check -o llm # Top defect as markdown for LLMs
104
+ cq check . -o score # Numeric score only for CI
105
+ cq check . -o json # Detailed parsed JSON output for jq
106
+ cq check . -o raw # Raw tool output for debug
107
+ cq check path/to/file.py # Just one file (skips pytest and coverage)
108
+ cq check . --workers 1 # Run sequentially if you like things slow
109
+ cq check . --clear-cache # Clear cached results before running (rarely needed)
110
+ cq config path/to/project/ # Show effective tool configuration
97
111
  ```
98
112
 
99
- ## Output
113
+ ## Table output
100
114
 
101
115
  ```bash
102
116
  > cq check .
@@ -123,6 +137,8 @@ cq config path/to/project/
123
137
  │ │ │ Score │ 0.965 │ │
124
138
  └──────────────────┴──────────┴───────────────────────────┴─────────┴──────────┘
125
139
  ```
140
+
141
+ ## Single score output
126
142
  ```bash
127
143
  > cq check . -o score
128
144
  ```
@@ -130,23 +146,42 @@ cq config path/to/project/
130
146
  0.9662730667181059 # this is designed to approach but not reach 1.0
131
147
  ```
132
148
 
149
+ ## Json output
133
150
  ```bash
134
- > cq check . -o llm
151
+ > cq check . -o json
135
152
  ```
136
153
 
137
- ```md
138
- `data/problems/travelling_salesman/ts_bad.py:21` — **F841**: Local variable `unused_variable` is assigned to but never used
154
+ ```json
155
+ [
156
+ {
157
+ "tool_name": "compile",
158
+ "metrics": {
159
+ "compile": 1.0
160
+ },
161
+ "details": {},
162
+ "duration_s": 0.05611889995634556
163
+ }
164
+ ...
165
+ ]
166
+ ```
139
167
 
140
- 18: min_dist = float("inf")
141
- 19: nearest_city = None
142
- 20: for city in cities:
143
- 21: unused_variable = 67
144
- 22: dist = calc_dist(current_city, city)
145
- 23: if dist < min_dist:
146
- 24: min_dist = dist
147
- 25: nearest_city = city
168
+ ## Raw output
169
+ ```bash
170
+ > cq check -o raw
171
+ ```
148
172
 
149
- Please fix only this issue. After fixing, run `cq check . -o llm` to verify.
173
+ ```json
174
+ [
175
+ {
176
+ "tool_name": "compile",
177
+ "command": "D:\\ai\\py-cq\\.venv\\Scripts\\python.exe -m compileall -r 10 -j 8 . -x .*venv",
178
+ "stdout": "",
179
+ "stderr": "",
180
+ "return_code": 0,
181
+ "timestamp": "2026-02-20 10:01:22"
182
+ }
183
+ ...
184
+ ]
150
185
  ```
151
186
 
152
187
  ## Configuration
@@ -166,14 +201,111 @@ error = 0.7
166
201
 
167
202
  Tool IDs match the keys in `config/tools.yaml`: `compilation`, `bandit`, `ruff`, `ty`, `pytest`, `coverage`, `complexity`, `maintainability`, `halstead`, `vulture`, `interrogate`.
168
203
 
169
- ## LLM workflow
170
204
 
171
- `-o llm` selects the single worst-scoring tool and formats its top defect as
172
- concise markdown. The LLM fixes it, you re-run `cq check -o llm`, and repeat
173
- until all tools are green. Priority order ensures the most critical category
174
- (security, type errors, failing tests) is fixed before cosmetic ones.
205
+ ### Default config
206
+
207
+ ```yaml
208
+ tools:
209
+
210
+ compilation:
211
+ name: "compile"
212
+ command: "{python} -m compileall -r 10 -j 8 {context_path} -x .*venv"
213
+ parser: "CompileParser"
214
+ order: 1
215
+ warning_threshold: 0.9999
216
+ error_threshold: 0.9999
217
+
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
+ ruff:
227
+ name: "ruff"
228
+ command: "{python} -m ruff check --output-format concise --no-cache {context_path}"
229
+ parser: "RuffParser"
230
+ order: 3
231
+ warning_threshold: 0.9999
232
+ error_threshold: 0.9
233
+
234
+ ty:
235
+ name: "ty"
236
+ command: "{python} -m ty check --output-format concise --color never {context_path}"
237
+ parser: "TyParser"
238
+ order: 4
239
+ warning_threshold: 0.9999
240
+ error_threshold: 0.8
241
+ run_in_target_env: true
242
+ extra_deps:
243
+ - ty
244
+
245
+ pytest:
246
+ name: "pytest"
247
+ command: "{python} -m pytest -v {context_path}"
248
+ parser: "PytestParser"
249
+ order: 5
250
+ warning_threshold: 0.7
251
+ error_threshold: 0.5
252
+ run_in_target_env: true
253
+
254
+ coverage:
255
+ name: "coverage"
256
+ command: "{python} -m coverage run --omit=*/tests/*,*/test_*.py -m pytest {context_path} && {python} -m coverage report --omit=*/tests/*,*/test_*.py"
257
+ parser: "CoverageParser"
258
+ order: 6
259
+ warning_threshold: 0.9
260
+ error_threshold: 0.5
261
+ run_in_target_env: true
262
+ extra_deps:
263
+ - coverage
264
+ - pytest
265
+
266
+ complexity:
267
+ name: "radon cc"
268
+ command: "{python} -m radon cc --json {context_path}"
269
+ parser: "ComplexityParser"
270
+ order: 7
271
+ warning_threshold: 0.6
272
+ error_threshold: 0.4
273
+
274
+ maintainability:
275
+ name: "radon mi"
276
+ command: "{python} -m radon mi -s --json {context_path}"
277
+ parser: "MaintainabilityParser"
278
+ order: 8
279
+ warning_threshold: 0.6
280
+ error_threshold: 0.4
281
+
282
+ halstead:
283
+ name: "radon hal"
284
+ command: "{python} -m radon hal -f --json {context_path}"
285
+ parser: "HalsteadParser"
286
+ order: 9
287
+ warning_threshold: 0.5
288
+ error_threshold: 0.3
289
+
290
+ vulture:
291
+ name: "vulture"
292
+ command: "{python} -m vulture {context_path} --min-confidence 80 --exclude .venv,dist,.*_cache,docs,.git"
293
+ parser: "VultureParser"
294
+ order: 10
295
+ warning_threshold: 0.9999
296
+ error_threshold: 0.8
297
+
298
+ interrogate:
299
+ name: "interrogate"
300
+ command: "{python} -m interrogate {context_path} -v --fail-under 0"
301
+ parser: "InterrogateParser"
302
+ order: 11
303
+ warning_threshold: 0.8
304
+ error_threshold: 0.3
175
305
 
176
- ## Tools
306
+ ```
307
+
308
+ ## Respect
177
309
 
178
310
  Many thanks to all the wonderful maintainers of :
179
311
 
@@ -186,3 +318,5 @@ Many thanks to all the wonderful maintainers of :
186
318
  - [radon](https://github.com/rubik/radon)
187
319
  - [vulture](https://github.com/jendrikseipp/vulture)
188
320
  - [interrogate](https://github.com/econchick/interrogate)
321
+ - [diskcache](https://github.com/grantjenks/python-diskcache)
322
+ - [typer](https://github.com/fastapi/typer)
@@ -0,0 +1,296 @@
1
+ # CQ - Python Code Quality Analysis Tool
2
+
3
+ Feed the results from 11+ code quality tools to an LLM. Minimal tokens.
4
+
5
+ The primary workflow is:
6
+
7
+ ```bash
8
+ # get the single most critical defect as markdown
9
+ cq check . -o llm
10
+ ```
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
+
17
+ The code context is expanded if available.
18
+ ```md
19
+ `data/problems/travelling_salesman/ts_bad.py:21` — **F841**: Local variable `unused_variable` is assigned to but never used
20
+
21
+ 18: min_dist = float("inf")
22
+ 19: nearest_city = None
23
+ 20: for city in cities:
24
+ 21: unused_variable = 67
25
+ 22: dist = calc_dist(current_city, city)
26
+ 23: if dist < min_dist:
27
+ 24: min_dist = dist
28
+ 25: nearest_city = city
29
+
30
+ Please fix only this issue. After fixing, run `cq check . -o llm` to verify.
31
+ ```
32
+ Feed to an LLM with edit tools and repeat until there are no issues, e.g.
33
+
34
+ ```python
35
+ cq check . -o llm | claude -p "fix this"
36
+ # or
37
+ cq check . -o llm | ollama gpt-oss:20b "Explain how to fix this"
38
+ ```
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ # install the `cq` command line tool from PyPi
44
+ uv tool install python-code-quality
45
+
46
+ # or, clone it then install
47
+ git pull https://github.com/rhiza-fr/py-cq.git
48
+ cd py-cq
49
+ uv tool install .
50
+ ```
51
+
52
+ ## Tools
53
+
54
+ These tools are run in **parallel** except when looking for the first error in -o llm mode:
55
+
56
+ | Order | Tool | Measures |
57
+ |----------|------|----------|
58
+ | 1 | compileall | Syntax errors |
59
+ | 2 | bandit | Security vulnerabilities |
60
+ | 3 | ruff | Lint / style |
61
+ | 4 | ty | Type errors |
62
+ | 5 | pytest | Test pass rate |
63
+ | 6 | coverage | Test coverage |
64
+ | 7 | radon cc | Cyclomatic complexity |
65
+ | 8 | radon mi | Maintainability index |
66
+ | 9 | radon hal | Halstead volume / bug estimate |
67
+ | 10 | vulture | Dead code |
68
+ | 11 | interrogate | Docstring coverage |
69
+
70
+ Diskcache is used to cache tool output for lightning fast re-runs. Sane defaults: <100 Mb, <5 days, No pickle
71
+
72
+
73
+ ## Usage
74
+
75
+ ```bash
76
+ cq check . # Table overview of scores for humans
77
+ cq check -o llm # Top defect as markdown for LLMs
78
+ cq check . -o score # Numeric score only for CI
79
+ cq check . -o json # Detailed parsed JSON output for jq
80
+ cq check . -o raw # Raw tool output for debug
81
+ cq check path/to/file.py # Just one file (skips pytest and coverage)
82
+ cq check . --workers 1 # Run sequentially if you like things slow
83
+ cq check . --clear-cache # Clear cached results before running (rarely needed)
84
+ cq config path/to/project/ # Show effective tool configuration
85
+ ```
86
+
87
+ ## Table output
88
+
89
+ ```bash
90
+ > cq check .
91
+ ```
92
+
93
+ ```python
94
+ ┏━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━┓
95
+ ┃ Tool ┃ Time ┃ Metric ┃ Score ┃ Status ┃
96
+ ┡━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━┩
97
+ │ compile │ 0.42s │ compile │ 1.000 │ OK │
98
+ │ bandit │ 0.56s │ security │ 1.000 │ OK │
99
+ │ ruff │ 0.17s │ lint │ 1.000 │ OK │
100
+ │ ty │ 0.33s │ type_check │ 1.000 │ OK │
101
+ │ pytest │ 0.91s │ tests │ 1.000 │ OK │
102
+ │ coverage │ 1.26s │ coverage │ 0.910 │ OK │
103
+ │ radon cc │ 0.32s │ simplicity │ 0.982 │ OK │
104
+ │ radon mi │ 0.38s │ maintainability │ 0.869 │ OK │
105
+ │ radon hal │ 0.30s │ file_bug_free │ 0.928 │ OK │
106
+ │ radon hal │ │ file_smallness │ 0.851 │ OK │
107
+ │ radon hal │ │ functions_bug_free │ 0.913 │ OK │
108
+ │ radon hal │ │ functions_smallness │ 0.724 │ OK │
109
+ │ vulture │ 0.32s │ dead_code │ 1.000 │ OK │
110
+ │ interrogate │ 0.36s │ doc_coverage │ 1.000 │ OK │
111
+ │ │ │ Score │ 0.965 │ │
112
+ └──────────────────┴──────────┴───────────────────────────┴─────────┴──────────┘
113
+ ```
114
+
115
+ ## Single score output
116
+ ```bash
117
+ > cq check . -o score
118
+ ```
119
+ ```python
120
+ 0.9662730667181059 # this is designed to approach but not reach 1.0
121
+ ```
122
+
123
+ ## Json output
124
+ ```bash
125
+ > cq check . -o json
126
+ ```
127
+
128
+ ```json
129
+ [
130
+ {
131
+ "tool_name": "compile",
132
+ "metrics": {
133
+ "compile": 1.0
134
+ },
135
+ "details": {},
136
+ "duration_s": 0.05611889995634556
137
+ }
138
+ ...
139
+ ]
140
+ ```
141
+
142
+ ## Raw output
143
+ ```bash
144
+ > cq check -o raw
145
+ ```
146
+
147
+ ```json
148
+ [
149
+ {
150
+ "tool_name": "compile",
151
+ "command": "D:\\ai\\py-cq\\.venv\\Scripts\\python.exe -m compileall -r 10 -j 8 . -x .*venv",
152
+ "stdout": "",
153
+ "stderr": "",
154
+ "return_code": 0,
155
+ "timestamp": "2026-02-20 10:01:22"
156
+ }
157
+ ...
158
+ ]
159
+ ```
160
+
161
+ ## Configuration
162
+
163
+ Add a `[tool.cq]` section to your project's `pyproject.toml`:
164
+
165
+ ```toml
166
+ [tool.cq]
167
+ # Skip tools that are slow or not relevant to your project
168
+ disable = ["coverage", "interrogate"]
169
+
170
+ # Override warning/error thresholds per tool
171
+ [tool.cq.thresholds.coverage]
172
+ warning = 0.9
173
+ error = 0.7
174
+ ```
175
+
176
+ Tool IDs match the keys in `config/tools.yaml`: `compilation`, `bandit`, `ruff`, `ty`, `pytest`, `coverage`, `complexity`, `maintainability`, `halstead`, `vulture`, `interrogate`.
177
+
178
+
179
+ ### Default config
180
+
181
+ ```yaml
182
+ tools:
183
+
184
+ compilation:
185
+ name: "compile"
186
+ command: "{python} -m compileall -r 10 -j 8 {context_path} -x .*venv"
187
+ parser: "CompileParser"
188
+ order: 1
189
+ warning_threshold: 0.9999
190
+ error_threshold: 0.9999
191
+
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
+ ruff:
201
+ name: "ruff"
202
+ command: "{python} -m ruff check --output-format concise --no-cache {context_path}"
203
+ parser: "RuffParser"
204
+ order: 3
205
+ warning_threshold: 0.9999
206
+ error_threshold: 0.9
207
+
208
+ ty:
209
+ name: "ty"
210
+ command: "{python} -m ty check --output-format concise --color never {context_path}"
211
+ parser: "TyParser"
212
+ order: 4
213
+ warning_threshold: 0.9999
214
+ error_threshold: 0.8
215
+ run_in_target_env: true
216
+ extra_deps:
217
+ - ty
218
+
219
+ pytest:
220
+ name: "pytest"
221
+ command: "{python} -m pytest -v {context_path}"
222
+ parser: "PytestParser"
223
+ order: 5
224
+ warning_threshold: 0.7
225
+ error_threshold: 0.5
226
+ run_in_target_env: true
227
+
228
+ coverage:
229
+ name: "coverage"
230
+ command: "{python} -m coverage run --omit=*/tests/*,*/test_*.py -m pytest {context_path} && {python} -m coverage report --omit=*/tests/*,*/test_*.py"
231
+ parser: "CoverageParser"
232
+ order: 6
233
+ warning_threshold: 0.9
234
+ error_threshold: 0.5
235
+ run_in_target_env: true
236
+ extra_deps:
237
+ - coverage
238
+ - pytest
239
+
240
+ complexity:
241
+ name: "radon cc"
242
+ command: "{python} -m radon cc --json {context_path}"
243
+ parser: "ComplexityParser"
244
+ order: 7
245
+ warning_threshold: 0.6
246
+ error_threshold: 0.4
247
+
248
+ maintainability:
249
+ name: "radon mi"
250
+ command: "{python} -m radon mi -s --json {context_path}"
251
+ parser: "MaintainabilityParser"
252
+ order: 8
253
+ warning_threshold: 0.6
254
+ error_threshold: 0.4
255
+
256
+ halstead:
257
+ name: "radon hal"
258
+ command: "{python} -m radon hal -f --json {context_path}"
259
+ parser: "HalsteadParser"
260
+ order: 9
261
+ warning_threshold: 0.5
262
+ error_threshold: 0.3
263
+
264
+ vulture:
265
+ name: "vulture"
266
+ command: "{python} -m vulture {context_path} --min-confidence 80 --exclude .venv,dist,.*_cache,docs,.git"
267
+ parser: "VultureParser"
268
+ order: 10
269
+ warning_threshold: 0.9999
270
+ error_threshold: 0.8
271
+
272
+ interrogate:
273
+ name: "interrogate"
274
+ command: "{python} -m interrogate {context_path} -v --fail-under 0"
275
+ parser: "InterrogateParser"
276
+ order: 11
277
+ warning_threshold: 0.8
278
+ error_threshold: 0.3
279
+
280
+ ```
281
+
282
+ ## Respect
283
+
284
+ Many thanks to all the wonderful maintainers of :
285
+
286
+ - [compileall](https://docs.python.org/3/library/compileall.html)
287
+ - [bandit](https://github.com/PyCQA/bandit)
288
+ - [ruff](https://github.com/astral-sh/ruff)
289
+ - [ty](https://github.com/astral-sh/ty)
290
+ - [pytest](https://github.com/pytest-dev/pytest)
291
+ - [coverage.py](https://github.com/nedbat/coveragepy)
292
+ - [radon](https://github.com/rubik/radon)
293
+ - [vulture](https://github.com/jendrikseipp/vulture)
294
+ - [interrogate](https://github.com/econchick/interrogate)
295
+ - [diskcache](https://github.com/grantjenks/python-diskcache)
296
+ - [typer](https://github.com/fastapi/typer)
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "python-code-quality"
3
- version = "0.1.6"
4
- description = "Python Code Quality Analysis Tool - feed the results from 11 CQ CQ straight into an LLM."
3
+ version = "0.1.8"
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"
7
7
  license = "MIT"