crackerjack 0.31.10__py3-none-any.whl → 0.31.12__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 +288 -705
- crackerjack/__main__.py +22 -8
- crackerjack/agents/__init__.py +0 -3
- crackerjack/agents/architect_agent.py +0 -43
- crackerjack/agents/base.py +1 -9
- crackerjack/agents/coordinator.py +2 -148
- crackerjack/agents/documentation_agent.py +109 -81
- crackerjack/agents/dry_agent.py +122 -97
- crackerjack/agents/formatting_agent.py +3 -16
- crackerjack/agents/import_optimization_agent.py +1174 -130
- crackerjack/agents/performance_agent.py +956 -188
- crackerjack/agents/performance_helpers.py +229 -0
- crackerjack/agents/proactive_agent.py +1 -48
- crackerjack/agents/refactoring_agent.py +516 -246
- crackerjack/agents/refactoring_helpers.py +282 -0
- crackerjack/agents/security_agent.py +393 -90
- crackerjack/agents/test_creation_agent.py +1776 -120
- crackerjack/agents/test_specialist_agent.py +59 -15
- crackerjack/agents/tracker.py +0 -102
- crackerjack/api.py +145 -37
- crackerjack/cli/handlers.py +48 -30
- crackerjack/cli/interactive.py +11 -11
- crackerjack/cli/options.py +66 -4
- crackerjack/code_cleaner.py +808 -148
- crackerjack/config/global_lock_config.py +110 -0
- crackerjack/config/hooks.py +43 -64
- crackerjack/core/async_workflow_orchestrator.py +247 -97
- crackerjack/core/autofix_coordinator.py +192 -109
- crackerjack/core/enhanced_container.py +46 -63
- crackerjack/core/file_lifecycle.py +549 -0
- crackerjack/core/performance.py +9 -8
- crackerjack/core/performance_monitor.py +395 -0
- crackerjack/core/phase_coordinator.py +281 -94
- crackerjack/core/proactive_workflow.py +9 -58
- crackerjack/core/resource_manager.py +501 -0
- crackerjack/core/service_watchdog.py +490 -0
- crackerjack/core/session_coordinator.py +4 -8
- crackerjack/core/timeout_manager.py +504 -0
- crackerjack/core/websocket_lifecycle.py +475 -0
- crackerjack/core/workflow_orchestrator.py +343 -209
- crackerjack/dynamic_config.py +47 -6
- crackerjack/errors.py +3 -4
- crackerjack/executors/async_hook_executor.py +63 -13
- crackerjack/executors/cached_hook_executor.py +14 -14
- crackerjack/executors/hook_executor.py +100 -37
- crackerjack/executors/hook_lock_manager.py +856 -0
- crackerjack/executors/individual_hook_executor.py +120 -86
- crackerjack/intelligence/__init__.py +0 -7
- crackerjack/intelligence/adaptive_learning.py +13 -86
- crackerjack/intelligence/agent_orchestrator.py +15 -78
- crackerjack/intelligence/agent_registry.py +12 -59
- crackerjack/intelligence/agent_selector.py +31 -92
- crackerjack/intelligence/integration.py +1 -41
- crackerjack/interactive.py +9 -9
- crackerjack/managers/async_hook_manager.py +25 -8
- crackerjack/managers/hook_manager.py +9 -9
- crackerjack/managers/publish_manager.py +57 -59
- crackerjack/managers/test_command_builder.py +6 -36
- crackerjack/managers/test_executor.py +9 -61
- crackerjack/managers/test_manager.py +17 -63
- crackerjack/managers/test_manager_backup.py +77 -127
- crackerjack/managers/test_progress.py +4 -23
- crackerjack/mcp/cache.py +5 -12
- crackerjack/mcp/client_runner.py +10 -10
- crackerjack/mcp/context.py +64 -6
- crackerjack/mcp/dashboard.py +14 -11
- crackerjack/mcp/enhanced_progress_monitor.py +55 -55
- crackerjack/mcp/file_monitor.py +72 -42
- crackerjack/mcp/progress_components.py +103 -84
- crackerjack/mcp/progress_monitor.py +122 -49
- crackerjack/mcp/rate_limiter.py +12 -12
- crackerjack/mcp/server_core.py +16 -22
- crackerjack/mcp/service_watchdog.py +26 -26
- crackerjack/mcp/state.py +15 -0
- crackerjack/mcp/tools/core_tools.py +95 -39
- crackerjack/mcp/tools/error_analyzer.py +6 -32
- crackerjack/mcp/tools/execution_tools.py +1 -56
- crackerjack/mcp/tools/execution_tools_backup.py +35 -131
- crackerjack/mcp/tools/intelligence_tool_registry.py +0 -36
- crackerjack/mcp/tools/intelligence_tools.py +2 -55
- crackerjack/mcp/tools/monitoring_tools.py +308 -145
- crackerjack/mcp/tools/proactive_tools.py +12 -42
- crackerjack/mcp/tools/progress_tools.py +23 -15
- crackerjack/mcp/tools/utility_tools.py +3 -40
- crackerjack/mcp/tools/workflow_executor.py +40 -60
- crackerjack/mcp/websocket/app.py +0 -3
- crackerjack/mcp/websocket/endpoints.py +206 -268
- crackerjack/mcp/websocket/jobs.py +213 -66
- crackerjack/mcp/websocket/server.py +84 -6
- crackerjack/mcp/websocket/websocket_handler.py +137 -29
- crackerjack/models/config_adapter.py +3 -16
- crackerjack/models/protocols.py +162 -3
- crackerjack/models/resource_protocols.py +454 -0
- crackerjack/models/task.py +3 -3
- crackerjack/monitoring/__init__.py +0 -0
- crackerjack/monitoring/ai_agent_watchdog.py +25 -71
- crackerjack/monitoring/regression_prevention.py +28 -87
- crackerjack/orchestration/advanced_orchestrator.py +44 -78
- crackerjack/orchestration/coverage_improvement.py +10 -60
- crackerjack/orchestration/execution_strategies.py +16 -16
- crackerjack/orchestration/test_progress_streamer.py +61 -53
- crackerjack/plugins/base.py +1 -1
- crackerjack/plugins/managers.py +22 -20
- crackerjack/py313.py +65 -21
- crackerjack/services/backup_service.py +467 -0
- crackerjack/services/bounded_status_operations.py +627 -0
- crackerjack/services/cache.py +7 -9
- crackerjack/services/config.py +35 -52
- crackerjack/services/config_integrity.py +5 -16
- crackerjack/services/config_merge.py +542 -0
- crackerjack/services/contextual_ai_assistant.py +17 -19
- crackerjack/services/coverage_ratchet.py +44 -73
- crackerjack/services/debug.py +25 -39
- crackerjack/services/dependency_monitor.py +52 -50
- crackerjack/services/enhanced_filesystem.py +14 -11
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/filesystem.py +1 -12
- crackerjack/services/git.py +71 -47
- crackerjack/services/health_metrics.py +31 -27
- crackerjack/services/initialization.py +276 -428
- crackerjack/services/input_validator.py +760 -0
- crackerjack/services/log_manager.py +16 -16
- crackerjack/services/logging.py +7 -6
- crackerjack/services/metrics.py +43 -43
- crackerjack/services/pattern_cache.py +2 -31
- crackerjack/services/pattern_detector.py +26 -63
- crackerjack/services/performance_benchmarks.py +20 -45
- crackerjack/services/regex_patterns.py +2887 -0
- crackerjack/services/regex_utils.py +537 -0
- crackerjack/services/secure_path_utils.py +683 -0
- crackerjack/services/secure_status_formatter.py +534 -0
- crackerjack/services/secure_subprocess.py +605 -0
- crackerjack/services/security.py +47 -10
- crackerjack/services/security_logger.py +492 -0
- crackerjack/services/server_manager.py +109 -50
- crackerjack/services/smart_scheduling.py +8 -25
- crackerjack/services/status_authentication.py +603 -0
- crackerjack/services/status_security_manager.py +442 -0
- crackerjack/services/thread_safe_status_collector.py +546 -0
- crackerjack/services/tool_version_service.py +1 -23
- crackerjack/services/unified_config.py +36 -58
- crackerjack/services/validation_rate_limiter.py +269 -0
- crackerjack/services/version_checker.py +9 -40
- crackerjack/services/websocket_resource_limiter.py +572 -0
- crackerjack/slash_commands/__init__.py +52 -2
- crackerjack/tools/__init__.py +0 -0
- crackerjack/tools/validate_input_validator_patterns.py +262 -0
- crackerjack/tools/validate_regex_patterns.py +198 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/METADATA +197 -12
- crackerjack-0.31.12.dist-info/RECORD +178 -0
- crackerjack/cli/facade.py +0 -104
- crackerjack-0.31.10.dist-info/RECORD +0 -149
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/WHEEL +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/licenses/LICENSE +0 -0
crackerjack/agents/dry_agent.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import re
|
|
2
1
|
import typing as t
|
|
3
2
|
from pathlib import Path
|
|
4
3
|
|
|
4
|
+
from ..services.regex_patterns import SAFE_PATTERNS
|
|
5
5
|
from .base import (
|
|
6
6
|
FixResult,
|
|
7
7
|
Issue,
|
|
@@ -12,8 +12,6 @@ from .base import (
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class DRYAgent(SubAgent):
|
|
15
|
-
"""Agent specialized in detecting and fixing DRY (Don't Repeat Yourself) violations."""
|
|
16
|
-
|
|
17
15
|
def get_supported_types(self) -> set[IssueType]:
|
|
18
16
|
return {IssueType.DRY_VIOLATION}
|
|
19
17
|
|
|
@@ -42,7 +40,6 @@ class DRYAgent(SubAgent):
|
|
|
42
40
|
return self._create_dry_error_result(e)
|
|
43
41
|
|
|
44
42
|
def _validate_dry_issue(self, issue: Issue) -> FixResult | None:
|
|
45
|
-
"""Validate the DRY violation issue has required information."""
|
|
46
43
|
if not issue.file_path:
|
|
47
44
|
return FixResult(
|
|
48
45
|
success=False,
|
|
@@ -50,7 +47,6 @@ class DRYAgent(SubAgent):
|
|
|
50
47
|
remaining_issues=["No file path specified for DRY violation"],
|
|
51
48
|
)
|
|
52
49
|
|
|
53
|
-
# At this point, issue.file_path is not None due to the check above
|
|
54
50
|
file_path = Path(issue.file_path)
|
|
55
51
|
if not file_path.exists():
|
|
56
52
|
return FixResult(
|
|
@@ -62,7 +58,6 @@ class DRYAgent(SubAgent):
|
|
|
62
58
|
return None
|
|
63
59
|
|
|
64
60
|
async def _process_dry_violation(self, file_path: Path) -> FixResult:
|
|
65
|
-
"""Process DRY violation detection and fixing for a file."""
|
|
66
61
|
content = self.context.get_file_content(file_path)
|
|
67
62
|
if not content:
|
|
68
63
|
return FixResult(
|
|
@@ -88,7 +83,6 @@ class DRYAgent(SubAgent):
|
|
|
88
83
|
content: str,
|
|
89
84
|
violations: list[dict[str, t.Any]],
|
|
90
85
|
) -> FixResult:
|
|
91
|
-
"""Apply DRY fixes and save changes."""
|
|
92
86
|
fixed_content = self._apply_dry_fixes(content, violations)
|
|
93
87
|
|
|
94
88
|
if fixed_content == content:
|
|
@@ -114,7 +108,6 @@ class DRYAgent(SubAgent):
|
|
|
114
108
|
)
|
|
115
109
|
|
|
116
110
|
def _create_no_fixes_result(self) -> FixResult:
|
|
117
|
-
"""Create result for when no fixes could be applied."""
|
|
118
111
|
return FixResult(
|
|
119
112
|
success=False,
|
|
120
113
|
confidence=0.5,
|
|
@@ -127,7 +120,6 @@ class DRYAgent(SubAgent):
|
|
|
127
120
|
)
|
|
128
121
|
|
|
129
122
|
def _create_dry_error_result(self, error: Exception) -> FixResult:
|
|
130
|
-
"""Create result for DRY processing errors."""
|
|
131
123
|
return FixResult(
|
|
132
124
|
success=False,
|
|
133
125
|
confidence=0.0,
|
|
@@ -139,46 +131,41 @@ class DRYAgent(SubAgent):
|
|
|
139
131
|
content: str,
|
|
140
132
|
file_path: Path,
|
|
141
133
|
) -> list[dict[str, t.Any]]:
|
|
142
|
-
"""Detect various types of DRY violations in the code."""
|
|
143
134
|
violations: list[dict[str, t.Any]] = []
|
|
144
135
|
|
|
145
|
-
# Detect error response patterns
|
|
146
136
|
violations.extend(self._detect_error_response_patterns(content))
|
|
147
137
|
|
|
148
|
-
# Detect path conversion patterns
|
|
149
138
|
violations.extend(self._detect_path_conversion_patterns(content))
|
|
150
139
|
|
|
151
|
-
# Detect file existence patterns
|
|
152
140
|
violations.extend(self._detect_file_existence_patterns(content))
|
|
153
141
|
|
|
154
|
-
# Detect exception handling patterns
|
|
155
142
|
violations.extend(self._detect_exception_patterns(content))
|
|
156
143
|
|
|
157
144
|
return violations
|
|
158
145
|
|
|
159
146
|
def _detect_error_response_patterns(self, content: str) -> list[dict[str, t.Any]]:
|
|
160
|
-
"""Detect repetitive error response patterns."""
|
|
161
147
|
violations: list[dict[str, t.Any]] = []
|
|
162
148
|
lines = content.split("\n")
|
|
163
149
|
|
|
164
|
-
|
|
165
|
-
error_pattern = re.compile(
|
|
166
|
-
r'return\s+f?[\'\"]\{[\'\""]error[\'\""]:\s*[\'\""]([^\'\"]*)[\'\""].*\}[\'\""]',
|
|
167
|
-
)
|
|
150
|
+
error_pattern = SAFE_PATTERNS["detect_error_response_patterns"]
|
|
168
151
|
|
|
169
152
|
error_responses: list[dict[str, t.Any]] = []
|
|
170
153
|
for i, line in enumerate(lines):
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
+
compiled_pattern = error_pattern._get_compiled_pattern()
|
|
158
|
+
match = compiled_pattern.search(line.strip())
|
|
159
|
+
if match:
|
|
160
|
+
error_responses.append(
|
|
161
|
+
{
|
|
162
|
+
"line_number": i + 1,
|
|
163
|
+
"content": line.strip(),
|
|
164
|
+
"error_message": match.group(1),
|
|
165
|
+
},
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
if len(error_responses) >= 3:
|
|
182
169
|
violations.append(
|
|
183
170
|
{
|
|
184
171
|
"type": "error_response_pattern",
|
|
@@ -190,14 +177,10 @@ class DRYAgent(SubAgent):
|
|
|
190
177
|
return violations
|
|
191
178
|
|
|
192
179
|
def _detect_path_conversion_patterns(self, content: str) -> list[dict[str, t.Any]]:
|
|
193
|
-
"""Detect repetitive path conversion patterns."""
|
|
194
180
|
violations: list[dict[str, t.Any]] = []
|
|
195
181
|
lines = content.split("\n")
|
|
196
182
|
|
|
197
|
-
|
|
198
|
-
path_pattern = re.compile(
|
|
199
|
-
r"Path\([^)]+\)\s+if\s+isinstance\([^)]+,\s*str\)\s+else\s+[^)]+",
|
|
200
|
-
)
|
|
183
|
+
path_pattern = SAFE_PATTERNS["detect_path_conversion_patterns"]
|
|
201
184
|
|
|
202
185
|
path_conversions: list[dict[str, t.Any]] = [
|
|
203
186
|
{
|
|
@@ -205,7 +188,7 @@ class DRYAgent(SubAgent):
|
|
|
205
188
|
"content": line.strip(),
|
|
206
189
|
}
|
|
207
190
|
for i, line in enumerate(lines)
|
|
208
|
-
if path_pattern.
|
|
191
|
+
if path_pattern.test(line)
|
|
209
192
|
]
|
|
210
193
|
|
|
211
194
|
if len(path_conversions) >= 2:
|
|
@@ -220,12 +203,10 @@ class DRYAgent(SubAgent):
|
|
|
220
203
|
return violations
|
|
221
204
|
|
|
222
205
|
def _detect_file_existence_patterns(self, content: str) -> list[dict[str, t.Any]]:
|
|
223
|
-
"""Detect repetitive file existence check patterns."""
|
|
224
206
|
violations: list[dict[str, t.Any]] = []
|
|
225
207
|
lines = content.split("\n")
|
|
226
208
|
|
|
227
|
-
|
|
228
|
-
existence_pattern = re.compile(r"if\s+not\s+\w+\.exists\(\):")
|
|
209
|
+
existence_pattern = SAFE_PATTERNS["detect_file_existence_patterns"]
|
|
229
210
|
|
|
230
211
|
existence_checks: list[dict[str, t.Any]] = [
|
|
231
212
|
{
|
|
@@ -233,7 +214,7 @@ class DRYAgent(SubAgent):
|
|
|
233
214
|
"content": line.strip(),
|
|
234
215
|
}
|
|
235
216
|
for i, line in enumerate(lines)
|
|
236
|
-
if existence_pattern.
|
|
217
|
+
if existence_pattern.test(line.strip())
|
|
237
218
|
]
|
|
238
219
|
|
|
239
220
|
if len(existence_checks) >= 3:
|
|
@@ -248,17 +229,14 @@ class DRYAgent(SubAgent):
|
|
|
248
229
|
return violations
|
|
249
230
|
|
|
250
231
|
def _detect_exception_patterns(self, content: str) -> list[dict[str, t.Any]]:
|
|
251
|
-
"""Detect repetitive exception handling patterns."""
|
|
252
232
|
violations: list[dict[str, t.Any]] = []
|
|
253
233
|
lines = content.split("\n")
|
|
254
234
|
|
|
255
|
-
|
|
256
|
-
exception_pattern = re.compile(r"except\s+\w*Exception\s+as\s+\w+:")
|
|
235
|
+
exception_pattern = SAFE_PATTERNS["detect_exception_patterns"]
|
|
257
236
|
|
|
258
237
|
exception_handlers: list[dict[str, t.Any]] = []
|
|
259
238
|
for i, line in enumerate(lines):
|
|
260
|
-
if exception_pattern.
|
|
261
|
-
# Look ahead for error return pattern
|
|
239
|
+
if exception_pattern.test(line.strip()):
|
|
262
240
|
if (
|
|
263
241
|
i + 1 < len(lines)
|
|
264
242
|
and "error" in lines[i + 1]
|
|
@@ -284,105 +262,152 @@ class DRYAgent(SubAgent):
|
|
|
284
262
|
return violations
|
|
285
263
|
|
|
286
264
|
def _apply_dry_fixes(self, content: str, violations: list[dict[str, t.Any]]) -> str:
|
|
287
|
-
"""Apply fixes for detected DRY violations."""
|
|
288
265
|
lines = content.split("\n")
|
|
289
266
|
modified = False
|
|
290
267
|
|
|
291
268
|
for violation in violations:
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
modified = modified or changed
|
|
295
|
-
elif violation["type"] == "path_conversion_pattern":
|
|
296
|
-
lines, changed = self._fix_path_conversion_pattern(lines, violation)
|
|
297
|
-
modified = modified or changed
|
|
269
|
+
lines, changed = self._apply_violation_fix(lines, violation)
|
|
270
|
+
modified = modified or changed
|
|
298
271
|
|
|
299
272
|
return "\n".join(lines) if modified else content
|
|
300
273
|
|
|
274
|
+
def _apply_violation_fix(
|
|
275
|
+
self, lines: list[str], violation: dict[str, t.Any]
|
|
276
|
+
) -> tuple[list[str], bool]:
|
|
277
|
+
"""Apply fix for a specific violation type."""
|
|
278
|
+
violation_type = violation["type"]
|
|
279
|
+
|
|
280
|
+
if violation_type == "error_response_pattern":
|
|
281
|
+
return self._fix_error_response_pattern(lines, violation)
|
|
282
|
+
elif violation_type == "path_conversion_pattern":
|
|
283
|
+
return self._fix_path_conversion_pattern(lines, violation)
|
|
284
|
+
|
|
285
|
+
return lines, False
|
|
286
|
+
|
|
301
287
|
def _fix_error_response_pattern(
|
|
302
288
|
self,
|
|
303
289
|
lines: list[str],
|
|
304
290
|
violation: dict[str, t.Any],
|
|
305
291
|
) -> tuple[list[str], bool]:
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
292
|
+
# Add utility functions to the file
|
|
293
|
+
utility_lines = self._add_error_response_utilities(lines)
|
|
294
|
+
|
|
295
|
+
# Apply pattern replacements to affected lines
|
|
296
|
+
self._apply_error_pattern_replacements(lines, violation, len(utility_lines))
|
|
297
|
+
|
|
298
|
+
return lines, True
|
|
299
|
+
|
|
300
|
+
def _add_error_response_utilities(self, lines: list[str]) -> list[str]:
|
|
301
|
+
"""Add utility functions for error responses and path conversion."""
|
|
302
|
+
utility_function = """
|
|
309
303
|
def _create_error_response(message: str, success: bool = False) -> str:
|
|
310
|
-
|
|
304
|
+
|
|
311
305
|
import json
|
|
312
306
|
return json.dumps({"error": message, "success": success})
|
|
313
|
-
'''
|
|
314
307
|
|
|
315
|
-
|
|
308
|
+
def _ensure_path(path: str | Path) -> Path:
|
|
309
|
+
|
|
310
|
+
return Path(path) if isinstance(path, str) else path
|
|
311
|
+
"""
|
|
312
|
+
|
|
313
|
+
insert_pos = self._find_utility_insert_position(lines)
|
|
314
|
+
utility_lines = utility_function.strip().split("\n")
|
|
315
|
+
|
|
316
|
+
for i, util_line in enumerate(utility_lines):
|
|
317
|
+
lines.insert(insert_pos + i, util_line)
|
|
318
|
+
|
|
319
|
+
return [line for line in utility_lines]
|
|
320
|
+
|
|
321
|
+
def _find_utility_insert_position(self, lines: list[str]) -> int:
|
|
322
|
+
"""Find the best position to insert utility functions."""
|
|
316
323
|
insert_pos = 0
|
|
317
324
|
for i, line in enumerate(lines):
|
|
318
325
|
if line.strip().startswith(("import ", "from ")):
|
|
319
326
|
insert_pos = i + 1
|
|
320
327
|
elif line.strip() and not line.strip().startswith("#"):
|
|
321
328
|
break
|
|
329
|
+
return insert_pos
|
|
322
330
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
331
|
+
def _apply_error_pattern_replacements(
|
|
332
|
+
self, lines: list[str], violation: dict[str, t.Any], utility_lines_count: int
|
|
333
|
+
) -> None:
|
|
334
|
+
"""Apply pattern replacements to lines with error response patterns."""
|
|
335
|
+
path_pattern = SAFE_PATTERNS["fix_path_conversion_with_ensure_path"]
|
|
327
336
|
|
|
328
|
-
# Replace error response patterns
|
|
329
337
|
for instance in violation["instances"]:
|
|
330
338
|
line_number: int = int(instance["line_number"])
|
|
331
|
-
line_idx = line_number - 1 +
|
|
339
|
+
line_idx = line_number - 1 + utility_lines_count
|
|
340
|
+
|
|
332
341
|
if line_idx < len(lines):
|
|
333
342
|
original_line: str = lines[line_idx]
|
|
334
|
-
|
|
335
|
-
error_msg: str = str(instance["error_message"])
|
|
336
|
-
# Replace with utility function call
|
|
337
|
-
indent = len(original_line) - len(original_line.lstrip())
|
|
338
|
-
new_line = (
|
|
339
|
-
" " * indent + f'return _create_error_response("{error_msg}")'
|
|
340
|
-
)
|
|
343
|
+
new_line: str = path_pattern.apply(original_line)
|
|
341
344
|
lines[line_idx] = new_line
|
|
342
345
|
|
|
343
|
-
return lines, True
|
|
344
|
-
|
|
345
346
|
def _fix_path_conversion_pattern(
|
|
346
347
|
self,
|
|
347
348
|
lines: list[str],
|
|
348
349
|
violation: dict[str, t.Any],
|
|
349
350
|
) -> tuple[list[str], bool]:
|
|
350
351
|
"""Fix path conversion patterns by adding utility function."""
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
def _ensure_path(path: str | Path) -> Path:
|
|
354
|
-
"""Utility function to ensure a path is a Path object."""
|
|
355
|
-
return Path(path) if isinstance(path, str) else path
|
|
356
|
-
'''
|
|
352
|
+
utility_function_added = self._check_ensure_path_exists(lines)
|
|
353
|
+
adjustment = 0
|
|
357
354
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
355
|
+
if not utility_function_added:
|
|
356
|
+
adjustment = self._add_ensure_path_utility(lines)
|
|
357
|
+
|
|
358
|
+
modified = self._apply_path_pattern_replacements(
|
|
359
|
+
lines, violation, adjustment, utility_function_added
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
return lines, modified
|
|
363
|
+
|
|
364
|
+
def _check_ensure_path_exists(self, lines: list[str]) -> bool:
|
|
365
|
+
"""Check if _ensure_path utility function already exists."""
|
|
366
|
+
return any(
|
|
367
|
+
"_ensure_path" in line and "def _ensure_path" in line for line in lines
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
def _add_ensure_path_utility(self, lines: list[str]) -> int:
|
|
371
|
+
"""Add the _ensure_path utility function and return adjustment count."""
|
|
372
|
+
insert_pos = self._find_utility_insert_position(lines)
|
|
373
|
+
|
|
374
|
+
utility_lines = [
|
|
375
|
+
"",
|
|
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",
|
|
379
|
+
"",
|
|
380
|
+
]
|
|
365
381
|
|
|
366
|
-
# Insert utility function
|
|
367
|
-
utility_lines = utility_function.strip().split("\n")
|
|
368
382
|
for i, util_line in enumerate(utility_lines):
|
|
369
383
|
lines.insert(insert_pos + i, util_line)
|
|
370
384
|
|
|
371
|
-
|
|
372
|
-
path_pattern = re.compile(
|
|
373
|
-
r"Path\([^)]+\)\s+if\s+isinstance\([^)]+,\s*str\)\s+else\s+([^)]+)",
|
|
374
|
-
)
|
|
385
|
+
return len(utility_lines)
|
|
375
386
|
|
|
387
|
+
def _apply_path_pattern_replacements(
|
|
388
|
+
self,
|
|
389
|
+
lines: list[str],
|
|
390
|
+
violation: dict[str, t.Any],
|
|
391
|
+
adjustment: int,
|
|
392
|
+
utility_function_added: bool,
|
|
393
|
+
) -> bool:
|
|
394
|
+
"""Replace path conversion patterns with utility function calls."""
|
|
395
|
+
path_pattern = SAFE_PATTERNS["fix_path_conversion_simple"]
|
|
396
|
+
|
|
397
|
+
modified = False
|
|
376
398
|
for instance in violation["instances"]:
|
|
377
399
|
line_number: int = int(instance["line_number"])
|
|
378
|
-
line_idx = line_number - 1 +
|
|
400
|
+
line_idx = line_number - 1 + (0 if utility_function_added else adjustment)
|
|
401
|
+
|
|
379
402
|
if line_idx < len(lines):
|
|
380
403
|
original_line: str = lines[line_idx]
|
|
381
|
-
|
|
382
|
-
new_line: str = path_pattern.sub(r"_ensure_path(\1)", original_line)
|
|
383
|
-
lines[line_idx] = new_line
|
|
404
|
+
new_line = path_pattern.apply(original_line)
|
|
384
405
|
|
|
385
|
-
|
|
406
|
+
if new_line != original_line:
|
|
407
|
+
lines[line_idx] = new_line
|
|
408
|
+
modified = True
|
|
409
|
+
|
|
410
|
+
return modified
|
|
386
411
|
|
|
387
412
|
|
|
388
413
|
agent_registry.register(DRYAgent)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import re
|
|
2
1
|
from pathlib import Path
|
|
3
2
|
|
|
4
3
|
from .base import (
|
|
@@ -14,11 +13,6 @@ from .base import (
|
|
|
14
13
|
class FormattingAgent(SubAgent):
|
|
15
14
|
def __init__(self, context: AgentContext) -> None:
|
|
16
15
|
super().__init__(context)
|
|
17
|
-
self.supported_tools = [
|
|
18
|
-
"ruff",
|
|
19
|
-
"trailing-whitespace",
|
|
20
|
-
"end-of-file-fixer",
|
|
21
|
-
]
|
|
22
16
|
|
|
23
17
|
def get_supported_types(self) -> set[IssueType]:
|
|
24
18
|
return {IssueType.FORMATTING, IssueType.IMPORT_ERROR}
|
|
@@ -213,7 +207,6 @@ class FormattingAgent(SubAgent):
|
|
|
213
207
|
return fixes
|
|
214
208
|
|
|
215
209
|
def _validate_and_get_file_content(self, path: Path) -> str | None:
|
|
216
|
-
"""Validate file exists and retrieve its content."""
|
|
217
210
|
if not path.exists() or not path.is_file():
|
|
218
211
|
return None
|
|
219
212
|
|
|
@@ -221,22 +214,16 @@ class FormattingAgent(SubAgent):
|
|
|
221
214
|
return content or None
|
|
222
215
|
|
|
223
216
|
def _apply_content_formatting(self, content: str) -> str:
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
content =
|
|
217
|
+
from crackerjack.services.regex_patterns import apply_formatting_fixes
|
|
218
|
+
|
|
219
|
+
content = apply_formatting_fixes(content)
|
|
227
220
|
|
|
228
|
-
# Ensure file ends with newline
|
|
229
221
|
if content and not content.endswith("\n"):
|
|
230
222
|
content += "\n"
|
|
231
223
|
|
|
232
|
-
# Normalize excessive blank lines
|
|
233
|
-
content = re.sub(r"\n{3,}", "\n\n", content)
|
|
234
|
-
|
|
235
|
-
# Convert tabs to spaces
|
|
236
224
|
return self._convert_tabs_to_spaces(content)
|
|
237
225
|
|
|
238
226
|
def _convert_tabs_to_spaces(self, content: str) -> str:
|
|
239
|
-
"""Convert tab characters to 4 spaces."""
|
|
240
227
|
lines = content.split("\n")
|
|
241
228
|
fixed_lines = [line.expandtabs(4) for line in lines]
|
|
242
229
|
return "\n".join(fixed_lines)
|