crackerjack 0.32.0__py3-none-any.whl → 0.33.1__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 (200) hide show
  1. crackerjack/__main__.py +1350 -34
  2. crackerjack/adapters/__init__.py +17 -0
  3. crackerjack/adapters/lsp_client.py +358 -0
  4. crackerjack/adapters/rust_tool_adapter.py +194 -0
  5. crackerjack/adapters/rust_tool_manager.py +193 -0
  6. crackerjack/adapters/skylos_adapter.py +231 -0
  7. crackerjack/adapters/zuban_adapter.py +560 -0
  8. crackerjack/agents/base.py +7 -3
  9. crackerjack/agents/coordinator.py +271 -33
  10. crackerjack/agents/documentation_agent.py +9 -15
  11. crackerjack/agents/dry_agent.py +3 -15
  12. crackerjack/agents/formatting_agent.py +1 -1
  13. crackerjack/agents/import_optimization_agent.py +36 -180
  14. crackerjack/agents/performance_agent.py +17 -98
  15. crackerjack/agents/performance_helpers.py +7 -31
  16. crackerjack/agents/proactive_agent.py +1 -3
  17. crackerjack/agents/refactoring_agent.py +16 -85
  18. crackerjack/agents/refactoring_helpers.py +7 -42
  19. crackerjack/agents/security_agent.py +9 -48
  20. crackerjack/agents/test_creation_agent.py +356 -513
  21. crackerjack/agents/test_specialist_agent.py +0 -4
  22. crackerjack/api.py +6 -25
  23. crackerjack/cli/cache_handlers.py +204 -0
  24. crackerjack/cli/cache_handlers_enhanced.py +683 -0
  25. crackerjack/cli/facade.py +100 -0
  26. crackerjack/cli/handlers.py +224 -9
  27. crackerjack/cli/interactive.py +6 -4
  28. crackerjack/cli/options.py +642 -55
  29. crackerjack/cli/utils.py +2 -1
  30. crackerjack/code_cleaner.py +58 -117
  31. crackerjack/config/global_lock_config.py +8 -48
  32. crackerjack/config/hooks.py +53 -62
  33. crackerjack/core/async_workflow_orchestrator.py +24 -34
  34. crackerjack/core/autofix_coordinator.py +3 -17
  35. crackerjack/core/enhanced_container.py +64 -6
  36. crackerjack/core/file_lifecycle.py +12 -89
  37. crackerjack/core/performance.py +2 -2
  38. crackerjack/core/performance_monitor.py +15 -55
  39. crackerjack/core/phase_coordinator.py +257 -218
  40. crackerjack/core/resource_manager.py +14 -90
  41. crackerjack/core/service_watchdog.py +62 -95
  42. crackerjack/core/session_coordinator.py +149 -0
  43. crackerjack/core/timeout_manager.py +14 -72
  44. crackerjack/core/websocket_lifecycle.py +13 -78
  45. crackerjack/core/workflow_orchestrator.py +558 -240
  46. crackerjack/docs/INDEX.md +11 -0
  47. crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
  48. crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
  49. crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
  50. crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
  51. crackerjack/docs/generated/api/SERVICES.md +1252 -0
  52. crackerjack/documentation/__init__.py +31 -0
  53. crackerjack/documentation/ai_templates.py +756 -0
  54. crackerjack/documentation/dual_output_generator.py +765 -0
  55. crackerjack/documentation/mkdocs_integration.py +518 -0
  56. crackerjack/documentation/reference_generator.py +977 -0
  57. crackerjack/dynamic_config.py +55 -50
  58. crackerjack/executors/async_hook_executor.py +10 -15
  59. crackerjack/executors/cached_hook_executor.py +117 -43
  60. crackerjack/executors/hook_executor.py +8 -34
  61. crackerjack/executors/hook_lock_manager.py +26 -183
  62. crackerjack/executors/individual_hook_executor.py +13 -11
  63. crackerjack/executors/lsp_aware_hook_executor.py +270 -0
  64. crackerjack/executors/tool_proxy.py +417 -0
  65. crackerjack/hooks/lsp_hook.py +79 -0
  66. crackerjack/intelligence/adaptive_learning.py +25 -10
  67. crackerjack/intelligence/agent_orchestrator.py +2 -5
  68. crackerjack/intelligence/agent_registry.py +34 -24
  69. crackerjack/intelligence/agent_selector.py +5 -7
  70. crackerjack/interactive.py +17 -6
  71. crackerjack/managers/async_hook_manager.py +0 -1
  72. crackerjack/managers/hook_manager.py +79 -1
  73. crackerjack/managers/publish_manager.py +66 -13
  74. crackerjack/managers/test_command_builder.py +5 -17
  75. crackerjack/managers/test_executor.py +1 -3
  76. crackerjack/managers/test_manager.py +109 -7
  77. crackerjack/managers/test_manager_backup.py +10 -9
  78. crackerjack/mcp/cache.py +2 -2
  79. crackerjack/mcp/client_runner.py +1 -1
  80. crackerjack/mcp/context.py +191 -68
  81. crackerjack/mcp/dashboard.py +7 -5
  82. crackerjack/mcp/enhanced_progress_monitor.py +31 -28
  83. crackerjack/mcp/file_monitor.py +30 -23
  84. crackerjack/mcp/progress_components.py +31 -21
  85. crackerjack/mcp/progress_monitor.py +50 -53
  86. crackerjack/mcp/rate_limiter.py +6 -6
  87. crackerjack/mcp/server_core.py +161 -32
  88. crackerjack/mcp/service_watchdog.py +2 -1
  89. crackerjack/mcp/state.py +4 -7
  90. crackerjack/mcp/task_manager.py +11 -9
  91. crackerjack/mcp/tools/core_tools.py +174 -33
  92. crackerjack/mcp/tools/error_analyzer.py +3 -2
  93. crackerjack/mcp/tools/execution_tools.py +15 -12
  94. crackerjack/mcp/tools/execution_tools_backup.py +42 -30
  95. crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
  96. crackerjack/mcp/tools/intelligence_tools.py +5 -2
  97. crackerjack/mcp/tools/monitoring_tools.py +33 -70
  98. crackerjack/mcp/tools/proactive_tools.py +24 -11
  99. crackerjack/mcp/tools/progress_tools.py +5 -8
  100. crackerjack/mcp/tools/utility_tools.py +20 -14
  101. crackerjack/mcp/tools/workflow_executor.py +62 -40
  102. crackerjack/mcp/websocket/app.py +8 -0
  103. crackerjack/mcp/websocket/endpoints.py +352 -357
  104. crackerjack/mcp/websocket/jobs.py +40 -57
  105. crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
  106. crackerjack/mcp/websocket/server.py +7 -25
  107. crackerjack/mcp/websocket/websocket_handler.py +6 -17
  108. crackerjack/mixins/__init__.py +3 -0
  109. crackerjack/mixins/error_handling.py +145 -0
  110. crackerjack/models/config.py +21 -1
  111. crackerjack/models/config_adapter.py +49 -1
  112. crackerjack/models/protocols.py +176 -107
  113. crackerjack/models/resource_protocols.py +55 -210
  114. crackerjack/models/task.py +3 -0
  115. crackerjack/monitoring/ai_agent_watchdog.py +13 -13
  116. crackerjack/monitoring/metrics_collector.py +426 -0
  117. crackerjack/monitoring/regression_prevention.py +8 -8
  118. crackerjack/monitoring/websocket_server.py +643 -0
  119. crackerjack/orchestration/advanced_orchestrator.py +11 -6
  120. crackerjack/orchestration/coverage_improvement.py +3 -3
  121. crackerjack/orchestration/execution_strategies.py +26 -6
  122. crackerjack/orchestration/test_progress_streamer.py +8 -5
  123. crackerjack/plugins/base.py +2 -2
  124. crackerjack/plugins/hooks.py +7 -0
  125. crackerjack/plugins/managers.py +11 -8
  126. crackerjack/security/__init__.py +0 -1
  127. crackerjack/security/audit.py +90 -105
  128. crackerjack/services/anomaly_detector.py +392 -0
  129. crackerjack/services/api_extractor.py +615 -0
  130. crackerjack/services/backup_service.py +2 -2
  131. crackerjack/services/bounded_status_operations.py +15 -152
  132. crackerjack/services/cache.py +127 -1
  133. crackerjack/services/changelog_automation.py +395 -0
  134. crackerjack/services/config.py +18 -11
  135. crackerjack/services/config_merge.py +30 -85
  136. crackerjack/services/config_template.py +506 -0
  137. crackerjack/services/contextual_ai_assistant.py +48 -22
  138. crackerjack/services/coverage_badge_service.py +171 -0
  139. crackerjack/services/coverage_ratchet.py +41 -17
  140. crackerjack/services/debug.py +3 -3
  141. crackerjack/services/dependency_analyzer.py +460 -0
  142. crackerjack/services/dependency_monitor.py +14 -11
  143. crackerjack/services/documentation_generator.py +491 -0
  144. crackerjack/services/documentation_service.py +675 -0
  145. crackerjack/services/enhanced_filesystem.py +6 -5
  146. crackerjack/services/enterprise_optimizer.py +865 -0
  147. crackerjack/services/error_pattern_analyzer.py +676 -0
  148. crackerjack/services/file_hasher.py +1 -1
  149. crackerjack/services/git.py +41 -45
  150. crackerjack/services/health_metrics.py +10 -8
  151. crackerjack/services/heatmap_generator.py +735 -0
  152. crackerjack/services/initialization.py +30 -33
  153. crackerjack/services/input_validator.py +5 -97
  154. crackerjack/services/intelligent_commit.py +327 -0
  155. crackerjack/services/log_manager.py +15 -12
  156. crackerjack/services/logging.py +4 -3
  157. crackerjack/services/lsp_client.py +628 -0
  158. crackerjack/services/memory_optimizer.py +409 -0
  159. crackerjack/services/metrics.py +42 -33
  160. crackerjack/services/parallel_executor.py +416 -0
  161. crackerjack/services/pattern_cache.py +1 -1
  162. crackerjack/services/pattern_detector.py +6 -6
  163. crackerjack/services/performance_benchmarks.py +250 -576
  164. crackerjack/services/performance_cache.py +382 -0
  165. crackerjack/services/performance_monitor.py +565 -0
  166. crackerjack/services/predictive_analytics.py +510 -0
  167. crackerjack/services/quality_baseline.py +234 -0
  168. crackerjack/services/quality_baseline_enhanced.py +646 -0
  169. crackerjack/services/quality_intelligence.py +785 -0
  170. crackerjack/services/regex_patterns.py +605 -524
  171. crackerjack/services/regex_utils.py +43 -123
  172. crackerjack/services/secure_path_utils.py +5 -164
  173. crackerjack/services/secure_status_formatter.py +30 -141
  174. crackerjack/services/secure_subprocess.py +11 -92
  175. crackerjack/services/security.py +61 -30
  176. crackerjack/services/security_logger.py +18 -22
  177. crackerjack/services/server_manager.py +124 -16
  178. crackerjack/services/status_authentication.py +16 -159
  179. crackerjack/services/status_security_manager.py +4 -131
  180. crackerjack/services/terminal_utils.py +0 -0
  181. crackerjack/services/thread_safe_status_collector.py +19 -125
  182. crackerjack/services/unified_config.py +21 -13
  183. crackerjack/services/validation_rate_limiter.py +5 -54
  184. crackerjack/services/version_analyzer.py +459 -0
  185. crackerjack/services/version_checker.py +1 -1
  186. crackerjack/services/websocket_resource_limiter.py +10 -144
  187. crackerjack/services/zuban_lsp_service.py +390 -0
  188. crackerjack/slash_commands/__init__.py +2 -7
  189. crackerjack/slash_commands/run.md +2 -2
  190. crackerjack/tools/validate_input_validator_patterns.py +14 -40
  191. crackerjack/tools/validate_regex_patterns.py +19 -48
  192. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/METADATA +197 -26
  193. crackerjack-0.33.1.dist-info/RECORD +229 -0
  194. crackerjack/CLAUDE.md +0 -207
  195. crackerjack/RULES.md +0 -380
  196. crackerjack/py313.py +0 -234
  197. crackerjack-0.32.0.dist-info/RECORD +0 -180
  198. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/WHEEL +0 -0
  199. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
  200. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,8 +1,11 @@
