crackerjack 0.33.0__py3-none-any.whl ā 0.33.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of crackerjack might be problematic. Click here for more details.
- crackerjack/__main__.py +1350 -34
- crackerjack/adapters/__init__.py +17 -0
- crackerjack/adapters/lsp_client.py +358 -0
- crackerjack/adapters/rust_tool_adapter.py +194 -0
- crackerjack/adapters/rust_tool_manager.py +193 -0
- crackerjack/adapters/skylos_adapter.py +231 -0
- crackerjack/adapters/zuban_adapter.py +560 -0
- crackerjack/agents/base.py +7 -3
- crackerjack/agents/coordinator.py +271 -33
- crackerjack/agents/documentation_agent.py +9 -15
- crackerjack/agents/dry_agent.py +3 -15
- crackerjack/agents/formatting_agent.py +1 -1
- crackerjack/agents/import_optimization_agent.py +36 -180
- crackerjack/agents/performance_agent.py +17 -98
- crackerjack/agents/performance_helpers.py +7 -31
- crackerjack/agents/proactive_agent.py +1 -3
- crackerjack/agents/refactoring_agent.py +16 -85
- crackerjack/agents/refactoring_helpers.py +7 -42
- crackerjack/agents/security_agent.py +9 -48
- crackerjack/agents/test_creation_agent.py +356 -513
- crackerjack/agents/test_specialist_agent.py +0 -4
- crackerjack/api.py +6 -25
- crackerjack/cli/cache_handlers.py +204 -0
- crackerjack/cli/cache_handlers_enhanced.py +683 -0
- crackerjack/cli/facade.py +100 -0
- crackerjack/cli/handlers.py +224 -9
- crackerjack/cli/interactive.py +6 -4
- crackerjack/cli/options.py +642 -55
- crackerjack/cli/utils.py +2 -1
- crackerjack/code_cleaner.py +58 -117
- crackerjack/config/global_lock_config.py +8 -48
- crackerjack/config/hooks.py +53 -62
- crackerjack/core/async_workflow_orchestrator.py +24 -34
- crackerjack/core/autofix_coordinator.py +3 -17
- crackerjack/core/enhanced_container.py +4 -13
- crackerjack/core/file_lifecycle.py +12 -89
- crackerjack/core/performance.py +2 -2
- crackerjack/core/performance_monitor.py +15 -55
- crackerjack/core/phase_coordinator.py +104 -204
- crackerjack/core/resource_manager.py +14 -90
- crackerjack/core/service_watchdog.py +62 -95
- crackerjack/core/session_coordinator.py +149 -0
- crackerjack/core/timeout_manager.py +14 -72
- crackerjack/core/websocket_lifecycle.py +13 -78
- crackerjack/core/workflow_orchestrator.py +171 -174
- crackerjack/docs/INDEX.md +11 -0
- crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
- crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
- crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
- crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
- crackerjack/docs/generated/api/SERVICES.md +1252 -0
- crackerjack/documentation/__init__.py +31 -0
- crackerjack/documentation/ai_templates.py +756 -0
- crackerjack/documentation/dual_output_generator.py +765 -0
- crackerjack/documentation/mkdocs_integration.py +518 -0
- crackerjack/documentation/reference_generator.py +977 -0
- crackerjack/dynamic_config.py +55 -50
- crackerjack/executors/async_hook_executor.py +10 -15
- crackerjack/executors/cached_hook_executor.py +117 -43
- crackerjack/executors/hook_executor.py +8 -34
- crackerjack/executors/hook_lock_manager.py +26 -183
- crackerjack/executors/individual_hook_executor.py +13 -11
- crackerjack/executors/lsp_aware_hook_executor.py +270 -0
- crackerjack/executors/tool_proxy.py +417 -0
- crackerjack/hooks/lsp_hook.py +79 -0
- crackerjack/intelligence/adaptive_learning.py +25 -10
- crackerjack/intelligence/agent_orchestrator.py +2 -5
- crackerjack/intelligence/agent_registry.py +34 -24
- crackerjack/intelligence/agent_selector.py +5 -7
- crackerjack/interactive.py +17 -6
- crackerjack/managers/async_hook_manager.py +0 -1
- crackerjack/managers/hook_manager.py +79 -1
- crackerjack/managers/publish_manager.py +44 -8
- crackerjack/managers/test_command_builder.py +1 -15
- crackerjack/managers/test_executor.py +1 -3
- crackerjack/managers/test_manager.py +98 -7
- crackerjack/managers/test_manager_backup.py +10 -9
- crackerjack/mcp/cache.py +2 -2
- crackerjack/mcp/client_runner.py +1 -1
- crackerjack/mcp/context.py +191 -68
- crackerjack/mcp/dashboard.py +7 -5
- crackerjack/mcp/enhanced_progress_monitor.py +31 -28
- crackerjack/mcp/file_monitor.py +30 -23
- crackerjack/mcp/progress_components.py +31 -21
- crackerjack/mcp/progress_monitor.py +50 -53
- crackerjack/mcp/rate_limiter.py +6 -6
- crackerjack/mcp/server_core.py +17 -16
- crackerjack/mcp/service_watchdog.py +2 -1
- crackerjack/mcp/state.py +4 -7
- crackerjack/mcp/task_manager.py +11 -9
- crackerjack/mcp/tools/core_tools.py +173 -32
- crackerjack/mcp/tools/error_analyzer.py +3 -2
- crackerjack/mcp/tools/execution_tools.py +8 -10
- crackerjack/mcp/tools/execution_tools_backup.py +42 -30
- crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
- crackerjack/mcp/tools/intelligence_tools.py +5 -2
- crackerjack/mcp/tools/monitoring_tools.py +33 -70
- crackerjack/mcp/tools/proactive_tools.py +24 -11
- crackerjack/mcp/tools/progress_tools.py +5 -8
- crackerjack/mcp/tools/utility_tools.py +20 -14
- crackerjack/mcp/tools/workflow_executor.py +62 -40
- crackerjack/mcp/websocket/app.py +8 -0
- crackerjack/mcp/websocket/endpoints.py +352 -357
- crackerjack/mcp/websocket/jobs.py +40 -57
- crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
- crackerjack/mcp/websocket/server.py +7 -25
- crackerjack/mcp/websocket/websocket_handler.py +6 -17
- crackerjack/mixins/__init__.py +0 -2
- crackerjack/mixins/error_handling.py +1 -70
- crackerjack/models/config.py +12 -1
- crackerjack/models/config_adapter.py +49 -1
- crackerjack/models/protocols.py +122 -122
- crackerjack/models/resource_protocols.py +55 -210
- crackerjack/monitoring/ai_agent_watchdog.py +13 -13
- crackerjack/monitoring/metrics_collector.py +426 -0
- crackerjack/monitoring/regression_prevention.py +8 -8
- crackerjack/monitoring/websocket_server.py +643 -0
- crackerjack/orchestration/advanced_orchestrator.py +11 -6
- crackerjack/orchestration/coverage_improvement.py +3 -3
- crackerjack/orchestration/execution_strategies.py +26 -6
- crackerjack/orchestration/test_progress_streamer.py +8 -5
- crackerjack/plugins/base.py +2 -2
- crackerjack/plugins/hooks.py +7 -0
- crackerjack/plugins/managers.py +11 -8
- crackerjack/security/__init__.py +0 -1
- crackerjack/security/audit.py +6 -35
- crackerjack/services/anomaly_detector.py +392 -0
- crackerjack/services/api_extractor.py +615 -0
- crackerjack/services/backup_service.py +2 -2
- crackerjack/services/bounded_status_operations.py +15 -152
- crackerjack/services/cache.py +127 -1
- crackerjack/services/changelog_automation.py +395 -0
- crackerjack/services/config.py +15 -9
- crackerjack/services/config_merge.py +19 -80
- crackerjack/services/config_template.py +506 -0
- crackerjack/services/contextual_ai_assistant.py +48 -22
- crackerjack/services/coverage_badge_service.py +171 -0
- crackerjack/services/coverage_ratchet.py +27 -25
- crackerjack/services/debug.py +3 -3
- crackerjack/services/dependency_analyzer.py +460 -0
- crackerjack/services/dependency_monitor.py +14 -11
- crackerjack/services/documentation_generator.py +491 -0
- crackerjack/services/documentation_service.py +675 -0
- crackerjack/services/enhanced_filesystem.py +6 -5
- crackerjack/services/enterprise_optimizer.py +865 -0
- crackerjack/services/error_pattern_analyzer.py +676 -0
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/git.py +8 -25
- crackerjack/services/health_metrics.py +10 -8
- crackerjack/services/heatmap_generator.py +735 -0
- crackerjack/services/initialization.py +11 -30
- crackerjack/services/input_validator.py +5 -97
- crackerjack/services/intelligent_commit.py +327 -0
- crackerjack/services/log_manager.py +15 -12
- crackerjack/services/logging.py +4 -3
- crackerjack/services/lsp_client.py +628 -0
- crackerjack/services/memory_optimizer.py +19 -87
- crackerjack/services/metrics.py +42 -33
- crackerjack/services/parallel_executor.py +9 -67
- crackerjack/services/pattern_cache.py +1 -1
- crackerjack/services/pattern_detector.py +6 -6
- crackerjack/services/performance_benchmarks.py +18 -59
- crackerjack/services/performance_cache.py +20 -81
- crackerjack/services/performance_monitor.py +27 -95
- crackerjack/services/predictive_analytics.py +510 -0
- crackerjack/services/quality_baseline.py +234 -0
- crackerjack/services/quality_baseline_enhanced.py +646 -0
- crackerjack/services/quality_intelligence.py +785 -0
- crackerjack/services/regex_patterns.py +605 -524
- crackerjack/services/regex_utils.py +43 -123
- crackerjack/services/secure_path_utils.py +5 -164
- crackerjack/services/secure_status_formatter.py +30 -141
- crackerjack/services/secure_subprocess.py +11 -92
- crackerjack/services/security.py +9 -41
- crackerjack/services/security_logger.py +12 -24
- crackerjack/services/server_manager.py +124 -16
- crackerjack/services/status_authentication.py +16 -159
- crackerjack/services/status_security_manager.py +4 -131
- crackerjack/services/thread_safe_status_collector.py +19 -125
- crackerjack/services/unified_config.py +21 -13
- crackerjack/services/validation_rate_limiter.py +5 -54
- crackerjack/services/version_analyzer.py +459 -0
- crackerjack/services/version_checker.py +1 -1
- crackerjack/services/websocket_resource_limiter.py +10 -144
- crackerjack/services/zuban_lsp_service.py +390 -0
- crackerjack/slash_commands/__init__.py +2 -7
- crackerjack/slash_commands/run.md +2 -2
- crackerjack/tools/validate_input_validator_patterns.py +14 -40
- crackerjack/tools/validate_regex_patterns.py +19 -48
- {crackerjack-0.33.0.dist-info ā crackerjack-0.33.1.dist-info}/METADATA +196 -25
- crackerjack-0.33.1.dist-info/RECORD +229 -0
- crackerjack/CLAUDE.md +0 -207
- crackerjack/RULES.md +0 -380
- crackerjack/py313.py +0 -234
- crackerjack-0.33.0.dist-info/RECORD +0 -187
- {crackerjack-0.33.0.dist-info ā crackerjack-0.33.1.dist-info}/WHEEL +0 -0
- {crackerjack-0.33.0.dist-info ā crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.33.0.dist-info ā crackerjack-0.33.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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" + "-" *
|
|
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("-" *
|
|
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
|