error-solution-ai-buddy 0.0.0-stage → 1.0.1
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 +195 -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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sithija Harshana Jayasinghe
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,196 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# errbuddy
|
|
4
|
+
|
|
5
|
+
**Plain-English explanations for errors, right in your terminal.**
|
|
6
|
+
|
|
7
|
+
Stop copying stack traces into a browser. Prefix your command with `errbuddy` and get *what* went wrong, *why*, and *how to fix it* — from an AI model that runs on your own machine.
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/error-solution-ai-buddy)
|
|
10
|
+
[](https://www.npmjs.com/package/error-solution-ai-buddy)
|
|
11
|
+

|
|
12
|
+

|
|
13
|
+
[](https://github.com/sithija-jayasinghe/solution-ai-buddy-v1/blob/main/error-solution-ai-buddy/LICENSE)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
$ errbuddy node app.js
|
|
19
|
+
|
|
20
|
+
TypeError: Cannot read properties of undefined (reading 'map')
|
|
21
|
+
at app.js:12:5
|
|
22
|
+
|
|
23
|
+
╭─ 🤖 Error Buddy Explanation ──────────────────────────────────────╮
|
|
24
|
+
│ │
|
|
25
|
+
│ 📘 What is this? │
|
|
26
|
+
│ You tried to call .map() on a variable that is undefined. │
|
|
27
|
+
│ │
|
|
28
|
+
│ ❓ Why it happens │
|
|
29
|
+
│ The data you expected (probably from an API) wasn't there yet. │
|
|
30
|
+
│ │
|
|
31
|
+
│ ✅ How to fix it │
|
|
32
|
+
│ Check that the variable exists before calling .map(). │
|
|
33
|
+
│ │
|
|
34
|
+
│ 💡 Example │
|
|
35
|
+
│ if (data) { │
|
|
36
|
+
│ data.map(...) │
|
|
37
|
+
│ } │
|
|
38
|
+
│ │
|
|
39
|
+
╰───────────────────────────────────────────────────────────────────╯
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Why errbuddy
|
|
43
|
+
|
|
44
|
+
When an error shows up, most of us copy it, open a browser, paste it into a search engine or chatbot, read, switch back, and lose our train of thought. errbuddy removes every one of those steps.
|
|
45
|
+
|
|
46
|
+
- **Zero context switching.** The explanation appears right under the error.
|
|
47
|
+
- **Private by default.** The AI runs locally through [Ollama](https://ollama.com). Your code and errors never leave your computer.
|
|
48
|
+
- **Free.** No API keys, no accounts, no usage limits.
|
|
49
|
+
- **Works offline, and without AI.** If Ollama isn't running, errbuddy falls back to built-in explanations for common errors.
|
|
50
|
+
- **Drop-in.** Your program runs exactly as before: same output, same input, same exit code.
|
|
51
|
+
|
|
52
|
+
## Quick start
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install -g error-solution-ai-buddy
|
|
56
|
+
errbuddy node app.js
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
That's it. You'll get pattern-based explanations straight away.
|
|
60
|
+
|
|
61
|
+
### Turn on AI explanations (optional)
|
|
62
|
+
|
|
63
|
+
1. Install Ollama from [ollama.com/download](https://ollama.com/download)
|
|
64
|
+
2. Download the default model (about 400 MB, runs fine on low-RAM laptops):
|
|
65
|
+
```bash
|
|
66
|
+
ollama pull qwen2.5:0.5b
|
|
67
|
+
```
|
|
68
|
+
3. Make sure Ollama is running: open the Ollama app, or run `ollama serve`
|
|
69
|
+
|
|
70
|
+
The next time you run `errbuddy`, it detects Ollama automatically. If the default model isn't installed but another one is, errbuddy uses that instead.
|
|
71
|
+
|
|
72
|
+
## Usage
|
|
73
|
+
|
|
74
|
+
Put `errbuddy` in front of the command you normally run:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
errbuddy node app.js # instead of: node app.js
|
|
78
|
+
errbuddy npm start # instead of: npm start
|
|
79
|
+
errbuddy java MyApp # instead of: java MyApp
|
|
80
|
+
errbuddy dotnet run # instead of: dotnet run
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Options
|
|
84
|
+
|
|
85
|
+
| Option | What it does |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `--model <name>` | Use a different Ollama model, e.g. `--model llama3.2` for richer answers |
|
|
88
|
+
| `--offline` | Skip AI and use built-in pattern matching only |
|
|
89
|
+
| `--no-ai` | Show errors without any explanation |
|
|
90
|
+
| `-V, --version` | Show the installed version |
|
|
91
|
+
| `-h, --help` | Show help |
|
|
92
|
+
|
|
93
|
+
| Command | What it does |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `errbuddy examples` | Show usage examples |
|
|
96
|
+
| `errbuddy config` | Show the current model and Ollama URL |
|
|
97
|
+
|
|
98
|
+
> Put errbuddy's own options **before** your command: `errbuddy --model llama3.2 node app.js`. Anything after the command is passed straight through to it.
|
|
99
|
+
|
|
100
|
+
## Supported languages
|
|
101
|
+
|
|
102
|
+
| Language | Detected errors include |
|
|
103
|
+
|---|---|
|
|
104
|
+
| JavaScript / Node.js | `TypeError`, `ReferenceError`, `SyntaxError`, missing modules |
|
|
105
|
+
| Java | `NullPointerException`, `ArrayIndexOutOfBoundsException`, `ClassNotFoundException`, compile errors |
|
|
106
|
+
| C# / .NET | `NullReferenceException`, `IndexOutOfRangeException`, `CSxxxx` compiler errors |
|
|
107
|
+
|
|
108
|
+
With AI enabled, errbuddy can explain other errors too; pattern matching covers the cases above.
|
|
109
|
+
|
|
110
|
+
## How it works
|
|
111
|
+
|
|
112
|
+
```text
|
|
113
|
+
errbuddy node app.js
|
|
114
|
+
│
|
|
115
|
+
▼
|
|
116
|
+
Runs your command ──► stdout is shown as normal
|
|
117
|
+
│ stderr is shown AND captured
|
|
118
|
+
▼
|
|
119
|
+
Error detector ──► finds the language, error type, file and line
|
|
120
|
+
│
|
|
121
|
+
▼
|
|
122
|
+
Sanitizer ──► strips file paths and anything that looks like a secret
|
|
123
|
+
│
|
|
124
|
+
├── Ollama running? ──► local AI explains WHAT / WHY / FIX / EXAMPLE
|
|
125
|
+
└── Not running? ──► built-in pattern explanation
|
|
126
|
+
│
|
|
127
|
+
▼
|
|
128
|
+
Formatted explanation printed under the error, then your exit code is returned
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Want the design reasoning? See [architecture](https://github.com/sithija-jayasinghe/solution-ai-buddy-v1/blob/main/error-solution-ai-buddy/docs/architecture.md) and [design decisions](https://github.com/sithija-jayasinghe/solution-ai-buddy-v1/blob/main/error-solution-ai-buddy/docs/decisions.md).
|
|
132
|
+
|
|
133
|
+
## Privacy
|
|
134
|
+
|
|
135
|
+
- The AI runs **100% locally** through Ollama. Nothing is sent to the cloud.
|
|
136
|
+
- Before an error reaches the model, absolute file paths (`/Users/...`, `C:\Users\...`) are replaced with `[path]` and values that look like API keys, tokens or passwords are replaced with `[REDACTED]`.
|
|
137
|
+
- No telemetry, no analytics, no account.
|
|
138
|
+
- A local history of errors and explanations is kept in `~/.errbuddy/history.json`. Delete that file at any time to clear it.
|
|
139
|
+
|
|
140
|
+
## VS Code extension
|
|
141
|
+
|
|
142
|
+
The repo also includes an Error Buddy extension for VS Code that explains errors from the integrated terminal or from selected text. It isn't on the Marketplace yet. To try it, install [`error-buddy-1.0.0.vsix`](https://github.com/sithija-jayasinghe/solution-ai-buddy-v1/tree/main/error-solution-ai-buddy/extension) with **Extensions → … → Install from VSIX**.
|
|
143
|
+
|
|
144
|
+
Commands: *Explain Selected Error*, *Explain Last Terminal Error*, *Toggle Auto-Explain*, *Open Error Buddy Terminal*, *Show Error History*.
|
|
145
|
+
|
|
146
|
+
## Troubleshooting
|
|
147
|
+
|
|
148
|
+
**"Ollama not detected"**
|
|
149
|
+
1. Open the Ollama app, or run `ollama serve`
|
|
150
|
+
2. Check it responds: `curl http://localhost:11434/api/tags`
|
|
151
|
+
3. Make sure you have a model: `ollama pull qwen2.5:0.5b`
|
|
152
|
+
|
|
153
|
+
**The AI explanation timed out**
|
|
154
|
+
Your machine may be busy or the model too large. Use the default `qwen2.5:0.5b`, or run with `--offline`.
|
|
155
|
+
|
|
156
|
+
**`errbuddy: command not found`**
|
|
157
|
+
Restart your terminal. If it still fails, check that npm's global bin folder is on your `PATH` (`npm prefix -g` shows where it is).
|
|
158
|
+
|
|
159
|
+
## Development
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
git clone https://github.com/sithija-jayasinghe/solution-ai-buddy-v1.git
|
|
163
|
+
cd solution-ai-buddy-v1/error-solution-ai-buddy
|
|
164
|
+
npm install
|
|
165
|
+
npm test # 62 tests, Node's built-in test runner
|
|
166
|
+
npm link # use your local copy as the global `errbuddy`
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
```text
|
|
170
|
+
error-solution-ai-buddy/
|
|
171
|
+
├── cli/
|
|
172
|
+
│ ├── index.js # CLI entry: runs the command, wires everything together
|
|
173
|
+
│ ├── error-listener.js # language detection and error patterns
|
|
174
|
+
│ └── formatter.js # terminal output
|
|
175
|
+
├── backend/src/
|
|
176
|
+
│ ├── ai/ollama-service.js # Ollama client, prompt and response parsing
|
|
177
|
+
│ └── services/history-service.js # local error history
|
|
178
|
+
├── extension/ # VS Code extension (TypeScript)
|
|
179
|
+
├── docs/ # architecture and design decisions
|
|
180
|
+
└── tests/
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Roadmap
|
|
184
|
+
|
|
185
|
+
- [ ] Python and Go error patterns
|
|
186
|
+
- [ ] Optional cloud models (bring your own API key) for people who don't want to run Ollama
|
|
187
|
+
- [ ] `errbuddy history` command to search past errors
|
|
188
|
+
- [ ] Publish the VS Code extension to the Marketplace
|
|
189
|
+
|
|
190
|
+
## Contributing
|
|
191
|
+
|
|
192
|
+
Issues and pull requests are welcome. If errbuddy explained something badly, [open an issue](https://github.com/sithija-jayasinghe/solution-ai-buddy-v1/issues) with the error text so the patterns and prompts can improve.
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
[MIT](https://github.com/sithija-jayasinghe/solution-ai-buddy-v1/blob/main/error-solution-ai-buddy/LICENSE) © Sithija Harshana Jayasinghe
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ollama AI Service - Handles communication with local Ollama server
|
|
3
|
+
*
|
|
4
|
+
* WHY OLLAMA:
|
|
5
|
+
* 1. 100% free - no API keys needed
|
|
6
|
+
* 2. Runs locally - your code never leaves your machine
|
|
7
|
+
* 3. Works offline - no internet required after model download
|
|
8
|
+
* 4. Fast - local inference with no network latency
|
|
9
|
+
*
|
|
10
|
+
* SETUP:
|
|
11
|
+
* 1. Install Ollama: https://ollama.ai
|
|
12
|
+
* 2. Run: ollama pull llama3.2
|
|
13
|
+
* 3. Run: ollama serve
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* System prompt for error explanation
|
|
18
|
+
*
|
|
19
|
+
* WHY this structure:
|
|
20
|
+
* - Forces consistent output format
|
|
21
|
+
* - Beginner-friendly language
|
|
22
|
+
* - Prevents hallucinations with explicit rules
|
|
23
|
+
*/
|
|
24
|
+
const SYSTEM_PROMPT = `You are an expert programming assistant that explains errors to developers.
|
|
25
|
+
|
|
26
|
+
RULES (VERY IMPORTANT):
|
|
27
|
+
1. Always respond in this EXACT format:
|
|
28
|
+
WHAT: [Name the specific error code/exception (e.g. "NullPointerException", "CS1061") and what it means]
|
|
29
|
+
WHY: [Explain specifically why this code causes that error]
|
|
30
|
+
FIX: [Clear steps to fix the error]
|
|
31
|
+
EXAMPLE: [Short code example if helpful]
|
|
32
|
+
|
|
33
|
+
2. Use simple language a junior developer can understand
|
|
34
|
+
3. Be concise - no long paragraphs
|
|
35
|
+
4. Do NOT assume frameworks (like Spring, React) unless the error explicitly mentions them
|
|
36
|
+
5. Focus on the most likely cause first (typos, missing variables, syntax)
|
|
37
|
+
6. IF suggesting a fix involves installing a new package, ONLY do so if the error is explicitly "Module/Package not found".
|
|
38
|
+
7. If the error mentions "not a statement" and looks like "variable;", explain that standalone variables are not valid statements.
|
|
39
|
+
8. Never make up information - if unsure, say so
|
|
40
|
+
9. Always explicitly mention the error name or code in the "WHAT" section.
|
|
41
|
+
|
|
42
|
+
LANGUAGE-SPECIFIC TIPS:
|
|
43
|
+
- JavaScript: Common issues are undefined/null, async/await, imports
|
|
44
|
+
- Java:
|
|
45
|
+
- "not a statement" often means a standalone variable (e.g. 'x;') which is invalid in Java. It must be an assignment ('x=1;') or method call.
|
|
46
|
+
- Common issues: NullPointer, missing variables, types
|
|
47
|
+
- Avoid suggesting @Autowired unless it's a Spring error
|
|
48
|
+
- C#: Common issues are null reference, missing using, async
|
|
49
|
+
|
|
50
|
+
Remember: Developers are frustrated when they see errors. Be helpful and direct.`;
|
|
51
|
+
|
|
52
|
+
export class AIService {
|
|
53
|
+
constructor(baseUrl = 'http://localhost:11434', model = 'llama3.2') {
|
|
54
|
+
this.baseUrl = baseUrl;
|
|
55
|
+
this.model = model;
|
|
56
|
+
this.timeout = 30000; // 30 second timeout
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check if Ollama is running and accessible
|
|
61
|
+
*/
|
|
62
|
+
async checkConnection() {
|
|
63
|
+
try {
|
|
64
|
+
const controller = new AbortController();
|
|
65
|
+
const timeoutId = setTimeout(() => controller.abort(), 3000);
|
|
66
|
+
|
|
67
|
+
const response = await fetch(`${this.baseUrl}/api/tags`, {
|
|
68
|
+
method: 'GET',
|
|
69
|
+
signal: controller.signal, // 3 second timeout for health check
|
|
70
|
+
}).finally(() => clearTimeout(timeoutId));
|
|
71
|
+
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const data = await response.json();
|
|
77
|
+
|
|
78
|
+
// Check if the model we want is available
|
|
79
|
+
const models = data.models || [];
|
|
80
|
+
const modelBaseName = this.model.split(':')[0];
|
|
81
|
+
const hasModel = models.some(m => m.name.startsWith(modelBaseName));
|
|
82
|
+
|
|
83
|
+
if (!hasModel && models.length > 0) {
|
|
84
|
+
// Use first available model as fallback
|
|
85
|
+
this.model = models[0].name;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return true;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get explanation for an error from Ollama
|
|
96
|
+
*/
|
|
97
|
+
async explainError(errorText, language = 'unknown') {
|
|
98
|
+
const prompt = this.buildPrompt(errorText, language);
|
|
99
|
+
|
|
100
|
+
const controller = new AbortController();
|
|
101
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const response = await fetch(`${this.baseUrl}/api/generate`, {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers: {
|
|
107
|
+
'Content-Type': 'application/json',
|
|
108
|
+
},
|
|
109
|
+
body: JSON.stringify({
|
|
110
|
+
model: this.model,
|
|
111
|
+
prompt: prompt,
|
|
112
|
+
system: SYSTEM_PROMPT,
|
|
113
|
+
stream: false,
|
|
114
|
+
options: {
|
|
115
|
+
temperature: 0.1, // Very low to prevent hallucinations
|
|
116
|
+
top_p: 0.9,
|
|
117
|
+
num_predict: 500, // Limit response length
|
|
118
|
+
}
|
|
119
|
+
}),
|
|
120
|
+
signal: controller.signal,
|
|
121
|
+
}).finally(() => clearTimeout(timeoutId));
|
|
122
|
+
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
let errorMsg = '';
|
|
125
|
+
try {
|
|
126
|
+
const errorJson = await response.json();
|
|
127
|
+
errorMsg = errorJson.error || JSON.stringify(errorJson);
|
|
128
|
+
} catch {
|
|
129
|
+
errorMsg = await response.text();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Check for memory error
|
|
133
|
+
if (errorMsg.includes('memory')) {
|
|
134
|
+
throw new Error('Not enough RAM. Try: errbuddy --model qwen2.5:0.5b');
|
|
135
|
+
}
|
|
136
|
+
throw new Error(`Ollama returned ${response.status}: ${errorMsg}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const data = await response.json();
|
|
140
|
+
|
|
141
|
+
// Check for error in response
|
|
142
|
+
if (data.error) {
|
|
143
|
+
throw new Error(data.error);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return this.parseResponse(data.response);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
if (error.name === 'TimeoutError' || error.name === 'AbortError') {
|
|
149
|
+
throw new Error('AI explanation timed out. Try a smaller model.');
|
|
150
|
+
}
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Build the prompt for the AI
|
|
157
|
+
*/
|
|
158
|
+
buildPrompt(errorText, language) {
|
|
159
|
+
const langName = {
|
|
160
|
+
javascript: 'JavaScript/Node.js',
|
|
161
|
+
java: 'Java',
|
|
162
|
+
csharp: 'C#/.NET',
|
|
163
|
+
unknown: 'programming'
|
|
164
|
+
}[language] || language;
|
|
165
|
+
|
|
166
|
+
return `Explain this ${langName} error to a developer:
|
|
167
|
+
|
|
168
|
+
\`\`\`
|
|
169
|
+
${errorText}
|
|
170
|
+
\`\`\`
|
|
171
|
+
|
|
172
|
+
Remember to use the format: WHAT, WHY, FIX, EXAMPLE`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Parse the AI response into structured format
|
|
177
|
+
*/
|
|
178
|
+
parseResponse(response) {
|
|
179
|
+
const result = {
|
|
180
|
+
what: null,
|
|
181
|
+
why: null,
|
|
182
|
+
fix: null,
|
|
183
|
+
example: null,
|
|
184
|
+
raw: response
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// Try to extract structured parts
|
|
188
|
+
const whatMatch = response.match(/WHAT:\s*(.+?)(?=WHY:|FIX:|EXAMPLE:|$)/is);
|
|
189
|
+
const whyMatch = response.match(/WHY:\s*(.+?)(?=WHAT:|FIX:|EXAMPLE:|$)/is);
|
|
190
|
+
const fixMatch = response.match(/FIX:\s*(.+?)(?=WHAT:|WHY:|EXAMPLE:|$)/is);
|
|
191
|
+
const exampleMatch = response.match(/EXAMPLE:\s*(.+?)(?=WHAT:|WHY:|FIX:|$)/is);
|
|
192
|
+
|
|
193
|
+
if (whatMatch) result.what = whatMatch[1].trim();
|
|
194
|
+
if (whyMatch) result.why = whyMatch[1].trim();
|
|
195
|
+
if (fixMatch) result.fix = fixMatch[1].trim();
|
|
196
|
+
if (exampleMatch) result.example = exampleMatch[1].trim();
|
|
197
|
+
|
|
198
|
+
// If structured parsing failed, try to make sense of the response
|
|
199
|
+
if (!result.what && !result.why && !result.fix) {
|
|
200
|
+
// Split by sentences and assign
|
|
201
|
+
const sentences = response.split(/[.!]\s+/).filter(s => s.trim());
|
|
202
|
+
if (sentences.length >= 1) result.what = sentences[0].trim() + '.';
|
|
203
|
+
if (sentences.length >= 2) result.why = sentences[1].trim() + '.';
|
|
204
|
+
if (sentences.length >= 3) result.fix = sentences.slice(2).join('. ').trim();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Extract code blocks for example
|
|
208
|
+
if (!result.example) {
|
|
209
|
+
const codeMatch = response.match(/```[\w]*\n?([\s\S]+?)```/);
|
|
210
|
+
if (codeMatch) {
|
|
211
|
+
result.example = codeMatch[1].trim();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Stream explanation (for future use - better UX for long responses)
|
|
220
|
+
* Currently not used but ready for implementation
|
|
221
|
+
*/
|
|
222
|
+
async *streamExplanation(errorText, language = 'unknown') {
|
|
223
|
+
const prompt = this.buildPrompt(errorText, language);
|
|
224
|
+
|
|
225
|
+
const response = await fetch(`${this.baseUrl}/api/generate`, {
|
|
226
|
+
method: 'POST',
|
|
227
|
+
headers: {
|
|
228
|
+
'Content-Type': 'application/json',
|
|
229
|
+
},
|
|
230
|
+
body: JSON.stringify({
|
|
231
|
+
model: this.model,
|
|
232
|
+
prompt: prompt,
|
|
233
|
+
system: SYSTEM_PROMPT,
|
|
234
|
+
stream: true,
|
|
235
|
+
options: {
|
|
236
|
+
temperature: 0.3,
|
|
237
|
+
num_predict: 500,
|
|
238
|
+
}
|
|
239
|
+
}),
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const reader = response.body.getReader();
|
|
243
|
+
const decoder = new TextDecoder();
|
|
244
|
+
|
|
245
|
+
while (true) {
|
|
246
|
+
const { done, value } = await reader.read();
|
|
247
|
+
if (done) break;
|
|
248
|
+
|
|
249
|
+
const chunk = decoder.decode(value);
|
|
250
|
+
const lines = chunk.split('\n').filter(line => line.trim());
|
|
251
|
+
|
|
252
|
+
for (const line of lines) {
|
|
253
|
+
try {
|
|
254
|
+
const data = JSON.parse(line);
|
|
255
|
+
if (data.response) {
|
|
256
|
+
yield data.response;
|
|
257
|
+
}
|
|
258
|
+
} catch {
|
|
259
|
+
// Ignore JSON parse errors for incomplete chunks
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -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
|
+
}
|