1
1
  import asyncio
2
+ import hashlib
2
3
  import logging
3
4
  import typing as t
4
5
  from collections import defaultdict
6
+ from itertools import starmap
5
7
 
8
+ from crackerjack.services.cache import CrackerjackCache
6
9
  from crackerjack.services.debug import get_ai_agent_debugger
7
10
 
8
11
  from .base import (
@@ -15,9 +18,29 @@ from .base import (
15
18
  )
16
19
  from .tracker import get_agent_tracker
17
20
 
21
+ # Static mapping for O(1) agent lookup by issue type
22
+ ISSUE_TYPE_TO_AGENTS = {
23
+ IssueType.COMPLEXITY: ["RefactoringAgent"],
24
+ IssueType.DEAD_CODE: ["RefactoringAgent", "ImportOptimizationAgent"],
25
+ IssueType.SECURITY: ["SecurityAgent"],
26
+ IssueType.PERFORMANCE: ["PerformanceAgent", "RefactoringAgent"],
27
+ IssueType.TEST_FAILURE: ["TestCreationAgent", "TestSpecialistAgent"],
28
+ IssueType.TYPE_ERROR: ["TestCreationAgent", "RefactoringAgent"],
29
+ IssueType.FORMATTING: ["FormattingAgent", "ImportOptimizationAgent"],
30
+ IssueType.DOCUMENTATION: ["DocumentationAgent"],
31
+ IssueType.DRY_VIOLATION: ["DRYAgent", "RefactoringAgent"],
32
+ IssueType.IMPORT_ERROR: ["ImportOptimizationAgent", "RefactoringAgent"],
33
+ IssueType.COVERAGE_IMPROVEMENT: ["TestCreationAgent", "TestSpecialistAgent"],
34
+ IssueType.TEST_ORGANIZATION: ["TestSpecialistAgent", "TestCreationAgent"],
35
+ IssueType.DEPENDENCY: ["RefactoringAgent"],
36
+ IssueType.REGEX_VALIDATION: ["SecurityAgent", "RefactoringAgent"],
37
+ }
38
+
18
39
 
19
40
  class AgentCoordinator:
20
- def __init__(self, context: AgentContext) -> None:
41
+ def __init__(
42
+ self, context: AgentContext, cache: CrackerjackCache | None = None
43
+ ) -> None:
21
44
  self.context = context
22
45
  self.agents: list[SubAgent] = []
23
46
  self.logger = logging.getLogger(__name__)
@@ -26,6 +49,7 @@ class AgentCoordinator:
26
49
  self.tracker = get_agent_tracker()
27
50
  self.debugger = get_ai_agent_debugger()
28
51
  self.proactive_mode = True
52
+ self.cache = cache or CrackerjackCache()
29
53
 
30
54
  def initialize_agents(self) -> None:
31
55
  self.agents = agent_registry.create_all(self.context)
@@ -52,15 +76,25 @@ class AgentCoordinator:
52
76
 
53
77
  issues_by_type = self._group_issues_by_type(issues)
54
78
 
55
- overall_result = FixResult(success=True, confidence=1.0)
79
+ # Optimization: Run ALL issue types in parallel instead of sequential
80
+ tasks = list[t.Any](
81
+ starmap(self._handle_issues_by_type, issues_by_type.items())
82
+ )
56
83
 
57
- for issue_type, type_issues in issues_by_type.items():
58
- type_result = await self._handle_issues_by_type(issue_type, type_issues)
59
- overall_result = overall_result.merge_with(type_result)
84
+ results = await asyncio.gather(*tasks, return_exceptions=True)
60
85
 
61
- return overall_result
86
+ overall_result = FixResult(success=True, confidence=1.0)
87
+ for result in results:
88
+ if isinstance(result, FixResult):
89
+ overall_result = overall_result.merge_with(result)
90
+ else:
91
+ self.logger.error(f"Issue type handling failed: {result}")
92
+ overall_result.success = False
93
+ overall_result.remaining_issues.append(
94
+ f"Type handling failed: {result}"
95
+ )
62
96
 
63
- # Removed unused method: handle_single_issue
97
+ return overall_result
64
98
 
65
99
  async def _handle_issues_by_type(
66
100
  self,
@@ -69,10 +103,25 @@ class AgentCoordinator:
69
103
  ) -> FixResult:
70
104
  self.logger.info(f"Handling {len(issues)} {issue_type.value} issues")
71
105
 
106
+ # Fast agent lookup using static mapping
107
+ preferred_agent_names = ISSUE_TYPE_TO_AGENTS.get(issue_type, [])
108
+ specialist_agents = []
109
+
110
+ # First, try to use agents from static mapping for O(1) lookup
72
111
  specialist_agents = [
73
- agent for agent in self.agents if issue_type in agent.get_supported_types()
112
+ agent
113
+ for agent in self.agents
114
+ if agent.__class__.__name__ in preferred_agent_names
74
115
  ]
75
116
 
117
+ # Fallback: use traditional dynamic lookup if no static match
118
+ if not specialist_agents:
119
+ specialist_agents = [
120
+ agent
121
+ for agent in self.agents
122
+ if issue_type in agent.get_supported_types()
123
+ ]
124
+
76
125
  if not specialist_agents:
77
126
  self.logger.warning(f"No specialist agents for {issue_type.value}")
78
127
  return FixResult(
@@ -109,20 +158,112 @@ class AgentCoordinator:
109
158
  specialists: list[SubAgent],
110
159
  issue: Issue,
111
160
  ) -> SubAgent | None:
112
- best_agent = None
113
- best_score = 0.0
161
+ candidates = await self._score_all_specialists(specialists, issue)
162
+ if not candidates:
163
+ return None
164
+
165
+ best_agent, best_score = self._find_highest_scoring_agent(candidates)
166
+ return self._apply_built_in_preference(candidates, best_agent, best_score)
167
+
168
+ async def _score_all_specialists(
169
+ self, specialists: list[SubAgent], issue: Issue
170
+ ) -> list[tuple[SubAgent, float]]:
171
+ """Score all specialist agents for handling an issue."""
172
+ candidates: list[tuple[SubAgent, float]] = []
114
173
 
115
174
  for agent in specialists:
116
175
  try:
117
176
  score = await agent.can_handle(issue)
118
- if score > best_score:
119
- best_score = score
120
- best_agent = agent
177
+ candidates.append((agent, score))
121
178
  except Exception as e:
122
179
  self.logger.exception(f"Error evaluating specialist {agent.name}: {e}")
123
180
 
181
+ return candidates
182
+
183
+ def _find_highest_scoring_agent(
184
+ self, candidates: list[tuple[SubAgent, float]]
185
+ ) -> tuple[SubAgent | None, float]:
186
+ """Find the agent with the highest score."""
187
+ best_agent = None
188
+ best_score = 0.0
189
+
190
+ for agent, score in candidates:
191
+ if score > best_score:
192
+ best_score = score
193
+ best_agent = agent
194
+
195
+ return best_agent, best_score
196
+
197
+ def _apply_built_in_preference(
198
+ self,
199
+ candidates: list[tuple[SubAgent, float]],
200
+ best_agent: SubAgent | None,
201
+ best_score: float,
202
+ ) -> SubAgent | None:
203
+ """Apply preference for built-in agents when scores are close."""
204
+ if not best_agent or best_score <= 0:
205
+ return best_agent
206
+
207
+ # Threshold for considering scores "close" (5% difference)
208
+ CLOSE_SCORE_THRESHOLD = 0.05
209
+
210
+ for agent, score in candidates:
211
+ if self._should_prefer_built_in_agent(
212
+ agent, best_agent, score, best_score, CLOSE_SCORE_THRESHOLD
213
+ ):
214
+ self._log_built_in_preference(
215
+ agent, score, best_agent, best_score, best_score - score
216
+ )
217
+ return agent
218
+
124
219
  return best_agent
125
220
 
221
+ def _should_prefer_built_in_agent(
222
+ self,
223
+ agent: SubAgent,
224
+ best_agent: SubAgent,
225
+ score: float,
226
+ best_score: float,
227
+ threshold: float,
228
+ ) -> bool:
229
+ """Check if a built-in agent should be preferred over the current best."""
230
+ return (
231
+ agent != best_agent
232
+ and self._is_built_in_agent(agent)
233
+ and 0 < (best_score - score) <= threshold
234
+ )
235
+
236
+ def _log_built_in_preference(
237
+ self,
238
+ agent: SubAgent,
239
+ score: float,
240
+ best_agent: SubAgent,
241
+ best_score: float,
242
+ score_difference: float,
243
+ ) -> None:
244
+ """Log when preferring a built-in agent."""
245
+ self.logger.info(
246
+ f"Preferring built-in agent {agent.name} (score: {score:.2f}) "
247
+ f"over {best_agent.name} (score: {best_score:.2f}) "
248
+ f"due to {score_difference:.2f} threshold preference"
249
+ )
250
+
251
+ def _is_built_in_agent(self, agent: SubAgent) -> bool:
252
+ """Check if agent is a built-in Crackerjack agent."""
253
+ built_in_agent_names = {
254
+ "ArchitectAgent",
255
+ "DocumentationAgent",
256
+ "DRYAgent",
257
+ "FormattingAgent",
258
+ "ImportOptimizationAgent",
259
+ "PerformanceAgent",
260
+ "RefactoringAgent",
261
+ "SecurityAgent",
262
+ "TestCreationAgent",
263
+ "TestSpecialistAgent",
264
+ }
265
+ return agent.__class__.__name__ in built_in_agent_names
266
+
126
267
  async def _handle_with_single_agent(
127
268
  self,
128
269
  agent: SubAgent,
@@ -130,6 +271,16 @@ class AgentCoordinator:
130
271
  ) -> FixResult:
131
272
  self.logger.info(f"Handling issue with {agent.name}: {issue.message[:100]}")
132
273
 
274
+ # Create cache key from issue content
275
+ issue_hash = self._create_issue_hash(issue)
276
+
277
+ # Check cache for previous agent decision
278
+ cached_decision = self.cache.get_agent_decision(agent.name, issue_hash)
279
+ if cached_decision:
280
+ self.logger.debug(f"Using cached decision for {agent.name}")
281
+ self.tracker.track_agent_complete(agent.name, cached_decision)
282
+ return cached_decision # type: ignore[no-any-return]
283
+
133
284
  confidence = await agent.can_handle(issue)
134
285
  self.tracker.track_agent_processing(agent.name, issue, confidence)
135
286
 
@@ -142,7 +293,8 @@ class AgentCoordinator:
142
293
  )
