python-code-quality 0.1.8__tar.gz → 0.1.9__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 (68) hide show
  1. python_code_quality-0.1.9/.gitea/workflows/python-multi-version-test.yaml +65 -0
  2. python_code_quality-0.1.9/.gitea/workflows/test.yaml +11 -0
  3. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/.gitignore +1 -0
  4. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/CLAUDE.md +3 -3
  5. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/PKG-INFO +43 -46
  6. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/README.md +42 -45
  7. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/pyproject.toml +1 -1
  8. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/cli.py +54 -5
  9. python_code_quality-0.1.8/src/py_cq/config/tools.yaml → python_code_quality-0.1.9/src/py_cq/config/config.yaml +28 -37
  10. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/execution_engine.py +2 -2
  11. python_code_quality-0.1.9/src/py_cq/language_detector.py +29 -0
  12. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/llm_formatter.py +2 -1
  13. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/localtypes.py +5 -1
  14. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/parsers/banditparser.py +2 -2
  15. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/parsers/common.py +31 -0
  16. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/parsers/compileparser.py +2 -2
  17. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/parsers/coverageparser.py +1 -1
  18. python_code_quality-0.1.9/src/py_cq/parsers/exitcodeparser.py +16 -0
  19. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/parsers/halsteadparser.py +1 -1
  20. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/parsers/interrogateparser.py +1 -1
  21. python_code_quality-0.1.9/src/py_cq/parsers/linecountparser.py +26 -0
  22. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/parsers/pytestparser.py +53 -10
  23. python_code_quality-0.1.9/src/py_cq/parsers/regexcountparser.py +35 -0
  24. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/parsers/ruffparser.py +2 -2
  25. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/parsers/typarser.py +2 -2
  26. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/parsers/vultureparser.py +2 -2
  27. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/tool_registry.py +5 -4
  28. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/tests/test_cli.py +135 -2
  29. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/tests/test_common.py +46 -0
  30. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/tests/test_execution_engine.py +3 -1
  31. python_code_quality-0.1.9/tests/test_integration_user_tools.py +152 -0
  32. python_code_quality-0.1.9/tests/test_language_detector.py +87 -0
  33. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/tests/test_localtypes.py +28 -0
  34. python_code_quality-0.1.9/tests/test_parser_exitcode.py +39 -0
  35. python_code_quality-0.1.9/tests/test_parser_linecount.py +42 -0
  36. python_code_quality-0.1.9/tests/test_parser_pytest.py +112 -0
  37. python_code_quality-0.1.9/tests/test_parser_regexcount.py +42 -0
  38. python_code_quality-0.1.9/tests/test_tool_registry.py +16 -0
  39. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/uv.lock +56 -56
  40. python_code_quality-0.1.8/data/problems/travelling_salesman/ts_bad.py +0 -65
  41. python_code_quality-0.1.8/data/problems/travelling_salesman/ts_good.py +0 -61
  42. python_code_quality-0.1.8/tests/test_parser_pytest.py +0 -49
  43. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/.github/workflows/python-publish.yml +0 -0
  44. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/.python-version +0 -0
  45. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/LICENSE +0 -0
  46. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/__init__.py +0 -0
  47. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/config/__init__.py +0 -0
  48. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/context_hash.py +0 -0
  49. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/main.py +0 -0
  50. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/metric_aggregator.py +0 -0
  51. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/parsers/__init__.py +0 -0
  52. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/parsers/complexityparser.py +0 -0
  53. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/parsers/maintainabilityparser.py +0 -0
  54. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/src/py_cq/py.typed +0 -0
  55. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/tests/conftest.py +0 -0
  56. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/tests/test_config.py +0 -0
  57. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/tests/test_context_hash.py +0 -0
  58. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/tests/test_llm_formatter.py +0 -0
  59. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/tests/test_parser_bandit.py +0 -0
  60. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/tests/test_parser_compile.py +0 -0
  61. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/tests/test_parser_complexity.py +0 -0
  62. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/tests/test_parser_coverage.py +0 -0
  63. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/tests/test_parser_halstead.py +0 -0
  64. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/tests/test_parser_interrogate.py +0 -0
  65. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/tests/test_parser_maintainability.py +0 -0
  66. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/tests/test_parser_ruff.py +0 -0
  67. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/tests/test_parser_ty.py +0 -0
  68. {python_code_quality-0.1.8 → python_code_quality-0.1.9}/tests/test_parser_vulture.py +0 -0
