python-code-quality 0.2.2__py3-none-any.whl → 0.2.3__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.
py_cq/api.py CHANGED
@@ -148,13 +148,14 @@ class CQ:
148
148
  if clear_cache:
149
149
  _cache.clear()
150
150
 
151
- def raw(self, *, early_exit: bool = False) -> list[ToolResult]:
151
+ def raw(self, *, early_exit: bool = False, order: str = "severity") -> list[ToolResult]:
152
152
  """Run all tools and return parsed results before aggregation."""
153
153
  return run_tools(
154
154
  self._registry.values(),
155
155
  str(self.path),
156
156
  self._workers,
157
157
  early_exit=early_exit,
158
+ order=order,
158
159
  excludes=self._excludes,
159
160
  )
160
161
 
@@ -168,22 +169,42 @@ class CQ:
168
169
  limit: int = 1,
169
170
  silence: list[str] | None = None,
170
171
  hint: bool = False,
172
+ order: str = "severity",
171
173
  ) -> dict:
172
174
  """Return the top defect as a dict with keys: id, file, project, message.
173
175
 
174
- Stops running tools after the first error (early_exit) for speed.
176
+ Stops running tools after the first issue (early_exit) for speed.
177
+ ``order="phase"`` selects the first issue of the earliest non-clean
178
+ phase (dependency order); ``"severity"`` (default) selects the most
179
+ severe issue first.
175
180
  """
176
- results = self.raw(early_exit=True)
181
+ _silence = silence or []
182
+ results = self.raw(early_exit=True, order=order)
177
183
  combined = aggregate_metrics(str(self.path), results)
