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.
Files changed (59) hide show
  1. crapkit/__init__.py +2 -0
  2. crapkit/__main__.py +5 -0
  3. crapkit/_pygdefer.py +86 -0
  4. crapkit/analyze.py +375 -0
  5. crapkit/cache.py +58 -0
  6. crapkit/churn.py +113 -0
  7. crapkit/churn_cache.py +108 -0
  8. crapkit/churn_log.py +286 -0
  9. crapkit/cli/__init__.py +316 -0
  10. crapkit/cli/_shared.py +130 -0
  11. crapkit/cli/admin.py +650 -0
  12. crapkit/cli/analyses.py +144 -0
  13. crapkit/cli/parser.py +384 -0
  14. crapkit/cli/queue.py +926 -0
  15. crapkit/cli/ratchet_cmds.py +172 -0
  16. crapkit/cli/reports.py +459 -0
  17. crapkit/cli/scoring.py +500 -0
  18. crapkit/cli/verifying.py +580 -0
  19. crapkit/config.py +289 -0
  20. crapkit/coupling.py +89 -0
  21. crapkit/coverage_istanbul.py +225 -0
  22. crapkit/coverage_py.py +87 -0
  23. crapkit/covstream.py +320 -0
  24. crapkit/diffparse.py +98 -0
  25. crapkit/digest.py +191 -0
  26. crapkit/discover.py +365 -0
  27. crapkit/doctor.py +308 -0
  28. crapkit/dup.py +179 -0
  29. crapkit/errors.py +18 -0
  30. crapkit/gitio.py +504 -0
  31. crapkit/hook.py +167 -0
  32. crapkit/junitparse.py +87 -0
  33. crapkit/lanes.py +373 -0
  34. crapkit/lizardcognitive.py +238 -0
  35. crapkit/mcp_server.py +167 -0
  36. crapkit/merge.py +77 -0
  37. crapkit/mutate.py +96 -0
  38. crapkit/mutate_pool.py +152 -0
  39. crapkit/override.py +94 -0
  40. crapkit/packet.py +343 -0
  41. crapkit/ratchet.py +236 -0
  42. crapkit/ratchet_report.py +135 -0
  43. crapkit/sarif.py +82 -0
  44. crapkit/sarifio.py +49 -0
  45. crapkit/scaffold.py +361 -0
  46. crapkit/score.py +255 -0
  47. crapkit/snapshot.py +51 -0
  48. crapkit/store.py +1066 -0
  49. crapkit/uncovered.py +131 -0
  50. crapkit/universe.py +157 -0
  51. crapkit/verify.py +194 -0
  52. crapkit/watch.py +112 -0
  53. crapkit/worklist.py +290 -0
  54. crapkit-0.2.0.dist-info/METADATA +802 -0
  55. crapkit-0.2.0.dist-info/RECORD +59 -0
  56. crapkit-0.2.0.dist-info/WHEEL +5 -0
  57. crapkit-0.2.0.dist-info/entry_points.txt +2 -0
  58. crapkit-0.2.0.dist-info/licenses/LICENSE +21 -0
  59. crapkit-0.2.0.dist-info/top_level.txt +1 -0
@@ -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