@@ -0,0 +1,65 @@
1
+ # Template: Multi-version Python testing workflow for Gitea Actions
2
+ # Copy this file to your project's .gitea/workflows/ directory
3
+ #
4
+ # Runs tests against multiple Python versions using generic ubuntu runners.
5
+ # Includes optional dependency upgrade testing.
6
+
7
+ name: Python Tests
8
+
9
+ on:
10
+ push:
11
+ branches: [main]
12
+ pull_request:
13
+ branches: [main]
14
+
15
+ jobs:
16
+ test:
17
+ name: Python ${{ matrix.python-version }}
18
+ runs-on: ubuntu-latest
19
+ strategy:
20
+ fail-fast: false
21
+ matrix:
22
+ python-version: ['3.12', '3.13']
23
+
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+
27
+ - name: Set up Python ${{ matrix.python-version }}
28
+ uses: actions/setup-python@v5
29
+ with:
30
+ python-version: ${{ matrix.python-version }}
31
+
32
+ - name: Install uv
33
+ uses: astral-sh/setup-uv@v7
34
+
35
+ - name: Install dependencies
36
+ run: uv sync --all-extras
37
+
38
+ - name: Run tests
39
+ run: uv run pytest
40
+
41
+ # Optional: Test if dependencies can be upgraded
42
+ upgrade-test:
43
+ name: Dependency Upgrade Test
44
+ runs-on: ubuntu-latest
45
+ continue-on-error: true # Don't fail the whole workflow if upgrades break
46
+
47
+ steps:
48
+ - uses: actions/checkout@v4
49
+
50
+ - name: Set up Python
51
+ uses: actions/setup-python@v5
52
+ with:
53
+ python-version: '3.14' # Test upgrades on latest Python
54
+
55
+ - name: Install uv
56
+ uses: astral-sh/setup-uv@v7
57
+
58
+ - name: Upgrade all dependencies
59
+ run: uv lock --upgrade
60
+
61
+ - name: Install upgraded dependencies
62
+ run: uv sync --all-extras
63
+
64
+ - name: Run tests with upgraded dependencies
65
+ run: uv run pytest
@@ -0,0 +1,11 @@
1
+ name: Tests
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ test:
7
+ runs-on: python-test-image
8
+ steps:
9
+ - uses: actions/checkout@v4
10
+ - run: uv sync --all-extras
11
+ - run: uv run pytest
@@ -15,3 +15,4 @@ analysis_results.json
15
15
  .claude
16
16
  .cq.json
17
17
 
