python-code-quality 0.1.7__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.
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/CLAUDE.md +5 -5
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/PKG-INFO +63 -65
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/README.md +62 -64
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/pyproject.toml +1 -1
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/cli.py +13 -9
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/config/tools.yaml +12 -12
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/execution_engine.py +14 -2
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/llm_formatter.py +1 -1
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/localtypes.py +4 -4
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/parsers/halsteadparser.py +1 -1
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/parsers/pytestparser.py +8 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/tool_registry.py +1 -1
- python_code_quality-0.1.8/tests/test_cli.py +228 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/tests/test_common.py +18 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/tests/test_config.py +2 -2
- python_code_quality-0.1.8/tests/test_execution_engine.py +291 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/tests/test_llm_formatter.py +14 -14
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/tests/test_localtypes.py +17 -2
- python_code_quality-0.1.8/tests/test_parser_halstead.py +116 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/tests/test_parser_pytest.py +14 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/uv.lock +1 -1
- python_code_quality-0.1.7/tests/test_execution_engine.py +0 -131
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/.github/workflows/python-publish.yml +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/.gitignore +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/.python-version +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/LICENSE +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/data/problems/travelling_salesman/ts_bad.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/data/problems/travelling_salesman/ts_good.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/__init__.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/config/__init__.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/context_hash.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/main.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/metric_aggregator.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/parsers/__init__.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/parsers/banditparser.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/parsers/common.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/parsers/compileparser.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/parsers/complexityparser.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/parsers/coverageparser.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/parsers/interrogateparser.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/parsers/maintainabilityparser.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/parsers/ruffparser.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/parsers/typarser.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/parsers/vultureparser.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/src/py_cq/py.typed +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/tests/conftest.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/tests/test_context_hash.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/tests/test_parser_bandit.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/tests/test_parser_compile.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/tests/test_parser_complexity.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/tests/test_parser_coverage.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/tests/test_parser_interrogate.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/tests/test_parser_maintainability.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/tests/test_parser_ruff.py +0 -0
- {python_code_quality-0.1.7 → python_code_quality-0.1.8}/tests/test_parser_ty.py +0 -0
- {python_code_quality-0.1.7 → 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
|
|
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,
|
|
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
|
|
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
|
|
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,
|
|
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,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-code-quality
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8
|
|
4
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
|
|
@@ -34,7 +34,13 @@ The primary workflow is:
|
|
|
34
34
|
# get the single most critical defect as markdown
|
|
35
35
|
cq check . -o llm
|
|
36
36
|
```
|
|
37
|
-
|
|
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
|
+
|
|
43
|
+
The code context is expanded if available.
|
|
38
44
|
```md
|
|
39
45
|
`data/problems/travelling_salesman/ts_bad.py:21` — **F841**: Local variable `unused_variable` is assigned to but never used
|
|
40
46
|
|
|
@@ -51,8 +57,10 @@ Please fix only this issue. After fixing, run `cq check . -o llm` to verify.
|
|
|
51
57
|
```
|
|
52
58
|
Feed to an LLM with edit tools and repeat until there are no issues, e.g.
|
|
53
59
|
|
|
54
|
-
```
|
|
60
|
+
```python
|
|
55
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"
|
|
56
64
|
```
|
|
57
65
|
|
|
58
66
|
## Install
|
|
@@ -69,9 +77,9 @@ uv tool install .
|
|
|
69
77
|
|
|
70
78
|
## Tools
|
|
71
79
|
|
|
72
|
-
These tools are run in **parallel
|
|
80
|
+
These tools are run in **parallel** except when looking for the first error in -o llm mode:
|
|
73
81
|
|
|
74
|
-
|
|
|
82
|
+
| Order | Tool | Measures |
|
|
75
83
|
|----------|------|----------|
|
|
76
84
|
| 1 | compileall | Syntax errors |
|
|
77
85
|
| 2 | bandit | Security vulnerabilities |
|
|
@@ -91,31 +99,15 @@ Diskcache is used to cache tool output for lightning fast re-runs. Sane defaults
|
|
|
91
99
|
## Usage
|
|
92
100
|
|
|
93
101
|
```bash
|
|
94
|
-
|
|
95
|
-
cq check -o llm
|
|
96
|
-
|
|
97
|
-
#
|
|
98
|
-
cq check .
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
cq check . -
|
|
102
|
-
|
|
103
|
-
# Full JSON output, including raw test results
|
|
104
|
-
cq check . -o json
|
|
105
|
-
|
|
106
|
-
# Explicit path
|
|
107
|
-
cq check path/to/project/
|
|
108
|
-
cq check path/to/file.py
|
|
109
|
-
|
|
110
|
-
# Run sequentially if you like things slow
|
|
111
|
-
cq check . --workers 1
|
|
112
|
-
|
|
113
|
-
# Clear cached results before running (rarely needed)
|
|
114
|
-
cq check . --clear-cache
|
|
115
|
-
|
|
116
|
-
# Show effective tool configuration (thresholds, enabled/disabled status)
|
|
117
|
-
cq config
|
|
118
|
-
cq config path/to/project/
|
|
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
|
|
119
111
|
```
|
|
120
112
|
|
|
121
113
|
## Table output
|
|
@@ -160,30 +152,36 @@ cq config path/to/project/
|
|
|
160
152
|
```
|
|
161
153
|
|
|
162
154
|
```json
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
},
|
|
169
|
-
"details": {},
|
|
170
|
-
"raw": {
|
|
171
|
-
"tool_name": "compile",
|
|
172
|
-
"command": ".venv\\Scripts\\python.exe -m compileall -r 10 -j 8 . -x .*venv",
|
|
173
|
-
"stdout": "Compiling './src/project/file.py'...",
|
|
174
|
-
"stderr": "",
|
|
175
|
-
"return_code": 0,
|
|
176
|
-
"timestamp": "2026-02-19 05:03:11"
|
|
177
|
-
},
|
|
178
|
-
"duration_s": 0.08294440002646297
|
|
155
|
+
[
|
|
156
|
+
{
|
|
157
|
+
"tool_name": "compile",
|
|
158
|
+
"metrics": {
|
|
159
|
+
"compile": 1.0
|
|
179
160
|
},
|
|
180
|
-
{
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
161
|
+
"details": {},
|
|
162
|
+
"duration_s": 0.05611889995634556
|
|
163
|
+
}
|
|
164
|
+
...
|
|
165
|
+
]
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Raw output
|
|
169
|
+
```bash
|
|
170
|
+
> cq check -o raw
|
|
171
|
+
```
|
|
172
|
+
|
|
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
|
+
]
|
|
187
185
|
```
|
|
188
186
|
|
|
189
187
|
## Configuration
|
|
@@ -213,7 +211,7 @@ tools:
|
|
|
213
211
|
name: "compile"
|
|
214
212
|
command: "{python} -m compileall -r 10 -j 8 {context_path} -x .*venv"
|
|
215
213
|
parser: "CompileParser"
|
|
216
|
-
|
|
214
|
+
order: 1
|
|
217
215
|
warning_threshold: 0.9999
|
|
218
216
|
error_threshold: 0.9999
|
|
219
217
|
|
|
@@ -221,7 +219,7 @@ tools:
|
|
|
221
219
|
name: "bandit"
|
|
222
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"
|
|
223
221
|
parser: "BanditParser"
|
|
224
|
-
|
|
222
|
+
order: 2
|
|
225
223
|
warning_threshold: 0.9999
|
|
226
224
|
error_threshold: 0.8
|
|
227
225
|
|
|
@@ -229,7 +227,7 @@ tools:
|
|
|
229
227
|
name: "ruff"
|
|
230
228
|
command: "{python} -m ruff check --output-format concise --no-cache {context_path}"
|
|
231
229
|
parser: "RuffParser"
|
|
232
|
-
|
|
230
|
+
order: 3
|
|
233
231
|
warning_threshold: 0.9999
|
|
234
232
|
error_threshold: 0.9
|
|
235
233
|
|
|
@@ -237,7 +235,7 @@ tools:
|
|
|
237
235
|
name: "ty"
|
|
238
236
|
command: "{python} -m ty check --output-format concise --color never {context_path}"
|
|
239
237
|
parser: "TyParser"
|
|
240
|
-
|
|
238
|
+
order: 4
|
|
241
239
|
warning_threshold: 0.9999
|
|
242
240
|
error_threshold: 0.8
|
|
243
241
|
run_in_target_env: true
|
|
@@ -248,16 +246,16 @@ tools:
|
|
|
248
246
|
name: "pytest"
|
|
249
247
|
command: "{python} -m pytest -v {context_path}"
|
|
250
248
|
parser: "PytestParser"
|
|
251
|
-
|
|
249
|
+
order: 5
|
|
252
250
|
warning_threshold: 0.7
|
|
253
251
|
error_threshold: 0.5
|
|
254
252
|
run_in_target_env: true
|
|
255
253
|
|
|
256
254
|
coverage:
|
|
257
255
|
name: "coverage"
|
|
258
|
-
command: "{python} -m coverage run -m pytest {context_path} && {python} -m coverage report"
|
|
256
|
+
command: "{python} -m coverage run --omit=*/tests/*,*/test_*.py -m pytest {context_path} && {python} -m coverage report --omit=*/tests/*,*/test_*.py"
|
|
259
257
|
parser: "CoverageParser"
|
|
260
|
-
|
|
258
|
+
order: 6
|
|
261
259
|
warning_threshold: 0.9
|
|
262
260
|
error_threshold: 0.5
|
|
263
261
|
run_in_target_env: true
|
|
@@ -269,7 +267,7 @@ tools:
|
|
|
269
267
|
name: "radon cc"
|
|
270
268
|
command: "{python} -m radon cc --json {context_path}"
|
|
271
269
|
parser: "ComplexityParser"
|
|
272
|
-
|
|
270
|
+
order: 7
|
|
273
271
|
warning_threshold: 0.6
|
|
274
272
|
error_threshold: 0.4
|
|
275
273
|
|
|
@@ -277,7 +275,7 @@ tools:
|
|
|
277
275
|
name: "radon mi"
|
|
278
276
|
command: "{python} -m radon mi -s --json {context_path}"
|
|
279
277
|
parser: "MaintainabilityParser"
|
|
280
|
-
|
|
278
|
+
order: 8
|
|
281
279
|
warning_threshold: 0.6
|
|
282
280
|
error_threshold: 0.4
|
|
283
281
|
|
|
@@ -285,7 +283,7 @@ tools:
|
|
|
285
283
|
name: "radon hal"
|
|
286
284
|
command: "{python} -m radon hal -f --json {context_path}"
|
|
287
285
|
parser: "HalsteadParser"
|
|
288
|
-
|
|
286
|
+
order: 9
|
|
289
287
|
warning_threshold: 0.5
|
|
290
288
|
error_threshold: 0.3
|
|
291
289
|
|
|
@@ -293,7 +291,7 @@ tools:
|
|
|
293
291
|
name: "vulture"
|
|
294
292
|
command: "{python} -m vulture {context_path} --min-confidence 80 --exclude .venv,dist,.*_cache,docs,.git"
|
|
295
293
|
parser: "VultureParser"
|
|
296
|
-
|
|
294
|
+
order: 10
|
|
297
295
|
warning_threshold: 0.9999
|
|
298
296
|
error_threshold: 0.8
|
|
299
297
|
|
|
@@ -301,7 +299,7 @@ tools:
|
|
|
301
299
|
name: "interrogate"
|
|
302
300
|
command: "{python} -m interrogate {context_path} -v --fail-under 0"
|
|
303
301
|
parser: "InterrogateParser"
|
|
304
|
-
|
|
302
|
+
order: 11
|
|
305
303
|
warning_threshold: 0.8
|
|
306
304
|
error_threshold: 0.3
|
|
307
305
|
|
|
@@ -8,7 +8,13 @@ The primary workflow is:
|
|
|
8
8
|
# get the single most critical defect as markdown
|
|
9
9
|
cq check . -o llm
|
|
10
10
|
```
|
|
11
|
-
|
|
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.
|
|
12
18
|
```md
|
|
13
19
|
`data/problems/travelling_salesman/ts_bad.py:21` — **F841**: Local variable `unused_variable` is assigned to but never used
|
|
14
20
|
|
|
@@ -25,8 +31,10 @@ Please fix only this issue. After fixing, run `cq check . -o llm` to verify.
|
|
|
25
31
|
```
|
|
26
32
|
Feed to an LLM with edit tools and repeat until there are no issues, e.g.
|
|
27
33
|
|
|
28
|
-
```
|
|
34
|
+
```python
|
|
29
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"
|
|
30
38
|
```
|
|
31
39
|
|
|
32
40
|
## Install
|
|
@@ -43,9 +51,9 @@ uv tool install .
|
|
|
43
51
|
|
|
44
52
|
## Tools
|
|
45
53
|
|
|
46
|
-
These tools are run in **parallel
|
|
54
|
+
These tools are run in **parallel** except when looking for the first error in -o llm mode:
|
|
47
55
|
|
|
48
|
-
|
|
|
56
|
+
| Order | Tool | Measures |
|
|
49
57
|
|----------|------|----------|
|
|
50
58
|
| 1 | compileall | Syntax errors |
|
|
51
59
|
| 2 | bandit | Security vulnerabilities |
|
|
@@ -65,31 +73,15 @@ Diskcache is used to cache tool output for lightning fast re-runs. Sane defaults
|
|
|
65
73
|
## Usage
|
|
66
74
|
|
|
67
75
|
```bash
|
|
68
|
-
|
|
69
|
-
cq check -o llm
|
|
70
|
-
|
|
71
|
-
#
|
|
72
|
-
cq check .
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
cq check . -
|
|
76
|
-
|
|
77
|
-
# Full JSON output, including raw test results
|
|
78
|
-
cq check . -o json
|
|
79
|
-
|
|
80
|
-
# Explicit path
|
|
81
|
-
cq check path/to/project/
|
|
82
|
-
cq check path/to/file.py
|
|
83
|
-
|
|
84
|
-
# Run sequentially if you like things slow
|
|
85
|
-
cq check . --workers 1
|
|
86
|
-
|
|
87
|
-
# Clear cached results before running (rarely needed)
|
|
88
|
-
cq check . --clear-cache
|
|
89
|
-
|
|
90
|
-
# Show effective tool configuration (thresholds, enabled/disabled status)
|
|
91
|
-
cq config
|
|
92
|
-
cq config path/to/project/
|
|
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
|
|
93
85
|
```
|
|
94
86
|
|
|
95
87
|
## Table output
|
|
@@ -134,30 +126,36 @@ cq config path/to/project/
|
|
|
134
126
|
```
|
|
135
127
|
|
|
136
128
|
```json
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
},
|
|
143
|
-
"details": {},
|
|
144
|
-
"raw": {
|
|
145
|
-
"tool_name": "compile",
|
|
146
|
-
"command": ".venv\\Scripts\\python.exe -m compileall -r 10 -j 8 . -x .*venv",
|
|
147
|
-
"stdout": "Compiling './src/project/file.py'...",
|
|
148
|
-
"stderr": "",
|
|
149
|
-
"return_code": 0,
|
|
150
|
-
"timestamp": "2026-02-19 05:03:11"
|
|
151
|
-
},
|
|
152
|
-
"duration_s": 0.08294440002646297
|
|
129
|
+
[
|
|
130
|
+
{
|
|
131
|
+
"tool_name": "compile",
|
|
132
|
+
"metrics": {
|
|
133
|
+
"compile": 1.0
|
|
153
134
|
},
|
|
154
|
-
{
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
+
]
|
|
161
159
|
```
|
|
162
160
|
|
|
163
161
|
## Configuration
|
|
@@ -187,7 +185,7 @@ tools:
|
|
|
187
185
|
name: "compile"
|
|
188
186
|
command: "{python} -m compileall -r 10 -j 8 {context_path} -x .*venv"
|
|
189
187
|
parser: "CompileParser"
|
|
190
|
-
|
|
188
|
+
order: 1
|
|
191
189
|
warning_threshold: 0.9999
|
|
192
190
|
error_threshold: 0.9999
|
|
193
191
|
|
|
@@ -195,7 +193,7 @@ tools:
|
|
|
195
193
|
name: "bandit"
|
|
196
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"
|
|
197
195
|
parser: "BanditParser"
|
|
198
|
-
|
|
196
|
+
order: 2
|
|
199
197
|
warning_threshold: 0.9999
|
|
200
198
|
error_threshold: 0.8
|
|
201
199
|
|
|
@@ -203,7 +201,7 @@ tools:
|
|
|
203
201
|
name: "ruff"
|
|
204
202
|
command: "{python} -m ruff check --output-format concise --no-cache {context_path}"
|
|
205
203
|
parser: "RuffParser"
|
|
206
|
-
|
|
204
|
+
order: 3
|
|
207
205
|
warning_threshold: 0.9999
|
|
208
206
|
error_threshold: 0.9
|
|
209
207
|
|
|
@@ -211,7 +209,7 @@ tools:
|
|
|
211
209
|
name: "ty"
|
|
212
210
|
command: "{python} -m ty check --output-format concise --color never {context_path}"
|
|
213
211
|
parser: "TyParser"
|
|
214
|
-
|
|
212
|
+
order: 4
|
|
215
213
|
warning_threshold: 0.9999
|
|
216
214
|
error_threshold: 0.8
|
|
217
215
|
run_in_target_env: true
|
|
@@ -222,16 +220,16 @@ tools:
|
|
|
222
220
|
name: "pytest"
|
|
223
221
|
command: "{python} -m pytest -v {context_path}"
|
|
224
222
|
parser: "PytestParser"
|
|
225
|
-
|
|
223
|
+
order: 5
|
|
226
224
|
warning_threshold: 0.7
|
|
227
225
|
error_threshold: 0.5
|
|
228
226
|
run_in_target_env: true
|
|
229
227
|
|
|
230
228
|
coverage:
|
|
231
229
|
name: "coverage"
|
|
232
|
-
command: "{python} -m coverage run -m pytest {context_path} && {python} -m coverage report"
|
|
230
|
+
command: "{python} -m coverage run --omit=*/tests/*,*/test_*.py -m pytest {context_path} && {python} -m coverage report --omit=*/tests/*,*/test_*.py"
|
|
233
231
|
parser: "CoverageParser"
|
|
234
|
-
|
|
232
|
+
order: 6
|
|
235
233
|
warning_threshold: 0.9
|
|
236
234
|
error_threshold: 0.5
|
|
237
235
|
run_in_target_env: true
|
|
@@ -243,7 +241,7 @@ tools:
|
|
|
243
241
|
name: "radon cc"
|
|
244
242
|
command: "{python} -m radon cc --json {context_path}"
|
|
245
243
|
parser: "ComplexityParser"
|
|
246
|
-
|
|
244
|
+
order: 7
|
|
247
245
|
warning_threshold: 0.6
|
|
248
246
|
error_threshold: 0.4
|
|
249
247
|
|
|
@@ -251,7 +249,7 @@ tools:
|
|
|
251
249
|
name: "radon mi"
|
|
252
250
|
command: "{python} -m radon mi -s --json {context_path}"
|
|
253
251
|
parser: "MaintainabilityParser"
|
|
254
|
-
|
|
252
|
+
order: 8
|
|
255
253
|
warning_threshold: 0.6
|
|
256
254
|
error_threshold: 0.4
|
|
257
255
|
|
|
@@ -259,7 +257,7 @@ tools:
|
|
|
259
257
|
name: "radon hal"
|
|
260
258
|
command: "{python} -m radon hal -f --json {context_path}"
|
|
261
259
|
parser: "HalsteadParser"
|
|
262
|
-
|
|
260
|
+
order: 9
|
|
263
261
|
warning_threshold: 0.5
|
|
264
262
|
error_threshold: 0.3
|
|
265
263
|
|
|
@@ -267,7 +265,7 @@ tools:
|
|
|
267
265
|
name: "vulture"
|
|
268
266
|
command: "{python} -m vulture {context_path} --min-confidence 80 --exclude .venv,dist,.*_cache,docs,.git"
|
|
269
267
|
parser: "VultureParser"
|
|
270
|
-
|
|
268
|
+
order: 10
|
|
271
269
|
warning_threshold: 0.9999
|
|
272
270
|
error_threshold: 0.8
|
|
273
271
|
|
|
@@ -275,7 +273,7 @@ tools:
|
|
|
275
273
|
name: "interrogate"
|
|
276
274
|
command: "{python} -m interrogate {context_path} -v --fail-under 0"
|
|
277
275
|
parser: "InterrogateParser"
|
|
278
|
-
|
|
276
|
+
order: 11
|
|
279
277
|
warning_threshold: 0.8
|
|
280
278
|
error_threshold: 0.3
|
|
281
279
|
|
|
@@ -43,7 +43,8 @@ app = typer.Typer(
|
|
|
43
43
|
" cq check . # full table with all metrics (default)\n\n"
|
|
44
44
|
" cq check . -o llm # top defect as markdown (primary LLM workflow)\n\n"
|
|
45
45
|
" cq check . -o score # numeric score only\n\n"
|
|
46
|
-
" cq check . -o json #
|
|
46
|
+
" cq check . -o json # parsed metrics as json\n\n"
|
|
47
|
+
" cq check . -o raw # unprocessed tool output as json\n\n"
|
|
47
48
|
" cq config . # show effective tool configuration"
|
|
48
49
|
),
|
|
49
50
|
)
|
|
@@ -74,6 +75,7 @@ class OutputMode(str, Enum):
|
|
|
74
75
|
SCORE = "score"
|
|
75
76
|
JSON = "json"
|
|
76
77
|
LLM = "llm"
|
|
78
|
+
RAW = "raw"
|
|
77
79
|
|
|
78
80
|
|
|
79
81
|
@app.callback()
|
|
@@ -114,16 +116,18 @@ def check(
|
|
|
114
116
|
effective_registry = _apply_user_config(tool_registry, load_user_config(path_obj))
|
|
115
117
|
if clear_cache:
|
|
116
118
|
tool_cache.clear()
|
|
117
|
-
tool_results = run_tools(effective_registry.values(), path, workers)
|
|
118
|
-
for tr in tool_results:
|
|
119
|
-
|
|
119
|
+
tool_results = run_tools(effective_registry.values(), path, workers, early_exit=(output == OutputMode.LLM))
|
|
120
|
+
# for tr in tool_results:
|
|
121
|
+
# log.debug(json.dumps(tr.to_dict(), indent=2))
|
|
120
122
|
combined_metrics = aggregate_metrics(path=path, metrics=tool_results)
|
|
121
123
|
if output == OutputMode.SCORE:
|
|
122
124
|
console.print(combined_metrics.score)
|
|
123
125
|
elif output == OutputMode.JSON:
|
|
124
|
-
console.print(json.dumps(
|
|
126
|
+
console.print(json.dumps([tr.to_dict() for tr in tool_results], indent=2))
|
|
127
|
+
elif output == OutputMode.RAW:
|
|
128
|
+
console.print(json.dumps([tr.raw.to_dict() for tr in tool_results], indent=2))
|
|
125
129
|
elif output == OutputMode.LLM:
|
|
126
|
-
log.setLevel("CRITICAL")
|
|
130
|
+
# log.setLevel("CRITICAL")
|
|
127
131
|
from py_cq.llm_formatter import format_for_llm
|
|
128
132
|
console.print(format_for_llm(effective_registry, combined_metrics))
|
|
129
133
|
else:
|
|
@@ -163,18 +167,18 @@ def config(
|
|
|
163
167
|
|
|
164
168
|
table = Table()
|
|
165
169
|
table.add_column("Tool", style="cyan")
|
|
166
|
-
table.add_column("
|
|
170
|
+
table.add_column("Order", justify="right")
|
|
167
171
|
table.add_column("Warning", justify="right")
|
|
168
172
|
table.add_column("Error", justify="right")
|
|
169
173
|
table.add_column("Status", justify="center")
|
|
170
174
|
|
|
171
|
-
for tool_id in sorted(tool_registry, key=lambda t: tool_registry[t].
|
|
175
|
+
for tool_id in sorted(tool_registry, key=lambda t: tool_registry[t].order):
|
|
172
176
|
tc = effective_registry.get(tool_id, tool_registry[tool_id])
|
|
173
177
|
is_disabled = tool_id in disabled_ids
|
|
174
178
|
status = "[red]disabled[/red]" if is_disabled else "[green]enabled[/green]"
|
|
175
179
|
table.add_row(
|
|
176
180
|
tc.name,
|
|
177
|
-
str(tc.
|
|
181
|
+
str(tc.order),
|
|
178
182
|
f"{tc.warning_threshold:.2f}",
|
|
179
183
|
f"{tc.error_threshold:.2f}",
|
|
180
184
|
status,
|
|
@@ -4,7 +4,7 @@ tools:
|
|
|
4
4
|
name: "compile"
|
|
5
5
|
command: "{python} -m compileall -r 10 -j 8 {context_path} -x .*venv"
|
|
6
6
|
parser: "CompileParser"
|
|
7
|
-
|
|
7
|
+
order: 1
|
|
8
8
|
warning_threshold: 0.9999
|
|
9
9
|
error_threshold: 0.9999
|
|
10
10
|
|
|
@@ -12,7 +12,7 @@ tools:
|
|
|
12
12
|
name: "bandit"
|
|
13
13
|
command: "{python} -m bandit -r {context_path} -f json -q -s B101 --severity-level medium --exclude {input_path_posix}/.venv,{input_path_posix}/tests"
|
|
14
14
|
parser: "BanditParser"
|
|
15
|
-
|
|
15
|
+
order: 2
|
|
16
16
|
warning_threshold: 0.9999
|
|
17
17
|
error_threshold: 0.8
|
|
18
18
|
|
|
@@ -20,7 +20,7 @@ tools:
|
|
|
20
20
|
name: "ruff"
|
|
21
21
|
command: "{python} -m ruff check --output-format concise --no-cache {context_path}"
|
|
22
22
|
parser: "RuffParser"
|
|
23
|
-
|
|
23
|
+
order: 3
|
|
24
24
|
warning_threshold: 0.9999
|
|
25
25
|
error_threshold: 0.9
|
|
26
26
|
|
|
@@ -28,7 +28,7 @@ tools:
|
|
|
28
28
|
name: "ty"
|
|
29
29
|
command: "{python} -m ty check --output-format concise --color never {context_path}"
|
|
30
30
|
parser: "TyParser"
|
|
31
|
-
|
|
31
|
+
order: 4
|
|
32
32
|
warning_threshold: 0.9999
|
|
33
33
|
error_threshold: 0.8
|
|
34
34
|
run_in_target_env: true
|
|
@@ -39,16 +39,16 @@ tools:
|
|
|
39
39
|
name: "pytest"
|
|
40
40
|
command: "{python} -m pytest -v {context_path}"
|
|
41
41
|
parser: "PytestParser"
|
|
42
|
-
|
|
42
|
+
order: 5
|
|
43
43
|
warning_threshold: 0.7
|
|
44
44
|
error_threshold: 0.5
|
|
45
45
|
run_in_target_env: true
|
|
46
46
|
|
|
47
47
|
coverage:
|
|
48
48
|
name: "coverage"
|
|
49
|
-
command: "{python} -m coverage run -m pytest {context_path} && {python} -m coverage report"
|
|
49
|
+
command: "{python} -m coverage run --omit=*/tests/*,*/test_*.py -m pytest {context_path} && {python} -m coverage report --omit=*/tests/*,*/test_*.py"
|
|
50
50
|
parser: "CoverageParser"
|
|
51
|
-
|
|
51
|
+
order: 6
|
|
52
52
|
warning_threshold: 0.9
|
|
53
53
|
error_threshold: 0.5
|
|
54
54
|
run_in_target_env: true
|
|
@@ -60,7 +60,7 @@ tools:
|
|
|
60
60
|
name: "radon cc"
|
|
61
61
|
command: "{python} -m radon cc --json {context_path}"
|
|
62
62
|
parser: "ComplexityParser"
|
|
63
|
-
|
|
63
|
+
order: 7
|
|
64
64
|
warning_threshold: 0.6
|
|
65
65
|
error_threshold: 0.4
|
|
66
66
|
|
|
@@ -68,7 +68,7 @@ tools:
|
|
|
68
68
|
name: "radon mi"
|
|
69
69
|
command: "{python} -m radon mi -s --json {context_path}"
|
|
70
70
|
parser: "MaintainabilityParser"
|
|
71
|
-
|
|
71
|
+
order: 8
|
|
72
72
|
warning_threshold: 0.6
|
|
73
73
|
error_threshold: 0.4
|
|
74
74
|
|
|
@@ -76,7 +76,7 @@ tools:
|
|
|
76
76
|
name: "radon hal"
|
|
77
77
|
command: "{python} -m radon hal -f --json {context_path}"
|
|
78
78
|
parser: "HalsteadParser"
|
|
79
|
-
|
|
79
|
+
order: 9
|
|
80
80
|
warning_threshold: 0.5
|
|
81
81
|
error_threshold: 0.3
|
|
82
82
|
|
|
@@ -84,7 +84,7 @@ tools:
|
|
|
84
84
|
name: "vulture"
|
|
85
85
|
command: "{python} -m vulture {context_path} --min-confidence 80 --exclude .venv,dist,.*_cache,docs,.git"
|
|
86
86
|
parser: "VultureParser"
|
|
87
|
-
|
|
87
|
+
order: 10
|
|
88
88
|
warning_threshold: 0.9999
|
|
89
89
|
error_threshold: 0.8
|
|
90
90
|
|
|
@@ -92,6 +92,6 @@ tools:
|
|
|
92
92
|
name: "interrogate"
|
|
93
93
|
command: "{python} -m interrogate {context_path} -v --fail-under 0"
|
|
94
94
|
parser: "InterrogateParser"
|
|
95
|
-
|
|
95
|
+
order: 11
|
|
96
96
|
warning_threshold: 0.8
|
|
97
97
|
error_threshold: 0.3
|