baseguard 1.0.2 → 1.0.4

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 (169) hide show
  1. package/.baseguardrc.example.json +63 -63
  2. package/.eslintrc.json +24 -24
  3. package/.prettierrc +7 -7
  4. package/CHANGELOG.md +195 -195
  5. package/DEPLOYMENT.md +624 -624
  6. package/DEPLOYMENT_CHECKLIST.md +239 -239
  7. package/DEPLOYMENT_SUMMARY_v1.0.2.md +202 -202
  8. package/QUICK_START.md +134 -134
  9. package/README.md +488 -488
  10. package/RELEASE_NOTES_v1.0.2.md +434 -434
  11. package/bin/base.js +628 -613
  12. package/dist/ai/fix-manager.d.ts.map +1 -1
  13. package/dist/ai/fix-manager.js +1 -1
  14. package/dist/ai/fix-manager.js.map +1 -1
  15. package/dist/ai/gemini-analyzer.d.ts.map +1 -1
  16. package/dist/ai/gemini-analyzer.js +29 -35
  17. package/dist/ai/gemini-analyzer.js.map +1 -1
  18. package/dist/ai/gemini-code-fixer.d.ts.map +1 -1
  19. package/dist/ai/gemini-code-fixer.js +58 -58
  20. package/dist/ai/gemini-code-fixer.js.map +1 -1
  21. package/dist/ai/jules-implementer.d.ts +3 -0
  22. package/dist/ai/jules-implementer.d.ts.map +1 -1
  23. package/dist/ai/jules-implementer.js +63 -32
  24. package/dist/ai/jules-implementer.js.map +1 -1
  25. package/dist/ai/unified-code-fixer.js.map +1 -1
  26. package/dist/commands/check.d.ts.map +1 -1
  27. package/dist/commands/check.js +1 -1
  28. package/dist/commands/check.js.map +1 -1
  29. package/dist/commands/config.js +2 -1
  30. package/dist/commands/config.js.map +1 -1
  31. package/dist/commands/fix.d.ts.map +1 -1
  32. package/dist/commands/fix.js +44 -15
  33. package/dist/commands/fix.js.map +1 -1
  34. package/dist/core/api-key-manager.js +2 -2
  35. package/dist/core/api-key-manager.js.map +1 -1
  36. package/dist/core/baseguard.d.ts +1 -0
  37. package/dist/core/baseguard.d.ts.map +1 -1
  38. package/dist/core/baseguard.js +13 -10
  39. package/dist/core/baseguard.js.map +1 -1
  40. package/dist/core/baseline-checker.d.ts.map +1 -1
  41. package/dist/core/baseline-checker.js +2 -1
  42. package/dist/core/baseline-checker.js.map +1 -1
  43. package/dist/core/configuration-recovery.d.ts.map +1 -1
  44. package/dist/core/configuration-recovery.js +1 -1
  45. package/dist/core/configuration-recovery.js.map +1 -1
  46. package/dist/core/debug-logger.d.ts.map +1 -1
  47. package/dist/core/debug-logger.js +1 -1
  48. package/dist/core/debug-logger.js.map +1 -1
  49. package/dist/core/error-handler.d.ts.map +1 -1
  50. package/dist/core/error-handler.js +2 -1
  51. package/dist/core/error-handler.js.map +1 -1
  52. package/dist/core/gitignore-manager.js +5 -5
  53. package/dist/core/graceful-degradation-manager.d.ts.map +1 -1
  54. package/dist/core/graceful-degradation-manager.js +16 -16
  55. package/dist/core/graceful-degradation-manager.js.map +1 -1
  56. package/dist/core/lazy-loader.d.ts.map +1 -1
  57. package/dist/core/lazy-loader.js +9 -2
  58. package/dist/core/lazy-loader.js.map +1 -1
  59. package/dist/core/memory-manager.d.ts +0 -3
  60. package/dist/core/memory-manager.d.ts.map +1 -1
  61. package/dist/core/memory-manager.js.map +1 -1
  62. package/dist/core/parser-worker.d.ts +2 -0
  63. package/dist/core/parser-worker.d.ts.map +1 -0
  64. package/dist/core/parser-worker.js +19 -0
  65. package/dist/core/parser-worker.js.map +1 -0
  66. package/dist/core/startup-optimizer.d.ts +2 -0
  67. package/dist/core/startup-optimizer.d.ts.map +1 -1
  68. package/dist/core/startup-optimizer.js +19 -12
  69. package/dist/core/startup-optimizer.js.map +1 -1
  70. package/dist/core/system-error-handler.d.ts.map +1 -1
  71. package/dist/core/system-error-handler.js +18 -11
  72. package/dist/core/system-error-handler.js.map +1 -1
  73. package/dist/git/automation-engine.d.ts.map +1 -1
  74. package/dist/git/automation-engine.js +5 -4
  75. package/dist/git/automation-engine.js.map +1 -1
  76. package/dist/git/github-manager.d.ts.map +1 -1
  77. package/dist/git/github-manager.js.map +1 -1
  78. package/dist/git/hook-manager.js +5 -5
  79. package/dist/git/hook-manager.js.map +1 -1
  80. package/dist/parsers/parser-manager.d.ts.map +1 -1
  81. package/dist/parsers/parser-manager.js +1 -1
  82. package/dist/parsers/parser-manager.js.map +1 -1
  83. package/dist/parsers/svelte-parser.js +1 -1
  84. package/dist/parsers/svelte-parser.js.map +1 -1
  85. package/dist/parsers/vanilla-parser.d.ts.map +1 -1
  86. package/dist/parsers/vanilla-parser.js.map +1 -1
  87. package/dist/parsers/vue-parser.d.ts.map +1 -1
  88. package/dist/parsers/vue-parser.js.map +1 -1
  89. package/dist/ui/components.d.ts +1 -1
  90. package/dist/ui/components.d.ts.map +1 -1
  91. package/dist/ui/components.js +11 -11
  92. package/dist/ui/components.js.map +1 -1
  93. package/dist/ui/terminal-header.js +14 -14
  94. package/package.json +105 -105
  95. package/src/ai/__tests__/gemini-analyzer.test.ts +180 -180
  96. package/src/ai/agentkit-orchestrator.ts +533 -533
  97. package/src/ai/fix-manager.ts +362 -362
  98. package/src/ai/gemini-analyzer.ts +665 -671
  99. package/src/ai/gemini-code-fixer.ts +539 -540
  100. package/src/ai/index.ts +3 -3
  101. package/src/ai/jules-implementer.ts +504 -460
  102. package/src/ai/unified-code-fixer.ts +347 -347
  103. package/src/commands/automation.ts +343 -343
  104. package/src/commands/check.ts +298 -299
  105. package/src/commands/config.ts +584 -583
  106. package/src/commands/fix.ts +264 -238
  107. package/src/commands/index.ts +6 -6
  108. package/src/commands/init.ts +155 -155
  109. package/src/commands/status.ts +306 -306
  110. package/src/core/api-key-manager.ts +298 -298
  111. package/src/core/baseguard.ts +757 -756
  112. package/src/core/baseline-checker.ts +564 -563
  113. package/src/core/cache-manager.ts +271 -271
  114. package/src/core/configuration-recovery.ts +672 -673
  115. package/src/core/configuration.ts +595 -595
  116. package/src/core/debug-logger.ts +590 -590
  117. package/src/core/directory-filter.ts +420 -420
  118. package/src/core/error-handler.ts +518 -517
  119. package/src/core/file-processor.ts +337 -337
  120. package/src/core/gitignore-manager.ts +168 -168
  121. package/src/core/graceful-degradation-manager.ts +596 -596
  122. package/src/core/index.ts +16 -16
  123. package/src/core/lazy-loader.ts +317 -307
  124. package/src/core/memory-manager.ts +290 -295
  125. package/src/core/parser-worker.ts +33 -0
  126. package/src/core/startup-optimizer.ts +246 -243
  127. package/src/core/system-error-handler.ts +755 -750
  128. package/src/git/automation-engine.ts +361 -361
  129. package/src/git/github-manager.ts +190 -192
  130. package/src/git/hook-manager.ts +210 -210
  131. package/src/git/index.ts +3 -3
  132. package/src/index.ts +7 -7
  133. package/src/parsers/feature-validator.ts +558 -558
  134. package/src/parsers/index.ts +7 -7
  135. package/src/parsers/parser-manager.ts +418 -419
  136. package/src/parsers/parser.ts +25 -25
  137. package/src/parsers/react-parser-optimized.ts +160 -160
  138. package/src/parsers/react-parser.ts +358 -358
  139. package/src/parsers/svelte-parser.ts +510 -510
  140. package/src/parsers/vanilla-parser.ts +685 -686
  141. package/src/parsers/vue-parser.ts +476 -478
  142. package/src/types/index.ts +95 -95
  143. package/src/ui/components.ts +567 -567
  144. package/src/ui/help.ts +192 -192
  145. package/src/ui/index.ts +3 -3
  146. package/src/ui/prompts.ts +680 -680
  147. package/src/ui/terminal-header.ts +58 -58
  148. package/test-build.js +40 -40
  149. package/test-config-commands.js +55 -55
  150. package/test-header-simple.js +32 -32
  151. package/test-terminal-header.js +11 -11
  152. package/test-ui.js +28 -28
  153. package/tests/e2e/baseguard.e2e.test.ts +515 -515
  154. package/tests/e2e/cross-platform.e2e.test.ts +419 -419
  155. package/tests/e2e/git-integration.e2e.test.ts +486 -486
  156. package/tests/fixtures/react-project/package.json +13 -13
  157. package/tests/fixtures/react-project/src/App.css +75 -75
  158. package/tests/fixtures/react-project/src/App.tsx +76 -76
  159. package/tests/fixtures/svelte-project/package.json +10 -10
  160. package/tests/fixtures/svelte-project/src/App.svelte +368 -368
  161. package/tests/fixtures/vanilla-project/index.html +75 -75
  162. package/tests/fixtures/vanilla-project/script.js +330 -330
  163. package/tests/fixtures/vanilla-project/styles.css +358 -358
  164. package/tests/fixtures/vue-project/package.json +11 -11
  165. package/tests/fixtures/vue-project/src/App.vue +215 -215
  166. package/tsconfig.json +34 -34
  167. package/vitest.config.ts +11 -11
  168. package/dist/terminal-header.d.ts +0 -12
  169. package/dist/terminal-header.js +0 -45
