gruff-py 0.1.0__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.
Files changed (216) hide show
  1. gruff_py-0.1.0.dist-info/METADATA +279 -0
  2. gruff_py-0.1.0.dist-info/RECORD +216 -0
  3. gruff_py-0.1.0.dist-info/WHEEL +4 -0
  4. gruff_py-0.1.0.dist-info/entry_points.txt +2 -0
  5. gruff_py-0.1.0.dist-info/licenses/LICENSE.md +21 -0
  6. gruffpy/__init__.py +5 -0
  7. gruffpy/__main__.py +9 -0
  8. gruffpy/analysis/__init__.py +15 -0
  9. gruffpy/analysis/report.py +163 -0
  10. gruffpy/analysis/run_diagnostic.py +37 -0
  11. gruffpy/analysis/runner.py +286 -0
  12. gruffpy/analysis/schema.py +5 -0
  13. gruffpy/cli.py +741 -0
  14. gruffpy/cli_menu.py +131 -0
  15. gruffpy/cli_options.py +769 -0
  16. gruffpy/cli_state.py +54 -0
  17. gruffpy/command/__init__.py +4 -0
  18. gruffpy/command/dashboard_page_renderer.py +229 -0
  19. gruffpy/command/dashboard_server.py +282 -0
  20. gruffpy/command/metric_calibration.py +723 -0
  21. gruffpy/command/rule_docs.py +322 -0
  22. gruffpy/config/__init__.py +13 -0
  23. gruffpy/config/analysis_config.py +182 -0
  24. gruffpy/config/dead_code_allowlist.py +66 -0
  25. gruffpy/config/exceptions.py +5 -0
  26. gruffpy/config/loader.py +382 -0
  27. gruffpy/config/rule_selection.py +69 -0
  28. gruffpy/config/rule_settings.py +180 -0
  29. gruffpy/config/yaml_loader.py +41 -0
  30. gruffpy/finding/__init__.py +19 -0
  31. gruffpy/finding/confidence.py +9 -0
  32. gruffpy/finding/fail_threshold.py +50 -0
  33. gruffpy/finding/finding.py +98 -0
  34. gruffpy/finding/fingerprint.py +48 -0
  35. gruffpy/finding/output_format.py +31 -0
  36. gruffpy/finding/pillar.py +20 -0
  37. gruffpy/finding/rule_tier.py +7 -0
  38. gruffpy/finding/severity.py +27 -0
  39. gruffpy/parser/__init__.py +4 -0
  40. gruffpy/parser/analysis_unit.py +49 -0
  41. gruffpy/parser/python_parser.py +61 -0
  42. gruffpy/py.typed +0 -0
  43. gruffpy/reporting/__init__.py +19 -0
  44. gruffpy/reporting/finding_display_filter.py +103 -0
  45. gruffpy/reporting/github_annotations_reporter.py +55 -0
  46. gruffpy/reporting/hotspot_reporter.py +36 -0
  47. gruffpy/reporting/html_reporter.py +442 -0
  48. gruffpy/reporting/json_reporter.py +41 -0
  49. gruffpy/reporting/markdown_reporter.py +108 -0
  50. gruffpy/reporting/sarif_reporter.py +184 -0
  51. gruffpy/reporting/text_reporter.py +115 -0
  52. gruffpy/rule/__init__.py +6 -0
  53. gruffpy/rule/_python_dynamism.py +309 -0
  54. gruffpy/rule/builtins.py +11 -0
  55. gruffpy/rule/catalog.py +574 -0
  56. gruffpy/rule/complexity/__init__.py +0 -0
  57. gruffpy/rule/complexity/_halstead.py +213 -0
  58. gruffpy/rule/complexity/_walks.py +88 -0
  59. gruffpy/rule/complexity/cognitive_complexity_rule.py +310 -0
  60. gruffpy/rule/complexity/cyclomatic_complexity_rule.py +159 -0
  61. gruffpy/rule/complexity/halstead_volume_rule.py +106 -0
  62. gruffpy/rule/complexity/maintainability_index_rule.py +133 -0
  63. gruffpy/rule/complexity/nesting_depth_rule.py +191 -0
  64. gruffpy/rule/complexity/npath_complexity_rule.py +248 -0
  65. gruffpy/rule/context.py +34 -0
  66. gruffpy/rule/dead_code/__init__.py +0 -0
  67. gruffpy/rule/dead_code/unused_private_attribute_rule.py +209 -0
  68. gruffpy/rule/dead_code/unused_private_function_rule.py +300 -0
  69. gruffpy/rule/definition.py +70 -0
  70. gruffpy/rule/design/__init__.py +3 -0
  71. gruffpy/rule/design/single_implementor_protocol_rule.py +454 -0
  72. gruffpy/rule/docs/__init__.py +0 -0
  73. gruffpy/rule/docs/_comment_scanner.py +50 -0
  74. gruffpy/rule/docs/_docstring_parser.py +138 -0
  75. gruffpy/rule/docs/_helpers.py +182 -0
  76. gruffpy/rule/docs/complex_branch_rationale_rule.py +227 -0
  77. gruffpy/rule/docs/dataclass_attributes_rule.py +270 -0
  78. gruffpy/rule/docs/ignore_directive_reason_rule.py +173 -0
  79. gruffpy/rule/docs/missing_class_docstring_rule.py +115 -0
  80. gruffpy/rule/docs/missing_function_docstring_rule.py +158 -0
  81. gruffpy/rule/docs/missing_module_docstring_rule.py +105 -0
  82. gruffpy/rule/docs/missing_param_doc_rule.py +191 -0
  83. gruffpy/rule/docs/missing_raises_doc_rule.py +126 -0
  84. gruffpy/rule/docs/missing_readme_rule.py +86 -0
  85. gruffpy/rule/docs/missing_return_doc_rule.py +129 -0
  86. gruffpy/rule/docs/stale_param_doc_rule.py +136 -0
  87. gruffpy/rule/docs/todo_density_rule.py +108 -0
  88. gruffpy/rule/docs/useless_docstring_rule.py +298 -0
  89. gruffpy/rule/naming/__init__.py +0 -0
  90. gruffpy/rule/naming/_allowlists.py +23 -0
  91. gruffpy/rule/naming/_identifier_tokenizer.py +64 -0
  92. gruffpy/rule/naming/abbreviation_rule.py +205 -0
  93. gruffpy/rule/naming/boolean_prefix_rule.py +315 -0
  94. gruffpy/rule/naming/confusing_name_rule.py +109 -0
  95. gruffpy/rule/naming/generic_function_rule.py +116 -0
  96. gruffpy/rule/naming/hungarian_notation_rule.py +200 -0
  97. gruffpy/rule/naming/identifier_quality_rule.py +181 -0
  98. gruffpy/rule/naming/module_name_mismatch_rule.py +267 -0
  99. gruffpy/rule/naming/parameter_type_name_rule.py +393 -0
  100. gruffpy/rule/naming/short_variable_rule.py +178 -0
  101. gruffpy/rule/naming/test_naming_consistency_rule.py +142 -0
  102. gruffpy/rule/project_rule.py +19 -0
  103. gruffpy/rule/registry.py +198 -0
  104. gruffpy/rule/rule.py +21 -0
  105. gruffpy/rule/security/__init__.py +0 -0
  106. gruffpy/rule/security/_security_metadata.py +127 -0
  107. gruffpy/rule/security/_security_node_helper.py +265 -0
  108. gruffpy/rule/security/_security_taint_helper.py +376 -0
  109. gruffpy/rule/security/cors_wildcard_with_credentials_rule.py +156 -0
  110. gruffpy/rule/security/dangerous_function_call_rule.py +122 -0
  111. gruffpy/rule/security/disabled_ssl_verification_rule.py +383 -0
  112. gruffpy/rule/security/django_mark_safe_rule.py +161 -0
  113. gruffpy/rule/security/django_raw_sql_rule.py +139 -0
  114. gruffpy/rule/security/error_suppression_rule.py +134 -0
  115. gruffpy/rule/security/extract_compact_user_input_rule.py +121 -0
  116. gruffpy/rule/security/flask_debug_enabled_rule.py +175 -0
  117. gruffpy/rule/security/hardcoded_bind_all_interfaces_rule.py +172 -0
  118. gruffpy/rule/security/hardcoded_framework_secret_key_rule.py +149 -0
  119. gruffpy/rule/security/header_injection_rule.py +114 -0
  120. gruffpy/rule/security/insecure_random_rule.py +132 -0
  121. gruffpy/rule/security/insecure_temp_file_rule.py +186 -0
  122. gruffpy/rule/security/insecure_tls_protocol_rule.py +132 -0
  123. gruffpy/rule/security/jinja2_autoescape_off_rule.py +185 -0
  124. gruffpy/rule/security/paramiko_no_host_key_check_rule.py +175 -0
  125. gruffpy/rule/security/path_traversal_rule.py +191 -0
  126. gruffpy/rule/security/shell_injection_rule.py +137 -0
  127. gruffpy/rule/security/silent_except_rule.py +109 -0
  128. gruffpy/rule/security/sql_concatenation_rule.py +160 -0
  129. gruffpy/rule/security/ssrf_rule.py +172 -0
  130. gruffpy/rule/security/unsafe_pickle_rule.py +166 -0
  131. gruffpy/rule/security/unsafe_yaml_load_rule.py +355 -0
  132. gruffpy/rule/security/variable_import_rule.py +103 -0
  133. gruffpy/rule/security/weak_crypto_rule.py +260 -0
  134. gruffpy/rule/security/xxe_rule.py +231 -0
  135. gruffpy/rule/sensitive_data/__init__.py +0 -0
  136. gruffpy/rule/sensitive_data/_secret_scanner_helper.py +138 -0
  137. gruffpy/rule/sensitive_data/api_key_pattern_rule.py +108 -0
  138. gruffpy/rule/sensitive_data/aws_access_key_rule.py +90 -0
  139. gruffpy/rule/sensitive_data/database_url_password_rule.py +137 -0
  140. gruffpy/rule/sensitive_data/hardcoded_env_value_rule.py +120 -0
  141. gruffpy/rule/sensitive_data/high_entropy_string_rule.py +120 -0
  142. gruffpy/rule/sensitive_data/jwt_token_rule.py +80 -0
  143. gruffpy/rule/sensitive_data/phi_pattern_rule.py +103 -0
  144. gruffpy/rule/sensitive_data/pii_test_fixture_rule.py +132 -0
  145. gruffpy/rule/sensitive_data/private_key_rule.py +86 -0
  146. gruffpy/rule/size/__init__.py +3 -0
  147. gruffpy/rule/size/_lines.py +89 -0
  148. gruffpy/rule/size/attribute_count_rule.py +172 -0
  149. gruffpy/rule/size/average_function_length_rule.py +150 -0
  150. gruffpy/rule/size/class_length_rule.py +129 -0
  151. gruffpy/rule/size/file_length_rule.py +88 -0
  152. gruffpy/rule/size/function_length_rule.py +135 -0
  153. gruffpy/rule/size/parameter_count_rule.py +128 -0
  154. gruffpy/rule/size/public_method_count_rule.py +134 -0
  155. gruffpy/rule/test_quality/__init__.py +0 -0
  156. gruffpy/rule/test_quality/_pytest_config.py +159 -0
  157. gruffpy/rule/test_quality/_test_quality_node_helper.py +359 -0
  158. gruffpy/rule/test_quality/_test_quality_scope.py +33 -0
  159. gruffpy/rule/test_quality/conditional_logic_rule.py +97 -0
  160. gruffpy/rule/test_quality/eager_test_rule.py +104 -0
  161. gruffpy/rule/test_quality/empty_parametrize_rule.py +99 -0
  162. gruffpy/rule/test_quality/exception_type_only_rule.py +124 -0
  163. gruffpy/rule/test_quality/excessive_mocking_rule.py +98 -0
  164. gruffpy/rule/test_quality/extends_production_class_rule.py +149 -0
  165. gruffpy/rule/test_quality/global_state_mutation_rule.py +95 -0
  166. gruffpy/rule/test_quality/loop_assertion_without_message_rule.py +102 -0
  167. gruffpy/rule/test_quality/loop_in_test_rule.py +98 -0
  168. gruffpy/rule/test_quality/magic_number_assertion_rule.py +317 -0
  169. gruffpy/rule/test_quality/mock_only_test_rule.py +120 -0
  170. gruffpy/rule/test_quality/mock_without_expectation_rule.py +125 -0
  171. gruffpy/rule/test_quality/mocking_domain_object_rule.py +136 -0
  172. gruffpy/rule/test_quality/multiple_aaa_cycles_rule.py +129 -0
  173. gruffpy/rule/test_quality/mystery_guest_rule.py +121 -0
  174. gruffpy/rule/test_quality/naming_consistency_rule.py +103 -0
  175. gruffpy/rule/test_quality/no_assertions_rule.py +107 -0
  176. gruffpy/rule/test_quality/parametrize_annotation_rule.py +142 -0
  177. gruffpy/rule/test_quality/private_reflection_rule.py +107 -0
  178. gruffpy/rule/test_quality/pytest_coverage_source_missing_rule.py +86 -0
  179. gruffpy/rule/test_quality/pytest_deprecations_not_fatal_rule.py +85 -0
  180. gruffpy/rule/test_quality/pytest_strict_config_missing_rule.py +93 -0
  181. gruffpy/rule/test_quality/repeated_structure_missing_parametrize_rule.py +133 -0
  182. gruffpy/rule/test_quality/setup_bloat_rule.py +114 -0
  183. gruffpy/rule/test_quality/skipped_without_reason_rule.py +156 -0
  184. gruffpy/rule/test_quality/sleep_in_test_rule.py +98 -0
  185. gruffpy/rule/test_quality/sut_not_called_rule.py +273 -0
  186. gruffpy/rule/test_quality/tautological_type_assertion_rule.py +140 -0
  187. gruffpy/rule/test_quality/test_function_too_long_rule.py +105 -0
  188. gruffpy/rule/test_quality/test_longer_than_sut_rule.py +130 -0
  189. gruffpy/rule/test_quality/testdox_readability_rule.py +100 -0
  190. gruffpy/rule/test_quality/trivial_assertion_rule.py +104 -0
  191. gruffpy/rule/test_quality/trivial_snapshot_rule.py +113 -0
  192. gruffpy/rule/test_quality/unused_mock_rule.py +109 -0
  193. gruffpy/rule/waste/__init__.py +0 -0
  194. gruffpy/rule/waste/commented_out_code_rule.py +155 -0
  195. gruffpy/rule/waste/empty_class_rule.py +152 -0
  196. gruffpy/rule/waste/empty_function_rule.py +123 -0
  197. gruffpy/rule/waste/one_line_function_rule.py +166 -0
  198. gruffpy/rule/waste/redundant_variable_rule.py +151 -0
  199. gruffpy/rule/waste/unreachable_code_rule.py +231 -0
  200. gruffpy/rule/waste/unused_import_rule.py +208 -0
  201. gruffpy/rule/waste/unused_parameter_rule.py +211 -0
  202. gruffpy/scoring/__init__.py +25 -0
  203. gruffpy/scoring/composite_finding_factory.py +151 -0
  204. gruffpy/scoring/file_score.py +60 -0
  205. gruffpy/scoring/grade.py +52 -0
  206. gruffpy/scoring/pillar_score.py +52 -0
  207. gruffpy/scoring/score_calculator.py +196 -0
  208. gruffpy/scoring/score_report.py +48 -0
  209. gruffpy/source/__init__.py +20 -0
  210. gruffpy/source/discovery.py +343 -0
  211. gruffpy/source/gitignore.py +170 -0
  212. gruffpy/source/source_file.py +32 -0
  213. gruffpy/suppression/__init__.py +11 -0
  214. gruffpy/suppression/filter.py +37 -0
  215. gruffpy/suppression/parser.py +273 -0
  216. gruffpy/version.py +4 -0
