debug-toolkit 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +236 -0
- package/SKILL.md +104 -0
- package/dist/capture.d.ts +46 -0
- package/dist/capture.js +246 -0
- package/dist/capture.js.map +1 -0
- package/dist/cleanup.d.ts +12 -0
- package/dist/cleanup.js +103 -0
- package/dist/cleanup.js.map +1 -0
- package/dist/cli.d.ts +40 -0
- package/dist/cli.js +109 -0
- package/dist/cli.js.map +1 -0
- package/dist/context.d.ts +59 -0
- package/dist/context.js +338 -0
- package/dist/context.js.map +1 -0
- package/dist/demo.d.ts +12 -0
- package/dist/demo.js +347 -0
- package/dist/demo.js.map +1 -0
- package/dist/hook.d.ts +17 -0
- package/dist/hook.js +106 -0
- package/dist/hook.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +203 -0
- package/dist/index.js.map +1 -0
- package/dist/injected.js +151 -0
- package/dist/instrument.d.ts +15 -0
- package/dist/instrument.js +87 -0
- package/dist/instrument.js.map +1 -0
- package/dist/mcp.d.ts +14 -0
- package/dist/mcp.js +420 -0
- package/dist/mcp.js.map +1 -0
- package/dist/memory.d.ts +73 -0
- package/dist/memory.js +291 -0
- package/dist/memory.js.map +1 -0
- package/dist/methodology.d.ts +7 -0
- package/dist/methodology.js +100 -0
- package/dist/methodology.js.map +1 -0
- package/dist/proxy.d.ts +12 -0
- package/dist/proxy.js +168 -0
- package/dist/proxy.js.map +1 -0
- package/dist/security.d.ts +27 -0
- package/dist/security.js +158 -0
- package/dist/security.js.map +1 -0
- package/dist/session.d.ts +53 -0
- package/dist/session.js +94 -0
- package/dist/session.js.map +1 -0
- package/package.json +33 -0
package/dist/cleanup.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, renameSync } from "node:fs";
|
|
2
|
+
import { saveSession } from "./session.js";
|
|
3
|
+
// --- Marker removal patterns ---
|
|
4
|
+
// JS/Go: single-line inline markers with /* */ comments
|
|
5
|
+
const INLINE_RE = /[ \t]*\/\*\s*__DBG_START_(\w+)__\s*\*\/.*?\/\*\s*__DBG_END_\1__\s*\*\/[ \t]*\n?/g;
|
|
6
|
+
// Python: multi-line block markers with # comments (handles indentation)
|
|
7
|
+
const BLOCK_RE = /[ \t]*#\s*__DBG_START_(\w+)__.*\n(?:[ \t]*.*\n)*?[ \t]*#\s*__DBG_END_\1__.*\n?/gm;
|
|
8
|
+
// Detection: any marker present?
|
|
9
|
+
const HAS_MARKER = /__DBG_(?:START|END)_\w+__/;
|
|
10
|
+
function removeMarkers(content) {
|
|
11
|
+
const hadMarkers = HAS_MARKER.test(content);
|
|
12
|
+
if (!hadMarkers)
|
|
13
|
+
return { cleaned: content, hadMarkers: false };
|
|
14
|
+
let cleaned = content.replace(INLINE_RE, "");
|
|
15
|
+
cleaned = cleaned.replace(BLOCK_RE, "");
|
|
16
|
+
return { cleaned, hadMarkers: true };
|
|
17
|
+
}
|
|
18
|
+
function atomicWriteFile(path, data) {
|
|
19
|
+
const tmp = `${path}.dbg_clean_${process.pid}`;
|
|
20
|
+
writeFileSync(tmp, data);
|
|
21
|
+
renameSync(tmp, path);
|
|
22
|
+
}
|
|
23
|
+
export function cleanupSession(cwd, session) {
|
|
24
|
+
const errors = [];
|
|
25
|
+
let cleaned = 0;
|
|
26
|
+
const filesProcessed = [];
|
|
27
|
+
const files = [...new Set(session.instrumentation.filter((r) => r.active).map((r) => r.filePath))];
|
|
28
|
+
for (const fp of files) {
|
|
29
|
+
if (!existsSync(fp)) {
|
|
30
|
+
errors.push(`File missing: ${fp}`);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const content = readFileSync(fp, "utf-8");
|
|
34
|
+
const { cleaned: result, hadMarkers } = removeMarkers(content);
|
|
35
|
+
if (hadMarkers) {
|
|
36
|
+
atomicWriteFile(fp, result);
|
|
37
|
+
cleaned++;
|
|
38
|
+
filesProcessed.push(fp);
|
|
39
|
+
// Single-pass verify: check the content we just wrote
|
|
40
|
+
if (HAS_MARKER.test(result)) {
|
|
41
|
+
errors.push(`Markers remain after cleanup: ${fp}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Mark all records inactive
|
|
46
|
+
for (const r of session.instrumentation)
|
|
47
|
+
r.active = false;
|
|
48
|
+
session._markerIndex = {};
|
|
49
|
+
session.status = "resolved";
|
|
50
|
+
saveSession(cwd, session);
|
|
51
|
+
return {
|
|
52
|
+
cleaned,
|
|
53
|
+
verified: errors.length === 0,
|
|
54
|
+
errors,
|
|
55
|
+
filesProcessed,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Emergency cleanup from manifest-less scan of session files.
|
|
60
|
+
*/
|
|
61
|
+
export function cleanupFromManifest(cwd) {
|
|
62
|
+
// Scan all session files to find active instrumentation
|
|
63
|
+
const { existsSync: ex, readdirSync } = require("node:fs");
|
|
64
|
+
const { join } = require("node:path");
|
|
65
|
+
const sessionsDir = join(cwd, ".debug", "sessions");
|
|
66
|
+
if (!ex(sessionsDir))
|
|
67
|
+
return { cleaned: 0, verified: true, errors: [], filesProcessed: [] };
|
|
68
|
+
const allFiles = new Set();
|
|
69
|
+
for (const f of readdirSync(sessionsDir)) {
|
|
70
|
+
if (!f.endsWith(".json"))
|
|
71
|
+
continue;
|
|
72
|
+
try {
|
|
73
|
+
const session = JSON.parse(readFileSync(join(sessionsDir, f), "utf-8"));
|
|
74
|
+
for (const r of session.instrumentation ?? []) {
|
|
75
|
+
if (r.active)
|
|
76
|
+
allFiles.add(r.filePath);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch { /* skip corrupt sessions */ }
|
|
80
|
+
}
|
|
81
|
+
if (allFiles.size === 0)
|
|
82
|
+
return { cleaned: 0, verified: true, errors: [], filesProcessed: [] };
|
|
83
|
+
const errors = [];
|
|
84
|
+
let cleaned = 0;
|
|
85
|
+
const filesProcessed = [];
|
|
86
|
+
for (const fp of allFiles) {
|
|
87
|
+
if (!existsSync(fp)) {
|
|
88
|
+
errors.push(`Missing: ${fp}`);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const content = readFileSync(fp, "utf-8");
|
|
92
|
+
const { cleaned: result, hadMarkers } = removeMarkers(content);
|
|
93
|
+
if (hadMarkers) {
|
|
94
|
+
atomicWriteFile(fp, result);
|
|
95
|
+
cleaned++;
|
|
96
|
+
filesProcessed.push(fp);
|
|
97
|
+
if (HAS_MARKER.test(result))
|
|
98
|
+
errors.push(`Markers remain: ${fp}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { cleaned, verified: errors.length === 0, errors, filesProcessed };
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=cleanup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../src/cleanup.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal UI utilities — colors, symbols, formatting.
|
|
3
|
+
* Zero dependencies. Works in any terminal.
|
|
4
|
+
*/
|
|
5
|
+
declare const c: {
|
|
6
|
+
reset: string;
|
|
7
|
+
bold: string;
|
|
8
|
+
dim: string;
|
|
9
|
+
green: string;
|
|
10
|
+
yellow: string;
|
|
11
|
+
blue: string;
|
|
12
|
+
magenta: string;
|
|
13
|
+
cyan: string;
|
|
14
|
+
red: string;
|
|
15
|
+
gray: string;
|
|
16
|
+
white: string;
|
|
17
|
+
bgGreen: string;
|
|
18
|
+
bgBlue: string;
|
|
19
|
+
};
|
|
20
|
+
export declare const sym: {
|
|
21
|
+
check: string;
|
|
22
|
+
cross: string;
|
|
23
|
+
arrow: string;
|
|
24
|
+
dot: string;
|
|
25
|
+
circle: string;
|
|
26
|
+
bar: string;
|
|
27
|
+
dash: string;
|
|
28
|
+
bolt: string;
|
|
29
|
+
};
|
|
30
|
+
export declare function banner(): void;
|
|
31
|
+
export declare function info(msg: string): void;
|
|
32
|
+
export declare function success(msg: string): void;
|
|
33
|
+
export declare function warn(msg: string): void;
|
|
34
|
+
export declare function error(msg: string): void;
|
|
35
|
+
export declare function dim(msg: string): void;
|
|
36
|
+
export declare function section(title: string): void;
|
|
37
|
+
export declare function kv(key: string, value: string): void;
|
|
38
|
+
export declare function ready(toolCount: number): void;
|
|
39
|
+
export declare function printHelp(): void;
|
|
40
|
+
export { c };
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal UI utilities — colors, symbols, formatting.
|
|
3
|
+
* Zero dependencies. Works in any terminal.
|
|
4
|
+
*/
|
|
5
|
+
const isColor = process.env.NO_COLOR === undefined && process.stderr.isTTY;
|
|
6
|
+
// --- ANSI colors ---
|
|
7
|
+
const c = {
|
|
8
|
+
reset: isColor ? "\x1b[0m" : "",
|
|
9
|
+
bold: isColor ? "\x1b[1m" : "",
|
|
10
|
+
dim: isColor ? "\x1b[2m" : "",
|
|
11
|
+
green: isColor ? "\x1b[32m" : "",
|
|
12
|
+
yellow: isColor ? "\x1b[33m" : "",
|
|
13
|
+
blue: isColor ? "\x1b[34m" : "",
|
|
14
|
+
magenta: isColor ? "\x1b[35m" : "",
|
|
15
|
+
cyan: isColor ? "\x1b[36m" : "",
|
|
16
|
+
red: isColor ? "\x1b[31m" : "",
|
|
17
|
+
gray: isColor ? "\x1b[90m" : "",
|
|
18
|
+
white: isColor ? "\x1b[97m" : "",
|
|
19
|
+
bgGreen: isColor ? "\x1b[42m" : "",
|
|
20
|
+
bgBlue: isColor ? "\x1b[44m" : "",
|
|
21
|
+
};
|
|
22
|
+
// --- Symbols ---
|
|
23
|
+
export const sym = {
|
|
24
|
+
check: isColor ? "✓" : "[OK]",
|
|
25
|
+
cross: isColor ? "✗" : "[FAIL]",
|
|
26
|
+
arrow: isColor ? "→" : "->",
|
|
27
|
+
dot: isColor ? "●" : "*",
|
|
28
|
+
circle: isColor ? "○" : "-",
|
|
29
|
+
bar: isColor ? "│" : "|",
|
|
30
|
+
dash: isColor ? "─" : "-",
|
|
31
|
+
bolt: isColor ? "⚡" : "!",
|
|
32
|
+
};
|
|
33
|
+
// --- Log functions (all go to stderr, stdout reserved for MCP) ---
|
|
34
|
+
export function banner() {
|
|
35
|
+
const lines = [
|
|
36
|
+
"",
|
|
37
|
+
` ${c.bold}${c.cyan}debug-toolkit${c.reset} ${c.dim}v0.3.0${c.reset}`,
|
|
38
|
+
` ${c.dim}closed-loop debugging for AI agents${c.reset}`,
|
|
39
|
+
"",
|
|
40
|
+
];
|
|
41
|
+
process.stderr.write(lines.join("\n") + "\n");
|
|
42
|
+
}
|
|
43
|
+
export function info(msg) {
|
|
44
|
+
process.stderr.write(` ${c.blue}${sym.dot}${c.reset} ${msg}\n`);
|
|
45
|
+
}
|
|
46
|
+
export function success(msg) {
|
|
47
|
+
process.stderr.write(` ${c.green}${sym.check}${c.reset} ${msg}\n`);
|
|
48
|
+
}
|
|
49
|
+
export function warn(msg) {
|
|
50
|
+
process.stderr.write(` ${c.yellow}${sym.bolt}${c.reset} ${msg}\n`);
|
|
51
|
+
}
|
|
52
|
+
export function error(msg) {
|
|
53
|
+
process.stderr.write(` ${c.red}${sym.cross}${c.reset} ${msg}\n`);
|
|
54
|
+
}
|
|
55
|
+
export function dim(msg) {
|
|
56
|
+
process.stderr.write(` ${c.dim}${msg}${c.reset}\n`);
|
|
57
|
+
}
|
|
58
|
+
export function section(title) {
|
|
59
|
+
process.stderr.write(`\n ${c.bold}${title}${c.reset}\n`);
|
|
60
|
+
}
|
|
61
|
+
export function kv(key, value) {
|
|
62
|
+
process.stderr.write(` ${c.dim}${key}${c.reset} ${value}\n`);
|
|
63
|
+
}
|
|
64
|
+
export function ready(toolCount) {
|
|
65
|
+
process.stderr.write(`\n ${c.bgBlue}${c.white}${c.bold} READY ${c.reset} ${c.bold}${toolCount} MCP tools available${c.reset}\n\n`);
|
|
66
|
+
}
|
|
67
|
+
export function printHelp() {
|
|
68
|
+
console.log(`
|
|
69
|
+
${c.bold}${c.cyan}debug-toolkit${c.reset} ${c.dim}v0.3.0${c.reset}
|
|
70
|
+
${c.dim}Closed-loop debugging for AI agents${c.reset}
|
|
71
|
+
|
|
72
|
+
${c.bold}SETUP${c.reset} ${c.dim}(one time)${c.reset}
|
|
73
|
+
${c.green}npx debug-toolkit init${c.reset}
|
|
74
|
+
|
|
75
|
+
${c.bold}TWO MODES${c.reset}
|
|
76
|
+
${c.white}Pure MCP${c.reset} Just add to your MCP config. No wrapper needed.
|
|
77
|
+
${c.dim}Agent gets: investigate, instrument, capture, verify, cleanup${c.reset}
|
|
78
|
+
|
|
79
|
+
${c.white}Serve${c.reset} ${c.green}npx debug-toolkit serve -- npm run dev${c.reset}
|
|
80
|
+
${c.dim}Everything above + browser console/network capture via proxy${c.reset}
|
|
81
|
+
|
|
82
|
+
${c.bold}8 TOOLS + 1 RESOURCE${c.reset} ${c.dim}(what the AI agent sees)${c.reset}
|
|
83
|
+
${c.cyan}debug_investigate${c.reset} ${c.bold}Error in ${sym.arrow} full context out${c.reset} ${c.dim}+ auto-recall past fixes${c.reset}
|
|
84
|
+
${c.cyan}debug_recall${c.reset} Search past sessions ${c.dim}(with staleness + causal chains)${c.reset}
|
|
85
|
+
${c.cyan}debug_patterns${c.reset} Detect recurring errors, hot files, regressions
|
|
86
|
+
${c.cyan}debug_instrument${c.reset} Add tagged logging to source files
|
|
87
|
+
${c.cyan}debug_capture${c.reset} Collect runtime output ${c.dim}(paginated)${c.reset}
|
|
88
|
+
${c.cyan}debug_verify${c.reset} Run command, check pass/fail
|
|
89
|
+
${c.cyan}debug_cleanup${c.reset} Remove instrumentation ${c.dim}+ save diagnosis + causal chain${c.reset}
|
|
90
|
+
${c.cyan}debug_session${c.reset} View session state
|
|
91
|
+
${c.dim}debug://methodology${c.reset} ${c.dim}Always-available debugging guide (MCP resource)${c.reset}
|
|
92
|
+
|
|
93
|
+
${c.bold}THE WORKFLOW${c.reset}
|
|
94
|
+
${c.dim}1.${c.reset} ${c.cyan}debug_recall${c.reset} ${c.dim}${sym.arrow} check if solved before${c.reset}
|
|
95
|
+
${c.dim}2.${c.reset} ${c.cyan}debug_investigate${c.reset} ${c.dim}${sym.arrow} understand the error${c.reset}
|
|
96
|
+
${c.dim}3.${c.reset} ${c.cyan}debug_instrument${c.reset} ${c.dim}${sym.arrow} add logging to probe${c.reset}
|
|
97
|
+
${c.dim}4.${c.reset} ${c.cyan}debug_capture${c.reset} ${c.dim}${sym.arrow} collect evidence${c.reset}
|
|
98
|
+
${c.dim}5.${c.reset} ${c.white}apply fix${c.reset} ${c.dim}${sym.arrow} agent edits code${c.reset}
|
|
99
|
+
${c.dim}6.${c.reset} ${c.cyan}debug_verify${c.reset} ${c.dim}${sym.arrow} confirm it works${c.reset}
|
|
100
|
+
${c.dim}7.${c.reset} ${c.cyan}debug_cleanup${c.reset} ${c.dim}${sym.arrow} remove markers, save to memory${c.reset}
|
|
101
|
+
|
|
102
|
+
${c.bold}SECURITY${c.reset}
|
|
103
|
+
${c.green}${sym.check}${c.reset} Path traversal protection ${c.green}${sym.check}${c.reset} Auto-redact secrets
|
|
104
|
+
${c.green}${sym.check}${c.reset} Localhost-only proxy ${c.green}${sym.check}${c.reset} Pre-commit safety hook
|
|
105
|
+
${c.green}${sym.check}${c.reset} .debug/ auto-gitignored ${c.green}${sym.check}${c.reset} Atomic file writes
|
|
106
|
+
`);
|
|
107
|
+
}
|
|
108
|
+
export { c };
|
|
109
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;AAE3E,sBAAsB;AACtB,MAAM,CAAC,GAAG;IACR,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;IAC/B,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;IAC9B,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;IAC7B,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;IAChC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;IACjC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;IAC/B,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;IAClC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;IAC/B,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;IAC9B,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;IAC/B,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;IAChC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;IAClC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;CAClC,CAAC;AAEF,kBAAkB;AAClB,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM;IAC7B,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ;IAC/B,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI;IAC3B,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;IACxB,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;IAC3B,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;IACxB,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;IACzB,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;CAC1B,CAAC;AAEF,oEAAoE;AAEpE,MAAM,UAAU,MAAM;IACpB,MAAM,KAAK,GAAG;QACZ,EAAE;QACF,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,gBAAgB,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,KAAK,EAAE;QACtE,KAAK,CAAC,CAAC,GAAG,sCAAsC,CAAC,CAAC,KAAK,EAAE;QACzD,EAAE;KACH,CAAC;IACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,GAAW;IAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,GAAW;IACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,GAAW;IAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,GAAW;IAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,GAAG,CAAC,GAAW;IAC7B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,GAAG,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,EAAE,CAAC,GAAW,EAAE,KAAa;IAC3C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC,KAAK,IAAI,KAAK,IAAI,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,SAAiB;IACrC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,GAAG,SAAS,uBAAuB,CAAC,CAAC,KAAK,MAAM,CAC9G,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO,CAAC,GAAG,CAAC;IACV,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,gBAAgB,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,KAAK;IAC/D,CAAC,CAAC,GAAG,sCAAsC,CAAC,CAAC,KAAK;;IAElD,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,KAAK;MAChD,CAAC,CAAC,KAAK,yBAAyB,CAAC,CAAC,KAAK;;IAEzC,CAAC,CAAC,IAAI,YAAY,CAAC,CAAC,KAAK;MACvB,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,KAAK;iBACd,CAAC,CAAC,GAAG,gEAAgE,CAAC,CAAC,KAAK;;MAEvF,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,KAAK,yCAAyC,CAAC,CAAC,KAAK;iBAC3E,CAAC,CAAC,GAAG,+DAA+D,CAAC,CAAC,KAAK;;IAExF,CAAC,CAAC,IAAI,uBAAuB,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,2BAA2B,CAAC,CAAC,KAAK;MAC7E,CAAC,CAAC,IAAI,oBAAoB,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,IAAI,YAAY,GAAG,CAAC,KAAK,oBAAoB,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,2BAA2B,CAAC,CAAC,KAAK;MACxI,CAAC,CAAC,IAAI,eAAe,CAAC,CAAC,KAAK,gCAAgC,CAAC,CAAC,GAAG,mCAAmC,CAAC,CAAC,KAAK;MAC3G,CAAC,CAAC,IAAI,iBAAiB,CAAC,CAAC,KAAK;MAC9B,CAAC,CAAC,IAAI,mBAAmB,CAAC,CAAC,KAAK;MAChC,CAAC,CAAC,IAAI,gBAAgB,CAAC,CAAC,KAAK,iCAAiC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,KAAK;MACxF,CAAC,CAAC,IAAI,eAAe,CAAC,CAAC,KAAK;MAC5B,CAAC,CAAC,IAAI,gBAAgB,CAAC,CAAC,KAAK,iCAAiC,CAAC,CAAC,GAAG,kCAAkC,CAAC,CAAC,KAAK;MAC5G,CAAC,CAAC,IAAI,gBAAgB,CAAC,CAAC,KAAK;MAC7B,CAAC,CAAC,GAAG,sBAAsB,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,GAAG,kDAAkD,CAAC,CAAC,KAAK;;IAEvG,CAAC,CAAC,IAAI,eAAe,CAAC,CAAC,KAAK;MAC1B,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,eAAe,CAAC,CAAC,KAAK,UAAU,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,KAAK,0BAA0B,CAAC,CAAC,KAAK;MAC7G,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,oBAAoB,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,KAAK,wBAAwB,CAAC,CAAC,KAAK;MAC3G,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,mBAAmB,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,KAAK,wBAAwB,CAAC,CAAC,KAAK;MAC3G,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,gBAAgB,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,KAAK,oBAAoB,CAAC,CAAC,KAAK;MACvG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,YAAY,CAAC,CAAC,KAAK,cAAc,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,KAAK,oBAAoB,CAAC,CAAC,KAAK;MACzG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,eAAe,CAAC,CAAC,KAAK,UAAU,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,KAAK,oBAAoB,CAAC,CAAC,KAAK;MACvG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,gBAAgB,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,KAAK,kCAAkC,CAAC,CAAC,KAAK;;IAEvH,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,KAAK;MACtB,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,iCAAiC,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK;MAC3F,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,iCAAiC,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK;MAC3F,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,iCAAiC,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK;CAChG,CAAC,CAAC;AACH,CAAC;AAED,OAAO,EAAE,CAAC,EAAE,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* context.ts — Automatic context gathering engine.
|
|
3
|
+
*
|
|
4
|
+
* The #1 insight: developers waste most debugging time gathering context,
|
|
5
|
+
* not fixing bugs. This module automates that entirely.
|
|
6
|
+
*
|
|
7
|
+
* Given an error string, it:
|
|
8
|
+
* 1. Parses stack frames to find relevant source files
|
|
9
|
+
* 2. Reads those files (the exact lines around the error)
|
|
10
|
+
* 3. Gets the git diff showing recent changes to those files
|
|
11
|
+
* 4. Captures the runtime environment
|
|
12
|
+
* 5. Returns a single structured object with everything the agent needs
|
|
13
|
+
*/
|
|
14
|
+
export interface StackFrame {
|
|
15
|
+
fn: string;
|
|
16
|
+
file: string;
|
|
17
|
+
line: number;
|
|
18
|
+
col: number | null;
|
|
19
|
+
isUserCode: boolean;
|
|
20
|
+
}
|
|
21
|
+
interface SourceSnippet {
|
|
22
|
+
file: string;
|
|
23
|
+
relativePath: string;
|
|
24
|
+
startLine: number;
|
|
25
|
+
endLine: number;
|
|
26
|
+
lines: string;
|
|
27
|
+
errorLine: number;
|
|
28
|
+
}
|
|
29
|
+
interface GitContext {
|
|
30
|
+
branch: string | null;
|
|
31
|
+
commit: string | null;
|
|
32
|
+
dirty: number;
|
|
33
|
+
recentChanges: string | null;
|
|
34
|
+
}
|
|
35
|
+
interface EnvSnapshot {
|
|
36
|
+
platform: string;
|
|
37
|
+
node: string;
|
|
38
|
+
python: string | null;
|
|
39
|
+
rust: string | null;
|
|
40
|
+
project: string | null;
|
|
41
|
+
frameworks: Record<string, string>;
|
|
42
|
+
envVars: Record<string, string>;
|
|
43
|
+
}
|
|
44
|
+
export interface ErrorClassification {
|
|
45
|
+
type: string;
|
|
46
|
+
summary: string;
|
|
47
|
+
category: string;
|
|
48
|
+
severity: "fatal" | "error" | "warning";
|
|
49
|
+
suggestion: string;
|
|
50
|
+
}
|
|
51
|
+
export interface InvestigationResult {
|
|
52
|
+
error: ErrorClassification;
|
|
53
|
+
sourceCode: SourceSnippet[];
|
|
54
|
+
git: GitContext;
|
|
55
|
+
environment: EnvSnapshot;
|
|
56
|
+
frames: StackFrame[];
|
|
57
|
+
}
|
|
58
|
+
export declare function investigate(errorText: string, cwd: string): InvestigationResult;
|
|
59
|
+
export {};
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* context.ts — Automatic context gathering engine.
|
|
3
|
+
*
|
|
4
|
+
* The #1 insight: developers waste most debugging time gathering context,
|
|
5
|
+
* not fixing bugs. This module automates that entirely.
|
|
6
|
+
*
|
|
7
|
+
* Given an error string, it:
|
|
8
|
+
* 1. Parses stack frames to find relevant source files
|
|
9
|
+
* 2. Reads those files (the exact lines around the error)
|
|
10
|
+
* 3. Gets the git diff showing recent changes to those files
|
|
11
|
+
* 4. Captures the runtime environment
|
|
12
|
+
* 5. Returns a single structured object with everything the agent needs
|
|
13
|
+
*/
|
|
14
|
+
import { execSync } from "node:child_process";
|
|
15
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
16
|
+
import { join, relative, isAbsolute } from "node:path";
|
|
17
|
+
import { redactSensitiveData } from "./security.js";
|
|
18
|
+
// Match "at FnName (file:line:col)" or "at file:line:col"
|
|
19
|
+
const NODE_FRAME = /at\s+(?:([\w$.< >\[\]]+?)\s+)?\(?([^\s()]+):(\d+):(\d+)\)?/gm;
|
|
20
|
+
const PY_FRAME = /File "(.+?)", line (\d+)(?:, in (.+))?/g;
|
|
21
|
+
// Rust backtrace: " 4: my_app::handler at ./src-tauri/src/main.rs:15:10"
|
|
22
|
+
const RUST_FRAME = /^\s*\d+:\s+([\w:<>]+)(?:\s+at\s+(.+?):(\d+)(?::(\d+))?)?$/gm;
|
|
23
|
+
// Rust panic location: "thread 'main' panicked at 'msg', src/main.rs:15:10"
|
|
24
|
+
const RUST_PANIC = /panicked at (?:'[^']*'|"[^"]*"),\s*(.+?):(\d+):(\d+)/;
|
|
25
|
+
// Cargo error: "error[E0308]: mismatched types\n --> src/main.rs:15:10"
|
|
26
|
+
const CARGO_ERROR_LOC = /-->\s*(.+?):(\d+):(\d+)/g;
|
|
27
|
+
function parseStackFrames(error, cwd) {
|
|
28
|
+
const frames = [];
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
// Detect if this is a Rust error (parse Rust-specific formats FIRST,
|
|
31
|
+
// before Node.js regex grabs "at file:line:col" from backtraces)
|
|
32
|
+
const isRustError = /panicked at|stack backtrace:|error\[E\d+]|-->\s*\S+\.rs:/.test(error);
|
|
33
|
+
let m;
|
|
34
|
+
if (isRustError) {
|
|
35
|
+
// Rust panic location
|
|
36
|
+
const panic = RUST_PANIC.exec(error);
|
|
37
|
+
if (panic) {
|
|
38
|
+
const key = `${panic[1]}:${panic[2]}`;
|
|
39
|
+
seen.add(key);
|
|
40
|
+
frames.push({
|
|
41
|
+
fn: "<panic>",
|
|
42
|
+
file: panic[1],
|
|
43
|
+
line: +panic[2],
|
|
44
|
+
col: +panic[3],
|
|
45
|
+
isUserCode: !panic[1].includes(".cargo") && !panic[1].includes("/rustc/"),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
// Rust backtrace frames
|
|
49
|
+
RUST_FRAME.lastIndex = 0;
|
|
50
|
+
while ((m = RUST_FRAME.exec(error)) !== null) {
|
|
51
|
+
if (!m[2])
|
|
52
|
+
continue;
|
|
53
|
+
const file = m[2].replace(/^\.\//, "");
|
|
54
|
+
const key = `${file}:${m[3]}`;
|
|
55
|
+
if (seen.has(key))
|
|
56
|
+
continue;
|
|
57
|
+
seen.add(key);
|
|
58
|
+
frames.push({
|
|
59
|
+
fn: m[1],
|
|
60
|
+
file,
|
|
61
|
+
line: +m[3],
|
|
62
|
+
col: m[4] ? +m[4] : null,
|
|
63
|
+
isUserCode: !file.includes(".cargo") && !file.includes("/rustc/")
|
|
64
|
+
&& !file.includes("registry/src"),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// Cargo compiler error locations
|
|
68
|
+
CARGO_ERROR_LOC.lastIndex = 0;
|
|
69
|
+
while ((m = CARGO_ERROR_LOC.exec(error)) !== null) {
|
|
70
|
+
const key = `${m[1]}:${m[2]}`;
|
|
71
|
+
if (seen.has(key))
|
|
72
|
+
continue;
|
|
73
|
+
seen.add(key);
|
|
74
|
+
frames.push({
|
|
75
|
+
fn: "<compile>",
|
|
76
|
+
file: m[1],
|
|
77
|
+
line: +m[2],
|
|
78
|
+
col: +m[3],
|
|
79
|
+
isUserCode: true,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Node.js / JS / TS (skip if already parsed as Rust)
|
|
84
|
+
if (!isRustError) {
|
|
85
|
+
NODE_FRAME.lastIndex = 0;
|
|
86
|
+
while ((m = NODE_FRAME.exec(error)) !== null) {
|
|
87
|
+
const key = `${m[2]}:${m[3]}`;
|
|
88
|
+
if (seen.has(key))
|
|
89
|
+
continue;
|
|
90
|
+
seen.add(key);
|
|
91
|
+
const file = m[2];
|
|
92
|
+
frames.push({
|
|
93
|
+
fn: m[1] ?? "<anonymous>",
|
|
94
|
+
file,
|
|
95
|
+
line: +m[3],
|
|
96
|
+
col: +m[4],
|
|
97
|
+
isUserCode: !file.includes("node_modules") && !file.startsWith("node:"),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Python
|
|
102
|
+
PY_FRAME.lastIndex = 0;
|
|
103
|
+
while ((m = PY_FRAME.exec(error)) !== null) {
|
|
104
|
+
const key = `${m[1]}:${m[2]}`;
|
|
105
|
+
if (seen.has(key))
|
|
106
|
+
continue;
|
|
107
|
+
seen.add(key);
|
|
108
|
+
frames.push({
|
|
109
|
+
fn: m[3] ?? "<module>",
|
|
110
|
+
file: m[1],
|
|
111
|
+
line: +m[2],
|
|
112
|
+
col: null,
|
|
113
|
+
isUserCode: !m[1].includes("site-packages") && !m[1].includes("/lib/python"),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return frames;
|
|
117
|
+
}
|
|
118
|
+
function extractSourceSnippets(frames, cwd, contextLines = 5) {
|
|
119
|
+
const snippets = [];
|
|
120
|
+
const seen = new Set();
|
|
121
|
+
// Only user code frames, up to 3 files
|
|
122
|
+
const userFrames = frames.filter((f) => f.isUserCode).slice(0, 3);
|
|
123
|
+
for (const frame of userFrames) {
|
|
124
|
+
let filePath = frame.file;
|
|
125
|
+
// Resolve relative to cwd
|
|
126
|
+
if (!isAbsolute(filePath))
|
|
127
|
+
filePath = join(cwd, filePath);
|
|
128
|
+
if (!existsSync(filePath))
|
|
129
|
+
continue;
|
|
130
|
+
if (seen.has(filePath))
|
|
131
|
+
continue;
|
|
132
|
+
seen.add(filePath);
|
|
133
|
+
try {
|
|
134
|
+
const stat = statSync(filePath);
|
|
135
|
+
if (stat.size > 1_000_000)
|
|
136
|
+
continue; // Skip files > 1MB
|
|
137
|
+
const content = readFileSync(filePath, "utf-8");
|
|
138
|
+
const allLines = content.split("\n");
|
|
139
|
+
const start = Math.max(0, frame.line - contextLines - 1);
|
|
140
|
+
const end = Math.min(allLines.length, frame.line + contextLines);
|
|
141
|
+
const slice = allLines.slice(start, end);
|
|
142
|
+
// Number the lines
|
|
143
|
+
const numbered = slice.map((l, i) => {
|
|
144
|
+
const lineNum = start + i + 1;
|
|
145
|
+
const marker = lineNum === frame.line ? " >> " : " ";
|
|
146
|
+
return `${marker}${lineNum} | ${l}`;
|
|
147
|
+
}).join("\n");
|
|
148
|
+
snippets.push({
|
|
149
|
+
file: filePath,
|
|
150
|
+
relativePath: relative(cwd, filePath),
|
|
151
|
+
startLine: start + 1,
|
|
152
|
+
endLine: end,
|
|
153
|
+
lines: numbered,
|
|
154
|
+
errorLine: frame.line,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
catch { /* skip unreadable files */ }
|
|
158
|
+
}
|
|
159
|
+
return snippets;
|
|
160
|
+
}
|
|
161
|
+
function getGitContext(cwd, files) {
|
|
162
|
+
const ctx = { branch: null, commit: null, dirty: 0, recentChanges: null };
|
|
163
|
+
try {
|
|
164
|
+
ctx.branch = execSync("git branch --show-current 2>/dev/null", { cwd, timeout: 3000 }).toString().trim();
|
|
165
|
+
ctx.commit = execSync("git rev-parse --short HEAD 2>/dev/null", { cwd, timeout: 3000 }).toString().trim();
|
|
166
|
+
const status = execSync("git status --porcelain 2>/dev/null", { cwd, timeout: 3000 }).toString().trim();
|
|
167
|
+
ctx.dirty = status ? status.split("\n").length : 0;
|
|
168
|
+
// Get recent changes to relevant files (last 3 commits)
|
|
169
|
+
if (files.length > 0) {
|
|
170
|
+
const relFiles = files.map((f) => relative(cwd, f)).filter((f) => !f.startsWith(".."));
|
|
171
|
+
if (relFiles.length > 0) {
|
|
172
|
+
const fileArgs = relFiles.map((f) => `"${f}"`).join(" ");
|
|
173
|
+
try {
|
|
174
|
+
const diff = execSync(`git log --oneline -3 --diff-filter=M -- ${fileArgs} 2>/dev/null`, { cwd, timeout: 5000 }).toString().trim();
|
|
175
|
+
if (diff)
|
|
176
|
+
ctx.recentChanges = diff;
|
|
177
|
+
}
|
|
178
|
+
catch { }
|
|
179
|
+
// Also get unstaged changes
|
|
180
|
+
try {
|
|
181
|
+
const unstaged = execSync(`git diff --stat -- ${fileArgs} 2>/dev/null`, { cwd, timeout: 5000 }).toString().trim();
|
|
182
|
+
if (unstaged) {
|
|
183
|
+
ctx.recentChanges = (ctx.recentChanges ? ctx.recentChanges + "\n\n" : "") +
|
|
184
|
+
"Unstaged changes:\n" + unstaged;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch { }
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch { /* not a git repo */ }
|
|
192
|
+
return ctx;
|
|
193
|
+
}
|
|
194
|
+
function getEnvironment(cwd) {
|
|
195
|
+
const env = {
|
|
196
|
+
platform: `${process.platform}/${process.arch}`,
|
|
197
|
+
node: process.version,
|
|
198
|
+
python: null,
|
|
199
|
+
rust: null,
|
|
200
|
+
project: null,
|
|
201
|
+
frameworks: {},
|
|
202
|
+
envVars: {},
|
|
203
|
+
};
|
|
204
|
+
try {
|
|
205
|
+
env.python = execSync("python3 --version 2>&1", { timeout: 2000 }).toString().trim();
|
|
206
|
+
}
|
|
207
|
+
catch { }
|
|
208
|
+
try {
|
|
209
|
+
env.rust = execSync("rustc --version 2>&1", { timeout: 2000 }).toString().trim();
|
|
210
|
+
}
|
|
211
|
+
catch { }
|
|
212
|
+
const pkgPath = join(cwd, "package.json");
|
|
213
|
+
if (existsSync(pkgPath)) {
|
|
214
|
+
try {
|
|
215
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
216
|
+
env.project = pkg.name ?? null;
|
|
217
|
+
for (const name of ["react", "next", "vue", "nuxt", "svelte", "express", "fastify", "vite", "typescript", "tailwindcss", "prisma", "drizzle", "@tauri-apps/api", "@tauri-apps/cli"]) {
|
|
218
|
+
const v = pkg.dependencies?.[name] ?? pkg.devDependencies?.[name];
|
|
219
|
+
if (v)
|
|
220
|
+
env.frameworks[name] = v;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch { }
|
|
224
|
+
}
|
|
225
|
+
// Detect Tauri project (Cargo.toml with tauri dependency)
|
|
226
|
+
const tauriConfPath = join(cwd, "src-tauri", "tauri.conf.json");
|
|
227
|
+
const cargoPath = join(cwd, "src-tauri", "Cargo.toml");
|
|
228
|
+
if (existsSync(tauriConfPath)) {
|
|
229
|
+
try {
|
|
230
|
+
const conf = JSON.parse(readFileSync(tauriConfPath, "utf-8"));
|
|
231
|
+
env.frameworks["tauri"] = conf.version ?? "v2";
|
|
232
|
+
env.frameworks["tauri-identifier"] = conf.identifier ?? conf.bundle?.identifier ?? "unknown";
|
|
233
|
+
}
|
|
234
|
+
catch { }
|
|
235
|
+
}
|
|
236
|
+
if (existsSync(cargoPath)) {
|
|
237
|
+
try {
|
|
238
|
+
const cargo = readFileSync(cargoPath, "utf-8");
|
|
239
|
+
const tauriVer = cargo.match(/tauri\s*=\s*\{[^}]*version\s*=\s*"([^"]+)"/);
|
|
240
|
+
if (tauriVer)
|
|
241
|
+
env.frameworks["tauri-core"] = tauriVer[1];
|
|
242
|
+
// Detect plugins
|
|
243
|
+
const plugins = [...cargo.matchAll(/tauri-plugin-(\w+)/g)].map((m) => m[1]);
|
|
244
|
+
if (plugins.length > 0)
|
|
245
|
+
env.frameworks["tauri-plugins"] = plugins.join(", ");
|
|
246
|
+
}
|
|
247
|
+
catch { }
|
|
248
|
+
}
|
|
249
|
+
for (const k of ["NODE_ENV", "PORT", "HOST", "DATABASE_URL", "API_URL", "RUST_BACKTRACE", "RUST_LOG"]) {
|
|
250
|
+
if (process.env[k])
|
|
251
|
+
env.envVars[k] = redactSensitiveData(process.env[k]);
|
|
252
|
+
}
|
|
253
|
+
return env;
|
|
254
|
+
}
|
|
255
|
+
function classifyError(raw) {
|
|
256
|
+
const r = {
|
|
257
|
+
type: "Unknown",
|
|
258
|
+
summary: raw.split("\n")[0]?.slice(0, 200) ?? "",
|
|
259
|
+
category: "runtime",
|
|
260
|
+
severity: "error",
|
|
261
|
+
suggestion: "",
|
|
262
|
+
};
|
|
263
|
+
const tm = raw.match(/^(\w+Error):\s*(.*)/m);
|
|
264
|
+
if (tm) {
|
|
265
|
+
r.type = tm[1];
|
|
266
|
+
r.summary = tm[2];
|
|
267
|
+
}
|
|
268
|
+
// Detect Rust panic first (overrides generic matching)
|
|
269
|
+
const panicMatch = raw.match(/thread '(.+?)' panicked at (?:'([^']*)'|"([^"]*)")/);
|
|
270
|
+
if (panicMatch) {
|
|
271
|
+
r.type = "Panic";
|
|
272
|
+
r.summary = panicMatch[2] ?? panicMatch[3] ?? "panic";
|
|
273
|
+
r.category = "rust-panic";
|
|
274
|
+
r.severity = "fatal";
|
|
275
|
+
r.suggestion = "Rust panic — check the unwrap()/expect() call. Set RUST_BACKTRACE=1 for a full backtrace.";
|
|
276
|
+
return r;
|
|
277
|
+
}
|
|
278
|
+
// Detect Cargo build errors
|
|
279
|
+
const cargoErr = raw.match(/^error\[E(\d+)]: (.+)/m);
|
|
280
|
+
if (cargoErr) {
|
|
281
|
+
r.type = `CargoError[E${cargoErr[1]}]`;
|
|
282
|
+
r.summary = cargoErr[2];
|
|
283
|
+
r.category = "rust-compile";
|
|
284
|
+
r.severity = "fatal";
|
|
285
|
+
r.suggestion = `Rust compilation error E${cargoErr[1]}. Check the source location shown after "-->".`;
|
|
286
|
+
return r;
|
|
287
|
+
}
|
|
288
|
+
const rules = [
|
|
289
|
+
// ── Tauri-specific (check before generic web errors) ──
|
|
290
|
+
[/invoke\s+error|__TAURI_IPC__|ipc.*error/i, "tauri-ipc", "error", "Tauri invoke failed — check command registration in generate_handler![] and argument types"],
|
|
291
|
+
[/capability.*not.*found|permission.*denied.*tauri|not allowed.*command/i, "tauri-capability", "error", "Tauri capability/permission error — add the command permission to src-tauri/capabilities/*.json"],
|
|
292
|
+
[/PluginInitialization|plugin.*failed.*init/i, "tauri-plugin", "error", "Tauri plugin failed to initialize — check plugin setup in Builder::plugin()"],
|
|
293
|
+
[/WindowLabelAlreadyExists|WebviewLabelAlreadyExists/i, "tauri-window", "error", "Duplicate window/webview label — use a unique label for each window"],
|
|
294
|
+
[/WebviewNotFound|WindowNotFound/i, "tauri-window", "error", "Window/webview not found — check the label matches what was created"],
|
|
295
|
+
[/AssetNotFound/i, "tauri-asset", "error", "Frontend asset not found — check frontendDist in tauri.conf.json points to your build output"],
|
|
296
|
+
[/CannotDeserializeScope/i, "tauri-acl", "error", "Tauri ACL scope deserialization failed — check capability scope definitions"],
|
|
297
|
+
[/tauri.*setup|Setup.*error/i, "tauri-setup", "fatal", "Tauri app setup failed — check the setup closure in Builder::setup()"],
|
|
298
|
+
// ── Rust-specific ──
|
|
299
|
+
[/unwrap\(\).*on.*None/i, "rust-panic", "fatal", "Called unwrap() on None — use match, if let, or ? operator instead"],
|
|
300
|
+
[/unwrap\(\).*on.*Err/i, "rust-panic", "fatal", "Called unwrap() on Err — use match or ? operator to handle the error"],
|
|
301
|
+
[/borrow.*already.*mutably|cannot borrow/i, "rust-borrow", "fatal", "Rust borrow checker error — check for overlapping mutable references"],
|
|
302
|
+
[/overflow|underflow/i, "rust-arithmetic", "error", "Integer overflow/underflow — use checked_add/checked_sub or wrapping operations"],
|
|
303
|
+
// ── General (JS/TS/Python/Go) ──
|
|
304
|
+
[/TypeError/i, "type", "error", "Check for null/undefined values being accessed as objects"],
|
|
305
|
+
[/ReferenceError/i, "reference", "error", "Check for typos in variable/function names or missing imports"],
|
|
306
|
+
[/SyntaxError/i, "syntax", "fatal", "Check for missing brackets, quotes, or invalid syntax"],
|
|
307
|
+
[/ECONNREFUSED/i, "network", "error", "The server/API isn't running — start it first"],
|
|
308
|
+
[/ENOENT|no such file/i, "filesystem", "error", "File doesn't exist — check the path"],
|
|
309
|
+
[/Cannot find module/i, "dependency", "error", "Run `npm install` to install missing dependencies"],
|
|
310
|
+
[/ERR_MODULE_NOT_FOUND/i, "esm", "error", "Add .js extension to import or set type:module in package.json"],
|
|
311
|
+
[/EACCES|Permission denied/i, "permissions", "error", "Check file permissions or run with elevated privileges"],
|
|
312
|
+
[/\b401\b/, "auth", "error", "Authentication failed — token may be expired"],
|
|
313
|
+
[/\b403\b/, "authz", "error", "Permission denied — check authorization"],
|
|
314
|
+
[/\b404\b/, "not-found", "error", "Endpoint doesn't exist — check the URL path"],
|
|
315
|
+
[/\b5\d{2}\b/, "server", "error", "Server error — check the backend logs"],
|
|
316
|
+
[/out of memory|heap/i, "memory", "fatal", "Process ran out of memory — check for leaks or increase limit"],
|
|
317
|
+
[/SIGKILL|SIGTERM/i, "killed", "fatal", "Process was killed — may be OOM or timeout"],
|
|
318
|
+
];
|
|
319
|
+
for (const [pat, cat, sev, sugg] of rules) {
|
|
320
|
+
if (pat.test(raw)) {
|
|
321
|
+
r.category = cat;
|
|
322
|
+
r.severity = sev;
|
|
323
|
+
r.suggestion = sugg;
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return r;
|
|
328
|
+
}
|
|
329
|
+
export function investigate(errorText, cwd) {
|
|
330
|
+
const frames = parseStackFrames(errorText, cwd);
|
|
331
|
+
const sourceCode = extractSourceSnippets(frames, cwd);
|
|
332
|
+
const relevantFiles = sourceCode.map((s) => s.file);
|
|
333
|
+
const git = getGitContext(cwd, relevantFiles);
|
|
334
|
+
const environment = getEnvironment(cwd);
|
|
335
|
+
const error = classifyError(errorText);
|
|
336
|
+
return { error, sourceCode, git, environment, frames };
|
|
337
|
+
}
|
|
338
|
+
//# sourceMappingURL=context.js.map
|