crackerjack 0.33.0__py3-none-any.whl → 0.33.1__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.
- crackerjack/__main__.py +1350 -34
- crackerjack/adapters/__init__.py +17 -0
- crackerjack/adapters/lsp_client.py +358 -0
- crackerjack/adapters/rust_tool_adapter.py +194 -0
- crackerjack/adapters/rust_tool_manager.py +193 -0
- crackerjack/adapters/skylos_adapter.py +231 -0
- crackerjack/adapters/zuban_adapter.py +560 -0
- crackerjack/agents/base.py +7 -3
- crackerjack/agents/coordinator.py +271 -33
- crackerjack/agents/documentation_agent.py +9 -15
- crackerjack/agents/dry_agent.py +3 -15
- crackerjack/agents/formatting_agent.py +1 -1
- crackerjack/agents/import_optimization_agent.py +36 -180
- crackerjack/agents/performance_agent.py +17 -98
- crackerjack/agents/performance_helpers.py +7 -31
- crackerjack/agents/proactive_agent.py +1 -3
- crackerjack/agents/refactoring_agent.py +16 -85
- crackerjack/agents/refactoring_helpers.py +7 -42
- crackerjack/agents/security_agent.py +9 -48
- crackerjack/agents/test_creation_agent.py +356 -513
- crackerjack/agents/test_specialist_agent.py +0 -4
- crackerjack/api.py +6 -25
- crackerjack/cli/cache_handlers.py +204 -0
- crackerjack/cli/cache_handlers_enhanced.py +683 -0
- crackerjack/cli/facade.py +100 -0
- crackerjack/cli/handlers.py +224 -9
- crackerjack/cli/interactive.py +6 -4
- crackerjack/cli/options.py +642 -55
- crackerjack/cli/utils.py +2 -1
- crackerjack/code_cleaner.py +58 -117
- crackerjack/config/global_lock_config.py +8 -48
- crackerjack/config/hooks.py +53 -62
- crackerjack/core/async_workflow_orchestrator.py +24 -34
- crackerjack/core/autofix_coordinator.py +3 -17
- crackerjack/core/enhanced_container.py +4 -13
- crackerjack/core/file_lifecycle.py +12 -89
- crackerjack/core/performance.py +2 -2
- crackerjack/core/performance_monitor.py +15 -55
- crackerjack/core/phase_coordinator.py +104 -204
- crackerjack/core/resource_manager.py +14 -90
- crackerjack/core/service_watchdog.py +62 -95
- crackerjack/core/session_coordinator.py +149 -0
- crackerjack/core/timeout_manager.py +14 -72
- crackerjack/core/websocket_lifecycle.py +13 -78
- crackerjack/core/workflow_orchestrator.py +171 -174
- crackerjack/docs/INDEX.md +11 -0
- crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
- crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
- crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
- crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
- crackerjack/docs/generated/api/SERVICES.md +1252 -0
- crackerjack/documentation/__init__.py +31 -0
- crackerjack/documentation/ai_templates.py +756 -0
- crackerjack/documentation/dual_output_generator.py +765 -0
- crackerjack/documentation/mkdocs_integration.py +518 -0
- crackerjack/documentation/reference_generator.py +977 -0
- crackerjack/dynamic_config.py +55 -50
- crackerjack/executors/async_hook_executor.py +10 -15
- crackerjack/executors/cached_hook_executor.py +117 -43
- crackerjack/executors/hook_executor.py +8 -34
- crackerjack/executors/hook_lock_manager.py +26 -183
- crackerjack/executors/individual_hook_executor.py +13 -11
- crackerjack/executors/lsp_aware_hook_executor.py +270 -0
- crackerjack/executors/tool_proxy.py +417 -0
- crackerjack/hooks/lsp_hook.py +79 -0
- crackerjack/intelligence/adaptive_learning.py +25 -10
- crackerjack/intelligence/agent_orchestrator.py +2 -5
- crackerjack/intelligence/agent_registry.py +34 -24
- crackerjack/intelligence/agent_selector.py +5 -7
- crackerjack/interactive.py +17 -6
- crackerjack/managers/async_hook_manager.py +0 -1
- crackerjack/managers/hook_manager.py +79 -1
- crackerjack/managers/publish_manager.py +44 -8
- crackerjack/managers/test_command_builder.py +1 -15
- crackerjack/managers/test_executor.py +1 -3
- crackerjack/managers/test_manager.py +98 -7
- crackerjack/managers/test_manager_backup.py +10 -9
- crackerjack/mcp/cache.py +2 -2
- crackerjack/mcp/client_runner.py +1 -1
- crackerjack/mcp/context.py +191 -68
- crackerjack/mcp/dashboard.py +7 -5
- crackerjack/mcp/enhanced_progress_monitor.py +31 -28
- crackerjack/mcp/file_monitor.py +30 -23
- crackerjack/mcp/progress_components.py +31 -21
- crackerjack/mcp/progress_monitor.py +50 -53
- crackerjack/mcp/rate_limiter.py +6 -6
- crackerjack/mcp/server_core.py +17 -16
- crackerjack/mcp/service_watchdog.py +2 -1
- crackerjack/mcp/state.py +4 -7
- crackerjack/mcp/task_manager.py +11 -9
- crackerjack/mcp/tools/core_tools.py +173 -32
- crackerjack/mcp/tools/error_analyzer.py +3 -2
- crackerjack/mcp/tools/execution_tools.py +8 -10
- crackerjack/mcp/tools/execution_tools_backup.py +42 -30
- crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
- crackerjack/mcp/tools/intelligence_tools.py +5 -2
- crackerjack/mcp/tools/monitoring_tools.py +33 -70
- crackerjack/mcp/tools/proactive_tools.py +24 -11
- crackerjack/mcp/tools/progress_tools.py +5 -8
- crackerjack/mcp/tools/utility_tools.py +20 -14
- crackerjack/mcp/tools/workflow_executor.py +62 -40
- crackerjack/mcp/websocket/app.py +8 -0
- crackerjack/mcp/websocket/endpoints.py +352 -357
- crackerjack/mcp/websocket/jobs.py +40 -57
- crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
- crackerjack/mcp/websocket/server.py +7 -25
- crackerjack/mcp/websocket/websocket_handler.py +6 -17
- crackerjack/mixins/__init__.py +0 -2
- crackerjack/mixins/error_handling.py +1 -70
- crackerjack/models/config.py +12 -1
- crackerjack/models/config_adapter.py +49 -1
- crackerjack/models/protocols.py +122 -122
- crackerjack/models/resource_protocols.py +55 -210
- crackerjack/monitoring/ai_agent_watchdog.py +13 -13
- crackerjack/monitoring/metrics_collector.py +426 -0
- crackerjack/monitoring/regression_prevention.py +8 -8
- crackerjack/monitoring/websocket_server.py +643 -0
- crackerjack/orchestration/advanced_orchestrator.py +11 -6
- crackerjack/orchestration/coverage_improvement.py +3 -3
- crackerjack/orchestration/execution_strategies.py +26 -6
- crackerjack/orchestration/test_progress_streamer.py +8 -5
- crackerjack/plugins/base.py +2 -2
- crackerjack/plugins/hooks.py +7 -0
- crackerjack/plugins/managers.py +11 -8
- crackerjack/security/__init__.py +0 -1
- crackerjack/security/audit.py +6 -35
- crackerjack/services/anomaly_detector.py +392 -0
- crackerjack/services/api_extractor.py +615 -0
- crackerjack/services/backup_service.py +2 -2
- crackerjack/services/bounded_status_operations.py +15 -152
- crackerjack/services/cache.py +127 -1
- crackerjack/services/changelog_automation.py +395 -0
- crackerjack/services/config.py +15 -9
- crackerjack/services/config_merge.py +19 -80
- crackerjack/services/config_template.py +506 -0
- crackerjack/services/contextual_ai_assistant.py +48 -22
- crackerjack/services/coverage_badge_service.py +171 -0
- crackerjack/services/coverage_ratchet.py +27 -25
- crackerjack/services/debug.py +3 -3
- crackerjack/services/dependency_analyzer.py +460 -0
- crackerjack/services/dependency_monitor.py +14 -11
- crackerjack/services/documentation_generator.py +491 -0
- crackerjack/services/documentation_service.py +675 -0
- crackerjack/services/enhanced_filesystem.py +6 -5
- crackerjack/services/enterprise_optimizer.py +865 -0
- crackerjack/services/error_pattern_analyzer.py +676 -0
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/git.py +8 -25
- crackerjack/services/health_metrics.py +10 -8
- crackerjack/services/heatmap_generator.py +735 -0
- crackerjack/services/initialization.py +11 -30
- crackerjack/services/input_validator.py +5 -97
- crackerjack/services/intelligent_commit.py +327 -0
- crackerjack/services/log_manager.py +15 -12
- crackerjack/services/logging.py +4 -3
- crackerjack/services/lsp_client.py +628 -0
- crackerjack/services/memory_optimizer.py +19 -87
- crackerjack/services/metrics.py +42 -33
- crackerjack/services/parallel_executor.py +9 -67
- crackerjack/services/pattern_cache.py +1 -1
- crackerjack/services/pattern_detector.py +6 -6
- crackerjack/services/performance_benchmarks.py +18 -59
- crackerjack/services/performance_cache.py +20 -81
- crackerjack/services/performance_monitor.py +27 -95
- crackerjack/services/predictive_analytics.py +510 -0
- crackerjack/services/quality_baseline.py +234 -0
- crackerjack/services/quality_baseline_enhanced.py +646 -0
- crackerjack/services/quality_intelligence.py +785 -0
- crackerjack/services/regex_patterns.py +605 -524
- crackerjack/services/regex_utils.py +43 -123
- crackerjack/services/secure_path_utils.py +5 -164
- crackerjack/services/secure_status_formatter.py +30 -141
- crackerjack/services/secure_subprocess.py +11 -92
- crackerjack/services/security.py +9 -41
- crackerjack/services/security_logger.py +12 -24
- crackerjack/services/server_manager.py +124 -16
- crackerjack/services/status_authentication.py +16 -159
- crackerjack/services/status_security_manager.py +4 -131
- crackerjack/services/thread_safe_status_collector.py +19 -125
- crackerjack/services/unified_config.py +21 -13
- crackerjack/services/validation_rate_limiter.py +5 -54
- crackerjack/services/version_analyzer.py +459 -0
- crackerjack/services/version_checker.py +1 -1
- crackerjack/services/websocket_resource_limiter.py +10 -144
- crackerjack/services/zuban_lsp_service.py +390 -0
- crackerjack/slash_commands/__init__.py +2 -7
- crackerjack/slash_commands/run.md +2 -2
- crackerjack/tools/validate_input_validator_patterns.py +14 -40
- crackerjack/tools/validate_regex_patterns.py +19 -48
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.1.dist-info}/METADATA +196 -25
- crackerjack-0.33.1.dist-info/RECORD +229 -0
- crackerjack/CLAUDE.md +0 -207
- crackerjack/RULES.md +0 -380
- crackerjack/py313.py +0 -234
- crackerjack-0.33.0.dist-info/RECORD +0 -187
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.1.dist-info}/WHEEL +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.1.dist-info}/licenses/LICENSE +0 -0
crackerjack/dynamic_config.py
CHANGED
|
@@ -21,6 +21,7 @@ class HookMetadata(t.TypedDict):
|
|
|
21
21
|
language: str | None
|
|
22
22
|
entry: str | None
|
|
23
23
|
experimental: bool
|
|
24
|
+
pass_filenames: bool | None
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
class ConfigMode(t.TypedDict):
|
|
@@ -48,6 +49,7 @@ HOOKS_REGISTRY: dict[str, list[HookMetadata]] = {
|
|
|
48
49
|
"language": "system",
|
|
49
50
|
"entry": "uv run python -m crackerjack.tools.validate_regex_patterns",
|
|
50
51
|
"experimental": False,
|
|
52
|
+
"pass_filenames": None,
|
|
51
53
|
},
|
|
52
54
|
{
|
|
53
55
|
"id": "trailing-whitespace",
|
|
@@ -65,6 +67,7 @@ HOOKS_REGISTRY: dict[str, list[HookMetadata]] = {
|
|
|
65
67
|
"language": None,
|
|
66
68
|
"entry": None,
|
|
67
69
|
"experimental": False,
|
|
70
|
+
"pass_filenames": None,
|
|
68
71
|
},
|
|
69
72
|
{
|
|
70
73
|
"id": "end-of-file-fixer",
|
|
@@ -82,6 +85,7 @@ HOOKS_REGISTRY: dict[str, list[HookMetadata]] = {
|
|
|
82
85
|
"language": None,
|
|
83
86
|
"entry": None,
|
|
84
87
|
"experimental": False,
|
|
88
|
+
"pass_filenames": None,
|
|
85
89
|
},
|
|
86
90
|
{
|
|
87
91
|
"id": "check-yaml",
|
|
@@ -99,6 +103,7 @@ HOOKS_REGISTRY: dict[str, list[HookMetadata]] = {
|
|
|
99
103
|
"language": None,
|
|
100
104
|
"entry": None,
|
|
101
105
|
"experimental": False,
|
|
106
|
+
"pass_filenames": None,
|
|
102
107
|
},
|
|
103
108
|
{
|
|
104
109
|
"id": "check-toml",
|
|
@@ -116,6 +121,7 @@ HOOKS_REGISTRY: dict[str, list[HookMetadata]] = {
|
|
|
116
121
|
"language": None,
|
|
117
122
|
"entry": None,
|
|
118
123
|
"experimental": False,
|
|
124
|
+
"pass_filenames": None,
|
|
119
125
|
},
|
|
120
126
|
{
|
|
121
127
|
"id": "check-added-large-files",
|
|
@@ -133,6 +139,7 @@ HOOKS_REGISTRY: dict[str, list[HookMetadata]] = {
|
|
|
133
139
|
"language": None,
|
|
134
140
|
"entry": None,
|
|
135
141
|
"experimental": False,
|
|
142
|
+
"pass_filenames": None,
|
|
136
143
|
},
|
|
137
144
|
],
|
|
138
145
|
"package_management": [
|
|
@@ -152,6 +159,7 @@ HOOKS_REGISTRY: dict[str, list[HookMetadata]] = {
|
|
|
152
159
|
"language": None,
|
|
153
160
|
"entry": None,
|
|
154
161
|
"experimental": False,
|
|
162
|
+
"pass_filenames": None,
|
|
155
163
|
},
|
|
156
164
|
],
|
|
157
165
|
"security": [
|
|
@@ -171,6 +179,7 @@ HOOKS_REGISTRY: dict[str, list[HookMetadata]] = {
|
|
|
171
179
|
"language": None,
|
|
172
180
|
"entry": None,
|
|
173
181
|
"experimental": False,
|
|
182
|
+
"pass_filenames": None,
|
|
174
183
|
},
|
|
175
184
|
{
|
|
176
185
|
"id": "bandit",
|
|
@@ -188,6 +197,7 @@ HOOKS_REGISTRY: dict[str, list[HookMetadata]] = {
|
|
|
188
197
|
"language": None,
|
|
189
198
|
"entry": None,
|
|
190
199
|
"experimental": False,
|
|
200
|
+
"pass_filenames": None,
|
|
191
201
|
},
|
|
192
202
|
],
|
|
193
203
|
"formatting": [
|
|
@@ -207,6 +217,7 @@ HOOKS_REGISTRY: dict[str, list[HookMetadata]] = {
|
|
|
207
217
|
"language": None,
|
|
208
218
|
"entry": None,
|
|
209
219
|
"experimental": False,
|
|
220
|
+
"pass_filenames": None,
|
|
210
221
|
},
|
|
211
222
|
{
|
|
212
223
|
"id": "ruff-check",
|
|
@@ -224,6 +235,7 @@ HOOKS_REGISTRY: dict[str, list[HookMetadata]] = {
|
|
|
224
235
|
"language": None,
|
|
225
236
|
"entry": None,
|
|
226
237
|
"experimental": False,
|
|
238
|
+
"pass_filenames": None,
|
|
227
239
|
},
|
|
228
240
|
{
|
|
229
241
|
"id": "ruff-format",
|
|
@@ -241,6 +253,7 @@ HOOKS_REGISTRY: dict[str, list[HookMetadata]] = {
|
|
|
241
253
|
"language": None,
|
|
242
254
|
"entry": None,
|
|
243
255
|
"experimental": False,
|
|
256
|
+
"pass_filenames": None,
|
|
244
257
|
},
|
|
245
258
|
{
|
|
246
259
|
"id": "mdformat",
|
|
@@ -258,25 +271,27 @@ HOOKS_REGISTRY: dict[str, list[HookMetadata]] = {
|
|
|
258
271
|
"language": None,
|
|
259
272
|
"entry": None,
|
|
260
273
|
"experimental": False,
|
|
274
|
+
"pass_filenames": None,
|
|
261
275
|
},
|
|
262
276
|
],
|
|
263
277
|
"analysis": [
|
|
264
278
|
{
|
|
265
|
-
"id": "
|
|
266
|
-
"name":
|
|
267
|
-
"repo": "
|
|
268
|
-
"rev": "
|
|
279
|
+
"id": "skylos",
|
|
280
|
+
"name": "skylos-dead-code-detection",
|
|
281
|
+
"repo": "local",
|
|
282
|
+
"rev": "",
|
|
269
283
|
"tier": 3,
|
|
270
|
-
"time_estimate":
|
|
284
|
+
"time_estimate": 0.1,
|
|
271
285
|
"stages": ["pre-push", "manual"],
|
|
272
|
-
"args":
|
|
273
|
-
"files":
|
|
286
|
+
"args": ["crackerjack"],
|
|
287
|
+
"files": None,
|
|
274
288
|
"exclude": None,
|
|
275
289
|
"additional_dependencies": None,
|
|
276
290
|
"types_or": None,
|
|
277
|
-
"language":
|
|
278
|
-
"entry":
|
|
291
|
+
"language": "system",
|
|
292
|
+
"entry": "skylos",
|
|
279
293
|
"experimental": False,
|
|
294
|
+
"pass_filenames": False,
|
|
280
295
|
},
|
|
281
296
|
{
|
|
282
297
|
"id": "creosote",
|
|
@@ -294,6 +309,7 @@ HOOKS_REGISTRY: dict[str, list[HookMetadata]] = {
|
|
|
294
309
|
"language": None,
|
|
295
310
|
"entry": None,
|
|
296
311
|
"experimental": False,
|
|
312
|
+
"pass_filenames": None,
|
|
297
313
|
},
|
|
298
314
|
{
|
|
299
315
|
"id": "complexipy",
|
|
@@ -311,6 +327,7 @@ HOOKS_REGISTRY: dict[str, list[HookMetadata]] = {
|
|
|
311
327
|
"language": None,
|
|
312
328
|
"entry": None,
|
|
313
329
|
"experimental": False,
|
|
330
|
+
"pass_filenames": None,
|
|
314
331
|
},
|
|
315
332
|
{
|
|
316
333
|
"id": "refurb",
|
|
@@ -328,61 +345,46 @@ HOOKS_REGISTRY: dict[str, list[HookMetadata]] = {
|
|
|
328
345
|
"language": None,
|
|
329
346
|
"entry": None,
|
|
330
347
|
"experimental": False,
|
|
348
|
+
"pass_filenames": None,
|
|
331
349
|
},
|
|
332
350
|
{
|
|
333
|
-
"id": "
|
|
334
|
-
"name":
|
|
335
|
-
"repo": "
|
|
336
|
-
"rev": "
|
|
351
|
+
"id": "zuban",
|
|
352
|
+
"name": "zuban-type-checking",
|
|
353
|
+
"repo": "local",
|
|
354
|
+
"rev": "",
|
|
337
355
|
"tier": 3,
|
|
338
|
-
"time_estimate":
|
|
356
|
+
"time_estimate": 0.15,
|
|
339
357
|
"stages": ["pre-push", "manual"],
|
|
340
|
-
"args":
|
|
341
|
-
"files":
|
|
342
|
-
"exclude":
|
|
358
|
+
"args": ["--config-file", "mypy.ini", "./crackerjack"],
|
|
359
|
+
"files": None,
|
|
360
|
+
"exclude": None,
|
|
343
361
|
"additional_dependencies": None,
|
|
344
362
|
"types_or": None,
|
|
345
|
-
"language":
|
|
346
|
-
"entry":
|
|
363
|
+
"language": "system",
|
|
364
|
+
"entry": "uv run zuban check",
|
|
347
365
|
"experimental": False,
|
|
348
|
-
|
|
349
|
-
],
|
|
350
|
-
"experimental": [
|
|
351
|
-
{
|
|
352
|
-
"id": "pyrefly",
|
|
353
|
-
"name": "pyrefly",
|
|
354
|
-
"repo": "local",
|
|
355
|
-
"rev": "",
|
|
356
|
-
"tier": 3,
|
|
357
|
-
"time_estimate": 5.0,
|
|
358
|
-
"stages": ["manual"],
|
|
359
|
-
"args": ["--check"],
|
|
360
|
-
"files": "^crackerjack/.*\\.py$",
|
|
361
|
-
"exclude": None,
|
|
362
|
-
"additional_dependencies": ["pyrefly >= 0.1.0"],
|
|
363
|
-
"types_or": ["python"],
|
|
364
|
-
"language": "python",
|
|
365
|
-
"entry": "python -m pyrefly",
|
|
366
|
-
"experimental": True,
|
|
366
|
+
"pass_filenames": False,
|
|
367
367
|
},
|
|
368
368
|
{
|
|
369
|
-
"id": "
|
|
370
|
-
"name": "
|
|
369
|
+
"id": "pyright",
|
|
370
|
+
"name": "pyright-type-checking",
|
|
371
371
|
"repo": "local",
|
|
372
372
|
"rev": "",
|
|
373
373
|
"tier": 3,
|
|
374
|
-
"time_estimate":
|
|
375
|
-
"stages": ["manual"],
|
|
376
|
-
"args": [
|
|
374
|
+
"time_estimate": 0.25,
|
|
375
|
+
"stages": ["pre-push", "manual"],
|
|
376
|
+
"args": [],
|
|
377
377
|
"files": "^crackerjack/.*\\.py$",
|
|
378
|
-
"exclude":
|
|
379
|
-
"additional_dependencies":
|
|
380
|
-
"types_or":
|
|
381
|
-
"language": "
|
|
382
|
-
"entry": "
|
|
383
|
-
"experimental":
|
|
378
|
+
"exclude": r"^crackerjack/(mcp|plugins)/.*\.py$|crackerjack/code_cleaner\.py$",
|
|
379
|
+
"additional_dependencies": None,
|
|
380
|
+
"types_or": None,
|
|
381
|
+
"language": "system",
|
|
382
|
+
"entry": "uv run pyright",
|
|
383
|
+
"experimental": False,
|
|
384
|
+
"pass_filenames": None,
|
|
384
385
|
},
|
|
385
386
|
],
|
|
387
|
+
"experimental": [],
|
|
386
388
|
}
|
|
387
389
|
|
|
388
390
|
CONFIG_MODES: dict[str, ConfigMode] = {
|
|
@@ -430,6 +432,9 @@ PRE_COMMIT_TEMPLATE = """repos:
|
|
|
430
432
|
{%- if hook.args %}
|
|
431
433
|
args: {{ hook.args | tojson }}
|
|
432
434
|
{%- endif %}
|
|
435
|
+
{%- if hook.pass_filenames is not none %}
|
|
436
|
+
pass_filenames: {{ hook.pass_filenames | lower }}
|
|
437
|
+
{%- endif %}
|
|
433
438
|
{%- if hook.files %}
|
|
434
439
|
files: {{ hook.files }}
|
|
435
440
|
{%- endif %}
|
|
@@ -580,7 +585,7 @@ def generate_config_for_mode(
|
|
|
580
585
|
|
|
581
586
|
|
|
582
587
|
def get_available_modes() -> list[str]:
|
|
583
|
-
return list(CONFIG_MODES.keys())
|
|
588
|
+
return list[t.Any](CONFIG_MODES.keys())
|
|
584
589
|
|
|
585
590
|
|
|
586
591
|
def add_experimental_hook(hook_id: str, hook_config: HookMetadata) -> None:
|
|
@@ -70,14 +70,14 @@ class AsyncHookExecutor:
|
|
|
70
70
|
|
|
71
71
|
self._semaphore = asyncio.Semaphore(max_concurrent)
|
|
72
72
|
|
|
73
|
-
# Use dependency injection for hook lock manager
|
|
74
73
|
if hook_lock_manager is None:
|
|
75
|
-
# Import here to avoid circular imports
|
|
76
74
|
from crackerjack.executors.hook_lock_manager import (
|
|
77
75
|
hook_lock_manager as default_manager,
|
|
78
76
|
)
|
|
79
77
|
|
|
80
|
-
self.hook_lock_manager =
|
|
78
|
+
self.hook_lock_manager: HookLockManagerProtocol = t.cast(
|
|
79
|
+
HookLockManagerProtocol, default_manager
|
|
80
|
+
)
|
|
81
81
|
else:
|
|
82
82
|
self.hook_lock_manager = hook_lock_manager
|
|
83
83
|
|
|
@@ -143,11 +143,9 @@ class AsyncHookExecutor:
|
|
|
143
143
|
)
|
|
144
144
|
|
|
145
145
|
def get_lock_statistics(self) -> dict[str, t.Any]:
|
|
146
|
-
"""Get comprehensive lock usage statistics for monitoring."""
|
|
147
146
|
return self.hook_lock_manager.get_lock_stats()
|
|
148
147
|
|
|
149
148
|
def get_comprehensive_status(self) -> dict[str, t.Any]:
|
|
150
|
-
"""Get comprehensive status including lock manager status."""
|
|
151
149
|
return {
|
|
152
150
|
"executor_config": {
|
|
153
151
|
"max_concurrent": self.max_concurrent,
|
|
@@ -158,7 +156,7 @@ class AsyncHookExecutor:
|
|
|
158
156
|
}
|
|
159
157
|
|
|
160
158
|
def _print_strategy_header(self, strategy: HookStrategy) -> None:
|
|
161
|
-
self.console.print("\n" + "-" *
|
|
159
|
+
self.console.print("\n" + "-" * 74)
|
|
162
160
|
if strategy.name == "fast":
|
|
163
161
|
self.console.print(
|
|
164
162
|
"[bold bright_cyan]🔍 HOOKS[/ bold bright_cyan] [bold bright_white]Running code quality checks (async)[/ bold bright_white]",
|
|
@@ -171,7 +169,7 @@ class AsyncHookExecutor:
|
|
|
171
169
|
self.console.print(
|
|
172
170
|
f"[bold bright_cyan]🔍 HOOKS[/ bold bright_cyan] [bold bright_white]Running {strategy.name} hooks (async)[/ bold bright_white]",
|
|
173
171
|
)
|
|
174
|
-
self.console.print("-" *
|
|
172
|
+
self.console.print("-" * 74 + "\n")
|
|
175
173
|
|
|
176
174
|
async def _execute_sequential(self, strategy: HookStrategy) -> list[HookResult]:
|
|
177
175
|
results: list[HookResult] = []
|
|
@@ -200,21 +198,21 @@ class AsyncHookExecutor:
|
|
|
200
198
|
tasks = [self._execute_single_hook(hook) for hook in other_hooks]
|
|
201
199
|
parallel_results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
202
200
|
|
|
203
|
-
for i,
|
|
204
|
-
if isinstance(
|
|
201
|
+
for i, task_result in enumerate(parallel_results):
|
|
202
|
+
if isinstance(task_result, Exception):
|
|
205
203
|
hook = other_hooks[i]
|
|
206
204
|
error_result = HookResult(
|
|
207
205
|
id=getattr(hook, "name", f"hook_{i}"),
|
|
208
206
|
name=getattr(hook, "name", f"hook_{i}"),
|
|
209
207
|
status="error",
|
|
210
208
|
duration=0.0,
|
|
211
|
-
issues_found=[str(
|
|
209
|
+
issues_found=[str(task_result)],
|
|
212
210
|
stage=hook.stage.value,
|
|
213
211
|
)
|
|
214
212
|
results.append(error_result)
|
|
215
213
|
self._display_hook_result(error_result)
|
|
216
214
|
else:
|
|
217
|
-
hook_result = t.cast(
|
|
215
|
+
hook_result = t.cast(HookResult, task_result)
|
|
218
216
|
results.append(hook_result)
|
|
219
217
|
self._display_hook_result(hook_result)
|
|
220
218
|
|
|
@@ -222,7 +220,6 @@ class AsyncHookExecutor:
|
|
|
222
220
|
|
|
223
221
|
async def _execute_single_hook(self, hook: HookDefinition) -> HookResult:
|
|
224
222
|
async with self._semaphore:
|
|
225
|
-
# Check if hook requires sequential execution
|
|
226
223
|
if self.hook_lock_manager.requires_lock(hook.name):
|
|
227
224
|
self.logger.debug(
|
|
228
225
|
f"Hook {hook.name} requires sequential execution lock"
|
|
@@ -232,7 +229,6 @@ class AsyncHookExecutor:
|
|
|
232
229
|
f"[dim]🔒 {hook.name} (sequential execution)[/dim]"
|
|
233
230
|
)
|
|
234
231
|
|
|
235
|
-
# Acquire hook-specific lock if required (e.g., for complexipy)
|
|
236
232
|
if self.hook_lock_manager.requires_lock(hook.name):
|
|
237
233
|
self.logger.debug(
|
|
238
234
|
f"Hook {hook.name} requires sequential execution lock"
|
|
@@ -242,7 +238,7 @@ class AsyncHookExecutor:
|
|
|
242
238
|
f"[dim]🔒 {hook.name} (sequential execution)[/dim]"
|
|
243
239
|
)
|
|
244
240
|
|
|
245
|
-
async with self.hook_lock_manager.acquire_hook_lock(hook.name):
|
|
241
|
+
async with self.hook_lock_manager.acquire_hook_lock(hook.name):
|
|
246
242
|
return await self._run_hook_subprocess(hook)
|
|
247
243
|
else:
|
|
248
244
|
return await self._run_hook_subprocess(hook)
|
|
@@ -261,7 +257,6 @@ class AsyncHookExecutor:
|
|
|
261
257
|
timeout=timeout_val,
|
|
262
258
|
)
|
|
263
259
|
|
|
264
|
-
# Pre-commit must run from repository root, not package directory
|
|
265
260
|
repo_root = (
|
|
266
261
|
self.pkg_path.parent
|
|
267
262
|
if self.pkg_path.name == "crackerjack"
|
|
@@ -50,62 +50,120 @@ class CachedHookExecutor:
|
|
|
50
50
|
)
|
|
51
51
|
|
|
52
52
|
start_time = time.time()
|
|
53
|
-
|
|
54
|
-
cache_hits = 0
|
|
55
|
-
cache_misses = 0
|
|
53
|
+
execution_context = self._initialize_execution_context(strategy)
|
|
56
54
|
|
|
55
|
+
for hook_def in strategy.hooks:
|
|
56
|
+
self._execute_single_hook_with_cache(hook_def, execution_context)
|
|
57
|
+
|
|
58
|
+
return self._build_execution_result(strategy, execution_context, start_time)
|
|
59
|
+
|
|
60
|
+
def _initialize_execution_context(self, strategy: HookStrategy) -> dict[str, t.Any]:
|
|
61
|
+
"""Initialize execution context for the strategy."""
|
|
57
62
|
relevant_files = self._get_relevant_files_for_strategy(strategy)
|
|
58
63
|
current_file_hashes = self.file_hasher.get_files_hash_list(relevant_files)
|
|
59
64
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
return {
|
|
66
|
+
"results": [],
|
|
67
|
+
"cache_hits": 0,
|
|
68
|
+
"cache_misses": 0,
|
|
69
|
+
"current_file_hashes": current_file_hashes,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
def _execute_single_hook_with_cache(
|
|
73
|
+
self, hook_def: HookDefinition, context: dict[str, t.Any]
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Execute a single hook with caching logic."""
|
|
76
|
+
cached_result = self._get_cached_result(
|
|
77
|
+
hook_def, context["current_file_hashes"]
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
if cached_result and self._is_cache_valid(cached_result, hook_def):
|
|
81
|
+
self._handle_cache_hit(hook_def, cached_result, context)
|
|
82
|
+
else:
|
|
83
|
+
self._handle_cache_miss(hook_def, context)
|
|
84
|
+
|
|
85
|
+
def _get_cached_result(
|
|
86
|
+
self, hook_def: HookDefinition, current_file_hashes: list[str]
|
|
87
|
+
) -> HookResult | None:
|
|
88
|
+
"""Get cached result for a hook definition."""
|
|
89
|
+
try:
|
|
90
|
+
if hook_def.name in self.cache.EXPENSIVE_HOOKS:
|
|
91
|
+
tool_version = self._get_tool_version(hook_def.name)
|
|
92
|
+
return self.cache.get_expensive_hook_result(
|
|
93
|
+
hook_def.name, current_file_hashes, tool_version
|
|
94
|
+
)
|
|
95
|
+
else:
|
|
96
|
+
return self.cache.get_hook_result(hook_def.name, current_file_hashes)
|
|
97
|
+
except Exception as e:
|
|
98
|
+
self.logger.warning(f"Cache error for hook {hook_def.name}: {e}")
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
def _handle_cache_hit(
|
|
102
|
+
self,
|
|
103
|
+
hook_def: HookDefinition,
|
|
104
|
+
cached_result: HookResult,
|
|
105
|
+
context: dict[str, t.Any],
|
|
106
|
+
) -> None:
|
|
107
|
+
"""Handle a cache hit scenario."""
|
|
108
|
+
self.logger.debug(f"Using cached result for hook: {hook_def.name}")
|
|
109
|
+
context["results"].append(cached_result)
|
|
110
|
+
context["cache_hits"] += 1
|
|
111
|
+
|
|
112
|
+
def _handle_cache_miss(
|
|
113
|
+
self, hook_def: HookDefinition, context: dict[str, t.Any]
|
|
114
|
+
) -> None:
|
|
115
|
+
"""Handle a cache miss scenario."""
|
|
116
|
+
self.logger.debug(f"Executing hook (cache miss): {hook_def.name}")
|
|
117
|
+
|
|
118
|
+
hook_result = self.base_executor.execute_single_hook(hook_def)
|
|
119
|
+
context["results"].append(hook_result)
|
|
120
|
+
context["cache_misses"] += 1
|
|
121
|
+
|
|
122
|
+
if hook_result.status == "passed":
|
|
123
|
+
self._cache_successful_result(
|
|
124
|
+
hook_def, hook_result, context["current_file_hashes"]
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def _cache_successful_result(
|
|
128
|
+
self,
|
|
129
|
+
hook_def: HookDefinition,
|
|
130
|
+
hook_result: HookResult,
|
|
131
|
+
current_file_hashes: list[str],
|
|
132
|
+
) -> None:
|
|
133
|
+
"""Cache a successful hook result."""
|
|
134
|
+
try:
|
|
135
|
+
if hook_def.name in self.cache.EXPENSIVE_HOOKS:
|
|
136
|
+
tool_version = self._get_tool_version(hook_def.name)
|
|
137
|
+
self.cache.set_expensive_hook_result(
|
|
138
|
+
hook_def.name, current_file_hashes, hook_result, tool_version
|
|
66
139
|
)
|
|
67
|
-
except Exception as e:
|
|
68
|
-
self.logger.warning(f"Cache error for hook {hook_def.name}: {e}")
|
|
69
|
-
cached_result = None
|
|
70
|
-
|
|
71
|
-
if cached_result and self._is_cache_valid(cached_result, hook_def):
|
|
72
|
-
self.logger.debug(f"Using cached result for hook: {hook_def.name}")
|
|
73
|
-
results.append(cached_result)
|
|
74
|
-
cache_hits += 1
|
|
75
140
|
else:
|
|
76
|
-
self.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if hook_result.status == "passed":
|
|
83
|
-
try:
|
|
84
|
-
self.cache.set_hook_result(
|
|
85
|
-
hook_def.name,
|
|
86
|
-
current_file_hashes,
|
|
87
|
-
hook_result,
|
|
88
|
-
)
|
|
89
|
-
except Exception as e:
|
|
90
|
-
self.logger.warning(
|
|
91
|
-
f"Failed to cache result for {hook_def.name}: {e}",
|
|
92
|
-
)
|
|
141
|
+
self.cache.set_hook_result(
|
|
142
|
+
hook_def.name, current_file_hashes, hook_result
|
|
143
|
+
)
|
|
144
|
+
except Exception as e:
|
|
145
|
+
self.logger.warning(f"Failed to cache result for {hook_def.name}: {e}")
|
|
93
146
|
|
|
147
|
+
def _build_execution_result(
|
|
148
|
+
self, strategy: HookStrategy, context: dict[str, t.Any], start_time: float
|
|
149
|
+
) -> HookExecutionResult:
|
|
150
|
+
"""Build the final execution result."""
|
|
94
151
|
total_time = time.time() - start_time
|
|
95
|
-
success = all(result.status == "passed" for result in results)
|
|
152
|
+
success = all(result.status == "passed" for result in context["results"])
|
|
96
153
|
|
|
97
154
|
self.logger.info(
|
|
98
|
-
f"Cached strategy '{strategy.name}' completed in {total_time
|
|
99
|
-
f"Success: {success}, Cache hits: {cache_hits},
|
|
155
|
+
f"Cached strategy '{strategy.name}' completed in {total_time:.2f}s - "
|
|
156
|
+
f"Success: {success}, Cache hits: {context['cache_hits']}, "
|
|
157
|
+
f"Cache misses: {context['cache_misses']}"
|
|
100
158
|
)
|
|
101
159
|
|
|
102
160
|
return HookExecutionResult(
|
|
103
161
|
strategy_name=strategy.name,
|
|
104
|
-
results=results,
|
|
162
|
+
results=context["results"],
|
|
105
163
|
success=success,
|
|
106
164
|
total_duration=total_time,
|
|
107
|
-
cache_hits=cache_hits,
|
|
108
|
-
cache_misses=cache_misses,
|
|
165
|
+
cache_hits=context["cache_hits"],
|
|
166
|
+
cache_misses=context["cache_misses"],
|
|
109
167
|
)
|
|
110
168
|
|
|
111
169
|
def _get_relevant_files_for_strategy(self, strategy: HookStrategy) -> list[Path]:
|
|
@@ -118,7 +176,7 @@ class CachedHookExecutor:
|
|
|
118
176
|
|
|
119
177
|
files: list[Path] = []
|
|
120
178
|
for pattern in patterns:
|
|
121
|
-
files.extend(list(self.pkg_path.rglob(pattern)))
|
|
179
|
+
files.extend(list[t.Any](self.pkg_path.rglob(pattern)))
|
|
122
180
|
|
|
123
181
|
return [f for f in files if f.is_file() and not self._should_ignore_file(f)]
|
|
124
182
|
|
|
@@ -177,6 +235,21 @@ class CachedHookExecutor:
|
|
|
177
235
|
def cleanup_cache(self) -> dict[str, int]:
|
|
178
236
|
return self.cache.cleanup_all()
|
|
179
237
|
|
|
238
|
+
def _get_tool_version(self, tool_name: str) -> str | None:
|
|
239
|
+
"""Get version of a tool for cache invalidation."""
|
|
240
|
+
# This is a simplified version - in production, you might want to
|
|
241
|
+
# actually call the tool to get its version
|
|
242
|
+
version_mapping = {
|
|
243
|
+
"pyright": "1.1.0", # Could be dynamic: subprocess.run(["pyright", "--version"])
|
|
244
|
+
"bandit": "1.7.5",
|
|
245
|
+
"vulture": "2.7.0",
|
|
246
|
+
"complexipy": "0.13.0",
|
|
247
|
+
"refurb": "1.17.0",
|
|
248
|
+
"ruff": "0.1.0",
|
|
249
|
+
"gitleaks": "8.18.0",
|
|
250
|
+
}
|
|
251
|
+
return version_mapping.get(tool_name)
|
|
252
|
+
|
|
180
253
|
|
|
181
254
|
class SmartCacheManager:
|
|
182
255
|
def __init__(self, cached_executor: CachedHookExecutor) -> None:
|
|
@@ -203,7 +276,8 @@ class SmartCacheManager:
|
|
|
203
276
|
}
|
|
204
277
|
if hook_name in formatting_hooks:
|
|
205
278
|
recent_changes = project_state.get("recent_changes", 0)
|
|
206
|
-
|
|
279
|
+
result: bool = recent_changes < 5
|
|
280
|
+
return result
|
|
207
281
|
|
|
208
282
|
return True
|
|
209
283
|
|
|
@@ -237,6 +311,6 @@ class SmartCacheManager:
|
|
|
237
311
|
|
|
238
312
|
return {
|
|
239
313
|
"recent_changes": recent_changes,
|
|
240
|
-
"total_python_files": len(list(pkg_path.rglob("*.py"))),
|
|
314
|
+
"total_python_files": len(list[t.Any](pkg_path.rglob("*.py"))),
|
|
241
315
|
"project_size": "large" if recent_changes > 50 else "small",
|
|
242
316
|
}
|