@@ -0,0 +1,279 @@
1
+ Metadata-Version: 2.4
2
+ Name: gruff-py
3
+ Version: 0.1.0
4
+ Summary: Python project quality analyser.
5
+ Project-URL: Homepage, https://github.com/blundergoat/gruff-py
6
+ Project-URL: Repository, https://github.com/blundergoat/gruff-py
7
+ Project-URL: Issues, https://github.com/blundergoat/gruff-py/issues
8
+ Project-URL: Changelog, https://github.com/blundergoat/gruff-py/blob/main/CHANGELOG.md
9
+ Author: Matthew Hansen
10
+ License: MIT
11
+ License-File: LICENSE.md
12
+ Keywords: code-quality,code-review,dashboard,dead-code,linter,quality-gate,sarif,security-scanner,static-analysis,test-quality
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Topic :: Software Development :: Quality Assurance
23
+ Classifier: Topic :: Software Development :: Testing
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.11
26
+ Requires-Dist: click>=8.1
27
+ Requires-Dist: docstring-parser<1,>=0.15
28
+ Requires-Dist: pathspec>=0.12
29
+ Requires-Dist: pyyaml>=6.0
30
+ Provides-Extra: dev
31
+ Requires-Dist: mypy>=1.10; extra == 'dev'
32
+ Requires-Dist: pre-commit>=3.7; extra == 'dev'
33
+ Requires-Dist: pytest>=8; extra == 'dev'
34
+ Requires-Dist: ruff>=0.5; extra == 'dev'
35
+ Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
36
+ Description-Content-Type: text/markdown
37
+
38
+ # gruff-py
39
+
40
+ `gruff-py` is the Python implementation of **gruff**, an opinionated project
41
+ quality analyser. It walks Python projects, applies a broad rule catalogue, and
42
+ emits scored reports for local review, CI, code scanning, and a browser
43
+ dashboard.
44
+
45
+ It is heuristic static analysis. Use it beside tools such as `ruff`, `mypy`,
46
+ `pytest`, and security scanners, not as a replacement for them.
47
+
48
+ ## Status
49
+
50
+ `0.1.0` is the first public release.
51
+
52
+ - Python 3.11+
53
+ - 116 rules across 10 active quality pillars
54
+ - Text, JSON, HTML, Markdown, GitHub annotation, hotspot, and SARIF reports
55
+ - Local dashboard served from `127.0.0.1` by default
56
+ - `.gruff-py.yaml` and `[tool.gruff-py]` configuration
57
+ - PHP-compatible finding fingerprints
58
+ - Python import package `gruffpy` with a typed package marker via `py.typed`
59
+ - MIT licensed (see [`LICENSE.md`](LICENSE.md))
60
+
61
+ ## Install
62
+
63
+ From this repository:
64
+
65
+ ```bash
66
+ uv sync
67
+ ./bin/gruff-py --help
68
+ ```
69
+
70
+ The package entry point is `gruff-py`, so `uv run gruff-py --help` is
71
+ equivalent after `uv sync`.
72
+
73
+ After the package is published:
74
+
75
+ ```bash
76
+ pipx install gruff-py
77
+ gruff-py --help
78
+ ```
79
+
80
+ ## Quick Start
81
+
82
+ Analyse a project:
83
+
84
+ ```bash
85
+ uv run gruff-py analyse src/
86
+ ```
87
+
88
+ Emit JSON for automation:
89
+
90
+ ```bash
91
+ uv run gruff-py analyse src/ --format json --fail-on error > gruff.json
92
+ ```
93
+
94
+ Emit SARIF for code scanning:
95
+
96
+ ```bash
97
+ uv run gruff-py analyse src/ --format sarif --fail-on none > gruff.sarif
98
+ ```
99
+
100
+ Create a standalone HTML report:
101
+
102
+ ```bash
103
+ uv run gruff-py analyse src/ --format html --report-interactive > gruff-report.html
104
+ ```
105
+
106
+ Run the local dashboard:
107
+
108
+ ```bash
109
+ uv run gruff-py dashboard src/ --report-interactive
110
+ ```
111
+
112
+ Then open:
113
+
114
+ ```text
115
+ http://127.0.0.1:8765/
116
+ ```
117
+
118
+ ## CLI
119
+
120
+ ```bash
121
+ gruff-py [GLOBAL OPTIONS] <command>
122
+ gruff-py analyse [OPTIONS] [PATHS]...
123
+ gruff-py report [OPTIONS] [PATHS]...
124
+ gruff-py summary [OPTIONS] [PATHS]...
125
+ gruff-py list-rules [OPTIONS]
126
+ gruff-py dashboard [OPTIONS] [PATHS]...
127
+ gruff-py completion [SHELL]
128
+ ```
129
+
130
+ Global options mirror the gruff-php CLI surface: `--silent`, `--quiet`,
131
+ `--version`, `--ansi` / `--no-ansi`, `--no-interaction`, and `--verbose`.
132
+
133
+ Common `analyse` and `report` options:
134
+
135
+ | Option | Meaning |
136
+ |---|---|
137
+ | `--format text` | Terminal summary, the default |
138
+ | `--format json` | Full `gruff-py.analysis.v1` payload |
139
+ | `--format html` | Self-contained dark HTML report |
140
+ | `--format markdown` | Pull-request or issue comment summary |
141
+ | `--format github` | GitHub Actions annotation commands |
142
+ | `--format hotspot` | `gruff-py.hotspot.v1` file offender JSON |
143
+ | `--format sarif` | SARIF 2.1.0 code-scanning output |
144
+ | `--fail-on error` | Exit non-zero for findings at or above the threshold |
145
+ | `--no-config` | Ignore `.gruff-py.yaml` and `[tool.gruff-py]` |
146
+ | `--include-ignored` | Scan default-ignored directories and `.gitignore` exclusions |
147
+ | `--min-severity warning` | Display only warning/error findings |
148
+ | `--include-pillar documentation` | Display only selected pillar findings |
149
+ | `--exclude-rule docs.missing-function-docstring` | Hide selected rule findings |
150
+
151
+ Additional commands:
152
+
153
+ | Command | Meaning |
154
+ |---|---|
155
+ | `gruff-py report --format html --output gruff.html` | Render an HTML or JSON report to a file or stdout |
156
+ | `gruff-py summary --format text` | Print compact per-pillar, top-rule, and top-file counts |
157
+ | `gruff-py list-rules --format json` | Print registered rule metadata |
158
+ | `gruff-py list` | List available commands |
159
+ | `gruff-py help analyse` | Display command help |
160
+ | `gruff-py completion bash` | Dump a shell completion script |
161
+
162
+ Exit codes:
163
+
164
+ | Code | Meaning |
165
+ |---|---|
166
+ | `0` | Run completed and no finding reached `--fail-on` |
167
+ | `1` | At least one finding reached `--fail-on` |
168
+ | `2` | Input, parse, or configuration diagnostic |
169
+
170
+ ## Configuration
171
+
172
+ Config is optional. Precedence is:
173
+
174
+ 1. `--config <path>`
175
+ 2. `.gruff-py.yaml` in the project root
176
+ 3. `[tool.gruff-py]` in `pyproject.toml`
177
+ 4. Built-in defaults
178
+
179
+ Example `.gruff-py.yaml`:
180
+
181
+ ```yaml
182
+ paths:
183
+ ignore:
184
+ - "tests/fixtures/**"
185
+
186
+ selection:
187
+ excludeRules:
188
+ - docs.missing-module-docstring
189
+
190
+ rules:
191
+ size.file-length:
192
+ threshold: 900
193
+ severity: error
194
+ ```
195
+
196
+ See [Configuration](docs/CONFIGURATION.md) for the full shape.
197
+
198
+ ## Quality Pillars
199
+
200
+ gruff-py scores findings across these active pillars:
201
+
202
+ - `size`
203
+ - `complexity`
204
+ - `maintainability`
205
+ - `dead-code`
206
+ - `naming`
207
+ - `documentation`
208
+ - `security`
209
+ - `sensitive-data`
210
+ - `test-quality`
211
+ - `design`
212
+
213
+ `modernisation`, `coupling`, `architecture`, and `mutation` are reserved schema
214
+ or future catalogue names. They do not all have shipping rules in `0.1`.
215
+
216
+ See [Rules](docs/RULES.md) for the rule catalogue.
217
+
218
+ ## Reports And Dashboard
219
+
220
+ - [Reports](docs/REPORTING.md) explains every output format and CI use case.
221
+ - [Dashboard](docs/DASHBOARD.md) documents the local browser dashboard.
222
+
223
+ The JSON schema string is `gruff-py.analysis.v1`; hotspot output uses
224
+ `gruff-py.hotspot.v1`. Finding fingerprints are 16-character SHA-256 derivatives
225
+ kept compatible with the PHP implementation. SARIF is rendered from the same
226
+ native report data without changing native schemas or fingerprints; SARIF result
227
+ fingerprints use `partialFingerprints.gruffFingerprint`.
228
+
229
+ ## Development
230
+
231
+ ```bash
232
+ uv sync --extra dev
233
+ uv run ruff check src tests
234
+ uv run ruff format --check src tests
235
+ uv run mypy src
236
+ uv run pytest
237
+ ```
238
+
239
+ The Makefile mirrors these commands:
240
+
241
+ ```bash
242
+ make check
243
+ ```
244
+
245
+ Note that `make check` uses the `lint` target, which runs `ruff check --fix`.
246
+ Use the explicit commands above when you need non-mutating release verification.
247
+
248
+ ### Performance harness
249
+
250
+ `scripts/test-performance.sh` runs a fixed workload matrix (cold-start,
251
+ analyse on `src/`/`tests/`, reporter variants, synthetic 100/1000-file
252
+ fixtures) with median/p95/min/max wall-clock, peak RSS via
253
+ `/usr/bin/time -v`, and per-rule cost attribution from `cProfile`. Baselines
254
+ live under `scripts/performance-baselines/<host>.json`; pass `--baseline` to
255
+ fail the script on regressions.
256
+
257
+ ```bash
258
+ make perf-quick # CI smoke: cold-start + analyse-src vs baseline
259
+ make perf # full suite vs baseline
260
+ make perf-baseline # overwrite the linux-x86_64 baseline with the current run
261
+ ```
262
+
263
+ ## Project Docs
264
+
265
+ - [Changelog](CHANGELOG.md)
266
+ - [Contributing](CONTRIBUTING.md)
267
+ - [Code of Conduct](CODE_OF_CONDUCT.md)
268
+ - [Security](SECURITY.md)
269
+ - [Support](SUPPORT.md)
270
+ - [Release checklist](docs/RELEASING.md)
271
+ - [License](LICENSE.md)
272
+
273
+ ## Author
274
+
275
+ Built by [Matthew Hansen](https://www.blundergoat.com/about).
276
+
277
+ ## License
278
+
279
+ [MIT](LICENSE)
@@ -0,0 +1,216 @@
1
+ gruffpy/__init__.py,sha256=aoU75zQDJTDD1hToVoLBeL9T_7yW2KftihlTFMmEQfw,110
2
+ gruffpy/__main__.py,sha256=F-EyR4jUxKQGRm8MFmYllIMYTbJDktDvQ7fNkpcAQNs,186
3
+ gruffpy/cli.py,sha256=EUici4IMwGwzfg7LOegkS5J0N2qrxKzVPan0mlpexsw,25508
4
+ gruffpy/cli_menu.py,sha256=Tohr3-nYUfN-cZuAGhlV8UnL4lDOpR7Phd2Ble3MREw,4467
5
+ gruffpy/cli_options.py,sha256=VBeKEnQN2R68fUoNamQT3MzYzbKa4puKSHNPeK6cCS0,23870
6
+ gruffpy/cli_state.py,sha256=c0iQoPPmRSU3NL8iuEjb5pExJ-XlONtXq9A2FNiAI5w,1727
7
+ gruffpy/version.py,sha256=GVX_W8Ucx_0_wT9d0LFP3BQ78HeRR4bU_PyEn9QAqXE,108
8
+ gruffpy/analysis/__init__.py,sha256=gG0LlebiSdba_0ozJgUha1p-LIIkD3q9V6QfvgPNHcI,385
9
+ gruffpy/analysis/report.py,sha256=2gylEAlZif_p5yYEnSGNLGLCI5bmBYLMN3rtVQW4H1k,5655
10
+ gruffpy/analysis/run_diagnostic.py,sha256=1s75jeZXh-WMnwdyU8El_nwt4Ben-c01xr1VdqtBmig,1087
11
+ gruffpy/analysis/runner.py,sha256=Z78FYmeAAYMl8N6s-FvMP-Sex9gyowwNgPbIVkIJuf8,10046
12
+ gruffpy/analysis/schema.py,sha256=-smudlMOPvDu8HKoFw7WZDXiJLXGy7FE5WuyaZKCfVo,204
13
+ gruffpy/command/__init__.py,sha256=HizXq80wBBkjTmqXtQKj3jWybLdgtcI8Ti8N-sCCD7g,241
14
+ gruffpy/command/dashboard_page_renderer.py,sha256=sMaeyOBiEqJ3IlRuE2omgRZrA0_0fZC2qpzLaqnteCk,16846
15
+ gruffpy/command/dashboard_server.py,sha256=-He0zJ1wShih1WBblG2bYHzURRp3-jEZneomACoiCsE,10577
16
+ gruffpy/command/metric_calibration.py,sha256=RhWm_-gaerSLHDNnlpzbSabF1UysIcH_os3xuFMs2h0,23238
17
+ gruffpy/command/rule_docs.py,sha256=jEJBe-TMrnSeNVhRr9c4xMs--BRX3JUoLGUCO215Dwg,10485
18
+ gruffpy/config/__init__.py,sha256=6fzJ5RX8_XxVZx_TjwUmC01MzMVdV5CzA1HVbCVwgUM,382
19
+ gruffpy/config/analysis_config.py,sha256=gDmdeJCep7HDMYtfDnfpyaYf0Tlu1ipli-GqDUrpEqI,7173
20
+ gruffpy/config/dead_code_allowlist.py,sha256=t1Pn0E-VW-VhAIr9YkwvM6QFR4lCrQfB04SHxNxiPKE,2324
21
+ gruffpy/config/exceptions.py,sha256=_XVoQ6qowk6k6VMiPFscCxa9HiogqIiRH11WBuqI8mw,188
22
+ gruffpy/config/loader.py,sha256=KfAqhEWVJdN1NAqTX-gXz8NUNHYu-e5OmKHKLWvcNOc,15862
23
+ gruffpy/config/rule_selection.py,sha256=86Ppz0acvUiAO1Ff7QssOC3z61zarUBmExzhWsiwQFk,2352
24
+ gruffpy/config/rule_settings.py,sha256=swJY9D7JuTsKxY4jewBFE5_-fZDfAR4aPG-M2Wu4vVs,6313
25
+ gruffpy/config/yaml_loader.py,sha256=wlTQdSk6YlS0-FnumOgWad0bhC13jkrwzbTVRT4p3og,1309
26
+ gruffpy/finding/__init__.py,sha256=basTypb_vso9w1UBu43UPIcgG5UaJ2EofMa8b5CLHEc,555
27
+ gruffpy/finding/confidence.py,sha256=MDha1Pl9k3J16irJS4dCAlCzVbgnsSUp6-TEuiTQABU,178
28
+ gruffpy/finding/fail_threshold.py,sha256=KoKBRiN3C9l1442v8kAwulfKmeEzoTvaKQOEmUrPZN4,1541
29
+ gruffpy/finding/finding.py,sha256=F1dzdK3cTBweVx8tEGBcTu-B6kc__GolW9bqSuJb-pw,3516
30
+ gruffpy/finding/fingerprint.py,sha256=Zyt6afAnQnRnWaEgdHJr8Q4b1F92GD_-596OxDkB58w,1668
31
+ gruffpy/finding/output_format.py,sha256=lXDA1ajPhV4iz-MQ2MFipbSi0ryiLvrvqQH2jEXQElw,820
32
+ gruffpy/finding/pillar.py,sha256=kA85TbkWj0HxeGz9GxACL1Uk1D6j463qgbkxN1kvu0w,549
33
+ gruffpy/finding/rule_tier.py,sha256=1xzhHGMOm_jVzaD70RKCuuJ4nYe2TkueZ9MK6T9qmIc,149
34
+ gruffpy/finding/severity.py,sha256=ycLqVVDqZnA3HD9I_NhFPSXfptS0u0kRt8SdY-RFTsg,744
35
+ gruffpy/parser/__init__.py,sha256=Jjwnc0wq-hBAnSlyZPZzHKe785erjPjBrcod54h3N24,196
36
+ gruffpy/parser/analysis_unit.py,sha256=ry-EzdBBGIo9cjEeVT0uPFHOHxKQ4m3PQpEGw1zBvX4,1369
37
+ gruffpy/parser/python_parser.py,sha256=JrOT72BA0xVMjg76TbMIXHtvELOHprFowOhYs6EnKso,2093
38
+ gruffpy/reporting/__init__.py,sha256=IeBjmZr8dA6Hhr_DzikZIInpTRfxaOn_0N7NOEtKy1M,720
39
+ gruffpy/reporting/finding_display_filter.py,sha256=HbHTwKGYQOVEWwJTx3EBzSP3tfcMtJGpposhKu8w91A,3734
40
+ gruffpy/reporting/github_annotations_reporter.py,sha256=v563demTexIv5OWpp-J8yKF0SBkV-YRpXeK75sKIX78,1844
41
+ gruffpy/reporting/hotspot_reporter.py,sha256=ccNC2r5QDEjOavE6hYtNZL54smj1XRAK-sCeScmueTg,1436
42
+ gruffpy/reporting/html_reporter.py,sha256=v6JqYp1AjHOFdOOCe87-svo8GL2zMhTJvwEMCrhrV6Q,32717
43
+ gruffpy/reporting/json_reporter.py,sha256=Zn92xOeuUpDjOnIrmBgPr_XcoivSS5u_wPNtWV4HanI,1395
44
+ gruffpy/reporting/markdown_reporter.py,sha256=zs5spPv5l80gw8Q0JdSrKoOW1govNBtCozIL4VhnIR8,3941
45
+ gruffpy/reporting/sarif_reporter.py,sha256=VWOpOEYJrzqs8mQvo0BBwxGNZIQrJSV3MRNRqdRqIZw,6719
46
+ gruffpy/reporting/text_reporter.py,sha256=mLajbVCudfogAlW0eMKrsxjdHZLQB_L3h9yo8iYlmIU,4200
47
+ gruffpy/rule/__init__.py,sha256=TCxKdR8kOS_nV7Nhj4yARZrTVChEuAnwyxCGRyK2thk,281
48
+ gruffpy/rule/_python_dynamism.py,sha256=xqk23s4JjxFluv_RtJKSvIpYaZ8VvkpqRLDEocYVxtM,9893
49
+ gruffpy/rule/builtins.py,sha256=L49WloGobWfm-4PsXB-um5kk__wzizEk6YPJvSbs97c,299
50
+ gruffpy/rule/catalog.py,sha256=5vZYZkVJjrWwjbQXqxbICM3Sb3WH3MM6HCG9lZJR2X0,25814
51
+ gruffpy/rule/context.py,sha256=0dKbh0dKOP-0RvO2ALz_LQpvLTDgyIUWtc5tBwUi_RU,1159
52
+ gruffpy/rule/definition.py,sha256=EMexl4Aee7BpWr6453GR7qGra89ckREU9KSlAjndovs,2733
53
+ gruffpy/rule/project_rule.py,sha256=fx5JzYW4cx2K0qkmm2BDtbrBE-QSsffDGXZlXl6nG-0,570
54
+ gruffpy/rule/registry.py,sha256=lsmiSVpI7D_bZqHj2jTXx6PtpUTFAZsTowPr45wlErg,7035
55
+ gruffpy/rule/rule.py,sha256=aSm_UGYTlXZ4m1Dg4_dXqbWM4vb4oOS-K1oTPOfby2w,662
56
+ gruffpy/rule/complexity/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
+ gruffpy/rule/complexity/_halstead.py,sha256=jbIKbpWx9wjLEu5Y8xD24aqPacuPnOws0hEW6iAXkrg,7720
58
+ gruffpy/rule/complexity/_walks.py,sha256=8DN3DTAOv48Fe1OL1X7WOVKBTlzsuk8f0N7pcauofxI,2903
59
+ gruffpy/rule/complexity/cognitive_complexity_rule.py,sha256=AkxPkvRfaMhQK9t-HvA6OFSs4sxxvYRvfb-h9yxkWl8,12007
60
+ gruffpy/rule/complexity/cyclomatic_complexity_rule.py,sha256=bWkp51kgdf1SnaiOCkDMf-7m4MFTS0Gcc3IGleUpsy8,5547
61
+ gruffpy/rule/complexity/halstead_volume_rule.py,sha256=R5JYbCzCVgu00hL7SoqAKiAowX8e6Av6LU_-HguhELY,4074
62
+ gruffpy/rule/complexity/maintainability_index_rule.py,sha256=VesKEFiEQpOqwoOc3yZwJoqPCg7nJK9nYk6hO3AZ1UQ,5207
63
+ gruffpy/rule/complexity/nesting_depth_rule.py,sha256=e_ULU_Hlc-WFxUQPMrGgM5hMMykLgzO6Ehe3jUvRm9w,6362
64
+ gruffpy/rule/complexity/npath_complexity_rule.py,sha256=yCIR0hr5SQXQ3shndR_adw7_f2ClkcHlX7WLJ0X_fT4,8199
65
+ gruffpy/rule/dead_code/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
66
+ gruffpy/rule/dead_code/unused_private_attribute_rule.py,sha256=_0tQp9t7BQMFU8_GrKzl4UZZnuBZp7TQShgVXm12uM8,7387
67
+ gruffpy/rule/dead_code/unused_private_function_rule.py,sha256=gGZhYmuM1qt5egzbDZ-HCy-bqF2c8PYfcG06FPSrEfQ,10479
68
+ gruffpy/rule/design/__init__.py,sha256=TBD7Mmb2wGNiCnoYvhA1Y_DYWp_GYzN2DBWfd-kVUNM,140
69
+ gruffpy/rule/design/single_implementor_protocol_rule.py,sha256=PaKCnZ4fQ52FScBqRb4XSynRUNyp9_S175qXO54-klU,14972
70
+ gruffpy/rule/docs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
71
+ gruffpy/rule/docs/_comment_scanner.py,sha256=nW4IqnXMagaRwGCboubPGlQQSVDy1pNctpOn2SOe5XQ,1433
72
+ gruffpy/rule/docs/_docstring_parser.py,sha256=guOdoBMH9wSynRTUYrL5Wqvyr1IOHlWnd2GCDfEw5lU,4334
73
+ gruffpy/rule/docs/_helpers.py,sha256=6tjEOVEL4vEEXG34qUXw7xx4pdN8tv3DASLLeUUFtvM,6112
74
+ gruffpy/rule/docs/complex_branch_rationale_rule.py,sha256=3h17C87aLJWuwuQCkppGGSbsZ1TcFud731DpxHP5OR4,7572
75
+ gruffpy/rule/docs/dataclass_attributes_rule.py,sha256=PghMF3ZEB1yUql1M2YBzPpgJW_YtV7d4LbRmi2REPVE,9583
76
+ gruffpy/rule/docs/ignore_directive_reason_rule.py,sha256=ZmHdIhgmmamEwcZ8f5pezw6H7xvoVoWL_JEiSOSs7yw,5526
77
+ gruffpy/rule/docs/missing_class_docstring_rule.py,sha256=ubq_2f9niDolncX7kQXIKk77xQuCgA504hV0tgQ6mN4,4112
78
+ gruffpy/rule/docs/missing_function_docstring_rule.py,sha256=lnLURsaeVqHQoYk82_f0afQi4wM3AGX1nmS8BloEjL4,5662
79
+ gruffpy/rule/docs/missing_module_docstring_rule.py,sha256=EDejbxKiZVyZJIWqSHof62HjOnATJr3wzQPXqjiDgbs,3738
80
+ gruffpy/rule/docs/missing_param_doc_rule.py,sha256=xzUHTmuuPCgaj401C_YpG4zcDUCyHYIoRHJ95JRUUyg,6435
81
+ gruffpy/rule/docs/missing_raises_doc_rule.py,sha256=p__0bFX6WbuT7q1PAdmUqopy4foIbVdxl3aW09X9JoQ,4251
82
+ gruffpy/rule/docs/missing_readme_rule.py,sha256=T0KxpKQSjlw9-IZQI6g_yFZe046udpwMVI2KVQdWXBE,2975
83
+ gruffpy/rule/docs/missing_return_doc_rule.py,sha256=-ZCXvTWO3iDR9DdDMvsBJoFbafk13pvWX9fjdqKZgKY,4497
84
+ gruffpy/rule/docs/stale_param_doc_rule.py,sha256=d4wtK09iXyl8UoKuXT_WZteZDWaQIm-igM-aQwsviNY,4747
85
+ gruffpy/rule/docs/todo_density_rule.py,sha256=v1W5_NWbE72jNfB3yal-2as6m8-BjcYfhdMMQoErfHk,4107
86
+ gruffpy/rule/docs/useless_docstring_rule.py,sha256=Nmo1f9qKXbIQMohq_rvjwORYg_q0SiF8dje4I3fFinI,8971
87
+ gruffpy/rule/naming/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
88
+ gruffpy/rule/naming/_allowlists.py,sha256=VONfvnMrEpEHCFfm0o_MPZvcbh8PatmEztpDt3hfXXo,318
89
+ gruffpy/rule/naming/_identifier_tokenizer.py,sha256=YZtd2Dv1sxsk6PCHWLdLG0FeWJSt0RLPXa9w9NeSKYs,1987
90
+ gruffpy/rule/naming/abbreviation_rule.py,sha256=UVir1cpp-bmWuog5v06Igw0yz16jwp6uJouvZPILN9M,6859
91
+ gruffpy/rule/naming/boolean_prefix_rule.py,sha256=HLE6AD-40ke7JrAUMbWN9ItG_Q3u3Mo3rHeEDV3YGlM,10143
92
+ gruffpy/rule/naming/confusing_name_rule.py,sha256=jb6Wa2G3KCcFDaUqGUwIWWYVPh2CzQHW5CQZi4tN3j4,4022
93
+ gruffpy/rule/naming/generic_function_rule.py,sha256=DrANy-3CLwppmBVf2c0Lfrcc5w2tvtv-wwIFrjuCR3M,4451
94
+ gruffpy/rule/naming/hungarian_notation_rule.py,sha256=p6-EM27i6dULHB51JrMMjmwwjagCdXafdrfa9KkXlHI,7039
95
+ gruffpy/rule/naming/identifier_quality_rule.py,sha256=lGUskuyTqrpHA40Ztn8YOAIckHlL1uutM9X-rsRLaiw,6485
96
+ gruffpy/rule/naming/module_name_mismatch_rule.py,sha256=VDmmhG1VtFXw4nAUdemthltUIlYdfcefHwQFBQoSWQ8,8840
97
+ gruffpy/rule/naming/parameter_type_name_rule.py,sha256=i6ZuB2E4f2VuJl-a7mHNv6SPUfei8tzwzYlLQcvHTxg,14130
98
+ gruffpy/rule/naming/short_variable_rule.py,sha256=zNgEB0Mnh5NA4JdiTpiCNCrYF5zLIkw1o7IGKgg7Txc,6322
99
+ gruffpy/rule/naming/test_naming_consistency_rule.py,sha256=vDqRb5pt9-9WfY72MiK71DQQ2iuwpCND7eXk_uHTCI0,5266
100
+ gruffpy/rule/security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
101
+ gruffpy/rule/security/_security_metadata.py,sha256=sPJpD2Ac9QN5i-OF_FNwn06qpxpWcjWp9RIbG6U4bls,4004
102
+ gruffpy/rule/security/_security_node_helper.py,sha256=HTrS19VYsHhw8J5xtwFXD44DCsjXBegrl6qo8MSHs7M,8007
103
+ gruffpy/rule/security/_security_taint_helper.py,sha256=GnDk7xiyOZoFV83Os8Gdm5s9hT3fK5YXP6jvyRBlCEc,14435
104
+ gruffpy/rule/security/cors_wildcard_with_credentials_rule.py,sha256=AAXBH_15LkoXfPFPYdwF9NZodJr_SY3CrGZ6C5EnaMY,6008
105
+ gruffpy/rule/security/dangerous_function_call_rule.py,sha256=7GBGnRs98TL-iIlgd5BDJ2a7_T1txHV28vAVbZAz4Vw,4346
106
+ gruffpy/rule/security/disabled_ssl_verification_rule.py,sha256=Os1vyifxrMWql7jKQhlN8RXOlPCHcCM6BMTZmQTUZH0,14017
107
+ gruffpy/rule/security/django_mark_safe_rule.py,sha256=1ANsnANFkScR2SloM5tTeuo6msQcwAslz7_yFKISoZM,5829
108
+ gruffpy/rule/security/django_raw_sql_rule.py,sha256=BErJXjHip2gTq5kxm_TMTBJblzXgJ2QS9aVPiF0R6QQ,5125
109
+ gruffpy/rule/security/error_suppression_rule.py,sha256=5n0ZHUthFlMhzXsKwQjr6wkly62kW5jCdS8hvJG_JgA,5271
110
+ gruffpy/rule/security/extract_compact_user_input_rule.py,sha256=TKklw6h1t6SbHSXSfS7KL5a00cIXOfKuWMlHez5xtxY,4819
111
+ gruffpy/rule/security/flask_debug_enabled_rule.py,sha256=hSHWH1tFJXqBxgbfIsHypxnI51i5N6mE210U-qG3uCs,6170
112
+ gruffpy/rule/security/hardcoded_bind_all_interfaces_rule.py,sha256=XIyPDUzxJP4kpw0Z7rHTASieV29w9r0-pdeE0BsvWyw,6296
113
+ gruffpy/rule/security/hardcoded_framework_secret_key_rule.py,sha256=et7CYkBnG0PXqFrEYdyvs7MAY_g0bbikstmfYRxzLBI,5688
114
+ gruffpy/rule/security/header_injection_rule.py,sha256=bZD6UNwXnEj6G-oSOzNwEg2i-yYBXKmbS8BjItdbImY,4015
115
+ gruffpy/rule/security/insecure_random_rule.py,sha256=Qqa2ltBSxLFaybbkZShZ5H0CtipYFM0jAucZ_JhW4CE,4804
116
+ gruffpy/rule/security/insecure_temp_file_rule.py,sha256=MEPZj87ySrSCV-2TzQIT_7BjJNzxB7GI6TpGUaUyvPI,6829
117
+ gruffpy/rule/security/insecure_tls_protocol_rule.py,sha256=Dyts5fmWxitF4RA6PJckFwF6JFtxtc85qLEa8IuVNNE,4799
118
+ gruffpy/rule/security/jinja2_autoescape_off_rule.py,sha256=mmvCTGiuPw28LVdgQP-k_j4ivIUJtT1KU3GF-B9FSBQ,6695
119
+ gruffpy/rule/security/paramiko_no_host_key_check_rule.py,sha256=DSqFesbJyd4yNCBoCvYgc7h8biEtBeV8UgA_FFlS_Vg,6611
120
+ gruffpy/rule/security/path_traversal_rule.py,sha256=-k1KOzqgbjol4937ZuY8OgyA5acIhlgvyIrVWmkHFns,7188
121
+ gruffpy/rule/security/shell_injection_rule.py,sha256=Uu9W7CuD67y-1bnCHq6wHhAjh0ZrQFoscS9Ut3jRaPc,5042
122
+ gruffpy/rule/security/silent_except_rule.py,sha256=YaxiQ_1vfPRaHjdpYn78NNxzqZOqTQRNENjizYgR7xg,4246
123
+ gruffpy/rule/security/sql_concatenation_rule.py,sha256=9ESjsh4BnRWFPEJ9UR6lYEVhDKym6Ic7a2AW07msv7Y,5851
124
+ gruffpy/rule/security/ssrf_rule.py,sha256=IMK0twyTS5FsGpTMox4FL-eYD0ZnF26snbBkM0kBbzo,6620
125
+ gruffpy/rule/security/unsafe_pickle_rule.py,sha256=o7QRlzPl04Mk9KlNeoyNjq7BcSX27aKqq5mnlNeb8l8,5666
126
+ gruffpy/rule/security/unsafe_yaml_load_rule.py,sha256=vtmY0R4QMPavtokDJsiFcHXb9P9hKGALDTiqWEJgnCA,12423
127
+ gruffpy/rule/security/variable_import_rule.py,sha256=9iuoKFRdPEc8bN9NGuHi41T8MGdbFqu-dnIr-M2MIhE,4011
128
+ gruffpy/rule/security/weak_crypto_rule.py,sha256=nriYTuK9hJsa4Le6jwib-xP4ytoku02QN32JNzQqOJE,9140
129
+ gruffpy/rule/security/xxe_rule.py,sha256=vBBHJzAVLjCwVuqiiUHG0cODJ_TiGVy6jdLgAILC_v0,8375
130
+ gruffpy/rule/sensitive_data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
131
+ gruffpy/rule/sensitive_data/_secret_scanner_helper.py,sha256=RJdpYfoc9664aeSgPdqxINJwXWzeQl3ucNjmtMxoNWA,4539
132
+ gruffpy/rule/sensitive_data/api_key_pattern_rule.py,sha256=hyZ6epn9jzkzDO_Z_vX4CtJAbaFQEKdKJv9-bXh1YDA,4326
133
+ gruffpy/rule/sensitive_data/aws_access_key_rule.py,sha256=YPAkvCw0MJWuMXvTdEtlZVDQonORrhFJrGp7FMlLYoA,3550
134
+ gruffpy/rule/sensitive_data/database_url_password_rule.py,sha256=kYtigpbLsle7ETC023_9aax1fgY3_qF43AAcKuK-1eU,4772
135
+ gruffpy/rule/sensitive_data/hardcoded_env_value_rule.py,sha256=XtZ2a0hcyPu0kd8YjZowsFwOVmF31akZtYczOLOYcDM,4928
136
+ gruffpy/rule/sensitive_data/high_entropy_string_rule.py,sha256=ouuCm1m3k_tjEP7Cd2uCeBXwB5EzyQ4CWEJoF51Km0Y,4862
137
+ gruffpy/rule/sensitive_data/jwt_token_rule.py,sha256=lry7yhIuDfsMqcms4S6HguU2zVaDPI0c5ZxbMy0WZ94,3138
138
+ gruffpy/rule/sensitive_data/phi_pattern_rule.py,sha256=GkRLqk-I7CBt86nB-tdIKggvP9hcbK-vppdJY0o2GDM,3994
139
+ gruffpy/rule/sensitive_data/pii_test_fixture_rule.py,sha256=WVcpA2d2k7xO5gfMQr1RjkfA3bTDZwup_9BvBtNZc-0,4881
140
+ gruffpy/rule/sensitive_data/private_key_rule.py,sha256=vjXit-Ti5puI4PuofRbijh7CYUFvoUlEA07omK0qdVU,3345
141
+ gruffpy/rule/size/__init__.py,sha256=drNTdePLLLceQMgKwm7ViKVIpapljDGLGdTTvGJJzgc,92
142
+ gruffpy/rule/size/_lines.py,sha256=UUMmqqU7nG3zg4sgdR0BvM7HKuVbses7cPKC44dyfvs,3126
143
+ gruffpy/rule/size/attribute_count_rule.py,sha256=KCdHr4IZt5v2FMRcJAz0xAgiOJ49fhWDt55eAXMxN0E,6465
144
+ gruffpy/rule/size/average_function_length_rule.py,sha256=oblfMvIk1Ra7NkOCr08T5djDLfTzn7S4_50esQao0v0,5446
145
+ gruffpy/rule/size/class_length_rule.py,sha256=d_tZKBrUjFF_T1H9ThnNCCRpNzub08rMNNH9BybAwhs,4481
146
+ gruffpy/rule/size/file_length_rule.py,sha256=fdvbAIrWb-60IRP0KdLvvwdHkpvo47igbtH0vYSyOa4,3404
147
+ gruffpy/rule/size/function_length_rule.py,sha256=_Vae7aI7hD3R-n6XZcX0fsv3UEc8vgUZ2t2LMlE9fs4,4797
148
+ gruffpy/rule/size/parameter_count_rule.py,sha256=Kb2xjGnyy8hZpibfnZ0qLDLE6c5Io0-G1oHVckuvlok,4863
149
+ gruffpy/rule/size/public_method_count_rule.py,sha256=ZZ9xgbXiGYvGemquYjKTK_Bu7nH5_bcBIWXCAlRBdBk,5019
150
+ gruffpy/rule/test_quality/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
151
+ gruffpy/rule/test_quality/_pytest_config.py,sha256=-obKn8apxOD7XK-GwM--pdQMyxUMXkl9o4u9wpFXuig,5151
152
+ gruffpy/rule/test_quality/_test_quality_node_helper.py,sha256=DaCb7eNSxP3rQ1Lr9MsNNVuhM0HUAuFG5BpAkZuKVGQ,11432
153
+ gruffpy/rule/test_quality/_test_quality_scope.py,sha256=cvQUjyRgdynuxyM-tlyPwlOp30PB9OUFikaAOkacjco,1197
154
+ gruffpy/rule/test_quality/conditional_logic_rule.py,sha256=Zl9fcwXKtQyLTMlQbFnftfNBkl3muFx0jNqw5pMm8kc,3910
155
+ gruffpy/rule/test_quality/eager_test_rule.py,sha256=-UfUnWw4yR2S2c5M_-2dpujGMJEIOZO6sxJEulIb3QU,4060
156
+ gruffpy/rule/test_quality/empty_parametrize_rule.py,sha256=uNanpS7UT-_SbU38cnF-ACkboOeAabdZRopJGQtMoig,4025
157
+ gruffpy/rule/test_quality/exception_type_only_rule.py,sha256=hhyzyrwjWy2ZXRSS3fyfN12mbu8HeN_gsGtnpu3jwTM,4686
158
+ gruffpy/rule/test_quality/excessive_mocking_rule.py,sha256=NTH9xtGbW7C6gNjHFCMEVTwAaVKgPa3p5D2v7BrcZBA,3906
159
+ gruffpy/rule/test_quality/extends_production_class_rule.py,sha256=aSejBrtofljWxGaINpvCAP55eVKjXXzRLUz7so4FPe0,5235
160
+ gruffpy/rule/test_quality/global_state_mutation_rule.py,sha256=wYb2PiUMAmdcFEOOBf93yx7lKANCwanblftweu5ALGI,3758
161
+ gruffpy/rule/test_quality/loop_assertion_without_message_rule.py,sha256=XnI9A_oO70WyRv7uZl9uPjH1nRLLIYQOgmhehMf4gyE,4263
162
+ gruffpy/rule/test_quality/loop_in_test_rule.py,sha256=fmUFFni1iLYlcU5STsc_Sv5KJDhIzB5_cdWk-ltDFQY,3870
163
+ gruffpy/rule/test_quality/magic_number_assertion_rule.py,sha256=RCsDRb89BZEaKbzwO7QbSJD6VVFZpfZm6LFEHZ_kfwQ,9965
164
+ gruffpy/rule/test_quality/mock_only_test_rule.py,sha256=O8qK_hBAyHaSfr3DwtmdF_0hK235tjPXaoUaYg2VU8s,4553
165
+ gruffpy/rule/test_quality/mock_without_expectation_rule.py,sha256=T52RLmaBANimY-aoTFALRCLGBbCqVLrm8KflMNBK-Vw,4797
166
+ gruffpy/rule/test_quality/mocking_domain_object_rule.py,sha256=YS_XbFjeBaoGEfhQhGnAXOqBTXXZ4gadPlnb0Bo4Y4k,5344
167
+ gruffpy/rule/test_quality/multiple_aaa_cycles_rule.py,sha256=3yQ4Zr8txr17Aorb_rL3ePMYtcBW26GdAp_py6APr6Q,4801
168
+ gruffpy/rule/test_quality/mystery_guest_rule.py,sha256=DjzHQLJtXapF6GYzp6iZYf4_70UjkRqcDtlr_BbB7YY,4646
169
+ gruffpy/rule/test_quality/naming_consistency_rule.py,sha256=Q3tpHlw6EpLtcVnEWu3RBLbyhsFDSuudNyqHyIJRJrI,4110
170
+ gruffpy/rule/test_quality/no_assertions_rule.py,sha256=tUybZcQWeKO-d6SXb1d6qINuHxpH6ewdtN8apMyxXi0,4119
171
+ gruffpy/rule/test_quality/parametrize_annotation_rule.py,sha256=f-OS6dwSOU3kPoG5mJjINnpRkkMSEXkm6mmD0r_Vogc,5302
172
+ gruffpy/rule/test_quality/private_reflection_rule.py,sha256=AFPdvEejIERzzX04z5LdxNvl2rFDiAI_acBWmyP4jwI,4396
173
+ gruffpy/rule/test_quality/pytest_coverage_source_missing_rule.py,sha256=5LGhR_00TsIfeQVihho07LGvH-WpVFh5Q9vjcLCSd0g,3697
174
+ gruffpy/rule/test_quality/pytest_deprecations_not_fatal_rule.py,sha256=Y6Zr1mw3BlLGCTjVYNnva-hoCPQEqDL6F-B1PmzvWFA,3622
175
+ gruffpy/rule/test_quality/pytest_strict_config_missing_rule.py,sha256=uoQM9yxjScHZK28Hq0zfGuX0ZarQU-7TXtpXZio-owQ,3836
176
+ gruffpy/rule/test_quality/repeated_structure_missing_parametrize_rule.py,sha256=EDMe1w6TQux81WYjXwTidlCT0T9QRLtXT8NUibxEtoI,5534
177
+ gruffpy/rule/test_quality/setup_bloat_rule.py,sha256=0SaJuhLf9om7MiBonE4auSjdXzXRg6H1bSiSsfFvH_w,4415
178
+ gruffpy/rule/test_quality/skipped_without_reason_rule.py,sha256=A8fpVs98lW_kVBHqpYxvt6EEqjAbJJbjGb3njRPt16s,5767
179
+ gruffpy/rule/test_quality/sleep_in_test_rule.py,sha256=Bhz-1d19VpIZbE9_aprbLE_eaTZ_GZwRxRiau4hf2Uk,4002
180
+ gruffpy/rule/test_quality/sut_not_called_rule.py,sha256=C2istLsO5toXBS-Z_eWI8oQHWlLG__oQ_jEC-jqP4aA,10350
181
+ gruffpy/rule/test_quality/tautological_type_assertion_rule.py,sha256=tEIkOOCdF66IFePA4fIx67psKGVOos1gyWYVTWIgqMI,5458
182
+ gruffpy/rule/test_quality/test_function_too_long_rule.py,sha256=YGAJwRKnzbas6wlzjRDRa_r66I7hlNCLcp-iWo_G4zw,4544
183
+ gruffpy/rule/test_quality/test_longer_than_sut_rule.py,sha256=V38fhzNO5k1KTClfa-r2ps-oK6LkSo9CbyfXkS4VHkc,5217
184
+ gruffpy/rule/test_quality/testdox_readability_rule.py,sha256=SmKuP7Ooek_Ev78NonHvLc40riaNExg1zsTsYVz2q3o,4194
185
+ gruffpy/rule/test_quality/trivial_assertion_rule.py,sha256=trdqaI5wHo3EnUWOA8y9xFfO917cjjL-rnWtF8zws2o,4124
186
+ gruffpy/rule/test_quality/trivial_snapshot_rule.py,sha256=BaPpImPtA81ZchAKD5vbBtVJrtUSeKyM4asJlJ3WWvw,4461
187
+ gruffpy/rule/test_quality/unused_mock_rule.py,sha256=CxhQ6-YMO4A2wU0we6CMJ3LWKQmdyKBLpF2H4r8qxL8,4256
188
+ gruffpy/rule/waste/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
189
+ gruffpy/rule/waste/commented_out_code_rule.py,sha256=BPdoJbi2Zp_HeSCG58dR1p-AsQCvHKOzsBkByfhn5WA,5616
190
+ gruffpy/rule/waste/empty_class_rule.py,sha256=Nk1WqHs2wYm2VFjG9JE9eQQ6yGSMlAM3AJf-SV1yrIU,5163
191
+ gruffpy/rule/waste/empty_function_rule.py,sha256=KP4I2YIBByOy_3jFWd3Xb8tBT0t72f5DIGEuL4Y3N-Q,4239
192
+ gruffpy/rule/waste/one_line_function_rule.py,sha256=tmHbdvhDSA514U-LRJFGULq6PkFIqmYNQCSI2xJaX1Y,6180
193
+ gruffpy/rule/waste/redundant_variable_rule.py,sha256=OvtgSClM30kTPHcHDMqQhWjlV0Pp9Gs_s7uuhwWwNgU,5407
194
+ gruffpy/rule/waste/unreachable_code_rule.py,sha256=eT-40p983RSknJMI3HQrIFCBRi1aY0jS7Wx4CUZEWKw,8880
195
+ gruffpy/rule/waste/unused_import_rule.py,sha256=lNYSo2D6TgRCjYkUy5lauwekA7BbEuY7gK9AJhdgYYw,7654
196
+ gruffpy/rule/waste/unused_parameter_rule.py,sha256=h8fTo2bRr-SNl0FQ7WRVDZbJ09psNNLSR-25nPxaTLQ,7270
197
+ gruffpy/scoring/__init__.py,sha256=Z4zlSlbEBTNm5iowqMbdyfAcorPOotHYA5W_8TJjT9U,633
198
+ gruffpy/scoring/composite_finding_factory.py,sha256=YnR3OwzzujRZhnz2bngYFNyz8JQyGvhwkP_3tdtIKqE,5396
199
+ gruffpy/scoring/file_score.py,sha256=23rC8qzCxPBclBNw_PWH3q24b9jiBzgOi1gPbQhF9_U,2105
200
+ gruffpy/scoring/grade.py,sha256=CmEixVlAzofqxnRN3rdgXCTjox8POmikLpiQwqaBa9Q,1456
201
+ gruffpy/scoring/pillar_score.py,sha256=H1xkPgJCULEbyZEqkAOkOjEiI1kqYDOv-7z0_npHx4g,1791
202
+ gruffpy/scoring/score_calculator.py,sha256=snztYUSFV3bG6rJu2eUoDKqJkhlxmjhTfp38wNvjgO0,7527
203
+ gruffpy/scoring/score_report.py,sha256=IY24J6VbyMN54yrR7sPSUVcC9sWTaFHTD8OERQ5pOnM,1731
204
+ gruffpy/source/__init__.py,sha256=BsoxqpzeaBwTL2aLO2Z6RgKshF96PnWszi72sv_SjM0,500
205
+ gruffpy/source/discovery.py,sha256=zfxXlHFgsuAQ6KCph2u0TkYUNrJ2p-oewvs2MhLOzkE,10707
206
+ gruffpy/source/gitignore.py,sha256=CueIOG7OmyljBoqnKTyfwd9mouAk56kbbzh7LMDWGVg,5649
207
+ gruffpy/source/source_file.py,sha256=9fIcV21JkxQ8uRk2xJXM1NpKXwXrAs3Cwbws2-uYXog,997
208
+ gruffpy/suppression/__init__.py,sha256=u1HcPfl87XUCGFj1ueRrjp58PiCuBOQXK-XLKfTG2I4,371
209
+ gruffpy/suppression/filter.py,sha256=GCp8AxGRMk_9xdM_Pz531Bd_5amVqie4j9Ww9i3UsuY,1205
210
+ gruffpy/suppression/parser.py,sha256=45NAoRkcrWMzcm70XgfoDyLTEGgIhiaqD9F5rp0G74g,9014
211
+ gruffpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
212
+ gruff_py-0.1.0.dist-info/METADATA,sha256=UNwdKTH8-TEOZ2wHiCNOVRmyWm3Qavzei7WtIEwnooM,8068
213
+ gruff_py-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
214
+ gruff_py-0.1.0.dist-info/entry_points.txt,sha256=mxpZH9qHp-MeX0jFxXXxtgEUOqEYrztQKXWKj-h_SfY,46
215
+ gruff_py-0.1.0.dist-info/licenses/LICENSE.md,sha256=S32zDN0QnatTqL5uCIyvb2hmg4CXYPfWSWaHqsvdbv0,1073
216
+ gruff_py-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ gruff-py = gruffpy.cli:main
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright (c) 2026 Matthew Hansen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
gruffpy/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """gruff-py - Python project quality analyser."""
2
+
3
+ from gruffpy.version import VERSION
4
+
5
+ __version__ = VERSION
gruffpy/__main__.py ADDED
@@ -0,0 +1,9 @@
1
+ """Module execution entry point that delegates ``python -m gruffpy`` to the Click CLI."""
2
+
3
+ from typing import cast
4
+
5
+ import click
6
+
7
+ from gruffpy.cli import main
8
+
9
+ cast(click.Group, main)()
@@ -0,0 +1,15 @@
1
+ from gruffpy.analysis.report import AnalysisReport
2
+ from gruffpy.analysis.run_diagnostic import RunDiagnostic
3
+ from gruffpy.analysis.schema import (
4
+ ANALYSIS_SCHEMA_VERSION,
5
+ BASELINE_SCHEMA_VERSION,
6
+ HOTSPOT_SCHEMA_VERSION,
7
+ )
8
+
9
+ __all__ = [
10
+ "ANALYSIS_SCHEMA_VERSION",
11
+ "AnalysisReport",
12
+ "BASELINE_SCHEMA_VERSION",
13
+ "HOTSPOT_SCHEMA_VERSION",
14
+ "RunDiagnostic",
15
+ ]