explain-my-error 1.0.8 → 1.0.10
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/README.md +33 -2
- package/dist/cli.js +395 -29
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +175 -16
- package/dist/index.js +408 -33
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/SKILL.md +41 -4
package/dist/index.js
CHANGED
|
@@ -13,13 +13,45 @@ import axios from "axios";
|
|
|
13
13
|
|
|
14
14
|
// src/types/error.ts
|
|
15
15
|
import { z } from "zod";
|
|
16
|
+
var hypothesisSchema = z.object({
|
|
17
|
+
hypothesis: z.string().min(1),
|
|
18
|
+
confidence: z.number().min(0).max(1),
|
|
19
|
+
why: z.string().min(1)
|
|
20
|
+
});
|
|
21
|
+
var fixPlanSchema = z.object({
|
|
22
|
+
plan: z.enum(["fast_patch", "proper_fix", "long_term_fix"]),
|
|
23
|
+
summary: z.string().min(1),
|
|
24
|
+
steps: z.array(z.string().min(1)).min(1),
|
|
25
|
+
tradeoffs: z.object({
|
|
26
|
+
risk: z.string().min(1),
|
|
27
|
+
performance: z.string().min(1),
|
|
28
|
+
maintainability: z.string().min(1)
|
|
29
|
+
})
|
|
30
|
+
});
|
|
31
|
+
var frameworkRecipeSchema = z.object({
|
|
32
|
+
framework: z.string().min(1),
|
|
33
|
+
when_to_use: z.string().min(1),
|
|
34
|
+
steps: z.array(z.string().min(1)).min(1),
|
|
35
|
+
code_snippet: z.string().min(1)
|
|
36
|
+
});
|
|
37
|
+
var remediationBlockSchema = z.object({
|
|
38
|
+
title: z.string().min(1),
|
|
39
|
+
commands: z.array(z.string().min(1)).min(1),
|
|
40
|
+
snippet: z.string().optional()
|
|
41
|
+
});
|
|
16
42
|
var explainedErrorSchema = z.object({
|
|
17
43
|
title: z.string().min(1),
|
|
18
44
|
explanation: z.string().min(1),
|
|
19
45
|
common_causes: z.array(z.string().min(1)).min(1),
|
|
20
46
|
fix: z.string().min(1),
|
|
21
47
|
code_example: z.string().min(1),
|
|
22
|
-
eli5: z.string().min(1)
|
|
48
|
+
eli5: z.string().min(1),
|
|
49
|
+
likely_root_cause: z.string().min(1),
|
|
50
|
+
hypotheses: z.array(hypothesisSchema).min(1).max(3),
|
|
51
|
+
fix_plans: z.array(fixPlanSchema).min(3),
|
|
52
|
+
framework_recipes: z.array(frameworkRecipeSchema).min(1),
|
|
53
|
+
remediation_blocks: z.array(remediationBlockSchema).min(1),
|
|
54
|
+
verify_checklist: z.array(z.string().min(1)).min(1)
|
|
23
55
|
});
|
|
24
56
|
|
|
25
57
|
// src/services/ai.ts
|
|
@@ -28,15 +60,81 @@ var PRIMARY_GROQ_MODEL = "llama3-70b-8192";
|
|
|
28
60
|
var FALLBACK_GROQ_MODEL = process.env.GROQ_FALLBACK_MODEL ?? "llama-3.3-70b-versatile";
|
|
29
61
|
function extractJson(content) {
|
|
30
62
|
const trimmed = content.trim();
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
63
|
+
const tryParse = (input) => {
|
|
64
|
+
try {
|
|
65
|
+
return JSON.parse(input);
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const sanitizeControlCharsInStrings = (input) => {
|
|
71
|
+
let result = "";
|
|
72
|
+
let inString = false;
|
|
73
|
+
let escaping = false;
|
|
74
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
75
|
+
const ch = input[i];
|
|
76
|
+
if (!inString) {
|
|
77
|
+
result += ch;
|
|
78
|
+
if (ch === '"') {
|
|
79
|
+
inString = true;
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (escaping) {
|
|
84
|
+
result += ch;
|
|
85
|
+
escaping = false;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (ch === "\\") {
|
|
89
|
+
result += ch;
|
|
90
|
+
escaping = true;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (ch === '"') {
|
|
94
|
+
result += ch;
|
|
95
|
+
inString = false;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (ch === "\n") {
|
|
99
|
+
result += "\\n";
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (ch === "\r") {
|
|
103
|
+
result += "\\r";
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (ch === " ") {
|
|
107
|
+
result += "\\t";
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const code = ch.charCodeAt(0);
|
|
111
|
+
if (code < 32) {
|
|
112
|
+
result += `\\u${code.toString(16).padStart(4, "0")}`;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
result += ch;
|
|
37
116
|
}
|
|
38
|
-
return
|
|
117
|
+
return result;
|
|
118
|
+
};
|
|
119
|
+
const direct = tryParse(trimmed);
|
|
120
|
+
if (direct !== null) {
|
|
121
|
+
return direct;
|
|
122
|
+
}
|
|
123
|
+
const match = trimmed.match(/\{[\s\S]*\}/);
|
|
124
|
+
if (!match) {
|
|
125
|
+
throw new Error("AI did not return valid JSON.");
|
|
39
126
|
}
|
|
127
|
+
const extracted = match[0];
|
|
128
|
+
const extractedParsed = tryParse(extracted);
|
|
129
|
+
if (extractedParsed !== null) {
|
|
130
|
+
return extractedParsed;
|
|
131
|
+
}
|
|
132
|
+
const sanitized = sanitizeControlCharsInStrings(extracted);
|
|
133
|
+
const sanitizedParsed = tryParse(sanitized);
|
|
134
|
+
if (sanitizedParsed !== null) {
|
|
135
|
+
return sanitizedParsed;
|
|
136
|
+
}
|
|
137
|
+
throw new Error("AI did not return valid JSON.");
|
|
40
138
|
}
|
|
41
139
|
function stringifyField(value) {
|
|
42
140
|
if (typeof value === "string") {
|
|
@@ -54,23 +152,160 @@ function stringifyField(value) {
|
|
|
54
152
|
}
|
|
55
153
|
return String(value);
|
|
56
154
|
}
|
|
155
|
+
function toStringArray(value) {
|
|
156
|
+
if (Array.isArray(value)) {
|
|
157
|
+
return value.map((item) => stringifyField(item)).map((item) => item.trim()).filter(Boolean);
|
|
158
|
+
}
|
|
159
|
+
return stringifyField(value).split(/\n|,/).map((item) => item.trim()).filter(Boolean);
|
|
160
|
+
}
|
|
161
|
+
function toConfidence(value) {
|
|
162
|
+
if (typeof value === "number") {
|
|
163
|
+
return Math.max(0, Math.min(1, value));
|
|
164
|
+
}
|
|
165
|
+
const parsed = Number(stringifyField(value));
|
|
166
|
+
if (!Number.isNaN(parsed)) {
|
|
167
|
+
return Math.max(0, Math.min(1, parsed));
|
|
168
|
+
}
|
|
169
|
+
return 0.5;
|
|
170
|
+
}
|
|
57
171
|
function normalizeResponseShape(payload) {
|
|
58
172
|
if (!payload || typeof payload !== "object") {
|
|
59
173
|
return payload;
|
|
60
174
|
}
|
|
61
175
|
const data = payload;
|
|
62
|
-
const
|
|
63
|
-
const
|
|
176
|
+
const commonCauses = toStringArray(data.common_causes);
|
|
177
|
+
const hypothesesRaw = Array.isArray(data.hypotheses) ? data.hypotheses : [];
|
|
178
|
+
const hypotheses = hypothesesRaw.map((item) => {
|
|
179
|
+
const row = item;
|
|
180
|
+
return {
|
|
181
|
+
hypothesis: stringifyField(row?.hypothesis),
|
|
182
|
+
confidence: toConfidence(row?.confidence),
|
|
183
|
+
why: stringifyField(row?.why)
|
|
184
|
+
};
|
|
185
|
+
}).filter((item) => item.hypothesis && item.why).slice(0, 3);
|
|
186
|
+
const fixPlansRaw = Array.isArray(data.fix_plans) ? data.fix_plans : [];
|
|
187
|
+
const normalizedFixPlans = fixPlansRaw.map((item, index) => {
|
|
188
|
+
const row = item;
|
|
189
|
+
const defaultPlan = index === 0 ? "fast_patch" : index === 1 ? "proper_fix" : "long_term_fix";
|
|
190
|
+
const planText = stringifyField(row?.plan || defaultPlan).toLowerCase();
|
|
191
|
+
const plan = planText === "fast_patch" || planText === "proper_fix" || planText === "long_term_fix" ? planText : defaultPlan;
|
|
192
|
+
const tradeoffs = row?.tradeoffs ?? {};
|
|
193
|
+
return {
|
|
194
|
+
plan,
|
|
195
|
+
summary: stringifyField(row?.summary),
|
|
196
|
+
steps: toStringArray(row?.steps),
|
|
197
|
+
tradeoffs: {
|
|
198
|
+
risk: stringifyField(tradeoffs.risk || "Unknown"),
|
|
199
|
+
performance: stringifyField(tradeoffs.performance || "Unknown"),
|
|
200
|
+
maintainability: stringifyField(tradeoffs.maintainability || "Unknown")
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}).filter((item) => item.summary && item.steps.length > 0);
|
|
204
|
+
const fixPlans = normalizedFixPlans.length >= 3 ? normalizedFixPlans.slice(0, 3) : [
|
|
205
|
+
{
|
|
206
|
+
plan: "fast_patch",
|
|
207
|
+
summary: stringifyField(data.fix),
|
|
208
|
+
steps: [stringifyField(data.fix) || "Apply a defensive guard around the failing line."],
|
|
209
|
+
tradeoffs: {
|
|
210
|
+
risk: "May hide deeper data contract issues.",
|
|
211
|
+
performance: "Minimal runtime overhead.",
|
|
212
|
+
maintainability: "Good short-term, revisit for root-cause cleanup."
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
plan: "proper_fix",
|
|
217
|
+
summary: "Fix data flow and initialization path.",
|
|
218
|
+
steps: [
|
|
219
|
+
"Trace where the failing value is created.",
|
|
220
|
+
"Initialize value before usage and add proper null checks.",
|
|
221
|
+
"Add a regression test for this scenario."
|
|
222
|
+
],
|
|
223
|
+
tradeoffs: {
|
|
224
|
+
risk: "Moderate code changes.",
|
|
225
|
+
performance: "Neutral to slightly improved.",
|
|
226
|
+
maintainability: "Better long-term readability and reliability."
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
plan: "long_term_fix",
|
|
231
|
+
summary: "Enforce stronger contracts and prevention tooling.",
|
|
232
|
+
steps: [
|
|
233
|
+
"Enable stricter type checks and lint rules.",
|
|
234
|
+
"Introduce runtime validation for external inputs.",
|
|
235
|
+
"Create reusable utility guards in shared modules."
|
|
236
|
+
],
|
|
237
|
+
tradeoffs: {
|
|
238
|
+
risk: "Requires broader refactor.",
|
|
239
|
+
performance: "Potential minor validation overhead.",
|
|
240
|
+
maintainability: "Highest long-term stability."
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
];
|
|
244
|
+
const frameworkRecipesRaw = Array.isArray(data.framework_recipes) ? data.framework_recipes : [];
|
|
245
|
+
const frameworkRecipes = frameworkRecipesRaw.map((item) => {
|
|
246
|
+
const row = item;
|
|
247
|
+
return {
|
|
248
|
+
framework: stringifyField(row?.framework),
|
|
249
|
+
when_to_use: stringifyField(row?.when_to_use),
|
|
250
|
+
steps: toStringArray(row?.steps),
|
|
251
|
+
code_snippet: stringifyField(row?.code_snippet)
|
|
252
|
+
};
|
|
253
|
+
}).filter(
|
|
254
|
+
(item) => item.framework && item.when_to_use && item.steps.length > 0 && item.code_snippet
|
|
255
|
+
);
|
|
256
|
+
const remediationBlocksRaw = Array.isArray(data.remediation_blocks) ? data.remediation_blocks : [];
|
|
257
|
+
const remediationBlocks = remediationBlocksRaw.map((item) => {
|
|
258
|
+
const row = item;
|
|
259
|
+
return {
|
|
260
|
+
title: stringifyField(row?.title),
|
|
261
|
+
commands: toStringArray(row?.commands),
|
|
262
|
+
snippet: stringifyField(row?.snippet) || void 0
|
|
263
|
+
};
|
|
264
|
+
}).filter((item) => item.title && item.commands.length > 0);
|
|
265
|
+
const verifyChecklist = toStringArray(data.verify_checklist);
|
|
266
|
+
const title = stringifyField(data.title);
|
|
267
|
+
const explanation = stringifyField(data.explanation);
|
|
268
|
+
const codeExample = stringifyField(data.code_example);
|
|
269
|
+
const fix = stringifyField(data.fix);
|
|
270
|
+
const likelyRootCause = stringifyField(data.likely_root_cause) || commonCauses[0] || "Insufficient context from error text.";
|
|
64
271
|
return {
|
|
65
|
-
title:
|
|
66
|
-
explanation:
|
|
67
|
-
common_causes: commonCauses,
|
|
68
|
-
fix:
|
|
69
|
-
code_example:
|
|
70
|
-
eli5: stringifyField(data.eli5)
|
|
272
|
+
title: title || "Unknown error",
|
|
273
|
+
explanation: explanation || "Unable to derive explanation from model response.",
|
|
274
|
+
common_causes: commonCauses.length > 0 ? commonCauses : ["Insufficient context in provided error text."],
|
|
275
|
+
fix: fix || "Add guards and trace value initialization before the failing line.",
|
|
276
|
+
code_example: codeExample || "// Add null checks around the failing line.",
|
|
277
|
+
eli5: stringifyField(data.eli5) || "Your code expected one thing, but got something different.",
|
|
278
|
+
likely_root_cause: likelyRootCause,
|
|
279
|
+
hypotheses: hypotheses.length > 0 ? hypotheses : [
|
|
280
|
+
{
|
|
281
|
+
hypothesis: likelyRootCause,
|
|
282
|
+
confidence: 0.6,
|
|
283
|
+
why: "Derived from error pattern and available context."
|
|
284
|
+
}
|
|
285
|
+
],
|
|
286
|
+
fix_plans: fixPlans,
|
|
287
|
+
framework_recipes: frameworkRecipes.length > 0 ? frameworkRecipes : [
|
|
288
|
+
{
|
|
289
|
+
framework: "Node.js",
|
|
290
|
+
when_to_use: "Default runtime fallback when framework is unknown.",
|
|
291
|
+
steps: [
|
|
292
|
+
"Validate all external data before use.",
|
|
293
|
+
"Guard nullable values before method calls.",
|
|
294
|
+
"Add test coverage for this failure path."
|
|
295
|
+
],
|
|
296
|
+
code_snippet: "const safeValue = input ?? defaultValue;"
|
|
297
|
+
}
|
|
298
|
+
],
|
|
299
|
+
remediation_blocks: remediationBlocks.length > 0 ? remediationBlocks : [
|
|
300
|
+
{
|
|
301
|
+
title: "Quick remediation commands",
|
|
302
|
+
commands: ["npm run test", "npm run lint"]
|
|
303
|
+
}
|
|
304
|
+
],
|
|
305
|
+
verify_checklist: verifyChecklist.length > 0 ? verifyChecklist : ["Re-run failing command.", "Confirm no regression in related flows."]
|
|
71
306
|
};
|
|
72
307
|
}
|
|
73
|
-
async function explainErrorWithAI(errorMessage) {
|
|
308
|
+
async function explainErrorWithAI(errorMessage, context = {}) {
|
|
74
309
|
const apiKey = process.env.GROQ_API_KEY;
|
|
75
310
|
if (!apiKey) {
|
|
76
311
|
throw new Error(
|
|
@@ -88,14 +323,44 @@ async function explainErrorWithAI(errorMessage) {
|
|
|
88
323
|
].join("\n")
|
|
89
324
|
);
|
|
90
325
|
}
|
|
91
|
-
const prompt =
|
|
326
|
+
const prompt = `You are a senior software engineer helping debug errors with context.
|
|
327
|
+
Return STRICT JSON only (no markdown) with fields:
|
|
328
|
+
- title
|
|
329
|
+
- explanation
|
|
330
|
+
- common_causes (string[])
|
|
331
|
+
- fix
|
|
332
|
+
- code_example
|
|
333
|
+
- eli5
|
|
334
|
+
- likely_root_cause
|
|
335
|
+
- hypotheses (2-3 items): [{ hypothesis, confidence (0..1), why }]
|
|
336
|
+
- fix_plans (exactly 3): [
|
|
337
|
+
{ plan: "fast_patch", summary, steps[], tradeoffs: { risk, performance, maintainability } },
|
|
338
|
+
{ plan: "proper_fix", summary, steps[], tradeoffs: { risk, performance, maintainability } },
|
|
339
|
+
{ plan: "long_term_fix", summary, steps[], tradeoffs: { risk, performance, maintainability } }
|
|
340
|
+
]
|
|
341
|
+
- framework_recipes (1+): [{ framework, when_to_use, steps[], code_snippet }]
|
|
342
|
+
- remediation_blocks (1+): [{ title, commands[], snippet? }]
|
|
343
|
+
- verify_checklist (string[])
|
|
344
|
+
|
|
345
|
+
Use provided runtime/framework/code/stack context to produce context-aware likely root cause and framework-specific fixes.`;
|
|
346
|
+
const contextBlock = [
|
|
347
|
+
`Error message:
|
|
348
|
+
${errorMessage}`,
|
|
349
|
+
`Runtime:
|
|
350
|
+
${context.runtime ?? "unknown"}`,
|
|
351
|
+
`Framework:
|
|
352
|
+
${context.framework ?? "unknown"}`,
|
|
353
|
+
`Stack trace:
|
|
354
|
+
${context.stackTrace ?? "not provided"}`,
|
|
355
|
+
`Code snippet:
|
|
356
|
+
${context.codeSnippet ?? "not provided"}`
|
|
357
|
+
].join("\n\n");
|
|
92
358
|
const requestBody = {
|
|
93
359
|
messages: [
|
|
94
360
|
{ role: "system", content: prompt },
|
|
95
361
|
{
|
|
96
362
|
role: "user",
|
|
97
|
-
content:
|
|
98
|
-
${errorMessage}
|
|
363
|
+
content: `${contextBlock}
|
|
99
364
|
|
|
100
365
|
Return strict JSON only.`
|
|
101
366
|
}
|
|
@@ -158,8 +423,42 @@ function block(title, body) {
|
|
|
158
423
|
const rows = body.split("\n").map((row) => pc.white(row));
|
|
159
424
|
return [pc.cyan(line()), pc.bold(pc.cyan(title)), ...rows, pc.cyan(line())].join("\n");
|
|
160
425
|
}
|
|
426
|
+
function list(items) {
|
|
427
|
+
return items.map((item, index) => `${index + 1}. ${item}`).join("\n");
|
|
428
|
+
}
|
|
161
429
|
function formatExplainedError(result) {
|
|
162
|
-
const commonCauses = result.common_causes
|
|
430
|
+
const commonCauses = list(result.common_causes);
|
|
431
|
+
const hypotheses = result.hypotheses.map(
|
|
432
|
+
(item, index) => `${index + 1}. ${item.hypothesis}
|
|
433
|
+
confidence: ${(item.confidence * 100).toFixed(0)}%
|
|
434
|
+
why: ${item.why}`
|
|
435
|
+
).join("\n\n");
|
|
436
|
+
const fixPlans = result.fix_plans.map((plan) => {
|
|
437
|
+
const label = plan.plan === "fast_patch" ? "Fast Patch" : plan.plan === "proper_fix" ? "Proper Fix" : "Long-Term Fix";
|
|
438
|
+
return [
|
|
439
|
+
`${pc.bold(pc.green(label))}: ${plan.summary}`,
|
|
440
|
+
...plan.steps.map((step, index) => ` ${index + 1}. ${step}`),
|
|
441
|
+
` tradeoffs -> risk: ${plan.tradeoffs.risk}; performance: ${plan.tradeoffs.performance}; maintainability: ${plan.tradeoffs.maintainability}`
|
|
442
|
+
].join("\n");
|
|
443
|
+
}).join("\n\n");
|
|
444
|
+
const frameworkRecipes = result.framework_recipes.map(
|
|
445
|
+
(recipe) => [
|
|
446
|
+
`${pc.bold(pc.cyan(recipe.framework))}: ${recipe.when_to_use}`,
|
|
447
|
+
...recipe.steps.map((step, index) => ` ${index + 1}. ${step}`),
|
|
448
|
+
" snippet:",
|
|
449
|
+
recipe.code_snippet.split("\n").map((lineText) => ` ${lineText}`).join("\n")
|
|
450
|
+
].join("\n")
|
|
451
|
+
).join("\n\n");
|
|
452
|
+
const remediationBlocks = result.remediation_blocks.map((blockItem) => {
|
|
453
|
+
const snippet = blockItem.snippet?.trim() ? `
|
|
454
|
+
snippet:
|
|
455
|
+
${blockItem.snippet.split("\n").map((lineText) => ` ${lineText}`).join("\n")}` : "";
|
|
456
|
+
return [
|
|
457
|
+
`${pc.bold(pc.green(blockItem.title))}`,
|
|
458
|
+
...blockItem.commands.map((command) => ` $ ${command}`)
|
|
459
|
+
].join("\n") + snippet;
|
|
460
|
+
}).join("\n\n");
|
|
461
|
+
const verifyChecklist = list(result.verify_checklist);
|
|
163
462
|
const hero = [
|
|
164
463
|
pc.cyan(line("=")),
|
|
165
464
|
framedLine(pc.bold(pc.cyan("EXPLAIN MY ERROR"))),
|
|
@@ -170,16 +469,27 @@ function formatExplainedError(result) {
|
|
|
170
469
|
hero,
|
|
171
470
|
"",
|
|
172
471
|
`${pc.bold(pc.cyan("ERROR"))}: ${pc.bold(pc.white(result.title))}`,
|
|
472
|
+
`${pc.bold(pc.yellow("LIKELY ROOT CAUSE"))}: ${pc.white(result.likely_root_cause)}`,
|
|
173
473
|
"",
|
|
174
474
|
block("EXPLANATION", result.explanation),
|
|
175
475
|
"",
|
|
176
476
|
block("COMMON CAUSES", commonCauses),
|
|
177
477
|
"",
|
|
478
|
+
block("HYPOTHESES (WITH CONFIDENCE)", hypotheses),
|
|
479
|
+
"",
|
|
480
|
+
block("RANKED FIX PLANS", fixPlans),
|
|
481
|
+
"",
|
|
178
482
|
`${pc.bold(pc.green("FIX"))}
|
|
179
483
|
${pc.white(result.fix)}`,
|
|
180
484
|
"",
|
|
181
485
|
block("CODE EXAMPLE", result.code_example),
|
|
182
486
|
"",
|
|
487
|
+
block("FRAMEWORK-SPECIFIC RECIPES", frameworkRecipes),
|
|
488
|
+
"",
|
|
489
|
+
block("COPY-PASTE REMEDIATION", remediationBlocks),
|
|
490
|
+
"",
|
|
491
|
+
block("VERIFY CHECKLIST", verifyChecklist),
|
|
492
|
+
"",
|
|
183
493
|
block("ELI5", result.eli5),
|
|
184
494
|
"",
|
|
185
495
|
pc.dim('Tip: run `eme explain "<error>"` for quick mode.')
|
|
@@ -208,7 +518,16 @@ var logger = {
|
|
|
208
518
|
};
|
|
209
519
|
|
|
210
520
|
// src/commands/explain.ts
|
|
211
|
-
|
|
521
|
+
function isDepsObject(value) {
|
|
522
|
+
if (!value || typeof value !== "object") {
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
const candidate = value;
|
|
526
|
+
return "explainError" in candidate || "createSpinner" in candidate || "formatOutput" in candidate || "log" in candidate;
|
|
527
|
+
}
|
|
528
|
+
async function runExplainCommand(errorMessage, optionsOrDeps = { output: "pretty" }, depsArg = {}) {
|
|
529
|
+
const options = isDepsObject(optionsOrDeps) ? { output: "pretty" } : optionsOrDeps;
|
|
530
|
+
const deps = isDepsObject(optionsOrDeps) ? optionsOrDeps : depsArg;
|
|
212
531
|
const explainError2 = deps.explainError ?? explainErrorWithAI;
|
|
213
532
|
const createSpinner = deps.createSpinner ?? ((text) => ora(text).start());
|
|
214
533
|
const formatOutput = deps.formatOutput ?? formatExplainedError;
|
|
@@ -219,10 +538,14 @@ async function runExplainCommand(errorMessage, deps = {}) {
|
|
|
219
538
|
}
|
|
220
539
|
const spinner = createSpinner("Analyzing your error...");
|
|
221
540
|
try {
|
|
222
|
-
const result = await explainError2(errorMessage.trim());
|
|
541
|
+
const result = await explainError2(errorMessage.trim(), options.context);
|
|
223
542
|
spinner.succeed("Explanation ready.");
|
|
224
543
|
log.info("");
|
|
225
|
-
|
|
544
|
+
if (options.output === "json") {
|
|
545
|
+
log.info(JSON.stringify(result, null, 2));
|
|
546
|
+
} else {
|
|
547
|
+
log.info(formatOutput(result));
|
|
548
|
+
}
|
|
226
549
|
} catch (error) {
|
|
227
550
|
spinner.fail("Could not explain this error.");
|
|
228
551
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -250,6 +573,46 @@ async function readStdin() {
|
|
|
250
573
|
}
|
|
251
574
|
return Buffer.concat(chunks).toString("utf8").trim();
|
|
252
575
|
}
|
|
576
|
+
async function safeReadText(filePath) {
|
|
577
|
+
if (!filePath) {
|
|
578
|
+
return void 0;
|
|
579
|
+
}
|
|
580
|
+
try {
|
|
581
|
+
return await readFile(filePath, "utf8");
|
|
582
|
+
} catch {
|
|
583
|
+
return void 0;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
async function detectFrameworkFromPackageJson() {
|
|
587
|
+
try {
|
|
588
|
+
const raw = await readFile("package.json", "utf8");
|
|
589
|
+
const pkg = JSON.parse(raw);
|
|
590
|
+
const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
591
|
+
if ("next" in deps) return "Next.js";
|
|
592
|
+
if ("react" in deps) return "React";
|
|
593
|
+
if ("express" in deps) return "Express";
|
|
594
|
+
if ("typescript" in deps) return "TypeScript";
|
|
595
|
+
if ("fastify" in deps) return "Fastify";
|
|
596
|
+
if ("nestjs" in deps || "@nestjs/core" in deps) return "NestJS";
|
|
597
|
+
} catch {
|
|
598
|
+
}
|
|
599
|
+
return void 0;
|
|
600
|
+
}
|
|
601
|
+
async function buildExplainOptions(options) {
|
|
602
|
+
const stackFromFile = await safeReadText(options.stackFile);
|
|
603
|
+
const codeFromFile = await safeReadText(options.codeFile);
|
|
604
|
+
const detectedFramework = await detectFrameworkFromPackageJson();
|
|
605
|
+
const context = {
|
|
606
|
+
stackTrace: options.stack ?? stackFromFile,
|
|
607
|
+
codeSnippet: options.code ?? codeFromFile,
|
|
608
|
+
runtime: options.runtime ?? `Node ${process.version}`,
|
|
609
|
+
framework: options.framework ?? detectedFramework ?? "unknown"
|
|
610
|
+
};
|
|
611
|
+
return {
|
|
612
|
+
output: options.json ? "json" : "pretty",
|
|
613
|
+
context
|
|
614
|
+
};
|
|
615
|
+
}
|
|
253
616
|
async function promptForError() {
|
|
254
617
|
if (!process.stdin.isTTY) {
|
|
255
618
|
return "";
|
|
@@ -358,30 +721,33 @@ Input modes:
|
|
|
358
721
|
$ pnpm run build 2>&1 | eme
|
|
359
722
|
`
|
|
360
723
|
);
|
|
361
|
-
program.command("explain").description("Explain a programming error message").argument("[error...]", "Error message to analyze").addHelpText(
|
|
724
|
+
program.command("explain").description("Explain a programming error message").argument("[error...]", "Error message to analyze").option("--json", "Output structured JSON").option("--stack <stackTrace>", "Additional stack trace context").option("--stack-file <path>", "Read stack trace from a file").option("--code <codeSnippet>", "Additional code snippet context").option("--code-file <path>", "Read code snippet from a file").option("--runtime <runtime>", "Runtime context (example: node 20, bun 1.1)").option("--framework <framework>", "Framework context (example: react, nextjs, express)").addHelpText(
|
|
362
725
|
"after",
|
|
363
726
|
`
|
|
364
727
|
Examples:
|
|
365
728
|
$ explain-my-error explain "SyntaxError: Unexpected token }"
|
|
366
729
|
$ eme explain "Module not found: Can't resolve 'axios'"
|
|
367
730
|
$ cat error.txt | explain-my-error explain
|
|
731
|
+
$ eme explain "TypeError: Cannot read property 'map' of undefined" --framework react --runtime "node 20"
|
|
732
|
+
$ eme explain --stack-file ./error.log --code-file ./src/app.ts --json
|
|
368
733
|
`
|
|
369
|
-
).action(async (errorParts) => {
|
|
734
|
+
).action(async (errorParts, options) => {
|
|
370
735
|
const inlineError = errorParts.join(" ").trim();
|
|
371
736
|
const pipedError = inlineError ? "" : await readStdinFn();
|
|
372
737
|
const promptedError = !inlineError && !pipedError ? await promptForErrorFn() : "";
|
|
373
738
|
const finalError = inlineError || pipedError || promptedError;
|
|
739
|
+
const explainOptions = await buildExplainOptions(options);
|
|
374
740
|
const hasApiKey = await ensureApiKeyFn();
|
|
375
741
|
if (!hasApiKey) {
|
|
376
742
|
log.warn("GROQ_API_KEY is required. Set it and run again.");
|
|
377
743
|
return;
|
|
378
744
|
}
|
|
379
|
-
await runExplain(finalError);
|
|
745
|
+
await runExplain(finalError, explainOptions);
|
|
380
746
|
});
|
|
381
747
|
program.action(async () => {
|
|
382
748
|
if (stdinIsTTY()) {
|
|
383
749
|
const promptedError = await promptForErrorFn();
|
|
384
|
-
await runExplain(promptedError);
|
|
750
|
+
await runExplain(promptedError, await buildExplainOptions({}));
|
|
385
751
|
return;
|
|
386
752
|
}
|
|
387
753
|
const pipedError = await readStdinFn();
|
|
@@ -394,24 +760,33 @@ Examples:
|
|
|
394
760
|
log.warn("GROQ_API_KEY is required. Set it and run again.");
|
|
395
761
|
return;
|
|
396
762
|
}
|
|
397
|
-
await runExplain(pipedError);
|
|
763
|
+
await runExplain(pipedError, await buildExplainOptions({}));
|
|
398
764
|
});
|
|
399
765
|
await program.parseAsync(argv);
|
|
400
766
|
}
|
|
401
767
|
|
|
402
768
|
// src/explain.ts
|
|
403
|
-
async function explainError(errorMessage) {
|
|
404
|
-
return explainErrorWithAI(errorMessage);
|
|
769
|
+
async function explainError(errorMessage, context = {}) {
|
|
770
|
+
return explainErrorWithAI(errorMessage, context);
|
|
405
771
|
}
|
|
406
772
|
|
|
407
773
|
// src/skills/explainError.skill.ts
|
|
408
774
|
import { z as z2 } from "zod";
|
|
409
775
|
var explainErrorSkillInputSchema = z2.object({
|
|
410
|
-
error: z2.string().min(1, "error is required")
|
|
776
|
+
error: z2.string().min(1, "error is required"),
|
|
777
|
+
stack_trace: z2.string().optional(),
|
|
778
|
+
code_snippet: z2.string().optional(),
|
|
779
|
+
runtime: z2.string().optional(),
|
|
780
|
+
framework: z2.string().optional()
|
|
411
781
|
});
|
|
412
782
|
async function runExplainErrorSkill(input) {
|
|
413
783
|
const parsedInput = explainErrorSkillInputSchema.parse(input);
|
|
414
|
-
return explainErrorWithAI(parsedInput.error
|
|
784
|
+
return explainErrorWithAI(parsedInput.error, {
|
|
785
|
+
stackTrace: parsedInput.stack_trace,
|
|
786
|
+
codeSnippet: parsedInput.code_snippet,
|
|
787
|
+
runtime: parsedInput.runtime,
|
|
788
|
+
framework: parsedInput.framework
|
|
789
|
+
});
|
|
415
790
|
}
|
|
416
791
|
export {
|
|
417
792
|
explainError,
|