python-code-quality 0.1.16__py3-none-any.whl → 0.2.1__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-code-quality
3
- Version: 0.1.16
3
+ Version: 0.2.1
4
4
  Summary: Python Code Quality Analysis Tool - feed the results from 11 CQ tools straight into an LLM. Minimal tokens.
5
5
  Author: Chris Kilner
6
6
  Author-email: Chris Kilner <chris@rhiza.fr>
@@ -15,10 +15,10 @@ Requires-Dist: interrogate>=1.7.0
15
15
  Requires-Dist: pytest>=8.4.0
16
16
  Requires-Dist: pytest-cov>=6.1.1
17
17
  Requires-Dist: pytest-json-report>=1.5.0
18
- Requires-Dist: pyyaml>=6.0.2
19
18
  Requires-Dist: radon>=6.0.1
20
19
  Requires-Dist: rich>=14.0.0
21
20
  Requires-Dist: ruff>=0.14.1
21
+ Requires-Dist: tomlkit>=0.13.0
22
22
  Requires-Dist: ty>=0.0.17
23
23
  Requires-Dist: typer>=0.16.0
24
24
  Requires-Dist: vulture>=2.14
@@ -88,6 +88,7 @@ Diskcache is used to cache tool output for lightning fast re-runs. Sane defaults
88
88
  ```bash
89
89
  cq check . # Table overview of scores for humans
90
90
  cq check . -o llm # Top defect as markdown for LLMs
91
+ cq check . -o llm-json # Top defect as JSON with fingerprint (for automation)
91
92
  cq check . -o score # Numeric score only for CI
92
93
  cq check . -o json # Detailed parsed JSON output for jq
93
94
  cq check . -o raw # Raw tool output for debug
@@ -97,7 +98,9 @@ cq check . --skip bandit # Skip specific tools
97
98
  cq check . --exclude demo # Exclude paths from all tools
98
99
  cq check . --workers 1 # Run sequentially if you like things slow
99
100
  cq check . --clear-cache # Clear cached results before running (rarely needed)
101
+ cq check . -o llm --hint # Append "run cq again to verify" (for human workflows)
100
102
  cq config path/to/project/ # Show effective tool configuration
103
+ cq is-fixed <fingerprint> # Check whether a specific issue has been resolved
101
104
  ```
102
105
 
103
106
  **Exit codes:** `cq check` exits with code `1` if any tool metric falls below its `error_threshold`, making it suitable as a CI gate:
@@ -107,6 +110,88 @@ cq check . && deploy # block deploy on errors
107
110
  cq check . -o score # print score, exit 1 on errors