18
+ docs/plans/*
@@ -39,8 +39,8 @@ uv run ruff check src/
39
39
  **Pipeline flow:** CLI (`cli.py`) → tool registry → execution engine → parsers → metric aggregator → output
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
- - **`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, order, warning/error thresholds. Tools are listed and executed in order.
42
+ - **`tool_registry.py`**: Loads `src/cq/config/config.yaml` at import time via `importlib.resources`, dynamically imports parser classes, builds a `dict[str, ToolConfig]` registry.
43
+ - **`config/config.yaml`** (at `src/cq/config/config.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
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.
@@ -49,6 +49,6 @@ uv run ruff check src/
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, order, and thresholds.
52
+ 1. Add tool entry in `config/config.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.8
3
+ Version: 0.1.9
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
@@ -28,20 +28,16 @@ Description-Content-Type: text/markdown
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
- The code context is expanded if available.
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 pull https://github.com/rhiza-fr/py-cq.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 when looking for the first error in -o llm mode:
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 | bandit | Security vulnerabilities |
86
- | 3 | ruff | Lint / style |
87
- | 4 | ty | Type errors |
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 # Top defect as markdown for LLMs
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,13 @@ 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
+
113
118
  ## Table output
114
119
 
115
120
  ```bash
@@ -121,9 +126,9 @@ cq config path/to/project/ # Show effective tool configuration
121
126
  ┃ Tool ┃ Time ┃ Metric ┃ Score ┃ Status ┃
122
127
  ┡━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━┩
123
128
  │ compile │ 0.42s │ compile │ 1.000 │ OK │
124
- │ bandit │ 0.56s │ security │ 1.000 │ OK │
125
129
  │ ruff │ 0.17s │ lint │ 1.000 │ OK │
126
130
  │ ty │ 0.33s │ type_check │ 1.000 │ OK │
131
+ │ bandit │ 0.56s │ security │ 1.000 │ OK │
127
132
  │ pytest │ 0.91s │ tests │ 1.000 │ OK │
128
133
  │ coverage │ 1.26s │ coverage │ 0.910 │ OK │
129
134
  │ radon cc │ 0.32s │ simplicity │ 0.982 │ OK │
@@ -167,7 +172,7 @@ cq config path/to/project/ # Show effective tool configuration
167
172
 
168
173
  ## Raw output
169
174
  ```bash
170
- > cq check -o raw
175
+ > cq check . -o raw
171
176
  ```
172
177
 
173
178
  ```json
@@ -193,66 +198,63 @@ Add a `[tool.cq]` section to your project's `pyproject.toml`:
193
198
  # Skip tools that are slow or not relevant to your project
194
199
  disable = ["coverage", "interrogate"]
195
200
 
201
+ # Lines of source context shown around each defect in LLM output (default: 15)
202
+ context_lines = 15
203
+
196
204
  # Override warning/error thresholds per tool
197
205
  [tool.cq.thresholds.coverage]
198
206
  warning = 0.9
199
207
  error = 0.7
200
208
  ```
201
209
 
202
- Tool IDs match the keys in `config/tools.yaml`: `compilation`, `bandit`, `ruff`, `ty`, `pytest`, `coverage`, `complexity`, `maintainability`, `halstead`, `vulture`, `interrogate`.
210
+ Tool IDs match the keys in `config/config.yaml`: `compile`, `ruff`, `ty`, `bandit`, `pytest`, `coverage`, `radon-cc`, `radon-mi`, `radon-hal`, `vulture`, `interrogate`.
203
211
 
204
212
 
205
213
  ### Default config
206
214
 
207
215
  ```yaml
208
- tools:
216
+ python:
209
217
 
210
- compilation:
211
- name: "compile"
218
+ compile:
212
219
  command: "{python} -m compileall -r 10 -j 8 {context_path} -x .*venv"
213
220
  parser: "CompileParser"
214
221
  order: 1
215
222
  warning_threshold: 0.9999
216
223
  error_threshold: 0.9999
217
224
 
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
225
  ruff:
227
- name: "ruff"
228
226
  command: "{python} -m ruff check --output-format concise --no-cache {context_path}"
229
227
  parser: "RuffParser"
230
- order: 3
228
+ order: 2
231
229
  warning_threshold: 0.9999
232
230
  error_threshold: 0.9
233
231
 
234
232
  ty:
235
- name: "ty"
236
233
  command: "{python} -m ty check --output-format concise --color never {context_path}"
237
234
  parser: "TyParser"
238
- order: 4
235
+ order: 3
239
236
  warning_threshold: 0.9999
240
237
  error_threshold: 0.8
241
238
  run_in_target_env: true
242
239
  extra_deps:
243
240
  - ty
244
241
 
242
+ bandit:
243
+ command: "{python} -m bandit -r {context_path} -f json -q -s B101 --severity-level medium --exclude {input_path_posix}/.venv,{input_path_posix}/tests"
244
+ parser: "BanditParser"
245
+ order: 4
246
+ warning_threshold: 0.9999
247
+ error_threshold: 0.8
248
+
245
249
  pytest:
246
- name: "pytest"
247
250
  command: "{python} -m pytest -v {context_path}"
248
251
  parser: "PytestParser"
249
252
  order: 5
250
- warning_threshold: 0.7
251
- error_threshold: 0.5
253
+ warning_threshold: 1.0
254
+ error_threshold: 1.0
252
255
  run_in_target_env: true
253
256
 
254
257
  coverage:
255
- name: "coverage"
256
258
  command: "{python} -m coverage run --omit=*/tests/*,*/test_*.py -m pytest {context_path} && {python} -m coverage report --omit=*/tests/*,*/test_*.py"
