======================================================================== EVOLUTION IA — CONTEXTE RÉEL DE L'APPEL IA ======================================================================== Appel : #773 Provider : openai Rôle : development_engine Phase : economy Modèle demandé : gpt-5.6-luna Tokens entrée fournisseur : 20747 Tokens total fournisseur : 20900 NOTE : copie capturée juste avant l'appel HTTP. Les secrets sont expurgés. ======================================================================== [1] INSTRUCTIONS / SYSTEM PROMPT ======================================================================== Tu es un moteur de développement logiciel. Analyse le projet local, demande les autorisations nécessaires avant de lire ou modifier les fichiers, puis propose des changements précis, réversibles et vérifiables. --- === AUTONOMOUS DEVELOPMENT CHAT — CONTEXTE EFFECTIF === Fichiers PINNED confirmés et vérifiés : - app/Views/auth/form.php | 28966 octets | sha256=819a115653a9e3d92bf02e7cd107c31a74d310d8523a815d6bdf1e81b5b06ac4 - app/Controllers/Auth.php | 3888 octets | sha256=1cf1f5063353cc7ef97175cbd6d6d1461a5e6b684f7b55a29febe572a86a2f20 Schémas REAL DATABASE ciblés : aucun RÈGLE : ce contexte a été assemblé par evolution en lecture seule juste avant cet appel. Ne demande pas à l'administrateur de fournir à nouveau un élément déjà présent. === END AUTONOMOUS DEVELOPMENT CHAT === === CONTEXTE FINAL VERROUILLE evolution — SOURCE DE VERITE === v2.4.7 : tout chemin exact explicite et resolu est promu OBLIGATOIRE, meme s'il avait deja ete decouvert. Les blocs PINNED sont injectes entiers et jamais tronques. Ces fichiers viennent d'etre relus sur le disque juste avant la generation du diff. Les marqueurs COMPLET signifient que le contenu entre BEGIN/END est integral. PLAN D'EXPLORATION EXECUTE PAR evolution : - path: app/Views/auth/form.php => 1 nouveau(x) fichier(s), cible exacte promue OBLIGATOIRE - path: app/Controllers/Auth.php => 1 nouveau(x) fichier(s), cible exacte promue OBLIGATOIRE - search: app/Controllers/Auth.php => 0 nouveau(x) fichier(s) épinglé(s) - route: admin/evolution-ia => 4 nouveau(x) fichier(s) épinglé(s) - route: admin/evolution-ia/stream => 0 nouveau(x) fichier(s) épinglé(s) - route: admin/evolution-ia/transcrire => 1 nouveau(x) fichier(s) épinglé(s) - route: admin/evolution-ia/prompt => 0 nouveau(x) fichier(s) épinglé(s) - route: admin/evolution-ia/systeme/analyser => 1 nouveau(x) fichier(s) épinglé(s) - route: admin/evolution-ia/systeme/mettre-a-jour => 0 nouveau(x) fichier(s) épinglé(s) - PIN path obligatoire -> app/Views/auth/form.php - PIN path obligatoire -> app/Controllers/Auth.php - PIN route admin/evolution-ia -> app/Config/Routes.php - PIN controller route admin/evolution-ia -> app/Controllers/Admin/Development.php - PIN view route admin/evolution-ia -> app/Views/admin/development/recipes.php - PIN view route admin/evolution-ia -> app/Views/admin/development/index.php - PIN route admin/evolution-ia/stream -> app/Config/Routes.php - PIN controller route admin/evolution-ia/stream -> app/Controllers/Admin/Development.php - PIN route admin/evolution-ia/transcrire -> app/Config/Routes.php - PIN controller route admin/evolution-ia/transcrire -> app/Controllers/Admin/DevelopmentVoice.php - PIN route admin/evolution-ia/prompt -> app/Config/Routes.php - PIN controller route admin/evolution-ia/prompt -> app/Controllers/Admin/Development.php - PIN route admin/evolution-ia/systeme/analyser -> app/Config/Routes.php - PIN controller route admin/evolution-ia/systeme/analyser -> app/Controllers/Admin/SystemUpdates.php - PIN route admin/evolution-ia/systeme/mettre-a-jour -> app/Config/Routes.php - PIN controller route admin/evolution-ia/systeme/mettre-a-jour -> app/Controllers/Admin/SystemUpdates.php === CONTEXTE OBLIGATOIRE — CHEMINS EXACTS AUTORISES === --- BEGIN PINNED app/Views/auth/form.php | 28966 octets | sha256=819a115653a9e3d92bf02e7cd107c31a74d310d8523a815d6bdf1e81b5b06ac4 | COMPLET --- <?= esc($title) ?> | Nexum AI
Nexum AI Nexum AI

Matrice de développement

Accès sécurisé à l’administration Nexum AI.

