error-solution-ai-buddy 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Error History Service - Stores and retrieves past errors
3
+ *
4
+ * WHY JSON file instead of SQLite:
5
+ * 1. No native compilation needed (works on all systems without Visual Studio)
6
+ * 2. Simple to understand and debug
7
+ * 3. Good enough for local history
8
+ * 4. Can be upgraded to SQLite later if needed
9
+ *
10
+ * This service allows:
11
+ * - Saving errors and their explanations for later reference
12
+ * - Searching past errors ("have I seen this before?")
13
+ * - Learning which errors are most common
14
+ */
15
+
16
+ import { join } from 'path';
17
+ import { homedir } from 'os';
18
+ import { mkdirSync, existsSync, readFileSync, writeFileSync } from 'fs';
19
+
20
+ export class HistoryService {
21
+ constructor(filePath = null) {
22
+ // Default location: ~/.errbuddy/history.json
23
+ if (!filePath) {
24
+ const errbuddyDir = join(homedir(), '.errbuddy');
25
+ if (!existsSync(errbuddyDir)) {
26
+ mkdirSync(errbuddyDir, { recursive: true });
27
+ }
28
+ filePath = join(errbuddyDir, 'history.json');
29
+ }
30
+
31
+ this.filePath = filePath;
32
+ this.data = this.load();
33
+ }
34
+
35
+ /**
36
+ * Load history from file
37
+ */
38
+ load() {
39
+ try {
40
+ if (existsSync(this.filePath)) {
41
+ const content = readFileSync(this.filePath, 'utf-8');
42
+ return JSON.parse(content);
43
+ }
44
+ } catch (err) {
45
+ // Silently fail and use default
46
+ }
47
+
48
+ // Return default structure
49
+ return {
50
+ errors: [],
51
+ nextId: 1
52
+ };
53
+ }
54
+
55
+ /**
56
+ * Save history to file
57
+ */
58
+ save() {
59
+ try {
60
+ writeFileSync(this.filePath, JSON.stringify(this.data, null, 2));
61
+ } catch (err) {
62
+ // Silently fail - history is not critical
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Save an error and its explanation
68
+ */
69
+ saveError(error, explanation, command = null) {
70
+ const entry = {
71
+ id: this.data.nextId++,
72
+ timestamp: new Date().toISOString(),
73
+ language: error.language || null,
74
+ errorType: error.errorType || null,
75
+ errorMessage: error.errorMessage || null,
76
+ errorFull: (error.fullText && error.fullText.length > 2000) ? error.fullText.substring(0, 2000) + '... (truncated)' : (error.fullText || null),
77
+ filePath: error.file || null,
78
+ lineNumber: error.line || null,
79
+ explanationWhat: explanation?.what || null,
80
+ explanationWhy: explanation?.why || null,
81
+ explanationFix: explanation?.fix || null,
82
+ explanationExample: explanation?.example || null,
83
+ command: command,
84
+ helpful: null
85
+ };
86
+
87
+ this.data.errors.push(entry);
88
+
89
+ // Keep only last 500 errors to prevent file from growing too large
90
+ if (this.data.errors.length > 500) {
91
+ this.data.errors = this.data.errors.slice(-500);
92
+ }
93
+
94
+ this.save();
95
+ return entry.id;
96
+ }
97
+
98
+ /**
99
+ * Mark an explanation as helpful or not
100
+ */
101
+ markHelpful(id, helpful) {
102
+ const entry = this.data.errors.find(e => e.id === id);
103
+ if (entry) {
104
+ entry.helpful = helpful;
105
+ this.save();
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Get recent errors
111
+ */
112
+ getRecent(limit = 10) {
113
+ return this.data.errors
114
+ .slice(-limit)
115
+ .reverse();
116
+ }
117
+
118
+ /**
119
+ * Search errors by type or message
120
+ */
121
+ search(query, limit = 20) {
122
+ const lowerQuery = query.toLowerCase();
123
+ return this.data.errors
124
+ .filter(e =>
125
+ (e.errorType && e.errorType.toLowerCase().includes(lowerQuery)) ||
126
+ (e.errorMessage && e.errorMessage.toLowerCase().includes(lowerQuery))
127
+ )
128
+ .slice(-limit)
129
+ .reverse();
130
+ }
131
+
132
+ /**
133
+ * Find similar errors (useful for "have I seen this before?")
134
+ */
135
+ findSimilar(errorType, language) {
136
+ return this.data.errors
137
+ .filter(e => e.errorType === errorType && e.language === language)
138
+ .slice(-5)
139
+ .reverse();
140
+ }
141
+
142
+ /**
143
+ * Get error statistics
144
+ */
145
+ getStats() {
146
+ const errors = this.data.errors;
147
+
148
+ // Count by language
149
+ const byLanguage = {};
150
+ errors.forEach(e => {
151
+ if (e.language) {
152
+ byLanguage[e.language] = (byLanguage[e.language] || 0) + 1;
153
+ }
154
+ });
155
+
156
+ // Count by error type
157
+ const byType = {};
158
+ errors.forEach(e => {
159
+ if (e.errorType) {
160
+ byType[e.errorType] = (byType[e.errorType] || 0) + 1;
161
+ }
162
+ });
163
+
164
+ // Sort and format
165
+ const sortedByLanguage = Object.entries(byLanguage)
166
+ .map(([language, count]) => ({ language, count }))
167
+ .sort((a, b) => b.count - a.count);
168
+
169
+ const sortedByType = Object.entries(byType)
170
+ .map(([errorType, count]) => ({ errorType, count }))
171
+ .sort((a, b) => b.count - a.count)
172
+ .slice(0, 10);
173
+
174
+ // Helpful stats
175
+ const helpful = errors.filter(e => e.helpful === true).length;
176
+ const notHelpful = errors.filter(e => e.helpful === false).length;
177
+ const noFeedback = errors.filter(e => e.helpful === null).length;
178
+
179
+ return {
180
+ total: errors.length,
181
+ byLanguage: sortedByLanguage,
182
+ topErrors: sortedByType,
183
+ feedback: { helpful, notHelpful, noFeedback }
184
+ };
185
+ }
186
+
187
+ /**
188
+ * Clear all history
189
+ */
190
+ clearHistory() {
191
+ this.data = { errors: [], nextId: 1 };
192
+ this.save();
193
+ }
194
+ }
@@ -0,0 +1,409 @@
1
+ /**
2
+ * Error Listener - Detects and analyzes errors from stderr
3
+ *
4
+ * This module is responsible for:
5
+ * 1. Detecting if output contains an error (vs. just warnings/info)
6
+ * 2. Identifying the programming language
7
+ * 3. Extracting useful context (error type, line number, file)
8
+ * 4. Providing pattern-based explanations (fallback when AI unavailable)
9
+ */
10
+
11
+ /**
12
+ * Error patterns for each language
13
+ *
14
+ * WHY we use patterns:
15
+ * 1. Fast detection without AI
16
+ * 2. Fallback when Ollama is offline
17
+ * 3. Helps identify language for better AI prompts
18
+ */
19
+ const ERROR_PATTERNS = {
20
+ javascript: {
21
+ // Pattern: matches the error type and extracts it
22
+ patterns: [
23
+ /^(\w*Error): (.+)$/m, // TypeError: message
24
+ /^(\w*Exception): (.+)$/m, // ReferenceException: message
25
+ /SyntaxError: (.+)/, // Syntax errors
26
+ /Cannot find module '(.+)'/, // Module not found
27
+ /is not defined/, // Undefined variable
28
+ /is not a function/, // Calling non-function
29
+ /Cannot read propert(?:y|ies) of (undefined|null)/, // Null/undefined access
30
+ /ENOENT|EACCES|ECONNREFUSED/, // System errors
31
+ ],
32
+ stackTraceIndicator: /at\s+(?:\S+\s+)?\(?.*:\d+:\d+\)?/,
33
+ fileLinePattern: /at\s+.*\(?(.*):(\d+):(\d+)\)?/,
34
+ },
35
+
36
+ java: {
37
+ patterns: [
38
+ /^Exception in thread ".*" (\w+(?:\.\w+)*): (.*)$/m, // Exception in thread "main"
39
+ /^(\w+(?:\.\w+)*Exception): (.*)$/m, // NullPointerException:
40
+ /^(\w+(?:\.\w+)*Error): (.*)$/m, // OutOfMemoryError:
41
+ /^Caused by: (\w+(?:\.\w+)*): (.*)$/m, // Caused by:
42
+ /cannot find symbol/, // Compile error
43
+ /error: (.+)/, // Generic error
44
+ /incompatible types/, // Type mismatch
45
+ ],
46
+ stackTraceIndicator: /at\s+[\w.$]+\([\w.]+:\d+\)/,
47
+ fileLinePattern: /at\s+[\w.$]+\(([\w.]+):(\d+)\)/,
48
+ },
49
+
50
+ csharp: {
51
+ patterns: [
52
+ /^Unhandled exception\. (\w+(?:\.\w+)*): (.*)$/m, // Unhandled exception.
53
+ /^(\w+(?:\.\w+)*Exception): (.*)$/m, // NullReferenceException:
54
+ /error CS\d+: (.+)/, // Compiler error
55
+ /Object reference not set/, // NullRef
56
+ /Index was outside the bounds/, // Array bounds
57
+ /The type or namespace name .* could not be found/, // Missing type
58
+ ],
59
+ stackTraceIndicator: /at\s+[\w.<>]+\(.*\)\s+in\s+.*:line\s+\d+/,
60
+ fileLinePattern: /in\s+(.*):line\s+(\d+)/,
61
+ }
62
+ };
63
+
64
+ /**
65
+ * Common error explanations (used when AI is unavailable)
66
+ *
67
+ * WHY we have local explanations:
68
+ * - Works offline
69
+ * - Instant response (no network latency)
70
+ * - Fallback if Ollama fails
71
+ */
72
+ const COMMON_ERRORS = {
73
+ javascript: {
74
+ 'TypeError': {
75
+ 'Cannot read propert': {
76
+ what: 'You tried to access a property on something that is undefined or null.',
77
+ why: 'The variable you\'re accessing doesn\'t exist or hasn\'t been assigned a value yet.',
78
+ fix: 'Add a null check before accessing properties.',
79
+ example: `// Instead of: data.users.map(...)
80
+ // Do this:
81
+ if (data && data.users) {
82
+ data.users.map(...)
83
+ }`
84
+ },
85
+ 'is not a function': {
86
+ what: 'You tried to call something as a function, but it\'s not a function.',
87
+ why: 'The variable might be undefined, or you imported/accessed the wrong thing.',
88
+ fix: 'Check that you\'re calling the right function and it\'s properly imported.',
89
+ example: `// Check if it's a function first:
90
+ if (typeof myFunc === 'function') {
91
+ myFunc();
92
+ }`
93
+ }
94
+ },
95
+ 'ReferenceError': {
96
+ 'is not defined': {
97
+ what: 'You\'re using a variable that doesn\'t exist in the current scope.',
98
+ why: 'Either the variable was never declared, or it\'s spelled wrong, or it\'s out of scope.',
99
+ fix: 'Make sure the variable is declared (const, let, var) before using it.',
100
+ example: `// Wrong:
101
+ console.log(myVar);
102
+
103
+ // Right:
104
+ const myVar = 'hello';
105
+ console.log(myVar);`
106
+ }
107
+ },
108
+ 'SyntaxError': {
109
+ 'default': {
110
+ what: 'Your code has a syntax mistake that JavaScript cannot understand.',
111
+ why: 'Missing brackets, quotes, semicolons, or other syntax issues.',
112
+ fix: 'Check the line number mentioned and look for missing or extra characters.',
113
+ example: `// Common issues:
114
+ // - Missing closing bracket: { or }
115
+ // - Missing closing parenthesis: ( or )
116
+ // - Missing quotes: " or '
117
+ // - Extra/missing comma in arrays/objects`
118
+ }
119
+ },
120
+ 'ENOENT': {
121
+ 'default': {
122
+ what: 'A file or directory you tried to access doesn\'t exist.',
123
+ why: 'The path is wrong, or the file was deleted/moved.',
124
+ fix: 'Check that the file path is correct and the file exists.',
125
+ example: `// Check if file exists first:
126
+ const fs = require('fs');
127
+ if (fs.existsSync('./myfile.txt')) {
128
+ // read file
129
+ }`
130
+ }
131
+ },
132
+ 'ECONNREFUSED': {
133
+ 'default': {
134
+ what: 'Your code tried to connect to a server that refused the connection.',
135
+ why: 'The server might not be running, wrong port, or firewall blocking.',
136
+ fix: 'Make sure the server is running and the port/host are correct.',
137
+ example: `// Common checks:
138
+ // 1. Is the database/server running?
139
+ // 2. Is the port correct? (e.g., 3000, 5432)
140
+ // 3. Is it localhost or 127.0.0.1?`
141
+ }
142
+ }
143
+ },
144
+
145
+ java: {
146
+ 'NullPointerException': {
147
+ 'default': {
148
+ what: 'You tried to use an object that is null (doesn\'t exist).',
149
+ why: 'A variable wasn\'t initialized, or a method returned null.',
150
+ fix: 'Add null checks before using objects.',
151
+ example: `// Instead of: user.getName()
152
+ // Do this:
153
+ if (user != null) {
154
+ user.getName();
155
+ }
156
+
157
+ // Or use Optional:
158
+ Optional.ofNullable(user).map(User::getName);`
159
+ }
160
+ },
161
+ 'ArrayIndexOutOfBoundsException': {
162
+ 'default': {
163
+ what: 'You tried to access an array index that doesn\'t exist.',
164
+ why: 'Arrays are 0-indexed, so an array of size 5 has indices 0-4.',
165
+ fix: 'Check array length before accessing an index.',
166
+ example: `// Wrong: accessing index 5 in array of size 5
167
+ int[] arr = new int[5];
168
+ arr[5] = 10; // Error! Valid indices are 0-4
169
+
170
+ // Right:
171
+ if (index >= 0 && index < arr.length) {
172
+ arr[index] = 10;
173
+ }`
174
+ }
175
+ },
176
+ 'ClassNotFoundException': {
177
+ 'default': {
178
+ what: 'Java cannot find a class you\'re trying to use.',
179
+ why: 'The class isn\'t in your classpath, or the name is misspelled.',
180
+ fix: 'Check your dependencies (pom.xml/build.gradle) and class name.',
181
+ example: `// Check:
182
+ // 1. Is the dependency in pom.xml?
183
+ // 2. Did you run 'mvn install'?
184
+ // 3. Is the class name spelled correctly?
185
+ // 4. Is the import statement correct?`
186
+ }
187
+ },
188
+ 'cannot find symbol': {
189
+ 'default': {
190
+ what: 'Java compiler cannot find a variable, method, or class you\'re using.',
191
+ why: 'Typo in name, missing import, or wrong scope.',
192
+ fix: 'Check spelling and make sure imports are correct.',
193
+ example: `// Common causes:
194
+ // 1. Typo: myVarible instead of myVariable
195
+ // 2. Missing import statement
196
+ // 3. Variable declared in different scope
197
+ // 4. Method doesn't exist in that class`
198
+ }
199
+ }
200
+ },
201
+
202
+ csharp: {
203
+ 'NullReferenceException': {
204
+ 'default': {
205
+ what: 'You tried to use an object that is null.',
206
+ why: 'A variable wasn\'t initialized, or a method returned null.',
207
+ fix: 'Add null checks or use null-conditional operators.',
208
+ example: `// Instead of: user.Name
209
+ // Use null-conditional:
210
+ var name = user?.Name;
211
+
212
+ // Or null check:
213
+ if (user != null)
214
+ {
215
+ var name = user.Name;
216
+ }`
217
+ }
218
+ },
219
+ 'IndexOutOfRangeException': {
220
+ 'default': {
221
+ what: 'You tried to access an array/list index that doesn\'t exist.',
222
+ why: 'The index is negative or greater than the collection size.',
223
+ fix: 'Check collection length before accessing an index.',
224
+ example: `// Wrong:
225
+ var items = new int[5];
226
+ var x = items[5]; // Error!
227
+
228
+ // Right:
229
+ if (index >= 0 && index < items.Length)
230
+ {
231
+ var x = items[index];
232
+ }`
233
+ }
234
+ },
235
+ 'CS0103': {
236
+ 'default': {
237
+ what: 'The name you\'re using doesn\'t exist in the current context.',
238
+ why: 'Variable not declared, typo, or missing using statement.',
239
+ fix: 'Check variable spelling and add any missing using statements.',
240
+ example: `// Did you forget to add:
241
+ using System.Linq;
242
+ using YourNamespace;
243
+
244
+ // Or declare the variable:
245
+ var myVariable = "value";`
246
+ }
247
+ },
248
+ 'CS0246': {
249
+ 'default': {
250
+ what: 'C# cannot find a type or namespace you\'re trying to use.',
251
+ why: 'Missing using statement, missing NuGet package, or typo.',
252
+ fix: 'Add the using statement or install the required NuGet package.',
253
+ example: `// Add using statement:
254
+ using Newtonsoft.Json;
255
+
256
+ // Or install package:
257
+ // dotnet add package Newtonsoft.Json`
258
+ }
259
+ }
260
+ }
261
+ };
262
+
263
+ export class ErrorListener {
264
+ constructor() {
265
+ this.patterns = ERROR_PATTERNS;
266
+ this.explanations = COMMON_ERRORS;
267
+ }
268
+
269
+ /**
270
+ * Quick check if text looks like an error
271
+ *
272
+ * WHY: We don't want to trigger AI for every output.
273
+ * Only analyze when there's likely an actual error.
274
+ */
275
+ detectError(text) {
276
+ const errorIndicators = [
277
+ /error/i,
278
+ /exception/i,
279
+ /failed/i,
280
+ /cannot/i,
281
+ /unable to/i,
282
+ /fatal/i,
283
+ /ENOENT|EACCES|ECONNREFUSED/,
284
+ /at\s+[\w.]+\(.*:\d+/, // Stack trace pattern
285
+ ];
286
+
287
+ return errorIndicators.some(pattern => pattern.test(text));
288
+ }
289
+
290
+ /**
291
+ * Analyze the error and extract useful information
292
+ */
293
+ analyzeError(errorText) {
294
+ const analysis = {
295
+ isError: false,
296
+ language: this.detectLanguage(errorText),
297
+ errorType: null,
298
+ errorMessage: null,
299
+ file: null,
300
+ line: null,
301
+ column: null,
302
+ localExplanation: null,
303
+ };
304
+
305
+ if (!analysis.language) {
306
+ // Try to still detect if it's an error
307
+ analysis.isError = this.detectError(errorText);
308
+ analysis.language = 'unknown';
309
+ return analysis;
310
+ }
311
+
312
+ const langPatterns = this.patterns[analysis.language];
313
+
314
+ // Try to match error patterns
315
+ for (const pattern of langPatterns.patterns) {
316
+ const match = errorText.match(pattern);
317
+ if (match) {
318
+ analysis.isError = true;
319
+ analysis.errorType = match[1] || null;
320
+ analysis.errorMessage = match[2] || match[1] || null;
321
+ break;
322
+ }
323
+ }
324
+
325
+ // Extract file and line number
326
+ if (langPatterns.fileLinePattern) {
327
+ const fileMatch = errorText.match(langPatterns.fileLinePattern);
328
+ if (fileMatch) {
329
+ analysis.file = fileMatch[1];
330
+ analysis.line = fileMatch[2];
331
+ analysis.column = fileMatch[3] || null;
332
+ }
333
+ }
334
+
335
+ // Get local explanation if available
336
+ analysis.localExplanation = this.getLocalExplanation(analysis, errorText);
337
+
338
+ return analysis;
339
+ }
340
+
341
+ /**
342
+ * Detect programming language from error output
343
+ */
344
+ detectLanguage(errorText) {
345
+ // JavaScript/Node.js indicators
346
+ if (
347
+ /(TypeError|ReferenceError|SyntaxError)[:\s]/i.test(errorText) ||
348
+ /at\s+\S+\s+\(.*\.js:\d+:\d+\)/.test(errorText) ||
349
+ /at\s+.*\.js:\d+:\d+/.test(errorText) ||
350
+ /node_modules/.test(errorText) ||
351
+ /ENOENT|EACCES|ECONNREFUSED/.test(errorText) ||
352
+ /Cannot find module/.test(errorText)
353
+ ) {
354
+ return 'javascript';
355
+ }
356
+
357
+ // Java indicators
358
+ if (
359
+ /at\s+[\w.$]+\([\w.]+\.java:\d+\)/.test(errorText) ||
360
+ /Exception in thread/.test(errorText) ||
361
+ /\.java:\d+:/.test(errorText) ||
362
+ /at\s+java\./.test(errorText)
363
+ ) {
364
+ return 'java';
365
+ }
366
+
367
+ // C# indicators
368
+ if (
369
+ /at\s+[\w.<>]+\(.*\)\s+in\s+.*:line\s+\d+/.test(errorText) ||
370
+ /error CS\d+/.test(errorText) ||
371
+ /\.cs\(\d+,\d+\)/.test(errorText) ||
372
+ /Unhandled exception\. System\./.test(errorText) ||
373
+ /at\s+System\./.test(errorText)
374
+ ) {
375
+ return 'csharp';
376
+ }
377
+
378
+ return null;
379
+ }
380
+
381
+ /**
382
+ * Get local explanation for an error (used when AI is unavailable)
383
+ */
384
+ getLocalExplanation(analysis, errorText) {
385
+ if (!analysis.language || !this.explanations[analysis.language]) {
386
+ return null;
387
+ }
388
+
389
+ const langExplanations = this.explanations[analysis.language];
390
+
391
+ // Try to find matching explanation
392
+ for (const [errorType, subErrors] of Object.entries(langExplanations)) {
393
+ if (errorText.includes(errorType) || analysis.errorType?.includes(errorType)) {
394
+ // Check for specific sub-patterns
395
+ for (const [subPattern, explanation] of Object.entries(subErrors)) {
396
+ if (subPattern === 'default' || errorText.includes(subPattern)) {
397
+ return explanation;
398
+ }
399
+ }
400
+ // Return default if exists
401
+ if (subErrors.default) {
402
+ return subErrors.default;
403
+ }
404
+ }
405
+ }
406
+
407
+ return null;
408
+ }
409
+ }