108
111
  ```
109
112
 
113
+ ## Fingerprint-based verification
114
+
115
+ `-o llm-json` returns a JSON object with a stable `id` fingerprint you can pass back to `cq is-fixed` to check whether a specific issue has been resolved — without re-running all tools:
116
+
117
+ ```bash
118
+ # Get the top defect as JSON
119
+ cq check . -o llm-json
120
+ ```
121
+
122
+ ```json
123
+ {
124
+ "id": "ruff::my-project::src/foo.py::42::E501",
125
+ "file": "src/foo.py",
126
+ "project": "/home/user/my-project",
127
+ "message": "..."
128
+ }
129
+ ```
130
+
131
+ ```bash
132
+ # After fixing, verify only that issue (fast — reruns one tool on one file)
133
+ cq is-fixed "ruff::my-project::src/foo.py::42::E501"
134
+ ```
135
+
136
+ This is most useful in automation: fix an issue, confirm it's gone, then fetch the next one. Note that this only verifies the original issue is no longer present — a full project-wide scan is still needed to guarantee no regressions, assuming you have sufficient tests.
137
+
138
+ ## Python Library
139
+
140
+ `py_cq` can be used as a library — no subprocess required. Instantiate `CQ` with a project root; config is loaded once from `pyproject.toml`.
141
+
142
+ ```python
143
+ from py_cq import CQ
144
+
145
+ cq = CQ(".") # load config from ./pyproject.toml
146
+ cq = CQ(".", skip=["bandit"]) # skip specific tools
147
+ cq = CQ(".", only=["ruff", "ty"]) # run only specific tools
148
+ cq = CQ(".", workers=4) # control parallelism
149
+ ```
150
+
151
+ **Methods mirror the CLI and return data objects:**
152
+
153
+ ```python
154
+ # list[ToolResult] — all parsed results before aggregation (-o raw / -o json)
155
+ results = cq.raw()
156
+
157
+ # CombinedToolResults — aggregated score and per-tool results (-o score / table)
158
+ combined = cq.check()
159
+ print(combined.score) # float
160
+ print(combined.tool_results) # list[ToolResult]
161
+
162
+ # dict — top defect as JSON, equivalent to -o llm-json
163
+ issue = cq.check_llm_json()
164
+ issue["id"] # fingerprint: "ruff::project::src/foo.py::42::E501"
165
+ issue["file"] # "src/foo.py"
166
+ issue["message"] # markdown prompt ready to send to an LLM
167
+ issue["project"] # absolute project root path
168
+
169
+ # bool — True if the fingerprinted issue is gone
170
+ # Reruns only the affected tool on the affected file — much faster than a full check.
171
+ # Pass the id from check_llm_json; also available as `cq is-fixed <id>` on the CLI.
172
+ fixed = cq.is_fixed(issue["id"])
173
+ ```
174
+
175
+ `check_llm_json` accepts the same options as `cq check . -o llm-json`:
176
+
177
+ ```python
178
+ issue = cq.check_llm_json(limit=3, silence=["src/generated.py"], hint=True)
179
+ ```
180
+
181
+ **Typical automation loop:**
182
+
183
+ ```python
184
+ from py_cq import CQ
185
+
186
+ cq = CQ(".")
187
+ while True:
188
+ issue = cq.check_llm_json()
189
+ if issue["id"] is None:
190
+ break # all clear
191
+ fix(issue["message"]) # call your LLM
192
+ assert cq.is_fixed(issue["id"])
193
+ ```
194
+
110
195
  ## Claude Code Integration
111
196
 
112
197
  Add a stop hook to your project's `.claude/settings.json` so Claude automatically checks quality after each session and loops until clean:
@@ -346,7 +431,7 @@ python:
346
431
  error_threshold: 0.8
347
432
 
348
433
  interrogate:
349
- command: "{python} -m interrogate \"{context_path}\" -e tests{exclude} -v --fail-under 0"
434
+ command: "{python} -m interrogate \"{context_path}\"{exclude} -v --fail-under 0"
350
435
  exclude_format: " -e {path}"
351
436
  parser: "InterrogateParser"
352
437
  order: 11
@@ -0,0 +1,35 @@
1
+ py_cq/__init__.py,sha256=-C6_QzQeydxft3ndlExTgWnvlqH8fJNPJxfySXULQmU,56
2
+ py_cq/api.py,sha256=iyiL5WDdQDYSwkqmZ_AuFY8-uJ-mM9WWAQhpdjqVk20,9170
3
+ py_cq/cli.py,sha256=5Vw3BPyqjKPeWQBXYCMs_ps7WJmjdhxQFgaNS9lAxiQ,13391
4
+ py_cq/config/__init__.py,sha256=f0wc51O_3kGDTZUnCbGv8_zWnC5yYGl4NWcf2buSImQ,670
5
+ py_cq/config/config.toml,sha256=L8WKLQg4utDvV1ZwcOCSi6uJAtE1mZPvY-_HJVOT2dk,2841
6
+ py_cq/context_hash.py,sha256=UKVrKpBYYw68tVcSAs6ZXkHXV3asQvsn6R9NINOHQ4M,3323
7
+ py_cq/execution_engine.py,sha256=GdrLk8Gok5Kf1FSr39LqMfj1GOoLyJeSfU9qWT_7n4Y,14147
8
+ py_cq/language_detector.py,sha256=rh5bAA3LyCQR3RODmx_EalqRNYvSYK2SJzh2hIkxOUo,1139
9
+ py_cq/llm_formatter.py,sha256=nCV3nFdxJAi2CnH9FKb5tB-0w-yjLWmjywQI5GJ2cSU,8412
10
+ py_cq/localtypes.py,sha256=ELzOFOFlrLSq5Wgj5LvBIPhWR8wfe7lQYkcbINhqE3A,7559
11
+ py_cq/main.py,sha256=5dgr8Zk1LNK5M-0wncYVksBp0qE-jWZYqmOXfb1eOhk,400
12
+ py_cq/metric_aggregator.py,sha256=M2ymo62S7p7qPUqjjoiPg4IVyXQhLMuTr9-jxLiFjCY,853
13
+ py_cq/parsers/__init__.py,sha256=uCy7R0cnvDPNvB5aWMLgM3FB9nDcu1jwUmznFMRROSY,28
14
+ py_cq/parsers/banditparser.py,sha256=YqpWljU5D4IhLdtzat7XVGBm9NXrbOQbr9LI9NKybK8,3298
15
+ py_cq/parsers/common.py,sha256=dU5JCbPJdngbtPjSqnP0kxT2Oux5D-iYiaBMz8KcZCw,13672
16
+ py_cq/parsers/compileparser.py,sha256=jl0DloyXTKqVHRIUft-t5lvJOndY-PdLPrXB38v9zuA,6927
17
+ py_cq/parsers/complexityparser.py,sha256=dOUCpCDcpNaQXZx_xC61X4dTMktDb9x_nh9tDoWjb5g,5509
18
+ py_cq/parsers/coverageparser.py,sha256=t15CFfTJv1QorhopyurDvY7Jr5JJOMLfet0fqttU3O0,7848
19
+ py_cq/parsers/exitcodeparser.py,sha256=b4LWIwwklPwCX3MnqNsGQC1slQU56DKE05vJ0OdWVoA,957
20
+ py_cq/parsers/halsteadparser.py,sha256=cfUNsLUYt74NMQW1sBkktn8AH7jQ_EU5IMFrLgIdk5M,10165
21
+ py_cq/parsers/interrogateparser.py,sha256=cc5Q4mOQlLUIPl3XUyH29QJ_JMBzAA7SiPAAiNGDJdM,11546
22
+ py_cq/parsers/linecountparser.py,sha256=-hqebwG4j7_QugzGLUUQRcPukGHicV2nbIp1pR8ZcAw,1310
23
+ py_cq/parsers/maintainabilityparser.py,sha256=3a4m93XNtUurw3IxEI1Iae9idgJY6qiEt7HMYl1wTu0,4793
24
+ py_cq/parsers/pytestparser.py,sha256=rhzFzsBEXv1xloOuEjQyBn8Vp0AyGoq2t5siyAwWWaA,11549
25
+ py_cq/parsers/regexcountparser.py,sha256=zGzHEYDIori6aiWnVkRFixdE7fWt66npAAWjzvpoBF4,1592
26
+ py_cq/parsers/ruffparser.py,sha256=ofQyVUuc0JK-6pVdwsRZteCWmNBOIfHzZloVPSH7_wI,7608
27
+ py_cq/parsers/typarser.py,sha256=x835OrapG1aUteW2NhC5U0bhj1w1pMtbfmbvb74-1AI,8077
28
+ py_cq/parsers/vultureparser.py,sha256=-yGAXzRY4_fzLRTk7dfwDrrRZ-n97j1Ph5_xo3c6TfU,2229
29
+ py_cq/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
+ py_cq/table_formatter.py,sha256=2_WZ2RHCwsdna0bzP6ZFxB2YtHtW95wxJB1yBXgCIDo,1610
31
+ py_cq/tool_registry.py,sha256=kWmGsqc_0ryYiW-hqUh03WdY1SJQ0wn5Z5ydH72VJK4,1718
32
+ python_code_quality-0.2.1.dist-info/WHEEL,sha256=Q9FtwzuR2QE37l-JIkuyklGnJJiCBHKnsPVQ9vzCMzQ,81
33
+ python_code_quality-0.2.1.dist-info/entry_points.txt,sha256=cfWbTw7eYO6Trv1-Z_odL6Zta9CqYU6Vk9lAHdfB60Q,40
34
+ python_code_quality-0.2.1.dist-info/METADATA,sha256=fXzBeBOrxJ9jO-fglDIUiZiqCnxjnN1f4OBcsBX4gn8,15650
35
+ python_code_quality-0.2.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.11.7
2
+ Generator: uv 0.11.17
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
py_cq/config/config.yaml DELETED
@@ -1,94 +0,0 @@
1
- python:
2
-
3
- compile:
4
- command: "{python} -m compileall -r 10 -j 8 \"{context_path}\" -x .*venv"
5
- parser: "CompileParser"
6
- order: 1
7
- warning_threshold: 0.9999
8
- error_threshold: 0.9999
9
-
10
- ruff:
11
- command: "{python} -m ruff check --output-format concise --no-cache \"{context_path}\"{exclude}"
12
- exclude_format: " --exclude {path}"
13
- parser: "RuffParser"
14
- order: 2
15
- warning_threshold: 0.9999
16
- error_threshold: 0.9
17
-
18
- ty:
19
- command: "{python} -m ty check --output-format concise --color never \"{context_path}\"{exclude}"
20
- exclude_format: " --exclude {path}"
21
- parser: "TyParser"
22
- order: 3
23
- warning_threshold: 0.9999
24
- error_threshold: 0.8
25
- run_in_target_env: true
26
- extra_deps:
27
- - ty
28
-
29
- bandit:
30
- command: "{python} -m bandit -r \"{context_path}\" -f json -q -s B101 --severity-level medium --exclude \"{input_path_posix}/.venv,{input_path_posix}/tests{exclude}\""
31
- exclude_format: ",{input_path_posix}/{path}"
32
- parser: "BanditParser"
33
- order: 4
34
- warning_threshold: 0.9999
35
- error_threshold: 0.8
36
-
37
- pytest:
38
- command: "{python} -m pytest -v \"{context_path}\"{exclude}"
39
- exclude_format: " --ignore {path}"
40
- parser: "PytestParser"
41
- order: 5
42
- warning_threshold: 1.0
43
- error_threshold: 1.0
44
- run_in_target_env: true
45
- extra_deps:
46
- - pytest
47
-
48
- coverage:
49
- command: "{python} -m coverage run --omit=*/tests/*,*/test_*.py -m pytest \"{context_path}\" && {python} -m coverage report --omit=*/tests/*,*/test_*.py"
50
- parser: "CoverageParser"
51
- order: 6
52
- warning_threshold: 0.9
53
- error_threshold: 0.5
54
- run_in_target_env: true
55
- extra_deps:
56
- - coverage
57
- - pytest
58
-
59
- radon-cc:
60
- command: "{python} -m radon cc --json \"{context_path}\""
61
- parser: "ComplexityParser"
62
- order: 7
63
- warning_threshold: 0.6
64
- error_threshold: 0.4
65
-
66
- radon-mi:
67
- command: "{python} -m radon mi -s --json \"{context_path}\""
68
- parser: "MaintainabilityParser"
69
- order: 8
70
- warning_threshold: 0.6
71
- error_threshold: 0.4
72
-
73
- radon-hal:
74
- command: "{python} -m radon hal -f --json \"{context_path}\""
75
- parser: "HalsteadParser"
76
- order: 9
77
- warning_threshold: 0.5
78
- error_threshold: 0.3
79
-
80
- vulture:
81
- command: "{python} -m vulture \"{context_path}\" --min-confidence 80 --exclude .venv,dist,.*_cache,docs,.git{exclude}"
82
- exclude_format: ",{path}"
83
- parser: "VultureParser"
84
- order: 10
85
- warning_threshold: 0.9999
86
- error_threshold: 0.8
87
-
88
- interrogate:
89
- command: "{python} -m interrogate \"{context_path}\" -e tests{exclude} -v --fail-under 0"
90
- exclude_format: " -e {path}"
91
- parser: "InterrogateParser"
92
- order: 11
93
- warning_threshold: 0.8
94
- error_threshold: 0.3
@@ -1,34 +0,0 @@
1
- py_cq/__init__.py,sha256=U2ysDtSFdv2mlXZz4w1Q42pfgfi6YY_3Ln24bkZq14I,260
2
- py_cq/cli.py,sha256=bbol813ZJMSmGixNUrNfmWqPv4evil0chwyIChEgc20,11265
3
- py_cq/config/__init__.py,sha256=f0wc51O_3kGDTZUnCbGv8_zWnC5yYGl4NWcf2buSImQ,670
4
- py_cq/config/config.yaml,sha256=TPZJogpWbyf0Ml2mHrHzTNyTik3k07KPVGsA1wT9GEc,2696
5
- py_cq/context_hash.py,sha256=h-i7Rhd7AUfLv9SkQvE79bjJvTsm_ZwoVwSmUKXWmfM,2977
6
- py_cq/execution_engine.py,sha256=nAsCvldUfDagZnfOwXf4A37VQLxCGiv12T8ymutflC4,8697
7
- py_cq/language_detector.py,sha256=6av5HaimcZ54RkN69xQmGgC0mxtTvGzPV3SL8NGG8Uc,1116
8
- py_cq/llm_formatter.py,sha256=l74O5iqNWMR16789Xncoi93xOqgQJhW5IQK9_OyA9mE,1632
9
- py_cq/localtypes.py,sha256=UGI2kl1xB2TedKGByth3URiqJKY56YZMNds1hnLEzvU,6228
10
- py_cq/main.py,sha256=5dgr8Zk1LNK5M-0wncYVksBp0qE-jWZYqmOXfb1eOhk,400
11
- py_cq/metric_aggregator.py,sha256=M2ymo62S7p7qPUqjjoiPg4IVyXQhLMuTr9-jxLiFjCY,853
12
- py_cq/parsers/__init__.py,sha256=YS3wPS0cMNU80zkdSZBEZOkqDKE6Jk--0Xd_bX7VMcA,27
13
- py_cq/parsers/banditparser.py,sha256=BbwDFyvG9uA-S7-u4QETCXbLRx62juxZUl5IHSQgoCs,2667
14
- py_cq/parsers/common.py,sha256=X4T8pbToJH-xFYmTvKq1k7bEAmA71XxBXbp8rXYUMBU,8654
15
- py_cq/parsers/compileparser.py,sha256=_SQzfADuZEDLn9M67PR8cScMyNQH9UJijGQIS54WtQs,6883
16
- py_cq/parsers/complexityparser.py,sha256=SHle0oF179aDTHV6ENDWg16w50g0of9QMG-Yah6QAcU,4220
17
- py_cq/parsers/coverageparser.py,sha256=cfkcEBWprjtdn16tBDEix9p30xL3eYcARvpLzpmJgIk,4066
18
- py_cq/parsers/exitcodeparser.py,sha256=ZFL3EbPhGvFjm2qZhfLD1_5dNjR9ULYNQpKgy0Z_dGo,729
19
- py_cq/parsers/halsteadparser.py,sha256=cRVi2su7v7DtRF339V4tmc8QY604wOYkg4qZ0RICCgM,9411
20
- py_cq/parsers/interrogateparser.py,sha256=7ROwvJKz-OBLd2ACsi2RINfYr3yjav-I3_SmYlStVTA,2309
21
- py_cq/parsers/linecountparser.py,sha256=nxWvFntuR8T6FDGpafrlevvt-jR3rwkbVo_t2JX4TF0,1067
22
- py_cq/parsers/maintainabilityparser.py,sha256=65w36toQ-gMhBliyvmMVVVNolrcB3xD4ebu5eJ7T1yQ,3658
23
- py_cq/parsers/pytestparser.py,sha256=_seSfvAD_88A-yDyWYpLuIrLL2Wjp3rbcqG3fNCNEnA,9705
24
- py_cq/parsers/regexcountparser.py,sha256=KSoNh2spucXU06pxxr2QW0LrPLfJkFMAsmSgjooaFv0,1316
25
- py_cq/parsers/ruffparser.py,sha256=C6NPA1ZIXtvLGb5WOApyixT6bGwfIT6th6Bhc65UVhE,2500
26
- py_cq/parsers/typarser.py,sha256=rjlM-fiKV5EoYCHV-6cHhvG2rJ7vz6F_aSzvbuQ7UJY,3361
27
- py_cq/parsers/vultureparser.py,sha256=A9AeB3GczXbWNgHIkI7MPlobxccR5FJMV1delQppxXY,2226
28
- py_cq/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
- py_cq/table_formatter.py,sha256=LpyVcl03OAjcifP-9qDy_BJQGyqTe0PJjYRrRCjo9Rc,1293
30
- py_cq/tool_registry.py,sha256=UfmwJH8rtmTY9kX02QaE4OJIWyboTMcXUavgb9R4fxc,1648
31
- python_code_quality-0.1.16.dist-info/WHEEL,sha256=WvwXFgRajeoYkfRVmDhkP4Qlqo31Mk687zIO2QQoFmw,80
32
- python_code_quality-0.1.16.dist-info/entry_points.txt,sha256=cfWbTw7eYO6Trv1-Z_odL6Zta9CqYU6Vk9lAHdfB60Q,40
33
- python_code_quality-0.1.16.dist-info/METADATA,sha256=FlRLE3X9lki316FiuZlD1twudLoSDPRMv9Uv0nHPWH8,12749
34
- python_code_quality-0.1.16.dist-info/RECORD,,