--- END PINNED app/Views/auth/form.php | COMPLET --- --- BEGIN PINNED app/Controllers/Auth.php | 3888 octets | sha256=1cf1f5063353cc7ef97175cbd6d6d1461a5e6b684f7b55a29febe572a86a2f20 | COMPLET --- 'login','title'=>'Connexion']); } public function register(){ return view('auth/form',['mode'=>'register','title'=>'Créer un compte']); } public function forgotPassword(){ return view('auth/form',['mode'=>'forgot','title'=>'Mot de passe oublié']); } public function attempt(){ $user=(new UserModel())->where('email',strtolower(trim((string)$this->request->getPost('email'))))->first(); if(!$user || !password_verify((string)$this->request->getPost('password'),(string)$user['password_hash'])) return redirect()->back()->withInput()->with('error','Identifiants invalides.'); session()->regenerate(); session()->set(['user_id'=>(int)$user['id'],'user_name'=>$user['name'],'is_admin'=>(int)$user['is_admin']]); $target=(string)(session('auth_redirect')?:site_url('admin/evolution-ia')); session()->remove('auth_redirect'); return redirect()->to($target); } public function store(){ $name=trim((string)$this->request->getPost('name')); $email=strtolower(trim((string)$this->request->getPost('email'))); $password=[EXPURGÉ] if($name==='' || !filter_var($email,FILTER_VALIDATE_EMAIL) || strlen($password)<12) return redirect()->back()->withInput()->with('error','Nom, e-mail valide et mot de passe de 12 caractères minimum requis.'); $users=new UserModel(); if($users->where('email',$email)->first()) return redirect()->back()->withInput()->with('error','Cet e-mail est déjà enregistré.'); $id=$users->insert(['name'=>$name,'email'=>$email,'password_hash'=>password_hash($password,PASSWORD_DEFAULT),'is_admin'=>1]); session()->regenerate(); session()->set(['user_id'=>(int)$id,'user_name'=>$name,'is_admin'=>1]); return redirect()->to(site_url('admin/evolution-ia')); } public function sendResetLink(){ $email=strtolower(trim((string)$this->request->getPost('email'))); $user=(new UserModel())->where('email',$email)->first(); if($user){ $token=bin2hex(random_bytes(32)); db_connect()->table('password_resets')->where('user_id',(int)$user['id'])->delete(); db_connect()->table('password_resets')->insert(['user_id'=>(int)$user['id'],'token_hash'=>hash('sha256',$token),'expires_at'=>date('Y-m-d H:i:s',time()+3600),'created_at'=>date('Y-m-d H:i:s')]); $url=site_url('auth/reset-password/'.$token); service('email')->setTo($email)->setSubject('Réinitialisation du mot de passe')->setMessage("Utilisez ce lien dans l’heure : $url")->send(); } return redirect()->to(site_url('auth/login'))->with('success','Si cette adresse existe, un lien de réinitialisation a été envoyé.'); } public function resetPassword(string $token){ return view('auth/form',['mode'=>'reset','title'=>'Nouveau mot de passe','token'=>$token]); } public function updatePassword(string $token){ $password=[EXPURGÉ] $reset=db_connect()->table('password_resets')->where('token_hash',hash('sha256',$token))->where('expires_at >=',date('Y-m-d H:i:s'))->get()->getRowArray(); if(!$reset || strlen($password)<12) return redirect()->back()->with('error','Lien invalide ou expiré, ou mot de passe trop court.'); (new UserModel())->update((int)$reset['user_id'],['password_hash'=>password_hash($password,PASSWORD_DEFAULT)]); db_connect()->table('password_resets')->where('id',(int)$reset['id'])->delete(); return redirect()->to(site_url('auth/login'))->with('success','Mot de passe modifié. Connectez-vous.'); } public function logout(){ session()->destroy(); return redirect()->to(site_url('auth/login')); } } --- END PINNED app/Controllers/Auth.php | COMPLET --- === CONTEXTE SECONDAIRE — DEPENDANCES RESOLUES === --- CONTEXT EXCERPT app/Config/Routes.php | 4589 octets | sha256=6914991fdef711570261fe65b26ed419e9c3399dfd79e6552fc72ef749c89868 | NON_COMPLET --- setDefaultNamespace('App\\Controllers'); $routes->setAutoRoute(false); // Site public Nexum AI. L'espace de travail reste sous /admin/evolution-ia. $routes->get('/', 'Home::index'); $routes->get('auth/login', 'Auth::login'); $routes->post('auth/login', 'Auth::attempt'); $routes->get('auth/register', 'Auth::register'); $routes->post('auth/register', 'Auth::store'); $routes->get('auth/forgot-password', 'Auth::forgotPassword'); $routes->post('auth/forgot-password', 'Auth::sendResetLink'); $routes->get('auth/reset-password/(:segment)', 'Auth::resetPassword/$1'); $routes->post('auth/reset-password/(:segment)', 'Auth::updatePassword/$1'); $routes->post('auth/logout', 'Auth::logout'); $routes->group('admin', ['namespace'=>'App\\Controllers\\Admin'], static function($routes){ $routes->get('evolution-ia', 'Development::index'); $routes->post('evolution-ia/stream', 'Development::stream'); $routes->post('evolution-ia/transcrire', 'DevelopmentVoice::transcribe'); $routes->post('evolution-ia/prompt', 'Development::savePrompt'); $routes->post('evolution-ia/systeme/analyser', 'SystemUpdates::analyze'); $routes->post('evolution-ia/systeme/mettre-a-jour', 'SystemUpdates::install'); $routes->post('evolution-ia/systeme/restaurer', 'SystemUpdates::rollback'); $routes->get('evolution-ia/systeme/historique', 'SystemUpdates::history'); $routes->get('evolution-ia/systeme/recuperation', 'SystemUpdates::recovery'); $routes->post('evolution-ia/apparence', 'ThemeSettings::save'); $routes->get('evolution-ia/autocomplete', 'Development::autocomplete'); $routes->post('evolution-ia/projets', 'Development::projectSave'); $routes->post('evolution-ia/projets/(:segment)/supprimer', 'Development::projectDelete/$1'); $routes->post('evolution-ia/projets/upload', 'Development::projectUpload'); $routes->post('evolution-ia/projets/(:segment)/runtime', 'Development::projectRuntime/$1'); $routes->post('evolution-ia/projets/(:segment)/analyser', 'Development::projectAnalyze/$1'); $routes->get('evolution-ia/projets/(:segment)/rapport', 'Development::projectReport/$1'); $routes->post('evolution-ia/projets/(:segment)/framework/plan', 'Development::frameworkPlan/$1'); $routes->post('evolution-ia/projets/(:segment)/framework/preparer', 'Development::frameworkPrepare/$1'); $routes->get('evolution-ia/acces/arbre', 'Development::accessTree'); $routes->get('evolution-ia/acces/etat', 'Development::accessState'); $routes->post('evolution-ia/acces/autoriser', 'Development::accessGrant'); $routes->post('evolution-ia/acces/resoudre', 'Development::accessResolve'); $routes->post('evolution-ia/acces/(:num)/revoquer', 'Development::accessRevoke/$1'); $routes->post('evolution-ia/acces/mode', 'Development::accessMode'); $routes->post('evolution-ia/acces/reprendre', 'Development::resumeAccess'); $routes->post('evolution-ia/propositions/preparer', 'Development::prepareProposal'); $routes->get('evolution-ia/propositions/(:num)', 'Development::proposal/$1'); $routes->get('evolution-ia/logs', 'Development::logs'); $routes->get('evolution-ia/logs/context/(:num)/(:segment)', 'Development::downloadAiContext/$1/$2'); $routes->post('evolution-ia/propositions/(:num)/installer', 'Development::installProposal/$1'); $routes->post('evolution-ia/propositions/(:num)/refuser', 'Development::rejectProposal/$1'); $routes->post('evolution-ia/propositions/(:num)/deployer', 'Development::deployProposal/$1'); $routes->post('evolution-ia/propositions/(:num)/rollback-deploiement', 'Development::rollbackDeployment/$1'); $routes->post('evolution-ia/propositions/(:num)/git-add', 'Development::gitAdd/$1'); $routes->post('evolution-ia/propositions/(:num)/git-commit', 'Development::gitCommit/$1'); $routes->post('evolution-ia/propositions/(:num)/git-diagnostic', 'Development::gitDiagnose/$1'); $routes->post('evolution-ia/propositions/(:num)/git-push', 'Development::gitPush/$1'); $routes->post('evolution-ia/propositions/(:num)/git-revert', 'Development::gitRevert/$1'); $routes->post('evolution-ia/propositions/(:num)/git-revert-push', 'Development::gitPushRevert/$1'); $routes->post('evolution-ia/propositions/(:num)/restaurer', 'Development::restoreProposal/$1'); $routes->get('evolution-ia/propositions/(:num)/backup-bdd', 'Development::downloadDatabaseBackup/$1'); $routes->post('evolution-ia/(:num)/supprimer', 'Development::delete/$1'); }); --- END CONTEXT EXCERPT app/Config/Routes.php --- --- CONTEXT EXCERPT app/Controllers/Admin/Development.php | 31451 octets | sha256=1b04eda404662096a07c21e1804378807ea8ff93f9799aab7f24b6250b78d9e7 | NON_COMPLET --- if(!empty($r['access_required'])) { if(!empty($r['answer'])) $this->emit(['type'=>'delta','delta'=>$r['answer']]); $this->emit(['type'=>'access_required','requests'=>$r['requests']??[]]); } $this->emit(['type'=>'done','conversation_id'=>$r['conversation_id']] + $this->csrfPayload()); } catch(\Throwable $e){ log_message('error','Development Engine: '.$e->getMessage()); $this->emit(['type'=>'error','message'=>$e->getMessage()] + $this->csrfPayload()); } } public function prepareProposal() { $conversationId=(int)$this->request->getPost('conversation_id'); $last=(new DevMessageModel())->where(['conversation_id'=>$conversationId,'role'=>'user'])->orderBy('id','DESC')->first(); if(!$last) return redirect()->back()->with('error','Aucune demande utilisateur à transformer en proposition.'); try { $id=(new DevelopmentProposalService())->prepare((int)session('user_id'),$conversationId,(string)$last['content']); } catch(\Throwable $e){ log_message('error','Dev proposal: '.$e->getMessage()); return redirect()->back()->with('error','Proposition impossible : '.$e->getMessage()); } return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id)); } public function proposal(int $id) { $p=(new DevProposalModel())->find($id); if(!$p) throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); $owner=(new DevConversationModel())->where(['id'=>$p['conversation_id'],'user_id'=>(int)session('user_id')])->first(); if(!$owner) throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); $dbPlan=db_connect()->tableExists('dev_proposal_database') ? (new DevProposalDatabaseModel())->where('proposal_id',$id)->orderBy('id','DESC')->first() : null; $deployment=null; try { $deployment=(new \App\Services\DevelopmentProjectDeploymentService())->status($id); } catch(\Throwable) {} $git=null; $gitError=null; try { $git=(new \App\Services\DevelopmentGitService())->status($id); } catch(\Throwable $e) { $gitError=$e->getMessage(); } return view('admin/development/proposal',['title'=>'Proposition #'.$id,'proposal'=>$p,'files'=>(new DevProposalFileModel())->where('proposal_id',$id)->orderBy('id','ASC')->findAll(),'databasePlan'=>$dbPlan,'deployment'=>$deployment,'git'=>$git,'gitError'=>$gitError]); } public function logs() { $proposalId=(int)($this->request->getGet('proposal_id')??0); if($proposalId<=0) throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); $this->assertProposalOwner($proposalId); $proposal=(new DevProposalModel())->find($proposalId); $usageService=new \App\Services\AiUsageLogService(); $logs=$usageService->forProposal($proposalId); $traceService=new \App\Services\AiContextTraceService(); foreach($logs as &$log){ $log['_context_txt']=$traceService->exists((int)$log['id'],'txt'); $log['_context_json']=$traceService->exists((int)$log['id'],'json'); } unset($log); return view('admin/development/logs',[ 'title'=>'Logs IA · Proposition #'.$proposalId, 'proposal'=>$proposal, 'logs'=>$logs, 'totals'=>$usageService->totalsForProposal($proposalId), 'appVersion'=>(string)config(\Config\Evolution::class)->version, ]); } public function downloadAiContext(int $logId, string $format) { if(!in_array($format,['txt','json'],true)) throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); $log=(new \App\Models\DevAiUsageLogModel())->find($logId); if(!$log || empty($log['proposal_id'])) throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); $this->assertProposalOwner((int)$log['proposal_id']); $trace=new \App\Services\AiContextTraceService(); if(!$trace->exists($logId,$format)) throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); $mime=$format==='json'?'application/json; charset=utf-8':'text/plain; charset=utf-8'; return $this->response->download($trace->path($logId,$format),null) ->setFileName('ai-call-'.$logId.'-context.'.$format) ->setContentType($mime); } public function installProposal(int $id) { $this->assertProposalOwner($id); try { (new DevelopmentProposalService())->install($id); return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id))->with('success','Correction installée. Les backups nécessaires ont été créés automatiquement.'); } catch(\Throwable $e){ return redirect()->back()->with('error','Installation refusée/échouée : '.$e->getMessage()); } } public function deployProposal(int $id) { $this->assertProposalOwner($id); try { (new \App\Services\DevelopmentProjectDeploymentService())->deploy($id); return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id))->with('success','Proposition déployée sur la cible locale réelle. Snapshot de rollback créé.'); } catch(\Throwable $e){ return redirect()->back()->with('error','Déploiement suspendu/échoué : '.$e->getMessage()); } } public function gitAdd(int $id) { $this->assertProposalOwner($id); try { (new \App\Services\DevelopmentGitService())->add($id); return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id))->with('success','Git add effectué uniquement sur les fichiers de cette proposition.'); } catch(\Throwable $e){ return redirect()->back()->with('error','Git add refusé/échoué : '.$e->getMessage()); } } public function gitCommit(int $id) { $this->assertProposalOwner($id); try { $out=(new \App\Services\DevelopmentGitService())->commit($id,(string)$this->request->getPost('commit_message')); return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id))->with('success','Commit Git créé. '.trim($out)); } catch(\Throwable $e){ return redirect()->back()->with('error','Commit Git refusé/échoué : '.$e->getMessage()); } } public function gitDiagnose(int $id) { $this->assertProposalOwner($id); try { $d=(new \App\Services\DevelopmentGitService())->diagnoseRemote($id); $agent=$d['agent']??[]; $ssh=$d['ssh_probe']??[]; $git=$d['git_probe']??[]; $runner=$d['runner_probe']??[]; $apache=$d['apache_environment']??[]; $injected=$d['injected_environment']??[]; $windows=$d['windows_context']??[]; $parts=[ 'Remote : '.($d['remote']??''), 'Racine Git : '.($d['root']??''), 'Git : '.($d['git_binary']??''), 'SSH : '.(($d['ssh_binary']??'')?:'Git par défaut'), 'known_hosts : '.(($d['ssh_known_hosts']??'')?:'SSH par défaut'), '', 'Environnement Apache :', 'USERPROFILE : '.(($apache['USERPROFILE']??'')?:'(vide)'), 'HOME : '.(($apache['HOME']??'')?:'(vide)'), '', 'Environnement injecté au Git Runner :', 'USERPROFILE : '.(($injected['USERPROFILE']??'')?:'(vide)'), 'HOME : '.(($injected['HOME']??'')?:'(vide)'), '', 'Contexte Windows vu par PHP/Apache :', 'whoami : '.trim((string)($windows['whoami']['stdout']??'')), 'session : '.trim((string)($windows['session']['stdout']??'')), 'service ssh-agent : '.trim((string)($windows['ssh_agent_service']['stdout']??'')), '', '[1/3] ssh-add -l : '.(!empty($agent['available'])?'OK':'ECHEC').' code '.(($agent['code']??null)===null?'?':$agent['code']), ]; $parts[]='sortie ssh-add : '.(trim((string)($agent['output']??''))!==''?trim((string)$agent['output']):'(aucune sortie stdout/stderr)'); $parts[]='[2/3] ssh -T : '.(!empty($ssh['ok'])?'AUTHENTIFIE':'ECHEC').' code '.(($ssh['code']??null)===null?'?':$ssh['code']); if(trim((string)($ssh['stdout']??''))!=='') $parts[]='stdout SSH : '.trim((string)$ssh['stdout']); $parts[]='stderr SSH (-vvv) : '.(trim((string)($ssh['stderr']??''))!==''?trim((string)$ssh['stderr']):'(aucune sortie)'); $parts[]='[3/3] git -C racine ls-remote origin : '.(!empty($d['ok'])?'OK':'ECHEC').' code '.(($git['code']??null)===null?'?':$git['code']); if(trim((string)($git['stdout']??''))!=='') $parts[]='stdout Git : '.trim((string)$git['stdout']); if(trim((string)($git['stderr']??''))!=='') $parts[]='stderr Git : '.trim((string)$git['stderr']); $parts[]=''; $parts[]='[4/4] Runner Windows natif (diagnostic sans effet de bord) :'; foreach($runner as $name=>$modes){ $parts[]=$name.' :'; foreach(['direct'=>'proc_open direct','cmd'=>'cmd.exe /D /S /C'] as $mode=>$label){ $r=$modes[$mode]??[]; $parts[]=' '.$label.' => code '.(($r['code']??null)===null?'?':$r['code']); $o=trim((string)($r['stdout']??'')); $e=trim((string)($r['stderr']??'')); if($o!=='') $parts[]=' stdout : '.$o; if($e!=='') $parts[]=' stderr : '.$e; if($o==='' && $e==='') $parts[]=' sortie : (aucune)'; } } return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id))->with(!empty($d['ok'])?'success':'error',"Diagnostic Git/SSH depuis PHP/Apache : ".implode(" ",$parts)); } catch(\Throwable $e){ return redirect()->back()->with('error','Diagnostic Git/SSH échoué : '.$e->getMessage()); } } public function gitPush(int $id) { $this->assertProposalOwner($id); try { $out=(new \App\Services\DevelopmentGitService())->push($id); return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id))->with('success','Push Git terminé. '.trim($out)); } catch(\Throwable $e){ return redirect()->back()->with('error','Push Git refusé/échoué : '.$e->getMessage()); } } public function gitRevert(int $id) { $this->assertProposalOwner($id); try { $out=(new \App\Services\DevelopmentGitService())->revert($id); return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id))->with('success','Commit d’annulation Git créé. '.trim($out)); } catch(\Throwable $e){ return redirect()->back()->with('error','Revert Git refusé/échoué : '.$e->getMessage()); } } public function gitPushRevert(int $id) { $this->assertProposalOwner($id); try { $out=(new \App\Services\DevelopmentGitService())->pushRevert($id); return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id))->with('success','Revert envoyé sur le dépôt distant. '.trim($out)); } catch(\Throwable $e){ return redirect()->back()->with('error','Push du revert refusé/échoué : '.$e->getMessage()); } } public function rollbackDeployment(int $id) { $this->assertProposalOwner($id); try { (new \App\Services\DevelopmentProjectDeploymentService())->rollback($id); return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id))->with('success','Cible locale restaurée depuis le snapshot de déploiement.'); } catch(\Throwable $e){ return redirect()->back()->with('error','Rollback suspendu/échoué : '.$e->getMessage()); } } public function rejectProposal(int $id) { $this->assertProposalOwner($id); (new DevelopmentProposalService())->reject($id); return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id))->with('success','Proposition refusée.'); } public function restoreProposal(int $id) { $this->assertProposalOwner($id); try { (new DevelopmentProposalService())->restore($id); return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id))->with('success','Version précédente restaurée avec succès.'); } catch(\Throwable $e){ return redirect()->back()->with('error','Restauration refusée/échouée : '.$e->getMessage()); } } public function downloadDatabaseBackup(int $id) { $this->assertProposalOwner($id); try { $file=(new DevelopmentProposalService())->databaseBackupPath($id); } catch(\Throwable $e){ return redirect()->back()->with('error',$e->getMessage()); } return $this->response->download($file['path'],null)->setFileName($file['filename']); } private function assertProposalOwner(int $id): void { $p=(new DevProposalModel())->find($id); if(!$p) throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); $owner=(new DevConversationModel())->where(['id'=>$p['conversation_id'],'user_id'=>(int)session('user_id')])->first(); if(!$owner) throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); } /** * Autocomplétion locale du projet, disponible avant la création d'une conversation. * Aucun appel IA : le Tool Gateway ne fait que lister les chemins autorisés. */ public function autocomplete() { $query=trim((string)$this->request->getGet('q')); $query=str_replace('\\','/',$query); if($query==='' || $query[0]!=='/') return $this->response->setJSON(['ok'=>true,'items'=>[]]); $relative=ltrim($query,'/'); $slash=strrpos($relative,'/'); $directory=$slash===false?'':substr($relative,0,$slash); $needle=$slash===false?$relative:substr($relative,$slash+1); try { $projectId=(string)($this->request->getGet('project_id')?:'system'); $gateway=new \App\Services\DevelopmentToolGatewayService(nu ...[extrait secondaire tronqué]... --- END CONTEXT EXCERPT app/Controllers/Admin/Development.php --- --- CONTEXT EXCERPT app/Views/admin/development/recipes.php | 4353 octets | sha256=be6ada64b7005985053b596679d9fcf13fca2401c244c4729bf6def6fd9cdd79 | NON_COMPLET --- extend('layouts/admin') ?> section('content') ?>

