[1mdiff --git a/.gitignore b/.gitignore[m
[1mindex cff1b08..7217f01 100644[m
[1m--- a/.gitignore[m
[1m+++ b/.gitignore[m
[36m@@ -53,8 +53,7 @@[m [mtest_*.js[m
 *_debug.js[m
 test-*.js[m
 [m
[31m-# HTML généré (logs viewer)[m
[31m-logs-viewer.html[m
[32m+[m[32m# HTML généré était ici mais maintenant on le garde dans tools/[m
 [m
 # Unit test reports[m
 TEST*.xml[m
[1mdiff --git a/code.js b/code.js[m
[1mindex a1dcd72..a2bdb22 100644[m
[1m--- a/code.js[m
[1m+++ b/code.js[m
[36m@@ -1,6 +1,6 @@[m
 /*[m
   code.js — bundle concaténé[m
[31m-  Généré: 2025-09-03T04:21:57.159Z[m
[32m+[m[32m  Généré: 2025-09-04T01:10:08.540Z[m
   Source: lib[m
   Fichiers: 16[m
   Ordre: topo[m
[36m@@ -57,12 +57,13 @@[m [mconst fileDest = pino.destination({[m
 });[m
 tee.pipe(fileDest);[m
 [m
[31m-// Custom levels for Pino to include TRACE and PROMPT[m
[32m+[m[32m// Custom levels for Pino to include TRACE, PROMPT, and LLM[m
 const customLevels = {[m
   trace: 5,   // Below debug (10)[m
   debug: 10,[m
   info: 20,[m
   prompt: 25, // New level for prompts (between info and warn)[m
[32m+[m[32m  llm: 26,    // New level for LLM interactions (between prompt and warn)[m
   warn: 30,[m
   error: 40,[m
   fatal: 50[m
[36m@@ -178,6 +179,9 @@[m [masync function logSh(message, level = 'INFO') {[m
     case 'prompt':[m
       logger.prompt(traceData, message);[m
       break;[m
[32m+[m[32m    case 'llm':[m
[32m+[m[32m      logger.llm(traceData, message);[m
[32m+[m[32m      break;[m
     default:[m
       logger.info(traceData, message);[m
   }[m
[36m@@ -1282,7 +1286,10 @@[m [masync function callLLM(llmProvider, prompt, options = {}, personality = null) {[m
     // 📢 AFFICHAGE PROMPT COMPLET POUR DEBUG AVEC INFO IA[m
     logSh(`\n🔍 ===== PROMPT ENVOYÉ À ${llmProvider.toUpperCase()} (${config.model}) | PERSONNALITÉ: ${personality?.nom || 'AUCUNE'} =====`, 'PROMPT');[m
     logSh(prompt, 'PROMPT');[m
[31m-    logSh(`===== FIN PROMPT ${llmProvider.toUpperCase()} (${personality?.nom || 'AUCUNE'}) =====\n`, 'PROMPT');[m
[32m+[m[41m    [m
[32m+[m[32m    // 📤 LOG LLM REQUEST COMPLET[m
[32m+[m[32m    logSh(`📤 LLM REQUEST [${llmProvider.toUpperCase()}] (${config.model}) | Personnalité: ${personality?.nom || 'AUCUNE'}`, 'LLM');[m
[32m+[m[32m    logSh(prompt, 'LLM');[m
     [m
     // Préparer la requête selon le provider[m
     const requestData = buildRequestData(llmProvider, prompt, options, personality);[m
[36m@@ -1293,8 +1300,12 @@[m [masync function callLLM(llmProvider, prompt, options = {}, personality = null) {[m
     // Parser la réponse selon le format du provider[m
     const content = parseResponse(llmProvider, response);[m
     [m
[32m+[m[32m    // 📥 LOG LLM RESPONSE COMPLET[m
[32m+[m[32m    logSh(`📥 LLM RESPONSE [${llmProvider.toUpperCase()}] (${config.model}) | Durée: ${Date.now() - startTime}ms`, 'LLM');[m
[32m+[m[32m    logSh(content, 'LLM');[m
[32m+[m[41m    [m
     const duration = Date.now() - startTime;[m
[31m-    logSh(`✅ ${llmProvider.toUpperCase()} (${personality?.nom || 'sans personnalité'}) réponse en ${duration}ms: "${content.substring(0, 150)}${content.length > 150 ? '...' : ''}"`, 'INFO');[m
[32m+[m[32m    logSh(`✅ ${llmProvider.toUpperCase()} (${personality?.nom || 'sans personnalité'}) réponse en ${duration}ms`, 'INFO');[m
     [m
     // Enregistrer les stats d'usage[m
     await recordUsageStats(llmProvider, prompt.length, content.length, duration);[m
[36m@@ -1727,6 +1738,8 @@[m [mmodule.exports = {[m
   LLM_CONFIG[m
 };[m
 [m
[32m+[m
[32m+[m
 /*[m
 ┌────────────────────────────────────────────────────────────────────┐[m
 │  File: lib/ElementExtraction.js                                    │[m
[36m@@ -2309,38 +2322,21 @@[m [mCONTEXTE:[m
 `;[m
 [m
   missingElements.forEach((missing, index) => {[m
[31m-    prompt += `${index + 1}. [${missing.name}] `;[m
[31m-    [m
[31m-    // INSTRUCTIONS SPÉCIFIQUES PAR TYPE[m
[31m-    if (missing.type.includes('titre_h1')) {[m
[31m-      prompt += `→ Titre H1 principal (8-10 mots) pour ${contextAnalysis.mainKeyword}\n`;[m
[31m-    } else if (missing.type.includes('titre_h2')) {[m
[31m-      prompt += `→ Titre H2 section (6-8 mots) lié à ${contextAnalysis.mainKeyword}\n`;[m
[31m-    } else if (missing.type.includes('titre_h3')) {[m
[31m-      prompt += `→ Sous-titre H3 (4-6 mots) spécialisé ${contextAnalysis.mainKeyword}\n`;[m
[31m-    } else if (missing.type.includes('texte') || missing.type.includes('txt')) {[m
[31m-      prompt += `→ Thème/sujet pour paragraphe 150 mots sur ${contextAnalysis.mainKeyword}\n`;[m
[31m-    } else if (missing.type.includes('faq_question')) {[m
[31m-      prompt += `→ Question client directe sur ${contextAnalysis.mainKeyword} (8-12 mots)\n`;[m
[31m-    } else if (missing.type.includes('faq_reponse')) {[m
[31m-      prompt += `→ Thème réponse experte ${contextAnalysis.mainKeyword} (2-4 mots)\n`;[m
[31m-    } else {[m
[31m-      prompt += `→ Expression/mot-clé pertinent ${contextAnalysis.mainKeyword}\n`;[m
[31m-    }[m
[32m+[m[32m    prompt += `${index + 1}. [${missing.name}] → Mot-clé SEO\n`;[m
   });[m
   [m
   prompt += `\nCONSIGNES:[m
[31m-- Reste dans le thème ${contextAnalysis.mainKeyword}[m
[31m-- Varie les angles et expressions[m
[31m-- Évite répétitions avec mots-clés existants[m
[31m-- Précis et pertinents[m
[32m+[m[32m- Thème: ${contextAnalysis.mainKeyword}[m
[32m+[m[32m- Mots-clés SEO naturels[m
[32m+[m[32m- Varie les termes[m
[32m+[m[32m- Évite répétitions[m
 [m
 FORMAT:[m
 [${missingElements[0].name}][m
[31m-Expression/mot-clé généré 1[m
[32m+[m[32mmot-clé[m
 [m
 [${missingElements[1] ? missingElements[1].name : 'exemple'}][m
[31m-Expression/mot-clé généré 2[m
[32m+[m[32mmot-clé[m
 [m
 etc...`;[m
 [m
[36m@@ -2596,6 +2592,11 @@[m [mconst { logSh } = require('./ErrorReporting');[m
 const { tracer } = require('./trace.js');[m
 const { selectMultiplePersonalitiesWithAI, getPersonalities } = require('./BrainConfig');[m
 [m
[32m+[m[32m// Utilitaire pour les délais[m
[32m+[m[32mfunction sleep(ms) {[m
[32m+[m[32m  return new Promise(resolve => setTimeout(resolve, ms));[m
[32m+[m[32m}[m
[32m+[m
 /**[m
  * NOUVELLE APPROCHE - Multi-Personnalités Batch Enhancement[m
  * 4 personnalités différentes utilisées dans le pipeline pour maximum d'anti-détection[m
[36m@@ -2804,8 +2805,8 @@[m [masync function generateAllContentBase(hierarchy, csvData, aiProvider) {[m
 }[m
 [m
 /**[m
[31m- * ÉTAPE 2 - Enhancement technique BATCH OPTIMISÉ avec IA configurable[m
[31m- * OPTIMISATION : 1 appel extraction + 1 appel enhancement au lieu de 20+[m
[32m+[m[32m * ÉTAPE 2 - Enhancement technique ÉLÉMENT PAR ÉLÉMENT avec IA configurable[m
[32m+[m[32m * NOUVEAU : Traitement individuel pour fiabilité maximale et debug précis[m
  */[m
 async function enhanceAllTechnicalTerms(baseContents, csvData, aiProvider) {[m
   logSh('🔧 === DÉBUT ENHANCEMENT TECHNIQUE ===', 'INFO');[m
[36m@@ -2872,96 +2873,96 @@[m [masync function enhanceAllTechnicalTerms(baseContents, csvData, aiProvider) {[m
 }[m
 [m
 /**[m
[31m- * NOUVELLE FONCTION : Extraction batch TOUS les termes techniques[m
[32m+[m[32m * Analyser un seul élément pour détecter les termes techniques[m
  */[m
[31m-async function extractAllTechnicalTermsBatch(baseContents, csvData, aiProvider) {[m
[31m-  const contentEntries = Object.keys(baseContents);[m
[31m-  [m
[31m-  const batchAnalysisPrompt = `MISSION: Analyser ces ${contentEntries.length} contenus et identifier leurs termes techniques.[m
[32m+[m[32masync function analyzeSingleElementTechnicalTerms(tag, content, csvData, aiProvider) {[m
[32m+[m[32m  const prompt = `MISSION: Analyser ce contenu et déterminer s'il contient des termes techniques.[m
 [m
 CONTEXTE: ${csvData.mc0} - Secteur: signalétique/impression[m
 [m
[31m-CONTENUS À ANALYSER:[m
[31m-[m
[31m-${contentEntries.map((tag, i) => `[${i + 1}] TAG: ${tag}[m
[31m-CONTENU: "${baseContents[tag]}"`).join('\n\n')}[m
[32m+[m[32mCONTENU À ANALYSER:[m
[32m+[m[32mTAG: ${tag}[m
[32m+[m[32mCONTENU: "${content}"[m
 [m
 CONSIGNES:[m
[31m-- Identifie UNIQUEMENT les vrais termes techniques métier/industrie[m
[32m+[m[32m- Cherche UNIQUEMENT des vrais termes techniques métier/industrie[m
 - Évite mots génériques (qualité, service, pratique, personnalisé, etc.)[m
[31m-- Focus: matériaux, procédés, normes, dimensions, technologies[m
[31m-- Si aucun terme technique → "AUCUN"[m
[32m+[m[32m- Focus: matériaux, procédés, normes, dimensions, technologies spécifiques[m
 [m
[31m-EXEMPLES VALIDES: dibond, impression UV, fraisage CNC, épaisseur 3mm, aluminium brossé[m
[31m-EXEMPLES INVALIDES: durable, pratique, personnalisé, moderne, esthétique[m
[32m+[m[32mEXEMPLES VALIDES: dibond, impression UV, fraisage CNC, épaisseur 3mm, aluminium brossé, anodisation[m
[32m+[m[32mEXEMPLES INVALIDES: durable, pratique, personnalisé, moderne, esthétique, haute performance[m
 [m
[31m-FORMAT RÉPONSE EXACT:[m
[31m-[1] dibond, impression UV, 3mm OU AUCUN[m
[31m-[2] aluminium, fraisage CNC OU AUCUN[m
[31m-[3] AUCUN[m
[31m-etc... (${contentEntries.length} lignes total)`;[m
[32m+[m[32mRÉPONSE REQUISE:[m
[32m+[m[32m- Si termes techniques trouvés: "OUI - termes: [liste des termes séparés par virgules]"[m
[32m+[m[32m- Si aucun terme technique: "NON"[m
[32m+[m
[32m+[m[32mEXEMPLE:[m
[32m+[m[32mOUI - termes: aluminium composite, impression numérique, gravure laser`;[m
 [m
   try {[m
[31m-    const analysisResponse = await callLLM(aiProvider, batchAnalysisPrompt, {[m
[31m-      temperature: 0.3,[m
[31m-      maxTokens: 2000[m
[31m-    }, csvData.personality);[m
[31m-    [m
[31m-    return parseAllTechnicalTermsResponse(analysisResponse, baseContents, contentEntries);[m
[31m-    [m
[32m+[m[32m    const response = await callLLM(aiProvider, prompt, { temperature: 0.3 });[m
[32m+[m[41m    [m
[32m+[m[32m    if (response.toUpperCase().startsWith('OUI')) {[m
[32m+[m[32m      // Extraire les termes de la réponse[m
[32m+[m[32m      const termsMatch = response.match(/termes:\s*(.+)/i);[m
[32m+[m[32m      const terms = termsMatch ? termsMatch[1].trim() : '';[m
[32m+[m[32m      logSh(`✅ [${tag}] Termes techniques détectés: ${terms}`, 'DEBUG');[m
[32m+[m[32m      return true;[m
[32m+[m[32m    } else {[m
[32m+[m[32m      logSh(`⏭️ [${tag}] Pas de termes techniques`, 'DEBUG');[m[41m  [m
[32m+[m[32m      return false;[m
[32m+[m[32m    }[m
   } catch (error) {[m
[31m-    logSh(`❌ FATAL: Extraction termes techniques batch échouée: ${error.message}`, 'ERROR');[m
[31m-    throw new Error(`FATAL: Analyse termes techniques impossible - arrêt du workflow: ${error.message}`);[m
[32m+[m[32m    logSh(`❌ ERREUR analyse ${tag}: ${error.message}`, 'ERROR');[m
[32m+[m[32m    return false; // En cas d'erreur, on skip l'enhancement[m
   }[m
 }[m
 [m
 /**[m
[31m- * NOUVELLE FONCTION : Enhancement batch TOUS les éléments[m
[32m+[m[32m * Enhancer un seul élément techniquement[m
  */[m
[31m-async function enhanceAllElementsTechnicalBatch(elementsNeedingEnhancement, csvData, aiProvider) {[m
[31m-  if (elementsNeedingEnhancement.length === 0) return {};[m
[31m-  [m
[31m-  const batchEnhancementPrompt = `MISSION: Améliore UNIQUEMENT la précision technique de ces ${elementsNeedingEnhancement.length} contenus.[m
[32m+[m[32masync function enhanceSingleElementTechnical(tag, content, csvData, aiProvider) {[m
[32m+[m[32m  const prompt = `MISSION: Améliore ce contenu en intégrant des termes techniques précis.[m
 [m
[31m-PERSONNALITÉ: ${csvData.personality?.nom} (${csvData.personality?.style})[m
[31m-CONTEXTE: ${csvData.mc0} - Secteur: Signalétique/impression[m
[31m-VOCABULAIRE PRÉFÉRÉ: ${csvData.personality?.vocabulairePref}[m
[31m-[m
[31m-CONTENUS + TERMES À AMÉLIORER:[m
[31m-[m
[31m-${elementsNeedingEnhancement.map((item, i) => `[${i + 1}] TAG: ${item.tag}[m
[31m-CONTENU ACTUEL: "${item.content}"[m
[31m-TERMES TECHNIQUES À INTÉGRER: ${item.technicalTerms.join(', ')}`).join('\n\n')}[m
[32m+[m[32mCONTEXTE: ${csvData.mc0} - Secteur: signalétique/impression[m
 [m
[31m-CONSIGNES STRICTES:[m
[31m-- Améliore UNIQUEMENT la précision technique, garde le style ${csvData.personality?.nom}[m
[31m-- GARDE la même longueur, structure et ton[m
[31m-- Intègre naturellement les termes techniques listés[m
[31m-- NE CHANGE PAS le fond du message ni le style personnel[m
[31m-- Utilise un vocabulaire expert mais accessible[m
[31m-- ÉVITE les répétitions excessives[m
[31m-- RESPECTE le niveau technique: ${csvData.personality?.niveauTechnique}[m
[31m-- Termes techniques secteur: dibond, aluminium, impression UV, fraisage, épaisseur, PMMA[m
[32m+[m[32mCONTENU À AMÉLIORER:[m
[32m+[m[32mTAG: ${tag}[m
[32m+[m[32mCONTENU: "${content}"[m
 [m
[31m-FORMAT RÉPONSE:[m
[31m-[1] Contenu avec amélioration technique selon ${csvData.personality?.nom}[m
[31m-[2] Contenu avec amélioration technique selon ${csvData.personality?.nom}[m
[31m-etc... (${elementsNeedingEnhancement.length} éléments total)`;[m
[32m+[m[32mOBJECTIFS:[m
[32m+[m[32m- Remplace les termes génériques par des termes techniques précis[m
[32m+[m[32m- Ajoute des spécifications techniques réalistes[m
[32m+[m[32m- Maintient le même style et longueur[m
[32m+[m[32m- Intègre naturellement: matériaux (dibond, aluminium composite), procédés (impression UV, gravure laser), dimensions, normes[m
[32m+[m
[32m+[m[32mEXEMPLE DE TRANSFORMATION:[m
[32m+[m[32m"matériaux haute performance" → "dibond 3mm ou aluminium composite"[m
[32m+[m[32m"impression moderne" → "impression UV haute définition"[m
[32m+[m[32m"fixation solide" → "fixation par chevilles inox Ø6mm"[m
[32m+[m
[32m+[m[32mCONTRAINTES:[m
[32m+[m[32m- GARDE la même structure[m
[32m+[m[32m- MÊME longueur approximative[m
[32m+[m[32m- Style cohérent avec l'original[m
[32m+[m[32m- RÉPONDS DIRECTEMENT par le contenu amélioré, sans préfixe`;[m
 [m
   try {[m
[31m-    const enhanced = await callLLM(aiProvider, batchEnhancementPrompt, {[m
[31m-      temperature: 0.4,[m
[31m-      maxTokens: 5000 // Plus large pour batch total[m
[31m-    }, csvData.personality);[m
[31m-    [m
[31m-    return parseTechnicalEnhancementBatchResponse(enhanced, elementsNeedingEnhancement);[m
[31m-    [m
[32m+[m[32m    const enhancedContent = await callLLM(aiProvider, prompt, { temperature: 0.7 });[m
[32m+[m[32m    return enhancedContent.trim();[m
   } catch (error) {[m
[31m-    logSh(`❌ FATAL: Enhancement technique batch échoué: ${error.message}`, 'ERROR');[m
[31m-    throw new Error(`FATAL: Enhancement technique batch impossible - arrêt du workflow: ${error.message}`);[m
[32m+[m[32m    logSh(`❌ ERREUR enhancement ${tag}: ${error.message}`, 'ERROR');[m
[32m+[m[32m    return content; // En cas d'erreur, on retourne le contenu original[m
   }[m
 }[m
 [m
[32m+[m[32m// ANCIENNES FONCTIONS BATCH SUPPRIMÉES - REMPLACÉES PAR TRAITEMENT INDIVIDUEL[m
[32m+[m
[32m+[m[32m/**[m
[32m+[m[32m * NOUVELLE FONCTION : Enhancement batch TOUS les éléments[m
[32m+[m[32m */[m
[32m+[m[32m// FONCTION SUPPRIMÉE : enhanceAllElementsTechnicalBatch() - Remplacée par traitement individuel[m
[32m+[m
 /**[m
  * ÉTAPE 3 - Enhancement transitions BATCH avec IA configurable[m
  */[m
[36m@@ -3015,7 +3016,8 @@[m [masync function enhanceAllTransitions(baseContents, csvData, aiProvider) {[m
       [m
       const batchTransitionsPrompt = `MISSION: Améliore UNIQUEMENT les transitions et fluidité de ces contenus.[m
 [m
[31m-PERSONNALITÉ: ${csvData.personality?.nom} (${csvData.personality?.style})[m
[32m+[m[32mCONTEXTE: Article SEO professionnel pour site web commercial[m
[32m+[m[32mPERSONNALITÉ: ${csvData.personality?.nom} (${csvData.personality?.style} adapté web)[m
 CONNECTEURS PRÉFÉRÉS: ${csvData.personality?.connecteursPref}[m
 [m
 CONTENUS:[m
[36m@@ -3093,9 +3095,10 @@[m [masync function enhanceAllPersonalityStyle(baseContents, csvData, aiProvider) {[m
       [m
       const batchStylePrompt = `MISSION: Adapte UNIQUEMENT le style de ces contenus selon ${personality.nom}.[m
 [m
[32m+[m[32mCONTEXTE: Finalisation article SEO pour site e-commerce professionnel[m
 PERSONNALITÉ: ${personality.nom}[m
 DESCRIPTION: ${personality.description}[m
[31m-STYLE CIBLE: ${personality.style}[m
[32m+[m[32mSTYLE CIBLE: ${personality.style} adapté au web professionnel[m
 VOCABULAIRE: ${personality.vocabulairePref}[m
 CONNECTEURS: ${personality.connecteursPref}[m
 NIVEAU TECHNIQUE: ${personality.niveauTechnique}[m
[36m@@ -3149,9 +3152,8 @@[m [metc...`;[m
 /**[m
  * Sleep function replacement for Utilities.sleep[m
  */[m
[31m-function sleep(ms) {[m
[31m-  return new Promise(resolve => setTimeout(resolve, ms));[m
[31m-}[m
[32m+[m
[32m+[m[32m// FONCTION SUPPRIMÉE : sleep() dupliquée - déjà définie ligne 12[m
 [m
 /**[m
  * RESTAURÉ DEPUIS .GS : Génération des paires FAQ cohérentes[m
[36m@@ -3187,32 +3189,33 @@[m [masync function generateFAQPairsRestored(faqPairs, csvData, aiProvider) {[m
 function createBatchFAQPairsPrompt(faqPairs, csvData) {[m
   const personality = csvData.personality;[m
   [m
[31m-  let prompt = `PERSONNALITÉ: ${personality.nom} | ${personality.description}[m
[31m-STYLE: ${personality.style}[m
[31m-VOCABULAIRE: ${personality.vocabulairePref}[m
[31m-CONNECTEURS: ${personality.connecteursPref}[m
[31m-NIVEAU TECHNIQUE: ${personality.niveauTechnique}[m
[32m+[m[32m  let prompt = `=== 1. CONTEXTE ===[m
[32m+[m[32mEntreprise: Autocollant.fr - signalétique personnalisée[m
[32m+[m[32mSujet: ${csvData.mc0}[m
[32m+[m[32mSection: FAQ pour article SEO commercial[m
 [m
[31m-GÉNÈRE ${faqPairs.length} PAIRES FAQ COHÉRENTES pour ${csvData.mc0}:[m
[32m+[m[32m=== 2. PERSONNALITÉ ===[m
[32m+[m[32mRédacteur: ${personality.nom}[m
[32m+[m[32mStyle: ${personality.style}[m
[32m+[m[32mTon: ${personality.description || 'professionnel'}[m
 [m
[31m-RÈGLES STRICTES:[m
[31m-- QUESTIONS: Neutres, directes, langage client naturel (8-15 mots)[m
[31m-- RÉPONSES: Style ${personality.style}, vocabulaire ${personality.vocabulairePref} (50-80 mots)[m
[31m-- Sujets à couvrir: prix, livraison, personnalisation, installation, durabilité[m
[31m-- ÉVITE répétitions excessives et expressions trop familières[m
[31m-- Style ${personality.nom} reconnaissable mais PROFESSIONNEL[m
[31m-- PAS de messages d'excuse ("je n'ai pas l'information")[m
[31m-- RÉPONDS DIRECTEMENT par questions et réponses, sans préfixe[m
[32m+[m[32m=== 3. RÈGLES GÉNÉRALES ===[m
[32m+[m[32m- Questions naturelles de clients[m
[32m+[m[32m- Réponses expertes et rassurantes[m[41m  [m
[32m+[m[32m- Langage professionnel mais accessible[m
[32m+[m[32m- Textes rédigés humainement et de façon authentique[m
[32m+[m[32m- Couvrir: prix, livraison, personnalisation, installation, durabilité[m
[32m+[m[32m- IMPÉRATIF: Respecter strictement les contraintes XML[m
[32m+[m
[32m+[m[32m=== 4. PAIRES FAQ À GÉNÉRER ===[m
 [m
[31m-PAIRES À GÉNÉRER:[m
 `;[m
 [m
   faqPairs.forEach((pair, index) => {[m
     const questionTag = pair.question.tag.replace(/\|/g, '').replace(/[{}]/g, '').replace(/<\/?strong>/g, '');[m
     const answerTag = pair.answer.tag.replace(/\|/g, '').replace(/[{}]/g, '').replace(/<\/?strong>/g, '');[m
     [m
[31m-    prompt += `${index + 1}. [${questionTag}] + [${answerTag}][m
[31m-   Question client sur ${csvData.mc0} → Réponse ${personality.style}[m
[32m+[m[32m    prompt += `${index + 1}. [${questionTag}] + [${answerTag}] - Paire FAQ naturelle[m
 `;[m
   });[m
   [m
[36m@@ -3533,10 +3536,25 @@[m [mfunction findAssociatedTitle(textElement, existingResults) {[m
 function createBatchBasePrompt(elements, type, csvData, existingResults = {}) {[m
   const personality = csvData.personality;[m
   [m
[31m-  let prompt = `RÉDACTEUR: ${personality.nom} | Style: ${personality.style}[m
[31m-SUJET: ${csvData.mc0}[m
[31m-[m
[31m-${type === 'titre' ? 'GÉNÈRE DES TITRES COURTS ET IMPACTANTS' : `GÉNÈRE ${elements.length} ${type.toUpperCase()}S PROFESSIONNELS`}:[m
[32m+[m[32m  let prompt = `=== 1. CONTEXTE ===[m
[32m+[m[32mEntreprise: Autocollant.fr - signalétique personnalisée[m
[32m+[m[32mSujet: ${csvData.mc0}[m
[32m+[m[32mType d'article: SEO professionnel pour site commercial[m
[32m+[m
[32m+[m[32m=== 2. PERSONNALITÉ ===[m
[32m+[m[32mRédacteur: ${personality.nom}[m
[32m+[m[32mStyle: ${personality.style}[m
[32m+[m[32mTon: ${personality.description || 'professionnel'}[m
[32m+[m
[32m+[m[32m=== 3. RÈGLES GÉNÉRALES ===[m
[32m+[m[32m- Contenu SEO optimisé[m
[32m+[m[32m- Langage naturel et fluide[m
[32m+[m[32m- Éviter répétitions[m
[32m+[m[32m- Pas de références techniques dans le contenu[m
[32m+[m[32m- Textes rédigés humainement et de façon authentique[m
[32m+[m[32m- IMPÉRATIF: Respecter strictement les contraintes XML (nombre de mots, etc.)[m
[32m+[m
[32m+[m[32m=== 4. ÉLÉMENTS À GÉNÉRER ===[m
 `;[m
 [m
   // AJOUTER CONTEXTE DES TITRES POUR LES TEXTES[m
[36m@@ -3548,7 +3566,7 @@[m [m${type === 'titre' ? 'GÉNÈRE DES TITRES COURTS ET IMPACTANTS' : `GÉNÈRE ${el[m
     [m
     if (generatedTitles.length > 0) {[m
       prompt += `[m
[31m-CONTEXTE - TITRES GÉNÉRÉS:[m
[32m+[m[32mTitres existants pour contexte:[m
 ${generatedTitles.join('\n')}[m
 [m
 `;[m
[36m@@ -3560,34 +3578,33 @@[m [m${generatedTitles.join('\n')}[m
     [m
     prompt += `${index + 1}. [${cleanTag}] `;[m
     [m
[31m-    // INSTRUCTIONS SPÉCIFIQUES ET COURTES PAR TYPE[m
[32m+[m[32m    // INSTRUCTIONS PROPRES PAR ÉLÉMENT[m
     if (type === 'titre') {[m
       if (elementInfo.element.type === 'titre_h1') {[m
[31m-        prompt += `CRÉER UN TITRE H1 PRINCIPAL (8-12 mots) sur "${csvData.t0}" - NE PAS écrire "Titre_H1_1"\n`;[m
[32m+[m[32m        prompt += `Titre principal accrocheur\n`;[m
       } else if (elementInfo.element.type === 'titre_h2') {[m
[31m-        prompt += `CRÉER UN TITRE H2 SECTION (6-10 mots) sur "${csvData.mc0}" - NE PAS écrire "Titre_H2_X"\n`;[m
[32m+[m[32m        prompt += `Titre de section engageant\n`;[m
       } else if (elementInfo.element.type === 'titre_h3') {[m
[31m-        prompt += `CRÉER UN TITRE H3 SOUS-SECTION (4-8 mots) - NE PAS écrire "Titre_H3_X"\n`;[m
[32m+[m[32m        prompt += `Sous-titre spécialisé\n`;[m
       } else {[m
[31m-        prompt += `CRÉER UN TITRE ACCROCHEUR (4-10 mots) sur "${csvData.mc0}" - NE PAS écrire "Titre_"\n`;[m
[32m+[m[32m        prompt += `Titre pertinent\n`;[m
       }[m
     } else if (type === 'texte') {[m
[31m-      const wordCount = elementInfo.element.name && elementInfo.element.name.includes('H2') ? '150' : '100';[m
[31m-      prompt += `Paragraphe ${wordCount} mots, style ${personality.style}\n`;[m
[32m+[m[32m      prompt += `Paragraphe informatif\n`;[m
       [m
       // ASSOCIER LE TITRE CORRESPONDANT AUTOMATIQUEMENT[m
       const associatedTitle = findAssociatedTitle(elementInfo, existingResults);[m
       if (associatedTitle) {[m
[31m-        prompt += `   Développe le titre: "${associatedTitle}"\n`;[m
[32m+[m[32m        prompt += `   Contexte: "${associatedTitle}"\n`;[m
       }[m
       [m
       if (elementInfo.element.resolvedContent) {[m
[31m-        prompt += `   Thème: "${elementInfo.element.resolvedContent}"\n`;[m
[32m+[m[32m        prompt += `   Angle: "${elementInfo.element.resolvedContent}"\n`;[m
       }[m
     } else if (type === 'intro') {[m
[31m-      prompt += `Introduction 80-100 mots, ton accueillant\n`;[m
[32m+[m[32m      prompt += `Introduction engageante\n`;[m
     } else {[m
[31m-      prompt += `Contenu pertinent pour ${csvData.mc0}\n`;[m
[32m+[m[32m      prompt += `Contenu pertinent\n`;[m
     }[m
   });[m
   [m
[36m@@ -3597,15 +3614,16 @@[m [m${generatedTitles.join('\n')}[m
 - Phrases: ${personality.longueurPhrases}[m
 - Niveau technique: ${personality.niveauTechnique}[m
 [m
[31m-CONSIGNES STRICTES:[m
[31m-- RESPECTE le style ${personality.style} de ${personality.nom} mais RESTE PROFESSIONNEL[m
[31m-- INTERDICTION ABSOLUE: "du coup", "bon", "alors", "franchement", "nickel", "tip-top", "costaud" en excès[m
[31m-- VARIE les connecteurs: ${personality.connecteursPref}[m
[31m-- POUR LES TITRES: SEULEMENT le titre réel, JAMAIS de référence "Titre_H1_1" ou "Titre_H2_7"[m
[31m-- EXEMPLE TITRE: "Plaques personnalisées résistantes aux intempéries" PAS "Titre_H2_1"  [m
[31m-- RÉPONDS DIRECTEMENT par le contenu demandé, SANS introduction ni nom de tag[m
[31m-- PAS de message d'excuse du type "je n'ai pas l'information"[m
[31m-- CONTENU cohérent et professionnel, évite la sur-familiarité[m
[32m+[m[32mCONSIGNES STRICTES POUR ARTICLE SEO:[m
[32m+[m[32m- CONTEXTE: Article professionnel pour site e-commerce, destiné aux clients potentiels[m
[32m+[m[32m- STYLE: ${personality.style} de ${personality.nom} mais ADAPTÉ au web professionnel[m
[32m+[m[32m- INTERDICTION ABSOLUE: expressions trop familières répétées ("du coup", "bon", "franchement", "nickel", "tip-top")[m[41m  [m
[32m+[m[32m- VOCABULAIRE: Mélange expertise technique + accessibilité client[m
[32m+[m[32m- SEO: Utilise naturellement "${csvData.mc0}" et termes associés[m
[32m+[m[32m- POUR LES TITRES: Titre SEO attractif UNIQUEMENT, JAMAIS "Titre_H1_1" ou "Titre_H2_7"[m
[32m+[m[32m- EXEMPLE TITRE: "Plaques personnalisées résistantes : guide complet 2024"[m[41m [m
[32m+[m[32m- CONTENU: Informatif, rassurant, incite à l'achat SANS être trop commercial[m
[32m+[m[32m- RÉPONDS DIRECTEMENT par le contenu web demandé, SANS préfixe[m
 [m
 FORMAT DE RÉPONSE ${type === 'titre' ? '(TITRES UNIQUEMENT)' : ''}:[m
 [${elements[0].tag.replace(/\|/g, '').replace(/[{}]/g, '').replace(/<\/?strong>/g, '')}][m
[36m@@ -3696,107 +3714,11 @@[m [mfunction cleanXMLTagsFromContent(content) {[m
 [m
 // ============= PARSING FUNCTIONS =============[m
 [m
[31m-/**[m
[31m- * Parser réponse extraction termes[m
[31m- */[m
[31m-function parseAllTechnicalTermsResponse(response, baseContents, contentEntries) {[m
[31m-  const results = [];[m
[31m-  const regex = /\[(\d+)\]\s*([^[]*?)(?=\[\d+\]|$)/gs;[m
[31m-  let match;[m
[31m-  const parsedItems = {};[m
[31m-  [m
[31m-  // Parser la réponse[m
[31m-  while ((match = regex.exec(response)) !== null) {[m
[31m-    const index = parseInt(match[1]) - 1; // Convertir en 0-indexé[m
[31m-    const termsText = match[2].trim();[m
[31m-    parsedItems[index] = termsText;[m
[31m-  }[m
[31m-  [m
[31m-  // Mapper aux éléments[m
[31m-  contentEntries.forEach((tag, index) => {[m
[31m-    const termsText = parsedItems[index] || 'AUCUN';[m
[31m-    const hasTerms = !termsText.toUpperCase().includes('AUCUN');[m
[31m-    [m
[31m-    const technicalTerms = hasTerms ? [m
[31m-      termsText.split(',').map(t => t.trim()).filter(t => t.length > 0) : [m
[31m-      [];[m
[31m-    [m
[31m-    results.push({[m
[31m-      tag: tag,[m
[31m-      content: baseContents[tag],[m
[31m-      technicalTerms: technicalTerms,[m
[31m-      needsEnhancement: hasTerms && technicalTerms.length > 0[m
[31m-    });[m
[31m-    [m
[31m-    logSh(`🔍 [${tag}]: ${hasTerms ? technicalTerms.join(', ') : 'pas de termes techniques'}`, 'DEBUG');[m
[31m-  });[m
[31m-  [m
[31m-  const enhancementCount = results.filter(r => r.needsEnhancement).length;[m
[31m-  logSh(`📊 Analyse terminée: ${enhancementCount}/${contentEntries.length} éléments ont besoin d'enhancement`, 'INFO');[m
[31m-  [m
[31m-  return results;[m
[31