crackerjack 0.32.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 (200) 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 +64 -6
  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 +257 -218
  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 +558 -240
  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 +66 -13
  74. crackerjack/managers/test_command_builder.py +5 -17
  75. crackerjack/managers/test_executor.py +1 -3
  76. crackerjack/managers/test_manager.py +109 -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 +161 -32
  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 +174 -33
  92. crackerjack/mcp/tools/error_analyzer.py +3 -2
  93. crackerjack/mcp/tools/execution_tools.py +15 -12
  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 +3 -0
  109. crackerjack/mixins/error_handling.py +145 -0
  110. crackerjack/models/config.py +21 -1
  111. crackerjack/models/config_adapter.py +49 -1
  112. crackerjack/models/protocols.py +176 -107
  113. crackerjack/models/resource_protocols.py +55 -210
  114. crackerjack/models/task.py +3 -0
  115. crackerjack/monitoring/ai_agent_watchdog.py +13 -13
  116. crackerjack/monitoring/metrics_collector.py +426 -0
  117. crackerjack/monitoring/regression_prevention.py +8 -8
  118. crackerjack/monitoring/websocket_server.py +643 -0
  119. crackerjack/orchestration/advanced_orchestrator.py +11 -6
  120. crackerjack/orchestration/coverage_improvement.py +3 -3
  121. crackerjack/orchestration/execution_strategies.py +26 -6
  122. crackerjack/orchestration/test_progress_streamer.py +8 -5
  123. crackerjack/plugins/base.py +2 -2
  124. crackerjack/plugins/hooks.py +7 -0
  125. crackerjack/plugins/managers.py +11 -8
  126. crackerjack/security/__init__.py +0 -1
  127. crackerjack/security/audit.py +90 -105
  128. crackerjack/services/anomaly_detector.py +392 -0
  129. crackerjack/services/api_extractor.py +615 -0
  130. crackerjack/services/backup_service.py +2 -2
  131. crackerjack/services/bounded_status_operations.py +15 -152
  132. crackerjack/services/cache.py +127 -1
  133. crackerjack/services/changelog_automation.py +395 -0
  134. crackerjack/services/config.py +18 -11
  135. crackerjack/services/config_merge.py +30 -85
  136. crackerjack/services/config_template.py +506 -0
  137. crackerjack/services/contextual_ai_assistant.py +48 -22
  138. crackerjack/services/coverage_badge_service.py +171 -0
  139. crackerjack/services/coverage_ratchet.py +41 -17
  140. crackerjack/services/debug.py +3 -3
  141. crackerjack/services/dependency_analyzer.py +460 -0
  142. crackerjack/services/dependency_monitor.py +14 -11
  143. crackerjack/services/documentation_generator.py +491 -0
  144. crackerjack/services/documentation_service.py +675 -0
  145. crackerjack/services/enhanced_filesystem.py +6 -5
  146. crackerjack/services/enterprise_optimizer.py +865 -0
  147. crackerjack/services/error_pattern_analyzer.py +676 -0
  148. crackerjack/services/file_hasher.py +1 -1
  149. crackerjack/services/git.py +41 -45
  150. crackerjack/services/health_metrics.py +10 -8
  151. crackerjack/services/heatmap_generator.py +735 -0
  152. crackerjack/services/initialization.py +30 -33
  153. crackerjack/services/input_validator.py +5 -97
  154. crackerjack/services/intelligent_commit.py +327 -0
  155. crackerjack/services/log_manager.py +15 -12
  156. crackerjack/services/logging.py +4 -3
  157. crackerjack/services/lsp_client.py +628 -0
  158. crackerjack/services/memory_optimizer.py +409 -0
  159. crackerjack/services/metrics.py +42 -33
  160. crackerjack/services/parallel_executor.py +416 -0
  161. crackerjack/services/pattern_cache.py +1 -1
  162. crackerjack/services/pattern_detector.py +6 -6
  163. crackerjack/services/performance_benchmarks.py +250 -576
  164. crackerjack/services/performance_cache.py +382 -0
  165. crackerjack/services/performance_monitor.py +565 -0
  166. crackerjack/services/predictive_analytics.py +510 -0
  167. crackerjack/services/quality_baseline.py +234 -0
  168. crackerjack/services/quality_baseline_enhanced.py +646 -0
  169. crackerjack/services/quality_intelligence.py +785 -0
  170. crackerjack/services/regex_patterns.py +605 -524
  171. crackerjack/services/regex_utils.py +43 -123
  172. crackerjack/services/secure_path_utils.py +5 -164
  173. crackerjack/services/secure_status_formatter.py +30 -141
  174. crackerjack/services/secure_subprocess.py +11 -92
  175. crackerjack/services/security.py +61 -30
  176. crackerjack/services/security_logger.py +18 -22
  177. crackerjack/services/server_manager.py +124 -16
  178. crackerjack/services/status_authentication.py +16 -159
  179. crackerjack/services/status_security_manager.py +4 -131
  180. crackerjack/services/terminal_utils.py +0 -0
  181. crackerjack/services/thread_safe_status_collector.py +19 -125
  182. crackerjack/services/unified_config.py +21 -13
  183. crackerjack/services/validation_rate_limiter.py +5 -54
  184. crackerjack/services/version_analyzer.py +459 -0
  185. crackerjack/services/version_checker.py +1 -1
  186. crackerjack/services/websocket_resource_limiter.py +10 -144
  187. crackerjack/services/zuban_lsp_service.py +390 -0
  188. crackerjack/slash_commands/__init__.py +2 -7
  189. crackerjack/slash_commands/run.md +2 -2
  190. crackerjack/tools/validate_input_validator_patterns.py +14 -40
  191. crackerjack/tools/validate_regex_patterns.py +19 -48
  192. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/METADATA +197 -26
  193. crackerjack-0.33.1.dist-info/RECORD +229 -0
  194. crackerjack/CLAUDE.md +0 -207
  195. crackerjack/RULES.md +0 -380
  196. crackerjack/py313.py +0 -234
  197. crackerjack-0.32.0.dist-info/RECORD +0 -180
  198. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/WHEEL +0 -0
  199. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
  200. {crackerjack-0.32.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:
@@ -0,0 +1,3 @@
1
+ from .error_handling import ErrorHandlingMixin
2
+
3
+ __all__ = ["ErrorHandlingMixin"]
@@ -0,0 +1,145 @@
1
+ import subprocess
2
+ import typing as t
3
+ from pathlib import Path
4
+
5
+ from rich.console import Console
6
+
7
+
8
+ class ErrorHandlingMixin:
9
+ def __init__(self) -> None:
10
+ self.console: Console
11
+ self.logger: t.Any
12
+
13
+ def handle_subprocess_error(
14
+ self,
15
+ error: Exception,
16
+ command: list[str],
17
+ operation_name: str,
18
+ critical: bool = False,
19
+ ) -> bool:
20
+ error_msg = f"{operation_name} failed: {error}"
21
+
22
+ if hasattr(self, "logger") and self.logger:
23
+ self.logger.error(
24
+ error_msg,
25
+ command=" ".join(command),
26
+ error_type=type(error).__name__,
27
+ critical=critical,
28
+ )
29
+
30
+ if critical:
31
+ self.console.print(f"[red]🚨 CRITICAL: {error_msg}[/red]")
32
+ else:
33
+ self.console.print(f"[red]❌ {error_msg}[/red]")
34
+
35
+ return False
36
+
37
+ def handle_file_operation_error(
38
+ self,
39
+ error: Exception,
40
+ file_path: Path,
41
+ operation: str,
42
+ critical: bool = False,
43
+ ) -> bool:
44
+ error_msg = f"Failed to {operation} {file_path}: {error}"
45
+
46
+ if hasattr(self, "logger") and self.logger:
47
+ self.logger.error(
48
+ error_msg,
49
+ file_path=str(file_path),
50
+ operation=operation,
51
+ error_type=type(error).__name__,
52
+ critical=critical,
53
+ )
54
+
55
+ if critical:
56
+ self.console.print(f"[red]🚨 CRITICAL: {error_msg}[/red]")
57
+ else:
58
+ self.console.print(f"[red]❌ {error_msg}[/red]")
59
+
60
+ return False
61
+
62
+ def handle_timeout_error(
63
+ self,
64
+ operation_name: str,
65
+ timeout_seconds: float,
66
+ command: list[str] | None = None,
67
+ ) -> bool:
68
+ error_msg = f"{operation_name} timed out after {timeout_seconds}s"
69
+
70
+ if hasattr(self, "logger") and self.logger:
71
+ self.logger.warning(
72
+ error_msg,
73
+ timeout=timeout_seconds,
74
+ command=" ".join(command) if command else None,
75
+ )
76
+
77
+ self.console.print(f"[yellow]⏰ {error_msg}[/yellow]")
78
+
79
+ return False
80
+
81
+ def log_operation_success(
82
+ self,
83
+ operation_name: str,
84
+ details: dict[str, t.Any] | None = None,
85
+ ) -> None:
86
+ if hasattr(self, "logger") and self.logger:
87
+ self.logger.info(
88
+ f"{operation_name} completed successfully", **(details or {})
89
+ )
90
+
91
+ def validate_required_tools(
92
+ self,
93
+ tools: dict[str, str],
94
+ operation_name: str,
95
+ ) -> bool:
96
+ missing_tools = []
97
+
98
+ for tool_name, command in tools.items():
99
+ try:
100
+ subprocess.run(
101
+ [command, "--version"],
102
+ capture_output=True,
103
+ check=True,
104
+ timeout=5,
105
+ )
106
+ except (
107
+ subprocess.CalledProcessError,
108
+ subprocess.TimeoutExpired,
109
+ FileNotFoundError,
110
+ ):
111
+ missing_tools.append(tool_name)
112
+
113
+ if missing_tools:
114
+ error_msg = f"Missing required tools for {operation_name}: {', '.join(missing_tools)}"
115
+
116
+ if hasattr(self, "logger") and self.logger:
117
+ self.logger.error(
118
+ error_msg,
119
+ missing_tools=missing_tools,
120
+ operation=operation_name,
121
+ )
122
+
123
+ self.console.print(f"[red]❌ {error_msg}[/red]")
124
+ return False
125
+
126
+ return True
127
+
128
+ def safe_get_attribute(
129
+ self,
130
+ obj: t.Any,
131
+ attribute: str,
132
+ default: t.Any = None,
133
+ operation_name: str = "attribute access",
134
+ ) -> t.Any:
135
+ try:
136
+ return getattr(obj, attribute, default)
137
+ except Exception as e:
138
+ if hasattr(self, "logger") and self.logger:
139
+ self.logger.warning(
140
+ f"Error accessing {attribute} during {operation_name}: {e}",
141
+ attribute=attribute,
142
+ operation=operation_name,
143
+ error_type=type(e).__name__,
144
+ )
145
+ return default
@@ -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
 
@@ -79,6 +80,23 @@ class EnterpriseConfig:
79
80
  organization: str | None = None
80
81
 
81
82
 
83
+ @dataclass
84
+ class MCPServerConfig:
85
+ http_port: int = 8676
86
+ http_host: str = "127.0.0.1"
87
+ websocket_port: int = 8675
88
+ http_enabled: bool = False
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
+
82
100
  @dataclass
83
101
  class WorkflowOptions:
84
102
  cleaning: CleaningConfig = field(default_factory=CleaningConfig)
@@ -91,3 +109,5 @@ class WorkflowOptions:
91
109
  progress: ProgressConfig = field(default_factory=ProgressConfig)
92
110
  cleanup: CleanupConfig = field(default_factory=CleanupConfig)
93
111
  enterprise: EnterpriseConfig = field(default_factory=EnterpriseConfig)
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