crackerjack 0.29.0__py3-none-any.whl → 0.31.4__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 (158) hide show
  1. crackerjack/CLAUDE.md +1005 -0
  2. crackerjack/RULES.md +380 -0
  3. crackerjack/__init__.py +42 -13
  4. crackerjack/__main__.py +225 -253
  5. crackerjack/agents/__init__.py +41 -0
  6. crackerjack/agents/architect_agent.py +281 -0
  7. crackerjack/agents/base.py +169 -0
  8. crackerjack/agents/coordinator.py +512 -0
  9. crackerjack/agents/documentation_agent.py +498 -0
  10. crackerjack/agents/dry_agent.py +388 -0
  11. crackerjack/agents/formatting_agent.py +245 -0
  12. crackerjack/agents/import_optimization_agent.py +281 -0
  13. crackerjack/agents/performance_agent.py +669 -0
  14. crackerjack/agents/proactive_agent.py +104 -0
  15. crackerjack/agents/refactoring_agent.py +788 -0
  16. crackerjack/agents/security_agent.py +529 -0
  17. crackerjack/agents/test_creation_agent.py +652 -0
  18. crackerjack/agents/test_specialist_agent.py +486 -0
  19. crackerjack/agents/tracker.py +212 -0
  20. crackerjack/api.py +560 -0
  21. crackerjack/cli/__init__.py +24 -0
  22. crackerjack/cli/facade.py +104 -0
  23. crackerjack/cli/handlers.py +267 -0
  24. crackerjack/cli/interactive.py +471 -0
  25. crackerjack/cli/options.py +401 -0
  26. crackerjack/cli/utils.py +18 -0
  27. crackerjack/code_cleaner.py +670 -0
  28. crackerjack/config/__init__.py +19 -0
  29. crackerjack/config/hooks.py +218 -0
  30. crackerjack/core/__init__.py +0 -0
  31. crackerjack/core/async_workflow_orchestrator.py +406 -0
  32. crackerjack/core/autofix_coordinator.py +200 -0
  33. crackerjack/core/container.py +104 -0
  34. crackerjack/core/enhanced_container.py +542 -0
  35. crackerjack/core/performance.py +243 -0
  36. crackerjack/core/phase_coordinator.py +561 -0
  37. crackerjack/core/proactive_workflow.py +316 -0
  38. crackerjack/core/session_coordinator.py +289 -0
  39. crackerjack/core/workflow_orchestrator.py +640 -0
  40. crackerjack/dynamic_config.py +577 -0
  41. crackerjack/errors.py +263 -41
  42. crackerjack/executors/__init__.py +11 -0
  43. crackerjack/executors/async_hook_executor.py +431 -0
  44. crackerjack/executors/cached_hook_executor.py +242 -0
  45. crackerjack/executors/hook_executor.py +345 -0
  46. crackerjack/executors/individual_hook_executor.py +669 -0
  47. crackerjack/intelligence/__init__.py +44 -0
  48. crackerjack/intelligence/adaptive_learning.py +751 -0
  49. crackerjack/intelligence/agent_orchestrator.py +551 -0
  50. crackerjack/intelligence/agent_registry.py +414 -0
  51. crackerjack/intelligence/agent_selector.py +502 -0
  52. crackerjack/intelligence/integration.py +290 -0
  53. crackerjack/interactive.py +576 -315
  54. crackerjack/managers/__init__.py +11 -0
  55. crackerjack/managers/async_hook_manager.py +135 -0
  56. crackerjack/managers/hook_manager.py +137 -0
  57. crackerjack/managers/publish_manager.py +411 -0
  58. crackerjack/managers/test_command_builder.py +151 -0
  59. crackerjack/managers/test_executor.py +435 -0
  60. crackerjack/managers/test_manager.py +258 -0
  61. crackerjack/managers/test_manager_backup.py +1124 -0
  62. crackerjack/managers/test_progress.py +144 -0
  63. crackerjack/mcp/__init__.py +0 -0
  64. crackerjack/mcp/cache.py +336 -0
  65. crackerjack/mcp/client_runner.py +104 -0
  66. crackerjack/mcp/context.py +615 -0
  67. crackerjack/mcp/dashboard.py +636 -0
  68. crackerjack/mcp/enhanced_progress_monitor.py +479 -0
  69. crackerjack/mcp/file_monitor.py +336 -0
  70. crackerjack/mcp/progress_components.py +569 -0
  71. crackerjack/mcp/progress_monitor.py +949 -0
  72. crackerjack/mcp/rate_limiter.py +332 -0
  73. crackerjack/mcp/server.py +22 -0
  74. crackerjack/mcp/server_core.py +244 -0
  75. crackerjack/mcp/service_watchdog.py +501 -0
  76. crackerjack/mcp/state.py +395 -0
  77. crackerjack/mcp/task_manager.py +257 -0
  78. crackerjack/mcp/tools/__init__.py +17 -0
  79. crackerjack/mcp/tools/core_tools.py +249 -0
  80. crackerjack/mcp/tools/error_analyzer.py +308 -0
  81. crackerjack/mcp/tools/execution_tools.py +370 -0
  82. crackerjack/mcp/tools/execution_tools_backup.py +1097 -0
  83. crackerjack/mcp/tools/intelligence_tool_registry.py +80 -0
  84. crackerjack/mcp/tools/intelligence_tools.py +314 -0
  85. crackerjack/mcp/tools/monitoring_tools.py +502 -0
  86. crackerjack/mcp/tools/proactive_tools.py +384 -0
  87. crackerjack/mcp/tools/progress_tools.py +141 -0
  88. crackerjack/mcp/tools/utility_tools.py +341 -0
  89. crackerjack/mcp/tools/workflow_executor.py +360 -0
  90. crackerjack/mcp/websocket/__init__.py +14 -0
  91. crackerjack/mcp/websocket/app.py +39 -0
  92. crackerjack/mcp/websocket/endpoints.py +559 -0
  93. crackerjack/mcp/websocket/jobs.py +253 -0
  94. crackerjack/mcp/websocket/server.py +116 -0
  95. crackerjack/mcp/websocket/websocket_handler.py +78 -0
  96. crackerjack/mcp/websocket_server.py +10 -0
  97. crackerjack/models/__init__.py +31 -0
  98. crackerjack/models/config.py +93 -0
  99. crackerjack/models/config_adapter.py +230 -0
  100. crackerjack/models/protocols.py +118 -0
  101. crackerjack/models/task.py +154 -0
  102. crackerjack/monitoring/ai_agent_watchdog.py +450 -0
  103. crackerjack/monitoring/regression_prevention.py +638 -0
  104. crackerjack/orchestration/__init__.py +0 -0
  105. crackerjack/orchestration/advanced_orchestrator.py +970 -0
  106. crackerjack/orchestration/execution_strategies.py +341 -0
  107. crackerjack/orchestration/test_progress_streamer.py +636 -0
  108. crackerjack/plugins/__init__.py +15 -0
  109. crackerjack/plugins/base.py +200 -0
  110. crackerjack/plugins/hooks.py +246 -0
  111. crackerjack/plugins/loader.py +335 -0
  112. crackerjack/plugins/managers.py +259 -0
  113. crackerjack/py313.py +8 -3
  114. crackerjack/services/__init__.py +22 -0
  115. crackerjack/services/cache.py +314 -0
  116. crackerjack/services/config.py +347 -0
  117. crackerjack/services/config_integrity.py +99 -0
  118. crackerjack/services/contextual_ai_assistant.py +516 -0
  119. crackerjack/services/coverage_ratchet.py +347 -0
  120. crackerjack/services/debug.py +736 -0
  121. crackerjack/services/dependency_monitor.py +617 -0
  122. crackerjack/services/enhanced_filesystem.py +439 -0
  123. crackerjack/services/file_hasher.py +151 -0
  124. crackerjack/services/filesystem.py +395 -0
  125. crackerjack/services/git.py +165 -0
  126. crackerjack/services/health_metrics.py +611 -0
  127. crackerjack/services/initialization.py +847 -0
  128. crackerjack/services/log_manager.py +286 -0
  129. crackerjack/services/logging.py +174 -0
  130. crackerjack/services/metrics.py +578 -0
  131. crackerjack/services/pattern_cache.py +362 -0
  132. crackerjack/services/pattern_detector.py +515 -0
  133. crackerjack/services/performance_benchmarks.py +653 -0
  134. crackerjack/services/security.py +163 -0
  135. crackerjack/services/server_manager.py +234 -0
  136. crackerjack/services/smart_scheduling.py +144 -0
  137. crackerjack/services/tool_version_service.py +61 -0
  138. crackerjack/services/unified_config.py +437 -0
  139. crackerjack/services/version_checker.py +248 -0
  140. crackerjack/slash_commands/__init__.py +14 -0
  141. crackerjack/slash_commands/init.md +122 -0
  142. crackerjack/slash_commands/run.md +163 -0
  143. crackerjack/slash_commands/status.md +127 -0
  144. crackerjack-0.31.4.dist-info/METADATA +742 -0
  145. crackerjack-0.31.4.dist-info/RECORD +148 -0
  146. crackerjack-0.31.4.dist-info/entry_points.txt +2 -0
  147. crackerjack/.gitignore +0 -34
  148. crackerjack/.libcst.codemod.yaml +0 -18
  149. crackerjack/.pdm.toml +0 -1
  150. crackerjack/.pre-commit-config-ai.yaml +0 -149
  151. crackerjack/.pre-commit-config-fast.yaml +0 -69
  152. crackerjack/.pre-commit-config.yaml +0 -114
  153. crackerjack/crackerjack.py +0 -4140
  154. crackerjack/pyproject.toml +0 -285
  155. crackerjack-0.29.0.dist-info/METADATA +0 -1289
  156. crackerjack-0.29.0.dist-info/RECORD +0 -17
  157. {crackerjack-0.29.0.dist-info → crackerjack-0.31.4.dist-info}/WHEEL +0 -0
  158. {crackerjack-0.29.0.dist-info → crackerjack-0.31.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,736 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ import time
5
+ import typing as t
6
+ from contextlib import contextmanager
7
+ from datetime import datetime
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from rich.console import Console
12
+ from rich.panel import Panel
13
+ from rich.table import Table
14
+
15
+ from .log_manager import get_log_manager
16
+ from .logging import get_logger
17
+
18
+
19
+ class AIAgentDebugger:
20
+ def __init__(self, enabled: bool = False, verbose: bool = False) -> None:
21
+ self.enabled = enabled
22
+ self.verbose = verbose
23
+ self.console = Console(force_terminal=True, stderr=True)
24
+ self.logger = get_logger("crackerjack.ai_agent.debug")
25
+ self.session_id = f"debug_{int(time.time())}"
26
+
27
+ if self.enabled:
28
+ log_manager = get_log_manager()
29
+ self.debug_log_path = log_manager.create_debug_log_file(
30
+ f"ai-agent-{self.session_id}",
31
+ )
32
+ else:
33
+ self.debug_log_path = None
34
+
35
+ self.mcp_operations: list[dict[str, Any]] = []
36
+ self.agent_activities: list[dict[str, Any]] = []
37
+ self.workflow_phases: list[dict[str, Any]] = []
38
+ self.error_events: list[dict[str, Any]] = []
39
+
40
+ # Enhanced iteration tracking
41
+ self.iteration_stats: list[dict[str, Any]] = []
42
+ self.current_iteration = 0
43
+ self.total_test_failures = 0
44
+ self.total_test_fixes = 0
45
+ self.total_hook_failures = 0
46
+ self.total_hook_fixes = 0
47
+ self.workflow_success = False
48
+
49
+ self._debug_logging_setup = False
50
+
51
+ if self.enabled:
52
+ self._print_debug_header()
53
+
54
+ def _ensure_debug_logging_setup(self) -> None:
55
+ if not self._debug_logging_setup and self.enabled:
56
+ self._setup_debug_logging()
57
+ self._debug_logging_setup = True
58
+
59
+ def _setup_debug_logging(self) -> None:
60
+ if not self.debug_log_path:
61
+ return
62
+
63
+ debug_handler = logging.FileHandler(self.debug_log_path)
64
+ debug_handler.setLevel(logging.DEBUG)
65
+ debug_formatter = logging.Formatter(
66
+ "%(asctime)s | %(name)s | %(levelname)s | %(message)s",
67
+ )
68
+ debug_handler.setFormatter(debug_formatter)
69
+
70
+ loggers = [
71
+ "crackerjack.ai_agent",
72
+ "crackerjack.mcp",
73
+ "crackerjack.agents",
74
+ "crackerjack.workflow",
75
+ ]
76
+
77
+ for logger_name in loggers:
78
+ logger = logging.getLogger(logger_name)
79
+ logger.addHandler(debug_handler)
80
+ logger.setLevel(logging.DEBUG)
81
+
82
+ def _print_debug_header(self) -> None:
83
+ debug_log_info = (
84
+ f"Debug Log: {self.debug_log_path}"
85
+ if self.debug_log_path
86
+ else "Debug Log: None (disabled)"
87
+ )
88
+ header = Panel(
89
+ f"[bold cyan]🐛 AI Agent Debug Mode Active[/bold cyan]\n"
90
+ f"Session ID: {self.session_id}\n"
91
+ f"{debug_log_info}\n"
92
+ f"Verbose Mode: {'✅' if self.verbose else '❌'}",
93
+ title="Debug Session",
94
+ border_style="cyan",
95
+ )
96
+ self.console.print(header)
97
+ self.console.print() # Add blank line after debug panel for better formatting
98
+
99
+ @contextmanager
100
+ def debug_operation(self, operation: str, **kwargs: Any) -> t.Iterator[str]:
101
+ if not self.enabled:
102
+ yield ""
103
+ return
104
+
105
+ self._ensure_debug_logging_setup()
106
+ op_id = f"{operation}_{int(time.time() * 1000)}"
107
+ start_time = time.time()
108
+
109
+ self.logger.debug(f"Starting operation: {operation}", extra=kwargs)
110
+
111
+ if self.verbose:
112
+ self.console.print(f"[dim]🔍 {operation} starting...[/dim]")
113
+
114
+ try:
115
+ yield op_id
116
+ duration = time.time() - start_time
117
+ self.logger.debug(
118
+ f"Operation completed: {operation}",
119
+ extra={"duration": duration} | kwargs,
120
+ )
121
+
122
+ if self.verbose:
123
+ self.console.print(
124
+ f"[dim green]✅ {operation} completed ({duration:.2f}s)[/dim green]",
125
+ )
126
+
127
+ except Exception as e:
128
+ duration = time.time() - start_time
129
+ self.logger.exception(
130
+ f"Operation failed: {operation}",
131
+ extra={"error": str(e), "duration": duration} | kwargs,
132
+ )
133
+
134
+ if self.verbose:
135
+ self.console.print(
136
+ f"[dim red]❌ {operation} failed ({duration:.2f}s): {e}[/dim red]",
137
+ )
138
+ raise
139
+
140
+ def log_mcp_operation(
141
+ self,
142
+ operation_type: str,
143
+ tool_name: str,
144
+ params: dict[str, Any] | None = None,
145
+ result: dict[str, Any] | None = None,
146
+ error: str | None = None,
147
+ duration: float | None = None,
148
+ ) -> None:
149
+ if not self.enabled:
150
+ return
151
+
152
+ self._ensure_debug_logging_setup()
153
+
154
+ event = {
155
+ "timestamp": datetime.now().isoformat(),
156
+ "type": "mcp_operation",
157
+ "operation": operation_type,
158
+ "tool": tool_name,
159
+ "params": params or {},
160
+ "result": result,
161
+ "error": error,
162
+ "duration": duration,
163
+ }
164
+
165
+ self.mcp_operations.append(event)
166
+
167
+ self.logger.info(
168
+ f"MCP {operation_type}: {tool_name}",
169
+ extra={
170
+ "tool": tool_name,
171
+ "params_count": len(params) if params else 0,
172
+ "success": error is None,
173
+ "duration": duration,
174
+ },
175
+ )
176
+
177
+ if self.verbose:
178
+ status_color = "green" if error is None else "red"
179
+ status_icon = "✅" if error is None else "❌"
180
+
181
+ self.console.print(
182
+ f"[{status_color}]{status_icon} MCP {operation_type}[/{status_color}]: "
183
+ f"[bold]{tool_name}[/bold]"
184
+ + (f" ({duration:.2f}s)" if duration else ""),
185
+ )
186
+
187
+ if error and self.verbose:
188
+ self.console.print(f" [red]Error: {error}[/red]")
189
+ self.console.print()
190
+
191
+ def log_agent_activity(
192
+ self,
193
+ agent_name: str,
194
+ activity: str,
195
+ issue_id: str | None = None,
196
+ confidence: float | None = None,
197
+ result: dict[str, Any] | None = None,
198
+ metadata: dict[str, Any] | None = None,
199
+ ) -> None:
200
+ if not self.enabled:
201
+ return
202
+
203
+ self._ensure_debug_logging_setup()
204
+
205
+ event = {
206
+ "timestamp": datetime.now().isoformat(),
207
+ "type": "agent_activity",
208
+ "agent": agent_name,
209
+ "activity": activity,
210
+ "issue_id": issue_id,
211
+ "confidence": confidence,
212
+ "result": result,
213
+ "metadata": metadata or {},
214
+ }
215
+
216
+ self.agent_activities.append(event)
217
+
218
+ self.logger.info(
219
+ f"Agent {activity}: {agent_name}",
220
+ extra={
221
+ "agent": agent_name,
222
+ "activity": activity,
223
+ "issue_id": issue_id,
224
+ "confidence": confidence,
225
+ },
226
+ )
227
+
228
+ if self.verbose:
229
+ confidence_text = f" (confidence: {confidence:.2f})" if confidence else ""
230
+ issue_text = f" [issue: {issue_id}]" if issue_id else ""
231
+
232
+ self.console.print(
233
+ f"[blue]🤖 {agent_name}[/blue]: {activity}{confidence_text}{issue_text}",
234
+ )
235
+
236
+ def log_workflow_phase(
237
+ self,
238
+ phase: str,
239
+ status: str,
240
+ details: dict[str, Any] | None = None,
241
+ duration: float | None = None,
242
+ ) -> None:
243
+ if not self.enabled:
244
+ return
245
+
246
+ self._ensure_debug_logging_setup()
247
+
248
+ event = {
249
+ "timestamp": datetime.now().isoformat(),
250
+ "type": "workflow_phase",
251
+ "phase": phase,
252
+ "status": status,
253
+ "details": details or {},
254
+ "duration": duration,
255
+ }
256
+
257
+ self.workflow_phases.append(event)
258
+
259
+ self.logger.info(
260
+ f"Workflow {status}: {phase}",
261
+ extra={"phase": phase, "status": status, "duration": duration},
262
+ )
263
+
264
+ if self.verbose:
265
+ status_colors = {
266
+ "started": "yellow",
267
+ "completed": "green",
268
+ "failed": "red",
269
+ "skipped": "dim",
270
+ }
271
+
272
+ color = status_colors.get(status, "white")
273
+ duration_text = f" ({duration:.2f}s)" if duration else ""
274
+
275
+ self.console.print(
276
+ f"[{color}]📋 Workflow {status}: {phase}{duration_text}[/{color}]",
277
+ )
278
+
279
+ def log_error_event(
280
+ self,
281
+ error_type: str,
282
+ message: str,
283
+ context: dict[str, Any] | None = None,
284
+ traceback_info: str | None = None,
285
+ ) -> None:
286
+ if not self.enabled:
287
+ return
288
+
289
+ self._ensure_debug_logging_setup()
290
+
291
+ event = {
292
+ "timestamp": datetime.now().isoformat(),
293
+ "type": "error_event",
294
+ "error_type": error_type,
295
+ "message": message,
296
+ "context": context or {},
297
+ "traceback": traceback_info,
298
+ }
299
+
300
+ self.error_events.append(event)
301
+
302
+ self.logger.error(
303
+ f"Error: {error_type}",
304
+ extra={"error_type": error_type, "message": message, "context": context},
305
+ )
306
+
307
+ if self.verbose:
308
+ self.console.print(f"[red]💥 {error_type}: {message}[/red]")
309
+
310
+ def print_debug_summary(self) -> None:
311
+ if not self.enabled:
312
+ return
313
+
314
+ # Determine border style based on workflow success
315
+ border_style = "green" if self.workflow_success else "red"
316
+ title_style = "green" if self.workflow_success else "red"
317
+
318
+ # Main debug summary table
319
+ table = Table(
320
+ title=f"[{title_style}]AI Agent Debug Summary[/{title_style}]",
321
+ border_style=border_style,
322
+ )
323
+ table.add_column("Category", style="cyan")
324
+ table.add_column("Count", justify="right", style="green")
325
+ table.add_column("Details", style="dim")
326
+
327
+ table.add_row(
328
+ "MCP Operations",
329
+ str(len(self.mcp_operations)),
330
+ f"Tools used: {len({op['tool'] for op in self.mcp_operations})}",
331
+ )
332
+
333
+ table.add_row(
334
+ "Agent Activities",
335
+ str(len(self.agent_activities)),
336
+ f"Agents active: {len({act['agent'] for act in self.agent_activities})}",
337
+ )
338
+
339
+ table.add_row(
340
+ "Workflow Phases",
341
+ str(len(self.workflow_phases)),
342
+ f"Completed: {len([p for p in self.workflow_phases if p['status'] == 'completed'])}",
343
+ )
344
+
345
+ table.add_row(
346
+ "Error Events",
347
+ str(len(self.error_events)),
348
+ f"Types: {len({err['error_type'] for err in self.error_events})}",
349
+ )
350
+
351
+ # Add iteration statistics row
352
+ table.add_row(
353
+ "Iterations Completed",
354
+ str(self.current_iteration),
355
+ f"Total test fixes: {self.total_test_fixes}, hook fixes: {self.total_hook_fixes}",
356
+ )
357
+
358
+ self.console.print(table)
359
+
360
+ # Print iteration breakdown if we have iterations
361
+ if self.iteration_stats:
362
+ self._print_iteration_breakdown(border_style)
363
+
364
+ if self.verbose and self.agent_activities:
365
+ self._print_agent_activity_breakdown(border_style)
366
+
367
+ if self.verbose and self.mcp_operations:
368
+ self._print_mcp_operation_breakdown(border_style)
369
+
370
+ # Print total statistics
371
+ self._print_total_statistics(border_style)
372
+
373
+ self.console.print(
374
+ f"\n[dim]📝 Full debug log available at: {self.debug_log_path}[/dim]"
375
+ if self.debug_log_path
376
+ else "",
377
+ )
378
+
379
+ def _print_iteration_breakdown(self, border_style: str = "red") -> None:
380
+ """Print detailed breakdown of each iteration."""
381
+ if not self.iteration_stats:
382
+ return
383
+
384
+ table = Table(
385
+ title="[cyan]Iteration Breakdown[/cyan]",
386
+ border_style=border_style,
387
+ )
388
+ table.add_column("Iteration", style="yellow")
389
+ table.add_column("Test Failures", justify="right", style="red")
390
+ table.add_column("Test Fixes", justify="right", style="green")
391
+ table.add_column("Hook Failures", justify="right", style="red")
392
+ table.add_column("Hook Fixes", justify="right", style="green")
393
+ table.add_column("Duration", justify="right", style="cyan")
394
+
395
+ for iteration in self.iteration_stats:
396
+ table.add_row(
397
+ str(iteration["iteration"]),
398
+ str(iteration["test_failures"]),
399
+ str(iteration["test_fixes"]),
400
+ str(iteration["hook_failures"]),
401
+ str(iteration["hook_fixes"]),
402
+ f"{iteration['duration']:.1f}s" if iteration.get("duration") else "N/A",
403
+ )
404
+
405
+ self.console.print(table)
406
+
407
+ def _print_agent_activity_breakdown(self, border_style: str = "red") -> None:
408
+ agent_stats = {}
409
+ for activity in self.agent_activities:
410
+ agent = activity["agent"]
411
+ if agent not in agent_stats:
412
+ agent_stats[agent] = {
413
+ "activities": 0,
414
+ "avg_confidence": 0.0,
415
+ "confidences": [],
416
+ }
417
+
418
+ agent_stats[agent]["activities"] += 1
419
+ if activity.get("confidence"):
420
+ agent_stats[agent]["confidences"].append(activity["confidence"])
421
+
422
+ for stats in agent_stats.values():
423
+ if stats["confidences"]:
424
+ stats["avg_confidence"] = sum(stats["confidences"]) / len(
425
+ stats["confidences"],
426
+ )
427
+
428
+ table = Table(
429
+ title="[cyan]Agent Activity Breakdown[/cyan]",
430
+ border_style=border_style,
431
+ )
432
+ table.add_column("Agent", style="blue")
433
+ table.add_column("Activities", justify="right", style="green")
434
+ table.add_column("Avg Confidence", justify="right", style="yellow")
435
+
436
+ for agent, stats in sorted(agent_stats.items()):
437
+ confidence_text = (
438
+ f"{stats['avg_confidence']:.2f}"
439
+ if stats["avg_confidence"] > 0
440
+ else "N/A"
441
+ )
442
+ table.add_row(agent, str(stats["activities"]), confidence_text)
443
+
444
+ self.console.print(table)
445
+
446
+ def _print_total_statistics(self, border_style: str = "red") -> None:
447
+ """Print total cumulative statistics across all iterations."""
448
+ success_icon = "✅" if self.workflow_success else "❌"
449
+ status_text = "SUCCESS" if self.workflow_success else "IN PROGRESS"
450
+ status_style = "green" if self.workflow_success else "red"
451
+
452
+ table = Table(
453
+ title=f"[{status_style}]{success_icon} TOTAL WORKFLOW STATISTICS {success_icon}[/{status_style}]",
454
+ border_style=border_style,
455
+ )
456
+ table.add_column("Metric", style="cyan")
457
+ table.add_column("Total", justify="right", style=status_style)
458
+ table.add_column("Status", style=status_style)
459
+
460
+ table.add_row("Iterations Completed", str(self.current_iteration), status_text)
461
+
462
+ table.add_row(
463
+ "Total Test Failures Found",
464
+ str(self.total_test_failures),
465
+ f"Fixed: {self.total_test_fixes}",
466
+ )
467
+
468
+ table.add_row(
469
+ "Total Hook Failures Found",
470
+ str(self.total_hook_failures),
471
+ f"Fixed: {self.total_hook_fixes}",
472
+ )
473
+
474
+ total_issues = self.total_test_failures + self.total_hook_failures
475
+ total_fixes = self.total_test_fixes + self.total_hook_fixes
476
+ fix_rate = (total_fixes / total_issues * 100) if total_issues > 0 else 100
477
+
478
+ table.add_row(
479
+ "Overall Fix Rate",
480
+ f"{fix_rate:.1f}%",
481
+ f"{total_fixes}/{total_issues} issues resolved",
482
+ )
483
+
484
+ self.console.print(table)
485
+
486
+ def _print_mcp_operation_breakdown(self, border_style: str = "red") -> None:
487
+ tool_stats = {}
488
+ for op in self.mcp_operations:
489
+ tool = op["tool"]
490
+ if tool not in tool_stats:
491
+ tool_stats[tool] = {"calls": 0, "errors": 0, "total_duration": 0.0}
492
+
493
+ tool_stats[tool]["calls"] += 1
494
+ if op.get("error"):
495
+ tool_stats[tool]["errors"] += 1
496
+ if op.get("duration"):
497
+ tool_stats[tool]["total_duration"] += op["duration"]
498
+
499
+ table = Table(
500
+ title="[cyan]MCP Tool Usage[/cyan]",
501
+ border_style=border_style,
502
+ )
503
+ table.add_column("Tool", style="cyan")
504
+ table.add_column("Calls", justify="right", style="green")
505
+ table.add_column("Errors", justify="right", style="red")
506
+ table.add_column("Avg Duration", justify="right", style="yellow")
507
+
508
+ for tool, stats in sorted(tool_stats.items()):
509
+ avg_duration = (
510
+ stats["total_duration"] / stats["calls"] if stats["calls"] > 0 else 0
511
+ )
512
+ table.add_row(
513
+ tool,
514
+ str(stats["calls"]),
515
+ str(stats["errors"]),
516
+ f"{avg_duration:.2f}s" if avg_duration > 0 else "N/A",
517
+ )
518
+
519
+ self.console.print(table)
520
+
521
+ def log_iteration_start(self, iteration_number: int) -> None:
522
+ """Log the start of a new iteration."""
523
+ if not self.enabled:
524
+ return
525
+
526
+ self.current_iteration = iteration_number
527
+ iteration_data = {
528
+ "iteration": iteration_number,
529
+ "start_time": time.time(),
530
+ "test_failures": 0,
531
+ "test_fixes": 0,
532
+ "hook_failures": 0,
533
+ "hook_fixes": 0,
534
+ "duration": 0.0,
535
+ }
536
+ self.iteration_stats.append(iteration_data)
537
+
538
+ if self.verbose:
539
+ self.console.print(
540
+ f"[yellow]🔄 Starting Iteration {iteration_number}[/yellow]",
541
+ )
542
+
543
+ def log_iteration_end(self, iteration_number: int, success: bool) -> None:
544
+ """Log the end of an iteration with statistics."""
545
+ if not self.enabled or not self.iteration_stats:
546
+ return
547
+
548
+ # Find the iteration data
549
+ iteration_data = None
550
+ for data in self.iteration_stats:
551
+ if data["iteration"] == iteration_number:
552
+ iteration_data = data
553
+ break
554
+
555
+ if iteration_data:
556
+ iteration_data["duration"] = time.time() - iteration_data["start_time"]
557
+
558
+ if self.verbose:
559
+ status = "✅ PASSED" if success else "❌ FAILED"
560
+ self.console.print(
561
+ f"[{'green' if success else 'red'}]🏁 Iteration {iteration_number} {status}[/{'green' if success else 'red'}]",
562
+ )
563
+
564
+ def log_test_failures(self, count: int) -> None:
565
+ """Log test failure count for current iteration."""
566
+ if not self.enabled or not self.iteration_stats:
567
+ return
568
+
569
+ if self.iteration_stats:
570
+ self.iteration_stats[-1]["test_failures"] = count
571
+ self.total_test_failures += count
572
+
573
+ def log_test_fixes(self, count: int) -> None:
574
+ """Log test fix count for current iteration."""
575
+ if not self.enabled or not self.iteration_stats:
576
+ return
577
+
578
+ if self.iteration_stats:
579
+ self.iteration_stats[-1]["test_fixes"] = count
580
+ self.total_test_fixes += count
581
+
582
+ def log_hook_failures(self, count: int) -> None:
583
+ """Log hook failure count for current iteration."""
584
+ if not self.enabled or not self.iteration_stats:
585
+ return
586
+
587
+ if self.iteration_stats:
588
+ self.iteration_stats[-1]["hook_failures"] = count
589
+ self.total_hook_failures += count
590
+
591
+ def log_hook_fixes(self, count: int) -> None:
592
+ """Log hook fix count for current iteration."""
593
+ if not self.enabled or not self.iteration_stats:
594
+ return
595
+
596
+ if self.iteration_stats:
597
+ self.iteration_stats[-1]["hook_fixes"] = count
598
+ self.total_hook_fixes += count
599
+
600
+ def set_workflow_success(self, success: bool) -> None:
601
+ """Set the overall workflow success status."""
602
+ if not self.enabled:
603
+ return
604
+
605
+ self.workflow_success = success
606
+
607
+ def export_debug_data(self, output_path: Path | None = None) -> Path:
608
+ if not self.enabled:
609
+ return Path("debug_not_enabled.json")
610
+
611
+ if output_path is None:
612
+ output_path = Path(f"crackerjack-debug-export-{self.session_id}.json")
613
+
614
+ debug_data = {
615
+ "session_id": self.session_id,
616
+ "timestamp": datetime.now().isoformat(),
617
+ "mcp_operations": self.mcp_operations,
618
+ "agent_activities": self.agent_activities,
619
+ "workflow_phases": self.workflow_phases,
620
+ "error_events": self.error_events,
621
+ }
622
+
623
+ with output_path.open("w") as f:
624
+ json.dump(debug_data, f, indent=2, default=str)
625
+
626
+ self.logger.info(f"Debug data exported to {output_path}")
627
+ return output_path
628
+
629
+
630
+ class NoOpDebugger:
631
+ def __init__(self) -> None:
632
+ self.enabled = False
633
+ self.verbose = False
634
+ self.debug_log_path = None
635
+ self.session_id = "disabled"
636
+
637
+ def debug_operation(self, operation: str, **kwargs: Any) -> t.Iterator[str]:
638
+ yield ""
639
+
640
+ def log_mcp_operation(
641
+ self,
642
+ operation_type: str,
643
+ tool_name: str,
644
+ params: dict[str, Any] | None = None,
645
+ result: dict[str, Any] | None = None,
646
+ error: str | None = None,
647
+ duration: float | None = None,
648
+ ) -> None:
649
+ pass
650
+
651
+ def log_agent_activity(
652
+ self,
653
+ agent_name: str,
654
+ activity: str,
655
+ issue_id: str | None = None,
656
+ confidence: float | None = None,
657
+ result: dict[str, Any] | None = None,
658
+ metadata: dict[str, Any] | None = None,
659
+ ) -> None:
660
+ pass
661
+
662
+ def log_workflow_phase(
663
+ self,
664
+ phase: str,
665
+ status: str,
666
+ details: dict[str, Any] | None = None,
667
+ duration: float | None = None,
668
+ ) -> None:
669
+ pass
670
+
671
+ def log_error_event(
672
+ self,
673
+ error_type: str,
674
+ message: str,
675
+ context: dict[str, Any] | None = None,
676
+ traceback_info: str | None = None,
677
+ ) -> None:
678
+ pass
679
+
680
+ def print_debug_summary(self) -> None:
681
+ pass
682
+
683
+ def export_debug_data(self, output_path: Path | None = None) -> Path:
684
+ return Path("debug_not_enabled.json")
685
+
686
+ def log_iteration_start(self, iteration_number: int) -> None:
687
+ pass
688
+
689
+ def log_iteration_end(self, iteration_number: int, success: bool) -> None:
690
+ pass
691
+
692
+ def log_test_failures(self, count: int) -> None:
693
+ pass
694
+
695
+ def log_test_fixes(self, count: int) -> None:
696
+ pass
697
+
698
+ def log_hook_failures(self, count: int) -> None:
699
+ pass
700
+
701
+ def log_hook_fixes(self, count: int) -> None:
702
+ pass
703
+
704
+ def set_workflow_success(self, success: bool) -> None:
705
+ pass
706
+
707
+
708
+ _ai_agent_debugger: AIAgentDebugger | NoOpDebugger | None = None
709
+
710
+
711
+ def get_ai_agent_debugger() -> AIAgentDebugger | NoOpDebugger:
712
+ global _ai_agent_debugger
713
+ if _ai_agent_debugger is None:
714
+ debug_enabled = os.environ.get("AI_AGENT_DEBUG", "0") == "1"
715
+ verbose_mode = os.environ.get("AI_AGENT_VERBOSE", "0") == "1"
716
+
717
+ if debug_enabled:
718
+ _ai_agent_debugger = AIAgentDebugger(
719
+ enabled=debug_enabled,
720
+ verbose=verbose_mode,
721
+ )
722
+ else:
723
+ _ai_agent_debugger = NoOpDebugger()
724
+ return _ai_agent_debugger
725
+
726
+
727
+ def enable_ai_agent_debugging(verbose: bool = False) -> AIAgentDebugger:
728
+ global _ai_agent_debugger
729
+ _ai_agent_debugger = AIAgentDebugger(enabled=True, verbose=verbose)
730
+ return _ai_agent_debugger
731
+
732
+
733
+ def disable_ai_agent_debugging() -> None:
734
+ global _ai_agent_debugger
735
+ if _ai_agent_debugger:
736
+ _ai_agent_debugger.enabled = False