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.

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 +47 -6
  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.12.dist-info}/METADATA +197 -12
  150. crackerjack-0.31.12.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.12.dist-info}/WHEEL +0 -0
  154. {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/entry_points.txt +0 -0
  155. {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,537 @@
1
+ """
2
+ Utility functions for immediate regex pattern testing and validation.
3
+
4
+ Provides quick functions for testing regex patterns before adding them
5
+ to the centralized registry, and utilities for migrating existing re.sub() calls.
6
+ """
7
+
8
+ import re
9
+ import typing as t
10
+ from pathlib import Path
11
+
12
+ from crackerjack.services.regex_patterns import SAFE_PATTERNS, CompiledPatternCache
13
+
14
+
15
+ def test_pattern_immediately(
16
+ pattern: str,
17
+ replacement: str,
18
+ test_cases: list[tuple[str, str]],
19
+ description: str = "",
20
+ ) -> dict[str, t.Any]:
21
+ """
22
+ Test a regex pattern immediately without adding to registry.
23
+
24
+ Returns a report of test results for quick validation.
25
+ """
26
+ results: dict[str, t.Any] = {
27
+ "pattern": pattern,
28
+ "replacement": replacement,
29
+ "description": description,
30
+ "all_passed": True,
31
+ "test_results": [],
32
+ "warnings": [],
33
+ "errors": [],
34
+ }
35
+
36
+ # Check for forbidden replacement syntax first using safe patterns\n forbidden_checks = [\n (r\"\\\\g\\\\s*<\\\\s*\\\\d+\\\\s*>\", \"\\\\g < 1 > with spaces\"),\n (r\"\\\\g<\\\\s+\\\\d+>\", \"\\\\g< 1> with space after <\"),\n (r\"\\\\g<\\\\d+\\\\s+>\", \"\\\\g<1 > with space before >\"),\n ]
37
+
38
+ # Validate pattern compilation using safe cache
39
+ try:
40
+ compiled = CompiledPatternCache.get_compiled_pattern(pattern)
41
+ except ValueError as e:
42
+ results["errors"].append(f"Invalid regex pattern: {e}")
43
+ results["all_passed"] = False
44
+ return results
45
+
46
+ # Test all cases
47
+ for i, (input_text, expected) in enumerate(test_cases):
48
+ try:
49
+ result = compiled.sub(replacement, input_text)
50
+ passed = result == expected
51
+ results["test_results"].append(
52
+ {
53
+ "test_case": i + 1,
54
+ "input": input_text,
55
+ "expected": expected,
56
+ "actual": result,
57
+ "passed": passed,
58
+ }
59
+ )
60
+ if not passed:
61
+ results["all_passed"] = False
62
+ except Exception as e:
63
+ results["test_results"].append(
64
+ {
65
+ "test_case": i + 1,
66
+ "input": input_text,
67
+ "expected": expected,
68
+ "actual": f"ERROR: {e}",
69
+ "passed": False,
70
+ }
71
+ )
72
+ results["all_passed"] = False
73
+
74
+ # Safety warnings
75
+ if ".*.*" in pattern:
76
+ results["warnings"].append(
77
+ "Multiple .* constructs may cause performance issues"
78
+ )
79
+ if ".+.+" in pattern:
80
+ results["warnings"].append(
81
+ "Multiple .+ constructs may cause performance issues"
82
+ )
83
+
84
+ return results
85
+
86
+
87
+ def print_pattern_test_report(results: dict[str, t.Any]) -> None:
88
+ """Print a formatted report of pattern test results."""
89
+ print("\n🔍 REGEX PATTERN TEST REPORT")
90
+ print("=" * 50)
91
+ print(f"Pattern: {results['pattern']}")
92
+ print(f"Replacement: {results['replacement']}")
93
+ if results["description"]:
94
+ print(f"Description: {results['description']}")
95
+ print()
96
+
97
+ if results["errors"]:
98
+ print("❌ ERRORS:")
99
+ for error in results["errors"]:
100
+ print(f" • {error}")
101
+ print()
102
+
103
+ if results["warnings"]:
104
+ print("⚠️ WARNINGS:")
105
+ for warning in results["warnings"]:
106
+ print(f" • {warning}")
107
+ print()
108
+
109
+ print("📋 TEST CASES:")
110
+ for test in results["test_results"]:
111
+ status = "✅ PASS" if test["passed"] else "❌ FAIL"
112
+ print(
113
+ f" {status} Test {test['test_case']}: '{test['input']}' → '{test['actual']}'"
114
+ )
115
+ if not test["passed"]:
116
+ print(f" Expected: '{test['expected']}'")
117
+
118
+ print(
119
+ f"\n🎯 OVERALL: {'✅ ALL TESTS PASSED' if results['all_passed'] else '❌ TESTS FAILED'}"
120
+ )
121
+ print("=" * 50)
122
+
123
+
124
+ def quick_pattern_test(
125
+ pattern: str,
126
+ replacement: str,
127
+ test_cases: list[tuple[str, str]],
128
+ description: str = "",
129
+ ) -> bool:
130
+ """Quick test function that returns True if all tests pass."""
131
+ results = test_pattern_immediately(pattern, replacement, test_cases, description)
132
+ print_pattern_test_report(results)
133
+ return results["all_passed"]
134
+
135
+
136
+ def find_safe_pattern_for_text(text: str) -> list[str]:
137
+ """Find which existing safe patterns would match the given text."""
138
+ matches = []
139
+ for name, pattern in SAFE_PATTERNS.items():
140
+ try:
141
+ if pattern.test(text):
142
+ matches.append(name)
143
+ except Exception:
144
+ # Skip patterns that error
145
+ continue
146
+ return matches
147
+
148
+
149
+ def suggest_migration_for_re_sub(
150
+ original_pattern: str, original_replacement: str, sample_text: str = ""
151
+ ) -> dict[str, t.Any]:
152
+ """
153
+ Suggest how to migrate a raw re.sub() call to use safe patterns.
154
+
155
+ Args:
156
+ original_pattern: The original regex pattern
157
+ original_replacement: The original replacement string
158
+ sample_text: Optional sample text to test against
159
+
160
+ Returns:
161
+ Dictionary with migration suggestions
162
+ """
163
+ suggestion: dict[str, t.Any] = {
164
+ "original_pattern": original_pattern,
165
+ "original_replacement": original_replacement,
166
+ "existing_matches": [],
167
+ "needs_new_pattern": True,
168
+ "safety_issues": [],
169
+ "suggested_name": "",
170
+ "test_cases_needed": [],
171
+ }
172
+
173
+ # Check for safety issues first using safe patterns
174
+ forbidden_checks = [
175
+ (r"\\g\s*<\s*\d+\s*>", "\\g < 1 > with spaces"),
176
+ (r"\\g<\s+\d+>", "\\g< 1> with space after <"),
177
+ (r"\\\\g<\\d+\\s+>", "\\\\g<1 > with space before >"),
178
+ ]
179
+ for forbidden_pattern, _ in forbidden_checks:
180
+ compiled = CompiledPatternCache.get_compiled_pattern(forbidden_pattern)
181
+ if compiled.search(original_replacement):
182
+ suggestion["safety_issues"].append(
183
+ "CRITICAL: Bad replacement syntax - spaces in \\g<1>"
184
+ )
185
+
186
+ # Look for existing patterns that might work
187
+ if sample_text:
188
+ matches = find_safe_pattern_for_text(sample_text)
189
+ suggestion["existing_matches"] = matches
190
+ if matches:
191
+ suggestion["needs_new_pattern"] = False
192
+
193
+ # Generate suggested pattern name based on original pattern
194
+ if "python.*-.*m" in original_pattern:
195
+ suggestion["suggested_name"] = "fix_python_command_spacing"
196
+ elif r"\-\s*\-" in original_pattern:
197
+ suggestion["suggested_name"] = "fix_double_dash_spacing"
198
+ elif "token" in original_pattern.lower():
199
+ suggestion["suggested_name"] = "fix_token_pattern"
200
+ elif "password" in original_pattern.lower():
201
+ suggestion["suggested_name"] = "fix_password_pattern"
202
+ else:
203
+ # Generate name from pattern keywords using safe pattern
204
+ keyword_pattern = CompiledPatternCache.get_compiled_pattern(r"[a-zA-Z]+")
205
+ keywords = keyword_pattern.findall(original_pattern)
206
+ if keywords:
207
+ suggestion["suggested_name"] = (
208
+ f"fix_{'_'.join(keywords[:3])}_pattern".lower()
209
+ )
210
+ else:
211
+ suggestion["suggested_name"] = "fix_custom_pattern"
212
+
213
+ # Suggest test cases based on common patterns
214
+ if sample_text:
215
+ suggestion["test_cases_needed"].append((sample_text, "Expected output needed"))
216
+
217
+ # Common test cases for spacing issues
218
+ if "-" in original_pattern:
219
+ suggestion["test_cases_needed"].extend(
220
+ [
221
+ ("word - word", "word-word"),
222
+ ("already-good", "already-good"), # No change
223
+ ("multiple - word - spacing", "multiple-word - spacing"), # Partial fix
224
+ ]
225
+ )
226
+
227
+ return suggestion
228
+
229
+
230
+ def print_migration_suggestion(suggestion: dict[str, t.Any]) -> None:
231
+ """Print a formatted migration suggestion report."""
232
+ print("\n🔄 REGEX MIGRATION SUGGESTION")
233
+ print("=" * 50)
234
+ print(f"Original Pattern: {suggestion['original_pattern']}")
235
+ print(f"Original Replacement: {suggestion['original_replacement']}")
236
+ print()
237
+
238
+ if suggestion["safety_issues"]:
239
+ print("❌ SAFETY ISSUES:")
240
+ for issue in suggestion["safety_issues"]:
241
+ print(f" • {issue}")
242
+ print()
243
+
244
+ if suggestion["existing_matches"]:
245
+ print("✅ EXISTING PATTERNS AVAILABLE:")
246
+ for pattern_name in suggestion["existing_matches"]:
247
+ pattern = SAFE_PATTERNS[pattern_name]
248
+ print(f" • {pattern_name}: {pattern.description}")
249
+ print("💡 Consider using existing patterns instead of creating new ones.")
250
+ print()
251
+
252
+ if suggestion["needs_new_pattern"]:
253
+ print("🆕 NEW PATTERN NEEDED:")
254
+ print(f" Suggested Name: {suggestion['suggested_name']}")
255
+ print(" Add to crackerjack/services/regex_patterns.py:")
256
+ print()
257
+ print(" ```python")
258
+ print(f' "{suggestion["suggested_name"]}": ValidatedPattern(')
259
+ print(f' name="{suggestion["suggested_name"]}",')
260
+ print(f' pattern=r"{suggestion["original_pattern"]}",')
261
+ print(f' replacement=r"{suggestion["original_replacement"]}",')
262
+ print(' description="TODO: Add description",')
263
+ print(" test_cases=[")
264
+ for test_input, test_output in suggestion["test_cases_needed"]:
265
+ print(f' ("{test_input}", "{test_output}"),')
266
+ print(" ]")
267
+ print(" ),")
268
+ print(" ```")
269
+ print()
270
+
271
+ print("🔧 MIGRATION STEPS:")
272
+ print(" 1. Fix any safety issues in replacement syntax")
273
+ if suggestion["existing_matches"]:
274
+ print(" 2. Use existing safe patterns if possible:")
275
+ for pattern_name in suggestion["existing_matches"]:
276
+ print(f" SAFE_PATTERNS['{pattern_name}'].apply(text)")
277
+ if suggestion["needs_new_pattern"]:
278
+ print(" 3. Add new ValidatedPattern to regex_patterns.py")
279
+ print(" 4. Test thoroughly with comprehensive test cases")
280
+ print(" 5. Replace re.sub() call with safe pattern usage")
281
+ print(" 6. Run pre-commit hook to validate")
282
+
283
+ print("=" * 50)
284
+
285
+
286
+ def audit_file_for_re_sub(file_path: Path) -> list[dict[str, t.Any]]:
287
+ """
288
+ Audit a file for re.sub() calls and return migration suggestions.
289
+
290
+ Returns list of findings with line numbers and suggestions.
291
+ """
292
+ findings = []
293
+
294
+ try:
295
+ content = file_path.read_text(encoding="utf-8")
296
+ lines = content.split("\n")
297
+
298
+ for i, line in enumerate(lines, 1):
299
+ # Look for re.sub() calls using safe pattern
300
+ re_sub_pattern = CompiledPatternCache.get_compiled_pattern(
301
+ r're\.sub\s*\(\s*[r]?["\']([^"\']+)["\'],\s*[r]?["\']([^"\']*)["\']'
302
+ )
303
+ re_sub_match = re_sub_pattern.search(line)
304
+ if re_sub_match:
305
+ pattern = re_sub_match.group(1)
306
+ replacement = re_sub_match.group(2)
307
+
308
+ finding = {
309
+ "file": str(file_path),
310
+ "line_number": i,
311
+ "line_content": line.strip(),
312
+ "pattern": pattern,
313
+ "replacement": replacement,
314
+ "suggestion": suggest_migration_for_re_sub(pattern, replacement),
315
+ }
316
+ findings.append(finding)
317
+
318
+ except Exception as e:
319
+ findings.append(
320
+ {
321
+ "file": str(file_path),
322
+ "line_number": 0,
323
+ "error": f"Failed to audit file: {e}",
324
+ }
325
+ )
326
+
327
+ return findings
328
+
329
+
330
+ def audit_codebase_re_sub() -> dict[str, list[dict[str, t.Any]]]:
331
+ """
332
+ Audit entire crackerjack codebase for re.sub() usage.
333
+
334
+ Returns dictionary mapping file paths to findings.
335
+ """
336
+ findings_by_file = {}
337
+
338
+ # Audit crackerjack package
339
+ crackerjack_dir = Path(__file__).parent.parent
340
+
341
+ for py_file in crackerjack_dir.rglob("*.py"):
342
+ # Skip test files and __pycache__
343
+ if "test_" in py_file.name or "__pycache__" in str(py_file):
344
+ continue
345
+
346
+ findings = audit_file_for_re_sub(py_file)
347
+ if findings:
348
+ findings_by_file[str(py_file)] = findings
349
+
350
+ return findings_by_file
351
+
352
+
353
+ def replace_unsafe_regex_with_safe_patterns(content: str) -> str:
354
+ """
355
+ Replace unsafe regex patterns in content with safe alternatives.
356
+
357
+ This function looks for common regex patterns and replaces them with
358
+ calls to SAFE_PATTERNS where possible.
359
+
360
+ Args:
361
+ content: The source code content to fix
362
+
363
+ Returns:
364
+ Fixed content with safe patterns applied
365
+ """
366
+ lines = content.split("\n")
367
+ modified = False
368
+
369
+ has_safe_patterns_import = _check_for_safe_patterns_import(lines)
370
+
371
+ for i, line in enumerate(lines):
372
+ original_line = line
373
+
374
+ # Fix critical replacement syntax issues first
375
+ line = _fix_replacement_syntax_issues(line)
376
+
377
+ # Process re.sub patterns
378
+ line, _, needs_import = _process_re_sub_patterns(line, has_safe_patterns_import)
379
+
380
+ if needs_import and not has_safe_patterns_import:
381
+ import_index = _find_import_insertion_point(lines)
382
+ lines.insert(
383
+ import_index,
384
+ "from crackerjack.services.regex_patterns import SAFE_PATTERNS",
385
+ )
386
+ has_safe_patterns_import = True
387
+ i += 1 # Adjust index for inserted line
388
+
389
+ # Update the line if it changed
390
+ if line != original_line:
391
+ lines[i] = line
392
+ modified = True
393
+
394
+ return "\n".join(lines) if modified else content
395
+
396
+
397
+ def _check_for_safe_patterns_import(lines: list[str]) -> bool:
398
+ """Check if SAFE_PATTERNS import is already present."""
399
+ return any(
400
+ "from crackerjack.services.regex_patterns import SAFE_PATTERNS" in line
401
+ or "SAFE_PATTERNS" in line
402
+ for line in lines
403
+ )
404
+
405
+
406
+ def _fix_replacement_syntax_issues(line: str) -> str:
407
+ """Fix critical replacement syntax issues in regex patterns."""
408
+ if r"\g < " in line or r"\g< " in line or r"\g <" in line:
409
+ # Fix spacing in replacement groups using safe pattern
410
+ spacing_fix_pattern = CompiledPatternCache.get_compiled_pattern(
411
+ r"\\g\s*<\s*(\d+)\s*>"
412
+ )
413
+ line = spacing_fix_pattern.sub(r"\\g<\1>", line)
414
+ return line
415
+
416
+
417
+ def _process_re_sub_patterns(
418
+ line: str, has_safe_patterns_import: bool
419
+ ) -> tuple[str, bool, bool]:
420
+ """Process re.sub patterns and replace with safe alternatives."""
421
+ re_sub_match = CompiledPatternCache.get_compiled_pattern(
422
+ r're\.sub\s*\(\s*r?["\']([^"\']+)["\']\s*,\s*r?["\']([^"\']*)["\']'
423
+ ).search(line)
424
+
425
+ if not re_sub_match:
426
+ return line, False, False
427
+
428
+ pattern = re_sub_match.group(1)
429
+ replacement = re_sub_match.group(2)
430
+
431
+ safe_pattern_name = _identify_safe_pattern(pattern, replacement)
432
+ if not safe_pattern_name:
433
+ return line, False, False
434
+
435
+ return _replace_with_safe_pattern(line, re_sub_match, safe_pattern_name)
436
+
437
+
438
+ def _identify_safe_pattern(pattern: str, replacement: str) -> str | None:
439
+ """Identify which safe pattern matches the given regex pattern."""
440
+ # Common patterns we can automatically replace
441
+ if pattern == r"(\w+)\s*-\s*(\w+)" and replacement in (
442
+ r"\1-\2",
443
+ r"\g<1>-\g<2>",
444
+ ):
445
+ return "fix_hyphenated_names"
446
+ elif "token" in pattern.lower() and "*" in replacement:
447
+ return "mask_tokens"
448
+ elif r"python\s*-\s*m" in pattern:
449
+ return "fix_python_command_spacing"
450
+ return None
451
+
452
+
453
+ def _replace_with_safe_pattern(
454
+ line: str, re_sub_match: re.Match[str], safe_pattern_name: str
455
+ ) -> tuple[str, bool, bool]:
456
+ """Replace re.sub call with safe pattern call."""
457
+ before_re_sub = line[: re_sub_match.start()]
458
+ after_re_sub = line[re_sub_match.end() :]
459
+
460
+ # Look for assignment pattern: var = re.sub(...)
461
+ assign_match = CompiledPatternCache.get_compiled_pattern(r"(\w+)\s*=\s*$").search(
462
+ before_re_sub
463
+ )
464
+
465
+ if assign_match:
466
+ return _handle_assignment_pattern(
467
+ line, assign_match, before_re_sub, after_re_sub, safe_pattern_name
468
+ )
469
+ return _handle_direct_replacement(line, re_sub_match, safe_pattern_name)
470
+
471
+
472
+ def _handle_assignment_pattern(
473
+ line: str,
474
+ assign_match: re.Match[str],
475
+ before_re_sub: str,
476
+ after_re_sub: str,
477
+ safe_pattern_name: str,
478
+ ) -> tuple[str, bool, bool]:
479
+ """Handle assignment pattern replacement."""
480
+ var_name = assign_match.group(1)
481
+ text_var = _extract_source_variable(line)
482
+ new_line = f"{var_name} = SAFE_PATTERNS['{safe_pattern_name}'].apply({text_var})"
483
+ return before_re_sub + new_line + after_re_sub, True, True
484
+
485
+
486
+ def _handle_direct_replacement(
487
+ line: str, re_sub_match: re.Match[str], safe_pattern_name: str
488
+ ) -> tuple[str, bool, bool]:
489
+ """Handle direct replacement of re.sub call."""
490
+ text_var = _extract_source_variable(line)
491
+ new_line = line.replace(
492
+ re_sub_match.group(0),
493
+ f"SAFE_PATTERNS['{safe_pattern_name}'].apply({text_var})",
494
+ )
495
+ return new_line, True, True
496
+
497
+
498
+ def _extract_source_variable(line: str) -> str:
499
+ """Extract the source variable from re.sub call."""
500
+ full_match = CompiledPatternCache.get_compiled_pattern(
501
+ r"re\.sub\s*\([^,]+,\s*[^,]+,\s*(\w+)"
502
+ ).search(line)
503
+ return full_match.group(1) if full_match else "text"
504
+
505
+
506
+ def _find_import_insertion_point(lines: list[str]) -> int:
507
+ """Find the right place to insert the import statement."""
508
+ import_index = 0
509
+ for j, check_line in enumerate(lines):
510
+ if check_line.strip().startswith(("import ", "from ")):
511
+ import_index = j + 1
512
+ elif check_line.strip() == "":
513
+ continue
514
+ else:
515
+ break
516
+ return import_index
517
+
518
+
519
+ if __name__ == "__main__":
520
+ # Example usage for testing patterns
521
+ test_result = quick_pattern_test(
522
+ pattern=r"(\w+)\s*-\s*(\w+)",
523
+ replacement=r"\1-\2",
524
+ test_cases=[
525
+ ("python - pro", "python-pro"),
526
+ ("already-good", "already-good"),
527
+ ("test - case - multiple", "test-case - multiple"),
528
+ ],
529
+ description="Fix spacing in hyphenated names",
530
+ )
531
+
532
+ # Example migration suggestion
533
+ print("\n" + "=" * 60)
534
+ migration = suggest_migration_for_re_sub(
535
+ r"python\s*-\s*m\s+", "python -m ", "python - m crackerjack"
536
+ )
537
+ print_migration_suggestion(migration)