178
- return format_for_llm_json(
184
+ issue = format_for_llm_json(
179
185
  self._registry,
180
186
  combined,
181
187
  context_lines=self._context_lines,
182
188
  hint=hint,
183
189
  limit=limit,
184
- silence=silence or [],
190
+ silence=_silence,
185
191
  project_root=self._project_root,
186
192
  )
193
+ if issue.get("id") is None and _silence:
194
+ # Early-exit stopped at a phase whose only issues are silenced.
195
+ # Re-run all tools to surface issues in later phases.
196
+ results = self.raw(early_exit=False, order=order)
197
+ combined = aggregate_metrics(str(self.path), results)
198
+ issue = format_for_llm_json(
199
+ self._registry,
200
+ combined,
201
+ context_lines=self._context_lines,
202
+ hint=hint,
203
+ limit=limit,
204
+ silence=_silence,
205
+ project_root=self._project_root,
206
+ )
207
+ return issue
187
208
 
188
209
  def is_fixed(self, fingerprint: str) -> bool:
189
210
  """Return True if the fingerprinted issue is no longer present.
py_cq/cli.py CHANGED
@@ -67,6 +67,13 @@ class OutputMode(str, Enum):
67
67
  RAW = "raw"
68
68
 
69
69
 
70
+ class OrderMode(str, Enum):
71
+ """Selection order for the top issue in -o llm/llm-json."""
72
+
73
+ SEVERITY = "severity"
74
+ PHASE = "phase"
75
+
76
+
70
77
  def _version_callback(value: bool) -> None:
71
78
  if not value:
72
79
  return
@@ -159,6 +166,11 @@ def check(
159
166
  "-s",
160
167
  help="Silence issues from -o llm output (e.g. -s src/foo.py or -s src/foo.py:42:E501)",
161
168
  ),
169
+ order: OrderMode = typer.Option(
170
+ OrderMode.SEVERITY,
171
+ "--order",
172
+ help="Issue selection order for -o llm/llm-json: severity (most severe first) or phase (first non-clean phase, in dependency order)",
173
+ ),
162
174
  ):
163
175
  """Feed the results from 11+ code quality tools to an LLM. Try: cq check . -o llm""" # --help
164
176
  path_obj = Path(path)
@@ -200,7 +212,7 @@ def check(
200
212
 
201
213
  is_llm = output in (OutputMode.LLM, OutputMode.LLM_JSON)
202
214
  t0 = time.perf_counter()
203
- tool_results = cq.raw(early_exit=is_llm)
215
+ tool_results = cq.raw(early_exit=is_llm, order=order.value)
204
216
  total_s = time.perf_counter() - t0
205
217
  combined = aggregate_metrics(path, tool_results)
206
218
 
py_cq/config/config.toml CHANGED
@@ -53,43 +53,47 @@ error_threshold = 0.5
53
53
  run_in_target_env = true
54
54
  extra_deps = ["coverage", "pytest"]
55
55
  skip_for_file = true
56
+ skip_if = "pytest"
57
+
58
+ [python.vulture]
59
+ command = "{python} -m vulture \"{context_path}\" --min-confidence 80 --exclude .venv,dist,.*_cache,docs,.git,.claude{exclude}"
60
+ exclude_format = ",{path}"
61
+ parser = "VultureParser"
62
+ order = 7
63
+ warning_threshold = 0.9999
64
+ error_threshold = 0.8
65
+
66
+ [python.interrogate]
67
+ command = "{python} -m interrogate \"{context_path}\" -e .claude{exclude} -v --fail-under 0"
68
+ exclude_format = " -e {path}"
69
+ parser = "InterrogateParser"
70
+ order = 8
71
+ warning_threshold = 0.8
72
+ error_threshold = 0.5
73
+
74
+ [python.interrogate.parser_config]
75
+ skip_empty_init = true
56
76
 
57
77
  [python.radon-cc]
58
78
  command = "{python} -m radon cc --json --exclude '.claude/**' \"{context_path}\""
59
79
  parser = "ComplexityParser"
60
- order = 7
80
+ order = 9
81
+ gate_strict = false
61
82
  warning_threshold = 0.6
62
83
  error_threshold = 0.4
63
84
 
64
85
  [python.radon-mi]
65
86
  command = "{python} -m radon mi -s --json --exclude '.claude/**' \"{context_path}\""
66
87
  parser = "MaintainabilityParser"
67
- order = 8
88
+ order = 10
89
+ gate_strict = false
68
90
  warning_threshold = 0.6
69
91
  error_threshold = 0.4
70
92
 
71
93
  [python.radon-hal]
72
94
  command = "{python} -m radon hal -f --json --exclude '.claude/**' \"{context_path}\""
73
95
  parser = "HalsteadParser"
74
- order = 9
75
- warning_threshold = 0.5
76
- error_threshold = 0.3
77
-
78
- [python.vulture]
79
- command = "{python} -m vulture \"{context_path}\" --min-confidence 80 --exclude .venv,dist,.*_cache,docs,.git,.claude{exclude}"
80
- exclude_format = ",{path}"
81
- parser = "VultureParser"
82
- order = 10
83
- warning_threshold = 0.9999
84
- error_threshold = 0.8
85
-
86
- [python.interrogate]
87
- command = "{python} -m interrogate \"{context_path}\" -e .claude{exclude} -v --fail-under 0"
88
- exclude_format = " -e {path}"
89
- parser = "InterrogateParser"
90
96
  order = 11
91
- warning_threshold = 0.8
97
+ gate_strict = false
98
+ warning_threshold = 0.35
92
99
  error_threshold = 0.3
93
-
94
- [python.interrogate.parser_config]
95
- skip_empty_init = true
py_cq/context_hash.py CHANGED
@@ -41,12 +41,15 @@ def get_sigs(path: str):
41
41
  >>> get_sigs('/tmp/project')
42
42
  ['/tmp/project/main.py:1024:1680000000.0', ...]
43
43
  """
44
+ _ENV_FILES = {".python-version", "pyproject.toml", "uv.lock"}
44
45
  items = []
45
46
  with os.scandir(path) as entries:
46
47
  for entry in entries:
47
48
  # Use follow_symlinks=False to prevent cache poisoning from
48
49
  # symlinks pointing outside the project tree (M-2)
49
- if entry.is_file(follow_symlinks=False) and entry.name.endswith(".py"):
50
+ if entry.is_file(follow_symlinks=False) and (
51
+ entry.name.endswith(".py") or entry.name in _ENV_FILES
52
+ ):
50
53
  stat_info = entry.stat(follow_symlinks=False)
51
54
  items.append(f"{entry.path}:{stat_info.st_size}:{stat_info.st_mtime}")
52
55
  if entry.is_dir(follow_symlinks=False) and entry.name not in [
py_cq/execution_engine.py CHANGED
@@ -257,6 +257,7 @@ def run_tools(
257
257
  path: str,
258
258
  max_workers: int = 0,
259
259
  early_exit: bool = False,
260
+ order: str = "severity",
260
261
  excludes: list[str] | None = None,
261
262
  project_root: str | None = None,
262
263
  ) -> list[ToolResult]:
@@ -336,28 +337,71 @@ def run_tools(
336
337
  log.warning(f"Early exit: skipped {n_skipped} tool(s): {remaining}")
337
338
  break
338
339
  _, tr = prioritized[-1]
339
- if tr.metrics and min(tr.metrics.values()) < tool_config.error_threshold:
340
+ # order="phase" stops at the first phase with *any* finding (not
341
+ # clean), so phase order is absolute and independent of error/warning
342
+ # threshold tuning; "severity" stops at the first error.
343
+ threshold = (
344
+ tool_config.warning_threshold
345
+ if order == "phase"
346
+ else tool_config.error_threshold
347
+ )
348
+ if tr.metrics and min(tr.metrics.values()) < threshold:
340
349
  n_skipped = n_total - i - 1
341
350
  if n_skipped:
342
351
  remaining = ", ".join(tc.name for tc in sorted_configs[i + 1 :])
343
352
  log.debug(
344
- f"Error threshold hit at {tool_config.name}: skipped {n_skipped} tool(s): {remaining}"
353
+ f"{'Not clean' if order == 'phase' else 'Error threshold hit'} at {tool_config.name}: skipped {n_skipped} tool(s): {remaining}"
345
354
  )
346
355
  break
347
356
  log.info(f"cq run_tools elapsed: {time.perf_counter() - t_start:.2f}s")
348
357
  return [tr for _, tr in sorted(prioritized)]
358
+ configs_by_name = {tc.name: tc for tc in tool_configs}
359
+
360
+ def _run_if_dep_passed(dep_future, tool_config: ToolConfig, dep_config: ToolConfig) -> tuple[int, ToolResult]:
361
+ # Wait for the dependency within the same thread pool - no phase split,
362
+ # so all other tools keep running in parallel while we block here.
363
+ try:
364
+ _, dep_tr = dep_future.result()
365
+ except Exception:
366
+ return _run_and_parse(tool_config)
367
+ if dep_tr.metrics and min(dep_tr.metrics.values()) < dep_config.error_threshold:
368
+ # Dependency failed its error threshold: skip this tool and return a
369
+ # synthetic empty result. Coverage declares skip_if = "pytest" so that
370
+ # it never re-invokes pytest (which the coverage command does internally)
371
+ # when we already know pytest is broken.
372
+ log.debug(f"{tool_config.name}: skipping because {tool_config.skip_if} failed")
373
+ t0 = time.perf_counter()
374
+ tr = tool_config.parser_class(tool_config.parser_config).parse(
375
+ RawResult(tool_name=tool_config.name)
376
+ )
377
+ tr.duration_s = time.perf_counter() - t0
378
+ return tool_config.order, tr
379
+ return _run_and_parse(tool_config)
380
+
381
+ timings: list[tuple[int, str, float]] = []
382
+ # Sort by order so that when we build name_to_future below, a dependency's
383
+ # future is always registered before the tool that declares skip_if on it.
384
+ sorted_configs = sorted(tool_configs, key=lambda tc: tc.order)
385
+ name_to_future: dict[str, Any] = {}
349
386
  with ThreadPoolExecutor(max_workers=max_workers or len(tool_configs)) as executor:
350
- future_to_tool = {
351
- executor.submit(_run_and_parse, tool_config): tool_config
352
- for tool_config in tool_configs
353
- }
354
- timings: list[tuple[int, str, float]] = []
387
+ future_to_tool = {}
388
+ for tc in sorted_configs:
389
+ dep_name = tc.skip_if
390
+ dep_future = name_to_future.get(dep_name) if dep_name else None
391
+ dep_config = configs_by_name.get(dep_name) if dep_name else None
392
+ if dep_future and dep_config:
393
+ # Submit a wrapper that waits on the dependency future first.
394
+ f = executor.submit(_run_if_dep_passed, dep_future, tc, dep_config)
395
+ else:
396
+ f = executor.submit(_run_and_parse, tc)
397
+ future_to_tool[f] = tc
398
+ name_to_future[tc.name] = f
355
399
  for future in as_completed(future_to_tool):
356
400
  tool_config = future_to_tool[future]
357
401
  try:
358
- order, tr = future.result()
359
- prioritized.append((order, tr))
360
- timings.append((order, tool_config.name, tr.duration_s))
402
+ tc_order, tr = future.result()
403
+ prioritized.append((tc_order, tr))
404
+ timings.append((tc_order, tool_config.name, tr.duration_s))
361
405
  except Exception as exc:
362
406
  log.error(f"{tool_config.name} generated an exception: {exc}")
363
407
  per_tool = ", ".join(f"{name}={dur:.2f}s" for _, name, dur in sorted(timings))
py_cq/localtypes.py CHANGED
@@ -43,7 +43,7 @@ class ToolConfig:
43
43
 
44
44
  name: str # e.g., "pytest", "coverage", "pydocstyle"
45
45
  command: str # The command to execute (can include placeholders)
46
- parser_class: Callable # Name of the parser class to use
46
+ parser_class: Callable # The parser class itself (resolved from its name in config)
47
47
  context_path: str = "" # Path to project or file
48
48
  order: int = 5 # 1=first (compilation), 11=last (style)
49
49
  warning_threshold: float = 0.7 # Yellow warning if below this
@@ -60,6 +60,8 @@ class ToolConfig:
60
60
  default_factory=list
61
61
  ) # Top-level dir/file names to omit from {scan_targets}
62
62
  skip_for_file: bool = False # If True, skip when context_path is a single file
63
+ gate_strict: bool = True # If False, small regressions above warning_threshold are accepted
64
+ skip_if: str = "" # Tool name; if that tool's score is below its error_threshold, skip this tool
63
65
 
64
66
 
65
67
  @dataclass
@@ -123,10 +125,12 @@ class ToolResult:
123
125
  }
124
126
 
125
127
 
126
- @dataclass
127
128
  class CombinedToolResults:
128
129
  """Aggregates results from multiple tools, stores the associated path, and calculates an overall score by averaging the mean metric values of each ``ToolResult``. If a ``ToolResult`` has no metrics, it contributes zero, and the score defaults to ``0.0`` when the list is empty."""
129
130
 
131
+ score: float
132
+ path: str
133
+
130
134
  def __init__(self, path: str, tool_results: list[ToolResult]):
131
135
  """Initializes a CombinedToolResults instance.
132
136
 
@@ -148,9 +152,6 @@ class CombinedToolResults:
148
152
  else 0.0
149
153
  )
150
154
 
151
- score: float = 0.0
152
- path: str = ""
153
-
154
155
  def to_dict(self) -> dict:
155
156
  """Returns a dictionary containing the path, overall score, and each ToolResult serialized."""
156
157
  return {
@@ -163,7 +164,7 @@ class CombinedToolResults:
163
164
  class AbstractParser(ABC):
164
165
  """Base class for parsers that transform raw tool output into structured `ToolResult` objects.
165
166
 
166
- Subclasses must implement `parse` to convert a `RawResult` into a `ToolResult`. An optional `provide_help` can be overridden to supply contextual guidance for a parsed result."""
167
+ Subclasses must implement `parse` to convert a `RawResult` into a `ToolResult`. The `format_llm_message` method can be overridden to supply a richer single-defect description for a parsed result."""
167
168
 
168
169
  def __init__(self, parser_config: dict | None = None):
169
170
  self.parser_config = parser_config or {}
py_cq/parsers/common.py CHANGED
@@ -1,16 +1,18 @@
1
- """Utility functions for normalising numeric values and scoring error magnitudes.
2
-
3
- This module provides two small helpers that are often used when working with
4
- performance metrics or error scores:
5
-
6
- * :func:`inv_normalize` - Inversely normalises a value relative to a maximum
7
- reference, yielding a float in the interval [0,\u202f1].
8
- * :func:`score_logistic_variant` - Maps an error magnitude to a bounded score
9
- using a logistic-style curve, with optional parameters controlling the scale
10
- and steepness of the transition.
11
-
12
- Both functions return a float and can be used directly in downstream analytics,
13
- visualisation or decision-making pipelines."""
1
+ """Shared helpers used across parsers and the LLM formatter.
2
+
3
+ This module groups small utilities for several concerns:
4
+
5
+ * Scoring - :func:`inv_normalize` and :func:`score_logistic_variant` map error
6
+ magnitudes to bounded ``[0, 1]`` scores.
7
+ * Source context - :func:`read_source_lines`, :func:`format_source_context`,
8
+ :func:`find_function_source`, :func:`find_enclosing_function`, and
9
+ :func:`enclosing_function_range` extract fenced code snippets for LLM prompts.
10
+ * Project navigation - :func:`find_in_project`, :func:`format_callee_context`,
11
+ :func:`extract_callee_name`, :func:`resolve_path`, and :func:`_relative_path`
12
+ locate and reference definitions across a project tree.
13
+ * Output formatting - :func:`format_issue_header` builds issue headers.
14
+ * Parsing - :func:`parse_json_dict` and :func:`extract_first_issue` read tool
15
+ output into Python structures."""
14
16
 
15
17
  import json
16
18
  import re
@@ -74,6 +74,8 @@ class ComplexityParser(AbstractParser):
74
74
  num_items = 0
75
75
  max_complexity = 30
76
76
  for file, functions in data.items():
77
+ if not isinstance(functions, list):
78
+ continue
77
79
  file_name = file.replace("\\", "/")
78
80
  if file_name not in tr.details:
79
81
  tr.details[file_name] = {}
@@ -146,6 +146,8 @@ class CoverageParser(AbstractParser):
146
146
  ]
