crapkit 0.2.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.
- crapkit/__init__.py +2 -0
- crapkit/__main__.py +5 -0
- crapkit/_pygdefer.py +86 -0
- crapkit/analyze.py +375 -0
- crapkit/cache.py +58 -0
- crapkit/churn.py +113 -0
- crapkit/churn_cache.py +108 -0
- crapkit/churn_log.py +286 -0
- crapkit/cli/__init__.py +316 -0
- crapkit/cli/_shared.py +130 -0
- crapkit/cli/admin.py +650 -0
- crapkit/cli/analyses.py +144 -0
- crapkit/cli/parser.py +384 -0
- crapkit/cli/queue.py +926 -0
- crapkit/cli/ratchet_cmds.py +172 -0
- crapkit/cli/reports.py +459 -0
- crapkit/cli/scoring.py +500 -0
- crapkit/cli/verifying.py +580 -0
- crapkit/config.py +289 -0
- crapkit/coupling.py +89 -0
- crapkit/coverage_istanbul.py +225 -0
- crapkit/coverage_py.py +87 -0
- crapkit/covstream.py +320 -0
- crapkit/diffparse.py +98 -0
- crapkit/digest.py +191 -0
- crapkit/discover.py +365 -0
- crapkit/doctor.py +308 -0
- crapkit/dup.py +179 -0
- crapkit/errors.py +18 -0
- crapkit/gitio.py +504 -0
- crapkit/hook.py +167 -0
- crapkit/junitparse.py +87 -0
- crapkit/lanes.py +373 -0
- crapkit/lizardcognitive.py +238 -0
- crapkit/mcp_server.py +167 -0
- crapkit/merge.py +77 -0
- crapkit/mutate.py +96 -0
- crapkit/mutate_pool.py +152 -0
- crapkit/override.py +94 -0
- crapkit/packet.py +343 -0
- crapkit/ratchet.py +236 -0
- crapkit/ratchet_report.py +135 -0
- crapkit/sarif.py +82 -0
- crapkit/sarifio.py +49 -0
- crapkit/scaffold.py +361 -0
- crapkit/score.py +255 -0
- crapkit/snapshot.py +51 -0
- crapkit/store.py +1066 -0
- crapkit/uncovered.py +131 -0
- crapkit/universe.py +157 -0
- crapkit/verify.py +194 -0
- crapkit/watch.py +112 -0
- crapkit/worklist.py +290 -0
- crapkit-0.2.0.dist-info/METADATA +802 -0
- crapkit-0.2.0.dist-info/RECORD +59 -0
- crapkit-0.2.0.dist-info/WHEEL +5 -0
- crapkit-0.2.0.dist-info/entry_points.txt +2 -0
- crapkit-0.2.0.dist-info/licenses/LICENSE +21 -0
- crapkit-0.2.0.dist-info/top_level.txt +1 -0
crapkit/cli/__init__.py
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"""The CLI, split by command family. This module is the seam every other file
|
|
2
|
+
still imports through: `crapkit.cli:main` is the console script and `python -m
|
|
3
|
+
crapkit`, and the test suite reaches dozens of the helpers below by name. Every
|
|
4
|
+
top-level name the single-file cli.py exposed is re-exported here, so nothing
|
|
5
|
+
outside the package has to know which family a helper landed in. No logic lives
|
|
6
|
+
here: each name is defined in the module its family owns.
|
|
7
|
+
|
|
8
|
+
The re-export is by name, not by import. Eight `from .family import ...` lines
|
|
9
|
+
loaded all eight families before anything read one of their names, which put the
|
|
10
|
+
cost of every subcommand on every invocation. `_OWNER` says which module owns
|
|
11
|
+
each name and `__getattr__` fetches it the first time it is read.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
_OWNER = {
|
|
17
|
+
"SCHEMA_VERSION": "_shared",
|
|
18
|
+
"_AMBIGUOUS_TEST": "verifying",
|
|
19
|
+
"_BriefLoader": "queue",
|
|
20
|
+
"_Corpus": "scoring",
|
|
21
|
+
"_Handles": "queue",
|
|
22
|
+
"_SCRIPT_SUFFIXES": "admin",
|
|
23
|
+
"_VersionAction": "parser",
|
|
24
|
+
"_actionable": "queue",
|
|
25
|
+
"_analysis_tools": "_shared",
|
|
26
|
+
"_analysis_workers": "scoring",
|
|
27
|
+
"_analyzed_corpus": "scoring",
|
|
28
|
+
"_apply_verify_override": "verifying",
|
|
29
|
+
"_at_level": "admin",
|
|
30
|
+
"_baseline_behind": "verifying",
|
|
31
|
+
"_baseline_failures": "verifying",
|
|
32
|
+
"_batch_json": "queue",
|
|
33
|
+
"_batch_rows": "queue",
|
|
34
|
+
"_brief_batch": "queue",
|
|
35
|
+
"_brief_churn": "queue",
|
|
36
|
+
"_brief_coupling": "queue",
|
|
37
|
+
"_brief_lines_text": "queue",
|
|
38
|
+
"_brief_mark": "queue",
|
|
39
|
+
"_brief_packet": "queue",
|
|
40
|
+
"_brief_payload": "queue",
|
|
41
|
+
"_brief_target": "queue",
|
|
42
|
+
"_brief_twins": "queue",
|
|
43
|
+
"_brief_versions": "queue",
|
|
44
|
+
"_build_inventory": "scoring",
|
|
45
|
+
"_by_scope": "scoring",
|
|
46
|
+
"_ceiling_breaches": "scoring",
|
|
47
|
+
"_churn_text": "queue",
|
|
48
|
+
"_claim_matches": "queue",
|
|
49
|
+
"_claimable": "queue",
|
|
50
|
+
"_claims_summary": "queue",
|
|
51
|
+
"_claims_to_release": "queue",
|
|
52
|
+
"_collect_lanes": "scoring",
|
|
53
|
+
"_collect_mutants": "analyses",
|
|
54
|
+
"_contexts_for_span": "reports",
|
|
55
|
+
"_coverage_summary": "scoring",
|
|
56
|
+
"_covered_scope_names": "admin",
|
|
57
|
+
"_diff_cover_breach": "verifying",
|
|
58
|
+
"_digest_pair": "reports",
|
|
59
|
+
"_dirty_tag": "_shared",
|
|
60
|
+
"_doctor_findings": "admin",
|
|
61
|
+
"_doctor_hook_modes": "admin",
|
|
62
|
+
"_doctor_keys": "admin",
|
|
63
|
+
"_doctor_lane_summary": "admin",
|
|
64
|
+
"_doctor_lanes": "admin",
|
|
65
|
+
"_doctor_oversized": "admin",
|
|
66
|
+
"_doctor_report": "admin",
|
|
67
|
+
"_doctor_scope_files": "admin",
|
|
68
|
+
"_doctor_scopes": "admin",
|
|
69
|
+
"_doctor_tools": "admin",
|
|
70
|
+
"_doctor_tune": "admin",
|
|
71
|
+
"_doctor_unclaimed": "admin",
|
|
72
|
+
"_doctor_uncovered": "admin",
|
|
73
|
+
"_doctor_unmeasured": "admin",
|
|
74
|
+
"_emit_baseline": "verifying",
|
|
75
|
+
"_emit_coverage_findings": "scoring",
|
|
76
|
+
"_emit_doctor": "admin",
|
|
77
|
+
"_emit_findings": "_shared",
|
|
78
|
+
"_emit_next": "queue",
|
|
79
|
+
"_emit_verify_findings": "verifying",
|
|
80
|
+
"_empty_reasons": "queue",
|
|
81
|
+
"_entry_json": "queue",
|
|
82
|
+
"_excluded_item": "queue",
|
|
83
|
+
"_execute_lanes": "scoring",
|
|
84
|
+
"_execute_parallel": "scoring",
|
|
85
|
+
"_explain_commits": "reports",
|
|
86
|
+
"_explain_extras": "reports",
|
|
87
|
+
"_explain_tests": "reports",
|
|
88
|
+
"_export_scored": "scoring",
|
|
89
|
+
"_extend_gitignore": "admin",
|
|
90
|
+
"_file_sizer": "_shared",
|
|
91
|
+
"_flag_counts": "scoring",
|
|
92
|
+
"_flake_retry": "verifying",
|
|
93
|
+
"_gate_candidates": "scoring",
|
|
94
|
+
"_gate_line": "_shared",
|
|
95
|
+
"_grant_env_override": "verifying",
|
|
96
|
+
"_group_files_by_scope": "verifying",
|
|
97
|
+
"_guard_ratchet_stamp": "verifying",
|
|
98
|
+
"_handle": "queue",
|
|
99
|
+
"_hook_modes": "admin",
|
|
100
|
+
"_interpreter": "admin",
|
|
101
|
+
"_is_test_path": "verifying",
|
|
102
|
+
"_junit_seconds": "admin",
|
|
103
|
+
"_lane_command_problems": "admin",
|
|
104
|
+
"_lane_durations": "admin",
|
|
105
|
+
"_lane_problem": "admin",
|
|
106
|
+
"_lane_problems": "admin",
|
|
107
|
+
"_lane_report": "admin",
|
|
108
|
+
"_lane_reports": "admin",
|
|
109
|
+
"_lane_reuse": "scoring",
|
|
110
|
+
"_lane_seconds": "admin",
|
|
111
|
+
"_latest_full_run": "ratchet_cmds",
|
|
112
|
+
"_latest_scored": "_shared",
|
|
113
|
+
"_latest_span": "reports",
|
|
114
|
+
"_listed_files": "admin",
|
|
115
|
+
"_lizard_version": "admin",
|
|
116
|
+
"_load_ratchet_or_die": "_shared",
|
|
117
|
+
"_load_repo_config": "_shared",
|
|
118
|
+
"_load_sources": "_shared",
|
|
119
|
+
"_mark_text": "queue",
|
|
120
|
+
"_matching_rows": "queue",
|
|
121
|
+
"_maybe_claim": "queue",
|
|
122
|
+
"_maybe_flake_retry": "verifying",
|
|
123
|
+
"_merge_stamp": "ratchet_cmds",
|
|
124
|
+
"_missing_named_script": "admin",
|
|
125
|
+
"_mutation_targets": "analyses",
|
|
126
|
+
"_name_matches": "queue",
|
|
127
|
+
"_name_prefix": "queue",
|
|
128
|
+
"_named_baseline": "verifying",
|
|
129
|
+
"_named_claims": "queue",
|
|
130
|
+
"_newest_coverage_run": "admin",
|
|
131
|
+
"_newest_run_report": "admin",
|
|
132
|
+
"_next_head": "queue",
|
|
133
|
+
"_next_item_payload": "queue",
|
|
134
|
+
"_next_ranked": "queue",
|
|
135
|
+
"_next_reasons": "queue",
|
|
136
|
+
"_no_baseline": "verifying",
|
|
137
|
+
"_no_handle_message": "queue",
|
|
138
|
+
"_no_lane_debt": "queue",
|
|
139
|
+
"_no_lane_gap": "queue",
|
|
140
|
+
"_no_line_message": "queue",
|
|
141
|
+
"_no_match_message": "queue",
|
|
142
|
+
"_no_scopes_reason": "admin",
|
|
143
|
+
"_note_stale_staged": "verifying",
|
|
144
|
+
"_open_store": "_shared",
|
|
145
|
+
"_owning_scope": "verifying",
|
|
146
|
+
"_package_json": "admin",
|
|
147
|
+
"_packet_commands": "queue",
|
|
148
|
+
"_packet_context": "queue",
|
|
149
|
+
"_packet_gate": "queue",
|
|
150
|
+
"_packet_scope": "queue",
|
|
151
|
+
"_pick_baseline": "verifying",
|
|
152
|
+
"_pick_function": "queue",
|
|
153
|
+
"_policy_findings": "ratchet_cmds",
|
|
154
|
+
"_present_markers": "admin",
|
|
155
|
+
"_present_on_disk": "scoring",
|
|
156
|
+
"_print_batches": "queue",
|
|
157
|
+
"_print_brief": "queue",
|
|
158
|
+
"_print_brief_context": "queue",
|
|
159
|
+
"_print_brief_neighbours": "queue",
|
|
160
|
+
"_print_claims": "queue",
|
|
161
|
+
"_print_coverage": "scoring",
|
|
162
|
+
"_print_duplication": "analyses",
|
|
163
|
+
"_print_finding_split": "verifying",
|
|
164
|
+
"_print_findings": "admin",
|
|
165
|
+
"_print_history": "reports",
|
|
166
|
+
"_print_init_summary": "admin",
|
|
167
|
+
"_print_json": "_shared",
|
|
168
|
+
"_print_mutation": "analyses",
|
|
169
|
+
"_print_prune": "reports",
|
|
170
|
+
"_print_ratchet_report": "ratchet_cmds",
|
|
171
|
+
"_print_released": "queue",
|
|
172
|
+
"_print_rescore_table": "scoring",
|
|
173
|
+
"_print_trend": "reports",
|
|
174
|
+
"_print_uncovered": "reports",
|
|
175
|
+
"_print_verify_findings": "verifying",
|
|
176
|
+
"_progress": "scoring",
|
|
177
|
+
"_prune_renames": "ratchet_cmds",
|
|
178
|
+
"_pruned": "ratchet_cmds",
|
|
179
|
+
"_pushdown_floor": "queue",
|
|
180
|
+
"_range_lines": "analyses",
|
|
181
|
+
"_rankable": "queue",
|
|
182
|
+
"_ratchet_entries": "_shared",
|
|
183
|
+
"_ratchet_mark": "reports",
|
|
184
|
+
"_ratchet_merge": "ratchet_cmds",
|
|
185
|
+
"_ratchet_move": "ratchet_cmds",
|
|
186
|
+
"_ratchet_report": "ratchet_cmds",
|
|
187
|
+
"_ratchet_sha256": "verifying",
|
|
188
|
+
"_records_by_scope": "scoring",
|
|
189
|
+
"_release_claims": "verifying",
|
|
190
|
+
"_release_target": "queue",
|
|
191
|
+
"_report_verify": "verifying",
|
|
192
|
+
"_require_ancestor": "verifying",
|
|
193
|
+
"_rescore_analyze": "scoring",
|
|
194
|
+
"_rescore_gate": "scoring",
|
|
195
|
+
"_rescore_json": "scoring",
|
|
196
|
+
"_rescore_overlay": "scoring",
|
|
197
|
+
"_rescored_records": "scoring",
|
|
198
|
+
"_resolve_batch": "queue",
|
|
199
|
+
"_resolve_batches": "queue",
|
|
200
|
+
"_resolve_top": "queue",
|
|
201
|
+
"_route_unowned": "verifying",
|
|
202
|
+
"_row_at_line": "queue",
|
|
203
|
+
"_row_by_handle": "queue",
|
|
204
|
+
"_row_ceiling": "queue",
|
|
205
|
+
"_row_marker": "queue",
|
|
206
|
+
"_run_kind": "scoring",
|
|
207
|
+
"_run_lanes": "scoring",
|
|
208
|
+
"_run_line": "reports",
|
|
209
|
+
"_run_one_lane": "scoring",
|
|
210
|
+
"_runs_list": "reports",
|
|
211
|
+
"_runs_prune": "reports",
|
|
212
|
+
"_scope_config": "queue",
|
|
213
|
+
"_scope_rollup_from_agg": "reports",
|
|
214
|
+
"_scoped_command": "verifying",
|
|
215
|
+
"_scored_run": "scoring",
|
|
216
|
+
"_scored_store": "queue",
|
|
217
|
+
"_select_lanes": "scoring",
|
|
218
|
+
"_send_digest_alert": "reports",
|
|
219
|
+
"_settle_verify": "verifying",
|
|
220
|
+
"_skip_reason": "queue",
|
|
221
|
+
"_split_worklist": "queue",
|
|
222
|
+
"_stale_warning": "queue",
|
|
223
|
+
"_store_if_any": "admin",
|
|
224
|
+
"_store_path": "admin",
|
|
225
|
+
"_store_report": "admin",
|
|
226
|
+
"_taint_note": "verifying",
|
|
227
|
+
"_traced_lane": "scoring",
|
|
228
|
+
"_tracked_files": "scoring",
|
|
229
|
+
"_trend_row": "reports",
|
|
230
|
+
"_tsv_baseline": "verifying",
|
|
231
|
+
"_unclaimed": "queue",
|
|
232
|
+
"_uncovered_fields": "queue",
|
|
233
|
+
"_uncovered_scopes": "admin",
|
|
234
|
+
"_uncovered_text": "reports",
|
|
235
|
+
"_under_scope_path": "verifying",
|
|
236
|
+
"_unknown_key_text": "admin",
|
|
237
|
+
"_unmarked_breaches": "scoring",
|
|
238
|
+
"_untracked_of": "scoring",
|
|
239
|
+
"_verify_attribution": "verifying",
|
|
240
|
+
"_verify_baseline": "verifying",
|
|
241
|
+
"_verify_basis": "verifying",
|
|
242
|
+
"_verify_exit_code": "verifying",
|
|
243
|
+
"_verify_result": "verifying",
|
|
244
|
+
"_verify_store": "verifying",
|
|
245
|
+
"_version_line": "parser",
|
|
246
|
+
"_version_report": "admin",
|
|
247
|
+
"_warn_diff_uncovered": "verifying",
|
|
248
|
+
"_warn_suite_shrink": "verifying",
|
|
249
|
+
"_warn_untracked": "scoring",
|
|
250
|
+
"_watch_banner": "admin",
|
|
251
|
+
"_watch_cycle": "admin",
|
|
252
|
+
"_watch_cycles": "admin",
|
|
253
|
+
"_watch_rescore": "admin",
|
|
254
|
+
"_watched_files": "admin",
|
|
255
|
+
"_working_marks": "ratchet_cmds",
|
|
256
|
+
"_worklist_batches": "queue",
|
|
257
|
+
"_worklist_marks": "queue",
|
|
258
|
+
"_worklist_payload": "queue",
|
|
259
|
+
"_worklist_print": "queue",
|
|
260
|
+
"_write_tsv": "_shared",
|
|
261
|
+
"build_parser": "parser",
|
|
262
|
+
"cmd_brief": "queue",
|
|
263
|
+
"cmd_claims": "queue",
|
|
264
|
+
"cmd_coupling": "analyses",
|
|
265
|
+
"cmd_coverage": "scoring",
|
|
266
|
+
"cmd_digest": "reports",
|
|
267
|
+
"cmd_doctor": "admin",
|
|
268
|
+
"cmd_duplication": "analyses",
|
|
269
|
+
"cmd_explain": "reports",
|
|
270
|
+
"cmd_hook_precommit": "verifying",
|
|
271
|
+
"cmd_init": "admin",
|
|
272
|
+
"cmd_inventory": "scoring",
|
|
273
|
+
"cmd_mcp": "analyses",
|
|
274
|
+
"cmd_mutate": "analyses",
|
|
275
|
+
"cmd_next_item": "queue",
|
|
276
|
+
"cmd_overrides": "reports",
|
|
277
|
+
"cmd_ratchet": "ratchet_cmds",
|
|
278
|
+
"cmd_rescore": "scoring",
|
|
279
|
+
"cmd_runs": "reports",
|
|
280
|
+
"cmd_test_scoped": "verifying",
|
|
281
|
+
"cmd_trend": "reports",
|
|
282
|
+
"cmd_verify": "verifying",
|
|
283
|
+
"cmd_watch": "admin",
|
|
284
|
+
"cmd_worklist": "queue",
|
|
285
|
+
"main": "parser",
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
_MODULES = frozenset(_OWNER.values())
|
|
289
|
+
|
|
290
|
+
__all__ = sorted(_OWNER)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def __getattr__(name):
|
|
294
|
+
"""Re-export on demand: reading a name loads the one family that defines it."""
|
|
295
|
+
family = _OWNER.get(name)
|
|
296
|
+
if family is None:
|
|
297
|
+
return _submodule(name)
|
|
298
|
+
from importlib import import_module
|
|
299
|
+
|
|
300
|
+
value = getattr(import_module(f"{__name__}.{family}"), name)
|
|
301
|
+
globals()[name] = value
|
|
302
|
+
return value
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _submodule(name: str):
|
|
306
|
+
"""`crapkit.cli.admin` after a bare `import crapkit.cli`: the family modules
|
|
307
|
+
were attributes here because __init__ imported every one of them."""
|
|
308
|
+
if name not in _MODULES:
|
|
309
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
310
|
+
from importlib import import_module
|
|
311
|
+
|
|
312
|
+
return import_module(f"{__name__}.{name}")
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def __dir__():
|
|
316
|
+
return sorted(set(_OWNER) | _MODULES | set(globals()))
|
crapkit/cli/_shared.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Helpers more than one command family needs: the JSON envelope and its schema
|
|
2
|
+
version, repo config loading, the snapshot store openers, TSV/SARIF writers, the
|
|
3
|
+
ratchet file reader, and the gate line every gate prints. Nothing here belongs to
|
|
4
|
+
one command; anything that does lives with its family."""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from ..config import load_config_text
|
|
11
|
+
from ..errors import ConfigError, CrapkitError, ToolError
|
|
12
|
+
from ..store import SnapshotStore
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
SCHEMA_VERSION = 1 # bumped whenever a --json field is removed or retyped
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _print_json(payload: dict) -> None:
|
|
19
|
+
"""Every machine-readable payload leaves through here, so a wrapper can pin
|
|
20
|
+
the shape it parses and fail loudly when the shape moves."""
|
|
21
|
+
print(json.dumps({**payload, "schema": SCHEMA_VERSION}, sort_keys=True))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _analysis_tools():
|
|
25
|
+
"""Import the analysis stack lazily so an absent tool maps to ToolError (exit 5).
|
|
26
|
+
|
|
27
|
+
Under deferred_pygments: lizard's Erlang reader binds pygments at module
|
|
28
|
+
scope, and crapkit analyzes no Erlang, so every process paid 26ms of import
|
|
29
|
+
for a reader it never reaches. The proxies come out again as lizard lands.
|
|
30
|
+
"""
|
|
31
|
+
from .._pygdefer import deferred_pygments
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
with deferred_pygments():
|
|
35
|
+
import lizard
|
|
36
|
+
|
|
37
|
+
from ..analyze import analyze_files, load_cache, save_cache
|
|
38
|
+
except ImportError as exc:
|
|
39
|
+
raise ToolError(f"required analysis tool unavailable: {exc}") from exc
|
|
40
|
+
return lizard, analyze_files, load_cache, save_cache
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _load_repo_config(root: Path):
|
|
44
|
+
config_path = root / "crapkit.toml"
|
|
45
|
+
if not config_path.is_file():
|
|
46
|
+
raise ConfigError(f"no crapkit.toml at {root} — nothing to analyze")
|
|
47
|
+
return load_config_text(config_path.read_text(encoding="utf-8"))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _file_sizer(root: Path):
|
|
51
|
+
"""Working-tree sizes for the max_file_bytes cut. A tracked path that is gone
|
|
52
|
+
reads as 0: absence is _present_on_disk's business, not the byte ceiling's."""
|
|
53
|
+
def size_of(path: str) -> int:
|
|
54
|
+
try:
|
|
55
|
+
return (root / path).stat().st_size
|
|
56
|
+
except OSError:
|
|
57
|
+
return 0
|
|
58
|
+
return size_of
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _write_tsv(path: Path, lines) -> None:
|
|
62
|
+
"""Stream an export to disk. newline="\\n" is the determinism contract: the
|
|
63
|
+
bytes must not pick up the host's line separator. Writing line by line keeps
|
|
64
|
+
the whole document from existing as a string next to the rows it came from."""
|
|
65
|
+
with open(path, "w", encoding="utf-8", newline="\n") as fh:
|
|
66
|
+
fh.writelines(lines)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _emit_findings(root: Path, sarif_path: str | None, github: bool, results: list) -> None:
|
|
70
|
+
from ..sarif import github_annotation
|
|
71
|
+
from ..sarifio import write_sarif
|
|
72
|
+
|
|
73
|
+
if sarif_path:
|
|
74
|
+
write_sarif(root / sarif_path, results)
|
|
75
|
+
if github:
|
|
76
|
+
for r in results:
|
|
77
|
+
print(github_annotation(r))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _load_ratchet_or_die(ratchet_path: Path, name: str) -> list:
|
|
81
|
+
from ..ratchet import load_ratchet
|
|
82
|
+
|
|
83
|
+
if not ratchet_path.is_file():
|
|
84
|
+
return []
|
|
85
|
+
try:
|
|
86
|
+
return load_ratchet(ratchet_path.read_text(encoding="utf-8"))
|
|
87
|
+
except ValueError as exc:
|
|
88
|
+
raise ConfigError(f"unreadable ratchet file {name}: {exc}") from exc
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _dirty_tag(dirty: bool) -> str:
|
|
92
|
+
return " [dirty]" if dirty else ""
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _gate_line(v) -> str:
|
|
96
|
+
"""One gate violation, however it was decided; verify and `rescore --gate`
|
|
97
|
+
report the same finding, so they must read the same."""
|
|
98
|
+
return (f" GATE crap {v.crap:8.1f} ccn {v.ccn:>3} cov {v.cov:.0%} "
|
|
99
|
+
f"{v.path}:{v.start} {v.long_name} -> {v.remedy}{_dirty_tag(v.dirty)}")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _latest_scored(store: SnapshotStore):
|
|
103
|
+
from ..store import default_baseline
|
|
104
|
+
return default_baseline(store)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _open_store(root: Path, first_command: str = "coverage") -> SnapshotStore:
|
|
108
|
+
db_path = root / ".crapkit" / "crap.sqlite"
|
|
109
|
+
if not db_path.is_file():
|
|
110
|
+
raise CrapkitError(f"no snapshot in {root} — run `crapkit {first_command}` first")
|
|
111
|
+
return SnapshotStore(db_path)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _ratchet_entries(root: Path, cfg) -> list | None:
|
|
115
|
+
"""The committed marks, or None when the repo carries no marks file yet."""
|
|
116
|
+
from ..ratchet import load_ratchet
|
|
117
|
+
|
|
118
|
+
ratchet_path = root / cfg.ratchet_file
|
|
119
|
+
if not ratchet_path.is_file():
|
|
120
|
+
return None
|
|
121
|
+
return load_ratchet(ratchet_path.read_text(encoding="utf-8"))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _load_sources(root: Path, paths: set) -> dict:
|
|
125
|
+
sources = {}
|
|
126
|
+
for rel in paths:
|
|
127
|
+
p = root / rel
|
|
128
|
+
if p.is_file():
|
|
129
|
+
sources[rel] = p.read_text(encoding="utf-8", errors="replace")
|
|
130
|
+
return sources
|