docstodev 1.0.0 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/package.json +2 -2
  2. package/src/ai/analyzer.ts +6 -2
  3. package/src/analyzers/languageAnalyzer.ts +580 -0
  4. package/src/cache/cacheManager.ts +179 -0
  5. package/src/cli/index.ts +307 -23
  6. package/src/commands/generateSummary.ts +129 -39
  7. package/src/commands/run.ts +312 -75
  8. package/src/exporters/html.ts +604 -106
  9. package/.env +0 -1
  10. package/dist/ai/analyzer.d.ts +0 -3
  11. package/dist/ai/analyzer.d.ts.map +0 -1
  12. package/dist/ai/analyzer.js +0 -42
  13. package/dist/ai/analyzer.js.map +0 -1
  14. package/dist/cli/index.d.ts +0 -3
  15. package/dist/cli/index.d.ts.map +0 -1
  16. package/dist/cli/index.js +0 -59
  17. package/dist/cli/index.js.map +0 -1
  18. package/dist/commands/classify.d.ts +0 -2
  19. package/dist/commands/classify.d.ts.map +0 -1
  20. package/dist/commands/classify.js +0 -37
  21. package/dist/commands/classify.js.map +0 -1
  22. package/dist/commands/generateSummary.d.ts +0 -5
  23. package/dist/commands/generateSummary.d.ts.map +0 -1
  24. package/dist/commands/generateSummary.js +0 -54
  25. package/dist/commands/generateSummary.js.map +0 -1
  26. package/dist/commands/run.d.ts +0 -2
  27. package/dist/commands/run.d.ts.map +0 -1
  28. package/dist/commands/run.js +0 -142
  29. package/dist/commands/run.js.map +0 -1
  30. package/dist/exporters/html.d.ts +0 -2
  31. package/dist/exporters/html.d.ts.map +0 -1
  32. package/dist/exporters/html.js +0 -148
  33. package/dist/exporters/html.js.map +0 -1
  34. package/dist/index.d.ts +0 -2
  35. package/dist/index.d.ts.map +0 -1
  36. package/dist/index.js +0 -2
  37. package/dist/index.js.map +0 -1
  38. package/docs/docs-to-dev.md +0 -111
  39. package/docs/report.html +0 -90
  40. package/docs/report.pdf +0 -0
  41. package/docs/summary.md +0 -16
  42. package/src/commands/classify.ts +0 -40
  43. package/src/index.ts +0 -0
@@ -3,55 +3,145 @@
3
3
  import { writeFileSync, mkdirSync } from "node:fs";
4
4
  import path from "node:path";
5
5
 
6
+ interface SummaryData {
7
+ totalFiles: number;
8
+ sourceFiles?: number;
9
+ totalComplexity?: number;
10
+ avgComplexity?: number;
11
+ totalDeps?: number;
12
+ topLanguages?: { lang: string; count: number }[];
13
+ criticalFiles?: string[];
14
+ }
15
+
6
16
  /**
7
17
  * Génère un résumé global du projet avec un ton humain et bilingue.
8
18
  */