143
294
 
144
295
  try:
145
- result = await agent.analyze_and_fix(issue)
296
+ # Use cached analysis for better performance
297
+ result = await self._cached_analyze_and_fix(agent, issue)
146
298
  if result.success:
147
299
  self.logger.info(f"{agent.name} successfully fixed issue")
148
300
  else:
@@ -183,6 +335,49 @@ class AgentCoordinator:
183
335
  grouped[issue.type].append(issue)
184
336
  return dict(grouped)
185
337
 
338
+ def _create_issue_hash(self, issue: Issue) -> str:
339
+ """Create a hash from issue content for caching decisions."""
340
+ content = (
341
+ f"{issue.type.value}:{issue.message}:{issue.file_path}:{issue.line_number}"
342
+ )
343
+ return hashlib.md5(content.encode(), usedforsecurity=False).hexdigest()
344
+
345
+ def _get_cache_key(self, agent_name: str, issue: Issue) -> str:
346
+ """Get cache key for agent-issue combination."""
347
+ issue_hash = self._create_issue_hash(issue)
348
+ return f"{agent_name}:{issue_hash}"
349
+
350
+ async def _cached_analyze_and_fix(self, agent: SubAgent, issue: Issue) -> FixResult:
351
+ """Analyze and fix issue with intelligent caching."""
352
+ cache_key = self._get_cache_key(agent.name, issue)
353
+
354
+ # Check in-memory cache first (fastest)
355
+ if cache_key in self._issue_cache:
356
+ self.logger.debug(f"Using in-memory cache for {agent.name}")
357
+ return self._issue_cache[cache_key]
358
+
359
+ # Check persistent cache
360
+ cached_result = self.cache.get_agent_decision(
361
+ agent.name, self._create_issue_hash(issue)
362
+ )
363
+ if cached_result:
364
+ self.logger.debug(f"Using persistent cache for {agent.name}")
365
+ # Store in memory cache for even faster future access
366
+ self._issue_cache[cache_key] = cached_result
367
+ return cached_result # type: ignore[no-any-return]
368
+
369
+ # No cache hit - perform actual analysis
370
+ result = await agent.analyze_and_fix(issue)
371
+
372
+ # Cache successful results with high confidence
373
+ if result.success and result.confidence > 0.7:
374
+ self._issue_cache[cache_key] = result
375
+ self.cache.set_agent_decision(
376
+ agent.name, self._create_issue_hash(issue), result
377
+ )
378
+
379
+ return result
380
+
186
381
  def get_agent_capabilities(self) -> dict[str, dict[str, t.Any]]:
