"use strict"; /** * API-based Context Extractor for AI Autofix * * Uses GitHub Contents API instead of local filesystem. * No git clone required - faster, cheaper, more reliable. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.extractIssueContextAPI = extractIssueContextAPI; exports.formatContextForLLMAPI = formatContextForLLMAPI; const ts_morph_1 = require("ts-morph"); // Configuration for context window sizes const CONTEXT_CONFIG = { // Lines before/after the issue SURROUNDING_LINES: 40, // Max lines for containing function (if we can identify it) MAX_FUNCTION_LINES: 200, // Max total context size (approximate token limit) MAX_CONTEXT_CHARS: 4000 }; /** * Extract localized context for an issue using GitHub API * Returns minimal but sufficient context for LLM to generate a fix */ async function extractIssueContextAPI(apiCheckout, location) { try { // Get file content via API const file = await apiCheckout.getFile(location.filename); const lines = file.content.split('\n'); // Detect language from file extension const language = location.filename.endsWith('.ts') ? 'typescript' : location.filename.endsWith('.js') ? 'javascript' : location.filename.endsWith('.py') ? 'python' : 'unknown'; // Extract target lines const startIdx = Math.max(0, location.startLine - 1); const endIdx = Math.min(lines.length, location.endLine); const targetLines = lines.slice(startIdx, endIdx).join('\n'); // Extract surrounding context const contextStart = Math.max(0, location.startLine - CONTEXT_CONFIG.SURROUNDING_LINES - 1); const contextEnd = Math.min(lines.length, location.endLine + CONTEXT_CONFIG.SURROUNDING_LINES); const surroundingLines = lines.slice(contextStart, contextEnd); const surroundingContext = surroundingLines.join('\n'); // Extract imports (first 50 lines) const importLines = []; for (let i = 0; i < Math.min(50, lines.length); i++) { const line = lines[i].trim(); if (line.startsWith('import ') || line.startsWith('from ') || line.startsWith('require(')) { importLines.push(lines[i]); } } // Try to find containing function for context-aware autofix let containingFunction = undefined; if (language === 'typescript' || language === 'javascript') { containingFunction = await findContainingFunctionAPI(file.content, location.startLine); } else if (language === 'python') { containingFunction = findContainingFunctionPython(lines, location.startLine); } // Build context object const context = { filename: location.filename, targetLines, surroundingContext, containingFunction, imports: importLines, language }; // Truncate if too large if (surroundingContext.length > CONTEXT_CONFIG.MAX_CONTEXT_CHARS) { console.log(`[CONTEXT] Truncating context from ${surroundingContext.length} to ${CONTEXT_CONFIG.MAX_CONTEXT_CHARS} chars`); context.surroundingContext = surroundingContext.substring(0, CONTEXT_CONFIG.MAX_CONTEXT_CHARS); } return context; } catch (error) { console.error(`[CONTEXT] Failed to extract context for ${location.filename}:`, error); return null; } } /** * Find containing function using ts-morph (API version) */ async function findContainingFunctionAPI(content, targetLine) { try { const project = new ts_morph_1.Project({ useInMemoryFileSystem: true }); const sourceFile = project.createSourceFile('temp.ts', content); // Convert 1-based line to character position const lineStartPos = sourceFile.compilerNode.getPositionOfLineAndCharacter(targetLine - 1, 0); const node = sourceFile.getDescendantAtPos(lineStartPos); if (!node) return undefined; // Find ancestor that is a function, method, class, or interface const parent = node.getFirstAncestor(a => a.getKind() === ts_morph_1.SyntaxKind.FunctionDeclaration || a.getKind() === ts_morph_1.SyntaxKind.MethodDeclaration || a.getKind() === ts_morph_1.SyntaxKind.ClassDeclaration || a.getKind() === ts_morph_1.SyntaxKind.InterfaceDeclaration || a.getKind() === ts_morph_1.SyntaxKind.ArrowFunction || a.getKind() === ts_morph_1.SyntaxKind.FunctionExpression); if (parent) { let name = 'anonymous'; try { if (parent.getName) { name = parent.getName() || 'anonymous'; } } catch { // Some nodes don't have getName } const startLine = parent.getStartLineNumber(); const endLine = parent.getEndLineNumber(); const code = parent.getText(); // Limit function size to avoid token overflow if (endLine - startLine > CONTEXT_CONFIG.MAX_FUNCTION_LINES) { return undefined; } return { name, startLine, endLine, code }; } else { console.log(`[CONTEXT] No function ancestor found at line ${targetLine}`); } } catch (e) { console.error(`[CONTEXT] ts-morph parsing failed for file (content length: ${content.length}): ${e}`); } return undefined; } /** * Find containing function in Python using regex + indentation * Ported from local context-extractor.ts for API-based extraction */ function findContainingFunctionPython(lines, targetLine) { const pyRegex = /^\s*(?:async\s+)?def\s+(\w+)/; let functionStart = -1; let functionName = ''; // Search backwards from target line to find function definition for (let i = targetLine - 1; i >= 0; i--) { const match = lines[i].match(pyRegex); if (match) { functionStart = i; functionName = match[1] || 'anonymous'; break; } } if (functionStart === -1) return undefined; // Find function end using indentation const baseIndent = lines[functionStart].match(/^(\s*)/)?.[1]?.length || 0; const maxLines = Math.min(lines.length, functionStart + CONTEXT_CONFIG.MAX_FUNCTION_LINES); for (let j = functionStart + 1; j < maxLines; j++) { const checkLine = lines[j]; // Skip empty lines and comments if (checkLine.trim() === '' || checkLine.trim().startsWith('#')) continue; const lineIndent = checkLine.match(/^(\s*)/)?.[1]?.length || 0; // Function ends when we hit same or lower indentation (non-empty, non-comment) if (lineIndent <= baseIndent && checkLine.trim()) { const functionCode = lines.slice(functionStart, j).join('\n'); return { name: functionName, startLine: functionStart + 1, endLine: j, code: functionCode }; } } // If we hit end of file, return everything from function start const functionCode = lines.slice(functionStart).join('\n'); return { name: functionName, startLine: functionStart + 1, endLine: lines.length, code: functionCode }; } /** * Format context for LLM consumption */ function formatContextForLLMAPI(context) { const parts = []; parts.push(`FILE: ${context.filename}`); parts.push(`LANGUAGE: ${context.language}`); parts.push(''); if (context.imports && context.imports.length > 0) { parts.push('IMPORTS:'); context.imports.forEach(imp => parts.push(imp)); parts.push(''); } if (context.containingFunction) { parts.push('CONTAINING FUNCTION:'); parts.push(`Name: ${context.containingFunction.name}`); parts.push(`Lines: ${context.containingFunction.startLine}-${context.containingFunction.endLine}`); parts.push(''); parts.push('```' + context.language); parts.push(context.containingFunction.code); parts.push('```'); parts.push(''); } parts.push('TARGET CODE (lines with issue):'); parts.push('```' + context.language); parts.push(context.targetLines); parts.push('```'); parts.push(''); parts.push('SURROUNDING CONTEXT:'); parts.push('```' + context.language); parts.push(context.surroundingContext); parts.push('```'); return parts.join('\n'); } //# sourceMappingURL=api-context-extractor.js.map