crackerjack 0.31.10__py3-none-any.whl → 0.31.13__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 (155) hide show
  1. crackerjack/CLAUDE.md +288 -705
  2. crackerjack/__main__.py +22 -8
  3. crackerjack/agents/__init__.py +0 -3
  4. crackerjack/agents/architect_agent.py +0 -43
  5. crackerjack/agents/base.py +1 -9
  6. crackerjack/agents/coordinator.py +2 -148
  7. crackerjack/agents/documentation_agent.py +109 -81
  8. crackerjack/agents/dry_agent.py +122 -97
  9. crackerjack/agents/formatting_agent.py +3 -16
  10. crackerjack/agents/import_optimization_agent.py +1174 -130
  11. crackerjack/agents/performance_agent.py +956 -188
  12. crackerjack/agents/performance_helpers.py +229 -0
  13. crackerjack/agents/proactive_agent.py +1 -48
  14. crackerjack/agents/refactoring_agent.py +516 -246
  15. crackerjack/agents/refactoring_helpers.py +282 -0
  16. crackerjack/agents/security_agent.py +393 -90
  17. crackerjack/agents/test_creation_agent.py +1776 -120
  18. crackerjack/agents/test_specialist_agent.py +59 -15
  19. crackerjack/agents/tracker.py +0 -102
  20. crackerjack/api.py +145 -37
  21. crackerjack/cli/handlers.py +48 -30
  22. crackerjack/cli/interactive.py +11 -11
  23. crackerjack/cli/options.py +66 -4
  24. crackerjack/code_cleaner.py +808 -148
  25. crackerjack/config/global_lock_config.py +110 -0
  26. crackerjack/config/hooks.py +43 -64
  27. crackerjack/core/async_workflow_orchestrator.py +247 -97
  28. crackerjack/core/autofix_coordinator.py +192 -109
  29. crackerjack/core/enhanced_container.py +46 -63
  30. crackerjack/core/file_lifecycle.py +549 -0
  31. crackerjack/core/performance.py +9 -8
  32. crackerjack/core/performance_monitor.py +395 -0
  33. crackerjack/core/phase_coordinator.py +281 -94
  34. crackerjack/core/proactive_workflow.py +9 -58
  35. crackerjack/core/resource_manager.py +501 -0
  36. crackerjack/core/service_watchdog.py +490 -0
  37. crackerjack/core/session_coordinator.py +4 -8
  38. crackerjack/core/timeout_manager.py +504 -0
  39. crackerjack/core/websocket_lifecycle.py +475 -0
  40. crackerjack/core/workflow_orchestrator.py +343 -209
  41. crackerjack/dynamic_config.py +50 -9
  42. crackerjack/errors.py +3 -4
  43. crackerjack/executors/async_hook_executor.py +63 -13
  44. crackerjack/executors/cached_hook_executor.py +14 -14
  45. crackerjack/executors/hook_executor.py +100 -37
  46. crackerjack/executors/hook_lock_manager.py +856 -0
  47. crackerjack/executors/individual_hook_executor.py +120 -86
  48. crackerjack/intelligence/__init__.py +0 -7
  49. crackerjack/intelligence/adaptive_learning.py +13 -86
  50. crackerjack/intelligence/agent_orchestrator.py +15 -78
  51. crackerjack/intelligence/agent_registry.py +12 -59
  52. crackerjack/intelligence/agent_selector.py +31 -92
  53. crackerjack/intelligence/integration.py +1 -41
  54. crackerjack/interactive.py +9 -9
  55. crackerjack/managers/async_hook_manager.py +25 -8
  56. crackerjack/managers/hook_manager.py +9 -9
  57. crackerjack/managers/publish_manager.py +57 -59
  58. crackerjack/managers/test_command_builder.py +6 -36
  59. crackerjack/managers/test_executor.py +9 -61
  60. crackerjack/managers/test_manager.py +17 -63
  61. crackerjack/managers/test_manager_backup.py +77 -127
  62. crackerjack/managers/test_progress.py +4 -23
  63. crackerjack/mcp/cache.py +5 -12
  64. crackerjack/mcp/client_runner.py +10 -10
  65. crackerjack/mcp/context.py +64 -6
  66. crackerjack/mcp/dashboard.py +14 -11
  67. crackerjack/mcp/enhanced_progress_monitor.py +55 -55
  68. crackerjack/mcp/file_monitor.py +72 -42
  69. crackerjack/mcp/progress_components.py +103 -84
  70. crackerjack/mcp/progress_monitor.py +122 -49
  71. crackerjack/mcp/rate_limiter.py +12 -12
  72. crackerjack/mcp/server_core.py +16 -22
  73. crackerjack/mcp/service_watchdog.py +26 -26
  74. crackerjack/mcp/state.py +15 -0
  75. crackerjack/mcp/tools/core_tools.py +95 -39
  76. crackerjack/mcp/tools/error_analyzer.py +6 -32
  77. crackerjack/mcp/tools/execution_tools.py +1 -56
  78. crackerjack/mcp/tools/execution_tools_backup.py +35 -131
  79. crackerjack/mcp/tools/intelligence_tool_registry.py +0 -36
  80. crackerjack/mcp/tools/intelligence_tools.py +2 -55
  81. crackerjack/mcp/tools/monitoring_tools.py +308 -145
  82. crackerjack/mcp/tools/proactive_tools.py +12 -42
  83. crackerjack/mcp/tools/progress_tools.py +23 -15
  84. crackerjack/mcp/tools/utility_tools.py +3 -40
  85. crackerjack/mcp/tools/workflow_executor.py +40 -60
  86. crackerjack/mcp/websocket/app.py +0 -3
  87. crackerjack/mcp/websocket/endpoints.py +206 -268
  88. crackerjack/mcp/websocket/jobs.py +213 -66
  89. crackerjack/mcp/websocket/server.py +84 -6
  90. crackerjack/mcp/websocket/websocket_handler.py +137 -29
  91. crackerjack/models/config_adapter.py +3 -16
  92. crackerjack/models/protocols.py +162 -3
  93. crackerjack/models/resource_protocols.py +454 -0
  94. crackerjack/models/task.py +3 -3
  95. crackerjack/monitoring/__init__.py +0 -0
  96. crackerjack/monitoring/ai_agent_watchdog.py +25 -71
  97. crackerjack/monitoring/regression_prevention.py +28 -87
  98. crackerjack/orchestration/advanced_orchestrator.py +44 -78
  99. crackerjack/orchestration/coverage_improvement.py +10 -60
  100. crackerjack/orchestration/execution_strategies.py +16 -16
  101. crackerjack/orchestration/test_progress_streamer.py +61 -53
  102. crackerjack/plugins/base.py +1 -1
  103. crackerjack/plugins/managers.py +22 -20
  104. crackerjack/py313.py +65 -21
  105. crackerjack/services/backup_service.py +467 -0
  106. crackerjack/services/bounded_status_operations.py +627 -0
  107. crackerjack/services/cache.py +7 -9
  108. crackerjack/services/config.py +35 -52
  109. crackerjack/services/config_integrity.py +5 -16
  110. crackerjack/services/config_merge.py +542 -0
  111. crackerjack/services/contextual_ai_assistant.py +17 -19
  112. crackerjack/services/coverage_ratchet.py +44 -73
  113. crackerjack/services/debug.py +25 -39
  114. crackerjack/services/dependency_monitor.py +52 -50
  115. crackerjack/services/enhanced_filesystem.py +14 -11
  116. crackerjack/services/file_hasher.py +1 -1
  117. crackerjack/services/filesystem.py +1 -12
  118. crackerjack/services/git.py +71 -47
  119. crackerjack/services/health_metrics.py +31 -27
  120. crackerjack/services/initialization.py +276 -428
  121. crackerjack/services/input_validator.py +760 -0
  122. crackerjack/services/log_manager.py +16 -16
  123. crackerjack/services/logging.py +7 -6
  124. crackerjack/services/metrics.py +43 -43
  125. crackerjack/services/pattern_cache.py +2 -31
  126. crackerjack/services/pattern_detector.py +26 -63
  127. crackerjack/services/performance_benchmarks.py +20 -45
  128. crackerjack/services/regex_patterns.py +2887 -0
  129. crackerjack/services/regex_utils.py +537 -0
  130. crackerjack/services/secure_path_utils.py +683 -0
  131. crackerjack/services/secure_status_formatter.py +534 -0
  132. crackerjack/services/secure_subprocess.py +605 -0
  133. crackerjack/services/security.py +47 -10
  134. crackerjack/services/security_logger.py +492 -0
  135. crackerjack/services/server_manager.py +109 -50
  136. crackerjack/services/smart_scheduling.py +8 -25
  137. crackerjack/services/status_authentication.py +603 -0
  138. crackerjack/services/status_security_manager.py +442 -0
  139. crackerjack/services/thread_safe_status_collector.py +546 -0
  140. crackerjack/services/tool_version_service.py +1 -23
  141. crackerjack/services/unified_config.py +36 -58
  142. crackerjack/services/validation_rate_limiter.py +269 -0
  143. crackerjack/services/version_checker.py +9 -40
  144. crackerjack/services/websocket_resource_limiter.py +572 -0
  145. crackerjack/slash_commands/__init__.py +52 -2
  146. crackerjack/tools/__init__.py +0 -0
  147. crackerjack/tools/validate_input_validator_patterns.py +262 -0
  148. crackerjack/tools/validate_regex_patterns.py +198 -0
  149. {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/METADATA +197 -12
  150. crackerjack-0.31.13.dist-info/RECORD +178 -0
  151. crackerjack/cli/facade.py +0 -104
  152. crackerjack-0.31.10.dist-info/RECORD +0 -149
  153. {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/WHEEL +0 -0
  154. {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/entry_points.txt +0 -0
  155. {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/licenses/LICENSE +0 -0
@@ -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
- # Pattern: return f'{"error": "message", "success": false}'
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
- match = error_pattern.search(line.strip())
172
- if match:
173
- error_responses.append(
174
- {
175
- "line_number": i + 1,
176
- "content": line.strip(),
177
- "error_message": match.group(1),
178
- },
179
- )
180
-
181
- if len(error_responses) >= 3: # Only flag if 3+ similar patterns
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
- # Pattern: Path(path) if isinstance(path, str) else path
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.search(line)
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
- # Pattern: if not *.exists():
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.search(line.strip())
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
- # Pattern: except Exception as e: return {"error": str(e)}
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.search(line.strip()):
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
- if violation["type"] == "error_response_pattern":
293
- lines, changed = self._fix_error_response_pattern(lines, violation)
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
- """Fix error response patterns by adding utility function."""
307
- # Add utility function at the top of the file (after imports)
308
- utility_function = '''
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
- """Utility function to create standardized error responses."""
304
+
311
305
  import json
312
306
  return json.dumps({"error": message, "success": success})
313
- '''
314
307
 
315
- # Find the right place to insert (after imports)
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
- # Insert utility function
324
- utility_lines = utility_function.strip().split("\n")
325
- for i, util_line in enumerate(utility_lines):
326
- lines.insert(insert_pos + i, util_line)
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 + len(utility_lines) # Adjust for inserted lines
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
- # Extract the error message
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
- # Add utility function
352
- utility_function = '''
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
- # Find insertion point (after imports)
359
- insert_pos = 0
360
- for i, line in enumerate(lines):
361
- if line.strip().startswith(("import ", "from ")):
362
- insert_pos = i + 1
363
- elif line.strip() and not line.strip().startswith("#"):
364
- break
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
- # Replace path conversion patterns
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 + len(utility_lines)
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
- # Replace pattern with utility function call
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
- return lines, True
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
- """Apply all formatting fixes to content."""
225
- # Remove trailing whitespace
226
- content = re.sub(r"[ \t]+$", "", content, flags=re.MULTILINE)
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)