@yonathan124/zentry 1.0.0

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.
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Zentry — Moteur de Découverte & Scanner RAG v2.0
3
+ *
4
+ * Fonctionnalités :
5
+ * - Découverte récursive (FileSystem API navigateur & Node.js)
6
+ * - Parseur de graphe d'imports : regroupe les fichiers dépendants ensemble
7
+ * - Filtrage intelligent par criticité (routes, auth, DB, config en priorité)
8
+ * - Tri prioritaire : les fichiers les plus critiques sont audités en premier
9
+ */
10
+
11
+ // ─── CONSTANTES ────────────────────────────────────────────────────────────
12
+ const IGNORE_LIST = [
13
+ '.git', 'node_modules', '.next', 'dist', 'build', 'public',
14
+ '.vscode', 'coverage', 'storybook-static', '.turbo', '__pycache__',
15
+ '.pytest_cache', 'target', 'vendor', '.cargo'
16
+ ];
17
+
18
+ const EXT_REGEX = /\.(ts|tsx|js|jsx|py|go|rs|php|java|rb|sql|prisma|yml|yaml|env\.example)$/i;
19
+
20
+ // Fichiers à exclure malgré l'extension correcte
21
+ const SKIP_PATTERNS = /\.(test|spec|stories|d)\.(ts|tsx|js|jsx)$/i;
22
+
23
+ // Groupes de criticité : les fichiers correspondants sont traités en priorité
24
+ const CRITICALITY = [
25
+ { pattern: /(middleware|auth|session|jwt|token|permission|role|policy|guard)\./i, score: 100 },
26
+ { pattern: /(route|api|handler|controller|endpoint)\./i, score: 90 },
27
+ { pattern: /(db|database|prisma|supabase|mongoose|sequelize|query|sql)\./i, score: 85 },
28
+ { pattern: /(config|env|secret|key|credential|vault)\./i, score: 80 },
29
+ { pattern: /(payment|stripe|webhook|billing|invoice)\./i, score: 75 },
30
+ { pattern: /(schema|validator|zod|yup|validation)\./i, score: 70 },
31
+ { pattern: /(upload|file|storage|s3|bucket)\./i, score: 65 },
32
+ { pattern: /(email|sms|notification|mailer)\./i, score: 60 },
33
+ ];
34
+
35
+ const MAX_FILES_PER_BLOCK = 4;
36
+
37
+ // ─── SCORING ───────────────────────────────────────────────────────────────
38
+ function getCriticalityScore(filePath) {
39
+ const filename = filePath.split('/').pop() || '';
40
+ for (const { pattern, score } of CRITICALITY) {
41
+ if (pattern.test(filename) || pattern.test(filePath)) return score;
42
+ }
43
+ return 30;
44
+ }
45
+
46
+ // ─── GRAPHE D'IMPORTS (RAG) ────────────────────────────────────────────────
47
+ /**
48
+ * Extrait les chemins importés depuis le contenu d'un fichier.
49
+ * Supporte : import/export ES6, require() CommonJS, dynamic import()
50
+ */
51
+ export function extractImports(content, filePath) {
52
+ const imports = new Set();
53
+ const dir = filePath.substring(0, filePath.lastIndexOf('/'));
54
+
55
+ const patterns = [
56
+ /import\s+(?:.*?\s+from\s+)?['"]([^'"]+)['"]/g, // import ... from '...'
57
+ /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g, // require('...')
58
+ /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g, // import('...')
59
+ /export\s+.*?\s+from\s+['"]([^'"]+)['"]/g, // export ... from '...'
60
+ ];
61
+
62
+ for (const pattern of patterns) {
63
+ let match;
64
+ while ((match = pattern.exec(content)) !== null) {
65
+ const imp = match[1];
66
+ // On ne garde que les imports relatifs (pas npm packages)
67
+ if (imp.startsWith('.')) {
68
+ // Normalise le chemin relatif
69
+ const normalized = resolveRelativePath(dir, imp);
70
+ if (normalized) imports.add(normalized);
71
+ }
72
+ }
73
+ }
74
+ return [...imports];
75
+ }
76
+
77
+ function resolveRelativePath(fromDir, relativePath) {
78
+ const parts = (fromDir + '/' + relativePath).split('/');
79
+ const resolved = [];
80
+ for (const part of parts) {
81
+ if (part === '..') resolved.pop();
82
+ else if (part !== '.') resolved.push(part);
83
+ }
84
+ return resolved.join('/');
85
+ }
86
+
87
+ /**
88
+ * Construit un graphe d'imports : { filePath: [importedFiles] }
89
+ */
90
+ export function buildImportGraph(filesWithContent) {
91
+ const graph = {};
92
+ for (const { path, content } of filesWithContent) {
93
+ graph[path] = extractImports(content, path);
94
+ }
95
+ return graph;
96
+ }
97
+
98
+ /**
99
+ * Algorithme de regroupement par dépendances.
100
+ * Les fichiers fortement connectés (qui s'importent mutuellement) sont mis dans le même bloc.
101
+ *
102
+ * Algorithme : Union-Find sur le graphe d'imports (non orienté)
103
+ */
104
+ export function groupByDependencies(files, importGraph) {
105
+ // Union-Find
106
+ const parent = {};
107
+ const find = (x) => {
108
+ if (parent[x] !== x) parent[x] = find(parent[x]);
109
+ return parent[x];
110
+ };
111
+ const union = (a, b) => {
112
+ const pa = find(a), pb = find(b);
113
+ if (pa !== pb) parent[pa] = pb;
114
+ };
115
+
116
+ // Initialise chaque fichier comme son propre parent
117
+ files.forEach(f => { parent[f] = f; });
118
+
119
+ // Unit les fichiers liés par import
120
+ for (const [file, imports] of Object.entries(importGraph)) {
121
+ for (const imp of imports) {
122
+ // Essaie plusieurs extensions pour trouver une correspondance
123
+ const candidates = [imp, imp + '.ts', imp + '.tsx', imp + '.js', imp + '/index.ts', imp + '/index.tsx'];
124
+ for (const candidate of candidates) {
125
+ if (files.includes(candidate)) {
126
+ union(file, candidate);
127
+ break;
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ // Regroupe par composante connexe
134
+ const groups = {};
135
+ for (const file of files) {
136
+ const root = find(file);
137
+ if (!groups[root]) groups[root] = [];
138
+ groups[root].push(file);
139
+ }
140
+
141
+ return Object.values(groups);
142
+ }
143
+
144
+ // ─── CONSTRUCTION DES BLOCS ────────────────────────────────────────────────
145
+ /**
146
+ * Construit les blocs d'audit depuis le graphe de dépendances.
147
+ *
148
+ * Stratégie :
149
+ * 1. Regrouper les fichiers par composante de dépendance (RAG)
150
+ * 2. Trier les groupes par score de criticité (décroissant)
151
+ * 3. Découper en blocs de MAX_FILES_PER_BLOCK fichiers max
152
+ *
153
+ * Résultat : les fichiers liés sont audités ensemble, les plus critiques d'abord
154
+ */
155
+ export function buildBlocks(files, importGraph = {}) {
156
+ if (Object.keys(importGraph).length > 0) {
157
+ // Mode RAG : regroupement par dépendances
158
+ const dependencyGroups = groupByDependencies(files, importGraph);
159
+
160
+ const blocks = [];
161
+ let bId = 1;
162
+
163
+ // Trie les groupes : les plus critiques (score moyen élevé) en premier
164
+ const scoredGroups = dependencyGroups.map(group => ({
165
+ group,
166
+ score: group.reduce((s, f) => s + getCriticalityScore(f), 0) / group.length
167
+ }));
168
+ scoredGroups.sort((a, b) => b.score - a.score);
169
+
170
+ for (const { group } of scoredGroups) {
171
+ // Trie les fichiers dans chaque groupe par criticité aussi
172
+ group.sort((a, b) => getCriticalityScore(b) - getCriticalityScore(a));
173
+
174
+ for (let i = 0; i < group.length; i += MAX_FILES_PER_BLOCK) {
175
+ const slice = group.slice(i, i + MAX_FILES_PER_BLOCK);
176
+ const critScore = Math.round(slice.reduce((s, f) => s + getCriticalityScore(f), 0) / slice.length);
177
+ blocks.push({
178
+ id: `B${bId.toString().padStart(3, '0')}`,
179
+ name: `Groupe ${bId} (Criticité: ${critScore}/100)`,
180
+ files: slice,
181
+ criticalityScore: critScore,
182
+ // Indique si ce bloc contient des fichiers fortement liés
183
+ isRelated: slice.length > 1 && group.length > 1
184
+ });
185
+ bId++;
186
+ }
187
+ }
188
+
189
+ return blocks;
190
+
191
+ } else {
192
+ // Mode dossier (fallback sans graphe d'imports) : regroupement par répertoire
193
+ const dirs = {};
194
+ files.forEach(f => {
195
+ const dir = f.substring(0, f.lastIndexOf('/')) || '/root';
196
+ if (!dirs[dir]) dirs[dir] = [];
197
+ dirs[dir].push(f);
198
+ });
199
+
200
+ const blocks = [];
201
+ let bId = 1;
202
+
203
+ // Trie les dossiers par criticité moyenne
204
+ const sortedDirs = Object.entries(dirs).sort((a, b) => {
205
+ const scoreA = a[1].reduce((s, f) => s + getCriticalityScore(f), 0) / a[1].length;
206
+ const scoreB = b[1].reduce((s, f) => s + getCriticalityScore(f), 0) / b[1].length;
207
+ return scoreB - scoreA;
208
+ });
209
+
210
+ for (const [dir, dirFiles] of sortedDirs) {
211
+ dirFiles.sort((a, b) => getCriticalityScore(b) - getCriticalityScore(a));
212
+ for (let i = 0; i < dirFiles.length; i += MAX_FILES_PER_BLOCK) {
213
+ const slice = dirFiles.slice(i, i + MAX_FILES_PER_BLOCK);
214
+ blocks.push({
215
+ id: `B${bId.toString().padStart(3, '0')}`,
216
+ name: `${dir.split('/').pop() || 'root'} (${Math.floor(i / MAX_FILES_PER_BLOCK) + 1})`,
217
+ files: slice,
218
+ criticalityScore: Math.round(slice.reduce((s, f) => s + getCriticalityScore(f), 0) / slice.length),
219
+ isRelated: false
220
+ });
221
+ bId++;
222
+ }
223
+ }
224
+ return blocks;
225
+ }
226
+ }
227
+
228
+ // ─── SCANNER NAVIGATEUR ────────────────────────────────────────────────────
229
+ /**
230
+ * Scan via l'API File System Access du navigateur (handle directory).
231
+ * Retourne les fichiers avec leur contenu pour le graphe d'imports.
232
+ */
233
+ export async function scanBrowserDirectory(dirHandle) {
234
+ const filesWithContent = [];
235
+
236
+ async function walk(handle, currentPath = '') {
237
+ for await (const entry of handle.values()) {
238
+ if (IGNORE_LIST.includes(entry.name)) continue;
239
+
240
+ const fullPath = currentPath + entry.name;
241
+ if (entry.kind === 'directory') {
242
+ await walk(entry, fullPath + '/');
243
+ } else if (entry.kind === 'file' && EXT_REGEX.test(entry.name) && !SKIP_PATTERNS.test(entry.name)) {
244
+ try {
245
+ const file = await entry.getFile();
246
+ // Limite à 200kb par fichier pour éviter de saturer le contexte IA
247
+ if (file.size < 204800) {
248
+ const content = await file.text();
249
+ filesWithContent.push({ path: fullPath, content, size: file.size });
250
+ } else {
251
+ // Fichier trop grand : on le garde mais sans contenu pour le graphe
252
+ filesWithContent.push({ path: fullPath, content: '', size: file.size, truncated: true });
253
+ }
254
+ } catch {
255
+ filesWithContent.push({ path: fullPath, content: '', size: 0, error: true });
256
+ }
257
+ }
258
+ }
259
+ }
260
+
261
+ await walk(dirHandle);
262
+ return filesWithContent;
263
+ }
264
+
265
+ /**
266
+ * Lit le contenu d'un fichier via FileList (input[type=file]) ou File System Access API
267
+ */
268
+ export async function readBrowserFile(source, filePath) {
269
+ try {
270
+ // Source = FileList (input[type=file])
271
+ if (source && source[0] && source[0].webkitRelativePath !== undefined) {
272
+ const f = [...source].find(f => f.webkitRelativePath === filePath || f.name === filePath.split('/').pop());
273
+ if (f) return await f.text();
274
+ }
275
+ // Source = Directory Handle
276
+ if (source && source.kind === 'directory') {
277
+ const parts = filePath.split('/');
278
+ let h = source;
279
+ for (let i = 0; i < parts.length - 1; i++) {
280
+ h = await h.getDirectoryHandle(parts[i]);
281
+ }
282
+ const fh = await h.getFileHandle(parts[parts.length - 1]);
283
+ const file = await fh.getFile();
284
+ return await file.text();
285
+ }
286
+ return `// ⚠️ ERREUR DE LECTURE: ${filePath}`;
287
+ } catch (e) {
288
+ return `// ⚠️ ERREUR DE LECTURE: ${filePath} — ${e.message}`;
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Construit le graphe d'imports et les blocs RAG depuis FileList (input[type=file])
294
+ */
295
+ export async function buildRAGBlocksFromFileList(fileList) {
296
+ const filesWithContent = [];
297
+
298
+ for (const file of fileList) {
299
+ const path = file.webkitRelativePath || file.name;
300
+ if (!EXT_REGEX.test(file.name) || SKIP_PATTERNS.test(file.name)) continue;
301
+
302
+ const IGNORE = /node_modules|\.next|dist|\.git/;
303
+ if (IGNORE.test(path)) continue;
304
+
305
+ try {
306
+ // Limite à 200kb
307
+ if (file.size < 204800) {
308
+ const content = await file.text();
309
+ filesWithContent.push({ path, content, size: file.size });
310
+ } else {
311
+ filesWithContent.push({ path, content: '', size: file.size, truncated: true });
312
+ }
313
+ } catch {
314
+ filesWithContent.push({ path, content: '', size: 0, error: true });
315
+ }
316
+ }
317
+
318
+ const files = filesWithContent.map(f => f.path);
319
+ const importGraph = buildImportGraph(filesWithContent);
320
+ const blocks = buildBlocks(files, importGraph);
321
+
322
+ return { files, filesWithContent, importGraph, blocks };
323
+ }
@@ -0,0 +1,3 @@
1
+ import { PHASES, STACKS } from '../config/constants';
2
+
3
+ export
package/src/index.js ADDED
@@ -0,0 +1,7 @@
1
+ // Exporte les composants React
2
+ export { default as ZentryDashboard } from './components/ZentryDashboard';
3
+
4
+ // Exporte les utilitaires et configurations
5
+ export * from './config/constants';
6
+ export * from './core/ai';
7
+ export * from './core/scanner';