PRIX / lib /src /fix-verifier.js
Rachit-Tw's picture
Upload 169 files
9284ad7 verified
Raw
History Blame Contribute Delete
19.2 kB
"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