"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