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.
Files changed (96) hide show
  1. package/bin/tzero-mcp.js +9 -16
  2. package/bin/tzero.js +9 -16
  3. package/index.js +7 -2
  4. package/package.json +2 -1
  5. package/src/addmcp.py +1194 -0
  6. package/src/main.py +60 -0
  7. package/src/siber_akademi_icon.py +77 -0
  8. package/src/tzero/jarvis/semantic_compressor.py +373 -0
  9. package/src/tzero.py +58 -0
  10. package/src/tzero_benchmark.py +475 -0
  11. package/src/tzero_bridge.py +362 -0
  12. package/src/tzero_ci.py +153 -0
  13. package/src/tzero_commit.py +192 -0
  14. package/src/tzero_cost.py +163 -0
  15. package/src/tzero_dashboard.py +1448 -0
  16. package/src/tzero_deps.py +287 -0
  17. package/src/tzero_drift.py +208 -0
  18. package/src/tzero_explain.py +240 -0
  19. package/src/tzero_features.py +3022 -0
  20. package/src/tzero_fix.py +228 -0
  21. package/src/tzero_fixer/__init__.py +63 -0
  22. package/src/tzero_fixer/amalgamation.py +334 -0
  23. package/src/tzero_fixer/ast_syntax_healer.py +464 -0
  24. package/src/tzero_fixer/ast_type_modernizer.py +288 -0
  25. package/src/tzero_fixer/async_io_modernizer.py +127 -0
  26. package/src/tzero_fixer/automated_regression_suite.py +600 -0
  27. package/src/tzero_fixer/benchmarks_and_diagnostics.py +174 -0
  28. package/src/tzero_fixer/ci_cd_self_healing_gate.py +246 -0
  29. package/src/tzero_fixer/code_duplicity_and_refactorer.py +151 -0
  30. package/src/tzero_fixer/code_smells_healer.py +260 -0
  31. package/src/tzero_fixer/cwe_remediator.py +171 -0
  32. package/src/tzero_fixer/dead_code_eliminator.py +150 -0
  33. package/src/tzero_fixer/dependency_vulnerability_auditor.py +309 -0
  34. package/src/tzero_fixer/git_advanced_surgeon.py +280 -0
  35. package/src/tzero_fixer/hardware_and_disk_doctor.py +325 -0
  36. package/src/tzero_fixer/import_graph_healer.py +412 -0
  37. package/src/tzero_fixer/interactive_tui.py +91 -0
  38. package/src/tzero_fixer/knowledge_base.py +295 -0
  39. package/src/tzero_fixer/knowledge_base_extended.py +940 -0
  40. package/src/tzero_fixer/linters_and_static_analyzer.py +307 -0
  41. package/src/tzero_fixer/mcp_server.py +447 -0
  42. package/src/tzero_fixer/models.py +385 -0
  43. package/src/tzero_fixer/orchestrator.py +375 -0
  44. package/src/tzero_fixer/os_doctor.py +502 -0
  45. package/src/tzero_fixer/packaging_abi_surgeon.py +323 -0
  46. package/src/tzero_fixer/pc_fixer_doctor.py +599 -0
  47. package/src/tzero_fixer/performance_and_memory_profiler.py +274 -0
  48. package/src/tzero_fixer/polyglot_ast_parsers.py +313 -0
  49. package/src/tzero_fixer/polyglot_build_healer.py +364 -0
  50. package/src/tzero_fixer/polyglot_config_healer.py +318 -0
  51. package/src/tzero_fixer/polyglot_doctor.py +372 -0
  52. package/src/tzero_fixer/polyglot_linters.py +619 -0
  53. package/src/tzero_fixer/polyglot_schema_validator.py +305 -0
  54. package/src/tzero_fixer/security_cwe_database.py +444 -0
  55. package/src/tzero_fixer/security_deep_rules.py +114 -0
  56. package/src/tzero_fixer/security_rules_catalog.py +110 -0
  57. package/src/tzero_fixer/security_zero_leak.py +326 -0
  58. package/src/tzero_fixer/smart_patch_recommender.py +223 -0
  59. package/src/tzero_fixer/synthetic_workspace_generator.py +220 -0
  60. package/src/tzero_fixer/system_diagnostics_extended.py +493 -0
  61. package/src/tzero_fixer/system_repair_recipes.py +447 -0
  62. package/src/tzero_fixer/venv_doctor.py +309 -0
  63. package/src/tzero_gateway.py +885 -0
  64. package/src/tzero_guard.py +243 -0
  65. package/src/tzero_infra.py +274 -0
  66. package/src/tzero_jarvis/__init__.py +124 -0
  67. package/src/tzero_jarvis/causal_graph.py +310 -0
  68. package/src/tzero_jarvis/graph_visualizer.py +395 -0
  69. package/src/tzero_jarvis/hybrid_search.py +214 -0
  70. package/src/tzero_jarvis/latent_press.py +137 -0
  71. package/src/tzero_jarvis/memory_consolidation.py +212 -0
  72. package/src/tzero_jarvis/minhash_lsh.py +184 -0
  73. package/src/tzero_jarvis/orchestrator.py +335 -0
  74. package/src/tzero_jarvis/self_healing.py +308 -0
  75. package/src/tzero_jarvis/semantic_compressor.py +887 -0
  76. package/src/tzero_jarvis/swarm.py +261 -0
  77. package/src/tzero_jarvis/swarm_bft.py +144 -0
  78. package/src/tzero_jarvis/workspace_orchestrator.py +1167 -0
  79. package/src/tzero_jarvis/zero_key_router.py +173 -0
  80. package/src/tzero_mcp.py +4858 -0
  81. package/src/tzero_neural_cortex.py +1425 -0
  82. package/src/tzero_packer.py +370 -0
  83. package/src/tzero_patch.py +624 -0
  84. package/src/tzero_pc_fixer.py +12657 -0
  85. package/src/tzero_pipe.py +95 -0
  86. package/src/tzero_polyglot.py +422 -0
  87. package/src/tzero_reachability.py +272 -0
  88. package/src/tzero_replay.py +141 -0
  89. package/src/tzero_security.py +570 -0
  90. package/src/tzero_snapshot.py +258 -0
  91. package/src/tzero_stubs.py +313 -0
  92. package/src/tzero_superdesign.py +774 -0
  93. package/src/tzero_sync.py +241 -0
  94. package/src/tzero_task_benchmark.py +363 -0
  95. package/src/tzero_tui.py +244 -0
  96. package/src/tzero_v3.py +9435 -0
