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,287 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""T-Zero dependency bootstrap and environment doctor (standard library only).
|
|
3
|
+
|
|
4
|
+
* ``ensure_dependencies()`` runs before anything else is imported. If a required package is
|
|
5
|
+
missing or older than the minimum version, it is installed with pip (quietly, to stderr only,
|
|
6
|
+
so the MCP stdio protocol on stdout is never disturbed).
|
|
7
|
+
* ``run_doctor()`` powers ``python main.py --doctor``: it checks Python, packages, tkinter, git
|
|
8
|
+
and the OS keyring and prints an easy-to-read report.
|
|
9
|
+
|
|
10
|
+
Set ``TZERO_NO_AUTO_INSTALL=1`` to disable automatic installation (the tool then only tells you
|
|
11
|
+
what is missing). Frozen builds (the .exe) never install anything.
|
|
12
|
+
|
|
13
|
+
Developer: Toprak Ahmet Aydoğmuş (Siber Akademi)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import importlib
|
|
17
|
+
import importlib.util
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
import shutil
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
from typing import List, NamedTuple, Optional, Tuple
|
|
24
|
+
|
|
25
|
+
OPT_OUT_ENV = "TZERO_NO_AUTO_INSTALL"
|
|
26
|
+
MIN_PYTHON = (3, 9)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Dependency(NamedTuple):
|
|
30
|
+
import_name: str # name used in `import x`
|
|
31
|
+
dist_name: str # name on PyPI / importlib.metadata
|
|
32
|
+
min_version: str # minimum acceptable version
|
|
33
|
+
purpose: str
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def spec(self) -> str:
|
|
37
|
+
return f"{self.dist_name}>={self.min_version}"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
REQUIRED: List[Dependency] = [
|
|
41
|
+
Dependency("requests", "requests", "2.28.0", "AI provider HTTP calls"),
|
|
42
|
+
Dependency("keyring", "keyring", "23.13.1", "OS credential storage"),
|
|
43
|
+
Dependency("darkdetect", "darkdetect", "0.8.0", "OS theme detection"),
|
|
44
|
+
Dependency("pygments", "pygments", "2.14.0", "syntax highlighting"),
|
|
45
|
+
Dependency("PIL", "Pillow", "9.0.0", "icon and image generation"),
|
|
46
|
+
]
|
|
47
|
+
MCP_DEPENDENCY = Dependency("mcp", "mcp", "1.0.0", "Model Context Protocol server")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _version_tuple(version: str) -> Tuple[int, ...]:
|
|
51
|
+
parts: List[int] = []
|
|
52
|
+
for token in re.split(r"[.\-+]", version):
|
|
53
|
+
match = re.match(r"\d+", token)
|
|
54
|
+
if not match:
|
|
55
|
+
break
|
|
56
|
+
parts.append(int(match.group()))
|
|
57
|
+
return tuple(parts)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def installed_version(dep: Dependency) -> Optional[str]:
|
|
61
|
+
"""Returns the installed version string, or None when the distribution is unknown."""
|
|
62
|
+
try:
|
|
63
|
+
from importlib import metadata
|
|
64
|
+
return metadata.version(dep.dist_name)
|
|
65
|
+
except Exception:
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def check_dependency(dep: Dependency) -> Tuple[str, Optional[str]]:
|
|
70
|
+
"""Returns (status, version) where status is 'ok', 'outdated' or 'missing'."""
|
|
71
|
+
try:
|
|
72
|
+
found = importlib.util.find_spec(dep.import_name) is not None
|
|
73
|
+
except (ImportError, ValueError):
|
|
74
|
+
found = False
|
|
75
|
+
if not found:
|
|
76
|
+
return "missing", None
|
|
77
|
+
version = installed_version(dep)
|
|
78
|
+
if version and _version_tuple(version) < _version_tuple(dep.min_version):
|
|
79
|
+
return "outdated", version
|
|
80
|
+
return "ok", version
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _err(message: str) -> None:
|
|
84
|
+
if sys.stderr is not None and hasattr(sys.stderr, "write"):
|
|
85
|
+
try:
|
|
86
|
+
sys.stderr.write(message + "\n")
|
|
87
|
+
sys.stderr.flush()
|
|
88
|
+
except Exception:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def ensure_dependencies(include_mcp: bool = False) -> bool:
|
|
93
|
+
"""Makes sure required packages are present, installing them when allowed.
|
|
94
|
+
|
|
95
|
+
Returns True when everything needed is available afterwards.
|
|
96
|
+
"""
|
|
97
|
+
if sys.version_info < MIN_PYTHON:
|
|
98
|
+
_err(f"[T-Zero] Python {MIN_PYTHON[0]}.{MIN_PYTHON[1]}+ is required "
|
|
99
|
+
f"(found {sys.version_info.major}.{sys.version_info.minor}).")
|
|
100
|
+
return False
|
|
101
|
+
if getattr(sys, "frozen", False): # PyInstaller build: dependencies are bundled
|
|
102
|
+
return True
|
|
103
|
+
|
|
104
|
+
ensure_scripts_in_path(quiet=True)
|
|
105
|
+
deps = list(REQUIRED) + ([MCP_DEPENDENCY] if include_mcp else [])
|
|
106
|
+
problems = [d for d in deps if check_dependency(d)[0] != "ok"]
|
|
107
|
+
if not problems:
|
|
108
|
+
return True
|
|
109
|
+
|
|
110
|
+
specs = [d.spec for d in problems]
|
|
111
|
+
manual = f'"{sys.executable}" -m pip install ' + " ".join(f'"{s}"' for s in specs)
|
|
112
|
+
if os.environ.get(OPT_OUT_ENV) == "1":
|
|
113
|
+
_err("[T-Zero] Missing or outdated packages: " + ", ".join(specs))
|
|
114
|
+
_err(f"[T-Zero] Automatic install is disabled ({OPT_OUT_ENV}=1). Run:\n {manual}")
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
_err("[T-Zero] Installing missing or outdated packages: " + ", ".join(specs))
|
|
118
|
+
try:
|
|
119
|
+
proc = subprocess.run(
|
|
120
|
+
[sys.executable, "-m", "pip", "install", "--disable-pip-version-check", "--quiet", *specs],
|
|
121
|
+
capture_output=True, text=True, timeout=900,
|
|
122
|
+
)
|
|
123
|
+
except (OSError, subprocess.SubprocessError) as exc:
|
|
124
|
+
_err(f"[T-Zero] Could not run pip: {exc}\n Install manually:\n {manual}")
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
importlib.invalidate_caches()
|
|
128
|
+
if proc.returncode != 0:
|
|
129
|
+
output = (proc.stderr or proc.stdout or "").strip()
|
|
130
|
+
_err("[T-Zero] pip failed:\n" + "\n".join(output.splitlines()[-8:]))
|
|
131
|
+
if "externally-managed-environment" in output:
|
|
132
|
+
_err("[T-Zero] This Python is managed by your OS. Use a virtual environment:\n"
|
|
133
|
+
" python -m venv .venv && .venv/bin/pip install -r requirements.txt")
|
|
134
|
+
else:
|
|
135
|
+
_err(f" Install manually:\n {manual}")
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
ensure_scripts_in_path(quiet=True)
|
|
139
|
+
still_bad = [d.spec for d in problems if check_dependency(d)[0] != "ok"]
|
|
140
|
+
if still_bad:
|
|
141
|
+
_err("[T-Zero] Still unavailable after install: " + ", ".join(still_bad))
|
|
142
|
+
return False
|
|
143
|
+
_err("[T-Zero] Dependencies ready.")
|
|
144
|
+
return True
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def ensure_scripts_in_path(quiet: bool = True) -> bool:
|
|
148
|
+
"""Ensures Python's Scripts/bin directory is in the user's PATH environment variable.
|
|
149
|
+
|
|
150
|
+
Automatically fixes Windows and Unix PATH when installing via pip so commands
|
|
151
|
+
like `tzero`, `tzero-gui`, `tzero-mcp`, `tzero-add-mcp` work everywhere immediately.
|
|
152
|
+
"""
|
|
153
|
+
import site
|
|
154
|
+
candidates = []
|
|
155
|
+
|
|
156
|
+
if os.name == "nt":
|
|
157
|
+
sys_scripts = os.path.join(sys.prefix, "Scripts")
|
|
158
|
+
user_scripts = os.path.join(site.USER_BASE, "Scripts")
|
|
159
|
+
else:
|
|
160
|
+
sys_scripts = os.path.join(sys.prefix, "bin")
|
|
161
|
+
user_scripts = os.path.join(site.USER_BASE, "bin")
|
|
162
|
+
|
|
163
|
+
for p in [sys_scripts, user_scripts]:
|
|
164
|
+
if p and os.path.isdir(p) and p not in candidates:
|
|
165
|
+
candidates.append(p)
|
|
166
|
+
|
|
167
|
+
current_path_dirs = [os.path.normcase(os.path.abspath(x)) for x in os.environ.get("PATH", "").split(os.pathsep) if x]
|
|
168
|
+
|
|
169
|
+
added_any = False
|
|
170
|
+
for candidate in candidates:
|
|
171
|
+
norm_cand = os.path.normcase(os.path.abspath(candidate))
|
|
172
|
+
if norm_cand not in current_path_dirs:
|
|
173
|
+
# Update current process
|
|
174
|
+
os.environ["PATH"] = candidate + os.pathsep + os.environ.get("PATH", "")
|
|
175
|
+
current_path_dirs.insert(0, norm_cand)
|
|
176
|
+
added_any = True
|
|
177
|
+
|
|
178
|
+
# Permanently update user PATH
|
|
179
|
+
if os.name == "nt":
|
|
180
|
+
try:
|
|
181
|
+
import winreg
|
|
182
|
+
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Environment", 0, winreg.KEY_READ | winreg.KEY_WRITE) as key:
|
|
183
|
+
try:
|
|
184
|
+
user_path, _ = winreg.QueryValueEx(key, "Path")
|
|
185
|
+
except FileNotFoundError:
|
|
186
|
+
user_path = ""
|
|
187
|
+
parts = [x.strip() for x in user_path.split(";") if x.strip()]
|
|
188
|
+
if candidate not in parts:
|
|
189
|
+
parts.append(candidate)
|
|
190
|
+
new_path = ";".join(parts)
|
|
191
|
+
winreg.SetValueEx(key, "Path", 0, winreg.REG_EXPAND_SZ, new_path)
|
|
192
|
+
if not quiet:
|
|
193
|
+
sys.stderr.write(f"[T-Zero] Added {candidate} to User PATH in Windows Registry.\n")
|
|
194
|
+
except Exception:
|
|
195
|
+
pass
|
|
196
|
+
else:
|
|
197
|
+
try:
|
|
198
|
+
home = os.path.expanduser("~")
|
|
199
|
+
for rc_name in [".bashrc", ".zshrc"]:
|
|
200
|
+
rc_path = os.path.join(home, rc_name)
|
|
201
|
+
if os.path.exists(rc_path):
|
|
202
|
+
with open(rc_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
203
|
+
rc_content = f.read()
|
|
204
|
+
export_line = f'export PATH="{candidate}:$PATH"'
|
|
205
|
+
if candidate not in rc_content:
|
|
206
|
+
with open(rc_path, "a", encoding="utf-8") as f:
|
|
207
|
+
f.write(f"\n# Added by T-Zero Context Architect\n{export_line}\n")
|
|
208
|
+
if not quiet:
|
|
209
|
+
sys.stderr.write(f"[T-Zero] Added {candidate} to {rc_name}\n")
|
|
210
|
+
except Exception:
|
|
211
|
+
pass
|
|
212
|
+
|
|
213
|
+
return added_any
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _line(level: str, text: str) -> str:
|
|
217
|
+
return f" [{level:^4}] {text}"
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def run_doctor() -> int:
|
|
221
|
+
"""Checks the environment, fixing missing packages when allowed. Returns a process exit code."""
|
|
222
|
+
print("T-Zero Doctor: environment check\n")
|
|
223
|
+
failures = 0
|
|
224
|
+
|
|
225
|
+
py = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
226
|
+
if sys.version_info >= MIN_PYTHON:
|
|
227
|
+
print(_line("OK", f"Python {py}"))
|
|
228
|
+
else:
|
|
229
|
+
failures += 1
|
|
230
|
+
print(_line("FAIL", f"Python {py}: version {MIN_PYTHON[0]}.{MIN_PYTHON[1]}+ is required"))
|
|
231
|
+
|
|
232
|
+
ensure_dependencies(include_mcp=True)
|
|
233
|
+
for dep in REQUIRED + [MCP_DEPENDENCY]:
|
|
234
|
+
status, version = check_dependency(dep)
|
|
235
|
+
label = f"{dep.dist_name} {version or ''}".strip()
|
|
236
|
+
if status == "ok":
|
|
237
|
+
print(_line("OK", f"{label} ({dep.purpose})"))
|
|
238
|
+
else:
|
|
239
|
+
failures += 1
|
|
240
|
+
print(_line("FAIL", f"{dep.dist_name}>={dep.min_version} is {status} ({dep.purpose})"))
|
|
241
|
+
|
|
242
|
+
if importlib.util.find_spec("mcp") is not None:
|
|
243
|
+
try:
|
|
244
|
+
if importlib.util.find_spec("mcp.server.mcpserver") is not None:
|
|
245
|
+
print(_line("OK", "MCP SDK flavor: MCPServer (mcp 2.x)"))
|
|
246
|
+
else:
|
|
247
|
+
print(_line("OK", "MCP SDK flavor: FastMCP (mcp 1.x)"))
|
|
248
|
+
except (ImportError, ValueError):
|
|
249
|
+
print(_line("OK", "MCP SDK flavor: FastMCP (mcp 1.x)"))
|
|
250
|
+
|
|
251
|
+
if importlib.util.find_spec("tkinter") is not None:
|
|
252
|
+
print(_line("OK", "tkinter (desktop GUI available)"))
|
|
253
|
+
else:
|
|
254
|
+
print(_line("WARN", "tkinter missing: the GUI is unavailable, the CLI and MCP server still work "
|
|
255
|
+
"(Debian/Ubuntu: sudo apt install python3-tk)"))
|
|
256
|
+
|
|
257
|
+
if shutil.which("git"):
|
|
258
|
+
print(_line("OK", "git found (commit history and diff features)"))
|
|
259
|
+
else:
|
|
260
|
+
print(_line("WARN", "git not found: git features are disabled"))
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
import keyring
|
|
264
|
+
backend = keyring.get_keyring()
|
|
265
|
+
name = f"{type(backend).__module__}.{type(backend).__name__}"
|
|
266
|
+
if "fail" in name.lower() or "null" in name.lower():
|
|
267
|
+
print(_line("WARN", "no usable OS keyring backend: saving API keys in the app will not work; "
|
|
268
|
+
"set NVIDIA_API_KEY / OPENAI_API_KEY / GEMINI_API_KEY / OPENROUTER_API_KEY / "
|
|
269
|
+
"ANTHROPIC_API_KEY instead"))
|
|
270
|
+
else:
|
|
271
|
+
print(_line("OK", f"keyring backend: {name}"))
|
|
272
|
+
except Exception as exc:
|
|
273
|
+
print(_line("WARN", f"keyring could not be checked: {exc}"))
|
|
274
|
+
|
|
275
|
+
ensure_scripts_in_path(quiet=True)
|
|
276
|
+
print(_line("OK", "Python Scripts PATH verified (tzero, tzero-gui, tzero-mcp accessible)"))
|
|
277
|
+
|
|
278
|
+
print()
|
|
279
|
+
if failures:
|
|
280
|
+
print(f"{failures} problem(s) found. See the FAIL lines above.")
|
|
281
|
+
return 1
|
|
282
|
+
print("All required checks passed.")
|
|
283
|
+
return 0
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
if __name__ == "__main__":
|
|
287
|
+
sys.exit(run_doctor())
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Siber Akademi — T-Zero Instruction & Architecture Drift Scorer.
|
|
4
|
+
Developer: Toprak Ahmet Aydoğmuş (Siber Akademi)
|
|
5
|
+
License: MIT
|
|
6
|
+
|
|
7
|
+
Quantifies how closely LLM code generations and workspace modifications adhere
|
|
8
|
+
to initial system prompt rules, negative constraints, and architectural boundaries.
|
|
9
|
+
Directly measures the "Lost in the Middle" attention degradation phenomenon:
|
|
10
|
+
proves high SNR via AST pruning prevents instruction decay over long sessions.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
import ast
|
|
16
|
+
import json
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from typing import Dict, List, Any, Optional
|
|
19
|
+
|
|
20
|
+
from tzero_security import ZeroLeakEngine
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class DriftFinding:
|
|
25
|
+
category: str
|
|
26
|
+
severity: str
|
|
27
|
+
file_path: str
|
|
28
|
+
line_no: int
|
|
29
|
+
message: str
|
|
30
|
+
penalty_points: float
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class DriftReport:
|
|
35
|
+
workspace_dir: str
|
|
36
|
+
drift_score: float # 0.0 = Perfect zero drift, 100.0 = Severe drift
|
|
37
|
+
adherence_pct: float # 100.0% = Flawless instruction following
|
|
38
|
+
snr_rating: str # "EXCELLENT (HIGH SNR)", "DEGRADED", "CRITICAL"
|
|
39
|
+
findings: List[DriftFinding] = field(default_factory=list)
|
|
40
|
+
files_audited: int = 0
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class DriftAnalyzer:
|
|
44
|
+
"""Measures instruction drift and architectural rule decay across the codebase."""
|
|
45
|
+
|
|
46
|
+
SKIP_DIRS = {
|
|
47
|
+
".git", "__pycache__", ".pytest_cache", "target", "dist",
|
|
48
|
+
"build", ".venv", "venv", "node_modules", ".superpowers", "bin"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def analyze_workspace(cls, workspace_dir: str = ".") -> DriftReport:
|
|
53
|
+
"""Analyzes active workspace files for rule drift, security drift, and complexity."""
|
|
54
|
+
abs_root = os.path.abspath(workspace_dir)
|
|
55
|
+
findings: List[DriftFinding] = []
|
|
56
|
+
files_count = 0
|
|
57
|
+
|
|
58
|
+
total_penalty = 0.0
|
|
59
|
+
|
|
60
|
+
for root, dirs, files in os.walk(abs_root):
|
|
61
|
+
dirs[:] = [d for d in dirs if d not in cls.SKIP_DIRS and not d.startswith(".")]
|
|
62
|
+
for fname in files:
|
|
63
|
+
if fname.endswith(".py"):
|
|
64
|
+
fpath = os.path.join(root, fname)
|
|
65
|
+
rel = os.path.relpath(fpath, abs_root).replace("\\", "/")
|
|
66
|
+
if "test" in rel.lower():
|
|
67
|
+
continue
|
|
68
|
+
files_count += 1
|
|
69
|
+
try:
|
|
70
|
+
with open(fpath, "r", encoding="utf-8", errors="replace") as f:
|
|
71
|
+
content = f.read()
|
|
72
|
+
|
|
73
|
+
# 1. Zero-Leak Security Drift
|
|
74
|
+
sec_hits = ZeroLeakEngine.scan_text(content, check_entropy=True)
|
|
75
|
+
for hit in sec_hits:
|
|
76
|
+
penalty = 15.0
|
|
77
|
+
total_penalty += penalty
|
|
78
|
+
findings.append(DriftFinding(
|
|
79
|
+
category="Zero-Leak Security",
|
|
80
|
+
severity="CRITICAL",
|
|
81
|
+
file_path=rel,
|
|
82
|
+
line_no=hit.line_no,
|
|
83
|
+
message=f"Secret leak introduced: {hit.description}",
|
|
84
|
+
penalty_points=penalty
|
|
85
|
+
))
|
|
86
|
+
|
|
87
|
+
# 2. AST Code Smell & Instruction Drift
|
|
88
|
+
tree = ast.parse(content, filename=rel)
|
|
89
|
+
for node in ast.walk(tree):
|
|
90
|
+
# Bare exceptions swallow interrupts and drift from error guidelines
|
|
91
|
+
if isinstance(node, ast.ExceptHandler) and node.type is None:
|
|
92
|
+
penalty = 5.0
|
|
93
|
+
total_penalty += penalty
|
|
94
|
+
findings.append(DriftFinding(
|
|
95
|
+
category="Error Handling",
|
|
96
|
+
severity="HIGH",
|
|
97
|
+
file_path=rel,
|
|
98
|
+
line_no=node.lineno,
|
|
99
|
+
message="Bare `except:` clause used instead of typed exception",
|
|
100
|
+
penalty_points=penalty
|
|
101
|
+
))
|
|
102
|
+
|
|
103
|
+
# Large un-decomposed functions (>60 lines) violate clean architecture
|
|
104
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
105
|
+
length = getattr(node, "end_lineno", node.lineno) - node.lineno
|
|
106
|
+
if length > 60:
|
|
107
|
+
penalty = 3.0
|
|
108
|
+
total_penalty += penalty
|
|
109
|
+
findings.append(DriftFinding(
|
|
110
|
+
category="Architectural Modularity",
|
|
111
|
+
severity="MEDIUM",
|
|
112
|
+
file_path=rel,
|
|
113
|
+
line_no=node.lineno,
|
|
114
|
+
message=f"Function `{node.name}` ({length} lines) exceeds clean modularity limit (60 max)",
|
|
115
|
+
penalty_points=penalty
|
|
116
|
+
))
|
|
117
|
+
|
|
118
|
+
except Exception:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
# Normalize drift score between 0.0 and 100.0
|
|
122
|
+
normalized_drift = min(100.0, round(total_penalty, 1))
|
|
123
|
+
adherence_pct = max(0.0, round(100.0 - normalized_drift, 1))
|
|
124
|
+
|
|
125
|
+
if adherence_pct >= 90.0:
|
|
126
|
+
snr = "EXCELLENT (HIGH SNR / ZERO DECAY)"
|
|
127
|
+
elif adherence_pct >= 75.0:
|
|
128
|
+
snr = "MODERATE (MINOR ATTENTION DEGRADATION)"
|
|
129
|
+
else:
|
|
130
|
+
snr = "POOR (ATTENTION DILUTION / HIGH DRIFT)"
|
|
131
|
+
|
|
132
|
+
return DriftReport(
|
|
133
|
+
workspace_dir=abs_root,
|
|
134
|
+
drift_score=normalized_drift,
|
|
135
|
+
adherence_pct=adherence_pct,
|
|
136
|
+
snr_rating=snr,
|
|
137
|
+
findings=findings,
|
|
138
|
+
files_audited=files_count
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
@classmethod
|
|
142
|
+
def render_cli(cls, report: DriftReport) -> None:
|
|
143
|
+
"""Renders rich ANSI terminal drift score report."""
|
|
144
|
+
print("\n" + "=" * 80)
|
|
145
|
+
print("🎯 \033[36mT-ZERO INSTRUCTION & ARCHITECTURAL DRIFT ANALYZER\033[0m")
|
|
146
|
+
print("=" * 80)
|
|
147
|
+
print(f"📁 Workspace: \033[33m{report.workspace_dir}\033[0m ({report.files_audited} modules audited)")
|
|
148
|
+
|
|
149
|
+
# Color badge for adherence
|
|
150
|
+
if report.adherence_pct >= 90.0:
|
|
151
|
+
badge = f"\033[32m{report.adherence_pct}% (EXCELLENT)\033[0m"
|
|
152
|
+
elif report.adherence_pct >= 75.0:
|
|
153
|
+
badge = f"\033[33m{report.adherence_pct}% (ACCEPTABLE)\033[0m"
|
|
154
|
+
else:
|
|
155
|
+
badge = f"\033[31m{report.adherence_pct}% (CRITICAL DRIFT)\033[0m"
|
|
156
|
+
|
|
157
|
+
print(f"📈 Prompt Instruction Adherence: {badge}")
|
|
158
|
+
print(f"📉 Cumulative Drift Score: \033[33m{report.drift_score} / 100\033[0m")
|
|
159
|
+
print(f"⚡ Context Attention SNR: \033[32m{report.snr_rating}\033[0m")
|
|
160
|
+
print("-" * 80)
|
|
161
|
+
|
|
162
|
+
if not report.findings:
|
|
163
|
+
print("🌟 \033[32mFlawless Adherence! Zero rule drift, zero secret leaks, zero boundary violations.\033[0m")
|
|
164
|
+
else:
|
|
165
|
+
print(f"Identified {len(report.findings)} rule drift / violation points:")
|
|
166
|
+
for f in report.findings[:15]:
|
|
167
|
+
print(f" • \033[33m{f.file_path}:{f.line_no}\033[0m [{f.category}]: {f.message}")
|
|
168
|
+
if len(report.findings) > 15:
|
|
169
|
+
print(f" ... and {len(report.findings) - 15} more findings.")
|
|
170
|
+
|
|
171
|
+
print("=" * 80)
|
|
172
|
+
print("💡 \033[32mAST pruning preserves high Signal-to-Noise Ratio (SNR), preventing model forgetting over 50+ turns.\033[0m\n")
|
|
173
|
+
|
|
174
|
+
@classmethod
|
|
175
|
+
def to_json(cls, report: DriftReport) -> str:
|
|
176
|
+
"""Serializes drift report into JSON."""
|
|
177
|
+
return json.dumps({
|
|
178
|
+
"workspace_dir": report.workspace_dir,
|
|
179
|
+
"drift_score": report.drift_score,
|
|
180
|
+
"adherence_pct": report.adherence_pct,
|
|
181
|
+
"snr_rating": report.snr_rating,
|
|
182
|
+
"files_audited": report.files_audited,
|
|
183
|
+
"findings_count": len(report.findings),
|
|
184
|
+
"findings": [
|
|
185
|
+
{
|
|
186
|
+
"category": f.category,
|
|
187
|
+
"severity": f.severity,
|
|
188
|
+
"file_path": f.file_path,
|
|
189
|
+
"line_no": f.line_no,
|
|
190
|
+
"message": f.message,
|
|
191
|
+
"penalty_points": f.penalty_points
|
|
192
|
+
}
|
|
193
|
+
for f in report.findings
|
|
194
|
+
]
|
|
195
|
+
}, indent=2)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def main():
|
|
199
|
+
target = sys.argv[1] if len(sys.argv) > 1 and not sys.argv[1].startswith("-") else "."
|
|
200
|
+
rep = DriftAnalyzer.analyze_workspace(target)
|
|
201
|
+
if "--json" in sys.argv:
|
|
202
|
+
print(DriftAnalyzer.to_json(rep))
|
|
203
|
+
else:
|
|
204
|
+
DriftAnalyzer.render_cli(rep)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
if __name__ == "__main__":
|
|
208
|
+
main()
|