9
- export function generateSummary(docsDir: string, totalFiles: number, lang: "fr" | "en" = "fr") {
19
+ export function generateSummary(
20
+ docsDir: string,
21
+ data: SummaryData | number,
22
+ lang: "fr" | "en" = "fr"
23
+ ) {
10
24
  mkdirSync(docsDir, { recursive: true });
11
25
 
12
- const dateStr = new Date().toLocaleString();
13
-
14
- // Contenu bilingue avec un ton "Expert & Humain"
15
- const content = lang === "fr"
16
- ? `# 📊 Vue d'ensemble du Projet – DocsToDev
17
-
18
- > 📅 **Rapport généré le :** ${dateStr}
19
-
20
- ## 🚀 État des lieux
21
- Salut ! J'ai passé ton projet au peigne fin. Voici un résumé rapide de ce que j'ai trouvé :
22
- - **Volume traité :** ${totalFiles} fichiers analysés avec succès.
23
- - **Diagnostic :** La structure est claire et l'analyse est complète. ✅
24
-
25
- ## 🧭 Navigation Rapide
26
- Pour aller plus loin, tu peux consulter les documents suivants :
27
- - **[📂 Arborescence & Analyse](../docs/docs-to-dev.md)** : Pour comprendre la vocation de chaque fichier et les capacités du système.
28
- - **[💻 Code Source](../)** : Pour retourner explorer la racine de ton projet.
29
-
30
- ---
31
- *DocsToDev – Ton allié pour une documentation technique vivante et intelligente.*
32
- `
33
- : `# 📊 Project Overview – DocsToDev
26
+ // Support de l'ancien format (juste un nombre) et du nouveau format (objet complet)
27
+ const summaryData: SummaryData = typeof data === 'number'
28
+ ? { totalFiles: data }
29
+ : data;
34
30
 
35
- > 📅 **Generated on:** ${dateStr}
36
-
37
- ## 🚀 Project Status
38
- Hey! I've scanned your entire codebase. Here's a quick summary of the findings:
39
- - **Volume processed:** ${totalFiles} files successfully analyzed.
40
- - **Diagnosis:** The structure is clear and the analysis is complete. ✅
41
-
42
- ## 🧭 Quick Navigation
43
- To dive deeper, check out these documents:
44
- - **[📂 Tree & Analysis](../docs/docs-to-dev.md)**: Explore file purposes and system capabilities.
45
- - **[💻 Source Code](../)**: Head back to the project root.
46
-
47
- ---
48
- *DocsToDev – Your partner for living and intelligent technical documentation.*
49
- `;
31
+ const dateStr = new Date().toLocaleString(lang === "fr" ? "fr-FR" : "en-US", {
32
+ dateStyle: "full",
33
+ timeStyle: "short"
34
+ });
35
+
36
+ const t = lang === "fr" ? {
37
+ title: "📊 Vue d'ensemble du Projet – DocsToDev",
38
+ genOn: "Rapport généré le",
39
+ status: "🚀 État des lieux",
40
+ intro: "Salut ! J'ai passé ton projet au peigne fin. Voici un résumé rapide de ce que j'ai trouvé :",
41
+ volume: "Volume traité",
42
+ filesAnalyzed: "fichiers analysés avec succès",
43
+ sourceFiles: "fichiers sources détectés",
44
+ diagnosis: "Diagnostic",
45
+ structureClear: "La structure est claire et l'analyse est complète. ✅",
46
+ metrics: "📈 Métriques clés",
47
+ complexity: "Complexité totale",
48
+ avgComplexity: "Complexité moyenne",
49
+ dependencies: "Dépendances uniques",
50
+ topLangs: "🔤 Langages détectés",
51
+ criticalFiles: "⚠️ Fichiers critiques (haute complexité)",
52
+ navigation: "🧭 Navigation Rapide",
53
+ navDesc: "Pour aller plus loin, tu peux consulter les documents suivants :",
54
+ treeLink: "📂 Arborescence & Analyse",
55
+ treeDesc: "Pour comprendre la vocation de chaque fichier et les capacités du système.",
56
+ codeLink: "💻 Code Source",
57
+ codeDesc: "Pour retourner explorer la racine de ton projet.",
58
+ footer: "DocsToDev – Ton allié pour une documentation technique vivante et intelligente."
59
+ } : {
60
+ title: "📊 Project Overview – DocsToDev",
61
+ genOn: "Generated on",
62
+ status: "🚀 Project Status",
63
+ intro: "Hey! I've scanned your entire codebase. Here's a quick summary of the findings:",
64
+ volume: "Volume processed",
65
+ filesAnalyzed: "files successfully analyzed",
66
+ sourceFiles: "source files detected",
67
+ diagnosis: "Diagnosis",
68
+ structureClear: "The structure is clear and the analysis is complete. ✅",
69
+ metrics: "📈 Key Metrics",
70
+ complexity: "Total complexity",
71
+ avgComplexity: "Average complexity",
72
+ dependencies: "Unique dependencies",
73
+ topLangs: "🔤 Languages Detected",
74
+ criticalFiles: "⚠️ Critical Files (high complexity)",
75
+ navigation: "🧭 Quick Navigation",
76
+ navDesc: "To dive deeper, check out these documents:",
77
+ treeLink: "📂 Tree & Analysis",
78
+ treeDesc: "Explore file purposes and system capabilities.",
79
+ codeLink: "💻 Source Code",
80
+ codeDesc: "Head back to the project root.",
81
+ footer: "DocsToDev – Your partner for living and intelligent technical documentation."
82
+ };
83
+
84
+ let content = `# ${t.title}\n\n`;
85
+ content += `> 📅 **${t.genOn} :** ${dateStr}\n\n`;
86
+
87
+ content += `## ${t.status}\n`;
88
+ content += `${t.intro}\n`;
89
+ content += `- **${t.volume} :** ${summaryData.totalFiles} ${t.filesAnalyzed}.\n`;
90
+
91
+ if (summaryData.sourceFiles) {
92
+ content += `- **${t.sourceFiles} :** ${summaryData.sourceFiles} ${t.sourceFiles.toLowerCase()}.\n`;
93
+ }
94
+
95
+ content += `- **${t.diagnosis} :** ${t.structureClear}\n\n`;
96
+
97
+ // Métriques additionnelles si disponibles
98
+ if (summaryData.totalComplexity || summaryData.avgComplexity || summaryData.totalDeps) {
99
+ content += `## ${t.metrics}\n`;
100
+
101
+ if (summaryData.totalComplexity) {
102
+ content += `- **${t.complexity} :** ${summaryData.totalComplexity}\n`;
103
+ }
104
+
105
+ if (summaryData.avgComplexity) {
106
+ content += `- **${t.avgComplexity} :** ${summaryData.avgComplexity}\n`;
107
+ }
108
+
109
+ if (summaryData.totalDeps) {
110
+ content += `- **${t.dependencies} :** ${summaryData.totalDeps}\n`;
111
+ }
112
+
113
+ content += `\n`;
114
+ }
115
+
116
+ // Langages détectés
117
+ if (summaryData.topLanguages && summaryData.topLanguages.length > 0) {
118
+ content += `## ${t.topLangs}\n`;
119
+ summaryData.topLanguages.forEach(({ lang, count }) => {
120
+ content += `- **${lang}** : ${count} ${lang === "fr" ? "fichiers" : "files"}\n`;
121
+ });
122
+ content += `\n`;
123
+ }
124
+
125
+ // Fichiers critiques
126
+ if (summaryData.criticalFiles && summaryData.criticalFiles.length > 0) {
127
+ content += `## ${t.criticalFiles}\n`;
128
+ summaryData.criticalFiles.forEach(file => {
129
+ content += `- \`${file}\`\n`;
130
+ });
131
+ content += `\n`;
132
+ }
133
+
134
+ content += `## ${t.navigation}\n`;
135
+ content += `${t.navDesc}\n`;
136
+ content += `- **[${t.treeLink}](../docs/docs-to-dev.md)** : ${t.treeDesc}\n`;
137
+ content += `- **[${t.codeLink}](../)** : ${t.codeDesc}\n\n`;
138
+
139
+ content += `---\n`;
140
+ content += `*${t.footer}*\n`;
50
141
 
51
142
  const outputPath = path.join(docsDir, "summary.md");
52
143
  writeFileSync(outputPath, content);
53
144
 
54
- // Log de succès bilingue
55
145
  const successMsg = lang === "fr"
56
146
  ? `📑 Résumé humain généré avec succès : ${outputPath}`
57
147
  : `📑 Human-friendly summary successfully generated: ${outputPath}`;
@@ -2,34 +2,56 @@
2
2
  import { globby } from "globby";
3
3
  import { writeFileSync, readFileSync, existsSync, mkdirSync } from "node:fs";
4
4
  import path from "node:path";
5
- import puppeteer from "puppeteer";
5
+ import puppeteer, { Browser, Page } from "puppeteer";
6
6
  import { exportToHTML } from "../exporters/html.js";
7
7
  import { askAI } from "../ai/analyzer.js";
8
+ import { getAnalyzer, getSupportedExtensions } from "../analyzers/languageAnalyzer.js";
9
+ import { CacheManager } from "../cache/cacheManager.js";
8
10
 
9
- type TreeNode = { [key: string]: TreeNode | null };
11
+ type TreeNode = { [key: string]: TreeNode };
12
+
13
+ interface FileAnalysis {
14
+ path: string;
15
+ lines: number;
16
+ exports: string[];
17
+ // Ajout de '| undefined' pour supporter les propriétés optionnelles en mode strict
18
+ imports: Array<{ name: string; type: string; usage?: string | undefined }>;
19
+ functions: string[];
20
+ classes: string[];
21
+ types: string[];
22
+ complexity: number;
23
+ }
10
24
 
11
25
  const i18n = {
12
26
  fr: {
13
- role: "Vocation du fichier",
14
- density: "Densité et maintenance",
15
- exports: "Capacités offertes",
16
- imports: "Collaborations sollicitées",
17
- structure: "Architecture des dossiers",
18
- deps: "Inventaire des modules externes",
19
- aiTitle: "Synthèse Métier (par MakazouIA)",
27
+ role: "Rôle et responsabilités",
28
+ density: "Complexité et maintenance",
29
+ exports: "Exports publics",
30
+ imports: "Dépendances",
31
+ structure: "Architecture du projet",
32
+ deps: "Modules externes",
33
+ aiTitle: "Analyse Intelligente",
20
34
  genAt: "Généré le",
21
- reportTitle: "Rapport d'Analyse Technique DocsToDev"
35
+ reportTitle: "Rapport Technique DocsToDev",
36
+ functions: "Fonctions",
37
+ classes: "Classes",
38
+ types: "Types/Interfaces",
39
+ complexity: "Score de complexité"
22
40
  },
23
41
  en: {
24
- role: "File Purpose",
25
- density: "Density & Maintenance",
26
- exports: "Capabilities offered",
27
- imports: "External services",
42
+ role: "Role and responsibilities",
43
+ density: "Complexity & Maintenance",
44
+ exports: "Public exports",
45
+ imports: "Dependencies",
28
46
  structure: "Project Architecture",
29
- deps: "External Modules Inventory",
30
- aiTitle: "Business Insights (by MakazouIA)",
47
+ deps: "External Modules",
48
+ aiTitle: "Smart Analysis",
31
49
  genAt: "Generated on",
32
- reportTitle: "DocsToDev Technical Report"
50
+ reportTitle: "DocsToDev Technical Report",
51
+ functions: "Functions",
52
+ classes: "Classes",
53
+ types: "Types/Interfaces",
54
+ complexity: "Complexity score"
33
55
  }
34
56
  };
35
57
 
@@ -37,9 +59,11 @@ function buildTree(files: string[]): TreeNode {
37
59
  const root: TreeNode = {};
38
60
  for (const file of files) {
39
61
  const parts = file.split(path.sep);
40
- let current = root;
62
+ let current: TreeNode = root;
41
63
  for (const part of parts) {
42
- if (!current[part]) current[part] = {};
64
+ if (!current[part]) {
65
+ current[part] = {};
66
+ }
43
67
  current = current[part] as TreeNode;
44
68
  }
45
69
  }
@@ -57,100 +81,313 @@ function renderTree(tree: TreeNode, prefix = "", currentPath = ""): string {
57
81
  const isFile = !child || Object.keys(child).length === 0;
58
82
  const label = isFile ? `[${key}](../${fullPath.replace(/\\/g, '/')})` : `**${key}/**`;
59
83
  output += `${prefix}${connector}${label}\n`;
60
- if (child && Object.keys(child).length > 0) {
84
+ if (child && typeof child === 'object' && Object.keys(child).length > 0) {
61
85
  output += renderTree(child, prefix + (isLast ? " " : "│ "), fullPath);
62
86
  }
63
87
  });
64
88
  return output;
65
89
  }
66
90
 
67
- export async function runCommand(language: "fr" | "en" = "fr"): Promise<number> {
68
- const t = i18n[language];
69
- console.log(language === "fr" ? "🚀 DocsToDev : Analyse Deep Scan & Graphe..." : "🚀 DocsToDev: Deep Scan & Graph analysis...");
91
+ function analyzeFile(file: string, content: string): FileAnalysis {
92
+ const lines = content.split(/\r?\n/);
93
+ const analyzer = getAnalyzer(file);
94
+
95
+ if (analyzer) {
96
+ const result = analyzer.analyzeFile(content);
97
+
98
+ const ifCount = (content.match(/\b(if|elif|else if)\s*\(/g) || []).length;
99
+ const forCount = (content.match(/\b(for|foreach|while)\s*\(/g) || []).length;
100
+ const whileCount = (content.match(/\bwhile\s*\(/g) || []).length;
101
+
102
+ const complexity =
103
+ lines.length * 0.1 +
104
+ result.functions.length * 2 +
105
+ result.classes.length * 3 +
106
+ result.imports.length * 1.5 +
107
+ ifCount * 1 +
108
+ forCount * 1.5 +
109
+ whileCount * 1.5;
110
+
111
+ return {
112
+ path: file,
113
+ lines: lines.length,
114
+ exports: result.exports,
115
+ imports: result.imports,
116
+ functions: result.functions,
117
+ classes: result.classes,
118
+ types: result.types,
119
+ complexity: Math.round(complexity)
120
+ };
121
+ }
122
+
123
+ return {
124
+ path: file,
125
+ lines: lines.length,
126
+ exports: [],
127
+ imports: [],
128
+ functions: [],
129
+ classes: [],
130
+ types: [],
131
+ complexity: 0
132
+ };
133
+ }
134
+
135
+ function getComplexityLevel(score: number): string {
136
+ if (score < 50) return "🟢 Faible";
137
+ if (score < 150) return "🟡 Modérée";
138
+ if (score < 300) return "🟠 Élevée";
139
+ return "🔴 Très élevée";
140
+ }
70
141
 
142
+ function formatAISummary(summary: string): string {
143
+ let formatted = summary.replace(/([a-zA-Z0-9_-]+\.(ts|js|py|java|cs|go|rs|tsx|jsx|mjs|cjs))/g, '`$1`');
144
+ formatted = formatted.replace(/([a-zA-Z0-9_-]+\/[a-zA-Z0-9_\/-]+)/g, '`$1`');
145
+ formatted = formatted.replace(/\b([a-z][a-zA-Z0-9]*[A-Z][a-zA-Z0-9]*|[A-Z][a-z][a-zA-Z0-9]*)\b/g, '`$1`');
146
+ formatted = formatted.replace(/\.\s+/g, '.\n\n');
147
+ formatted = formatted.replace(/`+/g, '`');
148
+ return formatted.trim();
149
+ }
150
+
151
+ export async function runCommand(
152
+ language: "fr" | "en" = "fr",
153
+ options: { incremental?: boolean; clearCache?: boolean } = {}
154
+ ): Promise<number> {
155
+ const t = i18n[language];
156
+
157
+ console.log("\n╔══════════════════════════════════════════╗");
158
+ console.log("║ 🚀 DocsToDev - Analyse Avancée ║");
159
+ console.log("╚══════════════════════════════════════════╝\n");
160
+
161
+ const projectRoot = process.cwd();
162
+ const cache = new CacheManager(projectRoot);
163
+
164
+ if (options.clearCache) {
165
+ console.log("🗑️ Nettoyage du cache...");
166
+ cache.invalidateAll();
167
+ console.log(" ✓ Cache vidé\n");
168
+ }
169
+
170
+ console.log("📂 Étape 1/6 : Scan des fichiers du projet...");
171
+ const supportedExts = getSupportedExtensions();
71
172
  const files = await globby(["**/*"], {
72
173
  gitignore: true,
73
- ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/docs/**"]
174
+ ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/docs/**", "**/.docstodev/**"]
74
175
  });
176
+
177
+ const sourceFiles = files.filter(f => {
178
+ const ext = path.extname(f);
179
+ return supportedExts.includes(ext);
180
+ });
181
+
182
+ console.log(` ✓ ${files.length} fichiers détectés (${sourceFiles.length} fichiers sources)\n`);
75
183
 
76
184
  const docsDir = "docs";
77
- if (!existsSync(docsDir)) mkdirSync(docsDir, { recursive: true });
185
+ if (!existsSync(docsDir)) {
186
+ console.log("📁 Création du dossier 'docs'...");
187
+ mkdirSync(docsDir, { recursive: true });
188
+ }
189
+
190
+ let filesToAnalyze = sourceFiles;
191
+ let cachedCount = 0;
192
+
193
+ if (options.incremental) {
194
+ console.log("⚡ Mode incrémental activé - Détection des modifications...");
195
+ const modifiedFiles = cache.getModifiedFiles(sourceFiles);
196
+ filesToAnalyze = modifiedFiles;
197
+ cachedCount = sourceFiles.length - modifiedFiles.length;
198
+ console.log(` ✓ ${modifiedFiles.length} fichiers modifiés, ${cachedCount} en cache\n`);
199
+ }
78
200
 
79
- let aiContext = `Analyse ces fichiers pour DocsToDev. Explique pourquoi ils utilisent leurs dépendances. Format: "Utilise [X] pour [Y]".\n\n`;
201
+ console.log("🔍 Étape 2/6 : Analyse approfondie des fichiers sources...");
202
+ let aiContext = `Tu es un expert en analyse de code. Analyse ces fichiers et explique leur architecture, leurs responsabilités et comment ils collaborent ensemble. Identifie le rôle de chaque fichier (Page, Component, Service, API, Utility, etc.). Sois précis et technique.\n\n`;
80
203
  let detailContent = `## 🔬 Analyse détaillée des composants\n\n`;
81
- let mermaidGraph = "";
204
+ let mermaidGraph = "graph LR\n";
82
205
  const allDeps = new Map<string, string>();
83
-
84
- for (const file of files) {
85
- const ext = path.extname(file);
86
- if (![".ts", ".js", ".tsx", ".jsx"].includes(ext)) continue;
206
+ const analyses: FileAnalysis[] = [];
207
+
208
+ let processedCount = 0;
209
+
210
+ if (options.incremental) {
211
+ for (const file of sourceFiles) {
212
+ if (!filesToAnalyze.includes(file)) {
213
+ const cached = cache.get(file);
214
+ if (cached) {
215
+ analyses.push(cached.analysis);
216
+ }
217
+ }
218
+ }
219
+ }
220
+
221
+ for (const file of filesToAnalyze) {
222
+ processedCount++;
223
+ if (processedCount % 5 === 0) {
224
+ process.stdout.write(` Analysé: ${processedCount}/${filesToAnalyze.length} fichiers...\r`);
225
+ }
87
226
 
88
227
  const content = readFileSync(file, "utf-8");
89
- const lines = content.split(/\r?\n/);
90
- const fileNameSanitized = path.basename(file).replace(/\./g, '_');
91
-
92
- const exports = lines
93
- .filter(l => l.startsWith("export "))
94
- .map(l => l.match(/(?:function|const|class|type|interface)\s+([a-zA-Z0-9_]+)/)?.[1])
95
- .filter(Boolean) as string[];
96
-
97
- const importLines = lines.filter(l => l.trim().startsWith("import "));
98
-
99
- detailContent += `### 📄 [\`${file}\`](../${file.replace(/\\/g, '/')})\n`;
100
- detailContent += `• **${t.density} :** ${lines.length} lignes\n`;
101
- if (exports.length > 0) detailContent += `• **${t.exports} :** ${exports.map(e => `\`${e}\``).join(", ")}\n`;
102
-
103
- if (importLines.length > 0) {
104
- detailContent += `• **${t.imports} :**\n`;
105
- importLines.forEach(line => {
106
- const match = line.match(/from ['"]([^'"]+)['"]/);
107
- if (match?.[1]) {
108
- const name = match[1];
109
- const type = name.startsWith('.') ? 'Internal' : (name.startsWith('node:') ? 'Node.js' : 'External');
110
- allDeps.set(name, type);
111
- detailContent += ` - Utilise \`${name}\` (${type})\n`;
112
-
113
- if (type === 'Internal') {
114
- const targetName = path.basename(name).replace(/\./g, '_');
115
- mermaidGraph += ` ${fileNameSanitized} --> ${targetName}\n`;
116
- }
228
+ const analysis = analyzeFile(file, content);
229
+ analyses.push(analysis);
230
+
231
+ if (options.incremental) {
232
+ cache.set(file, content, analysis);
233
+ }
234
+
235
+ const fileNameSanitized = path.basename(file).replace(/[.\-]/g, '_');
236
+
237
+ detailContent += `### 📄 [\`${file}\`](../${file.replace(/\\/g, '/')})\n\n`;
238
+ detailContent += `**${t.density}** : ${analysis.lines} lignes • ${getComplexityLevel(analysis.complexity)} (${analysis.complexity})\n\n`;
239
+
240
+ if (analysis.functions.length > 0) {
241
+ detailContent += `**${t.functions}** : ${analysis.functions.map(f => `\`${f}()\``).join(", ")}\n\n`;
242
+ }
243
+
244
+ if (analysis.classes.length > 0) {
245
+ detailContent += `**${t.classes}** : ${analysis.classes.map(c => `\`${c}\``).join(", ")}\n\n`;
246
+ }
247
+
248
+ if (analysis.types.length > 0) {
249
+ detailContent += `**${t.types}** : ${analysis.types.map(t => `\`${t}\``).join(", ")}\n\n`;
250
+ }
251
+
252
+ if (analysis.exports.length > 0) {
253
+ detailContent += `**${t.exports}** : ${analysis.exports.map(e => `\`${e}\``).join(", ")}\n\n`;
254
+ }
255
+
256
+ if (analysis.imports.length > 0) {
257
+ detailContent += `**${t.imports}** :\n\n`;
258
+ analysis.imports.forEach(imp => {
259
+ allDeps.set(imp.name, imp.type);
260
+ const usageInfo = imp.usage ? ` → utilise \`${imp.usage}\`` : '';
261
+ detailContent += `• \`${imp.name}\` (${imp.type})${usageInfo}\n`;
262
+
263
+ if (imp.type === 'Interne') {
264
+ const targetName = path.basename(imp.name).replace(/[.\-]/g, '_');
265
+ mermaidGraph += ` ${fileNameSanitized} --> ${targetName}\n`;
117
266
  }
118
267
  });
268
+ detailContent += `\n`;
119
269
  }
120
- detailContent += `\n---\n\n`;
121
- aiContext += `FICHIER: ${file}\nEXPORTS: ${exports.join(",")}\nCODE:\n${lines.slice(0, 40).join("\n")}\n\n`;
270
+
271
+ detailContent += `---\n\n`;
272
+
273
+ aiContext += `═══ FICHIER: ${file} ═══\n`;
274
+ aiContext += `Langage: ${path.extname(file)}\n`;
275
+ aiContext += `Complexité: ${analysis.complexity}\n`;
276
+ aiContext += `Fonctions: ${analysis.functions.join(", ") || "Aucune"}\n`;
277
+ aiContext += `Classes: ${analysis.classes.join(", ") || "Aucune"}\n`;
278
+ aiContext += `Exports: ${analysis.exports.join(", ") || "Aucun"}\n`;
279
+ aiContext += `Dépendances: ${analysis.imports.map(i => i.name).join(", ")}\n`;
280
+ aiContext += `\nExtrait du code:\n${content.split(/\r?\n/).slice(0, 50).join("\n")}\n\n`;
122
281
  }
282
+
283
+ const totalAnalyzed = processedCount + cachedCount;
284
+ console.log(` ✓ ${totalAnalyzed} fichiers analysés (${processedCount} nouvelles analyses, ${cachedCount} depuis cache)\n`);
123
285
 
124
- const aiSummary = await askAI(aiContext) || "Synthèse IA indisponible.";
286
+ console.log("🤖 Étape 3/6 : Génération de l'analyse intelligente (IA)...");
287
+ const aiSummary = await askAI(aiContext) || "⚠️ Analyse IA indisponible. Vérifiez votre configuration.";
288
+ const formattedAiSummary = formatAISummary(aiSummary);
289
+ console.log(` ✓ Synthèse IA générée\n`);
125
290
 
291
+ console.log("📝 Étape 4/6 : Construction du rapport Markdown...");
126
292
  let finalMD = `# ${t.reportTitle}\n\n`;
127
- finalMD += `> 📅 ${t.genAt} : ${new Date().toLocaleString()}\n\n`;
128
- finalMD += `## 💡 ${t.aiTitle}\n${aiSummary}\n\n`;
129
- finalMD += `## 📂 ${t.structure}\n\n${renderTree(buildTree(files))}\n\n`;
293
+ finalMD += `> 📅 ${t.genAt} : ${new Date().toLocaleString("fr-FR", { dateStyle: "full", timeStyle: "short" })}\n`;
294
+ finalMD += `> 📊 ${totalAnalyzed} fichiers analysés • ${allDeps.size} dépendances identifiées\n`;
295
+ if (options.incremental && cachedCount > 0) {
296
+ finalMD += `> ⚡ Mode incrémental : ${cachedCount} fichiers depuis cache\n`;
297
+ }
298
+ finalMD += `\n`;
299
+ finalMD += `## 💡 ${t.aiTitle}\n\n${formattedAiSummary}\n\n`;
300
+ finalMD += `## 📂 ${t.structure}\n\n`;
301
+ finalMD += "```\n";
302
+ finalMD += renderTree(buildTree(files));
303
+ finalMD += "```\n\n";
130
304
  finalMD += detailContent;
131
- finalMD += `## 📦 ${t.deps}\n\n| Module | Type |\n| :--- | :--- |\n`;
132
- Array.from(allDeps.entries()).sort().forEach(([n, ty]) => finalMD += `| \`${n}\` | ${ty} |\n`);
305
+ finalMD += `## 📦 ${t.deps}\n\n`;
306
+ finalMD += `| Module | Type | Occurrences |\n`;
307
+ finalMD += `| :--- | :--- | :---: |\n`;
308
+
309
+ const depCount = new Map<string, number>();
310
+ analyses.forEach(a => {
311
+ a.imports.forEach(imp => {
312
+ depCount.set(imp.name, (depCount.get(imp.name) || 0) + 1);
313
+ });
314
+ });
315
+
316
+ Array.from(allDeps.entries())
317
+ .sort(([a], [b]) => a.localeCompare(b))
318
+ .forEach(([name, type]) => {
319
+ finalMD += `| \`${name}\` | ${type} | ${depCount.get(name) || 1} |\n`;
320
+ });
133
321
 
134
322
  writeFileSync(path.join(docsDir, "docs-to-dev.md"), finalMD);
135
- exportToHTML(docsDir, finalMD, mermaidGraph, language);
323
+ console.log(` ✓ Rapport Markdown généré : docs/docs-to-dev.md\n`);
324
+
325
+ console.log("🎨 Étape 5/6 : Export HTML avec graphiques...");
326
+ const fileTreeStructure = buildTree(files);
327
+ exportToHTML(docsDir, finalMD, mermaidGraph, language, fileTreeStructure);
328
+ console.log(` ✓ Rapport HTML généré : docs/report.html\n`);
136
329
 
137
- console.log("📄 Génération du PDF automatique...");
330
+ console.log("📄 Étape 6/6 : Génération du PDF...");
138
331
  await generatePDF(path.join(docsDir, "report.html"), path.join(docsDir, "report.pdf"));
332
+ console.log(` ✓ PDF exporté : docs/report.pdf\n`);
333
+
334
+ console.log("╔══════════════════════════════════════════╗");
335
+ console.log("║ ✨ Analyse terminée avec succès ! ║");
336
+ console.log("╚══════════════════════════════════════════╝\n");
337
+ console.log(`📊 Statistiques :`);
338
+ console.log(` • ${files.length} fichiers scannés`);
339
+ console.log(` • ${totalAnalyzed} fichiers analysés`);
340
+ if (options.incremental) {
341
+ console.log(` • ${cachedCount} fichiers depuis cache`);
342
+ }
343
+ console.log(` • ${allDeps.size} dépendances uniques`);
344
+ const avgComp = analyses.length > 0 ? Math.round(analyses.reduce((sum, a) => sum + a.complexity, 0) / analyses.length) : 0;
345
+ console.log(` • Complexité moyenne : ${avgComp}`);
346
+
347
+ if (options.incremental) {
348
+ const cacheStats = cache.getStats();
349
+ console.log(`\n💾 Cache :`);
350
+ console.log(` • ${cacheStats.total} entrées`);
351
+ console.log(` • ${Math.round(cacheStats.size / 1024)} KB`);
352
+ }
353
+ console.log("");
139
354
 
140
355
  return files.length;
141
356
  }
142
357
 
143
- async function generatePDF(htmlPath: string, outputPath: string) {
358
+ async function generatePDF(htmlPath: string, outputPath: string): Promise<void> {
359
+ let browser: Browser | null = null;
144
360
  try {
145
- const browser = await puppeteer.launch({ headless: true });
146
- const page = await browser.newPage();
147
- await page.goto(`file://${path.resolve(htmlPath)}`, { waitUntil: "networkidle0" });
361
+ browser = await puppeteer.launch({
362
+ headless: true,
363
+ args: ['--no-sandbox', '--disable-setuid-sandbox']
364
+ });
365
+ const page: Page = await browser.newPage();
366
+
367
+ await page.goto(`file://${path.resolve(htmlPath)}`, {
368
+ waitUntil: "networkidle0",
369
+ timeout: 30000
370
+ });
371
+
372
+ await page.waitForFunction(() => {
373
+ return typeof (window as any).mermaid !== 'undefined';
374
+ }, { timeout: 5000 }).catch(() => {});
375
+
376
+ await new Promise(resolve => setTimeout(resolve, 2000));
377
+
148
378
  await page.pdf({
149
379
  path: outputPath,
150
380
  format: 'A4',
151
381
  printBackground: true,
152
- margin: { top: '1cm', bottom: '1cm', left: '1cm', right: '1cm' }
382
+ margin: { top: '1cm', bottom: '1cm', left: '1cm', right: '1cm' },
383
+ preferCSSPageSize: true
153
384
  });
385
+
154
386
  await browser.close();
155
- } catch (e) { console.error("Erreur PDF:", e); }
387
+ } catch (e) {
388
+ console.error(" ⚠️ Erreur lors de la génération du PDF:", e);
389
+ if (browser) {
390
+ await browser.close().catch(() => {});
391
+ }
392
+ }
156
393
  }