crackerjack 0.31.9__py3-none-any.whl → 0.31.12__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/CLAUDE.md +288 -705
- crackerjack/__main__.py +22 -8
- crackerjack/agents/__init__.py +0 -3
- crackerjack/agents/architect_agent.py +0 -43
- crackerjack/agents/base.py +1 -9
- crackerjack/agents/coordinator.py +2 -148
- crackerjack/agents/documentation_agent.py +109 -81
- crackerjack/agents/dry_agent.py +122 -97
- crackerjack/agents/formatting_agent.py +3 -16
- crackerjack/agents/import_optimization_agent.py +1174 -130
- crackerjack/agents/performance_agent.py +956 -188
- crackerjack/agents/performance_helpers.py +229 -0
- crackerjack/agents/proactive_agent.py +1 -48
- crackerjack/agents/refactoring_agent.py +516 -246
- crackerjack/agents/refactoring_helpers.py +282 -0
- crackerjack/agents/security_agent.py +393 -90
- crackerjack/agents/test_creation_agent.py +1776 -120
- crackerjack/agents/test_specialist_agent.py +59 -15
- crackerjack/agents/tracker.py +0 -102
- crackerjack/api.py +145 -37
- crackerjack/cli/handlers.py +48 -30
- crackerjack/cli/interactive.py +11 -11
- crackerjack/cli/options.py +66 -4
- crackerjack/code_cleaner.py +808 -148
- crackerjack/config/global_lock_config.py +110 -0
- crackerjack/config/hooks.py +43 -64
- crackerjack/core/async_workflow_orchestrator.py +247 -97
- crackerjack/core/autofix_coordinator.py +192 -109
- crackerjack/core/enhanced_container.py +46 -63
- crackerjack/core/file_lifecycle.py +549 -0
- crackerjack/core/performance.py +9 -8
- crackerjack/core/performance_monitor.py +395 -0
- crackerjack/core/phase_coordinator.py +282 -95
- crackerjack/core/proactive_workflow.py +9 -58
- crackerjack/core/resource_manager.py +501 -0
- crackerjack/core/service_watchdog.py +490 -0
- crackerjack/core/session_coordinator.py +4 -8
- crackerjack/core/timeout_manager.py +504 -0
- crackerjack/core/websocket_lifecycle.py +475 -0
- crackerjack/core/workflow_orchestrator.py +355 -204
- crackerjack/dynamic_config.py +47 -6
- crackerjack/errors.py +3 -4
- crackerjack/executors/async_hook_executor.py +63 -13
- crackerjack/executors/cached_hook_executor.py +14 -14
- crackerjack/executors/hook_executor.py +100 -37
- crackerjack/executors/hook_lock_manager.py +856 -0
- crackerjack/executors/individual_hook_executor.py +120 -86
- crackerjack/intelligence/__init__.py +0 -7
- crackerjack/intelligence/adaptive_learning.py +13 -86
- crackerjack/intelligence/agent_orchestrator.py +15 -78
- crackerjack/intelligence/agent_registry.py +12 -59
- crackerjack/intelligence/agent_selector.py +31 -92
- crackerjack/intelligence/integration.py +1 -41
- crackerjack/interactive.py +9 -9
- crackerjack/managers/async_hook_manager.py +25 -8
- crackerjack/managers/hook_manager.py +9 -9
- crackerjack/managers/publish_manager.py +57 -59
- crackerjack/managers/test_command_builder.py +6 -36
- crackerjack/managers/test_executor.py +9 -61
- crackerjack/managers/test_manager.py +52 -62
- crackerjack/managers/test_manager_backup.py +77 -127
- crackerjack/managers/test_progress.py +4 -23
- crackerjack/mcp/cache.py +5 -12
- crackerjack/mcp/client_runner.py +10 -10
- crackerjack/mcp/context.py +64 -6
- crackerjack/mcp/dashboard.py +14 -11
- crackerjack/mcp/enhanced_progress_monitor.py +55 -55
- crackerjack/mcp/file_monitor.py +72 -42
- crackerjack/mcp/progress_components.py +103 -84
- crackerjack/mcp/progress_monitor.py +122 -49
- crackerjack/mcp/rate_limiter.py +12 -12
- crackerjack/mcp/server_core.py +16 -22
- crackerjack/mcp/service_watchdog.py +26 -26
- crackerjack/mcp/state.py +15 -0
- crackerjack/mcp/tools/core_tools.py +95 -39
- crackerjack/mcp/tools/error_analyzer.py +6 -32
- crackerjack/mcp/tools/execution_tools.py +1 -56
- crackerjack/mcp/tools/execution_tools_backup.py +35 -131
- crackerjack/mcp/tools/intelligence_tool_registry.py +0 -36
- crackerjack/mcp/tools/intelligence_tools.py +2 -55
- crackerjack/mcp/tools/monitoring_tools.py +308 -145
- crackerjack/mcp/tools/proactive_tools.py +12 -42
- crackerjack/mcp/tools/progress_tools.py +23 -15
- crackerjack/mcp/tools/utility_tools.py +3 -40
- crackerjack/mcp/tools/workflow_executor.py +40 -60
- crackerjack/mcp/websocket/app.py +0 -3
- crackerjack/mcp/websocket/endpoints.py +206 -268
- crackerjack/mcp/websocket/jobs.py +213 -66
- crackerjack/mcp/websocket/server.py +84 -6
- crackerjack/mcp/websocket/websocket_handler.py +137 -29
- crackerjack/models/config_adapter.py +3 -16
- crackerjack/models/protocols.py +162 -3
- crackerjack/models/resource_protocols.py +454 -0
- crackerjack/models/task.py +3 -3
- crackerjack/monitoring/__init__.py +0 -0
- crackerjack/monitoring/ai_agent_watchdog.py +25 -71
- crackerjack/monitoring/regression_prevention.py +28 -87
- crackerjack/orchestration/advanced_orchestrator.py +44 -78
- crackerjack/orchestration/coverage_improvement.py +10 -60
- crackerjack/orchestration/execution_strategies.py +16 -16
- crackerjack/orchestration/test_progress_streamer.py +61 -53
- crackerjack/plugins/base.py +1 -1
- crackerjack/plugins/managers.py +22 -20
- crackerjack/py313.py +65 -21
- crackerjack/services/backup_service.py +467 -0
- crackerjack/services/bounded_status_operations.py +627 -0
- crackerjack/services/cache.py +7 -9
- crackerjack/services/config.py +35 -52
- crackerjack/services/config_integrity.py +5 -16
- crackerjack/services/config_merge.py +542 -0
- crackerjack/services/contextual_ai_assistant.py +17 -19
- crackerjack/services/coverage_ratchet.py +51 -76
- crackerjack/services/debug.py +25 -39
- crackerjack/services/dependency_monitor.py +52 -50
- crackerjack/services/enhanced_filesystem.py +14 -11
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/filesystem.py +1 -12
- crackerjack/services/git.py +78 -44
- crackerjack/services/health_metrics.py +31 -27
- crackerjack/services/initialization.py +281 -433
- crackerjack/services/input_validator.py +760 -0
- crackerjack/services/log_manager.py +16 -16
- crackerjack/services/logging.py +7 -6
- crackerjack/services/metrics.py +43 -43
- crackerjack/services/pattern_cache.py +2 -31
- crackerjack/services/pattern_detector.py +26 -63
- crackerjack/services/performance_benchmarks.py +20 -45
- crackerjack/services/regex_patterns.py +2887 -0
- crackerjack/services/regex_utils.py +537 -0
- crackerjack/services/secure_path_utils.py +683 -0
- crackerjack/services/secure_status_formatter.py +534 -0
- crackerjack/services/secure_subprocess.py +605 -0
- crackerjack/services/security.py +47 -10
- crackerjack/services/security_logger.py +492 -0
- crackerjack/services/server_manager.py +109 -50
- crackerjack/services/smart_scheduling.py +8 -25
- crackerjack/services/status_authentication.py +603 -0
- crackerjack/services/status_security_manager.py +442 -0
- crackerjack/services/thread_safe_status_collector.py +546 -0
- crackerjack/services/tool_version_service.py +1 -23
- crackerjack/services/unified_config.py +36 -58
- crackerjack/services/validation_rate_limiter.py +269 -0
- crackerjack/services/version_checker.py +9 -40
- crackerjack/services/websocket_resource_limiter.py +572 -0
- crackerjack/slash_commands/__init__.py +52 -2
- crackerjack/tools/__init__.py +0 -0
- crackerjack/tools/validate_input_validator_patterns.py +262 -0
- crackerjack/tools/validate_regex_patterns.py +198 -0
- {crackerjack-0.31.9.dist-info → crackerjack-0.31.12.dist-info}/METADATA +197 -12
- crackerjack-0.31.12.dist-info/RECORD +178 -0
- crackerjack/cli/facade.py +0 -104
- crackerjack-0.31.9.dist-info/RECORD +0 -149
- {crackerjack-0.31.9.dist-info → crackerjack-0.31.12.dist-info}/WHEEL +0 -0
- {crackerjack-0.31.9.dist-info → crackerjack-0.31.12.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.31.9.dist-info → crackerjack-0.31.12.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import re
|
|
3
2
|
import subprocess
|
|
4
3
|
import time
|
|
5
4
|
import typing as t
|
|
@@ -9,7 +8,9 @@ from pathlib import Path
|
|
|
9
8
|
from rich.console import Console
|
|
10
9
|
|
|
11
10
|
from crackerjack.config.hooks import HookDefinition, HookStrategy
|
|
11
|
+
from crackerjack.models.protocols import HookLockManagerProtocol
|
|
12
12
|
from crackerjack.models.task import HookResult
|
|
13
|
+
from crackerjack.services.regex_patterns import SAFE_PATTERNS
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
@dataclass
|
|
@@ -73,33 +74,32 @@ class IndividualExecutionResult:
|
|
|
73
74
|
|
|
74
75
|
|
|
75
76
|
class HookOutputParser:
|
|
76
|
-
|
|
77
|
+
# Pattern mapping: tool -> pattern_type -> safe_pattern_name
|
|
78
|
+
HOOK_PATTERNS: dict[str, dict[str, str]] = {
|
|
77
79
|
"ruff-check": {
|
|
78
|
-
"error":
|
|
79
|
-
"summary":
|
|
80
|
+
"error": "ruff_check_error",
|
|
81
|
+
"summary": "ruff_check_summary",
|
|
80
82
|
},
|
|
81
83
|
"pyright": {
|
|
82
|
-
"error":
|
|
83
|
-
"warning":
|
|
84
|
-
"summary":
|
|
84
|
+
"error": "pyright_error",
|
|
85
|
+
"warning": "pyright_warning",
|
|
86
|
+
"summary": "pyright_summary",
|
|
85
87
|
},
|
|
86
88
|
"bandit": {
|
|
87
|
-
"issue":
|
|
88
|
-
"location":
|
|
89
|
-
"confidence":
|
|
90
|
-
"severity":
|
|
89
|
+
"issue": "bandit_issue",
|
|
90
|
+
"location": "bandit_location",
|
|
91
|
+
"confidence": "bandit_confidence",
|
|
92
|
+
"severity": "bandit_severity",
|
|
91
93
|
},
|
|
92
94
|
"mypy": {
|
|
93
|
-
"error":
|
|
94
|
-
"note":
|
|
95
|
+
"error": "mypy_error",
|
|
96
|
+
"note": "mypy_note",
|
|
95
97
|
},
|
|
96
98
|
"vulture": {
|
|
97
|
-
"unused":
|
|
99
|
+
"unused": "vulture_unused",
|
|
98
100
|
},
|
|
99
101
|
"complexipy": {
|
|
100
|
-
"complex":
|
|
101
|
-
r"^(.+?):(\d+):(\d+) - (.+) is too complex \((\d+)\)",
|
|
102
|
-
),
|
|
102
|
+
"complex": "complexipy_complex",
|
|
103
103
|
},
|
|
104
104
|
}
|
|
105
105
|
|
|
@@ -136,14 +136,16 @@ class HookOutputParser:
|
|
|
136
136
|
def _parse_ruff_check(
|
|
137
137
|
self,
|
|
138
138
|
output_lines: list[str],
|
|
139
|
-
patterns: dict[str,
|
|
139
|
+
patterns: dict[str, str],
|
|
140
140
|
result: dict[str, t.Any],
|
|
141
141
|
) -> None:
|
|
142
|
+
error_pattern = SAFE_PATTERNS[patterns["error"]]._get_compiled_pattern()
|
|
143
|
+
|
|
142
144
|
for line in output_lines:
|
|
143
145
|
line = line.strip()
|
|
144
146
|
if not line:
|
|
145
147
|
continue
|
|
146
|
-
if match :=
|
|
148
|
+
if match := error_pattern.match(line):
|
|
147
149
|
file_path, line_num, col_num, code, message = match.groups()
|
|
148
150
|
result["files_processed"].add(file_path)
|
|
149
151
|
result["errors"].append(
|
|
@@ -160,14 +162,17 @@ class HookOutputParser:
|
|
|
160
162
|
def _parse_pyright(
|
|
161
163
|
self,
|
|
162
164
|
output_lines: list[str],
|
|
163
|
-
patterns: dict[str,
|
|
165
|
+
patterns: dict[str, str],
|
|
164
166
|
result: dict[str, t.Any],
|
|
165
167
|
) -> None:
|
|
168
|
+
error_pattern = SAFE_PATTERNS[patterns["error"]]._get_compiled_pattern()
|
|
169
|
+
warning_pattern = SAFE_PATTERNS[patterns["warning"]]._get_compiled_pattern()
|
|
170
|
+
|
|
166
171
|
for line in output_lines:
|
|
167
172
|
line = line.strip()
|
|
168
173
|
if not line:
|
|
169
174
|
continue
|
|
170
|
-
if match :=
|
|
175
|
+
if match := error_pattern.match(line):
|
|
171
176
|
file_path, line_num, col_num, message = match.groups()
|
|
172
177
|
result["files_processed"].add(file_path)
|
|
173
178
|
result["errors"].append(
|
|
@@ -179,7 +184,7 @@ class HookOutputParser:
|
|
|
179
184
|
"type": "error",
|
|
180
185
|
},
|
|
181
186
|
)
|
|
182
|
-
elif match :=
|
|
187
|
+
elif match := warning_pattern.match(line):
|
|
183
188
|
file_path, line_num, col_num, message = match.groups()
|
|
184
189
|
result["files_processed"].add(file_path)
|
|
185
190
|
result["warnings"].append(
|
|
@@ -195,14 +200,16 @@ class HookOutputParser:
|
|
|
195
200
|
def _parse_bandit(
|
|
196
201
|
self,
|
|
197
202
|
output_lines: list[str],
|
|
198
|
-
patterns: dict[str,
|
|
203
|
+
patterns: dict[str, str],
|
|
199
204
|
result: dict[str, t.Any],
|
|
200
205
|
) -> None:
|
|
206
|
+
issue_pattern = SAFE_PATTERNS[patterns["issue"]]._get_compiled_pattern()
|
|
207
|
+
|
|
201
208
|
for line in output_lines:
|
|
202
209
|
line = line.strip()
|
|
203
210
|
if not line:
|
|
204
211
|
continue
|
|
205
|
-
if match :=
|
|
212
|
+
if match := issue_pattern.match(line):
|
|
206
213
|
code, message = match.groups()
|
|
207
214
|
result["errors"].append(
|
|
208
215
|
{"code": code, "message": message, "type": "security"},
|
|
@@ -211,14 +218,16 @@ class HookOutputParser:
|
|
|
211
218
|
def _parse_vulture(
|
|
212
219
|
self,
|
|
213
220
|
output_lines: list[str],
|
|
214
|
-
patterns: dict[str,
|
|
221
|
+
patterns: dict[str, str],
|
|
215
222
|
result: dict[str, t.Any],
|
|
216
223
|
) -> None:
|
|
224
|
+
unused_pattern = SAFE_PATTERNS[patterns["unused"]]._get_compiled_pattern()
|
|
225
|
+
|
|
217
226
|
for line in output_lines:
|
|
218
227
|
line = line.strip()
|
|
219
228
|
if not line:
|
|
220
229
|
continue
|
|
221
|
-
if match :=
|
|
230
|
+
if match := unused_pattern.match(line):
|
|
222
231
|
file_path, line_num, item_type, item_name = match.groups()
|
|
223
232
|
result["files_processed"].add(file_path)
|
|
224
233
|
result["warnings"].append(
|
|
@@ -233,14 +242,16 @@ class HookOutputParser:
|
|
|
233
242
|
def _parse_complexipy(
|
|
234
243
|
self,
|
|
235
244
|
output_lines: list[str],
|
|
236
|
-
patterns: dict[str,
|
|
245
|
+
patterns: dict[str, str],
|
|
237
246
|
result: dict[str, t.Any],
|
|
238
247
|
) -> None:
|
|
248
|
+
complex_pattern = SAFE_PATTERNS[patterns["complex"]]._get_compiled_pattern()
|
|
249
|
+
|
|
239
250
|
for line in output_lines:
|
|
240
251
|
line = line.strip()
|
|
241
252
|
if not line:
|
|
242
253
|
continue
|
|
243
|
-
if match :=
|
|
254
|
+
if match := complex_pattern.match(line):
|
|
244
255
|
file_path, line_num, col_num, function_name, complexity = match.groups()
|
|
245
256
|
result["files_processed"].add(file_path)
|
|
246
257
|
result["errors"].append(
|
|
@@ -256,15 +267,14 @@ class HookOutputParser:
|
|
|
256
267
|
def _parse_default_hook(
|
|
257
268
|
self,
|
|
258
269
|
output_lines: list[str],
|
|
259
|
-
patterns: dict[str,
|
|
270
|
+
patterns: dict[str, str],
|
|
260
271
|
result: dict[str, t.Any],
|
|
261
272
|
) -> None:
|
|
262
|
-
# Default parser for hooks not specifically handled
|
|
263
273
|
for line in output_lines:
|
|
264
274
|
line = line.strip()
|
|
265
275
|
if not line:
|
|
266
276
|
continue
|
|
267
|
-
|
|
277
|
+
|
|
268
278
|
if "error" in line.lower() or "fail" in line.lower():
|
|
269
279
|
result["errors"].append(
|
|
270
280
|
{
|
|
@@ -303,32 +313,42 @@ class HookOutputParser:
|
|
|
303
313
|
|
|
304
314
|
|
|
305
315
|
class IndividualHookExecutor:
|
|
306
|
-
def __init__(
|
|
316
|
+
def __init__(
|
|
317
|
+
self,
|
|
318
|
+
console: Console,
|
|
319
|
+
pkg_path: Path,
|
|
320
|
+
hook_lock_manager: HookLockManagerProtocol | None = None,
|
|
321
|
+
) -> None:
|
|
307
322
|
self.console = console
|
|
308
323
|
self.pkg_path = pkg_path
|
|
309
324
|
self.parser = HookOutputParser()
|
|
310
325
|
self.progress_callback: t.Callable[[HookProgress], None] | None = None
|
|
311
326
|
self.suppress_realtime_output = False
|
|
312
|
-
self.progress_callback_interval =
|
|
313
|
-
|
|
314
|
-
|
|
327
|
+
self.progress_callback_interval = 1
|
|
328
|
+
|
|
329
|
+
# Use dependency injection for hook lock manager
|
|
330
|
+
if hook_lock_manager is None:
|
|
331
|
+
# Import here to avoid circular imports
|
|
332
|
+
from crackerjack.executors.hook_lock_manager import (
|
|
333
|
+
hook_lock_manager as default_manager,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
self.hook_lock_manager = default_manager
|
|
337
|
+
else:
|
|
338
|
+
self.hook_lock_manager = hook_lock_manager
|
|
315
339
|
|
|
316
340
|
def set_progress_callback(self, callback: t.Callable[[HookProgress], None]) -> None:
|
|
317
341
|
self.progress_callback = callback
|
|
318
342
|
|
|
319
343
|
def set_mcp_mode(self, enable: bool = True) -> None:
|
|
320
|
-
"""Enable MCP mode which suppresses real-time output to prevent terminal lockup."""
|
|
321
344
|
self.suppress_realtime_output = enable
|
|
322
345
|
if enable:
|
|
323
|
-
self.progress_callback_interval =
|
|
324
|
-
10 # Reduce callback frequency in MCP mode
|
|
325
|
-
)
|
|
346
|
+
self.progress_callback_interval = 10
|
|
326
347
|
|
|
327
348
|
async def execute_strategy_individual(
|
|
328
349
|
self,
|
|
329
350
|
strategy: HookStrategy,
|
|
330
351
|
) -> IndividualExecutionResult:
|
|
331
|
-
"""Execute all hooks in a strategy individually (non-parallel)."""
|
|
332
352
|
start_time = time.time()
|
|
333
353
|
self._print_strategy_header(strategy)
|
|
334
354
|
|
|
@@ -340,7 +360,6 @@ class IndividualHookExecutor:
|
|
|
340
360
|
return self._finalize_execution_result(strategy, execution_state, start_time)
|
|
341
361
|
|
|
342
362
|
def _initialize_execution_state(self) -> dict[str, t.Any]:
|
|
343
|
-
"""Initialize state tracking for strategy execution."""
|
|
344
363
|
return {"hook_results": [], "hook_progress": [], "execution_order": []}
|
|
345
364
|
|
|
346
365
|
async def _execute_single_hook_in_strategy(
|
|
@@ -348,7 +367,6 @@ class IndividualHookExecutor:
|
|
|
348
367
|
hook: HookDefinition,
|
|
349
368
|
execution_state: dict[str, t.Any],
|
|
350
369
|
) -> None:
|
|
351
|
-
"""Execute a single hook and update execution state."""
|
|
352
370
|
execution_state["execution_order"].append(hook.name)
|
|
353
371
|
|
|
354
372
|
progress = HookProgress(
|
|
@@ -368,7 +386,6 @@ class IndividualHookExecutor:
|
|
|
368
386
|
progress: HookProgress,
|
|
369
387
|
result: HookResult,
|
|
370
388
|
) -> None:
|
|
371
|
-
"""Update progress status after hook execution."""
|
|
372
389
|
progress.status = "completed" if result.status == "passed" else "failed"
|
|
373
390
|
progress.end_time = time.time()
|
|
374
391
|
progress.duration = progress.end_time - progress.start_time
|
|
@@ -382,7 +399,6 @@ class IndividualHookExecutor:
|
|
|
382
399
|
execution_state: dict[str, t.Any],
|
|
383
400
|
start_time: float,
|
|
384
401
|
) -> IndividualExecutionResult:
|
|
385
|
-
"""Finalize and return the execution result."""
|
|
386
402
|
total_duration = time.time() - start_time
|
|
387
403
|
success = all(r.status == "passed" for r in execution_state["hook_results"])
|
|
388
404
|
|
|
@@ -410,38 +426,50 @@ class IndividualHookExecutor:
|
|
|
410
426
|
if self.progress_callback:
|
|
411
427
|
self.progress_callback(progress)
|
|
412
428
|
|
|
413
|
-
|
|
429
|
+
# Check if this hook requires a lock for sequential execution
|
|
430
|
+
if self.hook_lock_manager.requires_lock(hook.name):
|
|
431
|
+
self.console.print(
|
|
432
|
+
f"\n[bold cyan]🔍 Running {hook.name} (with lock)[/ bold cyan]"
|
|
433
|
+
)
|
|
434
|
+
else:
|
|
435
|
+
self.console.print(f"\n[bold cyan]🔍 Running {hook.name}[/ bold cyan]")
|
|
414
436
|
|
|
415
437
|
cmd = hook.get_command()
|
|
416
438
|
|
|
417
439
|
try:
|
|
418
|
-
|
|
440
|
+
# Acquire lock if the hook requires it
|
|
441
|
+
async with self.hook_lock_manager.acquire_hook_lock(hook.name): # type: ignore
|
|
442
|
+
result = await self._run_command_with_streaming(
|
|
443
|
+
cmd, hook.timeout, progress
|
|
444
|
+
)
|
|
419
445
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
446
|
+
parsed_output = self.parser.parse_hook_output(
|
|
447
|
+
hook.name,
|
|
448
|
+
progress.output_lines or [],
|
|
449
|
+
)
|
|
450
|
+
progress.errors_found = len(parsed_output["errors"])
|
|
451
|
+
progress.warnings_found = len(parsed_output["warnings"])
|
|
452
|
+
progress.files_processed = parsed_output["files_processed"]
|
|
453
|
+
progress.lines_processed = parsed_output["total_lines"]
|
|
454
|
+
progress.error_details = (
|
|
455
|
+
parsed_output["errors"] + parsed_output["warnings"]
|
|
456
|
+
)
|
|
429
457
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
458
|
+
hook_result = HookResult(
|
|
459
|
+
id=hook.name,
|
|
460
|
+
name=hook.name,
|
|
461
|
+
status="passed" if result.returncode == 0 else "failed",
|
|
462
|
+
duration=progress.duration or 0,
|
|
463
|
+
)
|
|
436
464
|
|
|
437
|
-
|
|
465
|
+
self._print_hook_summary(hook.name, hook_result, progress)
|
|
438
466
|
|
|
439
|
-
|
|
467
|
+
return hook_result
|
|
440
468
|
|
|
441
469
|
except TimeoutError:
|
|
442
470
|
progress.status = "failed"
|
|
443
471
|
error_msg = f"Hook {hook.name} timed out after {hook.timeout}s"
|
|
444
|
-
self.console.print(f"[red]⏰ {error_msg}[/red]")
|
|
472
|
+
self.console.print(f"[red]⏰ {error_msg}[/ red]")
|
|
445
473
|
|
|
446
474
|
return HookResult(
|
|
447
475
|
id=hook.name,
|
|
@@ -449,6 +477,17 @@ class IndividualHookExecutor:
|
|
|
449
477
|
status="failed",
|
|
450
478
|
duration=hook.timeout,
|
|
451
479
|
)
|
|
480
|
+
except Exception as e:
|
|
481
|
+
progress.status = "failed"
|
|
482
|
+
error_msg = f"Hook {hook.name} failed with error: {e}"
|
|
483
|
+
self.console.print(f"[red]❌ {error_msg}[/ red]")
|
|
484
|
+
|
|
485
|
+
return HookResult(
|
|
486
|
+
id=hook.name,
|
|
487
|
+
name=hook.name,
|
|
488
|
+
status="failed",
|
|
489
|
+
duration=progress.duration or 0,
|
|
490
|
+
)
|
|
452
491
|
|
|
453
492
|
async def _run_command_with_streaming(
|
|
454
493
|
self,
|
|
@@ -456,7 +495,6 @@ class IndividualHookExecutor:
|
|
|
456
495
|
timeout: int,
|
|
457
496
|
progress: HookProgress,
|
|
458
497
|
) -> subprocess.CompletedProcess[str]:
|
|
459
|
-
"""Run command with streaming output and progress tracking."""
|
|
460
498
|
process = await self._create_subprocess(cmd)
|
|
461
499
|
|
|
462
500
|
stdout_lines: list[str] = []
|
|
@@ -478,10 +516,15 @@ class IndividualHookExecutor:
|
|
|
478
516
|
return self._create_completed_process(cmd, process, stdout_lines, stderr_lines)
|
|
479
517
|
|
|
480
518
|
async def _create_subprocess(self, cmd: list[str]) -> asyncio.subprocess.Process:
|
|
481
|
-
|
|
519
|
+
# Pre-commit must run from repository root, not package directory
|
|
520
|
+
repo_root = (
|
|
521
|
+
self.pkg_path.parent
|
|
522
|
+
if self.pkg_path.name == "crackerjack"
|
|
523
|
+
else self.pkg_path
|
|
524
|
+
)
|
|
482
525
|
return await asyncio.create_subprocess_exec(
|
|
483
526
|
*cmd,
|
|
484
|
-
cwd=
|
|
527
|
+
cwd=repo_root,
|
|
485
528
|
stdout=asyncio.subprocess.PIPE,
|
|
486
529
|
stderr=asyncio.subprocess.PIPE,
|
|
487
530
|
)
|
|
@@ -493,7 +536,6 @@ class IndividualHookExecutor:
|
|
|
493
536
|
stderr_lines: list[str],
|
|
494
537
|
progress: HookProgress,
|
|
495
538
|
) -> list[asyncio.Task[None]]:
|
|
496
|
-
"""Create tasks for reading stdout and stderr streams."""
|
|
497
539
|
return [
|
|
498
540
|
asyncio.create_task(
|
|
499
541
|
self._read_stream(process.stdout, stdout_lines, progress),
|
|
@@ -509,7 +551,6 @@ class IndividualHookExecutor:
|
|
|
509
551
|
output_list: list[str],
|
|
510
552
|
progress: HookProgress,
|
|
511
553
|
) -> None:
|
|
512
|
-
"""Read lines from stream and update progress."""
|
|
513
554
|
if not stream:
|
|
514
555
|
return
|
|
515
556
|
|
|
@@ -533,7 +574,6 @@ class IndividualHookExecutor:
|
|
|
533
574
|
break
|
|
534
575
|
|
|
535
576
|
def _process_stream_line(self, line: bytes | str) -> str:
|
|
536
|
-
"""Process a line from stream into clean string."""
|
|
537
577
|
return (line.decode() if isinstance(line, bytes) else line).rstrip()
|
|
538
578
|
|
|
539
579
|
def _update_progress_with_line(
|
|
@@ -543,7 +583,6 @@ class IndividualHookExecutor:
|
|
|
543
583
|
progress: HookProgress,
|
|
544
584
|
line_count: int,
|
|
545
585
|
) -> None:
|
|
546
|
-
"""Update progress tracking with new line."""
|
|
547
586
|
output_list.append(line_str)
|
|
548
587
|
progress.output_lines = progress.output_lines or []
|
|
549
588
|
progress.output_lines.append(line_str)
|
|
@@ -552,12 +591,10 @@ class IndividualHookExecutor:
|
|
|
552
591
|
self._maybe_callback_progress(progress, line_count)
|
|
553
592
|
|
|
554
593
|
def _maybe_print_line(self, line_str: str) -> None:
|
|
555
|
-
"""Print line to console if not suppressed."""
|
|
556
594
|
if not self.suppress_realtime_output and line_str.strip():
|
|
557
|
-
self.console.print(f"[dim] {line_str}[/dim]")
|
|
595
|
+
self.console.print(f"[dim] {line_str}[/ dim]")
|
|
558
596
|
|
|
559
597
|
def _maybe_callback_progress(self, progress: HookProgress, line_count: int) -> None:
|
|
560
|
-
"""Callback progress if conditions are met."""
|
|
561
598
|
if self.progress_callback and (
|
|
562
599
|
line_count % self.progress_callback_interval == 0
|
|
563
600
|
):
|
|
@@ -569,7 +606,6 @@ class IndividualHookExecutor:
|
|
|
569
606
|
tasks: list[asyncio.Task[None]],
|
|
570
607
|
timeout: int,
|
|
571
608
|
) -> None:
|
|
572
|
-
"""Wait for process completion with timeout."""
|
|
573
609
|
await asyncio.wait_for(process.wait(), timeout=timeout)
|
|
574
610
|
await asyncio.gather(*tasks, return_exceptions=True)
|
|
575
611
|
|
|
@@ -578,7 +614,6 @@ class IndividualHookExecutor:
|
|
|
578
614
|
process: asyncio.subprocess.Process,
|
|
579
615
|
tasks: list[asyncio.Task[None]],
|
|
580
616
|
) -> None:
|
|
581
|
-
"""Handle process timeout by killing process and canceling tasks."""
|
|
582
617
|
process.kill()
|
|
583
618
|
for task in tasks:
|
|
584
619
|
task.cancel()
|
|
@@ -590,7 +625,6 @@ class IndividualHookExecutor:
|
|
|
590
625
|
stdout_lines: list[str],
|
|
591
626
|
stderr_lines: list[str],
|
|
592
627
|
) -> subprocess.CompletedProcess[str]:
|
|
593
|
-
"""Create CompletedProcess result."""
|
|
594
628
|
return subprocess.CompletedProcess(
|
|
595
629
|
args=cmd,
|
|
596
630
|
returncode=process.returncode or 0,
|
|
@@ -601,11 +635,11 @@ class IndividualHookExecutor:
|
|
|
601
635
|
def _print_strategy_header(self, strategy: HookStrategy) -> None:
|
|
602
636
|
self.console.print("\n" + "=" * 80)
|
|
603
637
|
self.console.print(
|
|
604
|
-
f"[bold bright_cyan]🔍 INDIVIDUAL HOOK EXECUTION[/bold bright_cyan] "
|
|
605
|
-
f"[bold bright_white]{strategy.name.upper()} HOOKS[/bold bright_white]",
|
|
638
|
+
f"[bold bright_cyan]🔍 INDIVIDUAL HOOK EXECUTION[/ bold bright_cyan] "
|
|
639
|
+
f"[bold bright_white]{strategy.name.upper()} HOOKS[/ bold bright_white]",
|
|
606
640
|
)
|
|
607
641
|
self.console.print(
|
|
608
|
-
f"[dim]Running {len(strategy.hooks)} hooks individually with real-time streaming[/dim]",
|
|
642
|
+
f"[dim]Running {len(strategy.hooks)} hooks individually with real-time streaming[/ dim]",
|
|
609
643
|
)
|
|
610
644
|
self.console.print("=" * 80)
|
|
611
645
|
|
|
@@ -616,7 +650,7 @@ class IndividualHookExecutor:
|
|
|
616
650
|
progress: HookProgress,
|
|
617
651
|
) -> None:
|
|
618
652
|
status_icon = "✅" if result.status == "passed" else "❌"
|
|
619
|
-
duration_str = f"{progress.duration
|
|
653
|
+
duration_str = f"{progress.duration: .1f}s" if progress.duration else "0.0s"
|
|
620
654
|
|
|
621
655
|
summary_parts: list[str] = []
|
|
622
656
|
if progress.errors_found > 0:
|
|
@@ -629,7 +663,7 @@ class IndividualHookExecutor:
|
|
|
629
663
|
summary = ", ".join(summary_parts) if summary_parts else "clean"
|
|
630
664
|
|
|
631
665
|
self.console.print(
|
|
632
|
-
f"[bold]{status_icon} {hook_name}[/bold] - {duration_str}
|
|
666
|
+
f"[bold]{status_icon} {hook_name}[/ bold] - {duration_str}-{summary}",
|
|
633
667
|
)
|
|
634
668
|
|
|
635
669
|
def _print_individual_summary(
|
|
@@ -646,17 +680,17 @@ class IndividualHookExecutor:
|
|
|
646
680
|
|
|
647
681
|
self.console.print("\n" + "-" * 80)
|
|
648
682
|
self.console.print(
|
|
649
|
-
f"[bold]📊 INDIVIDUAL EXECUTION SUMMARY[/bold]
|
|
683
|
+
f"[bold]📊 INDIVIDUAL EXECUTION SUMMARY[/ bold]-{strategy.name.upper()}",
|
|
650
684
|
)
|
|
651
685
|
self.console.print(f"✅ Passed: {passed} | ❌ Failed: {failed}")
|
|
652
686
|
if total_errors > 0:
|
|
653
687
|
self.console.print(f"🚨 Total Errors: {total_errors}")
|
|
654
688
|
if total_warnings > 0:
|
|
655
689
|
self.console.print(f"⚠️ Total Warnings: {total_warnings}")
|
|
656
|
-
self.console.print(f"⏱️ Total Duration: {total_duration
|
|
690
|
+
self.console.print(f"⏱️ Total Duration: {total_duration: .1f}s")
|
|
657
691
|
|
|
658
692
|
if failed > 0:
|
|
659
|
-
self.console.print("\n[bold red]Failed Hooks: [/bold red]")
|
|
693
|
+
self.console.print("\n[bold red]Failed Hooks: [/ bold red]")
|
|
660
694
|
for progress in progress_list:
|
|
661
695
|
if progress.status == "failed":
|
|
662
696
|
error_summary = (
|
|
@@ -664,6 +698,6 @@ class IndividualHookExecutor:
|
|
|
664
698
|
if progress.errors_found > 0
|
|
665
699
|
else "failed"
|
|
666
700
|
)
|
|
667
|
-
self.console.print(f" ❌ {progress.hook_name}
|
|
701
|
+
self.console.print(f" ❌ {progress.hook_name}-{error_summary}")
|
|
668
702
|
|
|
669
703
|
self.console.print("-" * 80)
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
"""Intelligent Agent Selection System.
|
|
2
|
-
|
|
3
|
-
This module provides intelligent selection and orchestration of agents from
|
|
4
|
-
multiple sources (crackerjack, user, system) with smart prioritization and
|
|
5
|
-
adaptive learning capabilities.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
1
|
from .adaptive_learning import AdaptiveLearningSystem, get_learning_system
|
|
9
2
|
from .agent_orchestrator import (
|
|
10
3
|
AgentOrchestrator,
|