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,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
+ }