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.
- crackerjack/CLAUDE.md +1005 -0
- crackerjack/RULES.md +380 -0
- crackerjack/__init__.py +42 -13
- crackerjack/__main__.py +225 -253
- crackerjack/agents/__init__.py +41 -0
- crackerjack/agents/architect_agent.py +281 -0
- crackerjack/agents/base.py +169 -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 +652 -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 +401 -0
- crackerjack/cli/utils.py +18 -0
- crackerjack/code_cleaner.py +670 -0
- 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 +561 -0
- crackerjack/core/proactive_workflow.py +316 -0
- crackerjack/core/session_coordinator.py +289 -0
- crackerjack/core/workflow_orchestrator.py +640 -0
- crackerjack/dynamic_config.py +577 -0
- 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 +411 -0
- crackerjack/managers/test_command_builder.py +151 -0
- crackerjack/managers/test_executor.py +435 -0
- crackerjack/managers/test_manager.py +258 -0
- crackerjack/managers/test_manager_backup.py +1124 -0
- crackerjack/managers/test_progress.py +144 -0
- crackerjack/mcp/__init__.py +0 -0
- crackerjack/mcp/cache.py +336 -0
- crackerjack/mcp/client_runner.py +104 -0
- crackerjack/mcp/context.py +615 -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 +370 -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 +141 -0
- crackerjack/mcp/tools/utility_tools.py +341 -0
- crackerjack/mcp/tools/workflow_executor.py +360 -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/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 +347 -0
- crackerjack/services/config_integrity.py +99 -0
- crackerjack/services/contextual_ai_assistant.py +516 -0
- crackerjack/services/coverage_ratchet.py +347 -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 +395 -0
- crackerjack/services/git.py +165 -0
- crackerjack/services/health_metrics.py +611 -0
- crackerjack/services/initialization.py +847 -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.4.dist-info/METADATA +742 -0
- crackerjack-0.31.4.dist-info/RECORD +148 -0
- crackerjack-0.31.4.dist-info/entry_points.txt +2 -0
- crackerjack/.gitignore +0 -34
- crackerjack/.libcst.codemod.yaml +0 -18
- crackerjack/.pdm.toml +0 -1
- crackerjack/.pre-commit-config-ai.yaml +0 -149
- crackerjack/.pre-commit-config-fast.yaml +0 -69
- crackerjack/.pre-commit-config.yaml +0 -114
- crackerjack/crackerjack.py +0 -4140
- crackerjack/pyproject.toml +0 -285
- crackerjack-0.29.0.dist-info/METADATA +0 -1289
- crackerjack-0.29.0.dist-info/RECORD +0 -17
- {crackerjack-0.29.0.dist-info → crackerjack-0.31.4.dist-info}/WHEEL +0 -0
- {crackerjack-0.29.0.dist-info → crackerjack-0.31.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import operator
|
|
4
|
+
import typing as t
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
|
|
7
|
+
from crackerjack.services.debug import get_ai_agent_debugger
|
|
8
|
+
|
|
9
|
+
from .base import (
|
|
10
|
+
AgentContext,
|
|
11
|
+
FixResult,
|
|
12
|
+
Issue,
|
|
13
|
+
IssueType,
|
|
14
|
+
Priority,
|
|
15
|
+
SubAgent,
|
|
16
|
+
agent_registry,
|
|
17
|
+
)
|
|
18
|
+
from .tracker import get_agent_tracker
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AgentCoordinator:
|
|
22
|
+
def __init__(self, context: AgentContext) -> None:
|
|
23
|
+
self.context = context
|
|
24
|
+
self.agents: list[SubAgent] = []
|
|
25
|
+
self.logger = logging.getLogger(__name__)
|
|
26
|
+
self._issue_cache: dict[str, FixResult] = {}
|
|
27
|
+
self._collaboration_threshold = 0.7
|
|
28
|
+
self.tracker = get_agent_tracker()
|
|
29
|
+
self.debugger = get_ai_agent_debugger()
|
|
30
|
+
self.proactive_mode = True # Enabled by default
|
|
31
|
+
|
|
32
|
+
def initialize_agents(self) -> None:
|
|
33
|
+
self.agents = agent_registry.create_all(self.context)
|
|
34
|
+
agent_types = [a.name for a in self.agents]
|
|
35
|
+
self.logger.info(f"Initialized {len(self.agents)} agents: {agent_types}")
|
|
36
|
+
|
|
37
|
+
self.tracker.register_agents(agent_types)
|
|
38
|
+
self.tracker.set_coordinator_status("active")
|
|
39
|
+
|
|
40
|
+
self.debugger.log_agent_activity(
|
|
41
|
+
agent_name="coordinator",
|
|
42
|
+
activity="agents_initialized",
|
|
43
|
+
metadata={"agent_count": len(self.agents), "agent_types": agent_types},
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
async def handle_issues(self, issues: list[Issue]) -> FixResult:
|
|
47
|
+
if not self.agents:
|
|
48
|
+
self.initialize_agents()
|
|
49
|
+
|
|
50
|
+
if not issues:
|
|
51
|
+
return FixResult(success=True, confidence=1.0)
|
|
52
|
+
|
|
53
|
+
self.logger.info(f"Handling {len(issues)} issues")
|
|
54
|
+
|
|
55
|
+
issues_by_type = self._group_issues_by_type(issues)
|
|
56
|
+
|
|
57
|
+
overall_result = FixResult(success=True, confidence=1.0)
|
|
58
|
+
|
|
59
|
+
for issue_type, type_issues in issues_by_type.items():
|
|
60
|
+
type_result = await self._handle_issues_by_type(issue_type, type_issues)
|
|
61
|
+
overall_result = overall_result.merge_with(type_result)
|
|
62
|
+
|
|
63
|
+
return overall_result
|
|
64
|
+
|
|
65
|
+
async def handle_single_issue(self, issue: Issue) -> FixResult:
|
|
66
|
+
if not self.agents:
|
|
67
|
+
self.initialize_agents()
|
|
68
|
+
|
|
69
|
+
cache_key = issue.context_key
|
|
70
|
+
if cache_key in self._issue_cache:
|
|
71
|
+
cached_result = self._issue_cache[cache_key]
|
|
72
|
+
self.logger.info(f"Using cached result for {cache_key}")
|
|
73
|
+
self.tracker.track_cache_hit()
|
|
74
|
+
return cached_result
|
|
75
|
+
|
|
76
|
+
self.tracker.track_cache_miss()
|
|
77
|
+
|
|
78
|
+
agent_scores = await self._evaluate_agents_for_issue(issue)
|
|
79
|
+
|
|
80
|
+
if not agent_scores:
|
|
81
|
+
self.logger.warning(f"No agents can handle issue: {issue.message}")
|
|
82
|
+
return FixResult(
|
|
83
|
+
success=False,
|
|
84
|
+
confidence=0.0,
|
|
85
|
+
remaining_issues=[f"No agent available for: {issue.message}"],
|
|
86
|
+
recommendations=["Manual intervention required"],
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
best_agent, best_score = agent_scores[0]
|
|
90
|
+
|
|
91
|
+
if best_score >= self._collaboration_threshold:
|
|
92
|
+
result = await self._handle_with_single_agent(best_agent, issue)
|
|
93
|
+
else:
|
|
94
|
+
result = await self._handle_with_collaboration(agent_scores[:3], issue)
|
|
95
|
+
|
|
96
|
+
self._issue_cache[cache_key] = result
|
|
97
|
+
|
|
98
|
+
return result
|
|
99
|
+
|
|
100
|
+
async def _handle_issues_by_type(
|
|
101
|
+
self,
|
|
102
|
+
issue_type: IssueType,
|
|
103
|
+
issues: list[Issue],
|
|
104
|
+
) -> FixResult:
|
|
105
|
+
self.logger.info(f"Handling {len(issues)} {issue_type.value} issues")
|
|
106
|
+
|
|
107
|
+
specialist_agents = [
|
|
108
|
+
agent for agent in self.agents if issue_type in agent.get_supported_types()
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
if not specialist_agents:
|
|
112
|
+
self.logger.warning(f"No specialist agents for {issue_type.value}")
|
|
113
|
+
return FixResult(
|
|
114
|
+
success=False,
|
|
115
|
+
confidence=0.0,
|
|
116
|
+
remaining_issues=[f"No agents for {issue_type.value} issues"],
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
tasks: list[t.Coroutine[t.Any, t.Any, FixResult]] = []
|
|
120
|
+
for issue in issues:
|
|
121
|
+
best_specialist = await self._find_best_specialist(specialist_agents, issue)
|
|
122
|
+
if best_specialist:
|
|
123
|
+
task = self._handle_with_single_agent(best_specialist, issue)
|
|
124
|
+
tasks.append(task)
|
|
125
|
+
|
|
126
|
+
if not tasks:
|
|
127
|
+
return FixResult(success=False, confidence=0.0)
|
|
128
|
+
|
|
129
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
130
|
+
|
|
131
|
+
combined_result = FixResult(success=True, confidence=1.0)
|
|
132
|
+
for result in results:
|
|
133
|
+
if isinstance(result, FixResult):
|
|
134
|
+
combined_result = combined_result.merge_with(result)
|
|
135
|
+
else:
|
|
136
|
+
self.logger.error(f"Agent task failed: {result}")
|
|
137
|
+
combined_result.success = False
|
|
138
|
+
combined_result.remaining_issues.append(f"Agent failed: {result}")
|
|
139
|
+
|
|
140
|
+
return combined_result
|
|
141
|
+
|
|
142
|
+
async def _evaluate_agents_for_issue(
|
|
143
|
+
self,
|
|
144
|
+
issue: Issue,
|
|
145
|
+
) -> list[tuple[SubAgent, float]]:
|
|
146
|
+
evaluations: list[tuple[SubAgent, float]] = []
|
|
147
|
+
|
|
148
|
+
for agent in self.agents:
|
|
149
|
+
try:
|
|
150
|
+
confidence = await agent.can_handle(issue)
|
|
151
|
+
if confidence > 0.0:
|
|
152
|
+
evaluations.append((agent, confidence))
|
|
153
|
+
except Exception as e:
|
|
154
|
+
self.logger.exception(f"Error evaluating {agent.name} for issue: {e}")
|
|
155
|
+
|
|
156
|
+
evaluations.sort(key=operator.itemgetter(1), reverse=True)
|
|
157
|
+
return evaluations
|
|
158
|
+
|
|
159
|
+
async def _find_best_specialist(
|
|
160
|
+
self,
|
|
161
|
+
specialists: list[SubAgent],
|
|
162
|
+
issue: Issue,
|
|
163
|
+
) -> SubAgent | None:
|
|
164
|
+
best_agent = None
|
|
165
|
+
best_score = 0.0
|
|
166
|
+
|
|
167
|
+
for agent in specialists:
|
|
168
|
+
try:
|
|
169
|
+
score = await agent.can_handle(issue)
|
|
170
|
+
if score > best_score:
|
|
171
|
+
best_score = score
|
|
172
|
+
best_agent = agent
|
|
173
|
+
except Exception as e:
|
|
174
|
+
self.logger.exception(f"Error evaluating specialist {agent.name}: {e}")
|
|
175
|
+
|
|
176
|
+
return best_agent
|
|
177
|
+
|
|
178
|
+
async def _handle_with_single_agent(
|
|
179
|
+
self,
|
|
180
|
+
agent: SubAgent,
|
|
181
|
+
issue: Issue,
|
|
182
|
+
) -> FixResult:
|
|
183
|
+
self.logger.info(f"Handling issue with {agent.name}: {issue.message[:100]}")
|
|
184
|
+
|
|
185
|
+
confidence = await agent.can_handle(issue)
|
|
186
|
+
self.tracker.track_agent_processing(agent.name, issue, confidence)
|
|
187
|
+
|
|
188
|
+
self.debugger.log_agent_activity(
|
|
189
|
+
agent_name=agent.name,
|
|
190
|
+
activity="processing_started",
|
|
191
|
+
issue_id=issue.id,
|
|
192
|
+
confidence=confidence,
|
|
193
|
+
metadata={"issue_type": issue.type.value, "severity": issue.severity.value},
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
result = await agent.analyze_and_fix(issue)
|
|
198
|
+
if result.success:
|
|
199
|
+
self.logger.info(f"{agent.name} successfully fixed issue")
|
|
200
|
+
else:
|
|
201
|
+
self.logger.warning(f"{agent.name} failed to fix issue")
|
|
202
|
+
|
|
203
|
+
self.tracker.track_agent_complete(agent.name, result)
|
|
204
|
+
|
|
205
|
+
self.debugger.log_agent_activity(
|
|
206
|
+
agent_name=agent.name,
|
|
207
|
+
activity="processing_completed",
|
|
208
|
+
issue_id=issue.id,
|
|
209
|
+
confidence=result.confidence,
|
|
210
|
+
result={
|
|
211
|
+
"success": result.success,
|
|
212
|
+
"remaining_issues": len(result.remaining_issues),
|
|
213
|
+
},
|
|
214
|
+
metadata={"fix_applied": result.success},
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
return result
|
|
218
|
+
except Exception as e:
|
|
219
|
+
self.logger.exception(f"{agent.name} threw exception: {e}")
|
|
220
|
+
error_result = FixResult(
|
|
221
|
+
success=False,
|
|
222
|
+
confidence=0.0,
|
|
223
|
+
remaining_issues=[f"{agent.name} failed with exception: {e}"],
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
self.tracker.track_agent_complete(agent.name, error_result)
|
|
227
|
+
return error_result
|
|
228
|
+
|
|
229
|
+
async def _handle_with_collaboration(
|
|
230
|
+
self,
|
|
231
|
+
agent_scores: list[tuple[SubAgent, float]],
|
|
232
|
+
issue: Issue,
|
|
233
|
+
) -> FixResult:
|
|
234
|
+
self.logger.info(
|
|
235
|
+
f"Using collaborative approach for issue: {issue.message[:100]}",
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
results: list[FixResult] = []
|
|
239
|
+
|
|
240
|
+
for agent, _ in agent_scores:
|
|
241
|
+
try:
|
|
242
|
+
result = await agent.analyze_and_fix(issue)
|
|
243
|
+
results.append(result)
|
|
244
|
+
|
|
245
|
+
if result.success and result.confidence >= 0.8:
|
|
246
|
+
self.logger.info(f"{agent.name} solved issue with high confidence")
|
|
247
|
+
break
|
|
248
|
+
|
|
249
|
+
except Exception as e:
|
|
250
|
+
self.logger.exception(f"Collaborative agent {agent.name} failed: {e}")
|
|
251
|
+
results.append(
|
|
252
|
+
FixResult(
|
|
253
|
+
success=False,
|
|
254
|
+
confidence=0.0,
|
|
255
|
+
remaining_issues=[f"{agent.name} failed: {e}"],
|
|
256
|
+
),
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
if not results:
|
|
260
|
+
return FixResult(success=False, confidence=0.0)
|
|
261
|
+
|
|
262
|
+
combined_result = results[0]
|
|
263
|
+
for result in results[1:]:
|
|
264
|
+
combined_result = combined_result.merge_with(result)
|
|
265
|
+
|
|
266
|
+
return combined_result
|
|
267
|
+
|
|
268
|
+
def _group_issues_by_type(
|
|
269
|
+
self,
|
|
270
|
+
issues: list[Issue],
|
|
271
|
+
) -> dict[IssueType, list[Issue]]:
|
|
272
|
+
grouped: defaultdict[IssueType, list[Issue]] = defaultdict(list)
|
|
273
|
+
for issue in issues:
|
|
274
|
+
grouped[issue.type].append(issue)
|
|
275
|
+
return dict(grouped)
|
|
276
|
+
|
|
277
|
+
def get_agent_capabilities(self) -> dict[str, dict[str, t.Any]]:
|
|
278
|
+
if not self.agents:
|
|
279
|
+
self.initialize_agents()
|
|
280
|
+
|
|
281
|
+
capabilities = {}
|
|
282
|
+
for agent in self.agents:
|
|
283
|
+
capabilities[agent.name] = {
|
|
284
|
+
"supported_types": [t.value for t in agent.get_supported_types()],
|
|
285
|
+
"class": agent.__class__.__name__,
|
|
286
|
+
}
|
|
287
|
+
return capabilities
|
|
288
|
+
|
|
289
|
+
def clear_cache(self) -> None:
|
|
290
|
+
self._issue_cache.clear()
|
|
291
|
+
self.logger.info("Cleared issue cache")
|
|
292
|
+
|
|
293
|
+
async def test_agent_connectivity(self) -> dict[str, bool]:
|
|
294
|
+
if not self.agents:
|
|
295
|
+
self.initialize_agents()
|
|
296
|
+
|
|
297
|
+
test_issue = Issue(
|
|
298
|
+
id="test",
|
|
299
|
+
type=IssueType.FORMATTING,
|
|
300
|
+
severity=Priority.LOW,
|
|
301
|
+
message="Test connectivity",
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
connectivity = {}
|
|
305
|
+
for agent in self.agents:
|
|
306
|
+
try:
|
|
307
|
+
confidence = await agent.can_handle(test_issue)
|
|
308
|
+
connectivity[agent.name] = confidence >= 0.0
|
|
309
|
+
except Exception as e:
|
|
310
|
+
self.logger.exception(f"Connectivity test failed for {agent.name}: {e}")
|
|
311
|
+
connectivity[agent.name] = False
|
|
312
|
+
|
|
313
|
+
return connectivity
|
|
314
|
+
|
|
315
|
+
async def handle_issues_proactively(self, issues: list[Issue]) -> FixResult:
|
|
316
|
+
"""Handle issues with proactive planning phase."""
|
|
317
|
+
if not self.proactive_mode:
|
|
318
|
+
return await self.handle_issues(issues)
|
|
319
|
+
|
|
320
|
+
if not self.agents:
|
|
321
|
+
self.initialize_agents()
|
|
322
|
+
|
|
323
|
+
if not issues:
|
|
324
|
+
return FixResult(success=True, confidence=1.0)
|
|
325
|
+
|
|
326
|
+
self.logger.info(f"Handling {len(issues)} issues with proactive planning")
|
|
327
|
+
|
|
328
|
+
# Phase 1: Create architectural plan
|
|
329
|
+
architectural_plan = await self._create_architectural_plan(issues)
|
|
330
|
+
|
|
331
|
+
# Phase 2: Apply fixes following the plan
|
|
332
|
+
overall_result = await self._apply_fixes_with_plan(issues, architectural_plan)
|
|
333
|
+
|
|
334
|
+
# Phase 3: Validate against plan
|
|
335
|
+
validation_result = await self._validate_against_plan(
|
|
336
|
+
overall_result, architectural_plan
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
return validation_result
|
|
340
|
+
|
|
341
|
+
async def _create_architectural_plan(self, issues: list[Issue]) -> dict[str, t.Any]:
|
|
342
|
+
"""Create architectural plan using ArchitectAgent."""
|
|
343
|
+
architect = self._get_architect_agent()
|
|
344
|
+
|
|
345
|
+
if not architect:
|
|
346
|
+
self.logger.warning("No ArchitectAgent available for planning")
|
|
347
|
+
return {"strategy": "reactive_fallback", "patterns": []}
|
|
348
|
+
|
|
349
|
+
# Analyze all issues together for comprehensive planning
|
|
350
|
+
complex_issues = [
|
|
351
|
+
issue
|
|
352
|
+
for issue in issues
|
|
353
|
+
if issue.type
|
|
354
|
+
in {IssueType.COMPLEXITY, IssueType.DRY_VIOLATION, IssueType.PERFORMANCE}
|
|
355
|
+
]
|
|
356
|
+
|
|
357
|
+
if not complex_issues:
|
|
358
|
+
return {"strategy": "simple_fixes", "patterns": ["standard_patterns"]}
|
|
359
|
+
|
|
360
|
+
# Use the first complex issue for planning (represents the architectural challenge)
|
|
361
|
+
primary_issue = complex_issues[0]
|
|
362
|
+
|
|
363
|
+
try:
|
|
364
|
+
# Get plan from ArchitectAgent
|
|
365
|
+
plan = await architect.plan_before_action(primary_issue)
|
|
366
|
+
|
|
367
|
+
# Extend plan to cover all issues
|
|
368
|
+
plan["all_issues"] = [issue.id for issue in issues]
|
|
369
|
+
plan["issue_types"] = list({issue.type.value for issue in issues})
|
|
370
|
+
|
|
371
|
+
self.logger.info(
|
|
372
|
+
f"Created architectural plan: {plan.get('strategy', 'unknown')}"
|
|
373
|
+
)
|
|
374
|
+
return plan
|
|
375
|
+
|
|
376
|
+
except Exception as e:
|
|
377
|
+
self.logger.exception(f"Failed to create architectural plan: {e}")
|
|
378
|
+
return {"strategy": "reactive_fallback", "patterns": [], "error": str(e)}
|
|
379
|
+
|
|
380
|
+
async def _apply_fixes_with_plan(
|
|
381
|
+
self, issues: list[Issue], plan: dict[str, t.Any]
|
|
382
|
+
) -> FixResult:
|
|
383
|
+
"""Apply fixes following the architectural plan."""
|
|
384
|
+
strategy = plan.get("strategy", "reactive_fallback")
|
|
385
|
+
|
|
386
|
+
if strategy == "reactive_fallback":
|
|
387
|
+
# Fallback to standard processing
|
|
388
|
+
return await self.handle_issues(issues)
|
|
389
|
+
|
|
390
|
+
self.logger.info(f"Applying fixes with {strategy} strategy")
|
|
391
|
+
|
|
392
|
+
# Group issues by priority based on plan
|
|
393
|
+
prioritized_issues = self._prioritize_issues_by_plan(issues, plan)
|
|
394
|
+
|
|
395
|
+
overall_result = FixResult(success=True, confidence=1.0)
|
|
396
|
+
|
|
397
|
+
for issue_group in prioritized_issues:
|
|
398
|
+
group_result = await self._handle_issue_group_with_plan(issue_group, plan)
|
|
399
|
+
overall_result = overall_result.merge_with(group_result)
|
|
400
|
+
|
|
401
|
+
# If a critical group fails, consider the overall strategy
|
|
402
|
+
if not group_result.success and self._is_critical_group(issue_group, plan):
|
|
403
|
+
overall_result.success = False
|
|
404
|
+
overall_result.remaining_issues.append(
|
|
405
|
+
f"Critical issue group failed: {[i.id for i in issue_group]}"
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
return overall_result
|
|
409
|
+
|
|
410
|
+
async def _validate_against_plan(
|
|
411
|
+
self, result: FixResult, plan: dict[str, t.Any]
|
|
412
|
+
) -> FixResult:
|
|
413
|
+
"""Validate the results against the architectural plan."""
|
|
414
|
+
validation_steps = plan.get("validation", [])
|
|
415
|
+
|
|
416
|
+
if not validation_steps:
|
|
417
|
+
return result
|
|
418
|
+
|
|
419
|
+
self.logger.info(f"Validating against plan: {validation_steps}")
|
|
420
|
+
|
|
421
|
+
# Add validation recommendations
|
|
422
|
+
result.recommendations.extend(
|
|
423
|
+
[
|
|
424
|
+
f"Validate with: {', '.join(validation_steps)}",
|
|
425
|
+
f"Applied strategy: {plan.get('strategy', 'unknown')}",
|
|
426
|
+
f"Used patterns: {', '.join(plan.get('patterns', []))}",
|
|
427
|
+
]
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
return result
|
|
431
|
+
|
|
432
|
+
def _get_architect_agent(self) -> SubAgent | None:
|
|
433
|
+
"""Get the ArchitectAgent from available agents."""
|
|
434
|
+
for agent in self.agents:
|
|
435
|
+
if agent.__class__.__name__ == "ArchitectAgent":
|
|
436
|
+
return agent
|
|
437
|
+
return None
|
|
438
|
+
|
|
439
|
+
def _prioritize_issues_by_plan(
|
|
440
|
+
self, issues: list[Issue], plan: dict[str, t.Any]
|
|
441
|
+
) -> list[list[Issue]]:
|
|
442
|
+
"""Prioritize issues based on the architectural plan."""
|
|
443
|
+
strategy = plan.get("strategy", "reactive_fallback")
|
|
444
|
+
|
|
445
|
+
if strategy == "external_specialist_guided":
|
|
446
|
+
# Handle complex issues first, then simple ones
|
|
447
|
+
complex_issues = [
|
|
448
|
+
issue
|
|
449
|
+
for issue in issues
|
|
450
|
+
if issue.type in {IssueType.COMPLEXITY, IssueType.DRY_VIOLATION}
|
|
451
|
+
]
|
|
452
|
+
other_issues = [issue for issue in issues if issue not in complex_issues]
|
|
453
|
+
return [complex_issues, other_issues] if complex_issues else [other_issues]
|
|
454
|
+
|
|
455
|
+
# Default: group by type for parallel processing
|
|
456
|
+
groups = self._group_issues_by_type(issues)
|
|
457
|
+
return list(groups.values())
|
|
458
|
+
|
|
459
|
+
async def _handle_issue_group_with_plan(
|
|
460
|
+
self, issues: list[Issue], plan: dict[str, t.Any]
|
|
461
|
+
) -> FixResult:
|
|
462
|
+
"""Handle a group of issues with architectural guidance."""
|
|
463
|
+
if not issues:
|
|
464
|
+
return FixResult(success=True, confidence=1.0)
|
|
465
|
+
|
|
466
|
+
# Get the best agent for this group, preferring ArchitectAgent for complex issues
|
|
467
|
+
representative_issue = issues[0]
|
|
468
|
+
|
|
469
|
+
if self._should_use_architect_for_group(issues, plan):
|
|
470
|
+
architect = self._get_architect_agent()
|
|
471
|
+
if architect:
|
|
472
|
+
# Use architect for the whole group
|
|
473
|
+
group_result = FixResult(success=True, confidence=1.0)
|
|
474
|
+
|
|
475
|
+
for issue in issues:
|
|
476
|
+
issue_result = await architect.analyze_and_fix(issue)
|
|
477
|
+
group_result = group_result.merge_with(issue_result)
|
|
478
|
+
|
|
479
|
+
return group_result
|
|
480
|
+
|
|
481
|
+
# Use standard routing for simpler issues
|
|
482
|
+
return await self._handle_issues_by_type(representative_issue.type, issues)
|
|
483
|
+
|
|
484
|
+
def _should_use_architect_for_group(
|
|
485
|
+
self, issues: list[Issue], plan: dict[str, t.Any]
|
|
486
|
+
) -> bool:
|
|
487
|
+
"""Determine if ArchitectAgent should handle this group."""
|
|
488
|
+
strategy = plan.get("strategy", "")
|
|
489
|
+
|
|
490
|
+
# Always use architect for specialist-guided strategies
|
|
491
|
+
if strategy == "external_specialist_guided":
|
|
492
|
+
return True
|
|
493
|
+
|
|
494
|
+
# Use architect for complex or architectural issues
|
|
495
|
+
architectural_types = {
|
|
496
|
+
IssueType.COMPLEXITY,
|
|
497
|
+
IssueType.DRY_VIOLATION,
|
|
498
|
+
IssueType.PERFORMANCE,
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return any(issue.type in architectural_types for issue in issues)
|
|
502
|
+
|
|
503
|
+
def _is_critical_group(self, issues: list[Issue], plan: dict[str, t.Any]) -> bool:
|
|
504
|
+
"""Determine if this issue group is critical to the plan."""
|
|
505
|
+
# Complex and DRY violations are typically critical
|
|
506
|
+
critical_types = {IssueType.COMPLEXITY, IssueType.DRY_VIOLATION}
|
|
507
|
+
return any(issue.type in critical_types for issue in issues)
|
|
508
|
+
|
|
509
|
+
def set_proactive_mode(self, enabled: bool) -> None:
|
|
510
|
+
"""Enable or disable proactive planning mode."""
|
|
511
|
+
self.proactive_mode = enabled
|
|
512
|
+
self.logger.info(f"Proactive mode {'enabled' if enabled else 'disabled'}")
|