"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.autonomousAudit = exports.run = exports.codeReview = exports.repo = exports.context = void 0; const ts_morph_1 = require("ts-morph"); const promises_1 = require("fs/promises"); const fs_1 = require("fs"); const path_1 = require("path"); const utils_1 = require("./utils"); const file_discoverer_1 = require("./file-discoverer"); const pr_service_1 = require("./services/pr-service"); const p_limit_1 = __importDefault(require("p-limit")); const bot_1 = require("./bot"); const commenter_1 = require("./commenter"); const inputs_1 = require("./inputs"); const options_1 = require("./options"); const octokit_1 = require("./octokit"); const tokenizer_1 = require("./tokenizer"); const context_1 = require("./context"); const symbol_graph_1 = require("./symbol-graph"); const test_generator_1 = require("./test-generator"); const token_scheduler_1 = require("./services/token-scheduler"); const patch_utils_1 = require("./utils/patch-utils"); const pino_1 = __importDefault(require("pino")); const logger = (0, pino_1.default)({ level: process.env.LOG_LEVEL || 'info' }); let error = (msg) => logger.error(msg); let info = (msg) => logger.info(msg); let warning = (msg) => logger.warn(msg); exports.context = new Proxy({}, { get(target, prop) { return (context_1.als.getStore()?.probotContext)[prop]; } }); exports.repo = new Proxy({}, { get(target, prop) { return (context_1.als.getStore()?.repo)[prop]; } }); const ignoreKeyword = '@ai-pr-reviewer: ignore'; const codeReview = async (lightBot, heavyBot, options, prompts) => { const commenter = new commenter_1.Commenter(); const project = new ts_morph_1.Project(); // Sync schedulers with their respective model limits token_scheduler_1.lightScheduler.setLimit(options.lightTokenLimits.maxTokens); token_scheduler_1.heavyScheduler.setLimit(options.heavyTokenLimits.maxTokens); // Initialize shared context engine once at the start using the cloned repo path try { const store = context_1.als.getStore(); const workingDir = store?.workingDir || process.cwd(); const repoInfo = store?.repo; const stableId = repoInfo ? `${repoInfo.owner}/${repoInfo.repo}` : undefined; await symbol_graph_1.unifiedContextEngine.initialize(workingDir, stableId); } catch (e) { info(`Context engine initialization failed: ${e}`); } const aiConcurrencyLimit = (0, p_limit_1.default)(options.concurrencyLimit); const githubConcurrencyLimit = (0, p_limit_1.default)(options.githubConcurrencyLimit); if (exports.context.name !== 'pull_request' && exports.context.name !== 'pull_request_target') { warning(`Skipped: current event is ${exports.context.name}, only support pull_request event`); return; } if (exports.context.payload.pull_request == null) { warning('Skipped: context.payload.pull_request is null'); return; } const prUser = exports.context.payload.pull_request.user; const prAuthor = prUser?.type; const branchName = exports.context.payload.pull_request.head.ref; if (prAuthor === 'Bot' || branchName.startsWith('ai-remedy/') || branchName.startsWith('github-actions[bot]')) { info(`Skipping audit: PR author type=${prAuthor}, branch=${branchName}`); return; } const inputs = new inputs_1.Inputs(); inputs.title = exports.context.payload.pull_request.title; if (exports.context.payload.pull_request.body != null) { inputs.description = commenter.getDescription(exports.context.payload.pull_request.body); } if (inputs.description.includes(ignoreKeyword)) { info('Skipped: description contains ignore_keyword'); return; } inputs.systemMessage = options.systemMessage; const existingSummarizeCmt = await commenter.findCommentWithTag(commenter_1.SUMMARIZE_TAG, exports.context.payload.pull_request.number); let existingCommitIdsBlock = ''; let existingSummarizeCmtBody = ''; if (existingSummarizeCmt != null) { existingSummarizeCmtBody = existingSummarizeCmt.body; inputs.rawSummary = commenter.getRawSummary(existingSummarizeCmtBody); inputs.shortSummary = commenter.getShortSummary(existingSummarizeCmtBody); existingCommitIdsBlock = commenter.getReviewedCommitIdsBlock(existingSummarizeCmtBody); } const allCommitIds = await commenter.getAllCommitIds(); let highestReviewedCommitId = ''; if (existingCommitIdsBlock !== '') { highestReviewedCommitId = commenter.getHighestReviewedCommitId(allCommitIds, commenter.getReviewedCommitIds(existingCommitIdsBlock)); } if (highestReviewedCommitId === '' || highestReviewedCommitId === exports.context.payload.pull_request.head.sha) { info(`Will review from the base commit: ${exports.context.payload.pull_request.base.sha}`); highestReviewedCommitId = exports.context.payload.pull_request.base.sha; } else { info(`Will review from commit: ${highestReviewedCommitId}`); } const incrementalDiff = await octokit_1.octokit.rest.repos.compareCommits({ owner: exports.repo.owner, repo: exports.repo.repo, base: highestReviewedCommitId, head: exports.context.payload.pull_request.head.sha }); const targetBranchDiff = await octokit_1.octokit.rest.repos.compareCommits({ owner: exports.repo.owner, repo: exports.repo.repo, base: exports.context.payload.pull_request.base.sha, head: exports.context.payload.pull_request.head.sha }); const incrementalFiles = incrementalDiff.data.files; const targetBranchFiles = targetBranchDiff.data.files; if (incrementalFiles == null || targetBranchFiles == null) { warning('Skipped: files data is missing'); return; } const files = targetBranchFiles.filter(targetBranchFile => incrementalFiles.some(incrementalFile => incrementalFile.filename === targetBranchFile.filename)); if (files.length === 0) { warning('Skipped: files is null'); return; } const filterSelectedFiles = []; const filterIgnoredFiles = []; for (const file of files) { if (!options.checkPath(file.filename)) { info(`skip for excluded path: ${file.filename}`); filterIgnoredFiles.push(file); } else { filterSelectedFiles.push(file); } } if (filterSelectedFiles.length === 0) { warning('Skipped: filterSelectedFiles is null'); return; } const commits = incrementalDiff.data.commits; if (commits.length === 0) { warning('Skipped: commits is null'); return; } const filteredFiles = await Promise.all(filterSelectedFiles.map(file => githubConcurrencyLimit(async () => { let fileContent = ''; if (exports.context.payload.pull_request == null) { warning('Skipped: context.payload.pull_request is null'); return null; } try { const store = context_1.als.getStore(); const workingDir = store?.workingDir || process.cwd(); const localPath = (0, path_1.join)(workingDir, file.filename); if ((0, fs_1.existsSync)(localPath)) { fileContent = await (0, promises_1.readFile)(localPath, 'utf8'); } else { const contents = await octokit_1.octokit.rest.repos.getContent({ owner: exports.repo.owner, repo: exports.repo.repo, path: file.filename, ref: exports.context.payload.pull_request.base.sha }); if (contents.data != null && !Array.isArray(contents.data)) { if (contents.data.type === 'file' && contents.data.content != null) { fileContent = Buffer.from(contents.data.content, 'base64').toString(); } } } } catch (e) { warning(`Failed to get file contents: ${e}. This is OK if it's a new file.`); } let fileDiff = ''; if (file.patch != null) { fileDiff = file.patch; } const patches = []; for (const patch of (0, patch_utils_1.splitPatch)(file.patch)) { const patchLines = (0, patch_utils_1.patchStartEndLine)(patch); if (patchLines == null) { continue; } const hunks = (0, patch_utils_1.parsePatch)(patch); if (hunks == null) { continue; } const hunksStr = ` ---new_hunk--- \`\`\` ${hunks.newHunk} \`\`\` ---old_hunk--- \`\`\` ${hunks.oldHunk} \`\`\` `; patches.push([ patchLines.newHunk.startLine, patchLines.newHunk.endLine, hunksStr ]); } if (patches.length > 0) { return [file.filename, fileContent, fileDiff, patches]; } else { return null; } }))); const filesAndChanges = filteredFiles.filter(file => file !== null); // Sort files by diff size (token count) so small files are processed first // This prevents massive files from starving the token budget for small changes filesAndChanges.sort((a, b) => (0, tokenizer_1.getTokenCount)(a[2]) - (0, tokenizer_1.getTokenCount)(b[2])); // HARD LIMIT: Max 100 files to prevent memory exhaustion const MAX_FILES_LIMIT = 100; const originalCount = filesAndChanges.length; if (filesAndChanges.length > MAX_FILES_LIMIT) { filesAndChanges.length = MAX_FILES_LIMIT; warning(`Truncated from ${originalCount} to ${MAX_FILES_LIMIT} files to prevent memory exhaustion. ` + `Consider breaking this PR into smaller chunks.`); } if (filesAndChanges.length === 0) { error('Skipped: no files to review'); return; } let statusMsg = `
Commits Files that changed from the base of the PR and between ${highestReviewedCommitId} and ${exports.context.payload.pull_request.head.sha} commits.
${filesAndChanges.length > 0 ? `
Files selected (${filesAndChanges.length}) * ${filesAndChanges .map(([filename, , , patches]) => `${filename} (${patches.length})`) .join('\n* ')}
` : ''} ${filterIgnoredFiles.length > 0 ? `
Files ignored due to filter (${filterIgnoredFiles.length}) * ${filterIgnoredFiles.map(file => file.filename).join('\n* ')}
` : ''} `; const summariesFailed = []; const doSummary = async (filename, fileContent, fileDiff) => { info(`summarize: ${filename}`); const ins = inputs.clone(); if (fileDiff.length === 0) { warning(`summarize: file_diff is empty, skip ${filename}`); summariesFailed.push(`${filename} (empty diff)`); return null; } ins.filename = filename; ins.fileDiff = fileDiff; const summarizePrompt = prompts.renderSummarizeFileDiff(ins, options.reviewSimpleChanges); try { const isDocumentationOnly = (0, patch_utils_1.checkIfDocumentationOnly)(fileDiff); if (isDocumentationOnly && options.reviewSimpleChanges === false) { info(`summarize: skipping review for documentation-only change: ${filename}`); return [filename, 'Documentation/Comment changes only.', false]; } const promptTokens = (0, tokenizer_1.getTokenCount)(summarizePrompt); if (promptTokens > options.lightTokenLimits.requestTokens) { warning(`summarize: skipping ${filename} as it exceeds token limit (${promptTokens} > ${options.lightTokenLimits.requestTokens})`); return [ filename, 'File diff is too large for AI summarization. Please review manually.', false ]; } await token_scheduler_1.lightScheduler.wait(promptTokens); const [summarizeResp] = await lightBot.chat(summarizePrompt, {}); if (summarizeResp === '') { info('summarize: nothing obtained from AI'); summariesFailed.push(`${filename} (nothing obtained from AI)`); return null; } else { if (options.reviewSimpleChanges === false) { const triageRegex = /\[TRIAGE\]:\s*(NEEDS_REVIEW|APPROVED)/; const triageMatch = summarizeResp.match(triageRegex); if (triageMatch != null) { const triage = triageMatch[1]; const needsReview = triage === 'NEEDS_REVIEW'; const summary = summarizeResp.replace(triageRegex, '').trim(); info(`filename: ${filename}, triage: ${triage}`); return [filename, summary, needsReview]; } } return [filename, summarizeResp, true]; } } catch (e) { warning(`summarize: error from AI: ${e}`); summariesFailed.push(`${filename} (error from AI: ${e})})`); return null; } }; const summaryPromises = []; const skippedFiles = []; for (const [filename, fileContent, fileDiff] of filesAndChanges) { if (options.maxFiles <= 0 || summaryPromises.length < options.maxFiles) { summaryPromises.push(aiConcurrencyLimit(async () => await doSummary(filename, fileContent, fileDiff))); } else { skippedFiles.push(filename); } } const summaries = []; for (const promise of summaryPromises) { const result = await promise; if (result) summaries.push(result); } if (summaries.length > 0) { const batchSize = 10; for (let i = 0; i < summaries.length; i += batchSize) { const summariesBatch = summaries.slice(i, i + batchSize); for (const [filename, summary] of summariesBatch) { inputs.rawSummary += `--- ${filename}: ${summary} `; } const changesetPrompt = prompts.renderSummarizeChangesets(inputs); await token_scheduler_1.lightScheduler.wait((0, tokenizer_1.getTokenCount)(changesetPrompt)); const [summarizeResp] = await lightBot.chat(changesetPrompt, {}); if (summarizeResp === '') { warning('summarize: nothing obtained from AI'); } else { inputs.rawSummary = summarizeResp; } } } const impactMap = await generateImpactMap(filesAndChanges, project); inputs.description += `\n\n### Downstream Impact Analysis\n${impactMap}`; const finalSummarizePrompt = prompts.renderSummarize(inputs); await token_scheduler_1.heavyScheduler.wait((0, tokenizer_1.getTokenCount)(finalSummarizePrompt)); const [summarizeFinalResponse] = await heavyBot.chat(finalSummarizePrompt, {}); const shortSummarizePrompt = prompts.renderSummarizeShort(inputs); await token_scheduler_1.heavyScheduler.wait((0, tokenizer_1.getTokenCount)(shortSummarizePrompt)); const [summarizeShortResponse] = await heavyBot.chat(shortSummarizePrompt, {}); inputs.shortSummary = summarizeShortResponse; const verifiedSuggestions = []; const reviewsFailed = []; let lgtmCount = 0; let reviewCount = 0; const severityCounts = { critical: 0, major: 0, minor: 0, info: 0 }; const confidenceSum = { total: 0, count: 0 }; async function processReviewFinding(review, filename, ins, patches, project, verifiedSuggestions, severityCounts, confidenceStats) { let currentRemedy = review.remedy; if (currentRemedy) { let agentRetries = 0; const maxAgentRetries = 3; let isVerified = false; let lastError = ''; while (agentRetries < maxAgentRetries && !isVerified) { const feedback = await runCIFeedback(filename, currentRemedy, review.startLine, review.endLine); if (feedback === '' || !feedback.includes('❌')) { const astValid = (0, patch_utils_1.validateRemedyAST)(filename, currentRemedy, project); if (astValid) { isVerified = true; review.remedy = currentRemedy; } else { lastError = '❌ Hallucination Detected: Remedy refers to undefined local symbols.'; } } else { lastError = feedback; } if (!isVerified && agentRetries < maxAgentRetries - 1) { agentRetries++; const retryPrompt = prompts.renderContextAwareFixSuggestion(ins, currentRemedy, lastError); await token_scheduler_1.heavyScheduler.wait((0, tokenizer_1.getTokenCount)(retryPrompt)); const [retryResponse] = await heavyBot.chat(retryPrompt, {}); const { remedy: newRemedy } = (0, patch_utils_1.parseReview)(retryResponse, patches, filename)[0] || {}; if (newRemedy) currentRemedy = newRemedy; else break; } else break; } if (!isVerified) { delete review.remedy; review.verified = false; review.verificationFeedback = lastError; } else { ; review.verified = true; review.verificationFeedback = 'All verification checks passed'; } const finalRemedy = review.remedy; if (finalRemedy) { const hasCollision = verifiedSuggestions.some(s => s.filename === filename && ((review.startLine >= s.startLine && review.startLine <= s.endLine) || (review.endLine >= s.startLine && review.endLine <= s.endLine))); if (!hasCollision) { // Generate test case for verified remedy let testCase; if (isVerified && review.comment) { try { const testResult = await test_generator_1.testGenerator.generateTestCase(filename, review.comment, finalRemedy, heavyBot); if (testResult) { testCase = test_generator_1.testGenerator.formatTestSuggestion(testResult); } } catch (e) { info(`Test generation skipped: ${e}`); } } const entry = { filename, startLine: review.startLine, endLine: review.endLine, suggestion: finalRemedy, verified: isVerified, verificationFeedback: isVerified ? 'All verification checks passed' : lastError, testCase }; verifiedSuggestions.push(entry); } } } if (!options.reviewCommentLGTM && (review.comment.includes('LGTM') || review.comment.includes('looks good to me'))) { lgtmCount++; return; } if (review.severity) severityCounts[review.severity]++; if (review.confidence !== undefined) { confidenceStats.total += review.confidence; confidenceStats.count++; } reviewCount++; // Find test case if this review has a verified remedy const verifiedEntry = review.remedy ? verifiedSuggestions.find(s => s.filename === filename && s.startLine === review.startLine && s.endLine === review.endLine) : null; await commenter.bufferReviewComment(filename, review.startLine, review.endLine, review.comment, review.verified, review.verificationFeedback, verifiedEntry?.testCase); } const doReview = async (filename, fileContent, patches, project) => { const ins = new inputs_1.Inputs(); ins.title = inputs.title; ins.systemMessage = inputs.systemMessage; ins.shortSummary = inputs.shortSummary; ins.filename = filename; const usages = await findUsages(filename, fileContent, project); if (usages !== '') ins.description += `\n\n### Usage Context\n\`\`\`text\n${usages}\n\`\`\``; try { const lineContext = patches.length > 0 ? patches[0][0] : 1; const codeSnippet = patches.length > 0 ? patches[0][2] : ''; ins.remedyContext = symbol_graph_1.unifiedContextEngine.getRemedyContext(filename, lineContext, codeSnippet, 20); } catch (e) { ins.remedyContext = ''; } // Hunk-by-hunk / Sub-batching logic for large files let currentPatches = ''; let currentPatchesCount = 0; const basePrompt = prompts.renderReviewFileDiff(ins); const baseTokens = (0, tokenizer_1.getTokenCount)(basePrompt); for (let i = 0; i < patches.length; i++) { const [, , patch] = patches[i]; const patchTokens = (0, tokenizer_1.getTokenCount)(patch); const isLastPatch = i === patches.length - 1; // If a single patch is so large it exceeds the entire limit even by itself if (baseTokens + patchTokens > options.heavyTokenLimits.requestTokens) { warning(`review: hunk in ${filename} is too large (${patchTokens} tokens). Skipping this hunk.`); await commenter.bufferReviewComment(filename, patches[i][0], patches[i][1], '⚠️ **Hunk Too Large**: This specific change block is too large for the AI model. Please review this section manually.', false, 'Patch limit exceeded'); continue; } // If adding this patch exceeds the batch limit, process the current batch first if (currentPatches !== '' && baseTokens + (0, tokenizer_1.getTokenCount)(currentPatches + patch) > options.heavyTokenLimits.requestTokens) { await processBatch(currentPatches); currentPatches = ''; currentPatchesCount = 0; } currentPatches += `${patch}\n---\n`; currentPatchesCount++; // If it's the last patch, process whatever is left if (isLastPatch && currentPatches !== '') { await processBatch(currentPatches); } } async function processBatch(batchPatches) { ins.patches = batchPatches; const prompt = prompts.renderReviewFileDiff(ins); const totalTokens = (0, tokenizer_1.getTokenCount)(prompt); info(`reviewing ${filename} (sub-batch with ${currentPatchesCount} hunks, ${totalTokens} tokens)`); await token_scheduler_1.heavyScheduler.wait(totalTokens); try { const [response] = await heavyBot.chat(prompt, {}); const reviews = (0, patch_utils_1.parseReview)(response, patches, filename, options.debug); for (const review of reviews) { await processReviewFinding(review, filename, ins, patches, project, verifiedSuggestions, severityCounts, confidenceSum); } } catch (e) { error(`review: sub-batch failed for ${filename}: ${e.message}`); reviewsFailed.push(`${filename} (sub-batch error: ${e.message})`); } } }; const doBatchReview = async (batch, project) => { const ins = new inputs_1.Inputs(); ins.title = inputs.title; ins.systemMessage = inputs.systemMessage; ins.shortSummary = inputs.shortSummary; let batchContent = ''; for (const [filename, , patches] of batch) { batchContent += `### File: ${filename}\n`; for (const [, , patch] of patches) { batchContent += `${patch}\n`; } batchContent += `\n---\n`; } ins.batchContent = batchContent; const prompt = prompts.renderReviewFileBatch(ins); await token_scheduler_1.heavyScheduler.wait((0, tokenizer_1.getTokenCount)(prompt)); try { const [response] = await heavyBot.chat(prompt, {}); const fileResponses = response .split(/### File: /) .filter(s => s.trim() !== ''); for (const fileRes of fileResponses) { const lines = fileRes.split('\n'); const filename = lines[0].trim(); const content = fileRes.substring(fileRes.indexOf('\n') + 1); const item = batch.find(([f]) => f === filename); if (item) { const reviews = (0, patch_utils_1.parseReview)(content, item[2], filename, options.debug); for (const review of reviews) { await processReviewFinding(review, filename, ins, item[2], project, verifiedSuggestions, severityCounts, confidenceSum); } } } } catch (e) { warning(`Batch review failed: ${e.message}`); } }; if (!options.disableReview) { const filesAndChangesReview = filesAndChanges.filter(([filename]) => { return (summaries.find(([summaryFilename]) => summaryFilename === filename)?.[2] ?? true); }); const reviewBatches = []; let currentBatch = []; let currentBatchTokens = 0; const BATCH_LIMIT = 5000; for (const [filename, fileContent, , patches] of filesAndChangesReview) { // Logic for cost estimation: System message + PR info + Diff patches + 1k margin for context const diffStr = patches.map(([, , p]) => p).join('\n'); const fileTokens = (0, tokenizer_1.getTokenCount)(diffStr) + 1000; // 1000 for surrounding prompt/context if (fileTokens > BATCH_LIMIT || currentBatchTokens + fileTokens > BATCH_LIMIT) { if (currentBatch.length > 0) reviewBatches.push(currentBatch); currentBatch = [[filename, fileContent, patches]]; currentBatchTokens = fileTokens; } else { currentBatch.push([filename, fileContent, patches]); currentBatchTokens += fileTokens; } } if (currentBatch.length > 0) reviewBatches.push(currentBatch); for (const batch of reviewBatches) { if (batch.length === 1) { await doReview(batch[0][0], batch[0][1], batch[0][2], project); } else { await doBatchReview(batch, project); } } const commits = await commenter.getAllCommitIds(); // Build beautiful severity summary const totalIssues = severityCounts.critical + severityCounts.major + severityCounts.minor + severityCounts.info; const actionableCount = severityCounts.critical + severityCounts.major; const hasIssues = totalIssues > 0; // Status emoji based on issues found let statusEmoji = '✅'; let statusText = 'All Clear'; if (severityCounts.critical > 0) { statusEmoji = '🚨'; statusText = 'Critical Issues Found'; } else if (severityCounts.major > 0) { statusEmoji = '⚠️'; statusText = 'Issues Found'; } else if (severityCounts.minor > 0 || severityCounts.info > 0) { statusEmoji = '💡'; statusText = 'Suggestions Available'; } // CodeRabbit-style top summary let statusMsg = `## 📝 Actionable Comments **Actionable comments posted**: ${actionableCount} ${actionableCount > 0 ? `
🔧 Fix all issues with AI Agents Each critical/major issue below includes a "Prompt for AI Agents" section. Copy-paste those prompts into Cursor, Windsurf, or any AI IDE to auto-fix the issues.
--- ` : '---\n\n'} ${prompts.renderSummarizeShort(inputs)} ${prompts.renderSummarizeReleaseNotes(inputs)} --- ## ${statusEmoji} PRIX Review Summary > **Status**: ${statusText} > **Total Findings**: ${totalIssues} issue${totalIssues !== 1 ? 's' : ''} ### Issue Breakdown | Severity | Count | Indicator | |:---------|:-----:|:----------| | 🚨 Critical | **${severityCounts.critical}** | ${severityCounts.critical > 0 ? '🔴 Attention Required' : '✓ None'} | | 🔴 Major | **${severityCounts.major}** | ${severityCounts.major > 0 ? '⚠️ Review Recommended' : '✓ None'} | | 🟠 Minor | **${severityCounts.minor}** | ${severityCounts.minor > 0 ? '💡 Suggestions' : '✓ None'} | | ℹ️ Info | **${severityCounts.info}** | ${severityCounts.info > 0 ? '📝 Notes' : '✓ None'} | `; // Add confidence score if we have reviews if (confidenceSum.count > 0) { const avgConfidence = Math.round(confidenceSum.total / confidenceSum.count); statusMsg += ` ### Review Quality - **Average Confidence**: ${avgConfidence}% - **Files Reviewed**: ${reviewCount} `; } // Add legend for quick understanding if (hasIssues) { statusMsg += ` ---
📖 Severity Legend - **Critical**: Security vulnerabilities, data loss risks, or crash-causing bugs - **Major**: Performance issues, significant bugs, or maintainability problems - **Minor**: Code style, minor optimizations, or documentation improvements - **Info**: General observations and notes
`; } if (options.createRemedyPR && verifiedSuggestions.length > 0) { await createRemedyPR(verifiedSuggestions, options); } await commenter.submitReview(exports.context.payload.pull_request.number, commits[commits.length - 1], statusMsg); } }; exports.codeReview = codeReview; async function generateImpactMap(files, project) { const filenames = files.map(([f]) => f); return symbol_graph_1.unifiedContextEngine.generateVisualImpactMap(filenames); } async function findUsages(f, c, p) { return ""; } async function runCIFeedback(f, r, s, e) { return ""; } async function createRemedyPR(verifiedSuggestions, options) { if (verifiedSuggestions.length === 0) return; const ctx = context_1.als.getStore(); if (!ctx) return; const pullRequest = ctx.probotContext.payload.pull_request; const owner = ctx.repo.owner; const repo = ctx.repo.repo; const headRef = pullRequest.head.ref; const pullNumber = pullRequest.number; const remedyBranch = `prix-remedy-pr-${pullNumber}-${Date.now()}`; info(`🚀 [RemedyEngine] Generating auto-fix branch: ${remedyBranch}`); try { const workingDir = ctx.workingDir || process.cwd(); // 1. Create a isolated branch from the current head (0, utils_1.prixExec)(`git checkout -b ${remedyBranch}`, { cwd: workingDir }); // 2. Apply verified fixes one by one let appliedCount = 0; for (const fix of verifiedSuggestions) { const filePath = (0, path_1.join)(workingDir, fix.filename); if ((0, fs_1.existsSync)(filePath)) { const content = (0, fs_1.readFileSync)(filePath, 'utf8').split('\n'); const startLine = fix.startLine; const endLine = fix.endLine; const suggestion = fix.suggestion; // Replace the lines (1-indexed adjust) content.splice(startLine - 1, endLine - startLine + 1, suggestion); (0, fs_1.writeFileSync)(filePath, content.join('\n'), 'utf8'); appliedCount++; } } if (appliedCount === 0) return; // 3. Commit and Push (0, utils_1.prixExec)(`git add .`, { cwd: workingDir }); (0, utils_1.prixExec)(`git commit -m "fix(remedy): automated audit fix by PRIX for #${pullNumber}"`, { cwd: workingDir }); (0, utils_1.prixExec)(`git push origin ${remedyBranch}`, { cwd: workingDir }); // 4. Use GitHub API to create the PR const prResponse = await octokit_1.octokit.rest.pulls.create({ owner, repo, title: `PRIX Remedies for PR #${pullNumber}`, body: `👋 This automated Pull Request corrects identified bugs from the PRIX audit of #${pullNumber}. ### Verified Fixes Applied: ${verifiedSuggestions .map(f => `- **${f.filename}** (L${f.startLine}-${f.endLine})`) .join('\n')} *Verified by Syntax & AST Validation.*`, head: remedyBranch, base: headRef }); info(`✅ [RemedyEngine] Created Remedy PR: ${prResponse.data.html_url}`); // 5. Post acknowledgement in the original PR await octokit_1.octokit.rest.issues.createComment({ owner, repo, // eslint-disable-next-line camelcase issue_number: pullNumber, body: `🚨 **PRIX identified high-confidence bugfixes.** I have created a secondary Pull Request with ${appliedCount} suggested remedies: ${prResponse.data.html_url}` }); } catch (e) { error(`❌ [RemedyEngine] Failed to create Remedy PR: ${e.message}`); } } const run = async (probotContext, options, prompts) => { info = (msg) => probotContext.log.info(msg); warning = (msg) => probotContext.log.warn(msg); error = (msg) => probotContext.log.error(msg); (0, commenter_1.setCommenterContext)(probotContext); (0, octokit_1.setOctokit)(probotContext.octokit); const lb = new bot_1.Bot(options, new options_1.AIOptions(options.lightModel)); const hb = new bot_1.Bot(options, new options_1.AIOptions(options.heavyModel)); await (0, exports.codeReview)(lb, hb, options, prompts); }; exports.run = run; const autonomousAudit = async (probotContext, options, prompts) => { info = (msg) => probotContext.log.info(msg); warning = (msg) => probotContext.log.warn(msg); error = (msg) => probotContext.log.error(msg); (0, octokit_1.setOctokit)(probotContext.octokit); info('Starting autonomous audit...'); const store = context_1.als.getStore(); const workingDir = store?.workingDir || process.cwd(); // 1. Discover files const files = (0, file_discoverer_1.discoverFiles)(workingDir, { exclude: ['node_modules', '.git', 'dist', 'build'] }); // We could implement an AI review for all files, but to keep it simple and focused: // For the autonomous audit, we will select some files that might be problematic, // or we could review everything. For now, we simulate finding an issue or we just run test files. const verifiedSuggestions = []; // Example dummy logic: iterate over discovered files, maybe we run some simple regex to find easy bugs. // Real implementation would invoke heavyBot for dense scanning. if (options.enableAutoPR) { info(`enableAutoPR is true. Handling verified suggestions...`); if (verifiedSuggestions.length > 0) { const prService = new pr_service_1.PRService(workingDir); const branchName = `prix-auto-fix-${Date.now()}`; await prService.createFixBranch(branchName); let appliedCount = 0; for (const suggestion of verifiedSuggestions) { try { await prService.applyFix(suggestion.filename, suggestion.suggestion, suggestion.startLine, suggestion.endLine); await prService.commitAndPush(branchName, `fix: apply PRIX AI suggestion for ${suggestion.filename}`, suggestion.filename); appliedCount++; } catch (e) { warning(`Failed to apply fix for ${suggestion.filename}: ${e.message}`); } } if (appliedCount > 0) { await prService.submitPR(branchName, `🔧 PRIX Autonomous Fixes (${appliedCount})`, `This PR was automatically generated by PRIX autonomous auditor.`); } } else { info('No verified suggestions found during autonomous audit.'); } } else { info('Auto PR is disabled, not generating PRs.'); } }; exports.autonomousAudit = autonomousAudit; //# sourceMappingURL=review.js.map