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
@@ -4,6 +4,7 @@ import signal
4
4
  import subprocess
5
5
  import tempfile
6
6
  import time
7
+ import typing as t
7
8
  from pathlib import Path
8
9
 
9
10
  import uvicorn
@@ -23,9 +24,9 @@ class WebSocketServer:
23
24
  self.progress_dir = Path(tempfile.gettempdir()) / "crackerjack-mcp-progress"
24
25
  self.is_running = True
25
26
  self.job_manager: JobManager | None = None
26
- self.app = None
27
+ self.app: t.Any = None
27
28
  self.timeout_manager = get_timeout_manager()
28
- self.server_task: asyncio.Task | None = None
29
+ self.server_task: asyncio.Task[t.Any] | None = None
29
30
 
30
31
  def setup(self) -> None:
31
32
  self.progress_dir.mkdir(exist_ok=True)
@@ -37,29 +38,22 @@ class WebSocketServer:
37
38
  signal.signal(signal.SIGINT, self._signal_handler)
38
39
  signal.signal(signal.SIGTERM, self._signal_handler)
39
40
 
40
- def _signal_handler(self, _signum: int, _frame) -> None:
41
+ def _signal_handler(self, _signum: int, _frame: t.Any) -> None:
41
42
  console.print("\n[yellow]Shutting down WebSocket server...[/yellow]")
42
43
  self.is_running = False
43
44
 
44
- # Cancel server task if running
45
45
  if self.server_task and not self.server_task.done():
46
46
  self.server_task.cancel()
47
47
 
48
- # Clean up job manager connections
49
48
  if self.job_manager:
50
49
  with contextlib.suppress(Exception):
51
- # Give existing connections 5 seconds to close
52
50
  asyncio.create_task(self._graceful_shutdown())
53
51
 
54
52
  async def _graceful_shutdown(self) -> None:
55
- """Gracefully shutdown WebSocket connections."""
56
53
  if self.job_manager:
57
54
  try:
58
- # Wait briefly for connections to close naturally
59
55
  await asyncio.sleep(2.0)
60
56
 
61
- # Force close any remaining connections
62
- # Note: Implementation depends on JobManager API
63
57
  console.print(
64
58
  "[yellow]Forcing remaining WebSocket connections to close[/yellow]"
65
59
  )
@@ -80,14 +74,12 @@ class WebSocketServer:
80
74
  port=self.port,
81
75
  host="127.0.0.1",
82
76
  log_level="info",
83
- # Add timeout configurations
84
- timeout_keep_alive=30, # Keep-alive timeout
85
- timeout_graceful_shutdown=30, # Graceful shutdown timeout
77
+ timeout_keep_alive=30,
78
+ timeout_graceful_shutdown=30,
86
79
  )
87
80
 
88
81
  server = uvicorn.Server(config)
89
82
 
90
- # Use asyncio event loop for better control
91
83
  try:
92
84
  asyncio.run(self._run_with_timeout(server))
93
85
  except KeyboardInterrupt:
@@ -101,22 +93,13 @@ class WebSocketServer:
101
93
  console.print("[green]WebSocket server shutdown complete[/green]")
102
94
 
103
95
  async def _run_with_timeout(self, server: uvicorn.Server) -> None:
104
- """Run the server with timeout protection."""
105
96
  try:
106
- # Start server as a background task
107
97
  self.server_task = asyncio.create_task(server.serve())
108
98
 
109
- # Monitor server health while running
110
99
  while self.is_running and not self.server_task.done():
111
100
  try:
112
- # Check server health periodically
113
101
  await asyncio.sleep(5.0)
114
102
 
115
- # Optional: Add health checks here
116
- # if not await self._server_health_check():
117
- # console.print("[yellow]Server health check failed[/yellow]")
118
- # break
119
-
120
103
  except asyncio.CancelledError:
121
104
  console.print("[yellow]Server monitoring cancelled[/yellow]")
122
105
  break
@@ -124,7 +107,6 @@ class WebSocketServer:
124
107
  console.print(f"[red]Server monitoring error: {e}[/red]")
125
108
  break
126
109
 
127
- # Wait for server task to complete
128
110
  if self.server_task and not self.server_task.done():
129
111
  try:
130
112
  await asyncio.wait_for(self.server_task, timeout=30.0)
