crackerjack 0.33.0__py3-none-any.whl → 0.33.2__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/__main__.py +1350 -34
- crackerjack/adapters/__init__.py +17 -0
- crackerjack/adapters/lsp_client.py +358 -0
- crackerjack/adapters/rust_tool_adapter.py +194 -0
- crackerjack/adapters/rust_tool_manager.py +193 -0
- crackerjack/adapters/skylos_adapter.py +231 -0
- crackerjack/adapters/zuban_adapter.py +560 -0
- crackerjack/agents/base.py +7 -3
- crackerjack/agents/coordinator.py +271 -33
- crackerjack/agents/documentation_agent.py +9 -15
- crackerjack/agents/dry_agent.py +3 -15
- crackerjack/agents/formatting_agent.py +1 -1
- crackerjack/agents/import_optimization_agent.py +36 -180
- crackerjack/agents/performance_agent.py +17 -98
- crackerjack/agents/performance_helpers.py +7 -31
- crackerjack/agents/proactive_agent.py +1 -3
- crackerjack/agents/refactoring_agent.py +16 -85
- crackerjack/agents/refactoring_helpers.py +7 -42
- crackerjack/agents/security_agent.py +9 -48
- crackerjack/agents/test_creation_agent.py +356 -513
- crackerjack/agents/test_specialist_agent.py +0 -4
- crackerjack/api.py +6 -25
- crackerjack/cli/cache_handlers.py +204 -0
- crackerjack/cli/cache_handlers_enhanced.py +683 -0
- crackerjack/cli/facade.py +100 -0
- crackerjack/cli/handlers.py +224 -9
- crackerjack/cli/interactive.py +6 -4
- crackerjack/cli/options.py +642 -55
- crackerjack/cli/utils.py +2 -1
- crackerjack/code_cleaner.py +58 -117
- crackerjack/config/global_lock_config.py +8 -48
- crackerjack/config/hooks.py +53 -62
- crackerjack/core/async_workflow_orchestrator.py +24 -34
- crackerjack/core/autofix_coordinator.py +3 -17
- crackerjack/core/enhanced_container.py +4 -13
- crackerjack/core/file_lifecycle.py +12 -89
- crackerjack/core/performance.py +2 -2
- crackerjack/core/performance_monitor.py +15 -55
- crackerjack/core/phase_coordinator.py +104 -204
- crackerjack/core/resource_manager.py +14 -90
- crackerjack/core/service_watchdog.py +62 -95
- crackerjack/core/session_coordinator.py +149 -0
- crackerjack/core/timeout_manager.py +14 -72
- crackerjack/core/websocket_lifecycle.py +13 -78
- crackerjack/core/workflow_orchestrator.py +171 -174
- crackerjack/docs/INDEX.md +11 -0
- crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
- crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
- crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
- crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
- crackerjack/docs/generated/api/SERVICES.md +1252 -0
- crackerjack/documentation/__init__.py +31 -0
- crackerjack/documentation/ai_templates.py +756 -0
- crackerjack/documentation/dual_output_generator.py +765 -0
- crackerjack/documentation/mkdocs_integration.py +518 -0
- crackerjack/documentation/reference_generator.py +977 -0
- crackerjack/dynamic_config.py +55 -50
- crackerjack/executors/async_hook_executor.py +10 -15
- crackerjack/executors/cached_hook_executor.py +117 -43
- crackerjack/executors/hook_executor.py +8 -34
- crackerjack/executors/hook_lock_manager.py +26 -183
- crackerjack/executors/individual_hook_executor.py +13 -11
- crackerjack/executors/lsp_aware_hook_executor.py +270 -0
- crackerjack/executors/tool_proxy.py +417 -0
- crackerjack/hooks/lsp_hook.py +79 -0
- crackerjack/intelligence/adaptive_learning.py +25 -10
- crackerjack/intelligence/agent_orchestrator.py +2 -5
- crackerjack/intelligence/agent_registry.py +34 -24
- crackerjack/intelligence/agent_selector.py +5 -7
- crackerjack/interactive.py +17 -6
- crackerjack/managers/async_hook_manager.py +0 -1
- crackerjack/managers/hook_manager.py +79 -1
- crackerjack/managers/publish_manager.py +44 -8
- crackerjack/managers/test_command_builder.py +1 -15
- crackerjack/managers/test_executor.py +1 -3
- crackerjack/managers/test_manager.py +98 -7
- crackerjack/managers/test_manager_backup.py +10 -9
- crackerjack/mcp/cache.py +2 -2
- crackerjack/mcp/client_runner.py +1 -1
- crackerjack/mcp/context.py +191 -68
- crackerjack/mcp/dashboard.py +7 -5
- crackerjack/mcp/enhanced_progress_monitor.py +31 -28
- crackerjack/mcp/file_monitor.py +30 -23
- crackerjack/mcp/progress_components.py +31 -21
- crackerjack/mcp/progress_monitor.py +50 -53
- crackerjack/mcp/rate_limiter.py +6 -6
- crackerjack/mcp/server_core.py +17 -16
- crackerjack/mcp/service_watchdog.py +2 -1
- crackerjack/mcp/state.py +4 -7
- crackerjack/mcp/task_manager.py +11 -9
- crackerjack/mcp/tools/core_tools.py +173 -32
- crackerjack/mcp/tools/error_analyzer.py +3 -2
- crackerjack/mcp/tools/execution_tools.py +8 -10
- crackerjack/mcp/tools/execution_tools_backup.py +42 -30
- crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
- crackerjack/mcp/tools/intelligence_tools.py +5 -2
- crackerjack/mcp/tools/monitoring_tools.py +33 -70
- crackerjack/mcp/tools/proactive_tools.py +24 -11
- crackerjack/mcp/tools/progress_tools.py +5 -8
- crackerjack/mcp/tools/utility_tools.py +20 -14
- crackerjack/mcp/tools/workflow_executor.py +62 -40
- crackerjack/mcp/websocket/app.py +8 -0
- crackerjack/mcp/websocket/endpoints.py +352 -357
- crackerjack/mcp/websocket/jobs.py +40 -57
- crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
- crackerjack/mcp/websocket/server.py +7 -25
- crackerjack/mcp/websocket/websocket_handler.py +6 -17
- crackerjack/mixins/__init__.py +0 -2
- crackerjack/mixins/error_handling.py +1 -70
- crackerjack/models/config.py +12 -1
- crackerjack/models/config_adapter.py +49 -1
- crackerjack/models/protocols.py +122 -122
- crackerjack/models/resource_protocols.py +55 -210
- crackerjack/monitoring/ai_agent_watchdog.py +13 -13
- crackerjack/monitoring/metrics_collector.py +426 -0
- crackerjack/monitoring/regression_prevention.py +8 -8
- crackerjack/monitoring/websocket_server.py +643 -0
- crackerjack/orchestration/advanced_orchestrator.py +11 -6
- crackerjack/orchestration/coverage_improvement.py +3 -3
- crackerjack/orchestration/execution_strategies.py +26 -6
- crackerjack/orchestration/test_progress_streamer.py +8 -5
- crackerjack/plugins/base.py +2 -2
- crackerjack/plugins/hooks.py +7 -0
- crackerjack/plugins/managers.py +11 -8
- crackerjack/security/__init__.py +0 -1
- crackerjack/security/audit.py +6 -35
- crackerjack/services/anomaly_detector.py +392 -0
- crackerjack/services/api_extractor.py +615 -0
- crackerjack/services/backup_service.py +2 -2
- crackerjack/services/bounded_status_operations.py +15 -152
- crackerjack/services/cache.py +127 -1
- crackerjack/services/changelog_automation.py +395 -0
- crackerjack/services/config.py +15 -9
- crackerjack/services/config_merge.py +19 -80
- crackerjack/services/config_template.py +506 -0
- crackerjack/services/contextual_ai_assistant.py +48 -22
- crackerjack/services/coverage_badge_service.py +171 -0
- crackerjack/services/coverage_ratchet.py +27 -25
- crackerjack/services/debug.py +3 -3
- crackerjack/services/dependency_analyzer.py +460 -0
- crackerjack/services/dependency_monitor.py +14 -11
- crackerjack/services/documentation_generator.py +491 -0
- crackerjack/services/documentation_service.py +675 -0
- crackerjack/services/enhanced_filesystem.py +6 -5
- crackerjack/services/enterprise_optimizer.py +865 -0
- crackerjack/services/error_pattern_analyzer.py +676 -0
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/git.py +8 -25
- crackerjack/services/health_metrics.py +10 -8
- crackerjack/services/heatmap_generator.py +735 -0
- crackerjack/services/initialization.py +11 -30
- crackerjack/services/input_validator.py +5 -97
- crackerjack/services/intelligent_commit.py +327 -0
- crackerjack/services/log_manager.py +15 -12
- crackerjack/services/logging.py +4 -3
- crackerjack/services/lsp_client.py +628 -0
- crackerjack/services/memory_optimizer.py +19 -87
- crackerjack/services/metrics.py +42 -33
- crackerjack/services/parallel_executor.py +9 -67
- crackerjack/services/pattern_cache.py +1 -1
- crackerjack/services/pattern_detector.py +6 -6
- crackerjack/services/performance_benchmarks.py +18 -59
- crackerjack/services/performance_cache.py +20 -81
- crackerjack/services/performance_monitor.py +27 -95
- crackerjack/services/predictive_analytics.py +510 -0
- crackerjack/services/quality_baseline.py +234 -0
- crackerjack/services/quality_baseline_enhanced.py +646 -0
- crackerjack/services/quality_intelligence.py +785 -0
- crackerjack/services/regex_patterns.py +618 -524
- crackerjack/services/regex_utils.py +43 -123
- crackerjack/services/secure_path_utils.py +5 -164
- crackerjack/services/secure_status_formatter.py +30 -141
- crackerjack/services/secure_subprocess.py +11 -92
- crackerjack/services/security.py +9 -41
- crackerjack/services/security_logger.py +12 -24
- crackerjack/services/server_manager.py +124 -16
- crackerjack/services/status_authentication.py +16 -159
- crackerjack/services/status_security_manager.py +4 -131
- crackerjack/services/thread_safe_status_collector.py +19 -125
- crackerjack/services/unified_config.py +21 -13
- crackerjack/services/validation_rate_limiter.py +5 -54
- crackerjack/services/version_analyzer.py +459 -0
- crackerjack/services/version_checker.py +1 -1
- crackerjack/services/websocket_resource_limiter.py +10 -144
- crackerjack/services/zuban_lsp_service.py +390 -0
- crackerjack/slash_commands/__init__.py +2 -7
- crackerjack/slash_commands/run.md +2 -2
- crackerjack/tools/validate_input_validator_patterns.py +14 -40
- crackerjack/tools/validate_regex_patterns.py +19 -48
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/METADATA +196 -25
- crackerjack-0.33.2.dist-info/RECORD +229 -0
- crackerjack/CLAUDE.md +0 -207
- crackerjack/RULES.md +0 -380
- crackerjack/py313.py +0 -234
- crackerjack-0.33.0.dist-info/RECORD +0 -187
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/WHEEL +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.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__(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
113
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
225
|
-
|
|
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
|
|
273
|
-
overall_result
|
|
274
|
-
|
|
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
|
|
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
|
|
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
|
|
327
|
-
and this project adheres to [Semantic Versioning](https
|
|
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
|
)
|
crackerjack/agents/dry_agent.py
CHANGED
|
@@ -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
|
-
'
|
|
378
|
-
"
|
|
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
|