Spaces:
Sleeping
Sleeping
| /** | |
| * errorSignatureBuilder.ts — S697: Structured error diagnosis for self-healing | |
| * | |
| * Builds a rich ErrorSignature from raw error strings: | |
| * - 16 categories vs 8 flat regex (previous adaptiveSolver) | |
| * - Python traceback frame parsing (File "x.py", line N, in fn) | |
| * - JS/TS stack trace parsing (at fn (file:line:col)) | |
| * - missingModule / missingSymbol extraction | |
| * - repairComplexity: trivial → simple → complex → requires_rewrite | |
| * - isTransient flag: timeout/network = retry; syntax = must change code | |
| * - Anti-loop fingerprint (djb2 hash, Safari-safe, no crypto) | |
| */ | |
| export type ErrorCategory = | |
| | "import_error" | |
| | "type_error" | |
| | "syntax_error" | |
| | "reference_error" | |
| | "assertion_fail" | |
| | "runtime_crash" | |
| | "timeout" | |
| | "network_fail" | |
| | "cors" | |
| | "permission" | |
| | "rate_limit" | |
| | "not_found" | |
| | "dependency" | |
| | "build_fail" | |
| | "quota" | |
| | "unknown"; | |
| export type RepairComplexity = "trivial" | "simple" | "complex" | "requires_rewrite"; | |
| export interface TracebackFrame { | |
| file: string; | |
| line: number; | |
| column?: number; | |
| fn?: string; | |
| } | |
| export interface ErrorSignature { | |
| raw: string; | |
| category: ErrorCategory; | |
| exitCode?: number; | |
| stderrLines: string[]; | |
| errorType?: string; | |
| errorMessage?: string; | |
| failedFile?: string; | |
| failedLine?: number; | |
| failedColumn?: number; | |
| missingModule?: string; | |
| missingSymbol?: string; | |
| tracebackFrames: TracebackFrame[]; | |
| isTransient: boolean; | |
| repairComplexity: RepairComplexity; | |
| tool: string; | |
| args: Record<string, unknown>; | |
| iteration: number; | |
| attemptNumber: number; | |
| isKnown: boolean; | |
| knownFix?: string; | |
| fingerprint: string; | |
| } | |
| // ─── Frame parsers ──────────────────────────────────────────────────────────── | |
| function parsePythonTraceback(raw: string): TracebackFrame[] { | |
| const frames: TracebackFrame[] = []; | |
| const re = /File\s+"([^"]+)",\s+line\s+(\d+)(?:,\s+in\s+(\S+))?/g; | |
| let m: RegExpExecArray | null; | |
| while ((m = re.exec(raw)) !== null) { | |
| frames.push({ file: m[1], line: parseInt(m[2]), fn: m[3] }); | |
| } | |
| return frames; | |
| } | |
| function parseJsStack(raw: string): TracebackFrame[] { | |
| const frames: TracebackFrame[] = []; | |
| const re = /at\s+(?:(\S+)\s+\()?([^\s:()]+):(\d+):(\d+)\)?/g; | |
| let m: RegExpExecArray | null; | |
| while ((m = re.exec(raw)) !== null) { | |
| frames.push({ fn: m[1] ?? undefined, file: m[2], line: parseInt(m[3]), column: parseInt(m[4]) }); | |
| } | |
| return frames.slice(0, 10); | |
| } | |
| // ─── Extractors ─────────────────────────────────────────────────────────────── | |
| function extractMissingModule(raw: string): string | undefined { | |
| const jsM = raw.match(/(?:Cannot find module|Could not resolve|Module not found)[^'"]*['"]([^'"]+)['"]/i); | |
| if (jsM) return jsM[1]; | |
| const pyM = raw.match(/(?:No module named|ModuleNotFoundError)[^'"]*['"]?([a-zA-Z0-9_.\-]+)['"]?/i); | |
| if (pyM) return pyM[1]; | |
| const pyI = raw.match(/cannot import name\s+['"]([^'"]+)['"]/i); | |
| if (pyI) return pyI[1]; | |
| return undefined; | |
| } | |
| function extractMissingSymbol(raw: string): string | undefined { | |
| const r1 = raw.match(/ReferenceError:\s+(\S+)\s+is not defined/i); | |
| if (r1) return r1[1]; | |
| const r2 = raw.match(/TypeError:\s+(\S+)\s+is not a (?:function|constructor)/i); | |
| if (r2) return r2[1]; | |
| const r3 = raw.match(/Cannot (?:read|set) properties of (?:undefined|null) \((?:reading|setting) '([^']+)'\)/i); | |
| if (r3) return r3[1]; | |
| return undefined; | |
| } | |
| function extractFailedLine(raw: string): number | undefined { | |
| const m = raw.match(/(?::(\d+):\d+|at line (\d+)|line (\d+),|\((\d+)\))/); | |
| if (m) return parseInt(m[1] ?? m[2] ?? m[3] ?? m[4]); | |
| return undefined; | |
| } | |
| function extractFailedFile(raw: string): string | undefined { | |
| const m = raw.match(/(?:File "([^"]+)"|at\s+\S+\s+\(([^:)]+):\d+:\d+\)|Error in ([^\n:]+\.(?:ts|js|tsx|jsx|py)))/); | |
| if (m) return m[1] ?? m[2] ?? m[3]; | |
| return undefined; | |
| } | |
| function djb2Fingerprint(category: ErrorCategory, msg: string, tool: string): string { | |
| const key = `${category}:${msg.slice(0, 80).replace(/\s+/g, " ")}:${tool}`; | |
| let h = 5381; | |
| for (let i = 0; i < key.length; i++) { | |
| h = (((h << 5) + h) ^ key.charCodeAt(i)) >>> 0; | |
| } | |
| return h.toString(16); | |
| } | |
| // ─── Main classifier ────────────────────────────────────────────────────────── | |
| export function buildErrorSignature( | |
| raw: string, | |
| tool: string, | |
| args: Record<string, unknown>, | |
| iteration: number, | |
| attemptNumber: number, | |
| ): ErrorSignature { | |
| const lines = raw.split("\n").filter(l => l.trim().length > 0); | |
| let category: ErrorCategory = "unknown"; | |
| let errorType: string | undefined; | |
| let errorMessage: string | undefined; | |
| let isTransient = false; | |
| let repairComplexity: RepairComplexity = "complex"; | |
| let missingModule: string | undefined; | |
| let missingSymbol: string | undefined; | |
| const tracebackFrames: TracebackFrame[] = []; | |
| // ── Classification priority order ───────────────────────────────────────── | |
| if (/cannot find module|could not resolve|module not found|no module named|modulenotfounderror|importerror|cannot import name/i.test(raw)) { | |
| category = "import_error"; missingModule = extractMissingModule(raw); | |
| repairComplexity = missingModule ? "trivial" : "simple"; errorType = "ImportError"; | |
| } else if (/referenceerror|is not defined/i.test(raw)) { | |
| category = "reference_error"; missingSymbol = extractMissingSymbol(raw); | |
| repairComplexity = missingSymbol ? "trivial" : "simple"; errorType = "ReferenceError"; | |
| } else if (/typeerror|is not a function|cannot read propert|cannot set propert|is not a constructor/i.test(raw)) { | |
| category = "type_error"; missingSymbol = extractMissingSymbol(raw); | |
| repairComplexity = missingSymbol ? "simple" : "complex"; errorType = "TypeError"; | |
| } else if (/syntaxerror|unexpected token|unterminated string|unexpected end|parse error|invalid syntax/i.test(raw)) { | |
| category = "syntax_error"; repairComplexity = "simple"; errorType = "SyntaxError"; | |
| } else if (/assertionerror|assertion failed|expected.*to.*equal|test.*failed/i.test(raw)) { | |
| category = "assertion_fail"; repairComplexity = "requires_rewrite"; errorType = "AssertionError"; | |
| } else if (/429|rate.?limit|too many requests|ratelimitexceeded/i.test(raw)) { | |
| category = "rate_limit"; isTransient = true; repairComplexity = "trivial"; | |
| } else if (/401|403|forbidden|unauthorized|access denied|permission denied|eperm/i.test(raw)) { | |
| category = "permission"; repairComplexity = "simple"; | |
| } else if (/cors|cross.?origin|access.control.allow.origin|blocked by cors/i.test(raw)) { | |
| category = "cors"; isTransient = true; repairComplexity = "trivial"; | |
| } else if (/timeout|timed.?out|etimedout|operation.*timeout|abortError/i.test(raw)) { | |
| category = "timeout"; isTransient = true; repairComplexity = "trivial"; | |
| } else if (/econnrefused|enotfound|failed to fetch|network error|net::err|fetch.*failed|connection.*refused/i.test(raw)) { | |
| category = "network_fail"; isTransient = true; repairComplexity = "trivial"; | |
| } else if (/404|enoent|file not found|no such file|path.*does not exist/i.test(raw)) { | |
| category = "not_found"; repairComplexity = "simple"; | |
| } else if (/quota|storage full|no space left|disk.*full|exceeded.*storage/i.test(raw)) { | |
| category = "quota"; repairComplexity = "complex"; | |
| } else if (/peer dependency|version mismatch|incompatible.*version|dependency.*conflict/i.test(raw)) { | |
| category = "dependency"; repairComplexity = "simple"; | |
| } else if (/build failed|compilation error|tsc.*error|vite.*build.*error|rollup.*error/i.test(raw)) { | |
| category = "build_fail"; repairComplexity = "complex"; | |
| } else if (/exit code [^0]|exited with \d+|uncaught exception|unhandled.*rejection|segfault/i.test(raw)) { | |
| category = "runtime_crash"; repairComplexity = "complex"; | |
| } | |
| // ── Traceback frames ─────────────────────────────────────────────────────── | |
| if (/traceback \(most recent call last\)/i.test(raw)) { | |
| tracebackFrames.push(...parsePythonTraceback(raw)); | |
| } else if (/\bat\s+(?:\S+\s+)?\([^)]+:\d+:\d+\)/.test(raw)) { | |
| tracebackFrames.push(...parseJsStack(raw)); | |
| } | |
| // ── Error type/message from first error line ─────────────────────────────── | |
| const firstErrLine = lines.find(l => /Error|error|Exception/i.test(l)) ?? lines[0] ?? ""; | |
| const typeMatch = firstErrLine.match(/^([A-Z][a-zA-Z]+Error|[A-Z][a-zA-Z]+Exception):\s*(.+)/); | |
| if (typeMatch && !errorType) { | |
| errorType = typeMatch[1]; | |
| errorMessage = typeMatch[2]?.trim().slice(0, 200); | |
| } | |
| // ── Numeric fields ───────────────────────────────────────────────────────── | |
| const failedLine = extractFailedLine(raw); | |
| const failedFile = extractFailedFile(raw); | |
| const colMatch = raw.match(/:(\d+):(\d+)/); | |
| const failedColumn = colMatch ? parseInt(colMatch[2]) : undefined; | |
| // ── SelfLearning integration (best-effort) ───────────────────────────────── | |
| let isKnown = false; let knownFix: string | undefined; | |
| try { | |
| // Dynamic import to avoid circular dependency | |
| const sl = (globalThis as Record<string, unknown>)._selfLearningCache as { preCheck?: (t: string, a: Record<string, unknown>) => { hasKnownIssue: boolean; suggestion?: string } } | undefined; | |
| if (sl?.preCheck) { const pc = sl.preCheck(tool, args); if (pc.hasKnownIssue) { isKnown = true; knownFix = pc.suggestion; } } | |
| } catch { /* non-blocking */ } | |
| const fingerprint = djb2Fingerprint(category, errorMessage ?? raw.slice(0, 80), tool); | |
| return { | |
| raw: raw.slice(0, 500), | |
| category, | |
| stderrLines: lines.slice(0, 20), | |
| errorType, | |
| errorMessage, | |
| failedFile, | |
| failedLine, | |
| failedColumn, | |
| missingModule, | |
| missingSymbol, | |
| tracebackFrames, | |
| isTransient, | |
| repairComplexity, | |
| tool, | |
| args, | |
| iteration, | |
| attemptNumber, | |
| isKnown, | |
| knownFix, | |
| fingerprint, | |
| }; | |
| } | |