crackerjack 0.33.0__py3-none-any.whl → 0.33.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of crackerjack might be problematic. Click here for more details.

Files changed (198) hide show
  1. crackerjack/__main__.py +1350 -34
  2. crackerjack/adapters/__init__.py +17 -0
  3. crackerjack/adapters/lsp_client.py +358 -0
  4. crackerjack/adapters/rust_tool_adapter.py +194 -0
  5. crackerjack/adapters/rust_tool_manager.py +193 -0
  6. crackerjack/adapters/skylos_adapter.py +231 -0
  7. crackerjack/adapters/zuban_adapter.py +560 -0
  8. crackerjack/agents/base.py +7 -3
  9. crackerjack/agents/coordinator.py +271 -33
  10. crackerjack/agents/documentation_agent.py +9 -15
  11. crackerjack/agents/dry_agent.py +3 -15
  12. crackerjack/agents/formatting_agent.py +1 -1
  13. crackerjack/agents/import_optimization_agent.py +36 -180
  14. crackerjack/agents/performance_agent.py +17 -98
  15. crackerjack/agents/performance_helpers.py +7 -31
  16. crackerjack/agents/proactive_agent.py +1 -3
  17. crackerjack/agents/refactoring_agent.py +16 -85
  18. crackerjack/agents/refactoring_helpers.py +7 -42
  19. crackerjack/agents/security_agent.py +9 -48
  20. crackerjack/agents/test_creation_agent.py +356 -513
  21. crackerjack/agents/test_specialist_agent.py +0 -4
  22. crackerjack/api.py +6 -25
  23. crackerjack/cli/cache_handlers.py +204 -0
  24. crackerjack/cli/cache_handlers_enhanced.py +683 -0
  25. crackerjack/cli/facade.py +100 -0
  26. crackerjack/cli/handlers.py +224 -9
  27. crackerjack/cli/interactive.py +6 -4
  28. crackerjack/cli/options.py +642 -55
  29. crackerjack/cli/utils.py +2 -1
  30. crackerjack/code_cleaner.py +58 -117
  31. crackerjack/config/global_lock_config.py +8 -48
  32. crackerjack/config/hooks.py +53 -62
  33. crackerjack/core/async_workflow_orchestrator.py +24 -34
  34. crackerjack/core/autofix_coordinator.py +3 -17
  35. crackerjack/core/enhanced_container.py +4 -13
  36. crackerjack/core/file_lifecycle.py +12 -89
  37. crackerjack/core/performance.py +2 -2
  38. crackerjack/core/performance_monitor.py +15 -55
  39. crackerjack/core/phase_coordinator.py +104 -204
  40. crackerjack/core/resource_manager.py +14 -90
  41. crackerjack/core/service_watchdog.py +62 -95
  42. crackerjack/core/session_coordinator.py +149 -0
  43. crackerjack/core/timeout_manager.py +14 -72
  44. crackerjack/core/websocket_lifecycle.py +13 -78
  45. crackerjack/core/workflow_orchestrator.py +171 -174
  46. crackerjack/docs/INDEX.md +11 -0
  47. crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
  48. crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
  49. crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
  50. crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
  51. crackerjack/docs/generated/api/SERVICES.md +1252 -0
  52. crackerjack/documentation/__init__.py +31 -0
  53. crackerjack/documentation/ai_templates.py +756 -0
  54. crackerjack/documentation/dual_output_generator.py +765 -0
  55. crackerjack/documentation/mkdocs_integration.py +518 -0
  56. crackerjack/documentation/reference_generator.py +977 -0
  57. crackerjack/dynamic_config.py +55 -50
  58. crackerjack/executors/async_hook_executor.py +10 -15
  59. crackerjack/executors/cached_hook_executor.py +117 -43
  60. crackerjack/executors/hook_executor.py +8 -34
  61. crackerjack/executors/hook_lock_manager.py +26 -183
  62. crackerjack/executors/individual_hook_executor.py +13 -11
  63. crackerjack/executors/lsp_aware_hook_executor.py +270 -0
  64. crackerjack/executors/tool_proxy.py +417 -0
  65. crackerjack/hooks/lsp_hook.py +79 -0
  66. crackerjack/intelligence/adaptive_learning.py +25 -10
  67. crackerjack/intelligence/agent_orchestrator.py +2 -5
  68. crackerjack/intelligence/agent_registry.py +34 -24
  69. crackerjack/intelligence/agent_selector.py +5 -7
  70. crackerjack/interactive.py +17 -6
  71. crackerjack/managers/async_hook_manager.py +0 -1
  72. crackerjack/managers/hook_manager.py +79 -1
  73. crackerjack/managers/publish_manager.py +44 -8
  74. crackerjack/managers/test_command_builder.py +1 -15
  75. crackerjack/managers/test_executor.py +1 -3
  76. crackerjack/managers/test_manager.py +98 -7
  77. crackerjack/managers/test_manager_backup.py +10 -9
  78. crackerjack/mcp/cache.py +2 -2
  79. crackerjack/mcp/client_runner.py +1 -1
  80. crackerjack/mcp/context.py +191 -68
  81. crackerjack/mcp/dashboard.py +7 -5
  82. crackerjack/mcp/enhanced_progress_monitor.py +31 -28
  83. crackerjack/mcp/file_monitor.py +30 -23
  84. crackerjack/mcp/progress_components.py +31 -21
  85. crackerjack/mcp/progress_monitor.py +50 -53
  86. crackerjack/mcp/rate_limiter.py +6 -6
  87. crackerjack/mcp/server_core.py +17 -16
  88. crackerjack/mcp/service_watchdog.py +2 -1
  89. crackerjack/mcp/state.py +4 -7
  90. crackerjack/mcp/task_manager.py +11 -9
  91. crackerjack/mcp/tools/core_tools.py +173 -32
  92. crackerjack/mcp/tools/error_analyzer.py +3 -2
  93. crackerjack/mcp/tools/execution_tools.py +8 -10
  94. crackerjack/mcp/tools/execution_tools_backup.py +42 -30
  95. crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
  96. crackerjack/mcp/tools/intelligence_tools.py +5 -2
  97. crackerjack/mcp/tools/monitoring_tools.py +33 -70
  98. crackerjack/mcp/tools/proactive_tools.py +24 -11
  99. crackerjack/mcp/tools/progress_tools.py +5 -8
  100. crackerjack/mcp/tools/utility_tools.py +20 -14
  101. crackerjack/mcp/tools/workflow_executor.py +62 -40
  102. crackerjack/mcp/websocket/app.py +8 -0
  103. crackerjack/mcp/websocket/endpoints.py +352 -357
  104. crackerjack/mcp/websocket/jobs.py +40 -57
  105. crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
  106. crackerjack/mcp/websocket/server.py +7 -25
  107. crackerjack/mcp/websocket/websocket_handler.py +6 -17
  108. crackerjack/mixins/__init__.py +0 -2
  109. crackerjack/mixins/error_handling.py +1 -70
  110. crackerjack/models/config.py +12 -1
  111. crackerjack/models/config_adapter.py +49 -1
  112. crackerjack/models/protocols.py +122 -122
  113. crackerjack/models/resource_protocols.py +55 -210
  114. crackerjack/monitoring/ai_agent_watchdog.py +13 -13
  115. crackerjack/monitoring/metrics_collector.py +426 -0
  116. crackerjack/monitoring/regression_prevention.py +8 -8
  117. crackerjack/monitoring/websocket_server.py +643 -0
  118. crackerjack/orchestration/advanced_orchestrator.py +11 -6
  119. crackerjack/orchestration/coverage_improvement.py +3 -3
  120. crackerjack/orchestration/execution_strategies.py +26 -6
  121. crackerjack/orchestration/test_progress_streamer.py +8 -5
  122. crackerjack/plugins/base.py +2 -2
  123. crackerjack/plugins/hooks.py +7 -0
  124. crackerjack/plugins/managers.py +11 -8
  125. crackerjack/security/__init__.py +0 -1
  126. crackerjack/security/audit.py +6 -35
  127. crackerjack/services/anomaly_detector.py +392 -0
  128. crackerjack/services/api_extractor.py +615 -0
  129. crackerjack/services/backup_service.py +2 -2
  130. crackerjack/services/bounded_status_operations.py +15 -152
  131. crackerjack/services/cache.py +127 -1
  132. crackerjack/services/changelog_automation.py +395 -0
  133. crackerjack/services/config.py +15 -9
  134. crackerjack/services/config_merge.py +19 -80
  135. crackerjack/services/config_template.py +506 -0
  136. crackerjack/services/contextual_ai_assistant.py +48 -22
  137. crackerjack/services/coverage_badge_service.py +171 -0
  138. crackerjack/services/coverage_ratchet.py +27 -25
  139. crackerjack/services/debug.py +3 -3
  140. crackerjack/services/dependency_analyzer.py +460 -0
  141. crackerjack/services/dependency_monitor.py +14 -11
  142. crackerjack/services/documentation_generator.py +491 -0
  143. crackerjack/services/documentation_service.py +675 -0
  144. crackerjack/services/enhanced_filesystem.py +6 -5
  145. crackerjack/services/enterprise_optimizer.py +865 -0
  146. crackerjack/services/error_pattern_analyzer.py +676 -0
  147. crackerjack/services/file_hasher.py +1 -1
  148. crackerjack/services/git.py +8 -25
  149. crackerjack/services/health_metrics.py +10 -8
  150. crackerjack/services/heatmap_generator.py +735 -0
  151. crackerjack/services/initialization.py +11 -30
  152. crackerjack/services/input_validator.py +5 -97
  153. crackerjack/services/intelligent_commit.py +327 -0
  154. crackerjack/services/log_manager.py +15 -12
  155. crackerjack/services/logging.py +4 -3
  156. crackerjack/services/lsp_client.py +628 -0
  157. crackerjack/services/memory_optimizer.py +19 -87
  158. crackerjack/services/metrics.py +42 -33
  159. crackerjack/services/parallel_executor.py +9 -67
  160. crackerjack/services/pattern_cache.py +1 -1
  161. crackerjack/services/pattern_detector.py +6 -6
  162. crackerjack/services/performance_benchmarks.py +18 -59
  163. crackerjack/services/performance_cache.py +20 -81
  164. crackerjack/services/performance_monitor.py +27 -95
  165. crackerjack/services/predictive_analytics.py +510 -0
  166. crackerjack/services/quality_baseline.py +234 -0
  167. crackerjack/services/quality_baseline_enhanced.py +646 -0
  168. crackerjack/services/quality_intelligence.py +785 -0
  169. crackerjack/services/regex_patterns.py +618 -524
  170. crackerjack/services/regex_utils.py +43 -123
  171. crackerjack/services/secure_path_utils.py +5 -164
  172. crackerjack/services/secure_status_formatter.py +30 -141
  173. crackerjack/services/secure_subprocess.py +11 -92
  174. crackerjack/services/security.py +9 -41
  175. crackerjack/services/security_logger.py +12 -24
  176. crackerjack/services/server_manager.py +124 -16
  177. crackerjack/services/status_authentication.py +16 -159
  178. crackerjack/services/status_security_manager.py +4 -131
  179. crackerjack/services/thread_safe_status_collector.py +19 -125
  180. crackerjack/services/unified_config.py +21 -13
  181. crackerjack/services/validation_rate_limiter.py +5 -54
  182. crackerjack/services/version_analyzer.py +459 -0
  183. crackerjack/services/version_checker.py +1 -1
  184. crackerjack/services/websocket_resource_limiter.py +10 -144
  185. crackerjack/services/zuban_lsp_service.py +390 -0
  186. crackerjack/slash_commands/__init__.py +2 -7
  187. crackerjack/slash_commands/run.md +2 -2
  188. crackerjack/tools/validate_input_validator_patterns.py +14 -40
  189. crackerjack/tools/validate_regex_patterns.py +19 -48
  190. {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/METADATA +196 -25
  191. crackerjack-0.33.2.dist-info/RECORD +229 -0
  192. crackerjack/CLAUDE.md +0 -207
  193. crackerjack/RULES.md +0 -380
  194. crackerjack/py313.py +0 -234
  195. crackerjack-0.33.0.dist-info/RECORD +0 -187
  196. {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/WHEEL +0 -0
  197. {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/entry_points.txt +0 -0
  198. {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,12 +1,8 @@
1
- """Refactoring analysis helper classes and utilities."""
2
-
3
1
  import ast
4
2
  import typing as t
5
3
 
6
4
 
7
5
  class ComplexityCalculator(ast.NodeVisitor):
8
- """Calculator for cognitive complexity analysis."""
9
-
10
6
  def __init__(self) -> None:
11
7
  self.complexity = 0
12
8
  self.nesting_level = 0
@@ -43,36 +39,28 @@ class ComplexityCalculator(ast.NodeVisitor):
43
39
  self._process_comprehension(node)
44
40
 
45
41
  def _process_conditional_node(self, node: ast.If) -> None:
46
- """Process if/elif nodes with condition complexity."""
47
- # Base complexity + nesting penalty
48
42
  self.complexity += 1 + self.nesting_level
49
43
 
50
- # Penalty for complex conditions
51
44
  if self._has_complex_condition(node.test):
52
45
  self.complexity += 1
53
46
 
54
47
  self._visit_with_nesting(node)
55
48
 
56
49
  def _process_loop_node(self, node: ast.For | ast.While) -> None:
57
- """Process for/while loop nodes."""
58
50
  self.complexity += 1 + self.nesting_level
59
51
  self._visit_with_nesting(node)
60
52
 
61
53
  def _process_try_node(self, node: ast.Try) -> None:
62
- """Process try/except nodes with handler penalty."""
63
- # Base complexity for try + number of except handlers
64
54
  self.complexity += 1 + self.nesting_level + len(node.handlers)
65
55
  self._visit_with_nesting(node)
66
56
 
67
57
  def _process_context_node(self, node: ast.With) -> None:
68
- """Process with/context manager nodes."""
69
58
  self.complexity += 1 + self.nesting_level
70
59
  self._visit_with_nesting(node)
71
60
 
72
61
  def _process_boolean_operation(self, node: ast.BoolOp) -> None:
73
- """Process boolean operations with chain penalty."""
74
62
  penalty = len(node.values) - 1
75
- if penalty > 2: # Long chains are more complex
63
+ if penalty > 2:
76
64
  penalty += 1
77
65
  self.complexity += penalty
78
66
  self.generic_visit(node)
@@ -80,31 +68,26 @@ class ComplexityCalculator(ast.NodeVisitor):
80
68
  def _process_comprehension(
81
69
  self, node: ast.ListComp | ast.DictComp | ast.SetComp | ast.GeneratorExp
82
70
  ) -> None:
83
- """Process comprehensions with condition penalty."""
84
71
  self.complexity += 1
85
- # Check each generator for ifs conditions
72
+
86
73
  for generator in node.generators:
87
74
  if hasattr(generator, "ifs") and generator.ifs:
88
75
  self.complexity += len(generator.ifs)
89
76
  self.generic_visit(node)
90
77
 
91
78
  def _visit_with_nesting(self, node: ast.AST) -> None:
92
- """Visit a node with proper nesting level tracking."""
93
79
  self.nesting_level += 1
94
80
  self.generic_visit(node)
95
81
  self.nesting_level -= 1
96
82
 
97
83
  def _has_complex_condition(self, node: ast.expr) -> bool:
98
- """Check if condition involves complex expressions."""
99
84
  return (isinstance(node, ast.BoolOp) and len(node.values) > 2) or isinstance(
100
85
  node, ast.Compare | ast.Call
101
86
  )
102
87
 
103
88
 
104
89
  class UsageDataCollector:
105
- """Collector for usage data analysis."""
106
-
107
- def __init__(self):
90
+ def __init__(self) -> None:
108
91
  self.defined_names: set[str] = set()
109
92
  self.used_names: set[str] = set()
110
93
  self.import_lines: list[tuple[int, str, str]] = []
@@ -113,7 +96,6 @@ class UsageDataCollector:
113
96
  self.unused_variables: list[dict[str, t.Any]] = []
114
97
 
115
98
  def get_results(self, analyzer: "EnhancedUsageAnalyzer") -> dict[str, t.Any]:
116
- """Get collected usage data results."""
117
99
  return {
118
100
  "defined_names": self.defined_names,
119
101
  "used_names": self.used_names,
@@ -126,12 +108,10 @@ class UsageDataCollector:
126
108
 
127
109
 
128
110
  class EnhancedUsageAnalyzer(ast.NodeVisitor):
129
- """Analyzer for enhanced usage data collection."""
130
-
131
- def __init__(self, collector: UsageDataCollector):
132
- self.scope_stack = [set()] # Track variable scopes
133
- self.class_methods = {} # Track class method usage
134
- self.function_calls = set() # Track function calls
111
+ def __init__(self, collector: UsageDataCollector) -> None:
112
+ self.scope_stack: list[set[str]] = [set()]
113
+ self.class_methods: dict[str, list[str]] = {}
114
+ self.function_calls: set[str] = set()
135
115
  self.collector = collector
136
116
 
137
117
  def visit_Import(self, node: ast.Import) -> None:
@@ -165,21 +145,18 @@ class EnhancedUsageAnalyzer(ast.NodeVisitor):
165
145
  self._process_attribute_access(node)
166
146
 
167
147
  def _process_import_node(self, node: ast.Import) -> None:
168
- """Process import statements."""
169
148
  for alias in node.names:
170
149
  name = alias.asname or alias.name
171
150
  self.collector.defined_names.add(name)
172
151
  self.collector.import_lines.append((node.lineno, name, "import"))
173
152
 
174
153
  def _process_import_from_node(self, node: ast.ImportFrom) -> None:
175
- """Process from-import statements."""
176
154
  for alias in node.names:
177
155
  name = alias.asname or alias.name
178
156
  self.collector.defined_names.add(name)
179
157
  self.collector.import_lines.append((node.lineno, name, "from_import"))
180
158
 
181
159
  def _process_function_definition(self, node: ast.FunctionDef) -> None:
182
- """Process function definitions."""
183
160
  self.collector.defined_names.add(node.name)
184
161
  if self._should_track_function(node.name):
185
162
  function_info = self._create_function_info(node)
@@ -187,7 +164,6 @@ class EnhancedUsageAnalyzer(ast.NodeVisitor):
187
164
  self._visit_with_scope(node)
188
165
 
189
166
  def _process_async_function_definition(self, node: ast.AsyncFunctionDef) -> None:
190
- """Process async function definitions."""
191
167
  self.collector.defined_names.add(node.name)
192
168
  if not node.name.startswith("_"):
193
169
  function_info = self._create_function_info(node)
@@ -195,7 +171,6 @@ class EnhancedUsageAnalyzer(ast.NodeVisitor):
195
171
  self._visit_with_scope(node)
196
172
 
197
173
  def _process_class_definition(self, node: ast.ClassDef) -> None:
198
- """Process class definitions."""
199
174
  self.collector.defined_names.add(node.name)
200
175
  self.collector.unused_classes.append(
201
176
  {
@@ -207,7 +182,6 @@ class EnhancedUsageAnalyzer(ast.NodeVisitor):
207
182
  self._visit_with_scope(node)
208
183
 
209
184
  def _process_assignment(self, node: ast.Assign) -> None:
210
- """Process variable assignments."""
211
185
  for target in node.targets:
212
186
  if isinstance(target, ast.Name):
213
187
  self.collector.defined_names.add(target.id)
@@ -217,20 +191,17 @@ class EnhancedUsageAnalyzer(ast.NodeVisitor):
217
191
  self.generic_visit(node)
218
192
 
219
193
  def _process_annotated_assignment(self, node: ast.AnnAssign) -> None:
220
- """Process annotated assignments."""
221
194
  if isinstance(node.target, ast.Name):
222
195
  self.collector.defined_names.add(node.target.id)
223
196
  self.generic_visit(node)
224
197
 
225
198
  def _process_name_usage(self, node: ast.Name) -> None:
226
- """Process name usage (variable references)."""
227
199
  if isinstance(node.ctx, ast.Load):
228
200
  self.collector.used_names.add(node.id)
229
201
  if self.scope_stack:
230
202
  self.scope_stack[-1].add(node.id)
231
203
 
232
204
  def _process_function_call(self, node: ast.Call) -> None:
233
- """Process function/method calls."""
234
205
  if isinstance(node.func, ast.Name):
235
206
  self.function_calls.add(node.func.id)
236
207
  self.collector.used_names.add(node.func.id)
@@ -241,19 +212,16 @@ class EnhancedUsageAnalyzer(ast.NodeVisitor):
241
212
  self.generic_visit(node)
242
213
 
243
214
  def _process_attribute_access(self, node: ast.Attribute) -> None:
244
- """Process attribute access."""
245
215
  if isinstance(node.value, ast.Name):
246
216
  self.collector.used_names.add(node.value.id)
247
217
  self.generic_visit(node)
248
218
 
249
219
  def _should_track_function(self, name: str) -> bool:
250
- """Determine if function should be tracked for unused analysis."""
251
220
  return not name.startswith("_") and name != "__init__"
252
221
 
253
222
  def _create_function_info(
254
223
  self, node: ast.FunctionDef | ast.AsyncFunctionDef
255
224
  ) -> dict[str, t.Any]:
256
- """Create function information dictionary."""
257
225
  return {
258
226
  "name": node.name,
259
227
  "line": node.lineno,
@@ -264,7 +232,6 @@ class EnhancedUsageAnalyzer(ast.NodeVisitor):
264
232
  def _create_variable_info(
265
233
  self, target: ast.Name, node: ast.Assign
266
234
  ) -> dict[str, t.Any]:
267
- """Create variable information dictionary."""
268
235
  return {
269
236
  "name": target.id,
270
237
  "line": node.lineno,
@@ -272,11 +239,9 @@ class EnhancedUsageAnalyzer(ast.NodeVisitor):
272
239
  }
273
240
 
274
241
  def _is_in_function_or_class_scope(self) -> bool:
275
- """Check if currently in function or class scope."""
276
242
  return len(self.scope_stack) > 1
277
243
 
278
244
  def _visit_with_scope(self, node: ast.AST) -> None:
279
- """Visit node with proper scope tracking."""
280
245
  self.scope_stack.append(set())
281
246
  self.generic_visit(node)
282
247
  self.scope_stack.pop()
@@ -24,11 +24,9 @@ class SecurityAgent(SubAgent):
24
24
 
25
25
  message_lower = issue.message.lower()
26
26
 
27
- # High confidence (0.95) for regex validation issues
28
27
  if issue.type == IssueType.REGEX_VALIDATION:
29
28
  return 0.95
30
29
 
31
- # High confidence (0.95) for regex validation keywords
32
30
  if any(
33
31
  keyword in message_lower
34
32
  for keyword in (
@@ -44,7 +42,6 @@ class SecurityAgent(SubAgent):
44
42
  ):
45
43
  return 0.95
46
44
 
47
- # High confidence (0.9) for critical security issues
48
45
  if any(
49
46
  keyword in message_lower
50
47
  for keyword in (
@@ -68,7 +65,6 @@ class SecurityAgent(SubAgent):
68
65
  ):
69
66
  return 0.9
70
67
 
71
- # Enhanced detection using new security patterns
72
68
  enhanced_patterns = [
73
69
  "detect_security_keywords",
74
70
  "detect_crypto_weak_algorithms",
@@ -81,14 +77,12 @@ class SecurityAgent(SubAgent):
81
77
  if SAFE_PATTERNS[pattern_name].test(issue.message):
82
78
  return 0.9
83
79
 
84
- # Medium confidence for security-related files
85
80
  if issue.file_path and any(
86
81
  keyword in issue.file_path.lower()
87
82
  for keyword in ("security", "auth", "crypto", "password", "token", "jwt")
88
83
  ):
89
84
  return 0.7
90
85
 
91
- # Base confidence for security type
92
86
  if issue.type == IssueType.SECURITY:
93
87
  return 0.6
94
88
 
@@ -156,7 +150,7 @@ class SecurityAgent(SubAgent):
156
150
  "insecure_random": self._fix_insecure_random,
157
151
  }
158
152
 
159
- if fix_method := vulnerability_fix_map.get(vulnerability_type):
153
+ if (fix_method := vulnerability_fix_map.get(vulnerability_type)) is not None:
160
154
  fixes = await fix_method(issue)
161
155
  fixes_applied.extend(fixes["fixes"])
162
156
  files_modified.extend(fixes["files"])
@@ -184,7 +178,7 @@ class SecurityAgent(SubAgent):
184
178
  def _get_security_recommendations(self) -> list[str]:
185
179
  return [
186
180
  "Use centralized SAFE_PATTERNS for regex operations to prevent ReDoS attacks",
187
- "Avoid raw regex patterns with vulnerable replacement syntax like \\g < 1 >",
181
+ "Avoid raw regex patterns with vulnerable replacement syntax like \\g<1>",
188
182
  "Use tempfile module for temporary file creation instead of hardcoded paths",
189
183
  "Avoid shell=True in subprocess calls to prevent command injection",
190
184
  "Store secrets in environment variables using os.getenv(), never hardcode them",
@@ -215,33 +209,27 @@ class SecurityAgent(SubAgent):
215
209
  def _identify_vulnerability_type(self, issue: Issue) -> str:
216
210
  message = issue.message
217
211
 
218
- # Regex validation issues
219
212
  if self._is_regex_validation_issue(issue):
220
213
  return "regex_validation"
221
214
 
222
- # Enhanced pattern-based detection
223
215
  pattern_checks = self._check_enhanced_patterns(message)
224
216
  if pattern_checks:
225
217
  return pattern_checks
226
218
 
227
- # Bandit code-based detection
228
219
  bandit_checks = self._check_bandit_patterns(message)
229
220
  if bandit_checks:
230
221
  return bandit_checks
231
222
 
232
- # Legacy pattern-based detection
233
223
  legacy_checks = self._check_legacy_patterns(message)
234
224
  if legacy_checks:
235
225
  return legacy_checks
236
226
 
237
- # JWT and other specific patterns
238
227
  if self._is_jwt_secret_issue(message):
239
228
  return "jwt_secrets"
240
229
 
241
230
  return "unknown"
242
231
 
243
232
  def _is_regex_validation_issue(self, issue: Issue) -> bool:
244
- """Check if issue is related to regex validation."""
245
233
  if issue.type == IssueType.REGEX_VALIDATION:
246
234
  return True
247
235
 
@@ -258,7 +246,6 @@ class SecurityAgent(SubAgent):
258
246
  )
259
247
 
260
248
  def _check_enhanced_patterns(self, message: str) -> str | None:
261
- """Check enhanced security patterns."""
262
249
  pattern_map = {
263
250
  "detect_crypto_weak_algorithms": "weak_crypto",
264
251
  "detect_hardcoded_credentials_advanced": "hardcoded_secrets",
@@ -274,7 +261,6 @@ class SecurityAgent(SubAgent):
274
261
  return None
275
262
 
276
263
  def _check_bandit_patterns(self, message: str) -> str | None:
277
- """Check Bandit-specific patterns."""
278
264
  if "B108" in message:
279
265
  return "hardcoded_temp_paths"
280
266
  if "B602" in message or "shell=True" in message:
@@ -289,7 +275,6 @@ class SecurityAgent(SubAgent):
289
275
  return None
290
276
 
291
277
  def _check_legacy_patterns(self, message: str) -> str | None:
292
- """Check legacy security patterns."""
293
278
  pattern_map = {
294
279
  "detect_hardcoded_temp_paths_basic": "hardcoded_temp_paths",
295
280
  "detect_hardcoded_secrets": "hardcoded_secrets",
@@ -303,19 +288,16 @@ class SecurityAgent(SubAgent):
303
288
  return None
304
289
 
305
290
  def _is_jwt_secret_issue(self, message: str) -> bool:
306
- """Check if message indicates JWT secret issue."""
307
291
  message_lower = message.lower()
308
292
  return "jwt" in message_lower and (
309
293
  "secret" in message_lower or "hardcoded" in message_lower
310
294
  )
311
295
 
312
296
  async def _fix_regex_validation_issues(self, issue: Issue) -> dict[str, list[str]]:
313
- """Fix unsafe regex patterns by converting them to use centralized SAFE_PATTERNS."""
314
297
  fixes: list[str] = []
315
298
  files: list[str] = []
316
299
 
317
300
  if not issue.file_path:
318
- # If no specific file path, scan all Python files in the project
319
301
  await self._fix_regex_patterns_project_wide(fixes, files)
320
302
  return {"fixes": fixes, "files": files}
321
303
 
@@ -341,7 +323,6 @@ class SecurityAgent(SubAgent):
341
323
  async def _fix_regex_patterns_project_wide(
342
324
  self, fixes: list[str], files: list[str]
343
325
  ) -> None:
344
- """Fix regex patterns across the entire project."""
345
326
  try:
346
327
  python_files = self._get_python_files_for_security_scan()
347
328
  await self._process_python_files_for_regex_fixes(python_files, fixes, files)
@@ -349,28 +330,24 @@ class SecurityAgent(SubAgent):
349
330
  self.log(f"Error during project-wide regex fixes: {e}", "ERROR")
350
331
 
351
332
  def _get_python_files_for_security_scan(self) -> list[Path]:
352
- """Get list of Python files that should be scanned for security issues."""
353
333
  python_files = list(self.context.project_path.rglob("*.py"))
354
334
  return [
355
335
  f for f in python_files if not self._should_skip_file_for_security_scan(f)
356
336
  ]
357
337
 
358
338
  def _should_skip_file_for_security_scan(self, file_path: Path) -> bool:
359
- """Check if a file should be skipped during security scanning."""
360
339
  skip_patterns = [".venv", "__pycache__", ".git"]
361
340
  return any(part in str(file_path) for part in skip_patterns)
362
341
 
363
342
  async def _process_python_files_for_regex_fixes(
364
343
  self, python_files: list[Path], fixes: list[str], files: list[str]
365
344
  ) -> None:
366
- """Process each Python file for regex fixes."""
367
345
  for file_path in python_files:
368
346
  await self._process_single_file_for_regex_fixes(file_path, fixes, files)
369
347
 
370
348
  async def _process_single_file_for_regex_fixes(
371
349
  self, file_path: Path, fixes: list[str], files: list[str]
372
350
  ) -> None:
373
- """Process a single file for regex pattern fixes."""
374
351
  content = self.context.get_file_content(file_path)
375
352
  if not content:
376
353
  return
@@ -382,27 +359,22 @@ class SecurityAgent(SubAgent):
382
359
  await self._save_regex_fixes_to_file(file_path, content, fixes, files)
383
360
 
384
361
  def _should_save_regex_fixes(self, content: str, original_content: str) -> bool:
385
- """Check if regex fixes should be saved."""
386
362
  return content != original_content
387
363
 
388
364
  async def _save_regex_fixes_to_file(
389
365
  self, file_path: Path, content: str, fixes: list[str], files: list[str]
390
366
  ) -> None:
391
- """Save regex fixes to file and update tracking lists."""
392
367
  if self.context.write_file_content(file_path, content):
393
368
  fixes.append(f"Fixed unsafe regex patterns in {file_path}")
394
369
  files.append(str(file_path))
395
370
  self.log(f"Fixed regex patterns in {file_path}")
396
371
 
397
372
  async def _apply_regex_pattern_fixes(self, content: str) -> str:
398
- """Apply all regex pattern fixes using SAFE_PATTERNS."""
399
- # Import regex fix utilities
400
373
  from crackerjack.services.regex_utils import (
401
374
  replace_unsafe_regex_with_safe_patterns,
402
375
  )
403
376
 
404
377
  try:
405
- # Apply centralized regex fixes
406
378
  fixed_content = replace_unsafe_regex_with_safe_patterns(content)
407
379
  return fixed_content
408
380
  except Exception as e:
@@ -464,12 +436,9 @@ class SecurityAgent(SubAgent):
464
436
  return lines, True
465
437
 
466
438
  def _replace_hardcoded_temp_paths(self, lines: list[str]) -> tuple[list[str], bool]:
467
- # Apply temp path replacements using SAFE_PATTERNS
468
439
  new_content = "\n".join(lines)
469
440
 
470
- # Check if any temp paths need replacement
471
441
  if SAFE_PATTERNS["detect_hardcoded_temp_paths_basic"].test(new_content):
472
- # Apply multiple replacement patterns
473
442
  new_content = SAFE_PATTERNS["replace_hardcoded_temp_paths"].apply(
474
443
  new_content
475
444
  )
@@ -571,9 +540,11 @@ class SecurityAgent(SubAgent):
571
540
  return SAFE_PATTERNS["detect_hardcoded_secrets"].test(line)
572
541
 
573
542
  def _replace_hardcoded_secret_with_env_var(self, line: str) -> str:
574
- # Extract variable name using safe patterns
575
- var_name = SAFE_PATTERNS["extract_variable_name_from_assignment"].apply(line)
576
- if var_name != line: # If pattern matched and extracted something
543
+ var_name_result = SAFE_PATTERNS["extract_variable_name_from_assignment"].apply(
544
+ line
545
+ )
546
+ if var_name_result != line: # Pattern matched and extracted variable name
547
+ var_name = var_name_result
577
548
  env_var_name = var_name.upper()
578
549
  return f"{var_name} = os.getenv('{env_var_name}', '')"
579
550
  return line
@@ -642,7 +613,6 @@ class SecurityAgent(SubAgent):
642
613
  return {"fixes": fixes, "files": files}
643
614
 
644
615
  async def _fix_jwt_secrets(self, issue: Issue) -> dict[str, list[str]]:
645
- """Fix hardcoded JWT secrets by replacing with environment variables."""
646
616
  fixes: list[str] = []
647
617
  files: list[str] = []
648
618
 
@@ -656,10 +626,8 @@ class SecurityAgent(SubAgent):
656
626
 
657
627
  original_content = content
658
628
 
659
- # Apply JWT secret fix pattern
660
629
  content = SAFE_PATTERNS["fix_hardcoded_jwt_secret"].apply(content)
661
630
 
662
- # Ensure os import is present
663
631
  if "os.getenv" in content and "import os" not in content:
664
632
  lines = content.split("\n")
665
633
  import_index = 0
@@ -678,7 +646,6 @@ class SecurityAgent(SubAgent):
678
646
  return {"fixes": fixes, "files": files}
679
647
 
680
648
  async def _fix_pickle_usage(self, issue: Issue) -> dict[str, list[str]]:
681
- """Fix unsafe pickle usage by documenting the security risk."""
682
649
  fixes: list[str] = []
683
650
  files: list[str] = []
684
651
 
@@ -690,19 +657,16 @@ class SecurityAgent(SubAgent):
690
657
  if not content:
691
658
  return {"fixes": fixes, "files": files}
692
659
 
693
- # For pickle usage, we document the risk rather than auto-fixing
694
- # as pickle is sometimes necessary and the fix depends on context
695
660
  fixes.append(
696
661
  f"Documented unsafe pickle usage in {issue.file_path} - manual review required"
697
662
  )
698
663
 
699
- # Add a security comment if pickle.load/loads is found
700
664
  if "pickle.load" in content:
701
665
  lines = content.split("\n")
702
666
  for i, line in enumerate(lines):
703
- if "pickle.load" in line and "# SECURITY:" not in line:
667
+ if "pickle.load" in line and "# SECURITY: " not in line:
704
668
  lines[i] = (
705
- line + " # SECURITY: pickle.load is unsafe with untrusted data"
669
+ line + " # SECURITY: pickle.load is unsafe with untrusted data"
706
670
  )
707
671
  if self.context.write_file_content(file_path, "\n".join(lines)):
708
672
  fixes.append(
@@ -717,7 +681,6 @@ class SecurityAgent(SubAgent):
717
681
  return {"fixes": fixes, "files": files}
718
682
 
719
683
  async def _fix_insecure_random(self, issue: Issue) -> dict[str, list[str]]:
720
- """Fix insecure random usage by replacing with secrets module."""
721
684
  fixes: list[str] = []
722
685
  files: list[str] = []
723
686
 
@@ -731,10 +694,8 @@ class SecurityAgent(SubAgent):
731
694
 
732
695
  original_content = content
733
696
 
734
- # Apply insecure random fix
735
697
  content = SAFE_PATTERNS["fix_insecure_random_choice"].apply(content)
736
698
 
737
- # Ensure secrets import
738
699
  if "secrets.choice" in content and "import secrets" not in content:
739
700
  lines = content.split("\n")
740
701
  import_index = 0