| "use strict"; |
| |
| |
| |
| |
| |
| |
| Object.defineProperty(exports, "__esModule", { value: true }); |
| exports.applyPatchAPI = void 0; |
| exports.rollbackPatchAPI = rollbackPatchAPI; |
| const ts_morph_1 = require("ts-morph"); |
| function getIndentation(line) { |
| const match = line.match(/^(\s*)/); |
| return match ? match[1] : ''; |
| } |
| function normalizeIndentation(newCode, referenceLine) { |
| const baseIndent = getIndentation(referenceLine); |
| const lines = newCode.split('\n'); |
| let minIndent = Infinity; |
| for (const line of lines) { |
| if (line.trim()) { |
| const indent = getIndentation(line).length; |
| minIndent = Math.min(minIndent, indent); |
| } |
| } |
| if (minIndent === Infinity) |
| return newCode; |
| const normalized = lines.map(line => { |
| if (!line.trim()) |
| return ''; |
| const currentIndent = getIndentation(line).length; |
| const relativeIndent = currentIndent - minIndent; |
| return baseIndent + ' '.repeat(relativeIndent) + line.trimStart(); |
| }); |
| return normalized.join('\n'); |
| } |
| |
| |
| |
| const applyPatchAPI = async (apiCheckout, request) => { |
| try { |
| console.log(`[PATCH] Applying to ${request.filename}`); |
| |
| const currentFile = await apiCheckout.getFile(request.filename); |
| const originalLines = currentFile.content.split('\n'); |
| |
| let modifiedContent; |
| if (request.functionStartLine && request.functionEndLine) { |
| |
| const startIdx = Math.max(0, request.functionStartLine - 1); |
| const endIdx = Math.min(originalLines.length, request.functionEndLine); |
| const referenceLine = originalLines[startIdx]; |
| const normalizedCode = normalizeIndentation(request.newCode, referenceLine); |
| const before = originalLines.slice(0, startIdx); |
| const after = originalLines.slice(endIdx); |
| modifiedContent = [ |
| ...before, |
| ...normalizedCode.split('\n'), |
| ...after |
| ].join('\n'); |
| } |
| else if (request.lineStart && request.lineEnd) { |
| |
| const startIdx = Math.max(0, request.lineStart - 1); |
| const endIdx = Math.min(originalLines.length, request.lineEnd); |
| |
| const originalRangeLen = endIdx - startIdx; |
| const newCodeLines = request.newCode.split('\n'); |
| let patchedNewCode = request.newCode; |
| if (originalRangeLen > 0 && newCodeLines.length > originalRangeLen * 3) { |
| console.warn(`[PATCH] LLM response (${newCodeLines.length} lines) is ${Math.round(newCodeLines.length / originalRangeLen)}x larger than target range (${originalRangeLen} lines). Attempting to extract only changed lines...`); |
| const originalRange = originalLines.slice(startIdx, endIdx); |
| const extracted = extractChangedLines(originalRange, newCodeLines); |
| if (extracted && extracted.length > 0 && extracted.length <= originalRangeLen * 2) { |
| console.log(`[PATCH] Extracted ${extracted.length} changed lines from LLM response`); |
| patchedNewCode = extracted.join('\n'); |
| } |
| else { |
| console.warn(`[PATCH] Could not extract changed lines. Proceeding with full LLM response (risks duplication).`); |
| } |
| } |
| const referenceLine = originalLines[startIdx]; |
| const normalizedCode = normalizeIndentation(patchedNewCode, referenceLine); |
| const before = originalLines.slice(0, startIdx); |
| const after = originalLines.slice(endIdx); |
| modifiedContent = [ |
| ...before, |
| ...normalizedCode.split('\n'), |
| ...after |
| ].join('\n'); |
| } |
| else { |
| throw new Error('Invalid patch request: must specify either function or line range'); |
| } |
| |
| const project = new ts_morph_1.Project({ useInMemoryFileSystem: true }); |
| const ext = request.filename.endsWith('.tsx') ? '.tsx' : '.ts'; |
| const sourceFile = project.createSourceFile(`temp${ext}`, modifiedContent); |
| |
| if (request.language === 'python') { |
| const indentationErrors = validatePythonIndentation(modifiedContent); |
| if (indentationErrors.length > 0) { |
| console.warn(`[PATCH] ⚠️ Python indentation errors detected (${indentationErrors.length}):`); |
| for (const err of indentationErrors.slice(0, 5)) { |
| console.warn(` - line ${err.line}: ${err.message}`); |
| } |
| |
| } |
| } |
| |
| |
| |
| |
| |
| const hasSyntaxErrors = false; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| let commitSha = undefined; |
| if (!request.dryRun) { |
| const commitMessage = `Fix: ${request.functionName || 'code issue'} in ${request.filename}`; |
| commitSha = await apiCheckout.updateFile(request.filename, modifiedContent, commitMessage); |
| console.log(`[PATCH] ✓ Successfully applied patch to ${request.filename}`); |
| } |
| else { |
| console.log(`[PATCH] ✓ Dry run complete for ${request.filename} (skipped commit)`); |
| } |
| return { |
| success: true, |
| originalContent: currentFile.content, |
| modifiedContent, |
| commitSha, |
| appliedStartLine: request.functionStartLine || request.lineStart, |
| appliedEndLine: request.functionEndLine || request.lineEnd, |
| |
| originalCode: currentFile.content, |
| patchedCode: modifiedContent |
| }; |
| } |
| catch (error) { |
| return { |
| success: false, |
| error: `Patch application failed: ${error}` |
| }; |
| } |
| }; |
| exports.applyPatchAPI = applyPatchAPI; |
| |
| |
| |
| async function rollbackPatchAPI(apiCheckout, filename, originalContent, commitSha) { |
| try { |
| console.log(`[ROLLBACK] Rolling back ${filename}`); |
| |
| const commitMessage = `Rollback: Restore ${filename} to previous state`; |
| const newCommitSha = await apiCheckout.updateFile(filename, originalContent, commitMessage); |
| console.log(`[ROLLBACK] ✓ Successfully rolled back ${filename}`); |
| return { |
| success: true, |
| commitSha: newCommitSha |
| }; |
| } |
| catch (error) { |
| return { |
| success: false, |
| error: `Rollback failed: ${error}` |
| }; |
| } |
| } |
| |
| |
| |
| |
| function extractChangedLines(originalRange, llmOutput) { |
| |
| const originalStr = originalRange.join('\n').trim(); |
| |
| const fullOutput = llmOutput.join('\n'); |
| |
| |
| let diffStart = -1; |
| for (let i = 0; i < Math.min(originalRange.length, llmOutput.length); i++) { |
| if (originalRange[i].trim() !== llmOutput[i].trim()) { |
| diffStart = i; |
| break; |
| } |
| } |
| if (diffStart === -1) { |
| |
| return null; |
| } |
| |
| let diffEnd = diffStart + 1; |
| for (let i = diffStart + 1; i < llmOutput.length; i++) { |
| const origIdx = i - diffStart + diffStart; |
| if (origIdx < originalRange.length && llmOutput[i].trim() === originalRange[origIdx].trim()) { |
| |
| break; |
| } |
| diffEnd = i + 1; |
| } |
| const changedLines = llmOutput.slice(diffStart, diffEnd); |
| |
| if (changedLines.length > originalRange.length * 3) { |
| return null; |
| } |
| return changedLines; |
| } |
| |
| |
| |
| |
| |
| |
| |
| function validatePythonIndentation(code) { |
| const errors = []; |
| const lines = code.split('\n'); |
| let inDedentedBlock = false; |
| let blockIndent = 0; |
| for (let i = 0; i < lines.length; i++) { |
| const line = lines[i]; |
| const trimmed = line.trim(); |
| |
| if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('"""') || trimmed.startsWith("'''")) { |
| continue; |
| } |
| const indent = line.length - line.trimStart().length; |
| |
| if (trimmed.endsWith(':') && !trimmed.startsWith('#')) { |
| blockIndent = indent; |
| inDedentedBlock = true; |
| continue; |
| } |
| |
| if (inDedentedBlock && trimmed) { |
| if (indent <= blockIndent && !trimmed.startsWith('#') && !trimmed.startsWith('"""') && !trimmed.startsWith("'''")) { |
| errors.push({ |
| line: i + 1, |
| message: `Expected indented block body after line ending with ':', found indentation ${indent} (expected > ${blockIndent})` |
| }); |
| } |
| inDedentedBlock = false; |
| } |
| |
| if (indent > 0 && indent % 4 !== 0 && indent % 2 !== 0) { |
| |
| errors.push({ |
| line: i + 1, |
| message: `Suspicious indentation: ${indent} spaces (not a multiple of 2 or 4)` |
| }); |
| } |
| } |
| return errors; |
| } |
| |