baseguard 1.0.3 → 1.0.5

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 +627 -627
  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 +48 -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 +8 -5
  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.map +1 -1
  67. package/dist/core/startup-optimizer.js +4 -8
  68. package/dist/core/startup-optimizer.js.map +1 -1
  69. package/dist/core/system-error-handler.d.ts.map +1 -1
  70. package/dist/core/system-error-handler.js.map +1 -1
  71. package/dist/git/automation-engine.d.ts.map +1 -1
  72. package/dist/git/automation-engine.js +5 -4
  73. package/dist/git/automation-engine.js.map +1 -1
  74. package/dist/git/github-manager.d.ts.map +1 -1
  75. package/dist/git/github-manager.js.map +1 -1
  76. package/dist/git/hook-manager.js +5 -5
  77. package/dist/git/hook-manager.js.map +1 -1
  78. package/dist/parsers/parser-manager.d.ts.map +1 -1
  79. package/dist/parsers/parser-manager.js +1 -1
  80. package/dist/parsers/parser-manager.js.map +1 -1
  81. package/dist/parsers/svelte-parser.js +1 -1
  82. package/dist/parsers/svelte-parser.js.map +1 -1
  83. package/dist/parsers/vanilla-parser.d.ts.map +1 -1
  84. package/dist/parsers/vanilla-parser.js.map +1 -1
  85. package/dist/parsers/vue-parser.d.ts.map +1 -1
  86. package/dist/parsers/vue-parser.js.map +1 -1
  87. package/dist/ui/components.d.ts +1 -1
  88. package/dist/ui/components.d.ts.map +1 -1
  89. package/dist/ui/components.js +11 -11
  90. package/dist/ui/components.js.map +1 -1
  91. package/dist/ui/terminal-header.js +14 -14
  92. package/package.json +105 -105
  93. package/src/ai/__tests__/gemini-analyzer.test.ts +180 -180
  94. package/src/ai/agentkit-orchestrator.ts +533 -533
  95. package/src/ai/fix-manager.ts +362 -362
  96. package/src/ai/gemini-analyzer.ts +665 -671
  97. package/src/ai/gemini-code-fixer.ts +539 -540
  98. package/src/ai/index.ts +3 -3
  99. package/src/ai/jules-implementer.ts +504 -460
  100. package/src/ai/unified-code-fixer.ts +347 -347
  101. package/src/commands/automation.ts +343 -343
  102. package/src/commands/check.ts +298 -299
  103. package/src/commands/config.ts +584 -583
  104. package/src/commands/fix.ts +269 -238
  105. package/src/commands/index.ts +6 -6
  106. package/src/commands/init.ts +155 -155
  107. package/src/commands/status.ts +306 -306
  108. package/src/core/api-key-manager.ts +298 -298
  109. package/src/core/baseguard.ts +757 -756
  110. package/src/core/baseline-checker.ts +566 -563
  111. package/src/core/cache-manager.ts +271 -271
  112. package/src/core/configuration-recovery.ts +672 -673
  113. package/src/core/configuration.ts +595 -595
  114. package/src/core/debug-logger.ts +590 -590
  115. package/src/core/directory-filter.ts +420 -420
  116. package/src/core/error-handler.ts +518 -517
  117. package/src/core/file-processor.ts +337 -337
  118. package/src/core/gitignore-manager.ts +168 -168
  119. package/src/core/graceful-degradation-manager.ts +596 -596
  120. package/src/core/index.ts +16 -16
  121. package/src/core/lazy-loader.ts +317 -307
  122. package/src/core/memory-manager.ts +290 -295
  123. package/src/core/parser-worker.ts +33 -0
  124. package/src/core/startup-optimizer.ts +246 -255
  125. package/src/core/system-error-handler.ts +755 -756
  126. package/src/git/automation-engine.ts +361 -361
  127. package/src/git/github-manager.ts +190 -192
  128. package/src/git/hook-manager.ts +210 -210
  129. package/src/git/index.ts +3 -3
  130. package/src/index.ts +7 -7
  131. package/src/parsers/feature-validator.ts +558 -558
  132. package/src/parsers/index.ts +7 -7
  133. package/src/parsers/parser-manager.ts +418 -419
  134. package/src/parsers/parser.ts +25 -25
  135. package/src/parsers/react-parser-optimized.ts +160 -160
  136. package/src/parsers/react-parser.ts +358 -358
  137. package/src/parsers/svelte-parser.ts +510 -510
  138. package/src/parsers/vanilla-parser.ts +685 -686
  139. package/src/parsers/vue-parser.ts +476 -478
  140. package/src/types/index.ts +95 -95
  141. package/src/ui/components.ts +567 -567
  142. package/src/ui/help.ts +192 -192
  143. package/src/ui/index.ts +3 -3
  144. package/src/ui/prompts.ts +680 -680
  145. package/src/ui/terminal-header.ts +58 -58
  146. package/test-build.js +40 -40
  147. package/test-config-commands.js +55 -55
  148. package/test-header-simple.js +32 -32
  149. package/test-terminal-header.js +11 -11
  150. package/test-ui.js +28 -28
  151. package/tests/e2e/baseguard.e2e.test.ts +515 -515
  152. package/tests/e2e/cross-platform.e2e.test.ts +419 -419
  153. package/tests/e2e/git-integration.e2e.test.ts +486 -486
  154. package/tests/fixtures/react-project/package.json +13 -13
  155. package/tests/fixtures/react-project/src/App.css +75 -75
  156. package/tests/fixtures/react-project/src/App.tsx +76 -76
  157. package/tests/fixtures/svelte-project/package.json +10 -10
  158. package/tests/fixtures/svelte-project/src/App.svelte +368 -368
  159. package/tests/fixtures/vanilla-project/index.html +75 -75
  160. package/tests/fixtures/vanilla-project/script.js +330 -330
  161. package/tests/fixtures/vanilla-project/styles.css +358 -358
  162. package/tests/fixtures/vue-project/package.json +11 -11
  163. package/tests/fixtures/vue-project/src/App.vue +215 -215
  164. package/tmp-smoke/.baseguard/backups/config-2026-02-19T12-04-11-067Z-auto.json +30 -0
  165. package/tmp-smoke/src/bad.css +3 -0
  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,460 +1,504 @@
