elsabro 2.3.0 → 3.7.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.
Files changed (67) hide show
  1. package/README.md +668 -20
  2. package/bin/install.js +0 -0
  3. package/flows/development-flow.json +452 -0
  4. package/flows/quick-flow.json +118 -0
  5. package/package.json +3 -2
  6. package/references/SYSTEM_INDEX.md +379 -5
  7. package/references/agent-marketplace.md +2274 -0
  8. package/references/agent-protocol.md +1126 -0
  9. package/references/ai-code-suggestions.md +2413 -0
  10. package/references/checkpointing.md +595 -0
  11. package/references/collaboration-patterns.md +851 -0
  12. package/references/collaborative-sessions.md +1081 -0
  13. package/references/configuration-management.md +1810 -0
  14. package/references/cost-tracking.md +1095 -0
  15. package/references/enterprise-sso.md +2001 -0
  16. package/references/error-contracts-v2.md +968 -0
  17. package/references/event-driven.md +1031 -0
  18. package/references/flow-orchestration.md +940 -0
  19. package/references/flow-visualization.md +1557 -0
  20. package/references/ide-integrations.md +3513 -0
  21. package/references/interrupt-system.md +681 -0
  22. package/references/kubernetes-deployment.md +3099 -0
  23. package/references/memory-system.md +683 -0
  24. package/references/mobile-companion.md +3236 -0
  25. package/references/multi-llm-providers.md +2494 -0
  26. package/references/multi-project-memory.md +1182 -0
  27. package/references/observability.md +793 -0
  28. package/references/output-schemas.md +858 -0
  29. package/references/performance-profiler.md +955 -0
  30. package/references/plugin-system.md +1526 -0
  31. package/references/prompt-management.md +292 -0
  32. package/references/sandbox-execution.md +303 -0
  33. package/references/security-system.md +1253 -0
  34. package/references/streaming.md +696 -0
  35. package/references/testing-framework.md +1151 -0
  36. package/references/time-travel.md +802 -0
  37. package/references/tool-registry.md +886 -0
  38. package/references/voice-commands.md +3296 -0
  39. package/templates/agent-marketplace-config.json +220 -0
  40. package/templates/agent-protocol-config.json +136 -0
  41. package/templates/ai-suggestions-config.json +100 -0
  42. package/templates/checkpoint-state.json +61 -0
  43. package/templates/collaboration-config.json +157 -0
  44. package/templates/collaborative-sessions-config.json +153 -0
  45. package/templates/configuration-config.json +245 -0
  46. package/templates/cost-tracking-config.json +148 -0
  47. package/templates/enterprise-sso-config.json +438 -0
  48. package/templates/events-config.json +148 -0
  49. package/templates/flow-visualization-config.json +196 -0
  50. package/templates/ide-integrations-config.json +442 -0
  51. package/templates/kubernetes-config.json +764 -0
  52. package/templates/memory-state.json +84 -0
  53. package/templates/mobile-companion-config.json +600 -0
  54. package/templates/multi-llm-config.json +544 -0
  55. package/templates/multi-project-memory-config.json +145 -0
  56. package/templates/observability-config.json +109 -0
  57. package/templates/performance-profiler-config.json +125 -0
  58. package/templates/plugin-config.json +170 -0
  59. package/templates/prompt-management-config.json +86 -0
  60. package/templates/sandbox-config.json +185 -0
  61. package/templates/schemas-config.json +65 -0
  62. package/templates/security-config.json +120 -0
  63. package/templates/streaming-config.json +72 -0
  64. package/templates/testing-config.json +81 -0
  65. package/templates/timetravel-config.json +62 -0
  66. package/templates/tool-registry-config.json +109 -0
  67. package/templates/voice-commands-config.json +658 -0
