#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const EXPORTS_DIR = path.join(__dirname, '../claude-exports-last-3-days'); /** * Parse un fichier de session pour extraire les tool uses */ function parseSessionFile(filePath) { const content = fs.readFileSync(filePath, 'utf-8'); const tools = []; // Chercher tous les blocs JSON qui contiennent des tool uses const jsonBlockRegex = /\[\s*\{[\s\S]*?"type":\s*"tool_use"[\s\S]*?\}\s*\]/g; const matches = content.match(jsonBlockRegex); if (!matches) return tools; for (const match of matches) { try { const parsed = JSON.parse(match); for (const item of parsed) { if (item.type === 'tool_use' && (item.name === 'Edit' || item.name === 'Write')) { tools.push({ name: item.name, input: item.input }); } } } catch (e) { // Skip invalid JSON } } return tools; } /** * Analyse pourquoi un Edit a été skippé */ function analyzeSkippedEdit(filePath, oldString) { if (!fs.existsSync(filePath)) { return { reason: 'FILE_NOT_EXIST', details: 'Fichier n\'existe pas' }; } const content = fs.readFileSync(filePath, 'utf-8'); if (!content.includes(oldString)) { // Vérifier si une partie de old_string existe const oldLines = oldString.split('\n').filter(l => l.trim()); const matchingLines = oldLines.filter(line => content.includes(line.trim())); if (matchingLines.length > 0) { return { reason: 'PARTIAL_MATCH', details: `${matchingLines.length}/${oldLines.length} lignes trouvées - code probablement modifié` }; } else { return { reason: 'NO_MATCH', details: 'Code complètement différent - changement déjà appliqué ou code refactorisé' }; } } return { reason: 'OK', details: 'Devrait fonctionner' }; } /** * Main */ function main() { console.log('🔍 Analyse des exports Claude skippés...\n'); const sessionFiles = fs.readdirSync(EXPORTS_DIR) .filter(f => f.endsWith('-session.md')) .sort((a, b) => { const numA = parseInt(a.split('-')[0]); const numB = parseInt(b.split('-')[0]); return numB - numA; }); const skippedAnalysis = { FILE_NOT_EXIST: [], PARTIAL_MATCH: [], NO_MATCH: [], FILE_EXISTS: [] // Pour les Write }; let totalSkipped = 0; for (const sessionFile of sessionFiles) { const filePath = path.join(EXPORTS_DIR, sessionFile); const tools = parseSessionFile(filePath); for (const tool of tools) { if (tool.name === 'Edit') { const { file_path, old_string } = tool.input; if (!fs.existsSync(file_path)) { skippedAnalysis.FILE_NOT_EXIST.push({ session: sessionFile, file: file_path, preview: old_string.substring(0, 80) }); totalSkipped++; } else { const content = fs.readFileSync(file_path, 'utf-8'); if (!content.includes(old_string)) { const analysis = analyzeSkippedEdit(file_path, old_string); if (analysis.reason === 'PARTIAL_MATCH') { skippedAnalysis.PARTIAL_MATCH.push({ session: sessionFile, file: file_path, details: analysis.details, preview: old_string.substring(0, 80) }); } else { skippedAnalysis.NO_MATCH.push({ session: sessionFile, file: file_path, preview: old_string.substring(0, 80) }); } totalSkipped++; } } } else if (tool.name === 'Write') { const { file_path } = tool.input; if (fs.existsSync(file_path)) { skippedAnalysis.FILE_EXISTS.push({ session: sessionFile, file: file_path }); totalSkipped++; } } } } console.log(`📊 Total skippés: ${totalSkipped}\n`); console.log('═══════════════════════════════════════════════════════'); console.log('🚫 FICHIERS N\'EXISTANT PAS (Edit)'); console.log(` ${skippedAnalysis.FILE_NOT_EXIST.length} cas\n`); const fileNotExistByFile = {}; for (const item of skippedAnalysis.FILE_NOT_EXIST) { if (!fileNotExistByFile[item.file]) { fileNotExistByFile[item.file] = 0; } fileNotExistByFile[item.file]++; } Object.entries(fileNotExistByFile) .sort((a, b) => b[1] - a[1]) .slice(0, 10) .forEach(([file, count]) => { console.log(` ${count}x - ${file}`); }); console.log('\n═══════════════════════════════════════════════════════'); console.log('⚠️ CORRESPONDANCE PARTIELLE (Edit - code probablement modifié)'); console.log(` ${skippedAnalysis.PARTIAL_MATCH.length} cas\n`); const partialByFile = {}; for (const item of skippedAnalysis.PARTIAL_MATCH) { if (!partialByFile[item.file]) { partialByFile[item.file] = 0; } partialByFile[item.file]++; } Object.entries(partialByFile) .sort((a, b) => b[1] - a[1]) .slice(0, 10) .forEach(([file, count]) => { console.log(` ${count}x - ${file}`); }); console.log('\n═══════════════════════════════════════════════════════'); console.log('❌ AUCUNE CORRESPONDANCE (Edit - changement déjà appliqué)'); console.log(` ${skippedAnalysis.NO_MATCH.length} cas\n`); const noMatchByFile = {}; for (const item of skippedAnalysis.NO_MATCH) { if (!noMatchByFile[item.file]) { noMatchByFile[item.file] = 0; } noMatchByFile[item.file]++; } Object.entries(noMatchByFile) .sort((a, b) => b[1] - a[1]) .slice(0, 10) .forEach(([file, count]) => { console.log(` ${count}x - ${file}`); }); console.log('\n═══════════════════════════════════════════════════════'); console.log('✅ FICHIERS DÉJÀ EXISTANTS (Write - comportement normal)'); console.log(` ${skippedAnalysis.FILE_EXISTS.length} cas\n`); skippedAnalysis.FILE_EXISTS.forEach(item => { console.log(` ${item.session} → ${item.file}`); }); console.log('\n═══════════════════════════════════════════════════════'); console.log('💡 CONCLUSION:\n'); console.log(` ✅ Write skippés: ${skippedAnalysis.FILE_EXISTS.length} (NORMAL - ne pas écraser)`); console.log(` ❌ Edit skippés (NO_MATCH): ${skippedAnalysis.NO_MATCH.length} (changements déjà appliqués)`); console.log(` ⚠️ Edit skippés (PARTIAL_MATCH): ${skippedAnalysis.PARTIAL_MATCH.length} (code modifié depuis)`); console.log(` 🚫 Edit skippés (FILE_NOT_EXIST): ${skippedAnalysis.FILE_NOT_EXIST.length} (fichiers supprimés?)\n`); } main();