@@ -1,540 +1,539 @@
1
- import type { Violation, Analysis, Fix } from '../types/index.js';
2
- import { promises as fs } from 'fs';
3
- import { ErrorHandler, APIError, ErrorType } from '../core/error-handler.js';
4
- import { logger } from '../core/debug-logger.js';
5
- import chalk from 'chalk';
6
-
7
- /**
8
- * Gemini 2.5 Pro code fixer for local files and repositories
9
- * Works with any code (GitHub or not, committed or uncommitted)
10
- */
11
- export class GeminiCodeFixer {
12
- private apiKey: string;
13
- private baseUrl = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent';
14
- private categoryLogger: ReturnType<typeof logger.createCategoryLogger>;
15
-
16
- constructor(apiKey: string) {
17
- this.apiKey = apiKey;
18
- this.categoryLogger = logger.createCategoryLogger('gemini-code-fixer');
19
- }
20
-
21
- /**
22
- * Generate a fix using Gemini 2.5 Pro with grounding
23
- */
24
- async generateFix(violation: Violation, analysis: Analysis): Promise<Fix> {
25
- const context = ErrorHandler.createContext('gemini_code_fix', {
26
- feature: violation.feature,
27
- file: violation.file,
28
- browser: violation.browser
29
- });
30
-
31
- try {
32
- this.categoryLogger.info('Generating code fix', {
33
- feature: violation.feature,
34
- file: violation.file,
35
- strategy: analysis.fixStrategy
36
- });
37
-
38
- // Read the current file content
39
- const fileContent = await this.readFileContent(violation.file);
40
-
41
- // Build the fix prompt with file context
42
- const prompt = this.buildFixPrompt(violation, analysis, fileContent);
43
-
44
- const response = await ErrorHandler.withRetry(
45
- () => this.makeApiCall(prompt),
46
- {
47
- maxRetries: 2,
48
- retryableErrors: [ErrorType.NETWORK, ErrorType.TIMEOUT, ErrorType.RATE_LIMIT, ErrorType.SERVER_ERROR]
49
- }
50
- );
51
-
52
- const data = await response.json();
53
-
54
- // Validate response structure
55
- ErrorHandler.validateAPIResponse(data, ['candidates']);
56
-
57
- if (!data.candidates || data.candidates.length === 0) {
58
- throw new APIError(
59
- 'No fix candidates returned from Gemini API',
60
- ErrorType.VALIDATION,
61
- {
62
- suggestions: [
63
- 'Try rephrasing the fix request',
64
- 'Check if the code is valid',
65
- 'Try again with a different approach'
66
- ],
67
- context
68
- }
69
- );
70
- }
71
-
72
- const candidate = data.candidates[0];
73
- const content = candidate.content?.parts?.[0]?.text;
74
-
75
- if (!content) {
76
- throw new APIError(
77
- 'Empty fix content returned from Gemini API',
78
- ErrorType.VALIDATION,
79
- {
80
- suggestions: [
81
- 'Try the fix generation again',
82
- 'Check if the violation data is complete',
83
- 'Verify API key permissions'
84
- ],
85
- context
86
- }
87
- );
88
- }
89
-
90
- // Parse the fix response and generate proper diff
91
- const fix = await this.parseFixResponse(content, violation, analysis, fileContent);
92
-
93
- this.categoryLogger.info('Code fix generated successfully', {
94
- feature: violation.feature,
95
- confidence: fix.confidence,
96
- hasPreview: fix.preview.length > 0
97
- });
98
-
99
- return fix;
100
-
101
- } catch (error) {
102
- const apiError = ErrorHandler.handleAPIError(error, context);
103
- this.categoryLogger.error('Code fix generation failed', { error: apiError });
104
- throw apiError;
105
- }
106
- }
107
-
108
- /**
109
- * Make API call to Gemini 2.5 Pro with grounding
110
- */
111
- private async makeApiCall(prompt: string): Promise<Response> {
112
- const response = await fetch(this.baseUrl, {
113
- method: 'POST',
114
- headers: {
115
- 'x-goog-api-key': this.apiKey,
116
- 'Content-Type': 'application/json'
117
- },
118
- body: JSON.stringify({
119
- contents: [{
120
- parts: [{ text: prompt }]
121
- }],
122
- tools: [{
123
- googleSearchRetrieval: {
124
- dynamicRetrievalConfig: {
125
- mode: 'MODE_DYNAMIC',
126
- dynamicThreshold: 0.7
127
- }
128
- }
129
- }],
130
- generationConfig: {
131
- temperature: 0.1,
132
- topK: 40,
133
- topP: 0.95,
134
- maxOutputTokens: 4096,
135
- candidateCount: 1
136
- }
137
- })
138
- });
139
-
140
- if (!response.ok) {
141
- const error = new Error(`HTTP ${response.status}: ${response.statusText}`);
142
- (error as any).response = response;
143
- throw error;
144
- }
145
-
146
- return response;
147
- }
148
-
149
- /**
150
- * Build comprehensive fix prompt for Gemini 2.5 Pro
151
- */
152
- private buildFixPrompt(violation: Violation, analysis: Analysis, fileContent: string): string {
153
- return `You are an expert web developer tasked with fixing browser compatibility issues. Generate a precise code fix with proper diff format.
154
-
155
- ## COMPATIBILITY ISSUE
156
- - **Feature**: ${violation.feature}
157
- - **File**: ${violation.file} (line ${violation.line})
158
- - **Unsupported Browser**: ${violation.browser} ${violation.required}
159
- - **Baseline Status**: ${violation.baselineStatus}
160
- - **Current Code Context**: ${violation.context}
161
-
162
- ## ANALYSIS INSIGHTS
163
- - **Fix Strategy**: ${analysis.fixStrategy}
164
- - **User Impact**: ${analysis.userImpact}
165
- - **Market Share Affected**: ${(analysis.marketShare * 100).toFixed(1)}%
166
- - **Best Practices**: ${analysis.bestPractices.join(', ')}
167
-
168
- ## CURRENT FILE CONTENT
169
- \`\`\`${this.getFileExtension(violation.file)}
170
- ${fileContent}
171
- \`\`\`
172
-
173
- ## REQUIREMENTS
174
- 1. **Fix the compatibility issue** using ${analysis.fixStrategy}
175
- 2. **Preserve all existing functionality** - no breaking changes
176
- 3. **Add proper fallbacks** for ${violation.browser} ${violation.required}
177
- 4. **Follow best practices**: ${analysis.bestPractices.join(', ')}
178
- 5. **Generate a unified diff** showing exactly what changes to make
179
- 6. **Provide clear explanation** of the fix approach
180
-
181
- ## OUTPUT FORMAT
182
- Please provide your response in this exact format:
183
-
184
- ### EXPLANATION
185
- [Explain the fix approach and why it works]
186
-
187
- ### DIFF
188
- \`\`\`diff
189
- --- a/${violation.file}
190
- +++ b/${violation.file}
191
- @@ -[start_line],[num_lines] +[start_line],[num_lines] @@
192
- [unified diff content showing the exact changes]
193
- \`\`\`
194
-
195
- ### PREVIEW
196
- \`\`\`${this.getFileExtension(violation.file)}
197
- [Show the key changed sections with context]
198
- \`\`\`
199
-
200
- ### TESTING
201
- [Suggest how to test the fix works in ${violation.browser} ${violation.required}]
202
-
203
- Focus on creating a production-ready fix that maintains compatibility across all browsers while adding proper support for ${violation.browser} ${violation.required}.`;
204
- }
205
-
206
- /**
207
- * Read file content with error handling
208
- */
209
- private async readFileContent(filePath: string): Promise<string> {
210
- try {
211
- return await fs.readFile(filePath, 'utf-8');
212
- } catch (error) {
213
- this.categoryLogger.warn('Could not read file content', {
214
- file: filePath,
215
- error: error instanceof Error ? error.message : 'Unknown error'
216
- });
217
-
218
- // Return empty content if file can't be read
219
- return `// File content could not be read: ${filePath}`;
220
- }
221
- }
222
-
223
- /**
224
- * Get file extension for syntax highlighting
225
- */
226
- private getFileExtension(filePath: string): string {
227
- const ext = filePath.split('.').pop()?.toLowerCase();
228
-
229
- const extensionMap: Record<string, string> = {
230
- 'js': 'javascript',
231
- 'jsx': 'javascript',
232
- 'ts': 'typescript',
233
- 'tsx': 'typescript',
234
- 'vue': 'vue',
235
- 'svelte': 'svelte',
236
- 'css': 'css',
237
- 'scss': 'scss',
238
- 'sass': 'sass',
239
- 'html': 'html',
240
- 'htm': 'html'
241
- };
242
-
243
- return extensionMap[ext || ''] || 'text';
244
- }
245
-
246
- /**
247
- * Parse Gemini's fix response and create Fix object
248
- */
249
- private async parseFixResponse(
250
- content: string,
251
- violation: Violation,
252
- analysis: Analysis,
253
- originalContent: string
254
- ): Promise<Fix> {
255
- try {
256
- // Extract sections from the response
257
- const explanation = this.extractSection(content, 'EXPLANATION');
258
- const diffContent = this.extractSection(content, 'DIFF');
259
- const preview = this.extractSection(content, 'PREVIEW');
260
- const testing = this.extractSection(content, 'TESTING');
261
-
262
- // Clean up the diff content
263
- const cleanDiff = this.cleanDiffContent(diffContent);
264
-
265
- // Generate a human-readable preview if not provided
266
- const finalPreview = preview || this.generatePreviewFromDiff(cleanDiff, originalContent);
267
-
268
- // Calculate confidence based on response quality
269
- const confidence = this.calculateFixConfidence(content, diffContent);
270
-
271
- // Create comprehensive explanation
272
- const fullExplanation = this.buildFullExplanation(
273
- violation,
274
- analysis,
275
- explanation,
276
- testing
277
- );
278
-
279
- return {
280
- violation,
281
- analysis,
282
- patch: cleanDiff,
283
- explanation: fullExplanation,
284
- filePath: violation.file,
285
- preview: finalPreview,
286
- confidence,
287
- testable: true
288
- };
289
-
290
- } catch (error) {
291
- this.categoryLogger.error('Failed to parse fix response', { error });
292
-
293
- // Return a basic fix with the raw content
294
- return {
295
- violation,
296
- analysis,
297
- patch: this.generateBasicDiff(violation.file, content),
298
- explanation: `Fix generated for ${violation.feature} compatibility issue.\n\n${content}`,
299
- filePath: violation.file,
300
- preview: content.substring(0, 500) + (content.length > 500 ? '...' : ''),
301
- confidence: 0.6,
302
- testable: true
303
- };
304
- }
305
- }
306
-
307
- /**
308
- * Extract a specific section from the response
309
- */
310
- private extractSection(content: string, sectionName: string): string {
311
- const regex = new RegExp(`### ${sectionName}\\s*\\n([\\s\\S]*?)(?=\\n### |$)`, 'i');
312
- const match = content.match(regex);
313
-
314
- if (match && match[1]) {
315
- return match[1].trim();
316
- }
317
-
318
- return '';
319
- }
320
-
321
- /**
322
- * Clean and validate diff content
323
- */
324
- private cleanDiffContent(diffContent: string): string {
325
- if (!diffContent) {
326
- return '';
327
- }
328
-
329
- // Remove code block markers if present
330
- let cleaned = diffContent.replace(/^```diff\\n|```$/gm, '').trim();
331
-
332
- // Ensure it starts with proper diff headers
333
- if (!cleaned.startsWith('---') && !cleaned.startsWith('@@')) {
334
- // If it's just the diff content without headers, add basic headers
335
- cleaned = `--- a/file\n+++ b/file\n${cleaned}`;
336
- }
337
-
338
- return cleaned;
339
- }
340
-
341
- /**
342
- * Generate preview from diff content
343
- */
344
- private generatePreviewFromDiff(diffContent: string, originalContent: string): string {
345
- if (!diffContent) {
346
- return 'No preview available';
347
- }
348
-
349
- try {
350
- // Extract added lines from diff
351
- const addedLines = diffContent
352
- .split('\n')
353
- .filter(line => line.startsWith('+') && !line.startsWith('+++'))
354
- .map(line => line.substring(1))
355
- .join('\n');
356
-
357
- if (addedLines) {
358
- return `Changes to be applied:\n\n${addedLines}`;
359
- }
360
- } catch (error) {
361
- // Fallback to showing first part of diff
362
- }
363
-
364
- return diffContent.substring(0, 300) + (diffContent.length > 300 ? '...' : '');
365
- }
366
-
367
- /**
368
- * Calculate confidence score based on response quality
369
- */
370
- private calculateFixConfidence(content: string, diffContent: string): number {
371
- let confidence = 0.5; // Base confidence
372
-
373
- // Check if response has proper structure
374
- if (content.includes('### EXPLANATION')) confidence += 0.1;
375
- if (content.includes('### DIFF')) confidence += 0.1;
376
- if (content.includes('### PREVIEW')) confidence += 0.1;
377
- if (content.includes('### TESTING')) confidence += 0.1;
378
-
379
- // Check diff quality
380
- if (diffContent && diffContent.includes('@@')) confidence += 0.1;
381
- if (diffContent && (diffContent.includes('+') || diffContent.includes('-'))) confidence += 0.1;
382
-
383
- return Math.min(confidence, 0.9); // Cap at 0.9
384
- }
385
-
386
- /**
387
- * Build comprehensive explanation
388
- */
389
- private buildFullExplanation(
390
- violation: Violation,
391
- analysis: Analysis,
392
- explanation: string,
393
- testing: string
394
- ): string {
395
- let fullExplanation = `Fixed ${violation.feature} compatibility issue in ${violation.file}\n\n`;
396
-
397
- if (explanation) {
398
- fullExplanation += `## Fix Approach\n${explanation}\n\n`;
399
- }
400
-
401
- fullExplanation += `## Strategy Applied\n${analysis.fixStrategy}\n\n`;
402
- fullExplanation += `## Browser Support\nThis fix ensures compatibility with ${violation.browser} ${violation.required} and newer versions.\n\n`;
403
-
404
- if (testing) {
405
- fullExplanation += `## Testing\n${testing}\n\n`;
406
- }
407
-
408
- fullExplanation += `## Best Practices Applied\n${analysis.bestPractices.map(practice => `• ${practice}`).join('\n')}`;
409
-
410
- return fullExplanation;
411
- }
412
-
413
- /**
414
- * Generate basic diff as fallback
415
- */
416
- private generateBasicDiff(filePath: string, content: string): string {
417
- return `--- a/${filePath}
418
- +++ b/${filePath}
419
- @@ -1,1 +1,1 @@
420
- -// Original code (manual review required)
421
- +// Fixed code: ${content.substring(0, 100)}${content.length > 100 ? '...' : ''}
422
-
423
- # Manual fix required - please review the generated content above
424
- # and apply the appropriate changes to your code.`;
425
- }
426
-
427
- /**
428
- * Test API connectivity
429
- */
430
- async testConnection(): Promise<{ success: boolean; error?: string; errorType?: ErrorType }> {
431
- try {
432
- const testPrompt = 'Generate a simple "Hello, World!" comment in JavaScript.';
433
- const response = await this.makeApiCall(testPrompt);
434
-
435
- const data = await response.json();
436
-
437
- if (data.candidates && data.candidates.length > 0) {
438
- return { success: true };
439
- } else {
440
- return {
441
- success: false,
442
- error: 'API responded but returned no content',
443
- errorType: ErrorType.VALIDATION
444
- };
445
- }
446
- } catch (error) {
447
- const apiError = ErrorHandler.handleAPIError(error);
448
-
449
- return {
450
- success: false,
451
- error: apiError.message,
452
- errorType: apiError.type
453
- };
454
- }
455
- }
456
-
457
- /**
458
- * Validate API key format
459
- */
460
- public static validateApiKey(apiKey: string): boolean {
461
- // Gemini API keys typically start with 'AIza' and are 39+ characters long
462
- return /^AIza[A-Za-z0-9_-]{35,}$/.test(apiKey) || apiKey.length >= 20;
463
- }
464
-
465
- /**
466
- * Generate multiple fix options for complex issues
467
- */
468
- async generateFixOptions(violation: Violation, analysis: Analysis): Promise<Fix[]> {
469
- const fixes: Fix[] = [];
470
-
471
- try {
472
- // Generate primary fix
473
- const primaryFix = await this.generateFix(violation, analysis);
474
- fixes.push(primaryFix);
475
-
476
- // Generate alternative approaches if confidence is low
477
- if (primaryFix.confidence < 0.7) {
478
- this.categoryLogger.info('Generating alternative fix approaches', {
479
- feature: violation.feature,
480
- primaryConfidence: primaryFix.confidence
481
- });
482
-
483
- const alternativeFix = await this.generateAlternativeFix(violation, analysis);
484
- if (alternativeFix) {
485
- fixes.push(alternativeFix);
486
- }
487
- }
488
-
489
- } catch (error) {
490
- this.categoryLogger.error('Failed to generate fix options', { error });
491
- throw error;
492
- }
493
-
494
- return fixes;
495
- }
496
-
497
- /**
498
- * Generate alternative fix approach
499
- */
500
- private async generateAlternativeFix(violation: Violation, analysis: Analysis): Promise<Fix | null> {
501
- try {
502
- const fileContent = await this.readFileContent(violation.file);
503
-
504
- // Build alternative prompt with different strategy
505
- const alternativePrompt = this.buildAlternativeFixPrompt(violation, analysis, fileContent);
506
-
507
- const response = await this.makeApiCall(alternativePrompt);
508
- const data = await response.json();
509
-
510
- if (data.candidates && data.candidates.length > 0) {
511
- const content = data.candidates[0].content?.parts?.[0]?.text;
512
- if (content) {
513
- const fix = await this.parseFixResponse(content, violation, analysis, fileContent);
514
- fix.explanation = `Alternative approach: ${fix.explanation}`;
515
- return fix;
516
- }
517
- }
518
-
519
- } catch (error) {
520
- this.categoryLogger.warn('Failed to generate alternative fix', { error });
521
- }
522
-
523
- return null;
524
- }
525
-
526
- /**
527
- * Build alternative fix prompt with different strategy
528
- */
529
- private buildAlternativeFixPrompt(violation: Violation, analysis: Analysis, fileContent: string): string {
530
- const alternativeStrategies = {
531
- 'progressive enhancement with @supports': 'feature detection with JavaScript',
532
- 'feature detection with polyfills': 'progressive enhancement with @supports',
533
- 'graceful degradation': 'polyfill-based approach'
534
- };
535
-
536
- const altStrategy = alternativeStrategies[analysis.fixStrategy as keyof typeof alternativeStrategies] || 'alternative progressive enhancement';
537
-
538
- return this.buildFixPrompt(violation, { ...analysis, fixStrategy: altStrategy }, fileContent);
539
- }
540
- }
1
+ import type { Violation, Analysis, Fix } from '../types/index.js';
2
+ import { promises as fs } from 'fs';
3
+ import { ErrorHandler, APIError, ErrorType } from '../core/error-handler.js';
4
+ import { logger } from '../core/debug-logger.js';
5
+
6
+ /**
7
+ * Gemini 2.5 Pro code fixer for local files and repositories
8
+ * Works with any code (GitHub or not, committed or uncommitted)
9
+ */
10
+ export class GeminiCodeFixer {
11
+ private apiKey: string;
12
+ private baseUrl = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent';
13
+ private categoryLogger: ReturnType<typeof logger.createCategoryLogger>;
14
+
15
+ constructor(apiKey: string) {
16
+ this.apiKey = apiKey;
17
+ this.categoryLogger = logger.createCategoryLogger('gemini-code-fixer');
18
+ }
19
+
20
+ /**
21
+ * Generate a fix using Gemini 2.5 Pro with grounding
22
+ */
23
+ async generateFix(violation: Violation, analysis: Analysis): Promise<Fix> {
24
+ const context = ErrorHandler.createContext('gemini_code_fix', {
25
+ feature: violation.feature,
26
+ file: violation.file,
27
+ browser: violation.browser
28
+ });
29
+
30
+ try {
31
+ this.categoryLogger.info('Generating code fix', {
32
+ feature: violation.feature,
33
+ file: violation.file,
34
+ strategy: analysis.fixStrategy
35
+ });
36
+
37
+ // Read the current file content
38
+ const fileContent = await this.readFileContent(violation.file);
39
+
40
+ // Build the fix prompt with file context
41
+ const prompt = this.buildFixPrompt(violation, analysis, fileContent);
42
+
43
+ const response = await ErrorHandler.withRetry(
44
+ () => this.makeApiCall(prompt),
45
+ {
46
+ maxRetries: 2,
47
+ retryableErrors: [ErrorType.NETWORK, ErrorType.TIMEOUT, ErrorType.RATE_LIMIT, ErrorType.SERVER_ERROR]
48
+ }
49
+ );
50
+
51
+ const data = await response.json();
52
+
53
+ // Validate response structure
54
+ ErrorHandler.validateAPIResponse(data, ['candidates']);
55
+
56
+ if (!data.candidates || data.candidates.length === 0) {
57
+ throw new APIError(
58
+ 'No fix candidates returned from Gemini API',
59
+ ErrorType.VALIDATION,
60
+ {
61
+ suggestions: [
62
+ 'Try rephrasing the fix request',
63
+ 'Check if the code is valid',
64
+ 'Try again with a different approach'
65
+ ],
66
+ context
67
+ }
68
+ );
69
+ }
70
+
71
+ const candidate = data.candidates[0];
72
+ const content = candidate.content?.parts?.[0]?.text;
73
+
74
+ if (!content) {
75
+ throw new APIError(
76
+ 'Empty fix content returned from Gemini API',
77
+ ErrorType.VALIDATION,
78
+ {
79
+ suggestions: [
80
+ 'Try the fix generation again',
81
+ 'Check if the violation data is complete',
82
+ 'Verify API key permissions'
83
+ ],
84
+ context
85
+ }
86
+ );
87
+ }
88
+
89
+ // Parse the fix response and generate proper diff
90
+ const fix = await this.parseFixResponse(content, violation, analysis, fileContent);
91
+
92
+ this.categoryLogger.info('Code fix generated successfully', {
93
+ feature: violation.feature,
94
+ confidence: fix.confidence,
95
+ hasPreview: fix.preview.length > 0
96
+ });
97
+
98
+ return fix;
99
+
100
+ } catch (error) {
101
+ const apiError = ErrorHandler.handleAPIError(error, context);
102
+ this.categoryLogger.error('Code fix generation failed', { error: apiError });
103
+ throw apiError;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Make API call to Gemini 2.5 Pro with grounding
109
+ */
110
+ private async makeApiCall(prompt: string): Promise<Response> {
111
+ const response = await fetch(this.baseUrl, {
112
+ method: 'POST',
113
+ headers: {
114
+ 'x-goog-api-key': this.apiKey,
115
+ 'Content-Type': 'application/json'
116
+ },
117
+ body: JSON.stringify({
118
+ contents: [{
119
+ parts: [{ text: prompt }]
120
+ }],
121
+ tools: [{
122
+ googleSearchRetrieval: {
123
+ dynamicRetrievalConfig: {
124
+ mode: 'MODE_DYNAMIC',
125
+ dynamicThreshold: 0.7
126
+ }
127
+ }
128
+ }],
129
+ generationConfig: {
130
+ temperature: 0.1,
131
+ topK: 40,
132
+ topP: 0.95,
133
+ maxOutputTokens: 4096,
134
+ candidateCount: 1
135
+ }
136
+ })
137
+ });
138
+
139
+ if (!response.ok) {
140
+ const error = new Error(`HTTP ${response.status}: ${response.statusText}`);
141
+ (error as any).response = response;
142
+ throw error;
143
+ }
144
+
145
+ return response;
146
+ }
147
+
148
+ /**
149
+ * Build comprehensive fix prompt for Gemini 2.5 Pro
150
+ */
151
+ private buildFixPrompt(violation: Violation, analysis: Analysis, fileContent: string): string {
152
+ return `You are an expert web developer tasked with fixing browser compatibility issues. Generate a precise code fix with proper diff format.
153
+
154
+ ## COMPATIBILITY ISSUE
155
+ - **Feature**: ${violation.feature}
156
+ - **File**: ${violation.file} (line ${violation.line})
157
+ - **Unsupported Browser**: ${violation.browser} ${violation.required}
158
+ - **Baseline Status**: ${violation.baselineStatus}
159
+ - **Current Code Context**: ${violation.context}
160
+
161
+ ## ANALYSIS INSIGHTS
162
+ - **Fix Strategy**: ${analysis.fixStrategy}
163
+ - **User Impact**: ${analysis.userImpact}
164
+ - **Market Share Affected**: ${(analysis.marketShare * 100).toFixed(1)}%
165
+ - **Best Practices**: ${analysis.bestPractices.join(', ')}
166
+
167
+ ## CURRENT FILE CONTENT
168
+ \`\`\`${this.getFileExtension(violation.file)}
169
+ ${fileContent}
170
+ \`\`\`
171
+
172
+ ## REQUIREMENTS
173
+ 1. **Fix the compatibility issue** using ${analysis.fixStrategy}
174
+ 2. **Preserve all existing functionality** - no breaking changes
175
+ 3. **Add proper fallbacks** for ${violation.browser} ${violation.required}
176
+ 4. **Follow best practices**: ${analysis.bestPractices.join(', ')}
177
+ 5. **Generate a unified diff** showing exactly what changes to make
178
+ 6. **Provide clear explanation** of the fix approach
179
+
180
+ ## OUTPUT FORMAT
181
+ Please provide your response in this exact format:
182
+
183
+ ### EXPLANATION
184
+ [Explain the fix approach and why it works]
185
+
186
+ ### DIFF
187
+ \`\`\`diff
188
+ --- a/${violation.file}
189
+ +++ b/${violation.file}
190
+ @@ -[start_line],[num_lines] +[start_line],[num_lines] @@
191
+ [unified diff content showing the exact changes]
192
+ \`\`\`
193
+
194
+ ### PREVIEW
195
+ \`\`\`${this.getFileExtension(violation.file)}
196
+ [Show the key changed sections with context]
197
+ \`\`\`
198
+
199
+ ### TESTING
200
+ [Suggest how to test the fix works in ${violation.browser} ${violation.required}]
201
+
202
+ Focus on creating a production-ready fix that maintains compatibility across all browsers while adding proper support for ${violation.browser} ${violation.required}.`;
203
+ }
204
+
205
+ /**
206
+ * Read file content with error handling
207
+ */
208
+ private async readFileContent(filePath: string): Promise<string> {
209
+ try {
210
+ return await fs.readFile(filePath, 'utf-8');
211
+ } catch (error) {
212
+ this.categoryLogger.warn('Could not read file content', {
213
+ file: filePath,
214
+ error: error instanceof Error ? error.message : 'Unknown error'
215
+ });
216
+
217
+ // Return empty content if file can't be read
218
+ return `// File content could not be read: ${filePath}`;
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Get file extension for syntax highlighting
224
+ */
225
+ private getFileExtension(filePath: string): string {
226
+ const ext = filePath.split('.').pop()?.toLowerCase();
227
+
228
+ const extensionMap: Record<string, string> = {
229
+ 'js': 'javascript',
230
+ 'jsx': 'javascript',
231
+ 'ts': 'typescript',
232
+ 'tsx': 'typescript',
233
+ 'vue': 'vue',
234
+ 'svelte': 'svelte',
235
+ 'css': 'css',
236
+ 'scss': 'scss',
237
+ 'sass': 'sass',
238
+ 'html': 'html',
239
+ 'htm': 'html'
240
+ };
241
+
242
+ return extensionMap[ext || ''] || 'text';
243
+ }
244
+
245
+ /**
246
+ * Parse Gemini's fix response and create Fix object
247
+ */
248
+ private async parseFixResponse(
249
+ content: string,
250
+ violation: Violation,
251
+ analysis: Analysis,
252
+ originalContent: string
253
+ ): Promise<Fix> {
254
+ try {
255
+ // Extract sections from the response
256
+ const explanation = this.extractSection(content, 'EXPLANATION');
257
+ const diffContent = this.extractSection(content, 'DIFF');
258
+ const preview = this.extractSection(content, 'PREVIEW');
259
+ const testing = this.extractSection(content, 'TESTING');
260
+
261
+ // Clean up the diff content
262
+ const cleanDiff = this.cleanDiffContent(diffContent);
263
+
264
+ // Generate a human-readable preview if not provided
265
+ const finalPreview = preview || this.generatePreviewFromDiff(cleanDiff, originalContent);
266
+
267
+ // Calculate confidence based on response quality
268
+ const confidence = this.calculateFixConfidence(content, diffContent);
269
+
270
+ // Create comprehensive explanation
271
+ const fullExplanation = this.buildFullExplanation(
272
+ violation,
273
+ analysis,
274
+ explanation,
275
+ testing
276
+ );
277
+
278
+ return {
279
+ violation,
280
+ analysis,
281
+ patch: cleanDiff,
282
+ explanation: fullExplanation,
283
+ filePath: violation.file,
284
+ preview: finalPreview,
285
+ confidence,
286
+ testable: true
287
+ };
288
+
289
+ } catch (error) {
290
+ this.categoryLogger.error('Failed to parse fix response', { error });
291
+
292
+ // Return a basic fix with the raw content
293
+ return {
294
+ violation,
295
+ analysis,
296
+ patch: this.generateBasicDiff(violation.file, content),
297
+ explanation: `Fix generated for ${violation.feature} compatibility issue.\n\n${content}`,
298
+ filePath: violation.file,
299
+ preview: content.substring(0, 500) + (content.length > 500 ? '...' : ''),
300
+ confidence: 0.6,
301
+ testable: true
302
+ };
303
+ }
304
+ }
305
+
306
+ /**
307
+ * Extract a specific section from the response
308
+ */
309
+ private extractSection(content: string, sectionName: string): string {
310
+ const regex = new RegExp(`### ${sectionName}\\s*\\n([\\s\\S]*?)(?=\\n### |$)`, 'i');
311
+ const match = content.match(regex);
312
+
313
+ if (match && match[1]) {
314
+ return match[1].trim();
315
+ }
316
+
317
+ return '';
318
+ }
319
+
320
+ /**
321
+ * Clean and validate diff content
322
+ */
323
+ private cleanDiffContent(diffContent: string): string {
324
+ if (!diffContent) {
325
+ return '';
326
+ }
327
+
328
+ // Remove code block markers if present
329
+ let cleaned = diffContent.replace(/^```diff\\n|```$/gm, '').trim();
330
+
331
+ // Ensure it starts with proper diff headers
332
+ if (!cleaned.startsWith('---') && !cleaned.startsWith('@@')) {
333
+ // If it's just the diff content without headers, add basic headers
334
+ cleaned = `--- a/file\n+++ b/file\n${cleaned}`;
335
+ }
336
+
337
+ return cleaned;
338
+ }
339
+
340
+ /**
341
+ * Generate preview from diff content
342
+ */
343
+ private generatePreviewFromDiff(diffContent: string, _originalContent: string): string {
344
+ if (!diffContent) {
345
+ return 'No preview available';
346
+ }
347
+
348
+ try {
349
+ // Extract added lines from diff
350
+ const addedLines = diffContent
351
+ .split('\n')
352
+ .filter(line => line.startsWith('+') && !line.startsWith('+++'))
353
+ .map(line => line.substring(1))
354
+ .join('\n');
355
+
356
+ if (addedLines) {
357
+ return `Changes to be applied:\n\n${addedLines}`;
358
+ }
359
+ } catch (error) {
360
+ // Fallback to showing first part of diff
361
+ }
362
+
363
+ return diffContent.substring(0, 300) + (diffContent.length > 300 ? '...' : '');
364
+ }
365
+
366
+ /**
367
+ * Calculate confidence score based on response quality
368
+ */
369
+ private calculateFixConfidence(content: string, diffContent: string): number {
370
+ let confidence = 0.5; // Base confidence
371
+
372
+ // Check if response has proper structure
373
+ if (content.includes('### EXPLANATION')) confidence += 0.1;
374
+ if (content.includes('### DIFF')) confidence += 0.1;
375
+ if (content.includes('### PREVIEW')) confidence += 0.1;
376
+ if (content.includes('### TESTING')) confidence += 0.1;
377
+
378
+ // Check diff quality
379
+ if (diffContent && diffContent.includes('@@')) confidence += 0.1;
380
+ if (diffContent && (diffContent.includes('+') || diffContent.includes('-'))) confidence += 0.1;
381
+
382
+ return Math.min(confidence, 0.9); // Cap at 0.9
383
+ }
384
+
385
+ /**
386
+ * Build comprehensive explanation
387
+ */
388
+ private buildFullExplanation(
389
+ violation: Violation,
390
+ analysis: Analysis,
391
+ explanation: string,
392
+ testing: string
393
+ ): string {
394
+ let fullExplanation = `Fixed ${violation.feature} compatibility issue in ${violation.file}\n\n`;
395
+
396
+ if (explanation) {
397
+ fullExplanation += `## Fix Approach\n${explanation}\n\n`;
398
+ }
399
+
400
+ fullExplanation += `## Strategy Applied\n${analysis.fixStrategy}\n\n`;
401
+ fullExplanation += `## Browser Support\nThis fix ensures compatibility with ${violation.browser} ${violation.required} and newer versions.\n\n`;
402
+
403
+ if (testing) {
404
+ fullExplanation += `## Testing\n${testing}\n\n`;
405
+ }
406
+
407
+ fullExplanation += `## Best Practices Applied\n${analysis.bestPractices.map(practice => `• ${practice}`).join('\n')}`;
408
+
409
+ return fullExplanation;
410
+ }
411
+
412
+ /**
413
+ * Generate basic diff as fallback
414
+ */
415
+ private generateBasicDiff(filePath: string, content: string): string {
416
+ return `--- a/${filePath}
417
+ +++ b/${filePath}
418
+ @@ -1,1 +1,1 @@
419
+ -// Original code (manual review required)
420
+ +// Fixed code: ${content.substring(0, 100)}${content.length > 100 ? '...' : ''}
421
+
422
+ # Manual fix required - please review the generated content above
423
+ # and apply the appropriate changes to your code.`;
424
+ }
425
+
426
+ /**
427
+ * Test API connectivity
428
+ */
429
+ async testConnection(): Promise<{ success: boolean; error?: string; errorType?: ErrorType }> {
430
+ try {
431
+ const testPrompt = 'Generate a simple "Hello, World!" comment in JavaScript.';
432
+ const response = await this.makeApiCall(testPrompt);
433
+
434
+ const data = await response.json();
435
+
436
+ if (data.candidates && data.candidates.length > 0) {
437
+ return { success: true };
438
+ } else {
439
+ return {
440
+ success: false,
441
+ error: 'API responded but returned no content',
442
+ errorType: ErrorType.VALIDATION
443
+ };
444
+ }
445
+ } catch (error) {
446
+ const apiError = ErrorHandler.handleAPIError(error);
447
+
448
+ return {
449
+ success: false,
450
+ error: apiError.message,
451
+ errorType: apiError.type
452
+ };
453
+ }
454
+ }
455
+
456
+ /**
457
+ * Validate API key format
458
+ */
459
+ public static validateApiKey(apiKey: string): boolean {
460
+ // Gemini API keys typically start with 'AIza' and are 39+ characters long
461
+ return /^AIza[A-Za-z0-9_-]{35,}$/.test(apiKey) || apiKey.length >= 20;
462
+ }
463
+
464
+ /**
465
+ * Generate multiple fix options for complex issues
466
+ */
467
+ async generateFixOptions(violation: Violation, analysis: Analysis): Promise<Fix[]> {
468
+ const fixes: Fix[] = [];
469
+
470
+ try {
471
+ // Generate primary fix
472
+ const primaryFix = await this.generateFix(violation, analysis);
473
+ fixes.push(primaryFix);
474
+
475
+ // Generate alternative approaches if confidence is low
476
+ if (primaryFix.confidence < 0.7) {
477
+ this.categoryLogger.info('Generating alternative fix approaches', {
478
+ feature: violation.feature,
479
+ primaryConfidence: primaryFix.confidence
480
+ });
481
+
482
+ const alternativeFix = await this.generateAlternativeFix(violation, analysis);
483
+ if (alternativeFix) {
484
+ fixes.push(alternativeFix);
485
+ }
486
+ }
487
+
488
+ } catch (error) {
489
+ this.categoryLogger.error('Failed to generate fix options', { error });
490
+ throw error;
491
+ }
492
+
493
+ return fixes;
494
+ }
495
+
496
+ /**
497
+ * Generate alternative fix approach
498
+ */
499
+ private async generateAlternativeFix(violation: Violation, analysis: Analysis): Promise<Fix | null> {
500
+ try {
501
+ const fileContent = await this.readFileContent(violation.file);
502
+
503
+ // Build alternative prompt with different strategy
504
+ const alternativePrompt = this.buildAlternativeFixPrompt(violation, analysis, fileContent);
505
+
506
+ const response = await this.makeApiCall(alternativePrompt);
507
+ const data = await response.json();
508
+
509
+ if (data.candidates && data.candidates.length > 0) {
510
+ const content = data.candidates[0].content?.parts?.[0]?.text;
511
+ if (content) {
512
+ const fix = await this.parseFixResponse(content, violation, analysis, fileContent);
513
+ fix.explanation = `Alternative approach: ${fix.explanation}`;
514
+ return fix;
515
+ }
516
+ }
517
+
518
+ } catch (error) {
519
+ this.categoryLogger.warn('Failed to generate alternative fix', { error });
520
+ }
521
+
522
+ return null;
523
+ }
524
+
525
+ /**
526
+ * Build alternative fix prompt with different strategy
527
+ */
528
+ private buildAlternativeFixPrompt(violation: Violation, analysis: Analysis, fileContent: string): string {
529
+ const alternativeStrategies = {
530
+ 'progressive enhancement with @supports': 'feature detection with JavaScript',
531
+ 'feature detection with polyfills': 'progressive enhancement with @supports',
532
+ 'graceful degradation': 'polyfill-based approach'
533
+ };
534
+
535
+ const altStrategy = alternativeStrategies[analysis.fixStrategy as keyof typeof alternativeStrategies] || 'alternative progressive enhancement';
536
+
537
+ return this.buildFixPrompt(violation, { ...analysis, fixStrategy: altStrategy }, fileContent);
538
+ }
539
+ }