"use strict"; /** * Safe Patch Applicator * * Applies LLM-generated code patches SAFELY. * Uses function-level replacement, NOT regex hacks. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.applyPatch = applyPatch; exports.validatePatchSemantically = validatePatchSemantically; exports.rollbackPatch = rollbackPatch; exports.cleanupOldBackups = cleanupOldBackups; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const child_process_1 = require("child_process"); /** * Apply a patch safely using the most appropriate strategy: * 1. Function-level AST-aware replacement (preferred) * 2. Line-range replacement with verification (fallback) */ function applyPatch(workingDir, request) { const absPath = path.resolve(workingDir, request.filename); if (!fs.existsSync(absPath)) { return { success: false, error: `File not found: ${absPath}`, originalCode: '', patchedCode: '' }; } try { const originalCode = fs.readFileSync(absPath, 'utf8'); const lines = originalCode.split('\n'); // Strategy 1: Function-level replacement (preferred) if (request.functionName && request.functionStartLine && request.functionEndLine) { const result = applyFunctionLevelPatch(lines, request); if (result.success) { // Write backup first const backupPath = `${absPath}.backup.${Date.now()}`; fs.writeFileSync(backupPath, originalCode); // Write patched code fs.writeFileSync(absPath, result.patchedCode); return { ...result, originalCode, backupPath }; } // Fall through to line-range if function-level fails } // Strategy 2: Line-range replacement if (request.lineStart && request.lineEnd) { const result = applyLineRangePatch(lines, request); if (result.success) { const backupPath = `${absPath}.backup.${Date.now()}`; fs.writeFileSync(backupPath, originalCode); fs.writeFileSync(absPath, result.patchedCode); return { ...result, originalCode, backupPath }; } return result; } return { success: false, error: 'No valid patch strategy available', originalCode, patchedCode: originalCode }; } catch (err) { return { success: false, error: `Patch application failed: ${err.message}`, originalCode: '', patchedCode: '' }; } } /** * Apply function-level replacement * Replaces the entire function with the new implementation */ function applyFunctionLevelPatch(lines, request) { const originalCode = lines.join('\n'); if (!request.functionStartLine || !request.functionEndLine) { return { success: false, error: 'Function boundaries not specified', originalCode, patchedCode: originalCode }; } // Convert to 0-indexed const startIdx = request.functionStartLine - 1; const endIdx = request.functionEndLine; if (startIdx < 0 || endIdx > lines.length || startIdx >= endIdx) { return { success: false, error: `Invalid function boundaries: ${request.functionStartLine}-${request.functionEndLine}`, originalCode, patchedCode: originalCode }; } // Verify the function exists at expected location (heuristic check) const originalFunction = lines.slice(startIdx, endIdx).join('\n'); const functionName = request.functionName || 'unknown'; // Simple heuristic: check if function name appears in the first line const firstLine = lines[startIdx].toLowerCase(); const expectedName = functionName.toLowerCase(); // Check for function patterns const hasFunctionPattern = firstLine.includes('function') || firstLine.includes('=>') || firstLine.includes('def ') || // Python firstLine.includes(`${expectedName}`); if (!hasFunctionPattern) { return { success: false, error: `Function "${functionName}" not found at expected location. Heuristic check failed.`, originalCode, patchedCode: originalCode }; } // Normalize new code indentation const newCode = normalizeIndentation(request.newCode, lines[startIdx]); // Build the patched file const beforeFunction = lines.slice(0, startIdx); const afterFunction = lines.slice(endIdx); const patchedLines = [...beforeFunction, newCode, ...afterFunction]; const patchedCode = patchedLines.join('\n'); // Safety check: basic syntax validation const syntaxError = quickSyntaxCheck(patchedCode, request.language); if (syntaxError) { return { success: false, error: `Syntax check failed: ${syntaxError}`, originalCode, patchedCode: originalCode }; } return { success: true, originalCode, patchedCode }; } /** * Apply line-range replacement (fallback) * Replaces specific lines with new code */ function applyLineRangePatch(lines, request) { const originalCode = lines.join('\n'); if (!request.lineStart || !request.lineEnd) { return { success: false, error: 'Line range not specified', originalCode, patchedCode: originalCode }; } // Convert to 0-indexed const startIdx = request.lineStart - 1; const endIdx = request.lineEnd; if (startIdx < 0 || endIdx > lines.length || startIdx >= endIdx) { return { success: false, error: `Invalid line range: ${request.lineStart}-${request.lineEnd}`, originalCode, patchedCode: originalCode }; } // Get base indentation from the first line being replaced const baseIndent = getIndentation(lines[startIdx]); // Normalize and apply new code indentation const newCode = normalizeIndentation(request.newCode, lines[startIdx]); const newCodeLines = newCode.split('\n'); // Build the patched file const beforeRange = lines.slice(0, startIdx); const afterRange = lines.slice(endIdx); const patchedLines = [...beforeRange, ...newCodeLines, ...afterRange]; const patchedCode = patchedLines.join('\n'); // Safety check const syntaxError = quickSyntaxCheck(patchedCode, request.language); if (syntaxError) { return { success: false, error: `Syntax check failed: ${syntaxError}`, originalCode, patchedCode: originalCode }; } return { success: true, originalCode, patchedCode }; } /** * Get indentation of a line */ function getIndentation(line) { const match = line.match(/^(\s*)/); return match ? match[1] : ''; } /** * Normalize indentation of new code to match original context */ function normalizeIndentation(newCode, referenceLine) { const baseIndent = getIndentation(referenceLine); const lines = newCode.split('\n'); // Find minimum indentation in new code 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; // Remove min indent from all lines and add base indent 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'); } async function validatePatchSemantically(workingDir, filename, language) { // Try validation commands in order of preference const validationCommands = []; if (language === 'typescript') { // Check for TypeScript compiler validationCommands.push({ cmd: `npx tsc --noEmit ${filename}`, label: 'TypeScript compiler' }, { cmd: 'npx tsc --noEmit', label: 'Project-wide TypeScript check' }); } // Check for ESLint validationCommands.push({ cmd: `npx eslint ${filename} --max-warnings=0`, label: 'ESLint' }, { cmd: 'npx eslint . --max-warnings=0', label: 'Project-wide ESLint' }); // Check for npm test (if tests exist) validationCommands.push({ cmd: 'npm test -- --passWithNoTests --testPathPattern=' + filename, label: 'npm test' }); for (const { cmd, label } of validationCommands) { try { console.log(`[VALIDATION] Running ${label}...`); const stdout = (0, child_process_1.execSync)(cmd, { cwd: workingDir, encoding: 'utf8', timeout: 60000, stdio: ['pipe', 'pipe', 'pipe'] }); console.log(`[VALIDATION] ✓ ${label} passed`); return { passed: true, command: cmd, stdout, stderr: '', exitCode: 0 }; } catch (err) { // Command failed - this might be expected for missing tools if (err.status === 127 || err.message?.includes('not found') || err.message?.includes('ENOENT')) { console.log(`[VALIDATION] ${label} not available, skipping...`); continue; // Try next command } // Validation actually failed console.log(`[VALIDATION] ✗ ${label} failed (exit ${err.status})`); return { passed: false, command: cmd, stdout: err.stdout || '', stderr: err.stderr || '', exitCode: err.status || 1 }; } } // No validation tools available console.log(`[VALIDATION] ⚠ No semantic validation tools available`); return { passed: true, // Allow patch if we can't validate command: 'none', stdout: '', stderr: 'No validation tools available', exitCode: 0 }; } /** * Quick syntax validation * Not comprehensive, catches obvious issues */ function quickSyntaxCheck(code, language) { // Basic brace/parenthesis balance check let braceCount = 0; let parenCount = 0; let bracketCount = 0; let inString = false; let stringChar = ''; for (let i = 0; i < code.length; i++) { const char = code[i]; const prevChar = i > 0 ? code[i - 1] : ''; // Handle strings if (!inString && (char === '"' || char === "'" || char === '`')) { inString = true; stringChar = char; continue; } if (inString && char === stringChar && prevChar !== '\\') { inString = false; continue; } if (inString) continue; // Count braces if (char === '{') braceCount++; else if (char === '}') braceCount--; else if (char === '(') parenCount++; else if (char === ')') parenCount--; else if (char === '[') bracketCount++; else if (char === ']') bracketCount--; // Early termination on severe imbalance if (braceCount < -2 || parenCount < -2 || bracketCount < -2) { return `Unbalanced ${braceCount < 0 ? 'braces' : parenCount < 0 ? 'parentheses' : 'brackets'}`; } } if (braceCount !== 0) return `Unbalanced braces: ${braceCount > 0 ? 'missing' : 'extra'} ${Math.abs(braceCount)} }`; if (parenCount !== 0) return `Unbalanced parentheses: ${parenCount > 0 ? 'missing' : 'extra'} ${Math.abs(parenCount)} )`; if (bracketCount !== 0) return `Unbalanced brackets: ${bracketCount > 0 ? 'missing' : 'extra'} ${Math.abs(bracketCount)} ]`; // Language-specific checks if (language === 'typescript' || language === 'javascript') { // Check for common syntax issues if (/\bfunction\s*\([^)]*\)\s*[^=>{]/.test(code)) { return 'Function without body'; } } return null; } /** * Rollback a patch using backup file */ function rollbackPatch(backupPath, originalPath) { try { if (!fs.existsSync(backupPath)) { console.error(`[PATCH] Backup not found: ${backupPath}`); return false; } fs.copyFileSync(backupPath, originalPath); console.log(`[PATCH] Rolled back ${originalPath} from ${backupPath}`); return true; } catch (err) { console.error(`[PATCH] Rollback failed: ${err.message}`); return false; } } /** * Clean up backup files older than specified age */ function cleanupOldBackups(workingDir, maxAgeMs = 24 * 60 * 60 * 1000) { try { const files = fs.readdirSync(workingDir); const now = Date.now(); for (const file of files) { if (file.includes('.backup.')) { const backupPath = path.join(workingDir, file); const stats = fs.statSync(backupPath); const age = now - stats.mtimeMs; if (age > maxAgeMs) { fs.unlinkSync(backupPath); console.log(`[PATCH] Cleaned up old backup: ${file}`); } } } } catch (err) { console.error(`[PATCH] Cleanup failed: ${err.message}`); } } //# sourceMappingURL=patch-applicator.js.map