crackerjack 0.30.3__py3-none-any.whl → 0.31.7__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 +1005 -0
- crackerjack/RULES.md +380 -0
- crackerjack/__init__.py +42 -13
- crackerjack/__main__.py +227 -299
- crackerjack/agents/__init__.py +41 -0
- crackerjack/agents/architect_agent.py +281 -0
- crackerjack/agents/base.py +170 -0
- crackerjack/agents/coordinator.py +512 -0
- crackerjack/agents/documentation_agent.py +498 -0
- crackerjack/agents/dry_agent.py +388 -0
- crackerjack/agents/formatting_agent.py +245 -0
- crackerjack/agents/import_optimization_agent.py +281 -0
- crackerjack/agents/performance_agent.py +669 -0
- crackerjack/agents/proactive_agent.py +104 -0
- crackerjack/agents/refactoring_agent.py +788 -0
- crackerjack/agents/security_agent.py +529 -0
- crackerjack/agents/test_creation_agent.py +657 -0
- crackerjack/agents/test_specialist_agent.py +486 -0
- crackerjack/agents/tracker.py +212 -0
- crackerjack/api.py +560 -0
- crackerjack/cli/__init__.py +24 -0
- crackerjack/cli/facade.py +104 -0
- crackerjack/cli/handlers.py +267 -0
- crackerjack/cli/interactive.py +471 -0
- crackerjack/cli/options.py +409 -0
- crackerjack/cli/utils.py +18 -0
- crackerjack/code_cleaner.py +618 -928
- crackerjack/config/__init__.py +19 -0
- crackerjack/config/hooks.py +218 -0
- crackerjack/core/__init__.py +0 -0
- crackerjack/core/async_workflow_orchestrator.py +406 -0
- crackerjack/core/autofix_coordinator.py +200 -0
- crackerjack/core/container.py +104 -0
- crackerjack/core/enhanced_container.py +542 -0
- crackerjack/core/performance.py +243 -0
- crackerjack/core/phase_coordinator.py +585 -0
- crackerjack/core/proactive_workflow.py +316 -0
- crackerjack/core/session_coordinator.py +289 -0
- crackerjack/core/workflow_orchestrator.py +826 -0
- crackerjack/dynamic_config.py +94 -103
- crackerjack/errors.py +263 -41
- crackerjack/executors/__init__.py +11 -0
- crackerjack/executors/async_hook_executor.py +431 -0
- crackerjack/executors/cached_hook_executor.py +242 -0
- crackerjack/executors/hook_executor.py +345 -0
- crackerjack/executors/individual_hook_executor.py +669 -0
- crackerjack/intelligence/__init__.py +44 -0
- crackerjack/intelligence/adaptive_learning.py +751 -0
- crackerjack/intelligence/agent_orchestrator.py +551 -0
- crackerjack/intelligence/agent_registry.py +414 -0
- crackerjack/intelligence/agent_selector.py +502 -0
- crackerjack/intelligence/integration.py +290 -0
- crackerjack/interactive.py +576 -315
- crackerjack/managers/__init__.py +11 -0
- crackerjack/managers/async_hook_manager.py +135 -0
- crackerjack/managers/hook_manager.py +137 -0
- crackerjack/managers/publish_manager.py +433 -0
- crackerjack/managers/test_command_builder.py +151 -0
- crackerjack/managers/test_executor.py +443 -0
- crackerjack/managers/test_manager.py +258 -0
- crackerjack/managers/test_manager_backup.py +1124 -0
- crackerjack/managers/test_progress.py +114 -0
- crackerjack/mcp/__init__.py +0 -0
- crackerjack/mcp/cache.py +336 -0
- crackerjack/mcp/client_runner.py +104 -0
- crackerjack/mcp/context.py +621 -0
- crackerjack/mcp/dashboard.py +636 -0
- crackerjack/mcp/enhanced_progress_monitor.py +479 -0
- crackerjack/mcp/file_monitor.py +336 -0
- crackerjack/mcp/progress_components.py +569 -0
- crackerjack/mcp/progress_monitor.py +949 -0
- crackerjack/mcp/rate_limiter.py +332 -0
- crackerjack/mcp/server.py +22 -0
- crackerjack/mcp/server_core.py +244 -0
- crackerjack/mcp/service_watchdog.py +501 -0
- crackerjack/mcp/state.py +395 -0
- crackerjack/mcp/task_manager.py +257 -0
- crackerjack/mcp/tools/__init__.py +17 -0
- crackerjack/mcp/tools/core_tools.py +249 -0
- crackerjack/mcp/tools/error_analyzer.py +308 -0
- crackerjack/mcp/tools/execution_tools.py +372 -0
- crackerjack/mcp/tools/execution_tools_backup.py +1097 -0
- crackerjack/mcp/tools/intelligence_tool_registry.py +80 -0
- crackerjack/mcp/tools/intelligence_tools.py +314 -0
- crackerjack/mcp/tools/monitoring_tools.py +502 -0
- crackerjack/mcp/tools/proactive_tools.py +384 -0
- crackerjack/mcp/tools/progress_tools.py +217 -0
- crackerjack/mcp/tools/utility_tools.py +341 -0
- crackerjack/mcp/tools/workflow_executor.py +565 -0
- crackerjack/mcp/websocket/__init__.py +14 -0
- crackerjack/mcp/websocket/app.py +39 -0
- crackerjack/mcp/websocket/endpoints.py +559 -0
- crackerjack/mcp/websocket/jobs.py +253 -0
- crackerjack/mcp/websocket/server.py +116 -0
- crackerjack/mcp/websocket/websocket_handler.py +78 -0
- crackerjack/mcp/websocket_server.py +10 -0
- crackerjack/models/__init__.py +31 -0
- crackerjack/models/config.py +93 -0
- crackerjack/models/config_adapter.py +230 -0
- crackerjack/models/protocols.py +118 -0
- crackerjack/models/task.py +154 -0
- crackerjack/monitoring/ai_agent_watchdog.py +450 -0
- crackerjack/monitoring/regression_prevention.py +638 -0
- crackerjack/orchestration/__init__.py +0 -0
- crackerjack/orchestration/advanced_orchestrator.py +970 -0
- crackerjack/orchestration/coverage_improvement.py +223 -0
- crackerjack/orchestration/execution_strategies.py +341 -0
- crackerjack/orchestration/test_progress_streamer.py +636 -0
- crackerjack/plugins/__init__.py +15 -0
- crackerjack/plugins/base.py +200 -0
- crackerjack/plugins/hooks.py +246 -0
- crackerjack/plugins/loader.py +335 -0
- crackerjack/plugins/managers.py +259 -0
- crackerjack/py313.py +8 -3
- crackerjack/services/__init__.py +22 -0
- crackerjack/services/cache.py +314 -0
- crackerjack/services/config.py +358 -0
- crackerjack/services/config_integrity.py +99 -0
- crackerjack/services/contextual_ai_assistant.py +516 -0
- crackerjack/services/coverage_ratchet.py +356 -0
- crackerjack/services/debug.py +736 -0
- crackerjack/services/dependency_monitor.py +617 -0
- crackerjack/services/enhanced_filesystem.py +439 -0
- crackerjack/services/file_hasher.py +151 -0
- crackerjack/services/filesystem.py +421 -0
- crackerjack/services/git.py +176 -0
- crackerjack/services/health_metrics.py +611 -0
- crackerjack/services/initialization.py +873 -0
- crackerjack/services/log_manager.py +286 -0
- crackerjack/services/logging.py +174 -0
- crackerjack/services/metrics.py +578 -0
- crackerjack/services/pattern_cache.py +362 -0
- crackerjack/services/pattern_detector.py +515 -0
- crackerjack/services/performance_benchmarks.py +653 -0
- crackerjack/services/security.py +163 -0
- crackerjack/services/server_manager.py +234 -0
- crackerjack/services/smart_scheduling.py +144 -0
- crackerjack/services/tool_version_service.py +61 -0
- crackerjack/services/unified_config.py +437 -0
- crackerjack/services/version_checker.py +248 -0
- crackerjack/slash_commands/__init__.py +14 -0
- crackerjack/slash_commands/init.md +122 -0
- crackerjack/slash_commands/run.md +163 -0
- crackerjack/slash_commands/status.md +127 -0
- crackerjack-0.31.7.dist-info/METADATA +742 -0
- crackerjack-0.31.7.dist-info/RECORD +149 -0
- crackerjack-0.31.7.dist-info/entry_points.txt +2 -0
- crackerjack/.gitignore +0 -34
- crackerjack/.libcst.codemod.yaml +0 -18
- crackerjack/.pdm.toml +0 -1
- crackerjack/crackerjack.py +0 -3805
- crackerjack/pyproject.toml +0 -286
- crackerjack-0.30.3.dist-info/METADATA +0 -1290
- crackerjack-0.30.3.dist-info/RECORD +0 -16
- {crackerjack-0.30.3.dist-info → crackerjack-0.31.7.dist-info}/WHEEL +0 -0
- {crackerjack-0.30.3.dist-info → crackerjack-0.31.7.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
|