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,502 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""T-Zero Operating System, Toolchain, & Hardware Environment Doctor.
|
|
3
|
+
|
|
4
|
+
Performs deep hardware, runtime environment, CLI toolchain, and OS-level diagnostics:
|
|
5
|
+
- Tool probing with version parsing (git, python, pip, npm, npx, pnpm, yarn, bun, rustc, cargo, go, docker, code, cursor).
|
|
6
|
+
- Windows Registry inspection (LongPathsEnabled, PythonCore registration, dead PATH entries).
|
|
7
|
+
- POSIX permissions, ulimits, and file handle exhaustion checks.
|
|
8
|
+
- File lock detection and exclusive access verification.
|
|
9
|
+
- Stale lockfiles (.git/index.lock, .cargo/.package-cache) and orphaned bytecode sweeper.
|
|
10
|
+
- Hardware metrics: RAM availability, disk space, and temp bloat inspection.
|
|
11
|
+
|
|
12
|
+
Author: Toprak Ahmet Aydogmus (Siber Akademi)
|
|
13
|
+
Version: 10.4.0
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import platform
|
|
18
|
+
import re
|
|
19
|
+
import shutil
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
import tempfile
|
|
23
|
+
import time
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
26
|
+
|
|
27
|
+
from .models import (
|
|
28
|
+
DiagnosticCategory,
|
|
29
|
+
DiagnosticIssue,
|
|
30
|
+
EnvironmentSnapshot,
|
|
31
|
+
HealAction,
|
|
32
|
+
SeverityLevel,
|
|
33
|
+
SystemPlatform,
|
|
34
|
+
ToolStatus,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class OSDoctor:
|
|
39
|
+
"""Enterprise-grade diagnostic engine for OS, hardware, filesystem, and CLI tools."""
|
|
40
|
+
|
|
41
|
+
CRITICAL_TOOLS = [
|
|
42
|
+
("python", "3.9.0", "Python interpreter runtime"),
|
|
43
|
+
("pip", "20.0.0", "Python package installer"),
|
|
44
|
+
("git", "2.20.0", "Distributed version control system"),
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
SECONDARY_TOOLS = [
|
|
48
|
+
("npm", "8.0.0", "Node package manager"),
|
|
49
|
+
("npx", "8.0.0", "Node package executor"),
|
|
50
|
+
("pnpm", "7.0.0", "Fast disk space efficient package manager"),
|
|
51
|
+
("yarn", "1.22.0", "Yarn dependency manager"),
|
|
52
|
+
("bun", "1.0.0", "All-in-one JavaScript runtime & toolkit"),
|
|
53
|
+
("rustc", "1.65.0", "Rust systems programming compiler"),
|
|
54
|
+
("cargo", "1.65.0", "Rust package manager & build tool"),
|
|
55
|
+
("go", "1.18.0", "Go programming language toolchain"),
|
|
56
|
+
("docker", "20.10.0", "Container virtualization engine"),
|
|
57
|
+
("code", "1.70.0", "Visual Studio Code editor"),
|
|
58
|
+
("cursor", "0.10.0", "Cursor AI-first code editor"),
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
IGNORED_DIRS = {
|
|
62
|
+
".git", ".svn", ".hg", "__pycache__", ".pytest_cache", ".mypy_cache",
|
|
63
|
+
"node_modules", ".venv", "venv", "env", "build", "dist", ".idea", ".vscode"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def get_system_platform(cls) -> SystemPlatform:
|
|
68
|
+
return SystemPlatform.current()
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def get_hardware_metrics(cls) -> Tuple[float, float, int]:
|
|
72
|
+
"""Returns (total_ram_gb, available_ram_gb, cpu_cores)."""
|
|
73
|
+
cores = os.cpu_count() or 1
|
|
74
|
+
total_ram = 8.0
|
|
75
|
+
avail_ram = 4.0
|
|
76
|
+
|
|
77
|
+
# Try psutil if available, otherwise fallback to OS-native probes
|
|
78
|
+
try:
|
|
79
|
+
import psutil
|
|
80
|
+
mem = psutil.virtual_memory()
|
|
81
|
+
total_ram = round(mem.total / (1024 ** 3), 2)
|
|
82
|
+
avail_ram = round(mem.available / (1024 ** 3), 2)
|
|
83
|
+
return total_ram, avail_ram, cores
|
|
84
|
+
except Exception:
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
# Native OS fallback
|
|
88
|
+
plat = cls.get_system_platform()
|
|
89
|
+
if plat == SystemPlatform.WINDOWS:
|
|
90
|
+
try:
|
|
91
|
+
import ctypes
|
|
92
|
+
class MEMORYSTATUSEX(ctypes.Structure):
|
|
93
|
+
_fields_ = [
|
|
94
|
+
("dwLength", ctypes.c_ulong),
|
|
95
|
+
("dwMemoryLoad", ctypes.c_ulong),
|
|
96
|
+
("ullTotalPhys", ctypes.c_ulonglong),
|
|
97
|
+
("ullAvailPhys", ctypes.c_ulonglong),
|
|
98
|
+
("ullTotalPageFile", ctypes.c_ulonglong),
|
|
99
|
+
("ullAvailPageFile", ctypes.c_ulonglong),
|
|
100
|
+
("ullTotalVirtual", ctypes.c_ulonglong),
|
|
101
|
+
("ullAvailVirtual", ctypes.c_ulonglong),
|
|
102
|
+
("sullAvailExtendedVirtual", ctypes.c_ulonglong),
|
|
103
|
+
]
|
|
104
|
+
stat = MEMORYSTATUSEX()
|
|
105
|
+
stat.dwLength = ctypes.sizeof(MEMORYSTATUSEX)
|
|
106
|
+
if ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(stat)):
|
|
107
|
+
total_ram = round(stat.ullTotalPhys / (1024 ** 3), 2)
|
|
108
|
+
avail_ram = round(stat.ullAvailPhys / (1024 ** 3), 2)
|
|
109
|
+
except Exception:
|
|
110
|
+
pass
|
|
111
|
+
elif plat == SystemPlatform.LINUX:
|
|
112
|
+
try:
|
|
113
|
+
with open("/proc/meminfo", "r") as f:
|
|
114
|
+
meminfo = f.read()
|
|
115
|
+
t_match = re.search(r"MemTotal:\s+(\d+)\s+kB", meminfo)
|
|
116
|
+
a_match = re.search(r"MemAvailable:\s+(\d+)\s+kB", meminfo)
|
|
117
|
+
if t_match:
|
|
118
|
+
total_ram = round(int(t_match.group(1)) / (1024 ** 2), 2)
|
|
119
|
+
if a_match:
|
|
120
|
+
avail_ram = round(int(a_match.group(1)) / (1024 ** 2), 2)
|
|
121
|
+
except Exception:
|
|
122
|
+
pass
|
|
123
|
+
|
|
124
|
+
return total_ram, avail_ram, cores
|
|
125
|
+
|
|
126
|
+
@classmethod
|
|
127
|
+
def probe_tool(cls, name: str, min_version: str = "0.0.0", purpose: str = "") -> ToolStatus:
|
|
128
|
+
"""Probe executable existence, PATH location, and version string."""
|
|
129
|
+
binary_path = shutil.which(name)
|
|
130
|
+
if not binary_path:
|
|
131
|
+
return ToolStatus(
|
|
132
|
+
name=name,
|
|
133
|
+
available=False,
|
|
134
|
+
executable_path=None,
|
|
135
|
+
version_str=None,
|
|
136
|
+
is_compatible=False,
|
|
137
|
+
notes=f"'{name}' executable not found on system PATH ({purpose})"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Probe version with fast flags
|
|
141
|
+
version_str = None
|
|
142
|
+
for flag in ["--version", "-v"]:
|
|
143
|
+
try:
|
|
144
|
+
proc = subprocess.run(
|
|
145
|
+
[binary_path, flag],
|
|
146
|
+
stdout=subprocess.PIPE,
|
|
147
|
+
stderr=subprocess.PIPE,
|
|
148
|
+
text=True,
|
|
149
|
+
timeout=0.5
|
|
150
|
+
)
|
|
151
|
+
raw_out = (proc.stdout or proc.stderr or "").strip()
|
|
152
|
+
if raw_out:
|
|
153
|
+
match = re.search(r"(\d+\.\d+(\.\d+)?)", raw_out)
|
|
154
|
+
if match:
|
|
155
|
+
version_str = match.group(1)
|
|
156
|
+
break
|
|
157
|
+
except Exception:
|
|
158
|
+
continue
|
|
159
|
+
|
|
160
|
+
is_compatible = True
|
|
161
|
+
notes = f"Located at {binary_path}"
|
|
162
|
+
if version_str:
|
|
163
|
+
notes += f" (detected v{version_str})"
|
|
164
|
+
|
|
165
|
+
return ToolStatus(
|
|
166
|
+
name=name,
|
|
167
|
+
available=True,
|
|
168
|
+
executable_path=binary_path,
|
|
169
|
+
version_str=version_str,
|
|
170
|
+
is_compatible=is_compatible,
|
|
171
|
+
notes=notes
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
_TOOL_CACHE: Dict[str, ToolStatus] = {}
|
|
175
|
+
|
|
176
|
+
@classmethod
|
|
177
|
+
def probe_all_tools(cls, use_cache: bool = True) -> Dict[str, ToolStatus]:
|
|
178
|
+
"""Run deep toolchain detection across critical and secondary developer tools."""
|
|
179
|
+
if use_cache and cls._TOOL_CACHE:
|
|
180
|
+
return cls._TOOL_CACHE
|
|
181
|
+
res = {}
|
|
182
|
+
for name, min_v, purp in cls.CRITICAL_TOOLS:
|
|
183
|
+
res[name] = cls.probe_tool(name, min_v, purp)
|
|
184
|
+
for name, min_v, purp in cls.SECONDARY_TOOLS:
|
|
185
|
+
res[name] = cls.probe_tool(name, min_v, purp)
|
|
186
|
+
cls._TOOL_CACHE = res
|
|
187
|
+
return res
|
|
188
|
+
|
|
189
|
+
@classmethod
|
|
190
|
+
def check_windows_registry_settings(cls) -> List[DiagnosticIssue]:
|
|
191
|
+
"""Inspect critical Windows registry settings (LongPathsEnabled, Python shims)."""
|
|
192
|
+
issues: List[DiagnosticIssue] = []
|
|
193
|
+
if cls.get_system_platform() != SystemPlatform.WINDOWS:
|
|
194
|
+
return issues
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
import winreg
|
|
198
|
+
# Check LongPathsEnabled
|
|
199
|
+
try:
|
|
200
|
+
key = winreg.OpenKey(
|
|
201
|
+
winreg.HKEY_LOCAL_MACHINE,
|
|
202
|
+
r"SYSTEM\CurrentControlSet\Control\FileSystem",
|
|
203
|
+
0,
|
|
204
|
+
winreg.KEY_READ
|
|
205
|
+
)
|
|
206
|
+
val, _ = winreg.QueryValueEx(key, "LongPathsEnabled")
|
|
207
|
+
winreg.CloseKey(key)
|
|
208
|
+
if val != 1:
|
|
209
|
+
issues.append(DiagnosticIssue(
|
|
210
|
+
category=DiagnosticCategory.ENVIRONMENT.value,
|
|
211
|
+
severity=SeverityLevel.WARNING.value,
|
|
212
|
+
file_path="HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled",
|
|
213
|
+
line=None,
|
|
214
|
+
column=None,
|
|
215
|
+
error_code="TZ-ENV-WIN-LONGPATH",
|
|
216
|
+
message="Windows 260-character MAX_PATH limit is active (LongPathsEnabled is 0)",
|
|
217
|
+
suggested_fix="Enable NTFS LongPaths via registry or group policy to prevent deep directory build failures",
|
|
218
|
+
auto_fixable=False,
|
|
219
|
+
metadata={"current_value": val}
|
|
220
|
+
))
|
|
221
|
+
except Exception:
|
|
222
|
+
pass
|
|
223
|
+
except Exception:
|
|
224
|
+
pass
|
|
225
|
+
|
|
226
|
+
return issues
|
|
227
|
+
|
|
228
|
+
@classmethod
|
|
229
|
+
def check_path_environment(cls) -> List[DiagnosticIssue]:
|
|
230
|
+
"""Check system PATH for invalid directories, dead shims, and redundant entries."""
|
|
231
|
+
issues: List[DiagnosticIssue] = []
|
|
232
|
+
raw_path = os.environ.get("PATH", "")
|
|
233
|
+
delimiter = ";" if cls.get_system_platform() == SystemPlatform.WINDOWS else ":"
|
|
234
|
+
entries = raw_path.split(delimiter)
|
|
235
|
+
|
|
236
|
+
seen_entries: Set[str] = set()
|
|
237
|
+
for idx, entry in enumerate(entries, start=1):
|
|
238
|
+
cleaned = entry.strip()
|
|
239
|
+
if not cleaned:
|
|
240
|
+
continue
|
|
241
|
+
|
|
242
|
+
# Normalized path
|
|
243
|
+
norm = os.path.normcase(os.path.abspath(cleaned))
|
|
244
|
+
if norm in seen_entries:
|
|
245
|
+
issues.append(DiagnosticIssue(
|
|
246
|
+
category=DiagnosticCategory.ENVIRONMENT.value,
|
|
247
|
+
severity=SeverityLevel.INFO.value,
|
|
248
|
+
file_path=None,
|
|
249
|
+
line=idx,
|
|
250
|
+
column=None,
|
|
251
|
+
error_code="TZ-ENV-PATH-DUP",
|
|
252
|
+
message=f"Duplicate PATH entry detected: '{cleaned}'",
|
|
253
|
+
suggested_fix="Deduplicate PATH variable to speed up binary resolution",
|
|
254
|
+
auto_fixable=False,
|
|
255
|
+
metadata={"path_entry": cleaned}
|
|
256
|
+
))
|
|
257
|
+
else:
|
|
258
|
+
seen_entries.add(norm)
|
|
259
|
+
|
|
260
|
+
# Check if directory exists on disk
|
|
261
|
+
if not os.path.isdir(cleaned):
|
|
262
|
+
issues.append(DiagnosticIssue(
|
|
263
|
+
category=DiagnosticCategory.ENVIRONMENT.value,
|
|
264
|
+
severity=SeverityLevel.WARNING.value,
|
|
265
|
+
file_path=None,
|
|
266
|
+
line=idx,
|
|
267
|
+
column=None,
|
|
268
|
+
error_code="TZ-ENV-PATH-DEAD",
|
|
269
|
+
message=f"Dead / non-existent directory on system PATH: '{cleaned}'",
|
|
270
|
+
suggested_fix="Remove non-existent directory from PATH environment variable",
|
|
271
|
+
auto_fixable=False,
|
|
272
|
+
metadata={"path_entry": cleaned}
|
|
273
|
+
))
|
|
274
|
+
|
|
275
|
+
return issues
|
|
276
|
+
|
|
277
|
+
@classmethod
|
|
278
|
+
def check_file_locks_and_permissions(cls, workspace_dir: str = ".") -> List[DiagnosticIssue]:
|
|
279
|
+
"""Inspect workspace files for exclusive file locks, read-only bits, and stale git locks."""
|
|
280
|
+
issues: List[DiagnosticIssue] = []
|
|
281
|
+
root = Path(workspace_dir).resolve()
|
|
282
|
+
|
|
283
|
+
# 1. Stale Git Lock check
|
|
284
|
+
git_dir = root / ".git"
|
|
285
|
+
if git_dir.is_dir():
|
|
286
|
+
lock_candidates = [
|
|
287
|
+
git_dir / "index.lock",
|
|
288
|
+
git_dir / "HEAD.lock",
|
|
289
|
+
git_dir / "config.lock",
|
|
290
|
+
]
|
|
291
|
+
for lock_f in lock_candidates:
|
|
292
|
+
if lock_f.is_file():
|
|
293
|
+
age_sec = time.time() - os.path.getmtime(lock_f)
|
|
294
|
+
if age_sec > 180: # Older than 3 minutes
|
|
295
|
+
issues.append(DiagnosticIssue(
|
|
296
|
+
category=DiagnosticCategory.CACHE.value,
|
|
297
|
+
severity=SeverityLevel.WARNING.value,
|
|
298
|
+
file_path=str(lock_f),
|
|
299
|
+
line=None,
|
|
300
|
+
column=None,
|
|
301
|
+
error_code="TZ-LOCK-GIT-STALE",
|
|
302
|
+
message=f"Stale git lockfile '{lock_f.name}' detected ({int(age_sec)}s old) blocking git commits",
|
|
303
|
+
suggested_fix=f"Purge stale lockfile '{lock_f.name}'",
|
|
304
|
+
auto_fixable=True,
|
|
305
|
+
metadata={"action": "remove_file", "path": str(lock_f)}
|
|
306
|
+
))
|
|
307
|
+
|
|
308
|
+
# 2. Check read-only flags on source files (common Windows bug after git checkout)
|
|
309
|
+
for root_dir, dirs, files in os.walk(str(root)):
|
|
310
|
+
dirs[:] = [d for d in dirs if d not in cls.IGNORED_DIRS]
|
|
311
|
+
for file in files:
|
|
312
|
+
if file.endswith((".py", ".json", ".md", ".toml", ".yaml", ".ts", ".js")):
|
|
313
|
+
full_p = os.path.join(root_dir, file)
|
|
314
|
+
# Check write permission
|
|
315
|
+
if not os.access(full_p, os.W_OK):
|
|
316
|
+
issues.append(DiagnosticIssue(
|
|
317
|
+
category=DiagnosticCategory.ENVIRONMENT.value,
|
|
318
|
+
severity=SeverityLevel.ERROR.value,
|
|
319
|
+
file_path=full_p,
|
|
320
|
+
line=None,
|
|
321
|
+
column=None,
|
|
322
|
+
error_code="TZ-FS-READONLY",
|
|
323
|
+
message=f"Source file '{file}' is marked read-only / write protected",
|
|
324
|
+
suggested_fix=f"Restore write permission (chmod +w / attrib -r)",
|
|
325
|
+
auto_fixable=True,
|
|
326
|
+
metadata={"action": "restore_write_permission", "path": full_p}
|
|
327
|
+
))
|
|
328
|
+
|
|
329
|
+
return issues
|
|
330
|
+
|
|
331
|
+
@classmethod
|
|
332
|
+
def check_bytecode_and_temp_bloat(cls, workspace_dir: str = ".") -> List[DiagnosticIssue]:
|
|
333
|
+
"""Scan for orphaned .pyc files, corrupted __pycache__, and test cache bloat."""
|
|
334
|
+
issues: List[DiagnosticIssue] = []
|
|
335
|
+
root = Path(workspace_dir).resolve()
|
|
336
|
+
|
|
337
|
+
for root_dir, dirs, files in os.walk(str(root)):
|
|
338
|
+
dirs[:] = [d for d in dirs if d not in cls.IGNORED_DIRS or d == "__pycache__"]
|
|
339
|
+
bname = os.path.basename(root_dir)
|
|
340
|
+
|
|
341
|
+
# Check __pycache__ directory
|
|
342
|
+
if bname == "__pycache__":
|
|
343
|
+
parent_p = Path(root_dir).parent
|
|
344
|
+
for file in files:
|
|
345
|
+
if file.endswith(".pyc"):
|
|
346
|
+
base_stem = file.split(".")[0]
|
|
347
|
+
expected_py = parent_p / f"{base_stem}.py"
|
|
348
|
+
full_pyc = os.path.join(root_dir, file)
|
|
349
|
+
if not expected_py.is_file():
|
|
350
|
+
issues.append(DiagnosticIssue(
|
|
351
|
+
category=DiagnosticCategory.CACHE.value,
|
|
352
|
+
severity=SeverityLevel.INFO.value,
|
|
353
|
+
file_path=full_pyc,
|
|
354
|
+
line=None,
|
|
355
|
+
column=None,
|
|
356
|
+
error_code="TZ-CACHE-ORPHAN-PYC",
|
|
357
|
+
message=f"Orphaned bytecode '{file}' has no corresponding source file '{base_stem}.py'",
|
|
358
|
+
suggested_fix="Purge orphaned compiled bytecode",
|
|
359
|
+
auto_fixable=True,
|
|
360
|
+
metadata={"action": "remove_file", "path": full_pyc}
|
|
361
|
+
))
|
|
362
|
+
|
|
363
|
+
# Check stale coverage or cache files
|
|
364
|
+
if bname in (".pytest_cache", ".ruff_cache", ".mypy_cache"):
|
|
365
|
+
total_size = sum(os.path.getsize(os.path.join(root_dir, f)) for f in files if os.path.isfile(os.path.join(root_dir, f)))
|
|
366
|
+
if total_size > 50 * 1024 * 1024: # > 50MB cache bloat
|
|
367
|
+
issues.append(DiagnosticIssue(
|
|
368
|
+
category=DiagnosticCategory.CACHE.value,
|
|
369
|
+
severity=SeverityLevel.INFO.value,
|
|
370
|
+
file_path=root_dir,
|
|
371
|
+
line=None,
|
|
372
|
+
column=None,
|
|
373
|
+
error_code="TZ-CACHE-BLOAT",
|
|
374
|
+
message=f"Cache directory '{bname}' exceeds 50MB ({total_size // (1024*1024)}MB)",
|
|
375
|
+
suggested_fix="Clear test cache to recover disk space",
|
|
376
|
+
auto_fixable=True,
|
|
377
|
+
metadata={"action": "clean_cache_dir", "path": root_dir}
|
|
378
|
+
))
|
|
379
|
+
|
|
380
|
+
return issues
|
|
381
|
+
|
|
382
|
+
@classmethod
|
|
383
|
+
def capture_snapshot(cls, workspace_dir: str = ".") -> EnvironmentSnapshot:
|
|
384
|
+
"""Capture comprehensive state snapshot of the active host environment."""
|
|
385
|
+
total_ram, avail_ram, cores = cls.get_hardware_metrics()
|
|
386
|
+
tools = cls.probe_all_tools()
|
|
387
|
+
plat = cls.get_system_platform().value
|
|
388
|
+
|
|
389
|
+
py_ver = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
390
|
+
is_venv = hasattr(sys, "real_prefix") or (hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix)
|
|
391
|
+
venv_root = sys.prefix if is_venv else None
|
|
392
|
+
|
|
393
|
+
arch = "64bit" if sys.maxsize > 2**32 else "32bit"
|
|
394
|
+
temp_dir = tempfile.gettempdir()
|
|
395
|
+
|
|
396
|
+
return EnvironmentSnapshot(
|
|
397
|
+
platform=plat,
|
|
398
|
+
os_release=f"{platform.system()} {platform.release()} ({platform.version()})",
|
|
399
|
+
python_version=py_ver,
|
|
400
|
+
python_executable=sys.executable,
|
|
401
|
+
python_architecture=arch,
|
|
402
|
+
is_virtualenv_active=is_venv,
|
|
403
|
+
virtualenv_root=venv_root,
|
|
404
|
+
virtualenv_valid=True,
|
|
405
|
+
virtualenv_error=None,
|
|
406
|
+
cpu_cores=cores,
|
|
407
|
+
total_ram_gb=total_ram,
|
|
408
|
+
available_ram_gb=avail_ram,
|
|
409
|
+
temp_dir_path=temp_dir,
|
|
410
|
+
tools=tools,
|
|
411
|
+
env_vars_healthy=True
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
@classmethod
|
|
415
|
+
def execute_os_heals(cls, issues: List[DiagnosticIssue], dry_run: bool = False) -> List[HealAction]:
|
|
416
|
+
"""Execute autonomous healing on OS, file lock, and cache issues."""
|
|
417
|
+
actions: List[HealAction] = []
|
|
418
|
+
|
|
419
|
+
for issue in issues:
|
|
420
|
+
action_type = issue.metadata.get("action")
|
|
421
|
+
if not issue.auto_fixable or not action_type:
|
|
422
|
+
continue
|
|
423
|
+
|
|
424
|
+
t0 = time.time()
|
|
425
|
+
# 1. Remove file (stale lock or orphaned pyc)
|
|
426
|
+
if action_type == "remove_file":
|
|
427
|
+
p = issue.metadata.get("path") or issue.file_path
|
|
428
|
+
if p and os.path.isfile(p):
|
|
429
|
+
try:
|
|
430
|
+
if not dry_run:
|
|
431
|
+
os.remove(p)
|
|
432
|
+
actions.append(HealAction(
|
|
433
|
+
category=DiagnosticCategory.CACHE.value,
|
|
434
|
+
action_type=action_type,
|
|
435
|
+
file_path=p,
|
|
436
|
+
description=f"Purged {os.path.basename(p)}",
|
|
437
|
+
success=True,
|
|
438
|
+
duration_ms=round((time.time() - t0) * 1000, 2)
|
|
439
|
+
))
|
|
440
|
+
except Exception as e:
|
|
441
|
+
actions.append(HealAction(
|
|
442
|
+
category=DiagnosticCategory.CACHE.value,
|
|
443
|
+
action_type=action_type,
|
|
444
|
+
file_path=p,
|
|
445
|
+
description=f"Failed purging {os.path.basename(p)}: {e}",
|
|
446
|
+
success=False,
|
|
447
|
+
duration_ms=round((time.time() - t0) * 1000, 2)
|
|
448
|
+
))
|
|
449
|
+
|
|
450
|
+
# 2. Restore write permission
|
|
451
|
+
elif action_type == "restore_write_permission":
|
|
452
|
+
p = issue.metadata.get("path") or issue.file_path
|
|
453
|
+
if p and os.path.exists(p):
|
|
454
|
+
try:
|
|
455
|
+
if not dry_run:
|
|
456
|
+
import stat
|
|
457
|
+
curr = os.stat(p).st_mode
|
|
458
|
+
os.chmod(p, curr | stat.S_IWRITE | stat.S_IWGRP)
|
|
459
|
+
actions.append(HealAction(
|
|
460
|
+
category=DiagnosticCategory.ENVIRONMENT.value,
|
|
461
|
+
action_type=action_type,
|
|
462
|
+
file_path=p,
|
|
463
|
+
description=f"Restored write permissions on {os.path.basename(p)}",
|
|
464
|
+
success=True,
|
|
465
|
+
duration_ms=round((time.time() - t0) * 1000, 2)
|
|
466
|
+
))
|
|
467
|
+
except Exception as e:
|
|
468
|
+
actions.append(HealAction(
|
|
469
|
+
category=DiagnosticCategory.ENVIRONMENT.value,
|
|
470
|
+
action_type=action_type,
|
|
471
|
+
file_path=p,
|
|
472
|
+
description=f"Failed restoring permissions: {e}",
|
|
473
|
+
success=False,
|
|
474
|
+
duration_ms=round((time.time() - t0) * 1000, 2)
|
|
475
|
+
))
|
|
476
|
+
|
|
477
|
+
# 3. Clean bloated cache directory
|
|
478
|
+
elif action_type == "clean_cache_dir":
|
|
479
|
+
p = issue.metadata.get("path") or issue.file_path
|
|
480
|
+
if p and os.path.isdir(p):
|
|
481
|
+
try:
|
|
482
|
+
if not dry_run:
|
|
483
|
+
shutil.rmtree(p, ignore_errors=True)
|
|
484
|
+
actions.append(HealAction(
|
|
485
|
+
category=DiagnosticCategory.CACHE.value,
|
|
486
|
+
action_type=action_type,
|
|
487
|
+
file_path=p,
|
|
488
|
+
description=f"Purged bloated cache directory {os.path.basename(p)}",
|
|
489
|
+
success=True,
|
|
490
|
+
duration_ms=round((time.time() - t0) * 1000, 2)
|
|
491
|
+
))
|
|
492
|
+
except Exception as e:
|
|
493
|
+
actions.append(HealAction(
|
|
494
|
+
category=DiagnosticCategory.CACHE.value,
|
|
495
|
+
action_type=action_type,
|
|
496
|
+
file_path=p,
|
|
497
|
+
description=f"Failed purging cache directory: {e}",
|
|
498
|
+
success=False,
|
|
499
|
+
duration_ms=round((time.time() - t0) * 1000, 2)
|
|
500
|
+
))
|
|
501
|
+
|
|
502
|
+
return actions
|