1
- import type { Violation, Analysis, Fix, JulesSession, JulesActivity } from '../types/index.js';
2
- import { GitHubManager } from '../git/github-manager.js';
3
- import { FixManager } from './fix-manager.js';
4
- import { ErrorHandler, APIError, ErrorType } from '../core/error-handler.js';
5
- import chalk from 'chalk';
6
-
7
- /**
8
- * Jules AI implementer for autonomous code fixing
9
- * Note: Jules requires GitHub repositories and cannot work with local files
10
- */
11
- export class JulesImplementer {
12
- private apiKey: string;
13
- private baseUrl = 'https://jules.googleapis.com/v1alpha';
14
- private githubManager: GitHubManager;
15
- private fixManager: FixManager;
16
-
17
- constructor(apiKey: string) {
18
- this.apiKey = apiKey;
19
- this.githubManager = new GitHubManager();
20
- this.fixManager = new FixManager();
21
- }
22
-
23
- /**
24
- * Generate a fix using Jules AI
25
- */
26
- async generateFix(violation: Violation, analysis: Analysis, repoSource?: string): Promise<Fix> {
27
- const context = ErrorHandler.createContext('jules_fix_generation', {
28
- feature: violation.feature,
29
- file: violation.file,
30
- browser: violation.browser
31
- });
32
-
33
- try {
34
- // Get repository source if not provided
35
- const source = repoSource || await this.githubManager.getCurrentSourceIdentifier();
36
-
37
- // Create a session for this specific fix with retry logic
38
- const session = await ErrorHandler.withRetry(
39
- () => this.createSession(violation, analysis, source),
40
- {
41
- maxRetries: 2,
42
- retryableErrors: [ErrorType.NETWORK, ErrorType.TIMEOUT, ErrorType.RATE_LIMIT, ErrorType.SERVER_ERROR]
43
- }
44
- );
45
-
46
- // Monitor session activities until completion
47
- const activities = await this.waitForCompletion(session.id);
48
-
49
- // Extract the generated code changes
50
- const fix = await this.extractFix(session.id, activities, violation, analysis);
51
-
52
- return fix;
53
- } catch (error) {
54
- const apiError = ErrorHandler.handleAPIError(error, context);
55
-
56
- // Log error for debugging
57
- console.error('Jules fix generation failed:', {
58
- feature: violation.feature,
59
- error: apiError.message,
60
- type: apiError.type
61
- });
62
-
63
- // Re-throw with proper error handling
64
- throw apiError;
65
- }
66
- }
67
-
68
- /**
69
- * Create a Jules session for fixing
70
- */
71
- private async createSession(violation: Violation, analysis: Analysis, source: string): Promise<JulesSession> {
72
- const prompt = this.buildFixPrompt(violation, analysis);
73
-
74
- const response = await fetch(`${this.baseUrl}/sessions`, {
75
- method: 'POST',
76
- headers: {
77
- 'Content-Type': 'application/json',
78
- 'X-Goog-Api-Key': this.apiKey
79
- },
80
- body: JSON.stringify({
81
- prompt: prompt,
82
- sourceContext: {
83
- source: source, // e.g., "sources/github/user/repo"
84
- githubRepoContext: {
85
- startingBranch: "main"
86
- }
87
- },
88
- title: `Fix ${violation.feature} compatibility in ${violation.file}`,
89
- requirePlanApproval: false // Auto-approve for BaseGuard automation
90
- })
91
- });
92
-
93
- if (!response.ok) {
94
- const errorText = await response.text();
95
- const error = new Error(`Jules API error: ${response.status} ${response.statusText} - ${errorText}`);
96
- (error as any).response = response;
97
- throw error;
98
- }
99
-
100
- const sessionData = await response.json();
101
-
102
- // Validate session response
103
- ErrorHandler.validateAPIResponse(sessionData, ['id']);
104
-
105
- return sessionData;
106
- }
107
-
108
- /**
109
- * Wait for Jules session completion
110
- */
111
- private async waitForCompletion(sessionId: string): Promise<JulesActivity[]> {
112
- let attempts = 0;
113
- const maxAttempts = 30; // 5 minutes max (10 seconds * 30)
114
-
115
- while (attempts < maxAttempts) {
116
- const activities = await this.getActivities(sessionId);
117
- const lastActivity = activities[activities.length - 1];
118
-
119
- // Check if session is complete
120
- if (lastActivity?.status === 'completed' || lastActivity?.type === 'agent_finished') {
121
- return activities;
122
- }
123
-
124
- // Check for failure states
125
- if (lastActivity?.status === 'failed' || lastActivity?.status === 'error') {
126
- throw new Error(`Jules session failed: ${lastActivity.status}`);
127
- }
128
-
129
- // Wait 10 seconds before checking again
130
- await new Promise(resolve => setTimeout(resolve, 10000));
131
- attempts++;
132
- }
133
-
134
- throw new Error('Jules session timed out after 5 minutes');
135
- }
136
-
137
- /**
138
- * Get activities for a Jules session
139
- */
140
- private async getActivities(sessionId: string): Promise<JulesActivity[]> {
141
- const response = await fetch(`${this.baseUrl}/sessions/${sessionId}/activities`, {
142
- headers: {
143
- 'X-Goog-Api-Key': this.apiKey
144
- }
145
- });
146
-
147
- if (!response.ok) {
148
- const error = new Error(`Failed to get activities: ${response.status} ${response.statusText}`);
149
- (error as any).response = response;
150
- throw error;
151
- }
152
-
153
- const data = await response.json();
154
-
155
- // Validate response structure
156
- if (!data || typeof data !== 'object') {
157
- throw new APIError(
158
- 'Invalid activities response from Jules API',
159
- ErrorType.VALIDATION,
160
- {
161
- suggestions: [
162
- 'Check Jules API service status',
163
- 'Verify session ID is valid',
164
- 'Try the request again'
165
- ]
166
- }
167
- );
168
- }
169
-
170
- return data.activities || [];
171
- }
172
-
173
- /**
174
- * Extract fix from completed Jules session
175
- */
176
- private async extractFix(sessionId: string, activities: JulesActivity[], violation: Violation, analysis: Analysis): Promise<Fix> {
177
- // Get the final session state to extract code changes
178
- const sessionResponse = await fetch(`${this.baseUrl}/sessions/${sessionId}`, {
179
- headers: {
180
- 'X-Goog-Api-Key': this.apiKey
181
- }
182
- });
183
-
184
- if (!sessionResponse.ok) {
185
- throw new Error(`Failed to get session details: ${sessionResponse.status}`);
186
- }
187
-
188
- const sessionData = await sessionResponse.json();
189
-
190
- // Extract code changes from activities or session data
191
- const codeChanges = this.extractCodeChanges(activities, sessionData);
192
-
193
- // Generate unified diff patch
194
- const patch = this.generateUnifiedDiff(violation.file, codeChanges);
195
-
196
- return {
197
- violation,
198
- analysis,
199
- patch,
200
- explanation: this.generateFixExplanation(violation, analysis, codeChanges),
201
- filePath: violation.file,
202
- preview: this.generatePreview(codeChanges),
203
- confidence: 0.8, // Jules confidence score
204
- testable: true
205
- };
206
- }
207
-
208
- /**
209
- * Extract code changes from Jules activities and session data
210
- */
211
- private extractCodeChanges(activities: JulesActivity[], sessionData: any): { original: string; modified: string } {
212
- // Look for code changes in activities
213
- const codeActivity = activities.find(activity =>
214
- activity.type === 'code_change' || activity.type === 'file_edit'
215
- );
216
-
217
- if (codeActivity) {
218
- // Extract from activity data (implementation depends on Jules API response format)
219
- return {
220
- original: sessionData.originalCode || '',
221
- modified: sessionData.modifiedCode || ''
222
- };
223
- }
224
-
225
- // Fallback: extract from session data
226
- return {
227
- original: sessionData.originalCode || '',
228
- modified: sessionData.modifiedCode || ''
229
- };
230
- }
231
-
232
- /**
233
- * Generate unified diff patch
234
- */
235
- private generateUnifiedDiff(filePath: string, changes: { original: string; modified: string }): string {
236
- const originalLines = changes.original.split('\n');
237
- const modifiedLines = changes.modified.split('\n');
238
-
239
- // Simple diff generation (in production, use a proper diff library)
240
- let patch = `--- a/${filePath}\n+++ b/${filePath}\n`;
241
-
242
- // Find differences and generate patch format
243
- for (let i = 0; i < Math.max(originalLines.length, modifiedLines.length); i++) {
244
- const originalLine = originalLines[i] || '';
245
- const modifiedLine = modifiedLines[i] || '';
246
-
247
- if (originalLine !== modifiedLine) {
248
- if (originalLine) {
249
- patch += `-${originalLine}\n`;
250
- }
251
- if (modifiedLine) {
252
- patch += `+${modifiedLine}\n`;
253
- }
254
- }
255
- }
256
-
257
- return patch;
258
- }
259
-
260
- /**
261
- * Generate fix explanation
262
- */
263
- private generateFixExplanation(violation: Violation, analysis: Analysis, changes: { original: string; modified: string }): string {
264
- return `Fixed ${violation.feature} compatibility issue in ${violation.file}:\n\n` +
265
- `- Added progressive enhancement using ${analysis.fixStrategy}\n` +
266
- `- Implemented fallback for ${violation.browser} ${violation.required}\n` +
267
- `- Applied best practices: ${analysis.bestPractices.join(', ')}\n\n` +
268
- `This fix ensures the feature works across all target browsers while maintaining the original functionality.`;
269
- }
270
-
271
- /**
272
- * Generate human-readable preview
273
- */
274
- private generatePreview(changes: { original: string; modified: string }): string {
275
- const originalLines = changes.original.split('\n');
276
- const modifiedLines = changes.modified.split('\n');
277
-
278
- let preview = 'Changes:\n';
279
-
280
- for (let i = 0; i < Math.max(originalLines.length, modifiedLines.length); i++) {
281
- const originalLine = originalLines[i];
282
- const modifiedLine = modifiedLines[i];
283
-
284
- if (originalLine !== modifiedLine) {
285
- if (originalLine) {
286
- preview += `- ${originalLine}\n`;
287
- }
288
- if (modifiedLine) {
289
- preview += `+ ${modifiedLine}\n`;
290
- }
291
- }
292
- }
293
-
294
- return preview;
295
- }
296
-
297
- /**
298
- * Build fix prompt for Jules
299
- */
300
- private buildFixPrompt(violation: Violation, analysis: Analysis): string {
301
- return `Fix browser compatibility issue in ${violation.file}:
302
-
303
- ISSUE:
304
- - Feature: ${violation.feature} (line ${violation.line})
305
- - Unsupported in: ${violation.browser} ${violation.required}
306
- - Baseline Status: ${violation.baselineStatus}
307
- - Current code: ${violation.context}
308
-
309
- ANALYSIS:
310
- ${analysis.plainEnglish}
311
-
312
- FIX STRATEGY:
313
- ${analysis.fixStrategy}
314
-
315
- REQUIREMENTS:
316
- 1. Implement progressive enhancement using @supports for CSS features
317
- 2. Use feature detection for JavaScript APIs
318
- 3. Add appropriate fallbacks for older browsers
319
- 4. Preserve all original functionality
320
- 5. Follow best practices: ${analysis.bestPractices.join(', ')}
321
- 6. Ensure the fix works in ${violation.browser} ${violation.required} and newer versions
322
-
323
- Please fix this compatibility issue while maintaining the existing functionality. The fix should be production-ready and follow web standards.`;
324
- }
325
-
326
- /**
327
- * Test Jules API connectivity
328
- */
329
- async testConnection(): Promise<{ success: boolean; error?: string; errorType?: ErrorType }> {
330
- try {
331
- const response = await fetch(`${this.baseUrl}/sessions`, {
332
- method: 'GET',
333
- headers: {
334
- 'X-Goog-Api-Key': this.apiKey
335
- }
336
- });
337
-
338
- if (response.ok || response.status === 404) {
339
- // 404 is acceptable for sessions endpoint when no sessions exist
340
- return { success: true };
341
- } else {
342
- const error = new Error(`HTTP ${response.status}: ${response.statusText}`);
343
- (error as any).response = response;
344
- throw error;
345
- }
346
- } catch (error) {
347
- const apiError = ErrorHandler.handleAPIError(error);
348
-
349
- return {
350
- success: false,
351
- error: apiError.message,
352
- errorType: apiError.type
353
- };
354
- }
355
- }
356
-
357
- /**
358
- * Get repository source identifier (GitHub integration is handled on Jules dashboard)
359
- */
360
- async getRepositorySource(): Promise<string> {
361
- return await this.githubManager.getSourceIdentifier();
362
- }
363
-
364
- /**
365
- * Check if repository information is available (GitHub integration is handled on Jules dashboard)
366
- */
367
- async isRepositoryDetected(): Promise<boolean> {
368
- return await this.githubManager.isJulesIntegrationSetup();
369
- }
370
-
371
- /**
372
- * Verify GitHub connection and permissions
373
- */
374
- async verifyGitHubConnection(): Promise<boolean> {
375
- return await this.githubManager.verifyGitHubConnection();
376
- }
377
-
378
- /**
379
- * Get repository information
380
- */
381
- getRepositoryInfo(): { owner: string | null; name: string | null } {
382
- return this.githubManager.getRepositoryInfo();
383
- }
384
-
385
- /**
386
- * Get current source identifier
387
- */
388
- async getSourceIdentifier(): Promise<string> {
389
- return await this.githubManager.getCurrentSourceIdentifier();
390
- }
391
-
392
- /**
393
- * Generate and apply fixes with interactive preview
394
- */
395
- async generateAndApplyFixes(violations: Violation[], analyses: Analysis[]): Promise<{ applied: Fix[]; skipped: Fix[]; failed: { fix: Fix; error: string }[] }> {
396
- const fixes: Fix[] = [];
397
-
398
- // Generate fixes for each violation
399
- for (let i = 0; i < violations.length; i++) {
400
- const violation = violations[i];
401
- const analysis = analyses[i];
402
-
403
- if (!violation || !analysis) {
404
- console.log(chalk.red(`āŒ Missing violation or analysis data for index ${i}`));
405
- continue;
406
- }
407
-
408
- try {
409
- console.log(chalk.cyan(`\nšŸ”§ Generating fix ${i + 1}/${violations.length} for ${violation.feature}...`));
410
- const fix = await this.generateFix(violation, analysis);
411
- fixes.push(fix);
412
- } catch (error) {
413
- console.log(chalk.red(`āŒ Failed to generate fix for ${violation.feature}: ${error instanceof Error ? error.message : 'Unknown error'}`));
414
- }
415
- }
416
-
417
- if (fixes.length === 0) {
418
- console.log(chalk.yellow('āš ļø No fixes were generated'));
419
- return { applied: [], skipped: [], failed: [] };
420
- }
421
-
422
- // Apply fixes with interactive preview
423
- return await this.fixManager.applyFixes(fixes);
424
- }
425
-
426
- /**
427
- * Preview a single fix
428
- */
429
- async previewFix(fix: Fix): Promise<string> {
430
- return await this.fixManager.generatePreview(fix);
431
- }
432
-
433
- /**
434
- * Apply a single fix
435
- */
436
- async applySingleFix(fix: Fix): Promise<void> {
437
- await this.fixManager.applyFix(fix);
438
- }
439
-
440
- /**
441
- * Rollback applied fixes
442
- */
443
- async rollbackFix(filePath: string): Promise<void> {
444
- await this.fixManager.rollbackFix(filePath);
445
- }
446
-
447
- /**
448
- * Rollback all applied fixes
449
- */
450
- async rollbackAllFixes(): Promise<void> {
451
- await this.fixManager.rollbackAllFixes();
452
- }
453
-
454
- /**
455
- * Get list of applied fixes
456
- */
457
- getAppliedFixes(): string[] {
458
- return this.fixManager.getAppliedFixes();
459
- }
460
- }
1
+ import type { Violation, Analysis, Fix, JulesSession, JulesActivity } from '../types/index.js';
2
+ import { GitHubManager } from '../git/github-manager.js';
3
+ import { FixManager } from './fix-manager.js';
4
+ import { ErrorHandler, APIError, ErrorType } from '../core/error-handler.js';
5
+ import chalk from 'chalk';
6
+
7
+ /**
8
+ * Jules AI implementer for autonomous code fixing
9
+ * Note: Jules requires GitHub repositories and cannot work with local files
10
+ */
11
+ export class JulesImplementer {
12
+ private apiKey: string;
13
+ private baseUrl = 'https://jules.googleapis.com/v1alpha';
14
+ private githubManager: GitHubManager;
15
+ private fixManager: FixManager;
16
+
17
+ constructor(apiKey: string) {
18
+ this.apiKey = apiKey;
19
+ this.githubManager = new GitHubManager();
20
+ this.fixManager = new FixManager();
21
+ }
22
+
23
+ /**
24
+ * Generate a fix using Jules AI
25
+ */
26
+ async generateFix(violation: Violation, analysis: Analysis, repoSource?: string): Promise<Fix> {
27
+ const context = ErrorHandler.createContext('jules_fix_generation', {
28
+ feature: violation.feature,
29
+ file: violation.file,
30
+ browser: violation.browser
31
+ });
32
+
33
+ try {
34
+ // Get repository source if not provided
35
+ const source = repoSource || await this.githubManager.getCurrentSourceIdentifier();
36
+
37
+ // Create a session for this specific fix with retry logic
38
+ const session = await ErrorHandler.withRetry(
39
+ () => this.createSession(violation, analysis, source),
40
+ {
41
+ maxRetries: 2,
42
+ retryableErrors: [ErrorType.NETWORK, ErrorType.TIMEOUT, ErrorType.RATE_LIMIT, ErrorType.SERVER_ERROR]
43
+ }
44
+ );
45
+
46
+ // Monitor session activities until completion
47
+ const activities = await this.waitForCompletion(session.id);
48
+
49
+ // Extract the generated code changes
50
+ const fix = await this.extractFix(session.id, activities, violation, analysis);
51
+
52
+ return fix;
53
+ } catch (error) {
54
+ const apiError = ErrorHandler.handleAPIError(error, context);
55
+
56
+ // Log error for debugging
57
+ console.error('Jules fix generation failed:', {
58
+ feature: violation.feature,
59
+ error: apiError.message,
60
+ type: apiError.type
61
+ });
62
+
63
+ // Re-throw with proper error handling
64
+ throw apiError;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Create a Jules session for fixing
70
+ */
71
+ private async createSession(violation: Violation, analysis: Analysis, source: string): Promise<JulesSession> {
72
+ const prompt = this.buildFixPrompt(violation, analysis);
73
+
74
+ const response = await fetch(`${this.baseUrl}/sessions`, {
75
+ method: 'POST',
76
+ headers: {
77
+ 'Content-Type': 'application/json',
78
+ 'X-Goog-Api-Key': this.apiKey
79
+ },
80
+ body: JSON.stringify({
81
+ prompt: prompt,
82
+ sourceContext: {
83
+ source: source, // e.g., "sources/github/user/repo"
84
+ githubRepoContext: {
85
+ startingBranch: "main"
86
+ }
87
+ },
88
+ title: `Fix ${violation.feature} compatibility in ${violation.file}`,
89
+ requirePlanApproval: false // Auto-approve for BaseGuard automation
90
+ })
91
+ });
92
+
93
+ if (!response.ok) {
94
+ const errorText = await response.text();
95
+ const error = new Error(`Jules API error: ${response.status} ${response.statusText} - ${errorText}`);
96
+ (error as any).response = response;
97
+ throw error;
98
+ }
99
+
100
+ const sessionData = await response.json();
101
+
102
+ // Validate session response
103
+ ErrorHandler.validateAPIResponse(sessionData, ['id']);
104
+
105
+ return sessionData;
106
+ }
107
+
108
+ /**
109
+ * Wait for Jules session completion
110
+ */
111
+ private async waitForCompletion(sessionId: string): Promise<JulesActivity[]> {
112
+ let attempts = 0;
113
+ const maxAttempts = 30; // 5 minutes max (10 seconds * 30)
114
+
115
+ while (attempts < maxAttempts) {
116
+ const activities = await this.getActivities(sessionId);
117
+ const lastActivity = activities[activities.length - 1];
118
+
119
+ // Check if session is complete
120
+ if (lastActivity && this.isCompletionActivity(lastActivity)) {
121
+ return activities;
122
+ }
123
+
124
+ // Check for failure states
125
+ if (lastActivity && this.isFailureActivity(lastActivity)) {
126
+ throw new Error(`Jules session failed: ${JSON.stringify(lastActivity)}`);
127
+ }
128
+
129
+ // Wait 10 seconds before checking again
130
+ await new Promise(resolve => setTimeout(resolve, 10000));
131
+ attempts++;
132
+ }
133
+
134
+ throw new Error('Jules session timed out after 5 minutes');
135
+ }
136
+
137
+ private isCompletionActivity(activity: any): boolean {
138
+ return (
139
+ activity?.type === 'sessionCompleted' ||
140
+ activity?.type === 'agent_finished' ||
141
+ activity?.status === 'completed' ||
142
+ Boolean(activity?.sessionCompleted)
143
+ );
144
+ }
145
+
146
+ private isFailureActivity(activity: any): boolean {
147
+ return (
148
+ activity?.type === 'sessionFailed' ||
149
+ activity?.status === 'failed' ||
150
+ activity?.status === 'error' ||
151
+ Boolean(activity?.sessionFailed)
152
+ );
153
+ }
154
+
155
+ /**
156
+ * Get activities for a Jules session
157
+ */
158
+ private async getActivities(sessionId: string): Promise<JulesActivity[]> {
159
+ const response = await fetch(`${this.baseUrl}/sessions/${sessionId}/activities`, {
160
+ headers: {
161
+ 'X-Goog-Api-Key': this.apiKey
162
+ }
163
+ });
164
+
165
+ if (!response.ok) {
166
+ const error = new Error(`Failed to get activities: ${response.status} ${response.statusText}`);
167
+ (error as any).response = response;
168
+ throw error;
169
+ }
170
+
171
+ const data = await response.json();
172
+
173
+ // Validate response structure
174
+ if (!data || typeof data !== 'object') {
175
+ throw new APIError(
176
+ 'Invalid activities response from Jules API',
177
+ ErrorType.VALIDATION,
178
+ {
179
+ suggestions: [
180
+ 'Check Jules API service status',
181
+ 'Verify session ID is valid',
182
+ 'Try the request again'
183
+ ]
184
+ }
185
+ );
186
+ }
187
+
188
+ return data.activities || [];
189
+ }
190
+
191
+ /**
192
+ * Extract fix from completed Jules session
193
+ */
194
+ private async extractFix(sessionId: string, activities: JulesActivity[], violation: Violation, analysis: Analysis): Promise<Fix> {
195
+ // Get the final session state to extract code changes
196
+ const sessionResponse = await fetch(`${this.baseUrl}/sessions/${sessionId}`, {
197
+ headers: {
198
+ 'X-Goog-Api-Key': this.apiKey
199
+ }
200
+ });
201
+
202
+ if (!sessionResponse.ok) {
203
+ throw new Error(`Failed to get session details: ${sessionResponse.status}`);
204
+ }
205
+
206
+ const sessionData = await sessionResponse.json();
207
+
208
+ // Prefer official Jules patch artifact when available
209
+ const patch = this.extractPatchFromActivities(activities, sessionData);
210
+ const codeChanges = this.extractCodeChanges(activities, sessionData);
211
+ const finalPatch = patch || this.generateUnifiedDiff(violation.file, codeChanges);
212
+
213
+ return {
214
+ violation,
215
+ analysis,
216
+ patch: finalPatch,
217
+ explanation: this.generateFixExplanation(violation, analysis, codeChanges),
218
+ filePath: violation.file,
219
+ preview: this.generatePreview(codeChanges, finalPatch),
220
+ confidence: 0.8, // Jules confidence score
221
+ testable: true
222
+ };
223
+ }
224
+
225
+ private extractPatchFromActivities(activities: JulesActivity[], sessionData: any): string {
226
+ const activityPatch = activities
227
+ .map((activity: any) =>
228
+ activity?.changeSet?.gitPatch?.unidiffPatch ||
229
+ activity?.changeSet?.gitPatch?.patch ||
230
+ activity?.gitPatch?.unidiffPatch ||
231
+ activity?.gitPatch?.patch
232
+ )
233
+ .find((patch: string | undefined) => typeof patch === 'string' && patch.length > 0);
234
+
235
+ if (activityPatch) {
236
+ return activityPatch;
237
+ }
238
+
239
+ return (
240
+ sessionData?.changeSet?.gitPatch?.unidiffPatch ||
241
+ sessionData?.changeSet?.gitPatch?.patch ||
242
+ sessionData?.latestChangeSet?.gitPatch?.unidiffPatch ||
243
+ sessionData?.latestChangeSet?.gitPatch?.patch ||
244
+ ''
245
+ );
246
+ }
247
+
248
+ /**
249
+ * Extract code changes from Jules activities and session data
250
+ */
251
+ private extractCodeChanges(activities: JulesActivity[], sessionData: any): { original: string; modified: string } {
252
+ // Look for code changes in activities
253
+ const codeActivity = activities.find(activity =>
254
+ activity.type === 'code_change' || activity.type === 'file_edit'
255
+ );
256
+
257
+ if (codeActivity) {
258
+ // Extract from activity data (implementation depends on Jules API response format)
259
+ return {
260
+ original: sessionData.originalCode || '',
261
+ modified: sessionData.modifiedCode || ''
262
+ };
263
+ }
264
+
265
+ // Fallback: extract from session data
266
+ return {
267
+ original: sessionData.originalCode || '',
268
+ modified: sessionData.modifiedCode || ''
269
+ };
270
+ }
271
+
272
+ /**
273
+ * Generate unified diff patch
274
+ */
275
+ private generateUnifiedDiff(filePath: string, changes: { original: string; modified: string }): string {
276
+ const originalLines = changes.original.split('\n');
277
+ const modifiedLines = changes.modified.split('\n');
278
+
279
+ // Simple diff generation (in production, use a proper diff library)
280
+ let patch = `--- a/${filePath}\n+++ b/${filePath}\n`;
281
+
282
+ // Find differences and generate patch format
283
+ for (let i = 0; i < Math.max(originalLines.length, modifiedLines.length); i++) {
284
+ const originalLine = originalLines[i] || '';
285
+ const modifiedLine = modifiedLines[i] || '';
286
+
287
+ if (originalLine !== modifiedLine) {
288
+ if (originalLine) {
289
+ patch += `-${originalLine}\n`;
290
+ }
291
+ if (modifiedLine) {
292
+ patch += `+${modifiedLine}\n`;
293
+ }
294
+ }
295
+ }
296
+
297
+ return patch;
298
+ }
299
+
300
+ /**
301
+ * Generate fix explanation
302
+ */
303
+ private generateFixExplanation(violation: Violation, analysis: Analysis, _changes: { original: string; modified: string }): string {
304
+ return `Fixed ${violation.feature} compatibility issue in ${violation.file}:\n\n` +
305
+ `- Added progressive enhancement using ${analysis.fixStrategy}\n` +
306
+ `- Implemented fallback for ${violation.browser} ${violation.required}\n` +
307
+ `- Applied best practices: ${analysis.bestPractices.join(', ')}\n\n` +
308
+ `This fix ensures the feature works across all target browsers while maintaining the original functionality.`;
309
+ }
310
+
311
+ /**
312
+ * Generate human-readable preview
313
+ */
314
+ private generatePreview(changes: { original: string; modified: string }, patch: string): string {
315
+ if (patch) {
316
+ return patch.split('\n').slice(0, 40).join('\n');
317
+ }
318
+
319
+ const originalLines = changes.original.split('\n');
320
+ const modifiedLines = changes.modified.split('\n');
321
+
322
+ let preview = 'Changes:\n';
323
+
324
+ for (let i = 0; i < Math.max(originalLines.length, modifiedLines.length); i++) {
325
+ const originalLine = originalLines[i];
326
+ const modifiedLine = modifiedLines[i];
327
+
328
+ if (originalLine !== modifiedLine) {
329
+ if (originalLine) {
330
+ preview += `- ${originalLine}\n`;
331
+ }
332
+ if (modifiedLine) {
333
+ preview += `+ ${modifiedLine}\n`;
334
+ }
335
+ }
336
+ }
337
+
338
+ return preview;
339
+ }
340
+
341
+ /**
342
+ * Build fix prompt for Jules
343
+ */
344
+ private buildFixPrompt(violation: Violation, analysis: Analysis): string {
345
+ return `Fix browser compatibility issue in ${violation.file}:
346
+
347
+ ISSUE:
348
+ - Feature: ${violation.feature} (line ${violation.line})
349
+ - Unsupported in: ${violation.browser} ${violation.required}
350
+ - Baseline Status: ${violation.baselineStatus}
351
+ - Current code: ${violation.context}
352
+
353
+ ANALYSIS:
354
+ ${analysis.plainEnglish}
355
+
356
+ FIX STRATEGY:
357
+ ${analysis.fixStrategy}
358
+
359
+ REQUIREMENTS:
360
+ 1. Implement progressive enhancement using @supports for CSS features
361
+ 2. Use feature detection for JavaScript APIs
362
+ 3. Add appropriate fallbacks for older browsers
363
+ 4. Preserve all original functionality
364
+ 5. Follow best practices: ${analysis.bestPractices.join(', ')}
365
+ 6. Ensure the fix works in ${violation.browser} ${violation.required} and newer versions
366
+
367
+ Please fix this compatibility issue while maintaining the existing functionality. The fix should be production-ready and follow web standards.`;
368
+ }
369
+
370
+ /**
371
+ * Test Jules API connectivity
372
+ */
373
+ async testConnection(): Promise<{ success: boolean; error?: string; errorType?: ErrorType }> {
374
+ try {
375
+ const response = await fetch(`${this.baseUrl}/sessions`, {
376
+ method: 'GET',
377
+ headers: {
378
+ 'X-Goog-Api-Key': this.apiKey
379
+ }
380
+ });
381
+
382
+ if (response.ok || response.status === 404) {
383
+ // 404 is acceptable for sessions endpoint when no sessions exist
384
+ return { success: true };
385
+ } else {
386
+ const error = new Error(`HTTP ${response.status}: ${response.statusText}`);
387
+ (error as any).response = response;
388
+ throw error;
389
+ }
390
+ } catch (error) {
391
+ const apiError = ErrorHandler.handleAPIError(error);
392
+
393
+ return {
394
+ success: false,
395
+ error: apiError.message,
396
+ errorType: apiError.type
397
+ };
398
+ }
399
+ }
400
+
401
+ /**
402
+ * Get repository source identifier (GitHub integration is handled on Jules dashboard)
403
+ */
404
+ async getRepositorySource(): Promise<string> {
405
+ return await this.githubManager.getSourceIdentifier();
406
+ }
407
+
408
+ /**
409
+ * Check if repository information is available (GitHub integration is handled on Jules dashboard)
410
+ */
411
+ async isRepositoryDetected(): Promise<boolean> {
412
+ return await this.githubManager.isJulesIntegrationSetup();
413
+ }
414
+
415
+ /**
416
+ * Verify GitHub connection and permissions
417
+ */
418
+ async verifyGitHubConnection(): Promise<boolean> {
419
+ return await this.githubManager.verifyGitHubConnection();
420
+ }
421
+
422
+ /**
423
+ * Get repository information
424
+ */
425
+ getRepositoryInfo(): { owner: string | null; name: string | null } {
426
+ return this.githubManager.getRepositoryInfo();
427
+ }
428
+
429
+ /**
430
+ * Get current source identifier
431
+ */
432
+ async getSourceIdentifier(): Promise<string> {
433
+ return await this.githubManager.getCurrentSourceIdentifier();
434
+ }
435
+
436
+ /**
437
+ * Generate and apply fixes with interactive preview
438
+ */
439
+ async generateAndApplyFixes(violations: Violation[], analyses: Analysis[]): Promise<{ applied: Fix[]; skipped: Fix[]; failed: { fix: Fix; error: string }[] }> {
440
+ const fixes: Fix[] = [];
441
+
442
+ // Generate fixes for each violation
443
+ for (let i = 0; i < violations.length; i++) {
444
+ const violation = violations[i];
445
+ const analysis = analyses[i];
446
+
447
+ if (!violation || !analysis) {
448
+ console.log(chalk.red(`āŒ Missing violation or analysis data for index ${i}`));
449
+ continue;
450
+ }
451
+
452
+ try {
453
+ console.log(chalk.cyan(`\nšŸ”§ Generating fix ${i + 1}/${violations.length} for ${violation.feature}...`));
454
+ const fix = await this.generateFix(violation, analysis);
455
+ fixes.push(fix);
456
+ } catch (error) {
457
+ console.log(chalk.red(`āŒ Failed to generate fix for ${violation.feature}: ${error instanceof Error ? error.message : 'Unknown error'}`));
458
+ }
459
+ }
460
+
461
+ if (fixes.length === 0) {
462
+ console.log(chalk.yellow('āš ļø No fixes were generated'));
463
+ return { applied: [], skipped: [], failed: [] };
464
+ }
465
+
466
+ // Apply fixes with interactive preview
467
+ return await this.fixManager.applyFixes(fixes);
468
+ }
469
+
470
+ /**
471
+ * Preview a single fix
472
+ */
473
+ async previewFix(fix: Fix): Promise<string> {
474
+ return await this.fixManager.generatePreview(fix);
475
+ }
476
+
477
+ /**
478
+ * Apply a single fix
479
+ */
480
+ async applySingleFix(fix: Fix): Promise<void> {
481
+ await this.fixManager.applyFix(fix);
482
+ }
483
+
484
+ /**
485
+ * Rollback applied fixes
486
+ */
487
+ async rollbackFix(filePath: string): Promise<void> {
488
+ await this.fixManager.rollbackFix(filePath);
489
+ }
490
+
491
+ /**
492
+ * Rollback all applied fixes
493
+ */
494
+ async rollbackAllFixes(): Promise<void> {
495
+ await this.fixManager.rollbackAllFixes();
496
+ }
497
+
498
+ /**
499
+ * Get list of applied fixes
500
+ */
501
+ getAppliedFixes(): string[] {
502
+ return this.fixManager.getAppliedFixes();
503
+ }
504
+ }