@@ -153,7 +135,7 @@ def handle_websocket_server_command(
153
135
 
154
136
  try:
155
137
  result = subprocess.run(
156
- ["pkill", "-f", f"uvicorn.*:{port}"],
138
+ ["pkill", "-f", f"uvicorn.*: {port}"],
157
139
  check=False,
158
140
  capture_output=True,
159
141
  text=True,
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import typing as t
2
3
  from contextlib import suppress
3
4
  from pathlib import Path
4
5
 
@@ -24,10 +25,9 @@ class WebSocketHandler:
24
25
  return
25
26
 
26
27
  try:
27
- # Add timeout to the entire connection handling
28
28
  async with self.timeout_manager.timeout_context(
29
29
  "websocket_connection",
30
- timeout=3600.0, # 1 hour max connection time
30
+ timeout=3600.0,
31
31
  strategy=TimeoutStrategy.GRACEFUL_DEGRADATION,
32
32
  ):
33
33
  await self._establish_connection(websocket, job_id)
@@ -44,13 +44,11 @@ class WebSocketHandler:
44
44
  await self._cleanup_connection(job_id, websocket)
45
45
 
46
46
  async def _establish_connection(self, websocket: WebSocket, job_id: str) -> None:
47
- """Establish WebSocket connection and add to job manager."""
48
47
  await websocket.accept()
49
48
  self.job_manager.add_connection(job_id, websocket)
50
49
  console.print(f"[green]WebSocket connected for job: {job_id}[/green]")
51
50
 
52
51
  async def _send_initial_progress(self, websocket: WebSocket, job_id: str) -> None:
53
- """Send initial progress data to the connected WebSocket."""
54
52
  try:
55
53
  async with self.timeout_manager.timeout_context(
56
54
  "websocket_broadcast",
@@ -69,8 +67,7 @@ class WebSocketHandler:
69
67
  f"[red]Failed to send initial progress for {job_id}: {e}[/red]"
70
68
  )
71
69
 
72
- def _create_initial_progress_message(self, job_id: str) -> dict:
73
- """Create initial progress message for new jobs."""
70
+ def _create_initial_progress_message(self, job_id: str) -> dict[str, t.Any]:
74
71
  return {
75
72
  "job_id": job_id,
76
73
  "status": "waiting",
@@ -82,9 +79,8 @@ class WebSocketHandler:
82
79
  }
83
80
 
84
81
  async def _handle_message_loop(self, websocket: WebSocket, job_id: str) -> None:
85
- """Handle the main message processing loop."""
86
82
  message_count = 0
87
- max_messages = 10000 # Prevent infinite message loops
83
+ max_messages = 10000
88
84
 
89
85
  while message_count < max_messages:
90
86
  try:
@@ -105,25 +101,21 @@ class WebSocketHandler:
105
101
  async def _process_single_message(
106
102
  self, websocket: WebSocket, job_id: str, message_count: int
107
103
  ) -> bool:
108
- """Process a single WebSocket message. Returns False to break the loop."""
109
104
  try:
110
- # Add timeout to individual message operations
111
105
  async with self.timeout_manager.timeout_context(
112
106
  "websocket_message",
113
- timeout=30.0, # 30 second timeout per message
107
+ timeout=30.0,
114
108
  strategy=TimeoutStrategy.FAIL_FAST,
115
109
  ):
116
- # Use asyncio.wait_for for additional protection
117
110
  data = await asyncio.wait_for(
118
111
  websocket.receive_text(),
119
- timeout=25.0, # Slightly less than timeout context
112
+ timeout=25.0,
120
113
  )
121
114
 
122
115
  console.print(
123
116
  f"[blue]Received message {message_count} for {job_id}: {data[:100]}...[/blue]",
124
117
  )
125
118
 
126
- # Respond with timeout protection
127
119
  await asyncio.wait_for(
128
120
  websocket.send_json(
129
121
  {
@@ -151,7 +143,6 @@ class WebSocketHandler:
151
143
  return False
152
144
 
153
145
  async def _handle_timeout_error(self, websocket: WebSocket, job_id: str) -> None:
154
- """Handle timeout errors during connection."""
155
146
  console.print(
156
147
  f"[yellow]WebSocket connection timeout for job: {job_id}[/yellow]"
157
148
  )
@@ -161,13 +152,11 @@ class WebSocketHandler:
161
152
  async def _handle_connection_error(
162
153
  self, websocket: WebSocket, job_id: str, error: Exception
163
154
  ) -> None:
164
- """Handle connection errors."""
165
155
  console.print(f"[red]WebSocket error for job {job_id}: {error}[/red]")
166
156
  with suppress(Exception):
167
157
  await websocket.close(code=1011, reason="Internal error")
168
158
 
169
159
  async def _cleanup_connection(self, job_id: str, websocket: WebSocket) -> None:
170
- """Clean up the connection."""
171
160
  try:
172
161
  self.job_manager.remove_connection(job_id, websocket)
173
162
  except Exception as e:
@@ -1,5 +1,3 @@
1
- """Common mixins for crackerjack components."""
2
-
3
1
  from .error_handling import ErrorHandlingMixin
4
2
 
5
3
  __all__ = ["ErrorHandlingMixin"]
@@ -1,5 +1,3 @@
1
- """Common error handling patterns for crackerjack components."""
2
-
3
1
  import subprocess
4
2
  import typing as t
5
3
  from pathlib import Path
@@ -8,12 +6,9 @@ from rich.console import Console
8
6
 
9
7
 
10
8
  class ErrorHandlingMixin:
11
- """Mixin providing common error handling patterns for crackerjack components."""
12
-
13
9
  def __init__(self) -> None:
14
- # These attributes should be provided by the class using the mixin
15
10
  self.console: Console
16
- self.logger: t.Any # Logger instance
11
+ self.logger: t.Any
17
12
 
18
13
  def handle_subprocess_error(
19
14
  self,
@@ -22,20 +17,8 @@ class ErrorHandlingMixin:
22
17
  operation_name: str,
23
18
  critical: bool = False,
24
19
  ) -> bool:
25
- """Handle subprocess errors with consistent logging and user feedback.
26
-
27
- Args:
28
- error: The exception that occurred
29
- command: The command that failed
30
- operation_name: Human-readable name of the operation
31
- critical: Whether this is a critical error that should stop execution
32
-
33
- Returns:
34
- False to indicate failure
35
- """
36
20
  error_msg = f"{operation_name} failed: {error}"
37
21
 
38
- # Log the error
39
22
  if hasattr(self, "logger") and self.logger:
40
23
  self.logger.error(
41
24
  error_msg,
@@ -44,7 +27,6 @@ class ErrorHandlingMixin:
44
27
  critical=critical,
45
28
  )
46
29
 
47
- # Display user-friendly error message
48
30
  if critical:
49
31
  self.console.print(f"[red]🚨 CRITICAL: {error_msg}[/red]")
50
32
  else:
@@ -59,20 +41,8 @@ class ErrorHandlingMixin:
59
41
  operation: str,
60
42
  critical: bool = False,
61
43
  ) -> bool:
62
- """Handle file operation errors with consistent logging and user feedback.
63
-
64
- Args:
65
- error: The exception that occurred
66
- file_path: The file that caused the error
67
- operation: The operation that failed (e.g., "read", "write", "delete")
68
- critical: Whether this is a critical error that should stop execution
69
-
70
- Returns:
71
- False to indicate failure
72
- """
73
44
  error_msg = f"Failed to {operation} {file_path}: {error}"
74
45
 
75
- # Log the error
76
46
  if hasattr(self, "logger") and self.logger:
77
47
  self.logger.error(
78
48
  error_msg,
@@ -82,7 +52,6 @@ class ErrorHandlingMixin:
82
52
  critical=critical,
83
53
  )
84
54
 
85
- # Display user-friendly error message
86
55
  if critical:
87
56
  self.console.print(f"[red]🚨 CRITICAL: {error_msg}[/red]")
88
57
  else:
@@ -96,19 +65,8 @@ class ErrorHandlingMixin:
96
65
  timeout_seconds: float,
97
66
  command: list[str] | None = None,
98
67
  ) -> bool:
99
- """Handle timeout errors with consistent logging and user feedback.
100
-
101
- Args:
102
- operation_name: Human-readable name of the operation
103
- timeout_seconds: The timeout that was exceeded
104
- command: Optional command that timed out
105
-
106
- Returns:
107
- False to indicate failure
108
- """
109
68
  error_msg = f"{operation_name} timed out after {timeout_seconds}s"
110
69
 
111
- # Log the error
112
70
  if hasattr(self, "logger") and self.logger:
113
71
  self.logger.warning(
114
72
  error_msg,
@@ -116,7 +74,6 @@ class ErrorHandlingMixin:
116
74
  command=" ".join(command) if command else None,
117
75
  )
118
76
 
119
- # Display user-friendly error message
120
77
  self.console.print(f"[yellow]⏰ {error_msg}[/yellow]")
121
78
 
122
79
  return False
@@ -126,12 +83,6 @@ class ErrorHandlingMixin:
126
83
  operation_name: str,
127
84
  details: dict[str, t.Any] | None = None,
128
85
  ) -> None:
129
- """Log successful operations with consistent formatting.
130
-
131
- Args:
132
- operation_name: Human-readable name of the operation
133
- details: Optional additional details to log
134
- """
135
86
  if hasattr(self, "logger") and self.logger:
136
87
  self.logger.info(
137
88
  f"{operation_name} completed successfully", **(details or {})
@@ -142,15 +93,6 @@ class ErrorHandlingMixin:
142
93
  tools: dict[str, str],
143
94
  operation_name: str,
144
95
  ) -> bool:
145
- """Validate that required external tools are available.
146
-
147
- Args:
148
- tools: Dict mapping tool names to their expected commands
149
- operation_name: Name of operation requiring the tools
150
-
151
- Returns:
152
- True if all tools are available, False otherwise
153
- """
154
96
  missing_tools = []
155
97
 
156
98
  for tool_name, command in tools.items():
@@ -190,17 +132,6 @@ class ErrorHandlingMixin:
190
132
  default: t.Any = None,
191
133
  operation_name: str = "attribute access",
192
134
  ) -> t.Any:
193
- """Safely get an attribute with error handling.
194
-
195
- Args:
196
- obj: Object to get attribute from
197
- attribute: Name of attribute to get
198
- default: Default value if attribute doesn't exist
199
- operation_name: Name of operation for error logging
200
-
201
- Returns:
202
- The attribute value or default
203
- """
204
135
  try:
205
136
  return getattr(obj, attribute, default)
206
137
  except Exception as e:
@@ -18,6 +18,7 @@ class HookConfig:
18
18
  experimental_hooks: bool = False
19
19
  enable_pyrefly: bool = False
20
20
  enable_ty: bool = False
21
+ enable_lsp_optimization: bool = False
21
22
 
22
23
 
23
24
  @dataclass
@@ -47,7 +48,7 @@ class GitConfig:
47
48
  class AIConfig:
48
49
  ai_agent: bool = False
49
50
  start_mcp_server: bool = False
50
- max_iterations: int = 10
51
+ max_iterations: int = 5
51
52
  autofix: bool = True
52
53
  ai_agent_autofix: bool = False
53
54
 
@@ -87,6 +88,15 @@ class MCPServerConfig:
87
88
  http_enabled: bool = False
88
89
 
89
90
 
91
+ @dataclass
92
+ class ZubanLSPConfig:
93
+ enabled: bool = True
94
+ auto_start: bool = True
95
+ port: int = 8677
96
+ mode: str = "stdio"
97
+ timeout: int = 30
98
+
99
+
90
100
  @dataclass
91
101
  class WorkflowOptions:
92
102
  cleaning: CleaningConfig = field(default_factory=CleaningConfig)
@@ -100,3 +110,4 @@ class WorkflowOptions:
100
110
  cleanup: CleanupConfig = field(default_factory=CleanupConfig)
101
111
  enterprise: EnterpriseConfig = field(default_factory=EnterpriseConfig)
102
112
  mcp_server: MCPServerConfig = field(default_factory=MCPServerConfig)
113
+ zuban_lsp: ZubanLSPConfig = field(default_factory=ZubanLSPConfig)
@@ -3,18 +3,43 @@ import typing as t
3
3
  from .config import (
4
4
  AIConfig,
5
5
  CleaningConfig,
6
+ CleanupConfig,
6
7
  EnterpriseConfig,
7
8
  ExecutionConfig,
8
9
  GitConfig,
9
10
  HookConfig,
11
+ MCPServerConfig,
10
12
  ProgressConfig,
11
13
  PublishConfig,
12
14
  TestConfig,
13
15
  WorkflowOptions,
16
+ ZubanLSPConfig,
14
17
  )
15
18
  from .protocols import OptionsProtocol
16
19
 
17
20
 
21
+ def _determine_max_iterations(options: OptionsProtocol) -> int:
22
+ """Determine max_iterations using effective_max_iterations if available, otherwise fallback logic.
23
+
24
+ Priority:
25
+ 1. Use effective_max_iterations property if available (handles quick/thorough flags)
26
+ 2. Explicit max_iterations value
27
+ 3. Default: 5 iterations
28
+ """
29
+ # Use effective_max_iterations property if available (Options class has this)
30
+ if hasattr(options, "effective_max_iterations"):
31
+ return getattr(options, "effective_max_iterations") # type: ignore[no-any-return]
32
+
33
+ # Fallback for other OptionsProtocol implementations
34
+ if hasattr(options, "max_iterations") and getattr(
35
+ options, "max_iterations", None
36
+ ) not in (0, None):
37
+ return getattr(options, "max_iterations") # type: ignore[no-any-return]
38
+
39
+ # Default to 5 iterations
40
+ return 5
41
+
42
+
18
43
  class OptionsAdapter:
19
44
  @staticmethod
20
45
  def from_options_protocol(options: OptionsProtocol) -> WorkflowOptions:
@@ -32,6 +57,7 @@ class OptionsAdapter:
32
57
  experimental_hooks=getattr(options, "experimental_hooks", False),
33
58
  enable_pyrefly=getattr(options, "enable_pyrefly", False),
34
59
  enable_ty=getattr(options, "enable_ty", False),
60
+ enable_lsp_optimization=getattr(options, "enable_lsp_hooks", False),
35
61
  ),
