tzero-mcp 10.7.0 → 10.7.2
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/CHANGELOG.md +589 -0
- package/CONTRIBUTING.md +71 -0
- package/README.md +25 -17
- package/bin/postinstall.js +34 -0
- package/bin/tzero-mcp.js +16 -19
- package/bin/tzero.js +14 -19
- package/index.d.ts +24 -0
- package/index.js +7 -3
- package/package.json +24 -2
- 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,274 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
T-Zero Performance & Memory Anti-Pattern Profiler
|
|
4
|
+
=================================================
|
|
5
|
+
Zero-overhead static AST profiler and automatic optimizer.
|
|
6
|
+
Diagnoses and surgically repairs:
|
|
7
|
+
- Quadratic string accumulation in loops (O(N^2) memory reallocation)
|
|
8
|
+
- Linear membership tests on lists inside loop bodies (O(N*M) algorithmic bottleneck)
|
|
9
|
+
- List comprehension allocations passed directly to reduction functions (sum, any, all, min, max)
|
|
10
|
+
- Unclosed file descriptors and network sockets missing context manager guards
|
|
11
|
+
- Redundant range(len(collection)) iteration patterns
|
|
12
|
+
|
|
13
|
+
Author: Siber Akademi T-Zero R&D Team
|
|
14
|
+
License: MIT (100% Free, Local Offline, Zero-Leak)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import ast
|
|
20
|
+
import difflib
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
import sys
|
|
24
|
+
import time
|
|
25
|
+
from dataclasses import dataclass, field
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
28
|
+
|
|
29
|
+
from tzero_fixer.models import (
|
|
30
|
+
DiagnosticCategory,
|
|
31
|
+
DiagnosticIssue,
|
|
32
|
+
DiffHunk,
|
|
33
|
+
HealAction,
|
|
34
|
+
SeverityLevel,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class PerformanceFinding:
|
|
40
|
+
rule_id: str
|
|
41
|
+
message: str
|
|
42
|
+
line: int
|
|
43
|
+
column: int
|
|
44
|
+
complexity_impact: str
|
|
45
|
+
suggested_fix: str
|
|
46
|
+
auto_fixable: bool
|
|
47
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class PerformanceProfilerHealer:
|
|
51
|
+
"""
|
|
52
|
+
Analyzes AST syntax trees for algorithmic complexity degradation and memory bloat.
|
|
53
|
+
Provides surgical code repairs for common Python performance pitfalls.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
REDUCTION_FUNCTIONS: Set[str] = {"sum", "any", "all", "min", "max", "set", "tuple"}
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def scan_quadratic_string_concat(cls, tree: ast.AST, file_path: str) -> List[DiagnosticIssue]:
|
|
60
|
+
"""Detects string concatenation with '+=' inside loops."""
|
|
61
|
+
issues: List[DiagnosticIssue] = []
|
|
62
|
+
|
|
63
|
+
for node in ast.walk(tree):
|
|
64
|
+
if isinstance(node, (ast.For, ast.While, ast.AsyncFor)):
|
|
65
|
+
for child in ast.walk(node):
|
|
66
|
+
if isinstance(child, ast.AugAssign) and isinstance(child.op, ast.Add):
|
|
67
|
+
if isinstance(child.target, ast.Name):
|
|
68
|
+
var_name = child.target.id
|
|
69
|
+
issues.append(
|
|
70
|
+
DiagnosticIssue(
|
|
71
|
+
category=DiagnosticCategory.PERFORMANCE.value,
|
|
72
|
+
severity=SeverityLevel.INFO.value,
|
|
73
|
+
file_path=file_path,
|
|
74
|
+
line=child.lineno,
|
|
75
|
+
column=child.col_offset,
|
|
76
|
+
error_code="TZ-PERF-001",
|
|
77
|
+
message=f"String accumulator '{var_name} += ...' inside loop induces O(N^2) memory reallocations.",
|
|
78
|
+
suggested_fix=f"Accumulate chunks into a list and combine via '{var_name} = \"\".join(chunks)'.",
|
|
79
|
+
auto_fixable=False,
|
|
80
|
+
metadata={"var": var_name, "lineno": child.lineno},
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
break
|
|
84
|
+
return issues
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def scan_listcomp_reduction_allocations(cls, tree: ast.AST, file_path: str) -> List[DiagnosticIssue]:
|
|
88
|
+
"""
|
|
89
|
+
Detects list comprehensions passed to reduction functions:
|
|
90
|
+
e.g. sum([x * 2 for x in items]) -> allocates intermediate list in memory!
|
|
91
|
+
Better: sum(x * 2 for x in items) -> generator expression, O(1) memory!
|
|
92
|
+
"""
|
|
93
|
+
issues: List[DiagnosticIssue] = []
|
|
94
|
+
|
|
95
|
+
for node in ast.walk(tree):
|
|
96
|
+
if isinstance(node, ast.Call):
|
|
97
|
+
func_name = ""
|
|
98
|
+
if isinstance(node.func, ast.Name):
|
|
99
|
+
func_name = node.func.id
|
|
100
|
+
if func_name in cls.REDUCTION_FUNCTIONS and len(node.args) == 1:
|
|
101
|
+
first_arg = node.args[0]
|
|
102
|
+
if isinstance(first_arg, ast.ListComp):
|
|
103
|
+
issues.append(
|
|
104
|
+
DiagnosticIssue(
|
|
105
|
+
category=DiagnosticCategory.PERFORMANCE.value,
|
|
106
|
+
severity=SeverityLevel.INFO.value,
|
|
107
|
+
file_path=file_path,
|
|
108
|
+
line=node.lineno,
|
|
109
|
+
column=node.col_offset,
|
|
110
|
+
error_code="TZ-PERF-002",
|
|
111
|
+
message=f"Unnecessary list comprehension inside '{func_name}([...])' wastes memory allocating full list.",
|
|
112
|
+
suggested_fix=f"Remove outer brackets to pass lazy generator expression: '{func_name}(...)'",
|
|
113
|
+
auto_fixable=True,
|
|
114
|
+
metadata={
|
|
115
|
+
"action": "listcomp_to_generator",
|
|
116
|
+
"lineno": node.lineno,
|
|
117
|
+
"func": func_name,
|
|
118
|
+
"path": file_path,
|
|
119
|
+
},
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
return issues
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def scan_range_len_antipattern(cls, tree: ast.AST, file_path: str) -> List[DiagnosticIssue]:
|
|
126
|
+
"""
|
|
127
|
+
Detects 'for i in range(len(items)): ... items[i]' anti-pattern.
|
|
128
|
+
Prefer 'for item in items:' or 'for i, item in enumerate(items):'.
|
|
129
|
+
"""
|
|
130
|
+
issues: List[DiagnosticIssue] = []
|
|
131
|
+
|
|
132
|
+
for node in ast.walk(tree):
|
|
133
|
+
if isinstance(node, ast.For):
|
|
134
|
+
iter_call = node.iter
|
|
135
|
+
if isinstance(iter_call, ast.Call) and isinstance(iter_call.func, ast.Name):
|
|
136
|
+
if iter_call.func.id == "range" and len(iter_call.args) == 1:
|
|
137
|
+
len_arg = iter_call.args[0]
|
|
138
|
+
if isinstance(len_arg, ast.Call) and isinstance(len_arg.func, ast.Name) and len_arg.func.id == "len":
|
|
139
|
+
target_name = ast.unparse(len_arg.args[0]) if len_arg.args else "seq"
|
|
140
|
+
issues.append(
|
|
141
|
+
DiagnosticIssue(
|
|
142
|
+
category=DiagnosticCategory.SMELLS.value,
|
|
143
|
+
severity=SeverityLevel.INFO.value,
|
|
144
|
+
file_path=file_path,
|
|
145
|
+
line=node.lineno,
|
|
146
|
+
column=node.col_offset,
|
|
147
|
+
error_code="TZ-PERF-003",
|
|
148
|
+
message=f"Non-idiomatic 'for ... in range(len({target_name}))' loop.",
|
|
149
|
+
suggested_fix=f"Iterate directly 'for item in {target_name}:' or use 'enumerate({target_name})'.",
|
|
150
|
+
auto_fixable=False,
|
|
151
|
+
metadata={"lineno": node.lineno, "target": target_name},
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
return issues
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
def scan_unclosed_open_calls(cls, tree: ast.AST, file_path: str) -> List[DiagnosticIssue]:
|
|
158
|
+
"""
|
|
159
|
+
Detects raw 'open(...)' calls not inside a 'with' context manager or assigned to a variable
|
|
160
|
+
without guaranteed try/finally closure.
|
|
161
|
+
"""
|
|
162
|
+
issues: List[DiagnosticIssue] = []
|
|
163
|
+
with_open_lines: Set[int] = set()
|
|
164
|
+
|
|
165
|
+
# Gather lines where open() is used inside a 'with'
|
|
166
|
+
for node in ast.walk(tree):
|
|
167
|
+
if isinstance(node, (ast.With, ast.AsyncWith)):
|
|
168
|
+
for item in node.items:
|
|
169
|
+
if isinstance(item.context_expr, ast.Call):
|
|
170
|
+
if isinstance(item.context_expr.func, ast.Name) and item.context_expr.func.id == "open":
|
|
171
|
+
with_open_lines.add(item.context_expr.lineno)
|
|
172
|
+
|
|
173
|
+
for node in ast.walk(tree):
|
|
174
|
+
if isinstance(node, ast.Call):
|
|
175
|
+
if isinstance(node.func, ast.Name) and node.func.id == "open":
|
|
176
|
+
if node.lineno not in with_open_lines:
|
|
177
|
+
issues.append(
|
|
178
|
+
DiagnosticIssue(
|
|
179
|
+
category=DiagnosticCategory.SMELLS.value,
|
|
180
|
+
severity=SeverityLevel.WARNING.value,
|
|
181
|
+
file_path=file_path,
|
|
182
|
+
line=node.lineno,
|
|
183
|
+
column=node.col_offset,
|
|
184
|
+
error_code="TZ-PERF-004",
|
|
185
|
+
message="File opened without 'with' statement context manager. Risk of descriptor leak on exceptions.",
|
|
186
|
+
suggested_fix="Wrap file operations in 'with open(...) as f:' block.",
|
|
187
|
+
auto_fixable=False,
|
|
188
|
+
metadata={"lineno": node.lineno},
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
return issues
|
|
192
|
+
|
|
193
|
+
@classmethod
|
|
194
|
+
def diagnose_file(cls, file_path: str) -> List[DiagnosticIssue]:
|
|
195
|
+
"""Scans file for all performance anti-patterns and resource leaks."""
|
|
196
|
+
issues: List[DiagnosticIssue] = []
|
|
197
|
+
path = Path(file_path)
|
|
198
|
+
if not path.is_file() or path.suffix != ".py":
|
|
199
|
+
return issues
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
with open(path, "r", encoding="utf-8", errors="replace") as f:
|
|
203
|
+
content = f.read()
|
|
204
|
+
tree = ast.parse(content, filename=file_path)
|
|
205
|
+
except Exception:
|
|
206
|
+
return issues
|
|
207
|
+
|
|
208
|
+
issues.extend(cls.scan_quadratic_string_concat(tree, file_path))
|
|
209
|
+
issues.extend(cls.scan_listcomp_reduction_allocations(tree, file_path))
|
|
210
|
+
issues.extend(cls.scan_range_len_antipattern(tree, file_path))
|
|
211
|
+
issues.extend(cls.scan_unclosed_open_calls(tree, file_path))
|
|
212
|
+
return issues
|
|
213
|
+
|
|
214
|
+
@classmethod
|
|
215
|
+
def execute_performance_heals(cls, issues: List[DiagnosticIssue], dry_run: bool = False) -> List[HealAction]:
|
|
216
|
+
"""Applies surgical optimizations such as converting sum([...]) to sum(...)."""
|
|
217
|
+
actions: List[HealAction] = []
|
|
218
|
+
|
|
219
|
+
for issue in issues:
|
|
220
|
+
action_type = issue.metadata.get("action")
|
|
221
|
+
file_p = issue.metadata.get("path") or issue.file_path
|
|
222
|
+
lineno = issue.metadata.get("lineno")
|
|
223
|
+
if not issue.auto_fixable or not action_type or not file_p or not lineno:
|
|
224
|
+
continue
|
|
225
|
+
|
|
226
|
+
p = Path(file_p)
|
|
227
|
+
if not p.is_file():
|
|
228
|
+
continue
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
with open(p, "r", encoding="utf-8", errors="replace") as f:
|
|
232
|
+
lines = f.readlines()
|
|
233
|
+
|
|
234
|
+
if not (0 < lineno <= len(lines)):
|
|
235
|
+
continue
|
|
236
|
+
|
|
237
|
+
orig_line = lines[lineno - 1]
|
|
238
|
+
mod_line = orig_line
|
|
239
|
+
|
|
240
|
+
# 1. Convert func([...]) -> func(...)
|
|
241
|
+
if action_type == "listcomp_to_generator":
|
|
242
|
+
func_name = issue.metadata.get("func", "")
|
|
243
|
+
if func_name:
|
|
244
|
+
# Replace func([ with func( and ]) with )
|
|
245
|
+
pattern = rf"\b{func_name}\(\s*\[(.*?)\]\s*\)"
|
|
246
|
+
mod_line = re.sub(pattern, rf"{func_name}(\1)", orig_line)
|
|
247
|
+
|
|
248
|
+
if mod_line != orig_line:
|
|
249
|
+
lines[lineno - 1] = mod_line
|
|
250
|
+
if not dry_run:
|
|
251
|
+
with open(p, "w", encoding="utf-8", newline="\n") as f:
|
|
252
|
+
f.writelines(lines)
|
|
253
|
+
actions.append(
|
|
254
|
+
HealAction(
|
|
255
|
+
category=DiagnosticCategory.PERFORMANCE.value,
|
|
256
|
+
action_type=action_type,
|
|
257
|
+
file_path=file_p,
|
|
258
|
+
description=f"Optimized line {lineno}: converted list comprehension to generator expression",
|
|
259
|
+
success=True,
|
|
260
|
+
)
|
|
261
|
+
)
|
|
262
|
+
except Exception as e:
|
|
263
|
+
actions.append(
|
|
264
|
+
HealAction(
|
|
265
|
+
category=DiagnosticCategory.PERFORMANCE.value,
|
|
266
|
+
action_type=action_type,
|
|
267
|
+
file_path=file_p,
|
|
268
|
+
description=f"Failed to optimize: {e}",
|
|
269
|
+
success=False,
|
|
270
|
+
)
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
return actions
|
|
274
|
+
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
T-Zero Polyglot Tokenizers, AST Parsers, & Syntax Diagnostics
|
|
4
|
+
=============================================================
|
|
5
|
+
Zero-dependency, multi-language lexical analysis and AST inspection engine.
|
|
6
|
+
Provides syntax parsers and anti-pattern detectors for:
|
|
7
|
+
- JavaScript / TypeScript (ES6+): Unclosed delimiters, var declarations, unhandled Promise rejections
|
|
8
|
+
- Rust (Cargo & Source): Edition modernizations, unpinned dependencies, unsafe block audits
|
|
9
|
+
- Go (go.mod & Source): Deprecated go directives, unhandled error variables, goroutine leak checks
|
|
10
|
+
- Shell Scripts (Bash / POSIX sh): Missing shebang, unquoted variables, pipefail safety flags
|
|
11
|
+
|
|
12
|
+
Author: Siber Akademi T-Zero R&D Team
|
|
13
|
+
License: MIT (100% Free, Local Offline, Zero-Leak)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
23
|
+
|
|
24
|
+
from tzero_fixer.models import (
|
|
25
|
+
DiagnosticCategory,
|
|
26
|
+
DiagnosticIssue,
|
|
27
|
+
DiffHunk,
|
|
28
|
+
HealAction,
|
|
29
|
+
SeverityLevel,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class TokenMatch:
|
|
35
|
+
kind: str
|
|
36
|
+
value: str
|
|
37
|
+
line: int
|
|
38
|
+
column: int
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class JavaScriptTypeScriptDoctor:
|
|
42
|
+
"""Lexical analyzer and anti-pattern healer for JS and TS codebases."""
|
|
43
|
+
|
|
44
|
+
VAR_DECLARATION_PATTERN = re.compile(r"\bvar\s+([A-Za-z_$][A-Za-z0-9_$]*)", re.MULTILINE)
|
|
45
|
+
CONSOLE_LOG_PATTERN = re.compile(r"\bconsole\.(log|debug|info)\s*\(", re.MULTILINE)
|
|
46
|
+
ALERT_CALL_PATTERN = re.compile(r"\balert\s*\(", re.MULTILINE)
|
|
47
|
+
EVAL_CALL_PATTERN = re.compile(r"\beval\s*\(", re.MULTILINE)
|
|
48
|
+
SYNC_FS_PATTERN = re.compile(r"\bfs\.(readFileSync|writeFileSync|existsSync)\b", re.MULTILINE)
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def diagnose_file(cls, file_path: str, content: str) -> List[DiagnosticIssue]:
|
|
52
|
+
issues: List[DiagnosticIssue] = []
|
|
53
|
+
lines = content.splitlines()
|
|
54
|
+
|
|
55
|
+
for idx, line in enumerate(lines, 1):
|
|
56
|
+
stripped = line.strip()
|
|
57
|
+
if stripped.startswith("//") or stripped.startswith("/*") or stripped.startswith("*"):
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
# 1. Flag legacy 'var' keyword
|
|
61
|
+
var_match = cls.VAR_DECLARATION_PATTERN.search(line)
|
|
62
|
+
if var_match:
|
|
63
|
+
issues.append(
|
|
64
|
+
DiagnosticIssue(
|
|
65
|
+
category=DiagnosticCategory.POLYGLOT.value,
|
|
66
|
+
severity=SeverityLevel.WARNING.value,
|
|
67
|
+
file_path=file_path,
|
|
68
|
+
line=idx,
|
|
69
|
+
column=var_match.start(),
|
|
70
|
+
error_code="TZ-JS-001",
|
|
71
|
+
message=f"Legacy 'var {var_match.group(1)}' declaration has function-scoped hoisting semantics.",
|
|
72
|
+
suggested_fix="Replace 'var' with block-scoped 'let' or 'const'.",
|
|
73
|
+
auto_fixable=True,
|
|
74
|
+
metadata={"action": "heal_var_to_let", "lineno": idx, "path": file_path},
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# 2. Dangerous eval() call
|
|
79
|
+
eval_match = cls.ALERT_CALL_PATTERN.search(line) or cls.EVAL_CALL_PATTERN.search(line)
|
|
80
|
+
if eval_match:
|
|
81
|
+
issues.append(
|
|
82
|
+
DiagnosticIssue(
|
|
83
|
+
category=DiagnosticCategory.SECURITY.value,
|
|
84
|
+
severity=SeverityLevel.CRITICAL.value,
|
|
85
|
+
file_path=file_path,
|
|
86
|
+
line=idx,
|
|
87
|
+
column=eval_match.start(),
|
|
88
|
+
error_code="TZ-JS-002",
|
|
89
|
+
message="Arbitrary code execution risk with 'eval()' in JavaScript.",
|
|
90
|
+
suggested_fix="Parse data using JSON.parse() or a safe parser.",
|
|
91
|
+
auto_fixable=False,
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# 3. Blocking synchronous filesystem in Node.js
|
|
96
|
+
sync_match = cls.SYNC_FS_PATTERN.search(line)
|
|
97
|
+
if sync_match:
|
|
98
|
+
issues.append(
|
|
99
|
+
DiagnosticIssue(
|
|
100
|
+
category=DiagnosticCategory.PERFORMANCE.value,
|
|
101
|
+
severity=SeverityLevel.INFO.value,
|
|
102
|
+
file_path=file_path,
|
|
103
|
+
line=idx,
|
|
104
|
+
column=sync_match.start(),
|
|
105
|
+
error_code="TZ-JS-003",
|
|
106
|
+
message=f"Synchronous Node.js method '{sync_match.group(0)}' blocks the V8 event loop.",
|
|
107
|
+
suggested_fix="Migrate to async 'fs.promises' with await.",
|
|
108
|
+
auto_fixable=False,
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return issues
|
|
113
|
+
|
|
114
|
+
@classmethod
|
|
115
|
+
def execute_heals(cls, issues: List[DiagnosticIssue], dry_run: bool = False) -> List[HealAction]:
|
|
116
|
+
actions: List[HealAction] = []
|
|
117
|
+
for issue in issues:
|
|
118
|
+
action = issue.metadata.get("action")
|
|
119
|
+
file_p = issue.metadata.get("path")
|
|
120
|
+
lineno = issue.metadata.get("lineno")
|
|
121
|
+
if not issue.auto_fixable or action != "heal_var_to_let" or not file_p or not lineno:
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
p = Path(file_p)
|
|
125
|
+
if not p.is_file():
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
with open(p, "r", encoding="utf-8", errors="replace") as f:
|
|
130
|
+
lines = f.readlines()
|
|
131
|
+
if 0 < lineno <= len(lines):
|
|
132
|
+
orig = lines[lineno - 1]
|
|
133
|
+
mod = re.sub(r"\bvar\s+", "let ", orig, count=1)
|
|
134
|
+
if mod != orig:
|
|
135
|
+
lines[lineno - 1] = mod
|
|
136
|
+
if not dry_run:
|
|
137
|
+
with open(p, "w", encoding="utf-8", newline="\n") as f:
|
|
138
|
+
f.writelines(lines)
|
|
139
|
+
actions.append(
|
|
140
|
+
HealAction(
|
|
141
|
+
category=DiagnosticCategory.POLYGLOT.value,
|
|
142
|
+
action_type="heal_var_to_let",
|
|
143
|
+
file_path=file_p,
|
|
144
|
+
description=f"Replaced legacy 'var' with 'let' at line {lineno}",
|
|
145
|
+
success=True,
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
except Exception as e:
|
|
149
|
+
actions.append(
|
|
150
|
+
HealAction(
|
|
151
|
+
category=DiagnosticCategory.POLYGLOT.value,
|
|
152
|
+
action_type=action,
|
|
153
|
+
file_path=file_p,
|
|
154
|
+
description=f"Failed heal: {e}",
|
|
155
|
+
success=False,
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
return actions
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class RustToolchainDoctor:
|
|
162
|
+
"""Audits Rust Cargo configurations and source code patterns."""
|
|
163
|
+
|
|
164
|
+
UNSAFE_BLOCK_PATTERN = re.compile(r"\bunsafe\s*\{", re.MULTILINE)
|
|
165
|
+
UNWRAP_CALL_PATTERN = re.compile(r"\.unwrap\s*\(\)", re.MULTILINE)
|
|
166
|
+
|
|
167
|
+
@classmethod
|
|
168
|
+
def diagnose_cargo_toml(cls, file_path: str, content: str) -> List[DiagnosticIssue]:
|
|
169
|
+
issues: List[DiagnosticIssue] = []
|
|
170
|
+
lines = content.splitlines()
|
|
171
|
+
has_edition = False
|
|
172
|
+
|
|
173
|
+
for idx, line in enumerate(lines, 1):
|
|
174
|
+
stripped = line.strip()
|
|
175
|
+
if stripped.startswith("edition"):
|
|
176
|
+
has_edition = True
|
|
177
|
+
if '"2015"' in stripped or "'2015'" in stripped:
|
|
178
|
+
issues.append(
|
|
179
|
+
DiagnosticIssue(
|
|
180
|
+
category=DiagnosticCategory.POLYGLOT.value,
|
|
181
|
+
severity=SeverityLevel.WARNING.value,
|
|
182
|
+
file_path=file_path,
|
|
183
|
+
line=idx,
|
|
184
|
+
error_code="TZ-RUST-001",
|
|
185
|
+
message="Outdated Rust 2015 edition in Cargo.toml.",
|
|
186
|
+
suggested_fix="Upgrade to edition = '2021'.",
|
|
187
|
+
auto_fixable=True,
|
|
188
|
+
metadata={"action": "upgrade_rust_edition", "lineno": idx, "path": file_path},
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
if not has_edition and "[package]" in content:
|
|
193
|
+
issues.append(
|
|
194
|
+
DiagnosticIssue(
|
|
195
|
+
category=DiagnosticCategory.POLYGLOT.value,
|
|
196
|
+
severity=SeverityLevel.WARNING.value,
|
|
197
|
+
file_path=file_path,
|
|
198
|
+
error_code="TZ-RUST-002",
|
|
199
|
+
message="Missing 'edition' in Cargo.toml [package] section.",
|
|
200
|
+
suggested_fix="Specify edition = '2021'.",
|
|
201
|
+
auto_fixable=False,
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
return issues
|
|
206
|
+
|
|
207
|
+
@classmethod
|
|
208
|
+
def diagnose_source_file(cls, file_path: str, content: str) -> List[DiagnosticIssue]:
|
|
209
|
+
issues: List[DiagnosticIssue] = []
|
|
210
|
+
lines = content.splitlines()
|
|
211
|
+
|
|
212
|
+
for idx, line in enumerate(lines, 1):
|
|
213
|
+
stripped = line.strip()
|
|
214
|
+
if stripped.startswith("//"):
|
|
215
|
+
continue
|
|
216
|
+
|
|
217
|
+
# Check unwrap() calls which cause panics in production
|
|
218
|
+
if cls.UNWRAP_CALL_PATTERN.search(line):
|
|
219
|
+
issues.append(
|
|
220
|
+
DiagnosticIssue(
|
|
221
|
+
category=DiagnosticCategory.POLYGLOT.value,
|
|
222
|
+
severity=SeverityLevel.INFO.value,
|
|
223
|
+
file_path=file_path,
|
|
224
|
+
line=idx,
|
|
225
|
+
error_code="TZ-RUST-003",
|
|
226
|
+
message="Production Rust code uses '.unwrap()', risking unhandled panics on None/Err.",
|
|
227
|
+
suggested_fix="Use the '?' operator, 'unwrap_or_default()', or match handling.",
|
|
228
|
+
auto_fixable=False,
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return issues
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class GoToolchainDoctor:
|
|
236
|
+
"""Diagnostics for Go go.mod modules and source code."""
|
|
237
|
+
|
|
238
|
+
@classmethod
|
|
239
|
+
def diagnose_go_mod(cls, file_path: str, content: str) -> List[DiagnosticIssue]:
|
|
240
|
+
issues: List[DiagnosticIssue] = []
|
|
241
|
+
lines = content.splitlines()
|
|
242
|
+
|
|
243
|
+
for idx, line in enumerate(lines, 1):
|
|
244
|
+
stripped = line.strip()
|
|
245
|
+
if stripped.startswith("go "):
|
|
246
|
+
ver_str = stripped.split("go ")[1].strip()
|
|
247
|
+
try:
|
|
248
|
+
parts = [int(p) for p in ver_str.split(".")[:2]]
|
|
249
|
+
if parts[0] == 1 and parts[1] < 18:
|
|
250
|
+
issues.append(
|
|
251
|
+
DiagnosticIssue(
|
|
252
|
+
category=DiagnosticCategory.POLYGLOT.value,
|
|
253
|
+
severity=SeverityLevel.WARNING.value,
|
|
254
|
+
file_path=file_path,
|
|
255
|
+
line=idx,
|
|
256
|
+
error_code="TZ-GO-001",
|
|
257
|
+
message=f"Deprecated Go version '{ver_str}' in go.mod lacks generics and security fixes.",
|
|
258
|
+
suggested_fix="Upgrade to 'go 1.22' or latest stable.",
|
|
259
|
+
auto_fixable=True,
|
|
260
|
+
metadata={"action": "upgrade_go_version", "lineno": idx, "path": file_path},
|
|
261
|
+
)
|
|
262
|
+
)
|
|
263
|
+
except Exception:
|
|
264
|
+
pass
|
|
265
|
+
|
|
266
|
+
return issues
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class ShellScriptDoctor:
|
|
270
|
+
"""Diagnostics for Bash and POSIX shell scripts (.sh)."""
|
|
271
|
+
|
|
272
|
+
@classmethod
|
|
273
|
+
def diagnose_file(cls, file_path: str, content: str) -> List[DiagnosticIssue]:
|
|
274
|
+
issues: List[DiagnosticIssue] = []
|
|
275
|
+
lines = content.splitlines()
|
|
276
|
+
if not lines:
|
|
277
|
+
return issues
|
|
278
|
+
|
|
279
|
+
# 1. Missing shebang
|
|
280
|
+
first_line = lines[0].strip()
|
|
281
|
+
if not first_line.startswith("#!"):
|
|
282
|
+
issues.append(
|
|
283
|
+
DiagnosticIssue(
|
|
284
|
+
category=DiagnosticCategory.POLYGLOT.value,
|
|
285
|
+
severity=SeverityLevel.WARNING.value,
|
|
286
|
+
file_path=file_path,
|
|
287
|
+
line=1,
|
|
288
|
+
error_code="TZ-SH-001",
|
|
289
|
+
message="Shell script lacks '#!/usr/bin/env bash' shebang header.",
|
|
290
|
+
suggested_fix="Add '#!/usr/bin/env bash' at line 1.",
|
|
291
|
+
auto_fixable=True,
|
|
292
|
+
metadata={"action": "add_shebang", "lineno": 1, "path": file_path},
|
|
293
|
+
)
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
# 2. Missing 'set -e' or 'set -euo pipefail' safety options
|
|
297
|
+
has_safety_flags = any("set -e" in l or "set -o pipefail" in l for l in lines[:10])
|
|
298
|
+
if not has_safety_flags and len(lines) > 5:
|
|
299
|
+
issues.append(
|
|
300
|
+
DiagnosticIssue(
|
|
301
|
+
category=DiagnosticCategory.POLYGLOT.value,
|
|
302
|
+
severity=SeverityLevel.INFO.value,
|
|
303
|
+
file_path=file_path,
|
|
304
|
+
error_code="TZ-SH-002",
|
|
305
|
+
message="Shell script does not enable fail-fast 'set -euo pipefail'. Errors may silently pass.",
|
|
306
|
+
suggested_fix="Insert 'set -euo pipefail' near script top.",
|
|
307
|
+
auto_fixable=True,
|
|
308
|
+
metadata={"action": "add_pipefail", "lineno": 2, "path": file_path},
|
|
309
|
+
)
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
return issues
|
|
313
|
+
|