baseguard 1.0.3 โ†’ 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 (167) hide show
  1. package/.baseguardrc.example.json +63 -63
  2. package/.eslintrc.json +24 -24
  3. package/.prettierrc +7 -7
  4. package/CHANGELOG.md +195 -195
  5. package/DEPLOYMENT.md +624 -624
  6. package/DEPLOYMENT_CHECKLIST.md +239 -239
  7. package/DEPLOYMENT_SUMMARY_v1.0.2.md +202 -202
  8. package/QUICK_START.md +134 -134
  9. package/README.md +488 -488
  10. package/RELEASE_NOTES_v1.0.2.md +434 -434
  11. package/bin/base.js +627 -627
  12. package/dist/ai/fix-manager.d.ts.map +1 -1
  13. package/dist/ai/fix-manager.js +1 -1
  14. package/dist/ai/fix-manager.js.map +1 -1
  15. package/dist/ai/gemini-analyzer.d.ts.map +1 -1
  16. package/dist/ai/gemini-analyzer.js +29 -35
  17. package/dist/ai/gemini-analyzer.js.map +1 -1
  18. package/dist/ai/gemini-code-fixer.d.ts.map +1 -1
  19. package/dist/ai/gemini-code-fixer.js +58 -58
  20. package/dist/ai/gemini-code-fixer.js.map +1 -1
  21. package/dist/ai/jules-implementer.d.ts +3 -0
  22. package/dist/ai/jules-implementer.d.ts.map +1 -1
  23. package/dist/ai/jules-implementer.js +63 -32
  24. package/dist/ai/jules-implementer.js.map +1 -1
  25. package/dist/ai/unified-code-fixer.js.map +1 -1
  26. package/dist/commands/check.d.ts.map +1 -1
  27. package/dist/commands/check.js +1 -1
  28. package/dist/commands/check.js.map +1 -1
  29. package/dist/commands/config.js +2 -1
  30. package/dist/commands/config.js.map +1 -1
  31. package/dist/commands/fix.d.ts.map +1 -1
  32. package/dist/commands/fix.js +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.map +1 -1
  67. package/dist/core/startup-optimizer.js +4 -8
  68. package/dist/core/startup-optimizer.js.map +1 -1
  69. package/dist/core/system-error-handler.d.ts.map +1 -1
  70. package/dist/core/system-error-handler.js.map +1 -1
  71. package/dist/git/automation-engine.d.ts.map +1 -1
  72. package/dist/git/automation-engine.js +5 -4
  73. package/dist/git/automation-engine.js.map +1 -1
  74. package/dist/git/github-manager.d.ts.map +1 -1
  75. package/dist/git/github-manager.js.map +1 -1
  76. package/dist/git/hook-manager.js +5 -5
  77. package/dist/git/hook-manager.js.map +1 -1
  78. package/dist/parsers/parser-manager.d.ts.map +1 -1
  79. package/dist/parsers/parser-manager.js +1 -1
  80. package/dist/parsers/parser-manager.js.map +1 -1
  81. package/dist/parsers/svelte-parser.js +1 -1
  82. package/dist/parsers/svelte-parser.js.map +1 -1
  83. package/dist/parsers/vanilla-parser.d.ts.map +1 -1
  84. package/dist/parsers/vanilla-parser.js.map +1 -1
  85. package/dist/parsers/vue-parser.d.ts.map +1 -1
  86. package/dist/parsers/vue-parser.js.map +1 -1
  87. package/dist/ui/components.d.ts +1 -1
  88. package/dist/ui/components.d.ts.map +1 -1
  89. package/dist/ui/components.js +11 -11
  90. package/dist/ui/components.js.map +1 -1
  91. package/dist/ui/terminal-header.js +14 -14
  92. package/package.json +105 -105
  93. package/src/ai/__tests__/gemini-analyzer.test.ts +180 -180
  94. package/src/ai/agentkit-orchestrator.ts +533 -533
  95. package/src/ai/fix-manager.ts +362 -362
  96. package/src/ai/gemini-analyzer.ts +665 -671
  97. package/src/ai/gemini-code-fixer.ts +539 -540
  98. package/src/ai/index.ts +3 -3
  99. package/src/ai/jules-implementer.ts +504 -460
  100. package/src/ai/unified-code-fixer.ts +347 -347
  101. package/src/commands/automation.ts +343 -343
  102. package/src/commands/check.ts +298 -299
  103. package/src/commands/config.ts +584 -583
  104. package/src/commands/fix.ts +264 -238
  105. package/src/commands/index.ts +6 -6
  106. package/src/commands/init.ts +155 -155
  107. package/src/commands/status.ts +306 -306
  108. package/src/core/api-key-manager.ts +298 -298
  109. package/src/core/baseguard.ts +757 -756
  110. package/src/core/baseline-checker.ts +564 -563
  111. package/src/core/cache-manager.ts +271 -271
  112. package/src/core/configuration-recovery.ts +672 -673
  113. package/src/core/configuration.ts +595 -595
  114. package/src/core/debug-logger.ts +590 -590
  115. package/src/core/directory-filter.ts +420 -420
  116. package/src/core/error-handler.ts +518 -517
  117. package/src/core/file-processor.ts +337 -337
  118. package/src/core/gitignore-manager.ts +168 -168
  119. package/src/core/graceful-degradation-manager.ts +596 -596
  120. package/src/core/index.ts +16 -16
  121. package/src/core/lazy-loader.ts +317 -307
  122. package/src/core/memory-manager.ts +290 -295
  123. package/src/core/parser-worker.ts +33 -0
  124. package/src/core/startup-optimizer.ts +246 -255
  125. package/src/core/system-error-handler.ts +755 -756
  126. package/src/git/automation-engine.ts +361 -361
  127. package/src/git/github-manager.ts +190 -192
  128. package/src/git/hook-manager.ts +210 -210
  129. package/src/git/index.ts +3 -3
  130. package/src/index.ts +7 -7
  131. package/src/parsers/feature-validator.ts +558 -558
  132. package/src/parsers/index.ts +7 -7
  133. package/src/parsers/parser-manager.ts +418 -419
  134. package/src/parsers/parser.ts +25 -25
  135. package/src/parsers/react-parser-optimized.ts +160 -160
  136. package/src/parsers/react-parser.ts +358 -358
  137. package/src/parsers/svelte-parser.ts +510 -510
  138. package/src/parsers/vanilla-parser.ts +685 -686
  139. package/src/parsers/vue-parser.ts +476 -478
  140. package/src/types/index.ts +95 -95
  141. package/src/ui/components.ts +567 -567
  142. package/src/ui/help.ts +192 -192
  143. package/src/ui/index.ts +3 -3
  144. package/src/ui/prompts.ts +680 -680
  145. package/src/ui/terminal-header.ts +58 -58
  146. package/test-build.js +40 -40
  147. package/test-config-commands.js +55 -55
  148. package/test-header-simple.js +32 -32
  149. package/test-terminal-header.js +11 -11
  150. package/test-ui.js +28 -28
  151. package/tests/e2e/baseguard.e2e.test.ts +515 -515
  152. package/tests/e2e/cross-platform.e2e.test.ts +419 -419
  153. package/tests/e2e/git-integration.e2e.test.ts +486 -486
  154. package/tests/fixtures/react-project/package.json +13 -13
  155. package/tests/fixtures/react-project/src/App.css +75 -75
  156. package/tests/fixtures/react-project/src/App.tsx +76 -76
  157. package/tests/fixtures/svelte-project/package.json +10 -10
  158. package/tests/fixtures/svelte-project/src/App.svelte +368 -368
  159. package/tests/fixtures/vanilla-project/index.html +75 -75
  160. package/tests/fixtures/vanilla-project/script.js +330 -330
  161. package/tests/fixtures/vanilla-project/styles.css +358 -358
  162. package/tests/fixtures/vue-project/package.json +11 -11
  163. package/tests/fixtures/vue-project/src/App.vue +215 -215
  164. package/tsconfig.json +34 -34
  165. package/vitest.config.ts +11 -11
  166. package/dist/terminal-header.d.ts +0 -12
  167. package/dist/terminal-header.js +0 -45