147
147
 
148
148
  tr.details = details
149
+ if "coverage" not in tr.metrics:
150
+ tr.metrics["coverage"] = 0.0
149
151
  return tr
150
152
 
151
153
  def format_llm_message(
@@ -199,4 +201,6 @@ class CoverageParser(AbstractParser):
199
201
  parts.append(f"\nAdd tests to: {location}")
200
202
 
201
203
  return "\n".join(parts)
204
+ if tr.metrics.get("coverage", 1.0) == 0.0:
205
+ return "**Coverage: 0%** - pytest failed or collected no tests, so no coverage data is available."
202
206
  return ""
@@ -83,11 +83,12 @@ class HalsteadParser(AbstractParser):
83
83
  "functions": {},
84
84
  }
85
85
  if "error" in values:
86
+ tr.details[file_name]["error"] = values["error"]
86
87
  min_file_nb = 0.0
87
88
  min_file_sm = 0.0
88
89
  min_function_nb = 0.0
89
90
  min_function_sm = 0.0
90
- tr.details[file_name]["error"] = values["error"]
91
+ continue
91
92
  if "total" in values:
92
93
  nb, sm = self.extract_bugs_and_volume(
93
94
  values.get("total", {}), MAX_FILE_BUGS, MAX_FILE_VOLUME
@@ -106,7 +107,7 @@ class HalsteadParser(AbstractParser):
106
107
  min_function_nb = min(nb, min_function_nb)
107
108
  min_function_sm = min(sm, min_function_sm)
108
109
  tr.details[file_name]["functions"][function] = {
109
- "no_bugs": nb,
110
+ "bug_free": nb,
110
111
  "smallness": sm,
111
112
  "bugs": function_values.get("bugs", 0),
112
113
  "volume": function_values.get("volume", 0),
@@ -141,7 +142,7 @@ class HalsteadParser(AbstractParser):
141
142
  for file_name, file_data in tr.details.items():
142
143
  if is_function_metric:
143
144
  for func_name, func_data in file_data.get("functions", {}).items():
144
- s = func_data.get("no_bugs" if is_bug_metric else "smallness", 1.0)
145
+ s = func_data.get("bug_free" if is_bug_metric else "smallness", 1.0)
145
146
  if s < worst_score:
146
147
  worst_score = s
147
148
  worst_file = file_name
@@ -123,7 +123,7 @@ def _format_F821(file: str, line: int, message: str) -> str:
123
123
  base += f"\n\nOther references to `{name}` in this file:\n{ref_lines}"
124
124
  else:
125
125
  base += f"\n\n`{name}` is not imported or defined anywhere else in this file."
126
- if enclosing:
126
+ if enclosing and enclosing not in base:
127
127
  base += f"\n\nEnclosing function:{enclosing}"
128
128
  return base
129
129
 
py_cq/tool_registry.py CHANGED
@@ -32,6 +32,8 @@ def load_tool_configs() -> dict[str, ToolConfig]:
32
32
  exclude_format=tool_data.get("exclude_format", ""),
33
33
  scan_exclude_names=tool_data.get("scan_exclude_names", []),
34
34
  skip_for_file=tool_data.get("skip_for_file", False),
35
+ gate_strict=tool_data.get("gate_strict", True),
36
+ skip_if=tool_data.get("skip_if", ""),
35
37
  )
36
38
  return registry
37
39
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-code-quality
3
- Version: 0.2.2
3
+ Version: 0.2.3
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>
@@ -1,35 +1,35 @@
1
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
2
+ py_cq/api.py,sha256=AxhTQVX4ACZmgCpdfJgB2_1VXtClxc531NkRoLK1vA0,10118
3
+ py_cq/cli.py,sha256=pP7lKoGE_cW9AGXEfDQMcfpb7inAtn6LJ0-cv0t5ONo,13784
4
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=CvKkP80jt9_IoB7XYbj-vu1QrJrEVX2CbTxRKyAvblE,14458
5
+ py_cq/config/config.toml,sha256=1-S9e5WK6K19FC1_SaGI5dfePuSwCufH0CssZPfIZgE,2921
6
+ py_cq/context_hash.py,sha256=qYPMacgSEwy0nYLCBW8JllNWsGfaSjVq31EFI811ZcU,3449
7
+ py_cq/execution_engine.py,sha256=ap3tDMxVD9x4bq_leq7IGYZgLM-ryJRnrVFSxSy5jK0,16893
8
8
  py_cq/language_detector.py,sha256=rh5bAA3LyCQR3RODmx_EalqRNYvSYK2SJzh2hIkxOUo,1139
9
9
  py_cq/llm_formatter.py,sha256=nCV3nFdxJAi2CnH9FKb5tB-0w-yjLWmjywQI5GJ2cSU,8412
10
- py_cq/localtypes.py,sha256=ELzOFOFlrLSq5Wgj5LvBIPhWR8wfe7lQYkcbINhqE3A,7559
10
+ py_cq/localtypes.py,sha256=kHldvTJlwyoWg0xKbOL-jplLqwKnGmv6aRh95iq8r8Q,7783
11
11
  py_cq/main.py,sha256=5dgr8Zk1LNK5M-0wncYVksBp0qE-jWZYqmOXfb1eOhk,400
12
12
  py_cq/metric_aggregator.py,sha256=M2ymo62S7p7qPUqjjoiPg4IVyXQhLMuTr9-jxLiFjCY,853
13
13
  py_cq/parsers/__init__.py,sha256=uCy7R0cnvDPNvB5aWMLgM3FB9nDcu1jwUmznFMRROSY,28
14
14
  py_cq/parsers/banditparser.py,sha256=YqpWljU5D4IhLdtzat7XVGBm9NXrbOQbr9LI9NKybK8,3298
15
- py_cq/parsers/common.py,sha256=dU5JCbPJdngbtPjSqnP0kxT2Oux5D-iYiaBMz8KcZCw,13672
15
+ py_cq/parsers/common.py,sha256=csIv0M7Xq68HjXn37OAD2RfMmoQWaChQ9Jf9lamMkvg,13890
16
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
17
+ py_cq/parsers/complexityparser.py,sha256=mX1WHPRgBBc6kOqHfubowRbYXY0E1k4JVwr908Cj10U,5582
18
+ py_cq/parsers/coverageparser.py,sha256=9HtXyAMcQbhsUTb1VNClIHQajBwqCt26H-25t2ZQnY4,8092
19
19
  py_cq/parsers/exitcodeparser.py,sha256=b4LWIwwklPwCX3MnqNsGQC1slQU56DKE05vJ0OdWVoA,957
20
- py_cq/parsers/halsteadparser.py,sha256=cfUNsLUYt74NMQW1sBkktn8AH7jQ_EU5IMFrLgIdk5M,10165
20
+ py_cq/parsers/halsteadparser.py,sha256=789c_WWfomskM4i7SUEbKvB67-F2y5nfqt48aFXOusY,10192
21
21
  py_cq/parsers/interrogateparser.py,sha256=cc5Q4mOQlLUIPl3XUyH29QJ_JMBzAA7SiPAAiNGDJdM,11546
22
22
  py_cq/parsers/linecountparser.py,sha256=-hqebwG4j7_QugzGLUUQRcPukGHicV2nbIp1pR8ZcAw,1310
23
23
  py_cq/parsers/maintainabilityparser.py,sha256=3a4m93XNtUurw3IxEI1Iae9idgJY6qiEt7HMYl1wTu0,4793
24
24
  py_cq/parsers/pytestparser.py,sha256=rhzFzsBEXv1xloOuEjQyBn8Vp0AyGoq2t5siyAwWWaA,11549
25
25
  py_cq/parsers/regexcountparser.py,sha256=zGzHEYDIori6aiWnVkRFixdE7fWt66npAAWjzvpoBF4,1592
26
- py_cq/parsers/ruffparser.py,sha256=ofQyVUuc0JK-6pVdwsRZteCWmNBOIfHzZloVPSH7_wI,7608
26
+ py_cq/parsers/ruffparser.py,sha256=uE0QEaZ4Ch7IsWY_2ZkfHXPU0TYPGsGPfv66hGe5G34,7634
27
27
  py_cq/parsers/typarser.py,sha256=x835OrapG1aUteW2NhC5U0bhj1w1pMtbfmbvb74-1AI,8077
28
28
  py_cq/parsers/vultureparser.py,sha256=-yGAXzRY4_fzLRTk7dfwDrrRZ-n97j1Ph5_xo3c6TfU,2229
29
29
  py_cq/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
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.2.dist-info/WHEEL,sha256=V5-3dKee3Zs8C4JP6swr6zdqriLsOpItBEQxe6_oWpY,81
33
- python_code_quality-0.2.2.dist-info/entry_points.txt,sha256=cfWbTw7eYO6Trv1-Z_odL6Zta9CqYU6Vk9lAHdfB60Q,40
34
- python_code_quality-0.2.2.dist-info/METADATA,sha256=TzzyYDzvGY-zmvmsbP36KhUbZFajkxwrtnsr1s4pEC4,15650
35
- python_code_quality-0.2.2.dist-info/RECORD,,
31
+ py_cq/tool_registry.py,sha256=8B6EG7mGCKR6JeHtesFHa79svNfj1PUbboysXfwV7vY,1828
32
+ python_code_quality-0.2.3.dist-info/WHEEL,sha256=wXwAVsgVaOZ_pwDFqQm5Rd6PID-Fc74nkLc8X8gHiDo,81
33
+ python_code_quality-0.2.3.dist-info/entry_points.txt,sha256=cfWbTw7eYO6Trv1-Z_odL6Zta9CqYU6Vk9lAHdfB60Q,40
34
+ python_code_quality-0.2.3.dist-info/METADATA,sha256=dBNJKPiG5BRMtuPT8jUcYMoasGRsP4uQQG9RvFy_xig,15650
35
+ python_code_quality-0.2.3.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.11.18
2
+ Generator: uv 0.11.19
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any