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,750 +1,755 @@
1
- import chalk from 'chalk';
2
- import { promises as fs } from 'fs';
3
- import path from 'path';
4
- import { UIComponents } from '../ui/components.js';
5
-
6
- export interface SystemErrorContext {
7
- operation: string;
8
- file?: string;
9
- line?: number;
10
- details?: any;
11
- timestamp?: Date;
12
- userId?: string;
13
- }
14
-
15
- export interface RecoveryStrategy {
16
- name: string;
17
- description: string;
18
- execute: () => Promise<boolean>;
19
- priority: number;
20
- }
21
-
22
- export class SystemError extends Error {
23
- constructor(
24
- message: string,
25
- public code: string,
26
- public context?: SystemErrorContext,
27
- public recoverable: boolean = true,
28
- public severity: 'low' | 'medium' | 'high' | 'critical' = 'medium'
29
- ) {
30
- super(message);
31
- this.name = 'SystemError';
32
- this.context = {
33
- operation: context?.operation || 'unknown',
34
- file: context?.file,
35
- line: context?.line,
36
- details: context?.details,
37
- timestamp: new Date(),
38
- userId: context?.userId
39
- };
40
- }
41
- }
42
-
43
- export class SystemErrorHandler {
44
- private static retryAttempts = new Map<string, number>();
45
- private static maxRetries = 3;
46
- private static backoffBase = 1000; // 1 second
47
- private static errorLog: SystemError[] = [];
48
- private static maxLogSize = 100;
49
- private static recoveryStrategies = new Map<string, RecoveryStrategy[]>();
50
- private static gracefulDegradationEnabled = true;
51
- private static offlineMode = false;
52
-
53
- static async withRetry<T>(
54
- operation: () => Promise<T>,
55
- context: SystemErrorContext,
56
- maxRetries: number = SystemErrorHandler.maxRetries,
57
- customBackoff?: (attempt: number) => number
58
- ): Promise<T> {
59
- const key = `${context.operation}-${context.file || 'global'}`;
60
- let attempts = SystemErrorHandler.retryAttempts.get(key) || 0;
61
-
62
- try {
63
- const result = await operation();
64
- SystemErrorHandler.retryAttempts.delete(key); // Reset on success
65
- return result;
66
- } catch (error) {
67
- attempts++;
68
- SystemErrorHandler.retryAttempts.set(key, attempts);
69
-
70
- const wrappedError = SystemErrorHandler.wrapError(error, context);
71
- SystemErrorHandler.logError(wrappedError);
72
-
73
- if (attempts >= maxRetries) {
74
- SystemErrorHandler.retryAttempts.delete(key);
75
-
76
- // Try recovery strategies before giving up
77
- const recovered = await SystemErrorHandler.attemptRecovery(wrappedError);
78
- if (!recovered) {
79
- throw wrappedError;
80
- }
81
-
82
- // If recovery succeeded, try the operation once more
83
- SystemErrorHandler.retryAttempts.delete(key);
84
- return SystemErrorHandler.withRetry(operation, context, 1); // One more try after recovery
85
- }
86
-
87
- const delay = customBackoff ? customBackoff(attempts) : SystemErrorHandler.backoffBase * Math.pow(2, attempts - 1);
88
- console.warn(chalk.yellow(`โš ๏ธ Retry ${attempts}/${maxRetries} for ${context.operation} in ${delay}ms`));
89
-
90
- await SystemErrorHandler.sleep(delay);
91
- return SystemErrorHandler.withRetry(operation, context, maxRetries, customBackoff);
92
- }
93
- }
94
-
95
- static async handleGracefully<T>(
96
- operation: () => Promise<T>,
97
- fallback: T | (() => Promise<T>),
98
- context: SystemErrorContext,
99
- options: {
100
- logError?: boolean;
101
- showWarning?: boolean;
102
- attemptRecovery?: boolean;
103
- } = {}
104
- ): Promise<T> {
105
- const { logError = true, showWarning = true, attemptRecovery = true } = options;
106
-
107
- try {
108
- return await operation();
109
- } catch (error) {
110
- const wrappedError = SystemErrorHandler.wrapError(error, context);
111
-
112
- if (logError) {
113
- SystemErrorHandler.logError(wrappedError);
114
- }
115
-
116
- if (showWarning) {
117
- console.warn(chalk.yellow(`โš ๏ธ ${context.operation} failed: ${error instanceof Error ? error.message : 'Unknown error'}`));
118
- }
119
-
120
- // Attempt recovery if enabled
121
- if (attemptRecovery && wrappedError.recoverable) {
122
- const recovered = await SystemErrorHandler.attemptRecovery(wrappedError);
123
- if (recovered) {
124
- try {
125
- return await operation();
126
- } catch (retryError) {
127
- // Recovery didn't work, fall back
128
- }
129
- }
130
- }
131
-
132
- // Use fallback
133
- const fallbackValue = typeof fallback === 'function' ? await (fallback as () => Promise<T>)() : fallback;
134
-
135
- if (showWarning) {
136
- console.warn(chalk.cyan('๐Ÿ”„ Using fallback strategy'));
137
- }
138
-
139
- return fallbackValue;
140
- }
141
- }
142
-
143
- static wrapError(error: any, context: SystemErrorContext): SystemError {
144
- if (error instanceof SystemError) {
145
- return error;
146
- }
147
-
148
- let code = 'UNKNOWN_ERROR';
149
- let recoverable = true;
150
- let severity: 'low' | 'medium' | 'high' | 'critical' = 'medium';
151
-
152
- // Categorize errors with enhanced detection
153
- if (error.code === 'ENOENT') {
154
- code = 'FILE_NOT_FOUND';
155
- severity = 'low';
156
- } else if (error.code === 'EACCES' || error.code === 'EPERM') {
157
- code = 'PERMISSION_DENIED';
158
- recoverable = false;
159
- severity = 'high';
160
- } else if (error.code === 'ENOTDIR') {
161
- code = 'INVALID_PATH';
162
- severity = 'medium';
163
- } else if (error.name === 'SyntaxError') {
164
- code = 'SYNTAX_ERROR';
165
- severity = 'low';
166
- } else if (error.code === 'NETWORK_ERROR' || error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND') {
167
- code = 'NETWORK_ERROR';
168
- severity = 'medium';
169
- } else if (error.code === 'ETIMEDOUT') {
170
- code = 'TIMEOUT_ERROR';
171
- severity = 'medium';
172
- } else if (error.name === 'TypeError' && error.message.includes('fetch')) {
173
- code = 'API_ERROR';
174
- severity = 'medium';
175
- } else if (error.code === 'EMFILE' || error.code === 'ENFILE') {
176
- code = 'TOO_MANY_FILES';
177
- severity = 'high';
178
- } else if (error.code === 'ENOSPC') {
179
- code = 'DISK_FULL';
180
- recoverable = false;
181
- severity = 'critical';
182
- } else if (error.message?.includes('out of memory') || error.code === 'ERR_MEMORY_ALLOCATION_FAILED') {
183
- code = 'OUT_OF_MEMORY';
184
- recoverable = false;
185
- severity = 'critical';
186
- } else if (error.message?.includes('configuration') || error.message?.includes('config')) {
187
- code = 'CONFIGURATION_ERROR';
188
- severity = 'medium';
189
- } else if (error.message?.includes('parser') || error.message?.includes('parsing')) {
190
- code = 'PARSER_ERROR';
191
- severity = 'low';
192
- }
193
-
194
- return new SystemError(
195
- error.message || 'An unknown error occurred',
196
- code,
197
- context,
198
- recoverable,
199
- severity
200
- );
201
- }
202
-
203
- static displayError(error: SystemError, options: { verbose?: boolean; showRecovery?: boolean } = {}): void {
204
- const { verbose = false, showRecovery = true } = options;
205
-
206
- // Color based on severity
207
- const severityColors = {
208
- low: chalk.yellow,
209
- medium: chalk.red,
210
- high: chalk.redBright,
211
- critical: chalk.bgRed.white
212
- };
213
-
214
- const colorFn = severityColors[error.severity];
215
-
216
- console.error(colorFn('โŒ System Error:'), error.message);
217
-
218
- if (error.context?.file) {
219
- console.error(chalk.dim(` File: ${error.context.file}`));
220
- }
221
-
222
- if (error.context?.line) {
223
- console.error(chalk.dim(` Line: ${error.context.line}`));
224
- }
225
-
226
- if (verbose && error.context?.details) {
227
- console.error(chalk.dim(` Details: ${JSON.stringify(error.context.details, null, 2)}`));
228
- }
229
-
230
- // Show recovery options
231
- if (showRecovery && error.recoverable) {
232
- SystemErrorHandler.showRecoveryOptions(error);
233
- }
234
-
235
- // Provide helpful suggestions
236
- SystemErrorHandler.provideSuggestions(error);
237
- }
238
-
239
- private static async attemptRecovery(error: SystemError): Promise<boolean> {
240
- const strategies = SystemErrorHandler.recoveryStrategies.get(error.code) || [];
241
-
242
- if (strategies.length === 0) {
243
- return false;
244
- }
245
-
246
- console.log(chalk.cyan('๐Ÿ”ง Attempting automatic recovery...'));
247
-
248
- // Sort strategies by priority
249
- strategies.sort((a, b) => b.priority - a.priority);
250
-
251
- for (const strategy of strategies) {
252
- try {
253
- console.log(chalk.dim(` Trying: ${strategy.description}`));
254
- const success = await strategy.execute();
255
-
256
- if (success) {
257
- console.log(chalk.green(`โœ… Recovery successful: ${strategy.name}`));
258
- return true;
259
- }
260
- } catch (recoveryError) {
261
- console.log(chalk.dim(` Recovery failed: ${recoveryError instanceof Error ? recoveryError.message : 'Unknown error'}`));
262
- }
263
- }
264
-
265
- console.log(chalk.yellow('โš ๏ธ Automatic recovery failed'));
266
- return false;
267
- }
268
-
269
- private static showRecoveryOptions(error: SystemError): void {
270
- const strategies = SystemErrorHandler.recoveryStrategies.get(error.code) || [];
271
-
272
- if (strategies.length > 0) {
273
- console.error(chalk.cyan('\n๐Ÿ”ง Available recovery options:'));
274
- strategies.forEach((strategy, index) => {
275
- console.error(chalk.cyan(` ${index + 1}. ${strategy.description}`));
276
- });
277
- }
278
- }
279
-
280
- private static provideSuggestions(error: SystemError): void {
281
- const suggestions: string[] = [];
282
-
283
- switch (error.code) {
284
- case 'FILE_NOT_FOUND':
285
- suggestions.push('Check if the file path is correct');
286
- suggestions.push('Ensure the file exists and is accessible');
287
- suggestions.push('Verify the working directory is correct');
288
- break;
289
- case 'PERMISSION_DENIED':
290
- suggestions.push('Check file permissions with ls -la (Unix) or dir (Windows)');
291
- suggestions.push('Run with appropriate privileges if needed');
292
- suggestions.push('Ensure the file is not locked by another process');
293
- break;
294
- case 'SYNTAX_ERROR':
295
- suggestions.push('Check the file syntax with your editor');
296
- suggestions.push('Ensure the file is valid for its type');
297
- suggestions.push('Look for missing brackets, quotes, or semicolons');
298
- break;
299
- case 'NETWORK_ERROR':
300
- suggestions.push('Check your internet connection');
301
- suggestions.push('Verify API endpoints are accessible');
302
- suggestions.push('Consider running in offline mode with --offline flag');
303
- suggestions.push('Check if you\'re behind a proxy or firewall');
304
- break;
305
- case 'API_ERROR':
306
- suggestions.push('Verify your API keys are correct');
307
- suggestions.push('Check API rate limits and quotas');
308
- suggestions.push('Ensure the API service is available');
309
- break;
310
- case 'TIMEOUT_ERROR':
311
- suggestions.push('Increase timeout with --timeout flag');
312
- suggestions.push('Check network stability');
313
- suggestions.push('Try again later if the service is overloaded');
314
- break;
315
- case 'TOO_MANY_FILES':
316
- suggestions.push('Close unnecessary applications');
317
- suggestions.push('Increase system file descriptor limits');
318
- suggestions.push('Process files in smaller batches');
319
- break;
320
- case 'DISK_FULL':
321
- suggestions.push('Free up disk space');
322
- suggestions.push('Clean temporary files');
323
- suggestions.push('Move files to another drive');
324
- break;
325
- case 'OUT_OF_MEMORY':
326
- suggestions.push('Close other applications to free memory');
327
- suggestions.push('Process files in smaller batches');
328
- suggestions.push('Increase system memory if possible');
329
- break;
330
- case 'CONFIGURATION_ERROR':
331
- suggestions.push('Run "base init" to reconfigure BaseGuard');
332
- suggestions.push('Check .baseguardrc.json for syntax errors');
333
- suggestions.push('Verify all required configuration fields are present');
334
- break;
335
- case 'PARSER_ERROR':
336
- suggestions.push('Check file syntax and encoding');
337
- suggestions.push('Ensure file extension matches content type');
338
- suggestions.push('Try with a simpler version of the file');
339
- break;
340
- }
341
-
342
- if (suggestions.length > 0) {
343
- console.error(chalk.cyan('\n๐Ÿ’ก Suggestions:'));
344
- suggestions.forEach(suggestion => {
345
- console.error(chalk.cyan(` โ€ข ${suggestion}`));
346
- });
347
- }
348
-
349
- // Show degradation options
350
- if (SystemErrorHandler.gracefulDegradationEnabled) {
351
- SystemErrorHandler.showDegradationOptions(error);
352
- }
353
- }
354
-
355
- private static showDegradationOptions(error: SystemError): void {
356
- const degradationOptions: string[] = [];
357
-
358
- switch (error.code) {
359
- case 'NETWORK_ERROR':
360
- case 'API_ERROR':
361
- degradationOptions.push('Continue with baseline-only checking (no AI analysis)');
362
- degradationOptions.push('Use cached results if available');
363
- degradationOptions.push('Switch to offline mode automatically');
364
- break;
365
- case 'SYNTAX_ERROR':
366
- case 'PARSER_ERROR':
367
- degradationOptions.push('Skip malformed files and continue with others');
368
- degradationOptions.push('Use basic text parsing as fallback');
369
- degradationOptions.push('Report parsing issues and continue');
370
- break;
371
- case 'TOO_MANY_FILES':
372
- degradationOptions.push('Process files in smaller batches');
373
- degradationOptions.push('Skip non-essential file types');
374
- degradationOptions.push('Use streaming processing to reduce memory usage');
375
- break;
376
- case 'CONFIGURATION_ERROR':
377
- degradationOptions.push('Use default configuration settings');
378
- degradationOptions.push('Skip optional features that require configuration');
379
- degradationOptions.push('Continue with basic compatibility checking');
380
- break;
381
- }
382
-
383
- if (degradationOptions.length > 0) {
384
- console.error(chalk.magenta('\n๐Ÿ”„ Graceful degradation options:'));
385
- degradationOptions.forEach(option => {
386
- console.error(chalk.magenta(` โ€ข ${option}`));
387
- });
388
- }
389
- }
390
-
391
- static logError(error: SystemError): void {
392
- SystemErrorHandler.errorLog.push(error);
393
-
394
- // Keep log size manageable
395
- if (SystemErrorHandler.errorLog.length > SystemErrorHandler.maxLogSize) {
396
- SystemErrorHandler.errorLog.shift();
397
- }
398
- }
399
-
400
- static async saveErrorLog(filePath?: string): Promise<void> {
401
- const logPath = filePath || path.join(process.cwd(), '.baseguard-errors.log');
402
-
403
- const logData = {
404
- timestamp: new Date().toISOString(),
405
- version: process.env.npm_package_version || 'unknown',
406
- platform: process.platform,
407
- nodeVersion: process.version,
408
- errors: SystemErrorHandler.errorLog.map(error => ({
409
- message: error.message,
410
- code: error.code,
411
- severity: error.severity,
412
- context: error.context,
413
- recoverable: error.recoverable
414
- }))
415
- };
416
-
417
- try {
418
- await fs.writeFile(logPath, JSON.stringify(logData, null, 2));
419
- console.log(chalk.dim(`Error log saved to: ${logPath}`));
420
- } catch (writeError) {
421
- console.warn(chalk.yellow(`Failed to save error log: ${writeError instanceof Error ? writeError.message : 'Unknown error'}`));
422
- }
423
- }
424
-
425
- static getErrorSummary(): {
426
- total: number;
427
- bySeverity: Record<string, number>;
428
- byCode: Record<string, number>;
429
- recoverable: number;
430
- critical: number;
431
- } {
432
- const summary = {
433
- total: SystemErrorHandler.errorLog.length,
434
- bySeverity: { low: 0, medium: 0, high: 0, critical: 0 },
435
- byCode: {} as Record<string, number>,
436
- recoverable: 0,
437
- critical: 0
438
- };
439
-
440
- SystemErrorHandler.errorLog.forEach(error => {
441
- summary.bySeverity[error.severity]++;
442
- summary.byCode[error.code] = (summary.byCode[error.code] || 0) + 1;
443
- if (error.recoverable) summary.recoverable++;
444
- if (error.severity === 'critical') summary.critical++;
445
- });
446
-
447
- return summary;
448
- }
449
-
450
- static registerRecoveryStrategy(errorCode: string, strategy: RecoveryStrategy): void {
451
- if (!SystemErrorHandler.recoveryStrategies.has(errorCode)) {
452
- SystemErrorHandler.recoveryStrategies.set(errorCode, []);
453
- }
454
- SystemErrorHandler.recoveryStrategies.get(errorCode)!.push(strategy);
455
- }
456
-
457
- static enableGracefulDegradation(enabled: boolean = true): void {
458
- SystemErrorHandler.gracefulDegradationEnabled = enabled;
459
- }
460
-
461
- static setOfflineMode(offline: boolean = true): void {
462
- SystemErrorHandler.offlineMode = offline;
463
- if (offline) {
464
- console.log(chalk.cyan('๐Ÿ”Œ Offline mode enabled - AI features disabled'));
465
- }
466
- }
467
-
468
- static isOfflineMode(): boolean {
469
- return SystemErrorHandler.offlineMode || process.env.BASEGUARD_OFFLINE === 'true';
470
- }
471
-
472
- static clearErrorLog(): void {
473
- SystemErrorHandler.errorLog = [];
474
- }
475
-
476
- static async createCorruptionRecovery(configPath: string): Promise<boolean> {
477
- try {
478
- // Backup corrupted config
479
- const backupPath = `${configPath}.backup.${Date.now()}`;
480
- await fs.copyFile(configPath, backupPath);
481
- console.log(chalk.yellow(`Corrupted config backed up to: ${backupPath}`));
482
-
483
- // Create minimal working config
484
- const minimalConfig = {
485
- version: '1.0.0',
486
- targets: [{ browser: 'chrome', minVersion: 'baseline' }],
487
- apiKeys: { jules: null, gemini: null },
488
- automation: { enabled: false }
489
- };
490
-
491
- await fs.writeFile(configPath, JSON.stringify(minimalConfig, null, 2));
492
- console.log(chalk.green('โœ… Created minimal working configuration'));
493
- return true;
494
- } catch (error) {
495
- console.error(chalk.red('Failed to recover from configuration corruption'));
496
- return false;
497
- }
498
- }
499
-
500
- static async handleProcessSignals(): Promise<void> {
501
- const gracefulShutdown = async (signal: string) => {
502
- console.log(chalk.yellow(`\nโš ๏ธ Received ${signal}, shutting down gracefully...`));
503
-
504
- // Save error log before exit
505
- try {
506
- await SystemErrorHandler.saveErrorLog();
507
- } catch (error) {
508
- // Ignore errors during shutdown
509
- }
510
-
511
- // Show error summary if there were issues
512
- const summary = SystemErrorHandler.getErrorSummary();
513
- if (summary.total > 0) {
514
- console.log(chalk.cyan(`\n๐Ÿ“Š Session summary: ${summary.total} errors encountered`));
515
- if (summary.critical > 0) {
516
- console.log(chalk.red(` ${summary.critical} critical errors require attention`));
517
- }
518
- }
519
-
520
- process.exit(0);
521
- };
522
-
523
- process.on('SIGINT', () => gracefulShutdown('SIGINT'));
524
- process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
525
-
526
- // Handle uncaught exceptions
527
- process.on('uncaughtException', (error) => {
528
- const systemError = SystemErrorHandler.wrapError(error, {
529
- operation: 'uncaught-exception',
530
- details: { stack: error.stack }
531
- });
532
-
533
- SystemErrorHandler.displayError(systemError, { verbose: true });
534
- SystemErrorHandler.saveErrorLog().finally(() => {
535
- process.exit(1);
536
- });
537
- });
538
-
539
- // Handle unhandled promise rejections
540
- process.on('unhandledRejection', (reason, promise) => {
541
- const systemError = SystemErrorHandler.wrapError(reason, {
542
- operation: 'unhandled-rejection',
543
- details: { promise: promise.toString() }
544
- });
545
-
546
- SystemErrorHandler.displayError(systemError, { verbose: true });
547
- SystemErrorHandler.saveErrorLog().finally(() => {
548
- process.exit(1);
549
- });
550
- });
551
- }
552
-
553
- private static sleep(ms: number): Promise<void> {
554
- return new Promise(resolve => setTimeout(resolve, ms));
555
- }
556
-
557
- // Initialize default recovery strategies
558
- static initializeRecoveryStrategies(): void {
559
- // File not found recovery
560
- SystemErrorHandler.registerRecoveryStrategy('FILE_NOT_FOUND', {
561
- name: 'Create missing directories',
562
- description: 'Create missing parent directories',
563
- priority: 3,
564
- execute: async () => {
565
- try {
566
- // Try to create parent directories for common paths
567
- const commonPaths = ['src', 'app', 'components', 'pages', '.baseguard'];
568
- for (const dirPath of commonPaths) {
569
- try {
570
- await fs.mkdir(dirPath, { recursive: true });
571
- } catch (error) {
572
- // Ignore if directory already exists
573
- }
574
- }
575
- return true;
576
- } catch (error) {
577
- return false;
578
- }
579
- }
580
- });
581
-
582
- // Network error recovery
583
- SystemErrorHandler.registerRecoveryStrategy('NETWORK_ERROR', {
584
- name: 'Switch to offline mode',
585
- description: 'Continue with offline-only features',
586
- priority: 2,
587
- execute: async () => {
588
- console.log(chalk.cyan('๐Ÿ”Œ Switching to offline mode...'));
589
- SystemErrorHandler.setOfflineMode(true);
590
- process.env.BASEGUARD_OFFLINE = 'true';
591
- return true;
592
- }
593
- });
594
-
595
- // API error recovery
596
- SystemErrorHandler.registerRecoveryStrategy('API_ERROR', {
597
- name: 'Use cached responses',
598
- description: 'Fall back to cached API responses',
599
- priority: 1,
600
- execute: async () => {
601
- try {
602
- // Check if cache directory exists and has content
603
- const cacheDir = path.join(process.cwd(), '.baseguard', 'cache');
604
- const cacheExists = await fs.access(cacheDir).then(() => true).catch(() => false);
605
-
606
- if (cacheExists) {
607
- const files = await fs.readdir(cacheDir);
608
- if (files.length > 0) {
609
- console.log(chalk.cyan('๐Ÿ“ฆ Using cached API responses'));
610
- return true;
611
- }
612
- }
613
-
614
- // Enable offline mode as fallback
615
- SystemErrorHandler.setOfflineMode(true);
616
- return true;
617
- } catch (error) {
618
- return false;
619
- }
620
- }
621
- });
622
-
623
- // Syntax error recovery
624
- SystemErrorHandler.registerRecoveryStrategy('SYNTAX_ERROR', {
625
- name: 'Skip malformed file',
626
- description: 'Skip the file with syntax errors and continue',
627
- priority: 1,
628
- execute: async () => {
629
- console.log(chalk.cyan('โญ๏ธ Skipping malformed file and continuing...'));
630
- return true;
631
- }
632
- });
633
-
634
- // Configuration error recovery
635
- SystemErrorHandler.registerRecoveryStrategy('CONFIGURATION_ERROR', {
636
- name: 'Create minimal config',
637
- description: 'Create a minimal working configuration',
638
- priority: 2,
639
- execute: async () => {
640
- const configPath = path.join(process.cwd(), '.baseguardrc.json');
641
- return await SystemErrorHandler.createCorruptionRecovery(configPath);
642
- }
643
- });
644
-
645
- // Parser error recovery
646
- SystemErrorHandler.registerRecoveryStrategy('PARSER_ERROR', {
647
- name: 'Use fallback parser',
648
- description: 'Try with a simpler parsing strategy',
649
- priority: 1,
650
- execute: async () => {
651
- console.log(chalk.cyan('๐Ÿ”„ Using fallback parser strategy...'));
652
- return true;
653
- }
654
- });
655
-
656
- // Memory error recovery
657
- SystemErrorHandler.registerRecoveryStrategy('OUT_OF_MEMORY', {
658
- name: 'Reduce processing batch size',
659
- description: 'Process files in smaller batches to reduce memory usage',
660
- priority: 3,
661
- execute: async () => {
662
- console.log(chalk.cyan('๐Ÿง  Reducing memory usage by processing smaller batches...'));
663
- process.env.BASEGUARD_BATCH_SIZE = '10'; // Reduce from default
664
- return true;
665
- }
666
- });
667
-
668
- // Too many files recovery
669
- SystemErrorHandler.registerRecoveryStrategy('TOO_MANY_FILES', {
670
- name: 'Limit file processing',
671
- description: 'Reduce the number of files processed simultaneously',
672
- priority: 2,
673
- execute: async () => {
674
- console.log(chalk.cyan('๐Ÿ“ Limiting concurrent file processing...'));
675
- process.env.BASEGUARD_MAX_FILES = '100'; // Reduce from default
676
- return true;
677
- }
678
- });
679
-
680
- // Timeout error recovery
681
- SystemErrorHandler.registerRecoveryStrategy('TIMEOUT_ERROR', {
682
- name: 'Increase timeout and retry',
683
- description: 'Increase timeout settings and retry the operation',
684
- priority: 2,
685
- execute: async () => {
686
- console.log(chalk.cyan('โฑ๏ธ Increasing timeout settings...'));
687
- process.env.BASEGUARD_TIMEOUT = '60000'; // 60 seconds
688
- return true;
689
- }
690
- });
691
-
692
- // Permission denied recovery
693
- SystemErrorHandler.registerRecoveryStrategy('PERMISSION_DENIED', {
694
- name: 'Skip protected files',
695
- description: 'Skip files that cannot be accessed and continue',
696
- priority: 1,
697
- execute: async () => {
698
- console.log(chalk.cyan('๐Ÿ”’ Skipping protected files and continuing...'));
699
- return true;
700
- }
701
- });
702
-
703
- // Disk full recovery
704
- SystemErrorHandler.registerRecoveryStrategy('DISK_FULL', {
705
- name: 'Clean temporary files',
706
- description: 'Clean up temporary files to free space',
707
- priority: 3,
708
- execute: async () => {
709
- try {
710
- console.log(chalk.cyan('๐Ÿงน Cleaning temporary files...'));
711
-
712
- // Clean BaseGuard temp files
713
- const tempDir = path.join(process.cwd(), '.baseguard', 'temp');
714
- try {
715
- await fs.rm(tempDir, { recursive: true, force: true });
716
- await fs.mkdir(tempDir, { recursive: true });
717
- } catch (error) {
718
- // Ignore if temp dir doesn't exist
719
- }
720
-
721
- // Clean system temp files (be careful here)
722
- const systemTemp = process.env.TMPDIR || process.env.TEMP || '/tmp';
723
- const baseguardTempPattern = path.join(systemTemp, 'baseguard-*');
724
-
725
- try {
726
- const { glob } = await import('glob');
727
- const tempFiles = await glob(baseguardTempPattern);
728
- for (const file of tempFiles) {
729
- try {
730
- await fs.rm(file, { recursive: true, force: true });
731
- } catch (error) {
732
- // Ignore individual file errors
733
- }
734
- }
735
- } catch (error) {
736
- // Ignore if glob fails
737
- }
738
-
739
- return true;
740
- } catch (error) {
741
- return false;
742
- }
743
- }
744
- });
745
- }
746
- }
747
-
748
- // Initialize recovery strategies and signal handlers when module loads
749
- SystemErrorHandler.initializeRecoveryStrategies();
750
- SystemErrorHandler.handleProcessSignals();
1
+ import chalk from 'chalk';
2
+ import { promises as fs } from 'fs';
3
+ import path from 'path';
4
+
5
+ export interface SystemErrorContext {
6
+ operation: string;
7
+ file?: string;
8
+ line?: number;
9
+ details?: any;
10
+ timestamp?: Date;
11
+ userId?: string;
12
+ }
13
+
14
+ export interface RecoveryStrategy {
15
+ name: string;
16
+ description: string;
17
+ execute: () => Promise<boolean>;
18
+ priority: number;
19
+ }
20
+
21
+ export class SystemError extends Error {
22
+ constructor(
23
+ message: string,
24
+ public code: string,
25
+ public context?: SystemErrorContext,
26
+ public recoverable: boolean = true,
27
+ public severity: 'low' | 'medium' | 'high' | 'critical' = 'medium'
28
+ ) {
29
+ super(message);
30
+ this.name = 'SystemError';
31
+ this.context = {
32
+ operation: context?.operation || 'unknown',
33
+ file: context?.file,
34
+ line: context?.line,
35
+ details: context?.details,
36
+ timestamp: new Date(),
37
+ userId: context?.userId
38
+ };
39
+ }
40
+ }
41
+
42
+ export class SystemErrorHandler {
43
+ private static retryAttempts = new Map<string, number>();
44
+ private static maxRetries = 3;
45
+ private static backoffBase = 1000; // 1 second
46
+ private static errorLog: SystemError[] = [];
47
+ private static maxLogSize = 100;
48
+ private static recoveryStrategies = new Map<string, RecoveryStrategy[]>();
49
+ private static gracefulDegradationEnabled = true;
50
+ private static offlineMode = false;
51
+
52
+ static async withRetry<T>(
53
+ operation: () => Promise<T>,
54
+ context: SystemErrorContext,
55
+ maxRetries: number = SystemErrorHandler.maxRetries,
56
+ customBackoff?: (attempt: number) => number
57
+ ): Promise<T> {
58
+ const key = `${context.operation}-${context.file || 'global'}`;
59
+ let attempts = SystemErrorHandler.retryAttempts.get(key) || 0;
60
+
61
+ try {
62
+ const result = await operation();
63
+ SystemErrorHandler.retryAttempts.delete(key); // Reset on success
64
+ return result;
65
+ } catch (error) {
66
+ attempts++;
67
+ SystemErrorHandler.retryAttempts.set(key, attempts);
68
+
69
+ const wrappedError = SystemErrorHandler.wrapError(error, context);
70
+ SystemErrorHandler.logError(wrappedError);
71
+
72
+ if (attempts >= maxRetries) {
73
+ SystemErrorHandler.retryAttempts.delete(key);
74
+
75
+ // Try recovery strategies before giving up
76
+ const recovered = await SystemErrorHandler.attemptRecovery(wrappedError);
77
+ if (!recovered) {
78
+ throw wrappedError;
79
+ }
80
+
81
+ // If recovery succeeded, try the operation once more
82
+ SystemErrorHandler.retryAttempts.delete(key);
83
+ return SystemErrorHandler.withRetry(operation, context, 1); // One more try after recovery
84
+ }
85
+
86
+ const delay = customBackoff ? customBackoff(attempts) : SystemErrorHandler.backoffBase * Math.pow(2, attempts - 1);
87
+ console.warn(chalk.yellow(`โš ๏ธ Retry ${attempts}/${maxRetries} for ${context.operation} in ${delay}ms`));
88
+
89
+ await SystemErrorHandler.sleep(delay);
90
+ return SystemErrorHandler.withRetry(operation, context, maxRetries, customBackoff);
91
+ }
92
+ }
93
+
94
+ static async handleGracefully<T>(
95
+ operation: () => Promise<T>,
96
+ fallback: T | (() => Promise<T>),
97
+ context: SystemErrorContext,
98
+ options: {
99
+ logError?: boolean;
100
+ showWarning?: boolean;
101
+ attemptRecovery?: boolean;
102
+ } = {}
103
+ ): Promise<T> {
104
+ const { logError = true, showWarning = true, attemptRecovery = true } = options;
105
+
106
+ try {
107
+ return await operation();
108
+ } catch (error) {
109
+ const wrappedError = SystemErrorHandler.wrapError(error, context);
110
+
111
+ if (logError) {
112
+ SystemErrorHandler.logError(wrappedError);
113
+ }
114
+
115
+ if (showWarning) {
116
+ console.warn(chalk.yellow(`โš ๏ธ ${context.operation} failed: ${error instanceof Error ? error.message : 'Unknown error'}`));
117
+ }
118
+
119
+ // Attempt recovery if enabled
120
+ if (attemptRecovery && wrappedError.recoverable) {
121
+ const recovered = await SystemErrorHandler.attemptRecovery(wrappedError);
122
+ if (recovered) {
123
+ try {
124
+ return await operation();
125
+ } catch (retryError) {
126
+ // Recovery didn't work, fall back
127
+ }
128
+ }
129
+ }
130
+
131
+ // Use fallback
132
+ const fallbackValue = typeof fallback === 'function' ? await (fallback as () => Promise<T>)() : fallback;
133
+
134
+ if (showWarning) {
135
+ console.warn(chalk.cyan('๐Ÿ”„ Using fallback strategy'));
136
+ }
137
+
138
+ return fallbackValue;
139
+ }
140
+ }
141
+
142
+ static wrapError(error: any, context: SystemErrorContext): SystemError {
143
+ if (error instanceof SystemError) {
144
+ return error;
145
+ }
146
+
147
+ let code = 'UNKNOWN_ERROR';
148
+ let recoverable = true;
149
+ let severity: 'low' | 'medium' | 'high' | 'critical' = 'medium';
150
+
151
+ // Categorize errors with enhanced detection
152
+ if (error.code === 'ENOENT') {
153
+ code = 'FILE_NOT_FOUND';
154
+ severity = 'low';
155
+ } else if (error.code === 'EACCES' || error.code === 'EPERM') {
156
+ code = 'PERMISSION_DENIED';
157
+ recoverable = false;
158
+ severity = 'high';
159
+ } else if (error.code === 'ENOTDIR') {
160
+ code = 'INVALID_PATH';
161
+ severity = 'medium';
162
+ } else if (error.name === 'SyntaxError') {
163
+ code = 'SYNTAX_ERROR';
164
+ severity = 'low';
165
+ } else if (error.code === 'NETWORK_ERROR' || error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND') {
166
+ code = 'NETWORK_ERROR';
167
+ severity = 'medium';
168
+ } else if (error.code === 'ETIMEDOUT') {
169
+ code = 'TIMEOUT_ERROR';
170
+ severity = 'medium';
171
+ } else if (error.name === 'TypeError' && error.message.includes('fetch')) {
172
+ code = 'API_ERROR';
173
+ severity = 'medium';
174
+ } else if (error.code === 'EMFILE' || error.code === 'ENFILE') {
175
+ code = 'TOO_MANY_FILES';
176
+ severity = 'high';
177
+ } else if (error.code === 'ENOSPC') {
178
+ code = 'DISK_FULL';
179
+ recoverable = false;
180
+ severity = 'critical';
181
+ } else if (error.message?.includes('out of memory') || error.code === 'ERR_MEMORY_ALLOCATION_FAILED') {
182
+ code = 'OUT_OF_MEMORY';
183
+ recoverable = false;
184
+ severity = 'critical';
185
+ } else if (error.message?.includes('configuration') || error.message?.includes('config')) {
186
+ code = 'CONFIGURATION_ERROR';
187
+ severity = 'medium';
188
+ } else if (error.message?.includes('parser') || error.message?.includes('parsing')) {
189
+ code = 'PARSER_ERROR';
190
+ severity = 'low';
191
+ }
192
+
193
+ return new SystemError(
194
+ error.message || 'An unknown error occurred',
195
+ code,
196
+ context,
197
+ recoverable,
198
+ severity
199
+ );
200
+ }
201
+
202
+ static displayError(error: SystemError, options: { verbose?: boolean; showRecovery?: boolean } = {}): void {
203
+ const { verbose = false, showRecovery = true } = options;
204
+
205
+ // Color based on severity
206
+ const severityColors = {
207
+ low: chalk.yellow,
208
+ medium: chalk.red,
209
+ high: chalk.redBright,
210
+ critical: chalk.bgRed.white
211
+ };
212
+
213
+ const colorFn = severityColors[error.severity];
214
+
215
+ console.error(colorFn('โŒ System Error:'), error.message);
216
+
217
+ if (error.context?.file) {
218
+ console.error(chalk.dim(` File: ${error.context.file}`));
219
+ }
220
+
221
+ if (error.context?.line) {
222
+ console.error(chalk.dim(` Line: ${error.context.line}`));
223
+ }
224
+
225
+ if (verbose && error.context?.details) {
226
+ console.error(chalk.dim(` Details: ${JSON.stringify(error.context.details, null, 2)}`));
227
+ }
228
+
229
+ // Show recovery options
230
+ if (showRecovery && error.recoverable) {
231
+ SystemErrorHandler.showRecoveryOptions(error);
232
+ }
233
+
234
+ // Provide helpful suggestions
235
+ SystemErrorHandler.provideSuggestions(error);
236
+ }
237
+
238
+ private static async attemptRecovery(error: SystemError): Promise<boolean> {
239
+ const strategies = SystemErrorHandler.recoveryStrategies.get(error.code) || [];
240
+
241
+ if (strategies.length === 0) {
242
+ return false;
243
+ }
244
+
245
+ console.log(chalk.cyan('๐Ÿ”ง Attempting automatic recovery...'));
246
+
247
+ // Sort strategies by priority
248
+ strategies.sort((a, b) => b.priority - a.priority);
249
+
250
+ for (const strategy of strategies) {
251
+ try {
252
+ console.log(chalk.dim(` Trying: ${strategy.description}`));
253
+ const success = await strategy.execute();
254
+
255
+ if (success) {
256
+ console.log(chalk.green(`โœ… Recovery successful: ${strategy.name}`));
257
+ return true;
258
+ }
259
+ } catch (recoveryError) {
260
+ console.log(chalk.dim(` Recovery failed: ${recoveryError instanceof Error ? recoveryError.message : 'Unknown error'}`));
261
+ }
262
+ }
263
+
264
+ console.log(chalk.yellow('โš ๏ธ Automatic recovery failed'));
265
+ return false;
266
+ }
267
+
268
+ private static showRecoveryOptions(error: SystemError): void {
269
+ const strategies = SystemErrorHandler.recoveryStrategies.get(error.code) || [];
270
+
271
+ if (strategies.length > 0) {
272
+ console.error(chalk.cyan('\n๐Ÿ”ง Available recovery options:'));
273
+ strategies.forEach((strategy, index) => {
274
+ console.error(chalk.cyan(` ${index + 1}. ${strategy.description}`));
275
+ });
276
+ }
277
+ }
278
+
279
+ private static provideSuggestions(error: SystemError): void {
280
+ const suggestions: string[] = [];
281
+
282
+ switch (error.code) {
283
+ case 'FILE_NOT_FOUND':
284
+ suggestions.push('Check if the file path is correct');
285
+ suggestions.push('Ensure the file exists and is accessible');
286
+ suggestions.push('Verify the working directory is correct');
287
+ break;
288
+ case 'PERMISSION_DENIED':
289
+ suggestions.push('Check file permissions with ls -la (Unix) or dir (Windows)');
290
+ suggestions.push('Run with appropriate privileges if needed');
291
+ suggestions.push('Ensure the file is not locked by another process');
292
+ break;
293
+ case 'SYNTAX_ERROR':
294
+ suggestions.push('Check the file syntax with your editor');
295
+ suggestions.push('Ensure the file is valid for its type');
296
+ suggestions.push('Look for missing brackets, quotes, or semicolons');
297
+ break;
298
+ case 'NETWORK_ERROR':
299
+ suggestions.push('Check your internet connection');
300
+ suggestions.push('Verify API endpoints are accessible');
301
+ suggestions.push('Consider running in offline mode with --offline flag');
302
+ suggestions.push('Check if you\'re behind a proxy or firewall');
303
+ break;
304
+ case 'API_ERROR':
305
+ suggestions.push('Verify your API keys are correct');
306
+ suggestions.push('Check API rate limits and quotas');
307
+ suggestions.push('Ensure the API service is available');
308
+ break;
309
+ case 'TIMEOUT_ERROR':
310
+ suggestions.push('Increase timeout with --timeout flag');
311
+ suggestions.push('Check network stability');
312
+ suggestions.push('Try again later if the service is overloaded');
313
+ break;
314
+ case 'TOO_MANY_FILES':
315
+ suggestions.push('Close unnecessary applications');
316
+ suggestions.push('Increase system file descriptor limits');
317
+ suggestions.push('Process files in smaller batches');
318
+ break;
319
+ case 'DISK_FULL':
320
+ suggestions.push('Free up disk space');
321
+ suggestions.push('Clean temporary files');
322
+ suggestions.push('Move files to another drive');
323
+ break;
324
+ case 'OUT_OF_MEMORY':
325
+ suggestions.push('Close other applications to free memory');
326
+ suggestions.push('Process files in smaller batches');
327
+ suggestions.push('Increase system memory if possible');
328
+ break;
329
+ case 'CONFIGURATION_ERROR':
330
+ suggestions.push('Run "base init" to reconfigure BaseGuard');
331
+ suggestions.push('Check .baseguardrc.json for syntax errors');
332
+ suggestions.push('Verify all required configuration fields are present');
333
+ break;
334
+ case 'PARSER_ERROR':
335
+ suggestions.push('Check file syntax and encoding');
336
+ suggestions.push('Ensure file extension matches content type');
337
+ suggestions.push('Try with a simpler version of the file');
338
+ break;
339
+ }
340
+
341
+ if (suggestions.length > 0) {
342
+ console.error(chalk.cyan('\n๐Ÿ’ก Suggestions:'));
343
+ suggestions.forEach(suggestion => {
344
+ console.error(chalk.cyan(` โ€ข ${suggestion}`));
345
+ });
346
+ }
347
+
348
+ // Show degradation options
349
+ if (SystemErrorHandler.gracefulDegradationEnabled) {
350
+ SystemErrorHandler.showDegradationOptions(error);
351
+ }
352
+ }
353
+
354
+ private static showDegradationOptions(error: SystemError): void {
355
+ const degradationOptions: string[] = [];
356
+
357
+ switch (error.code) {
358
+ case 'NETWORK_ERROR':
359
+ case 'API_ERROR':
360
+ degradationOptions.push('Continue with baseline-only checking (no AI analysis)');
361
+ degradationOptions.push('Use cached results if available');
362
+ degradationOptions.push('Switch to offline mode automatically');
363
+ break;
364
+ case 'SYNTAX_ERROR':
365
+ case 'PARSER_ERROR':
366
+ degradationOptions.push('Skip malformed files and continue with others');
367
+ degradationOptions.push('Use basic text parsing as fallback');
368
+ degradationOptions.push('Report parsing issues and continue');
369
+ break;
370
+ case 'TOO_MANY_FILES':
371
+ degradationOptions.push('Process files in smaller batches');
372
+ degradationOptions.push('Skip non-essential file types');
373
+ degradationOptions.push('Use streaming processing to reduce memory usage');
374
+ break;
375
+ case 'CONFIGURATION_ERROR':
376
+ degradationOptions.push('Use default configuration settings');
377
+ degradationOptions.push('Skip optional features that require configuration');
378
+ degradationOptions.push('Continue with basic compatibility checking');
379
+ break;
380
+ }
381
+
382
+ if (degradationOptions.length > 0) {
383
+ console.error(chalk.magenta('\n๐Ÿ”„ Graceful degradation options:'));
384
+ degradationOptions.forEach(option => {
385
+ console.error(chalk.magenta(` โ€ข ${option}`));
386
+ });
387
+ }
388
+ }
389
+
390
+ static logError(error: SystemError): void {
391
+ SystemErrorHandler.errorLog.push(error);
392
+
393
+ // Keep log size manageable
394
+ if (SystemErrorHandler.errorLog.length > SystemErrorHandler.maxLogSize) {
395
+ SystemErrorHandler.errorLog.shift();
396
+ }
397
+ }
398
+
399
+ static async saveErrorLog(filePath?: string): Promise<void> {
400
+ const logPath = filePath || path.join(process.cwd(), '.baseguard-errors.log');
401
+
402
+ const logData = {
403
+ timestamp: new Date().toISOString(),
404
+ version: process.env.npm_package_version || 'unknown',
405
+ platform: process.platform,
406
+ nodeVersion: process.version,
407
+ errors: SystemErrorHandler.errorLog.map(error => ({
408
+ message: error.message,
409
+ code: error.code,
410
+ severity: error.severity,
411
+ context: error.context,
412
+ recoverable: error.recoverable
413
+ }))
414
+ };
415
+
416
+ try {
417
+ await fs.writeFile(logPath, JSON.stringify(logData, null, 2));
418
+ console.log(chalk.dim(`Error log saved to: ${logPath}`));
419
+ } catch (writeError) {
420
+ console.warn(chalk.yellow(`Failed to save error log: ${writeError instanceof Error ? writeError.message : 'Unknown error'}`));
421
+ }
422
+ }
423
+
424
+ static getErrorSummary(): {
425
+ total: number;
426
+ bySeverity: Record<string, number>;
427
+ byCode: Record<string, number>;
428
+ recoverable: number;
429
+ critical: number;
430
+ } {
431
+ const summary = {
432
+ total: SystemErrorHandler.errorLog.length,
433
+ bySeverity: { low: 0, medium: 0, high: 0, critical: 0 },
434
+ byCode: {} as Record<string, number>,
435
+ recoverable: 0,
436
+ critical: 0
437
+ };
438
+
439
+ SystemErrorHandler.errorLog.forEach(error => {
440
+ summary.bySeverity[error.severity]++;
441
+ summary.byCode[error.code] = (summary.byCode[error.code] || 0) + 1;
442
+ if (error.recoverable) summary.recoverable++;
443
+ if (error.severity === 'critical') summary.critical++;
444
+ });
445
+
446
+ return summary;
447
+ }
448
+
449
+ static registerRecoveryStrategy(errorCode: string, strategy: RecoveryStrategy): void {
450
+ if (!SystemErrorHandler.recoveryStrategies.has(errorCode)) {
451
+ SystemErrorHandler.recoveryStrategies.set(errorCode, []);
452
+ }
453
+ SystemErrorHandler.recoveryStrategies.get(errorCode)!.push(strategy);
454
+ }
455
+
456
+ static enableGracefulDegradation(enabled: boolean = true): void {
457
+ SystemErrorHandler.gracefulDegradationEnabled = enabled;
458
+ }
459
+
460
+ static setOfflineMode(offline: boolean = true): void {
461
+ SystemErrorHandler.offlineMode = offline;
462
+ if (offline) {
463
+ console.log(chalk.cyan('๐Ÿ”Œ Offline mode enabled - AI features disabled'));
464
+ }
465
+ }
466
+
467
+ static isOfflineMode(): boolean {
468
+ return SystemErrorHandler.offlineMode || process.env.BASEGUARD_OFFLINE === 'true';
469
+ }
470
+
471
+ static clearErrorLog(): void {
472
+ SystemErrorHandler.errorLog = [];
473
+ }
474
+
475
+ static async createCorruptionRecovery(configPath: string): Promise<boolean> {
476
+ try {
477
+ // Backup corrupted config
478
+ const backupPath = `${configPath}.backup.${Date.now()}`;
479
+ await fs.copyFile(configPath, backupPath);
480
+ console.log(chalk.yellow(`Corrupted config backed up to: ${backupPath}`));
481
+
482
+ // Create minimal working config
483
+ const minimalConfig = {
484
+ version: '1.0.0',
485
+ targets: [{ browser: 'chrome', minVersion: 'baseline' }],
486
+ apiKeys: { jules: null, gemini: null },
487
+ automation: { enabled: false }
488
+ };
489
+
490
+ await fs.writeFile(configPath, JSON.stringify(minimalConfig, null, 2));
491
+ console.log(chalk.green('โœ… Created minimal working configuration'));
492
+ return true;
493
+ } catch (error) {
494
+ console.error(chalk.red('Failed to recover from configuration corruption'));
495
+ return false;
496
+ }
497
+ }
498
+
499
+ static async handleProcessSignals(): Promise<void> {
500
+ const gracefulShutdown = async (signal: string) => {
501
+ // Only log for non-interactive signals (SIGTERM, not SIGINT from Ctrl+C)
502
+ if (signal !== 'SIGINT') {
503
+ console.log(chalk.yellow(`\nโš ๏ธ Received ${signal}, shutting down gracefully...`));
504
+ }
505
+
506
+ // Cleanup intervals and timers
507
+ try {
508
+ const { StartupOptimizer } = await import('./startup-optimizer.js');
509
+ StartupOptimizer.cleanup();
510
+ } catch (error) {
511
+ // Ignore cleanup errors
512
+ }
513
+
514
+ // Save error log before exit (but don't wait too long)
515
+ try {
516
+ const savePromise = SystemErrorHandler.saveErrorLog();
517
+ await Promise.race([
518
+ savePromise,
519
+ new Promise(resolve => setTimeout(resolve, 100)) // Max 100ms wait
520
+ ]);
521
+ } catch (error) {
522
+ // Ignore errors during shutdown
523
+ }
524
+
525
+ process.exit(0);
526
+ };
527
+
528
+ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
529
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
530
+
531
+ // Handle uncaught exceptions
532
+ process.on('uncaughtException', (error) => {
533
+ const systemError = SystemErrorHandler.wrapError(error, {
534
+ operation: 'uncaught-exception',
535
+ details: { stack: error.stack }
536
+ });
537
+
538
+ SystemErrorHandler.displayError(systemError, { verbose: true });
539
+ SystemErrorHandler.saveErrorLog().finally(() => {
540
+ process.exit(1);
541
+ });
542
+ });
543
+
544
+ // Handle unhandled promise rejections
545
+ process.on('unhandledRejection', (reason, promise) => {
546
+ const systemError = SystemErrorHandler.wrapError(reason, {
547
+ operation: 'unhandled-rejection',
548
+ details: { promise: promise.toString() }
549
+ });
550
+
551
+ SystemErrorHandler.displayError(systemError, { verbose: true });
552
+ SystemErrorHandler.saveErrorLog().finally(() => {
553
+ process.exit(1);
554
+ });
555
+ });
556
+ }
557
+
558
+ private static sleep(ms: number): Promise<void> {
559
+ return new Promise(resolve => setTimeout(resolve, ms));
560
+ }
561
+
562
+ // Initialize default recovery strategies
563
+ static initializeRecoveryStrategies(): void {
564
+ // File not found recovery
565
+ SystemErrorHandler.registerRecoveryStrategy('FILE_NOT_FOUND', {
566
+ name: 'Create missing directories',
567
+ description: 'Create missing parent directories',
568
+ priority: 3,
569
+ execute: async () => {
570
+ try {
571
+ // Try to create parent directories for common paths
572
+ const commonPaths = ['src', 'app', 'components', 'pages', '.baseguard'];
573
+ for (const dirPath of commonPaths) {
574
+ try {
575
+ await fs.mkdir(dirPath, { recursive: true });
576
+ } catch (error) {
577
+ // Ignore if directory already exists
578
+ }
579
+ }
580
+ return true;
581
+ } catch (error) {
582
+ return false;
583
+ }
584
+ }
585
+ });
586
+
587
+ // Network error recovery
588
+ SystemErrorHandler.registerRecoveryStrategy('NETWORK_ERROR', {
589
+ name: 'Switch to offline mode',
590
+ description: 'Continue with offline-only features',
591
+ priority: 2,
592
+ execute: async () => {
593
+ console.log(chalk.cyan('๐Ÿ”Œ Switching to offline mode...'));
594
+ SystemErrorHandler.setOfflineMode(true);
595
+ process.env.BASEGUARD_OFFLINE = 'true';
596
+ return true;
597
+ }
598
+ });
599
+
600
+ // API error recovery
601
+ SystemErrorHandler.registerRecoveryStrategy('API_ERROR', {
602
+ name: 'Use cached responses',
603
+ description: 'Fall back to cached API responses',
604
+ priority: 1,
605
+ execute: async () => {
606
+ try {
607
+ // Check if cache directory exists and has content
608
+ const cacheDir = path.join(process.cwd(), '.baseguard', 'cache');
609
+ const cacheExists = await fs.access(cacheDir).then(() => true).catch(() => false);
610
+
611
+ if (cacheExists) {
612
+ const files = await fs.readdir(cacheDir);
613
+ if (files.length > 0) {
614
+ console.log(chalk.cyan('๐Ÿ“ฆ Using cached API responses'));
615
+ return true;
616
+ }
617
+ }
618
+
619
+ // Enable offline mode as fallback
620
+ SystemErrorHandler.setOfflineMode(true);
621
+ return true;
622
+ } catch (error) {
623
+ return false;
624
+ }
625
+ }
626
+ });
627
+
628
+ // Syntax error recovery
629
+ SystemErrorHandler.registerRecoveryStrategy('SYNTAX_ERROR', {
630
+ name: 'Skip malformed file',
631
+ description: 'Skip the file with syntax errors and continue',
632
+ priority: 1,
633
+ execute: async () => {
634
+ console.log(chalk.cyan('โญ๏ธ Skipping malformed file and continuing...'));
635
+ return true;
636
+ }
637
+ });
638
+
639
+ // Configuration error recovery
640
+ SystemErrorHandler.registerRecoveryStrategy('CONFIGURATION_ERROR', {
641
+ name: 'Create minimal config',
642
+ description: 'Create a minimal working configuration',
643
+ priority: 2,
644
+ execute: async () => {
645
+ const configPath = path.join(process.cwd(), '.baseguardrc.json');
646
+ return await SystemErrorHandler.createCorruptionRecovery(configPath);
647
+ }
648
+ });
649
+
650
+ // Parser error recovery
651
+ SystemErrorHandler.registerRecoveryStrategy('PARSER_ERROR', {
652
+ name: 'Use fallback parser',
653
+ description: 'Try with a simpler parsing strategy',
654
+ priority: 1,
655
+ execute: async () => {
656
+ console.log(chalk.cyan('๐Ÿ”„ Using fallback parser strategy...'));
657
+ return true;
658
+ }
659
+ });
660
+
661
+ // Memory error recovery
662
+ SystemErrorHandler.registerRecoveryStrategy('OUT_OF_MEMORY', {
663
+ name: 'Reduce processing batch size',
664
+ description: 'Process files in smaller batches to reduce memory usage',
665
+ priority: 3,
666
+ execute: async () => {
667
+ console.log(chalk.cyan('๐Ÿง  Reducing memory usage by processing smaller batches...'));
668
+ process.env.BASEGUARD_BATCH_SIZE = '10'; // Reduce from default
669
+ return true;
670
+ }
671
+ });
672
+
673
+ // Too many files recovery
674
+ SystemErrorHandler.registerRecoveryStrategy('TOO_MANY_FILES', {
675
+ name: 'Limit file processing',
676
+ description: 'Reduce the number of files processed simultaneously',
677
+ priority: 2,
678
+ execute: async () => {
679
+ console.log(chalk.cyan('๐Ÿ“ Limiting concurrent file processing...'));
680
+ process.env.BASEGUARD_MAX_FILES = '100'; // Reduce from default
681
+ return true;
682
+ }
683
+ });
684
+
685
+ // Timeout error recovery
686
+ SystemErrorHandler.registerRecoveryStrategy('TIMEOUT_ERROR', {
687
+ name: 'Increase timeout and retry',
688
+ description: 'Increase timeout settings and retry the operation',
689
+ priority: 2,
690
+ execute: async () => {
691
+ console.log(chalk.cyan('โฑ๏ธ Increasing timeout settings...'));
692
+ process.env.BASEGUARD_TIMEOUT = '60000'; // 60 seconds
693
+ return true;
694
+ }
695
+ });
696
+
697
+ // Permission denied recovery
698
+ SystemErrorHandler.registerRecoveryStrategy('PERMISSION_DENIED', {
699
+ name: 'Skip protected files',
700
+ description: 'Skip files that cannot be accessed and continue',
701
+ priority: 1,
702
+ execute: async () => {
703
+ console.log(chalk.cyan('๐Ÿ”’ Skipping protected files and continuing...'));
704
+ return true;
705
+ }
706
+ });
707
+
708
+ // Disk full recovery
709
+ SystemErrorHandler.registerRecoveryStrategy('DISK_FULL', {
710
+ name: 'Clean temporary files',
711
+ description: 'Clean up temporary files to free space',
712
+ priority: 3,
713
+ execute: async () => {
714
+ try {
715
+ console.log(chalk.cyan('๐Ÿงน Cleaning temporary files...'));
716
+
717
+ // Clean BaseGuard temp files
718
+ const tempDir = path.join(process.cwd(), '.baseguard', 'temp');
719
+ try {
720
+ await fs.rm(tempDir, { recursive: true, force: true });
721
+ await fs.mkdir(tempDir, { recursive: true });
722
+ } catch (error) {
723
+ // Ignore if temp dir doesn't exist
724
+ }
725
+
726
+ // Clean system temp files (be careful here)
727
+ const systemTemp = process.env.TMPDIR || process.env.TEMP || '/tmp';
728
+ const baseguardTempPattern = path.join(systemTemp, 'baseguard-*');
729
+
730
+ try {
731
+ const { glob } = await import('glob');
732
+ const tempFiles = await glob(baseguardTempPattern);
733
+ for (const file of tempFiles) {
734
+ try {
735
+ await fs.rm(file, { recursive: true, force: true });
736
+ } catch (error) {
737
+ // Ignore individual file errors
738
+ }
739
+ }
740
+ } catch (error) {
741
+ // Ignore if glob fails
742
+ }
743
+
744
+ return true;
745
+ } catch (error) {
746
+ return false;
747
+ }
748
+ }
749
+ });
750
+ }
751
+ }
752
+
753
+ // Initialize recovery strategies and signal handlers when module loads
754
+ SystemErrorHandler.initializeRecoveryStrategies();
755
+ SystemErrorHandler.handleProcessSignals();