"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FixVerifier = void 0; exports.createVerifier = createVerifier; const promises_1 = require("fs/promises"); const fs_1 = require("fs"); const path_1 = require("path"); const ts_morph_1 = require("ts-morph"); const utils_1 = require("./utils"); const pino_1 = __importDefault(require("pino")); const logger = (0, pino_1.default)({ level: process.env.LOG_LEVEL || 'info' }); const DEFAULT_CONFIG = { enableLint: true, enableSyntax: true, enableAST: true, enableTests: false, maxRetries: 3, timeout: 30000 }; class FixVerifier { config; project; constructor(config = {}, project) { this.config = { ...DEFAULT_CONFIG, ...config }; this.project = project || new ts_morph_1.Project(); } async verify(filename, remedyCode, startLine, endLine, workingDir) { const checks = []; let allPassed = true; let canRetry = true; let errorMessage; if (this.config.enableSyntax) { const syntaxCheck = await this.runSyntaxCheck(filename, remedyCode, workingDir); checks.push(syntaxCheck); if (!syntaxCheck.passed) { allPassed = false; errorMessage = syntaxCheck.feedback; } } if (this.config.enableLint && allPassed) { const lintCheck = await this.runLintCheck(filename, remedyCode, workingDir); checks.push(lintCheck); if (!lintCheck.passed) { allPassed = false; canRetry = false; errorMessage = lintCheck.feedback; } } if (this.config.enableAST && allPassed) { const astCheck = this.runASTValidation(filename, remedyCode); checks.push(astCheck); if (!astCheck.passed) { allPassed = false; errorMessage = astCheck.feedback; } } if (this.config.enableTests && allPassed) { const testCheck = await this.runTestCheck(filename, remedyCode, startLine, endLine, workingDir); checks.push(testCheck); if (!testCheck.passed) { allPassed = false; canRetry = false; errorMessage = testCheck.feedback; } } const overallFeedback = checks .map(c => `${c.passed ? '✅' : '❌'} ${c.name}: ${c.feedback}`) .join('\n'); return { passed: allPassed, checks, overallFeedback, canRetry, errorMessage }; } async runSyntaxCheck(filename, remedyCode, workingDir) { const startTime = Date.now(); const ext = filename.split('.').pop()?.toLowerCase() || ''; let feedback = ''; let passed = true; const tempFile = (0, path_1.join)(workingDir, `__prix_verify_${Date.now()}.${ext}`); try { if (ext === 'ts' || ext === 'js') { await (0, promises_1.writeFile)(tempFile, remedyCode, 'utf8'); feedback = await this.verifyJSTypeScript(tempFile, ext, workingDir); passed = !feedback.includes('❌'); } else if (ext === 'py') { await (0, promises_1.writeFile)(tempFile, remedyCode, 'utf8'); feedback = await this.verifyPython(tempFile); passed = !feedback.includes('❌'); } else if (ext === 'go') { await (0, promises_1.writeFile)(tempFile, remedyCode, 'utf8'); feedback = await this.verifyGo(tempFile); passed = !feedback.includes('❌'); } else { feedback = '⚠️ Syntax check not supported for this file type'; } } catch (e) { passed = false; feedback = `❌ Syntax check failed: ${e.message}`; } finally { await this.cleanup(tempFile); } return { name: 'Syntax Check', passed, feedback, duration: Date.now() - startTime }; } async verifyJSTypeScript(tempFile, ext, workingDir) { let feedback = ''; try { if (ext === 'ts') { const hasTypeScript = (0, fs_1.existsSync)((0, path_1.join)(workingDir, 'node_modules/typescript')) || (0, fs_1.existsSync)((0, path_1.join)(workingDir, 'node_modules/.bin/tsc')); if (hasTypeScript) { try { await (0, utils_1.prixExecAsync)('npx', ['tsc', tempFile, '--noEmit', '--esModuleInterop', '--skipLibCheck', '--jsx', 'react'], { cwd: workingDir, timeout: 15000 }); feedback = 'TS check passed'; } catch (e) { const errorOutput = e.stdout?.toString() || e.message || ''; const relevantErrors = this.filterErrorsForFile(errorOutput, (0, path_1.basename)(tempFile)); if (relevantErrors) { feedback = `❌ TS error: ${relevantErrors}`; } else { feedback = '❌ TS syntax error'; } } } else { feedback = '⚠️ TypeScript not installed, skipping TS check'; } } else { try { await (0, utils_1.prixExecAsync)('node', ['--check', tempFile], { timeout: 5000 }); feedback = 'Syntax check passed'; } catch (e) { feedback = `❌ Syntax error: ${e.message}`; } } } catch (e) { feedback = `❌ Check failed: ${e.message}`; } return feedback; } async verifyPython(tempFile) { try { await (0, utils_1.prixExecAsync)('python3', ['-m', 'py_compile', tempFile], { timeout: 5000 }); return 'Python syntax passed'; } catch (e) { const errorOutput = e.stderr?.toString() || e.message; return `❌ Python syntax error: ${errorOutput}`.substring(0, 200); } } async verifyGo(tempFile) { try { await (0, utils_1.prixExecAsync)('go', ['vet', tempFile], { timeout: 5000 }); return 'Go syntax passed'; } catch (e) { const errorOutput = e.stderr?.toString() || e.message; return `❌ Go syntax error: ${errorOutput}`.substring(0, 200); } } async runLintCheck(filename, remedyCode, workingDir) { const startTime = Date.now(); const ext = filename.split('.').pop()?.toLowerCase() || ''; let feedback = ''; let passed = true; const tempFile = (0, path_1.join)(workingDir, `__prix_lint_${Date.now()}.${ext}`); try { if ((ext === 'ts' || ext === 'js') && (0, fs_1.existsSync)((0, path_1.join)(workingDir, 'package.json'))) { await (0, promises_1.writeFile)(tempFile, remedyCode, 'utf8'); const hasEslint = (0, fs_1.existsSync)((0, path_1.join)(workingDir, '.eslintrc.json')) || (0, fs_1.existsSync)((0, path_1.join)(workingDir, '.eslintrc.js')) || (0, fs_1.existsSync)((0, path_1.join)(workingDir, '.eslintrc')); if (hasEslint) { try { await (0, utils_1.prixExecAsync)('npx', ['eslint', tempFile, '--max-warnings', '0'], { cwd: workingDir, timeout: 15000 }); feedback = 'Lint passed'; } catch (e) { const errorOutput = e.stdout?.toString() || e.message || ''; const relevantErrors = this.filterErrorsForFile(errorOutput, (0, path_1.basename)(tempFile)); feedback = `❌ Lint failed: ${relevantErrors || ' ESLint errors'}`; passed = false; } } else { feedback = '⚠️ ESLint not configured'; } } else if (ext === 'py') { const hasPylint = await this.commandExists('pylint'); const hasFlake8 = await this.commandExists('flake8'); await (0, promises_1.writeFile)(tempFile, remedyCode, 'utf8'); if (hasPylint) { try { await (0, utils_1.prixExecAsync)('pylint', [tempFile, '--disable=all', '--enable=E'], { timeout: 10000 }); feedback = 'Pylint passed'; } catch (e) { feedback = `❌ Pylint: ${e.message}`.substring(0, 200); passed = false; } } else if (hasFlake8) { try { await (0, utils_1.prixExecAsync)('flake8', [tempFile], { timeout: 10000 }); feedback = 'Flake8 passed'; } catch (e) { feedback = `❌ Flake8: ${e.message}`.substring(0, 200); passed = false; } } else { feedback = '⚠️ Python linter not available'; } } } catch (e) { passed = false; feedback = `❌ Lint failed: ${e.message}`; } finally { await this.cleanup(tempFile); } return { name: 'Lint Check', passed, feedback, duration: Date.now() - startTime }; } runASTValidation(filename, remedyCode) { const startTime = Date.now(); if (!filename.endsWith('.ts') && !filename.endsWith('.js')) { return { name: 'AST Validation', passed: true, feedback: 'Not applicable for this file type', duration: 0 }; } try { const tempFile = `__prix_ast_${Date.now()}_${filename}`; const sourceFile = this.project.createSourceFile(tempFile, remedyCode, { overwrite: true }); const diagnostics = sourceFile.getPreEmitDiagnostics(); if (diagnostics.length > 0) { const errorMessages = diagnostics .slice(0, 3) .map(d => d.getMessageText()) .join('; '); return { name: 'AST Validation', passed: false, feedback: `❌ AST errors: ${errorMessages}`, duration: Date.now() - startTime }; } const identifiers = sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.Identifier); const undefinedSymbols = []; for (const id of identifiers) { if (id.getSymbol()) continue; const text = id.getText(); const skipList = [ 'console', 'process', 'require', 'exports', 'module', 'Buffer', 'Array', 'Object', 'String', 'Number', 'Boolean', 'Date', 'Promise', 'Math', 'JSON', 'Map', 'Set', 'RegExp', 'Error', 'undefined', 'NaN', 'Infinity', 'parseInt', 'parseFloat' ]; if (skipList.includes(text)) continue; if (id.getParentIfKind(ts_morph_1.SyntaxKind.ImportSpecifier)) continue; if (id.getParentIfKind(ts_morph_1.SyntaxKind.PropertyAccessExpression)) continue; const parentKind = id.getParent()?.getKind(); if (parentKind === ts_morph_1.SyntaxKind.PropertyAssignment) continue; undefinedSymbols.push(text); } const uniqueUndefined = [...new Set(undefinedSymbols)].slice(0, 5); if (uniqueUndefined.length > 0) { return { name: 'AST Validation', passed: false, feedback: `❌ Hallucination detected: undefined symbols: ${uniqueUndefined.join(', ')}`, duration: Date.now() - startTime }; } return { name: 'AST Validation', passed: true, feedback: 'No hallucinations detected', duration: Date.now() - startTime }; } catch (e) { return { name: 'AST Validation', passed: false, feedback: `❌ AST validation failed: ${e.message}`, duration: Date.now() - startTime }; } } async runTestCheck(filename, remedyCode, startLine, endLine, workingDir) { const startTime = Date.now(); if (!(0, fs_1.existsSync)((0, path_1.join)(workingDir, 'package.json'))) { return { name: 'Test Check', passed: true, feedback: '⚠️ No package.json found', duration: 0 }; } const testFile = this.findCorrespondingTestFile(filename); if (!testFile) { return { name: 'Test Check', passed: true, feedback: '⚠️ No corresponding test file found', duration: 0 }; } try { const testContent = await (0, promises_1.readFile)(testFile, 'utf8'); const testsForFunction = this.extractRelevantTests(testContent, filename, startLine); if (!testsForFunction) { return { name: 'Test Check', passed: true, feedback: '⚠️ No relevant tests found for modified code', duration: 0 }; } const tempTestFile = (0, path_1.join)(workingDir, `__prix_test_${Date.now()}.test.ts`); await (0, promises_1.writeFile)(tempTestFile, testsForFunction, 'utf8'); try { const hasJest = (0, fs_1.existsSync)((0, path_1.join)(workingDir, 'node_modules/jest')) || (0, fs_1.existsSync)((0, path_1.join)(workingDir, 'node_modules/.bin/jest')); if (hasJest) { try { await (0, utils_1.prixExecAsync)('npx', ['jest', tempTestFile, '--passWithNoTests'], { cwd: workingDir, timeout: 30000 }); return { name: 'Test Check', passed: true, feedback: 'Test check passed', duration: Date.now() - startTime }; } catch (e) { return { name: 'Test Check', passed: false, feedback: `❌ Test failed: ${e.message}`.substring(0, 200), duration: Date.now() - startTime }; } } } finally { await this.cleanup(tempTestFile); } return { name: 'Test Check', passed: true, feedback: '⚠️ Test runner not available', duration: 0 }; } catch (e) { return { name: 'Test Check', passed: false, feedback: `❌ Test check failed: ${e.message}`, duration: Date.now() - startTime }; } } findCorrespondingTestFile(filename) { const baseDir = filename.substring(0, filename.lastIndexOf('/')); const basename = filename.substring(filename.lastIndexOf('/') + 1); const nameWithoutExt = basename.replace(/\.[^.]+$/, ''); const patterns = [ (0, path_1.join)(baseDir, `${nameWithoutExt}.test.ts`), (0, path_1.join)(baseDir, `${nameWithoutExt}.spec.ts`), (0, path_1.join)(baseDir, '__tests__', `${nameWithoutExt}.ts`), (0, path_1.join)(baseDir, 'test', `${nameWithoutExt}.ts`), (0, path_1.join)(baseDir, 'tests', `${nameWithoutExt}.ts`) ]; for (const pattern of patterns) { if ((0, fs_1.existsSync)(pattern)) { return pattern; } } return null; } extractRelevantTests(testContent, sourceFile, lineNumber) { const functionNameMatch = testContent.match(new RegExp(`(test|it)\\(['"]{1,2}([^'"]{1,30})['"]{1,2}`)); if (!functionNameMatch) return null; return testContent; } filterErrorsForFile(errorOutput, tempFile) { const lines = errorOutput.split('\n'); const relevantLines = lines.filter(l => l.includes(tempFile) || l.includes('__prix')); if (relevantLines.length === 0) return null; return relevantLines.slice(0, 3).join('; '); } async commandExists(cmd) { try { await (0, utils_1.prixExecAsync)('which', [cmd], { timeout: 1000 }); return true; } catch { return false; } } async cleanup(tempFile) { try { if ((0, fs_1.existsSync)(tempFile)) { await (0, promises_1.unlink)(tempFile); } } catch { // Cleanup failures are non-fatal } } } exports.FixVerifier = FixVerifier; function createVerifier(project) { return new FixVerifier({}, project); } //# sourceMappingURL=fix-verifier.js.map