Matrice des recettes

Synthèse des évolutions installées et des propositions refusées.

Retour à Nexum IA
Cette matrice affiche les propositions installées ou refusées. Les propositions en brouillon ou en attente de validation ne sont pas affichées.

Aucune proposition archivée

Les évolutions installées ou refusées apparaîtront ici.

Statut Version Évolution Résumé Fichiers Traitée le Actions
InstalléeRefusée
endSection() ?> --- END CONTEXT EXCERPT app/Views/admin/development/recipes.php --- --- CONTEXT EXCERPT app/Views/admin/development/index.php | 37062 octets | sha256=103c68e56b8983fe54ad133a50cd3df16acbe8445a6e60e7e4d2919df21c3a5a | NON_COMPLET --- i bi-gear">
AI Access & Context Manager
Autorise ce que Nexum IA peut lire, sans modifier le code.
Protections absolues : .env*, .git, .ssh, writable, public/uploads, vendor et les données BDD ne sont jamais exposés. Pour la BDD, seul le schéma structurel peut être autorisé.
\n\n \n\n\n\n \n\n \n\n \n\n\n\n

Accès sécurisé à l’administration Nexum AI.

\n\n
\n\n
\n\n \n\n
\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n--- END PINNED app/Views/auth/form.php | COMPLET ---\n\n--- BEGIN PINNED app/Controllers/Auth.php | 3888 octets | sha256=1cf1f5063353cc7ef97175cbd6d6d1461a5e6b684f7b55a29febe572a86a2f20 | COMPLET ---\n'login','title'=>'Connexion']); }\n public function register(){ return view('auth/form',['mode'=>'register','title'=>'Créer un compte']); }\n public function forgotPassword(){ return view('auth/form',['mode'=>'forgot','title'=>'Mot de passe oublié']); }\n\n public function attempt(){\n $user=(new UserModel())->where('email',strtolower(trim((string)$this->request->getPost('email'))))->first();\n if(!$user || !password_verify((string)$this->request->getPost('password'),(string)$user['password_hash'])) return redirect()->back()->withInput()->with('error','Identifiants invalides.');\n session()->regenerate();\n session()->set(['user_id'=>(int)$user['id'],'user_name'=>$user['name'],'is_admin'=>(int)$user['is_admin']]);\n $target=(string)(session('auth_redirect')?:site_url('admin/evolution-ia')); session()->remove('auth_redirect');\n return redirect()->to($target);\n }\n\n public function store(){\n $name=trim((string)$this->request->getPost('name')); $email=strtolower(trim((string)$this->request->getPost('email'))); $password=[EXPURGÉ]\n if($name==='' || !filter_var($email,FILTER_VALIDATE_EMAIL) || strlen($password)<12) return redirect()->back()->withInput()->with('error','Nom, e-mail valide et mot de passe de 12 caractères minimum requis.');\n $users=new UserModel(); if($users->where('email',$email)->first()) return redirect()->back()->withInput()->with('error','Cet e-mail est déjà enregistré.');\n $id=$users->insert(['name'=>$name,'email'=>$email,'password_hash'=>password_hash($password,PASSWORD_DEFAULT),'is_admin'=>1]);\n session()->regenerate(); session()->set(['user_id'=>(int)$id,'user_name'=>$name,'is_admin'=>1]);\n return redirect()->to(site_url('admin/evolution-ia'));\n }\n\n public function sendResetLink(){\n $email=strtolower(trim((string)$this->request->getPost('email'))); $user=(new UserModel())->where('email',$email)->first();\n if($user){\n $token=bin2hex(random_bytes(32));\n db_connect()->table('password_resets')->where('user_id',(int)$user['id'])->delete();\n db_connect()->table('password_resets')->insert(['user_id'=>(int)$user['id'],'token_hash'=>hash('sha256',$token),'expires_at'=>date('Y-m-d H:i:s',time()+3600),'created_at'=>date('Y-m-d H:i:s')]);\n $url=site_url('auth/reset-password/'.$token);\n service('email')->setTo($email)->setSubject('Réinitialisation du mot de passe')->setMessage(\"Utilisez ce lien dans l’heure : $url\")->send();\n }\n return redirect()->to(site_url('auth/login'))->with('success','Si cette adresse existe, un lien de réinitialisation a été envoyé.');\n }\n\n public function resetPassword(string $token){ return view('auth/form',['mode'=>'reset','title'=>'Nouveau mot de passe','token'=>$token]); }\n public function updatePassword(string $token){\n $password=[EXPURGÉ] $reset=db_connect()->table('password_resets')->where('token_hash',hash('sha256',$token))->where('expires_at >=',date('Y-m-d H:i:s'))->get()->getRowArray();\n if(!$reset || strlen($password)<12) return redirect()->back()->with('error','Lien invalide ou expiré, ou mot de passe trop court.');\n (new UserModel())->update((int)$reset['user_id'],['password_hash'=>password_hash($password,PASSWORD_DEFAULT)]);\n db_connect()->table('password_resets')->where('id',(int)$reset['id'])->delete();\n return redirect()->to(site_url('auth/login'))->with('success','Mot de passe modifié. Connectez-vous.');\n }\n public function logout(){ session()->destroy(); return redirect()->to(site_url('auth/login')); }\n}\n\n--- END PINNED app/Controllers/Auth.php | COMPLET ---\n\n=== CONTEXTE SECONDAIRE — DEPENDANCES RESOLUES ===\n\n--- CONTEXT EXCERPT app/Config/Routes.php | 4589 octets | sha256=6914991fdef711570261fe65b26ed419e9c3399dfd79e6552fc72ef749c89868 | NON_COMPLET ---\nsetDefaultNamespace('App\\\\Controllers');\n$routes->setAutoRoute(false);\n\n// Site public Nexum AI. L'espace de travail reste sous /admin/evolution-ia.\n$routes->get('/', 'Home::index');\n\n$routes->get('auth/login', 'Auth::login');\n$routes->post('auth/login', 'Auth::attempt');\n$routes->get('auth/register', 'Auth::register');\n$routes->post('auth/register', 'Auth::store');\n$routes->get('auth/forgot-password', 'Auth::forgotPassword');\n$routes->post('auth/forgot-password', 'Auth::sendResetLink');\n$routes->get('auth/reset-password/(:segment)', 'Auth::resetPassword/$1');\n$routes->post('auth/reset-password/(:segment)', 'Auth::updatePassword/$1');\n$routes->post('auth/logout', 'Auth::logout');\n\n$routes->group('admin', ['namespace'=>'App\\\\Controllers\\\\Admin'], static function($routes){\n $routes->get('evolution-ia', 'Development::index');\n $routes->post('evolution-ia/stream', 'Development::stream');\n $routes->post('evolution-ia/transcrire', 'DevelopmentVoice::transcribe');\n $routes->post('evolution-ia/prompt', 'Development::savePrompt');\n $routes->post('evolution-ia/systeme/analyser', 'SystemUpdates::analyze');\n $routes->post('evolution-ia/systeme/mettre-a-jour', 'SystemUpdates::install');\n $routes->post('evolution-ia/systeme/restaurer', 'SystemUpdates::rollback');\n $routes->get('evolution-ia/systeme/historique', 'SystemUpdates::history');\n $routes->get('evolution-ia/systeme/recuperation', 'SystemUpdates::recovery');\n $routes->post('evolution-ia/apparence', 'ThemeSettings::save');\n $routes->get('evolution-ia/autocomplete', 'Development::autocomplete');\n $routes->post('evolution-ia/projets', 'Development::projectSave');\n $routes->post('evolution-ia/projets/(:segment)/supprimer', 'Development::projectDelete/$1');\n $routes->post('evolution-ia/projets/upload', 'Development::projectUpload');\n $routes->post('evolution-ia/projets/(:segment)/runtime', 'Development::projectRuntime/$1');\n $routes->post('evolution-ia/projets/(:segment)/analyser', 'Development::projectAnalyze/$1');\n $routes->get('evolution-ia/projets/(:segment)/rapport', 'Development::projectReport/$1');\n $routes->post('evolution-ia/projets/(:segment)/framework/plan', 'Development::frameworkPlan/$1');\n $routes->post('evolution-ia/projets/(:segment)/framework/preparer', 'Development::frameworkPrepare/$1');\n $routes->get('evolution-ia/acces/arbre', 'Development::accessTree');\n $routes->get('evolution-ia/acces/etat', 'Development::accessState');\n $routes->post('evolution-ia/acces/autoriser', 'Development::accessGrant');\n $routes->post('evolution-ia/acces/resoudre', 'Development::accessResolve');\n $routes->post('evolution-ia/acces/(:num)/revoquer', 'Development::accessRevoke/$1');\n $routes->post('evolution-ia/acces/mode', 'Development::accessMode');\n $routes->post('evolution-ia/acces/reprendre', 'Development::resumeAccess');\n $routes->post('evolution-ia/propositions/preparer', 'Development::prepareProposal');\n $routes->get('evolution-ia/propositions/(:num)', 'Development::proposal/$1');\n $routes->get('evolution-ia/logs', 'Development::logs');\n $routes->get('evolution-ia/logs/context/(:num)/(:segment)', 'Development::downloadAiContext/$1/$2');\n $routes->post('evolution-ia/propositions/(:num)/installer', 'Development::installProposal/$1');\n $routes->post('evolution-ia/propositions/(:num)/refuser', 'Development::rejectProposal/$1');\n $routes->post('evolution-ia/propositions/(:num)/deployer', 'Development::deployProposal/$1');\n $routes->post('evolution-ia/propositions/(:num)/rollback-deploiement', 'Development::rollbackDeployment/$1');\n $routes->post('evolution-ia/propositions/(:num)/git-add', 'Development::gitAdd/$1');\n $routes->post('evolution-ia/propositions/(:num)/git-commit', 'Development::gitCommit/$1');\n $routes->post('evolution-ia/propositions/(:num)/git-diagnostic', 'Development::gitDiagnose/$1');\n $routes->post('evolution-ia/propositions/(:num)/git-push', 'Development::gitPush/$1');\n $routes->post('evolution-ia/propositions/(:num)/git-revert', 'Development::gitRevert/$1');\n $routes->post('evolution-ia/propositions/(:num)/git-revert-push', 'Development::gitPushRevert/$1');\n $routes->post('evolution-ia/propositions/(:num)/restaurer', 'Development::restoreProposal/$1');\n $routes->get('evolution-ia/propositions/(:num)/backup-bdd', 'Development::downloadDatabaseBackup/$1');\n $routes->post('evolution-ia/(:num)/supprimer', 'Development::delete/$1');\n});\n\n--- END CONTEXT EXCERPT app/Config/Routes.php ---\n\n--- CONTEXT EXCERPT app/Controllers/Admin/Development.php | 31451 octets | sha256=1b04eda404662096a07c21e1804378807ea8ff93f9799aab7f24b6250b78d9e7 | NON_COMPLET ---\n if(!empty($r['access_required'])) { if(!empty($r['answer'])) $this->emit(['type'=>'delta','delta'=>$r['answer']]); $this->emit(['type'=>'access_required','requests'=>$r['requests']??[]]); }\n $this->emit(['type'=>'done','conversation_id'=>$r['conversation_id']] + $this->csrfPayload());\n } catch(\\Throwable $e){ log_message('error','Development Engine: '.$e->getMessage()); $this->emit(['type'=>'error','message'=>$e->getMessage()] + $this->csrfPayload()); }\n }\n\n public function prepareProposal()\n {\n $conversationId=(int)$this->request->getPost('conversation_id');\n $last=(new DevMessageModel())->where(['conversation_id'=>$conversationId,'role'=>'user'])->orderBy('id','DESC')->first();\n if(!$last) return redirect()->back()->with('error','Aucune demande utilisateur à transformer en proposition.');\n try { $id=(new DevelopmentProposalService())->prepare((int)session('user_id'),$conversationId,(string)$last['content']); }\n catch(\\Throwable $e){ log_message('error','Dev proposal: '.$e->getMessage()); return redirect()->back()->with('error','Proposition impossible : '.$e->getMessage()); }\n return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id));\n }\n\n public function proposal(int $id)\n {\n $p=(new DevProposalModel())->find($id); if(!$p) throw \\CodeIgniter\\Exceptions\\PageNotFoundException::forPageNotFound();\n $owner=(new DevConversationModel())->where(['id'=>$p['conversation_id'],'user_id'=>(int)session('user_id')])->first(); if(!$owner) throw \\CodeIgniter\\Exceptions\\PageNotFoundException::forPageNotFound();\n $dbPlan=db_connect()->tableExists('dev_proposal_database') ? (new DevProposalDatabaseModel())->where('proposal_id',$id)->orderBy('id','DESC')->first() : null;\n $deployment=null; try { $deployment=(new \\App\\Services\\DevelopmentProjectDeploymentService())->status($id); } catch(\\Throwable) {}\n $git=null; $gitError=null; try { $git=(new \\App\\Services\\DevelopmentGitService())->status($id); } catch(\\Throwable $e) { $gitError=$e->getMessage(); }\n return view('admin/development/proposal',['title'=>'Proposition #'.$id,'proposal'=>$p,'files'=>(new DevProposalFileModel())->where('proposal_id',$id)->orderBy('id','ASC')->findAll(),'databasePlan'=>$dbPlan,'deployment'=>$deployment,'git'=>$git,'gitError'=>$gitError]);\n }\n\n public function logs()\n {\n $proposalId=(int)($this->request->getGet('proposal_id')??0);\n if($proposalId<=0) throw \\CodeIgniter\\Exceptions\\PageNotFoundException::forPageNotFound();\n $this->assertProposalOwner($proposalId);\n $proposal=(new DevProposalModel())->find($proposalId);\n $usageService=new \\App\\Services\\AiUsageLogService();\n $logs=$usageService->forProposal($proposalId);\n $traceService=new \\App\\Services\\AiContextTraceService();\n foreach($logs as &$log){\n $log['_context_txt']=$traceService->exists((int)$log['id'],'txt');\n $log['_context_json']=$traceService->exists((int)$log['id'],'json');\n }\n unset($log);\n return view('admin/development/logs',[\n 'title'=>'Logs IA · Proposition #'.$proposalId,\n 'proposal'=>$proposal,\n 'logs'=>$logs,\n 'totals'=>$usageService->totalsForProposal($proposalId),\n 'appVersion'=>(string)config(\\Config\\Evolution::class)->version,\n ]);\n }\n\n\n public function downloadAiContext(int $logId, string $format)\n {\n if(!in_array($format,['txt','json'],true)) throw \\CodeIgniter\\Exceptions\\PageNotFoundException::forPageNotFound();\n $log=(new \\App\\Models\\DevAiUsageLogModel())->find($logId);\n if(!$log || empty($log['proposal_id'])) throw \\CodeIgniter\\Exceptions\\PageNotFoundException::forPageNotFound();\n $this->assertProposalOwner((int)$log['proposal_id']);\n $trace=new \\App\\Services\\AiContextTraceService();\n if(!$trace->exists($logId,$format)) throw \\CodeIgniter\\Exceptions\\PageNotFoundException::forPageNotFound();\n $mime=$format==='json'?'application/json; charset=utf-8':'text/plain; charset=utf-8';\n return $this->response->download($trace->path($logId,$format),null)\n ->setFileName('ai-call-'.$logId.'-context.'.$format)\n ->setContentType($mime);\n }\n\n public function installProposal(int $id)\n {\n $this->assertProposalOwner($id);\n try { (new DevelopmentProposalService())->install($id); return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id))->with('success','Correction installée. Les backups nécessaires ont été créés automatiquement.'); }\n catch(\\Throwable $e){ return redirect()->back()->with('error','Installation refusée/échouée : '.$e->getMessage()); }\n }\n\n public function deployProposal(int $id)\n {\n $this->assertProposalOwner($id);\n try { (new \\App\\Services\\DevelopmentProjectDeploymentService())->deploy($id); return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id))->with('success','Proposition déployée sur la cible locale réelle. Snapshot de rollback créé.'); }\n catch(\\Throwable $e){ return redirect()->back()->with('error','Déploiement suspendu/échoué : '.$e->getMessage()); }\n }\n\n public function gitAdd(int $id)\n {\n $this->assertProposalOwner($id);\n try { (new \\App\\Services\\DevelopmentGitService())->add($id); return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id))->with('success','Git add effectué uniquement sur les fichiers de cette proposition.'); }\n catch(\\Throwable $e){ return redirect()->back()->with('error','Git add refusé/échoué : '.$e->getMessage()); }\n }\n\n public function gitCommit(int $id)\n {\n $this->assertProposalOwner($id);\n try { $out=(new \\App\\Services\\DevelopmentGitService())->commit($id,(string)$this->request->getPost('commit_message')); return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id))->with('success','Commit Git créé. '.trim($out)); }\n catch(\\Throwable $e){ return redirect()->back()->with('error','Commit Git refusé/échoué : '.$e->getMessage()); }\n }\n\n public function gitDiagnose(int $id)\n {\n $this->assertProposalOwner($id);\n try {\n $d=(new \\App\\Services\\DevelopmentGitService())->diagnoseRemote($id);\n $agent=$d['agent']??[];\n $ssh=$d['ssh_probe']??[];\n $git=$d['git_probe']??[];\n $runner=$d['runner_probe']??[];\n $apache=$d['apache_environment']??[];\n $injected=$d['injected_environment']??[];\n $windows=$d['windows_context']??[];\n $parts=[\n 'Remote : '.($d['remote']??''),\n 'Racine Git : '.($d['root']??''),\n 'Git : '.($d['git_binary']??''),\n 'SSH : '.(($d['ssh_binary']??'')?:'Git par défaut'),\n 'known_hosts : '.(($d['ssh_known_hosts']??'')?:'SSH par défaut'),\n '',\n 'Environnement Apache :',\n 'USERPROFILE : '.(($apache['USERPROFILE']??'')?:'(vide)'),\n 'HOME : '.(($apache['HOME']??'')?:'(vide)'),\n '',\n 'Environnement injecté au Git Runner :',\n 'USERPROFILE : '.(($injected['USERPROFILE']??'')?:'(vide)'),\n 'HOME : '.(($injected['HOME']??'')?:'(vide)'),\n '',\n 'Contexte Windows vu par PHP/Apache :',\n 'whoami : '.trim((string)($windows['whoami']['stdout']??'')),\n 'session : '.trim((string)($windows['session']['stdout']??'')),\n 'service ssh-agent : '.trim((string)($windows['ssh_agent_service']['stdout']??'')),\n '',\n '[1/3] ssh-add -l : '.(!empty($agent['available'])?'OK':'ECHEC').' code '.(($agent['code']??null)===null?'?':$agent['code']),\n ];\n $parts[]='sortie ssh-add : '.(trim((string)($agent['output']??''))!==''?trim((string)$agent['output']):'(aucune sortie stdout/stderr)');\n $parts[]='[2/3] ssh -T : '.(!empty($ssh['ok'])?'AUTHENTIFIE':'ECHEC').' code '.(($ssh['code']??null)===null?'?':$ssh['code']);\n if(trim((string)($ssh['stdout']??''))!=='') $parts[]='stdout SSH : '.trim((string)$ssh['stdout']);\n $parts[]='stderr SSH (-vvv) : '.(trim((string)($ssh['stderr']??''))!==''?trim((string)$ssh['stderr']):'(aucune sortie)');\n $parts[]='[3/3] git -C racine ls-remote origin : '.(!empty($d['ok'])?'OK':'ECHEC').' code '.(($git['code']??null)===null?'?':$git['code']);\n if(trim((string)($git['stdout']??''))!=='') $parts[]='stdout Git : '.trim((string)$git['stdout']);\n if(trim((string)($git['stderr']??''))!=='') $parts[]='stderr Git : '.trim((string)$git['stderr']);\n $parts[]='';\n $parts[]='[4/4] Runner Windows natif (diagnostic sans effet de bord) :';\n foreach($runner as $name=>$modes){\n $parts[]=$name.' :';\n foreach(['direct'=>'proc_open direct','cmd'=>'cmd.exe /D /S /C'] as $mode=>$label){\n $r=$modes[$mode]??[];\n $parts[]=' '.$label.' => code '.(($r['code']??null)===null?'?':$r['code']);\n $o=trim((string)($r['stdout']??'')); $e=trim((string)($r['stderr']??''));\n if($o!=='') $parts[]=' stdout : '.$o;\n if($e!=='') $parts[]=' stderr : '.$e;\n if($o==='' && $e==='') $parts[]=' sortie : (aucune)';\n }\n }\n return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id))->with(!empty($d['ok'])?'success':'error',\"Diagnostic Git/SSH depuis PHP/Apache :\n\".implode(\"\n\",$parts));\n } catch(\\Throwable $e){ return redirect()->back()->with('error','Diagnostic Git/SSH échoué : '.$e->getMessage()); }\n }\n\n public function gitPush(int $id)\n {\n $this->assertProposalOwner($id);\n try { $out=(new \\App\\Services\\DevelopmentGitService())->push($id); return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id))->with('success','Push Git terminé. '.trim($out)); }\n catch(\\Throwable $e){ return redirect()->back()->with('error','Push Git refusé/échoué : '.$e->getMessage()); }\n }\n\n public function gitRevert(int $id)\n {\n $this->assertProposalOwner($id);\n try { $out=(new \\App\\Services\\DevelopmentGitService())->revert($id); return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id))->with('success','Commit d’annulation Git créé. '.trim($out)); }\n catch(\\Throwable $e){ return redirect()->back()->with('error','Revert Git refusé/échoué : '.$e->getMessage()); }\n }\n\n public function gitPushRevert(int $id)\n {\n $this->assertProposalOwner($id);\n try { $out=(new \\App\\Services\\DevelopmentGitService())->pushRevert($id); return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id))->with('success','Revert envoyé sur le dépôt distant. '.trim($out)); }\n catch(\\Throwable $e){ return redirect()->back()->with('error','Push du revert refusé/échoué : '.$e->getMessage()); }\n }\n\n public function rollbackDeployment(int $id)\n {\n $this->assertProposalOwner($id);\n try { (new \\App\\Services\\DevelopmentProjectDeploymentService())->rollback($id); return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id))->with('success','Cible locale restaurée depuis le snapshot de déploiement.'); }\n catch(\\Throwable $e){ return redirect()->back()->with('error','Rollback suspendu/échoué : '.$e->getMessage()); }\n }\n\n public function rejectProposal(int $id)\n {\n $this->assertProposalOwner($id);\n (new DevelopmentProposalService())->reject($id); return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id))->with('success','Proposition refusée.');\n }\n\n public function restoreProposal(int $id)\n {\n $this->assertProposalOwner($id);\n try { (new DevelopmentProposalService())->restore($id); return redirect()->to(site_url('admin/evolution-ia/propositions/'.$id))->with('success','Version précédente restaurée avec succès.'); }\n catch(\\Throwable $e){ return redirect()->back()->with('error','Restauration refusée/échouée : '.$e->getMessage()); }\n }\n\n public function downloadDatabaseBackup(int $id)\n {\n $this->assertProposalOwner($id);\n try { $file=(new DevelopmentProposalService())->databaseBackupPath($id); }\n catch(\\Throwable $e){ return redirect()->back()->with('error',$e->getMessage()); }\n return $this->response->download($file['path'],null)->setFileName($file['filename']);\n }\n\n private function assertProposalOwner(int $id): void\n {\n $p=(new DevProposalModel())->find($id);\n if(!$p) throw \\CodeIgniter\\Exceptions\\PageNotFoundException::forPageNotFound();\n $owner=(new DevConversationModel())->where(['id'=>$p['conversation_id'],'user_id'=>(int)session('user_id')])->first();\n if(!$owner) throw \\CodeIgniter\\Exceptions\\PageNotFoundException::forPageNotFound();\n }\n\n\n /**\n * Autocomplétion locale du projet, disponible avant la création d'une conversation.\n * Aucun appel IA : le Tool Gateway ne fait que lister les chemins autorisés.\n */\n public function autocomplete()\n {\n $query=trim((string)$this->request->getGet('q'));\n $query=str_replace('\\\\','/',$query);\n if($query==='' || $query[0]!=='/') return $this->response->setJSON(['ok'=>true,'items'=>[]]);\n\n $relative=ltrim($query,'/');\n $slash=strrpos($relative,'/');\n $directory=$slash===false?'':substr($relative,0,$slash);\n $needle=$slash===false?$relative:substr($relative,$slash+1);\n\n try {\n $projectId=(string)($this->request->getGet('project_id')?:'system');\n $gateway=new \\App\\Services\\DevelopmentToolGatewayService(nu\n...[extrait secondaire tronqué]...\n--- END CONTEXT EXCERPT app/Controllers/Admin/Development.php ---\n\n--- CONTEXT EXCERPT app/Views/admin/development/recipes.php | 4353 octets | sha256=be6ada64b7005985053b596679d9fcf13fca2401c244c4729bf6def6fd9cdd79 | NON_COMPLET ---\nextend('layouts/admin') ?> section('content') ?>\n
\n
\n
\n

Matrice des recettes

\n

Synthèse des évolutions installées et des propositions refusées.

\n
\n \"\n >Retour à Nexum IA\n
\n
\n Cette matrice affiche les propositions installées ou refusées. Les\n propositions en brouillon ou en attente de validation ne sont pas affichées.\n
\n \n
\n
\n \n

Aucune proposition archivée

\n

Les évolutions installées ou refusées apparaîtront ici.

\n
\n
\n \n
\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
StatutVersionÉvolutionRésuméFichiersTraitée leActions
\n InstalléeRefusée\n \n \n \n \n \n
\n \"\n >Détail\n \"\n title=\"Voir les logs Dev AI\"\n >Logs\n \n
\n
\n
\n \n
\nendSection() ?>\n\n--- END CONTEXT EXCERPT app/Views/admin/development/recipes.php ---\n\n--- CONTEXT EXCERPT app/Views/admin/development/index.php | 37062 octets | sha256=103c68e56b8983fe54ad133a50cd3df16acbe8445a6e60e7e4d2919df21c3a5a | NON_COMPLET ---\ni bi-gear\">\n \n \n \n \n \n
mt-3 text-end\">\n \"\n id=\"devPrepareProposalForm\"\n onsubmit=\"if(!confirm('Préparer une proposition de code à partir de la dernière demande ? Aucun fichier ne sera encore modifié.')) return false; document.getElementById('devPrepareProposalButton').disabled=true; document.getElementById('devPrepareProposalButton').innerHTML='Préparation en cours…'; document.getElementById('devProposalLoading').classList.remove('d-none');\"\n >\n \"\n />\n Préparer la correction / le diff\n \n \n
\n \n \n\n
\">\n
\n
Projets & Project Brain
System reste la racine de Nexum IA. Sélectionne un projet puis analyse-le localement pour construire sa carte sémantique.
\n
\n
\n \">\n
ProjetTypeRacineProject Brain
\" target=\"_blank\" rel=\"noopener\" title=\"Visualiser le projet\">
\n
\n
\" class=\"border rounded p-3 h-100\">\n
Ajouter une racine locale
\n
\n
\" method=\"post\" enctype=\"multipart/form-data\" class=\"border rounded p-3 h-100\">\n
Importer un projet ZIP
Extraction isolée, sans exécution du code contenu.
\n
\n
\n
\n
Migration vers un framework PHP
Installation Composer dans un workspace isolé. Le projet d’origine n’est jamais modifié pendant la préparation.
Expérimental sécurisé
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
Relier le workspace au projet local réel
\n
\n
\n
\n
\n
\n
\n
\n
\n
Evolution prépare dans le workspace. Une proposition validée pourra ensuite être déployée uniquement sur cette cible, avec contrôle SHA-256, snapshot obligatoire et rollback.
\n
\n
\n
\n
Project Brain · Analyse locale
Prêt
\n
\n            
\n
\n
\n
\n\n
\n
\n
\n AI Access & Context Manager\n
\n
Autorise ce que Nexum IA peut lire, sans modifier le code.
\n
\n \n
\n
\n
\n Protections absolues : .env*, .git, .ssh,\n writable, public/uploads, vendor et les données BDD ne sont jamais\n exposés. Pour la BDD, seul le schéma structurel peut être autorisé.\n
\n
\n
\n
\n