crackerjack 0.32.0__py3-none-any.whl → 0.33.1__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/__main__.py +1350 -34
- crackerjack/adapters/__init__.py +17 -0
- crackerjack/adapters/lsp_client.py +358 -0
- crackerjack/adapters/rust_tool_adapter.py +194 -0
- crackerjack/adapters/rust_tool_manager.py +193 -0
- crackerjack/adapters/skylos_adapter.py +231 -0
- crackerjack/adapters/zuban_adapter.py +560 -0
- crackerjack/agents/base.py +7 -3
- crackerjack/agents/coordinator.py +271 -33
- crackerjack/agents/documentation_agent.py +9 -15
- crackerjack/agents/dry_agent.py +3 -15
- crackerjack/agents/formatting_agent.py +1 -1
- crackerjack/agents/import_optimization_agent.py +36 -180
- crackerjack/agents/performance_agent.py +17 -98
- crackerjack/agents/performance_helpers.py +7 -31
- crackerjack/agents/proactive_agent.py +1 -3
- crackerjack/agents/refactoring_agent.py +16 -85
- crackerjack/agents/refactoring_helpers.py +7 -42
- crackerjack/agents/security_agent.py +9 -48
- crackerjack/agents/test_creation_agent.py +356 -513
- crackerjack/agents/test_specialist_agent.py +0 -4
- crackerjack/api.py +6 -25
- crackerjack/cli/cache_handlers.py +204 -0
- crackerjack/cli/cache_handlers_enhanced.py +683 -0
- crackerjack/cli/facade.py +100 -0
- crackerjack/cli/handlers.py +224 -9
- crackerjack/cli/interactive.py +6 -4
- crackerjack/cli/options.py +642 -55
- crackerjack/cli/utils.py +2 -1
- crackerjack/code_cleaner.py +58 -117
- crackerjack/config/global_lock_config.py +8 -48
- crackerjack/config/hooks.py +53 -62
- crackerjack/core/async_workflow_orchestrator.py +24 -34
- crackerjack/core/autofix_coordinator.py +3 -17
- crackerjack/core/enhanced_container.py +64 -6
- crackerjack/core/file_lifecycle.py +12 -89
- crackerjack/core/performance.py +2 -2
- crackerjack/core/performance_monitor.py +15 -55
- crackerjack/core/phase_coordinator.py +257 -218
- crackerjack/core/resource_manager.py +14 -90
- crackerjack/core/service_watchdog.py +62 -95
- crackerjack/core/session_coordinator.py +149 -0
- crackerjack/core/timeout_manager.py +14 -72
- crackerjack/core/websocket_lifecycle.py +13 -78
- crackerjack/core/workflow_orchestrator.py +558 -240
- crackerjack/docs/INDEX.md +11 -0
- crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
- crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
- crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
- crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
- crackerjack/docs/generated/api/SERVICES.md +1252 -0
- crackerjack/documentation/__init__.py +31 -0
- crackerjack/documentation/ai_templates.py +756 -0
- crackerjack/documentation/dual_output_generator.py +765 -0
- crackerjack/documentation/mkdocs_integration.py +518 -0
- crackerjack/documentation/reference_generator.py +977 -0
- crackerjack/dynamic_config.py +55 -50
- crackerjack/executors/async_hook_executor.py +10 -15
- crackerjack/executors/cached_hook_executor.py +117 -43
- crackerjack/executors/hook_executor.py +8 -34
- crackerjack/executors/hook_lock_manager.py +26 -183
- crackerjack/executors/individual_hook_executor.py +13 -11
- crackerjack/executors/lsp_aware_hook_executor.py +270 -0
- crackerjack/executors/tool_proxy.py +417 -0
- crackerjack/hooks/lsp_hook.py +79 -0
- crackerjack/intelligence/adaptive_learning.py +25 -10
- crackerjack/intelligence/agent_orchestrator.py +2 -5
- crackerjack/intelligence/agent_registry.py +34 -24
- crackerjack/intelligence/agent_selector.py +5 -7
- crackerjack/interactive.py +17 -6
- crackerjack/managers/async_hook_manager.py +0 -1
- crackerjack/managers/hook_manager.py +79 -1
- crackerjack/managers/publish_manager.py +66 -13
- crackerjack/managers/test_command_builder.py +5 -17
- crackerjack/managers/test_executor.py +1 -3
- crackerjack/managers/test_manager.py +109 -7
- crackerjack/managers/test_manager_backup.py +10 -9
- crackerjack/mcp/cache.py +2 -2
- crackerjack/mcp/client_runner.py +1 -1
- crackerjack/mcp/context.py +191 -68
- crackerjack/mcp/dashboard.py +7 -5
- crackerjack/mcp/enhanced_progress_monitor.py +31 -28
- crackerjack/mcp/file_monitor.py +30 -23
- crackerjack/mcp/progress_components.py +31 -21
- crackerjack/mcp/progress_monitor.py +50 -53
- crackerjack/mcp/rate_limiter.py +6 -6
- crackerjack/mcp/server_core.py +161 -32
- crackerjack/mcp/service_watchdog.py +2 -1
- crackerjack/mcp/state.py +4 -7
- crackerjack/mcp/task_manager.py +11 -9
- crackerjack/mcp/tools/core_tools.py +174 -33
- crackerjack/mcp/tools/error_analyzer.py +3 -2
- crackerjack/mcp/tools/execution_tools.py +15 -12
- crackerjack/mcp/tools/execution_tools_backup.py +42 -30
- crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
- crackerjack/mcp/tools/intelligence_tools.py +5 -2
- crackerjack/mcp/tools/monitoring_tools.py +33 -70
- crackerjack/mcp/tools/proactive_tools.py +24 -11
- crackerjack/mcp/tools/progress_tools.py +5 -8
- crackerjack/mcp/tools/utility_tools.py +20 -14
- crackerjack/mcp/tools/workflow_executor.py +62 -40
- crackerjack/mcp/websocket/app.py +8 -0
- crackerjack/mcp/websocket/endpoints.py +352 -357
- crackerjack/mcp/websocket/jobs.py +40 -57
- crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
- crackerjack/mcp/websocket/server.py +7 -25
- crackerjack/mcp/websocket/websocket_handler.py +6 -17
- crackerjack/mixins/__init__.py +3 -0
- crackerjack/mixins/error_handling.py +145 -0
- crackerjack/models/config.py +21 -1
- crackerjack/models/config_adapter.py +49 -1
- crackerjack/models/protocols.py +176 -107
- crackerjack/models/resource_protocols.py +55 -210
- crackerjack/models/task.py +3 -0
- crackerjack/monitoring/ai_agent_watchdog.py +13 -13
- crackerjack/monitoring/metrics_collector.py +426 -0
- crackerjack/monitoring/regression_prevention.py +8 -8
- crackerjack/monitoring/websocket_server.py +643 -0
- crackerjack/orchestration/advanced_orchestrator.py +11 -6
- crackerjack/orchestration/coverage_improvement.py +3 -3
- crackerjack/orchestration/execution_strategies.py +26 -6
- crackerjack/orchestration/test_progress_streamer.py +8 -5
- crackerjack/plugins/base.py +2 -2
- crackerjack/plugins/hooks.py +7 -0
- crackerjack/plugins/managers.py +11 -8
- crackerjack/security/__init__.py +0 -1
- crackerjack/security/audit.py +90 -105
- crackerjack/services/anomaly_detector.py +392 -0
- crackerjack/services/api_extractor.py +615 -0
- crackerjack/services/backup_service.py +2 -2
- crackerjack/services/bounded_status_operations.py +15 -152
- crackerjack/services/cache.py +127 -1
- crackerjack/services/changelog_automation.py +395 -0
- crackerjack/services/config.py +18 -11
- crackerjack/services/config_merge.py +30 -85
- crackerjack/services/config_template.py +506 -0
- crackerjack/services/contextual_ai_assistant.py +48 -22
- crackerjack/services/coverage_badge_service.py +171 -0
- crackerjack/services/coverage_ratchet.py +41 -17
- crackerjack/services/debug.py +3 -3
- crackerjack/services/dependency_analyzer.py +460 -0
- crackerjack/services/dependency_monitor.py +14 -11
- crackerjack/services/documentation_generator.py +491 -0
- crackerjack/services/documentation_service.py +675 -0
- crackerjack/services/enhanced_filesystem.py +6 -5
- crackerjack/services/enterprise_optimizer.py +865 -0
- crackerjack/services/error_pattern_analyzer.py +676 -0
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/git.py +41 -45
- crackerjack/services/health_metrics.py +10 -8
- crackerjack/services/heatmap_generator.py +735 -0
- crackerjack/services/initialization.py +30 -33
- crackerjack/services/input_validator.py +5 -97
- crackerjack/services/intelligent_commit.py +327 -0
- crackerjack/services/log_manager.py +15 -12
- crackerjack/services/logging.py +4 -3
- crackerjack/services/lsp_client.py +628 -0
- crackerjack/services/memory_optimizer.py +409 -0
- crackerjack/services/metrics.py +42 -33
- crackerjack/services/parallel_executor.py +416 -0
- crackerjack/services/pattern_cache.py +1 -1
- crackerjack/services/pattern_detector.py +6 -6
- crackerjack/services/performance_benchmarks.py +250 -576
- crackerjack/services/performance_cache.py +382 -0
- crackerjack/services/performance_monitor.py +565 -0
- crackerjack/services/predictive_analytics.py +510 -0
- crackerjack/services/quality_baseline.py +234 -0
- crackerjack/services/quality_baseline_enhanced.py +646 -0
- crackerjack/services/quality_intelligence.py +785 -0
- crackerjack/services/regex_patterns.py +605 -524
- crackerjack/services/regex_utils.py +43 -123
- crackerjack/services/secure_path_utils.py +5 -164
- crackerjack/services/secure_status_formatter.py +30 -141
- crackerjack/services/secure_subprocess.py +11 -92
- crackerjack/services/security.py +61 -30
- crackerjack/services/security_logger.py +18 -22
- crackerjack/services/server_manager.py +124 -16
- crackerjack/services/status_authentication.py +16 -159
- crackerjack/services/status_security_manager.py +4 -131
- crackerjack/services/terminal_utils.py +0 -0
- crackerjack/services/thread_safe_status_collector.py +19 -125
- crackerjack/services/unified_config.py +21 -13
- crackerjack/services/validation_rate_limiter.py +5 -54
- crackerjack/services/version_analyzer.py +459 -0
- crackerjack/services/version_checker.py +1 -1
- crackerjack/services/websocket_resource_limiter.py +10 -144
- crackerjack/services/zuban_lsp_service.py +390 -0
- crackerjack/slash_commands/__init__.py +2 -7
- crackerjack/slash_commands/run.md +2 -2
- crackerjack/tools/validate_input_validator_patterns.py +14 -40
- crackerjack/tools/validate_regex_patterns.py +19 -48
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/METADATA +197 -26
- crackerjack-0.33.1.dist-info/RECORD +229 -0
- crackerjack/CLAUDE.md +0 -207
- crackerjack/RULES.md +0 -380
- crackerjack/py313.py +0 -234
- crackerjack-0.32.0.dist-info/RECORD +0 -180
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/WHEEL +0 -0
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
self.
|
|
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
|
|
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
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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 + "
|
|
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
|