baseguard 1.0.5 → 1.0.6

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 (80) hide show
  1. package/dist/ai/gemini-analyzer.d.ts.map +1 -1
  2. package/dist/ai/gemini-analyzer.js +1 -1
  3. package/dist/ai/gemini-analyzer.js.map +1 -1
  4. package/dist/ai/gemini-code-fixer.d.ts.map +1 -1
  5. package/dist/ai/gemini-code-fixer.js +2 -7
  6. package/dist/ai/gemini-code-fixer.js.map +1 -1
  7. package/dist/ai/jules-implementer.d.ts +8 -0
  8. package/dist/ai/jules-implementer.d.ts.map +1 -1
  9. package/dist/ai/jules-implementer.js +115 -17
  10. package/dist/ai/jules-implementer.js.map +1 -1
  11. package/package.json +1 -1
  12. package/src/ai/__tests__/gemini-analyzer.test.ts +0 -181
  13. package/src/ai/agentkit-orchestrator.ts +0 -534
  14. package/src/ai/fix-manager.ts +0 -362
  15. package/src/ai/gemini-analyzer.ts +0 -665
  16. package/src/ai/gemini-code-fixer.ts +0 -539
  17. package/src/ai/index.ts +0 -4
  18. package/src/ai/jules-implementer.ts +0 -504
  19. package/src/ai/unified-code-fixer.ts +0 -347
  20. package/src/commands/automation.ts +0 -344
  21. package/src/commands/check.ts +0 -298
  22. package/src/commands/config.ts +0 -584
  23. package/src/commands/fix.ts +0 -269
  24. package/src/commands/index.ts +0 -7
  25. package/src/commands/init.ts +0 -156
  26. package/src/commands/status.ts +0 -307
  27. package/src/core/api-key-manager.ts +0 -298
  28. package/src/core/baseguard.ts +0 -757
  29. package/src/core/baseline-checker.ts +0 -566
  30. package/src/core/cache-manager.ts +0 -272
  31. package/src/core/configuration-recovery.ts +0 -672
  32. package/src/core/configuration.ts +0 -596
  33. package/src/core/debug-logger.ts +0 -590
  34. package/src/core/directory-filter.ts +0 -421
  35. package/src/core/error-handler.ts +0 -518
  36. package/src/core/file-processor.ts +0 -338
  37. package/src/core/gitignore-manager.ts +0 -169
  38. package/src/core/graceful-degradation-manager.ts +0 -596
  39. package/src/core/index.ts +0 -17
  40. package/src/core/lazy-loader.ts +0 -317
  41. package/src/core/logger.ts +0 -0
  42. package/src/core/memory-manager.ts +0 -290
  43. package/src/core/parser-worker.ts +0 -33
  44. package/src/core/startup-optimizer.ts +0 -246
  45. package/src/core/system-error-handler.ts +0 -755
  46. package/src/git/automation-engine.ts +0 -361
  47. package/src/git/github-manager.ts +0 -190
  48. package/src/git/hook-manager.ts +0 -210
  49. package/src/git/index.ts +0 -4
  50. package/src/index.ts +0 -8
  51. package/src/parsers/feature-validator.ts +0 -559
  52. package/src/parsers/index.ts +0 -8
  53. package/src/parsers/parser-manager.ts +0 -418
  54. package/src/parsers/parser.ts +0 -26
  55. package/src/parsers/react-parser-optimized.ts +0 -161
  56. package/src/parsers/react-parser.ts +0 -359
  57. package/src/parsers/svelte-parser.ts +0 -510
  58. package/src/parsers/vanilla-parser.ts +0 -685
  59. package/src/parsers/vue-parser.ts +0 -476
  60. package/src/types/index.ts +0 -96
  61. package/src/ui/components.ts +0 -567
  62. package/src/ui/help.ts +0 -193
  63. package/src/ui/index.ts +0 -4
  64. package/src/ui/prompts.ts +0 -681
  65. package/src/ui/terminal-header.ts +0 -59
  66. package/tests/e2e/baseguard.e2e.test.ts +0 -516
  67. package/tests/e2e/cross-platform.e2e.test.ts +0 -420
  68. package/tests/e2e/git-integration.e2e.test.ts +0 -487
  69. package/tests/fixtures/react-project/package.json +0 -14
  70. package/tests/fixtures/react-project/src/App.css +0 -76
  71. package/tests/fixtures/react-project/src/App.tsx +0 -77
  72. package/tests/fixtures/svelte-project/package.json +0 -11
  73. package/tests/fixtures/svelte-project/src/App.svelte +0 -369
  74. package/tests/fixtures/vanilla-project/index.html +0 -76
  75. package/tests/fixtures/vanilla-project/script.js +0 -331
  76. package/tests/fixtures/vanilla-project/styles.css +0 -359
  77. package/tests/fixtures/vue-project/package.json +0 -12
  78. package/tests/fixtures/vue-project/src/App.vue +0 -216
  79. package/tmp-smoke/.baseguard/backups/config-2026-02-19T12-04-11-067Z-auto.json +0 -30
  80. package/tmp-smoke/src/bad.css +0 -3