187
382
  if not self.agents:
188
383
  self.initialize_agents()
@@ -221,36 +416,59 @@ class AgentCoordinator:
221
416
  architect = self._get_architect_agent()
222
417
 
223
418
  if not architect:
224
- self.logger.warning("No ArchitectAgent available for planning")
225
- return {"strategy": "reactive_fallback", "patterns": []}
226
-
227
- complex_issues = [
228
- issue
229
- for issue in issues
230
- if issue.type
231
- in {IssueType.COMPLEXITY, IssueType.DRY_VIOLATION, IssueType.PERFORMANCE}
232
- ]
419
+ return self._create_fallback_plan(
420
+ "No ArchitectAgent available for planning"
421
+ )
233
422
 
423
+ complex_issues = self._filter_complex_issues(issues)
234
424
  if not complex_issues:
235
425
  return {"strategy": "simple_fixes", "patterns": ["standard_patterns"]}
236
426
 
427
+ return await self._generate_architectural_plan(
428
+ architect, complex_issues, issues
429
+ )
430
+
431
+ def _create_fallback_plan(self, reason: str) -> dict[str, t.Any]:
432
+ """Create a fallback plan when architectural planning fails."""
433
+ self.logger.warning(reason)
434
+ return {"strategy": "reactive_fallback", "patterns": []}
435
+
436
+ def _filter_complex_issues(self, issues: list[Issue]) -> list[Issue]:
437
+ """Filter issues that require architectural planning."""
438
+ complex_types = {
439
+ IssueType.COMPLEXITY,
440
+ IssueType.DRY_VIOLATION,
441
+ IssueType.PERFORMANCE,
442
+ }
443
+ return [issue for issue in issues if issue.type in complex_types]
444
+
445
+ async def _generate_architectural_plan(
446
+ self, architect: t.Any, complex_issues: list[Issue], all_issues: list[Issue]
447
+ ) -> dict[str, t.Any]:
448
+ """Generate architectural plan using the architect agent."""
237
449
  primary_issue = complex_issues[0]
