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.

Files changed (198) hide show
  1. crackerjack/__main__.py +1350 -34
  2. crackerjack/adapters/__init__.py +17 -0
  3. crackerjack/adapters/lsp_client.py +358 -0
  4. crackerjack/adapters/rust_tool_adapter.py +194 -0
  5. crackerjack/adapters/rust_tool_manager.py +193 -0
  6. crackerjack/adapters/skylos_adapter.py +231 -0
  7. crackerjack/adapters/zuban_adapter.py +560 -0
  8. crackerjack/agents/base.py +7 -3
  9. crackerjack/agents/coordinator.py +271 -33
  10. crackerjack/agents/documentation_agent.py +9 -15
  11. crackerjack/agents/dry_agent.py +3 -15
  12. crackerjack/agents/formatting_agent.py +1 -1
  13. crackerjack/agents/import_optimization_agent.py +36 -180
  14. crackerjack/agents/performance_agent.py +17 -98
  15. crackerjack/agents/performance_helpers.py +7 -31
  16. crackerjack/agents/proactive_agent.py +1 -3
  17. crackerjack/agents/refactoring_agent.py +16 -85
  18. crackerjack/agents/refactoring_helpers.py +7 -42
  19. crackerjack/agents/security_agent.py +9 -48
  20. crackerjack/agents/test_creation_agent.py +356 -513
  21. crackerjack/agents/test_specialist_agent.py +0 -4
  22. crackerjack/api.py +6 -25
  23. crackerjack/cli/cache_handlers.py +204 -0
  24. crackerjack/cli/cache_handlers_enhanced.py +683 -0
  25. crackerjack/cli/facade.py +100 -0
  26. crackerjack/cli/handlers.py +224 -9
  27. crackerjack/cli/interactive.py +6 -4
  28. crackerjack/cli/options.py +642 -55
  29. crackerjack/cli/utils.py +2 -1
  30. crackerjack/code_cleaner.py +58 -117
  31. crackerjack/config/global_lock_config.py +8 -48
  32. crackerjack/config/hooks.py +53 -62
  33. crackerjack/core/async_workflow_orchestrator.py +24 -34
  34. crackerjack/core/autofix_coordinator.py +3 -17
  35. crackerjack/core/enhanced_container.py +4 -13
  36. crackerjack/core/file_lifecycle.py +12 -89
  37. crackerjack/core/performance.py +2 -2
  38. crackerjack/core/performance_monitor.py +15 -55
  39. crackerjack/core/phase_coordinator.py +104 -204
  40. crackerjack/core/resource_manager.py +14 -90
  41. crackerjack/core/service_watchdog.py +62 -95
  42. crackerjack/core/session_coordinator.py +149 -0
  43. crackerjack/core/timeout_manager.py +14 -72
  44. crackerjack/core/websocket_lifecycle.py +13 -78
  45. crackerjack/core/workflow_orchestrator.py +171 -174
  46. crackerjack/docs/INDEX.md +11 -0
  47. crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
  48. crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
  49. crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
  50. crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
  51. crackerjack/docs/generated/api/SERVICES.md +1252 -0
  52. crackerjack/documentation/__init__.py +31 -0
  53. crackerjack/documentation/ai_templates.py +756 -0
  54. crackerjack/documentation/dual_output_generator.py +765 -0
  55. crackerjack/documentation/mkdocs_integration.py +518 -0
  56. crackerjack/documentation/reference_generator.py +977 -0
  57. crackerjack/dynamic_config.py +55 -50
  58. crackerjack/executors/async_hook_executor.py +10 -15
  59. crackerjack/executors/cached_hook_executor.py +117 -43
  60. crackerjack/executors/hook_executor.py +8 -34
  61. crackerjack/executors/hook_lock_manager.py +26 -183
  62. crackerjack/executors/individual_hook_executor.py +13 -11
  63. crackerjack/executors/lsp_aware_hook_executor.py +270 -0
  64. crackerjack/executors/tool_proxy.py +417 -0
  65. crackerjack/hooks/lsp_hook.py +79 -0
  66. crackerjack/intelligence/adaptive_learning.py +25 -10
  67. crackerjack/intelligence/agent_orchestrator.py +2 -5
  68. crackerjack/intelligence/agent_registry.py +34 -24
  69. crackerjack/intelligence/agent_selector.py +5 -7
  70. crackerjack/interactive.py +17 -6
  71. crackerjack/managers/async_hook_manager.py +0 -1
  72. crackerjack/managers/hook_manager.py +79 -1
  73. crackerjack/managers/publish_manager.py +44 -8
  74. crackerjack/managers/test_command_builder.py +1 -15
  75. crackerjack/managers/test_executor.py +1 -3
  76. crackerjack/managers/test_manager.py +98 -7
  77. crackerjack/managers/test_manager_backup.py +10 -9
  78. crackerjack/mcp/cache.py +2 -2
  79. crackerjack/mcp/client_runner.py +1 -1
  80. crackerjack/mcp/context.py +191 -68
  81. crackerjack/mcp/dashboard.py +7 -5
  82. crackerjack/mcp/enhanced_progress_monitor.py +31 -28
  83. crackerjack/mcp/file_monitor.py +30 -23
  84. crackerjack/mcp/progress_components.py +31 -21
  85. crackerjack/mcp/progress_monitor.py +50 -53
  86. crackerjack/mcp/rate_limiter.py +6 -6
  87. crackerjack/mcp/server_core.py +17 -16
  88. crackerjack/mcp/service_watchdog.py +2 -1
  89. crackerjack/mcp/state.py +4 -7
  90. crackerjack/mcp/task_manager.py +11 -9
  91. crackerjack/mcp/tools/core_tools.py +173 -32
  92. crackerjack/mcp/tools/error_analyzer.py +3 -2
  93. crackerjack/mcp/tools/execution_tools.py +8 -10
  94. crackerjack/mcp/tools/execution_tools_backup.py +42 -30
  95. crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
  96. crackerjack/mcp/tools/intelligence_tools.py +5 -2
  97. crackerjack/mcp/tools/monitoring_tools.py +33 -70
  98. crackerjack/mcp/tools/proactive_tools.py +24 -11
  99. crackerjack/mcp/tools/progress_tools.py +5 -8
  100. crackerjack/mcp/tools/utility_tools.py +20 -14
  101. crackerjack/mcp/tools/workflow_executor.py +62 -40
  102. crackerjack/mcp/websocket/app.py +8 -0
  103. crackerjack/mcp/websocket/endpoints.py +352 -357
  104. crackerjack/mcp/websocket/jobs.py +40 -57
  105. crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
  106. crackerjack/mcp/websocket/server.py +7 -25
  107. crackerjack/mcp/websocket/websocket_handler.py +6 -17
  108. crackerjack/mixins/__init__.py +0 -2
  109. crackerjack/mixins/error_handling.py +1 -70
  110. crackerjack/models/config.py +12 -1
  111. crackerjack/models/config_adapter.py +49 -1
  112. crackerjack/models/protocols.py +122 -122
  113. crackerjack/models/resource_protocols.py +55 -210
  114. crackerjack/monitoring/ai_agent_watchdog.py +13 -13
  115. crackerjack/monitoring/metrics_collector.py +426 -0
  116. crackerjack/monitoring/regression_prevention.py +8 -8
  117. crackerjack/monitoring/websocket_server.py +643 -0
  118. crackerjack/orchestration/advanced_orchestrator.py +11 -6
  119. crackerjack/orchestration/coverage_improvement.py +3 -3
  120. crackerjack/orchestration/execution_strategies.py +26 -6
  121. crackerjack/orchestration/test_progress_streamer.py +8 -5
  122. crackerjack/plugins/base.py +2 -2
  123. crackerjack/plugins/hooks.py +7 -0
  124. crackerjack/plugins/managers.py +11 -8
  125. crackerjack/security/__init__.py +0 -1
  126. crackerjack/security/audit.py +6 -35
  127. crackerjack/services/anomaly_detector.py +392 -0
  128. crackerjack/services/api_extractor.py +615 -0
  129. crackerjack/services/backup_service.py +2 -2
  130. crackerjack/services/bounded_status_operations.py +15 -152
  131. crackerjack/services/cache.py +127 -1
  132. crackerjack/services/changelog_automation.py +395 -0
  133. crackerjack/services/config.py +15 -9
  134. crackerjack/services/config_merge.py +19 -80
  135. crackerjack/services/config_template.py +506 -0
  136. crackerjack/services/contextual_ai_assistant.py +48 -22
  137. crackerjack/services/coverage_badge_service.py +171 -0
  138. crackerjack/services/coverage_ratchet.py +27 -25
  139. crackerjack/services/debug.py +3 -3
  140. crackerjack/services/dependency_analyzer.py +460 -0
  141. crackerjack/services/dependency_monitor.py +14 -11
  142. crackerjack/services/documentation_generator.py +491 -0
  143. crackerjack/services/documentation_service.py +675 -0
  144. crackerjack/services/enhanced_filesystem.py +6 -5
  145. crackerjack/services/enterprise_optimizer.py +865 -0
  146. crackerjack/services/error_pattern_analyzer.py +676 -0
  147. crackerjack/services/file_hasher.py +1 -1
  148. crackerjack/services/git.py +8 -25
  149. crackerjack/services/health_metrics.py +10 -8
  150. crackerjack/services/heatmap_generator.py +735 -0
  151. crackerjack/services/initialization.py +11 -30
  152. crackerjack/services/input_validator.py +5 -97
  153. crackerjack/services/intelligent_commit.py +327 -0
  154. crackerjack/services/log_manager.py +15 -12
  155. crackerjack/services/logging.py +4 -3
  156. crackerjack/services/lsp_client.py +628 -0
  157. crackerjack/services/memory_optimizer.py +19 -87
  158. crackerjack/services/metrics.py +42 -33
  159. crackerjack/services/parallel_executor.py +9 -67
  160. crackerjack/services/pattern_cache.py +1 -1
  161. crackerjack/services/pattern_detector.py +6 -6
  162. crackerjack/services/performance_benchmarks.py +18 -59
  163. crackerjack/services/performance_cache.py +20 -81
  164. crackerjack/services/performance_monitor.py +27 -95
  165. crackerjack/services/predictive_analytics.py +510 -0
  166. crackerjack/services/quality_baseline.py +234 -0
  167. crackerjack/services/quality_baseline_enhanced.py +646 -0
  168. crackerjack/services/quality_intelligence.py +785 -0
  169. crackerjack/services/regex_patterns.py +605 -524
  170. crackerjack/services/regex_utils.py +43 -123
  171. crackerjack/services/secure_path_utils.py +5 -164
  172. crackerjack/services/secure_status_formatter.py +30 -141
  173. crackerjack/services/secure_subprocess.py +11 -92
  174. crackerjack/services/security.py +9 -41
  175. crackerjack/services/security_logger.py +12 -24
  176. crackerjack/services/server_manager.py +124 -16
  177. crackerjack/services/status_authentication.py +16 -159
  178. crackerjack/services/status_security_manager.py +4 -131
  179. crackerjack/services/thread_safe_status_collector.py +19 -125
  180. crackerjack/services/unified_config.py +21 -13
  181. crackerjack/services/validation_rate_limiter.py +5 -54
  182. crackerjack/services/version_analyzer.py +459 -0
  183. crackerjack/services/version_checker.py +1 -1
  184. crackerjack/services/websocket_resource_limiter.py +10 -144
  185. crackerjack/services/zuban_lsp_service.py +390 -0
  186. crackerjack/slash_commands/__init__.py +2 -7
  187. crackerjack/slash_commands/run.md +2 -2
  188. crackerjack/tools/validate_input_validator_patterns.py +14 -40
  189. crackerjack/tools/validate_regex_patterns.py +19 -48
  190. {crackerjack-0.33.0.dist-info → crackerjack-0.33.1.dist-info}/METADATA +196 -25
  191. crackerjack-0.33.1.dist-info/RECORD +229 -0
  192. crackerjack/CLAUDE.md +0 -207
  193. crackerjack/RULES.md +0 -380
  194. crackerjack/py313.py +0 -234
  195. crackerjack-0.33.0.dist-info/RECORD +0 -187
  196. {crackerjack-0.33.0.dist-info → crackerjack-0.33.1.dist-info}/WHEEL +0 -0
  197. {crackerjack-0.33.0.dist-info → crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
  198. {crackerjack-0.33.0.dist-info → crackerjack-0.33.1.dist-info}/licenses/LICENSE +0 -0
@@ -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": "vulture",
266
- "name": None,
267
- "repo": "https://github.com/jendrikseipp/vulture",
268
- "rev": "v2.14",
279
+ "id": "skylos",
280
+ "name": "skylos-dead-code-detection",
281
+ "repo": "local",
282
+ "rev": "",
269
283
  "tier": 3,
270
- "time_estimate": 2.0,
284
+ "time_estimate": 0.1,
271
285
  "stages": ["pre-push", "manual"],
272
- "args": None,
273
- "files": "^crackerjack/.*\\.py$",
286
+ "args": ["crackerjack"],
287
+ "files": None,
274
288
  "exclude": None,
275
289
  "additional_dependencies": None,
276
290
  "types_or": None,
277
- "language": None,
278
- "entry": None,
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": "pyright",
334
- "name": None,
335
- "repo": "https://github.com/RobertCraigie/pyright-python",
336
- "rev": "v1.1.405",
351
+ "id": "zuban",
352
+ "name": "zuban-type-checking",
353
+ "repo": "local",
354
+ "rev": "",
337
355
  "tier": 3,
338
- "time_estimate": 5.0,
356
+ "time_estimate": 0.15,
339
357
  "stages": ["pre-push", "manual"],
340
- "args": None,
341
- "files": "^crackerjack/.*\\.py$",
342
- "exclude": r"^crackerjack/(mcp|plugins)/.*\.py$|crackerjack/code_cleaner\.py$",
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": None,
346
- "entry": None,
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": "ty",
370
- "name": "ty",
369
+ "id": "pyright",
370
+ "name": "pyright-type-checking",
371
371
  "repo": "local",
372
372
  "rev": "",
373
373
  "tier": 3,
374
- "time_estimate": 2.0,
375
- "stages": ["manual"],
376
- "args": ["--check"],
374
+ "time_estimate": 0.25,
375
+ "stages": ["pre-push", "manual"],
376
+ "args": [],
377
377
  "files": "^crackerjack/.*\\.py$",
378
- "exclude": None,
379
- "additional_dependencies": ["ty >= 0.1.0"],
380
- "types_or": ["python"],
381
- "language": "python",
382
- "entry": "python -m ty",
383
- "experimental": True,
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 = default_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" + "-" * 80)
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("-" * 80 + "\n")
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, result in enumerate(parallel_results):
204
- if isinstance(result, Exception):
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(result)],
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("HookResult", result)
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): # type: ignore
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
- results: list[HookResult] = []
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
- for hook_def in strategy.hooks:
61
- cached_result = None
62
- try:
63
- cached_result = self.cache.get_hook_result(
64
- hook_def.name,
65
- current_file_hashes,
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.logger.debug(f"Executing hook (cache miss): {hook_def.name}")
77
-
78
- hook_result = self.base_executor.execute_single_hook(hook_def)
79
- results.append(hook_result)
80
- cache_misses += 1
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: .2f}s-"
99
- f"Success: {success}, Cache hits: {cache_hits}, Cache misses: {cache_misses}",
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
- return recent_changes < 5
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
  }