Spaces:
Paused
Paused
| ; | |
| 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 |