238
450
 
239
451
  try:
240
452
  plan = await architect.plan_before_action(primary_issue)
241
-
242
- plan["all_issues"] = [issue.id for issue in issues]
243
- plan["issue_types"] = list({issue.type.value for issue in issues})
453
+ plan = self._enrich_architectural_plan(plan, all_issues)
244
454
 
245
455
  self.logger.info(
246
456
  f"Created architectural plan: {plan.get('strategy', 'unknown')}"
247
457
  )
248
- return plan
458
+ return plan # type: ignore[no-any-return]
249
459
 
250
460
  except Exception as e:
251
461
  self.logger.exception(f"Failed to create architectural plan: {e}")
252
462
  return {"strategy": "reactive_fallback", "patterns": [], "error": str(e)}
253
463
 
464
+ def _enrich_architectural_plan(
465
+ self, plan: dict[str, t.Any], issues: list[Issue]
466
+ ) -> dict[str, t.Any]:
467
+ """Enrich the architectural plan with issue metadata."""
468
+ plan["all_issues"] = [issue.id for issue in issues]
469
+ plan["issue_types"] = list[t.Any]({issue.type.value for issue in issues})
470
+ return plan
471
+
254
472
  async def _apply_fixes_with_plan(
255
473
  self, issues: list[Issue], plan: dict[str, t.Any]
256
474
  ) -> FixResult:
