Spaces:
Sleeping
Sleeping
File size: 10,809 Bytes
95eb75a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 | /**
* 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,
};
}
|