257
259
  parser: "CoverageParser"
258
260
  order: 6
@@ -263,24 +265,21 @@ tools:
263
265
  - coverage
264
266
  - pytest
265
267
 
266
- complexity:
267
- name: "radon cc"
268
+ radon-cc:
268
269
  command: "{python} -m radon cc --json {context_path}"
269
270
  parser: "ComplexityParser"
270
271
  order: 7
271
272
  warning_threshold: 0.6
272
273
  error_threshold: 0.4
273
274
 
274
- maintainability:
275
- name: "radon mi"
275
+ radon-mi:
276
276
  command: "{python} -m radon mi -s --json {context_path}"
277
277
  parser: "MaintainabilityParser"
278
278
  order: 8
279
279
  warning_threshold: 0.6
280
280
  error_threshold: 0.4
281
281
 
282
- halstead:
283
- name: "radon hal"
282
+ radon-hal:
284
283
  command: "{python} -m radon hal -f --json {context_path}"
285
284
  parser: "HalsteadParser"
286
285
  order: 9
@@ -288,7 +287,6 @@ tools:
288
287
  error_threshold: 0.3
289
288
 
290
289
  vulture:
291
- name: "vulture"
292
290
  command: "{python} -m vulture {context_path} --min-confidence 80 --exclude .venv,dist,.*_cache,docs,.git"
293
291
  parser: "VultureParser"
294
292
  order: 10
@@ -296,7 +294,6 @@ tools:
296
294
  error_threshold: 0.8
297
295
 
298
296
  interrogate:
299
- name: "interrogate"
300
297
  command: "{python} -m interrogate {context_path} -v --fail-under 0"
301
298
  parser: "InterrogateParser"
302
299
  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
- The code context is expanded if available.
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 pull https://github.com/rhiza-fr/py-cq.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 when looking for the first error in -o llm mode:
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 | bandit | Security vulnerabilities |
60
- | 3 | ruff | Lint / style |
61
- | 4 | ty | Type errors |
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 # Top defect as markdown for LLMs
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,13 @@ 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
+
87
92
  ## Table output
88
93
 
89
94
  ```bash
@@ -95,9 +100,9 @@ cq config path/to/project/ # Show effective tool configuration
95
100
  ┃ Tool ┃ Time ┃ Metric ┃ Score ┃ Status ┃
96
101
  ┡━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━┩
97
102
  │ compile │ 0.42s │ compile │ 1.000 │ OK │
98
- │ bandit │ 0.56s │ security │ 1.000 │ OK │
99
103
  │ ruff │ 0.17s │ lint │ 1.000 │ OK │
100
104
  │ ty │ 0.33s │ type_check │ 1.000 │ OK │
105
+ │ bandit │ 0.56s │ security │ 1.000 │ OK │
101
106
  │ pytest │ 0.91s │ tests │ 1.000 │ OK │
102
107
  │ coverage │ 1.26s │ coverage │ 0.910 │ OK │
103
108
  │ radon cc │ 0.32s │ simplicity │ 0.982 │ OK │
@@ -141,7 +146,7 @@ cq config path/to/project/ # Show effective tool configuration
141
146
 
142
147
  ## Raw output
143
148
  ```bash
144
- > cq check -o raw
149
+ > cq check . -o raw
145
150
  ```
146
151
 
147
152
  ```json
@@ -167,66 +172,63 @@ Add a `[tool.cq]` section to your project's `pyproject.toml`:
167
172
  # Skip tools that are slow or not relevant to your project
168
173
  disable = ["coverage", "interrogate"]
169
174
 
175
+ # Lines of source context shown around each defect in LLM output (default: 15)
176
+ context_lines = 15
177
+
170
178
  # Override warning/error thresholds per tool
171
179
  [tool.cq.thresholds.coverage]
172
180
  warning = 0.9
173
181
  error = 0.7
174
182
  ```
175
183
 
176
- Tool IDs match the keys in `config/tools.yaml`: `compilation`, `bandit`, `ruff`, `ty`, `pytest`, `coverage`, `complexity`, `maintainability`, `halstead`, `vulture`, `interrogate`.
184
+ Tool IDs match the keys in `config/config.yaml`: `compile`, `ruff`, `ty`, `bandit`, `pytest`, `coverage`, `radon-cc`, `radon-mi`, `radon-hal`, `vulture`, `interrogate`.
177
185
 
178
186
 
179
187
  ### Default config
180
188
 
181
189
  ```yaml