@@ -1,756 +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
- // Only log for non-interactive signals (SIGTERM, not SIGINT from Ctrl+C)
503
- if (signal !== 'SIGINT') {
504
- console.log(chalk.yellow(`\nโš ๏ธ Received ${signal}, shutting down gracefully...`));
505
- }
506
-
507
- // Cleanup intervals and timers
508
- try {
509
- const { StartupOptimizer } = await import('./startup-optimizer.js');
510
- StartupOptimizer.cleanup();
511
- } catch (error) {
512
- // Ignore cleanup errors
513
- }
514
-
515
- // Save error log before exit (but don't wait too long)
516
- try {
517
- const savePromise = SystemErrorHandler.saveErrorLog();
518
- await Promise.race([
519
- savePromise,
520
- new Promise(resolve => setTimeout(resolve, 100)) // Max 100ms wait
521
- ]);
522
- } catch (error) {
523
- // Ignore errors during shutdown
524
- }
525
-
526
- process.exit(0);
527
- };
528
-
529
- process.on('SIGINT', () => gracefulShutdown('SIGINT'));
530
- process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
531
-
532
- // Handle uncaught exceptions
533
- process.on('uncaughtException', (error) => {
534
- const systemError = SystemErrorHandler.wrapError(error, {
535
- operation: 'uncaught-exception',
536
- details: { stack: error.stack }
537
- });
538
-
539
- SystemErrorHandler.displayError(systemError, { verbose: true });
540
- SystemErrorHandler.saveErrorLog().finally(() => {
541
- process.exit(1);
542
- });
543
- });
544
-
545
- // Handle unhandled promise rejections
546
- process.on('unhandledRejection', (reason, promise) => {
547
- const systemError = SystemErrorHandler.wrapError(reason, {
548
- operation: 'unhandled-rejection',
549
- details: { promise: promise.toString() }
550
- });
551
-
552
- SystemErrorHandler.displayError(systemError, { verbose: true });
553
- SystemErrorHandler.saveErrorLog().finally(() => {
554
- process.exit(1);
555
- });
556
- });
557
- }
558
-
559
- private static sleep(ms: number): Promise<void> {
560
- return new Promise(resolve => setTimeout(resolve, ms));
561
- }
562
-
563
- // Initialize default recovery strategies
564
- static initializeRecoveryStrategies(): void {
565
- // File not found recovery
566
- SystemErrorHandler.registerRecoveryStrategy('FILE_NOT_FOUND', {
567
- name: 'Create missing directories',
568
- description: 'Create missing parent directories',
569
- priority: 3,
570
- execute: async () => {
571
- try {
572
- // Try to create parent directories for common paths
573
- const commonPaths = ['src', 'app', 'components', 'pages', '.baseguard'];
574
- for (const dirPath of commonPaths) {
575
- try {
576
- await fs.mkdir(dirPath, { recursive: true });
577
- } catch (error) {
578
- // Ignore if directory already exists
579
- }
580
- }
581
- return true;
582
- } catch (error) {
583
- return false;
584
- }
585
- }
586
- });
587
-
588
- // Network error recovery
589
- SystemErrorHandler.registerRecoveryStrategy('NETWORK_ERROR', {
590
- name: 'Switch to offline mode',
591
- description: 'Continue with offline-only features',
592
- priority: 2,
593
- execute: async () => {
594
- console.log(chalk.cyan('๐Ÿ”Œ Switching to offline mode...'));
595
- SystemErrorHandler.setOfflineMode(true);
596
- process.env.BASEGUARD_OFFLINE = 'true';
597
- return true;
598
- }
599
- });
600
-
601
- // API error recovery
602
- SystemErrorHandler.registerRecoveryStrategy('API_ERROR', {
603
- name: 'Use cached responses',
604
- description: 'Fall back to cached API responses',
605
- priority: 1,
606
- execute: async () => {
607
- try {
608
- // Check if cache directory exists and has content
609
- const cacheDir = path.join(process.cwd(), '.baseguard', 'cache');
610
- const cacheExists = await fs.access(cacheDir).then(() => true).catch(() => false);
611
-
612
- if (cacheExists) {
613
- const files = await fs.readdir(cacheDir);
614
- if (files.length > 0) {
615
- console.log(chalk.cyan('๐Ÿ“ฆ Using cached API responses'));
616
- return true;
617
- }
618
- }
619
-
620
- // Enable offline mode as fallback
621
- SystemErrorHandler.setOfflineMode(true);
622
- return true;
623
- } catch (error) {
624
- return false;
625
- }
626
- }
627
- });
628
-
629
- // Syntax error recovery
630
- SystemErrorHandler.registerRecoveryStrategy('SYNTAX_ERROR', {
631
- name: 'Skip malformed file',
632
- description: 'Skip the file with syntax errors and continue',
633
- priority: 1,
634
- execute: async () => {
635
- console.log(chalk.cyan('โญ๏ธ Skipping malformed file and continuing...'));
636
- return true;
637
- }
638
- });
639
-
640
- // Configuration error recovery
641
- SystemErrorHandler.registerRecoveryStrategy('CONFIGURATION_ERROR', {
642
- name: 'Create minimal config',
643
- description: 'Create a minimal working configuration',
644
- priority: 2,
645
- execute: async () => {
646
- const configPath = path.join(process.cwd(), '.baseguardrc.json');
647
- return await SystemErrorHandler.createCorruptionRecovery(configPath);
648
- }
649
- });
650
-
651
- // Parser error recovery
652
- SystemErrorHandler.registerRecoveryStrategy('PARSER_ERROR', {
653
- name: 'Use fallback parser',
654
- description: 'Try with a simpler parsing strategy',
655
- priority: 1,
656
- execute: async () => {
657
- console.log(chalk.cyan('๐Ÿ”„ Using fallback parser strategy...'));
658
- return true;
659
- }
660
- });
661
-
662
- // Memory error recovery
663
- SystemErrorHandler.registerRecoveryStrategy('OUT_OF_MEMORY', {
664
- name: 'Reduce processing batch size',
665
- description: 'Process files in smaller batches to reduce memory usage',
666
- priority: 3,
667
- execute: async () => {
668
- console.log(chalk.cyan('๐Ÿง  Reducing memory usage by processing smaller batches...'));
669
- process.env.BASEGUARD_BATCH_SIZE = '10'; // Reduce from default
670
- return true;
671
- }
672
- });
673
-
674
- // Too many files recovery
675
- SystemErrorHandler.registerRecoveryStrategy('TOO_MANY_FILES', {
676
- name: 'Limit file processing',
677
- description: 'Reduce the number of files processed simultaneously',
678
- priority: 2,
679
- execute: async () => {
680
- console.log(chalk.cyan('๐Ÿ“ Limiting concurrent file processing...'));
681
- process.env.BASEGUARD_MAX_FILES = '100'; // Reduce from default
682
- return true;
683
- }
684
- });
685
-
686
- // Timeout error recovery
687
- SystemErrorHandler.registerRecoveryStrategy('TIMEOUT_ERROR', {
688
- name: 'Increase timeout and retry',
689
- description: 'Increase timeout settings and retry the operation',
690
- priority: 2,
691
- execute: async () => {
692
- console.log(chalk.cyan('โฑ๏ธ Increasing timeout settings...'));
693
- process.env.BASEGUARD_TIMEOUT = '60000'; // 60 seconds
694
- return true;
695
- }
696
- });
697
-
698
- // Permission denied recovery
699
- SystemErrorHandler.registerRecoveryStrategy('PERMISSION_DENIED', {
700
- name: 'Skip protected files',
701
- description: 'Skip files that cannot be accessed and continue',
702
- priority: 1,
703
- execute: async () => {
704
- console.log(chalk.cyan('๐Ÿ”’ Skipping protected files and continuing...'));
705
- return true;
706
- }
707
- });
708
-
709
- // Disk full recovery
710
- SystemErrorHandler.registerRecoveryStrategy('DISK_FULL', {
711
- name: 'Clean temporary files',
712
- description: 'Clean up temporary files to free space',
713
- priority: 3,
714
- execute: async () => {
715
- try {
716
- console.log(chalk.cyan('๐Ÿงน Cleaning temporary files...'));
717
-
718
- // Clean BaseGuard temp files
719
- const tempDir = path.join(process.cwd(), '.baseguard', 'temp');
720
- try {
721
- await fs.rm(tempDir, { recursive: true, force: true });
722
- await fs.mkdir(tempDir, { recursive: true });
723
- } catch (error) {
724
- // Ignore if temp dir doesn't exist
725
- }
726
-
727
- // Clean system temp files (be careful here)
728
- const systemTemp = process.env.TMPDIR || process.env.TEMP || '/tmp';
729
- const baseguardTempPattern = path.join(systemTemp, 'baseguard-*');
730
-
731
- try {
732
- const { glob } = await import('glob');
733
- const tempFiles = await glob(baseguardTempPattern);
734
- for (const file of tempFiles) {
735
- try {
736
- await fs.rm(file, { recursive: true, force: true });
737
- } catch (error) {
738
- // Ignore individual file errors
739
- }
740
- }
741
- } catch (error) {
742
- // Ignore if glob fails
743
- }
744
-
745
- return true;
746
- } catch (error) {
747
- return false;
748
- }
749
- }
750
- });
751
- }
752
- }
753
-
754
- // Initialize recovery strategies and signal handlers when module loads
755
- SystemErrorHandler.initializeRecoveryStrategies();
756
- 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();