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
@@ -74,7 +74,6 @@ class IndividualExecutionResult:
74
74
 
75
75
 
76
76
  class HookOutputParser:
77
- # Pattern mapping: tool -> pattern_type -> safe_pattern_name
78
77
  HOOK_PATTERNS: dict[str, dict[str, str]] = {
79
78
  "ruff-check": {
80
79
  "error": "ruff_check_error",
@@ -130,7 +129,7 @@ class HookOutputParser:
130
129
  output_lines, patterns, result
131
130
  )
132
131
 
133
- result["files_processed"] = list(result["files_processed"])
132
+ result["files_processed"] = list[t.Any](result["files_processed"])
134
133
  return result
135
134
 
136
135
  def _parse_ruff_check(
@@ -146,6 +145,7 @@ class HookOutputParser:
146
145
  if not line:
147
146
  continue
148
147
  if match := error_pattern.match(line):
148
+ assert match is not None # Type checker: match cannot be None here
149
149
  file_path, line_num, col_num, code, message = match.groups()
150
150
  result["files_processed"].add(file_path)
151
151
  result["errors"].append(
@@ -173,6 +173,7 @@ class HookOutputParser:
173
173
  if not line:
174
174
  continue
175
175
  if match := error_pattern.match(line):
176
+ assert match is not None # Type checker: match cannot be None here
176
177
  file_path, line_num, col_num, message = match.groups()
177
178
  result["files_processed"].add(file_path)
178
179
  result["errors"].append(
@@ -185,6 +186,7 @@ class HookOutputParser:
185
186
  },
186
187
  )
187
188
  elif match := warning_pattern.match(line):
189
+ assert match is not None # Type checker: match cannot be None here
188
190
  file_path, line_num, col_num, message = match.groups()
189
191
  result["files_processed"].add(file_path)
190
192
  result["warnings"].append(
@@ -210,6 +212,7 @@ class HookOutputParser:
210
212
  if not line:
211
213
  continue
212
214
  if match := issue_pattern.match(line):
215
+ assert match is not None # Type checker: match cannot be None here
213
216
  code, message = match.groups()
214
217
  result["errors"].append(
215
218
  {"code": code, "message": message, "type": "security"},
@@ -228,6 +231,7 @@ class HookOutputParser:
228
231
  if not line:
229
232
  continue
230
233
  if match := unused_pattern.match(line):
234
+ assert match is not None # Type checker: match cannot be None here
231
235
  file_path, line_num, item_type, item_name = match.groups()
232
236
  result["files_processed"].add(file_path)
233
237
  result["warnings"].append(
@@ -252,6 +256,7 @@ class HookOutputParser:
252
256
  if not line:
253
257
  continue
254
258
  if match := complex_pattern.match(line):
259
+ assert match is not None # Type checker: match cannot be None here
255
260
  file_path, line_num, col_num, function_name, complexity = match.groups()
256
261
  result["files_processed"].add(file_path)
257
262
  result["errors"].append(
@@ -325,15 +330,15 @@ class IndividualHookExecutor:
325
330
  self.progress_callback: t.Callable[[HookProgress], None] | None = None
326
331
  self.suppress_realtime_output = False
327
332
  self.progress_callback_interval = 1
333
+ self.hook_lock_manager: HookLockManagerProtocol
328
334
 
329
- # Use dependency injection for hook lock manager
330
335
  if hook_lock_manager is None:
331
- # Import here to avoid circular imports
332
336
  from crackerjack.executors.hook_lock_manager import (
333
337
  hook_lock_manager as default_manager,
334
338
  )
335
339
 
336
- self.hook_lock_manager = default_manager
340
+ # Type cast: default_manager implements the protocol interface
341
+ self.hook_lock_manager = t.cast(HookLockManagerProtocol, default_manager)
337
342
  else:
338
343
  self.hook_lock_manager = hook_lock_manager
339
344
 
@@ -426,7 +431,6 @@ class IndividualHookExecutor:
426
431
  if self.progress_callback:
427
432
  self.progress_callback(progress)
428
433
 
429
- # Check if this hook requires a lock for sequential execution
430
434
  if self.hook_lock_manager.requires_lock(hook.name):
431
435
  self.console.print(
432
436
  f"\n[bold cyan]šŸ” Running {hook.name} (with lock)[/ bold cyan]"
@@ -437,8 +441,7 @@ class IndividualHookExecutor:
437
441
  cmd = hook.get_command()
438
442
 
439
443
  try:
440
- # Acquire lock if the hook requires it
441
- async with self.hook_lock_manager.acquire_hook_lock(hook.name): # type: ignore
444
+ async with self.hook_lock_manager.acquire_hook_lock(hook.name): # type: ignore[attr-defined]
442
445
  result = await self._run_command_with_streaming(
443
446
  cmd, hook.timeout, progress
444
447
  )
@@ -516,7 +519,6 @@ class IndividualHookExecutor:
516
519
  return self._create_completed_process(cmd, process, stdout_lines, stderr_lines)
517
520
 
518
521
  async def _create_subprocess(self, cmd: list[str]) -> asyncio.subprocess.Process:
519
- # Pre-commit must run from repository root, not package directory
520
522
  repo_root = (
521
523
  self.pkg_path.parent
522
524
  if self.pkg_path.name == "crackerjack"
@@ -678,7 +680,7 @@ class IndividualHookExecutor:
678
680
  total_warnings = sum(p.warnings_found for p in progress_list)
679
681
  total_duration = sum(p.duration or 0 for p in progress_list)
680
682
 
681
- self.console.print("\n" + "-" * 80)
683
+ self.console.print("\n" + "-" * 74)
682
684
  self.console.print(
683
685
  f"[bold]šŸ“Š INDIVIDUAL EXECUTION SUMMARY[/ bold]-{strategy.name.upper()}",
684
686
  )
@@ -700,4 +702,4 @@ class IndividualHookExecutor:
700
702
  )
701
703
  self.console.print(f" āŒ {progress.hook_name}-{error_summary}")
702
704
 
703
- self.console.print("-" * 80)
705
+ self.console.print("-" * 74)
@@ -0,0 +1,270 @@
1
+ import time
2
+ import typing as t
3
+ from pathlib import Path
4
+
5
+ from rich.console import Console
6
+
7
+ from crackerjack.config.hooks import HookDefinition, HookStrategy
8
+ from crackerjack.executors.hook_executor import HookExecutionResult, HookExecutor
9
+ from crackerjack.models.task import HookResult
10
+ from crackerjack.services.lsp_client import LSPClient
11
+
12
+ # Conditional import for ToolProxy
13
+ try:
14
+ from crackerjack.executors.tool_proxy import ToolProxy
15
+ except ImportError:
16
+ ToolProxy = None # type: ignore
17
+
18
+
19
+ class LSPAwareHookExecutor(HookExecutor):
20
+ """Hook executor that can leverage LSP server for enhanced performance."""
21
+
22
+ def __init__(
23
+ self,
24
+ console: Console,
25
+ pkg_path: Path,
26
+ verbose: bool = False,
27
+ quiet: bool = False,
28
+ use_tool_proxy: bool = True,
29
+ ) -> None:
30
+ super().__init__(console, pkg_path, verbose, quiet)
31
+ self.lsp_client = LSPClient(console)
32
+ self.use_tool_proxy = use_tool_proxy and ToolProxy is not None
33
+ self.tool_proxy = ToolProxy(console) if self.use_tool_proxy else None
34
+
35
+ def execute_strategy(self, strategy: HookStrategy) -> HookExecutionResult:
36
+ """Execute hook strategy with LSP optimization where possible."""
37
+ start_time = time.time()
38
+ results = []
39
+
40
+ # Check if LSP server is available
41
+ lsp_available = self.lsp_client.is_server_running()
42
+
43
+ if lsp_available and not self.quiet:
44
+ server_info = self.lsp_client.get_server_info()
45
+ if server_info:
46
+ self.console.print(
47
+ f"šŸ” LSP server available (PID: {server_info['pid']}), using optimized execution"
48
+ )
49
+
50
+ # Execute hooks with LSP optimization and tool proxy resilience
51
+ for hook in strategy.hooks:
52
+ if self._should_use_lsp_for_hook(hook, lsp_available):
53
+ result = self._execute_lsp_hook(hook)
54
+ elif self._should_use_tool_proxy(hook):
55
+ result = self._execute_hook_with_proxy(hook)
56
+ else:
57
+ result = self.execute_single_hook(hook)
58
+ results.append(result)
59
+
60
+ duration = time.time() - start_time
61
+ success = all(result.status in ("passed", "skipped") for result in results)
62
+
63
+ return HookExecutionResult(
64
+ strategy_name=strategy.name,
65
+ results=results,
66
+ total_duration=duration,
67
+ success=success,
68
+ concurrent_execution=False,
69
+ )
70
+
71
+ def _should_use_lsp_for_hook(
72
+ self, hook: HookDefinition, lsp_available: bool
73
+ ) -> bool:
74
+ """Determine if a hook should use LSP-based execution."""
75
+ # Only use LSP for type-checking hooks when server is available
76
+ return (
77
+ lsp_available
78
+ and hook.name == "zuban"
79
+ and hook.stage.value == "comprehensive"
80
+ )
81
+
82
+ def _execute_lsp_hook(self, hook: HookDefinition) -> HookResult:
83
+ """Execute a hook using LSP server with real-time feedback."""
84
+ start_time = time.time()
85
+
86
+ try:
87
+ return self._perform_lsp_execution(hook, start_time)
88
+ except Exception as e:
89
+ return self._handle_lsp_execution_error(hook, start_time, e)
90
+
91
+ def _perform_lsp_execution(
92
+ self, hook: HookDefinition, start_time: float
93
+ ) -> HookResult:
94
+ """Perform the actual LSP execution."""
95
+ if not self.quiet:
96
+ self.console.print(
97
+ f"šŸš€ Using LSP-optimized execution for {hook.name}", style="cyan"
98
+ )
99
+
100
+ # Use the new real-time feedback method
101
+ diagnostics, summary = self.lsp_client.check_project_with_feedback(
102
+ self.pkg_path, show_progress=not self.quiet
103
+ )
104
+
105
+ duration = time.time() - start_time
106
+ has_errors = any(diags for diags in diagnostics.values())
107
+ output = self._format_lsp_output(diagnostics, duration)
108
+
109
+ self._display_lsp_results(hook, has_errors, output, summary)
110
+
111
+ return HookResult(
112
+ id=f"{hook.name}-lsp-{int(time.time())}",
113
+ name=f"{hook.name}-lsp",
114
+ status="failed" if has_errors else "passed",
115
+ duration=duration,
116
+ files_processed=len(diagnostics),
117
+ issues_found=[output] if has_errors else [],
118
+ )
119
+
120
+ def _format_lsp_output(self, diagnostics: dict[str, t.Any], duration: float) -> str:
121
+ """Format LSP diagnostic output with performance info."""
122
+ output = self.lsp_client.format_diagnostics(diagnostics)
123
+ file_count = len(self.lsp_client.get_project_files(self.pkg_path))
124
+ perf_info = f"\n⚔ LSP-optimized check completed in {duration:.2f}s ({file_count} files)"
125
+ return output + perf_info
126
+
127
+ def _display_lsp_results(
128
+ self, hook: HookDefinition, has_errors: bool, output: str, summary: str
129
+ ) -> None:
130
+ """Display LSP execution results."""
131
+ if self.verbose or has_errors:
132
+ if not self.quiet:
133
+ self.console.print(f"šŸ” {hook.name} (LSP):", style="bold blue")
134
+ if has_errors:
135
+ self.console.print(output)
136
+ else:
137
+ self.console.print(summary, style="green")
138
+
139
+ def _handle_lsp_execution_error(
140
+ self, hook: HookDefinition, start_time: float, error: Exception
141
+ ) -> HookResult:
142
+ """Handle LSP execution errors with fallback."""
143
+ time.time() - start_time
144
+ error_msg = f"LSP execution failed: {error}"
145
+
146
+ if not self.quiet:
147
+ self.console.print(f"āŒ {hook.name} (LSP): {error_msg}", style="red")
148
+ self.console.print(f"šŸ”„ Falling back to regular {hook.name} execution")
149
+
150
+ return self.execute_single_hook(hook)
151
+
152
+ def _should_use_tool_proxy(self, hook: HookDefinition) -> bool:
153
+ """Determine if a hook should use tool proxy for resilient execution."""
154
+ if not self.use_tool_proxy or not self.tool_proxy:
155
+ return False
156
+
157
+ # Use tool proxy for known fragile tools
158
+ fragile_tools = {"zuban", "skylos", "bandit"}
159
+ return hook.name in fragile_tools
160
+
161
+ def _execute_hook_with_proxy(self, hook: HookDefinition) -> HookResult:
162
+ """Execute a hook using tool proxy for resilient execution."""
163
+ start_time = time.time()
164
+
165
+ try:
166
+ if not self.quiet:
167
+ self.console.print(
168
+ f"šŸ›”ļø Using resilient execution for {hook.name}", style="blue"
169
+ )
170
+
171
+ # Parse hook entry to extract tool name and args
172
+ tool_name, args = self._parse_hook_entry(hook)
173
+
174
+ # Execute through tool proxy
175
+ if self.tool_proxy is not None:
176
+ exit_code = self.tool_proxy.execute_tool(tool_name, args)
177
+ else:
178
+ exit_code = -1 # Error code when tool proxy is not available
179
+
180
+ duration = time.time() - start_time
181
+ status = "passed" if exit_code == 0 else "failed"
182
+
183
+ # Get tool status for output
184
+ tool_status = (
185
+ self.tool_proxy.get_tool_status().get(tool_name, {})
186
+ if self.tool_proxy is not None
187
+ else {}
188
+ )
189
+ output = self._format_proxy_output(tool_name, tool_status, duration)
190
+
191
+ return HookResult(
192
+ id=f"{hook.name}-proxy-{int(time.time())}",
193
+ name=f"{hook.name}-proxy",
194
+ status=status,
195
+ duration=duration,
196
+ files_processed=1, # Placeholder value
197
+ issues_found=[output] if status == "failed" else [],
198
+ )
199
+
200
+ except Exception as e:
201
+ duration = time.time() - start_time
202
+ error_msg = f"Tool proxy execution failed: {e}"
203
+
204
+ if not self.quiet:
205
+ self.console.print(f"āŒ {hook.name} (proxy): {error_msg}", style="red")
206
+ self.console.print(f"šŸ”„ Falling back to regular {hook.name} execution")
207
+
208
+ # Fallback to regular execution
209
+ return self.execute_single_hook(hook)
210
+
211
+ def _parse_hook_entry(self, hook: HookDefinition) -> tuple[str, list[str]]:
212
+ """Parse hook entry to extract tool name and arguments."""
213
+ entry_str = " ".join(hook.command)
214
+ entry_parts = entry_str.split()
215
+
216
+ if len(entry_parts) < 3: # e.g., "uv run zuban"
217
+ raise ValueError(f"Invalid hook entry format: {entry_str}")
218
+
219
+ # Extract tool name (assuming "uv run <tool>" format)
220
+ if entry_parts[0] == "uv" and entry_parts[1] == "run":
221
+ tool_name = entry_parts[2]
222
+ args = entry_parts[3:] if len(entry_parts) > 3 else []
223
+ else:
224
+ # Direct tool execution
225
+ tool_name = entry_parts[0]
226
+ args = entry_parts[1:]
227
+
228
+ return tool_name, args
229
+
230
+ def _format_proxy_output(
231
+ self, tool_name: str, tool_status: dict[str, t.Any], duration: float
232
+ ) -> str:
233
+ """Format tool proxy execution output."""
234
+ status_info = []
235
+
236
+ if tool_status.get("circuit_breaker_open"):
237
+ status_info.append("Circuit breaker: OPEN")
238
+
239
+ if tool_status.get("is_healthy") is False:
240
+ status_info.append("Health check: FAILED")
241
+
242
+ fallback_tools = tool_status.get("fallback_tools", [])
243
+ if fallback_tools:
244
+ status_info.append(f"Fallbacks: {', '.join(fallback_tools)}")
245
+
246
+ status_str = f" ({', '.join(status_info)})" if status_info else ""
247
+
248
+ return f"šŸ›”ļø Resilient execution completed in {duration:.2f}s{status_str}"
249
+
250
+ def get_execution_mode_summary(self) -> dict[str, t.Any]:
251
+ """Get summary of execution mode capabilities."""
252
+ lsp_available = self.lsp_client.is_server_running()
253
+ server_info = self.lsp_client.get_server_info() if lsp_available else None
254
+
255
+ summary = {
256
+ "lsp_server_available": lsp_available,
257
+ "lsp_server_info": server_info,
258
+ "optimization_enabled": lsp_available,
259
+ "supported_hooks": ["zuban"] if lsp_available else [],
260
+ "tool_proxy_enabled": self.use_tool_proxy,
261
+ "resilient_tools": ["zuban", "skylos", "bandit"]
262
+ if self.use_tool_proxy
263
+ else [],
264
+ }
265
+
266
+ # Add tool proxy status if available
267
+ if self.tool_proxy:
268
+ summary["tool_status"] = self.tool_proxy.get_tool_status()
269
+
270
+ return summary