crackerjack 0.31.10__py3-none-any.whl → 0.31.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of crackerjack might be problematic. Click here for more details.
- crackerjack/CLAUDE.md +288 -705
- crackerjack/__main__.py +22 -8
- crackerjack/agents/__init__.py +0 -3
- crackerjack/agents/architect_agent.py +0 -43
- crackerjack/agents/base.py +1 -9
- crackerjack/agents/coordinator.py +2 -148
- crackerjack/agents/documentation_agent.py +109 -81
- crackerjack/agents/dry_agent.py +122 -97
- crackerjack/agents/formatting_agent.py +3 -16
- crackerjack/agents/import_optimization_agent.py +1174 -130
- crackerjack/agents/performance_agent.py +956 -188
- crackerjack/agents/performance_helpers.py +229 -0
- crackerjack/agents/proactive_agent.py +1 -48
- crackerjack/agents/refactoring_agent.py +516 -246
- crackerjack/agents/refactoring_helpers.py +282 -0
- crackerjack/agents/security_agent.py +393 -90
- crackerjack/agents/test_creation_agent.py +1776 -120
- crackerjack/agents/test_specialist_agent.py +59 -15
- crackerjack/agents/tracker.py +0 -102
- crackerjack/api.py +145 -37
- crackerjack/cli/handlers.py +48 -30
- crackerjack/cli/interactive.py +11 -11
- crackerjack/cli/options.py +66 -4
- crackerjack/code_cleaner.py +808 -148
- crackerjack/config/global_lock_config.py +110 -0
- crackerjack/config/hooks.py +43 -64
- crackerjack/core/async_workflow_orchestrator.py +247 -97
- crackerjack/core/autofix_coordinator.py +192 -109
- crackerjack/core/enhanced_container.py +46 -63
- crackerjack/core/file_lifecycle.py +549 -0
- crackerjack/core/performance.py +9 -8
- crackerjack/core/performance_monitor.py +395 -0
- crackerjack/core/phase_coordinator.py +281 -94
- crackerjack/core/proactive_workflow.py +9 -58
- crackerjack/core/resource_manager.py +501 -0
- crackerjack/core/service_watchdog.py +490 -0
- crackerjack/core/session_coordinator.py +4 -8
- crackerjack/core/timeout_manager.py +504 -0
- crackerjack/core/websocket_lifecycle.py +475 -0
- crackerjack/core/workflow_orchestrator.py +343 -209
- crackerjack/dynamic_config.py +47 -6
- crackerjack/errors.py +3 -4
- crackerjack/executors/async_hook_executor.py +63 -13
- crackerjack/executors/cached_hook_executor.py +14 -14
- crackerjack/executors/hook_executor.py +100 -37
- crackerjack/executors/hook_lock_manager.py +856 -0
- crackerjack/executors/individual_hook_executor.py +120 -86
- crackerjack/intelligence/__init__.py +0 -7
- crackerjack/intelligence/adaptive_learning.py +13 -86
- crackerjack/intelligence/agent_orchestrator.py +15 -78
- crackerjack/intelligence/agent_registry.py +12 -59
- crackerjack/intelligence/agent_selector.py +31 -92
- crackerjack/intelligence/integration.py +1 -41
- crackerjack/interactive.py +9 -9
- crackerjack/managers/async_hook_manager.py +25 -8
- crackerjack/managers/hook_manager.py +9 -9
- crackerjack/managers/publish_manager.py +57 -59
- crackerjack/managers/test_command_builder.py +6 -36
- crackerjack/managers/test_executor.py +9 -61
- crackerjack/managers/test_manager.py +17 -63
- crackerjack/managers/test_manager_backup.py +77 -127
- crackerjack/managers/test_progress.py +4 -23
- crackerjack/mcp/cache.py +5 -12
- crackerjack/mcp/client_runner.py +10 -10
- crackerjack/mcp/context.py +64 -6
- crackerjack/mcp/dashboard.py +14 -11
- crackerjack/mcp/enhanced_progress_monitor.py +55 -55
- crackerjack/mcp/file_monitor.py +72 -42
- crackerjack/mcp/progress_components.py +103 -84
- crackerjack/mcp/progress_monitor.py +122 -49
- crackerjack/mcp/rate_limiter.py +12 -12
- crackerjack/mcp/server_core.py +16 -22
- crackerjack/mcp/service_watchdog.py +26 -26
- crackerjack/mcp/state.py +15 -0
- crackerjack/mcp/tools/core_tools.py +95 -39
- crackerjack/mcp/tools/error_analyzer.py +6 -32
- crackerjack/mcp/tools/execution_tools.py +1 -56
- crackerjack/mcp/tools/execution_tools_backup.py +35 -131
- crackerjack/mcp/tools/intelligence_tool_registry.py +0 -36
- crackerjack/mcp/tools/intelligence_tools.py +2 -55
- crackerjack/mcp/tools/monitoring_tools.py +308 -145
- crackerjack/mcp/tools/proactive_tools.py +12 -42
- crackerjack/mcp/tools/progress_tools.py +23 -15
- crackerjack/mcp/tools/utility_tools.py +3 -40
- crackerjack/mcp/tools/workflow_executor.py +40 -60
- crackerjack/mcp/websocket/app.py +0 -3
- crackerjack/mcp/websocket/endpoints.py +206 -268
- crackerjack/mcp/websocket/jobs.py +213 -66
- crackerjack/mcp/websocket/server.py +84 -6
- crackerjack/mcp/websocket/websocket_handler.py +137 -29
- crackerjack/models/config_adapter.py +3 -16
- crackerjack/models/protocols.py +162 -3
- crackerjack/models/resource_protocols.py +454 -0
- crackerjack/models/task.py +3 -3
- crackerjack/monitoring/__init__.py +0 -0
- crackerjack/monitoring/ai_agent_watchdog.py +25 -71
- crackerjack/monitoring/regression_prevention.py +28 -87
- crackerjack/orchestration/advanced_orchestrator.py +44 -78
- crackerjack/orchestration/coverage_improvement.py +10 -60
- crackerjack/orchestration/execution_strategies.py +16 -16
- crackerjack/orchestration/test_progress_streamer.py +61 -53
- crackerjack/plugins/base.py +1 -1
- crackerjack/plugins/managers.py +22 -20
- crackerjack/py313.py +65 -21
- crackerjack/services/backup_service.py +467 -0
- crackerjack/services/bounded_status_operations.py +627 -0
- crackerjack/services/cache.py +7 -9
- crackerjack/services/config.py +35 -52
- crackerjack/services/config_integrity.py +5 -16
- crackerjack/services/config_merge.py +542 -0
- crackerjack/services/contextual_ai_assistant.py +17 -19
- crackerjack/services/coverage_ratchet.py +44 -73
- crackerjack/services/debug.py +25 -39
- crackerjack/services/dependency_monitor.py +52 -50
- crackerjack/services/enhanced_filesystem.py +14 -11
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/filesystem.py +1 -12
- crackerjack/services/git.py +71 -47
- crackerjack/services/health_metrics.py +31 -27
- crackerjack/services/initialization.py +276 -428
- crackerjack/services/input_validator.py +760 -0
- crackerjack/services/log_manager.py +16 -16
- crackerjack/services/logging.py +7 -6
- crackerjack/services/metrics.py +43 -43
- crackerjack/services/pattern_cache.py +2 -31
- crackerjack/services/pattern_detector.py +26 -63
- crackerjack/services/performance_benchmarks.py +20 -45
- crackerjack/services/regex_patterns.py +2887 -0
- crackerjack/services/regex_utils.py +537 -0
- crackerjack/services/secure_path_utils.py +683 -0
- crackerjack/services/secure_status_formatter.py +534 -0
- crackerjack/services/secure_subprocess.py +605 -0
- crackerjack/services/security.py +47 -10
- crackerjack/services/security_logger.py +492 -0
- crackerjack/services/server_manager.py +109 -50
- crackerjack/services/smart_scheduling.py +8 -25
- crackerjack/services/status_authentication.py +603 -0
- crackerjack/services/status_security_manager.py +442 -0
- crackerjack/services/thread_safe_status_collector.py +546 -0
- crackerjack/services/tool_version_service.py +1 -23
- crackerjack/services/unified_config.py +36 -58
- crackerjack/services/validation_rate_limiter.py +269 -0
- crackerjack/services/version_checker.py +9 -40
- crackerjack/services/websocket_resource_limiter.py +572 -0
- crackerjack/slash_commands/__init__.py +52 -2
- crackerjack/tools/__init__.py +0 -0
- crackerjack/tools/validate_input_validator_patterns.py +262 -0
- crackerjack/tools/validate_regex_patterns.py +198 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/METADATA +197 -12
- crackerjack-0.31.12.dist-info/RECORD +178 -0
- crackerjack/cli/facade.py +0 -104
- crackerjack-0.31.10.dist-info/RECORD +0 -149
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/WHEEL +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"""Refactoring analysis helper classes and utilities."""
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import typing as t
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ComplexityCalculator(ast.NodeVisitor):
|
|
8
|
+
"""Calculator for cognitive complexity analysis."""
|
|
9
|
+
|
|
10
|
+
def __init__(self) -> None:
|
|
11
|
+
self.complexity = 0
|
|
12
|
+
self.nesting_level = 0
|
|
13
|
+
self.binary_sequences = 0
|
|
14
|
+
|
|
15
|
+
def visit_If(self, node: ast.If) -> None:
|
|
16
|
+
self._process_conditional_node(node)
|
|
17
|
+
|
|
18
|
+
def visit_For(self, node: ast.For) -> None:
|
|
19
|
+
self._process_loop_node(node)
|
|
20
|
+
|
|
21
|
+
def visit_While(self, node: ast.While) -> None:
|
|
22
|
+
self._process_loop_node(node)
|
|
23
|
+
|
|
24
|
+
def visit_Try(self, node: ast.Try) -> None:
|
|
25
|
+
self._process_try_node(node)
|
|
26
|
+
|
|
27
|
+
def visit_With(self, node: ast.With) -> None:
|
|
28
|
+
self._process_context_node(node)
|
|
29
|
+
|
|
30
|
+
def visit_BoolOp(self, node: ast.BoolOp) -> None:
|
|
31
|
+
self._process_boolean_operation(node)
|
|
32
|
+
|
|
33
|
+
def visit_ListComp(self, node: ast.ListComp) -> None:
|
|
34
|
+
self._process_comprehension(node)
|
|
35
|
+
|
|
36
|
+
def visit_DictComp(self, node: ast.DictComp) -> None:
|
|
37
|
+
self._process_comprehension(node)
|
|
38
|
+
|
|
39
|
+
def visit_SetComp(self, node: ast.SetComp) -> None:
|
|
40
|
+
self._process_comprehension(node)
|
|
41
|
+
|
|
42
|
+
def visit_GeneratorExp(self, node: ast.GeneratorExp) -> None:
|
|
43
|
+
self._process_comprehension(node)
|
|
44
|
+
|
|
45
|
+
def _process_conditional_node(self, node: ast.If) -> None:
|
|
46
|
+
"""Process if/elif nodes with condition complexity."""
|
|
47
|
+
# Base complexity + nesting penalty
|
|
48
|
+
self.complexity += 1 + self.nesting_level
|
|
49
|
+
|
|
50
|
+
# Penalty for complex conditions
|
|
51
|
+
if self._has_complex_condition(node.test):
|
|
52
|
+
self.complexity += 1
|
|
53
|
+
|
|
54
|
+
self._visit_with_nesting(node)
|
|
55
|
+
|
|
56
|
+
def _process_loop_node(self, node: ast.For | ast.While) -> None:
|
|
57
|
+
"""Process for/while loop nodes."""
|
|
58
|
+
self.complexity += 1 + self.nesting_level
|
|
59
|
+
self._visit_with_nesting(node)
|
|
60
|
+
|
|
61
|
+
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
|
+
self.complexity += 1 + self.nesting_level + len(node.handlers)
|
|
65
|
+
self._visit_with_nesting(node)
|
|
66
|
+
|
|
67
|
+
def _process_context_node(self, node: ast.With) -> None:
|
|
68
|
+
"""Process with/context manager nodes."""
|
|
69
|
+
self.complexity += 1 + self.nesting_level
|
|
70
|
+
self._visit_with_nesting(node)
|
|
71
|
+
|
|
72
|
+
def _process_boolean_operation(self, node: ast.BoolOp) -> None:
|
|
73
|
+
"""Process boolean operations with chain penalty."""
|
|
74
|
+
penalty = len(node.values) - 1
|
|
75
|
+
if penalty > 2: # Long chains are more complex
|
|
76
|
+
penalty += 1
|
|
77
|
+
self.complexity += penalty
|
|
78
|
+
self.generic_visit(node)
|
|
79
|
+
|
|
80
|
+
def _process_comprehension(
|
|
81
|
+
self, node: ast.ListComp | ast.DictComp | ast.SetComp | ast.GeneratorExp
|
|
82
|
+
) -> None:
|
|
83
|
+
"""Process comprehensions with condition penalty."""
|
|
84
|
+
self.complexity += 1
|
|
85
|
+
# Check each generator for ifs conditions
|
|
86
|
+
for generator in node.generators:
|
|
87
|
+
if hasattr(generator, "ifs") and generator.ifs:
|
|
88
|
+
self.complexity += len(generator.ifs)
|
|
89
|
+
self.generic_visit(node)
|
|
90
|
+
|
|
91
|
+
def _visit_with_nesting(self, node: ast.AST) -> None:
|
|
92
|
+
"""Visit a node with proper nesting level tracking."""
|
|
93
|
+
self.nesting_level += 1
|
|
94
|
+
self.generic_visit(node)
|
|
95
|
+
self.nesting_level -= 1
|
|
96
|
+
|
|
97
|
+
def _has_complex_condition(self, node: ast.expr) -> bool:
|
|
98
|
+
"""Check if condition involves complex expressions."""
|
|
99
|
+
return (isinstance(node, ast.BoolOp) and len(node.values) > 2) or isinstance(
|
|
100
|
+
node, ast.Compare | ast.Call
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class UsageDataCollector:
|
|
105
|
+
"""Collector for usage data analysis."""
|
|
106
|
+
|
|
107
|
+
def __init__(self):
|
|
108
|
+
self.defined_names: set[str] = set()
|
|
109
|
+
self.used_names: set[str] = set()
|
|
110
|
+
self.import_lines: list[tuple[int, str, str]] = []
|
|
111
|
+
self.unused_functions: list[dict[str, t.Any]] = []
|
|
112
|
+
self.unused_classes: list[dict[str, t.Any]] = []
|
|
113
|
+
self.unused_variables: list[dict[str, t.Any]] = []
|
|
114
|
+
|
|
115
|
+
def get_results(self, analyzer: "EnhancedUsageAnalyzer") -> dict[str, t.Any]:
|
|
116
|
+
"""Get collected usage data results."""
|
|
117
|
+
return {
|
|
118
|
+
"defined_names": self.defined_names,
|
|
119
|
+
"used_names": self.used_names,
|
|
120
|
+
"import_lines": self.import_lines,
|
|
121
|
+
"unused_functions": self.unused_functions,
|
|
122
|
+
"unused_classes": self.unused_classes,
|
|
123
|
+
"unused_variables": self.unused_variables,
|
|
124
|
+
"function_calls": analyzer.function_calls,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
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
|
|
135
|
+
self.collector = collector
|
|
136
|
+
|
|
137
|
+
def visit_Import(self, node: ast.Import) -> None:
|
|
138
|
+
self._process_import_node(node)
|
|
139
|
+
|
|
140
|
+
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
|
|
141
|
+
self._process_import_from_node(node)
|
|
142
|
+
|
|
143
|
+
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
|
|
144
|
+
self._process_function_definition(node)
|
|
145
|
+
|
|
146
|
+
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
|
|
147
|
+
self._process_async_function_definition(node)
|
|
148
|
+
|
|
149
|
+
def visit_ClassDef(self, node: ast.ClassDef) -> None:
|
|
150
|
+
self._process_class_definition(node)
|
|
151
|
+
|
|
152
|
+
def visit_Assign(self, node: ast.Assign) -> None:
|
|
153
|
+
self._process_assignment(node)
|
|
154
|
+
|
|
155
|
+
def visit_AnnAssign(self, node: ast.AnnAssign) -> None:
|
|
156
|
+
self._process_annotated_assignment(node)
|
|
157
|
+
|
|
158
|
+
def visit_Name(self, node: ast.Name) -> None:
|
|
159
|
+
self._process_name_usage(node)
|
|
160
|
+
|
|
161
|
+
def visit_Call(self, node: ast.Call) -> None:
|
|
162
|
+
self._process_function_call(node)
|
|
163
|
+
|
|
164
|
+
def visit_Attribute(self, node: ast.Attribute) -> None:
|
|
165
|
+
self._process_attribute_access(node)
|
|
166
|
+
|
|
167
|
+
def _process_import_node(self, node: ast.Import) -> None:
|
|
168
|
+
"""Process import statements."""
|
|
169
|
+
for alias in node.names:
|
|
170
|
+
name = alias.asname or alias.name
|
|
171
|
+
self.collector.defined_names.add(name)
|
|
172
|
+
self.collector.import_lines.append((node.lineno, name, "import"))
|
|
173
|
+
|
|
174
|
+
def _process_import_from_node(self, node: ast.ImportFrom) -> None:
|
|
175
|
+
"""Process from-import statements."""
|
|
176
|
+
for alias in node.names:
|
|
177
|
+
name = alias.asname or alias.name
|
|
178
|
+
self.collector.defined_names.add(name)
|
|
179
|
+
self.collector.import_lines.append((node.lineno, name, "from_import"))
|
|
180
|
+
|
|
181
|
+
def _process_function_definition(self, node: ast.FunctionDef) -> None:
|
|
182
|
+
"""Process function definitions."""
|
|
183
|
+
self.collector.defined_names.add(node.name)
|
|
184
|
+
if self._should_track_function(node.name):
|
|
185
|
+
function_info = self._create_function_info(node)
|
|
186
|
+
self.collector.unused_functions.append(function_info)
|
|
187
|
+
self._visit_with_scope(node)
|
|
188
|
+
|
|
189
|
+
def _process_async_function_definition(self, node: ast.AsyncFunctionDef) -> None:
|
|
190
|
+
"""Process async function definitions."""
|
|
191
|
+
self.collector.defined_names.add(node.name)
|
|
192
|
+
if not node.name.startswith("_"):
|
|
193
|
+
function_info = self._create_function_info(node)
|
|
194
|
+
self.collector.unused_functions.append(function_info)
|
|
195
|
+
self._visit_with_scope(node)
|
|
196
|
+
|
|
197
|
+
def _process_class_definition(self, node: ast.ClassDef) -> None:
|
|
198
|
+
"""Process class definitions."""
|
|
199
|
+
self.collector.defined_names.add(node.name)
|
|
200
|
+
self.collector.unused_classes.append(
|
|
201
|
+
{
|
|
202
|
+
"name": node.name,
|
|
203
|
+
"line": node.lineno,
|
|
204
|
+
"methods": [],
|
|
205
|
+
}
|
|
206
|
+
)
|
|
207
|
+
self._visit_with_scope(node)
|
|
208
|
+
|
|
209
|
+
def _process_assignment(self, node: ast.Assign) -> None:
|
|
210
|
+
"""Process variable assignments."""
|
|
211
|
+
for target in node.targets:
|
|
212
|
+
if isinstance(target, ast.Name):
|
|
213
|
+
self.collector.defined_names.add(target.id)
|
|
214
|
+
if self._is_in_function_or_class_scope():
|
|
215
|
+
var_info = self._create_variable_info(target, node)
|
|
216
|
+
self.collector.unused_variables.append(var_info)
|
|
217
|
+
self.generic_visit(node)
|
|
218
|
+
|
|
219
|
+
def _process_annotated_assignment(self, node: ast.AnnAssign) -> None:
|
|
220
|
+
"""Process annotated assignments."""
|
|
221
|
+
if isinstance(node.target, ast.Name):
|
|
222
|
+
self.collector.defined_names.add(node.target.id)
|
|
223
|
+
self.generic_visit(node)
|
|
224
|
+
|
|
225
|
+
def _process_name_usage(self, node: ast.Name) -> None:
|
|
226
|
+
"""Process name usage (variable references)."""
|
|
227
|
+
if isinstance(node.ctx, ast.Load):
|
|
228
|
+
self.collector.used_names.add(node.id)
|
|
229
|
+
if self.scope_stack:
|
|
230
|
+
self.scope_stack[-1].add(node.id)
|
|
231
|
+
|
|
232
|
+
def _process_function_call(self, node: ast.Call) -> None:
|
|
233
|
+
"""Process function/method calls."""
|
|
234
|
+
if isinstance(node.func, ast.Name):
|
|
235
|
+
self.function_calls.add(node.func.id)
|
|
236
|
+
self.collector.used_names.add(node.func.id)
|
|
237
|
+
elif isinstance(node.func, ast.Attribute):
|
|
238
|
+
if isinstance(node.func.value, ast.Name):
|
|
239
|
+
self.collector.used_names.add(node.func.value.id)
|
|
240
|
+
self.function_calls.add(node.func.attr)
|
|
241
|
+
self.generic_visit(node)
|
|
242
|
+
|
|
243
|
+
def _process_attribute_access(self, node: ast.Attribute) -> None:
|
|
244
|
+
"""Process attribute access."""
|
|
245
|
+
if isinstance(node.value, ast.Name):
|
|
246
|
+
self.collector.used_names.add(node.value.id)
|
|
247
|
+
self.generic_visit(node)
|
|
248
|
+
|
|
249
|
+
def _should_track_function(self, name: str) -> bool:
|
|
250
|
+
"""Determine if function should be tracked for unused analysis."""
|
|
251
|
+
return not name.startswith("_") and name != "__init__"
|
|
252
|
+
|
|
253
|
+
def _create_function_info(
|
|
254
|
+
self, node: ast.FunctionDef | ast.AsyncFunctionDef
|
|
255
|
+
) -> dict[str, t.Any]:
|
|
256
|
+
"""Create function information dictionary."""
|
|
257
|
+
return {
|
|
258
|
+
"name": node.name,
|
|
259
|
+
"line": node.lineno,
|
|
260
|
+
"is_method": len(self.scope_stack) > 1,
|
|
261
|
+
"args": [arg.arg for arg in node.args.args],
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
def _create_variable_info(
|
|
265
|
+
self, target: ast.Name, node: ast.Assign
|
|
266
|
+
) -> dict[str, t.Any]:
|
|
267
|
+
"""Create variable information dictionary."""
|
|
268
|
+
return {
|
|
269
|
+
"name": target.id,
|
|
270
|
+
"line": node.lineno,
|
|
271
|
+
"scope_level": len(self.scope_stack),
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
def _is_in_function_or_class_scope(self) -> bool:
|
|
275
|
+
"""Check if currently in function or class scope."""
|
|
276
|
+
return len(self.scope_stack) > 1
|
|
277
|
+
|
|
278
|
+
def _visit_with_scope(self, node: ast.AST) -> None:
|
|
279
|
+
"""Visit node with proper scope tracking."""
|
|
280
|
+
self.scope_stack.append(set())
|
|
281
|
+
self.generic_visit(node)
|
|
282
|
+
self.scope_stack.pop()
|