36
62
  testing=TestConfig(
37
63
  test=getattr(options, "test", False),
@@ -55,7 +81,7 @@ class OptionsAdapter:
55
81
  autofix=getattr(options, "autofix", True),
56
82
  ai_agent_autofix=getattr(options, "ai_agent_autofix", False),
57
83
  start_mcp_server=getattr(options, "start_mcp_server", False),
58
- max_iterations=getattr(options, "max_iterations", 10),
84
+ max_iterations=_determine_max_iterations(options),
59
85
  ),
60
86
  execution=ExecutionConfig(
61
87
  interactive=getattr(options, "interactive", False),
@@ -66,11 +92,29 @@ class OptionsAdapter:
66
92
  progress=ProgressConfig(
67
93
  enabled=getattr(options, "track_progress", False),
68
94
  ),
95
+ cleanup=CleanupConfig(
96
+ auto_cleanup=getattr(options, "auto_cleanup", True),
97
+ keep_debug_logs=getattr(options, "keep_debug_logs", 5),
98
+ keep_coverage_files=getattr(options, "keep_coverage_files", 10),
99
+ ),
69
100
  enterprise=EnterpriseConfig(
70
101
  enabled=getattr(options, "enterprise_batch", None) is not None,
71
102
  license_key=getattr(options, "license_key", None),
72
103
  organization=getattr(options, "organization", None),
73
104
  ),
105
+ mcp_server=MCPServerConfig(
106
+ http_port=getattr(options, "http_port", 8676),
107
+ http_host=getattr(options, "http_host", "127.0.0.1"),
108
+ websocket_port=getattr(options, "websocket_port", 8675),
109
+ http_enabled=getattr(options, "http_enabled", False),
110
+ ),
111
+ zuban_lsp=ZubanLSPConfig(
112
+ enabled=not getattr(options, "no_zuban_lsp", False),
113
+ auto_start=True,
114
+ port=getattr(options, "zuban_lsp_port", 8677),
115
+ mode=getattr(options, "zuban_lsp_mode", "stdio"),
116
+ timeout=getattr(options, "zuban_lsp_timeout", 30),
117
+ ),
74
118
  )
75
119
 
76
120
  @staticmethod
@@ -200,6 +244,10 @@ class LegacyOptionsWrapper:
200
244
  def enable_ty(self) -> bool:
201
245
  return self._options.hooks.enable_ty
202
246
 
247
+ @property
248
+ def enable_lsp_hooks(self) -> bool:
249
+ return self._options.hooks.enable_lsp_optimization
250
+
203
251
  @property
204
252
  def no_git_tags(self) -> bool:
205
253
  return self._options.publishing.no_git_tags