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.
- 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 +64 -6
- 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 +257 -218
- 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 +558 -240
- 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 +66 -13
- crackerjack/managers/test_command_builder.py +5 -17
- crackerjack/managers/test_executor.py +1 -3
- crackerjack/managers/test_manager.py +109 -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 +161 -32
- 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 +174 -33
- crackerjack/mcp/tools/error_analyzer.py +3 -2
- crackerjack/mcp/tools/execution_tools.py +15 -12
- 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 +3 -0
- crackerjack/mixins/error_handling.py +145 -0
- crackerjack/models/config.py +21 -1
- crackerjack/models/config_adapter.py +49 -1
- crackerjack/models/protocols.py +176 -107
- crackerjack/models/resource_protocols.py +55 -210
- crackerjack/models/task.py +3 -0
- 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 +90 -105
- 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 +18 -11
- crackerjack/services/config_merge.py +30 -85
- 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 +41 -17
- 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 +41 -45
- crackerjack/services/health_metrics.py +10 -8
- crackerjack/services/heatmap_generator.py +735 -0
- crackerjack/services/initialization.py +30 -33
- 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 +409 -0
- crackerjack/services/metrics.py +42 -33
- crackerjack/services/parallel_executor.py +416 -0
- crackerjack/services/pattern_cache.py +1 -1
- crackerjack/services/pattern_detector.py +6 -6
- crackerjack/services/performance_benchmarks.py +250 -576
- crackerjack/services/performance_cache.py +382 -0
- crackerjack/services/performance_monitor.py +565 -0
- 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 +61 -30
- crackerjack/services/security_logger.py +18 -22
- crackerjack/services/server_manager.py +124 -16
- crackerjack/services/status_authentication.py +16 -159
- crackerjack/services/status_security_manager.py +4 -131
- crackerjack/services/terminal_utils.py +0 -0
- 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.32.0.dist-info → crackerjack-0.33.1.dist-info}/METADATA +197 -26
- 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.32.0.dist-info/RECORD +0 -180
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/WHEEL +0 -0
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import time
|
|
3
|
+
import typing as t
|
|
4
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from crackerjack.config.hooks import HookDefinition, SecurityLevel
|
|
10
|
+
from crackerjack.services.logging import get_logger
|
|
11
|
+
from crackerjack.services.performance_cache import get_performance_cache
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ExecutionStrategy(Enum):
|
|
15
|
+
SEQUENTIAL = "sequential"
|
|
16
|
+
PARALLEL_SAFE = "parallel_safe"
|
|
17
|
+
PARALLEL_AGGRESSIVE = "parallel_aggressive"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ExecutionGroup:
|
|
22
|
+
name: str
|
|
23
|
+
operations: list[t.Any]
|
|
24
|
+
max_workers: int = 3
|
|
25
|
+
timeout_seconds: int = 300
|
|
26
|
+
dependencies: set[str] = field(default_factory=set)
|
|
27
|
+
security_level: SecurityLevel = SecurityLevel.MEDIUM
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class ExecutionResult:
|
|
32
|
+
operation_id: str
|
|
33
|
+
success: bool
|
|
34
|
+
duration_seconds: float
|
|
35
|
+
output: str = ""
|
|
36
|
+
error: str = ""
|
|
37
|
+
exit_code: int = 0
|
|
38
|
+
metadata: dict[str, t.Any] = field(default_factory=dict[str, t.Any])
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class ParallelExecutionResult:
|
|
43
|
+
group_name: str
|
|
44
|
+
total_operations: int
|
|
45
|
+
successful_operations: int
|
|
46
|
+
failed_operations: int
|
|
47
|
+
total_duration_seconds: float
|
|
48
|
+
results: list[ExecutionResult]
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def success_rate(self) -> float:
|
|
52
|
+
return (
|
|
53
|
+
self.successful_operations / self.total_operations
|
|
54
|
+
if self.total_operations > 0
|
|
55
|
+
else 0.0
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def overall_success(self) -> bool:
|
|
60
|
+
return self.failed_operations == 0
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ParallelHookExecutor:
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
max_workers: int = 3,
|
|
67
|
+
timeout_seconds: int = 300,
|
|
68
|
+
strategy: ExecutionStrategy = ExecutionStrategy.PARALLEL_SAFE,
|
|
69
|
+
):
|
|
70
|
+
self.max_workers = max_workers
|
|
71
|
+
self.timeout_seconds = timeout_seconds
|
|
72
|
+
self.strategy = strategy
|
|
73
|
+
self._logger = get_logger("crackerjack.parallel_executor")
|
|
74
|
+
self._cache = get_performance_cache()
|
|
75
|
+
|
|
76
|
+
def analyze_hook_dependencies(
|
|
77
|
+
self,
|
|
78
|
+
hooks: list[HookDefinition],
|
|
79
|
+
) -> dict[str, list[HookDefinition]]:
|
|
80
|
+
groups: dict[str, list[HookDefinition]] = {
|
|
81
|
+
"formatting": [],
|
|
82
|
+
"validation": [],
|
|
83
|
+
"security": [],
|
|
84
|
+
"comprehensive": [],
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for hook in hooks:
|
|
88
|
+
if hook.is_formatting or hook.security_level == SecurityLevel.LOW:
|
|
89
|
+
groups["formatting"].append(hook)
|
|
90
|
+
elif hook.security_level == SecurityLevel.CRITICAL:
|
|
91
|
+
groups["security"].append(hook)
|
|
92
|
+
elif hook.name in {"check-yaml", "check-json", "check-toml"}:
|
|
93
|
+
groups["validation"].append(hook)
|
|
94
|
+
else:
|
|
95
|
+
groups["comprehensive"].append(hook)
|
|
96
|
+
|
|
97
|
+
return {k: v for k, v in groups.items() if v}
|
|
98
|
+
|
|
99
|
+
def can_execute_in_parallel(
|
|
100
|
+
self,
|
|
101
|
+
hook1: HookDefinition,
|
|
102
|
+
hook2: HookDefinition,
|
|
103
|
+
) -> bool:
|
|
104
|
+
if hook1.security_level != hook2.security_level:
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
if hook1.is_formatting and not hook2.is_formatting:
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
safe_parallel_combinations = [
|
|
111
|
+
(lambda h: h.is_formatting, lambda h: h.is_formatting),
|
|
112
|
+
(
|
|
113
|
+
lambda h: h.name in {"check-yaml", "check-json", "check-toml"},
|
|
114
|
+
lambda h: h.name in {"check-yaml", "check-json", "check-toml"},
|
|
115
|
+
),
|
|
116
|
+
(
|
|
117
|
+
lambda h: not h.is_formatting
|
|
118
|
+
and h.security_level == SecurityLevel.MEDIUM,
|
|
119
|
+
lambda h: not h.is_formatting
|
|
120
|
+
and h.security_level == SecurityLevel.MEDIUM,
|
|
121
|
+
),
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
for check1, check2 in safe_parallel_combinations:
|
|
125
|
+
if check1(hook1) and check2(hook2):
|
|
126
|
+
return True
|
|
127
|
+
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
async def execute_hooks_parallel(
|
|
131
|
+
self,
|
|
132
|
+
hooks: list[HookDefinition],
|
|
133
|
+
hook_runner: t.Callable[[HookDefinition], t.Awaitable[ExecutionResult]],
|
|
134
|
+
) -> ParallelExecutionResult:
|
|
135
|
+
start_time = time.time()
|
|
136
|
+
|
|
137
|
+
if self.strategy == ExecutionStrategy.SEQUENTIAL:
|
|
138
|
+
return await self._execute_sequential(hooks, hook_runner, start_time)
|
|
139
|
+
|
|
140
|
+
groups = self.analyze_hook_dependencies(hooks)
|
|
141
|
+
all_results: list[ExecutionResult] = []
|
|
142
|
+
|
|
143
|
+
self._logger.info(
|
|
144
|
+
f"Executing {len(hooks)} hooks in {len(groups)} parallel groups"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
for group_name, group_hooks in groups.items():
|
|
148
|
+
if len(group_hooks) == 1 or not self._can_parallelize_group(group_hooks):
|
|
149
|
+
for hook in group_hooks:
|
|
150
|
+
result = await hook_runner(hook)
|
|
151
|
+
all_results.append(result)
|
|
152
|
+
else:
|
|
153
|
+
group_results = await self._execute_group_parallel(
|
|
154
|
+
group_hooks,
|
|
155
|
+
hook_runner,
|
|
156
|
+
group_name,
|
|
157
|
+
)
|
|
158
|
+
all_results.extend(group_results)
|
|
159
|
+
|
|
160
|
+
total_duration = time.time() - start_time
|
|
161
|
+
successful = sum(1 for r in all_results if r.success)
|
|
162
|
+
failed = len(all_results) - successful
|
|
163
|
+
|
|
164
|
+
return ParallelExecutionResult(
|
|
165
|
+
group_name="all_hooks",
|
|
166
|
+
total_operations=len(hooks),
|
|
167
|
+
successful_operations=successful,
|
|
168
|
+
failed_operations=failed,
|
|
169
|
+
total_duration_seconds=total_duration,
|
|
170
|
+
results=all_results,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
async def _execute_sequential(
|
|
174
|
+
self,
|
|
175
|
+
hooks: list[HookDefinition],
|
|
176
|
+
hook_runner: t.Callable[[HookDefinition], t.Awaitable[ExecutionResult]],
|
|
177
|
+
start_time: float,
|
|
178
|
+
) -> ParallelExecutionResult:
|
|
179
|
+
results: list[ExecutionResult] = []
|
|
180
|
+
|
|
181
|
+
for hook in hooks:
|
|
182
|
+
result = await hook_runner(hook)
|
|
183
|
+
results.append(result)
|
|
184
|
+
|
|
185
|
+
total_duration = time.time() - start_time
|
|
186
|
+
successful = sum(1 for r in results if r.success)
|
|
187
|
+
failed = len(results) - successful
|
|
188
|
+
|
|
189
|
+
return ParallelExecutionResult(
|
|
190
|
+
group_name="sequential",
|
|
191
|
+
total_operations=len(hooks),
|
|
192
|
+
successful_operations=successful,
|
|
193
|
+
failed_operations=failed,
|
|
194
|
+
total_duration_seconds=total_duration,
|
|
195
|
+
results=results,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def _can_parallelize_group(self, hooks: list[HookDefinition]) -> bool:
|
|
199
|
+
if len(hooks) < 2:
|
|
200
|
+
return False
|
|
201
|
+
|
|
202
|
+
for i, hook1 in enumerate(hooks):
|
|
203
|
+
for hook2 in hooks[i + 1 :]:
|
|
204
|
+
if not self.can_execute_in_parallel(hook1, hook2):
|
|
205
|
+
return False
|
|
206
|
+
|
|
207
|
+
return True
|
|
208
|
+
|
|
209
|
+
async def _execute_group_parallel(
|
|
210
|
+
self,
|
|
211
|
+
hooks: list[HookDefinition],
|
|
212
|
+
hook_runner: t.Callable[[HookDefinition], t.Awaitable[ExecutionResult]],
|
|
213
|
+
group_name: str,
|
|
214
|
+
) -> list[ExecutionResult]:
|
|
215
|
+
self._logger.debug(f"Executing {len(hooks)} {group_name} hooks in parallel")
|
|
216
|
+
|
|
217
|
+
max_workers = min(self.max_workers, len(hooks))
|
|
218
|
+
|
|
219
|
+
semaphore = asyncio.Semaphore(max_workers)
|
|
220
|
+
|
|
221
|
+
async def run_with_semaphore(hook: HookDefinition) -> ExecutionResult:
|
|
222
|
+
async with semaphore:
|
|
223
|
+
return await hook_runner(hook)
|
|
224
|
+
|
|
225
|
+
tasks = [run_with_semaphore(hook) for hook in hooks]
|
|
226
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
227
|
+
|
|
228
|
+
processed_results: list[ExecutionResult] = []
|
|
229
|
+
for i, result in enumerate(results):
|
|
230
|
+
if isinstance(result, Exception):
|
|
231
|
+
error_result = ExecutionResult(
|
|
232
|
+
operation_id=hooks[i].name,
|
|
233
|
+
success=False,
|
|
234
|
+
duration_seconds=0.0,
|
|
235
|
+
error=str(result),
|
|
236
|
+
)
|
|
237
|
+
processed_results.append(error_result)
|
|
238
|
+
self._logger.error(
|
|
239
|
+
f"Hook {hooks[i].name} failed with exception: {result}"
|
|
240
|
+
)
|
|
241
|
+
else:
|
|
242
|
+
processed_results.append(result)
|
|
243
|
+
|
|
244
|
+
successful = sum(1 for r in processed_results if r.success)
|
|
245
|
+
self._logger.info(
|
|
246
|
+
f"Parallel {group_name} execution: {successful}/{len(hooks)} succeeded"
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
return processed_results
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class AsyncCommandExecutor:
|
|
253
|
+
def __init__(
|
|
254
|
+
self,
|
|
255
|
+
max_workers: int = 4,
|
|
256
|
+
cache_results: bool = True,
|
|
257
|
+
):
|
|
258
|
+
self.max_workers = max_workers
|
|
259
|
+
self.cache_results = cache_results
|
|
260
|
+
self._logger = get_logger("crackerjack.async_executor")
|
|
261
|
+
self._cache = get_performance_cache()
|
|
262
|
+
self._thread_pool = ThreadPoolExecutor(max_workers=max_workers)
|
|
263
|
+
|
|
264
|
+
async def execute_command(
|
|
265
|
+
self,
|
|
266
|
+
command: list[str],
|
|
267
|
+
cwd: Path | None = None,
|
|
268
|
+
timeout: int = 60,
|
|
269
|
+
cache_ttl: int = 120,
|
|
270
|
+
) -> ExecutionResult:
|
|
271
|
+
if self.cache_results:
|
|
272
|
+
cached_result = await self._get_cached_result(command, cwd)
|
|
273
|
+
if cached_result:
|
|
274
|
+
self._logger.debug(
|
|
275
|
+
f"Using cached result for command: {' '.join(command)}"
|
|
276
|
+
)
|
|
277
|
+
return cached_result
|
|
278
|
+
|
|
279
|
+
start_time = time.time()
|
|
280
|
+
result = await self._run_command_async(command, cwd, timeout)
|
|
281
|
+
result.duration_seconds = time.time() - start_time
|
|
282
|
+
|
|
283
|
+
if self.cache_results and result.success:
|
|
284
|
+
await self._cache_result(command, result, cache_ttl, cwd)
|
|
285
|
+
|
|
286
|
+
return result
|
|
287
|
+
|
|
288
|
+
async def execute_commands_batch(
|
|
289
|
+
self,
|
|
290
|
+
commands: list[tuple[list[str], Path | None]],
|
|
291
|
+
timeout: int = 60,
|
|
292
|
+
) -> list[ExecutionResult]:
|
|
293
|
+
self._logger.info(f"Executing {len(commands)} commands in batch")
|
|
294
|
+
|
|
295
|
+
tasks = [self.execute_command(cmd, cwd, timeout) for cmd, cwd in commands]
|
|
296
|
+
|
|
297
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
298
|
+
|
|
299
|
+
processed_results: list[ExecutionResult] = []
|
|
300
|
+
for i, result in enumerate(results):
|
|
301
|
+
if isinstance(result, Exception):
|
|
302
|
+
error_result = ExecutionResult(
|
|
303
|
+
operation_id=f"command_{i}",
|
|
304
|
+
success=False,
|
|
305
|
+
duration_seconds=0.0,
|
|
306
|
+
error=str(result),
|
|
307
|
+
)
|
|
308
|
+
processed_results.append(error_result)
|
|
309
|
+
else:
|
|
310
|
+
processed_results.append(result)
|
|
311
|
+
|
|
312
|
+
successful = sum(1 for r in processed_results if r.success)
|
|
313
|
+
self._logger.info(
|
|
314
|
+
f"Batch execution: {successful}/{len(commands)} commands succeeded"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
return processed_results
|
|
318
|
+
|
|
319
|
+
async def _run_command_async(
|
|
320
|
+
self,
|
|
321
|
+
command: list[str],
|
|
322
|
+
cwd: Path | None = None,
|
|
323
|
+
timeout: int = 60,
|
|
324
|
+
) -> ExecutionResult:
|
|
325
|
+
loop = asyncio.get_event_loop()
|
|
326
|
+
|
|
327
|
+
def run_sync_command() -> ExecutionResult:
|
|
328
|
+
import subprocess
|
|
329
|
+
|
|
330
|
+
try:
|
|
331
|
+
result = subprocess.run(
|
|
332
|
+
command,
|
|
333
|
+
cwd=cwd,
|
|
334
|
+
capture_output=True,
|
|
335
|
+
text=True,
|
|
336
|
+
timeout=timeout,
|
|
337
|
+
check=False,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
return ExecutionResult(
|
|
341
|
+
operation_id=" ".join(command),
|
|
342
|
+
success=result.returncode == 0,
|
|
343
|
+
duration_seconds=0.0,
|
|
344
|
+
output=result.stdout,
|
|
345
|
+
error=result.stderr,
|
|
346
|
+
exit_code=result.returncode,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
except subprocess.TimeoutExpired:
|
|
350
|
+
return ExecutionResult(
|
|
351
|
+
operation_id=" ".join(command),
|
|
352
|
+
success=False,
|
|
353
|
+
duration_seconds=timeout,
|
|
354
|
+
error=f"Command timeout after {timeout}s",
|
|
355
|
+
exit_code=-1,
|
|
356
|
+
)
|
|
357
|
+
except Exception as e:
|
|
358
|
+
return ExecutionResult(
|
|
359
|
+
operation_id=" ".join(command),
|
|
360
|
+
success=False,
|
|
361
|
+
duration_seconds=0.0,
|
|
362
|
+
error=str(e),
|
|
363
|
+
exit_code=-1,
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
return await loop.run_in_executor(self._thread_pool, run_sync_command)
|
|
367
|
+
|
|
368
|
+
async def _get_cached_result(
|
|
369
|
+
self,
|
|
370
|
+
command: list[str],
|
|
371
|
+
cwd: Path | None = None,
|
|
372
|
+
) -> ExecutionResult | None:
|
|
373
|
+
from crackerjack.services.performance_cache import get_command_cache
|
|
374
|
+
|
|
375
|
+
cache_result = get_command_cache().get_command_result(command, cwd)
|
|
376
|
+
return t.cast(ExecutionResult | None, cache_result)
|
|
377
|
+
|
|
378
|
+
async def _cache_result(
|
|
379
|
+
self,
|
|
380
|
+
command: list[str],
|
|
381
|
+
result: ExecutionResult,
|
|
382
|
+
ttl_seconds: int,
|
|
383
|
+
cwd: Path | None = None,
|
|
384
|
+
) -> None:
|
|
385
|
+
from crackerjack.services.performance_cache import get_command_cache
|
|
386
|
+
|
|
387
|
+
command_cache = get_command_cache()
|
|
388
|
+
command_cache.set_command_result(command, result, cwd, ttl_seconds)
|
|
389
|
+
|
|
390
|
+
def __del__(self) -> None:
|
|
391
|
+
if hasattr(self, "_thread_pool"):
|
|
392
|
+
self._thread_pool.shutdown(wait=False)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
_parallel_executor: ParallelHookExecutor | None = None
|
|
396
|
+
_async_executor: AsyncCommandExecutor | None = None
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def get_parallel_executor(
|
|
400
|
+
max_workers: int = 3,
|
|
401
|
+
strategy: ExecutionStrategy = ExecutionStrategy.PARALLEL_SAFE,
|
|
402
|
+
) -> ParallelHookExecutor:
|
|
403
|
+
global _parallel_executor
|
|
404
|
+
if _parallel_executor is None:
|
|
405
|
+
_parallel_executor = ParallelHookExecutor(
|
|
406
|
+
max_workers=max_workers,
|
|
407
|
+
strategy=strategy,
|
|
408
|
+
)
|
|
409
|
+
return _parallel_executor
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def get_async_executor(max_workers: int = 4) -> AsyncCommandExecutor:
|
|
413
|
+
global _async_executor
|
|
414
|
+
if _async_executor is None:
|
|
415
|
+
_async_executor = AsyncCommandExecutor(max_workers=max_workers)
|
|
416
|
+
return _async_executor
|
|
@@ -66,7 +66,7 @@ class PatternDetector:
|
|
|
66
66
|
self.logger.info("Starting proactive anti-pattern analysis")
|
|
67
67
|
|
|
68
68
|
anti_patterns = []
|
|
69
|
-
python_files = list(self.project_path.glob("**/*.py"))
|
|
69
|
+
python_files = list[t.Any](self.project_path.glob("**/*.py"))
|
|
70
70
|
|
|
71
71
|
for file_path in python_files:
|
|
72
72
|
if self._should_skip_file(file_path):
|
|
@@ -155,7 +155,7 @@ class PatternDetector:
|
|
|
155
155
|
anti_patterns = []
|
|
156
156
|
|
|
157
157
|
lines = content.split("\n")
|
|
158
|
-
line_groups = {}
|
|
158
|
+
line_groups: dict[str, list[int]] = {}
|
|
159
159
|
|
|
160
160
|
for i, line in enumerate(lines, 1):
|
|
161
161
|
stripped = line.strip()
|
|
@@ -197,7 +197,7 @@ class PatternDetector:
|
|
|
197
197
|
(
|
|
198
198
|
node.lineno,
|
|
199
199
|
"Nested loop detected-potential O(n²) complexity",
|
|
200
|
-
"Consider using dictionary lookups or set operations",
|
|
200
|
+
"Consider using dictionary lookups or set[t.Any] operations",
|
|
201
201
|
)
|
|
202
202
|
)
|
|
203
203
|
break
|
|
@@ -212,7 +212,7 @@ class PatternDetector:
|
|
|
212
212
|
(
|
|
213
213
|
stmt.lineno,
|
|
214
214
|
"List concatenation in loop-inefficient",
|
|
215
|
-
"Use list.append() and join at the end",
|
|
215
|
+
"Use list[t.Any].append() and join at the end",
|
|
216
216
|
)
|
|
217
217
|
)
|
|
218
218
|
|
|
@@ -254,10 +254,10 @@ class PatternDetector:
|
|
|
254
254
|
) -> list[AntiPattern]:
|
|
255
255
|
anti_patterns = []
|
|
256
256
|
|
|
257
|
-
if "/tmp/" in content or "C
|
|
257
|
+
if "/tmp/" in content or "C: \\" in content: # nosec B108
|
|
258
258
|
lines = content.split("\n")
|
|
259
259
|
for i, line in enumerate(lines, 1):
|
|
260
|
-
if "/tmp/" in line or "C
|
|
260
|
+
if "/tmp/" in line or "C: \\" in line: # nosec B108
|
|
261
261
|
anti_patterns.append(
|
|
262
262
|
AntiPattern(
|
|
263
263
|
pattern_type="security_risks",
|