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.
- package/README.md +668 -20
- package/bin/install.js +0 -0
- package/flows/development-flow.json +452 -0
- package/flows/quick-flow.json +118 -0
- package/package.json +3 -2
- package/references/SYSTEM_INDEX.md +379 -5
- package/references/agent-marketplace.md +2274 -0
- package/references/agent-protocol.md +1126 -0
- package/references/ai-code-suggestions.md +2413 -0
- package/references/checkpointing.md +595 -0
- package/references/collaboration-patterns.md +851 -0
- package/references/collaborative-sessions.md +1081 -0
- package/references/configuration-management.md +1810 -0
- package/references/cost-tracking.md +1095 -0
- package/references/enterprise-sso.md +2001 -0
- package/references/error-contracts-v2.md +968 -0
- package/references/event-driven.md +1031 -0
- package/references/flow-orchestration.md +940 -0
- package/references/flow-visualization.md +1557 -0
- package/references/ide-integrations.md +3513 -0
- package/references/interrupt-system.md +681 -0
- package/references/kubernetes-deployment.md +3099 -0
- package/references/memory-system.md +683 -0
- package/references/mobile-companion.md +3236 -0
- package/references/multi-llm-providers.md +2494 -0
- package/references/multi-project-memory.md +1182 -0
- package/references/observability.md +793 -0
- package/references/output-schemas.md +858 -0
- package/references/performance-profiler.md +955 -0
- package/references/plugin-system.md +1526 -0
- package/references/prompt-management.md +292 -0
- package/references/sandbox-execution.md +303 -0
- package/references/security-system.md +1253 -0
- package/references/streaming.md +696 -0
- package/references/testing-framework.md +1151 -0
- package/references/time-travel.md +802 -0
- package/references/tool-registry.md +886 -0
- package/references/voice-commands.md +3296 -0
- package/templates/agent-marketplace-config.json +220 -0
- package/templates/agent-protocol-config.json +136 -0
- package/templates/ai-suggestions-config.json +100 -0
- package/templates/checkpoint-state.json +61 -0
- package/templates/collaboration-config.json +157 -0
- package/templates/collaborative-sessions-config.json +153 -0
- package/templates/configuration-config.json +245 -0
- package/templates/cost-tracking-config.json +148 -0
- package/templates/enterprise-sso-config.json +438 -0
- package/templates/events-config.json +148 -0
- package/templates/flow-visualization-config.json +196 -0
- package/templates/ide-integrations-config.json +442 -0
- package/templates/kubernetes-config.json +764 -0
- package/templates/memory-state.json +84 -0
- package/templates/mobile-companion-config.json +600 -0
- package/templates/multi-llm-config.json +544 -0
- package/templates/multi-project-memory-config.json +145 -0
- package/templates/observability-config.json +109 -0
- package/templates/performance-profiler-config.json +125 -0
- package/templates/plugin-config.json +170 -0
- package/templates/prompt-management-config.json +86 -0
- package/templates/sandbox-config.json +185 -0
- package/templates/schemas-config.json +65 -0
- package/templates/security-config.json +120 -0
- package/templates/streaming-config.json +72 -0
- package/templates/testing-config.json +81 -0
- package/templates/timetravel-config.json +62 -0
- package/templates/tool-registry-config.json +109 -0
- 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(``);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
if (ctx.hasTests) {
|
|
1403
|
+
badges.push('');
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
if (ctx.packageJson?.license) {
|
|
1407
|
+
badges.push(``);
|
|
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)
|