PRIX / lib /src /local-context.js
yamxxx1's picture
Upload 117 files
a2fe16e verified
Raw
History Blame
10 kB
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LocalContextEngine = void 0;
const fs_1 = require("fs");
const child_process_1 = require("child_process");
const inputs_1 = require("./inputs");
const review_1 = require("./review");
const confidence_1 = require("./confidence");
const p_limit_1 = __importDefault(require("p-limit"));
const web_tree_sitter_1 = __importDefault(require("web-tree-sitter"));
class LocalContextEngine {
files;
project;
changes = new Map();
constructor(files) {
this.files = files;
this.project = null;
}
async initialize() {
await this.loadChanges();
this.initializeParser();
}
async loadChanges() {
for (const file of this.files) {
if (!(0, fs_1.existsSync)(file))
continue;
try {
const newContent = (0, fs_1.readFileSync)(file, 'utf8');
let oldContent = '';
let diff = '';
try {
diff = (0, child_process_1.execSync)(`git diff HEAD -- "${file}"`, {
encoding: 'utf8',
cwd: process.cwd()
});
const parentCommit = (0, child_process_1.execSync)('git rev-parse HEAD^', {
encoding: 'utf8',
cwd: process.cwd()
}).trim();
try {
oldContent = (0, child_process_1.execSync)(`git show ${parentCommit}:${file}`, {
encoding: 'utf8',
cwd: process.cwd()
});
}
catch {
oldContent = '';
}
}
catch {
diff = '';
oldContent = '';
}
const hunks = this.parseDiff(diff);
this.changes.set(file, {
filename: file,
oldContent,
newContent,
diff,
hunks
});
}
catch (e) {
console.warn(`Failed to load ${file}: ${e}`);
}
}
}
parseDiff(diff) {
const hunks = [];
const hunkRegex = /^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@/gm;
let match;
while ((match = hunkRegex.exec(diff)) !== null) {
const oldStart = parseInt(match[1], 10);
const oldLines = parseInt(match[2] || '1', 10);
const newStart = parseInt(match[3], 10);
const newLines = parseInt(match[4] || '1', 10);
const startIdx = match.index + match[0].length;
const nextHunkIdx = diff.indexOf('@@', startIdx);
const endIdx = nextHunkIdx === -1 ? diff.length : nextHunkIdx;
const content = diff.substring(startIdx, endIdx).trim();
hunks.push({
oldStart,
oldLines,
newStart,
newLines,
content
});
}
return hunks;
}
initializeParser() {
try {
web_tree_sitter_1.default.init();
}
catch (e) {
console.warn(`Failed to initialize tree-sitter: ${e}`);
}
}
async runReview(lightBot, heavyBot, options, prompts) {
const findings = [];
const concurrencyLimit = (0, p_limit_1.default)(options.concurrencyLimit);
const reviewPromises = Array.from(this.changes.entries()).map(([filename, change]) => concurrencyLimit(async () => {
return this.reviewFile(filename, change, lightBot, heavyBot, options, prompts);
}));
const fileResults = await Promise.all(reviewPromises);
for (const result of fileResults) {
findings.push(...result);
}
return findings;
}
async reviewFile(filename, change, lightBot, heavyBot, options, prompts) {
const findings = [];
const inputs = new inputs_1.Inputs();
inputs.title = `Local review: ${filename}`;
inputs.description = '';
inputs.systemMessage = options.systemMessage;
for (const hunk of change.hunks) {
const ins = inputs.clone();
const patchContent = this.formatPatchForReview(filename, hunk, change.oldContent);
ins.fileDiff = patchContent;
ins.filename = filename;
ins.patches = hunk.content;
const prompt = prompts.renderReviewFileDiff(ins);
const [response] = await heavyBot.chat(prompt, {});
if (response && response.trim()) {
const fileFindings = this.parseFindings(response, filename, hunk.newStart, hunk.newStart + hunk.newLines);
for (const finding of fileFindings) {
if (options.meetsMinimumSeverity(finding.severity)) {
if (options.meetsConfidenceThreshold(finding.confidence)) {
findings.push(finding);
}
}
}
}
}
return findings;
}
formatPatchForReview(filename, hunk, oldContent) {
const oldLines = oldContent.split('\n');
const relevantOldLines = oldLines.slice(hunk.oldStart - 1, hunk.oldStart - 1 + hunk.oldLines);
const patchLines = hunk.content.split('\n');
const newHunkLines = patchLines.filter(l => l.startsWith('+') && !l.startsWith('+++'));
const oldHunkLines = patchLines.filter(l => l.startsWith('-') && !l.startsWith('---'));
return `## File: ${filename}
@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@
---old_hunk---
\`\`\`
${oldHunkLines.join('\n') || '(no old lines)'}
\`\`\`
---new_hunk---
\`\`\`
${newHunkLines.join('\n') || '(no new lines)'}
\`\`\`
`;
}
parseFindings(response, filename, startLine, endLine) {
const findings = [];
const lines = response.split('\n');
let currentFinding = null;
let currentMessage = '';
const lineRangeRegex = /^(\d+)-(\d+):/;
const levelTypeRegex = /^(CRITICAL|MAJOR|MINOR|INFO)\s*\|\s*(\w+)/;
const confidenceRegex = /CONFIDENCE:\s*(\d+)%?$/i;
for (const line of lines) {
const lineRangeMatch = line.match(lineRangeRegex);
if (lineRangeMatch) {
if (currentFinding && currentFinding.message) {
const calibrated = confidence_1.confidenceCalibrator.calibrate(currentFinding.confidence || 50, currentFinding.message, currentFinding.message.length, 2000, currentFinding.severity, 'local', 'local', (0, review_1.extractPatternType)(currentFinding.message), filename);
findings.push({
filename,
line: currentFinding.line || startLine,
endLine: currentFinding.endLine || endLine,
severity: currentFinding.severity || 'minor',
type: currentFinding.type || 'general',
message: currentFinding.message.trim(),
confidence: calibrated.score,
suggestion: currentFinding.suggestion,
patternType: (0, review_1.extractPatternType)(currentFinding.message)
});
}
currentFinding = {
line: parseInt(lineRangeMatch[1], 10),
endLine: parseInt(lineRangeMatch[2], 10),
severity: 'minor',
type: 'general',
confidence: 50
};
currentMessage = '';
continue;
}
const levelTypeMatch = line.match(levelTypeRegex);
if (levelTypeMatch && currentFinding) {
currentFinding.severity =
levelTypeMatch[1].toLowerCase();
currentFinding.type = levelTypeMatch[2].toLowerCase();
continue;
}
if (line.includes('```suggestion')) {
const suggestionMatch = response.match(/```suggestion\n([\s\S]*?)```/);
if (suggestionMatch && currentFinding) {
currentFinding.suggestion = suggestionMatch[1].trim();
}
}
const confidenceMatch = line.match(confidenceRegex);
if (confidenceMatch && currentFinding) {
currentFinding.confidence = parseInt(confidenceMatch[1], 10);
}
if (currentFinding) {
currentMessage += `${line}\n`;
currentFinding.message = currentMessage;
}
}
if (currentFinding && currentFinding.message) {
const calibrated = confidence_1.confidenceCalibrator.calibrate(currentFinding.confidence || 50, currentFinding.message, currentFinding.message.length, 2000, currentFinding.severity, 'local', 'local', (0, review_1.extractPatternType)(currentFinding.message), filename);
findings.push({
filename,
line: currentFinding.line || startLine,
endLine: currentFinding.endLine || endLine,
severity: currentFinding.severity || 'minor',
type: currentFinding.type || 'general',
message: currentFinding.message.trim(),
confidence: calibrated.score,
suggestion: currentFinding.suggestion,
patternType: (0, review_1.extractPatternType)(currentFinding.message)
});
}
return findings;
}
}
exports.LocalContextEngine = LocalContextEngine;
//# sourceMappingURL=local-context.js.map