182
- tools:
190
+ python:
183
191
 
184
- compilation:
185
- name: "compile"
192
+ compile:
186
193
  command: "{python} -m compileall -r 10 -j 8 {context_path} -x .*venv"
187
194
  parser: "CompileParser"
188
195
  order: 1
189
196
  warning_threshold: 0.9999
190
197
  error_threshold: 0.9999
191
198
 
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
199
  ruff:
201
- name: "ruff"
202
200
  command: "{python} -m ruff check --output-format concise --no-cache {context_path}"
203
201
  parser: "RuffParser"
204
- order: 3
202
+ order: 2
205
203
  warning_threshold: 0.9999
206
204
  error_threshold: 0.9
207
205
 
208
206
  ty:
209
- name: "ty"
210
207
  command: "{python} -m ty check --output-format concise --color never {context_path}"
211
208
  parser: "TyParser"
212
- order: 4
209
+ order: 3
213
210
  warning_threshold: 0.9999
214
211
  error_threshold: 0.8
215
212
  run_in_target_env: true
216
213
  extra_deps:
217
214
  - ty
218
215
 
216
+ bandit:
217
+ command: "{python} -m bandit -r {context_path} -f json -q -s B101 --severity-level medium --exclude {input_path_posix}/.venv,{input_path_posix}/tests"
218
+ parser: "BanditParser"
219
+ order: 4
220
+ warning_threshold: 0.9999
221
+ error_threshold: 0.8
222
+
219
223
  pytest:
220
- name: "pytest"
221
224
  command: "{python} -m pytest -v {context_path}"
222
225
  parser: "PytestParser"
223
226
  order: 5
224
- warning_threshold: 0.7
225
- error_threshold: 0.5
227
+ warning_threshold: 1.0
228
+ error_threshold: 1.0
226
229
  run_in_target_env: true
227
230
 
228
231
  coverage:
229
- name: "coverage"
230
232
  command: "{python} -m coverage run --omit=*/tests/*,*/test_*.py -m pytest {context_path} && {python} -m coverage report --omit=*/tests/*,*/test_*.py"
231
233
  parser: "CoverageParser"
232
234
  order: 6
@@ -237,24 +239,21 @@ tools:
237
239
  - coverage
238
240
  - pytest
239
241
 
240
- complexity:
241
- name: "radon cc"
242
+ radon-cc:
242
243
  command: "{python} -m radon cc --json {context_path}"
243
244
  parser: "ComplexityParser"
244
245
  order: 7
245
246
  warning_threshold: 0.6
246
247
  error_threshold: 0.4
247
248
 
248
- maintainability:
249
- name: "radon mi"
249
+ radon-mi:
250
250
  command: "{python} -m radon mi -s --json {context_path}"
251
251
  parser: "MaintainabilityParser"
252
252
  order: 8
253
253
  warning_threshold: 0.6
254
254
  error_threshold: 0.4
255
255
 
256
- halstead:
257
- name: "radon hal"
256
+ radon-hal:
258
257
  command: "{python} -m radon hal -f --json {context_path}"
259
258
  parser: "HalsteadParser"
260
259
  order: 9
@@ -262,7 +261,6 @@ tools:
262
261
  error_threshold: 0.3
263
262
 
264
263
  vulture:
265
- name: "vulture"
266
264
  command: "{python} -m vulture {context_path} --min-confidence 80 --exclude .venv,dist,.*_cache,docs,.git"
267
265
  parser: "VultureParser"
268
266
  order: 10
@@ -270,7 +268,6 @@ tools:
270
268
  error_threshold: 0.8
271
269
 
272
270
  interrogate:
273
- name: "interrogate"
274
271
  command: "{python} -m interrogate {context_path} -v --fail-under 0"
275
272
  parser: "InterrogateParser"
276
273
  order: 11
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-code-quality"
3
- version = "0.1.8"
3
+ version = "0.1.9"
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"