baseguard 1.0.0

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