@@ -0,0 +1,2413 @@
1
+ # AI Code Suggestions (v3.7)
2
+
3
+ Sistema de sugerencias de codigo impulsado por AI para ELSABRO, proporcionando autocompletado inteligente, refactoring, deteccion de patrones, y analisis de seguridad.
4
+
5
+ ## Arquitectura General
6
+
7
+ ```
8
+ +------------------------------------------------------------------+
9
+ | AI CODE SUGGESTIONS ENGINE |
10
+ +------------------------------------------------------------------+
11
+ | |
12
+ | +--------------------+ +--------------------+ |
13
+ | | Context Gatherer | | Embedding Cache | |
14
+ | | - Current file | | - Vector store | |
15
+ | | - Imports/types | | - Similarity search| |
16
+ | | - Sibling files | | - TTL management | |
17
+ | +--------+-----------+ +--------+-----------+ |
18
+ | | | |
19
+ | v v |
20
+ | +--------------------------------------------------+ |
21
+ | | SUGGESTION RANKER | |
22
+ | | - Relevance scoring - Context matching | |
23
+ | | - Type inference - Usage patterns | |
24
+ | +--------------------------------------------------+ |
25
+ | | |
26
+ | v |
27
+ | +--------------------------------------------------+ |
28
+ | | PROVIDER LAYER | |
29
+ | +--------------------------------------------------+ |
30
+ | | Completion | Refactor | Pattern | Docs | Test | |
31
+ | | Provider | Advisor | Detector| Gen | Suggest | |
32
+ | +--------------------------------------------------+ |
33
+ | | |
34
+ | v |
35
+ | +--------------------------------------------------+ |
36
+ | | SECURITY & PERFORMANCE | |
37
+ | | - OWASP scanning - N+1 detection | |
38
+ | | - Secret detection - Memory leak patterns | |
39
+ | +--------------------------------------------------+ |
40
+ | |
41
+ +------------------------------------------------------------------+
42
+ ```
43
+
44
+ ---
45
+
46
+ ## 1. CodeSuggestionEngine
47
+
48
+ Motor principal que coordina todas las sugerencias de codigo.
49
+
50
+ ### Arquitectura del Motor
51
+
52
+ ```typescript
53
+ interface CodeSuggestionEngine {
54
+ context: ContextGatherer;
55
+ embeddings: EmbeddingCache;
56
+ ranker: SuggestionRanker;
57
+ providers: Map<string, SuggestionProvider>;
58
+ }
59
+
60
+ interface ContextGatherer {
61
+ currentFile: FileContext;
62
+ imports: ImportGraph;
63
+ types: TypeRegistry;
64
+ siblings: FileContext[];
65
+ history: EditHistory;
66
+ }
67
+
68
+ interface SuggestionRanker {
69
+ score(suggestion: Suggestion, context: Context): number;
70
+ rank(suggestions: Suggestion[]): Suggestion[];
71
+ filter(suggestions: Suggestion[], threshold: number): Suggestion[];
72
+ }
73
+ ```
74
+
75
+ ### Implementacion Principal
76
+
77
+ ```typescript
78
+ class CodeSuggestionEngine {
79
+ private contextGatherer: ContextGatherer;
80
+ private embeddingCache: EmbeddingCache;
81
+ private ranker: SuggestionRanker;
82
+ private providers: Map<string, SuggestionProvider>;
83
+
84
+ constructor(config: SuggestionConfig) {
85
+ this.contextGatherer = new ContextGatherer(config.context);
86
+ this.embeddingCache = new EmbeddingCache(config.cache);
87
+ this.ranker = new SuggestionRanker(config.ranking);
88
+ this.providers = this.initializeProviders(config);
89
+ }
90
+
91
+ async getSuggestions(request: SuggestionRequest): Promise<Suggestion[]> {
92
+ // 1. Gather context
93
+ const context = await this.contextGatherer.gather({
94
+ file: request.file,
95
+ position: request.position,
96
+ prefix: request.prefix,
97
+ suffix: request.suffix
98
+ });
99
+
100
+ // 2. Get embeddings for similarity search
101
+ const embeddings = await this.embeddingCache.getOrCompute(
102
+ context.relevantCode,
103
+ context.fileHash
104
+ );
105
+
106
+ // 3. Query all relevant providers in parallel
107
+ const providerResults = await Promise.all(
108
+ Array.from(this.providers.entries())
109
+ .filter(([name]) => this.shouldQueryProvider(name, request))
110
+ .map(([name, provider]) =>
111
+ provider.suggest(context, embeddings)
112
+ .catch(err => {
113
+ console.error(`Provider ${name} failed:`, err);
114
+ return [];
115
+ })
116
+ )
117
+ );
118
+
119
+ // 4. Flatten and rank suggestions
120
+ const allSuggestions = providerResults.flat();
121
+ const ranked = this.ranker.rank(allSuggestions);
122
+
123
+ // 5. Apply filters and limits
124
+ return ranked
125
+ .filter(s => s.score >= request.minScore || 0.5)
126
+ .slice(0, request.maxSuggestions || 5);
127
+ }
128
+
129
+ private shouldQueryProvider(
130
+ name: string,
131
+ request: SuggestionRequest
132
+ ): boolean {
133
+ const providerMap: Record<string, SuggestionType[]> = {
134
+ completion: ['inline', 'multiline', 'fim'],
135
+ refactoring: ['extract', 'rename', 'move'],
136
+ patterns: ['antipattern', 'smell', 'solid'],
137
+ documentation: ['jsdoc', 'readme', 'changelog'],
138
+ testing: ['unit', 'integration', 'edge'],
139
+ security: ['owasp', 'secrets', 'deps'],
140
+ performance: ['n1', 'memory', 'bundle']
141
+ };
142
+
143
+ return !request.types ||
144
+ request.types.some(t => providerMap[name]?.includes(t));
145
+ }
146
+ }
147
+ ```
148
+
149
+ ### Context Gathering
150
+
151
+ ```typescript
152
+ class ContextGatherer {
153
+ private parser: CodeParser;
154
+ private typeResolver: TypeResolver;
155
+ private importResolver: ImportResolver;
156
+
157
+ async gather(request: GatherRequest): Promise<GatheredContext> {
158
+ const fileContent = await this.readFile(request.file);
159
+ const ast = this.parser.parse(fileContent);
160
+
161
+ // Get cursor position context
162
+ const cursorContext = this.getCursorContext(ast, request.position);
163
+
164
+ // Resolve imports and their exports
165
+ const imports = await this.importResolver.resolve(ast);
166
+
167
+ // Get type information
168
+ const types = await this.typeResolver.resolveAtPosition(
169
+ ast,
170
+ request.position,
171
+ imports
172
+ );
173
+
174
+ // Get sibling files for additional context
175
+ const siblings = await this.getSiblingContext(request.file);
176
+
177
+ // Get recent edit history
178
+ const history = await this.getEditHistory(request.file);
179
+
180
+ return {
181
+ file: {
182
+ path: request.file,
183
+ content: fileContent,
184
+ language: this.detectLanguage(request.file),
185
+ ast
186
+ },
187
+ cursor: cursorContext,
188
+ imports,
189
+ types,
190
+ siblings,
191
+ history,
192
+ prefix: request.prefix,
193
+ suffix: request.suffix
194
+ };
195
+ }
196
+
197
+ private getCursorContext(ast: AST, position: Position): CursorContext {
198
+ const node = this.findNodeAtPosition(ast, position);
199
+
200
+ return {
201
+ node,
202
+ scope: this.getScopeAtNode(node),
203
+ parentFunction: this.findParentFunction(node),
204
+ parentClass: this.findParentClass(node),
205
+ localVariables: this.getLocalVariables(node),
206
+ availableSymbols: this.getAvailableSymbols(node)
207
+ };
208
+ }
209
+
210
+ private async getSiblingContext(filePath: string): Promise<FileContext[]> {
211
+ const dir = path.dirname(filePath);
212
+ const files = await fs.readdir(dir);
213
+
214
+ return Promise.all(
215
+ files
216
+ .filter(f => this.isSupportedFile(f) && f !== path.basename(filePath))
217
+ .slice(0, 3) // Limit to 3 siblings
218
+ .map(async f => ({
219
+ path: path.join(dir, f),
220
+ content: await fs.readFile(path.join(dir, f), 'utf-8'),
221
+ exports: await this.extractExports(path.join(dir, f))
222
+ }))
223
+ );
224
+ }
225
+ }
226
+ ```
227
+
228
+ ### Embedding Cache
229
+
230
+ ```typescript
231
+ class EmbeddingCache {
232
+ private cache: LRUCache<string, Float32Array>;
233
+ private vectorStore: VectorStore;
234
+ private embeddingModel: EmbeddingModel;
235
+
236
+ constructor(config: CacheConfig) {
237
+ this.cache = new LRUCache({
238
+ max: config.maxEntries || 10000,
239
+ ttl: config.ttlSeconds * 1000
240
+ });
241
+ this.vectorStore = new VectorStore(config.embeddingsDir);
242
+ this.embeddingModel = new EmbeddingModel(config.model);
243
+ }
244
+
245
+ async getOrCompute(code: string, hash: string): Promise<Float32Array> {
246
+ // Check memory cache first
247
+ const cached = this.cache.get(hash);
248
+ if (cached) return cached;
249
+
250
+ // Check persistent store
251
+ const stored = await this.vectorStore.get(hash);
252
+ if (stored) {
253
+ this.cache.set(hash, stored);
254
+ return stored;
255
+ }
256
+
257
+ // Compute new embedding
258
+ const embedding = await this.embeddingModel.embed(code);
259
+
260
+ // Store in both caches
261
+ this.cache.set(hash, embedding);
262
+ await this.vectorStore.set(hash, embedding);
263
+
264
+ return embedding;
265
+ }
266
+
267
+ async findSimilar(
268
+ embedding: Float32Array,
269
+ topK: number = 10
270
+ ): Promise<SimilarResult[]> {
271
+ return this.vectorStore.similaritySearch(embedding, topK);
272
+ }
273
+ }
274
+ ```
275
+
276
+ ### Ranking de Sugerencias
277
+
278
+ ```typescript
279
+ class SuggestionRanker {
280
+ private weights: RankingWeights;
281
+
282
+ constructor(config: RankerConfig) {
283
+ this.weights = config.weights || {
284
+ relevance: 0.35,
285
+ confidence: 0.25,
286
+ recency: 0.15,
287
+ popularity: 0.15,
288
+ typeMatch: 0.10
289
+ };
290
+ }
291
+
292
+ score(suggestion: Suggestion, context: Context): number {
293
+ const scores = {
294
+ relevance: this.computeRelevance(suggestion, context),
295
+ confidence: suggestion.confidence,
296
+ recency: this.computeRecency(suggestion, context),
297
+ popularity: this.computePopularity(suggestion),
298
+ typeMatch: this.computeTypeMatch(suggestion, context)
299
+ };
300
+
301
+ return Object.entries(scores).reduce(
302
+ (total, [key, value]) => total + value * this.weights[key],
303
+ 0
304
+ );
305
+ }
306
+
307
+ rank(suggestions: Suggestion[]): Suggestion[] {
308
+ return suggestions
309
+ .map(s => ({ ...s, finalScore: s.score }))
310
+ .sort((a, b) => b.finalScore - a.finalScore);
311
+ }
312
+
313
+ private computeRelevance(s: Suggestion, ctx: Context): number {
314
+ // Cosine similarity between suggestion and context embeddings
315
+ const similarity = cosineSimilarity(s.embedding, ctx.embedding);
316
+
317
+ // Boost for matching identifiers
318
+ const identifierBoost = this.countMatchingIdentifiers(s, ctx) * 0.1;
319
+
320
+ // Boost for matching types
321
+ const typeBoost = this.hasMatchingTypes(s, ctx) ? 0.2 : 0;
322
+
323
+ return Math.min(1, similarity + identifierBoost + typeBoost);
324
+ }
325
+
326
+ private computeTypeMatch(s: Suggestion, ctx: Context): number {
327
+ if (!ctx.expectedType) return 0.5;
328
+
329
+ const suggestionType = this.inferType(s);
330
+ return this.typesCompatible(suggestionType, ctx.expectedType) ? 1 : 0;
331
+ }
332
+ }
333
+ ```
334
+
335
+ ---
336
+
337
+ ## 2. CompletionProvider
338
+
339
+ Proveedor de autocompletado inline estilo Copilot.
340
+
341
+ ### Arquitectura
342
+
343
+ ```
344
+ +----------------------------------------------------------+
345
+ | COMPLETION PROVIDER |
346
+ +----------------------------------------------------------+
347
+ | |
348
+ | +------------------+ +------------------+ |
349
+ | | Inline Completer | | Multiline Engine | |
350
+ | | - Single tokens | | - Block suggest | |
351
+ | | - Property access| | - Function body | |
352
+ | +--------+---------+ +--------+---------+ |
353
+ | | | |
354
+ | v v |
355
+ | +--------------------------------------------------+ |
356
+ | | FILL-IN-MIDDLE (FIM) | |
357
+ | | - Prefix analysis - Suffix analysis | |
358
+ | | - Gap detection - Context bridging | |
359
+ | +--------------------------------------------------+ |
360
+ | | |
361
+ | v |
362
+ | +--------------------------------------------------+ |
363
+ | | GHOST TEXT RENDERER | |
364
+ | | - Preview display - Accept/reject handling | |
365
+ | | - Partial accept - Tab completion | |
366
+ | +--------------------------------------------------+ |
367
+ | |
368
+ +----------------------------------------------------------+
369
+ ```
370
+
371
+ ### Implementacion
372
+
373
+ ```typescript
374
+ class CompletionProvider implements SuggestionProvider {
375
+ private llm: LLMClient;
376
+ private tokenizer: Tokenizer;
377
+ private fimEngine: FillInMiddleEngine;
378
+
379
+ async suggest(
380
+ context: GatheredContext,
381
+ embeddings: Float32Array
382
+ ): Promise<Suggestion[]> {
383
+ const completionType = this.detectCompletionType(context);
384
+
385
+ switch (completionType) {
386
+ case 'inline':
387
+ return this.getInlineCompletions(context);
388
+ case 'multiline':
389
+ return this.getMultilineCompletions(context);
390
+ case 'fim':
391
+ return this.getFillInMiddleCompletions(context);
392
+ default:
393
+ return [];
394
+ }
395
+ }
396
+
397
+ private detectCompletionType(context: GatheredContext): CompletionType {
398
+ const { cursor, prefix, suffix } = context;
399
+
400
+ // FIM if there's meaningful code after cursor
401
+ if (suffix && suffix.trim().length > 10) {
402
+ return 'fim';
403
+ }
404
+
405
+ // Multiline if at end of line after control structure
406
+ if (this.isAfterControlStructure(cursor)) {
407
+ return 'multiline';
408
+ }
409
+
410
+ // Multiline if inside empty function/method body
411
+ if (this.isEmptyFunctionBody(cursor)) {
412
+ return 'multiline';
413
+ }
414
+
415
+ return 'inline';
416
+ }
417
+
418
+ private async getInlineCompletions(
419
+ context: GatheredContext
420
+ ): Promise<Suggestion[]> {
421
+ const prompt = this.buildInlinePrompt(context);
422
+
423
+ const response = await this.llm.complete({
424
+ prompt,
425
+ maxTokens: 50,
426
+ temperature: 0.2,
427
+ stopSequences: ['\n', ';', '{', '}']
428
+ });
429
+
430
+ return response.completions.map(c => ({
431
+ type: 'inline',
432
+ text: c.text,
433
+ confidence: c.logprob ? Math.exp(c.logprob) : 0.8,
434
+ range: {
435
+ start: context.cursor.position,
436
+ end: context.cursor.position
437
+ },
438
+ preview: this.formatGhostText(c.text)
439
+ }));
440
+ }
441
+
442
+ private async getMultilineCompletions(
443
+ context: GatheredContext
444
+ ): Promise<Suggestion[]> {
445
+ const prompt = this.buildMultilinePrompt(context);
446
+
447
+ const response = await this.llm.complete({
448
+ prompt,
449
+ maxTokens: 500,
450
+ temperature: 0.3,
451
+ stopSequences: ['}\n\n', '\n\n\n']
452
+ });
453
+
454
+ return response.completions.map(c => ({
455
+ type: 'multiline',
456
+ text: c.text,
457
+ confidence: this.computeMultilineConfidence(c),
458
+ range: {
459
+ start: context.cursor.position,
460
+ end: context.cursor.position
461
+ },
462
+ preview: this.formatMultilineGhostText(c.text),
463
+ lines: c.text.split('\n').length
464
+ }));
465
+ }
466
+
467
+ private buildInlinePrompt(context: GatheredContext): string {
468
+ return `
469
+ // File: ${context.file.path}
470
+ // Language: ${context.file.language}
471
+
472
+ // Imports
473
+ ${context.imports.map(i => i.statement).join('\n')}
474
+
475
+ // Available types
476
+ ${this.formatTypes(context.types)}
477
+
478
+ // Current code context
479
+ ${context.prefix}
480
+ <CURSOR>
481
+ ${context.suffix?.slice(0, 200) || ''}
482
+
483
+ // Complete the code at <CURSOR>:
484
+ `.trim();
485
+ }
486
+ }
487
+ ```
488
+
489
+ ### Fill-in-the-Middle (FIM)
490
+
491
+ ```typescript
492
+ class FillInMiddleEngine {
493
+ private llm: LLMClient;
494
+
495
+ async complete(context: FIMContext): Promise<FIMResult[]> {
496
+ // Special FIM prompt format
497
+ const prompt = this.buildFIMPrompt(context);
498
+
499
+ const response = await this.llm.complete({
500
+ prompt,
501
+ maxTokens: 200,
502
+ temperature: 0.2,
503
+ fim: true // Enable FIM mode if supported
504
+ });
505
+
506
+ return response.completions.map(c => ({
507
+ text: c.text,
508
+ bridgesContext: this.validateBridge(c.text, context),
509
+ confidence: this.computeFIMConfidence(c, context)
510
+ }));
511
+ }
512
+
513
+ private buildFIMPrompt(context: FIMContext): string {
514
+ // Standard FIM format: <PRE>prefix<SUF>suffix<MID>
515
+ return `<PRE>${context.prefix}<SUF>${context.suffix}<MID>`;
516
+ }
517
+
518
+ private validateBridge(completion: string, context: FIMContext): boolean {
519
+ // Check if completion logically connects prefix and suffix
520
+ const combined = context.prefix + completion + context.suffix;
521
+
522
+ try {
523
+ // Try to parse combined code
524
+ const ast = parse(combined, { errorRecovery: true });
525
+ return ast.errors.length === 0;
526
+ } catch {
527
+ return false;
528
+ }
529
+ }
530
+ }
531
+ ```
532
+
533
+ ### Ghost Text Preview
534
+
535
+ ```typescript
536
+ class GhostTextRenderer {
537
+ private decorationType: TextEditorDecorationType;
538
+
539
+ constructor(editor: TextEditor) {
540
+ this.decorationType = window.createTextEditorDecorationType({
541
+ after: {
542
+ color: new ThemeColor('editorGhostText.foreground'),
543
+ fontStyle: 'italic'
544
+ }
545
+ });
546
+ }
547
+
548
+ show(suggestion: Suggestion, position: Position): void {
549
+ const decoration: DecorationOptions = {
550
+ range: new Range(position, position),
551
+ renderOptions: {
552
+ after: {
553
+ contentText: suggestion.preview,
554
+ color: 'rgba(128, 128, 128, 0.6)'
555
+ }
556
+ }
557
+ };
558
+
559
+ this.editor.setDecorations(this.decorationType, [decoration]);
560
+ }
561
+
562
+ hide(): void {
563
+ this.editor.setDecorations(this.decorationType, []);
564
+ }
565
+
566
+ acceptFull(suggestion: Suggestion): void {
567
+ this.editor.edit(builder => {
568
+ builder.insert(this.currentPosition, suggestion.text);
569
+ });
570
+ this.hide();
571
+ }
572
+
573
+ acceptPartial(suggestion: Suggestion, chars: number): void {
574
+ const partial = suggestion.text.slice(0, chars);
575
+ this.editor.edit(builder => {
576
+ builder.insert(this.currentPosition, partial);
577
+ });
578
+
579
+ // Update ghost text with remainder
580
+ const remainder = suggestion.text.slice(chars);
581
+ if (remainder) {
582
+ this.show({ ...suggestion, preview: remainder },
583
+ this.currentPosition.translate(0, chars));
584
+ } else {
585
+ this.hide();
586
+ }
587
+ }
588
+ }
589
+ ```
590
+
591
+ ---
592
+
593
+ ## 3. RefactoringAdvisor
594
+
595
+ Detecta oportunidades de refactoring y sugiere mejoras.
596
+
597
+ ### Arquitectura
598
+
599
+ ```
600
+ +----------------------------------------------------------+
601
+ | REFACTORING ADVISOR |
602
+ +----------------------------------------------------------+
603
+ | |
604
+ | +---------------------------------------------------+ |
605
+ | | CODE ANALYZER | |
606
+ | | - AST traversal - Complexity metrics | |
607
+ | | - Duplication - Dependency analysis | |
608
+ | +---------------------------------------------------+ |
609
+ | | |
610
+ | +--------------+---------------+ |
611
+ | v v v |
612
+ | +-------------+ +-------------+ +-------------+ |
613
+ | | Extract | | Rename | | Move | |
614
+ | | - Function | | - Symbol | | - To file | |
615
+ | | - Variable | | - Semantic | | - Organize | |
616
+ | | - Constant | | - Consistent| | - Split | |
617
+ | +-------------+ +-------------+ +-------------+ |
618
+ | |
619
+ +----------------------------------------------------------+
620
+ ```
621
+
622
+ ### Implementacion
623
+
624
+ ```typescript
625
+ class RefactoringAdvisor implements SuggestionProvider {
626
+ private analyzer: CodeAnalyzer;
627
+ private extractors: Map<string, Extractor>;
628
+
629
+ async suggest(
630
+ context: GatheredContext,
631
+ embeddings: Float32Array
632
+ ): Promise<Suggestion[]> {
633
+ const suggestions: Suggestion[] = [];
634
+
635
+ // Analyze code for refactoring opportunities
636
+ const analysis = await this.analyzer.analyze(context.file.ast);
637
+
638
+ // Check for extractable code
639
+ if (analysis.hasLongFunctions) {
640
+ suggestions.push(...this.suggestExtractFunction(analysis, context));
641
+ }
642
+
643
+ if (analysis.hasRepeatedExpressions) {
644
+ suggestions.push(...this.suggestExtractVariable(analysis, context));
645
+ }
646
+
647
+ // Check for rename opportunities
648
+ if (analysis.hasInconsistentNaming) {
649
+ suggestions.push(...this.suggestRename(analysis, context));
650
+ }
651
+
652
+ // Check for move opportunities
653
+ if (analysis.hasFileOrganizationIssues) {
654
+ suggestions.push(...this.suggestMove(analysis, context));
655
+ }
656
+
657
+ return suggestions;
658
+ }
659
+
660
+ private suggestExtractFunction(
661
+ analysis: CodeAnalysis,
662
+ context: GatheredContext
663
+ ): Suggestion[] {
664
+ return analysis.longFunctions.map(fn => {
665
+ const extractable = this.findExtractableBlock(fn);
666
+
667
+ return {
668
+ type: 'refactor',
669
+ subtype: 'extract-function',
670
+ title: `Extract function from ${fn.name}`,
671
+ description: `Lines ${extractable.start}-${extractable.end} can be extracted`,
672
+ confidence: this.computeExtractConfidence(extractable),
673
+ range: extractable.range,
674
+ preview: this.generateExtractPreview(extractable, context),
675
+ apply: async () => this.applyExtractFunction(extractable, context)
676
+ };
677
+ });
678
+ }
679
+
680
+ private findExtractableBlock(fn: FunctionNode): ExtractableBlock {
681
+ // Find coherent block that can be extracted
682
+ const blocks = this.identifyLogicalBlocks(fn.body);
683
+
684
+ return blocks
685
+ .filter(b => b.statements.length >= 3)
686
+ .filter(b => this.hasCleanDependencies(b))
687
+ .sort((a, b) => b.cohesion - a.cohesion)[0];
688
+ }
689
+
690
+ private generateExtractPreview(
691
+ block: ExtractableBlock,
692
+ context: GatheredContext
693
+ ): string {
694
+ const params = this.computeParameters(block);
695
+ const returnType = this.inferReturnType(block);
696
+ const name = this.suggestFunctionName(block);
697
+
698
+ return `
699
+ // Extracted function
700
+ function ${name}(${params.map(p => `${p.name}: ${p.type}`).join(', ')}): ${returnType} {
701
+ ${block.statements.map(s => ' ' + s.text).join('\n')}
702
+ }
703
+
704
+ // Updated call site
705
+ const result = ${name}(${params.map(p => p.name).join(', ')});
706
+ `.trim();
707
+ }
708
+ }
709
+ ```
710
+
711
+ ### Extract Function
712
+
713
+ ```typescript
714
+ class FunctionExtractor implements Extractor {
715
+ async extract(block: ExtractableBlock, context: GatheredContext): Promise<RefactorResult> {
716
+ // Analyze dependencies
717
+ const deps = this.analyzeDependencies(block);
718
+
719
+ // Determine parameters (variables used but not defined in block)
720
+ const params = deps.reads.filter(v => !deps.writes.includes(v));
721
+
722
+ // Determine return value (variables written and used after block)
723
+ const returns = deps.writes.filter(v => deps.usedAfter.includes(v));
724
+
725
+ // Generate function signature
726
+ const signature = this.generateSignature(params, returns, context);
727
+
728
+ // Generate function body
729
+ const body = this.transformToFunctionBody(block, params, returns);
730
+
731
+ // Generate call site
732
+ const callSite = this.generateCallSite(signature, params, returns);
733
+
734
+ return {
735
+ newFunction: {
736
+ signature,
737
+ body,
738
+ location: this.findBestLocation(context)
739
+ },
740
+ callSite,
741
+ removedLines: block.range
742
+ };
743
+ }
744
+
745
+ private analyzeDependencies(block: ExtractableBlock): Dependencies {
746
+ const reads = new Set<string>();
747
+ const writes = new Set<string>();
748
+
749
+ for (const stmt of block.statements) {
750
+ // Find all identifier references
751
+ traverse(stmt.ast, {
752
+ Identifier(path) {
753
+ if (isRead(path)) reads.add(path.node.name);
754
+ if (isWrite(path)) writes.add(path.node.name);
755
+ }
756
+ });
757
+ }
758
+
759
+ return {
760
+ reads: Array.from(reads),
761
+ writes: Array.from(writes),
762
+ usedAfter: this.findUsagesAfterBlock(block)
763
+ };
764
+ }
765
+ }
766
+ ```
767
+
768
+ ### Rename Suggestions
769
+
770
+ ```typescript
771
+ class RenameAdvisor {
772
+ private namingConventions: NamingConventions;
773
+
774
+ suggestRenames(analysis: CodeAnalysis, context: GatheredContext): Suggestion[] {
775
+ const suggestions: Suggestion[] = [];
776
+
777
+ // Check each symbol against naming conventions
778
+ for (const symbol of analysis.symbols) {
779
+ const issues = this.checkNamingConventions(symbol);
780
+
781
+ if (issues.length > 0) {
782
+ const suggestedName = this.suggestBetterName(symbol, issues);
783
+
784
+ suggestions.push({
785
+ type: 'refactor',
786
+ subtype: 'rename',
787
+ title: `Rename ${symbol.name} to ${suggestedName}`,
788
+ description: issues.join(', '),
789
+ confidence: 0.8,
790
+ preview: this.generateRenamePreview(symbol, suggestedName, context),
791
+ apply: async () => this.applyRename(symbol, suggestedName, context)
792
+ });
793
+ }
794
+ }
795
+
796
+ // Check for semantic improvements
797
+ for (const fn of analysis.functions) {
798
+ if (!this.isDescriptiveName(fn.name, fn)) {
799
+ const betterName = this.inferBetterFunctionName(fn);
800
+
801
+ suggestions.push({
802
+ type: 'refactor',
803
+ subtype: 'rename',
804
+ title: `Rename function ${fn.name} to ${betterName}`,
805
+ description: 'Name does not describe function behavior',
806
+ confidence: 0.7,
807
+ preview: `${fn.name} -> ${betterName}`
808
+ });
809
+ }
810
+ }
811
+
812
+ return suggestions;
813
+ }
814
+
815
+ private checkNamingConventions(symbol: Symbol): string[] {
816
+ const issues: string[] = [];
817
+
818
+ switch (symbol.kind) {
819
+ case 'class':
820
+ if (!isPascalCase(symbol.name)) {
821
+ issues.push('Classes should use PascalCase');
822
+ }
823
+ break;
824
+ case 'function':
825
+ case 'variable':
826
+ if (!isCamelCase(symbol.name)) {
827
+ issues.push('Should use camelCase');
828
+ }
829
+ break;
830
+ case 'constant':
831
+ if (!isUpperSnakeCase(symbol.name) && !isCamelCase(symbol.name)) {
832
+ issues.push('Constants should use UPPER_SNAKE_CASE or camelCase');
833
+ }
834
+ break;
835
+ }
836
+
837
+ // Check for common anti-patterns
838
+ if (symbol.name.length < 3 && !this.isAcceptableShortName(symbol)) {
839
+ issues.push('Name too short - not descriptive');
840
+ }
841
+
842
+ if (symbol.name.match(/^(data|info|item|thing|obj|val)$/i)) {
843
+ issues.push('Generic name - be more specific');
844
+ }
845
+
846
+ return issues;
847
+ }
848
+ }
849
+ ```
850
+
851
+ ---
852
+
853
+ ## 4. PatternDetector
854
+
855
+ Detecta anti-patterns, code smells, y violaciones SOLID.
856
+
857
+ ### Arquitectura
858
+
859
+ ```
860
+ +----------------------------------------------------------+
861
+ | PATTERN DETECTOR |
862
+ +----------------------------------------------------------+
863
+ | |
864
+ | +---------------------------------------------------+ |
865
+ | | PATTERN ANALYZERS | |
866
+ | +---------------------------------------------------+ |
867
+ | | | |
868
+ | | +---------------+ +---------------+ | |
869
+ | | | Anti-Pattern | | Code Smell | | |
870
+ | | | Detector | | Detector | | |
871
+ | | +---------------+ +---------------+ | |
872
+ | | | |
873
+ | | +---------------+ +---------------+ | |
874
+ | | | SOLID | | Framework | | |
875
+ | | | Analyzer | | Patterns | | |
876
+ | | +---------------+ +---------------+ | |
877
+ | | | |
878
+ | +---------------------------------------------------+ |
879
+ | | |
880
+ | v |
881
+ | +---------------------------------------------------+ |
882
+ | | FIX SUGGESTION ENGINE | |
883
+ | +---------------------------------------------------+ |
884
+ | |
885
+ +----------------------------------------------------------+
886
+ ```
887
+
888
+ ### Anti-Pattern Detection
889
+
890
+ ```typescript
891
+ class AntiPatternDetector {
892
+ private patterns: AntiPattern[] = [
893
+ {
894
+ name: 'callback-hell',
895
+ detect: (ast) => this.detectCallbackHell(ast),
896
+ severity: 'warning',
897
+ fix: 'Convert to async/await'
898
+ },
899
+ {
900
+ name: 'god-object',
901
+ detect: (ast) => this.detectGodObject(ast),
902
+ severity: 'error',
903
+ fix: 'Split into smaller, focused classes'
904
+ },
905
+ {
906
+ name: 'magic-numbers',
907
+ detect: (ast) => this.detectMagicNumbers(ast),
908
+ severity: 'info',
909
+ fix: 'Extract to named constants'
910
+ },
911
+ {
912
+ name: 'feature-envy',
913
+ detect: (ast) => this.detectFeatureEnvy(ast),
914
+ severity: 'warning',
915
+ fix: 'Move method to class it uses most'
916
+ }
917
+ ];
918
+
919
+ async detect(context: GatheredContext): Promise<DetectedPattern[]> {
920
+ const results: DetectedPattern[] = [];
921
+
922
+ for (const pattern of this.patterns) {
923
+ const matches = pattern.detect(context.file.ast);
924
+
925
+ for (const match of matches) {
926
+ results.push({
927
+ pattern: pattern.name,
928
+ severity: pattern.severity,
929
+ location: match.location,
930
+ description: this.describeMatch(pattern, match),
931
+ suggestedFix: pattern.fix,
932
+ fixPreview: this.generateFixPreview(pattern, match, context)
933
+ });
934
+ }
935
+ }
936
+
937
+ return results;
938
+ }
939
+
940
+ private detectCallbackHell(ast: AST): PatternMatch[] {
941
+ const matches: PatternMatch[] = [];
942
+ let currentDepth = 0;
943
+ const threshold = 3;
944
+
945
+ traverse(ast, {
946
+ CallExpression: {
947
+ enter(path) {
948
+ // Check if callback argument
949
+ const lastArg = path.node.arguments.at(-1);
950
+ if (lastArg?.type === 'ArrowFunctionExpression' ||
951
+ lastArg?.type === 'FunctionExpression') {
952
+ currentDepth++;
953
+
954
+ if (currentDepth >= threshold) {
955
+ matches.push({
956
+ location: path.node.loc,
957
+ depth: currentDepth,
958
+ suggestion: 'Nested callbacks detected'
959
+ });
960
+ }
961
+ }
962
+ },
963
+ exit() {
964
+ currentDepth = Math.max(0, currentDepth - 1);
965
+ }
966
+ }
967
+ });
968
+
969
+ return matches;
970
+ }
971
+
972
+ private detectGodObject(ast: AST): PatternMatch[] {
973
+ const matches: PatternMatch[] = [];
974
+ const thresholds = {
975
+ methods: 20,
976
+ properties: 15,
977
+ lines: 500,
978
+ responsibilities: 5
979
+ };
980
+
981
+ traverse(ast, {
982
+ ClassDeclaration(path) {
983
+ const cls = path.node;
984
+ const methods = cls.body.body.filter(m => m.type === 'MethodDefinition');
985
+ const properties = cls.body.body.filter(m => m.type === 'PropertyDefinition');
986
+
987
+ const issues: string[] = [];
988
+
989
+ if (methods.length > thresholds.methods) {
990
+ issues.push(`${methods.length} methods (threshold: ${thresholds.methods})`);
991
+ }
992
+
993
+ if (properties.length > thresholds.properties) {
994
+ issues.push(`${properties.length} properties (threshold: ${thresholds.properties})`);
995
+ }
996
+
997
+ if (issues.length > 0) {
998
+ matches.push({
999
+ location: cls.loc,
1000
+ className: cls.id?.name,
1001
+ issues,
1002
+ suggestion: 'Class has too many responsibilities'
1003
+ });
1004
+ }
1005
+ }
1006
+ });
1007
+
1008
+ return matches;
1009
+ }
1010
+ }
1011
+ ```
1012
+
1013
+ ### Code Smell Detection
1014
+
1015
+ ```typescript
1016
+ class CodeSmellDetector {
1017
+ async detect(context: GatheredContext): Promise<CodeSmell[]> {
1018
+ const smells: CodeSmell[] = [];
1019
+
1020
+ // Long functions
1021
+ smells.push(...this.detectLongFunctions(context.file.ast));
1022
+
1023
+ // Deep nesting
1024
+ smells.push(...this.detectDeepNesting(context.file.ast));
1025
+
1026
+ // Long parameter lists
1027
+ smells.push(...this.detectLongParameterLists(context.file.ast));
1028
+
1029
+ // Duplicate code
1030
+ smells.push(...this.detectDuplicateCode(context.file.ast));
1031
+
1032
+ // Complex conditionals
1033
+ smells.push(...this.detectComplexConditionals(context.file.ast));
1034
+
1035
+ return smells;
1036
+ }
1037
+
1038
+ private detectLongFunctions(ast: AST): CodeSmell[] {
1039
+ const smells: CodeSmell[] = [];
1040
+ const threshold = 50;
1041
+
1042
+ traverse(ast, {
1043
+ 'FunctionDeclaration|FunctionExpression|ArrowFunctionExpression'(path) {
1044
+ const lines = path.node.loc.end.line - path.node.loc.start.line;
1045
+
1046
+ if (lines > threshold) {
1047
+ smells.push({
1048
+ type: 'long-function',
1049
+ severity: lines > threshold * 2 ? 'error' : 'warning',
1050
+ location: path.node.loc,
1051
+ message: `Function has ${lines} lines (threshold: ${threshold})`,
1052
+ metric: { lines, threshold },
1053
+ fix: 'Extract smaller functions with single responsibilities'
1054
+ });
1055
+ }
1056
+ }
1057
+ });
1058
+
1059
+ return smells;
1060
+ }
1061
+
1062
+ private detectDeepNesting(ast: AST): CodeSmell[] {
1063
+ const smells: CodeSmell[] = [];
1064
+ const threshold = 4;
1065
+ let currentDepth = 0;
1066
+ let maxDepth = 0;
1067
+ let deepestNode: any = null;
1068
+
1069
+ const nestingNodes = [
1070
+ 'IfStatement',
1071
+ 'ForStatement',
1072
+ 'WhileStatement',
1073
+ 'DoWhileStatement',
1074
+ 'SwitchStatement',
1075
+ 'TryStatement'
1076
+ ];
1077
+
1078
+ traverse(ast, {
1079
+ enter(path) {
1080
+ if (nestingNodes.includes(path.node.type)) {
1081
+ currentDepth++;
1082
+ if (currentDepth > maxDepth) {
1083
+ maxDepth = currentDepth;
1084
+ deepestNode = path.node;
1085
+ }
1086
+ }
1087
+ },
1088
+ exit(path) {
1089
+ if (nestingNodes.includes(path.node.type)) {
1090
+ currentDepth--;
1091
+ }
1092
+ }
1093
+ });
1094
+
1095
+ if (maxDepth > threshold && deepestNode) {
1096
+ smells.push({
1097
+ type: 'deep-nesting',
1098
+ severity: 'warning',
1099
+ location: deepestNode.loc,
1100
+ message: `Nesting depth ${maxDepth} exceeds threshold ${threshold}`,
1101
+ metric: { depth: maxDepth, threshold },
1102
+ fix: 'Use early returns, extract methods, or flatten logic'
1103
+ });
1104
+ }
1105
+
1106
+ return smells;
1107
+ }
1108
+ }
1109
+ ```
1110
+
1111
+ ### SOLID Analyzer
1112
+
1113
+ ```typescript
1114
+ class SOLIDAnalyzer {
1115
+ async analyze(context: GatheredContext): Promise<SOLIDViolation[]> {
1116
+ const violations: SOLIDViolation[] = [];
1117
+
1118
+ // Single Responsibility Principle
1119
+ violations.push(...this.checkSRP(context.file.ast));
1120
+
1121
+ // Open/Closed Principle
1122
+ violations.push(...this.checkOCP(context.file.ast));
1123
+
1124
+ // Liskov Substitution Principle
1125
+ violations.push(...this.checkLSP(context.file.ast));
1126
+
1127
+ // Interface Segregation Principle
1128
+ violations.push(...this.checkISP(context.file.ast));
1129
+
1130
+ // Dependency Inversion Principle
1131
+ violations.push(...this.checkDIP(context.file.ast));
1132
+
1133
+ return violations;
1134
+ }
1135
+
1136
+ private checkSRP(ast: AST): SOLIDViolation[] {
1137
+ const violations: SOLIDViolation[] = [];
1138
+
1139
+ traverse(ast, {
1140
+ ClassDeclaration(path) {
1141
+ const cls = path.node;
1142
+ const methods = cls.body.body.filter(m => m.type === 'MethodDefinition');
1143
+
1144
+ // Cluster methods by functionality
1145
+ const clusters = clusterMethodsBySemantics(methods);
1146
+
1147
+ if (clusters.length > 2) {
1148
+ violations.push({
1149
+ principle: 'SRP',
1150
+ severity: 'warning',
1151
+ location: cls.loc,
1152
+ message: `Class ${cls.id?.name} has ${clusters.length} distinct responsibilities`,
1153
+ details: clusters.map(c => c.name),
1154
+ fix: `Split into ${clusters.length} focused classes`
1155
+ });
1156
+ }
1157
+ }
1158
+ });
1159
+
1160
+ return violations;
1161
+ }
1162
+ }
1163
+ ```
1164
+
1165
+ ### Framework Pattern Detection (React)
1166
+
1167
+ ```typescript
1168
+ class ReactPatternAnalyzer implements FrameworkAnalyzer {
1169
+ analyze(context: GatheredContext): FrameworkIssue[] {
1170
+ const issues: FrameworkIssue[] = [];
1171
+
1172
+ // Check hook rules
1173
+ issues.push(...this.checkHookRules(context.file.ast));
1174
+
1175
+ // Check for missing dependencies
1176
+ issues.push(...this.checkMissingDependencies(context.file.ast));
1177
+
1178
+ // Check for prop drilling
1179
+ issues.push(...this.checkPropDrilling(context.file.ast));
1180
+
1181
+ return issues;
1182
+ }
1183
+
1184
+ private checkHookRules(ast: AST): FrameworkIssue[] {
1185
+ const issues: FrameworkIssue[] = [];
1186
+
1187
+ traverse(ast, {
1188
+ CallExpression(path) {
1189
+ const callee = path.node.callee;
1190
+
1191
+ // Check if it's a hook call
1192
+ if (callee.type === 'Identifier' && callee.name.startsWith('use')) {
1193
+ // Rule 1: Only call hooks at top level
1194
+ if (this.isInsideConditional(path) || this.isInsideLoop(path)) {
1195
+ issues.push({
1196
+ pattern: 'hooks/rules-of-hooks',
1197
+ severity: 'error',
1198
+ location: path.node.loc,
1199
+ message: `Hook ${callee.name} called conditionally`,
1200
+ fix: 'Move hook call to top level of component'
1201
+ });
1202
+ }
1203
+
1204
+ // Rule 2: Only call hooks from React functions
1205
+ if (!this.isInsideReactComponent(path)) {
1206
+ issues.push({
1207
+ pattern: 'hooks/rules-of-hooks',
1208
+ severity: 'error',
1209
+ location: path.node.loc,
1210
+ message: `Hook ${callee.name} called outside React component`,
1211
+ fix: 'Only call hooks from React function components or custom hooks'
1212
+ });
1213
+ }
1214
+ }
1215
+ }
1216
+ });
1217
+
1218
+ return issues;
1219
+ }
1220
+
1221
+ private checkMissingDependencies(ast: AST): FrameworkIssue[] {
1222
+ const issues: FrameworkIssue[] = [];
1223
+
1224
+ traverse(ast, {
1225
+ CallExpression(path) {
1226
+ const callee = path.node.callee;
1227
+
1228
+ if (callee.type === 'Identifier' &&
1229
+ ['useEffect', 'useCallback', 'useMemo'].includes(callee.name)) {
1230
+
1231
+ const callback = path.node.arguments[0];
1232
+ const deps = path.node.arguments[1];
1233
+
1234
+ if (callback && deps?.type === 'ArrayExpression') {
1235
+ const usedVars = this.findUsedVariables(callback);
1236
+ const declaredDeps = deps.elements.map(e => e.name).filter(Boolean);
1237
+
1238
+ const missing = usedVars.filter(v =>
1239
+ !declaredDeps.includes(v) && !this.isStableReference(v)
1240
+ );
1241
+
1242
+ if (missing.length > 0) {
1243
+ issues.push({
1244
+ pattern: 'hooks/exhaustive-deps',
1245
+ severity: 'warning',
1246
+ location: deps.loc,
1247
+ message: `Missing dependencies: ${missing.join(', ')}`,
1248
+ fix: `Add [${missing.join(', ')}] to dependency array`
1249
+ });
1250
+ }
1251
+ }
1252
+ }
1253
+ }
1254
+ });
1255
+
1256
+ return issues;
1257
+ }
1258
+ }
1259
+ ```
1260
+
1261
+ ---
1262
+
1263
+ ## 5. DocumentationGenerator
1264
+
1265
+ Genera documentacion automatica para codigo.
1266
+
1267
+ ### Arquitectura
1268
+
1269
+ ```
1270
+ +----------------------------------------------------------+
1271
+ | DOCUMENTATION GENERATOR |
1272
+ +----------------------------------------------------------+
1273
+ | |
1274
+ | +---------------------------------------------------+ |
1275
+ | | CODE ANALYZER | |
1276
+ | | - Function signatures - Type extraction | |
1277
+ | | - Parameter analysis - Return type inference | |
1278
+ | +---------------------------------------------------+ |
1279
+ | | |
1280
+ | +--------------+---------------+ |
1281
+ | v v v |
1282
+ | +-------------+ +-------------+ +-------------+ |
1283
+ | | JSDoc/TSDoc | | README | | API Docs | |
1284
+ | | Generator | | Generator | | Generator | |
1285
+ | +-------------+ +-------------+ +-------------+ |
1286
+ | | | | |
1287
+ | v v v |
1288
+ | +---------------------------------------------------+ |
1289
+ | | CHANGELOG GENERATOR | |
1290
+ | +---------------------------------------------------+ |
1291
+ | |
1292
+ +----------------------------------------------------------+
1293
+ ```
1294
+
1295
+ ### JSDoc Generator
1296
+
1297
+ ```typescript
1298
+ class JSDocGenerator {
1299
+ private llm: LLMClient;
1300
+
1301
+ async generate(fn: FunctionNode, context: GatheredContext): Promise<string> {
1302
+ // Extract function information
1303
+ const info = this.extractFunctionInfo(fn);
1304
+
1305
+ // Use LLM to generate description
1306
+ const description = await this.generateDescription(fn, context);
1307
+
1308
+ // Build JSDoc
1309
+ const lines = ['/**'];
1310
+
1311
+ // Description
1312
+ lines.push(` * ${description}`);
1313
+ lines.push(' *');
1314
+
1315
+ // Parameters
1316
+ for (const param of info.params) {
1317
+ const paramDesc = await this.describeParameter(param, fn, context);
1318
+ lines.push(` * @param {${param.type}} ${param.name} - ${paramDesc}`);
1319
+ }
1320
+
1321
+ // Return type
1322
+ if (info.returnType && info.returnType !== 'void') {
1323
+ const returnDesc = await this.describeReturn(fn, context);
1324
+ lines.push(` * @returns {${info.returnType}} ${returnDesc}`);
1325
+ }
1326
+
1327
+ // Throws
1328
+ const throws = this.detectThrows(fn);
1329
+ for (const thrown of throws) {
1330
+ lines.push(` * @throws {${thrown.type}} ${thrown.description}`);
1331
+ }
1332
+
1333
+ // Examples
1334
+ if (info.complexity > 5) {
1335
+ const example = await this.generateExample(fn, context);
1336
+ lines.push(' *');
1337
+ lines.push(' * @example');
1338
+ for (const exLine of example.split('\n')) {
1339
+ lines.push(` * ${exLine}`);
1340
+ }
1341
+ }
1342
+
1343
+ lines.push(' */');
1344
+
1345
+ return lines.join('\n');
1346
+ }
1347
+
1348
+ private extractFunctionInfo(fn: FunctionNode): FunctionInfo {
1349
+ const params = fn.params.map(p => ({
1350
+ name: this.getParamName(p),
1351
+ type: this.getParamType(p),
1352
+ optional: p.optional || false,
1353
+ defaultValue: p.default ? this.getDefaultValue(p.default) : undefined
1354
+ }));
1355
+
1356
+ return {
1357
+ name: fn.id?.name || 'anonymous',
1358
+ params,
1359
+ returnType: this.inferReturnType(fn),
1360
+ isAsync: fn.async,
1361
+ isGenerator: fn.generator,
1362
+ complexity: this.computeComplexity(fn)
1363
+ };
1364
+ }
1365
+ }
1366
+ ```
1367
+
1368
+ ### README Generator
1369
+
1370
+ ```typescript
1371
+ class ReadmeGenerator {
1372
+ private llm: LLMClient;
1373
+
1374
+ async generate(projectContext: ProjectContext): Promise<string> {
1375
+ const sections = await Promise.all([
1376
+ this.generateTitle(projectContext),
1377
+ this.generateDescription(projectContext),
1378
+ this.generateInstallation(projectContext),
1379
+ this.generateUsage(projectContext),
1380
+ this.generateAPI(projectContext),
1381
+ this.generateContributing(projectContext),
1382
+ this.generateLicense(projectContext)
1383
+ ]);
1384
+
1385
+ return sections.join('\n\n');
1386
+ }
1387
+
1388
+ private async generateTitle(ctx: ProjectContext): Promise<string> {
1389
+ const name = ctx.packageJson?.name || path.basename(ctx.rootDir);
1390
+ const badges = this.generateBadges(ctx);
1391
+
1392
+ return `# ${name}\n\n${badges}`;
1393
+ }
1394
+
1395
+ private generateBadges(ctx: ProjectContext): string {
1396
+ const badges: string[] = [];
1397
+
1398
+ if (ctx.packageJson?.version) {
1399
+ badges.push(`![Version](https://img.shields.io/badge/version-${ctx.packageJson.version}-blue)`);
1400
+ }
1401
+
1402
+ if (ctx.hasTests) {
1403
+ badges.push('![Tests](https://img.shields.io/badge/tests-passing-green)');
1404
+ }
1405
+
1406
+ if (ctx.packageJson?.license) {
1407
+ badges.push(`![License](https://img.shields.io/badge/license-${ctx.packageJson.license}-blue)`);
1408
+ }
1409
+
1410
+ return badges.join(' ');
1411
+ }
1412
+ }
1413
+ ```
1414
+
1415
+ ### Changelog Generator
1416
+
1417
+ ```typescript
1418
+ class ChangelogGenerator {
1419
+ async generate(commits: Commit[], previousVersion: string): Promise<string> {
1420
+ // Categorize commits
1421
+ const categories = this.categorizeCommits(commits);
1422
+
1423
+ const lines = [
1424
+ `## [${this.nextVersion(previousVersion, categories)}] - ${this.today()}`,
1425
+ ''
1426
+ ];
1427
+
1428
+ // Breaking changes first
1429
+ if (categories.breaking.length > 0) {
1430
+ lines.push('### BREAKING CHANGES');
1431
+ lines.push('');
1432
+ for (const commit of categories.breaking) {
1433
+ lines.push(`- ${commit.message}`);
1434
+ }
1435
+ lines.push('');
1436
+ }
1437
+
1438
+ // Features
1439
+ if (categories.features.length > 0) {
1440
+ lines.push('### Added');
1441
+ lines.push('');
1442
+ for (const commit of categories.features) {
1443
+ lines.push(`- ${commit.message}`);
1444
+ }
1445
+ lines.push('');
1446
+ }
1447
+
1448
+ // Fixes
1449
+ if (categories.fixes.length > 0) {
1450
+ lines.push('### Fixed');
1451
+ lines.push('');
1452
+ for (const commit of categories.fixes) {
1453
+ lines.push(`- ${commit.message}`);
1454
+ }
1455
+ lines.push('');
1456
+ }
1457
+
1458
+ return lines.join('\n');
1459
+ }
1460
+
1461
+ private categorizeCommits(commits: Commit[]): CategorizedCommits {
1462
+ return {
1463
+ breaking: commits.filter(c =>
1464
+ c.message.includes('BREAKING') || c.message.includes('!')
1465
+ ),
1466
+ features: commits.filter(c =>
1467
+ c.message.startsWith('feat') || c.message.startsWith('add')
1468
+ ),
1469
+ fixes: commits.filter(c =>
1470
+ c.message.startsWith('fix') || c.message.startsWith('bug')
1471
+ ),
1472
+ other: commits.filter(c =>
1473
+ !c.message.startsWith('feat') &&
1474
+ !c.message.startsWith('fix') &&
1475
+ !c.message.includes('BREAKING')
1476
+ )
1477
+ };
1478
+ }
1479
+ }
1480
+ ```
1481
+
1482
+ ---
1483
+
1484
+ ## 6. TestSuggester
1485
+
1486
+ Sugiere tests unitarios y de integracion.
1487
+
1488
+ ### Arquitectura
1489
+
1490
+ ```
1491
+ +----------------------------------------------------------+
1492
+ | TEST SUGGESTER |
1493
+ +----------------------------------------------------------+
1494
+ | |
1495
+ | +---------------------------------------------------+ |
1496
+ | | FUNCTION ANALYZER | |
1497
+ | | - Input analysis - Output analysis | |
1498
+ | | - Branch detection - Edge case identification | |
1499
+ | +---------------------------------------------------+ |
1500
+ | | |
1501
+ | +--------------+---------------+ |
1502
+ | v v v |
1503
+ | +-------------+ +-------------+ +-------------+ |
1504
+ | | Unit Test | | Edge Case | | Integration | |
1505
+ | | Generator | | Generator | | Generator | |
1506
+ | +-------------+ +-------------+ +-------------+ |
1507
+ | | |
1508
+ | v |
1509
+ | +---------------------------------------------------+ |
1510
+ | | MOCK GENERATOR | |
1511
+ | +---------------------------------------------------+ |
1512
+ | |
1513
+ +----------------------------------------------------------+
1514
+ ```
1515
+
1516
+ ### Unit Test Generator
1517
+
1518
+ ```typescript
1519
+ class UnitTestGenerator {
1520
+ private llm: LLMClient;
1521
+
1522
+ async generate(fn: FunctionNode, context: GatheredContext): Promise<GeneratedTests> {
1523
+ // Analyze function
1524
+ const analysis = this.analyzeFunction(fn);
1525
+
1526
+ // Generate test cases
1527
+ const testCases = await this.generateTestCases(fn, analysis, context);
1528
+
1529
+ // Generate mocks if needed
1530
+ const mocks = await this.generateMocks(fn, context);
1531
+
1532
+ // Build test file
1533
+ const code = this.buildTestCode(fn, testCases, mocks, context);
1534
+
1535
+ return { code, cases: testCases, mocks };
1536
+ }
1537
+
1538
+ private analyzeFunction(fn: FunctionNode): FunctionAnalysis {
1539
+ return {
1540
+ params: this.extractParams(fn),
1541
+ returnType: this.inferReturnType(fn),
1542
+ branches: this.countBranches(fn),
1543
+ dependencies: this.extractDependencies(fn),
1544
+ sideEffects: this.detectSideEffects(fn),
1545
+ purity: this.assessPurity(fn)
1546
+ };
1547
+ }
1548
+
1549
+ private buildTestCode(
1550
+ fn: FunctionNode,
1551
+ cases: TestCase[],
1552
+ mocks: Mock[],
1553
+ context: GatheredContext
1554
+ ): string {
1555
+ const imports = this.generateImports(fn, context);
1556
+ const mockSetup = this.generateMockSetup(mocks);
1557
+ const testSuite = this.generateTestSuite(fn, cases);
1558
+
1559
+ return `
1560
+ ${imports}
1561
+
1562
+ ${mockSetup}
1563
+
1564
+ describe('${fn.name}', () => {
1565
+ ${testSuite}
1566
+ });
1567
+ `.trim();
1568
+ }
1569
+
1570
+ private generateTestSuite(fn: FunctionNode, cases: TestCase[]): string {
1571
+ return cases.map(tc => `
1572
+ ${tc.type === 'error' ? 'it' : 'it'}('${tc.description}', ${tc.async ? 'async ' : ''}() => {
1573
+ // Arrange
1574
+ ${tc.arrange.join('\n ')}
1575
+
1576
+ // Act
1577
+ ${tc.act}
1578
+
1579
+ // Assert
1580
+ ${tc.assertions.join('\n ')}
1581
+ });
1582
+ `).join('\n');
1583
+ }
1584
+ }
1585
+ ```
1586
+
1587
+ ### Edge Case Generator
1588
+
1589
+ ```typescript
1590
+ class EdgeCaseGenerator {
1591
+ async identify(fn: FunctionNode, context: GatheredContext): Promise<EdgeCase[]> {
1592
+ const edgeCases: EdgeCase[] = [];
1593
+ const params = this.extractParams(fn);
1594
+
1595
+ for (const param of params) {
1596
+ // Type-specific edge cases
1597
+ switch (param.type) {
1598
+ case 'string':
1599
+ edgeCases.push(
1600
+ { param: param.name, value: '""', description: 'Empty string' },
1601
+ { param: param.name, value: '" "', description: 'Whitespace only' },
1602
+ { param: param.name, value: '"a".repeat(10000)', description: 'Very long string' }
1603
+ );
1604
+ break;
1605
+
1606
+ case 'number':
1607
+ edgeCases.push(
1608
+ { param: param.name, value: '0', description: 'Zero' },
1609
+ { param: param.name, value: '-1', description: 'Negative number' },
1610
+ { param: param.name, value: 'Number.MAX_SAFE_INTEGER', description: 'Max safe integer' },
1611
+ { param: param.name, value: 'NaN', description: 'NaN value' },
1612
+ { param: param.name, value: 'Infinity', description: 'Infinity' }
1613
+ );
1614
+ break;
1615
+
1616
+ case 'array':
1617
+ edgeCases.push(
1618
+ { param: param.name, value: '[]', description: 'Empty array' },
1619
+ { param: param.name, value: '[null]', description: 'Array with null' },
1620
+ { param: param.name, value: 'new Array(10000).fill(1)', description: 'Large array' }
1621
+ );
1622
+ break;
1623
+
1624
+ case 'object':
1625
+ edgeCases.push(
1626
+ { param: param.name, value: '{}', description: 'Empty object' },
1627
+ { param: param.name, value: 'null', description: 'Null value' },
1628
+ { param: param.name, value: 'Object.create(null)', description: 'Object without prototype' }
1629
+ );
1630
+ break;
1631
+ }
1632
+ }
1633
+
1634
+ return edgeCases;
1635
+ }
1636
+ }
1637
+ ```
1638
+
1639
+ ### Mock Generator
1640
+
1641
+ ```typescript
1642
+ class MockGenerator {
1643
+ async generate(fn: FunctionNode, context: GatheredContext): Promise<Mock[]> {
1644
+ const mocks: Mock[] = [];
1645
+ const dependencies = this.extractDependencies(fn, context);
1646
+
1647
+ for (const dep of dependencies) {
1648
+ const mock = await this.createMock(dep, context);
1649
+ mocks.push(mock);
1650
+ }
1651
+
1652
+ return mocks;
1653
+ }
1654
+
1655
+ private generateMockSetup(dep: Dependency, methods: Method[]): string {
1656
+ if (dep.type === 'module') {
1657
+ return `
1658
+ jest.mock('${dep.path}', () => ({
1659
+ ${methods.map(m => `${m.name}: jest.fn()`).join(',\n ')}
1660
+ }));
1661
+ `.trim();
1662
+ }
1663
+
1664
+ return `
1665
+ const mock${dep.name} = {
1666
+ ${methods.map(m => `${m.name}: jest.fn()`).join(',\n ')}
1667
+ };
1668
+ `.trim();
1669
+ }
1670
+ }
1671
+ ```
1672
+
1673
+ ---
1674
+
1675
+ ## 7. SecurityScanner
1676
+
1677
+ Escanea codigo en busca de vulnerabilidades de seguridad.
1678
+
1679
+ ### Arquitectura
1680
+
1681
+ ```
1682
+ +----------------------------------------------------------+
1683
+ | SECURITY SCANNER |
1684
+ +----------------------------------------------------------+
1685
+ | |
1686
+ | +---------------------------------------------------+ |
1687
+ | | OWASP TOP 10 | |
1688
+ | | A01: Broken Access Control | |
1689
+ | | A02: Cryptographic Failures | |
1690
+ | | A03: Injection | |
1691
+ | | A07: XSS | |
1692
+ | +---------------------------------------------------+ |
1693
+ | | |
1694
+ | +---------------------------------------------------+ |
1695
+ | | SECRETS DETECTION | |
1696
+ | | - API keys - Passwords | |
1697
+ | | - Tokens - Private keys | |
1698
+ | +---------------------------------------------------+ |
1699
+ | | |
1700
+ | +---------------------------------------------------+ |
1701
+ | | DEPENDENCY VULNERABILITIES | |
1702
+ | | - CVE database - Version checking | |
1703
+ | | - Severity levels - Upgrade paths | |
1704
+ | +---------------------------------------------------+ |
1705
+ | |
1706
+ +----------------------------------------------------------+
1707
+ ```
1708
+
1709
+ ### OWASP Scanner
1710
+
1711
+ ```typescript
1712
+ class OWASPScanner {
1713
+ private rules: SecurityRule[] = [
1714
+ // A03: Injection
1715
+ {
1716
+ id: 'A03-SQL-INJECTION',
1717
+ category: 'injection',
1718
+ severity: 'critical',
1719
+ pattern: /\.(query|execute)\s*\(\s*[`'"].*\$\{/,
1720
+ message: 'Potential SQL injection - use parameterized queries',
1721
+ fix: 'Use prepared statements with parameter binding'
1722
+ },
1723
+ {
1724
+ id: 'A03-COMMAND-INJECTION',
1725
+ category: 'injection',
1726
+ severity: 'critical',
1727
+ detect: (ast) => this.detectCommandInjection(ast),
1728
+ message: 'Potential command injection',
1729
+ fix: 'Validate and sanitize user input, use safe APIs'
1730
+ },
1731
+ // A07: XSS
1732
+ {
1733
+ id: 'A07-XSS-INNERHTML',
1734
+ category: 'xss',
1735
+ severity: 'high',
1736
+ pattern: /\.innerHTML\s*=(?!\s*['"`])/,
1737
+ message: 'Potential XSS via innerHTML',
1738
+ fix: 'Use textContent or sanitize HTML input'
1739
+ },
1740
+ // A02: Cryptographic Failures
1741
+ {
1742
+ id: 'A02-WEAK-CRYPTO',
1743
+ category: 'crypto',
1744
+ severity: 'high',
1745
+ pattern: /crypto\.createHash\(['"]md5['"]\)|crypto\.createHash\(['"]sha1['"]\)/,
1746
+ message: 'Weak cryptographic algorithm',
1747
+ fix: 'Use SHA-256 or stronger algorithms'
1748
+ }
1749
+ ];
1750
+
1751
+ async scan(context: GatheredContext): Promise<SecurityIssue[]> {
1752
+ const issues: SecurityIssue[] = [];
1753
+
1754
+ for (const rule of this.rules) {
1755
+ let matches: Match[] = [];
1756
+
1757
+ if (rule.pattern) {
1758
+ matches = this.scanWithPattern(context.file.content, rule.pattern);
1759
+ } else if (rule.detect) {
1760
+ matches = rule.detect(context.file.ast);
1761
+ }
1762
+
1763
+ for (const match of matches) {
1764
+ issues.push({
1765
+ rule: rule.id,
1766
+ category: rule.category,
1767
+ severity: rule.severity,
1768
+ location: match.location,
1769
+ message: rule.message,
1770
+ fix: rule.fix,
1771
+ cwe: this.getCWE(rule.id),
1772
+ owasp: this.getOWASPCategory(rule.category)
1773
+ });
1774
+ }
1775
+ }
1776
+
1777
+ return issues;
1778
+ }
1779
+ }
1780
+ ```
1781
+
1782
+ ### Secrets Detector
1783
+
1784
+ ```typescript
1785
+ class SecretsDetector {
1786
+ private patterns: SecretPattern[] = [
1787
+ {
1788
+ name: 'AWS Access Key',
1789
+ pattern: /AKIA[0-9A-Z]{16}/,
1790
+ severity: 'critical'
1791
+ },
1792
+ {
1793
+ name: 'AWS Secret Key',
1794
+ pattern: /[A-Za-z0-9/+=]{40}/,
1795
+ context: /aws|secret|key/i,
1796
+ severity: 'critical'
1797
+ },
1798
+ {
1799
+ name: 'GitHub Token',
1800
+ pattern: /ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9]{22}_[A-Za-z0-9]{59}/,
1801
+ severity: 'critical'
1802
+ },
1803
+ {
1804
+ name: 'Generic API Key',
1805
+ pattern: /api[_-]?key\s*[:=]\s*['"][A-Za-z0-9]{20,}['"]/i,
1806
+ severity: 'high'
1807
+ },
1808
+ {
1809
+ name: 'Private Key',
1810
+ pattern: /-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/,
1811
+ severity: 'critical'
1812
+ },
1813
+ {
1814
+ name: 'Password in Code',
1815
+ pattern: /password\s*[:=]\s*['"][^'"]{8,}['"]/i,
1816
+ severity: 'high'
1817
+ }
1818
+ ];
1819
+
1820
+ async detect(context: GatheredContext): Promise<DetectedSecret[]> {
1821
+ const secrets: DetectedSecret[] = [];
1822
+ const lines = context.file.content.split('\n');
1823
+
1824
+ for (let i = 0; i < lines.length; i++) {
1825
+ const line = lines[i];
1826
+
1827
+ // Skip comments
1828
+ if (this.isComment(line)) continue;
1829
+
1830
+ for (const pattern of this.patterns) {
1831
+ const match = line.match(pattern.pattern);
1832
+
1833
+ if (match) {
1834
+ // Check context if required
1835
+ if (pattern.context && !pattern.context.test(line)) continue;
1836
+
1837
+ secrets.push({
1838
+ type: pattern.name,
1839
+ severity: pattern.severity,
1840
+ location: {
1841
+ line: i + 1,
1842
+ column: match.index!,
1843
+ length: match[0].length
1844
+ },
1845
+ masked: this.maskSecret(match[0]),
1846
+ fix: this.generateFix(pattern.name)
1847
+ });
1848
+ }
1849
+ }
1850
+ }
1851
+
1852
+ return secrets;
1853
+ }
1854
+
1855
+ private maskSecret(secret: string): string {
1856
+ if (secret.length <= 8) return '*'.repeat(secret.length);
1857
+ return secret.slice(0, 4) + '*'.repeat(secret.length - 8) + secret.slice(-4);
1858
+ }
1859
+
1860
+ private generateFix(secretType: string): string {
1861
+ const fixes: Record<string, string> = {
1862
+ 'AWS Access Key': 'Use environment variables: process.env.AWS_ACCESS_KEY_ID',
1863
+ 'AWS Secret Key': 'Use environment variables: process.env.AWS_SECRET_ACCESS_KEY',
1864
+ 'GitHub Token': 'Use GitHub Secrets or environment variables',
1865
+ 'Generic API Key': 'Store in .env file and add to .gitignore',
1866
+ 'Private Key': 'Store in secure vault (AWS Secrets Manager, HashiCorp Vault)',
1867
+ 'Password in Code': 'Use environment variables or secrets manager'
1868
+ };
1869
+
1870
+ return fixes[secretType] || 'Remove secret and use secure storage';
1871
+ }
1872
+ }
1873
+ ```
1874
+
1875
+ ### Dependency Scanner
1876
+
1877
+ ```typescript
1878
+ class DependencyScanner {
1879
+ private vulnDatabase: VulnerabilityDatabase;
1880
+
1881
+ async scan(context: GatheredContext): Promise<DependencyVulnerability[]> {
1882
+ const vulnerabilities: DependencyVulnerability[] = [];
1883
+
1884
+ // Read package.json
1885
+ const packageJson = await this.readPackageJson(context);
1886
+ if (!packageJson) return [];
1887
+
1888
+ const allDeps = {
1889
+ ...packageJson.dependencies,
1890
+ ...packageJson.devDependencies
1891
+ };
1892
+
1893
+ for (const [name, version] of Object.entries(allDeps)) {
1894
+ const vulns = await this.vulnDatabase.query(name, version as string);
1895
+
1896
+ for (const vuln of vulns) {
1897
+ vulnerabilities.push({
1898
+ package: name,
1899
+ installedVersion: version as string,
1900
+ vulnerability: {
1901
+ id: vuln.id,
1902
+ severity: vuln.severity,
1903
+ title: vuln.title,
1904
+ description: vuln.description,
1905
+ cve: vuln.cve,
1906
+ cvss: vuln.cvss
1907
+ },
1908
+ fix: {
1909
+ recommendedVersion: vuln.patchedVersion,
1910
+ breaking: this.isBreakingChange(version as string, vuln.patchedVersion),
1911
+ command: `npm update ${name}@${vuln.patchedVersion}`
1912
+ }
1913
+ });
1914
+ }
1915
+ }
1916
+
1917
+ return vulnerabilities.sort((a, b) =>
1918
+ this.severityScore(b.vulnerability.severity) -
1919
+ this.severityScore(a.vulnerability.severity)
1920
+ );
1921
+ }
1922
+
1923
+ private severityScore(severity: string): number {
1924
+ const scores: Record<string, number> = {
1925
+ critical: 4,
1926
+ high: 3,
1927
+ medium: 2,
1928
+ low: 1
1929
+ };
1930
+ return scores[severity] || 0;
1931
+ }
1932
+ }
1933
+ ```
1934
+
1935
+ ---
1936
+
1937
+ ## 8. PerformanceAdvisor
1938
+
1939
+ Detecta problemas de rendimiento y sugiere optimizaciones.
1940
+
1941
+ ### Arquitectura
1942
+
1943
+ ```
1944
+ +----------------------------------------------------------+
1945
+ | PERFORMANCE ADVISOR |
1946
+ +----------------------------------------------------------+
1947
+ | |
1948
+ | +---------------------------------------------------+ |
1949
+ | | N+1 QUERY DETECTOR | |
1950
+ | | - Loop analysis - Query tracking | |
1951
+ | | - Batch suggestions - Eager loading | |
1952
+ | +---------------------------------------------------+ |
1953
+ | |
1954
+ | +---------------------------------------------------+ |
1955
+ | | MEMORY LEAK DETECTOR | |
1956
+ | | - Event listeners - Closures | |
1957
+ | | - Uncleared timers - Growing collections | |
1958
+ | +---------------------------------------------------+ |
1959
+ | |
1960
+ | +---------------------------------------------------+ |
1961
+ | | BUNDLE SIZE ANALYZER | |
1962
+ | | - Import analysis - Tree shaking | |
1963
+ | | - Dynamic imports - Code splitting | |
1964
+ | +---------------------------------------------------+ |
1965
+ | |
1966
+ | +---------------------------------------------------+ |
1967
+ | | LAZY LOADING OPPORTUNITIES | |
1968
+ | | - Route analysis - Component analysis | |
1969
+ | | - Heavy modules - Conditional features | |
1970
+ | +---------------------------------------------------+ |
1971
+ | |
1972
+ +----------------------------------------------------------+
1973
+ ```
1974
+
1975
+ ### N+1 Query Detector
1976
+
1977
+ ```typescript
1978
+ class N1QueryDetector {
1979
+ async detect(context: GatheredContext): Promise<N1Issue[]> {
1980
+ const issues: N1Issue[] = [];
1981
+
1982
+ traverse(context.file.ast, {
1983
+ // Check for queries inside loops
1984
+ ForStatement: (path) => this.checkLoopForQueries(path, issues),
1985
+ ForOfStatement: (path) => this.checkLoopForQueries(path, issues),
1986
+ ForInStatement: (path) => this.checkLoopForQueries(path, issues),
1987
+ WhileStatement: (path) => this.checkLoopForQueries(path, issues),
1988
+
1989
+ // Check for queries inside map/forEach
1990
+ CallExpression(path) {
1991
+ const callee = path.node.callee;
1992
+
1993
+ if (callee.type === 'MemberExpression' &&
1994
+ ['map', 'forEach', 'filter', 'reduce'].includes(callee.property.name)) {
1995
+
1996
+ const callback = path.node.arguments[0];
1997
+ if (callback) {
1998
+ const queries = this.findQueriesInNode(callback);
1999
+
2000
+ if (queries.length > 0) {
2001
+ issues.push({
2002
+ type: 'n1-in-iterator',
2003
+ location: path.node.loc,
2004
+ iteratorMethod: callee.property.name,
2005
+ queries,
2006
+ estimatedCalls: 'N times per iteration',
2007
+ fix: this.generateBatchFix(queries, path)
2008
+ });
2009
+ }
2010
+ }
2011
+ }
2012
+ }
2013
+ });
2014
+
2015
+ return issues;
2016
+ }
2017
+
2018
+ private generateBatchFix(queries: QueryCall[], loopPath: NodePath): string {
2019
+ const orm = queries[0]?.orm || 'unknown';
2020
+
2021
+ const fixes: Record<string, string> = {
2022
+ prisma: `
2023
+ // Instead of querying in a loop:
2024
+ // for (const id of ids) { await prisma.user.findUnique({ where: { id } }) }
2025
+
2026
+ // Use batch query:
2027
+ const users = await prisma.user.findMany({
2028
+ where: { id: { in: ids } }
2029
+ });
2030
+ `,
2031
+ mongoose: `
2032
+ // Instead of querying in a loop, use $in operator:
2033
+ const users = await User.find({ _id: { $in: ids } });
2034
+ `
2035
+ };
2036
+
2037
+ return fixes[orm] || 'Batch queries together to reduce database round trips';
2038
+ }
2039
+ }
2040
+ ```
2041
+
2042
+ ### Memory Leak Detector
2043
+
2044
+ ```typescript
2045
+ class MemoryLeakDetector {
2046
+ async detect(context: GatheredContext): Promise<MemoryLeakIssue[]> {
2047
+ const issues: MemoryLeakIssue[] = [];
2048
+
2049
+ // Check for unremoved event listeners
2050
+ issues.push(...this.detectUnremovedListeners(context.file.ast));
2051
+
2052
+ // Check for uncleared intervals/timeouts
2053
+ issues.push(...this.detectUnclearedTimers(context.file.ast));
2054
+
2055
+ // Check for closures holding references
2056
+ issues.push(...this.detectClosureLeaks(context.file.ast));
2057
+
2058
+ return issues;
2059
+ }
2060
+
2061
+ private detectUnclearedTimers(ast: AST): MemoryLeakIssue[] {
2062
+ const issues: MemoryLeakIssue[] = [];
2063
+ const setTimers: Map<string, NodePath> = new Map();
2064
+ const clearedTimers: Set<string> = new Set();
2065
+
2066
+ traverse(ast, {
2067
+ VariableDeclarator(path) {
2068
+ const init = path.node.init;
2069
+
2070
+ if (init?.type === 'CallExpression') {
2071
+ const callee = init.callee;
2072
+
2073
+ if (callee.type === 'Identifier' &&
2074
+ ['setInterval', 'setTimeout'].includes(callee.name)) {
2075
+
2076
+ const varName = path.node.id.name;
2077
+ setTimers.set(varName, path);
2078
+ }
2079
+ }
2080
+ },
2081
+ CallExpression(path) {
2082
+ const callee = path.node.callee;
2083
+
2084
+ if (callee.type === 'Identifier' &&
2085
+ ['clearInterval', 'clearTimeout'].includes(callee.name)) {
2086
+
2087
+ const arg = path.node.arguments[0];
2088
+ if (arg?.type === 'Identifier') {
2089
+ clearedTimers.add(arg.name);
2090
+ }
2091
+ }
2092
+ }
2093
+ });
2094
+
2095
+ // Find timers that are set but never cleared
2096
+ for (const [varName, path] of setTimers) {
2097
+ const init = path.node.init;
2098
+ const isInterval = init.callee.name === 'setInterval';
2099
+
2100
+ if (isInterval && !clearedTimers.has(varName)) {
2101
+ issues.push({
2102
+ type: 'uncleared-interval',
2103
+ severity: 'error',
2104
+ location: path.node.loc,
2105
+ message: 'setInterval never cleared - will cause memory leak',
2106
+ fix: `Add cleanup: clearInterval(${varName})`
2107
+ });
2108
+ }
2109
+ }
2110
+
2111
+ return issues;
2112
+ }
2113
+ }
2114
+ ```
2115
+
2116
+ ### Bundle Size Analyzer
2117
+
2118
+ ```typescript
2119
+ class BundleSizeAnalyzer {
2120
+ private heavyPackages: Map<string, number> = new Map([
2121
+ ['moment', 290],
2122
+ ['lodash', 70],
2123
+ ['rxjs', 150],
2124
+ ['@material-ui/core', 300],
2125
+ ['antd', 400],
2126
+ ['chart.js', 200]
2127
+ ]);
2128
+
2129
+ async analyze(context: GatheredContext): Promise<BundleIssue[]> {
2130
+ const issues: BundleIssue[] = [];
2131
+ const imports = this.extractImports(context.file.ast);
2132
+
2133
+ for (const imp of imports) {
2134
+ // Check for heavy packages
2135
+ if (this.heavyPackages.has(imp.source)) {
2136
+ const size = this.heavyPackages.get(imp.source)!;
2137
+
2138
+ issues.push({
2139
+ type: 'heavy-import',
2140
+ severity: size > 200 ? 'warning' : 'info',
2141
+ location: imp.location,
2142
+ package: imp.source,
2143
+ estimatedSize: `~${size}KB`,
2144
+ fix: this.suggestLighterAlternative(imp.source)
2145
+ });
2146
+ }
2147
+ }
2148
+
2149
+ return issues;
2150
+ }
2151
+
2152
+ private suggestLighterAlternative(pkg: string): string {
2153
+ const alternatives: Record<string, string> = {
2154
+ 'moment': 'Use date-fns (~20KB) or dayjs (~2KB)',
2155
+ 'lodash': 'Use lodash-es with tree shaking or native methods',
2156
+ 'rxjs': 'Import specific operators: import { map } from "rxjs/operators"',
2157
+ '@material-ui/core': 'Use specific imports: import Button from "@material-ui/core/Button"',
2158
+ 'antd': 'Use babel-plugin-import for automatic tree shaking',
2159
+ 'chart.js': 'Use specific chart types: import { LineChart } from "chart.js"'
2160
+ };
2161
+
2162
+ return alternatives[pkg] || 'Consider lighter alternatives or code splitting';
2163
+ }
2164
+ }
2165
+ ```
2166
+
2167
+ ### Lazy Loading Advisor
2168
+
2169
+ ```typescript
2170
+ class LazyLoadingAdvisor {
2171
+ async identify(context: GatheredContext): Promise<LazyLoadingOpportunity[]> {
2172
+ const opportunities: LazyLoadingOpportunity[] = [];
2173
+
2174
+ // Check for route-level code splitting
2175
+ opportunities.push(...this.identifyRouteSplitting(context));
2176
+
2177
+ // Check for conditionally rendered components
2178
+ opportunities.push(...this.identifyConditionalComponents(context));
2179
+
2180
+ // Check for heavy imports
2181
+ opportunities.push(...this.identifyHeavyImports(context));
2182
+
2183
+ return opportunities;
2184
+ }
2185
+
2186
+ private identifyRouteSplitting(context: GatheredContext): LazyLoadingOpportunity[] {
2187
+ const opportunities: LazyLoadingOpportunity[] = [];
2188
+
2189
+ traverse(context.file.ast, {
2190
+ JSXElement(path) {
2191
+ const element = path.node.openingElement;
2192
+
2193
+ // Check for React Router routes
2194
+ if (element.name.name === 'Route') {
2195
+ const componentProp = element.attributes.find(
2196
+ a => a.name?.name === 'component' || a.name?.name === 'element'
2197
+ );
2198
+
2199
+ if (componentProp && !this.isLazyImport(componentProp)) {
2200
+ opportunities.push({
2201
+ type: 'route-splitting',
2202
+ location: path.node.loc,
2203
+ component: this.getComponentName(componentProp),
2204
+ impact: 'high',
2205
+ suggestion: `
2206
+ // Use React.lazy for route-level code splitting:
2207
+ const MyComponent = React.lazy(() => import('./MyComponent'));
2208
+
2209
+ // In your route:
2210
+ <Route path="/path" element={
2211
+ <Suspense fallback={<Loading />}>
2212
+ <MyComponent />
2213
+ </Suspense>
2214
+ } />
2215
+ `.trim()
2216
+ });
2217
+ }
2218
+ }
2219
+ }
2220
+ });
2221
+
2222
+ return opportunities;
2223
+ }
2224
+ }
2225
+ ```
2226
+
2227
+ ---
2228
+
2229
+ ## 9. Comandos
2230
+
2231
+ Comandos disponibles para el sistema de sugerencias.
2232
+
2233
+ ### /elsabro:suggest
2234
+
2235
+ ```
2236
+ /elsabro:suggest <subcommand> [options]
2237
+
2238
+ Subcommands:
2239
+ enable Enable AI suggestions
2240
+ disable Disable AI suggestions
2241
+ settings Configure suggestion settings
2242
+ stats Show suggestion statistics
2243
+
2244
+ Options:
2245
+ --completion Toggle completion suggestions
2246
+ --refactor Toggle refactoring suggestions
2247
+ --patterns Toggle pattern detection
2248
+ --docs Toggle documentation generation
2249
+ --tests Toggle test suggestions
2250
+ --security Toggle security scanning
2251
+ --performance Toggle performance analysis
2252
+ ```
2253
+
2254
+ ### Ejemplos de Uso
2255
+
2256
+ ```bash
2257
+ # Enable all suggestions
2258
+ /elsabro:suggest enable
2259
+
2260
+ # Disable only security scanning
2261
+ /elsabro:suggest settings --security=false
2262
+
2263
+ # View statistics
2264
+ /elsabro:suggest stats
2265
+
2266
+ # Configure completion settings
2267
+ /elsabro:suggest settings --completion.debounce=200 --completion.maxSuggestions=3
2268
+ ```
2269
+
2270
+ ### Implementacion de Comandos
2271
+
2272
+ ```typescript
2273
+ class SuggestCommand implements ElsabroCommand {
2274
+ name = 'suggest';
2275
+ description = 'AI Code Suggestions management';
2276
+
2277
+ async execute(args: string[], context: CommandContext): Promise<void> {
2278
+ const [subcommand, ...options] = args;
2279
+
2280
+ switch (subcommand) {
2281
+ case 'enable':
2282
+ await this.enable(context);
2283
+ break;
2284
+ case 'disable':
2285
+ await this.disable(context);
2286
+ break;
2287
+ case 'settings':
2288
+ await this.settings(options, context);
2289
+ break;
2290
+ case 'stats':
2291
+ await this.stats(context);
2292
+ break;
2293
+ default:
2294
+ this.showHelp();
2295
+ }
2296
+ }
2297
+
2298
+ private async enable(context: CommandContext): Promise<void> {
2299
+ const config = await context.loadConfig('ai-suggestions-config.json');
2300
+ config.aiCodeSuggestions.enabled = true;
2301
+ await context.saveConfig(config);
2302
+
2303
+ context.output.success('AI Code Suggestions enabled');
2304
+ context.output.info('Providers active: completion, refactoring, patterns, docs, tests, security, performance');
2305
+ }
2306
+
2307
+ private async stats(context: CommandContext): Promise<void> {
2308
+ const stats = await context.telemetry.getSuggestionStats();
2309
+
2310
+ context.output.table([
2311
+ ['Metric', 'Value'],
2312
+ ['Total Suggestions', stats.total.toString()],
2313
+ ['Accepted', `${stats.accepted} (${(stats.accepted/stats.total*100).toFixed(1)}%)`],
2314
+ ['Avg Latency', `${stats.avgLatency}ms`],
2315
+ ['By Type', ''],
2316
+ [' Completion', stats.byType.completion.toString()],
2317
+ [' Refactoring', stats.byType.refactoring.toString()],
2318
+ [' Patterns', stats.byType.patterns.toString()],
2319
+ [' Documentation', stats.byType.documentation.toString()],
2320
+ [' Tests', stats.byType.tests.toString()],
2321
+ [' Security', stats.byType.security.toString()],
2322
+ [' Performance', stats.byType.performance.toString()]
2323
+ ]);
2324
+ }
2325
+ }
2326
+ ```
2327
+
2328
+ ---
2329
+
2330
+ ## Configuracion
2331
+
2332
+ Ver archivo de configuracion: `templates/ai-suggestions-config.json`
2333
+
2334
+ ### Variables de Entorno
2335
+
2336
+ ```bash
2337
+ # Model configuration
2338
+ ELSABRO_SUGGESTION_MODEL=claude-3-sonnet
2339
+ ELSABRO_SUGGESTION_FALLBACK=claude-3-haiku
2340
+
2341
+ # Performance tuning
2342
+ ELSABRO_SUGGESTION_TIMEOUT=5000
2343
+ ELSABRO_SUGGESTION_CACHE_TTL=3600
2344
+
2345
+ # Feature flags
2346
+ ELSABRO_SUGGESTION_SECURITY=true
2347
+ ELSABRO_SUGGESTION_PERFORMANCE=true
2348
+ ```
2349
+
2350
+ ---
2351
+
2352
+ ## Metricas y Telemetria
2353
+
2354
+ ```typescript
2355
+ interface SuggestionMetrics {
2356
+ // Latency
2357
+ p50Latency: number;
2358
+ p95Latency: number;
2359
+ p99Latency: number;
2360
+
2361
+ // Acceptance
2362
+ totalSuggestions: number;
2363
+ acceptedSuggestions: number;
2364
+ acceptanceRate: number;
2365
+
2366
+ // By provider
2367
+ byProvider: Record<string, ProviderMetrics>;
2368
+
2369
+ // Errors
2370
+ errorRate: number;
2371
+ errorsByType: Record<string, number>;
2372
+
2373
+ // Cost
2374
+ tokensUsed: number;
2375
+ estimatedCost: number;
2376
+ }
2377
+ ```
2378
+
2379
+ ---
2380
+
2381
+ ## Integracion con Editores
2382
+
2383
+ ### VS Code Extension
2384
+
2385
+ ```typescript
2386
+ class ElsabroSuggestionsProvider implements CompletionItemProvider {
2387
+ async provideCompletionItems(
2388
+ document: TextDocument,
2389
+ position: Position,
2390
+ token: CancellationToken
2391
+ ): Promise<CompletionList> {
2392
+ const suggestions = await this.engine.getSuggestions({
2393
+ file: document.uri.fsPath,
2394
+ position: { line: position.line, column: position.character },
2395
+ prefix: document.getText(new Range(new Position(0, 0), position)),
2396
+ suffix: document.getText(new Range(position, document.positionAt(document.getText().length)))
2397
+ });
2398
+
2399
+ return new CompletionList(
2400
+ suggestions.map(s => this.toCompletionItem(s))
2401
+ );
2402
+ }
2403
+ }
2404
+ ```
2405
+
2406
+ ---
2407
+
2408
+ ## Referencias
2409
+
2410
+ - [LLM Inference Optimization](/references/llm-inference.md)
2411
+ - [Multi-LLM Router](/references/multi-llm-router.md)
2412
+ - [Error Contracts](/references/error-contracts.md)
2413
+ - [Telemetry System](/references/telemetry.md)