- Add comprehensive documentation (IMPLEMENTATION_COMPLETE, ProductionReady, QUICK_START, STARTUP_ANALYSIS) - Add startup scripts (start-server.sh, start-server.bat, check-setup.sh) - Add configs directory structure with README - Add ValidationGuards and Main.js backup - Add LLM monitoring HTML interface - Add cache templates and XML files - Add technical report (rapport_technique.md) - Add bundled code.js 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
283 lines
8.4 KiB
JavaScript
283 lines
8.4 KiB
JavaScript
// ========================================
|
|
// FICHIER: ValidationGuards.js
|
|
// SOLUTION D: Validations guard pour détecter problèmes de synchronisation
|
|
// ========================================
|
|
|
|
const { logSh } = require('./ErrorReporting');
|
|
|
|
/**
|
|
* PATTERNS DE PLACEHOLDERS DÉTECTÉS
|
|
*/
|
|
const PLACEHOLDER_PATTERNS = {
|
|
// Pattern "[XXX non défini]" ou "[XXX non résolu]"
|
|
unresolvedVariable: /\[([^\]]+) non (défini|résolu)\]/g,
|
|
|
|
// Pattern "{{XXX}}" (variable brute non résolue)
|
|
rawVariable: /\{\{([^}]+)\}\}/g,
|
|
|
|
// Pattern vide ou null
|
|
empty: /^\s*$/
|
|
};
|
|
|
|
/**
|
|
* Vérifie si une chaîne contient des placeholders non résolus
|
|
* @param {string} text - Texte à vérifier
|
|
* @param {Object} options - Options de vérification
|
|
* @returns {Object} Résultat de la validation
|
|
*/
|
|
function hasUnresolvedPlaceholders(text, options = {}) {
|
|
const {
|
|
checkRawVariables = false, // Vérifier aussi les {{XXX}}
|
|
allowEmpty = false // Autoriser les textes vides
|
|
} = options;
|
|
|
|
if (!text || typeof text !== 'string') {
|
|
return {
|
|
hasIssues: !allowEmpty,
|
|
issues: !allowEmpty ? ['Text is null or not a string'] : [],
|
|
placeholders: []
|
|
};
|
|
}
|
|
|
|
const issues = [];
|
|
const placeholders = [];
|
|
|
|
// Vérifier "[XXX non défini]" ou "[XXX non résolu]"
|
|
const unresolvedMatches = text.match(PLACEHOLDER_PATTERNS.unresolvedVariable);
|
|
if (unresolvedMatches) {
|
|
placeholders.push(...unresolvedMatches);
|
|
issues.push(`Found ${unresolvedMatches.length} unresolved placeholders: ${unresolvedMatches.join(', ')}`);
|
|
}
|
|
|
|
// Vérifier "{{XXX}}" si demandé
|
|
if (checkRawVariables) {
|
|
const rawMatches = text.match(PLACEHOLDER_PATTERNS.rawVariable);
|
|
if (rawMatches) {
|
|
placeholders.push(...rawMatches);
|
|
issues.push(`Found ${rawMatches.length} raw variables: ${rawMatches.join(', ')}`);
|
|
}
|
|
}
|
|
|
|
// Vérifier vide
|
|
if (!allowEmpty && PLACEHOLDER_PATTERNS.empty.test(text)) {
|
|
issues.push('Text is empty or whitespace only');
|
|
}
|
|
|
|
return {
|
|
hasIssues: issues.length > 0,
|
|
issues,
|
|
placeholders
|
|
};
|
|
}
|
|
|
|
/**
|
|
* GUARD: Valider un élément avant utilisation dans génération
|
|
* Throw une erreur si l'élément contient des placeholders non résolus
|
|
* @param {Object} element - Élément à valider
|
|
* @param {Object} options - Options de validation
|
|
* @throws {Error} Si validation échoue
|
|
*/
|
|
function validateElement(element, options = {}) {
|
|
const {
|
|
strict = true, // Mode strict: throw error
|
|
checkInstructions = true, // Vérifier les instructions
|
|
checkContent = true, // Vérifier le contenu résolu
|
|
context = '' // Contexte pour message d'erreur
|
|
} = options;
|
|
|
|
const errors = [];
|
|
|
|
// Vérifier resolvedContent
|
|
if (checkContent && element.resolvedContent) {
|
|
const contentCheck = hasUnresolvedPlaceholders(element.resolvedContent, { allowEmpty: false });
|
|
if (contentCheck.hasIssues) {
|
|
errors.push({
|
|
field: 'resolvedContent',
|
|
content: element.resolvedContent.substring(0, 100),
|
|
issues: contentCheck.issues,
|
|
placeholders: contentCheck.placeholders
|
|
});
|
|
}
|
|
}
|
|
|
|
// Vérifier instructions
|
|
if (checkInstructions && element.instructions) {
|
|
const instructionsCheck = hasUnresolvedPlaceholders(element.instructions, { allowEmpty: false });
|
|
if (instructionsCheck.hasIssues) {
|
|
errors.push({
|
|
field: 'instructions',
|
|
content: element.instructions.substring(0, 100),
|
|
issues: instructionsCheck.issues,
|
|
placeholders: instructionsCheck.placeholders
|
|
});
|
|
}
|
|
}
|
|
|
|
// Si erreurs trouvées
|
|
if (errors.length > 0) {
|
|
const errorMessage = `
|
|
❌ VALIDATION FAILED: Element [${element.name || 'unknown'}] contains unresolved placeholders
|
|
${context ? `Context: ${context}` : ''}
|
|
|
|
Errors found:
|
|
${errors.map((err, i) => `
|
|
${i + 1}. Field: ${err.field}
|
|
Content preview: "${err.content}..."
|
|
Issues: ${err.issues.join('; ')}
|
|
Placeholders: ${err.placeholders.join(', ')}
|
|
`).join('\n')}
|
|
|
|
⚠️ This indicates a synchronization problem between resolvedContent and instructions.
|
|
💡 Check that MissingKeywords.js properly updates BOTH fields when generating keywords.
|
|
`;
|
|
|
|
logSh(errorMessage, 'ERROR');
|
|
|
|
if (strict) {
|
|
throw new Error(`Element [${element.name}] validation failed: ${errors.map(e => e.issues.join('; ')).join(' | ')}`);
|
|
}
|
|
|
|
return { valid: false, errors };
|
|
}
|
|
|
|
logSh(`✅ Validation OK: Element [${element.name}] has no unresolved placeholders`, 'DEBUG');
|
|
return { valid: true, errors: [] };
|
|
}
|
|
|
|
/**
|
|
* GUARD: Valider une hiérarchie complète
|
|
* @param {Object} hierarchy - Hiérarchie à valider
|
|
* @param {Object} options - Options de validation
|
|
* @returns {Object} Résultat de validation avec statistiques
|
|
*/
|
|
function validateHierarchy(hierarchy, options = {}) {
|
|
const {
|
|
strict = false // En mode non-strict, on continue même avec des erreurs
|
|
} = options;
|
|
|
|
const stats = {
|
|
totalElements: 0,
|
|
validElements: 0,
|
|
invalidElements: 0,
|
|
errors: []
|
|
};
|
|
|
|
Object.entries(hierarchy).forEach(([path, section]) => {
|
|
// Valider titre
|
|
if (section.title && section.title.originalElement) {
|
|
stats.totalElements++;
|
|
try {
|
|
const result = validateElement(section.title.originalElement, {
|
|
strict,
|
|
context: `Hierarchy path: ${path} (title)`
|
|
});
|
|
if (result.valid) {
|
|
stats.validElements++;
|
|
} else {
|
|
stats.invalidElements++;
|
|
stats.errors.push({ path, element: 'title', errors: result.errors });
|
|
}
|
|
} catch (error) {
|
|
stats.invalidElements++;
|
|
stats.errors.push({ path, element: 'title', error: error.message });
|
|
if (strict) throw error;
|
|
}
|
|
}
|
|
|
|
// Valider texte
|
|
if (section.text && section.text.originalElement) {
|
|
stats.totalElements++;
|
|
try {
|
|
const result = validateElement(section.text.originalElement, {
|
|
strict,
|
|
context: `Hierarchy path: ${path} (text)`
|
|
});
|
|
if (result.valid) {
|
|
stats.validElements++;
|
|
} else {
|
|
stats.invalidElements++;
|
|
stats.errors.push({ path, element: 'text', errors: result.errors });
|
|
}
|
|
} catch (error) {
|
|
stats.invalidElements++;
|
|
stats.errors.push({ path, element: 'text', error: error.message });
|
|
if (strict) throw error;
|
|
}
|
|
}
|
|
|
|
// Valider questions FAQ
|
|
if (section.questions && section.questions.length > 0) {
|
|
section.questions.forEach((q, index) => {
|
|
if (q.originalElement) {
|
|
stats.totalElements++;
|
|
try {
|
|
const result = validateElement(q.originalElement, {
|
|
strict,
|
|
context: `Hierarchy path: ${path} (question ${index + 1})`
|
|
});
|
|
if (result.valid) {
|
|
stats.validElements++;
|
|
} else {
|
|
stats.invalidElements++;
|
|
stats.errors.push({ path, element: `question_${index + 1}`, errors: result.errors });
|
|
}
|
|
} catch (error) {
|
|
stats.invalidElements++;
|
|
stats.errors.push({ path, element: `question_${index + 1}`, error: error.message });
|
|
if (strict) throw error;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Log résumé
|
|
if (stats.invalidElements > 0) {
|
|
logSh(`⚠️ Hierarchy validation: ${stats.invalidElements}/${stats.totalElements} elements have unresolved placeholders`, 'WARNING');
|
|
logSh(` Errors: ${JSON.stringify(stats.errors, null, 2)}`, 'WARNING');
|
|
} else {
|
|
logSh(`✅ Hierarchy validation: All ${stats.totalElements} elements are valid`, 'INFO');
|
|
}
|
|
|
|
return {
|
|
valid: stats.invalidElements === 0,
|
|
stats
|
|
};
|
|
}
|
|
|
|
/**
|
|
* HELPER: Extraire tous les placeholders d'un texte
|
|
* @param {string} text - Texte à analyser
|
|
* @returns {Array} Liste des placeholders trouvés
|
|
*/
|
|
function extractPlaceholders(text) {
|
|
if (!text || typeof text !== 'string') return [];
|
|
|
|
const placeholders = [];
|
|
|
|
// Extraire "[XXX non défini]"
|
|
const unresolvedMatches = text.match(PLACEHOLDER_PATTERNS.unresolvedVariable);
|
|
if (unresolvedMatches) {
|
|
placeholders.push(...unresolvedMatches);
|
|
}
|
|
|
|
// Extraire "{{XXX}}"
|
|
const rawMatches = text.match(PLACEHOLDER_PATTERNS.rawVariable);
|
|
if (rawMatches) {
|
|
placeholders.push(...rawMatches);
|
|
}
|
|
|
|
return placeholders;
|
|
}
|
|
|
|
module.exports = {
|
|
// Fonctions principales
|
|
validateElement,
|
|
validateHierarchy,
|
|
hasUnresolvedPlaceholders,
|
|
|
|
// Helpers
|
|
extractPlaceholders,
|
|
PLACEHOLDER_PATTERNS
|
|
};
|