@@ -1,665 +0,0 @@
1
- import type { Violation, Analysis } from '../types/index.js';
2
- import { createHash } from 'crypto';
3
- import { ErrorHandler, APIError, ErrorType } from '../core/error-handler.js';
4
-
5
- /**
6
- * Cache entry for analysis results
7
- */
8
- interface CacheEntry {
9
- analysis: Analysis;
10
- timestamp: number;
11
- ttl: number; // Time to live in milliseconds
12
- }
13
-
14
- /**
15
- * Gemini AI analyzer for compatibility violations
16
- */
17
- export class GeminiAnalyzer {
18
- private apiKey: string;
19
- private baseUrl = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent';
20
- private cache = new Map<string, CacheEntry>();
21
- private readonly cacheTtl = 24 * 60 * 60 * 1000; // 24 hours
22
-
23
- constructor(apiKey: string) {
24
- this.apiKey = apiKey;
25
- }
26
-
27
- /**
28
- * Analyze a violation using Gemini AI with web search
29
- */
30
- async analyzeViolation(violation: Violation): Promise<Analysis> {
31
- // Check cache first
32
- const cacheKey = this.generateCacheKey(violation);
33
- const cachedResult = this.getCachedAnalysis(cacheKey);
34
-
35
- if (cachedResult) {
36
- console.log(`Using cached analysis for ${violation.feature}`);
37
- return cachedResult;
38
- }
39
-
40
- const context = ErrorHandler.createContext('gemini_analysis', {
41
- feature: violation.feature,
42
- browser: violation.browser,
43
- file: violation.file
44
- });
45
-
46
- try {
47
- const prompt = this.buildAnalysisPrompt(violation);
48
-
49
- const response = await ErrorHandler.withRetry(
50
- () => this.makeApiCall(prompt),
51
- {
52
- maxRetries: 2,
53
- retryableErrors: [ErrorType.NETWORK, ErrorType.TIMEOUT, ErrorType.RATE_LIMIT, ErrorType.SERVER_ERROR]
54
- }
55
- );
56
-
57
- const data = await response.json();
58
-
59
- // Validate response structure
60
- ErrorHandler.validateAPIResponse(data, ['candidates']);
61
-
62
- if (!data.candidates || data.candidates.length === 0) {
63
- throw new APIError(
64
- 'No analysis candidates returned from Gemini API',
65
- ErrorType.VALIDATION,
66
- {
67
- suggestions: [
68
- 'Try rephrasing the analysis request',
69
- 'Check if the feature is supported by Gemini',
70
- 'Try again with a different violation'
71
- ],
72
- context
73
- }
74
- );
75
- }
76
-
77
- const candidate = data.candidates[0];
78
- const content = candidate.content?.parts?.[0]?.text;
79
- const groundingMetadata = candidate.groundingMetadata;
80
-
81
- if (!content) {
82
- throw new APIError(
83
- 'Empty analysis content returned from Gemini API',
84
- ErrorType.VALIDATION,
85
- {
86
- suggestions: [
87
- 'Try the analysis again',
88
- 'Check if the violation data is complete',
89
- 'Verify API key permissions'
90
- ],
91
- context
92
- }
93
- );
94
- }
95
-
96
- const analysis = this.parseAnalysisResponse(content, groundingMetadata, violation);
97
-
98
- // Cache the result
99
- this.cacheAnalysis(cacheKey, analysis);
100
-
101
- return analysis;
102
- } catch (error) {
103
- const apiError = ErrorHandler.handleAPIError(error, context);
104
-
105
- // Log error for debugging
106
- console.error('Gemini analysis failed:', {
107
- feature: violation.feature,
108
- error: apiError.message,
109
- type: apiError.type
110
- });
111
-
112
- // Return fallback analysis with error context
113
- return this.createFallbackAnalysis(violation, apiError);
114
- }
115
- }
116
-
117
- /**
118
- * Make API call to Gemini (retry logic handled by ErrorHandler)
119
- */
120
- private async makeApiCall(prompt: string): Promise<Response> {
121
- const response = await fetch(this.baseUrl, {
122
- method: 'POST',
123
- headers: {
124
- 'x-goog-api-key': this.apiKey,
125
- 'Content-Type': 'application/json'
126
- },
127
- body: JSON.stringify({
128
- contents: [{
129
- parts: [{ text: prompt }]
130
- }],
131
- tools: [{
132
- google_search: {}
133
- }],
134
- generationConfig: {
135
- temperature: 0.1,
136
- topK: 40,
137
- topP: 0.95,
138
- maxOutputTokens: 2048
139
- }
140
- })
141
- });
142
-
143
- // Let ErrorHandler classify the error based on status code
144
- if (!response.ok) {
145
- const error = new Error(`HTTP ${response.status}: ${response.statusText}`);
146
- (error as any).response = response;
147
- throw error;
148
- }
149
-
150
- return response;
151
- }
152
-
153
- /**
154
- * Build analysis prompt for Gemini
155
- */
156
- private buildAnalysisPrompt(violation: Violation): string {
157
- return `
158
- Analyze this browser compatibility issue and provide actionable insights:
159
-
160
- COMPATIBILITY ISSUE:
161
- - Feature: ${violation.feature}
162
- - File: ${violation.file} (line ${violation.line})
163
- - Unsupported Browser: ${violation.browser} ${violation.required}
164
- - Baseline Status: ${violation.baselineStatus}
165
- - Code Context: ${violation.context}
166
-
167
- Please research and provide:
168
-
169
- 1. MARKET IMPACT: What percentage of users are affected by this compatibility issue? Search for current browser market share data for ${violation.browser} ${violation.required}.
170
-
171
- 2. USER EXPERIENCE: Explain in plain English what will happen to users on the unsupported browser. Will the feature fail silently, break the layout, or cause JavaScript errors?
172
-
173
- 3. FIX STRATEGY: What's the best approach to fix this? Options include:
174
- - Progressive enhancement with @supports
175
- - Feature detection with JavaScript
176
- - Polyfills or libraries
177
- - Alternative implementations
178
-
179
- 4. BEST PRACTICES: Search for current best practices from MDN, web.dev, or CSS-Tricks for implementing ${violation.feature} with fallbacks.
180
-
181
- 5. CODE EXAMPLES: What specific techniques should be used for the fix?
182
-
183
- Format your response with clear sections and cite your sources.
184
- `;
185
- }
186
-
187
- /**
188
- * Parse Gemini response into structured analysis
189
- */
190
- private parseAnalysisResponse(content: string, groundingMetadata: any, violation: Violation): Analysis {
191
- // Extract sources from grounding metadata
192
- const sources = this.extractSources(groundingMetadata);
193
-
194
- // Parse structured information from response
195
- const userImpact = this.extractUserImpact(content);
196
- const marketShare = this.extractMarketShare(content);
197
- const fixStrategy = this.extractFixStrategy(content);
198
- const bestPractices = this.extractBestPractices(content);
199
-
200
- // Calculate confidence based on grounding quality
201
- const confidence = this.calculateConfidence(groundingMetadata, content);
202
-
203
- // Clean up the plain English explanation
204
- const plainEnglish = this.cleanupPlainEnglish(content);
205
-
206
- return {
207
- violation,
208
- userImpact,
209
- marketShare,
210
- fixStrategy,
211
- bestPractices,
212
- sources,
213
- plainEnglish,
214
- confidence
215
- };
216
- }
217
-
218
- /**
219
- * Extract sources from grounding metadata
220
- */
221
- private extractSources(groundingMetadata: any): string[] {
222
- if (!groundingMetadata?.groundingChunks) {
223
- return [];
224
- }
225
-
226
- return groundingMetadata.groundingChunks
227
- .map((chunk: any) => chunk.web?.uri)
228
- .filter((uri: string) => uri && uri.startsWith('http'))
229
- .slice(0, 5); // Limit to top 5 sources
230
- }
231
-
232
- /**
233
- * Extract user impact from response text
234
- */
235
- private extractUserImpact(content: string): string {
236
- // Look for specific impact patterns with context
237
- const patterns = [
238
- /(\d+(?:\.\d+)?%[^.]*(?:users?|browsers?|market|traffic|visitors?))/i,
239
- /(?:affects?|impacts?)[^.]*(\d+(?:\.\d+)?%[^.]*)/i,
240
- /(?:approximately|around|about)\s+(\d+(?:\.\d+)?%[^.]*(?:users?|browsers?))/i,
241
- /(users?\s+on\s+[^.]*(?:may|will|could)[^.]*)/i
242
- ];
243
-
244
- for (const pattern of patterns) {
245
- const match = content.match(pattern);
246
- if (match && match[1]) {
247
- return match[1].trim();
248
- }
249
- }
250
-
251
- // Look for general impact statements without percentages
252
- const generalPatterns = [
253
- /(?:affects?|impacts?)[^.]*users?[^.]*\./i,
254
- /users?\s+(?:may|will|could)[^.]*experience[^.]*\./i,
255
- /compatibility\s+issues?[^.]*users?[^.]*\./i
256
- ];
257
-
258
- for (const pattern of generalPatterns) {
259
- const match = content.match(pattern);
260
- if (match && match[0]) {
261
- return match[0].trim();
262
- }
263
- }
264
-
265
- return `Users on ${this.getCurrentViolation()?.browser || 'older browsers'} may experience compatibility issues`;
266
- }
267
-
268
- /**
269
- * Extract market share percentage from response
270
- */
271
- private extractMarketShare(content: string): number {
272
- // Look for percentage patterns in context of market share or usage
273
- const patterns = [
274
- /market\s+share[^.]*?(\d+(?:\.\d+)?)%/i,
275
- /(\d+(?:\.\d+)?)%[^.]*(?:market|users?|browsers?|traffic)/i,
276
- /usage[^.]*?(\d+(?:\.\d+)?)%/i,
277
- /(\d+(?:\.\d+)?)%[^.]*(?:affected|impacted)/i
278
- ];
279
-
280
- for (const pattern of patterns) {
281
- const match = content.match(pattern);
282
- if (match && match[1]) {
283
- const percentage = parseFloat(match[1]);
284
- if (percentage >= 0 && percentage <= 100) {
285
- return percentage / 100;
286
- }
287
- }
288
- }
289
-
290
- // Fallback: look for any percentage and validate it's reasonable
291
- const anyPercentMatch = content.match(/(\d+(?:\.\d+)?)%/);
292
- if (anyPercentMatch && anyPercentMatch[1]) {
293
- const percentage = parseFloat(anyPercentMatch[1]);
294
- if (percentage >= 0.1 && percentage <= 50) { // Reasonable range for browser market share
295
- return percentage / 100;
296
- }
297
- }
298
-
299
- return 0.05; // Default 5% if not found
300
- }
301
-
302
- /**
303
- * Get current violation being processed (for fallback messages)
304
- */
305
- private getCurrentViolation(): Violation | null {
306
- // This would be set during processing, but for now return null
307
- return null;
308
- }
309
-
310
- /**
311
- * Extract fix strategy from response
312
- */
313
- private extractFixStrategy(content: string): string {
314
- // Look for strategy keywords
315
- const strategies = [
316
- 'progressive enhancement',
317
- 'feature detection',
318
- 'polyfill',
319
- 'fallback',
320
- '@supports',
321
- 'graceful degradation'
322
- ];
323
-
324
- for (const strategy of strategies) {
325
- if (content.toLowerCase().includes(strategy)) {
326
- return strategy;
327
- }
328
- }
329
-
330
- return 'progressive enhancement';
331
- }
332
-
333
- /**
334
- * Extract best practices from response
335
- */
336
- private extractBestPractices(content: string): string[] {
337
- const practices: string[] = [];
338
-
339
- // Look for specific best practices mentioned in the response
340
- const practicePatterns = [
341
- { pattern: /@supports|feature detection/i, practice: 'Use @supports for feature detection' },
342
- { pattern: /fallback|alternative/i, practice: 'Provide fallback implementations' },
343
- { pattern: /polyfill/i, practice: 'Consider using polyfills' },
344
- { pattern: /progressive enhancement/i, practice: 'Use progressive enhancement' },
345
- { pattern: /graceful degradation/i, practice: 'Implement graceful degradation' },
346
- { pattern: /test|testing/i, practice: 'Test across target browsers' },
347
- { pattern: /vendor prefix/i, practice: 'Use vendor prefixes when needed' },
348
- { pattern: /modernizr/i, practice: 'Consider using Modernizr for feature detection' }
349
- ];
350
-
351
- for (const { pattern, practice } of practicePatterns) {
352
- if (pattern.test(content) && !practices.includes(practice)) {
353
- practices.push(practice);
354
- }
355
- }
356
-
357
- // Extract numbered or bulleted best practices from the response
358
- const listMatches = content.match(/(?:^|\n)\s*[-*•]\s*([^.\n]+)/gm);
359
- if (listMatches) {
360
- for (const match of listMatches.slice(0, 3)) { // Limit to 3 additional practices
361
- const practice = match.replace(/^[\s\n-*•]+/, '').trim();
362
- if (practice.length > 10 && practice.length < 100 && !practices.some(p => p.includes(practice))) {
363
- practices.push(practice);
364
- }
365
- }
366
- }
367
-
368
- return practices.length > 0 ? practices : ['Follow progressive enhancement principles'];
369
- }
370
-
371
- /**
372
- * Clean up plain English explanation
373
- */
374
- private cleanupPlainEnglish(content: string): string {
375
- // Remove excessive whitespace and normalize line breaks
376
- let cleaned = content.replace(/\n\s*\n/g, '\n\n').trim();
377
-
378
- // Remove markdown formatting that might interfere with display
379
- cleaned = cleaned.replace(/\*\*(.*?)\*\*/g, '$1'); // Remove bold
380
- cleaned = cleaned.replace(/\*(.*?)\*/g, '$1'); // Remove italic
381
-
382
- // Ensure it's not too long for display
383
- if (cleaned.length > 1500) {
384
- const sentences = cleaned.split(/[.!?]+/);
385
- let truncated = '';
386
- for (const sentence of sentences) {
387
- if (truncated.length + sentence.length > 1400) break;
388
- truncated += sentence + '.';
389
- }
390
- cleaned = truncated + '..';
391
- }
392
-
393
- return cleaned;
394
- }
395
-
396
- /**
397
- * Calculate confidence score based on grounding quality
398
- */
399
- private calculateConfidence(groundingMetadata: any, content: string): number {
400
- let confidence = 0.5; // Base confidence
401
-
402
- // Increase confidence if we have grounding sources
403
- if (groundingMetadata?.groundingChunks?.length > 0) {
404
- confidence += 0.2;
405
- }
406
-
407
- // Increase confidence if response contains specific data
408
- if (content.includes('%')) {
409
- confidence += 0.1;
410
- }
411
-
412
- // Increase confidence if response mentions authoritative sources
413
- const authoritativeSources = ['mdn', 'web.dev', 'caniuse', 'w3c'];
414
- if (authoritativeSources.some(source => content.toLowerCase().includes(source))) {
415
- confidence += 0.2;
416
- }
417
-
418
- return Math.min(confidence, 1.0);
419
- }
420
-
421
- /**
422
- * Create fallback analysis when API fails
423
- */
424
- private createFallbackAnalysis(violation: Violation, error: APIError): Analysis {
425
- let fallbackMessage = `Analysis unavailable: ${error.message}`;
426
- let confidence = 0.3;
427
-
428
- // Provide more specific fallback based on error type
429
- if (error.type === ErrorType.RATE_LIMIT) {
430
- fallbackMessage = `Rate limit reached. Using basic compatibility analysis for ${violation.feature}.`;
431
- confidence = 0.4;
432
- } else if (error.type === ErrorType.AUTHENTICATION) {
433
- fallbackMessage = `API authentication failed. Using offline compatibility analysis for ${violation.feature}.`;
434
- confidence = 0.2;
435
- } else if (error.type === ErrorType.NETWORK) {
436
- fallbackMessage = `Network unavailable. Using cached compatibility data for ${violation.feature}.`;
437
- confidence = 0.5;
438
- }
439
-
440
- // Get fallback suggestions based on error type
441
- const fallbackSuggestions = ErrorHandler.getFallbackSuggestions(error.type);
442
-
443
- return {
444
- violation,
445
- userImpact: `Users on ${violation.browser} ${violation.required} may experience compatibility issues with ${violation.feature}`,
446
- marketShare: this.estimateMarketShare(violation.browser, violation.required),
447
- fixStrategy: this.getDefaultFixStrategy(violation.feature),
448
- bestPractices: [
449
- 'Use @supports for CSS feature detection',
450
- 'Implement fallback solutions',
451
- 'Test across target browsers',
452
- ...fallbackSuggestions.slice(0, 2)
453
- ],
454
- sources: [],
455
- plainEnglish: `${fallbackMessage} Consider using progressive enhancement techniques and testing across your target browsers.`,
456
- confidence
457
- };
458
- }
459
-
460
- /**
461
- * Estimate market share for fallback analysis
462
- */
463
- private estimateMarketShare(browser: string, version: string): number {
464
- // Conservative estimates based on typical browser usage patterns
465
- const estimates: Record<string, number> = {
466
- 'chrome': 0.65,
467
- 'firefox': 0.08,
468
- 'safari': 0.18,
469
- 'edge': 0.05,
470
- 'opera': 0.02,
471
- 'samsung': 0.02
472
- };
473
-
474
- const baseShare = estimates[browser.toLowerCase()] || 0.05;
475
-
476
- // Reduce estimate for older versions
477
- if (version !== 'baseline' && version !== 'baseline-newly') {
478
- const versionNum = parseInt(version, 10);
479
- if (!isNaN(versionNum)) {
480
- // Rough estimate: older versions have lower usage
481
- const currentYear = new Date().getFullYear();
482
- const estimatedYear = 2008 + (versionNum / 10); // Very rough estimation
483
- const yearsDiff = currentYear - estimatedYear;
484
-
485
- if (yearsDiff > 2) {
486
- return baseShare * 0.1; // Much lower for old versions
487
- } else if (yearsDiff > 1) {
488
- return baseShare * 0.3; // Lower for somewhat old versions
489
- }
490
- }
491
- }
492
-
493
- return baseShare;
494
- }
495
-
496
- /**
497
- * Get default fix strategy based on feature type
498
- */
499
- private getDefaultFixStrategy(feature: string): string {
500
- const lowerFeature = feature.toLowerCase();
501
-
502
- if (lowerFeature.includes('css') || lowerFeature.includes('grid') || lowerFeature.includes('flex')) {
503
- return 'progressive enhancement with @supports';
504
- } else if (lowerFeature.includes('api') || lowerFeature.includes('js')) {
505
- return 'feature detection with polyfills';
506
- } else if (lowerFeature.includes('element') || lowerFeature.includes('html')) {
507
- return 'graceful degradation';
508
- }
509
-
510
- return 'progressive enhancement';
511
- }
512
-
513
- /**
514
- * Generate cache key for a violation
515
- */
516
- private generateCacheKey(violation: Violation): string {
517
- // Create a hash based on feature, browser, and version
518
- const keyData = `${violation.feature}:${violation.browser}:${violation.required}:${violation.baselineStatus}`;
519
- return createHash('md5').update(keyData).digest('hex');
520
- }
521
-
522
- /**
523
- * Get cached analysis if available and not expired
524
- */
525
- private getCachedAnalysis(cacheKey: string): Analysis | null {
526
- const entry = this.cache.get(cacheKey);
527
-
528
- if (!entry) {
529
- return null;
530
- }
531
-
532
- const now = Date.now();
533
- if (now - entry.timestamp > entry.ttl) {
534
- // Cache expired, remove it
535
- this.cache.delete(cacheKey);
536
- return null;
537
- }
538
-
539
- return entry.analysis;
540
- }
541
-
542
- /**
543
- * Cache analysis result
544
- */
545
- private cacheAnalysis(cacheKey: string, analysis: Analysis): void {
546
- const entry: CacheEntry = {
547
- analysis,
548
- timestamp: Date.now(),
549
- ttl: this.cacheTtl
550
- };
551
-
552
- this.cache.set(cacheKey, entry);
553
-
554
- // Clean up old entries periodically
555
- this.cleanupCache();
556
- }
557
-
558
- /**
559
- * Clean up expired cache entries
560
- */
561
- private cleanupCache(): void {
562
- const now = Date.now();
563
-
564
- for (const [key, entry] of this.cache.entries()) {
565
- if (now - entry.timestamp > entry.ttl) {
566
- this.cache.delete(key);
567
- }
568
- }
569
- }
570
-
571
- /**
572
- * Clear all cached analyses
573
- */
574
- public clearCache(): void {
575
- this.cache.clear();
576
- }
577
-
578
- /**
579
- * Get cache statistics
580
- */
581
- public getCacheStats(): { size: number; hitRate?: number } {
582
- return {
583
- size: this.cache.size
584
- };
585
- }
586
-
587
- /**
588
- * Analyze multiple violations with concurrency control
589
- */
590
- async analyzeViolations(violations: Violation[], maxConcurrent = 3): Promise<Analysis[]> {
591
- const results: Analysis[] = [];
592
-
593
- // Process violations in batches to avoid overwhelming the API
594
- for (let i = 0; i < violations.length; i += maxConcurrent) {
595
- const batch = violations.slice(i, i + maxConcurrent);
596
- const batchPromises = batch.map(violation => this.analyzeViolation(violation));
597
-
598
- try {
599
- const batchResults = await Promise.all(batchPromises);
600
- results.push(...batchResults);
601
- } catch (error) {
602
- console.error(`Error processing batch ${i / maxConcurrent + 1}:`, error);
603
-
604
- // Process individually as fallback
605
- for (const violation of batch) {
606
- try {
607
- const analysis = await this.analyzeViolation(violation);
608
- results.push(analysis);
609
- } catch (individualError) {
610
- console.error(`Error analyzing ${violation.feature}:`, individualError);
611
- results.push(this.createFallbackAnalysis(violation, ErrorHandler.handleAPIError(individualError)));
612
- }
613
- }
614
- }
615
-
616
- // Add delay between batches to respect rate limits
617
- if (i + maxConcurrent < violations.length) {
618
- await new Promise(resolve => setTimeout(resolve, 1000));
619
- }
620
- }
621
-
622
- return results;
623
- }
624
-
625
- /**
626
- * Validate API key format
627
- */
628
- public static validateApiKey(apiKey: string): boolean {
629
- // Gemini API keys typically start with 'AIza' and are 39 characters long
630
- // But they can also have other formats, so let's be more flexible
631
- return /^AIza[A-Za-z0-9_-]{35,}$/.test(apiKey) || apiKey.length >= 20;
632
- }
633
-
634
- /**
635
- * Test API connectivity
636
- */
637
- async testConnection(): Promise<{ success: boolean; error?: string; errorType?: ErrorType }> {
638
- try {
639
- const testPrompt = 'Test connection. Please respond with "OK".';
640
- const response = await this.makeApiCall(testPrompt);
641
-
642
- // If we get here, the API call succeeded
643
- const data = await response.json();
644
-
645
- // Validate we got a proper response
646
- if (data.candidates && data.candidates.length > 0) {
647
- return { success: true };
648
- } else {
649
- return {
650
- success: false,
651
- error: 'API responded but returned no content',
652
- errorType: ErrorType.VALIDATION
653
- };
654
- }
655
- } catch (error) {
656
- const apiError = ErrorHandler.handleAPIError(error);
657
-
658
- return {
659
- success: false,
660
- error: apiError.message,
661
- errorType: apiError.type
662
- };
663
- }
664
- }
665
- }