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.

Files changed (156) hide show
  1. crackerjack/CLAUDE.md +1005 -0
  2. crackerjack/RULES.md +380 -0
  3. crackerjack/__init__.py +42 -13
  4. crackerjack/__main__.py +227 -299
  5. crackerjack/agents/__init__.py +41 -0
  6. crackerjack/agents/architect_agent.py +281 -0
  7. crackerjack/agents/base.py +170 -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 +657 -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 +409 -0
  26. crackerjack/cli/utils.py +18 -0
  27. crackerjack/code_cleaner.py +618 -928
  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 +585 -0
  37. crackerjack/core/proactive_workflow.py +316 -0
  38. crackerjack/core/session_coordinator.py +289 -0
  39. crackerjack/core/workflow_orchestrator.py +826 -0
  40. crackerjack/dynamic_config.py +94 -103
  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 +433 -0
  58. crackerjack/managers/test_command_builder.py +151 -0
  59. crackerjack/managers/test_executor.py +443 -0
  60. crackerjack/managers/test_manager.py +258 -0
  61. crackerjack/managers/test_manager_backup.py +1124 -0
  62. crackerjack/managers/test_progress.py +114 -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 +621 -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 +372 -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 +217 -0
  88. crackerjack/mcp/tools/utility_tools.py +341 -0
  89. crackerjack/mcp/tools/workflow_executor.py +565 -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/coverage_improvement.py +223 -0
  107. crackerjack/orchestration/execution_strategies.py +341 -0
  108. crackerjack/orchestration/test_progress_streamer.py +636 -0
  109. crackerjack/plugins/__init__.py +15 -0
  110. crackerjack/plugins/base.py +200 -0
  111. crackerjack/plugins/hooks.py +246 -0
  112. crackerjack/plugins/loader.py +335 -0
  113. crackerjack/plugins/managers.py +259 -0
  114. crackerjack/py313.py +8 -3
  115. crackerjack/services/__init__.py +22 -0
  116. crackerjack/services/cache.py +314 -0
  117. crackerjack/services/config.py +358 -0
  118. crackerjack/services/config_integrity.py +99 -0
  119. crackerjack/services/contextual_ai_assistant.py +516 -0
  120. crackerjack/services/coverage_ratchet.py +356 -0
  121. crackerjack/services/debug.py +736 -0
  122. crackerjack/services/dependency_monitor.py +617 -0
  123. crackerjack/services/enhanced_filesystem.py +439 -0
  124. crackerjack/services/file_hasher.py +151 -0
  125. crackerjack/services/filesystem.py +421 -0
  126. crackerjack/services/git.py +176 -0
  127. crackerjack/services/health_metrics.py +611 -0
  128. crackerjack/services/initialization.py +873 -0
  129. crackerjack/services/log_manager.py +286 -0
  130. crackerjack/services/logging.py +174 -0
  131. crackerjack/services/metrics.py +578 -0
  132. crackerjack/services/pattern_cache.py +362 -0
  133. crackerjack/services/pattern_detector.py +515 -0
  134. crackerjack/services/performance_benchmarks.py +653 -0
  135. crackerjack/services/security.py +163 -0
  136. crackerjack/services/server_manager.py +234 -0
  137. crackerjack/services/smart_scheduling.py +144 -0
  138. crackerjack/services/tool_version_service.py +61 -0
  139. crackerjack/services/unified_config.py +437 -0
  140. crackerjack/services/version_checker.py +248 -0
  141. crackerjack/slash_commands/__init__.py +14 -0
  142. crackerjack/slash_commands/init.md +122 -0
  143. crackerjack/slash_commands/run.md +163 -0
  144. crackerjack/slash_commands/status.md +127 -0
  145. crackerjack-0.31.7.dist-info/METADATA +742 -0
  146. crackerjack-0.31.7.dist-info/RECORD +149 -0
  147. crackerjack-0.31.7.dist-info/entry_points.txt +2 -0
  148. crackerjack/.gitignore +0 -34
  149. crackerjack/.libcst.codemod.yaml +0 -18
  150. crackerjack/.pdm.toml +0 -1
  151. crackerjack/crackerjack.py +0 -3805
  152. crackerjack/pyproject.toml +0 -286
  153. crackerjack-0.30.3.dist-info/METADATA +0 -1290
  154. crackerjack-0.30.3.dist-info/RECORD +0 -16
  155. {crackerjack-0.30.3.dist-info → crackerjack-0.31.7.dist-info}/WHEEL +0 -0
  156. {crackerjack-0.30.3.dist-info → crackerjack-0.31.7.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'}")