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