crackerjack 0.31.10__py3-none-any.whl → 0.31.13__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 +281 -94
- 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 +343 -209
- crackerjack/dynamic_config.py +50 -9
- 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 +17 -63
- 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 +44 -73
- 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 +71 -47
- crackerjack/services/health_metrics.py +31 -27
- crackerjack/services/initialization.py +276 -428
- 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.10.dist-info → crackerjack-0.31.13.dist-info}/METADATA +197 -12
- crackerjack-0.31.13.dist-info/RECORD +178 -0
- crackerjack/cli/facade.py +0 -104
- crackerjack-0.31.10.dist-info/RECORD +0 -149
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/WHEEL +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -80,7 +80,7 @@ class ServiceWatchdog:
|
|
|
80
80
|
|
|
81
81
|
for service in self.services:
|
|
82
82
|
if service.process:
|
|
83
|
-
console.print(f"[yellow]🛑 Stopping {service.name}...[/yellow]")
|
|
83
|
+
console.print(f"[yellow]🛑 Stopping {service.name}...[/ yellow]")
|
|
84
84
|
service.process.terminate()
|
|
85
85
|
try:
|
|
86
86
|
service.process.wait(timeout=10)
|
|
@@ -112,7 +112,7 @@ class ServiceWatchdog:
|
|
|
112
112
|
return await self._handle_service_start_error(service, e)
|
|
113
113
|
|
|
114
114
|
async def _check_websocket_server_running(self, service: ServiceConfig) -> bool:
|
|
115
|
-
if "websocket
|
|
115
|
+
if "websocket-server" in " ".join(service.command):
|
|
116
116
|
if self._is_port_in_use(8675):
|
|
117
117
|
await self._emit_event(
|
|
118
118
|
"port_in_use",
|
|
@@ -149,7 +149,7 @@ class ServiceWatchdog:
|
|
|
149
149
|
stdout, stderr = service.process.communicate()
|
|
150
150
|
error_msg = f"Process died (exit: {exit_code})"
|
|
151
151
|
if stderr and stderr.strip():
|
|
152
|
-
error_msg += f"
|
|
152
|
+
error_msg += f"-{stderr.strip()[:50]}"
|
|
153
153
|
service.last_error = error_msg
|
|
154
154
|
await self._emit_event("process_died", service.name, error_msg)
|
|
155
155
|
return False
|
|
@@ -179,7 +179,7 @@ class ServiceWatchdog:
|
|
|
179
179
|
return False
|
|
180
180
|
|
|
181
181
|
async def _handle_websocket_server_monitoring(self, service: ServiceConfig) -> bool:
|
|
182
|
-
if "websocket
|
|
182
|
+
if "websocket-server" not in " ".join(service.command):
|
|
183
183
|
return False
|
|
184
184
|
|
|
185
185
|
if self._is_port_in_use(8675):
|
|
@@ -276,7 +276,7 @@ class ServiceWatchdog:
|
|
|
276
276
|
error: Exception,
|
|
277
277
|
) -> None:
|
|
278
278
|
service.last_error = str(error)
|
|
279
|
-
console.print(f"[red]❌ Error monitoring {service.name}: {error}[/red]")
|
|
279
|
+
console.print(f"[red]❌ Error monitoring {service.name}: {error}[/ red]")
|
|
280
280
|
await asyncio.sleep(10.0)
|
|
281
281
|
|
|
282
282
|
async def _health_check(self, service: ServiceConfig) -> bool:
|
|
@@ -294,7 +294,7 @@ class ServiceWatchdog:
|
|
|
294
294
|
return False
|
|
295
295
|
|
|
296
296
|
with suppress(Exception):
|
|
297
|
-
async with self.session.get("http:
|
|
297
|
+
async with self.session.get("http: / / localhost: 8675 / ") as response:
|
|
298
298
|
if response.status == 200:
|
|
299
299
|
data = await response.json()
|
|
300
300
|
|
|
@@ -309,7 +309,7 @@ class ServiceWatchdog:
|
|
|
309
309
|
current_time = time.time()
|
|
310
310
|
reason = self._determine_restart_reason(service)
|
|
311
311
|
|
|
312
|
-
await self._emit_event("restarting", service.name, f"Restarting
|
|
312
|
+
await self._emit_event("restarting", service.name, f"Restarting-{reason}")
|
|
313
313
|
|
|
314
314
|
if not await self._check_restart_rate_limit(service, current_time):
|
|
315
315
|
return
|
|
@@ -338,7 +338,7 @@ class ServiceWatchdog:
|
|
|
338
338
|
|
|
339
339
|
if len(service.restart_timestamps) >= service.max_restarts:
|
|
340
340
|
console.print(
|
|
341
|
-
f"[red]🚨 {service.name} exceeded restart limit ({service.max_restarts} in {service.restart_window}s)[/red]",
|
|
341
|
+
f"[red]🚨 {service.name} exceeded restart limit ({service.max_restarts} in {service.restart_window}s)[/ red]",
|
|
342
342
|
)
|
|
343
343
|
service.last_error = "Restart rate limit exceeded"
|
|
344
344
|
await asyncio.sleep(60)
|
|
@@ -351,19 +351,19 @@ class ServiceWatchdog:
|
|
|
351
351
|
|
|
352
352
|
try:
|
|
353
353
|
console.print(
|
|
354
|
-
f"[yellow]🔪 Terminating existing {service.name} process (PID: {service.process.pid})[/yellow]",
|
|
354
|
+
f"[yellow]🔪 Terminating existing {service.name} process (PID: {service.process.pid})[/ yellow]",
|
|
355
355
|
)
|
|
356
356
|
service.process.terminate()
|
|
357
357
|
service.process.wait(timeout=10)
|
|
358
358
|
except subprocess.TimeoutExpired:
|
|
359
|
-
console.print(f"[red]💀 Force killing {service.name} process[/red]")
|
|
359
|
+
console.print(f"[red]💀 Force killing {service.name} process[/ red]")
|
|
360
360
|
service.process.kill()
|
|
361
361
|
except Exception as e:
|
|
362
|
-
console.print(f"[yellow]⚠️ Error terminating {service.name}: {e}[/yellow]")
|
|
362
|
+
console.print(f"[yellow]⚠️ Error terminating {service.name}: {e}[/ yellow]")
|
|
363
363
|
|
|
364
364
|
async def _wait_before_restart(self, service: ServiceConfig) -> None:
|
|
365
365
|
console.print(
|
|
366
|
-
f"[yellow]⏳ Waiting {service.restart_delay}s before restarting {service.name}...[/yellow]",
|
|
366
|
+
f"[yellow]⏳ Waiting {service.restart_delay}s before restarting {service.name}...[/ yellow]",
|
|
367
367
|
)
|
|
368
368
|
await asyncio.sleep(service.restart_delay)
|
|
369
369
|
|
|
@@ -386,7 +386,7 @@ class ServiceWatchdog:
|
|
|
386
386
|
await asyncio.sleep(10.0)
|
|
387
387
|
|
|
388
388
|
except Exception as e:
|
|
389
|
-
console.print(f"[red]Error updating display: {e}[/red]")
|
|
389
|
+
console.print(f"[red]Error updating display: {e}[/ red]")
|
|
390
390
|
await asyncio.sleep(5.0)
|
|
391
391
|
|
|
392
392
|
async def _update_status_display(self) -> None:
|
|
@@ -402,7 +402,7 @@ class ServiceWatchdog:
|
|
|
402
402
|
table.add_row(service.name, status, health, restarts, error)
|
|
403
403
|
|
|
404
404
|
console.print(table)
|
|
405
|
-
console.print("\n[dim]Press Ctrl + C to stop monitoring[/dim]")
|
|
405
|
+
console.print("\n[dim]Press Ctrl + C to stop monitoring[/ dim]")
|
|
406
406
|
|
|
407
407
|
def _create_status_table(self) -> Table:
|
|
408
408
|
table = Table(title="🔍 Crackerjack Service Watchdog")
|
|
@@ -415,20 +415,20 @@ class ServiceWatchdog:
|
|
|
415
415
|
|
|
416
416
|
def _get_service_status(self, service: ServiceConfig) -> str:
|
|
417
417
|
if service.process and service.process.poll() is None:
|
|
418
|
-
return "[green]✅ Running[/green]"
|
|
419
|
-
return "[red]❌ Stopped[/red]"
|
|
418
|
+
return "[green]✅ Running[/ green]"
|
|
419
|
+
return "[red]❌ Stopped[/ red]"
|
|
420
420
|
|
|
421
421
|
def _get_service_health(self, service: ServiceConfig) -> str:
|
|
422
422
|
if service.health_check_url:
|
|
423
423
|
return (
|
|
424
|
-
"[green]🟢 Healthy[/green]"
|
|
424
|
+
"[green]🟢 Healthy[/ green]"
|
|
425
425
|
if service.is_healthy
|
|
426
|
-
else "[red]🔴 Unhealthy[/red]"
|
|
426
|
+
else "[red]🔴 Unhealthy[/ red]"
|
|
427
427
|
)
|
|
428
|
-
return "[dim]N / A[/dim]"
|
|
428
|
+
return "[dim]N / A[/ dim]"
|
|
429
429
|
|
|
430
430
|
def _format_error_message(self, error_message: str | None) -> str:
|
|
431
|
-
error = error_message or "[dim]None[/dim]"
|
|
431
|
+
error = error_message or "[dim]None[/ dim]"
|
|
432
432
|
if len(error) > 30:
|
|
433
433
|
error = error[:27] + "..."
|
|
434
434
|
return error
|
|
@@ -466,20 +466,20 @@ async def create_default_watchdog(
|
|
|
466
466
|
name="MCP Server",
|
|
467
467
|
command=[
|
|
468
468
|
python_path,
|
|
469
|
-
"
|
|
469
|
+
"-m",
|
|
470
470
|
"crackerjack",
|
|
471
|
-
"
|
|
471
|
+
"--start-mcp-server",
|
|
472
472
|
],
|
|
473
473
|
),
|
|
474
474
|
ServiceConfig(
|
|
475
475
|
name="WebSocket Server",
|
|
476
476
|
command=[
|
|
477
477
|
python_path,
|
|
478
|
-
"
|
|
478
|
+
"-m",
|
|
479
479
|
"crackerjack",
|
|
480
|
-
"
|
|
480
|
+
" - - websocket-server",
|
|
481
481
|
],
|
|
482
|
-
health_check_url="http:
|
|
482
|
+
health_check_url="http: / / localhost: 8675 / ",
|
|
483
483
|
),
|
|
484
484
|
]
|
|
485
485
|
|
|
@@ -492,7 +492,7 @@ async def main() -> None:
|
|
|
492
492
|
try:
|
|
493
493
|
await watchdog.start()
|
|
494
494
|
except KeyboardInterrupt:
|
|
495
|
-
console.print("\n[yellow]🛑 Shutting down watchdog...[/yellow]")
|
|
495
|
+
console.print("\n[yellow]🛑 Shutting down watchdog...[/ yellow]")
|
|
496
496
|
finally:
|
|
497
497
|
await watchdog.stop()
|
|
498
498
|
|
crackerjack/mcp/state.py
CHANGED
|
@@ -350,6 +350,21 @@ class StateManager:
|
|
|
350
350
|
checkpoints.sort(key=operator.itemgetter("timestamp"), reverse=True)
|
|
351
351
|
return checkpoints
|
|
352
352
|
|
|
353
|
+
def start_session(self) -> None:
|
|
354
|
+
"""Start or initialize a session."""
|
|
355
|
+
# Session is already initialized in __init__, this is a no-op
|
|
356
|
+
# but provided for API compatibility
|
|
357
|
+
self._save_state()
|
|
358
|
+
|
|
359
|
+
def complete_session(self) -> None:
|
|
360
|
+
"""Complete the current session."""
|
|
361
|
+
# Mark session as complete in metadata
|
|
362
|
+
if not self.session_state.metadata:
|
|
363
|
+
self.session_state.metadata = {}
|
|
364
|
+
self.session_state.metadata["status"] = "completed"
|
|
365
|
+
self.session_state.metadata["completed_time"] = time.time()
|
|
366
|
+
self._save_state()
|
|
367
|
+
|
|
353
368
|
async def reset_session(self) -> None:
|
|
354
369
|
async with self._lock:
|
|
355
370
|
self.session_state = SessionState(
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import typing as t
|
|
2
2
|
|
|
3
3
|
from crackerjack.mcp.context import get_context
|
|
4
|
+
from crackerjack.services.input_validator import (
|
|
5
|
+
get_input_validator,
|
|
6
|
+
)
|
|
4
7
|
|
|
5
8
|
|
|
6
9
|
async def create_task_with_subagent(
|
|
@@ -8,31 +11,52 @@ async def create_task_with_subagent(
|
|
|
8
11
|
prompt: str,
|
|
9
12
|
subagent_type: str,
|
|
10
13
|
) -> dict[str, t.Any]:
|
|
11
|
-
|
|
14
|
+
try:
|
|
15
|
+
# Input validation with security checks
|
|
16
|
+
validator = get_input_validator()
|
|
17
|
+
|
|
18
|
+
# Validate description
|
|
19
|
+
desc_result = validator.validate_command_args(description)
|
|
20
|
+
if not desc_result.valid:
|
|
21
|
+
return {
|
|
22
|
+
"success": False,
|
|
23
|
+
"error": f"Invalid description: {desc_result.error_message}",
|
|
24
|
+
"validation_type": desc_result.validation_type,
|
|
25
|
+
}
|
|
12
26
|
|
|
13
|
-
|
|
14
|
-
|
|
27
|
+
# Validate prompt
|
|
28
|
+
prompt_result = validator.validate_command_args(prompt)
|
|
29
|
+
if not prompt_result.valid:
|
|
30
|
+
return {
|
|
31
|
+
"success": False,
|
|
32
|
+
"error": f"Invalid prompt: {prompt_result.error_message}",
|
|
33
|
+
"validation_type": prompt_result.validation_type,
|
|
34
|
+
}
|
|
15
35
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
36
|
+
# Validate subagent_type (should be safe identifier)
|
|
37
|
+
subagent_result = validator.sanitizer.sanitize_string(
|
|
38
|
+
subagent_type, max_length=100, strict_alphanumeric=True
|
|
39
|
+
)
|
|
40
|
+
if not subagent_result.valid:
|
|
41
|
+
return {
|
|
42
|
+
"success": False,
|
|
43
|
+
"error": f"Invalid subagent_type: {subagent_result.error_message}",
|
|
44
|
+
"validation_type": subagent_result.validation_type,
|
|
45
|
+
}
|
|
20
46
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
# For now, return a placeholder result indicating the task would be executed
|
|
26
|
-
# In a full implementation, this would integrate with the actual Task tool
|
|
47
|
+
# Use sanitized values
|
|
48
|
+
sanitized_description = desc_result.sanitized_value or description
|
|
49
|
+
sanitized_prompt = prompt_result.sanitized_value or prompt
|
|
50
|
+
sanitized_subagent = subagent_result.sanitized_value
|
|
27
51
|
|
|
28
52
|
result = {
|
|
29
53
|
"success": True,
|
|
30
|
-
"description":
|
|
31
|
-
"prompt":
|
|
32
|
-
"subagent_type":
|
|
33
|
-
"result": f"Task would be executed by {
|
|
54
|
+
"description": sanitized_description,
|
|
55
|
+
"prompt": sanitized_prompt,
|
|
56
|
+
"subagent_type": sanitized_subagent,
|
|
57
|
+
"result": f"Task would be executed by {sanitized_subagent}: {sanitized_prompt[:100]}...",
|
|
34
58
|
"agent_type": "user"
|
|
35
|
-
if
|
|
59
|
+
if sanitized_subagent
|
|
36
60
|
not in ("general-purpose", "statusline-setup", "output-style-setup")
|
|
37
61
|
else "system",
|
|
38
62
|
}
|
|
@@ -42,7 +66,7 @@ async def create_task_with_subagent(
|
|
|
42
66
|
except Exception as e:
|
|
43
67
|
return {
|
|
44
68
|
"success": False,
|
|
45
|
-
"error":
|
|
69
|
+
"error": f"Task creation failed: {e}",
|
|
46
70
|
"description": description,
|
|
47
71
|
"subagent_type": subagent_type,
|
|
48
72
|
}
|
|
@@ -52,7 +76,6 @@ async def _validate_stage_request(context, rate_limiter) -> str | None:
|
|
|
52
76
|
if not context:
|
|
53
77
|
return '{"error": "Server context not available", "success": false}'
|
|
54
78
|
|
|
55
|
-
# Skip rate limiting if not configured
|
|
56
79
|
if rate_limiter and hasattr(rate_limiter, "check_request_allowed"):
|
|
57
80
|
allowed, details = await rate_limiter.check_request_allowed()
|
|
58
81
|
if not allowed:
|
|
@@ -61,22 +84,61 @@ async def _validate_stage_request(context, rate_limiter) -> str | None:
|
|
|
61
84
|
|
|
62
85
|
|
|
63
86
|
def _parse_stage_args(args: str, kwargs: str) -> tuple[str, dict] | str:
|
|
64
|
-
|
|
87
|
+
try:
|
|
88
|
+
validator = get_input_validator()
|
|
89
|
+
|
|
90
|
+
# Validate stage argument
|
|
91
|
+
stage_validation = _validate_stage_argument(validator, args)
|
|
92
|
+
if isinstance(stage_validation, str):
|
|
93
|
+
return stage_validation
|
|
94
|
+
stage = stage_validation
|
|
95
|
+
|
|
96
|
+
# Validate and parse kwargs
|
|
97
|
+
kwargs_validation = _validate_kwargs_argument(validator, kwargs)
|
|
98
|
+
if isinstance(kwargs_validation, str):
|
|
99
|
+
return kwargs_validation
|
|
100
|
+
extra_kwargs = kwargs_validation
|
|
101
|
+
|
|
102
|
+
return stage, extra_kwargs
|
|
103
|
+
|
|
104
|
+
except Exception as e:
|
|
105
|
+
return f'{{"error": "Stage argument parsing failed: {e}", "success": false}}'
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _validate_stage_argument(validator, args: str) -> str:
|
|
109
|
+
"""Validate and sanitize the stage argument."""
|
|
110
|
+
stage_result = validator.sanitizer.sanitize_string(
|
|
111
|
+
args.strip(), max_length=50, strict_alphanumeric=True
|
|
112
|
+
)
|
|
113
|
+
if not stage_result.valid:
|
|
114
|
+
return f'{{"error": "Invalid stage argument: {stage_result.error_message}", "success": false}}'
|
|
115
|
+
|
|
116
|
+
stage = stage_result.sanitized_value.lower()
|
|
65
117
|
valid_stages = {"fast", "comprehensive", "tests", "cleaning", "init"}
|
|
66
118
|
|
|
67
119
|
if stage not in valid_stages:
|
|
68
120
|
return f'{{"error": "Invalid stage: {stage}. Valid stages: {valid_stages}", "success": false}}'
|
|
69
121
|
|
|
70
|
-
|
|
122
|
+
return stage
|
|
71
123
|
|
|
124
|
+
|
|
125
|
+
def _validate_kwargs_argument(validator, kwargs: str) -> dict | str:
|
|
126
|
+
"""Validate and parse the kwargs argument."""
|
|
72
127
|
extra_kwargs = {}
|
|
73
|
-
if kwargs.strip():
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
128
|
+
if not kwargs.strip():
|
|
129
|
+
return extra_kwargs
|
|
130
|
+
|
|
131
|
+
kwargs_result = validator.validate_json_payload(kwargs.strip())
|
|
132
|
+
if not kwargs_result.valid:
|
|
133
|
+
return f'{{"error": "Invalid JSON in kwargs: {kwargs_result.error_message}", "success": false}}'
|
|
134
|
+
|
|
135
|
+
extra_kwargs = kwargs_result.sanitized_value
|
|
136
|
+
|
|
137
|
+
# Additional validation on JSON structure
|
|
138
|
+
if not isinstance(extra_kwargs, dict):
|
|
139
|
+
return f'{{"error": "kwargs must be a JSON object, got {type(extra_kwargs).__name__}", "success": false}}'
|
|
78
140
|
|
|
79
|
-
return
|
|
141
|
+
return extra_kwargs
|
|
80
142
|
|
|
81
143
|
|
|
82
144
|
def _configure_stage_options(stage: str) -> "WorkflowOptions":
|
|
@@ -90,7 +152,6 @@ def _configure_stage_options(stage: str) -> "WorkflowOptions":
|
|
|
90
152
|
elif stage == "cleaning":
|
|
91
153
|
options.cleaning.clean = True
|
|
92
154
|
elif stage == "init":
|
|
93
|
-
# Init stage doesn't use standard workflow options
|
|
94
155
|
options.skip_hooks = True
|
|
95
156
|
return options
|
|
96
157
|
|
|
@@ -110,7 +171,6 @@ def _execute_stage(orchestrator, stage: str, options) -> bool:
|
|
|
110
171
|
|
|
111
172
|
|
|
112
173
|
def _execute_init_stage(orchestrator) -> bool:
|
|
113
|
-
"""Execute project initialization stage."""
|
|
114
174
|
try:
|
|
115
175
|
from pathlib import Path
|
|
116
176
|
|
|
@@ -118,25 +178,21 @@ def _execute_init_stage(orchestrator) -> bool:
|
|
|
118
178
|
from crackerjack.services.git import GitService
|
|
119
179
|
from crackerjack.services.initialization import InitializationService
|
|
120
180
|
|
|
121
|
-
# Get orchestrator dependencies
|
|
122
181
|
console = orchestrator.console
|
|
123
182
|
pkg_path = orchestrator.pkg_path
|
|
124
183
|
|
|
125
|
-
# Create service dependencies
|
|
126
184
|
filesystem = FileSystemService()
|
|
127
185
|
git_service = GitService(console, pkg_path)
|
|
128
186
|
|
|
129
|
-
# Initialize the service
|
|
130
187
|
init_service = InitializationService(console, filesystem, git_service, pkg_path)
|
|
131
188
|
|
|
132
|
-
# Run initialization in current directory
|
|
133
189
|
results = init_service.initialize_project(target_path=Path.cwd())
|
|
134
190
|
|
|
135
191
|
return results.get("success", False)
|
|
136
192
|
|
|
137
193
|
except Exception as e:
|
|
138
194
|
if hasattr(orchestrator, "console"):
|
|
139
|
-
orchestrator.console.print(f"[red]❌[/red] Initialization failed: {e}")
|
|
195
|
+
orchestrator.console.print(f"[red]❌[/ red] Initialization failed: {e}")
|
|
140
196
|
return False
|
|
141
197
|
|
|
142
198
|
|
|
@@ -177,11 +233,11 @@ def register_core_tools(mcp_app: t.Any) -> None:
|
|
|
177
233
|
|
|
178
234
|
def _get_error_patterns() -> list[tuple[str, str]]:
|
|
179
235
|
return [
|
|
180
|
-
("type_error", r"TypeError
|
|
181
|
-
("import_error", r"ImportError
|
|
236
|
+
("type_error", r"TypeError: | type object .* has no attribute"),
|
|
237
|
+
("import_error", r"ImportError: | ModuleNotFoundError: "),
|
|
182
238
|
("attribute_error", r"AttributeError: "),
|
|
183
|
-
("syntax_error", r"SyntaxError
|
|
184
|
-
("test_failure", r"FAILED|AssertionError:"),
|
|
239
|
+
("syntax_error", r"SyntaxError: | invalid syntax"),
|
|
240
|
+
("test_failure", r"FAILED | AssertionError: "),
|
|
185
241
|
("hook_failure", r"hook .* failed"),
|
|
186
242
|
]
|
|
187
243
|
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
"""Error analysis and pattern detection for MCP tools.
|
|
2
|
-
|
|
3
|
-
This module handles intelligent error analysis, pattern caching, and diagnostic
|
|
4
|
-
recommendations. Split from execution_tools.py for better separation of concerns.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
1
|
import typing as t
|
|
8
2
|
from contextlib import suppress
|
|
9
3
|
|
|
@@ -11,7 +5,6 @@ from contextlib import suppress
|
|
|
11
5
|
def analyze_errors_with_caching(
|
|
12
6
|
context: t.Any, use_cache: bool = True
|
|
13
7
|
) -> dict[str, t.Any]:
|
|
14
|
-
"""Analyze errors with intelligent caching and pattern detection."""
|
|
15
8
|
try:
|
|
16
9
|
cached_patterns = _get_cached_patterns(context, use_cache)
|
|
17
10
|
return _build_error_analysis(cached_patterns, context)
|
|
@@ -26,7 +19,6 @@ def analyze_errors_with_caching(
|
|
|
26
19
|
|
|
27
20
|
|
|
28
21
|
def _get_cached_patterns(context: t.Any, use_cache: bool) -> list[t.Any]:
|
|
29
|
-
"""Get cached error patterns from context."""
|
|
30
22
|
if not use_cache:
|
|
31
23
|
return []
|
|
32
24
|
|
|
@@ -39,7 +31,6 @@ def _get_cached_patterns(context: t.Any, use_cache: bool) -> list[t.Any]:
|
|
|
39
31
|
|
|
40
32
|
|
|
41
33
|
def _build_error_analysis(patterns: list[t.Any], context: t.Any) -> dict[str, t.Any]:
|
|
42
|
-
"""Build comprehensive error analysis from patterns."""
|
|
43
34
|
analysis = {
|
|
44
35
|
"status": "success",
|
|
45
36
|
"patterns_found": len(patterns),
|
|
@@ -52,7 +43,7 @@ def _build_error_analysis(patterns: list[t.Any], context: t.Any) -> dict[str, t.
|
|
|
52
43
|
if not patterns:
|
|
53
44
|
analysis.update(
|
|
54
45
|
{
|
|
55
|
-
"message": "No cached error patterns found
|
|
46
|
+
"message": "No cached error patterns found-this indicates clean execution history",
|
|
56
47
|
"recommendations": [
|
|
57
48
|
"Continue with current development practices",
|
|
58
49
|
"Consider running comprehensive quality checks if issues arise",
|
|
@@ -61,18 +52,14 @@ def _build_error_analysis(patterns: list[t.Any], context: t.Any) -> dict[str, t.
|
|
|
61
52
|
)
|
|
62
53
|
return analysis
|
|
63
54
|
|
|
64
|
-
# Categorize error patterns
|
|
65
55
|
categories = _categorize_error_patterns(patterns)
|
|
66
56
|
analysis["error_categories"] = categories
|
|
67
57
|
|
|
68
|
-
# Generate recommendations
|
|
69
58
|
recommendations = _generate_error_recommendations(categories)
|
|
70
59
|
analysis["recommendations"] = recommendations
|
|
71
60
|
|
|
72
|
-
# Determine urgency
|
|
73
61
|
analysis["urgency_level"] = _calculate_urgency_level(categories)
|
|
74
62
|
|
|
75
|
-
# Generate fix suggestions
|
|
76
63
|
analysis["fix_suggestions"] = _generate_fix_suggestions(categories)
|
|
77
64
|
|
|
78
65
|
analysis["message"] = (
|
|
@@ -83,7 +70,6 @@ def _build_error_analysis(patterns: list[t.Any], context: t.Any) -> dict[str, t.
|
|
|
83
70
|
|
|
84
71
|
|
|
85
72
|
def _categorize_error_patterns(patterns: list[t.Any]) -> dict[str, list[t.Any]]:
|
|
86
|
-
"""Categorize error patterns by type."""
|
|
87
73
|
categories = {
|
|
88
74
|
"syntax_errors": [],
|
|
89
75
|
"import_errors": [],
|
|
@@ -100,13 +86,10 @@ def _categorize_error_patterns(patterns: list[t.Any]) -> dict[str, list[t.Any]]:
|
|
|
100
86
|
category = _classify_error_pattern(pattern)
|
|
101
87
|
categories[category].append(pattern)
|
|
102
88
|
|
|
103
|
-
# Remove empty categories
|
|
104
89
|
return {k: v for k, v in categories.items() if v}
|
|
105
90
|
|
|
106
91
|
|
|
107
92
|
def _classify_error_pattern(pattern: t.Any) -> str:
|
|
108
|
-
"""Classify a single error pattern into a category."""
|
|
109
|
-
# Convert pattern to string for analysis
|
|
110
93
|
pattern_str = str(pattern).lower()
|
|
111
94
|
|
|
112
95
|
if any(
|
|
@@ -146,7 +129,6 @@ def _classify_error_pattern(pattern: t.Any) -> str:
|
|
|
146
129
|
|
|
147
130
|
|
|
148
131
|
def _generate_error_recommendations(categories: dict[str, list[t.Any]]) -> list[str]:
|
|
149
|
-
"""Generate actionable recommendations based on error categories."""
|
|
150
132
|
recommendations = []
|
|
151
133
|
|
|
152
134
|
if categories.get("syntax_errors"):
|
|
@@ -210,7 +192,6 @@ def _generate_error_recommendations(categories: dict[str, list[t.Any]]) -> list[
|
|
|
210
192
|
]
|
|
211
193
|
)
|
|
212
194
|
|
|
213
|
-
# Add general recommendations
|
|
214
195
|
if len(categories) > 3:
|
|
215
196
|
recommendations.extend(
|
|
216
197
|
[
|
|
@@ -223,26 +204,20 @@ def _generate_error_recommendations(categories: dict[str, list[t.Any]]) -> list[
|
|
|
223
204
|
|
|
224
205
|
|
|
225
206
|
def _calculate_urgency_level(categories: dict[str, list[t.Any]]) -> str:
|
|
226
|
-
"""Calculate urgency level based on error categories and counts."""
|
|
227
207
|
total_errors = sum(len(errors) for errors in categories.values())
|
|
228
208
|
|
|
229
|
-
# Security issues are always high priority
|
|
230
209
|
if categories.get("security_issues"):
|
|
231
210
|
return "high"
|
|
232
211
|
|
|
233
|
-
# Many test failures indicate critical issues
|
|
234
212
|
if categories.get("test_failures") and len(categories["test_failures"]) > 5:
|
|
235
213
|
return "high"
|
|
236
214
|
|
|
237
|
-
# Syntax errors block development
|
|
238
215
|
if categories.get("syntax_errors"):
|
|
239
216
|
return "medium"
|
|
240
217
|
|
|
241
|
-
# Large number of total errors
|
|
242
218
|
if total_errors > 20:
|
|
243
219
|
return "medium"
|
|
244
220
|
|
|
245
|
-
# Multiple categories indicate systemic issues
|
|
246
221
|
if len(categories) > 4:
|
|
247
222
|
return "medium"
|
|
248
223
|
|
|
@@ -252,7 +227,6 @@ def _calculate_urgency_level(categories: dict[str, list[t.Any]]) -> str:
|
|
|
252
227
|
def _generate_fix_suggestions(
|
|
253
228
|
categories: dict[str, list[t.Any]],
|
|
254
229
|
) -> list[dict[str, str]]:
|
|
255
|
-
"""Generate specific fix suggestions with commands."""
|
|
256
230
|
suggestions = []
|
|
257
231
|
|
|
258
232
|
if categories.get("formatting_issues"):
|
|
@@ -260,7 +234,7 @@ def _generate_fix_suggestions(
|
|
|
260
234
|
{
|
|
261
235
|
"category": "formatting",
|
|
262
236
|
"action": "Run code formatting",
|
|
263
|
-
"command": "python -m crackerjack
|
|
237
|
+
"command": "python - m crackerjack - - skip-tests",
|
|
264
238
|
"description": "Fix formatting and style issues",
|
|
265
239
|
}
|
|
266
240
|
)
|
|
@@ -270,7 +244,7 @@ def _generate_fix_suggestions(
|
|
|
270
244
|
{
|
|
271
245
|
"category": "types",
|
|
272
246
|
"action": "Fix type annotations",
|
|
273
|
-
"command": "python -m crackerjack
|
|
247
|
+
"command": "python - m crackerjack - - ai-agent",
|
|
274
248
|
"description": "Add missing type hints and resolve type conflicts",
|
|
275
249
|
}
|
|
276
250
|
)
|
|
@@ -280,7 +254,7 @@ def _generate_fix_suggestions(
|
|
|
280
254
|
{
|
|
281
255
|
"category": "tests",
|
|
282
256
|
"action": "Fix test failures",
|
|
283
|
-
"command": "python -m crackerjack -t
|
|
257
|
+
"command": "python - m crackerjack - t - - ai-agent",
|
|
284
258
|
"description": "Run tests with AI auto-fixing enabled",
|
|
285
259
|
}
|
|
286
260
|
)
|
|
@@ -290,7 +264,7 @@ def _generate_fix_suggestions(
|
|
|
290
264
|
{
|
|
291
265
|
"category": "security",
|
|
292
266
|
"action": "Address security issues",
|
|
293
|
-
"command": "python -m crackerjack
|
|
267
|
+
"command": "python - m crackerjack - - ai - agent-t",
|
|
294
268
|
"description": "Fix security vulnerabilities with AI assistance",
|
|
295
269
|
}
|
|
296
270
|
)
|
|
@@ -300,7 +274,7 @@ def _generate_fix_suggestions(
|
|
|
300
274
|
{
|
|
301
275
|
"category": "comprehensive",
|
|
302
276
|
"action": "Full quality check with AI fixing",
|
|
303
|
-
"command": "python -m crackerjack
|
|
277
|
+
"command": "python - m crackerjack - - ai - agent-t",
|
|
304
278
|
"description": "Comprehensive quality check with autonomous fixing",
|
|
305
279
|
}
|
|
306
280
|
)
|