@@ -260,23 +478,43 @@ class AgentCoordinator:
260
478
  return await self.handle_issues(issues)
261
479
 
262
480
  self.logger.info(f"Applying fixes with {strategy} strategy")
263
-
264
481
  prioritized_issues = self._prioritize_issues_by_plan(issues, plan)
265
482
 
483
+ return await self._process_prioritized_groups(prioritized_issues, plan)
484
+
485
+ async def _process_prioritized_groups(
486
+ self, prioritized_issues: list[list[Issue]], plan: dict[str, t.Any]
487
+ ) -> FixResult:
488
+ """Process prioritized issue groups according to the plan."""
266
489
  overall_result = FixResult(success=True, confidence=1.0)
267
490
 
268
491
  for issue_group in prioritized_issues:
269
492
  group_result = await self._handle_issue_group_with_plan(issue_group, plan)
270
493
  overall_result = overall_result.merge_with(group_result)
271
494
 
272
- if not group_result.success and self._is_critical_group(issue_group, plan):
273
- overall_result.success = False
274
- overall_result.remaining_issues.append(
275
- f"Critical issue group failed: {[i.id for i in issue_group]}"
495
+ if self._should_fail_on_group_failure(group_result, issue_group, plan):
496
+ overall_result = self._mark_critical_group_failure(
497
+ overall_result, issue_group
276
498
  )
277
499
 
278
500
  return overall_result
279
501
 
502
+ def _should_fail_on_group_failure(
503
+ self, group_result: FixResult, issue_group: list[Issue], plan: dict[str, t.Any]
504
+ ) -> bool:
505
+ """Determine if overall process should fail when a group fails."""
506
+ return not group_result.success and self._is_critical_group(issue_group, plan)
507
+
508
+ def _mark_critical_group_failure(
509
+ self, overall_result: FixResult, issue_group: list[Issue]
510
+ ) -> FixResult:
511
+ """Mark overall result as failed due to critical group failure."""
512
+ overall_result.success = False
513
+ overall_result.remaining_issues.append(
514
+ f"Critical issue group failed: {[i.id for i in issue_group]}"
515
+ )
516
+ return overall_result
517
+
280
518
  async def _validate_against_plan(
281
519
  self, result: FixResult, plan: dict[str, t.Any]
282
520
  ) -> FixResult:
@@ -318,7 +556,7 @@ class AgentCoordinator:
318
556
  return [complex_issues, other_issues] if complex_issues else [other_issues]
319
557
 
320
558
  groups = self._group_issues_by_type(issues)
321
- return list(groups.values())
559
+ return list[t.Any](groups.values())
322
560
 
323
561
  async def _handle_issue_group_with_plan(
324
562
  self, issues: list[Issue], plan: dict[str, t.Any]
@@ -94,7 +94,9 @@ class DocumentationAgent(SubAgent):
94
94
  async def _fix_documentation_consistency(self, issue: Issue) -> FixResult:
95
95
  self.log("Checking documentation consistency")
96
96
 
97
- md_files = list(Path().glob("*.md")) + list(Path("docs").glob("*.md"))
97
+ md_files = list[t.Any](Path().glob("*.md")) + list[t.Any](
98
+ Path("docs").glob("*.md")
99
+ )
98
100
 
99
101
  agent_count_issues = self._check_agent_count_consistency(md_files)
100
102
 
@@ -215,7 +217,7 @@ class DocumentationAgent(SubAgent):
215
217
 
216
218
  def _get_commit_messages(self, commit_range: str) -> str:
217
219
  result = subprocess.run(
218
- ["git", "log", commit_range, "--pretty=format:%s|%h|%an"],
220
+ ["git", "log", commit_range, "--pretty=format: %s|%h|%an"],
219
221
  capture_output=True,
220
222
  text=True,
221
223
  check=False,
@@ -267,11 +269,11 @@ class DocumentationAgent(SubAgent):
267
269
  return categories
268
270
 
269
271
  def _get_change_category(self, message: str) -> str:
270
- if message.startswith(("feat:", "feature:")):
272
+ if message.startswith(("feat: ", "feature: ")):
271
273
  return "features"
272
- if message.startswith("fix:"):
274
+ if message.startswith("fix: "):
273
275
  return "fixes"
274
- if message.startswith(("refactor:", "refact:")):
276
+ if message.startswith(("refactor: ", "refact: ")):
275
277
  return "refactors"
276
278
  return "other"
277
279
 
@@ -323,8 +325,8 @@ class DocumentationAgent(SubAgent):
323
325
 
324
326
  All notable changes to this project will be documented in this file.
325
327
 
326
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
327
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
328
+ The format is based on [Keep a Changelog](https: //keepachangelog.com/en/1.0.0/),
329
+ and this project adheres to [Semantic Versioning](https: //semver.org/spec/v2.0.0.html).
328
330
 
329
331
  {entry}
330
332
  """
@@ -376,7 +378,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
376
378
  patterns: list[str],
377
379
  expected_count: int,
378
380
  ) -> tuple[Path, int, int] | None:
379
- """Analyze file content for agent count inconsistencies."""
380
381
  pattern_map = self._get_safe_pattern_map()
381
382
 
382
383
  for pattern in patterns:
@@ -389,7 +390,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
389
390
  return None
390
391
 
391
392
  def _get_safe_pattern_map(self) -> dict[str, str]:
392
- """Get mapping of pattern strings to SAFE_PATTERNS keys."""
393
393
  return {
394
394
  SAFE_PATTERNS["agent_count_pattern"].pattern: "agent_count_pattern",
395
395
  SAFE_PATTERNS[
@@ -409,7 +409,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
409
409
  file_path: Path,
410
410
  expected_count: int,
411
411
  ) -> tuple[Path, int, int] | None:
412
- """Check a specific pattern for count mismatches."""
413
412
  if pattern not in pattern_map:
414
413
  return None
415
414
 
@@ -428,7 +427,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
428
427
  file_path: Path,
429
428
  expected_count: int,
430
429
  ) -> tuple[Path, int, int] | None:
431
- """Find count mismatches in pattern matches."""
432
430
  matches = safe_pattern.findall(content)
433
431
 
434
432
  for match in matches:
@@ -439,7 +437,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
439
437
  return None
440
438
 
441
439
  def _is_count_mismatch(self, count: int, expected_count: int) -> bool:
442
- """Check if count represents a mismatch worth reporting."""
443
440
  return count != expected_count and count > 4
444
441
 
445
442
  def _fix_agent_count_references(
@@ -448,16 +445,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
448
445
  current_count: int,
449
446
  expected_count: int,
450
447
  ) -> str:
451
- # Use SAFE_PATTERNS with dynamic replacement
452
448
  updated_content = content
453
449
 
454
- # Replace agent count references using safe patterns
455
450
  agent_pattern = SAFE_PATTERNS["update_agent_count"]
456
451
  specialized_pattern = SAFE_PATTERNS["update_specialized_agent_count"]
457
452
  config_pattern = SAFE_PATTERNS["update_total_agents_config"]
458
453
  sub_agent_pattern = SAFE_PATTERNS["update_sub_agent_count"]
459
454
 
460
- # Apply patterns with dynamic replacement (NEW_COUNT -> expected_count)
461
455
  updated_content = agent_pattern.apply(updated_content).replace(
462
456
  "NEW_COUNT", str(expected_count)
463
457
  )
@@ -152,8 +152,6 @@ class DRYAgent(SubAgent):
152
152
  error_responses: list[dict[str, t.Any]] = []
153
153
  for i, line in enumerate(lines):
154
154
  if error_pattern.test(line.strip()):
155
- # Extract error message using the pattern's compiled regex access
156
- # Access the compiled pattern from SAFE_PATTERNS to get groups
157
155
  compiled_pattern = error_pattern._get_compiled_pattern()
158
156
  match = compiled_pattern.search(line.strip())
159
157
  if match:
@@ -274,7 +272,6 @@ class DRYAgent(SubAgent):
274
272
  def _apply_violation_fix(
275
273
  self, lines: list[str], violation: dict[str, t.Any]
276
274
  ) -> tuple[list[str], bool]:
277
- """Apply fix for a specific violation type."""
278
275
  violation_type = violation["type"]
279
276
 
280
277
  if violation_type == "error_response_pattern":
@@ -289,16 +286,13 @@ class DRYAgent(SubAgent):
289
286
  lines: list[str],
290
287
  violation: dict[str, t.Any],
291
288
  ) -> tuple[list[str], bool]:
292
- # Add utility functions to the file
293
289
  utility_lines = self._add_error_response_utilities(lines)
294
290
 
295
- # Apply pattern replacements to affected lines
296
291
  self._apply_error_pattern_replacements(lines, violation, len(utility_lines))
297
292
 
298
293
  return lines, True
299
294
 
300
295
  def _add_error_response_utilities(self, lines: list[str]) -> list[str]:
301
- """Add utility functions for error responses and path conversion."""
302
296
  utility_function = """
303
297
  def _create_error_response(message: str, success: bool = False) -> str:
304
298
 
@@ -319,7 +313,6 @@ def _ensure_path(path: str | Path) -> Path:
319
313
  return [line for line in utility_lines]
320
314
 
321
315
  def _find_utility_insert_position(self, lines: list[str]) -> int:
322
- """Find the best position to insert utility functions."""
323
316
  insert_pos = 0
324
317
  for i, line in enumerate(lines):
325
318
  if line.strip().startswith(("import ", "from ")):
@@ -331,7 +324,6 @@ def _ensure_path(path: str | Path) -> Path:
331
324
  def _apply_error_pattern_replacements(
332
325
  self, lines: list[str], violation: dict[str, t.Any], utility_lines_count: int
333
326
  ) -> None:
334
- """Apply pattern replacements to lines with error response patterns."""
335
327
  path_pattern = SAFE_PATTERNS["fix_path_conversion_with_ensure_path"]
336
328
 
337
329
  for instance in violation["instances"]:
@@ -348,7 +340,6 @@ def _ensure_path(path: str | Path) -> Path:
348
340
  lines: list[str],
349
341
  violation: dict[str, t.Any],
350
342
  ) -> tuple[list[str], bool]:
351
- """Fix path conversion patterns by adding utility function."""
352
343
  utility_function_added = self._check_ensure_path_exists(lines)
353
344
  adjustment = 0
354
345
 
@@ -362,20 +353,18 @@ def _ensure_path(path: str | Path) -> Path:
362
353
  return lines, modified
363
354
 
364
355
  def _check_ensure_path_exists(self, lines: list[str]) -> bool:
365
- """Check if _ensure_path utility function already exists."""
366
356
  return any(
367
357
  "_ensure_path" in line and "def _ensure_path" in line for line in lines
368
358
  )
369
359
 
370
360
  def _add_ensure_path_utility(self, lines: list[str]) -> int:
371
- """Add the _ensure_path utility function and return adjustment count."""
372
361
  insert_pos = self._find_utility_insert_position(lines)
373
362
 
374
363
  utility_lines = [
375
364
  "",
376
- "def _ensure_path(path: str | Path) -> Path:",
377
- ' """Convert string path to Path object if needed."""',
378
- " return Path(path) if isinstance(path, str) else path",
365
+ "def _ensure_path(path: str | Path) -> Path: ",
366
+ ' """Convert string path to Path object if needed."""',
367
+ " return Path(path) if isinstance(path, str) else path",
379
368
  "",
380
369
  ]
381
370
 
@@ -391,7 +380,6 @@ def _ensure_path(path: str | Path) -> Path:
391
380
  adjustment: int,
392
381
  utility_function_added: bool,
393
382
  ) -> bool:
394
- """Replace path conversion patterns with utility function calls."""
395
383
  path_pattern = SAFE_PATTERNS["fix_path_conversion_simple"]
396
384
 
397
385
  modified = False
@@ -173,7 +173,7 @@ class FormattingAgent(SubAgent):
173
173
  "check",
174
174
  ".",
175
175
  "--select",
176
- "I,F401",
176
+ "I, F401",
177
177
  "--fix",
178
178
  ],
179
179
  )