crackerjack 0.29.0__py3-none-any.whl → 0.31.4__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.

Potentially problematic release.


This version of crackerjack might be problematic. Click here for more details.

Files changed (158) hide show
  1. crackerjack/CLAUDE.md +1005 -0
  2. crackerjack/RULES.md +380 -0
  3. crackerjack/__init__.py +42 -13
  4. crackerjack/__main__.py +225 -253
  5. crackerjack/agents/__init__.py +41 -0
  6. crackerjack/agents/architect_agent.py +281 -0
  7. crackerjack/agents/base.py +169 -0
  8. crackerjack/agents/coordinator.py +512 -0
  9. crackerjack/agents/documentation_agent.py +498 -0
  10. crackerjack/agents/dry_agent.py +388 -0
  11. crackerjack/agents/formatting_agent.py +245 -0
  12. crackerjack/agents/import_optimization_agent.py +281 -0
  13. crackerjack/agents/performance_agent.py +669 -0
  14. crackerjack/agents/proactive_agent.py +104 -0
  15. crackerjack/agents/refactoring_agent.py +788 -0
  16. crackerjack/agents/security_agent.py +529 -0
  17. crackerjack/agents/test_creation_agent.py +652 -0
  18. crackerjack/agents/test_specialist_agent.py +486 -0
  19. crackerjack/agents/tracker.py +212 -0
  20. crackerjack/api.py +560 -0
  21. crackerjack/cli/__init__.py +24 -0
  22. crackerjack/cli/facade.py +104 -0
  23. crackerjack/cli/handlers.py +267 -0
  24. crackerjack/cli/interactive.py +471 -0
  25. crackerjack/cli/options.py +401 -0
  26. crackerjack/cli/utils.py +18 -0
  27. crackerjack/code_cleaner.py +670 -0
  28. crackerjack/config/__init__.py +19 -0
  29. crackerjack/config/hooks.py +218 -0
  30. crackerjack/core/__init__.py +0 -0
  31. crackerjack/core/async_workflow_orchestrator.py +406 -0
  32. crackerjack/core/autofix_coordinator.py +200 -0
  33. crackerjack/core/container.py +104 -0
  34. crackerjack/core/enhanced_container.py +542 -0
  35. crackerjack/core/performance.py +243 -0
  36. crackerjack/core/phase_coordinator.py +561 -0
  37. crackerjack/core/proactive_workflow.py +316 -0
  38. crackerjack/core/session_coordinator.py +289 -0
  39. crackerjack/core/workflow_orchestrator.py +640 -0
  40. crackerjack/dynamic_config.py +577 -0
  41. crackerjack/errors.py +263 -41
  42. crackerjack/executors/__init__.py +11 -0
  43. crackerjack/executors/async_hook_executor.py +431 -0
  44. crackerjack/executors/cached_hook_executor.py +242 -0
  45. crackerjack/executors/hook_executor.py +345 -0
  46. crackerjack/executors/individual_hook_executor.py +669 -0
  47. crackerjack/intelligence/__init__.py +44 -0
  48. crackerjack/intelligence/adaptive_learning.py +751 -0
  49. crackerjack/intelligence/agent_orchestrator.py +551 -0
  50. crackerjack/intelligence/agent_registry.py +414 -0
  51. crackerjack/intelligence/agent_selector.py +502 -0
  52. crackerjack/intelligence/integration.py +290 -0
  53. crackerjack/interactive.py +576 -315
  54. crackerjack/managers/__init__.py +11 -0
  55. crackerjack/managers/async_hook_manager.py +135 -0
  56. crackerjack/managers/hook_manager.py +137 -0
  57. crackerjack/managers/publish_manager.py +411 -0
  58. crackerjack/managers/test_command_builder.py +151 -0
  59. crackerjack/managers/test_executor.py +435 -0
  60. crackerjack/managers/test_manager.py +258 -0
  61. crackerjack/managers/test_manager_backup.py +1124 -0
  62. crackerjack/managers/test_progress.py +144 -0
  63. crackerjack/mcp/__init__.py +0 -0
  64. crackerjack/mcp/cache.py +336 -0
  65. crackerjack/mcp/client_runner.py +104 -0
  66. crackerjack/mcp/context.py +615 -0
  67. crackerjack/mcp/dashboard.py +636 -0
  68. crackerjack/mcp/enhanced_progress_monitor.py +479 -0
  69. crackerjack/mcp/file_monitor.py +336 -0
  70. crackerjack/mcp/progress_components.py +569 -0
  71. crackerjack/mcp/progress_monitor.py +949 -0
  72. crackerjack/mcp/rate_limiter.py +332 -0
  73. crackerjack/mcp/server.py +22 -0
  74. crackerjack/mcp/server_core.py +244 -0
  75. crackerjack/mcp/service_watchdog.py +501 -0
  76. crackerjack/mcp/state.py +395 -0
  77. crackerjack/mcp/task_manager.py +257 -0
  78. crackerjack/mcp/tools/__init__.py +17 -0
  79. crackerjack/mcp/tools/core_tools.py +249 -0
  80. crackerjack/mcp/tools/error_analyzer.py +308 -0
  81. crackerjack/mcp/tools/execution_tools.py +370 -0
  82. crackerjack/mcp/tools/execution_tools_backup.py +1097 -0
  83. crackerjack/mcp/tools/intelligence_tool_registry.py +80 -0
  84. crackerjack/mcp/tools/intelligence_tools.py +314 -0
  85. crackerjack/mcp/tools/monitoring_tools.py +502 -0
  86. crackerjack/mcp/tools/proactive_tools.py +384 -0
  87. crackerjack/mcp/tools/progress_tools.py +141 -0
  88. crackerjack/mcp/tools/utility_tools.py +341 -0
  89. crackerjack/mcp/tools/workflow_executor.py +360 -0
  90. crackerjack/mcp/websocket/__init__.py +14 -0
  91. crackerjack/mcp/websocket/app.py +39 -0
  92. crackerjack/mcp/websocket/endpoints.py +559 -0
  93. crackerjack/mcp/websocket/jobs.py +253 -0
  94. crackerjack/mcp/websocket/server.py +116 -0
  95. crackerjack/mcp/websocket/websocket_handler.py +78 -0
  96. crackerjack/mcp/websocket_server.py +10 -0
  97. crackerjack/models/__init__.py +31 -0
  98. crackerjack/models/config.py +93 -0
  99. crackerjack/models/config_adapter.py +230 -0
  100. crackerjack/models/protocols.py +118 -0
  101. crackerjack/models/task.py +154 -0
  102. crackerjack/monitoring/ai_agent_watchdog.py +450 -0
  103. crackerjack/monitoring/regression_prevention.py +638 -0
  104. crackerjack/orchestration/__init__.py +0 -0
  105. crackerjack/orchestration/advanced_orchestrator.py +970 -0
  106. crackerjack/orchestration/execution_strategies.py +341 -0
  107. crackerjack/orchestration/test_progress_streamer.py +636 -0
  108. crackerjack/plugins/__init__.py +15 -0
  109. crackerjack/plugins/base.py +200 -0
  110. crackerjack/plugins/hooks.py +246 -0
  111. crackerjack/plugins/loader.py +335 -0
  112. crackerjack/plugins/managers.py +259 -0
  113. crackerjack/py313.py +8 -3
  114. crackerjack/services/__init__.py +22 -0
  115. crackerjack/services/cache.py +314 -0
  116. crackerjack/services/config.py +347 -0
  117. crackerjack/services/config_integrity.py +99 -0
  118. crackerjack/services/contextual_ai_assistant.py +516 -0
  119. crackerjack/services/coverage_ratchet.py +347 -0
  120. crackerjack/services/debug.py +736 -0
  121. crackerjack/services/dependency_monitor.py +617 -0
  122. crackerjack/services/enhanced_filesystem.py +439 -0
  123. crackerjack/services/file_hasher.py +151 -0
  124. crackerjack/services/filesystem.py +395 -0
  125. crackerjack/services/git.py +165 -0
  126. crackerjack/services/health_metrics.py +611 -0
  127. crackerjack/services/initialization.py +847 -0
  128. crackerjack/services/log_manager.py +286 -0
  129. crackerjack/services/logging.py +174 -0
  130. crackerjack/services/metrics.py +578 -0
  131. crackerjack/services/pattern_cache.py +362 -0
  132. crackerjack/services/pattern_detector.py +515 -0
  133. crackerjack/services/performance_benchmarks.py +653 -0
  134. crackerjack/services/security.py +163 -0
  135. crackerjack/services/server_manager.py +234 -0
  136. crackerjack/services/smart_scheduling.py +144 -0
  137. crackerjack/services/tool_version_service.py +61 -0
  138. crackerjack/services/unified_config.py +437 -0
  139. crackerjack/services/version_checker.py +248 -0
  140. crackerjack/slash_commands/__init__.py +14 -0
  141. crackerjack/slash_commands/init.md +122 -0
  142. crackerjack/slash_commands/run.md +163 -0
  143. crackerjack/slash_commands/status.md +127 -0
  144. crackerjack-0.31.4.dist-info/METADATA +742 -0
  145. crackerjack-0.31.4.dist-info/RECORD +148 -0
  146. crackerjack-0.31.4.dist-info/entry_points.txt +2 -0
  147. crackerjack/.gitignore +0 -34
  148. crackerjack/.libcst.codemod.yaml +0 -18
  149. crackerjack/.pdm.toml +0 -1
  150. crackerjack/.pre-commit-config-ai.yaml +0 -149
  151. crackerjack/.pre-commit-config-fast.yaml +0 -69
  152. crackerjack/.pre-commit-config.yaml +0 -114
  153. crackerjack/crackerjack.py +0 -4140
  154. crackerjack/pyproject.toml +0 -285
  155. crackerjack-0.29.0.dist-info/METADATA +0 -1289
  156. crackerjack-0.29.0.dist-info/RECORD +0 -17
  157. {crackerjack-0.29.0.dist-info → crackerjack-0.31.4.dist-info}/WHEEL +0 -0
  158. {crackerjack-0.29.0.dist-info → crackerjack-0.31.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,577 @@
1
+ import tempfile
2
+ import typing as t
3
+ from pathlib import Path
4
+
5
+ import jinja2
6
+
7
+
8
+ class HookMetadata(t.TypedDict):
9
+ id: str
10
+ name: str | None
11
+ repo: str
12
+ rev: str
13
+ tier: int
14
+ time_estimate: float
15
+ stages: list[str] | None
16
+ args: list[str] | None
17
+ files: str | None
18
+ exclude: str | None
19
+ additional_dependencies: list[str] | None
20
+ types_or: list[str] | None
21
+ language: str | None
22
+ entry: str | None
23
+ experimental: bool
24
+
25
+
26
+ class ConfigMode(t.TypedDict):
27
+ max_time: float
28
+ tiers: list[int]
29
+ experimental: bool
30
+ stages: list[str]
31
+
32
+
33
+ HOOKS_REGISTRY: dict[str, list[HookMetadata]] = {
34
+ "structure": [
35
+ {
36
+ "id": "trailing-whitespace",
37
+ "name": "trailing-whitespace",
38
+ "repo": "https://github.com/pre-commit/pre-commit-hooks",
39
+ "rev": "v6.0.0",
40
+ "tier": 1,
41
+ "time_estimate": 0.2,
42
+ "stages": None,
43
+ "args": None,
44
+ "files": None,
45
+ "exclude": r"^\.venv/",
46
+ "additional_dependencies": None,
47
+ "types_or": None,
48
+ "language": None,
49
+ "entry": None,
50
+ "experimental": False,
51
+ },
52
+ {
53
+ "id": "end-of-file-fixer",
54
+ "name": "end-of-file-fixer",
55
+ "repo": "https://github.com/pre-commit/pre-commit-hooks",
56
+ "rev": "v6.0.0",
57
+ "tier": 1,
58
+ "time_estimate": 0.2,
59
+ "stages": None,
60
+ "args": None,
61
+ "files": None,
62
+ "exclude": r"^\.venv/",
63
+ "additional_dependencies": None,
64
+ "types_or": None,
65
+ "language": None,
66
+ "entry": None,
67
+ "experimental": False,
68
+ },
69
+ {
70
+ "id": "check-yaml",
71
+ "name": "check-yaml",
72
+ "repo": "https://github.com/pre-commit/pre-commit-hooks",
73
+ "rev": "v6.0.0",
74
+ "tier": 1,
75
+ "time_estimate": 0.3,
76
+ "stages": None,
77
+ "args": None,
78
+ "files": None,
79
+ "exclude": r"^\.venv/",
80
+ "additional_dependencies": None,
81
+ "types_or": None,
82
+ "language": None,
83
+ "entry": None,
84
+ "experimental": False,
85
+ },
86
+ {
87
+ "id": "check-toml",
88
+ "name": "check-toml",
89
+ "repo": "https://github.com/pre-commit/pre-commit-hooks",
90
+ "rev": "v6.0.0",
91
+ "tier": 1,
92
+ "time_estimate": 0.3,
93
+ "stages": None,
94
+ "args": None,
95
+ "files": None,
96
+ "exclude": r"^\.venv/",
97
+ "additional_dependencies": None,
98
+ "types_or": None,
99
+ "language": None,
100
+ "entry": None,
101
+ "experimental": False,
102
+ },
103
+ {
104
+ "id": "check-added-large-files",
105
+ "name": "check-added-large-files",
106
+ "repo": "https://github.com/pre-commit/pre-commit-hooks",
107
+ "rev": "v6.0.0",
108
+ "tier": 1,
109
+ "time_estimate": 0.5,
110
+ "stages": None,
111
+ "args": None,
112
+ "files": None,
113
+ "exclude": r"^\.venv/",
114
+ "additional_dependencies": None,
115
+ "types_or": None,
116
+ "language": None,
117
+ "entry": None,
118
+ "experimental": False,
119
+ },
120
+ ],
121
+ "package_management": [
122
+ {
123
+ "id": "uv-lock",
124
+ "name": None,
125
+ "repo": "https://github.com/astral-sh/uv-pre-commit",
126
+ "rev": "0.8.13",
127
+ "tier": 1,
128
+ "time_estimate": 0.5,
129
+ "stages": None,
130
+ "args": None,
131
+ "files": r"^pyproject\.toml$",
132
+ "exclude": None,
133
+ "additional_dependencies": None,
134
+ "types_or": None,
135
+ "language": None,
136
+ "entry": None,
137
+ "experimental": False,
138
+ },
139
+ ],
140
+ "security": [
141
+ {
142
+ "id": "gitleaks",
143
+ "name": None,
144
+ "repo": "https://github.com/gitleaks/gitleaks",
145
+ "rev": "v8.28.0",
146
+ "tier": 2,
147
+ "time_estimate": 1.0,
148
+ "stages": None,
149
+ "args": None,
150
+ "files": None,
151
+ "exclude": r"uv\.lock|pyproject\.toml|tests/.*|docs/.*|.*\.md",
152
+ "additional_dependencies": None,
153
+ "types_or": None,
154
+ "language": None,
155
+ "entry": None,
156
+ "experimental": False,
157
+ },
158
+ {
159
+ "id": "bandit",
160
+ "name": None,
161
+ "repo": "https://github.com/PyCQA/bandit",
162
+ "rev": "1.8.6",
163
+ "tier": 3,
164
+ "time_estimate": 3.0,
165
+ "stages": ["pre-push", "manual"],
166
+ "args": ["-c", "pyproject.toml", "-r", "-ll"],
167
+ "files": "^crackerjack/.*\\.py$",
168
+ "exclude": None,
169
+ "additional_dependencies": None,
170
+ "types_or": None,
171
+ "language": None,
172
+ "entry": None,
173
+ "experimental": False,
174
+ },
175
+ ],
176
+ "formatting": [
177
+ {
178
+ "id": "codespell",
179
+ "name": None,
180
+ "repo": "https://github.com/codespell-project/codespell",
181
+ "rev": "v2.4.1",
182
+ "tier": 2,
183
+ "time_estimate": 1.0,
184
+ "stages": None,
185
+ "args": None,
186
+ "files": None,
187
+ "exclude": r"^\.venv/",
188
+ "additional_dependencies": ["tomli"],
189
+ "types_or": None,
190
+ "language": None,
191
+ "entry": None,
192
+ "experimental": False,
193
+ },
194
+ {
195
+ "id": "ruff-check",
196
+ "name": None,
197
+ "repo": "https://github.com/astral-sh/ruff-pre-commit",
198
+ "rev": "v0.12.10",
199
+ "tier": 2,
200
+ "time_estimate": 1.5,
201
+ "stages": None,
202
+ "args": None,
203
+ "files": None,
204
+ "exclude": r"^\.venv/",
205
+ "additional_dependencies": None,
206
+ "types_or": None,
207
+ "language": None,
208
+ "entry": None,
209
+ "experimental": False,
210
+ },
211
+ {
212
+ "id": "ruff-format",
213
+ "name": None,
214
+ "repo": "https://github.com/astral-sh/ruff-pre-commit",
215
+ "rev": "v0.12.10",
216
+ "tier": 2,
217
+ "time_estimate": 1.0,
218
+ "stages": None,
219
+ "args": None,
220
+ "files": None,
221
+ "exclude": r"^\.venv/",
222
+ "additional_dependencies": None,
223
+ "types_or": None,
224
+ "language": None,
225
+ "entry": None,
226
+ "experimental": False,
227
+ },
228
+ {
229
+ "id": "mdformat",
230
+ "name": None,
231
+ "repo": "https://github.com/executablebooks/mdformat",
232
+ "rev": "0.7.22",
233
+ "tier": 2,
234
+ "time_estimate": 0.5,
235
+ "stages": None,
236
+ "args": None,
237
+ "files": None,
238
+ "exclude": r"^\.venv/",
239
+ "additional_dependencies": ["mdformat-ruff"],
240
+ "types_or": None,
241
+ "language": None,
242
+ "entry": None,
243
+ "experimental": False,
244
+ },
245
+ ],
246
+ "analysis": [
247
+ {
248
+ "id": "vulture",
249
+ "name": None,
250
+ "repo": "https://github.com/jendrikseipp/vulture",
251
+ "rev": "v2.14",
252
+ "tier": 3,
253
+ "time_estimate": 2.0,
254
+ "stages": ["pre-push", "manual"],
255
+ "args": None,
256
+ "files": "^crackerjack/.*\\.py$",
257
+ "exclude": None,
258
+ "additional_dependencies": None,
259
+ "types_or": None,
260
+ "language": None,
261
+ "entry": None,
262
+ "experimental": False,
263
+ },
264
+ {
265
+ "id": "creosote",
266
+ "name": None,
267
+ "repo": "https://github.com/fredrikaverpil/creosote",
268
+ "rev": "v4.1.0",
269
+ "tier": 3,
270
+ "time_estimate": 1.5,
271
+ "stages": ["pre-push", "manual"],
272
+ "args": None,
273
+ "files": None,
274
+ "exclude": r"^\.venv/",
275
+ "additional_dependencies": None,
276
+ "types_or": None,
277
+ "language": None,
278
+ "entry": None,
279
+ "experimental": False,
280
+ },
281
+ {
282
+ "id": "complexipy",
283
+ "name": None,
284
+ "repo": "https://github.com/rohaquinlop/complexipy-pre-commit",
285
+ "rev": "v3.3.0",
286
+ "tier": 3,
287
+ "time_estimate": 2.0,
288
+ "stages": ["pre-push", "manual"],
289
+ "args": ["-d", "low"],
290
+ "files": r"^crackerjack/.*\.py$",
291
+ "exclude": r"^(\.venv/|tests/)",
292
+ "additional_dependencies": None,
293
+ "types_or": None,
294
+ "language": None,
295
+ "entry": None,
296
+ "experimental": False,
297
+ },
298
+ {
299
+ "id": "refurb",
300
+ "name": None,
301
+ "repo": "https://github.com/dosisod/refurb",
302
+ "rev": "v2.1.0",
303
+ "tier": 3,
304
+ "time_estimate": 3.0,
305
+ "stages": ["pre-push", "manual"],
306
+ "args": ["--ignore", "FURB184", "--ignore", "FURB120"],
307
+ "files": "^crackerjack/.*\\.py$",
308
+ "exclude": r"^tests/.*\.py$",
309
+ "additional_dependencies": None,
310
+ "types_or": None,
311
+ "language": None,
312
+ "entry": None,
313
+ "experimental": False,
314
+ },
315
+ {
316
+ "id": "pyright",
317
+ "name": None,
318
+ "repo": "https://github.com/RobertCraigie/pyright-python",
319
+ "rev": "v1.1.404",
320
+ "tier": 3,
321
+ "time_estimate": 5.0,
322
+ "stages": ["pre-push", "manual"],
323
+ "args": None,
324
+ "files": "^crackerjack/.*\\.py$",
325
+ "exclude": r"^crackerjack/(mcp|plugins)/.*\.py$|crackerjack/code_cleaner\.py$",
326
+ "additional_dependencies": None,
327
+ "types_or": None,
328
+ "language": None,
329
+ "entry": None,
330
+ "experimental": False,
331
+ },
332
+ ],
333
+ "experimental": [
334
+ {
335
+ "id": "pyrefly",
336
+ "name": "pyrefly",
337
+ "repo": "local",
338
+ "rev": "",
339
+ "tier": 3,
340
+ "time_estimate": 5.0,
341
+ "stages": ["manual"],
342
+ "args": ["--check"],
343
+ "files": "^crackerjack/.*\\.py$",
344
+ "exclude": None,
345
+ "additional_dependencies": ["pyrefly >= 0.1.0"],
346
+ "types_or": ["python"],
347
+ "language": "python",
348
+ "entry": "python -m pyrefly",
349
+ "experimental": True,
350
+ },
351
+ {
352
+ "id": "ty",
353
+ "name": "ty",
354
+ "repo": "local",
355
+ "rev": "",
356
+ "tier": 3,
357
+ "time_estimate": 2.0,
358
+ "stages": ["manual"],
359
+ "args": ["--check"],
360
+ "files": "^crackerjack/.*\\.py$",
361
+ "exclude": None,
362
+ "additional_dependencies": ["ty >= 0.1.0"],
363
+ "types_or": ["python"],
364
+ "language": "python",
365
+ "entry": "python -m ty",
366
+ "experimental": True,
367
+ },
368
+ ],
369
+ }
370
+
371
+ CONFIG_MODES: dict[str, ConfigMode] = {
372
+ "fast": {
373
+ "max_time": 5.0,
374
+ "tiers": [1, 2],
375
+ "experimental": False,
376
+ "stages": ["pre-commit"],
377
+ },
378
+ "comprehensive": {
379
+ "max_time": 30.0,
380
+ "tiers": [1, 2, 3],
381
+ "experimental": False,
382
+ "stages": ["pre-commit", "pre-push", "manual"],
383
+ },
384
+ "experimental": {
385
+ "max_time": 60.0,
386
+ "tiers": [1, 2, 3],
387
+ "experimental": True,
388
+ "stages": ["pre-commit", "pre-push", "manual"],
389
+ },
390
+ }
391
+
392
+ PRE_COMMIT_TEMPLATE = """repos:
393
+ {%- for repo in repos %}
394
+ {%- if repo.comment %}
395
+ # {{ repo.comment }}
396
+ {%- endif %}
397
+ - repo: {{ repo.repo }}
398
+ {%- if repo.rev %}
399
+ rev: {{ repo.rev }}
400
+ {%- endif %}
401
+ hooks:
402
+ {%- for hook in repo.hooks %}
403
+ - id: {{ hook.id }}
404
+ {%- if hook.name %}
405
+ name: {{ hook.name }}
406
+ {%- endif %}
407
+ {%- if hook.entry %}
408
+ entry: {{ hook.entry }}
409
+ {%- endif %}
410
+ {%- if hook.language %}
411
+ language: {{ hook.language }}
412
+ {%- endif %}
413
+ {%- if hook.args %}
414
+ args: {{ hook.args | tojson }}
415
+ {%- endif %}
416
+ {%- if hook.files %}
417
+ files: {{ hook.files }}
418
+ {%- endif %}
419
+ {%- if hook.exclude %}
420
+ exclude: {{ hook.exclude }}
421
+ {%- endif %}
422
+ {%- if hook.types_or %}
423
+ types_or: {{ hook.types_or | tojson }}
424
+ {%- endif %}
425
+ {%- if hook.stages %}
426
+ stages: {{ hook.stages | tojson }}
427
+ {%- endif %}
428
+ {%- if hook.additional_dependencies %}
429
+ additional_dependencies: {{ hook.additional_dependencies | tojson }}
430
+ {%- endif %}
431
+ {%- endfor %}
432
+
433
+ {%- endfor %}
434
+ """
435
+
436
+
437
+ class DynamicConfigGenerator:
438
+ def __init__(self) -> None:
439
+ self.template = jinja2.Template(PRE_COMMIT_TEMPLATE)
440
+
441
+ def _should_include_hook(
442
+ self,
443
+ hook: HookMetadata,
444
+ config: ConfigMode,
445
+ enabled_experimental: list[str],
446
+ ) -> bool:
447
+ if hook["tier"] not in config["tiers"]:
448
+ return False
449
+ if hook["experimental"]:
450
+ if not config["experimental"]:
451
+ return False
452
+ if enabled_experimental and hook["id"] not in enabled_experimental:
453
+ return False
454
+ return not hook["time_estimate"] > config["max_time"]
455
+
456
+ def filter_hooks_for_mode(
457
+ self,
458
+ mode: str,
459
+ enabled_experimental: list[str] | None = None,
460
+ ) -> list[HookMetadata]:
461
+ config = CONFIG_MODES[mode]
462
+ filtered_hooks: list[HookMetadata] = []
463
+ enabled_experimental = enabled_experimental or []
464
+ for category_hooks in HOOKS_REGISTRY.values():
465
+ for hook in category_hooks:
466
+ if self._should_include_hook(hook, config, enabled_experimental):
467
+ filtered_hooks.append(hook)
468
+
469
+ return filtered_hooks
470
+
471
+ def group_hooks_by_repo(
472
+ self,
473
+ hooks: list[HookMetadata],
474
+ ) -> dict[tuple[str, str], list[HookMetadata]]:
475
+ repo_groups: dict[tuple[str, str], list[HookMetadata]] = {}
476
+ for hook in hooks:
477
+ key = (hook["repo"], hook["rev"])
478
+ if key not in repo_groups:
479
+ repo_groups[key] = []
480
+ repo_groups[key].append(hook)
481
+
482
+ return repo_groups
483
+
484
+ def _get_repo_comment(self, repo_url: str) -> str | None:
485
+ repo_comments = {
486
+ "https://github.com/pre-commit/pre-commit-hooks": "File structure and format validators",
487
+ "local": "Local tools and custom hooks",
488
+ }
489
+ if repo_url in repo_comments:
490
+ return repo_comments[repo_url]
491
+ security_keywords = ["security", "bandit", "gitleaks"]
492
+ if any(keyword in repo_url for keyword in security_keywords):
493
+ return "Security checks"
494
+ formatting_keywords = ["ruff", "mdformat", "codespell"]
495
+ if any(keyword in repo_url for keyword in formatting_keywords):
496
+ return "Code formatting and quality"
497
+
498
+ return None
499
+
500
+ def _merge_configs(
501
+ self,
502
+ base_config: dict[str, t.Any],
503
+ new_config: dict[str, t.Any],
504
+ ) -> dict[str, t.Any]:
505
+ result = base_config.copy()
506
+
507
+ for key, value in new_config.items():
508
+ if (
509
+ key in result
510
+ and isinstance(result[key], dict)
511
+ and isinstance(value, dict)
512
+ ):
513
+ result[key] = self._merge_configs(result[key], value)
514
+ else:
515
+ result[key] = value
516
+
517
+ return result
518
+
519
+ def generate_config(
520
+ self,
521
+ mode: str,
522
+ enabled_experimental: list[str] | None = None,
523
+ ) -> str:
524
+ filtered_hooks = self.filter_hooks_for_mode(mode, enabled_experimental)
525
+ repo_groups = self.group_hooks_by_repo(filtered_hooks)
526
+ repos: list[dict[str, t.Any]] = []
527
+ for (repo_url, rev), hooks in repo_groups.items():
528
+ repo_data = {
529
+ "repo": repo_url,
530
+ "rev": rev,
531
+ "hooks": hooks,
532
+ "comment": self._get_repo_comment(repo_url),
533
+ }
534
+ repos.append(repo_data)
535
+
536
+ return self.template.render(repos=repos)
537
+
538
+ def create_temp_config(
539
+ self,
540
+ mode: str,
541
+ enabled_experimental: list[str] | None = None,
542
+ ) -> Path:
543
+ config_content = self.generate_config(mode, enabled_experimental)
544
+ temp_file = tempfile.NamedTemporaryFile(
545
+ mode="w",
546
+ suffix=".yaml",
547
+ prefix=f"crackerjack-{mode}-",
548
+ delete=False,
549
+ encoding="utf-8",
550
+ )
551
+ temp_file.write(config_content)
552
+ temp_file.flush()
553
+ temp_file.close()
554
+
555
+ return Path(temp_file.name)
556
+
557
+
558
+ def generate_config_for_mode(
559
+ mode: str,
560
+ enabled_experimental: list[str] | None = None,
561
+ ) -> Path:
562
+ return DynamicConfigGenerator().create_temp_config(mode, enabled_experimental)
563
+
564
+
565
+ def get_available_modes() -> list[str]:
566
+ return list(CONFIG_MODES.keys())
567
+
568
+
569
+ def add_experimental_hook(hook_id: str, hook_config: HookMetadata) -> None:
570
+ hook_config["experimental"] = True
571
+ HOOKS_REGISTRY["experimental"].append(hook_config)
572
+
573
+
574
+ def remove_experimental_hook(hook_id: str) -> None:
575
+ HOOKS_REGISTRY["experimental"] = [
576
+ hook for hook in HOOKS_REGISTRY["experimental"] if hook["id"] != hook_id
577
+ ]