@@ -0,0 +1,3022 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Siber Akademi — T-Zero Advanced Intelligence Suite.
3
+
4
+ Developer: Toprak Ahmet Aydoğmuş (Siber Akademi)
5
+ LinkedIn: https://linkedin.com/in/toprak-ahmet-aydo%C4%9Fmu%C5%9F-60462534b/
6
+ Bio & Socials: https://hopp.bio/siberegitim
7
+ GitHub: https://github.com/toprakahmetaydogmus/TZeroAlgorithm
8
+
9
+ Modules Included:
10
+ 1. ChangeImpactAnalyzer: Blast radius and change impact dependency tracer.
11
+ 2. ArchitectureRuleEngine: T-4 architectural boundary enforcement and CI gate.
12
+ 3. AgentRulesGenerator: One-click export for .cursorrules, .cursor/rules/*.mdc, .clinerules, and Copilot.
13
+ 4. LocalSemanticCodeSearch: 100% private, local BM25 + TF-IDF hybrid code search & RAG engine.
14
+ 5. TokenROICalculator: Token compression ratio, developer ROI, and cost savings telemetry.
15
+ 6. DashboardRequestHandler / launch_web_dashboard: Lightweight zero-dependency local web dashboard server (localhost:7300).
16
+ """
17
+
18
+ import os
19
+ import sys
20
+ import re
21
+ import ast
22
+ import json
23
+ import math
24
+ import collections
25
+ import http.server
26
+ import socketserver
27
+ import threading
28
+ import urllib.parse
29
+ import subprocess
30
+ import fnmatch
31
+ import difflib
32
+ import hashlib
33
+ import copy
34
+ from typing import Dict, List, Set, Tuple, Optional, Any
35
+
36
+ # Ensure parent directory is in sys.path
37
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
38
+ if SCRIPT_DIR not in sys.path:
39
+ sys.path.insert(0, SCRIPT_DIR)
40
+
41
+
42
+ _IGNORE_RULES_CACHE: Dict[str, List[str]] = {}
43
+
44
+ def get_ignore_patterns(base_dir: str) -> List[str]:
45
+ """Reads .gitignore and .tzeroignore patterns from project directory."""
46
+ if base_dir in _IGNORE_RULES_CACHE:
47
+ return _IGNORE_RULES_CACHE[base_dir]
48
+
49
+ patterns = [
50
+ ".git", ".git/*", "__pycache__", "*/__pycache__/*", "*.pyc",
51
+ "node_modules", "*/node_modules/*", ".venv", "venv", "*/.venv/*", "*/venv/*",
52
+ "build", "dist", "*.egg-info", "*.min.js", "*.min.css", "*.lock"
53
+ ]
54
+ for ign_file in (".gitignore", ".tzeroignore"):
55
+ p = os.path.join(base_dir, ign_file)
56
+ if os.path.isfile(p):
57
+ try:
58
+ with open(p, "r", encoding="utf-8", errors="ignore") as f:
59
+ for line in f:
60
+ line = line.strip()
61
+ if line and not line.startswith("#"):
62
+ patterns.append(line.rstrip("/"))
63
+ patterns.append(line.rstrip("/") + "/*")
64
+ except Exception:
65
+ pass
66
+ _IGNORE_RULES_CACHE[base_dir] = patterns
67
+ return patterns
68
+
69
+
70
+ def should_skip_dir(root: str, base_dir: str) -> bool:
71
+ """Helper to check if a subdirectory should be skipped relative to base project dir."""
72
+ rel = os.path.relpath(root, base_dir).replace("\\", "/")
73
+ if rel == ".":
74
+ return False
75
+ parts = rel.split("/")
76
+ if any(p.startswith(".") or p in ("__pycache__", "build", "dist", "node_modules", "venv", ".venv") for p in parts):
77
+ return True
78
+ patterns = get_ignore_patterns(base_dir)
79
+ for pat in patterns:
80
+ if fnmatch.fnmatch(rel, pat) or fnmatch.fnmatch(parts[-1], pat):
81
+ return True
82
+ return False
83
+
84
+
85
+ def should_skip_file(file_path: str, base_dir: str) -> bool:
86
+ """Helper to check if a file matches .gitignore or .tzeroignore patterns."""
87
+ rel = os.path.relpath(file_path, base_dir).replace("\\", "/")
88
+ fname = os.path.basename(file_path)
89
+ if fname.startswith(".") or fname.endswith((".pyc", ".min.js", ".min.css", ".lock")):
90
+ return True
91
+ patterns = get_ignore_patterns(base_dir)
92
+ for pat in patterns:
93
+ if fnmatch.fnmatch(rel, pat) or fnmatch.fnmatch(fname, pat):
94
+ return True
95
+ return False
96
+
97
+
98
+ # =============================================================================
99
+ # 1. CHANGE IMPACT & BLAST RADIUS ANALYZER
100
+ # =============================================================================
101
+
102
+ class ChangeImpactAnalyzer:
103
+ """Calculates blast radius and change impact for modified symbols and files.
104
+
105
+ Traces AST definitions, call references, and inbound import graphs to compute
106
+ risk scores and affected modules before making code changes.
107
+ """
108
+
109
+ def __init__(self, project_dir: str):
110
+ self.project_dir = os.path.abspath(project_dir)
111
+ self.files_list: List[str] = []
112
+ self.snippets: Dict[str, str] = {}
113
+ self.ast_trees: Dict[str, ast.AST] = {}
114
+ self.symbol_defs: Dict[str, str] = {} # symbol -> primary defining_file
115
+ self.symbol_locations: Dict[str, List[str]] = collections.defaultdict(list) # symbol -> [defining_files]
116
+ self.import_graph: Dict[str, Set[str]] = collections.defaultdict(set) # file -> imported_modules
117
+ self.inbound_graph: Dict[str, Set[str]] = collections.defaultdict(set) # module -> dependent_files
118
+ self._build_index()
119
+
120
+ def _add_symbol_def(self, symbol: str, rel_path: str):
121
+ if symbol not in self.symbol_defs:
122
+ self.symbol_defs[symbol] = rel_path
123
+ if rel_path not in self.symbol_locations[symbol]:
124
+ self.symbol_locations[symbol].append(rel_path)
125
+
126
+ def _build_index(self):
127
+ """Scans python files and builds symbol and dependency maps."""
128
+ for root, _, files in os.walk(self.project_dir):
129
+ if should_skip_dir(root, self.project_dir):
130
+ continue
131
+ for f in files:
132
+ if f.endswith('.py'):
133
+ full_p = os.path.join(root, f)
134
+ rel_p = os.path.relpath(full_p, self.project_dir).replace('\\', '/')
135
+ self.files_list.append(rel_p)
136
+ try:
137
+ with open(full_p, 'r', encoding='utf-8', errors='ignore') as fp:
138
+ content = fp.read()
139
+ self.snippets[rel_p] = content
140
+ self._index_file_ast(rel_p, content)
141
+ except Exception:
142
+ pass
143
+
144
+ def _index_file_ast(self, rel_path: str, code: str):
145
+ try:
146
+ tree = ast.parse(code)
147
+ self.ast_trees[rel_path] = tree
148
+ stem = os.path.splitext(os.path.basename(rel_path))[0]
149
+
150
+ for node in tree.body:
151
+ if isinstance(node, ast.ClassDef):
152
+ self._add_symbol_def(node.name, rel_path)
153
+ for item in node.body:
154
+ if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
155
+ self._add_symbol_def(item.name, rel_path)
156
+ self._add_symbol_def(f"{node.name}.{item.name}", rel_path)
157
+ elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
158
+ self._add_symbol_def(node.name, rel_path)
159
+ elif isinstance(node, ast.Import):
160
+ for name in node.names:
161
+ base = name.name.split('.')[0]
162
+ self.import_graph[rel_path].add(base)
163
+ self.inbound_graph[base].add(rel_path)
164
+ elif isinstance(node, ast.ImportFrom):
165
+ if node.module:
166
+ base = node.module.split('.')[0]
167
+ self.import_graph[rel_path].add(base)
168
+ self.inbound_graph[base].add(rel_path)
169
+ except Exception:
170
+ pass
171
+
172
+ def analyze_symbol(self, symbol_name: str) -> Dict[str, Any]:
173
+ """Calculates the blast radius for modifying a specific symbol (class/function).
174
+
175
+ Uses AST analysis over workspace code to accurately discover call sites and
176
+ attribute accesses without false positives from comments and string literals.
177
+ """
178
+ defining_file = self.symbol_defs.get(symbol_name)
179
+ defining_locations = self.symbol_locations.get(symbol_name, [defining_file] if defining_file else [])
180
+ direct_callers: List[Dict[str, Any]] = []
181
+ transitive_files: Set[str] = set()
182
+
183
+ target_attr = symbol_name.split('.')[-1]
184
+ target_class = symbol_name.split('.')[0] if '.' in symbol_name else ""
185
+
186
+ for f, content in self.snippets.items():
187
+ if f in defining_locations or target_attr not in content:
188
+ continue
189
+
190
+ matches_lines: List[int] = []
191
+ try:
192
+ tree = self.ast_trees.get(f)
193
+ if tree is None:
194
+ tree = ast.parse(content)
195
+ self.ast_trees[f] = tree
196
+ for node in ast.walk(tree):
197
+ if isinstance(node, ast.Call):
198
+ matched = False
199
+ if isinstance(node.func, ast.Name) and node.func.id == target_attr:
200
+ matched = True
201
+ elif isinstance(node.func, ast.Attribute) and node.func.attr == target_attr:
202
+ if target_class:
203
+ if isinstance(node.func.value, ast.Name) and node.func.value.id == target_class:
204
+ matched = True
205
+ else:
206
+ matched = True
207
+ if matched:
208
+ matches_lines.append(getattr(node, 'lineno', 1))
209
+ elif isinstance(node, ast.Attribute) and node.attr == target_attr:
210
+ if target_class and isinstance(node.value, ast.Name) and node.value.id == target_class:
211
+ matches_lines.append(getattr(node, 'lineno', 1))
212
+ elif isinstance(node, ast.Name) and node.id == target_attr:
213
+ # Direct symbol reference as argument, type annotation, or value
214
+ matches_lines.append(getattr(node, 'lineno', 1))
215
+ except Exception:
216
+ # Fallback for non-python files or invalid syntax
217
+ pattern = rf"\b{re.escape(target_attr)}\b"
218
+ for m in re.finditer(pattern, content):
219
+ line_no = content[:m.start()].count('\n') + 1
220
+ matches_lines.append(line_no)
221
+
222
+ unique_lines = sorted(list(set(matches_lines)))
223
+ if unique_lines:
224
+ direct_callers.append({
225
+ "file": f,
226
+ "occurrences": len(unique_lines),
227
+ "lines": unique_lines[:5]
228
+ })
229
+ transitive_files.add(f)
230
+
231
+ # Trace second-level dependents
232
+ second_level: Set[str] = set()
233
+ for df in transitive_files:
234
+ stem = os.path.splitext(os.path.basename(df))[0]
235
+ for dep in self.inbound_graph.get(stem, set()):
236
+ if dep not in defining_locations and dep not in transitive_files:
237
+ second_level.add(dep)
238
+
239
+ total_affected = len(transitive_files) + len(second_level)
240
+ # Blast Radius Score (0-100)
241
+ blast_score = min(100, int((len(direct_callers) * 20) + (len(second_level) * 10)))
242
+ if blast_score >= 75:
243
+ risk = "CRITICAL"
244
+ elif blast_score >= 45:
245
+ risk = "HIGH"
246
+ elif blast_score >= 20:
247
+ risk = "MEDIUM"
248
+ else:
249
+ risk = "LOW"
250
+
251
+ downstream_dependents = [
252
+ {"file": f, "level": 1} for f in sorted(transitive_files)
253
+ ] + [
254
+ {"file": f, "level": 2} for f in sorted(second_level)
255
+ ]
256
+
257
+ # Test Coverage Mapper: Locate test files covering this symbol or defining module
258
+ test_commands: List[str] = []
259
+ tests_dir = os.path.join(self.project_dir, "tests")
260
+ if os.path.isdir(tests_dir):
261
+ target_stem = os.path.splitext(os.path.basename(defining_file))[0] if defining_file else ""
262
+ for t_root, _, t_files in os.walk(tests_dir):
263
+ for tf in t_files:
264
+ if tf.startswith("test_") and tf.endswith(".py"):
265
+ tf_path = os.path.join(t_root, tf)
266
+ rel_tf = os.path.relpath(tf_path, self.project_dir).replace('\\', '/')
267
+ if target_stem and (target_stem in tf or f"test_{target_stem}.py" == tf):
268
+ cmd = f"pytest {rel_tf}"
269
+ if cmd not in test_commands:
270
+ test_commands.append(cmd)
271
+ elif tf in self.snippets:
272
+ snip = self.snippets[tf]
273
+ if target_attr in snip:
274
+ cmd = f"pytest {rel_tf}"
275
+ if cmd not in test_commands:
276
+ test_commands.append(cmd)
277
+
278
+ test_cmd = test_commands[0] if test_commands else "pytest tests/"
279
+
280
+ return {
281
+ "symbol": symbol_name,
282
+ "defining_file": defining_file or "Unknown / Built-in",
283
+ "defining_files": defining_locations,
284
+ "blast_score": blast_score,
285
+ "risk_score": blast_score,
286
+ "risk_level": risk,
287
+ "direct_callers": direct_callers,
288
+ "second_level_dependents": sorted(list(second_level)),
289
+ "downstream_dependents": downstream_dependents,
290
+ "total_affected_modules": total_affected,
291
+ "test_command": test_cmd,
292
+ "matching_tests": test_commands,
293
+ }
294
+
295
+ def format_report(self, analysis: Dict[str, Any]) -> str:
296
+ """Formats the analysis into an actionable markdown blast radius report."""
297
+ sym = analysis["symbol"]
298
+ score = analysis["blast_score"]
299
+ risk = analysis["risk_level"]
300
+ f_def = analysis["defining_file"]
301
+
302
+ risk_badges = {
303
+ "LOW": "🟢 LOW RISK",
304
+ "MEDIUM": "🟡 MEDIUM RISK",
305
+ "HIGH": "🟠 HIGH RISK",
306
+ "CRITICAL": "🔴 CRITICAL RISK"
307
+ }
308
+
309
+ out = [
310
+ f"# 💥 Change Impact & Blast Radius: `{sym}`\n",
311
+ f"> **Risk Assessment:** {risk_badges.get(risk, risk)} (Score: {score}/100) ",
312
+ f"> **Target Definition:** `{f_def}` ",
313
+ f"> **Total Affected Modules:** **{analysis['total_affected_modules']} files**\n",
314
+ "---",
315
+ "\n## 🎯 Direct Dependent Modules (Caller Files)",
316
+ ]
317
+
318
+ if analysis.get("test_command"):
319
+ out.append(f"\n> 🧪 **Targeted Test Command:** `{analysis['test_command']}`\n")
320
+
321
+ if analysis["direct_callers"]:
322
+ out.append("| File Path | Reference Count | Line Numbers |")
323
+ out.append("|:----------|:----------------|:-------------|")
324
+ for c in analysis["direct_callers"]:
325
+ lines_str = ", ".join(str(l) for l in c["lines"])
326
+ out.append(f"| `{c['file']}` | {c['occurrences']} | Lines {lines_str} |")
327
+ else:
328
+ out.append("✨ *No direct references found across the workspace.*")
329
+
330
+ if analysis["second_level_dependents"]:
331
+ out.append("\n## 🌊 Transitive Inbound Ripple (Second-Order Callers)")
332
+ for sec in analysis["second_level_dependents"]:
333
+ out.append(f"- `{sec}`")
334
+
335
+ out.append("\n## 🛡️ Recommended Safeguards")
336
+ if risk in ("HIGH", "CRITICAL"):
337
+ out.append("1. **Deprecation Grace Period:** Do not break existing parameter signatures; use optional arguments or decorators.")
338
+ out.append("2. **Regression Test Suite:** Run unit tests covering all direct callers before merging.")
339
+ out.append("3. **Downstream Sync:** Ensure dependent modules are updated simultaneously in this PR.")
340
+ else:
341
+ out.append("1. Verify local unit tests pass.")
342
+ out.append("2. Safe to refactor with standard caution.")
343
+
344
+ return "\n".join(out)
345
+
346
+
347
+ # =============================================================================
348
+ # 2. ARCHITECTURE RULE ENGINE & CI BOUNDARY GATE
349
+ # =============================================================================
350
+
351
+ DEFAULT_RULES_JSON = {
352
+ "version": "1.0",
353
+ "description": "T-Zero Architectural Boundary Rules for Modular Integrity",
354
+ "layers": {
355
+ "core": ["core/*", "engine/*"],
356
+ "api": ["api/*", "tzero_mcp.py", "server/*"],
357
+ "cli": ["main.py", "compile.py", "cli/*"],
358
+ "ui": ["gui/*", "views/*"]
359
+ },
360
+ "forbidden_imports": [
361
+ {
362
+ "from_layer": "core",
363
+ "cannot_import": ["ui", "views"],
364
+ "reason": "Core domain logic must never have visual UI dependencies."
365
+ },
366
+ {
367
+ "from_layer": "models",
368
+ "cannot_import": ["views", "controllers"],
369
+ "reason": "Data models must remain decoupled from presentation layers."
370
+ }
371
+ ]
372
+ }
373
+
374
+
375
+ class ArchitectureRuleEngine:
376
+ """Enforces active architectural boundary rules (T-4) across modules.
377
+
378
+ Prevents spaghetti code, circular imports, and architecture erosion.
379
+ Can be used as a CLI gate in GitHub Actions CI (`--enforce-boundaries`).
380
+ """
381
+
382
+ def __init__(self, project_dir: str, config_path: Optional[str] = None):
383
+ self.project_dir = os.path.abspath(project_dir)
384
+ self.config_path = config_path or os.path.join(self.project_dir, "tzero.rules.json")
385
+ self.rules = self._load_rules()
386
+
387
+ def _load_rules(self) -> Dict[str, Any]:
388
+ if os.path.isfile(self.config_path):
389
+ try:
390
+ with open(self.config_path, "r", encoding="utf-8") as f:
391
+ return json.load(f)
392
+ except Exception:
393
+ pass
394
+ return DEFAULT_RULES_JSON
395
+
396
+ @staticmethod
397
+ def generate_default_rules(target_path: str):
398
+ """Generates a starter tzero.rules.json file."""
399
+ with open(target_path, "w", encoding="utf-8") as f:
400
+ json.dump(DEFAULT_RULES_JSON, f, indent=2)
401
+
402
+ def _matches_layer(self, file_rel: str, patterns: List[str]) -> bool:
403
+ for p in patterns:
404
+ p_clean = p.replace("\\", "/").rstrip("/*")
405
+ if p.endswith("/*"):
406
+ if file_rel.startswith(p_clean + "/") or file_rel == p_clean:
407
+ return True
408
+ elif file_rel == p_clean or os.path.basename(file_rel) == p_clean:
409
+ return True
410
+ return False
411
+
412
+ def enforce_boundaries(self) -> Dict[str, Any]:
413
+ """Scans codebase and verifies all import rules."""
414
+ violations: List[Dict[str, Any]] = []
415
+ layers = self.rules.get("layers", {})
416
+ forbidden = self.rules.get("forbidden_imports", [])
417
+
418
+ # Scan python files
419
+ scanned_files = 0
420
+ for root, _, files in os.walk(self.project_dir):
421
+ if should_skip_dir(root, self.project_dir):
422
+ continue
423
+ for f in files:
424
+ if f.endswith(".py"):
425
+ scanned_files += 1
426
+ full_p = os.path.join(root, f)
427
+ rel_p = os.path.relpath(full_p, self.project_dir).replace("\\", "/")
428
+
429
+ try:
430
+ with open(full_p, "r", encoding="utf-8", errors="ignore") as fp:
431
+ tree = ast.parse(fp.read())
432
+ except Exception:
433
+ continue
434
+
435
+ # Extract file imports
436
+ # Extract file imports (full dotted names & resolved relative paths)
437
+ imported_modules = set()
438
+ for node in ast.walk(tree):
439
+ if isinstance(node, ast.Import):
440
+ line_no = getattr(node, 'lineno', 1)
441
+ for n in node.names:
442
+ imported_modules.add((n.name, line_no))
443
+ elif isinstance(node, ast.ImportFrom):
444
+ line_no = getattr(node, 'lineno', 1)
445
+ base_parts = []
446
+ file_dir = os.path.dirname(rel_p)
447
+ if file_dir:
448
+ base_parts = file_dir.replace('\\', '/').split('/')
449
+
450
+ if node.level and node.level > 0:
451
+ # Relative import resolution based on file position
452
+ if node.level <= len(base_parts):
453
+ prefix_parts = base_parts[:len(base_parts) - node.level + 1]
454
+ if node.module:
455
+ resolved_mod = ".".join(prefix_parts + node.module.split('.'))
456
+ else:
457
+ resolved_mod = ".".join(prefix_parts)
458
+ else:
459
+ resolved_mod = node.module or ""
460
+ if resolved_mod:
461
+ imported_modules.add((resolved_mod, line_no))
462
+ for n in node.names:
463
+ if resolved_mod:
464
+ imported_modules.add((f"{resolved_mod}.{n.name}", line_no))
465
+ else:
466
+ imported_modules.add((n.name, line_no))
467
+ elif node.module:
468
+ imported_modules.add((node.module, line_no))
469
+ for n in node.names:
470
+ imported_modules.add((f"{node.module}.{n.name}", line_no))
471
+
472
+ # Check rules
473
+ for rule in forbidden:
474
+ from_layer = rule.get("from_layer")
475
+ cannot_import = rule.get("cannot_import", [])
476
+ reason = rule.get("reason", "Forbidden architectural dependency.")
477
+ layer_patterns = layers.get(from_layer, [])
478
+
479
+ if self._matches_layer(rel_p, layer_patterns):
480
+ for full_imp, line_no in imported_modules:
481
+ imp_segments = full_imp.split('.')
482
+ for forbidden_target in cannot_import:
483
+ clean_forbidden = forbidden_target.replace('/', '.').rstrip('/*')
484
+ is_violation = (
485
+ full_imp == clean_forbidden
486
+ or full_imp.startswith(clean_forbidden + '.')
487
+ or full_imp.endswith('.' + clean_forbidden)
488
+ or clean_forbidden in imp_segments
489
+ or fnmatch.fnmatch(full_imp, forbidden_target)
490
+ or fnmatch.fnmatch(full_imp, f"{clean_forbidden}.*")
491
+ )
492
+ if is_violation:
493
+ v_entry = {
494
+ "file": rel_p,
495
+ "line": line_no,
496
+ "layer": from_layer,
497
+ "forbidden_import": forbidden_target,
498
+ "imported_module": full_imp,
499
+ "reason": reason
500
+ }
501
+ if v_entry not in violations:
502
+ violations.append(v_entry)
503
+
504
+ is_clean = len(violations) == 0
505
+ return {
506
+ "clean": is_clean,
507
+ "scanned_files": scanned_files,
508
+ "violations": violations,
509
+ "total_violations": len(violations)
510
+ }
511
+
512
+ def format_report(self, result: Dict[str, Any]) -> str:
513
+ """Formats the audit results into a terminal or markdown report."""
514
+ if result["clean"]:
515
+ return (
516
+ f"✅ \033[32m[ARCH PASS] All {result['scanned_files']} files conform to architectural "
517
+ f"boundary rules (tzero.rules.json). Zero violations detected.\033[0m"
518
+ )
519
+
520
+ lines = [
521
+ f"❌ \033[31m[ARCH VIOLATION] Detected {result['total_violations']} architectural boundary violations:\033[0m\n"
522
+ ]
523
+ for v in result["violations"]:
524
+ lines.append(
525
+ f" - \033[33m{v['file']}:{v['line']}\033[0m -> "
526
+ f"Layer '\033[36m{v['layer']}\033[0m' cannot import '\033[31m{v['forbidden_import']}\033[0m'"
527
+ f"\n Reason: {v['reason']}"
528
+ )
529
+ lines.append("\n\033[31mExecution halted due to boundary rule enforcement.\033[0m")
530
+ return "\n".join(lines)
531
+
532
+
533
+ # =============================================================================
534
+ # 3. AI AGENT RULES GENERATOR (.cursorrules, .clinerules, Copilot, Cursor MDC)
535
+ # =============================================================================
536
+
537
+ class AgentRulesGenerator:
538
+ """Generates production-grade native rule files for AI coding agents."""
539
+
540
+ @staticmethod
541
+ def generate_cursorrules(project_name: str, tech_stack: str = "Python / Modern Toolchain") -> str:
542
+ return f"""# Cursor AI Rules for {project_name}
543
+ # Generated by Siber Akademi T-Zero Context Architect V3 (https://github.com/toprakahmetaydogmus/TZeroAlgorithm)
544
+
545
+ [PROJECT CONTEXT]
546
+ - Name: {project_name}
547
+ - Stack: {tech_stack}
548
+ - Architecture: Modular Clean Architecture & High Structural Cohesion
549
+
550
+ [STRICT OPERATIONAL DIRECTIVES]
551
+ 1. RULE T-001: ZERO-LEAK SECURITY GUARANTEE:
552
+ - NEVER hardcode API keys, tokens, or credentials anywhere.
553
+ - Use environment variables or OS keyring exclusively.
554
+ - Never commit sensitive .env files.
555
+
556
+ 2. RULE T-002: ARCHITECTURAL BOUNDARY ENFORCEMENT:
557
+ - Maintain clear separation of concerns between core logic, transport layers, and UI.
558
+ - Avoid creating circular import loops. Enforce tzero.rules.json.
559
+
560
+ 3. RULE T-003: AST CONTRACT FIDELITY:
561
+ - Preserve exact class declarations, typed signatures, and docstrings.
562
+ - Do not delete existing comments or docstrings unless explicitly requested.
563
+
564
+ 4. RULE T-004: ANTI-PATTERN & MINIMAL BLAST RADIUS:
565
+ - Keep functions concise (<= 30 lines) and arguments minimal (<= 6).
566
+ - Before modifying core functions, verify all downstream callers.
567
+ - Always run the test suite (`python -m unittest discover -s tests -v`) before concluding tasks.
568
+ """
569
+
570
+ @staticmethod
571
+ def generate_clinerules(project_name: str) -> str:
572
+ return f"""# Cline & Roo-Code System Rules: {project_name}
573
+ # Enforced by T-Zero Context Engine (Rules T-001 to T-004)
574
+
575
+ ## Persona & Standards
576
+ You are an elite Principal Software Engineer working on `{project_name}`.
577
+
578
+ ## Core Rules:
579
+ 1. **Rule T-001 (Zero-Leak Policy):** Never output or commit real API keys or personal secrets.
580
+ 2. **Rule T-002 (Architectural Boundaries):** Enforce layer isolation and avoid circular dependencies.
581
+ 3. **Rule T-003 (AST Contract Fidelity):** Keep all existing docstrings, type annotations, and signatures intact.
582
+ 4. **Rule T-004 (Quality & Verification):** Ensure every change passes unit tests cleanly with zero regressions.
583
+ """
584
+
585
+ @staticmethod
586
+ def generate_copilot_instructions(project_name: str) -> str:
587
+ return f"""# GitHub Copilot Instructions for {project_name}
588
+ # Enforced by T-Zero Algorithm Rules (T-001 to T-004)
589
+
590
+ ## Repository Guidelines
591
+ - **Project:** {project_name}
592
+ - **Rule T-001 (Security):** 100% Zero-Leak. Use environment variables for secrets.
593
+ - **Rule T-002 (Architecture):** Respect layer boundaries and avoid circular imports.
594
+ - **Rule T-003 (AST Contracts):** PEP 8 for Python. Clean, typed, modular code with complete signatures.
595
+ - **Rule T-004 (Testing):** Add unit tests in `tests/` for any new functionality; keep functions concise.
596
+ """
597
+
598
+ @staticmethod
599
+ def generate_cursor_mdc_rules(project_name: str, project_dir: Optional[str] = None) -> Dict[str, str]:
600
+ """Generates modern modular .cursor/rules/*.mdc format dynamically from tzero.rules.json."""
601
+ layers_desc = []
602
+ forbidden_desc = []
603
+
604
+ if project_dir:
605
+ rules_path = os.path.join(project_dir, "tzero.rules.json")
606
+ if os.path.isfile(rules_path):
607
+ try:
608
+ with open(rules_path, "r", encoding="utf-8") as f:
609
+ rules_data = json.load(f)
610
+ for l_name, patterns in rules_data.get("layers", {}).items():
611
+ layers_desc.append(f"- **Layer `{l_name}`:** `{', '.join(patterns)}`")
612
+ for rule in rules_data.get("forbidden_imports", []):
613
+ forbidden_desc.append(
614
+ f"- Layer `{rule.get('from_layer')}` cannot import `{', '.join(rule.get('cannot_import', []))}`: {rule.get('reason', '')}"
615
+ )
616
+ except Exception:
617
+ pass
618
+
619
+ layers_block = "\n".join(layers_desc) if layers_desc else "- Core business logic must remain decoupled from presentation or CLI entrypoints."
620
+ forbidden_block = "\n".join(forbidden_desc) if forbidden_desc else "- Avoid circular dependencies and cross-layer spaghettification."
621
+
622
+ arch_mdc = f"""---
623
+ description: Architectural Boundary & Modularity Standards for {project_name}
624
+ globs: **/*.py
625
+ ---
626
+ # Architecture Standards for {project_name}
627
+
628
+ ## Active Architectural Layers:
629
+ {layers_block}
630
+
631
+ ## Enforced Import Boundaries:
632
+ {forbidden_block}
633
+
634
+ - All public functions and classes must include descriptive docstrings.
635
+ - Follow PEP 8 and maintain type hints where applicable.
636
+ """
637
+ sec_mdc = f"""---
638
+ description: Zero-Leak Security & Secret Protection
639
+ globs: **/*
640
+ ---
641
+ # Zero-Leak Security Mandate
642
+
643
+ - NEVER write raw API keys (OpenAI, Anthropic, NVIDIA, Gemini) into code.
644
+ - Always retrieve credentials via OS keyring or environment variables.
645
+ - Keep test data strictly synthetic.
646
+ """
647
+ return {
648
+ "architecture.mdc": arch_mdc,
649
+ "security.mdc": sec_mdc
650
+ }
651
+
652
+ @staticmethod
653
+ def generate_windsurfrules(project_name: str) -> str:
654
+ return f"""# Windsurf AI Rules for {project_name}
655
+ # Enforced by Siber Akademi T-Zero Context Engine
656
+
657
+ ## Core Directives
658
+ 1. **Zero-Leak Guarantee:** NEVER hardcode credentials or expose production secrets.
659
+ 2. **Context-Driven Edits:** Always verify architectural layer boundaries and dependencies.
660
+ 3. **Preserve Documentation:** Do not strip comments, docstrings, or type hints unless requested.
661
+ 4. **Verification:** Ensure tests pass cleanly before concluding tasks.
662
+ """
663
+
664
+ @staticmethod
665
+ def generate_claude_instructions(project_name: str) -> str:
666
+ return f"""# Claude Code & Extended Thinking Directives: {project_name}
667
+
668
+ ## System Overview
669
+ - **Project:** {project_name}
670
+ - **Engine:** T-Zero Context Architect
671
+ - **Standards:** Clean Architecture, 100% Zero-Leak Security, PEP 8 / Idiomatic Typing.
672
+
673
+ ## Behavioral Directives:
674
+ - Maintain minimal blast radius on all code changes.
675
+ - Check downstream caller impacts before refactoring shared interfaces.
676
+ - Always run the test suite to prevent regressions.
677
+ """
678
+
679
+ @classmethod
680
+ def export_all(cls, project_dir: str) -> List[str]:
681
+ """Exports all agent rules into the target repository."""
682
+ p_name = os.path.basename(os.path.abspath(project_dir)) or "Project"
683
+ created = []
684
+
685
+ # 1. .cursorrules
686
+ cr_path = os.path.join(project_dir, ".cursorrules")
687
+ with open(cr_path, "w", encoding="utf-8") as f:
688
+ f.write(cls.generate_cursorrules(p_name))
689
+ created.append(cr_path)
690
+
691
+ # 2. .clinerules
692
+ cl_path = os.path.join(project_dir, ".clinerules")
693
+ with open(cl_path, "w", encoding="utf-8") as f:
694
+ f.write(cls.generate_clinerules(p_name))
695
+ created.append(cl_path)
696
+
697
+ # 3. .windsurfrules
698
+ ws_path = os.path.join(project_dir, ".windsurfrules")
699
+ with open(ws_path, "w", encoding="utf-8") as f:
700
+ f.write(cls.generate_windsurfrules(p_name))
701
+ created.append(ws_path)
702
+
703
+ # 4. CLAUDE.md
704
+ claude_path = os.path.join(project_dir, "CLAUDE.md")
705
+ with open(claude_path, "w", encoding="utf-8") as f:
706
+ f.write(cls.generate_claude_instructions(p_name))
707
+ created.append(claude_path)
708
+
709
+ # 5. .github/copilot-instructions.md
710
+ gh_dir = os.path.join(project_dir, ".github")
711
+ os.makedirs(gh_dir, exist_ok=True)
712
+ copilot_path = os.path.join(gh_dir, "copilot-instructions.md")
713
+ with open(copilot_path, "w", encoding="utf-8") as f:
714
+ f.write(cls.generate_copilot_instructions(p_name))
715
+ created.append(copilot_path)
716
+
717
+ # 6. .cursor/rules/*.mdc
718
+ c_rules_dir = os.path.join(project_dir, ".cursor", "rules")
719
+ os.makedirs(c_rules_dir, exist_ok=True)
720
+ for mdc_name, content in cls.generate_cursor_mdc_rules(p_name, project_dir=project_dir).items():
721
+ mdc_path = os.path.join(c_rules_dir, mdc_name)
722
+ with open(mdc_path, "w", encoding="utf-8") as f:
723
+ f.write(content)
724
+ created.append(mdc_path)
725
+
726
+ # 7. AGENT.md / AGENTS.md (Antigravity & Autonomous Orchestrator Directive)
727
+ from tzero_jarvis.workspace_orchestrator import WorkspaceProfiler, WorkspaceGovernanceSynthesizer
728
+ try:
729
+ prof = WorkspaceProfiler(project_dir).profile()
730
+ agent_md_content = WorkspaceGovernanceSynthesizer.generate_agent_md(prof)
731
+
732
+ agent_p = os.path.join(project_dir, "AGENT.md")
733
+ with open(agent_p, "w", encoding="utf-8") as f:
734
+ f.write(agent_md_content)
735
+ created.append(agent_p)
736
+
737
+ agents_p = os.path.join(project_dir, "AGENTS.md")
738
+ with open(agents_p, "w", encoding="utf-8") as f:
739
+ f.write(agent_md_content)
740
+ created.append(agents_p)
741
+
742
+ # 8. ARCHITECTURE.md if not exists
743
+ arch_p = os.path.join(project_dir, "ARCHITECTURE.md")
744
+ if not os.path.isfile(arch_p):
745
+ with open(arch_p, "w", encoding="utf-8") as f:
746
+ f.write(WorkspaceGovernanceSynthesizer.generate_architecture_md(prof))
747
+ created.append(arch_p)
748
+ except Exception:
749
+ pass
750
+
751
+ return created
752
+
753
+
754
+ # =============================================================================
755
+ # 4. LOCAL HYBRID SEMANTIC & BM25 CODE SEARCH (100% PRIVATE & ZERO-LEAK)
756
+ # =============================================================================
757
+
758
+ class LocalSemanticCodeSearch:
759
+ """100% Local, zero-network hybrid BM25 + symbol token code search with Reciprocal Rank Fusion (RRF).
760
+
761
+ Indexes functions, classes, docstrings, and signatures into an in-memory
762
+ vector/lexical index with AST-aware chunking, allowing natural language queries
763
+ without sending code to any external embedding API.
764
+ """
765
+
766
+ def __init__(self, project_dir: str):
767
+ self.project_dir = os.path.abspath(project_dir)
768
+ self.chunks: List[Dict[str, Any]] = []
769
+ self.doc_freqs: Dict[str, int] = collections.defaultdict(int)
770
+ self.total_docs = 0
771
+ self.avg_doc_len = 0.0
772
+ self._index_codebase()
773
+
774
+ @staticmethod
775
+ def _tokenize(text: str) -> List[str]:
776
+ # Split on non-alphanumeric, camelCase, snake_case
777
+ tokens = []
778
+ words = re.findall(r'[A-Za-z0-9_]+', text.lower())
779
+ for w in words:
780
+ sub = re.findall(r'[a-z]+|[0-9]+', w)
781
+ tokens.extend(sub)
782
+ tokens.append(w)
783
+ return [t for t in tokens if len(t) > 1]
784
+
785
+ def _index_codebase(self):
786
+ total_len = 0
787
+ for root, _, files in os.walk(self.project_dir):
788
+ if should_skip_dir(root, self.project_dir):
789
+ continue
790
+ for f in files:
791
+ if f.endswith(('.py', '.js', '.ts', '.go', '.rs', '.cpp', '.c', '.h', '.html', '.css')):
792
+ full_p = os.path.join(root, f)
793
+ rel_p = os.path.relpath(full_p, self.project_dir).replace('\\', '/')
794
+ try:
795
+ with open(full_p, 'r', encoding='utf-8', errors='ignore') as fp:
796
+ content = fp.read()
797
+ except Exception:
798
+ continue
799
+
800
+ lines = content.splitlines()
801
+ if not lines:
802
+ continue
803
+
804
+ # 1. AST-Aware Chunking for Python
805
+ indexed_ast = False
806
+ if f.endswith('.py'):
807
+ try:
808
+ tree = ast.parse(content)
809
+ for node in ast.walk(tree):
810
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
811
+ start_line = node.lineno
812
+ end_line = getattr(node, "end_lineno", node.lineno) or node.lineno
813
+ doc = ast.get_docstring(node) or ""
814
+ block = "\n".join(lines[start_line - 1 : min(len(lines), end_line)])
815
+ toks = (
816
+ self._tokenize(node.name) * 3
817
+ + self._tokenize(doc) * 2
818
+ + self._tokenize(block)
819
+ )
820
+ if not toks:
821
+ continue
822
+ chunk_entry = {
823
+ "file": rel_p,
824
+ "start_line": start_line,
825
+ "end_line": end_line,
826
+ "symbol_name": node.name,
827
+ "docstring": doc,
828
+ "snippet": block[:500],
829
+ "tokens": toks,
830
+ "term_counts": collections.Counter(toks),
831
+ "length": len(toks),
832
+ "is_ast": True
833
+ }
834
+ self.chunks.append(chunk_entry)
835
+ total_len += len(toks)
836
+ for term in set(toks):
837
+ self.doc_freqs[term] += 1
838
+ indexed_ast = True
839
+ except Exception:
840
+ pass
841
+
842
+ # 2. Sliding window chunking (for non-Python or whole file coverage)
843
+ chunk_size = 25
844
+ step = 15
845
+ for i in range(0, max(1, len(lines)), step):
846
+ block = "\n".join(lines[i:i+chunk_size])
847
+ toks = self._tokenize(block)
848
+ if not toks:
849
+ continue
850
+
851
+ chunk_entry = {
852
+ "file": rel_p,
853
+ "start_line": i + 1,
854
+ "end_line": min(len(lines), i + chunk_size),
855
+ "symbol_name": "",
856
+ "docstring": "",
857
+ "snippet": block[:400],
858
+ "tokens": toks,
859
+ "term_counts": collections.Counter(toks),
860
+ "length": len(toks),
861
+ "is_ast": False
862
+ }
863
+ self.chunks.append(chunk_entry)
864
+ total_len += len(toks)
865
+
866
+ seen = set(toks)
867
+ for term in seen:
868
+ self.doc_freqs[term] += 1
869
+
870
+ self.total_docs = len(self.chunks)
871
+ self.avg_doc_len = total_len / max(1, self.total_docs)
872
+
873
+ def search(self, query: str, top_k: int = 5) -> List[Dict[str, Any]]:
874
+ """Performs hybrid BM25 + Symbol Match search with Reciprocal Rank Fusion (RRF)."""
875
+ q_tokens = self._tokenize(query)
876
+ if not q_tokens or self.total_docs == 0:
877
+ return []
878
+
879
+ k1 = 1.5
880
+ b = 0.75
881
+ bm25_scores = []
882
+ symbol_scores = []
883
+ docstring_scores = []
884
+ q_lower = query.strip().lower()
885
+
886
+ for idx, chunk in enumerate(self.chunks):
887
+ # 1. BM25 score
888
+ score = 0.0
889
+ doc_len = chunk["length"]
890
+ term_counts = chunk["term_counts"]
891
+ for q in q_tokens:
892
+ if q in term_counts:
893
+ tf = term_counts[q]
894
+ df = self.doc_freqs.get(q, 0)
895
+ idf = math.log((self.total_docs - df + 0.5) / (df + 0.5) + 1.0)
896
+ tf_norm = (tf * (k1 + 1)) / (tf + k1 * (1 - b + b * (doc_len / self.avg_doc_len)))
897
+ score += idf * tf_norm
898
+ if score > 0.0:
899
+ bm25_scores.append((score, idx))
900
+
901
+ # 2. Exact / Prefix Symbol Score
902
+ sym = chunk.get("symbol_name", "").lower()
903
+ sym_score = 0.0
904
+ if sym:
905
+ if q_lower == sym:
906
+ sym_score = 10.0
907
+ elif sym.startswith(q_lower) or q_lower in sym:
908
+ sym_score = 6.0
909
+ elif any(q in sym for q in q_tokens):
910
+ sym_score = 3.0
911
+ if sym_score > 0.0:
912
+ symbol_scores.append((sym_score, idx))
913
+
914
+ # 3. Docstring semantic match
915
+ doc = chunk.get("docstring", "").lower()
916
+ doc_score = 0.0
917
+ if doc:
918
+ overlap = sum(1 for q in q_tokens if q in doc)
919
+ if overlap > 0:
920
+ doc_score = overlap * 2.0
921
+ docstring_scores.append((doc_score, idx))
922
+
923
+ # Sort each channel
924
+ bm25_scores.sort(key=lambda x: x[0], reverse=True)
925
+ symbol_scores.sort(key=lambda x: x[0], reverse=True)
926
+ docstring_scores.sort(key=lambda x: x[0], reverse=True)
927
+
928
+ bm25_ranks = {idx: rank + 1 for rank, (_, idx) in enumerate(bm25_scores)}
929
+ symbol_ranks = {idx: rank + 1 for rank, (_, idx) in enumerate(symbol_scores)}
930
+ doc_ranks = {idx: rank + 1 for rank, (_, idx) in enumerate(docstring_scores)}
931
+
932
+ # Combine with Reciprocal Rank Fusion (RRF)
933
+ all_candidate_indices = set(bm25_ranks.keys()) | set(symbol_ranks.keys()) | set(doc_ranks.keys())
934
+ rrf_k = 60
935
+ fused_scores = []
936
+
937
+ for idx in all_candidate_indices:
938
+ rrf_score = 0.0
939
+ if idx in bm25_ranks:
940
+ rrf_score += 1.0 / (rrf_k + bm25_ranks[idx])
941
+ if idx in symbol_ranks:
942
+ rrf_score += 2.5 / (rrf_k + symbol_ranks[idx])
943
+ if idx in doc_ranks:
944
+ rrf_score += 1.5 / (rrf_k + doc_ranks[idx])
945
+
946
+ # Bonus for AST chunks
947
+ if self.chunks[idx].get("is_ast"):
948
+ rrf_score *= 1.25
949
+
950
+ fused_scores.append((rrf_score, idx))
951
+
952
+ fused_scores.sort(key=lambda x: x[0], reverse=True)
953
+
954
+ results = []
955
+ seen_ranges = set()
956
+ for s, idx in fused_scores:
957
+ c = self.chunks[idx]
958
+ range_key = (c["file"], c["start_line"], c["end_line"])
959
+ if range_key in seen_ranges:
960
+ continue
961
+ seen_ranges.add(range_key)
962
+
963
+ scaled_relevance = round(s * 100.0, 2)
964
+ results.append({
965
+ "file": c["file"],
966
+ "start_line": c["start_line"],
967
+ "end_line": c["end_line"],
968
+ "snippet": c["snippet"],
969
+ "relevance_score": scaled_relevance
970
+ })
971
+ if len(results) >= top_k:
972
+ break
973
+
974
+ return results
975
+
976
+ def format_search_results(self, query: str, results: List[Dict[str, Any]]) -> str:
977
+ out = [
978
+ f"# 🔍 Semantic Code Search: \"{query}\"\n",
979
+ f"- **Found Matches:** {len(results)} relevant code blocks\n"
980
+ ]
981
+ if not results:
982
+ out.append("*(No relevant code matches found for this query)*")
983
+ return "\n".join(out)
984
+
985
+ for i, r in enumerate(results, 1):
986
+ out.append(f"### {i}. `{r['file']}` (Lines {r['start_line']}-{r['end_line']}) — Relevance: {r['relevance_score']}")
987
+ out.append("```\n" + r["snippet"] + "\n```\n")
988
+ return "\n".join(out)
989
+
990
+
991
+ # =============================================================================
992
+ # 5. TOKEN SAVINGS & DEVELOPER ROI CALCULATOR
993
+ # =============================================================================
994
+
995
+ class TokenROICalculator:
996
+ """Calculates quantitative token reduction savings and economic ROI."""
997
+
998
+ @staticmethod
999
+ def calculate(raw_tokens: int, reduced_tokens: int, team_size: int = 5, queries_per_day: int = 30) -> Dict[str, Any]:
1000
+ saved_per_query = max(0, raw_tokens - reduced_tokens)
1001
+ reduction_pct = (saved_per_query / max(1, raw_tokens)) * 100.0
1002
+
1003
+ # Based on average blended input cost $2.50 / 1M tokens (GPT-4o / Claude 3.7 Sonnet)
1004
+ rate_per_million = 2.50
1005
+ cost_raw_per_query = (raw_tokens / 1_000_000.0) * rate_per_million
1006
+ cost_reduced_per_query = (reduced_tokens / 1_000_000.0) * rate_per_million
1007
+ savings_per_query = cost_raw_per_query - cost_reduced_per_query
1008
+
1009
+ daily_team_queries = team_size * queries_per_day
1010
+ monthly_queries = daily_team_queries * 22 # 22 working days
1011
+ monthly_savings_usd = monthly_queries * savings_per_query
1012
+ annual_savings_usd = monthly_savings_usd * 12
1013
+
1014
+ return {
1015
+ "raw_tokens": raw_tokens,
1016
+ "reduced_tokens": reduced_tokens,
1017
+ "saved_tokens_per_query": saved_per_query,
1018
+ "reduction_percentage": round(reduction_pct, 1),
1019
+ "cost_raw_query_usd": round(cost_raw_per_query, 4),
1020
+ "cost_reduced_query_usd": round(cost_reduced_per_query, 4),
1021
+ "team_size": team_size,
1022
+ "monthly_queries": monthly_queries,
1023
+ "monthly_savings_usd": round(monthly_savings_usd, 2),
1024
+ "annual_savings_usd": round(annual_savings_usd, 2)
1025
+ }
1026
+
1027
+ compute_savings = calculate
1028
+
1029
+ @classmethod
1030
+ def format_report(cls, metrics: Dict[str, Any]) -> str:
1031
+ pct = metrics["reduction_percentage"]
1032
+ m_usd = metrics["monthly_savings_usd"]
1033
+ a_usd = metrics["annual_savings_usd"]
1034
+
1035
+ return f"""# 📊 T-Zero Token Reduction & Economic ROI Report
1036
+
1037
+ > **Compression Ratio:** **{pct}% token reduction** ({metrics['raw_tokens']:,} tokens ➔ {metrics['reduced_tokens']:,} tokens)
1038
+ > **Team Model:** {metrics['team_size']} Engineers × ~{metrics['monthly_queries']:,} queries/month
1039
+ > **Estimated Financial ROI:** **${m_usd:,.2f} USD / month** (${a_usd:,.2f} USD / year)
1040
+
1041
+ | Metric | Raw Uncompressed | T-Zero Ultra Engine | Delta |
1042
+ |:-------|:-----------------|:-------------------|:------|
1043
+ | **Tokens per Query** | {metrics['raw_tokens']:,} | {metrics['reduced_tokens']:,} | **-{pct}%** |
1044
+ | **Cost per Prompt** | ${metrics['cost_raw_query_usd']:.4f} | ${metrics['cost_reduced_query_usd']:.4f} | **-${metrics['cost_raw_query_usd'] - metrics['cost_reduced_query_usd']:.4f}** |
1045
+ | **Monthly Team Cost** | ${metrics['cost_raw_query_usd'] * metrics['monthly_queries']:,.2f} | ${metrics['cost_reduced_query_usd'] * metrics['monthly_queries']:,.2f} | **-${m_usd:,.2f}** |
1046
+
1047
+ > [!TIP]
1048
+ > T-Zero AST pruning eliminates boilerplate internals while preserving exact semantic interfaces, allowing models to fit massive enterprise codebases into limited context windows.
1049
+ """
1050
+
1051
+
1052
+ # =============================================================================
1053
+ # 6. INCREMENTAL GIT DIFF & PR CONTEXT BUILDER (98%+ Token Savings)
1054
+ # =============================================================================
1055
+
1056
+ class GitDiffContextBuilder:
1057
+ """Computes incremental git diff context and downstream blast radius dependencies.
1058
+
1059
+ Extracts only modified symbol signatures + affected callers/importers,
1060
+ achieving 98%+ token savings compared to whole-repository prompt ingestion.
1061
+ """
1062
+
1063
+ def __init__(self, project_dir: str):
1064
+ self.project_dir = os.path.abspath(project_dir)
1065
+ self.impact_analyzer = ChangeImpactAnalyzer(self.project_dir)
1066
+
1067
+ def get_diff_changed_files(self, base_ref: str = "HEAD~1") -> List[Dict[str, Any]]:
1068
+ """Finds changed files using local git diff without external network calls."""
1069
+ changed = []
1070
+
1071
+ diff_output = ""
1072
+ try:
1073
+ r = subprocess.run(
1074
+ ["git", "diff", "--name-status", base_ref],
1075
+ cwd=self.project_dir,
1076
+ capture_output=True,
1077
+ text=True,
1078
+ check=False
1079
+ )
1080
+ if r.returncode == 0 and r.stdout.strip():
1081
+ diff_output = r.stdout
1082
+ else:
1083
+ r2 = subprocess.run(
1084
+ ["git", "status", "--porcelain"],
1085
+ cwd=self.project_dir,
1086
+ capture_output=True,
1087
+ text=True,
1088
+ check=False
1089
+ )
1090
+ if r2.returncode == 0:
1091
+ diff_output = r2.stdout
1092
+ except Exception:
1093
+ return []
1094
+
1095
+ for line in diff_output.splitlines():
1096
+ line = line.strip()
1097
+ if not line:
1098
+ continue
1099
+ parts = line.split()
1100
+ if len(parts) >= 2:
1101
+ status = parts[0]
1102
+ file_rel = parts[-1].replace("\\", "/")
1103
+ changed.append({
1104
+ "status": status,
1105
+ "file": file_rel
1106
+ })
1107
+ return changed
1108
+
1109
+ def build_diff_context(self, base_ref: str = "HEAD~1") -> Dict[str, Any]:
1110
+ """Builds hyper-compact diff context payload."""
1111
+ from tzero_v3 import TokenReducer
1112
+ changed_files = self.get_diff_changed_files(base_ref)
1113
+ if not changed_files:
1114
+ return {
1115
+ "base_ref": base_ref,
1116
+ "changed_files_count": 0,
1117
+ "modified_symbols_count": 0,
1118
+ "downstream_dependents_count": 0,
1119
+ "compressed_tokens": 0,
1120
+ "full_repo_tokens": 0,
1121
+ "token_savings_percent": 100.0,
1122
+ "markdown": "## No Git Changes Detected\nWorking tree is completely clean relative to base reference."
1123
+ }
1124
+
1125
+ modified_symbols = []
1126
+ downstream_dependents = set()
1127
+ file_snippets = {}
1128
+
1129
+ for item in changed_files:
1130
+ rel_p = item["file"]
1131
+ full_p = os.path.join(self.project_dir, rel_p)
1132
+ if not os.path.isfile(full_p):
1133
+ continue
1134
+
1135
+ try:
1136
+ with open(full_p, "r", encoding="utf-8", errors="ignore") as fp:
1137
+ content = fp.read()
1138
+ file_snippets[rel_p] = content
1139
+ except Exception:
1140
+ continue
1141
+
1142
+ if rel_p.endswith(".py"):
1143
+ try:
1144
+ tree = ast.parse(content)
1145
+ for node in tree.body:
1146
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
1147
+ modified_symbols.append((rel_p, node.name))
1148
+ impact = self.impact_analyzer.analyze_symbol(node.name)
1149
+ for caller in impact.get("direct_callers", []):
1150
+ if isinstance(caller, dict) and "file" in caller:
1151
+ downstream_dependents.add(caller["file"])
1152
+ elif isinstance(caller, str):
1153
+ downstream_dependents.add(caller)
1154
+ for dep in impact.get("second_level_dependents", []):
1155
+ if isinstance(dep, dict) and "file" in dep:
1156
+ downstream_dependents.add(dep["file"])
1157
+ elif isinstance(dep, str):
1158
+ downstream_dependents.add(dep)
1159
+ for dep in impact.get("downstream_dependents", []):
1160
+ if isinstance(dep, dict) and "file" in dep:
1161
+ downstream_dependents.add(dep["file"])
1162
+ elif isinstance(dep, str):
1163
+ downstream_dependents.add(dep)
1164
+ except Exception:
1165
+ pass
1166
+
1167
+ full_repo_tokens = sum(len(v) // 4 for v in self.impact_analyzer.snippets.values())
1168
+ reduced_snippets = {
1169
+ f: TokenReducer.reduce(c, f, mode="ultra") for f, c in file_snippets.items()
1170
+ }
1171
+ compressed_diff_tokens = sum(len(v) // 4 for v in reduced_snippets.values())
1172
+
1173
+ savings_pct = 0.0
1174
+ if full_repo_tokens > 0:
1175
+ savings_pct = round(max(0.0, 100.0 - (compressed_diff_tokens / full_repo_tokens * 100.0)), 1)
1176
+
1177
+ md = [
1178
+ f"# 🚀 T-Zero Incremental PR Context (Diff Mode)",
1179
+ f"",
1180
+ f"> **Compression Ratio:** **{savings_pct}% reduction** ({compressed_diff_tokens:,} tokens vs {full_repo_tokens:,} full repo tokens)",
1181
+ f"> **Base Ref:** `{base_ref}` | **Modified Files:** {len(file_snippets)} | **Downstream Impacted Files:** {len(downstream_dependents)}",
1182
+ f"",
1183
+ f"## 📝 1. Modified Modules & Changed Signatures",
1184
+ ]
1185
+
1186
+ for rel_p, reduced_code in reduced_snippets.items():
1187
+ ext = os.path.splitext(rel_p)[1].lstrip(".") or "txt"
1188
+ md.append(f"### `{rel_p}`")
1189
+ md.append(f"```{ext}")
1190
+ md.append(reduced_code[:3000] if len(reduced_code) > 3000 else reduced_code)
1191
+ md.append("```")
1192
+ md.append("")
1193
+
1194
+ if downstream_dependents:
1195
+ md.append("## 🎯 2. Downstream Blast Radius Dependents")
1196
+ for dep_file in sorted(downstream_dependents):
1197
+ md.append(f"- `{dep_file}` (Depends on modified symbols)")
1198
+ md.append("")
1199
+
1200
+ md_str = "\n".join(md)
1201
+ return {
1202
+ "base_ref": base_ref,
1203
+ "changed_files_count": len(file_snippets),
1204
+ "modified_symbols_count": len(modified_symbols),
1205
+ "downstream_dependents_count": len(downstream_dependents),
1206
+ "compressed_tokens": compressed_diff_tokens,
1207
+ "full_repo_tokens": full_repo_tokens,
1208
+ "token_savings_percent": savings_pct,
1209
+ "markdown": md_str
1210
+ }
1211
+
1212
+
1213
+ # =============================================================================
1214
+ # 7. LOCAL CYBERPUNK WEB DASHBOARD (Zero-Dependency Localhost Server)
1215
+ # =============================================================================
1216
+
1217
+ HTML_DASHBOARD_TEMPLATE = """<!DOCTYPE html>
1218
+ <html lang="en">
1219
+ <head>
1220
+ <meta charset="utf-8"/>
1221
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
1222
+ <title>Siber Akademi — T-Zero Context Engine Webview</title>
1223
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&family=Outfit:wght@400;600;800&display=swap" rel="stylesheet"/>
1224
+ <style>
1225
+ :root {
1226
+ --bg-dark: #0a0b10;
1227
+ --bg-card: rgba(18, 20, 29, 0.85);
1228
+ --border: rgba(0, 255, 216, 0.2);
1229
+ --accent: #00ffd8;
1230
+ --accent-glow: rgba(0, 255, 216, 0.35);
1231
+ --text: #e2e8f0;
1232
+ --text-dim: #94a3b8;
1233
+ --neon-pink: #ff007f;
1234
+ --neon-green: #00ff41;
1235
+ }
1236
+ * { box-sizing: border-box; margin: 0; padding: 0; }
1237
+ body {
1238
+ background: var(--bg-dark);
1239
+ color: var(--text);
1240
+ font-family: 'Outfit', sans-serif;
1241
+ min-height: 100vh;
1242
+ display: flex;
1243
+ flex-direction: column;
1244
+ }
1245
+ header {
1246
+ background: rgba(10, 11, 16, 0.95);
1247
+ backdrop-filter: blur(12px);
1248
+ border-bottom: 1px solid var(--border);
1249
+ padding: 16px 32px;
1250
+ display: flex;
1251
+ justify-content: space-between;
1252
+ align-items: center;
1253
+ position: sticky;
1254
+ top: 0;
1255
+ z-index: 100;
1256
+ }
1257
+ .brand { display: flex; align-items: center; gap: 12px; font-weight: 800; font-size: 1.25rem; letter-spacing: 1px; }
1258
+ .brand span { color: var(--accent); }
1259
+ nav { display: flex; gap: 8px; }
1260
+ button.tab-btn {
1261
+ background: transparent;
1262
+ border: 1px solid transparent;
1263
+ color: var(--text-dim);
1264
+ padding: 8px 16px;
1265
+ border-radius: 8px;
1266
+ cursor: pointer;
1267
+ font-family: 'Outfit', sans-serif;
1268
+ font-weight: 600;
1269
+ transition: all 0.2s ease;
1270
+ }
1271
+ button.tab-btn.active, button.tab-btn:hover {
1272
+ background: rgba(0, 255, 216, 0.1);
1273
+ border-color: var(--border);
1274
+ color: var(--accent);
1275
+ }
1276
+ main { flex: 1; padding: 32px; max-width: 1400px; margin: 0 auto; width: 100%; }
1277
+ .card {
1278
+ background: var(--bg-card);
1279
+ border: 1px solid var(--border);
1280
+ border-radius: 12px;
1281
+ padding: 24px;
1282
+ margin-bottom: 24px;
1283
+ backdrop-filter: blur(8px);
1284
+ }
1285
+ h2 { font-size: 1.5rem; margin-bottom: 16px; color: var(--accent); display: flex; align-items: center; gap: 8px; }
1286
+ pre, code { font-family: 'JetBrains Mono', monospace; }
1287
+ pre {
1288
+ background: #050608;
1289
+ padding: 16px;
1290
+ border-radius: 8px;
1291
+ border: 1px solid rgba(255,255,255,0.05);
1292
+ overflow-x: auto;
1293
+ font-size: 0.9rem;
1294
+ line-height: 1.5;
1295
+ }
1296
+ .search-box {
1297
+ display: flex;
1298
+ gap: 12px;
1299
+ margin-bottom: 20px;
1300
+ }
1301
+ input[type="text"] {
1302
+ flex: 1;
1303
+ background: #050608;
1304
+ border: 1px solid var(--border);
1305
+ padding: 12px 16px;
1306
+ border-radius: 8px;
1307
+ color: #fff;
1308
+ font-family: 'JetBrains Mono', monospace;
1309
+ }
1310
+ .btn-action {
1311
+ background: var(--accent);
1312
+ color: #000;
1313
+ border: none;
1314
+ padding: 12px 24px;
1315
+ border-radius: 8px;
1316
+ font-weight: 700;
1317
+ cursor: pointer;
1318
+ transition: 0.2s;
1319
+ }
1320
+ .btn-action:hover { background: #50fa7b; }
1321
+ .metric-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; margin-bottom: 24px; }
1322
+ .metric-card {
1323
+ background: rgba(255,255,255,0.02);
1324
+ border: 1px solid rgba(255,255,255,0.08);
1325
+ border-radius: 8px;
1326
+ padding: 16px;
1327
+ text-align: center;
1328
+ }
1329
+ .metric-val { font-size: 2rem; font-weight: 800; color: var(--accent); margin-top: 4px; }
1330
+ .tab-content { display: none; }
1331
+ .tab-content.active { display: block; }
1332
+ </style>
1333
+ </head>
1334
+ <body>
1335
+ <header>
1336
+ <div class="brand">⚡ <span>T-ZERO</span> CONTEXT ARCHITECT V3</div>
1337
+ <nav>
1338
+ <button class="tab-btn active" onclick="switchTab('overview')">🏛️ Overview</button>
1339
+ <button class="tab-btn" onclick="switchTab('search')">🔍 RAG Search</button>
1340
+ <button class="tab-btn" onclick="switchTab('impact')">💥 Blast Radius</button>
1341
+ <button class="tab-btn" onclick="switchTab('boundaries')">🛡️ Boundaries</button>
1342
+ <button class="tab-btn" onclick="switchTab('rules')">🤖 Agent Rules</button>
1343
+ </nav>
1344
+ </header>
1345
+ <main>
1346
+ <!-- OVERVIEW TAB -->
1347
+ <div id="tab-overview" class="tab-content active">
1348
+ <div class="metric-grid">
1349
+ <div class="metric-card"><div>Analyzed Modules</div><div class="metric-val" id="m-files">--</div></div>
1350
+ <div class="metric-card"><div>Token Reduction</div><div class="metric-val" style="color:var(--neon-green)">94.6%</div></div>
1351
+ <div class="metric-card"><div>Zero-Leak Security</div><div class="metric-val" style="color:var(--neon-pink)">100%</div></div>
1352
+ <div class="metric-card"><div>MCP Status</div><div class="metric-val">READY</div></div>
1353
+ </div>
1354
+ <div class="card">
1355
+ <h2>🏛️ Architectural Topology Tree</h2>
1356
+ <pre id="tree-box">Loading workspace context tree...</pre>
1357
+ </div>
1358
+ </div>
1359
+
1360
+ <!-- SEARCH TAB -->
1361
+ <div id="tab-search" class="tab-content">
1362
+ <div class="card">
1363
+ <h2>🔍 Local Hybrid Semantic Code Search (Zero-Leak)</h2>
1364
+ <p style="color:var(--text-dim); margin-bottom: 16px;">Query code semantically without uploading source code to third-party embeddings APIs.</p>
1365
+ <div class="search-box">
1366
+ <input type="text" id="search-input" placeholder="e.g. authentication jwt token, database pool, keyring encryption..."/>
1367
+ <button class="btn-action" onclick="runSearch()">Search</button>
1368
+ </div>
1369
+ <div id="search-results"></div>
1370
+ </div>
1371
+ </div>
1372
+
1373
+ <!-- BLAST RADIUS TAB -->
1374
+ <div id="tab-impact" class="tab-content">
1375
+ <div class="card">
1376
+ <h2>💥 Change Impact & Blast Radius Analyzer</h2>
1377
+ <p style="color:var(--text-dim); margin-bottom: 16px;">Inspect downstream dependents and caller cascades before refactoring functions or classes.</p>
1378
+ <div class="search-box">
1379
+ <input type="text" id="symbol-input" placeholder="e.g. generate_offline_context, TokenReducer, CodebaseScanner..."/>
1380
+ <button class="btn-action" onclick="runImpact()">Analyze Impact</button>
1381
+ </div>
1382
+ <pre id="impact-results">Enter a symbol name above and click 'Analyze Impact'.</pre>
1383
+ </div>
1384
+ </div>
1385
+
1386
+ <!-- BOUNDARIES TAB -->
1387
+ <div id="tab-boundaries" class="tab-content">
1388
+ <div class="card">
1389
+ <h2>🛡️ Architecture Boundary & Layer Enforcer (T-4 Rules)</h2>
1390
+ <p style="color:var(--text-dim); margin-bottom: 16px;">Validates modular separation and prevents unauthorized cross-layer imports.</p>
1391
+ <button class="btn-action" onclick="runBoundaries()">Verify Boundaries</button>
1392
+ <pre id="boundaries-results" style="margin-top:16px;">Click 'Verify Boundaries' to run AST import validation.</pre>
1393
+ </div>
1394
+ </div>
1395
+
1396
+ <!-- RULES TAB -->
1397
+ <div id="tab-rules" class="tab-content">
1398
+ <div class="card">
1399
+ <h2>🤖 Export AI Agent Rules (.cursorrules, .clinerules, Copilot)</h2>
1400
+ <p style="color:var(--text-dim); margin-bottom: 16px;">Generate native configuration files for Cursor, Claude, Antigravity IDE, and Cline.</p>
1401
+ <button class="btn-action" onclick="exportRules()">Generate All Rules to Workspace</button>
1402
+ <pre id="rules-output" style="margin-top: 16px;">Click button to generate rules.</pre>
1403
+ </div>
1404
+ </div>
1405
+ </main>
1406
+ <script>
1407
+ function switchTab(tabId) {
1408
+ document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
1409
+ document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
1410
+ event.target.classList.add('active');
1411
+ document.getElementById('tab-' + tabId).classList.add('active');
1412
+ }
1413
+
1414
+ fetch('/api/overview').then(r => r.json()).then(data => {
1415
+ document.getElementById('m-files').innerText = data.files_count;
1416
+ document.getElementById('tree-box').innerText = data.tree;
1417
+ }).catch(e => console.error(e));
1418
+
1419
+ function runSearch() {
1420
+ const q = document.getElementById('search-input').value;
1421
+ if (!q) return;
1422
+ fetch('/api/search?q=' + encodeURIComponent(q))
1423
+ .then(r => r.json())
1424
+ .then(data => {
1425
+ let html = '';
1426
+ if (!data.length) html = '<p>No matches found.</p>';
1427
+ data.forEach(m => {
1428
+ html += `<div style="margin-bottom:16px; border-left: 2px solid var(--accent); padding-left:12px;">
1429
+ <strong>${m.file}</strong> (Lines ${m.start_line}-${m.end_line}) — Score: ${m.relevance_score}
1430
+ <pre style="margin-top:6px;">${m.snippet}</pre>
1431
+ </div>`;
1432
+ });
1433
+ document.getElementById('search-results').innerHTML = html;
1434
+ });
1435
+ }
1436
+
1437
+ function runImpact() {
1438
+ const sym = document.getElementById('symbol-input').value;
1439
+ if (!sym) return;
1440
+ fetch('/api/impact?sym=' + encodeURIComponent(sym))
1441
+ .then(r => r.json())
1442
+ .then(data => {
1443
+ document.getElementById('impact-results').innerText = data.report;
1444
+ });
1445
+ }
1446
+
1447
+ function runBoundaries() {
1448
+ fetch('/api/boundaries')
1449
+ .then(r => r.json())
1450
+ .then(data => {
1451
+ document.getElementById('boundaries-results').innerText = data.report;
1452
+ });
1453
+ }
1454
+
1455
+ function exportRules() {
1456
+ fetch('/api/export-rules')
1457
+ .then(r => r.json())
1458
+ .then(data => {
1459
+ document.getElementById('rules-output').innerText = "Exported files:\n" + data.files.join("\n");
1460
+ });
1461
+ }
1462
+ </script>
1463
+ </body>
1464
+ </html>
1465
+ """
1466
+
1467
+
1468
+ class DashboardRequestHandler(http.server.BaseHTTPRequestHandler):
1469
+ """Zero-dependency secure HTTP handler serving the T-Zero Web Dashboard and REST APIs."""
1470
+
1471
+ project_dir = "."
1472
+ _search_engine: Optional[LocalSemanticCodeSearch] = None
1473
+ _impact_analyzer: Optional[ChangeImpactAnalyzer] = None
1474
+ _rule_engine: Optional[ArchitectureRuleEngine] = None
1475
+ _last_mtime: float = 0.0
1476
+
1477
+ @classmethod
1478
+ def _get_project_mtime(cls) -> float:
1479
+ """Finds latest modification timestamp across files to detect stale cache."""
1480
+ latest = 0.0
1481
+ for root, _, files in os.walk(cls.project_dir):
1482
+ if should_skip_dir(root, cls.project_dir):
1483
+ continue
1484
+ for f in files:
1485
+ if f.endswith(('.py', '.json', '.js', '.ts', '.go', '.rs')):
1486
+ try:
1487
+ m = os.path.getmtime(os.path.join(root, f))
1488
+ if m > latest:
1489
+ latest = m
1490
+ except OSError:
1491
+ pass
1492
+ return latest
1493
+
1494
+ @classmethod
1495
+ def get_engines(cls) -> Tuple[LocalSemanticCodeSearch, ChangeImpactAnalyzer, ArchitectureRuleEngine]:
1496
+ """Returns initialized analysis engines, auto-refreshing cache if files changed."""
1497
+ current_m = cls._get_project_mtime()
1498
+ if (
1499
+ cls._search_engine is None
1500
+ or cls._impact_analyzer is None
1501
+ or cls._rule_engine is None
1502
+ or current_m > cls._last_mtime
1503
+ ):
1504
+ cls._search_engine = LocalSemanticCodeSearch(cls.project_dir)
1505
+ cls._impact_analyzer = ChangeImpactAnalyzer(cls.project_dir)
1506
+ cls._rule_engine = ArchitectureRuleEngine(cls.project_dir)
1507
+ cls._last_mtime = current_m
1508
+ return cls._search_engine, cls._impact_analyzer, cls._rule_engine
1509
+
1510
+ def log_message(self, format, *args):
1511
+ pass # Quiet logging
1512
+
1513
+ def _send_security_headers(self):
1514
+ self.send_header("Access-Control-Allow-Origin", "*")
1515
+ self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
1516
+ self.send_header("Access-Control-Allow-Headers", "Content-Type, Authorization")
1517
+ self.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
1518
+ self.send_header("Pragma", "no-cache")
1519
+ self.send_header("Expires", "0")
1520
+ self.send_header("X-Content-Type-Options", "nosniff")
1521
+
1522
+ def do_OPTIONS(self):
1523
+ self.send_response(204)
1524
+ self._send_security_headers()
1525
+ self.end_headers()
1526
+
1527
+ def do_GET(self):
1528
+ parsed = urllib.parse.urlparse(self.path)
1529
+ path = parsed.path
1530
+ query = urllib.parse.parse_qs(parsed.query)
1531
+
1532
+ if path in ("/", "/index.html"):
1533
+ payload = HTML_DASHBOARD_TEMPLATE.encode("utf-8")
1534
+ self.send_response(200)
1535
+ self.send_header("Content-Type", "text/html; charset=utf-8")
1536
+ self.send_header("Content-Length", str(len(payload)))
1537
+ self._send_security_headers()
1538
+ self.end_headers()
1539
+ self.wfile.write(payload)
1540
+ return
1541
+
1542
+ search_engine, impact_analyzer, rule_engine = self.get_engines()
1543
+
1544
+ if path == "/api/overview":
1545
+ from tzero_v3 import CodebaseScanner
1546
+ scanner = CodebaseScanner()
1547
+ files, sizes, snippets = scanner.scan_directory(self.project_dir)
1548
+ tree_ascii = "\n".join(f"├── {f}" for f in files[:40])
1549
+ if len(files) > 40:
1550
+ tree_ascii += f"\n... and {len(files) - 40} more files"
1551
+ res = {"files_count": len(files), "tree": tree_ascii}
1552
+ self._send_json(res)
1553
+ return
1554
+
1555
+ if path == "/api/search":
1556
+ q = query.get("q", [""])[0]
1557
+ matches = search_engine.search(q, top_k=6)
1558
+ self._send_json(matches)
1559
+ return
1560
+
1561
+ if path == "/api/impact":
1562
+ sym = query.get("sym", [""])[0]
1563
+ analysis = impact_analyzer.analyze_symbol(sym)
1564
+ report = impact_analyzer.format_report(analysis)
1565
+ self._send_json({"analysis": analysis, "report": report})
1566
+ return
1567
+
1568
+ if path == "/api/boundaries":
1569
+ res = rule_engine.enforce_boundaries()
1570
+ # Clean report without ANSI colors for HTML
1571
+ if res["clean"]:
1572
+ rep = f"PASSED: All {res['scanned_files']} files conform to boundary rules. Zero violations."
1573
+ else:
1574
+ rep = f"VIOLATIONS DETECTED ({res['total_violations']}):\n"
1575
+ for v in res["violations"]:
1576
+ rep += f"- {v['file']}:{v['line']} -> Layer '{v['layer']}' cannot import '{v['forbidden_import']}' ({v['reason']})\n"
1577
+ self._send_json({"result": res, "report": rep})
1578
+ return
1579
+
1580
+ if path == "/api/export-rules":
1581
+ files = AgentRulesGenerator.export_all(self.project_dir)
1582
+ self._send_json({"success": True, "files": [os.path.relpath(f, self.project_dir) for f in files]})
1583
+ return
1584
+
1585
+ self.send_error(404, "Endpoint not found")
1586
+
1587
+ def _send_json(self, data: Any):
1588
+ payload = json.dumps(data).encode("utf-8")
1589
+ self.send_response(200)
1590
+ self.send_header("Content-Type", "application/json; charset=utf-8")
1591
+ self.send_header("Content-Length", str(len(payload)))
1592
+ self._send_security_headers()
1593
+ self.end_headers()
1594
+ self.wfile.write(payload)
1595
+
1596
+
1597
+ def launch_web_dashboard(project_dir: str = ".", port: int = 7300, open_browser: bool = True):
1598
+ """Starts the zero-dependency local web dashboard server."""
1599
+ import webbrowser
1600
+ DashboardRequestHandler.project_dir = os.path.abspath(project_dir)
1601
+ server_address = ("127.0.0.1", port)
1602
+
1603
+ try:
1604
+ httpd = socketserver.TCPServer(server_address, DashboardRequestHandler)
1605
+ except OSError:
1606
+ port += 1
1607
+ server_address = ("127.0.0.1", port)
1608
+ httpd = socketserver.TCPServer(server_address, DashboardRequestHandler)
1609
+
1610
+ url = f"http://127.0.0.1:{port}"
1611
+ print(f"\n\033[1m\033[36m=== SİBER AKADEMİ T-ZERO WEB DASHBOARD ===\033[0m")
1612
+ print(f"🚀 Running locally at: \033[32m{url}\033[0m")
1613
+ print(f"📁 Workspace Target: {os.path.abspath(project_dir)}")
1614
+ print("Press Ctrl+C to terminate the web server.\n")
1615
+
1616
+ if open_browser:
1617
+ webbrowser.open(url)
1618
+
1619
+ try:
1620
+ httpd.serve_forever()
1621
+ except KeyboardInterrupt:
1622
+ print("\n[INFO] Web dashboard shut down.")
1623
+ httpd.server_close()
1624
+
1625
+
1626
+ # =============================================================================
1627
+ # 7. ZERO-HALLUCINATION GUARD & PRE-FLIGHT SYMBOL / CALL VALIDATOR (TOOL 17)
1628
+ # =============================================================================
1629
+
1630
+ class _SelfHealingTransformer(ast.NodeTransformer):
1631
+ """AST transformer that automatically corrects hallucinated imports, function names, and kwargs."""
1632
+ def __init__(self, fixes: Dict[str, Any]):
1633
+ self.import_fixes = fixes.get("imports", {}) # (module, old_sym) -> new_sym
1634
+ self.call_fixes = fixes.get("calls", {}) # old_func_name -> new_func_name
1635
+ self.kwarg_fixes = fixes.get("kwargs", {}) # (func_name, old_kw) -> new_kw
1636
+
1637
+ def visit_ImportFrom(self, node: ast.ImportFrom) -> ast.AST:
1638
+ if node.module:
1639
+ for alias in node.names:
1640
+ key = (node.module, alias.name)
1641
+ if key in self.import_fixes:
1642
+ alias.name = self.import_fixes[key]
1643
+ return self.generic_visit(node)
1644
+
1645
+ def visit_Name(self, node: ast.Name) -> ast.AST:
1646
+ if node.id in self.call_fixes:
1647
+ node.id = self.call_fixes[node.id]
1648
+ return self.generic_visit(node)
1649
+
1650
+ def visit_Call(self, node: ast.Call) -> ast.AST:
1651
+ fname = ""
1652
+ full_name = ""
1653
+ if isinstance(node.func, ast.Name):
1654
+ fname = node.func.id
1655
+ if fname in self.call_fixes:
1656
+ node.func.id = self.call_fixes[fname]
1657
+ fname = node.func.id
1658
+ full_name = fname
1659
+ elif isinstance(node.func, ast.Attribute):
1660
+ attr = node.func.attr
1661
+ if attr in self.call_fixes:
1662
+ node.func.attr = self.call_fixes[attr]
1663
+ attr = node.func.attr
1664
+ fname = attr
1665
+ try:
1666
+ full_name = f"{ast.unparse(node.func.value)}.{attr}"
1667
+ except Exception:
1668
+ full_name = attr
1669
+
1670
+ for kw in node.keywords:
1671
+ if kw.arg:
1672
+ if (full_name, kw.arg) in self.kwarg_fixes:
1673
+ kw.arg = self.kwarg_fixes[(full_name, kw.arg)]
1674
+ elif (fname, kw.arg) in self.kwarg_fixes:
1675
+ kw.arg = self.kwarg_fixes[(fname, kw.arg)]
1676
+ elif ("", kw.arg) in self.kwarg_fixes:
1677
+ kw.arg = self.kwarg_fixes[("", kw.arg)]
1678
+ return self.generic_visit(node)
1679
+
1680
+
1681
+ class ZeroHallucinationGuard:
1682
+ """Pre-flight validator that eliminates AI coding hallucinations before code is applied.
1683
+
1684
+ 5-Tier Zero-Hallucination Architecture:
1685
+ 1. Ghost Dependency Guard: Blocks imports of non-existent packages (validates against stdlib, requirements.txt, pyproject.toml, and installed packages).
1686
+ 2. AST Symbol & Module Guard: Verifies imports against workspace modules and declared __all__ / exported symbols.
1687
+ 3. AST Call & Keyword Argument Guard: Validates positional counts and parameter names against known function signatures.
1688
+ 4. Schema & Field Contract Guard: Validates class instantiations, dataclasses, and attribute accesses against known fields.
1689
+ 5. Return Type & Chaining Guard: Detects invalid chaining or iterations on functions returning None or primitive types.
1690
+ + Self-Healing AST Patcher: Automatically synthesizes corrected, hallucination-free code via AST node transformation.
1691
+ + Grounding Contract Generator: Emits immutable Type & Symbol boundary contracts for prompt pinning.
1692
+ """
1693
+
1694
+ def __init__(self, project_dir: str):
1695
+ self.project_dir = os.path.abspath(project_dir)
1696
+ self.modules: Dict[str, str] = {} # mod_name -> rel_path
1697
+ self.exported_symbols: Dict[str, Set[str]] = collections.defaultdict(set)
1698
+ self.function_signatures: Dict[str, Dict[str, Any]] = {}
1699
+ self.class_fields: Dict[str, Set[str]] = collections.defaultdict(set)
1700
+ self.class_methods: Dict[str, Set[str]] = collections.defaultdict(set)
1701
+ self.declared_packages: Set[str] = set()
1702
+ self.installed_packages: Set[str] = set()
1703
+ self.stdlib_modules: Set[str] = set()
1704
+ self.known_package_aliases: Dict[str, str] = {}
1705
+ self._load_installed_and_stdlib()
1706
+ self._load_dependency_manifests()
1707
+ self._build_catalog()
1708
+
1709
+ def _load_installed_and_stdlib(self):
1710
+ """Loads Python standard library modules, installed environment packages, and alias mappings."""
1711
+ # 1. Standard Library Modules
1712
+ self.stdlib_modules = set(getattr(sys, "stdlib_module_names", sys.builtin_module_names))
1713
+ self.stdlib_modules.update({
1714
+ "typing", "typing_extensions", "__future__", "dataclasses", "unittest",
1715
+ "test", "abc", "contextlib", "collections", "functools", "itertools",
1716
+ "os", "sys", "re", "json", "ast", "math", "time", "datetime", "pathlib",
1717
+ "shutil", "tempfile", "subprocess", "socket", "http", "urllib", "hashlib",
1718
+ "logging", "threading", "asyncio", "multiprocessing", "queue", "signal",
1719
+ "copy", "inspect", "difflib", "fnmatch", "glob", "io", "pickle", "sqlite3",
1720
+ "csv", "xml", "email", "html", "base64", "binascii", "struct", "random",
1721
+ "warnings", "weakref", "zoneinfo", "uuid", "decimal", "enum", "numbers"
1722
+ })
1723
+
1724
+ # 2. Installed packages in the active environment
1725
+ try:
1726
+ import importlib.metadata as im
1727
+ for dist in im.distributions():
1728
+ dname = dist.metadata.get("Name", "")
1729
+ if dname:
1730
+ norm = dname.lower().replace("-", "_")
1731
+ self.installed_packages.add(norm)
1732
+ self.installed_packages.add(dname.lower())
1733
+ try:
1734
+ top_level = dist.read_text("top_level.txt")
1735
+ if top_level:
1736
+ for tl in top_level.splitlines():
1737
+ tl = tl.strip()
1738
+ if tl:
1739
+ self.installed_packages.add(tl.lower())
1740
+ except Exception:
1741
+ pass
1742
+ except Exception:
1743
+ pass
1744
+
1745
+ # 3. Canonical import aliases for top third-party packages
1746
+ self.known_package_aliases = {
1747
+ "yaml": "pyyaml",
1748
+ "cv2": "opencv-python",
1749
+ "pil": "pillow",
1750
+ "sklearn": "scikit-learn",
1751
+ "dateutil": "python-dateutil",
1752
+ "dotenv": "python-dotenv",
1753
+ "jwt": "pyjwt",
1754
+ "bs4": "beautifulsoup4",
1755
+ "magic": "python-magic",
1756
+ "multipart": "python-multipart",
1757
+ "git": "gitpython",
1758
+ "serial": "pyserial",
1759
+ "mcp": "mcp",
1760
+ "fastapi": "fastapi",
1761
+ "pydantic": "pydantic",
1762
+ "uvicorn": "uvicorn",
1763
+ "click": "click",
1764
+ "pytest": "pytest",
1765
+ "flask": "flask",
1766
+ "django": "django",
1767
+ "sqlalchemy": "sqlalchemy",
1768
+ "httpx": "httpx",
1769
+ "aiohttp": "aiohttp",
1770
+ "requests": "requests",
1771
+ }
1772
+
1773
+ def _load_dependency_manifests(self):
1774
+ """Scans project directory for requirements.txt, pyproject.toml, and setup.py to find declared dependencies."""
1775
+ for root, _, files in os.walk(self.project_dir):
1776
+ if should_skip_dir(root, self.project_dir):
1777
+ continue
1778
+ for f in files:
1779
+ f_lower = f.lower()
1780
+ full_p = os.path.join(root, f)
1781
+ if f_lower.startswith("requirements") and f_lower.endswith(".txt"):
1782
+ try:
1783
+ with open(full_p, "r", encoding="utf-8", errors="ignore") as fp:
1784
+ for line in fp:
1785
+ line = line.strip()
1786
+ if line and not line.startswith(("#", "-", "@")):
1787
+ pkg = re.split(r'[><=~!;\s\[]', line)[0].strip()
1788
+ if pkg:
1789
+ self.declared_packages.add(pkg.lower().replace("-", "_"))
1790
+ self.declared_packages.add(pkg.lower())
1791
+ except Exception:
1792
+ pass
1793
+ elif f_lower == "pyproject.toml":
1794
+ try:
1795
+ with open(full_p, "r", encoding="utf-8", errors="ignore") as fp:
1796
+ in_deps = False
1797
+ for line in fp:
1798
+ line = line.strip()
1799
+ if line.startswith("[") and ("dependencies" in line.lower() or "requires" in line.lower()):
1800
+ in_deps = True
1801
+ continue
1802
+ elif line.startswith("["):
1803
+ in_deps = False
1804
+ if in_deps and line:
1805
+ m = re.match(r'^["\']?([a-zA-Z0-9_\-]+)', line)
1806
+ if m:
1807
+ pkg = m.group(1).lower().replace("-", "_")
1808
+ self.declared_packages.add(pkg)
1809
+ except Exception:
1810
+ pass
1811
+
1812
+ def _build_catalog(self):
1813
+ for root, _, files in os.walk(self.project_dir):
1814
+ if should_skip_dir(root, self.project_dir):
1815
+ continue
1816
+ for f in files:
1817
+ if f.endswith('.py'):
1818
+ full_p = os.path.join(root, f)
1819
+ rel_p = os.path.relpath(full_p, self.project_dir).replace('\\', '/')
1820
+ mod_path = os.path.splitext(rel_p)[0].replace('/', '.')
1821
+ stem = os.path.splitext(f)[0]
1822
+ self.modules[mod_path] = rel_p
1823
+ self.modules[stem] = rel_p
1824
+
1825
+ try:
1826
+ with open(full_p, 'r', encoding='utf-8', errors='ignore') as fp:
1827
+ content = fp.read()
1828
+ tree = ast.parse(content)
1829
+ self._index_module_ast(mod_path, stem, tree)
1830
+ except Exception:
1831
+ pass
1832
+
1833
+ def _index_module_ast(self, mod_path: str, stem: str, tree: ast.AST):
1834
+ all_symbols: Set[str] = set()
1835
+ for node in tree.body:
1836
+ if isinstance(node, ast.Assign):
1837
+ for target in node.targets:
1838
+ if isinstance(target, ast.Name) and target.id == "__all__":
1839
+ if isinstance(node.value, (ast.List, ast.Tuple)):
1840
+ for elt in node.value.elts:
1841
+ if isinstance(elt, ast.Constant) and isinstance(elt.value, str):
1842
+ all_symbols.add(elt.value)
1843
+ elif isinstance(target, ast.Name):
1844
+ all_symbols.add(target.id)
1845
+ elif isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
1846
+ all_symbols.add(node.target.id)
1847
+ elif isinstance(node, ast.ClassDef):
1848
+ all_symbols.add(node.name)
1849
+ fields: Set[str] = set()
1850
+ methods: Set[str] = set()
1851
+ for item in node.body:
1852
+ if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
1853
+ fields.add(item.target.id)
1854
+ elif isinstance(item, ast.Assign):
1855
+ for t in item.targets:
1856
+ if isinstance(t, ast.Name):
1857
+ fields.add(t.id)
1858
+ elif isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
1859
+ methods.add(item.name)
1860
+ sig = self._extract_func_sig(item)
1861
+ self.function_signatures[f"{node.name}.{item.name}"] = sig
1862
+ if item.name not in self.function_signatures:
1863
+ self.function_signatures[item.name] = sig
1864
+ if item.name == "__init__":
1865
+ for sub in ast.walk(item):
1866
+ if isinstance(sub, ast.Assign):
1867
+ for t in sub.targets:
1868
+ if isinstance(t, ast.Attribute) and isinstance(t.value, ast.Name) and t.value.id == "self":
1869
+ fields.add(t.attr)
1870
+ elif isinstance(sub, ast.AnnAssign):
1871
+ if isinstance(sub.target, ast.Attribute) and isinstance(sub.target.value, ast.Name) and sub.target.value.id == "self":
1872
+ fields.add(sub.target.attr)
1873
+ self.class_fields[node.name] = fields
1874
+ self.class_fields[f"{stem}.{node.name}"] = fields
1875
+ self.class_methods[node.name] = methods
1876
+ self.class_methods[f"{stem}.{node.name}"] = methods
1877
+
1878
+ elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
1879
+ all_symbols.add(node.name)
1880
+ sig = self._extract_func_sig(node)
1881
+ self.function_signatures[node.name] = sig
1882
+
1883
+ self.exported_symbols[mod_path].update(all_symbols)
1884
+ self.exported_symbols[stem].update(all_symbols)
1885
+
1886
+ def _extract_func_sig(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> Dict[str, Any]:
1887
+ args = node.args
1888
+ posonly = [a.arg for a in args.posonlyargs]
1889
+ standard_args = [a.arg for a in args.args]
1890
+ all_pos = posonly + standard_args
1891
+ num_defaults = len(args.defaults)
1892
+ min_pos = len(all_pos) - num_defaults
1893
+ kwonly = [a.arg for a in args.kwonlyargs]
1894
+ all_params = set(all_pos + kwonly)
1895
+
1896
+ arg_strs = []
1897
+ defaults_offset = len(all_pos) - num_defaults
1898
+ for i, a in enumerate(args.posonlyargs):
1899
+ s = a.arg
1900
+ if a.annotation:
1901
+ s += f": {ast.unparse(a.annotation)}"
1902
+ if i >= defaults_offset:
1903
+ s += f" = {ast.unparse(args.defaults[i - defaults_offset])}"
1904
+ arg_strs.append(s)
1905
+ if posonly:
1906
+ arg_strs.append("/")
1907
+ for i, a in enumerate(args.args):
1908
+ idx = len(posonly) + i
1909
+ s = a.arg
1910
+ if a.annotation:
1911
+ s += f": {ast.unparse(a.annotation)}"
1912
+ if idx >= defaults_offset:
1913
+ s += f" = {ast.unparse(args.defaults[idx - defaults_offset])}"
1914
+ arg_strs.append(s)
1915
+ if args.vararg:
1916
+ v_s = f"*{args.vararg.arg}"
1917
+ if args.vararg.annotation:
1918
+ v_s += f": {ast.unparse(args.vararg.annotation)}"
1919
+ arg_strs.append(v_s)
1920
+ elif kwonly:
1921
+ arg_strs.append("*")
1922
+ for i, a in enumerate(args.kwonlyargs):
1923
+ s = a.arg
1924
+ if a.annotation:
1925
+ s += f": {ast.unparse(a.annotation)}"
1926
+ default = args.kw_defaults[i]
1927
+ if default:
1928
+ s += f" = {ast.unparse(default)}"
1929
+ arg_strs.append(s)
1930
+ if args.kwarg:
1931
+ k_s = f"**{args.kwarg.arg}"
1932
+ if args.kwarg.annotation:
1933
+ k_s += f": {ast.unparse(args.kwarg.annotation)}"
1934
+ arg_strs.append(k_s)
1935
+
1936
+ ret_type = ast.unparse(node.returns).strip() if node.returns else ""
1937
+ ret = f" -> {ret_type}" if ret_type else ""
1938
+ sig_str = f"{node.name}({', '.join(arg_strs)}){ret}"
1939
+
1940
+ return {
1941
+ "name": node.name,
1942
+ "min_pos": min_pos,
1943
+ "max_pos": float("inf") if args.vararg else len(all_pos),
1944
+ "has_vararg": bool(args.vararg),
1945
+ "has_kwarg": bool(args.kwarg),
1946
+ "all_params": all_params,
1947
+ "sig_str": sig_str,
1948
+ "return_type": ret_type,
1949
+ }
1950
+
1951
+ def validate_code(self, proposed_code: str, target_file: str = "") -> Dict[str, Any]:
1952
+ """Validates proposed code against workspace symbol, dependency manifests, and call contracts."""
1953
+ try:
1954
+ tree = ast.parse(proposed_code)
1955
+ except SyntaxError as e:
1956
+ return {
1957
+ "passed": False,
1958
+ "syntax_error": str(e),
1959
+ "violations": [f"Syntax error at line {e.lineno}: {e.msg}"],
1960
+ "repaired_code": "",
1961
+ "can_auto_heal": False,
1962
+ "repairs_applied": [],
1963
+ "report": f"[HALLUCINATION BLOCKED] Proposed code failed Python syntax parsing at line {e.lineno}: {e.msg}"
1964
+ }
1965
+
1966
+ violations: List[str] = []
1967
+ fixes: Dict[str, Any] = {"imports": {}, "calls": {}, "kwargs": {}}
1968
+ repairs_applied: List[str] = []
1969
+
1970
+ # ---------------------------------------------------------------------
1971
+ # 1. TIER 1 & 2: GHOST DEPENDENCY & WORKSPACE IMPORT GUARDS
1972
+ # ---------------------------------------------------------------------
1973
+ for node in ast.walk(tree):
1974
+ if isinstance(node, ast.ImportFrom):
1975
+ if node.module:
1976
+ base_mod = node.module.split('.')[0]
1977
+ is_workspace = base_mod in self.modules or node.module in self.modules
1978
+ if is_workspace:
1979
+ avail = self.exported_symbols.get(node.module, self.exported_symbols.get(base_mod, set()))
1980
+ for alias in node.names:
1981
+ if alias.name != "*" and alias.name not in avail:
1982
+ close = difflib.get_close_matches(alias.name, list(avail), n=1, cutoff=0.5)
1983
+ did_you_mean = f" Did you mean: `{close[0]}`?" if close else ""
1984
+ if close:
1985
+ fixes["imports"][(node.module, alias.name)] = close[0]
1986
+ fixes["calls"][alias.name] = close[0]
1987
+ repairs_applied.append(f"Import `{alias.name}` -> `{close[0]}` (in `{node.module}`)")
1988
+ violations.append(
1989
+ f"Import Hallucination: Symbol `{alias.name}` does not exist in workspace module `{node.module}`.{did_you_mean}"
1990
+ )
1991
+ else:
1992
+ # Check external third-party package against stdlib + manifests + environment
1993
+ b_lower = base_mod.lower()
1994
+ is_std = b_lower in self.stdlib_modules
1995
+ is_dec = b_lower in self.declared_packages or b_lower.replace("_", "-") in self.declared_packages
1996
+ is_inst = b_lower in self.installed_packages or b_lower in self.known_package_aliases or self.known_package_aliases.get(b_lower) in self.installed_packages
1997
+ if not (is_std or is_dec or is_inst):
1998
+ all_known = list(self.declared_packages | self.installed_packages | set(self.known_package_aliases.keys()))
1999
+ close_pkg = difflib.get_close_matches(b_lower, all_known, n=1, cutoff=0.6)
2000
+ hint = f" Did you mean `{close_pkg[0]}`?" if close_pkg else ""
2001
+ violations.append(
2002
+ f"Ghost Dependency Hallucination: Package `{base_mod}` is not part of Python stdlib, not declared in requirements.txt/pyproject.toml, and not installed in environment.{hint}"
2003
+ )
2004
+
2005
+ elif isinstance(node, ast.Import):
2006
+ for alias in node.names:
2007
+ base_mod = alias.name.split('.')[0]
2008
+ is_workspace = base_mod in self.modules
2009
+ if not is_workspace:
2010
+ b_lower = base_mod.lower()
2011
+ is_std = b_lower in self.stdlib_modules
2012
+ is_dec = b_lower in self.declared_packages or b_lower.replace("_", "-") in self.declared_packages
2013
+ is_inst = b_lower in self.installed_packages or b_lower in self.known_package_aliases or self.known_package_aliases.get(b_lower) in self.installed_packages
2014
+ if not (is_std or is_dec or is_inst):
2015
+ all_known = list(self.declared_packages | self.installed_packages | set(self.known_package_aliases.keys()))
2016
+ close_pkg = difflib.get_close_matches(b_lower, all_known, n=1, cutoff=0.6)
2017
+ hint = f" Did you mean `{close_pkg[0]}`?" if close_pkg else ""
2018
+ violations.append(
2019
+ f"Ghost Dependency Hallucination: Package `{base_mod}` is not part of Python stdlib, not declared in requirements.txt/pyproject.toml, and not installed in environment.{hint}"
2020
+ )
2021
+
2022
+ # ---------------------------------------------------------------------
2023
+ # 2. TIER 3 & 4: CALL SIGNATURE, KWARGS & SCHEMA FIELD GUARDS
2024
+ # ---------------------------------------------------------------------
2025
+ for node in ast.walk(tree):
2026
+ if isinstance(node, ast.Call):
2027
+ func_name = ""
2028
+ attr_name = ""
2029
+ if isinstance(node.func, ast.Name):
2030
+ func_name = node.func.id
2031
+ elif isinstance(node.func, ast.Attribute):
2032
+ val_str = ast.unparse(node.func.value)
2033
+ attr_name = node.func.attr
2034
+ func_name = f"{val_str}.{attr_name}"
2035
+
2036
+ sig = self.function_signatures.get(func_name)
2037
+ if not sig and attr_name:
2038
+ sig = self.function_signatures.get(attr_name)
2039
+
2040
+ # Schema & Dataclass field validation on class instantiation
2041
+ target_class = func_name or attr_name
2042
+ if target_class in self.class_fields and len(self.class_fields[target_class]) > 0:
2043
+ declared_fields = self.class_fields[target_class]
2044
+ valid_params = sig["all_params"] if sig else set()
2045
+ for kw in node.keywords:
2046
+ if kw.arg and kw.arg not in declared_fields and kw.arg not in valid_params:
2047
+ close_f = difflib.get_close_matches(kw.arg, list(declared_fields | valid_params), n=1, cutoff=0.5)
2048
+ hint = f" Did you mean field `{close_f[0]}`?" if close_f else ""
2049
+ if close_f:
2050
+ fixes["kwargs"][(target_class, kw.arg)] = close_f[0]
2051
+ repairs_applied.append(f"Field `{target_class}.{kw.arg}` -> `{close_f[0]}`")
2052
+ violations.append(
2053
+ f"Schema Field Hallucination: Class `{target_class}` has no declared attribute or field `{kw.arg}`.{hint}"
2054
+ )
2055
+
2056
+ if sig:
2057
+ # Validate keyword arguments
2058
+ if not sig["has_kwarg"]:
2059
+ for kw in node.keywords:
2060
+ if kw.arg and kw.arg not in sig["all_params"]:
2061
+ close_kw = difflib.get_close_matches(kw.arg, list(sig["all_params"]), n=1, cutoff=0.5)
2062
+ hint = f" Did you mean parameter `{close_kw[0]}`?" if close_kw else ""
2063
+ if close_kw:
2064
+ target_k = func_name or sig["name"]
2065
+ fixes["kwargs"][(target_k, kw.arg)] = close_kw[0]
2066
+ fixes["kwargs"][(sig["name"], kw.arg)] = close_kw[0]
2067
+ if attr_name:
2068
+ fixes["kwargs"][(attr_name, kw.arg)] = close_kw[0]
2069
+ repairs_applied.append(f"Param `{kw.arg}` -> `{close_kw[0]}` in `{func_name or sig['name']}()`")
2070
+ violations.append(
2071
+ f"Call Hallucination: `{func_name or sig['name']}` does not accept keyword argument `{kw.arg}`.{hint} Valid signature: `{sig['sig_str']}`"
2072
+ )
2073
+ # Validate positional argument counts
2074
+ num_pos = len(node.args)
2075
+ is_method_attr_call = isinstance(node.func, ast.Attribute)
2076
+ effective_min = sig["min_pos"]
2077
+ effective_max = sig["max_pos"]
2078
+ if is_method_attr_call and "self" in sig["all_params"]:
2079
+ effective_min = max(0, effective_min - 1)
2080
+ if effective_max != float("inf"):
2081
+ effective_max = max(0, effective_max - 1)
2082
+
2083
+ if num_pos < effective_min:
2084
+ violations.append(
2085
+ f"Call Hallucination: `{func_name or sig['name']}` missing required positional arguments ({num_pos} provided, at least {effective_min} required). Valid signature: `{sig['sig_str']}`"
2086
+ )
2087
+ elif not sig["has_vararg"] and num_pos > effective_max:
2088
+ violations.append(
2089
+ f"Call Hallucination: `{func_name or sig['name']}` received too many positional arguments ({num_pos} provided, at most {effective_max} allowed). Valid signature: `{sig['sig_str']}`"
2090
+ )
2091
+ elif isinstance(node.func, ast.Attribute):
2092
+ target_attr = node.func.attr
2093
+ all_sigs = list(self.function_signatures.keys())
2094
+ matches = difflib.get_close_matches(target_attr, [s.split('.')[-1] for s in all_sigs], n=1, cutoff=0.5)
2095
+ if matches:
2096
+ cand_sigs = [self.function_signatures[m]["sig_str"] for m in matches if m in self.function_signatures]
2097
+ val_str = ast.unparse(node.func.value)
2098
+ fixes["calls"][target_attr] = matches[0]
2099
+ repairs_applied.append(f"Method `{val_str}.{target_attr}()` -> `.{matches[0]}()`")
2100
+ violations.append(
2101
+ f"Call Hallucination: Method or function `{val_str}.{target_attr}` does not exist! Did you mean: `{'; '.join(cand_sigs or matches)}`?"
2102
+ )
2103
+
2104
+ # ---------------------------------------------------------------------
2105
+ # 3. TIER 5: RETURN TYPE & CHAINING GUARD
2106
+ # ---------------------------------------------------------------------
2107
+ for node in ast.walk(tree):
2108
+ # Check iterations: for x in func():
2109
+ if isinstance(node, (ast.For, ast.AsyncFor)):
2110
+ if isinstance(node.iter, ast.Call):
2111
+ c_name = ast.unparse(node.iter.func).strip()
2112
+ sig = self.function_signatures.get(c_name) or self.function_signatures.get(c_name.split('.')[-1])
2113
+ if sig and sig.get("return_type"):
2114
+ rt = sig["return_type"].strip()
2115
+ if rt in ("None", "NoneType", "bool", "int", "float"):
2116
+ violations.append(
2117
+ f"Return Contract Hallucination: Function `{c_name}` returns `{rt}`, which is not iterable in a loop!"
2118
+ )
2119
+ # Check attribute access on None return: func().attr
2120
+ elif isinstance(node, ast.Attribute):
2121
+ if isinstance(node.value, ast.Call):
2122
+ c_name = ast.unparse(node.value.func).strip()
2123
+ sig = self.function_signatures.get(c_name) or self.function_signatures.get(c_name.split('.')[-1])
2124
+ if sig and sig.get("return_type") in ("None", "NoneType"):
2125
+ violations.append(
2126
+ f"Return Contract Hallucination: Function `{c_name}` has declared return type 'None', cannot access member `.{node.attr}` on NoneType!"
2127
+ )
2128
+
2129
+ # ---------------------------------------------------------------------
2130
+ # 4. SELF-HEALING AST REPAIR SYNTHESIS
2131
+ # ---------------------------------------------------------------------
2132
+ repaired_code = ""
2133
+ can_auto_heal = False
2134
+ has_fixes = bool(fixes["imports"] or fixes["calls"] or fixes["kwargs"])
2135
+ if has_fixes:
2136
+ try:
2137
+ tree_copy = copy.deepcopy(tree)
2138
+ transformer = _SelfHealingTransformer(fixes)
2139
+ healed_tree = transformer.visit(tree_copy)
2140
+ ast.fix_missing_locations(healed_tree)
2141
+ repaired_code = ast.unparse(healed_tree)
2142
+ can_auto_heal = True
2143
+ except Exception:
2144
+ repaired_code = ""
2145
+ can_auto_heal = False
2146
+
2147
+ passed = len(violations) == 0
2148
+ if passed:
2149
+ report_str = "[PASS] Zero-Hallucination Guard verified. All imports, call contracts, dependency manifests, and schemas are valid."
2150
+ else:
2151
+ report_lines = [
2152
+ f"[HALLUCINATION BLOCKED] Found {len(violations)} contract violation(s) in proposed code:\n"
2153
+ ]
2154
+ for i, v in enumerate(violations, 1):
2155
+ report_lines.append(f"{i}. {v}")
2156
+
2157
+ if can_auto_heal and repaired_code:
2158
+ report_lines.append("\n### 🩹 Self-Healing AST Auto-Repair:")
2159
+ report_lines.append(f"> T-Zero automatically synthesized a corrected, hallucination-free version applying {len(repairs_applied)} fix(es):")
2160
+ for r in repairs_applied:
2161
+ report_lines.append(f"- 🔧 {r}")
2162
+ report_lines.append("\n```python")
2163
+ report_lines.append(repaired_code)
2164
+ report_lines.append("```\n")
2165
+ else:
2166
+ report_lines.append("\n> **Corrective Action:** Adjust imports or function calls to match declared AST interfaces above.")
2167
+
2168
+ report_str = "\n".join(report_lines)
2169
+
2170
+ return {
2171
+ "passed": passed,
2172
+ "violations": violations,
2173
+ "repaired_code": repaired_code,
2174
+ "can_auto_heal": can_auto_heal,
2175
+ "repairs_applied": repairs_applied,
2176
+ "report": report_str
2177
+ }
2178
+
2179
+ def repair_code(self, proposed_code: str, target_file: str = "") -> str:
2180
+ """Returns auto-healed code if hallucinations exist and can be safely repaired, or original code."""
2181
+ res = self.validate_code(proposed_code, target_file)
2182
+ if res.get("can_auto_heal") and res.get("repaired_code"):
2183
+ return res["repaired_code"]
2184
+ return proposed_code
2185
+
2186
+ def generate_grounding_contract(self, focus_modules: Optional[List[str]] = None, task_intent: str = "") -> str:
2187
+ """Emits an immutable AST Type & Symbol grounding contract for prompt pinning."""
2188
+ out = [
2189
+ "# 🔒 T-ZERO IMMUTABLE AST GROUNDING CONTRACT (ZERO-HALLUCINATION SPECIFICATION)",
2190
+ "",
2191
+ "> **CRITICAL DIRECTIVE FOR AI AGENT / COMPOSER:**",
2192
+ "> You MUST strictly conform to the exact symbol, function, parameter, and class definitions declared below.",
2193
+ "> Guessing parameter names, inventing unlisted methods, or importing ghost packages is strictly prohibited.",
2194
+ ""
2195
+ ]
2196
+
2197
+ if task_intent:
2198
+ out.append(f"**Task Objective:** `{task_intent}`\n")
2199
+
2200
+ # 1. Permitted Workspace Signatures
2201
+ out.append("## 1. 🔍 Permitted Workspace Function Signatures & Types")
2202
+ out.append("| Module | Function / Method Signature | Return Type |")
2203
+ out.append("|:---|:---|:---|")
2204
+
2205
+ target_keys = sorted(self.function_signatures.keys())
2206
+ if focus_modules:
2207
+ target_keys = [k for k in target_keys if any(m in k for m in focus_modules)]
2208
+
2209
+ for k in target_keys[:40]:
2210
+ sig = self.function_signatures[k]
2211
+ rt = sig.get("return_type") or "Any"
2212
+ out.append(f"| `{k.split('.')[0] if '.' in k else 'global'}` | `{sig['sig_str']}` | `{rt}` |")
2213
+ out.append("")
2214
+
2215
+ # 2. Permitted Class Schemas
2216
+ if self.class_fields:
2217
+ out.append("## 2. 🧩 Declared Class & Model Field Schemas")
2218
+ out.append("| Class | Declared Attributes & Fields |")
2219
+ out.append("|:---|:---|")
2220
+ for c_name, fields in list(self.class_fields.items())[:25]:
2221
+ if "." not in c_name and fields:
2222
+ out.append(f"| `{c_name}` | `{', '.join(sorted(fields))}` |")
2223
+ out.append("")
2224
+
2225
+ # 3. Permitted Dependencies
2226
+ declared_list = sorted(list(self.declared_packages))
2227
+ if declared_list:
2228
+ out.append("## 3. 📦 Declared Third-Party Packages (Manifest Verified)")
2229
+ out.append(f"`{', '.join(declared_list[:25])}`\n")
2230
+
2231
+ out.append("> **Enforcement:** Code failing this grounding contract will be blocked and rejected by the T-Zero Pre-Flight Compiler.")
2232
+ return "\n".join(out)
2233
+
2234
+
2235
+ # =============================================================================
2236
+ # 8. FOCUS-AWARE SMART SKELETON CONTEXT BUILDER (TOOL 18)
2237
+ # =============================================================================
2238
+
2239
+ class SmartSkeletonContextBuilder:
2240
+ """Builds focus-aware hybrid context with full implementation for the target
2241
+ and ultra-compressed T-3 AST skeletons for surrounding 1st-degree ecosystem.
2242
+ """
2243
+
2244
+ def __init__(self, project_dir: str):
2245
+ self.project_dir = os.path.abspath(project_dir)
2246
+ self.impact_analyzer = ChangeImpactAnalyzer(project_dir)
2247
+
2248
+ def build_skeleton(self, focus_file: str, focus_symbol: str = "", task_intent: str = "") -> str:
2249
+ """Returns full target code + 1st-degree AST skeletons and matching reusable helpers."""
2250
+ from tzero_v3 import TokenReducer
2251
+
2252
+ target_path = os.path.join(self.project_dir, focus_file) if not os.path.isabs(focus_file) else focus_file
2253
+ rel_target = os.path.relpath(target_path, self.project_dir).replace('\\', '/')
2254
+ if not os.path.isfile(target_path):
2255
+ return f"[ERROR] Focus file not found: {focus_file}"
2256
+
2257
+ with open(target_path, 'r', encoding='utf-8', errors='ignore') as fp:
2258
+ full_code = fp.read()
2259
+
2260
+ target_section = full_code
2261
+ target_title = f"`{rel_target}` (Full Implementation)"
2262
+
2263
+ if focus_symbol and rel_target.endswith(".py"):
2264
+ try:
2265
+ tree = ast.parse(full_code)
2266
+ lines = full_code.splitlines()
2267
+ for node in ast.walk(tree):
2268
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
2269
+ if node.name == focus_symbol:
2270
+ start_l = node.lineno - 1
2271
+ end_l = getattr(node, "end_lineno", len(lines))
2272
+ target_section = "\n".join(lines[start_l:end_l])
2273
+ target_title = f"`{rel_target}::{focus_symbol}` (Target Symbol Implementation)"
2274
+ break
2275
+ except Exception:
2276
+ pass
2277
+
2278
+ # Identify 1st-degree outbound imports
2279
+ outbound_files: Set[str] = set()
2280
+ stem = os.path.splitext(os.path.basename(rel_target))[0]
2281
+ imported_modules = self.impact_analyzer.import_graph.get(rel_target, set())
2282
+ for f in self.impact_analyzer.files_list:
2283
+ f_stem = os.path.splitext(os.path.basename(f))[0]
2284
+ if f_stem in imported_modules and f != rel_target:
2285
+ outbound_files.add(f)
2286
+
2287
+ # Identify 1st-degree inbound callers
2288
+ inbound_files: Set[str] = set()
2289
+ if focus_symbol:
2290
+ impact = self.impact_analyzer.analyze_symbol(focus_symbol)
2291
+ for caller in impact.get("direct_callers", []):
2292
+ inbound_files.add(caller["file"])
2293
+ else:
2294
+ for f in self.impact_analyzer.inbound_graph.get(stem, set()):
2295
+ if f != rel_target:
2296
+ inbound_files.add(f)
2297
+
2298
+ surrounding_files = sorted(list((outbound_files | inbound_files) - {rel_target}))
2299
+
2300
+ md = [
2301
+ f"# 🎯 Focus-Aware Smart Skeleton Context: {os.path.basename(rel_target)}",
2302
+ "",
2303
+ f"> **Architecture Pattern:** Zero-Hallucination Hybrid (Full Target + Surrounding T-3 AST Skeletons)",
2304
+ f"> **Target File:** `{rel_target}` | **Surrounding Ecosystem:** {len(surrounding_files)} modules",
2305
+ "",
2306
+ f"## 1. 🔍 Target Implementation: {target_title}",
2307
+ "```python" if rel_target.endswith(".py") else "```text",
2308
+ target_section,
2309
+ "```",
2310
+ "",
2311
+ "## 2. 🌐 Surrounding Interface Ecosystem (T-3 AST Skeletons)",
2312
+ "> The following 1st-degree dependencies and callers are pruned to AST signatures to guarantee interface fidelity with minimum token overhead:",
2313
+ ""
2314
+ ]
2315
+
2316
+ for s_file in surrounding_files:
2317
+ s_full = os.path.join(self.project_dir, s_file)
2318
+ try:
2319
+ with open(s_full, 'r', encoding='utf-8', errors='ignore') as fp:
2320
+ s_code = fp.read()
2321
+ pruned = TokenReducer.reduce(s_code, s_file, mode="ultra")
2322
+ ext = os.path.splitext(s_file)[1].lstrip('.') or "py"
2323
+ rel_type = "Outbound Dependency" if s_file in outbound_files else "Inbound Caller"
2324
+ md.append(f"### `{s_file}` ({rel_type})")
2325
+ md.append(f"```{ext}")
2326
+ md.append(pruned)
2327
+ md.append("```\n")
2328
+ except Exception:
2329
+ pass
2330
+
2331
+ # Section 3: Surface matching reusable helpers across repository
2332
+ try:
2333
+ suggester = ReusableHelperSuggester(self.project_dir)
2334
+ lookup_query = task_intent or focus_symbol or os.path.splitext(os.path.basename(rel_target))[0]
2335
+ helpers = suggester.suggest_helpers(lookup_query, target_file=rel_target, top_k=3)
2336
+ if helpers:
2337
+ md.append("## 3. 💡 Reusable Repository Helpers Available")
2338
+ md.append("> **T-Zero Anti-Reinvention Guard:** Pruned skeletons might conceal helper utilities. The following existing helpers match your context and are ready for direct import:")
2339
+ md.append("")
2340
+ for h in helpers:
2341
+ md.append(f"- **`{h['name']}`** in `{h['file']}:{h['line']}`")
2342
+ md.append(f" ```python\n {h['import_stmt']}\n ```")
2343
+ if h.get("docstring"):
2344
+ md.append(f" *{h['docstring']}*")
2345
+ md.append("")
2346
+ except Exception:
2347
+ pass
2348
+
2349
+ return "\n".join(md)
2350
+
2351
+
2352
+ # =============================================================================
2353
+ # 9. GRAPH-CENTRALITY ARCHITECTURAL PAGERANK (TOOL 19)
2354
+ # =============================================================================
2355
+
2356
+ class RepositoryPageRank:
2357
+ """Pure-Python PageRank implementation to identify the architectural backbone of a codebase."""
2358
+
2359
+ def __init__(self, project_dir: str):
2360
+ self.project_dir = os.path.abspath(project_dir)
2361
+ self.impact_analyzer = ChangeImpactAnalyzer(project_dir)
2362
+
2363
+ def rank(self, top_k: int = 15, damping: float = 0.85, max_iter: int = 40) -> List[Dict[str, Any]]:
2364
+ """Calculates PageRank centrality for all files in the repository."""
2365
+ nodes = sorted(self.impact_analyzer.files_list)
2366
+ N = len(nodes)
2367
+ if N == 0:
2368
+ return []
2369
+
2370
+ node_to_idx = {node: i for i, node in enumerate(nodes)}
2371
+ stem_to_node = {}
2372
+ for n in nodes:
2373
+ s = os.path.splitext(os.path.basename(n))[0]
2374
+ stem_to_node[s] = n
2375
+
2376
+ # Build adjacency
2377
+ out_edges: Dict[int, Set[int]] = collections.defaultdict(set)
2378
+ in_edges: Dict[int, Set[int]] = collections.defaultdict(set)
2379
+
2380
+ for src, imps in self.impact_analyzer.import_graph.items():
2381
+ if src in node_to_idx:
2382
+ u = node_to_idx[src]
2383
+ for imp in imps:
2384
+ if imp in stem_to_node:
2385
+ dst = stem_to_node[imp]
2386
+ v = node_to_idx[dst]
2387
+ if u != v:
2388
+ out_edges[u].add(v)
2389
+ in_edges[v].add(u)
2390
+
2391
+ # Power iteration
2392
+ scores = [1.0 / N] * N
2393
+ for _ in range(max_iter):
2394
+ new_scores = [(1.0 - damping) / N] * N
2395
+ dangling_sum = sum(scores[i] for i in range(N) if not out_edges[i])
2396
+ dangling_alloc = (damping * dangling_sum) / N
2397
+
2398
+ for i in range(N):
2399
+ new_scores[i] += dangling_alloc
2400
+ for parent in in_edges[i]:
2401
+ out_deg = len(out_edges[parent])
2402
+ if out_deg > 0:
2403
+ new_scores[i] += damping * (scores[parent] / out_deg)
2404
+
2405
+ scores = new_scores
2406
+
2407
+ # Rank
2408
+ ranked_indices = sorted(range(N), key=lambda i: scores[i], reverse=True)
2409
+ results = []
2410
+ for idx in ranked_indices[:top_k]:
2411
+ file_p = nodes[idx]
2412
+ in_deg = len(in_edges[idx])
2413
+ out_deg = len(out_edges[idx])
2414
+ raw_score = scores[idx]
2415
+
2416
+ # Extract key contracts
2417
+ contracts = []
2418
+ content = self.impact_analyzer.snippets.get(file_p, "")
2419
+ try:
2420
+ tree = ast.parse(content)
2421
+ for node in tree.body:
2422
+ if isinstance(node, ast.ClassDef):
2423
+ contracts.append(f"class {node.name}")
2424
+ elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
2425
+ contracts.append(f"def {node.name}()")
2426
+ except Exception:
2427
+ pass
2428
+
2429
+ results.append({
2430
+ "file": file_p,
2431
+ "pagerank_score": round(raw_score * 100, 3),
2432
+ "inbound_degree": in_deg,
2433
+ "outbound_degree": out_deg,
2434
+ "key_contracts": contracts[:5]
2435
+ })
2436
+
2437
+ return results
2438
+
2439
+ def format_report(self, rankings: List[Dict[str, Any]]) -> str:
2440
+ out = [
2441
+ f"# 🏛️ Repository Architectural PageRank & Centrality Backbone",
2442
+ "",
2443
+ "> **Graph Theory Analysis:** Identifies the foundational core modules of your project based on AST import topologies.",
2444
+ "",
2445
+ "| Rank | Module | Centrality Score | Inbound (Callers) | Outbound (Deps) | Key Exported Contracts |",
2446
+ "|:----:|:-------|:----------------:|:-----------------:|:---------------:|:-----------------------|"
2447
+ ]
2448
+ for rank, r in enumerate(rankings, 1):
2449
+ contracts_str = ", ".join(f"`{c}`" for c in r["key_contracts"]) if r["key_contracts"] else "*none*"
2450
+ out.append(f"| #{rank} | `{r['file']}` | **{r['pagerank_score']}** | {r['inbound_degree']} | {r['outbound_degree']} | {contracts_str} |")
2451
+
2452
+ out.append("\n> **Recommendation:** Protect high-centrality modules (Top #1-#5) with strict unit tests and boundary rules before refactoring.")
2453
+ return "\n".join(out)
2454
+
2455
+
2456
+ # =============================================================================
2457
+ # 10. DEEP SYMBOL CALL-CHAIN & EXECUTION PATH TRACER (TOOL 20)
2458
+ # =============================================================================
2459
+
2460
+ class SymbolCallChainTracer:
2461
+ """Traces upstream callers and downstream callees across the workspace AST."""
2462
+
2463
+ def __init__(self, project_dir: str):
2464
+ self.project_dir = os.path.abspath(project_dir)
2465
+ self.call_graph: Dict[str, Set[str]] = collections.defaultdict(set) # caller -> set(callees)
2466
+ self.reverse_call_graph: Dict[str, Set[str]] = collections.defaultdict(set) # callee -> set(callers)
2467
+ self._build_call_graph()
2468
+
2469
+ def _build_call_graph(self):
2470
+ for root, _, files in os.walk(self.project_dir):
2471
+ if should_skip_dir(root, self.project_dir):
2472
+ continue
2473
+ for f in files:
2474
+ if f.endswith('.py'):
2475
+ full_p = os.path.join(root, f)
2476
+ try:
2477
+ with open(full_p, 'r', encoding='utf-8', errors='ignore') as fp:
2478
+ content = fp.read()
2479
+ tree = ast.parse(content)
2480
+ self._index_calls(tree)
2481
+ except Exception:
2482
+ pass
2483
+
2484
+ def _index_calls(self, tree: ast.AST):
2485
+ for node in ast.walk(tree):
2486
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
2487
+ caller_name = node.name
2488
+ for child in ast.walk(node):
2489
+ if isinstance(child, ast.Call):
2490
+ callee_name = ""
2491
+ if isinstance(child.func, ast.Name):
2492
+ callee_name = child.func.id
2493
+ elif isinstance(child.func, ast.Attribute):
2494
+ callee_name = child.func.attr
2495
+ if callee_name and callee_name != caller_name:
2496
+ self.call_graph[caller_name].add(callee_name)
2497
+ self.reverse_call_graph[callee_name].add(caller_name)
2498
+
2499
+ def trace(self, symbol_name: str, max_depth: int = 3) -> Dict[str, Any]:
2500
+ """Traces upstream callers and downstream callees up to max_depth."""
2501
+ def trace_upstream(sym: str, depth: int, visited: Set[str]) -> List[Dict[str, Any]]:
2502
+ if depth >= max_depth or sym in visited:
2503
+ return []
2504
+ visited.add(sym)
2505
+ res = []
2506
+ for caller in sorted(self.reverse_call_graph.get(sym, set())):
2507
+ res.append({
2508
+ "symbol": caller,
2509
+ "depth": depth + 1,
2510
+ "callers": trace_upstream(caller, depth + 1, visited.copy())
2511
+ })
2512
+ return res
2513
+
2514
+ def trace_downstream(sym: str, depth: int, visited: Set[str]) -> List[Dict[str, Any]]:
2515
+ if depth >= max_depth or sym in visited:
2516
+ return []
2517
+ visited.add(sym)
2518
+ res = []
2519
+ for callee in sorted(self.call_graph.get(sym, set())):
2520
+ res.append({
2521
+ "symbol": callee,
2522
+ "depth": depth + 1,
2523
+ "callees": trace_downstream(callee, depth + 1, visited.copy())
2524
+ })
2525
+ return res
2526
+
2527
+ upstream = trace_upstream(symbol_name, 0, set())
2528
+ downstream = trace_downstream(symbol_name, 0, set())
2529
+
2530
+ return {
2531
+ "symbol": symbol_name,
2532
+ "max_depth": max_depth,
2533
+ "upstream": upstream,
2534
+ "downstream": downstream,
2535
+ "total_upstream_callers": len(self.reverse_call_graph.get(symbol_name, set())),
2536
+ "total_downstream_callees": len(self.call_graph.get(symbol_name, set())),
2537
+ }
2538
+
2539
+ def format_report(self, trace_result: Dict[str, Any]) -> str:
2540
+ sym = trace_result["symbol"]
2541
+ out = [
2542
+ f"# 🔗 Deep Symbol Call-Chain & Execution Path: `{sym}`",
2543
+ "",
2544
+ f"- **Target Symbol:** `{sym}`",
2545
+ f"- **Direct Inbound Callers:** {trace_result['total_upstream_callers']}",
2546
+ f"- **Direct Outbound Callees:** {trace_result['total_downstream_callees']}",
2547
+ f"- **Max Trace Depth:** {trace_result['max_depth']}",
2548
+ "",
2549
+ "## 1. ⬆️ Upstream Callers (Who calls this symbol):"
2550
+ ]
2551
+
2552
+ def render_tree(items: List[Dict[str, Any]], key: str, prefix: str = ""):
2553
+ for item in items:
2554
+ out.append(f"{prefix}└── `{item['symbol']}` (Level {item['depth']})")
2555
+ if item.get(key):
2556
+ render_tree(item[key], key, prefix + " ")
2557
+
2558
+ if trace_result["upstream"]:
2559
+ render_tree(trace_result["upstream"], "callers")
2560
+ else:
2561
+ out.append("*(No upstream callers detected in workspace)*")
2562
+
2563
+ out.append("\n## 2. ⬇️ Downstream Callees (What this symbol calls internally):")
2564
+ if trace_result["downstream"]:
2565
+ render_tree(trace_result["downstream"], "callees")
2566
+ else:
2567
+ out.append("*(No internal callees detected in workspace)*")
2568
+
2569
+ # Mermaid Diagram
2570
+ out.append("\n## 3. 📊 Visual Call Graph (Mermaid)")
2571
+ out.append("```mermaid")
2572
+ out.append("graph LR")
2573
+ out.append(f' T["{sym} (TARGET)"]:::target')
2574
+ out.append(" classDef target fill:#00ffd8,stroke:#000,color:#000,font-weight:bold;")
2575
+
2576
+ added_edges = set()
2577
+ for c in trace_result["upstream"]:
2578
+ edge = f' "{c["symbol"]}" --> T'
2579
+ if edge not in added_edges:
2580
+ out.append(edge)
2581
+ added_edges.add(edge)
2582
+
2583
+ for c in trace_result["downstream"]:
2584
+ edge = f' T --> "{c["symbol"]}"'
2585
+ if edge not in added_edges:
2586
+ out.append(edge)
2587
+ added_edges.add(edge)
2588
+
2589
+ out.append("```")
2590
+ return "\n".join(out)
2591
+
2592
+
2593
+ # =============================================================================
2594
+ # 11. CIRCULAR DEPENDENCY DETECTOR (DFS CYCLE TRAVERSAL)
2595
+ # =============================================================================
2596
+
2597
+ def detect_circular_dependencies(import_graph: Any) -> List[List[str]]:
2598
+ """Detects import cycles (A -> B -> A) using Depth-First Search with recursion stack.
2599
+ Accepts either an explicit adjacency graph Dict[str, Set[str]] or a project_dir path."""
2600
+ if isinstance(import_graph, (str, os.PathLike)):
2601
+ project_dir = os.path.abspath(str(import_graph))
2602
+ graph: Dict[str, Set[str]] = {}
2603
+ for root, _, files in os.walk(project_dir):
2604
+ if should_skip_dir(root, project_dir):
2605
+ continue
2606
+ for f in files:
2607
+ if f.endswith(".py"):
2608
+ full_p = os.path.join(root, f)
2609
+ rel_mod = os.path.relpath(full_p, project_dir).replace("\\", "/").replace(".py", "").replace("/", ".")
2610
+ deps = set()
2611
+ try:
2612
+ with open(full_p, "r", encoding="utf-8", errors="ignore") as io:
2613
+ tree = ast.parse(io.read(), filename=full_p)
2614
+ for node in ast.walk(tree):
2615
+ if isinstance(node, ast.Import):
2616
+ for alias in node.names:
2617
+ deps.add(alias.name)
2618
+ elif isinstance(node, ast.ImportFrom) and node.module:
2619
+ deps.add(node.module)
2620
+ except Exception:
2621
+ pass
2622
+ graph[rel_mod] = deps
2623
+ import_graph = graph
2624
+
2625
+ cycles = []
2626
+ visited: Set[str] = set()
2627
+ rec_stack: List[str] = []
2628
+
2629
+ def dfs(node: str):
2630
+ visited.add(node)
2631
+ rec_stack.append(node)
2632
+
2633
+ for neighbor in import_graph.get(node, set()):
2634
+ if neighbor not in visited:
2635
+ dfs(neighbor)
2636
+ elif neighbor in rec_stack:
2637
+ cycle_start = rec_stack.index(neighbor)
2638
+ cycle = rec_stack[cycle_start:] + [neighbor]
2639
+ core_cycle = cycle[:-1]
2640
+ min_idx = core_cycle.index(min(core_cycle))
2641
+ canonical = core_cycle[min_idx:] + core_cycle[:min_idx] + [core_cycle[min_idx]]
2642
+ if canonical not in cycles:
2643
+ cycles.append(canonical)
2644
+
2645
+ rec_stack.pop()
2646
+
2647
+ for n in sorted(import_graph.keys()):
2648
+ if n not in visited:
2649
+ dfs(n)
2650
+
2651
+ return cycles
2652
+
2653
+
2654
+ # =============================================================================
2655
+ # 12. DEAD CODE & UNUSED SYMBOL FINDER (TOOL 21)
2656
+ # =============================================================================
2657
+
2658
+ class DeadCodeFinder:
2659
+ """Discovers unused, unreferenced functions and classes across the codebase AST."""
2660
+
2661
+ def __init__(self, project_dir: str):
2662
+ self.project_dir = os.path.abspath(project_dir)
2663
+
2664
+ def find_dead_symbols(self) -> Dict[str, Any]:
2665
+ """Scans codebase to detect declared functions/classes never imported or referenced."""
2666
+ definitions = [] # (symbol_name, rel_path, line_no, kind)
2667
+ all_references: Set[str] = set()
2668
+ declared_all: Set[str] = set()
2669
+
2670
+ for root, _, files in os.walk(self.project_dir):
2671
+ if should_skip_dir(root, self.project_dir):
2672
+ continue
2673
+ for f in files:
2674
+ if f.endswith('.py'):
2675
+ full_p = os.path.join(root, f)
2676
+ rel_p = os.path.relpath(full_p, self.project_dir).replace('\\', '/')
2677
+ is_test = "test" in rel_p.lower() or f.startswith("test_")
2678
+ try:
2679
+ with open(full_p, 'r', encoding='utf-8', errors='ignore') as fp:
2680
+ content = fp.read()
2681
+ tree = ast.parse(content)
2682
+ except Exception:
2683
+ continue
2684
+
2685
+ for node in ast.walk(tree):
2686
+ if isinstance(node, ast.Name):
2687
+ all_references.add(node.id)
2688
+ elif isinstance(node, ast.Attribute):
2689
+ all_references.add(node.attr)
2690
+ elif isinstance(node, (ast.Import, ast.ImportFrom)):
2691
+ for alias in node.names:
2692
+ all_references.add(alias.name)
2693
+
2694
+ # Top-level declarations from non-test source files
2695
+ if not is_test and f != "__init__.py":
2696
+ if isinstance(node, ast.ClassDef):
2697
+ if not node.name.startswith("_") and node.name != "Config":
2698
+ definitions.append((node.name, rel_p, node.lineno, "class"))
2699
+ elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
2700
+ if not node.name.startswith("_") and node.name not in ("main", "run", "handler"):
2701
+ definitions.append((node.name, rel_p, node.lineno, "function"))
2702
+ elif isinstance(node, ast.Assign):
2703
+ for target in node.targets:
2704
+ if isinstance(target, ast.Name) and target.id == "__all__":
2705
+ if isinstance(node.value, (ast.List, ast.Tuple)):
2706
+ for elt in node.value.elts:
2707
+ if isinstance(elt, ast.Constant) and isinstance(elt.value, str):
2708
+ declared_all.add(elt.value)
2709
+
2710
+ impact = ChangeImpactAnalyzer(self.project_dir)
2711
+ dead_symbols = []
2712
+ for name, rel_p, lineno, kind in definitions:
2713
+ if name in declared_all:
2714
+ continue
2715
+ files_referencing = set()
2716
+ for snip_file, snip_content in impact.snippets.items():
2717
+ if re.search(rf"\b{re.escape(name)}\b", snip_content):
2718
+ files_referencing.add(snip_file)
2719
+
2720
+ external_refs = [sf for sf in files_referencing if sf != rel_p and "test" not in sf.lower()]
2721
+ if not external_refs:
2722
+ dead_symbols.append({
2723
+ "symbol": name,
2724
+ "kind": kind,
2725
+ "file": rel_p,
2726
+ "line": lineno
2727
+ })
2728
+
2729
+ return {
2730
+ "total_dead_symbols": len(dead_symbols),
2731
+ "dead_symbols": dead_symbols
2732
+ }
2733
+
2734
+ def format_report(self, res: Dict[str, Any]) -> str:
2735
+ count = res["total_dead_symbols"]
2736
+ if count == 0:
2737
+ return "✨ [CLEAN] Zero dead code detected across workspace. All defined symbols are referenced."
2738
+
2739
+ out = [
2740
+ f"# 🧹 Dead Code & Unused Symbol Audit ({count} unreferenced symbols)\n",
2741
+ "> **AST Static Analysis:** The following symbols are defined in the workspace but never referenced by other modules:\n",
2742
+ "| Symbol | Kind | Declaring File | Line |",
2743
+ "|:---|:---|:---|:---|"
2744
+ ]
2745
+ for s in res["dead_symbols"]:
2746
+ out.append(f"| `{s['symbol']}` | {s['kind']} | `{s['file']}` | Line {s['line']} |")
2747
+ out.append("\n> **Recommendation:** Verify if these are intended public API exports or safe to prune.")
2748
+ return "\n".join(out)
2749
+
2750
+ find_unused_symbols = find_dead_symbols
2751
+
2752
+
2753
+ # =============================================================================
2754
+ # 11. REUSABLE HELPER SUGGESTER & ANTI-REINVENTION GUARD
2755
+ # =============================================================================
2756
+
2757
+ class ReusableHelperSuggester:
2758
+ """Discovers, ranks, and surfaces reusable utility and helper functions across repository AST.
2759
+
2760
+ Prevents LLMs and developers from reinventing existing functions that may have been pruned
2761
+ from the context window or located in separate utility packages.
2762
+ """
2763
+
2764
+ def __init__(self, project_dir: str):
2765
+ self.project_dir = os.path.abspath(project_dir)
2766
+ self.helpers: List[Dict[str, Any]] = []
2767
+ self._indexed = False
2768
+
2769
+ def _ensure_indexed(self):
2770
+ if not self._indexed:
2771
+ self._index_codebase()
2772
+ self._indexed = True
2773
+
2774
+ def _index_codebase(self):
2775
+ """Scans workspace python files and extracts reusable helper functions."""
2776
+ for root, _, files in os.walk(self.project_dir):
2777
+ if should_skip_dir(root, self.project_dir):
2778
+ continue
2779
+ for f in files:
2780
+ if f.endswith('.py'):
2781
+ full_p = os.path.join(root, f)
2782
+ rel_p = os.path.relpath(full_p, self.project_dir).replace('\\', '/')
2783
+ # Skip test files and private hidden files
2784
+ if any(t in rel_p.lower() for t in ("/test", "\\test", "tests/", "tests\\", "test_")):
2785
+ continue
2786
+ try:
2787
+ with open(full_p, 'r', encoding='utf-8', errors='ignore') as fp:
2788
+ content = fp.read()
2789
+ self._extract_helpers_from_file(rel_p, content)
2790
+ except Exception:
2791
+ pass
2792
+
2793
+ def _extract_helpers_from_file(self, rel_path: str, code: str):
2794
+ try:
2795
+ tree = ast.parse(code)
2796
+ except Exception:
2797
+ return
2798
+
2799
+ lines = code.splitlines()
2800
+ is_util_file = any(u in rel_path.lower() for u in ("util", "helper", "common", "tools", "shared", "lib", "core"))
2801
+
2802
+ mod_name = rel_path
2803
+ if mod_name.endswith(".py"):
2804
+ mod_name = mod_name[:-3]
2805
+ if mod_name.endswith("/__init__"):
2806
+ mod_name = mod_name[:-9]
2807
+ mod_dotted = mod_name.replace("/", ".").replace("\\", ".")
2808
+
2809
+ helper_prefixes = (
2810
+ "get_", "parse_", "format_", "safe_", "is_", "has_", "compute_", "build_",
2811
+ "extract_", "clean_", "normalize_", "validate_", "to_", "from_", "find_",
2812
+ "convert_", "serialize_", "deserialize_", "read_", "write_", "load_", "dump_",
2813
+ "calculate_", "check_", "detect_", "scan_", "strip_", "ensure_", "resolve_"
2814
+ )
2815
+
2816
+ for node in ast.walk(tree):
2817
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
2818
+ fname = node.name
2819
+ if fname.startswith("__"):
2820
+ continue
2821
+ if fname.startswith("test_") or fname in ("setUp", "tearDown", "main", "run"):
2822
+ continue
2823
+
2824
+ is_prefixed = fname.startswith(helper_prefixes)
2825
+ doc = (ast.get_docstring(node) or "").strip()
2826
+
2827
+ if not (is_util_file or is_prefixed or doc):
2828
+ continue
2829
+
2830
+ start_l = node.lineno
2831
+ end_l = getattr(node, "end_lineno", len(lines))
2832
+ body = "\n".join(lines[start_l - 1:end_l])
2833
+
2834
+ # Extract parameters signature
2835
+ args_list = []
2836
+ for a in node.args.args:
2837
+ arg_str = a.arg
2838
+ if a.annotation:
2839
+ try:
2840
+ arg_str += f": {ast.unparse(a.annotation)}"
2841
+ except Exception:
2842
+ pass
2843
+ args_list.append(arg_str)
2844
+ ret_str = ""
2845
+ if node.returns:
2846
+ try:
2847
+ ret_str = f" -> {ast.unparse(node.returns)}"
2848
+ except Exception:
2849
+ pass
2850
+
2851
+ sig_str = f"def {fname}({', '.join(args_list)}){ret_str}"
2852
+
2853
+ tokens = set()
2854
+ for part in re.split(r'[_]+', fname):
2855
+ subparts = re.findall(r'[A-Za-z][a-z0-9]*', part)
2856
+ for sp in subparts:
2857
+ tokens.add(sp.lower())
2858
+ for a in node.args.args:
2859
+ tokens.add(a.arg.lower())
2860
+ if doc:
2861
+ for w in re.findall(r'[a-zA-Z]{3,}', doc):
2862
+ tokens.add(w.lower())
2863
+
2864
+ import_stmt = f"from {mod_dotted} import {fname}"
2865
+
2866
+ self.helpers.append({
2867
+ "name": fname,
2868
+ "file": rel_path,
2869
+ "line": start_l,
2870
+ "end_line": end_l,
2871
+ "signature": sig_str,
2872
+ "docstring": doc,
2873
+ "body": body,
2874
+ "import_stmt": import_stmt,
2875
+ "is_util_file": is_util_file,
2876
+ "tokens": tokens,
2877
+ })
2878
+
2879
+ def suggest_helpers(self, query: str = "", target_file: str = "", top_k: int = 5) -> List[Dict[str, Any]]:
2880
+ """Searches repository helpers matching natural language query, function name, or task."""
2881
+ self._ensure_indexed()
2882
+ if not self.helpers:
2883
+ return []
2884
+
2885
+ q_clean = (query or "").lower().strip()
2886
+ q_tokens = set(re.findall(r'[a-zA-Z0-9]+', q_clean))
2887
+
2888
+ scored: List[Tuple[float, Dict[str, Any]]] = []
2889
+ for h in self.helpers:
2890
+ score = 0.0
2891
+ h_name = h["name"].lower()
2892
+ h_tokens = h["tokens"]
2893
+ h_doc = h["docstring"].lower()
2894
+
2895
+ if q_clean and h_name == q_clean:
2896
+ score += 150.0
2897
+ elif q_clean and q_clean in h_name:
2898
+ score += 60.0
2899
+
2900
+ if q_tokens:
2901
+ token_matches = q_tokens & h_tokens
2902
+ score += len(token_matches) * 25.0
2903
+
2904
+ for qt in q_tokens:
2905
+ if qt in h_doc:
2906
+ score += 12.0
2907
+
2908
+ if h["is_util_file"]:
2909
+ score *= 1.25
2910
+
2911
+ if target_file and h["file"] == target_file:
2912
+ score *= 0.8 # Slight preference for importing from other modules
2913
+
2914
+ if score > 0 or not q_clean:
2915
+ if not q_clean:
2916
+ score = 20.0 if h["is_util_file"] else 5.0
2917
+ scored.append((score, h))
2918
+
2919
+ scored.sort(key=lambda x: x[0], reverse=True)
2920
+
2921
+ seen_names = set()
2922
+ results = []
2923
+ for s, h in scored:
2924
+ if h["name"] in seen_names:
2925
+ continue
2926
+ seen_names.add(h["name"])
2927
+ item = dict(h)
2928
+ item["relevance_score"] = round(s, 2)
2929
+ results.append(item)
2930
+ if len(results) >= top_k:
2931
+ break
2932
+
2933
+ return results
2934
+
2935
+ def check_reinvention_guard(self, code_snippet: str, target_file: str = "", top_k: int = 3) -> Dict[str, Any]:
2936
+ """Inspects newly written code snippet to detect if it re-implements existing workspace helpers."""
2937
+ self._ensure_indexed()
2938
+ warnings = []
2939
+ try:
2940
+ tree = ast.parse(code_snippet)
2941
+ for node in ast.walk(tree):
2942
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
2943
+ new_fname = node.name
2944
+ doc = ast.get_docstring(node) or ""
2945
+ query = f"{new_fname} {doc}"
2946
+ matches = self.suggest_helpers(query=query, target_file=target_file, top_k=top_k)
2947
+ for m in matches:
2948
+ if m["name"] == new_fname and target_file and m["file"] == target_file:
2949
+ continue
2950
+ if m["relevance_score"] >= 35.0:
2951
+ warnings.append({
2952
+ "new_symbol": new_fname,
2953
+ "matched_helper": m["name"],
2954
+ "file": m["file"],
2955
+ "line": m["line"],
2956
+ "import_stmt": m["import_stmt"],
2957
+ "docstring": m["docstring"],
2958
+ "body": m["body"],
2959
+ "relevance_score": m["relevance_score"]
2960
+ })
2961
+ except Exception:
2962
+ pass
2963
+
2964
+ return {
2965
+ "has_reinventions": len(warnings) > 0,
2966
+ "total_warnings": len(warnings),
2967
+ "warnings": warnings
2968
+ }
2969
+
2970
+ def format_helpers_report(self, results: List[Dict[str, Any]], query: str = "", include_body: bool = True) -> str:
2971
+ q_label = f': "{query}"' if query else ""
2972
+ out = [
2973
+ f"# 💡 Reusable Repository Helpers{q_label}\n",
2974
+ "> **T-Zero Anti-Reinvention Guard:** Pruning code into skeletons can sometimes hide utility helpers.",
2975
+ "> The following functions already exist in your workspace and can be directly imported and reused:\n"
2976
+ ]
2977
+
2978
+ if not results:
2979
+ out.append("*(No matching reusable helpers found in the repository for this query)*")
2980
+ return "\n".join(out)
2981
+
2982
+ out.append("| Helper Function | Module File | Line | Ready Import |")
2983
+ out.append("|:---|:---|:---|:---|")
2984
+ for r in results:
2985
+ out.append(f"| `{r['name']}` | `{r['file']}` | Line {r['line']} | `{r['import_stmt']}` |")
2986
+ out.append("")
2987
+
2988
+ for i, r in enumerate(results, 1):
2989
+ out.append(f"### {i}. `{r['signature']}`")
2990
+ out.append(f"- **File:** `{r['file']}` (Line {r['line']})")
2991
+ out.append(f"- **Import:** `{r['import_stmt']}`")
2992
+ if r.get("docstring"):
2993
+ out.append(f"- **Docstring:** *{r['docstring']}*")
2994
+ if include_body and r.get("body"):
2995
+ out.append("```python")
2996
+ out.append(r["body"])
2997
+ out.append("```\n")
2998
+
2999
+ out.append("> **Recommendation:** Import and reuse these existing helpers instead of writing duplicate logic.")
3000
+ return "\n".join(out)
3001
+
3002
+ def format_guard_report(self, report: Dict[str, Any], include_body: bool = True) -> str:
3003
+ count = report.get("total_warnings", 0)
3004
+ out = [
3005
+ f"# 🛡️ T-Zero Anti-Reinvention Guard Alert ({count} Existing Helpers Found)\n",
3006
+ "> **AST Wheel-Reinvention Warning:** The code snippet being introduced defines functions that replicate helpers already present in the codebase:\n"
3007
+ ]
3008
+ for w in report.get("warnings", []):
3009
+ out.append(f"### ⚠️ Proposed Function: `{w['new_symbol']}`")
3010
+ out.append(f"- **Existing Helper:** `{w['matched_helper']}` in `{w['file']}:{w['line']}`")
3011
+ out.append(f"- **Recommended Reuse:**")
3012
+ out.append(f" ```python\n {w['import_stmt']}\n ```")
3013
+ if w.get("docstring"):
3014
+ out.append(f"- **Existing Docstring:** *{w['docstring']}*")
3015
+ if include_body and w.get("body"):
3016
+ out.append("```python")
3017
+ out.append(w["body"])
3018
+ out.append("```\n")
3019
+ out.append("> **Action:** Avoid re-implementing these functions. Use the provided import statements instead.")
3020
+ return "\n".join(out)
3021
+
3022
+