MORSATIPIK / build_vortex.py
RDS777's picture
Update build_vortex.py
0826b97 verified
Raw
History Blame Contribute Delete
53.8 kB
#!/usr/bin/env python3
# build_vortex.py – Génère tous les modules VORTEX v5.1 (23 innovations)
import os
from pathlib import Path
BASE = Path("/app")
def write_file(path, content):
filepath = BASE / path
filepath.parent.mkdir(parents=True, exist_ok=True)
filepath.write_text(content, encoding='utf-8')
print(f"✅ {filepath}")
def build_all():
# ============================================================
# 1. agents/cognitive_engine.py
# ============================================================
write_file("agents/cognitive_engine.py", '''
import requests, hashlib, json, logging, os
log = logging.getLogger("vortex.llm")
OLLAMA_URL = os.environ.get("OLLAMA_URL","http://localhost:11434")
MODEL_MAIN = os.environ.get("LLM_MODEL","gemma4:e4b")
MODEL_FAST = os.environ.get("LLM_MODEL_FAST","phi3:mini")
TEMP_PROFILES = {"reasoning":0.1,"code_gen":0.35,"creative":0.65,"summary":0.15}
class MultiLLMEngine:
def __init__(self, memory=None):
self.memory=memory; self._cache={}; self.tools={}; self.call_stats={"main":0,"fast":0,"errors":0}
def register_tool(self, name, fn, desc, params):
self.tools[name] = {"fn":fn,"schema":{"type":"function","function":{"name":name,"description":desc,"parameters":{"type":"object","properties":params,"required":list(params.keys())}}}}
async def call(self, agent, system, user, max_tokens=512, temperature=0.3, use_cache=True, use_tools=False, fast=False, profile=None, **kw):
if profile and profile in TEMP_PROFILES: temperature = TEMP_PROFILES[profile]
model = MODEL_FAST if fast else MODEL_MAIN
key = hashlib.sha256(f"{model}|{system}|{user}|{max_tokens}|{temperature}".encode()).hexdigest()[:20]
if use_cache and key in self._cache: return type("R",(),{"content":self._cache[key],"tokens":0,"cached":True})()
payload = {"model":model,"messages":[{"role":"system","content":system or "Tu es VORTEX."},{"role":"user","content":user}],"options":{"num_predict":max_tokens,"temperature":temperature},"stream":False}
if use_tools and self.tools: payload["tools"] = [t["schema"] for t in self.tools.values()]
try:
resp = requests.post(f"{OLLAMA_URL}/api/chat", json=payload, timeout=180); msg = resp.json().get("message",{})
if msg.get("tool_calls"):
for tc in msg["tool_calls"]:
fname = tc["function"]["name"]; fargs = tc["function"]["arguments"]
if isinstance(fargs, str): fargs = json.loads(fargs)
if fname in self.tools:
result = self.tools[fname]["fn"](**fargs)
payload["messages"] += [msg, {"role":"tool","content":str(result)}]
resp = requests.post(f"{OLLAMA_URL}/api/chat", json=payload, timeout=180); msg = resp.json().get("message",{})
content = msg.get("content","")
if use_cache: self._cache[key] = content
self.call_stats["fast" if fast else "main"] += 1
return type("R",(),{"content":content,"tokens":len(content.split()),"cached":False})
except Exception as e:
self.call_stats["errors"] += 1; log.error(f"LLM error: {e}")
return type("R",(),{"content":f"[Erreur LLM: {e}]","tokens":0,"cached":False})
''')
# ============================================================
# 2. core/cybershield.py
# ============================================================
write_file("core/cybershield.py", '''
import ast, re, logging
log = logging.getLogger("vortex.cybershield")
CRITICAL_PATTERNS = {
"shell_injection": (r"os\\.system|os\\.popen|subprocess\\.(call|Popen|run)", -40),
"code_injection": (r"\\beval\\s*\\(|\\bexec\\s*\\(", -35),
"pickle_exploit": (r"\\bpickle\\.(loads|load)\\b", -30),
"import_hijack": (r"__import__\\s*\\(", -25),
"file_overwrite": (r"open\\s*\\([^)]*['\\\"w]['\\\"\\)]", -15),
"network_exfil": (r"(requests|urllib|aiohttp|socket)\\.", -10),
}
WARNING_PATTERNS = {
"bare_except": (r"except\\s*:", -8),
"infinite_loop": (r"while\\s+True(?!.*break)", -12),
"global_var": (r"\\bglobal\\s+\\w+", -5),
"hardcoded_secret": (r"(password|secret|token|api_key)\\s*=\\s*['\\\"][^'\\\"]{4,}", -15),
}
class CyberShield:
def __init__(self, llm_engine=None):
self.llm = llm_engine; self.scan_history = []
def scan_ast(self, code):
issues = []
try: tree = ast.parse(code)
except SyntaxError as e: return 0.0, [f"SyntaxError: {e}"]
for node in ast.walk(tree):
if isinstance(node, (ast.Import, ast.ImportFrom)):
mods = [a.name.split(".")[0] for a in node.names] if isinstance(node, ast.Import) else ([node.module.split(".")[0]] if node.module else [])
for m in mods:
if m in {"os","subprocess","socket","pickle","ctypes","importlib","shutil","signal","resource","mmap","cffi"}:
issues.append(f"Import risqué : {m}")
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id in {"eval","exec","compile","__import__"}:
issues.append(f"Appel dangereux : {node.func.id}()")
return len(issues), issues
def scan_patterns(self, code):
score, issues = 100.0, []
for name, (pattern, penalty) in {**CRITICAL_PATTERNS, **WARNING_PATTERNS}.items():
if re.search(pattern, code, re.MULTILINE):
issues.append(f"{name.replace('_',' ').title()} (−{abs(penalty)}pts)")
score += penalty
return max(0, score), issues
async def full_scan(self, code, context=""):
ast_cnt, ast_issues = self.scan_ast(code)
pat_score, pat_issues = self.scan_patterns(code)
base_score = max(0, pat_score - min(ast_cnt * 20, 60))
llm_score, llm_note = base_score, ""
if self.llm and base_score > 20:
try:
resp = await self.llm.call(None, "Expert sécurité Python.", f"Analyse sécurité : {code[:1000]}\\nScore actuel : {base_score}\\nRép : SCORE=XX NOTE=...", max_tokens=60, temperature=0.1, fast=True)
m_s = re.search(r"SCORE=([0-9]+)", resp.content)
if m_s: llm_score = (float(m_s.group(1)) + base_score) / 2
m_n = re.search(r"NOTE=(.+)", resp.content)
if m_n: llm_note = m_n.group(1).strip()
except: pass
final = round(llm_score, 1); level = "CRITICAL" if final < 30 else ("WARNING" if final < 65 else "SAFE")
result = {"score":final,"level":level,"passed":final>=60,"ast_issues":ast_issues,"pattern_issues":pat_issues,"llm_note":llm_note,"all_issues":list(set(ast_issues+pat_issues))[:8]}
self.scan_history.append(result); log.info(f"CyberShield: {level} ({final}/100)"); return result
def quick_scan(self, code):
_, ast_issues = self.scan_ast(code)
_, pat_issues = self.scan_patterns(code)
critical = [i for i in pat_issues if any(k in i.lower() for k in ["shell","injection","pickle","import hijack"])]
return len(ast_issues) == 0 and len(critical) == 0
def stats(self):
if not self.scan_history: return {"total":0}
passed = sum(1 for s in self.scan_history if s["passed"])
return {"total":len(self.scan_history),"passed":passed,"blocked":len(self.scan_history)-passed,"avg_score":round(sum(s["score"] for s in self.scan_history)/len(self.scan_history),1)}
''')
# ============================================================
# 3. core/web_search.py
# ============================================================
write_file("core/web_search.py", '''
import asyncio, hashlib, json, logging, time, aiohttp
from pathlib import Path
log = logging.getLogger("vortex.websearch")
CACHE_DIR = Path("data/web_cache"); CACHE_DIR.mkdir(parents=True, exist_ok=True)
class WebSearchAgent:
def __init__(self, llm_engine=None): self.llm = llm_engine; self.search_log = []
def _cache_key(self, query): return CACHE_DIR / (hashlib.sha256(query.encode()).hexdigest()[:16] + ".json")
def _load_cache(self, query):
k = self._cache_key(query)
if k.exists():
data = json.loads(k.read_text())
if time.time() - data.get("ts", 0) < 3600: return data["results"]
return None
def _save_cache(self, query, results):
self._cache_key(query).write_text(json.dumps({"results": results, "ts": time.time()}, ensure_ascii=False))
async def search_duckduckgo(self, query, max_results=5):
cached = self._load_cache(query)
if cached: return cached
results = []
try:
url = f"https://api.duckduckgo.com/?q={query}&format=json&no_html=1&skip_disambig=1"
async with aiohttp.ClientSession() as session:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=8)) as resp:
data = await resp.json(content_type=None)
for item in data.get("RelatedTopics", [])[:max_results]:
if isinstance(item, dict) and "Text" in item:
results.append({"title": item.get("Text","")[:100], "snippet": item.get("Text","")[:300], "url": item.get("FirstURL","")})
if data.get("Abstract"): results.insert(0, {"title": data.get("Heading",""), "snippet": data.get("Abstract","")[:400], "url": data.get("AbstractURL","")})
except Exception as e:
log.warning(f"DuckDuckGo unavailable: {e}")
results = [{"title": f"Recherche: {query}", "snippet": "Web indisponible.", "url": ""}]
self._save_cache(query, results); return results
async def research_for_rsi(self, domain):
queries = [f"{domain} optimization algorithm Python", f"best {domain} techniques 2024", f"{domain} benchmark comparison"]
all_snippets = []
for q in queries[:2]:
results = await self.search_duckduckgo(q, max_results=3)
all_snippets.extend([r["snippet"] for r in results if r["snippet"]])
if not all_snippets: return f"Aucune information web pour : {domain}"
raw_context = "\\n".join(all_snippets[:6])[:2000]
if self.llm:
try:
resp = await self.llm.call(None, "Tu résumes des résultats de recherche.", f"Résume en 3 points clés les techniques de {domain} :\\n{raw_context}\\n\\nRésumé :", max_tokens=150, temperature=0.15, fast=True, profile="summary")
summary = resp.content.strip()
if len(summary) > 20:
self.search_log.append({"domain":domain,"summary":summary,"ts":time.time()})
return f"[WebSearch] {domain} :\\n{summary}"
except: pass
return f"[WebSearch] {domain} :\\n" + raw_context[:500]
''')
# ============================================================
# 4. core/gitautofix.py
# ============================================================
write_file("core/gitautofix.py", '''
import subprocess, sys, tempfile, shutil, ast, re, logging, time
from pathlib import Path
log = logging.getLogger("vortex.gitautofix")
PATCHES_DIR = Path("data/git_patches"); PATCHES_DIR.mkdir(parents=True, exist_ok=True)
class GitAutoFix:
def __init__(self, llm_engine, cybershield):
self.llm=llm_engine; self.shield=cybershield; self.patch_log=[]; self.fix_count=0
def detect_bugs(self, code):
bugs = []
try: ast.parse(code)
except SyntaxError as e: bugs.append(f"SyntaxError ligne {e.lineno}: {e.msg}")
patterns = [(r"(\\w+)\\s*=\\s*\\1\\s*\\+\\s*1", "Auto-incrémentation sans déclaration"), (r"for\\s+\\w+\\s+in\\s+range\\([^)]*\\):\\s*$", "Boucle sans corps"), (r"def\\s+\\w+\\([^)]*\\):\\s*$", "Fonction sans corps"), (r"if\\s+\\w+\\s*=\\s*\\w+", "Assignation dans condition (=)")]
for pattern, msg in patterns:
if re.search(pattern, code, re.MULTILINE): bugs.append(msg)
return bugs
def run_tests(self, code):
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write(code); tmp = Path(f.name)
try:
proc = subprocess.run([sys.executable, str(tmp)], capture_output=True, text=True, timeout=8)
return {"passed": proc.returncode == 0, "stdout": proc.stdout[:300], "stderr": proc.stderr[:300]}
except subprocess.TimeoutExpired: return {"passed": False, "stderr": "Timeout"}
except Exception as e: return {"passed": False, "stderr": str(e)}
finally: tmp.unlink(missing_ok=True)
async def fix_code(self, code, error_context=""):
bugs = self.detect_bugs(code)
if not bugs and not error_context: return {"fixed":False,"reason":"Aucun bug","code":code}
prompt = (f"ÉTAPE 1 — Identifie les bugs.\\nÉTAPE 2 — Cause racine.\\nÉTAPE 3 — Code corrigé.\\n\\nBugs: {bugs}\\nErreur: {error_context[:300]}\\n\\nCODE:\\n{code[:2000]}\\n\\nCODE CORRIGÉ (uniquement):")
try:
resp = await self.llm.call(None, "Expert Python.", prompt, max_tokens=800, temperature=0.2, profile="reasoning")
fixed_code = resp.content.strip().replace("```python","").replace("```","").strip()
try: ast.parse(fixed_code)
except SyntaxError as e: return {"fixed":False,"reason":f"Patch invalide: {e}","code":code}
if not self.shield.quick_scan(fixed_code): return {"fixed":False,"reason":"Blocage CyberShield","code":code}
test_result = self.run_tests(fixed_code)
patch_id = f"fix_{int(time.time())}"
(PATCHES_DIR / f"{patch_id}.py").write_text(fixed_code)
self.fix_count += 1
return {"fixed":True,"patch_id":patch_id,"bugs_fixed":bugs,"test_passed":test_result["passed"],"code":fixed_code}
except Exception as e: return {"fixed":False,"reason":str(e),"code":code}
async def devin_deploy(self, filepath, auto_fix=True):
path = Path(filepath)
if not path.exists(): return {"status":"error","reason":"fichier introuvable"}
original = path.read_text(); backup = path.with_suffix(".py.bak"); shutil.copy(path, backup)
orig_test = self.run_tests(original)
if orig_test["passed"] and not self.detect_bugs(original): return {"status":"ok","reason":"Aucun bug"}
if auto_fix:
fix_result = await self.fix_code(original, orig_test.get("stderr",""))
if fix_result["fixed"]:
final_test = self.run_tests(fix_result["code"])
if final_test["passed"]:
path.write_text(fix_result["code"]); return {"status":"deployed","file":filepath,"bugs_fixed":fix_result["bugs_fixed"]}
else:
shutil.copy(backup, path); return {"status":"rollback","reason":"Tests échoués après correction"}
return {"status":"unchanged","bugs":self.detect_bugs(original)}
''')
# ============================================================
# 5. federation/nexus.py
# ============================================================
write_file("federation/nexus.py", '''
import asyncio, aiohttp, logging
log = logging.getLogger("vortex.nexus")
class NexusCore:
def __init__(self, base_url="http://localhost:7861"): self.base_url = base_url; self.peers = []
async def register_peer(self, url):
try:
async with aiohttp.ClientSession() as s:
async with s.get(f"{url}/health", timeout=3) as r:
if r.status == 200:
if url not in self.peers: self.peers.append(url); log.info(f"Pair: {url}")
return True
except: pass
return False
async def get_agents(self, url):
try:
async with aiohttp.ClientSession() as s:
async with s.get(f"{url}/api/agents", timeout=5) as r:
if r.status == 200: return (await r.json()).get("agents", [])
except: return []
async def broadcast_task(self, task, agent_type=None):
results = {}
for peer in self.peers:
try:
async with aiohttp.ClientSession() as s:
payload = {"task": task, "agent_type": agent_type or "all"}
async with s.post(f"{peer}/api/execute", json=payload, timeout=10) as r:
if r.status == 200:
results[peer] = await r.json()
except: pass
return results
nexus = NexusCore()
''')
# ============================================================
# 6. core/nexus_server.py
# ============================================================
write_file("core/nexus_server.py", '''
import asyncio, json, time, logging
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import uvicorn
log = logging.getLogger("vortex.nexus.server")
app = FastAPI(title="Nexus Core", version="1.0")
class AgentData(BaseModel): id: str; code: str; score: float; ts: float
class TaskRequest(BaseModel): task: str; agent_type: str = "all"
agents_db = {}
@app.get("/health")
async def health(): return {"status": "ok", "ts": time.time()}
@app.get("/api/agents")
async def list_agents(): return {"agents": list(agents_db.values())}
@app.post("/api/agents")
async def add_agent(agent: AgentData):
agents_db[agent.id] = agent.dict()
log.info(f"Agent reçu: {agent.id} (score {agent.score})")
return {"status": "ok", "id": agent.id}
@app.post("/api/execute")
async def execute_task(req: TaskRequest):
# Simule l'exécution d'une tâche
return {"status": "ok", "result": f"Task '{req.task}' executed on {req.agent_type}"}
def run_nexus_server(port=7861):
log.info(f"Démarrage Nexus Server sur le port {port}")
uvicorn.run(app, host="0.0.0.0", port=port, log_level="warning")
''')
# ============================================================
# 7. benchmarks/swe_bench_adapter.py
# ============================================================
write_file("benchmarks/swe_bench_adapter.py", '''
import subprocess, sys, tempfile, json, random
from pathlib import Path
PROBLEMS = [
{"id": "fib", "desc": "Fibonacci", "code": "def fib(n): return n if n<2 else fib(n-1)+fib(n-2)", "test": "assert fib(10)==55"},
{"id": "rev", "desc": "Reverse list", "code": "def rev(l): return l[::-1]", "test": "assert rev([1,2,3])==[3,2,1]"},
{"id": "pal", "desc": "Palindrome", "code": "def pal(s): return s==s[::-1]", "test": "assert pal('radar') and not pal('hello')"},
{"id": "vowels", "desc": "Count vowels", "code": "def count(s): return sum(1 for c in s if c in 'aeiou')", "test": "assert count('hello')==2"},
{"id": "max_list", "desc": "Max", "code": "def max_l(l): return max(l)", "test": "assert max_l([1,5,3])==5"},
]
def run_swe_bench(agent_code, max_instances=4):
passed = 0
for p in PROBLEMS[:max_instances]:
full = agent_code + "\\n" + p["code"] + "\\n" + p["test"]
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write(full); tmp = Path(f.name)
try:
r = subprocess.run([sys.executable, str(tmp)], capture_output=True, timeout=4)
if r.returncode == 0: passed += 1
except: pass
finally: tmp.unlink(missing_ok=True)
return {"resolved": passed, "total": max_instances, "rate": round(passed/max_instances*100, 1)}
''')
# ============================================================
# 8. core/rsi_loop.py
# ============================================================
write_file("core/rsi_loop.py", '''
import asyncio, importlib.util, shutil, time, math, json, logging
from pathlib import Path
log = logging.getLogger("vortex.rsi")
ARCHIVE_DIR = Path("data/rsi_archive"); ARCHIVE_DIR.mkdir(parents=True, exist_ok=True)
DREAM_PATH = Path("data/dream_state.json")
BENCH_FUNCS = [
("sphere", lambda p: sum(x**2 for x in p), [(-5,5)]*5, 0.0),
("rastrigin", lambda p: 10*len(p)+sum(xi**2-10*math.cos(2*math.pi*xi) for xi in p), [(-5.12,5.12)]*5, 0.0),
("rosenbrock", lambda p: sum(100*(p[i+1]-p[i]**2)**2+(1-p[i])**2 for i in range(len(p)-1)), [(-2,2)]*5, 0.0),
]
class RSILoop:
def __init__(self, llm, opt_path, shield=None, web=None, gitfix=None):
self.llm=llm; self.path=Path(opt_path); self.backup=self.path.with_suffix(".py.bak")
self.shield=shield; self.web=web; self.gitfix=gitfix
self.generation=0; self.best_score=self._bench_file(self.path); self.archive=self._load_archive()
log.info(f"RSI v2 init — score={self.best_score:.4f} arch={len(self.archive)}")
def _load_archive(self):
idx = ARCHIVE_DIR / "index.json"
return json.loads(idx.read_text()) if idx.exists() else []
def _save_archive(self):
(ARCHIVE_DIR/"index.json").write_text(json.dumps(self.archive, indent=2))
def _bench_module(self, mod):
try: Opt = getattr(mod, "FellowOptimizer")
except: return 0.0
scores = []
for _, fn, bounds, opt_v in BENCH_FUNCS:
try:
o = Opt(fn, bounds, max_evals=100)
best, _ = o.optimize()
scores.append(1.0 - math.tanh(abs(fn(best)-opt_v)))
except: scores.append(0.0)
return sum(scores)/len(scores) if scores else 0.0
def _bench_file(self, path):
try:
spec = importlib.util.spec_from_file_location("_b", path)
mod = importlib.util.module_from_spec(spec); spec.loader.exec_module(mod)
return self._bench_module(mod)
except: return 0.0
def _load_dream(self):
if DREAM_PATH.exists():
try:
state = json.loads(DREAM_PATH.read_text())
summary = state.get("summary","")
if summary and len(summary)>20: return f"\\n\\nCONTEXTE MÉMORIEL:\\n{summary[:400]}\\n"
except: pass
return ""
async def _gen_variant(self):
if not self.llm: return None
current = self.path.read_text()
dream_ctx = self._load_dream()
top_arc = sorted(self.archive, key=lambda x:x["score"], reverse=True)[:2]
arc_ctx = "\\n".join([f"# Archive score={a['score']:.3f}\\n{a['snippet']}" for a in top_arc])
web_ctx = ""
if self.web:
try: web_ctx = await asyncio.wait_for(self.web.research_for_rsi("evolutionary optimization Python"), timeout=6.0)
except: web_ctx = ""
prompt = (f"ÉTAPE 1 — Analyse les forces/faiblesses de ce code :\\n{current[:1500]}\\n\\n"
f"ÉTAPE 2 — Identifie la meilleure technique (DE, PSO, CMA-ES).\\n"
f"Archives: {arc_ctx[:400] if arc_ctx else 'aucune'}\\n"
f"Web: {web_ctx[:400] if web_ctx else 'non disponible'}"
f"{dream_ctx}"
f"ÉTAPE 3 — Écris la nouvelle version de FellowOptimizer.\\n"
f"CODE UNIQUEMENT (pas de markdown) :")
try:
resp = await self.llm.call(None, "Expert optimisation.", prompt, max_tokens=900, temperature=0.4, profile="code_gen", use_cache=False)
code = resp.content.strip().replace("```python","").replace("```","").strip()
return code if "class FellowOptimizer" in code else None
except: return None
async def run_generation(self):
self.generation += 1
code = await self._gen_variant()
if not code: return {"gen":self.generation,"action":"skip","reason":"bad_code","score":self.best_score}
if self.shield and not self.shield.quick_scan(code):
return {"gen":self.generation,"action":"blocked","reason":"CyberShield","score":self.best_score}
if self.gitfix:
bugs = self.gitfix.detect_bugs(code)
if bugs:
fix = await self.gitfix.fix_code(code, f"RSI gen {self.generation}")
if fix["fixed"]: code = fix["code"]
tmp = self.path.with_suffix(".py.new")
try:
ast.parse(code)
tmp.write_text(code)
spec = importlib.util.spec_from_file_location("_n", tmp)
mod = importlib.util.module_from_spec(spec); spec.loader.exec_module(mod)
new_score = self._bench_module(mod)
if new_score > self.best_score:
shutil.copy(self.path, self.backup)
shutil.move(str(tmp), str(self.path))
old = self.best_score; self.best_score = new_score
self.archive.append({"gen":self.generation,"score":round(new_score,4),"snippet":code[:300],"ts":time.time()})
self._save_archive()
return {"gen":self.generation,"action":"deployed","old":old,"new":new_score}
self.archive.append({"gen":self.generation,"score":self.best_score*0.85,"snippet":code[:300],"ts":time.time()})
self._save_archive()
return {"gen":self.generation,"action":"archived","score":self.best_score}
except Exception as e: return {"gen":self.generation,"action":"error","reason":str(e)}
finally:
if tmp.exists(): tmp.unlink(missing_ok=True)
def status(self):
return {"generation":self.generation,"best_score":round(self.best_score,4),"archive_size":len(self.archive)}
''')
# ============================================================
# 9. core/agent_registry.py
# ============================================================
write_file("core/agent_registry.py", '''
import logging
log = logging.getLogger("vortex.registry")
class AgentRegistry:
def __init__(self):
self.agents = {}
def register(self, name, instance, description, capabilities=None):
self.agents[name] = {"instance": instance, "description": description, "capabilities": capabilities or []}
log.info(f"Agent enregistré : {name}")
def list(self):
return {name: info["description"] for name, info in self.agents.items()}
def get(self, name):
return self.agents.get(name, {}).get("instance")
def get_capabilities(self, name):
return self.agents.get(name, {}).get("capabilities", [])
def filter_by_capability(self, cap):
return [n for n, info in self.agents.items() if cap in info.get("capabilities", [])]
registry = AgentRegistry()
''')
# ============================================================
# 10. core/judge_layer.py
# ============================================================
write_file("core/judge_layer.py", '''
import asyncio, json, logging, re
log = logging.getLogger("vortex.judge")
class JudgeLayer:
def __init__(self, llm_engine, registry=None):
self.llm = llm_engine; self.registry = registry; self.history = []
async def evaluate_candidates(self, task, candidates):
if not candidates: return {"error": "Aucun candidat"}
if len(candidates) == 1:
name = list(candidates.keys())[0]; return {"winner": name, "scores": {name: 1.0}, "confidence": 1.0}
prompt = f"Juge expert. Évalue les réponses pour la tâche : \\"{task}\\"\\n"
for name, content in candidates.items():
prompt += f"\\n--- Agent {name} ---\\n{content[:1000]}\\n"
prompt += """Attribue un score 0-1 pour chaque (précision, exhaustivité, qualité). Réponds UNIQUEMENT en JSON : {"scores": {"agent1": 0.9, ...}, "winner": "agent1", "confidence": 0.85}"""
try:
resp = await self.llm.call(None, "Expert évaluateur.", prompt, max_tokens=200, temperature=0.1, fast=True)
match = re.search(r"\\{.*\\}", resp.content, re.DOTALL)
if match:
result = json.loads(match.group())
if "winner" in result and result["winner"] not in candidates:
if "scores" in result:
best = max(result["scores"], key=lambda k: result["scores"][k])
result["winner"] = best
return result
else:
best_name = max(candidates, key=lambda k: len(candidates[k]))
return {"winner": best_name, "scores": {k: 0.5 for k in candidates}, "confidence": 0.5}
except Exception as e:
log.error(f"Judge error: {e}")
best_name = max(candidates, key=lambda k: len(candidates[k]))
return {"winner": best_name, "scores": {k: 0.5 for k in candidates}, "confidence": 0.3}
async def dispatch(self, task, agent_names=None):
if self.registry is None: return {"error": "AgentRegistry manquant"}
if agent_names is None: agent_names = list(self.registry.agents.keys())
results = {}; tasks = {}
for name in agent_names:
agent = self.registry.get(name)
if agent is None:
results[name] = "Agent non trouvé"; continue
if hasattr(agent, "run"): tasks[name] = asyncio.create_task(agent.run(task))
elif hasattr(agent, "evaluate"): tasks[name] = asyncio.create_task(agent.evaluate(task))
elif hasattr(agent, "research_for_rsi"): tasks[name] = asyncio.create_task(agent.research_for_rsi(task))
else: results[name] = "Agent sans méthode"
for name, future in tasks.items():
try:
result = await future
if hasattr(result, "content"): results[name] = result.content
elif isinstance(result, dict): results[name] = json.dumps(result, ensure_ascii=False)
else: results[name] = str(result)
except Exception as e: results[name] = f"Erreur: {e}"
judge_result = await self.evaluate_candidates(task, results)
judge_result["all_responses"] = results
self.history.append({"task": task, "judge_result": judge_result})
return judge_result
judge = None
''')
# ============================================================
# 11. core/orchestrator.py
# ============================================================
write_file("core/orchestrator.py", '''
import asyncio, json, logging, re
log = logging.getLogger("vortex.orchestrator")
class Orchestrator:
def __init__(self, llm_engine, judge_layer, registry):
self.llm = llm_engine; self.judge = judge_layer; self.registry = registry
self.roles = {"planner": "Planifier l'architecture", "writer": "Écrire le code", "reviewer": "Vérifier sécurité", "optimizer": "Optimiser performances"}
async def decompose(self, task):
prompt = f"Décompose cette tâche en 2-4 sous-tâches claires. Tâche : {task}\\nRetourne UNIQUEMENT une liste JSON : [\\"sous-tâche 1\\", ...]"
try:
resp = await self.llm.call(None, "Planificateur.", prompt, max_tokens=200, temperature=0.3, fast=True)
match = re.search(r"\\[.*\\]", resp.content, re.DOTALL)
if match:
subtasks = json.loads(match.group()); return [s.strip() for s in subtasks if s.strip()]
else: return [task]
except Exception as e: log.error(f"Decomp error: {e}"); return [task]
async def run(self, task, roles=None):
if roles is None: roles = self.roles
subtasks = await self.decompose(task)
if not subtasks: return {"error": "Impossible de décomposer"}
assigned = {}
role_names = list(roles.keys())
for i, st in enumerate(subtasks):
role = role_names[i % len(role_names)]
assigned[role] = st
results = {}
for role, st in assigned.items():
log.info(f"Orchestrator: {role} → {st}")
judge_result = await self.judge.dispatch(st)
results[role] = {
"task": st,
"winner": judge_result.get("winner"),
"score": judge_result.get("confidence", 0),
"content": judge_result.get("all_responses", {}).get(judge_result.get("winner"), "")
}
synth_prompt = f"Tâche initiale : {task}\\nSynthétise les résultats :\\n"
for role, data in results.items():
synth_prompt += f"\\n--- {role} ---\\n{data['content'][:500]}\\n"
synth_prompt += "\\nDonne la solution finale complète."
try:
synth_resp = await self.llm.call(None, "Synthétiseur.", synth_prompt, max_tokens=800, temperature=0.2, profile="reasoning")
synthesis = synth_resp.content.strip()
except Exception as e: synthesis = f"Erreur synthèse: {e}"
return {"task": task, "subtasks": assigned, "results": results, "synthesis": synthesis}
orchestrator = None
''')
# ============================================================
# 12. core/agent_manager.py
# ============================================================
write_file("core/agent_manager.py", '''
import logging
log = logging.getLogger("vortex.agent_manager")
class AgentManager:
def __init__(self, registry, judge, orchestrator):
self.registry = registry; self.judge = judge; self.orchestrator = orchestrator; self.default_agent = None
async def multi(self, task, agent_names=None): return await self.judge.dispatch(task, agent_names)
async def judge_task(self, task): return await self.judge.dispatch(task)
async def orchestrate(self, task, roles=None): return await self.orchestrator.run(task, roles)
async def switch_agent(self, name):
if name in self.registry.agents:
self.default_agent = name; return True
return False
def list_agents(self): return self.registry.list()
''')
# ============================================================
# 13. core/optimizer.py
# ============================================================
write_file("core/optimizer.py", '''
import random, math
class FellowOptimizer:
def __init__(self, func, bounds, max_evals=200):
self.func=func; self.bounds=bounds; self.max_evals=max_evals
def optimize(self):
dim=len(self.bounds); F,CR=0.8,0.9
pop=[[random.uniform(l,u) for l,u in self.bounds] for _ in range(15)]
vals=[self.func(p) for p in pop]
best=pop[vals.index(min(vals))][:]; best_val=min(vals); evals=15
while evals < self.max_evals:
for i in range(len(pop)):
a,b,c=random.sample([j for j in range(len(pop)) if j!=i],3)
trial=[max(l,min(u,pop[a][d]+F*(pop[b][d]-pop[c][d]))) if random.random()<CR else pop[i][d] for d,(l,u) in enumerate(self.bounds)]
v=self.func(trial); evals+=1
if v<vals[i]:
pop[i]=trial; vals[i]=v
if v<best_val:
best=trial[:]; best_val=v
if evals>=self.max_evals: break
return best,[best_val]
''')
# ============================================================
# 14. core/peer_review.py
# ============================================================
write_file("core/peer_review.py", '''
import re
class PeerReview:
def __init__(self, llm, shield=None): self.llm=llm; self.shield=shield
async def evaluate(self, code, context=""):
if not code: return {"score":0,"passed":False}
if self.shield:
r=await self.shield.full_scan(code,context)
if not r["passed"]: return {"score":0,"passed":False,"comment":"CyberShield bloque","issues":r["all_issues"]}
if not self.llm: return {"score":0.7,"passed":True}
try:
resp=await self.llm.call(None,"Expert revue.",f"Note ce code (0-1):\\n{code[:1200]}\\nRéponds: SCORE=0.XX", max_tokens=60, fast=True)
m=re.search(r"SCORE=([0-9.]+)", resp.content); s=float(m.group(1)) if m else 0.7
return {"score":round(s,3),"passed":s>=0.6}
except: return {"score":0.65,"passed":True}
''')
# ============================================================
# 15. core/dream_consolidator.py
# ============================================================
write_file("core/dream_consolidator.py", '''
import json, time, logging; from pathlib import Path
log=logging.getLogger("vortex.dream"); DREAM_PATH=Path("data/dream_state.json")
class DreamConsolidator:
def __init__(self, mem, llm):
self.mem=mem; self.llm=llm
self.state = {}
if DREAM_PATH.exists():
try: self.state = json.loads(DREAM_PATH.read_text())
except: self.state = {"summary":"","last_ts":0,"count":0}
else: self.state = {"summary":"","last_ts":0,"count":0}
def _save(self): DREAM_PATH.write_text(json.dumps(self.state, indent=2))
async def consolidate(self):
entries = self.mem.retrieve("", top_k=30) if self.mem else []
ctx = "\\n".join([e.content[:200] for e in entries if hasattr(e,"content")])
if not ctx:
self.state["summary"] = "Aucun apprentissage récent."
self._save()
return self.state
try:
resp = await self.llm.call(None, "Résumeur.", f"Résume en 5 points les apprentissages :\\n{ctx[:3000]}\\nRésumé :", max_tokens=200, fast=True, profile="summary")
self.state.update({"summary":resp.content.strip(),"last_ts":time.time(),"count":self.state.get("count",0)+1})
self._save()
except Exception as e:
log.error(f"Dream consolidation failed: {e}")
return self.state
''')
# ============================================================
# 16. core/autodev.py
# ============================================================
write_file("core/autodev.py", '''
import shutil, ast
class AutoDev:
def __init__(self, llm): self.llm=llm
async def improve(self, filepath):
from pathlib import Path
path=Path(filepath)
if not path.exists(): return {"error":"File not found"}
orig=path.read_text()
try:
resp=await self.llm.call(None,"Expert Python.",f"Améliore ce code:\\n{orig[:2000]}\\nCode uniquement:", max_tokens=900, temperature=0.4)
new=resp.content.strip().replace("```python","").replace("```","").strip()
try:
ast.parse(new)
except SyntaxError as e:
return {"error":f"SyntaxError dans le patch: {e}"}
backup=path.with_suffix(".py.bak"); shutil.copy(path,backup); path.write_text(new)
return {"status":"deployed","backup":str(backup)}
except Exception as e: return {"error":str(e)}
''')
# ============================================================
# 17. sandbox/secure_sandbox.py
# ============================================================
write_file("sandbox/secure_sandbox.py", '''
import subprocess, sys, tempfile, resource; from pathlib import Path
def run_in_sandbox(code, timeout=5, memory_mb=128):
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
f.write(code); tmp=Path(f.name)
try:
def _lim():
try: resource.setrlimit(resource.RLIMIT_AS,(memory_mb*1024*1024,memory_mb*1024*1024)); resource.setrlimit(resource.RLIMIT_CPU,(timeout,timeout+1))
except: pass
proc=subprocess.Popen([sys.executable,str(tmp)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=_lim)
out,err=proc.communicate(timeout=timeout+1)
return {"success":proc.returncode==0,"result":out.decode()[:2000],"error":err.decode()[:500]}
except subprocess.TimeoutExpired: proc.kill(); return {"success":False,"error":"Timeout"}
except Exception as e: return {"success":False,"error":str(e)}
finally: tmp.unlink(missing_ok=True)
''')
# ============================================================
# 18. benchmarks/humaneval_adapter.py
# ============================================================
write_file("benchmarks/humaneval_adapter.py", '''
import subprocess, sys, tempfile; from pathlib import Path
CASES=[("def add(a,b): return a+b","assert add(2,3)==5"),("def is_even(n): return n%2==0","assert is_even(4)"),("def fact(n): return 1 if n<=1 else n*fact(n-1)","assert fact(5)==120"),("def rev(s): return s[::-1]","assert rev('abc')=='cba'"),("def fib(n): return n if n<2 else fib(n-1)+fib(n-2)","assert fib(10)==55")]
def run_humaneval(preamble="",max_cases=5):
passed=0
for sol,test in CASES[:max_cases]:
full=(preamble+"\\n"+sol+"\\n"+test) if preamble else sol+"\\n"+test
with tempfile.NamedTemporaryFile(mode="w",suffix=".py",delete=False) as f: f.write(full); tmp=Path(f.name)
try:
r=subprocess.run([sys.executable,str(tmp)],capture_output=True,timeout=3)
if r.returncode==0: passed+=1
except: pass
finally: tmp.unlink(missing_ok=True)
return {"pass@1":round(passed/max_cases*100,1),"passed":passed,"total":max_cases}
''')
# ============================================================
# 19. core/hf_mcp.py – Intégration Hugging Face MCP
# ============================================================
write_file("core/hf_mcp.py", '''
import asyncio, json, logging, os, aiohttp
from typing import Dict, List, Optional
log = logging.getLogger("vortex.hf_mcp")
HF_MCP_URL = os.environ.get("HF_MCP_URL", "https://huggingface.co/api")
class HFMCPClient:
def __init__(self, use_mcp=False):
self.use_mcp = use_mcp
self.hub_api = "https://huggingface.co/api"
self.history = []
async def _hub_api_request(self, method: str, params: Dict) -> Dict:
if method == "search_models":
query = params.get("query", "")
url = f"{self.hub_api}/models?search={query}&limit=10"
async with aiohttp.ClientSession() as session:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as resp:
if resp.status != 200:
return {"error": f"API error {resp.status}"}
data = await resp.json()
return {"models": [{"id": m["id"], "downloads": m.get("downloads", 0)} for m in data[:5]]}
elif method == "search_datasets":
query = params.get("query", "")
url = f"{self.hub_api}/datasets?search={query}&limit=10"
async with aiohttp.ClientSession() as session:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as resp:
if resp.status != 200:
return {"error": f"API error {resp.status}"}
data = await resp.json()
return {"datasets": [{"id": d["id"]} for d in data[:5]]}
elif method == "get_space":
space_name = params.get("name", "")
url = f"{self.hub_api}/spaces/{space_name}"
async with aiohttp.ClientSession() as session:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as resp:
if resp.status != 200:
return {"error": f"Space not found or API error {resp.status}"}
data = await resp.json()
return {"space": {"id": data.get("id"), "likes": data.get("likes", 0)}}
elif method == "search_papers":
query = params.get("query", "")
url = f"http://export.arxiv.org/api/query?search_query=all:{query}&start=0&max_results=5"
async with aiohttp.ClientSession() as session:
try:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=15)) as resp:
if resp.status != 200:
return {"error": f"arXiv error {resp.status}"}
text = await resp.text()
import xml.etree.ElementTree as ET
root = ET.fromstring(text)
papers = []
for entry in root.findall("{http://www.w3.org/2005/Atom}entry"):
title = entry.find("{http://www.w3.org/2005/Atom}title")
if title is not None:
papers.append({"title": title.text.strip(), "id": "N/A"})
return {"papers": papers[:3]}
except Exception as e:
log.warning(f"arXiv API error: {e}")
return {"papers": [{"title": f"Recherche simulée pour '{query}'", "id": "arxiv"}]}
return {"error": "Méthode non supportée"}
async def search_models(self, query: str, limit: int = 5) -> List[Dict]:
result = await self._hub_api_request("search_models", {"query": query, "limit": limit})
return result.get("models", [])
async def search_datasets(self, query: str, limit: int = 5) -> List[Dict]:
result = await self._hub_api_request("search_datasets", {"query": query, "limit": limit})
return result.get("datasets", [])
async def get_space_info(self, space_name: str) -> Dict:
result = await self._hub_api_request("get_space", {"name": space_name})
return result.get("space", {})
async def search_papers(self, query: str) -> List[Dict]:
result = await self._hub_api_request("search_papers", {"query": query})
return result.get("papers", [])
def stats(self) -> Dict:
return {"history": len(self.history)}
hf_mcp = HFMCPClient(use_mcp=False)
''')
# ============================================================
# 20. app.py – VERSION HEADLESS FASTAPI (déjà fournie)
# ============================================================
write_file("app.py", '''
#!/usr/bin/env python3
import os, sys, asyncio, json, logging, time
from pathlib import Path
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import uvicorn
import nest_asyncio
nest_asyncio.apply()
BASE = Path("/app")
os.chdir(BASE); sys.path.insert(0, str(BASE))
DATA_DIR = Path(os.environ.get("DATA_DIR", "/data"))
DATA_DIR.mkdir(parents=True, exist_ok=True)
logging.basicConfig(level=logging.INFO, format="%(levelname)s [%(name)s] %(message)s")
log = logging.getLogger("vortex.headless")
from agents.cognitive_engine import MultiLLMEngine
from core.cybershield import CyberShield
from core.web_search import WebSearchAgent
from core.gitautofix import GitAutoFix
from core.rsi_loop import RSILoop
from core.peer_review import PeerReview
from core.autodev import AutoDev
from core.dream_consolidator import DreamConsolidator
from federation.nexus import nexus
from sandbox.secure_sandbox import run_in_sandbox
from benchmarks.humaneval_adapter import run_humaneval
from benchmarks.swe_bench_adapter import run_swe_bench
from core.agent_registry import registry
from core.judge_layer import JudgeLayer
from core.orchestrator import Orchestrator
from core.agent_manager import AgentManager
from core.hf_mcp import hf_mcp
class _Mem:
def __init__(self): self.store=[]
def ingest(self,c,**k): self.store.append({"content":c})
def retrieve(self,q,top_k=5): return [type("E",(),{"content":e["content"]})() for e in self.store[-top_k:]]
class _K:
def run_code(self,code,timeout=10): return run_in_sandbox(code,timeout=int(timeout))
def get_health(self):
try: import psutil; cpu=psutil.cpu_percent(0.1); mem=psutil.virtual_memory().percent
except: cpu,mem=0.0,0.0
return {"status":"healthy","cpu_percent":cpu,"memory_percent":mem}
KERNEL=_K(); mem=_Mem(); llm=MultiLLMEngine(mem); shield=CyberShield(llm); web=WebSearchAgent(llm)
gitfix=GitAutoFix(llm, shield); pr=PeerReview(llm, shield)
rsi=RSILoop(llm, "core/optimizer.py", shield, web, gitfix)
autodev=AutoDev(llm); dreamer=DreamConsolidator(mem, llm)
registry.register("rsi", rsi, "Auto-amélioration récursive", ["code_gen","optimization"])
registry.register("shield", shield, "Analyse de sécurité CyberShield", ["security","audit"])
registry.register("gitfix", gitfix, "Correction automatique de bugs", ["code_fix","deployment"])
registry.register("web", web, "Recherche web structurée", ["research","search"])
registry.register("dreamer", dreamer, "Consolidation mémoire", ["memory"])
registry.register("nexus", nexus, "Fédération P2P", ["federation"])
judge = JudgeLayer(llm, registry)
orchestrator = Orchestrator(llm, judge, registry)
manager = AgentManager(registry, judge, orchestrator)
# Scheduler
try:
from apscheduler.schedulers.background import BackgroundScheduler
sched=BackgroundScheduler(timezone="UTC")
def _j(coro):
loop=asyncio.new_event_loop()
try: loop.run_until_complete(coro)
finally: loop.close()
sched.add_job(lambda:_j(rsi.run_generation()), "interval", hours=2, id="rsi")
sched.add_job(lambda:_j(dreamer.consolidate()), "cron", hour=3, id="dream")
sched.start()
log.info("✅ Scheduler: RSI/2h, Dream/3h")
except Exception as e:
log.warning(f"Scheduler non démarré: {e}")
# ---- FastAPI ----
app = FastAPI(title="VORTEX v5.1 API", version="5.1")
class CommandRequest(BaseModel):
command: str
args: dict = {}
class CodeRequest(BaseModel):
code: str
timeout: int = 10
@app.get("/")
async def root():
return {"message": "VORTEX v5.1 Headless API"}
@app.post("/command")
async def execute_command(req: CommandRequest):
msg = req.command
if msg.startswith("@health"):
h = KERNEL.get_health()
return f"CPU {h['cpu_percent']:.0f}% · RAM {h['memory_percent']:.0f}% · RSI gen {rsi.generation}"
elif msg.startswith("@rsi"):
r = await rsi.run_generation()
return f"RSI gen={r['gen']} score={r.get('new', r.get('score',0))}"
elif msg.startswith("@shield"):
code = msg.replace("@shield","").strip()
if not code: return "Syntaxe: @shield <code>"
r = await shield.full_scan(code)
return f"{r['level']} {r['score']}/100 · Issues: {r['all_issues']}"
elif msg.startswith("@gitfix"):
path = msg.replace("@gitfix","").strip() or "core/optimizer.py"
r = await gitfix.devin_deploy(path)
return f"GitAutoFix: {r.get('status','?')} - {r.get('reason','')}"
elif msg.startswith("@web"):
q = msg.replace("@web","").strip() or "optimization"
r = await web.research_for_rsi(q)
return r[:600]
elif msg.startswith("@multi"):
parts = msg.split("--agents")
task = parts[0].replace("@multi","").strip()
agents = None
if len(parts)>1: agents = [a.strip() for a in parts[1].strip().split(",")]
result = await manager.multi(task, agents)
return f"Gagnant: {result.get('winner','?')} (confiance {result.get('confidence',0)})"
elif msg.startswith("@judge"):
task = msg.replace("@judge","").strip()
if not task: return "Syntaxe: @judge <tâche>"
result = await manager.judge_task(task)
return f"Meilleur agent: {result.get('winner','?')}"
elif msg.startswith("@orchestrate"):
task = msg.replace("@orchestrate","").strip()
if not task: return "Syntaxe: @orchestrate <tâche>"
result = await manager.orchestrate(task)
return f"Synthèse: {result.get('synthesis','')[:500]}"
elif msg.startswith("@hfsearch"):
query = msg.replace("@hfsearch","").strip()
if not query: return "Syntaxe: @hfsearch <requête>"
models = await hf_mcp.search_models(query)
return "\\n".join([f"- {m['id']}" for m in models[:5]]) if models else "Aucun modèle"
elif msg.startswith("@nexus"):
parts = msg.replace("@nexus","").strip().split()
if not parts: return "Syntaxe: @nexus add <url> | list | broadcast <tâche>"
if parts[0] == "add":
ok = await nexus.register_peer(parts[1])
return f"Pair {parts[1]} {'ajouté' if ok else 'inaccessible'}"
elif parts[0] == "list":
return f"Pairs: {', '.join(nexus.peers) if nexus.peers else 'Aucun'}"
elif parts[0] == "broadcast":
task = " ".join(parts[1:])
results = await nexus.broadcast_task(task)
return "\\n".join([f"{p}: {r}" for p,r in results.items()])
else: return "Commande invalide"
else:
return "Commande non reconnue"
@app.post("/sandbox")
async def sandbox(req: CodeRequest):
result = KERNEL.run_code(req.code, timeout=req.timeout)
return result
@app.get("/health")
async def health():
h = KERNEL.get_health()
return {"status": "ok", **h}
if __name__ == "__main__":
log.info("🚀 VORTEX v5.1 Headless API démarre sur le port 7860")
uvicorn.run(app, host="0.0.0.0", port=7860)
''')
# ============================================================
# 21. requirements.txt (explicit)
# ============================================================
write_file("requirements.txt", '''
gradio>=4.0.0
nest_asyncio>=1.5.0
psutil>=5.9.0
apscheduler>=3.10.0
aiohttp>=3.9.0
fastapi>=0.100.0
uvicorn>=0.23.0
requests>=2.31.0
pydantic>=2.0.0
''')
# ============================================================
# 22. Dockerfile (pour HF Spaces)
# ============================================================
write_file("Dockerfile", '''
FROM python:3.11-slim
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /app
RUN apt-get update && apt-get install -y curl git && rm -rf /var/lib/apt/lists/*
COPY build_vortex.py .
COPY requirements.txt .
COPY app.py .
RUN python build_vortex.py
RUN pip install --no-cache-dir -r requirements.txt
CMD ["python", "app.py"]
''')
print("✅ Tous les fichiers VORTEX v5.1 ont été générés.")
if __name__ == "__main__":
build_all()