docstodev 1.0.0 → 1.0.2

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.
@@ -3,7 +3,9 @@
3
3
  import "dotenv/config"; // Pour lire GROQ_API_KEY dans ton .env
4
4
 
5
5
  export async function askAI(technicalContext: string) {
6
- const GROQ_API_KEY = process.env.GROQ_API_KEY;
6
+
7
+ // clé par défaut gratuite
8
+ const GROQ_API_KEY = process.env.GROQ_API_KEY || "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";
7
9
 
8
10
  if (!GROQ_API_KEY) {
9
11
  return "⚠️ Erreur : Clé API Groq manquante dans le fichier .env";
@@ -0,0 +1,288 @@
1
+ export interface LanguageAnalyzer {
2
+ extensions: string[];
3
+ analyzeFile(content: string): FileAnalysisResult;
4
+ }
5
+
6
+ export interface FileAnalysisResult {
7
+ functions: string[];
8
+ classes: string[];
9
+ types: string[];
10
+ imports: Array<{ name: string; type: string; usage?: string | undefined }>;
11
+ exports: string[];
12
+ }
13
+
14
+ // Analyseur TypeScript/JavaScript
15
+ class TSJSAnalyzer implements LanguageAnalyzer {
16
+ extensions = [".ts", ".js", ".tsx", ".jsx", ".mjs", ".cjs"];
17
+
18
+ analyzeFile(content: string): FileAnalysisResult {
19
+ const exports: string[] = [];
20
+ const exportRegex = /export\s+(?:default\s+)?(?:async\s+)?(?:function|const|let|class|type|interface|enum)\s+([a-zA-Z0-9_]+)/g;
21
+ let match: RegExpExecArray | null;
22
+ while ((match = exportRegex.exec(content)) !== null) {
23
+ if (match[1]) exports.push(match[1]);
24
+ }
25
+
26
+ const functions: string[] = [];
27
+ const funcRegex = /(?:export\s+)?(?:async\s+)?function\s+([a-zA-Z0-9_]+)/g;
28
+ // Resetting regex because of global flag
29
+ funcRegex.lastIndex = 0;
30
+ while ((match = funcRegex.exec(content)) !== null) {
31
+ if (match[1]) functions.push(match[1]);
32
+ }
33
+
34
+ const arrowRegex = /(?:export\s+)?const\s+([a-zA-Z0-9_]+)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/g;
35
+ while ((match = arrowRegex.exec(content)) !== null) {
36
+ if (match[1]) functions.push(match[1]);
37
+ }
38
+
39
+ const classes: string[] = [];
40
+ const classRegex = /(?:export\s+)?(?:abstract\s+)?class\s+([a-zA-Z0-9_]+)/g;
41
+ while ((match = classRegex.exec(content)) !== null) {
42
+ if (match[1]) classes.push(match[1]);
43
+ }
44
+
45
+ const types: string[] = [];
46
+ const typeRegex = /(?:export\s+)?(?:type|interface)\s+([a-zA-Z0-9_]+)/g;
47
+ while ((match = typeRegex.exec(content)) !== null) {
48
+ if (match[1]) types.push(match[1]);
49
+ }
50
+
51
+ const imports: Array<{ name: string; type: string; usage?: string | undefined }> = [];
52
+ const lines = content.split(/\r?\n/);
53
+ const importLines = lines.filter(l => l.trim().startsWith("import "));
54
+
55
+ importLines.forEach(line => {
56
+ const fromMatch = line.match(/from ['"]([^'"]+)['"]/);
57
+ if (fromMatch?.[1]) {
58
+ const name = fromMatch[1];
59
+ const type = name.startsWith('.') ? 'Interne' : (name.startsWith('node:') ? 'Node.js' : 'NPM');
60
+ const importedMatch = line.match(/import\s+(?:\{([^}]+)\}|(\w+))/);
61
+ let usage: string | undefined = undefined;
62
+ if (importedMatch) {
63
+ const items = importedMatch[1] || importedMatch[2];
64
+ usage = items?.trim().split(',').map(s => s.trim()).join(', ');
65
+ }
66
+ imports.push({ name, type, usage });
67
+ }
68
+ });
69
+
70
+ return { functions, classes, types, imports, exports };
71
+ }
72
+ }
73
+
74
+ // Analyseur Python
75
+ class PythonAnalyzer implements LanguageAnalyzer {
76
+ extensions = [".py"];
77
+
78
+ analyzeFile(content: string): FileAnalysisResult {
79
+ const functions: string[] = [];
80
+ const funcRegex = /def\s+([a-zA-Z0-9_]+)\s*\(/g;
81
+ let match: RegExpExecArray | null;
82
+ while ((match = funcRegex.exec(content)) !== null) {
83
+ if (match[1] && !match[1].startsWith('__')) functions.push(match[1]);
84
+ }
85
+
86
+ const classes: string[] = [];
87
+ const classRegex = /class\s+([a-zA-Z0-9_]+)(?:\(|:)/g;
88
+ while ((match = classRegex.exec(content)) !== null) {
89
+ if (match[1]) classes.push(match[1]);
90
+ }
91
+
92
+ const imports: Array<{ name: string; type: string; usage?: string | undefined }> = [];
93
+ const lines = content.split(/\r?\n/);
94
+
95
+ lines.forEach(line => {
96
+ const importMatch = line.match(/^import\s+([a-zA-Z0-9_., ]+)/);
97
+ const fromMatch = line.match(/^from\s+([a-zA-Z0-9_.]+)\s+import\s+(.+)/);
98
+
99
+ if (importMatch?.[1]) {
100
+ const modules = importMatch[1].split(',').map(m => m.trim());
101
+ modules.forEach(mod => {
102
+ const type = mod.startsWith('.') ? 'Interne' : 'PyPI';
103
+ imports.push({ name: mod, type });
104
+ });
105
+ } else if (fromMatch?.[1]) {
106
+ const moduleName = fromMatch[1];
107
+ const imported = fromMatch[2]?.trim();
108
+ const type = moduleName.startsWith('.') ? 'Interne' : 'PyPI';
109
+ imports.push({ name: moduleName, type, usage: imported });
110
+ }
111
+ });
112
+
113
+ return { functions, classes, types: [], imports, exports: [] };
114
+ }
115
+ }
116
+
117
+ // Analyseur Java
118
+ class JavaAnalyzer implements LanguageAnalyzer {
119
+ extensions = [".java"];
120
+
121
+ analyzeFile(content: string): FileAnalysisResult {
122
+ const classes: string[] = [];
123
+ const classRegex = /(?:public\s+)?(?:abstract\s+)?class\s+([a-zA-Z0-9_]+)/g;
124
+ let match: RegExpExecArray | null;
125
+ while ((match = classRegex.exec(content)) !== null) {
126
+ if (match[1]) classes.push(match[1]);
127
+ }
128
+
129
+ const interfaceRegex = /(?:public\s+)?interface\s+([a-zA-Z0-9_]+)/g;
130
+ while ((match = interfaceRegex.exec(content)) !== null) {
131
+ if (match[1]) classes.push(match[1]);
132
+ }
133
+
134
+ const functions: string[] = [];
135
+ const methodRegex = /(?:public|private|protected)\s+(?:static\s+)?(?:\w+(?:<[^>]+>)?)\s+([a-zA-Z0-9_]+)\s*\(/g;
136
+ while ((match = methodRegex.exec(content)) !== null) {
137
+ if (match[1]) functions.push(match[1]);
138
+ }
139
+
140
+ const imports: Array<{ name: string; type: string; usage?: string | undefined }> = [];
141
+ const lines = content.split(/\r?\n/);
142
+
143
+ lines.forEach(line => {
144
+ const importMatch = line.match(/^import\s+([a-zA-Z0-9_.]+);/);
145
+ if (importMatch?.[1]) {
146
+ const name = importMatch[1];
147
+ const type = name.startsWith('java.') || name.startsWith('javax.') ? 'JDK' : 'Maven';
148
+ imports.push({ name, type });
149
+ }
150
+ });
151
+
152
+ return { functions, classes, types: [], imports, exports: [] };
153
+ }
154
+ }
155
+
156
+ // Analyseur C#
157
+ class CSharpAnalyzer implements LanguageAnalyzer {
158
+ extensions = [".cs"];
159
+
160
+ analyzeFile(content: string): FileAnalysisResult {
161
+ const classes: string[] = [];
162
+ const classRegex = /(?:public\s+)?(?:abstract\s+)?(?:partial\s+)?class\s+([a-zA-Z0-9_]+)/g;
163
+ let match: RegExpExecArray | null;
164
+ while ((match = classRegex.exec(content)) !== null) {
165
+ if (match[1]) classes.push(match[1]);
166
+ }
167
+
168
+ const interfaceRegex = /(?:public\s+)?interface\s+([a-zA-Z0-9_]+)/g;
169
+ while ((match = interfaceRegex.exec(content)) !== null) {
170
+ if (match[1]) classes.push(match[1]);
171
+ }
172
+
173
+ const functions: string[] = [];
174
+ const methodRegex = /(?:public|private|protected|internal)\s+(?:static\s+)?(?:async\s+)?(?:\w+(?:<[^>]+>)?)\s+([a-zA-Z0-9_]+)\s*\(/g;
175
+ while ((match = methodRegex.exec(content)) !== null) {
176
+ if (match[1]) functions.push(match[1]);
177
+ }
178
+
179
+ const imports: Array<{ name: string; type: string; usage?: string | undefined }> = [];
180
+ const lines = content.split(/\r?\n/);
181
+
182
+ lines.forEach(line => {
183
+ const usingMatch = line.match(/^using\s+([a-zA-Z0-9_.]+);/);
184
+ if (usingMatch?.[1]) {
185
+ const name = usingMatch[1];
186
+ const type = name.startsWith('System.') ? '.NET' : 'NuGet';
187
+ imports.push({ name, type });
188
+ }
189
+ });
190
+
191
+ return { functions, classes, types: [], imports, exports: [] };
192
+ }
193
+ }
194
+
195
+ // Analyseur Go
196
+ class GoAnalyzer implements LanguageAnalyzer {
197
+ extensions = [".go"];
198
+
199
+ analyzeFile(content: string): FileAnalysisResult {
200
+ const functions: string[] = [];
201
+ const funcRegex = /func\s+(?:\([^)]+\)\s+)?([a-zA-Z0-9_]+)\s*\(/g;
202
+ let match: RegExpExecArray | null;
203
+ while ((match = funcRegex.exec(content)) !== null) {
204
+ if (match[1]) functions.push(match[1]);
205
+ }
206
+
207
+ const types: string[] = [];
208
+ const typeRegex = /type\s+([a-zA-Z0-9_]+)\s+(?:struct|interface)/g;
209
+ while ((match = typeRegex.exec(content)) !== null) {
210
+ if (match[1]) types.push(match[1]);
211
+ }
212
+
213
+ const imports: Array<{ name: string; type: string; usage?: string | undefined }> = [];
214
+ const lines = content.split(/\r?\n/);
215
+
216
+ lines.forEach(line => {
217
+ const importMatch = line.match(/^\s*"([^"]+)"/);
218
+ if (importMatch?.[1]) {
219
+ const name = importMatch[1];
220
+ const type = name.includes('.') ? 'External' : 'Standard';
221
+ imports.push({ name, type });
222
+ }
223
+ });
224
+
225
+ return { functions, classes: [], types, imports, exports: [] };
226
+ }
227
+ }
228
+
229
+ // Analyseur Rust
230
+ class RustAnalyzer implements LanguageAnalyzer {
231
+ extensions = [".rs"];
232
+
233
+ analyzeFile(content: string): FileAnalysisResult {
234
+ const functions: string[] = [];
235
+ const funcRegex = /(?:pub\s+)?fn\s+([a-zA-Z0-9_]+)/g;
236
+ let match: RegExpExecArray | null;
237
+ while ((match = funcRegex.exec(content)) !== null) {
238
+ if (match[1]) functions.push(match[1]);
239
+ }
240
+
241
+ const types: string[] = [];
242
+ const structRegex = /(?:pub\s+)?struct\s+([a-zA-Z0-9_]+)/g;
243
+ while ((match = structRegex.exec(content)) !== null) {
244
+ if (match[1]) types.push(match[1]);
245
+ }
246
+
247
+ const enumRegex = /(?:pub\s+)?enum\s+([a-zA-Z0-9_]+)/g;
248
+ while ((match = enumRegex.exec(content)) !== null) {
249
+ if (match[1]) types.push(match[1]);
250
+ }
251
+
252
+ const imports: Array<{ name: string; type: string; usage?: string | undefined }> = [];
253
+ const lines = content.split(/\r?\n/);
254
+
255
+ lines.forEach(line => {
256
+ const useMatch = line.match(/^use\s+([a-zA-Z0-9_:]+)(?:::\{([^}]+)\})?/);
257
+ if (useMatch?.[1]) {
258
+ const name = useMatch[1];
259
+ const type = name.startsWith('std::') || name.startsWith('core::') ? 'Standard' : 'Crate';
260
+ const usage = useMatch[2];
261
+ imports.push({ name, type, usage });
262
+ }
263
+ });
264
+
265
+ return { functions, classes: [], types, imports, exports: [] };
266
+ }
267
+ }
268
+
269
+ // Registry des analyseurs
270
+ const analyzers: LanguageAnalyzer[] = [
271
+ new TSJSAnalyzer(),
272
+ new PythonAnalyzer(),
273
+ new JavaAnalyzer(),
274
+ new CSharpAnalyzer(),
275
+ new GoAnalyzer(),
276
+ new RustAnalyzer()
277
+ ];
278
+
279
+ export function getAnalyzer(filePath: string): LanguageAnalyzer | null {
280
+ const lastDotIndex = filePath.lastIndexOf('.');
281
+ if (lastDotIndex === -1) return null;
282
+ const ext = filePath.substring(lastDotIndex).toLowerCase();
283
+ return analyzers.find(a => a.extensions.includes(ext)) || null;
284
+ }
285
+
286
+ export function getSupportedExtensions(): string[] {
287
+ return analyzers.flatMap(a => a.extensions);
288
+ }
@@ -0,0 +1,179 @@
1
+ // Emplacement : M:\workspace\extensions\docstodev\src\cache\cacheManager.ts
2
+
3
+ import { createHash } from "node:crypto";
4
+ import { readFileSync, writeFileSync, existsSync, statSync, mkdirSync } from "node:fs";
5
+ import path from "node:path";
6
+
7
+ export interface CacheEntry {
8
+ hash: string;
9
+ timestamp: number;
10
+ analysis: any;
11
+ metadata: {
12
+ lines: number;
13
+ size: number;
14
+ };
15
+ }
16
+
17
+ export interface CacheManifest {
18
+ version: string;
19
+ files: Map<string, CacheEntry>;
20
+ lastUpdate: number;
21
+ }
22
+
23
+ export class CacheManager {
24
+ private cacheDir: string;
25
+ private manifestPath: string;
26
+ private manifest: CacheManifest;
27
+ private readonly CACHE_VERSION = "1.0.0";
28
+
29
+ constructor(projectRoot: string) {
30
+ this.cacheDir = path.join(projectRoot, ".docstodev", "cache");
31
+ this.manifestPath = path.join(this.cacheDir, "manifest.json");
32
+
33
+ if (!existsSync(this.cacheDir)) {
34
+ mkdirSync(this.cacheDir, { recursive: true });
35
+ }
36
+
37
+ this.manifest = this.loadManifest();
38
+ }
39
+
40
+ private loadManifest(): CacheManifest {
41
+ if (existsSync(this.manifestPath)) {
42
+ try {
43
+ const data = JSON.parse(readFileSync(this.manifestPath, "utf-8"));
44
+
45
+ // Vérifier la version du cache
46
+ if (data.version !== this.CACHE_VERSION) {
47
+ console.log("⚠️ Cache version mismatch, clearing cache...");
48
+ return this.createNewManifest();
49
+ }
50
+
51
+ // Reconstituer la Map depuis l'objet
52
+ return {
53
+ version: data.version,
54
+ files: new Map(Object.entries(data.files || {})),
55
+ lastUpdate: data.lastUpdate
56
+ };
57
+ } catch (e) {
58
+ console.error("⚠️ Failed to load cache manifest:", e);
59
+ return this.createNewManifest();
60
+ }
61
+ }
62
+ return this.createNewManifest();
63
+ }
64
+
65
+ private createNewManifest(): CacheManifest {
66
+ return {
67
+ version: this.CACHE_VERSION,
68
+ files: new Map(),
69
+ lastUpdate: Date.now()
70
+ };
71
+ }
72
+
73
+ private saveManifest(): void {
74
+ try {
75
+ const data = {
76
+ version: this.manifest.version,
77
+ files: Object.fromEntries(this.manifest.files),
78
+ lastUpdate: this.manifest.lastUpdate
79
+ };
80
+ writeFileSync(this.manifestPath, JSON.stringify(data, null, 2));
81
+ } catch (e) {
82
+ console.error("⚠️ Failed to save cache manifest:", e);
83
+ }
84
+ }
85
+
86
+ private computeHash(filePath: string, content: string): string {
87
+ const stats = statSync(filePath);
88
+ const hashContent = `${content}|${stats.mtime.getTime()}|${stats.size}`;
89
+ return createHash("sha256").update(hashContent).digest("hex");
90
+ }
91
+
92
+ public isCached(filePath: string, content: string): boolean {
93
+ const entry = this.manifest.files.get(filePath);
94
+ if (!entry) return false;
95
+
96
+ const currentHash = this.computeHash(filePath, content);
97
+ return entry.hash === currentHash;
98
+ }
99
+
100
+ public get(filePath: string): CacheEntry | null {
101
+ return this.manifest.files.get(filePath) || null;
102
+ }
103
+
104
+ public set(filePath: string, content: string, analysis: any): void {
105
+ const stats = statSync(filePath);
106
+ const hash = this.computeHash(filePath, content);
107
+
108
+ const entry: CacheEntry = {
109
+ hash,
110
+ timestamp: Date.now(),
111
+ analysis,
112
+ metadata: {
113
+ lines: content.split(/\r?\n/).length,
114
+ size: stats.size
115
+ }
116
+ };
117
+
118
+ this.manifest.files.set(filePath, entry);
119
+ this.manifest.lastUpdate = Date.now();
120
+ this.saveManifest();
121
+ }
122
+
123
+ public invalidate(filePath: string): void {
124
+ this.manifest.files.delete(filePath);
125
+ this.saveManifest();
126
+ }
127
+
128
+ public invalidateAll(): void {
129
+ this.manifest = this.createNewManifest();
130
+ this.saveManifest();
131
+ }
132
+
133
+ public getStats(): { total: number; size: number; oldestEntry: number } {
134
+ const entries = Array.from(this.manifest.files.values());
135
+ return {
136
+ total: entries.length,
137
+ size: entries.reduce((sum, e) => sum + e.metadata.size, 0),
138
+ oldestEntry: Math.min(...entries.map(e => e.timestamp), Date.now())
139
+ };
140
+ }
141
+
142
+ public pruneOldEntries(maxAgeMs: number = 7 * 24 * 60 * 60 * 1000): number {
143
+ const now = Date.now();
144
+ let pruned = 0;
145
+
146
+ for (const [filePath, entry] of this.manifest.files.entries()) {
147
+ if (now - entry.timestamp > maxAgeMs) {
148
+ this.manifest.files.delete(filePath);
149
+ pruned++;
150
+ }
151
+ }
152
+
153
+ if (pruned > 0) {
154
+ this.saveManifest();
155
+ }
156
+
157
+ return pruned;
158
+ }
159
+
160
+ public getModifiedFiles(files: string[]): string[] {
161
+ const modified: string[] = [];
162
+
163
+ for (const file of files) {
164
+ if (!existsSync(file)) continue;
165
+
166
+ try {
167
+ const content = readFileSync(file, "utf-8");
168
+ if (!this.isCached(file, content)) {
169
+ modified.push(file);
170
+ }
171
+ } catch (e) {
172
+ // Si erreur de lecture, considérer comme modifié
173
+ modified.push(file);
174
+ }
175
+ }
176
+
177
+ return modified;
178
+ }
179
+ }