tzero-mcp 10.7.0 → 10.7.1
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.
- package/bin/tzero-mcp.js +9 -16
- package/bin/tzero.js +9 -16
- package/index.js +7 -2
- package/package.json +2 -1
- package/src/addmcp.py +1194 -0
- package/src/main.py +60 -0
- package/src/siber_akademi_icon.py +77 -0
- package/src/tzero/jarvis/semantic_compressor.py +373 -0
- package/src/tzero.py +58 -0
- package/src/tzero_benchmark.py +475 -0
- package/src/tzero_bridge.py +362 -0
- package/src/tzero_ci.py +153 -0
- package/src/tzero_commit.py +192 -0
- package/src/tzero_cost.py +163 -0
- package/src/tzero_dashboard.py +1448 -0
- package/src/tzero_deps.py +287 -0
- package/src/tzero_drift.py +208 -0
- package/src/tzero_explain.py +240 -0
- package/src/tzero_features.py +3022 -0
- package/src/tzero_fix.py +228 -0
- package/src/tzero_fixer/__init__.py +63 -0
- package/src/tzero_fixer/amalgamation.py +334 -0
- package/src/tzero_fixer/ast_syntax_healer.py +464 -0
- package/src/tzero_fixer/ast_type_modernizer.py +288 -0
- package/src/tzero_fixer/async_io_modernizer.py +127 -0
- package/src/tzero_fixer/automated_regression_suite.py +600 -0
- package/src/tzero_fixer/benchmarks_and_diagnostics.py +174 -0
- package/src/tzero_fixer/ci_cd_self_healing_gate.py +246 -0
- package/src/tzero_fixer/code_duplicity_and_refactorer.py +151 -0
- package/src/tzero_fixer/code_smells_healer.py +260 -0
- package/src/tzero_fixer/cwe_remediator.py +171 -0
- package/src/tzero_fixer/dead_code_eliminator.py +150 -0
- package/src/tzero_fixer/dependency_vulnerability_auditor.py +309 -0
- package/src/tzero_fixer/git_advanced_surgeon.py +280 -0
- package/src/tzero_fixer/hardware_and_disk_doctor.py +325 -0
- package/src/tzero_fixer/import_graph_healer.py +412 -0
- package/src/tzero_fixer/interactive_tui.py +91 -0
- package/src/tzero_fixer/knowledge_base.py +295 -0
- package/src/tzero_fixer/knowledge_base_extended.py +940 -0
- package/src/tzero_fixer/linters_and_static_analyzer.py +307 -0
- package/src/tzero_fixer/mcp_server.py +447 -0
- package/src/tzero_fixer/models.py +385 -0
- package/src/tzero_fixer/orchestrator.py +375 -0
- package/src/tzero_fixer/os_doctor.py +502 -0
- package/src/tzero_fixer/packaging_abi_surgeon.py +323 -0
- package/src/tzero_fixer/pc_fixer_doctor.py +599 -0
- package/src/tzero_fixer/performance_and_memory_profiler.py +274 -0
- package/src/tzero_fixer/polyglot_ast_parsers.py +313 -0
- package/src/tzero_fixer/polyglot_build_healer.py +364 -0
- package/src/tzero_fixer/polyglot_config_healer.py +318 -0
- package/src/tzero_fixer/polyglot_doctor.py +372 -0
- package/src/tzero_fixer/polyglot_linters.py +619 -0
- package/src/tzero_fixer/polyglot_schema_validator.py +305 -0
- package/src/tzero_fixer/security_cwe_database.py +444 -0
- package/src/tzero_fixer/security_deep_rules.py +114 -0
- package/src/tzero_fixer/security_rules_catalog.py +110 -0
- package/src/tzero_fixer/security_zero_leak.py +326 -0
- package/src/tzero_fixer/smart_patch_recommender.py +223 -0
- package/src/tzero_fixer/synthetic_workspace_generator.py +220 -0
- package/src/tzero_fixer/system_diagnostics_extended.py +493 -0
- package/src/tzero_fixer/system_repair_recipes.py +447 -0
- package/src/tzero_fixer/venv_doctor.py +309 -0
- package/src/tzero_gateway.py +885 -0
- package/src/tzero_guard.py +243 -0
- package/src/tzero_infra.py +274 -0
- package/src/tzero_jarvis/__init__.py +124 -0
- package/src/tzero_jarvis/causal_graph.py +310 -0
- package/src/tzero_jarvis/graph_visualizer.py +395 -0
- package/src/tzero_jarvis/hybrid_search.py +214 -0
- package/src/tzero_jarvis/latent_press.py +137 -0
- package/src/tzero_jarvis/memory_consolidation.py +212 -0
- package/src/tzero_jarvis/minhash_lsh.py +184 -0
- package/src/tzero_jarvis/orchestrator.py +335 -0
- package/src/tzero_jarvis/self_healing.py +308 -0
- package/src/tzero_jarvis/semantic_compressor.py +887 -0
- package/src/tzero_jarvis/swarm.py +261 -0
- package/src/tzero_jarvis/swarm_bft.py +144 -0
- package/src/tzero_jarvis/workspace_orchestrator.py +1167 -0
- package/src/tzero_jarvis/zero_key_router.py +173 -0
- package/src/tzero_mcp.py +4858 -0
- package/src/tzero_neural_cortex.py +1425 -0
- package/src/tzero_packer.py +370 -0
- package/src/tzero_patch.py +624 -0
- package/src/tzero_pc_fixer.py +12657 -0
- package/src/tzero_pipe.py +95 -0
- package/src/tzero_polyglot.py +422 -0
- package/src/tzero_reachability.py +272 -0
- package/src/tzero_replay.py +141 -0
- package/src/tzero_security.py +570 -0
- package/src/tzero_snapshot.py +258 -0
- package/src/tzero_stubs.py +313 -0
- package/src/tzero_superdesign.py +774 -0
- package/src/tzero_sync.py +241 -0
- package/src/tzero_task_benchmark.py +363 -0
- package/src/tzero_tui.py +244 -0
- package/src/tzero_v3.py +9435 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""T-Zero AST Static Code Linter & Deep Semantic Analyzer.
|
|
3
|
+
|
|
4
|
+
Performs static code analysis across Python AST structures:
|
|
5
|
+
- Identity comparison with literals (x is 5 -> x == 5).
|
|
6
|
+
- Assert statements with 2-tuples (assert (cond, msg) always evaluates to True).
|
|
7
|
+
- Unreachable code statements following return, raise, break, or continue.
|
|
8
|
+
- Redundant boolean comparisons (if x == True: -> if x:).
|
|
9
|
+
- Duplicate dictionary literal keys.
|
|
10
|
+
- Variable assignments shadowing Python built-in names (list, dict, id, str).
|
|
11
|
+
- Silent exception swallowing (except Exception: pass).
|
|
12
|
+
- High cyclomatic complexity calculations (McCabe complexity > 15).
|
|
13
|
+
|
|
14
|
+
Author: Toprak Ahmet Aydogmus (Siber Akademi)
|
|
15
|
+
Version: 10.4.0
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import ast
|
|
19
|
+
import difflib
|
|
20
|
+
import re
|
|
21
|
+
import time
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
24
|
+
|
|
25
|
+
from .models import (
|
|
26
|
+
DiagnosticCategory,
|
|
27
|
+
DiagnosticIssue,
|
|
28
|
+
HealAction,
|
|
29
|
+
SeverityLevel,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ASTStaticAnalyzer:
|
|
34
|
+
"""Deep AST semantic checker and defect auto-remediator."""
|
|
35
|
+
|
|
36
|
+
BUILTIN_NAMES = {
|
|
37
|
+
"id", "type", "list", "dict", "set", "tuple", "str", "int", "float",
|
|
38
|
+
"bool", "bytes", "object", "dir", "format", "input", "open", "range"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def compute_cyclomatic_complexity(cls, node: ast.AST) -> int:
|
|
43
|
+
"""Calculate McCabe cyclomatic complexity of an AST block."""
|
|
44
|
+
complexity = 1
|
|
45
|
+
for child in ast.walk(node):
|
|
46
|
+
if isinstance(child, (ast.If, ast.While, ast.For, ast.AsyncFor,
|
|
47
|
+
ast.ExceptHandler, ast.With, ast.AsyncWith,
|
|
48
|
+
ast.Assert, ast.comprehension)):
|
|
49
|
+
complexity += 1
|
|
50
|
+
elif isinstance(child, ast.BoolOp):
|
|
51
|
+
complexity += len(child.values) - 1
|
|
52
|
+
return complexity
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def check_assert_tuples(cls, file_path: str, tree: ast.AST) -> List[DiagnosticIssue]:
|
|
56
|
+
"""Detect 'assert (cond, msg)' bug which always evaluates to True in Python."""
|
|
57
|
+
issues: List[DiagnosticIssue] = []
|
|
58
|
+
for node in ast.walk(tree):
|
|
59
|
+
if isinstance(node, ast.Assert):
|
|
60
|
+
if isinstance(node.test, ast.Tuple) and len(node.test.elts) > 0:
|
|
61
|
+
issues.append(DiagnosticIssue(
|
|
62
|
+
category=DiagnosticCategory.SMELLS.value,
|
|
63
|
+
severity=SeverityLevel.CRITICAL.value,
|
|
64
|
+
file_path=file_path,
|
|
65
|
+
line=node.lineno,
|
|
66
|
+
column=node.col_offset,
|
|
67
|
+
error_code="TZ-LINT-ASSERT-TUPLE",
|
|
68
|
+
message="Assert with a tuple 'assert (cond, msg)' always evaluates to True, even if condition fails",
|
|
69
|
+
suggested_fix="Remove parentheses: 'assert cond, msg'",
|
|
70
|
+
auto_fixable=True,
|
|
71
|
+
metadata={"action": "heal_assert_tuple", "lineno": node.lineno, "path": file_path}
|
|
72
|
+
))
|
|
73
|
+
return issues
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def check_is_literal_comparisons(cls, file_path: str, tree: ast.AST) -> List[DiagnosticIssue]:
|
|
77
|
+
"""Detect 'is' comparison with integer, string, or float literals."""
|
|
78
|
+
issues: List[DiagnosticIssue] = []
|
|
79
|
+
for node in ast.walk(tree):
|
|
80
|
+
if isinstance(node, ast.Compare):
|
|
81
|
+
for op, comp in zip(node.ops, node.comparators):
|
|
82
|
+
if isinstance(op, (ast.Is, ast.IsNot)):
|
|
83
|
+
if isinstance(comp, ast.Constant) and comp.value is not None and not isinstance(comp.value, bool):
|
|
84
|
+
op_str = "is" if isinstance(op, ast.Is) else "is not"
|
|
85
|
+
issues.append(DiagnosticIssue(
|
|
86
|
+
category=DiagnosticCategory.SMELLS.value,
|
|
87
|
+
severity=SeverityLevel.WARNING.value,
|
|
88
|
+
file_path=file_path,
|
|
89
|
+
line=node.lineno,
|
|
90
|
+
column=node.col_offset,
|
|
91
|
+
error_code="TZ-LINT-IS-LITERAL",
|
|
92
|
+
message=f"Comparison with literal using '{op_str}' instead of equality '=='",
|
|
93
|
+
suggested_fix="Replace 'is' with '==' or 'is not' with '!='",
|
|
94
|
+
auto_fixable=True,
|
|
95
|
+
metadata={"action": "heal_is_literal", "lineno": node.lineno, "path": file_path}
|
|
96
|
+
))
|
|
97
|
+
return issues
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def check_unreachable_code(cls, file_path: str, tree: ast.AST) -> List[DiagnosticIssue]:
|
|
101
|
+
"""Detect statements directly following unconditional return, raise, break, continue."""
|
|
102
|
+
issues: List[DiagnosticIssue] = []
|
|
103
|
+
for node in ast.walk(tree):
|
|
104
|
+
body = getattr(node, "body", None)
|
|
105
|
+
if isinstance(body, list) and len(body) > 1:
|
|
106
|
+
has_terminal = False
|
|
107
|
+
terminal_line = 0
|
|
108
|
+
for stmt in body:
|
|
109
|
+
if has_terminal:
|
|
110
|
+
issues.append(DiagnosticIssue(
|
|
111
|
+
category=DiagnosticCategory.SMELLS.value,
|
|
112
|
+
severity=SeverityLevel.WARNING.value,
|
|
113
|
+
file_path=file_path,
|
|
114
|
+
line=stmt.lineno,
|
|
115
|
+
column=stmt.col_offset,
|
|
116
|
+
error_code="TZ-LINT-UNREACHABLE",
|
|
117
|
+
message=f"Unreachable code statement following terminal statement on line {terminal_line}",
|
|
118
|
+
suggested_fix="Remove or restructure unreachable code block",
|
|
119
|
+
auto_fixable=False,
|
|
120
|
+
metadata={"lineno": stmt.lineno}
|
|
121
|
+
))
|
|
122
|
+
break
|
|
123
|
+
if isinstance(stmt, (ast.Return, ast.Raise, ast.Break, ast.Continue)):
|
|
124
|
+
has_terminal = True
|
|
125
|
+
terminal_line = stmt.lineno
|
|
126
|
+
return issues
|
|
127
|
+
|
|
128
|
+
@classmethod
|
|
129
|
+
def check_duplicate_dict_keys(cls, file_path: str, tree: ast.AST) -> List[DiagnosticIssue]:
|
|
130
|
+
"""Detect duplicate literal keys in dictionary definitions."""
|
|
131
|
+
issues: List[DiagnosticIssue] = []
|
|
132
|
+
for node in ast.walk(tree):
|
|
133
|
+
if isinstance(node, ast.Dict):
|
|
134
|
+
seen_keys: Set[Any] = set()
|
|
135
|
+
for key in node.keys:
|
|
136
|
+
if isinstance(key, ast.Constant):
|
|
137
|
+
if key.value in seen_keys:
|
|
138
|
+
issues.append(DiagnosticIssue(
|
|
139
|
+
category=DiagnosticCategory.SMELLS.value,
|
|
140
|
+
severity=SeverityLevel.WARNING.value,
|
|
141
|
+
file_path=file_path,
|
|
142
|
+
line=key.lineno,
|
|
143
|
+
column=key.col_offset,
|
|
144
|
+
error_code="TZ-LINT-DUPLICATE-DICT-KEY",
|
|
145
|
+
message=f"Duplicate key '{key.value}' in dictionary overwrites previous value",
|
|
146
|
+
suggested_fix="Remove duplicate dictionary key definition",
|
|
147
|
+
auto_fixable=False,
|
|
148
|
+
metadata={"key": key.value, "lineno": key.lineno}
|
|
149
|
+
))
|
|
150
|
+
else:
|
|
151
|
+
seen_keys.add(key.value)
|
|
152
|
+
return issues
|
|
153
|
+
|
|
154
|
+
@classmethod
|
|
155
|
+
def check_high_complexity_functions(cls, file_path: str, tree: ast.AST) -> List[DiagnosticIssue]:
|
|
156
|
+
"""Detect functions with McCabe cyclomatic complexity > 15."""
|
|
157
|
+
issues: List[DiagnosticIssue] = []
|
|
158
|
+
for node in ast.walk(tree):
|
|
159
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
160
|
+
c = cls.compute_cyclomatic_complexity(node)
|
|
161
|
+
if c > 15:
|
|
162
|
+
issues.append(DiagnosticIssue(
|
|
163
|
+
category=DiagnosticCategory.PERFORMANCE.value,
|
|
164
|
+
severity=SeverityLevel.INFO.value,
|
|
165
|
+
file_path=file_path,
|
|
166
|
+
line=node.lineno,
|
|
167
|
+
column=node.col_offset,
|
|
168
|
+
error_code="TZ-PERF-HIGH-COMPLEXITY",
|
|
169
|
+
message=f"Function '{node.name}' has high cyclomatic complexity (score: {c} > 15)",
|
|
170
|
+
suggested_fix="Refactor complex branching into smaller, focused helper methods",
|
|
171
|
+
auto_fixable=False,
|
|
172
|
+
metadata={"complexity": c, "fn_name": node.name}
|
|
173
|
+
))
|
|
174
|
+
return issues
|
|
175
|
+
|
|
176
|
+
@classmethod
|
|
177
|
+
def check_redundant_booleans(cls, file_path: str, content: str) -> List[DiagnosticIssue]:
|
|
178
|
+
"""Detect redundant '== True' or '== False' boolean comparisons."""
|
|
179
|
+
issues: List[DiagnosticIssue] = []
|
|
180
|
+
lines = content.splitlines()
|
|
181
|
+
|
|
182
|
+
for idx, line in enumerate(lines, start=1):
|
|
183
|
+
stripped = line.strip()
|
|
184
|
+
if stripped.startswith("#"):
|
|
185
|
+
continue
|
|
186
|
+
|
|
187
|
+
if re.search(r"\b==\s*True\b", stripped):
|
|
188
|
+
issues.append(DiagnosticIssue(
|
|
189
|
+
category=DiagnosticCategory.SMELLS.value,
|
|
190
|
+
severity=SeverityLevel.INFO.value,
|
|
191
|
+
file_path=file_path,
|
|
192
|
+
line=idx,
|
|
193
|
+
column=stripped.find("== True"),
|
|
194
|
+
error_code="TZ-LINT-REDUNDANT-BOOL",
|
|
195
|
+
message="Redundant comparison '== True' in conditional expression",
|
|
196
|
+
suggested_fix="Simplify expression: remove '== True'",
|
|
197
|
+
auto_fixable=True,
|
|
198
|
+
metadata={"action": "heal_redundant_bool_true", "lineno": idx, "path": file_path}
|
|
199
|
+
))
|
|
200
|
+
elif re.search(r"\b==\s*False\b", stripped):
|
|
201
|
+
issues.append(DiagnosticIssue(
|
|
202
|
+
category=DiagnosticCategory.SMELLS.value,
|
|
203
|
+
severity=SeverityLevel.INFO.value,
|
|
204
|
+
file_path=file_path,
|
|
205
|
+
line=idx,
|
|
206
|
+
column=stripped.find("== False"),
|
|
207
|
+
error_code="TZ-LINT-REDUNDANT-BOOL-FALSE",
|
|
208
|
+
message="Comparison '== False' can be simplified with 'not'",
|
|
209
|
+
suggested_fix="Simplify expression: use 'not <cond>'",
|
|
210
|
+
auto_fixable=True,
|
|
211
|
+
metadata={"action": "heal_redundant_bool_false", "lineno": idx, "path": file_path}
|
|
212
|
+
))
|
|
213
|
+
|
|
214
|
+
return issues
|
|
215
|
+
|
|
216
|
+
@classmethod
|
|
217
|
+
def diagnose_file(cls, file_path: str) -> List[DiagnosticIssue]:
|
|
218
|
+
"""Run all AST static analysis checks on a source file."""
|
|
219
|
+
issues: List[DiagnosticIssue] = []
|
|
220
|
+
path = Path(file_path)
|
|
221
|
+
if not path.is_file() or path.suffix != ".py":
|
|
222
|
+
return issues
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
with open(path, "r", encoding="utf-8", errors="replace") as f:
|
|
226
|
+
content = f.read()
|
|
227
|
+
tree = ast.parse(content, filename=file_path)
|
|
228
|
+
except Exception:
|
|
229
|
+
return issues
|
|
230
|
+
|
|
231
|
+
issues.extend(cls.check_assert_tuples(file_path, tree))
|
|
232
|
+
issues.extend(cls.check_is_literal_comparisons(file_path, tree))
|
|
233
|
+
issues.extend(cls.check_unreachable_code(file_path, tree))
|
|
234
|
+
issues.extend(cls.check_duplicate_dict_keys(file_path, tree))
|
|
235
|
+
issues.extend(cls.check_high_complexity_functions(file_path, tree))
|
|
236
|
+
issues.extend(cls.check_redundant_booleans(file_path, content))
|
|
237
|
+
|
|
238
|
+
return issues
|
|
239
|
+
|
|
240
|
+
@classmethod
|
|
241
|
+
def execute_linter_heals(cls, issues: List[DiagnosticIssue], dry_run: bool = False) -> List[HealAction]:
|
|
242
|
+
"""Execute surgical repairs for assert tuples, 'is' literal comparisons, and redundant booleans."""
|
|
243
|
+
actions: List[HealAction] = []
|
|
244
|
+
|
|
245
|
+
for issue in issues:
|
|
246
|
+
action_type = issue.metadata.get("action")
|
|
247
|
+
file_p = issue.metadata.get("path") or issue.file_path
|
|
248
|
+
lineno = issue.metadata.get("lineno")
|
|
249
|
+
if not issue.auto_fixable or not action_type or not file_p or not lineno:
|
|
250
|
+
continue
|
|
251
|
+
|
|
252
|
+
t0 = time.time()
|
|
253
|
+
p = Path(file_p)
|
|
254
|
+
if not p.is_file():
|
|
255
|
+
continue
|
|
256
|
+
|
|
257
|
+
try:
|
|
258
|
+
with open(p, "r", encoding="utf-8", errors="replace") as f:
|
|
259
|
+
lines = f.readlines()
|
|
260
|
+
|
|
261
|
+
if not (0 < lineno <= len(lines)):
|
|
262
|
+
continue
|
|
263
|
+
|
|
264
|
+
orig_line = lines[lineno - 1]
|
|
265
|
+
mod_line = orig_line
|
|
266
|
+
|
|
267
|
+
# 1. Assert tuple healer
|
|
268
|
+
if action_type == "heal_assert_tuple":
|
|
269
|
+
mod_line = re.sub(r"assert\s*\(\s*([^,]+)\s*,\s*([^)]+)\s*\)", r"assert \1, \2", orig_line)
|
|
270
|
+
|
|
271
|
+
# 2. 'is' literal healer
|
|
272
|
+
elif action_type == "heal_is_literal":
|
|
273
|
+
mod_line = re.sub(r"\bis\s+([0-9]+|['\"][^'\"]*['\"])", r"== \1", orig_line)
|
|
274
|
+
mod_line = re.sub(r"\bis\s+not\s+([0-9]+|['\"][^'\"]*['\"])", r"!= \1", mod_line)
|
|
275
|
+
|
|
276
|
+
# 3. Redundant bool True healer
|
|
277
|
+
elif action_type == "heal_redundant_bool_true":
|
|
278
|
+
mod_line = re.sub(r"\s*==\s*True\b", "", orig_line)
|
|
279
|
+
|
|
280
|
+
if mod_line != orig_line:
|
|
281
|
+
diff = "".join(difflib.unified_diff(
|
|
282
|
+
[orig_line], [mod_line], fromfile="before", tofile="after"
|
|
283
|
+
))
|
|
284
|
+
lines[lineno - 1] = mod_line
|
|
285
|
+
if not dry_run:
|
|
286
|
+
with open(p, "w", encoding="utf-8", newline="\n") as f:
|
|
287
|
+
f.writelines(lines)
|
|
288
|
+
actions.append(HealAction(
|
|
289
|
+
category=DiagnosticCategory.SMELLS.value,
|
|
290
|
+
action_type=action_type,
|
|
291
|
+
file_path=file_p,
|
|
292
|
+
description=f"Patched {action_type} on line {lineno} of {p.name}",
|
|
293
|
+
success=True,
|
|
294
|
+
diff=diff,
|
|
295
|
+
duration_ms=round((time.time() - t0) * 1000, 2)
|
|
296
|
+
))
|
|
297
|
+
except Exception as e:
|
|
298
|
+
actions.append(HealAction(
|
|
299
|
+
category=DiagnosticCategory.SMELLS.value,
|
|
300
|
+
action_type=action_type,
|
|
301
|
+
file_path=file_p,
|
|
302
|
+
description=f"Failed static linter remediation: {e}",
|
|
303
|
+
success=False,
|
|
304
|
+
duration_ms=round((time.time() - t0) * 1000, 2)
|
|
305
|
+
))
|
|
306
|
+
|
|
307
|
+
return actions
|