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.
- package/LICENSE +21 -0
- package/README.md +291 -2
- package/backend/src/ai/ollama-service.js +264 -0
- package/backend/src/services/history-service.js +194 -0
- package/cli/error-listener.js +409 -0
- package/cli/formatter.js +244 -0
- package/cli/index.js +241 -0
- package/cli/package-lock.json +6 -0
- package/cli/package.json +3 -0
- package/package.json +53 -4
|
@@ -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
|
+
}
|