explain-my-error 1.0.9 → 1.0.11
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 +345 -22
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +175 -16
- package/dist/index.js +358 -26
- 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
|
|
@@ -120,23 +152,160 @@ function stringifyField(value) {
|
|
|
120
152
|
}
|
|
121
153
|
return String(value);
|
|
122
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
|
+
}
|
|
123
171
|
function normalizeResponseShape(payload) {
|
|
124
172
|
if (!payload || typeof payload !== "object") {
|
|
125
173
|
return payload;
|
|
126
174
|
}
|
|
127
175
|
const data = payload;
|
|
128
|
-
const
|
|
129
|
-
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.";
|
|
130
271
|
return {
|
|
131
|
-
title:
|
|
132
|
-
explanation:
|
|
133
|
-
common_causes: commonCauses,
|
|
134
|
-
fix:
|
|
135
|
-
code_example:
|
|
136
|
-
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."]
|
|
137
306
|
};
|
|
138
307
|
}
|
|
139
|
-
async function explainErrorWithAI(errorMessage) {
|
|
308
|
+
async function explainErrorWithAI(errorMessage, context = {}) {
|
|
140
309
|
const apiKey = process.env.GROQ_API_KEY;
|
|
141
310
|
if (!apiKey) {
|
|
142
311
|
throw new Error(
|
|
@@ -154,14 +323,44 @@ async function explainErrorWithAI(errorMessage) {
|
|
|
154
323
|
].join("\n")
|
|
155
324
|
);
|
|
156
325
|
}
|
|
157
|
-
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");
|
|
158
358
|
const requestBody = {
|
|
159
359
|
messages: [
|
|
160
360
|
{ role: "system", content: prompt },
|
|
161
361
|
{
|
|
162
362
|
role: "user",
|
|
163
|
-
content:
|
|
164
|
-
${errorMessage}
|
|
363
|
+
content: `${contextBlock}
|
|
165
364
|
|
|
166
365
|
Return strict JSON only.`
|
|
167
366
|
}
|
|
@@ -224,8 +423,42 @@ function block(title, body) {
|
|
|
224
423
|
const rows = body.split("\n").map((row) => pc.white(row));
|
|
225
424
|
return [pc.cyan(line()), pc.bold(pc.cyan(title)), ...rows, pc.cyan(line())].join("\n");
|
|
226
425
|
}
|
|
426
|
+
function list(items) {
|
|
427
|
+
return items.map((item, index) => `${index + 1}. ${item}`).join("\n");
|
|
428
|
+
}
|
|
227
429
|
function formatExplainedError(result) {
|
|
228
|
-
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);
|
|
229
462
|
const hero = [
|
|
230
463
|
pc.cyan(line("=")),
|
|
231
464
|
framedLine(pc.bold(pc.cyan("EXPLAIN MY ERROR"))),
|
|
@@ -236,16 +469,27 @@ function formatExplainedError(result) {
|
|
|
236
469
|
hero,
|
|
237
470
|
"",
|
|
238
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)}`,
|
|
239
473
|
"",
|
|
240
474
|
block("EXPLANATION", result.explanation),
|
|
241
475
|
"",
|
|
242
476
|
block("COMMON CAUSES", commonCauses),
|
|
243
477
|
"",
|
|
478
|
+
block("HYPOTHESES (WITH CONFIDENCE)", hypotheses),
|
|
479
|
+
"",
|
|
480
|
+
block("RANKED FIX PLANS", fixPlans),
|
|
481
|
+
"",
|
|
244
482
|
`${pc.bold(pc.green("FIX"))}
|
|
245
483
|
${pc.white(result.fix)}`,
|
|
246
484
|
"",
|
|
247
485
|
block("CODE EXAMPLE", result.code_example),
|
|
248
486
|
"",
|
|
487
|
+
block("FRAMEWORK-SPECIFIC RECIPES", frameworkRecipes),
|
|
488
|
+
"",
|
|
489
|
+
block("COPY-PASTE REMEDIATION", remediationBlocks),
|
|
490
|
+
"",
|
|
491
|
+
block("VERIFY CHECKLIST", verifyChecklist),
|
|
492
|
+
"",
|
|
249
493
|
block("ELI5", result.eli5),
|
|
250
494
|
"",
|
|
251
495
|
pc.dim('Tip: run `eme explain "<error>"` for quick mode.')
|
|
@@ -274,7 +518,16 @@ var logger = {
|
|
|
274
518
|
};
|
|
275
519
|
|
|
276
520
|
// src/commands/explain.ts
|
|
277
|
-
|
|
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;
|
|
278
531
|
const explainError2 = deps.explainError ?? explainErrorWithAI;
|
|
279
532
|
const createSpinner = deps.createSpinner ?? ((text) => ora(text).start());
|
|
280
533
|
const formatOutput = deps.formatOutput ?? formatExplainedError;
|
|
@@ -285,10 +538,14 @@ async function runExplainCommand(errorMessage, deps = {}) {
|
|
|
285
538
|
}
|
|
286
539
|
const spinner = createSpinner("Analyzing your error...");
|
|
287
540
|
try {
|
|
288
|
-
const result = await explainError2(errorMessage.trim());
|
|
541
|
+
const result = await explainError2(errorMessage.trim(), options.context);
|
|
289
542
|
spinner.succeed("Explanation ready.");
|
|
290
543
|
log.info("");
|
|
291
|
-
|
|
544
|
+
if (options.output === "json") {
|
|
545
|
+
log.info(JSON.stringify(result, null, 2));
|
|
546
|
+
} else {
|
|
547
|
+
log.info(formatOutput(result));
|
|
548
|
+
}
|
|
292
549
|
} catch (error) {
|
|
293
550
|
spinner.fail("Could not explain this error.");
|
|
294
551
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -316,6 +573,46 @@ async function readStdin() {
|
|
|
316
573
|
}
|
|
317
574
|
return Buffer.concat(chunks).toString("utf8").trim();
|
|
318
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
|
+
}
|
|
319
616
|
async function promptForError() {
|
|
320
617
|
if (!process.stdin.isTTY) {
|
|
321
618
|
return "";
|
|
@@ -422,32 +719,58 @@ Input modes:
|
|
|
422
719
|
3) Pipe from files/commands
|
|
423
720
|
$ cat error.txt | explain-my-error
|
|
424
721
|
$ pnpm run build 2>&1 | eme
|
|
722
|
+
|
|
723
|
+
Power features:
|
|
724
|
+
- Context-aware analysis: pass stack traces, code snippets, runtime, and framework.
|
|
725
|
+
- Structured output: use --json for machine-readable responses.
|
|
726
|
+
- Auto framework detection: best-effort detection from local package.json.
|
|
425
727
|
`
|
|
426
728
|
);
|
|
427
|
-
program.command("explain").description("Explain a programming error message").argument("[error...]", "Error message to analyze").addHelpText(
|
|
729
|
+
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(
|
|
428
730
|
"after",
|
|
429
731
|
`
|
|
732
|
+
Context options:
|
|
733
|
+
--stack inline stack trace text
|
|
734
|
+
--stack-file file path for stack trace
|
|
735
|
+
--code inline code snippet text
|
|
736
|
+
--code-file file path for code snippet
|
|
737
|
+
--runtime runtime label (default: current Node version)
|
|
738
|
+
--framework framework label (auto-detected when possible)
|
|
739
|
+
|
|
740
|
+
Output modes:
|
|
741
|
+
default pretty output includes:
|
|
742
|
+
- likely root cause
|
|
743
|
+
- 2-3 hypotheses with confidence
|
|
744
|
+
- ranked fix plans (fast/proper/long-term) with tradeoffs
|
|
745
|
+
- framework-specific recipes
|
|
746
|
+
- remediation commands and verify checklist
|
|
747
|
+
|
|
748
|
+
--json output includes the same data in machine-readable JSON.
|
|
749
|
+
|
|
430
750
|
Examples:
|
|
431
751
|
$ explain-my-error explain "SyntaxError: Unexpected token }"
|
|
432
752
|
$ eme explain "Module not found: Can't resolve 'axios'"
|
|
433
753
|
$ cat error.txt | explain-my-error explain
|
|
754
|
+
$ eme explain "TypeError: Cannot read property 'map' of undefined" --framework react --runtime "node 20"
|
|
755
|
+
$ eme explain --stack-file ./error.log --code-file ./src/app.ts --json
|
|
434
756
|
`
|
|
435
|
-
).action(async (errorParts) => {
|
|
757
|
+
).action(async (errorParts, options) => {
|
|
436
758
|
const inlineError = errorParts.join(" ").trim();
|
|
437
759
|
const pipedError = inlineError ? "" : await readStdinFn();
|
|
438
760
|
const promptedError = !inlineError && !pipedError ? await promptForErrorFn() : "";
|
|
439
761
|
const finalError = inlineError || pipedError || promptedError;
|
|
762
|
+
const explainOptions = await buildExplainOptions(options);
|
|
440
763
|
const hasApiKey = await ensureApiKeyFn();
|
|
441
764
|
if (!hasApiKey) {
|
|
442
765
|
log.warn("GROQ_API_KEY is required. Set it and run again.");
|
|
443
766
|
return;
|
|
444
767
|
}
|
|
445
|
-
await runExplain(finalError);
|
|
768
|
+
await runExplain(finalError, explainOptions);
|
|
446
769
|
});
|
|
447
770
|
program.action(async () => {
|
|
448
771
|
if (stdinIsTTY()) {
|
|
449
772
|
const promptedError = await promptForErrorFn();
|
|
450
|
-
await runExplain(promptedError);
|
|
773
|
+
await runExplain(promptedError, await buildExplainOptions({}));
|
|
451
774
|
return;
|
|
452
775
|
}
|
|
453
776
|
const pipedError = await readStdinFn();
|
|
@@ -460,24 +783,33 @@ Examples:
|
|
|
460
783
|
log.warn("GROQ_API_KEY is required. Set it and run again.");
|
|
461
784
|
return;
|
|
462
785
|
}
|
|
463
|
-
await runExplain(pipedError);
|
|
786
|
+
await runExplain(pipedError, await buildExplainOptions({}));
|
|
464
787
|
});
|
|
465
788
|
await program.parseAsync(argv);
|
|
466
789
|
}
|
|
467
790
|
|
|
468
791
|
// src/explain.ts
|
|
469
|
-
async function explainError(errorMessage) {
|
|
470
|
-
return explainErrorWithAI(errorMessage);
|
|
792
|
+
async function explainError(errorMessage, context = {}) {
|
|
793
|
+
return explainErrorWithAI(errorMessage, context);
|
|
471
794
|
}
|
|
472
795
|
|
|
473
796
|
// src/skills/explainError.skill.ts
|
|
474
797
|
import { z as z2 } from "zod";
|
|
475
798
|
var explainErrorSkillInputSchema = z2.object({
|
|
476
|
-
error: z2.string().min(1, "error is required")
|
|
799
|
+
error: z2.string().min(1, "error is required"),
|
|
800
|
+
stack_trace: z2.string().optional(),
|
|
801
|
+
code_snippet: z2.string().optional(),
|
|
802
|
+
runtime: z2.string().optional(),
|
|
803
|
+
framework: z2.string().optional()
|
|
477
804
|
});
|
|
478
805
|
async function runExplainErrorSkill(input) {
|
|
479
806
|
const parsedInput = explainErrorSkillInputSchema.parse(input);
|
|
480
|
-
return explainErrorWithAI(parsedInput.error
|
|
807
|
+
return explainErrorWithAI(parsedInput.error, {
|
|
808
|
+
stackTrace: parsedInput.stack_trace,
|
|
809
|
+
codeSnippet: parsedInput.code_snippet,
|
|
810
|
+
runtime: parsedInput.runtime,
|
|
811
|
+
framework: parsedInput.framework
|
|
812
|
+
});
|
|
481
813
|
}
|
|
482
814
|
export {
|
|
483
815
|
explainError,
|