crackerjack 0.30.3__py3-none-any.whl → 0.31.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of crackerjack might be problematic. Click here for more details.
- crackerjack/CLAUDE.md +1005 -0
- crackerjack/RULES.md +380 -0
- crackerjack/__init__.py +42 -13
- crackerjack/__main__.py +225 -299
- crackerjack/agents/__init__.py +41 -0
- crackerjack/agents/architect_agent.py +281 -0
- crackerjack/agents/base.py +169 -0
- crackerjack/agents/coordinator.py +512 -0
- crackerjack/agents/documentation_agent.py +498 -0
- crackerjack/agents/dry_agent.py +388 -0
- crackerjack/agents/formatting_agent.py +245 -0
- crackerjack/agents/import_optimization_agent.py +281 -0
- crackerjack/agents/performance_agent.py +669 -0
- crackerjack/agents/proactive_agent.py +104 -0
- crackerjack/agents/refactoring_agent.py +788 -0
- crackerjack/agents/security_agent.py +529 -0
- crackerjack/agents/test_creation_agent.py +652 -0
- crackerjack/agents/test_specialist_agent.py +486 -0
- crackerjack/agents/tracker.py +212 -0
- crackerjack/api.py +560 -0
- crackerjack/cli/__init__.py +24 -0
- crackerjack/cli/facade.py +104 -0
- crackerjack/cli/handlers.py +267 -0
- crackerjack/cli/interactive.py +471 -0
- crackerjack/cli/options.py +401 -0
- crackerjack/cli/utils.py +18 -0
- crackerjack/code_cleaner.py +618 -928
- crackerjack/config/__init__.py +19 -0
- crackerjack/config/hooks.py +218 -0
- crackerjack/core/__init__.py +0 -0
- crackerjack/core/async_workflow_orchestrator.py +406 -0
- crackerjack/core/autofix_coordinator.py +200 -0
- crackerjack/core/container.py +104 -0
- crackerjack/core/enhanced_container.py +542 -0
- crackerjack/core/performance.py +243 -0
- crackerjack/core/phase_coordinator.py +561 -0
- crackerjack/core/proactive_workflow.py +316 -0
- crackerjack/core/session_coordinator.py +289 -0
- crackerjack/core/workflow_orchestrator.py +640 -0
- crackerjack/dynamic_config.py +94 -103
- crackerjack/errors.py +263 -41
- crackerjack/executors/__init__.py +11 -0
- crackerjack/executors/async_hook_executor.py +431 -0
- crackerjack/executors/cached_hook_executor.py +242 -0
- crackerjack/executors/hook_executor.py +345 -0
- crackerjack/executors/individual_hook_executor.py +669 -0
- crackerjack/intelligence/__init__.py +44 -0
- crackerjack/intelligence/adaptive_learning.py +751 -0
- crackerjack/intelligence/agent_orchestrator.py +551 -0
- crackerjack/intelligence/agent_registry.py +414 -0
- crackerjack/intelligence/agent_selector.py +502 -0
- crackerjack/intelligence/integration.py +290 -0
- crackerjack/interactive.py +576 -315
- crackerjack/managers/__init__.py +11 -0
- crackerjack/managers/async_hook_manager.py +135 -0
- crackerjack/managers/hook_manager.py +137 -0
- crackerjack/managers/publish_manager.py +411 -0
- crackerjack/managers/test_command_builder.py +151 -0
- crackerjack/managers/test_executor.py +435 -0
- crackerjack/managers/test_manager.py +258 -0
- crackerjack/managers/test_manager_backup.py +1124 -0
- crackerjack/managers/test_progress.py +144 -0
- crackerjack/mcp/__init__.py +0 -0
- crackerjack/mcp/cache.py +336 -0
- crackerjack/mcp/client_runner.py +104 -0
- crackerjack/mcp/context.py +615 -0
- crackerjack/mcp/dashboard.py +636 -0
- crackerjack/mcp/enhanced_progress_monitor.py +479 -0
- crackerjack/mcp/file_monitor.py +336 -0
- crackerjack/mcp/progress_components.py +569 -0
- crackerjack/mcp/progress_monitor.py +949 -0
- crackerjack/mcp/rate_limiter.py +332 -0
- crackerjack/mcp/server.py +22 -0
- crackerjack/mcp/server_core.py +244 -0
- crackerjack/mcp/service_watchdog.py +501 -0
- crackerjack/mcp/state.py +395 -0
- crackerjack/mcp/task_manager.py +257 -0
- crackerjack/mcp/tools/__init__.py +17 -0
- crackerjack/mcp/tools/core_tools.py +249 -0
- crackerjack/mcp/tools/error_analyzer.py +308 -0
- crackerjack/mcp/tools/execution_tools.py +370 -0
- crackerjack/mcp/tools/execution_tools_backup.py +1097 -0
- crackerjack/mcp/tools/intelligence_tool_registry.py +80 -0
- crackerjack/mcp/tools/intelligence_tools.py +314 -0
- crackerjack/mcp/tools/monitoring_tools.py +502 -0
- crackerjack/mcp/tools/proactive_tools.py +384 -0
- crackerjack/mcp/tools/progress_tools.py +141 -0
- crackerjack/mcp/tools/utility_tools.py +341 -0
- crackerjack/mcp/tools/workflow_executor.py +360 -0
- crackerjack/mcp/websocket/__init__.py +14 -0
- crackerjack/mcp/websocket/app.py +39 -0
- crackerjack/mcp/websocket/endpoints.py +559 -0
- crackerjack/mcp/websocket/jobs.py +253 -0
- crackerjack/mcp/websocket/server.py +116 -0
- crackerjack/mcp/websocket/websocket_handler.py +78 -0
- crackerjack/mcp/websocket_server.py +10 -0
- crackerjack/models/__init__.py +31 -0
- crackerjack/models/config.py +93 -0
- crackerjack/models/config_adapter.py +230 -0
- crackerjack/models/protocols.py +118 -0
- crackerjack/models/task.py +154 -0
- crackerjack/monitoring/ai_agent_watchdog.py +450 -0
- crackerjack/monitoring/regression_prevention.py +638 -0
- crackerjack/orchestration/__init__.py +0 -0
- crackerjack/orchestration/advanced_orchestrator.py +970 -0
- crackerjack/orchestration/execution_strategies.py +341 -0
- crackerjack/orchestration/test_progress_streamer.py +636 -0
- crackerjack/plugins/__init__.py +15 -0
- crackerjack/plugins/base.py +200 -0
- crackerjack/plugins/hooks.py +246 -0
- crackerjack/plugins/loader.py +335 -0
- crackerjack/plugins/managers.py +259 -0
- crackerjack/py313.py +8 -3
- crackerjack/services/__init__.py +22 -0
- crackerjack/services/cache.py +314 -0
- crackerjack/services/config.py +347 -0
- crackerjack/services/config_integrity.py +99 -0
- crackerjack/services/contextual_ai_assistant.py +516 -0
- crackerjack/services/coverage_ratchet.py +347 -0
- crackerjack/services/debug.py +736 -0
- crackerjack/services/dependency_monitor.py +617 -0
- crackerjack/services/enhanced_filesystem.py +439 -0
- crackerjack/services/file_hasher.py +151 -0
- crackerjack/services/filesystem.py +395 -0
- crackerjack/services/git.py +165 -0
- crackerjack/services/health_metrics.py +611 -0
- crackerjack/services/initialization.py +847 -0
- crackerjack/services/log_manager.py +286 -0
- crackerjack/services/logging.py +174 -0
- crackerjack/services/metrics.py +578 -0
- crackerjack/services/pattern_cache.py +362 -0
- crackerjack/services/pattern_detector.py +515 -0
- crackerjack/services/performance_benchmarks.py +653 -0
- crackerjack/services/security.py +163 -0
- crackerjack/services/server_manager.py +234 -0
- crackerjack/services/smart_scheduling.py +144 -0
- crackerjack/services/tool_version_service.py +61 -0
- crackerjack/services/unified_config.py +437 -0
- crackerjack/services/version_checker.py +248 -0
- crackerjack/slash_commands/__init__.py +14 -0
- crackerjack/slash_commands/init.md +122 -0
- crackerjack/slash_commands/run.md +163 -0
- crackerjack/slash_commands/status.md +127 -0
- crackerjack-0.31.4.dist-info/METADATA +742 -0
- crackerjack-0.31.4.dist-info/RECORD +148 -0
- crackerjack-0.31.4.dist-info/entry_points.txt +2 -0
- crackerjack/.gitignore +0 -34
- crackerjack/.libcst.codemod.yaml +0 -18
- crackerjack/.pdm.toml +0 -1
- crackerjack/crackerjack.py +0 -3805
- crackerjack/pyproject.toml +0 -286
- crackerjack-0.30.3.dist-info/METADATA +0 -1290
- crackerjack-0.30.3.dist-info/RECORD +0 -16
- {crackerjack-0.30.3.dist-info → crackerjack-0.31.4.dist-info}/WHEEL +0 -0
- {crackerjack-0.30.3.dist-info → crackerjack-0.31.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
|
|
3
|
+
from .base import FixResult, Issue, IssueType, agent_registry
|
|
4
|
+
from .proactive_agent import ProactiveAgent
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ArchitectAgent(ProactiveAgent):
|
|
8
|
+
"""Agent that provides architectural planning and guidance.
|
|
9
|
+
|
|
10
|
+
This agent bridges to the external crackerjack-architect specialist
|
|
11
|
+
for complex architectural decisions while handling simpler planning
|
|
12
|
+
internally through cached patterns.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def get_supported_types(self) -> set[IssueType]:
|
|
16
|
+
"""Support all issue types for architectural planning."""
|
|
17
|
+
return {
|
|
18
|
+
IssueType.COMPLEXITY,
|
|
19
|
+
IssueType.DRY_VIOLATION,
|
|
20
|
+
IssueType.PERFORMANCE,
|
|
21
|
+
IssueType.SECURITY,
|
|
22
|
+
IssueType.DEAD_CODE,
|
|
23
|
+
IssueType.IMPORT_ERROR,
|
|
24
|
+
IssueType.TYPE_ERROR,
|
|
25
|
+
IssueType.TEST_FAILURE,
|
|
26
|
+
IssueType.FORMATTING,
|
|
27
|
+
IssueType.DEPENDENCY,
|
|
28
|
+
IssueType.DOCUMENTATION,
|
|
29
|
+
IssueType.TEST_ORGANIZATION,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async def can_handle(self, issue: Issue) -> float:
|
|
33
|
+
"""Determine confidence in handling this issue architecturally."""
|
|
34
|
+
# High confidence for complex issues that benefit from planning
|
|
35
|
+
if issue.type == IssueType.COMPLEXITY:
|
|
36
|
+
return 0.9
|
|
37
|
+
|
|
38
|
+
# High confidence for DRY violations (architectural)
|
|
39
|
+
if issue.type == IssueType.DRY_VIOLATION:
|
|
40
|
+
return 0.85
|
|
41
|
+
|
|
42
|
+
# Medium-high confidence for performance issues
|
|
43
|
+
if issue.type == IssueType.PERFORMANCE:
|
|
44
|
+
return 0.8
|
|
45
|
+
|
|
46
|
+
# Medium confidence for security (architectural patterns)
|
|
47
|
+
if issue.type == IssueType.SECURITY:
|
|
48
|
+
return 0.75
|
|
49
|
+
|
|
50
|
+
# Lower confidence for formatting/imports (less architectural)
|
|
51
|
+
if issue.type in {IssueType.FORMATTING, IssueType.IMPORT_ERROR}:
|
|
52
|
+
return 0.4
|
|
53
|
+
|
|
54
|
+
# Medium confidence for other types
|
|
55
|
+
return 0.6
|
|
56
|
+
|
|
57
|
+
async def plan_before_action(self, issue: Issue) -> dict[str, t.Any]:
|
|
58
|
+
"""Create architectural plan for fixing the issue."""
|
|
59
|
+
# Check if this is a complex issue requiring external specialist
|
|
60
|
+
if await self._needs_external_specialist(issue):
|
|
61
|
+
return await self._get_specialist_plan(issue)
|
|
62
|
+
|
|
63
|
+
# Use internal planning for simpler issues
|
|
64
|
+
return await self._get_internal_plan(issue)
|
|
65
|
+
|
|
66
|
+
async def _needs_external_specialist(self, issue: Issue) -> bool:
|
|
67
|
+
"""Determine if external crackerjack-architect specialist is needed."""
|
|
68
|
+
# Always use specialist for complexity issues
|
|
69
|
+
if issue.type == IssueType.COMPLEXITY:
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
# Use specialist for DRY violations
|
|
73
|
+
if issue.type == IssueType.DRY_VIOLATION:
|
|
74
|
+
return True
|
|
75
|
+
|
|
76
|
+
# Use specialist for multiple related issues
|
|
77
|
+
# (This would be determined by the coordinator in practice)
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
async def _get_specialist_plan(self, issue: Issue) -> dict[str, t.Any]:
|
|
81
|
+
"""Get plan from external crackerjack-architect specialist."""
|
|
82
|
+
# This would use the Task tool to invoke crackerjack-architect
|
|
83
|
+
# For now, return a structured plan that mimics what the specialist would provide
|
|
84
|
+
|
|
85
|
+
plan = {
|
|
86
|
+
"strategy": "external_specialist_guided",
|
|
87
|
+
"specialist": "crackerjack-architect",
|
|
88
|
+
"approach": self._get_specialist_approach(issue),
|
|
89
|
+
"patterns": self._get_recommended_patterns(issue),
|
|
90
|
+
"dependencies": self._analyze_dependencies(issue),
|
|
91
|
+
"risks": self._identify_risks(issue),
|
|
92
|
+
"validation": self._get_validation_steps(issue),
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return plan
|
|
96
|
+
|
|
97
|
+
async def _get_internal_plan(self, issue: Issue) -> dict[str, t.Any]:
|
|
98
|
+
"""Create plan using internal architectural knowledge."""
|
|
99
|
+
plan = {
|
|
100
|
+
"strategy": "internal_pattern_based",
|
|
101
|
+
"approach": self._get_internal_approach(issue),
|
|
102
|
+
"patterns": self._get_cached_patterns_for_issue(issue),
|
|
103
|
+
"dependencies": [],
|
|
104
|
+
"risks": ["minimal"],
|
|
105
|
+
"validation": ["run_quality_checks"],
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return plan
|
|
109
|
+
|
|
110
|
+
def _get_specialist_approach(self, issue: Issue) -> str:
|
|
111
|
+
"""Get the approach that crackerjack-architect would recommend."""
|
|
112
|
+
if issue.type == IssueType.COMPLEXITY:
|
|
113
|
+
return "break_into_helper_methods"
|
|
114
|
+
elif issue.type == IssueType.DRY_VIOLATION:
|
|
115
|
+
return "extract_common_patterns"
|
|
116
|
+
elif issue.type == IssueType.PERFORMANCE:
|
|
117
|
+
return "optimize_algorithms"
|
|
118
|
+
elif issue.type == IssueType.SECURITY:
|
|
119
|
+
return "apply_secure_patterns"
|
|
120
|
+
return "apply_clean_code_principles"
|
|
121
|
+
|
|
122
|
+
def _get_internal_approach(self, issue: Issue) -> str:
|
|
123
|
+
"""Get internal approach for simpler issues."""
|
|
124
|
+
return {
|
|
125
|
+
IssueType.FORMATTING: "apply_standard_formatting",
|
|
126
|
+
IssueType.IMPORT_ERROR: "optimize_imports",
|
|
127
|
+
IssueType.TYPE_ERROR: "add_type_annotations",
|
|
128
|
+
IssueType.TEST_FAILURE: "fix_test_patterns",
|
|
129
|
+
IssueType.DEAD_CODE: "remove_unused_code",
|
|
130
|
+
IssueType.DOCUMENTATION: "update_documentation",
|
|
131
|
+
}.get(issue.type, "apply_standard_fix")
|
|
132
|
+
|
|
133
|
+
def _get_recommended_patterns(self, issue: Issue) -> list[str]:
|
|
134
|
+
"""Get patterns recommended by crackerjack-architect."""
|
|
135
|
+
return {
|
|
136
|
+
IssueType.COMPLEXITY: [
|
|
137
|
+
"extract_method",
|
|
138
|
+
"dependency_injection",
|
|
139
|
+
"protocol_interfaces",
|
|
140
|
+
"helper_methods",
|
|
141
|
+
],
|
|
142
|
+
IssueType.DRY_VIOLATION: [
|
|
143
|
+
"common_base_class",
|
|
144
|
+
"utility_functions",
|
|
145
|
+
"protocol_pattern",
|
|
146
|
+
"composition",
|
|
147
|
+
],
|
|
148
|
+
IssueType.PERFORMANCE: [
|
|
149
|
+
"list_comprehension",
|
|
150
|
+
"generator_pattern",
|
|
151
|
+
"caching",
|
|
152
|
+
"algorithm_optimization",
|
|
153
|
+
],
|
|
154
|
+
IssueType.SECURITY: [
|
|
155
|
+
"secure_temp_files",
|
|
156
|
+
"input_validation",
|
|
157
|
+
"safe_subprocess",
|
|
158
|
+
"token_handling",
|
|
159
|
+
],
|
|
160
|
+
}.get(issue.type, ["standard_patterns"])
|
|
161
|
+
|
|
162
|
+
def _get_cached_patterns_for_issue(self, issue: Issue) -> list[str]:
|
|
163
|
+
"""Get cached patterns that match this issue type."""
|
|
164
|
+
cached = self.get_cached_patterns()
|
|
165
|
+
matching_patterns = []
|
|
166
|
+
|
|
167
|
+
for pattern_key, pattern_data in cached.items():
|
|
168
|
+
if pattern_key.startswith(issue.type.value):
|
|
169
|
+
matching_patterns.extend(
|
|
170
|
+
pattern_data.get("plan", {}).get("patterns", [])
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
return matching_patterns or ["default_pattern"]
|
|
174
|
+
|
|
175
|
+
def _analyze_dependencies(self, issue: Issue) -> list[str]:
|
|
176
|
+
"""Analyze what other changes might be needed."""
|
|
177
|
+
dependencies = []
|
|
178
|
+
|
|
179
|
+
if issue.type == IssueType.COMPLEXITY:
|
|
180
|
+
dependencies.extend(
|
|
181
|
+
[
|
|
182
|
+
"update_tests_for_extracted_methods",
|
|
183
|
+
"update_type_annotations",
|
|
184
|
+
"verify_imports",
|
|
185
|
+
]
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if issue.type == IssueType.DRY_VIOLATION:
|
|
189
|
+
dependencies.extend(
|
|
190
|
+
["update_all_usage_sites", "ensure_backward_compatibility"]
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
return dependencies
|
|
194
|
+
|
|
195
|
+
def _identify_risks(self, issue: Issue) -> list[str]:
|
|
196
|
+
"""Identify potential risks in fixing this issue."""
|
|
197
|
+
risks = []
|
|
198
|
+
|
|
199
|
+
if issue.type == IssueType.COMPLEXITY:
|
|
200
|
+
risks.extend(
|
|
201
|
+
[
|
|
202
|
+
"breaking_existing_functionality",
|
|
203
|
+
"changing_method_signatures",
|
|
204
|
+
"test_failures",
|
|
205
|
+
]
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if issue.type == IssueType.DRY_VIOLATION:
|
|
209
|
+
risks.extend(["breaking_dependent_code", "performance_impact"])
|
|
210
|
+
|
|
211
|
+
return risks
|
|
212
|
+
|
|
213
|
+
def _get_validation_steps(self, issue: Issue) -> list[str]:
|
|
214
|
+
"""Get steps to validate the fix."""
|
|
215
|
+
return [
|
|
216
|
+
"run_fast_hooks",
|
|
217
|
+
"run_full_tests",
|
|
218
|
+
"run_comprehensive_hooks",
|
|
219
|
+
"validate_complexity_reduction",
|
|
220
|
+
"check_pattern_compliance",
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
async def analyze_and_fix(self, issue: Issue) -> FixResult:
|
|
224
|
+
"""Standard fix method - delegates to proactive version."""
|
|
225
|
+
return await self.analyze_and_fix_proactively(issue)
|
|
226
|
+
|
|
227
|
+
async def _execute_with_plan(
|
|
228
|
+
self, issue: Issue, plan: dict[str, t.Any]
|
|
229
|
+
) -> FixResult:
|
|
230
|
+
"""Execute fix following the architectural plan."""
|
|
231
|
+
strategy = plan.get("strategy", "internal_pattern_based")
|
|
232
|
+
|
|
233
|
+
if strategy == "external_specialist_guided":
|
|
234
|
+
return await self._execute_specialist_guided_fix(issue, plan)
|
|
235
|
+
return await self._execute_pattern_based_fix(issue, plan)
|
|
236
|
+
|
|
237
|
+
async def _execute_specialist_guided_fix(
|
|
238
|
+
self, issue: Issue, plan: dict[str, t.Any]
|
|
239
|
+
) -> FixResult:
|
|
240
|
+
"""Execute fix guided by external specialist plan."""
|
|
241
|
+
# This would invoke the actual Task tool with crackerjack-architect
|
|
242
|
+
# For now, return a structured result indicating the plan was followed
|
|
243
|
+
|
|
244
|
+
return FixResult(
|
|
245
|
+
success=True,
|
|
246
|
+
confidence=0.9,
|
|
247
|
+
fixes_applied=[
|
|
248
|
+
f"Applied {plan.get('approach', 'specialist')} approach",
|
|
249
|
+
f"Used patterns: {', '.join(plan.get('patterns', []))}",
|
|
250
|
+
"Followed crackerjack-architect guidance",
|
|
251
|
+
],
|
|
252
|
+
remaining_issues=[],
|
|
253
|
+
recommendations=[
|
|
254
|
+
f"Validate with: {', '.join(plan.get('validation', []))}",
|
|
255
|
+
"Consider running full test suite",
|
|
256
|
+
],
|
|
257
|
+
files_modified=[issue.file_path] if issue.file_path else [],
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
async def _execute_pattern_based_fix(
|
|
261
|
+
self, issue: Issue, plan: dict[str, t.Any]
|
|
262
|
+
) -> FixResult:
|
|
263
|
+
"""Execute fix using cached patterns and internal logic."""
|
|
264
|
+
patterns = plan.get("patterns", [])
|
|
265
|
+
approach = plan.get("approach", "standard")
|
|
266
|
+
|
|
267
|
+
return FixResult(
|
|
268
|
+
success=True,
|
|
269
|
+
confidence=0.75,
|
|
270
|
+
fixes_applied=[
|
|
271
|
+
f"Applied {approach} approach",
|
|
272
|
+
f"Used cached patterns: {', '.join(patterns)}",
|
|
273
|
+
],
|
|
274
|
+
remaining_issues=[],
|
|
275
|
+
recommendations=["Consider validating with crackerjack quality checks"],
|
|
276
|
+
files_modified=[issue.file_path] if issue.file_path else [],
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
# Register the agent
|
|
281
|
+
agent_registry.register(ArchitectAgent)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import typing as t
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Priority(Enum):
|
|
10
|
+
LOW = "low"
|
|
11
|
+
MEDIUM = "medium"
|
|
12
|
+
HIGH = "high"
|
|
13
|
+
CRITICAL = "critical"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class IssueType(Enum):
|
|
17
|
+
FORMATTING = "formatting"
|
|
18
|
+
TYPE_ERROR = "type_error"
|
|
19
|
+
SECURITY = "security"
|
|
20
|
+
TEST_FAILURE = "test_failure"
|
|
21
|
+
IMPORT_ERROR = "import_error"
|
|
22
|
+
COMPLEXITY = "complexity"
|
|
23
|
+
DEAD_CODE = "dead_code"
|
|
24
|
+
DEPENDENCY = "dependency"
|
|
25
|
+
DRY_VIOLATION = "dry_violation"
|
|
26
|
+
PERFORMANCE = "performance"
|
|
27
|
+
DOCUMENTATION = "documentation"
|
|
28
|
+
TEST_ORGANIZATION = "test_organization"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class Issue:
|
|
33
|
+
id: str
|
|
34
|
+
type: IssueType
|
|
35
|
+
severity: Priority
|
|
36
|
+
message: str
|
|
37
|
+
file_path: str | None = None
|
|
38
|
+
line_number: int | None = None
|
|
39
|
+
details: list[str] = field(default_factory=list)
|
|
40
|
+
stage: str = "unknown"
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def context_key(self) -> str:
|
|
44
|
+
return f"{self.type.value}: {self.file_path}: {self.line_number}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class FixResult:
|
|
49
|
+
success: bool
|
|
50
|
+
confidence: float
|
|
51
|
+
fixes_applied: list[str] = field(default_factory=list)
|
|
52
|
+
remaining_issues: list[str] = field(default_factory=list)
|
|
53
|
+
recommendations: list[str] = field(default_factory=list)
|
|
54
|
+
files_modified: list[str] = field(default_factory=list)
|
|
55
|
+
|
|
56
|
+
def merge_with(self, other: "FixResult") -> "FixResult":
|
|
57
|
+
return FixResult(
|
|
58
|
+
success=self.success and other.success,
|
|
59
|
+
confidence=max(self.confidence, other.confidence),
|
|
60
|
+
fixes_applied=self.fixes_applied + other.fixes_applied,
|
|
61
|
+
remaining_issues=list(set(self.remaining_issues + other.remaining_issues)),
|
|
62
|
+
recommendations=self.recommendations + other.recommendations,
|
|
63
|
+
files_modified=list(set(self.files_modified + other.files_modified)),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class AgentContext:
|
|
69
|
+
project_path: Path
|
|
70
|
+
temp_dir: Path | None = None
|
|
71
|
+
config: dict[str, t.Any] = field(default_factory=dict)
|
|
72
|
+
session_id: str | None = None
|
|
73
|
+
|
|
74
|
+
subprocess_timeout: int = 300
|
|
75
|
+
max_file_size: int = 10_000_000
|
|
76
|
+
|
|
77
|
+
def get_file_content(self, file_path: str | Path) -> str | None:
|
|
78
|
+
try:
|
|
79
|
+
path = Path(file_path)
|
|
80
|
+
if not path.is_file():
|
|
81
|
+
return None
|
|
82
|
+
if path.stat().st_size > self.max_file_size:
|
|
83
|
+
return None
|
|
84
|
+
return path.read_text(encoding="utf-8")
|
|
85
|
+
except Exception:
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
def write_file_content(self, file_path: str | Path, content: str) -> bool:
|
|
89
|
+
try:
|
|
90
|
+
path = Path(file_path)
|
|
91
|
+
path.write_text(content, encoding="utf-8")
|
|
92
|
+
return True
|
|
93
|
+
except Exception:
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class SubAgent(ABC):
|
|
98
|
+
def __init__(self, context: AgentContext) -> None:
|
|
99
|
+
self.context = context
|
|
100
|
+
self.name = self.__class__.__name__
|
|
101
|
+
|
|
102
|
+
@abstractmethod
|
|
103
|
+
async def can_handle(self, issue: Issue) -> float:
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
@abstractmethod
|
|
107
|
+
async def analyze_and_fix(self, issue: Issue) -> FixResult:
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
@abstractmethod
|
|
111
|
+
def get_supported_types(self) -> set[IssueType]:
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
async def run_command(
|
|
115
|
+
self,
|
|
116
|
+
cmd: list[str],
|
|
117
|
+
cwd: Path | None = None,
|
|
118
|
+
timeout: int | None = None,
|
|
119
|
+
) -> tuple[int, str, str]:
|
|
120
|
+
try:
|
|
121
|
+
process = await asyncio.create_subprocess_exec(
|
|
122
|
+
*cmd,
|
|
123
|
+
cwd=cwd or self.context.project_path,
|
|
124
|
+
stdout=asyncio.subprocess.PIPE,
|
|
125
|
+
stderr=asyncio.subprocess.PIPE,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
stdout, stderr = await asyncio.wait_for(
|
|
129
|
+
process.communicate(),
|
|
130
|
+
timeout=timeout or self.context.subprocess_timeout,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
process.returncode or 0,
|
|
135
|
+
stdout.decode() if stdout else "",
|
|
136
|
+
stderr.decode() if stderr else "",
|
|
137
|
+
)
|
|
138
|
+
except TimeoutError:
|
|
139
|
+
return (-1, "", "Command timed out")
|
|
140
|
+
except Exception as e:
|
|
141
|
+
return (-1, "", f"Command failed: {e}")
|
|
142
|
+
|
|
143
|
+
def log(self, message: str, level: str = "INFO") -> None:
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
async def plan_before_action(self, issue: Issue) -> dict[str, t.Any]:
|
|
147
|
+
"""Plan actions before executing fixes. Override in subclasses."""
|
|
148
|
+
return {"strategy": "default", "confidence": 0.5}
|
|
149
|
+
|
|
150
|
+
def get_cached_patterns(self) -> dict[str, t.Any]:
|
|
151
|
+
"""Get cached patterns for this agent. Override in subclasses."""
|
|
152
|
+
return {}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class AgentRegistry:
|
|
156
|
+
def __init__(self) -> None:
|
|
157
|
+
self._agents: dict[str, type[SubAgent]] = {}
|
|
158
|
+
|
|
159
|
+
def register(self, agent_class: type[SubAgent]) -> None:
|
|
160
|
+
self._agents[agent_class.__name__] = agent_class
|
|
161
|
+
|
|
162
|
+
def create_all(self, context: AgentContext) -> list[SubAgent]:
|
|
163
|
+
return [agent_cls(context) for agent_cls in self._agents.values()]
|
|
164
|
+
|
|
165
|
+
def get_by_name(self, name: str) -> type[SubAgent] | None:
|
|
166
|
+
return self._agents.get(name)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
agent_registry = AgentRegistry()
|