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 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
- # Temporary Holding Version
1
+ <div align="center">
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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
+ [![npm version](https://img.shields.io/npm/v/error-solution-ai-buddy?color=cb3837&logo=npm)](https://www.npmjs.com/package/error-solution-ai-buddy)
10
+ [![npm downloads](https://img.shields.io/npm/dm/error-solution-ai-buddy)](https://www.npmjs.com/package/error-solution-ai-buddy)
11
+ ![Node](https://img.shields.io/badge/node-%3E%3D18-339933?logo=node.js&logoColor=white)
12
+ ![AI](https://img.shields.io/badge/AI-local%20via%20Ollama-black)
13
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](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
+ }