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,599 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
T-Zero PC Fixer Doctor Classic Facade and Compatibility Layer
|
|
4
|
+
============================================================
|
|
5
|
+
Provides backward-compatible PCFixerDoctor class for legacy test suites and APIs.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import ast
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import shutil
|
|
15
|
+
import sys
|
|
16
|
+
import time
|
|
17
|
+
from dataclasses import asdict
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
20
|
+
|
|
21
|
+
from tzero_fixer.models import (
|
|
22
|
+
DiagnosticCategory,
|
|
23
|
+
DiagnosticIssue,
|
|
24
|
+
DiagnosticReport,
|
|
25
|
+
HealAction,
|
|
26
|
+
HealReport,
|
|
27
|
+
SeverityLevel,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
class PCFixerDoctor:
|
|
31
|
+
"""Enterprise-grade self-healing PC, runtime environment, and codebase doctor."""
|
|
32
|
+
|
|
33
|
+
COMMON_STDLIB_SYMBOLS = {
|
|
34
|
+
"os": "import os",
|
|
35
|
+
"sys": "import sys",
|
|
36
|
+
"re": "import re",
|
|
37
|
+
"json": "import json",
|
|
38
|
+
"math": "import math",
|
|
39
|
+
"time": "import time",
|
|
40
|
+
"pathlib": "from pathlib import Path",
|
|
41
|
+
"Path": "from pathlib import Path",
|
|
42
|
+
"ast": "import ast",
|
|
43
|
+
"dataclass": "from dataclasses import dataclass",
|
|
44
|
+
"field": "from dataclasses import field",
|
|
45
|
+
"asdict": "from dataclasses import asdict",
|
|
46
|
+
"Dict": "from typing import Dict",
|
|
47
|
+
"List": "from typing import List",
|
|
48
|
+
"Optional": "from typing import Optional",
|
|
49
|
+
"Union": "from typing import Union",
|
|
50
|
+
"Tuple": "from typing import Tuple",
|
|
51
|
+
"Any": "from typing import Any",
|
|
52
|
+
"Callable": "from typing import Callable",
|
|
53
|
+
"Set": "from typing import Set",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
IGNORE_DIRS = {
|
|
57
|
+
".git", ".svn", ".hg", "__pycache__", ".pytest_cache", ".mypy_cache",
|
|
58
|
+
"node_modules", ".venv", "venv", "env", "build", "dist", ".idea", ".vscode"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def check_tools_on_path(cls) -> Dict[str, bool]:
|
|
63
|
+
"""Check availability of essential development CLI tools on system PATH."""
|
|
64
|
+
tools = ["git", "python", "pip", "npm", "npx", "code", "cursor"]
|
|
65
|
+
res = {}
|
|
66
|
+
for t in tools:
|
|
67
|
+
res[t] = shutil.which(t) is not None
|
|
68
|
+
return res
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def inspect_virtualenv(cls, target_dir: str = ".") -> Dict[str, Any]:
|
|
72
|
+
"""Check integrity of local virtual environments (.venv, venv, env)."""
|
|
73
|
+
root = Path(target_dir).resolve()
|
|
74
|
+
candidates = [root / ".venv", root / "venv", root / "env"]
|
|
75
|
+
found = None
|
|
76
|
+
for c in candidates:
|
|
77
|
+
if c.is_dir() and (c / "pyvenv.cfg").is_file():
|
|
78
|
+
found = c
|
|
79
|
+
break
|
|
80
|
+
|
|
81
|
+
if not found:
|
|
82
|
+
return {
|
|
83
|
+
"active": hasattr(sys, "real_prefix") or (hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix),
|
|
84
|
+
"detected": False,
|
|
85
|
+
"path": None,
|
|
86
|
+
"valid": True,
|
|
87
|
+
"error": None
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
cfg_file = found / "pyvenv.cfg"
|
|
91
|
+
home_path = None
|
|
92
|
+
try:
|
|
93
|
+
with open(cfg_file, "r", encoding="utf-8", errors="replace") as f:
|
|
94
|
+
for line in f:
|
|
95
|
+
if line.strip().startswith("home ="):
|
|
96
|
+
home_path = line.split("=", 1)[1].strip()
|
|
97
|
+
break
|
|
98
|
+
except Exception as e:
|
|
99
|
+
return {"active": True, "detected": True, "path": str(found), "valid": False, "error": f"Cannot read pyvenv.cfg: {e}"}
|
|
100
|
+
|
|
101
|
+
home_valid = bool(home_path and os.path.isdir(home_path))
|
|
102
|
+
return {
|
|
103
|
+
"active": True,
|
|
104
|
+
"detected": True,
|
|
105
|
+
"path": str(found),
|
|
106
|
+
"cfg_home": home_path,
|
|
107
|
+
"valid": home_valid,
|
|
108
|
+
"error": None if home_valid else f"Base python home invalid: '{home_path}'"
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def scan_syntax_and_imports(cls, file_path: str) -> List[DiagnosticIssue]:
|
|
113
|
+
"""Scan a single Python file for syntax flaws, BOM artifacts, and missing imports."""
|
|
114
|
+
issues: List[DiagnosticIssue] = []
|
|
115
|
+
path = Path(file_path)
|
|
116
|
+
if not path.is_file() or path.suffix != ".py":
|
|
117
|
+
return issues
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
with open(path, "rb") as f:
|
|
121
|
+
raw_bytes = f.read()
|
|
122
|
+
except Exception as e:
|
|
123
|
+
issues.append(DiagnosticIssue(
|
|
124
|
+
category="env",
|
|
125
|
+
severity="error",
|
|
126
|
+
file_path=str(path),
|
|
127
|
+
line=None,
|
|
128
|
+
message=f"Permission or read error: {e}",
|
|
129
|
+
suggested_fix="Check file permissions",
|
|
130
|
+
auto_fixable=False
|
|
131
|
+
))
|
|
132
|
+
return issues
|
|
133
|
+
|
|
134
|
+
# Check BOM / Zero-width characters
|
|
135
|
+
if raw_bytes.startswith(b"\xef\xbb\xbf"):
|
|
136
|
+
issues.append(DiagnosticIssue(
|
|
137
|
+
category="encoding",
|
|
138
|
+
severity="warning",
|
|
139
|
+
file_path=str(path),
|
|
140
|
+
line=1,
|
|
141
|
+
message="File contains UTF-8 Byte Order Mark (BOM) which may disrupt interpreters",
|
|
142
|
+
suggested_fix="Strip UTF-8 BOM",
|
|
143
|
+
auto_fixable=True,
|
|
144
|
+
metadata={"action": "strip_bom"}
|
|
145
|
+
))
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
content = raw_bytes.decode("utf-8")
|
|
149
|
+
except UnicodeDecodeError:
|
|
150
|
+
issues.append(DiagnosticIssue(
|
|
151
|
+
category="encoding",
|
|
152
|
+
severity="error",
|
|
153
|
+
file_path=str(path),
|
|
154
|
+
line=1,
|
|
155
|
+
message="File encoding is not valid UTF-8",
|
|
156
|
+
suggested_fix="Transcode to clean UTF-8",
|
|
157
|
+
auto_fixable=True,
|
|
158
|
+
metadata={"action": "fix_encoding"}
|
|
159
|
+
))
|
|
160
|
+
content = raw_bytes.decode("utf-8", errors="replace")
|
|
161
|
+
|
|
162
|
+
# Zero-width spaces
|
|
163
|
+
if "\u200b" in content or "\ufeff" in content:
|
|
164
|
+
issues.append(DiagnosticIssue(
|
|
165
|
+
category="encoding",
|
|
166
|
+
severity="warning",
|
|
167
|
+
file_path=str(path),
|
|
168
|
+
line=1,
|
|
169
|
+
message="File contains hidden zero-width spaces causing phantom syntax bugs",
|
|
170
|
+
suggested_fix="Strip zero-width characters",
|
|
171
|
+
auto_fixable=True,
|
|
172
|
+
metadata={"action": "strip_zero_width"}
|
|
173
|
+
))
|
|
174
|
+
|
|
175
|
+
# Check AST parse
|
|
176
|
+
try:
|
|
177
|
+
tree = ast.parse(content, filename=str(path))
|
|
178
|
+
except SyntaxError as se:
|
|
179
|
+
# Analyze syntax error reason
|
|
180
|
+
err_line = se.lineno or 1
|
|
181
|
+
err_msg = se.msg or "Syntax error"
|
|
182
|
+
suggested = "Fix syntax error"
|
|
183
|
+
action = "syntax_fix"
|
|
184
|
+
|
|
185
|
+
lines = content.splitlines()
|
|
186
|
+
if 0 < err_line <= len(lines):
|
|
187
|
+
line_str = lines[err_line - 1].strip()
|
|
188
|
+
# Check missing colon
|
|
189
|
+
block_starters = ("def ", "class ", "if ", "elif ", "else", "try", "except", "finally", "for ", "while ", "with ")
|
|
190
|
+
if any(line_str.startswith(b) for b in block_starters) and not line_str.endswith(":"):
|
|
191
|
+
suggested = "Append missing colon ':' to block definition"
|
|
192
|
+
action = "append_colon"
|
|
193
|
+
# Check Python 2 print
|
|
194
|
+
elif re.match(r"^print\s+[\"'].*", line_str):
|
|
195
|
+
suggested = "Convert Python 2 print statement to print(...) function call"
|
|
196
|
+
action = "convert_print2"
|
|
197
|
+
|
|
198
|
+
issues.append(DiagnosticIssue(
|
|
199
|
+
category="syntax",
|
|
200
|
+
severity="error",
|
|
201
|
+
file_path=str(path),
|
|
202
|
+
line=err_line,
|
|
203
|
+
message=f"SyntaxError: {err_msg} on line {err_line}",
|
|
204
|
+
suggested_fix=suggested,
|
|
205
|
+
auto_fixable=True,
|
|
206
|
+
metadata={"action": action, "lineno": err_line}
|
|
207
|
+
))
|
|
208
|
+
return issues
|
|
209
|
+
|
|
210
|
+
# Check missing top-level standard library imports
|
|
211
|
+
imported_symbols: Set[str] = set()
|
|
212
|
+
for node in ast.walk(tree):
|
|
213
|
+
if isinstance(node, ast.Import):
|
|
214
|
+
for alias in node.names:
|
|
215
|
+
imported_symbols.add(alias.name.split(".")[0])
|
|
216
|
+
imported_symbols.add(alias.asname or alias.name)
|
|
217
|
+
elif isinstance(node, ast.ImportFrom):
|
|
218
|
+
for alias in node.names:
|
|
219
|
+
imported_symbols.add(alias.asname or alias.name)
|
|
220
|
+
|
|
221
|
+
referenced_names: Set[str] = set()
|
|
222
|
+
for node in ast.walk(tree):
|
|
223
|
+
if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Load):
|
|
224
|
+
referenced_names.add(node.id)
|
|
225
|
+
|
|
226
|
+
# Builtins
|
|
227
|
+
builtins_set = set(dir(__builtins__))
|
|
228
|
+
|
|
229
|
+
for sym, import_stmt in cls.COMMON_STDLIB_SYMBOLS.items():
|
|
230
|
+
if sym in referenced_names and sym not in imported_symbols and sym not in builtins_set:
|
|
231
|
+
issues.append(DiagnosticIssue(
|
|
232
|
+
category="import",
|
|
233
|
+
severity="warning",
|
|
234
|
+
file_path=str(path),
|
|
235
|
+
line=1,
|
|
236
|
+
message=f"Referenced symbol '{sym}' is missing explicit import statement",
|
|
237
|
+
suggested_fix=f"Add '{import_stmt}' at top of file",
|
|
238
|
+
auto_fixable=True,
|
|
239
|
+
metadata={"action": "add_import", "symbol": sym, "stmt": import_stmt}
|
|
240
|
+
))
|
|
241
|
+
|
|
242
|
+
return issues
|
|
243
|
+
|
|
244
|
+
@classmethod
|
|
245
|
+
def scan_cache_and_bloat(cls, target_dir: str = ".") -> List[DiagnosticIssue]:
|
|
246
|
+
"""Scan for orphaned .pyc files, stale git locks, and bloated caches."""
|
|
247
|
+
issues: List[DiagnosticIssue] = []
|
|
248
|
+
root = Path(target_dir).resolve()
|
|
249
|
+
|
|
250
|
+
# Check stale .git/index.lock
|
|
251
|
+
git_lock = root / ".git" / "index.lock"
|
|
252
|
+
if git_lock.is_file():
|
|
253
|
+
age_sec = time.time() - os.path.getmtime(git_lock)
|
|
254
|
+
if age_sec > 300: # older than 5 minutes
|
|
255
|
+
issues.append(DiagnosticIssue(
|
|
256
|
+
category="cache",
|
|
257
|
+
severity="warning",
|
|
258
|
+
file_path=str(git_lock),
|
|
259
|
+
line=None,
|
|
260
|
+
message=f"Stale git lockfile detected ({int(age_sec)}s old) blocking git operations",
|
|
261
|
+
suggested_fix="Remove stale .git/index.lock",
|
|
262
|
+
auto_fixable=True,
|
|
263
|
+
metadata={"action": "remove_file", "path": str(git_lock)}
|
|
264
|
+
))
|
|
265
|
+
|
|
266
|
+
# Check orphaned .pyc in __pycache__
|
|
267
|
+
for root_dir, dirs, files in os.walk(str(root)):
|
|
268
|
+
# skip ignored
|
|
269
|
+
dirs[:] = [d for d in dirs if d not in cls.IGNORE_DIRS or d == "__pycache__"]
|
|
270
|
+
if os.path.basename(root_dir) == "__pycache__":
|
|
271
|
+
parent_dir = Path(root_dir).parent
|
|
272
|
+
for f in files:
|
|
273
|
+
if f.endswith(".pyc"):
|
|
274
|
+
base_name = f.split(".")[0]
|
|
275
|
+
py_sibling = parent_dir / f"{base_name}.py"
|
|
276
|
+
if not py_sibling.is_file():
|
|
277
|
+
pyc_path = os.path.join(root_dir, f)
|
|
278
|
+
issues.append(DiagnosticIssue(
|
|
279
|
+
category="cache",
|
|
280
|
+
severity="info",
|
|
281
|
+
file_path=pyc_path,
|
|
282
|
+
line=None,
|
|
283
|
+
message=f"Orphaned bytecode {f} has no corresponding .py source file",
|
|
284
|
+
suggested_fix="Purge orphaned bytecode",
|
|
285
|
+
auto_fixable=True,
|
|
286
|
+
metadata={"action": "remove_file", "path": pyc_path}
|
|
287
|
+
))
|
|
288
|
+
|
|
289
|
+
return issues
|
|
290
|
+
|
|
291
|
+
@classmethod
|
|
292
|
+
def diagnose(cls, target_dir: str = ".") -> DiagnosticReport:
|
|
293
|
+
"""Run comprehensive diagnostic inspection across environment, workspace, and files."""
|
|
294
|
+
root = Path(target_dir).resolve()
|
|
295
|
+
tools_status = cls.check_tools_on_path()
|
|
296
|
+
venv_status = cls.inspect_virtualenv(target_dir)
|
|
297
|
+
|
|
298
|
+
issues: List[DiagnosticIssue] = []
|
|
299
|
+
|
|
300
|
+
# Check Python version
|
|
301
|
+
py_ver = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
302
|
+
if sys.version_info < (3, 9):
|
|
303
|
+
issues.append(DiagnosticIssue(
|
|
304
|
+
category="env",
|
|
305
|
+
severity="error",
|
|
306
|
+
file_path=None,
|
|
307
|
+
line=None,
|
|
308
|
+
message=f"Python version {py_ver} is below minimum supported 3.9",
|
|
309
|
+
suggested_fix="Upgrade Python to 3.9 or higher",
|
|
310
|
+
auto_fixable=False
|
|
311
|
+
))
|
|
312
|
+
|
|
313
|
+
# Check venv
|
|
314
|
+
if venv_status.get("detected") and not venv_status.get("valid"):
|
|
315
|
+
issues.append(DiagnosticIssue(
|
|
316
|
+
category="env",
|
|
317
|
+
severity="error",
|
|
318
|
+
file_path=os.path.join(venv_status["path"], "pyvenv.cfg"),
|
|
319
|
+
line=None,
|
|
320
|
+
message=f"Virtualenv corrupted: {venv_status.get('error')}",
|
|
321
|
+
suggested_fix="Re-point pyvenv.cfg home to active python interpreter",
|
|
322
|
+
auto_fixable=True,
|
|
323
|
+
metadata={"action": "repair_venv_cfg", "path": venv_status["path"]}
|
|
324
|
+
))
|
|
325
|
+
|
|
326
|
+
# Scan files
|
|
327
|
+
for root_dir, dirs, files in os.walk(str(root)):
|
|
328
|
+
dirs[:] = [d for d in dirs if d not in cls.IGNORE_DIRS]
|
|
329
|
+
for file in files:
|
|
330
|
+
if file.endswith(".py"):
|
|
331
|
+
full_path = os.path.join(root_dir, file)
|
|
332
|
+
issues.extend(cls.scan_syntax_and_imports(full_path))
|
|
333
|
+
|
|
334
|
+
# Scan cache & bloat
|
|
335
|
+
issues.extend(cls.scan_cache_and_bloat(target_dir))
|
|
336
|
+
|
|
337
|
+
# Tally counts
|
|
338
|
+
syntax_errors = sum(1 for i in issues if i.category == "syntax")
|
|
339
|
+
missing_imports = sum(1 for i in issues if i.category == "import")
|
|
340
|
+
env_issues = sum(1 for i in issues if i.category == "env")
|
|
341
|
+
cache_issues = sum(1 for i in issues if i.category in ("cache", "encoding"))
|
|
342
|
+
|
|
343
|
+
# Health score calculation
|
|
344
|
+
penalty = (syntax_errors * 20) + (missing_imports * 5) + (env_issues * 15) + (cache_issues * 2)
|
|
345
|
+
score = max(0, min(100, 100 - penalty))
|
|
346
|
+
|
|
347
|
+
return DiagnosticReport(
|
|
348
|
+
timestamp=time.time(),
|
|
349
|
+
target_dir=str(root),
|
|
350
|
+
health_score_pct=score,
|
|
351
|
+
total_issues=len(issues),
|
|
352
|
+
syntax_errors=syntax_errors,
|
|
353
|
+
missing_imports=missing_imports,
|
|
354
|
+
env_issues=env_issues,
|
|
355
|
+
cache_issues=cache_issues,
|
|
356
|
+
issues=issues,
|
|
357
|
+
tools_status=tools_status,
|
|
358
|
+
python_version=py_ver,
|
|
359
|
+
virtualenv_status=venv_status
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
@classmethod
|
|
363
|
+
def auto_heal(cls, target_dir: str = ".", dry_run: bool = False,
|
|
364
|
+
fix_syntax: bool = True, add_imports: bool = True,
|
|
365
|
+
clean_caches: bool = True, repair_venv: bool = True) -> HealReport:
|
|
366
|
+
"""Execute autonomous self-healing for detected codebase and environment issues."""
|
|
367
|
+
diag_before = cls.diagnose(target_dir)
|
|
368
|
+
actions: List[HealAction] = []
|
|
369
|
+
|
|
370
|
+
files_to_recheck: Set[str] = set()
|
|
371
|
+
|
|
372
|
+
for issue in diag_before.issues:
|
|
373
|
+
action_type = issue.metadata.get("action")
|
|
374
|
+
if not issue.auto_fixable or not action_type:
|
|
375
|
+
continue
|
|
376
|
+
|
|
377
|
+
# 1. Clean UTF-8 BOM or Zero-width chars
|
|
378
|
+
if action_type in ("strip_bom", "strip_zero_width", "fix_encoding") and issue.file_path:
|
|
379
|
+
try:
|
|
380
|
+
p = Path(issue.file_path)
|
|
381
|
+
with open(p, "rb") as f:
|
|
382
|
+
data = f.read()
|
|
383
|
+
if action_type == "strip_bom" and data.startswith(b"\xef\xbb\xbf"):
|
|
384
|
+
data = data[3:]
|
|
385
|
+
text = data.decode("utf-8", errors="replace")
|
|
386
|
+
if action_type == "strip_zero_width":
|
|
387
|
+
text = text.replace("\u200b", "").replace("\ufeff", "")
|
|
388
|
+
if not dry_run:
|
|
389
|
+
with open(p, "w", encoding="utf-8", newline="\n") as f:
|
|
390
|
+
f.write(text)
|
|
391
|
+
actions.append(HealAction(
|
|
392
|
+
category="encoding",
|
|
393
|
+
file_path=issue.file_path,
|
|
394
|
+
description=f"Sanitized encoding/BOM in {p.name}",
|
|
395
|
+
success=True
|
|
396
|
+
))
|
|
397
|
+
files_to_recheck.add(issue.file_path)
|
|
398
|
+
except Exception as e:
|
|
399
|
+
actions.append(HealAction(
|
|
400
|
+
category="encoding",
|
|
401
|
+
file_path=issue.file_path,
|
|
402
|
+
description=f"Failed sanitizing encoding: {e}",
|
|
403
|
+
success=False
|
|
404
|
+
))
|
|
405
|
+
|
|
406
|
+
# 2. Fix syntax errors (missing colon, Python 2 print)
|
|
407
|
+
elif action_type in ("append_colon", "convert_print2") and fix_syntax and issue.file_path:
|
|
408
|
+
try:
|
|
409
|
+
p = Path(issue.file_path)
|
|
410
|
+
with open(p, "r", encoding="utf-8", errors="replace") as f:
|
|
411
|
+
lines = f.readlines()
|
|
412
|
+
lineno = issue.metadata.get("lineno", 1)
|
|
413
|
+
if 0 < lineno <= len(lines):
|
|
414
|
+
orig_line = lines[lineno - 1]
|
|
415
|
+
mod_line = orig_line
|
|
416
|
+
if action_type == "append_colon":
|
|
417
|
+
mod_line = orig_line.rstrip("\r\n") + ":\n"
|
|
418
|
+
elif action_type == "convert_print2":
|
|
419
|
+
mod_line = re.sub(r"^(\s*)print\s+(.*)$", r"\1print(\2)", orig_line)
|
|
420
|
+
|
|
421
|
+
if orig_line != mod_line:
|
|
422
|
+
diff = "".join(difflib.unified_diff(
|
|
423
|
+
[orig_line], [mod_line], fromfile="before", tofile="after"
|
|
424
|
+
))
|
|
425
|
+
lines[lineno - 1] = mod_line
|
|
426
|
+
if not dry_run:
|
|
427
|
+
with open(p, "w", encoding="utf-8", newline="\n") as f:
|
|
428
|
+
f.writelines(lines)
|
|
429
|
+
actions.append(HealAction(
|
|
430
|
+
category="syntax",
|
|
431
|
+
file_path=issue.file_path,
|
|
432
|
+
description=f"Patched {action_type} on line {lineno} of {p.name}",
|
|
433
|
+
success=True,
|
|
434
|
+
diff=diff
|
|
435
|
+
))
|
|
436
|
+
files_to_recheck.add(issue.file_path)
|
|
437
|
+
except Exception as e:
|
|
438
|
+
actions.append(HealAction(
|
|
439
|
+
category="syntax",
|
|
440
|
+
file_path=issue.file_path,
|
|
441
|
+
description=f"Failed patching syntax: {e}",
|
|
442
|
+
success=False
|
|
443
|
+
))
|
|
444
|
+
|
|
445
|
+
# 3. Add missing standard library import
|
|
446
|
+
elif action_type == "add_import" and add_imports and issue.file_path:
|
|
447
|
+
try:
|
|
448
|
+
p = Path(issue.file_path)
|
|
449
|
+
with open(p, "r", encoding="utf-8", errors="replace") as f:
|
|
450
|
+
lines = f.readlines()
|
|
451
|
+
stmt = issue.metadata.get("stmt")
|
|
452
|
+
if stmt:
|
|
453
|
+
# Find safe insert location (after future imports / docstring or line 0)
|
|
454
|
+
insert_idx = 0
|
|
455
|
+
for idx, l in enumerate(lines[:15]):
|
|
456
|
+
if l.startswith("#") or "coding:" in l or l.startswith('"""') or l.startswith("'''"):
|
|
457
|
+
insert_idx = idx + 1
|
|
458
|
+
elif l.startswith("from __future__"):
|
|
459
|
+
insert_idx = idx + 1
|
|
460
|
+
|
|
461
|
+
lines.insert(insert_idx, f"{stmt}\n")
|
|
462
|
+
diff = f"+ {stmt}\n"
|
|
463
|
+
if not dry_run:
|
|
464
|
+
with open(p, "w", encoding="utf-8", newline="\n") as f:
|
|
465
|
+
f.writelines(lines)
|
|
466
|
+
actions.append(HealAction(
|
|
467
|
+
category="import",
|
|
468
|
+
file_path=issue.file_path,
|
|
469
|
+
description=f"Injected missing '{stmt}' into {p.name}",
|
|
470
|
+
success=True,
|
|
471
|
+
diff=diff
|
|
472
|
+
))
|
|
473
|
+
files_to_recheck.add(issue.file_path)
|
|
474
|
+
except Exception as e:
|
|
475
|
+
actions.append(HealAction(
|
|
476
|
+
category="import",
|
|
477
|
+
file_path=issue.file_path,
|
|
478
|
+
description=f"Failed adding import: {e}",
|
|
479
|
+
success=False
|
|
480
|
+
))
|
|
481
|
+
|
|
482
|
+
# 4. Remove stale lockfiles or orphaned bytecode
|
|
483
|
+
elif action_type == "remove_file" and clean_caches:
|
|
484
|
+
target_path = issue.metadata.get("path") or issue.file_path
|
|
485
|
+
if target_path and os.path.exists(target_path):
|
|
486
|
+
try:
|
|
487
|
+
if not dry_run:
|
|
488
|
+
os.remove(target_path)
|
|
489
|
+
actions.append(HealAction(
|
|
490
|
+
category="cache",
|
|
491
|
+
file_path=target_path,
|
|
492
|
+
description=f"Purged {os.path.basename(target_path)}",
|
|
493
|
+
success=True
|
|
494
|
+
))
|
|
495
|
+
except Exception as e:
|
|
496
|
+
actions.append(HealAction(
|
|
497
|
+
category="cache",
|
|
498
|
+
file_path=target_path,
|
|
499
|
+
description=f"Failed removing {target_path}: {e}",
|
|
500
|
+
success=False
|
|
501
|
+
))
|
|
502
|
+
|
|
503
|
+
# 5. Repair virtualenv config
|
|
504
|
+
elif action_type == "repair_venv_cfg" and repair_venv:
|
|
505
|
+
venv_dir = issue.metadata.get("path")
|
|
506
|
+
if venv_dir:
|
|
507
|
+
cfg_file = os.path.join(venv_dir, "pyvenv.cfg")
|
|
508
|
+
curr_py_home = os.path.dirname(sys.executable)
|
|
509
|
+
try:
|
|
510
|
+
lines = []
|
|
511
|
+
if os.path.isfile(cfg_file):
|
|
512
|
+
with open(cfg_file, "r", encoding="utf-8", errors="replace") as f:
|
|
513
|
+
for line in f:
|
|
514
|
+
if line.strip().startswith("home ="):
|
|
515
|
+
lines.append(f"home = {curr_py_home}\n")
|
|
516
|
+
else:
|
|
517
|
+
lines.append(line)
|
|
518
|
+
if not dry_run:
|
|
519
|
+
with open(cfg_file, "w", encoding="utf-8", newline="\n") as f:
|
|
520
|
+
f.writelines(lines)
|
|
521
|
+
actions.append(HealAction(
|
|
522
|
+
category="env",
|
|
523
|
+
file_path=cfg_file,
|
|
524
|
+
description=f"Updated pyvenv.cfg home path to '{curr_py_home}'",
|
|
525
|
+
success=True
|
|
526
|
+
))
|
|
527
|
+
except Exception as e:
|
|
528
|
+
actions.append(HealAction(
|
|
529
|
+
category="env",
|
|
530
|
+
file_path=cfg_file,
|
|
531
|
+
description=f"Failed updating pyvenv.cfg: {e}",
|
|
532
|
+
success=False
|
|
533
|
+
))
|
|
534
|
+
|
|
535
|
+
# Re-diagnose to evaluate final score
|
|
536
|
+
diag_after = cls.diagnose(target_dir) if not dry_run else diag_before
|
|
537
|
+
successful_heals = sum(1 for a in actions if a.success)
|
|
538
|
+
|
|
539
|
+
return HealReport(
|
|
540
|
+
timestamp=time.time(),
|
|
541
|
+
target_dir=str(Path(target_dir).resolve()),
|
|
542
|
+
initial_health_score=diag_before.health_score_pct,
|
|
543
|
+
final_health_score=diag_after.health_score_pct,
|
|
544
|
+
issues_detected=diag_before.total_issues,
|
|
545
|
+
issues_healed=successful_heals,
|
|
546
|
+
dry_run=dry_run,
|
|
547
|
+
actions=actions
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
@classmethod
|
|
551
|
+
def render_cli(cls, report: DiagnosticReport, heal_report: Optional[HealReport] = None):
|
|
552
|
+
"""Render beautiful, ANSI-colored doctor report to terminal."""
|
|
553
|
+
print("=" * 80)
|
|
554
|
+
print("\033[1;36m🩺 T-ZERO PC-FIXER & ENVIRONMENT AUTONOMOUS DOCTOR\033[0m")
|
|
555
|
+
print("=" * 80)
|
|
556
|
+
print(f"Target Directory: \033[33m{report.target_dir}\033[0m")
|
|
557
|
+
print(f"Python Runtime: \033[32mv{report.python_version}\033[0m ({sys.executable})")
|
|
558
|
+
print(f"Virtualenv: \033[32m{report.virtualenv_status.get('path') or 'Global / System'}\033[0m")
|
|
559
|
+
|
|
560
|
+
# Tools status
|
|
561
|
+
tools_str = " ".join([
|
|
562
|
+
f"\033[32m[OK] {t}\033[0m" if ok else f"\033[31m[--] {t}\033[0m"
|
|
563
|
+
for t, ok in report.tools_status.items()
|
|
564
|
+
])
|
|
565
|
+
print(f"CLI Tools: {tools_str}")
|
|
566
|
+
|
|
567
|
+
score_color = "\033[32m" if report.health_score_pct >= 90 else ("\033[33m" if report.health_score_pct >= 70 else "\033[31m")
|
|
568
|
+
print(f"Health Score: {score_color}{report.health_score_pct} / 100\033[0m")
|
|
569
|
+
print("-" * 80)
|
|
570
|
+
|
|
571
|
+
if report.total_issues == 0:
|
|
572
|
+
print("\033[32m✨ Codebase and Environment are in 100% pristine condition! Zero issues found.\033[0m")
|
|
573
|
+
else:
|
|
574
|
+
print(f"Detected Issues: \033[1;31m{report.total_issues}\033[0m "
|
|
575
|
+
f"(Syntax: {report.syntax_errors}, Imports: {report.missing_imports}, "
|
|
576
|
+
f"Env: {report.env_issues}, Cache: {report.cache_issues})")
|
|
577
|
+
print()
|
|
578
|
+
for idx, issue in enumerate(report.issues[:15], start=1):
|
|
579
|
+
loc = f"{os.path.basename(issue.file_path or 'system')}:{issue.line or '-'}"
|
|
580
|
+
icon = "\033[31m[FAIL]\033[0m" if issue.severity == "error" else "\033[33m[WARN]\033[0m"
|
|
581
|
+
print(f" {idx:2d}. {icon} \033[1m{loc:<22}\033[0m {issue.message}")
|
|
582
|
+
print(f" \033[36mFix:\033[0m {issue.suggested_fix}")
|
|
583
|
+
|
|
584
|
+
if len(report.issues) > 15:
|
|
585
|
+
print(f" ... and {len(report.issues) - 15} more issues.")
|
|
586
|
+
|
|
587
|
+
if heal_report:
|
|
588
|
+
print("-" * 80)
|
|
589
|
+
print("\033[1;32m💉 AUTONOMOUS SELF-HEALING RESULTS:\033[0m")
|
|
590
|
+
print(f" Issues Healed: \033[32m{heal_report.issues_healed} / {heal_report.issues_detected}\033[0m")
|
|
591
|
+
print(f" Health Boost: \033[33m{heal_report.initial_health_score}%\033[0m -> \033[1;32m{heal_report.final_health_score}%\033[0m")
|
|
592
|
+
for a in heal_report.actions:
|
|
593
|
+
status_icon = "\033[32m✔\033[0m" if a.success else "\033[31m✘\033[0m"
|
|
594
|
+
print(f" {status_icon} [{a.category.upper()}] {a.description}")
|
|
595
|
+
if a.diff:
|
|
596
|
+
for dline in a.diff.splitlines()[:3]:
|
|
597
|
+
print(f" \033[35m{dline}\033[0m")
|
|
598
|
+
|
|
599
|
+
print("=" * 80)
|