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.

Files changed (155) hide show
  1. crackerjack/CLAUDE.md +288 -705
  2. crackerjack/__main__.py +22 -8
  3. crackerjack/agents/__init__.py +0 -3
  4. crackerjack/agents/architect_agent.py +0 -43
  5. crackerjack/agents/base.py +1 -9
  6. crackerjack/agents/coordinator.py +2 -148
  7. crackerjack/agents/documentation_agent.py +109 -81
  8. crackerjack/agents/dry_agent.py +122 -97
  9. crackerjack/agents/formatting_agent.py +3 -16
  10. crackerjack/agents/import_optimization_agent.py +1174 -130
  11. crackerjack/agents/performance_agent.py +956 -188
  12. crackerjack/agents/performance_helpers.py +229 -0
  13. crackerjack/agents/proactive_agent.py +1 -48
  14. crackerjack/agents/refactoring_agent.py +516 -246
  15. crackerjack/agents/refactoring_helpers.py +282 -0
  16. crackerjack/agents/security_agent.py +393 -90
  17. crackerjack/agents/test_creation_agent.py +1776 -120
  18. crackerjack/agents/test_specialist_agent.py +59 -15
  19. crackerjack/agents/tracker.py +0 -102
  20. crackerjack/api.py +145 -37
  21. crackerjack/cli/handlers.py +48 -30
  22. crackerjack/cli/interactive.py +11 -11
  23. crackerjack/cli/options.py +66 -4
  24. crackerjack/code_cleaner.py +808 -148
  25. crackerjack/config/global_lock_config.py +110 -0
  26. crackerjack/config/hooks.py +43 -64
  27. crackerjack/core/async_workflow_orchestrator.py +247 -97
  28. crackerjack/core/autofix_coordinator.py +192 -109
  29. crackerjack/core/enhanced_container.py +46 -63
  30. crackerjack/core/file_lifecycle.py +549 -0
  31. crackerjack/core/performance.py +9 -8
  32. crackerjack/core/performance_monitor.py +395 -0
  33. crackerjack/core/phase_coordinator.py +281 -94
  34. crackerjack/core/proactive_workflow.py +9 -58
  35. crackerjack/core/resource_manager.py +501 -0
  36. crackerjack/core/service_watchdog.py +490 -0
  37. crackerjack/core/session_coordinator.py +4 -8
  38. crackerjack/core/timeout_manager.py +504 -0
  39. crackerjack/core/websocket_lifecycle.py +475 -0
  40. crackerjack/core/workflow_orchestrator.py +343 -209
  41. crackerjack/dynamic_config.py +50 -9
  42. crackerjack/errors.py +3 -4
  43. crackerjack/executors/async_hook_executor.py +63 -13
  44. crackerjack/executors/cached_hook_executor.py +14 -14
  45. crackerjack/executors/hook_executor.py +100 -37
  46. crackerjack/executors/hook_lock_manager.py +856 -0
  47. crackerjack/executors/individual_hook_executor.py +120 -86
  48. crackerjack/intelligence/__init__.py +0 -7
  49. crackerjack/intelligence/adaptive_learning.py +13 -86
  50. crackerjack/intelligence/agent_orchestrator.py +15 -78
  51. crackerjack/intelligence/agent_registry.py +12 -59
  52. crackerjack/intelligence/agent_selector.py +31 -92
  53. crackerjack/intelligence/integration.py +1 -41
  54. crackerjack/interactive.py +9 -9
  55. crackerjack/managers/async_hook_manager.py +25 -8
  56. crackerjack/managers/hook_manager.py +9 -9
  57. crackerjack/managers/publish_manager.py +57 -59
  58. crackerjack/managers/test_command_builder.py +6 -36
  59. crackerjack/managers/test_executor.py +9 -61
  60. crackerjack/managers/test_manager.py +17 -63
  61. crackerjack/managers/test_manager_backup.py +77 -127
  62. crackerjack/managers/test_progress.py +4 -23
  63. crackerjack/mcp/cache.py +5 -12
  64. crackerjack/mcp/client_runner.py +10 -10
  65. crackerjack/mcp/context.py +64 -6
  66. crackerjack/mcp/dashboard.py +14 -11
  67. crackerjack/mcp/enhanced_progress_monitor.py +55 -55
  68. crackerjack/mcp/file_monitor.py +72 -42
  69. crackerjack/mcp/progress_components.py +103 -84
  70. crackerjack/mcp/progress_monitor.py +122 -49
  71. crackerjack/mcp/rate_limiter.py +12 -12
  72. crackerjack/mcp/server_core.py +16 -22
  73. crackerjack/mcp/service_watchdog.py +26 -26
  74. crackerjack/mcp/state.py +15 -0
  75. crackerjack/mcp/tools/core_tools.py +95 -39
  76. crackerjack/mcp/tools/error_analyzer.py +6 -32
  77. crackerjack/mcp/tools/execution_tools.py +1 -56
  78. crackerjack/mcp/tools/execution_tools_backup.py +35 -131
  79. crackerjack/mcp/tools/intelligence_tool_registry.py +0 -36
  80. crackerjack/mcp/tools/intelligence_tools.py +2 -55
  81. crackerjack/mcp/tools/monitoring_tools.py +308 -145
  82. crackerjack/mcp/tools/proactive_tools.py +12 -42
  83. crackerjack/mcp/tools/progress_tools.py +23 -15
  84. crackerjack/mcp/tools/utility_tools.py +3 -40
  85. crackerjack/mcp/tools/workflow_executor.py +40 -60
  86. crackerjack/mcp/websocket/app.py +0 -3
  87. crackerjack/mcp/websocket/endpoints.py +206 -268
  88. crackerjack/mcp/websocket/jobs.py +213 -66
  89. crackerjack/mcp/websocket/server.py +84 -6
  90. crackerjack/mcp/websocket/websocket_handler.py +137 -29
  91. crackerjack/models/config_adapter.py +3 -16
  92. crackerjack/models/protocols.py +162 -3
  93. crackerjack/models/resource_protocols.py +454 -0
  94. crackerjack/models/task.py +3 -3
  95. crackerjack/monitoring/__init__.py +0 -0
  96. crackerjack/monitoring/ai_agent_watchdog.py +25 -71
  97. crackerjack/monitoring/regression_prevention.py +28 -87
  98. crackerjack/orchestration/advanced_orchestrator.py +44 -78
  99. crackerjack/orchestration/coverage_improvement.py +10 -60
  100. crackerjack/orchestration/execution_strategies.py +16 -16
  101. crackerjack/orchestration/test_progress_streamer.py +61 -53
  102. crackerjack/plugins/base.py +1 -1
  103. crackerjack/plugins/managers.py +22 -20
  104. crackerjack/py313.py +65 -21
  105. crackerjack/services/backup_service.py +467 -0
  106. crackerjack/services/bounded_status_operations.py +627 -0
  107. crackerjack/services/cache.py +7 -9
  108. crackerjack/services/config.py +35 -52
  109. crackerjack/services/config_integrity.py +5 -16
  110. crackerjack/services/config_merge.py +542 -0
  111. crackerjack/services/contextual_ai_assistant.py +17 -19
  112. crackerjack/services/coverage_ratchet.py +44 -73
  113. crackerjack/services/debug.py +25 -39
  114. crackerjack/services/dependency_monitor.py +52 -50
  115. crackerjack/services/enhanced_filesystem.py +14 -11
  116. crackerjack/services/file_hasher.py +1 -1
  117. crackerjack/services/filesystem.py +1 -12
  118. crackerjack/services/git.py +71 -47
  119. crackerjack/services/health_metrics.py +31 -27
  120. crackerjack/services/initialization.py +276 -428
  121. crackerjack/services/input_validator.py +760 -0
  122. crackerjack/services/log_manager.py +16 -16
  123. crackerjack/services/logging.py +7 -6
  124. crackerjack/services/metrics.py +43 -43
  125. crackerjack/services/pattern_cache.py +2 -31
  126. crackerjack/services/pattern_detector.py +26 -63
  127. crackerjack/services/performance_benchmarks.py +20 -45
  128. crackerjack/services/regex_patterns.py +2887 -0
  129. crackerjack/services/regex_utils.py +537 -0
  130. crackerjack/services/secure_path_utils.py +683 -0
  131. crackerjack/services/secure_status_formatter.py +534 -0
  132. crackerjack/services/secure_subprocess.py +605 -0
  133. crackerjack/services/security.py +47 -10
  134. crackerjack/services/security_logger.py +492 -0
  135. crackerjack/services/server_manager.py +109 -50
  136. crackerjack/services/smart_scheduling.py +8 -25
  137. crackerjack/services/status_authentication.py +603 -0
  138. crackerjack/services/status_security_manager.py +442 -0
  139. crackerjack/services/thread_safe_status_collector.py +546 -0
  140. crackerjack/services/tool_version_service.py +1 -23
  141. crackerjack/services/unified_config.py +36 -58
  142. crackerjack/services/validation_rate_limiter.py +269 -0
  143. crackerjack/services/version_checker.py +9 -40
  144. crackerjack/services/websocket_resource_limiter.py +572 -0
  145. crackerjack/slash_commands/__init__.py +52 -2
  146. crackerjack/tools/__init__.py +0 -0
  147. crackerjack/tools/validate_input_validator_patterns.py +262 -0
  148. crackerjack/tools/validate_regex_patterns.py +198 -0
  149. {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/METADATA +197 -12
  150. crackerjack-0.31.13.dist-info/RECORD +178 -0
  151. crackerjack/cli/facade.py +0 -104
  152. crackerjack-0.31.10.dist-info/RECORD +0 -149
  153. {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/WHEEL +0 -0
  154. {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/entry_points.txt +0 -0
  155. {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 - server" in " ".join(service.command):
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" - {stderr.strip()[:50]}"
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 - server" not in " ".join(service.command):
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: // localhost: 8675 / ") as response:
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 - {reason}")
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
- " - m",
469
+ "-m",
470
470
  "crackerjack",
471
- " -- start - mcp - server",
471
+ "--start-mcp-server",
472
472
  ],
473
473
  ),
474
474
  ServiceConfig(
475
475
  name="WebSocket Server",
476
476
  command=[
477
477
  python_path,
478
- " - m",
478
+ "-m",
479
479
  "crackerjack",
480
- " -- websocket - server",
480
+ " - - websocket-server",
481
481
  ],
482
- health_check_url="http: // localhost: 8675 / ",
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
- """Create a task using a specific subagent type.
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
- This function provides integration with the Task tool for executing
14
- user agents and system agents through the intelligent agent system.
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
- Args:
17
- description: Description of the task
18
- prompt: The actual task prompt/content
19
- subagent_type: Type of subagent to use
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
- Returns:
22
- Dictionary with task execution results
23
- """
24
- try:
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": description,
31
- "prompt": prompt,
32
- "subagent_type": subagent_type,
33
- "result": f"Task would be executed by {subagent_type}: {prompt[:100]}...",
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 subagent_type
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": str(e),
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
- stage = args.strip().lower()
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
- import json
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
- try:
75
- extra_kwargs = json.loads(kwargs)
76
- except json.JSONDecodeError as e:
77
- return f'{{"error": "Invalid JSON in kwargs: {e}", "success": false}}'
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 stage, extra_kwargs
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:|type object .* has no attribute"),
181
- ("import_error", r"ImportError:|ModuleNotFoundError:"),
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:|invalid syntax"),
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 - this indicates clean execution history",
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 --skip-tests",
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 --ai-agent",
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 --ai-agent",
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 --ai-agent -t",
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 --ai-agent -t",
277
+ "command": "python - m crackerjack - - ai - agent-t",
304
278
  "description": "Comprehensive quality check with autonomous fixing",
305
279
  }
306
280
  )