| "use strict"; |
| |
| |
| |
| |
| |
| |
| Object.defineProperty(exports, "__esModule", { value: true }); |
| exports.extractIssueContextAPI = extractIssueContextAPI; |
| exports.formatContextForLLMAPI = formatContextForLLMAPI; |
| const ts_morph_1 = require("ts-morph"); |
| |
| const CONTEXT_CONFIG = { |
| |
| SURROUNDING_LINES: 40, |
| |
| MAX_FUNCTION_LINES: 200, |
| |
| MAX_CONTEXT_CHARS: 4000 |
| }; |
| |
| |
| |
| |
| async function extractIssueContextAPI(apiCheckout, location) { |
| try { |
| |
| const file = await apiCheckout.getFile(location.filename); |
| const lines = file.content.split('\n'); |
| |
| const language = location.filename.endsWith('.ts') |
| ? 'typescript' |
| : location.filename.endsWith('.js') |
| ? 'javascript' |
| : location.filename.endsWith('.py') |
| ? 'python' |
| : 'unknown'; |
| |
| const startIdx = Math.max(0, location.startLine - 1); |
| const endIdx = Math.min(lines.length, location.endLine); |
| const targetLines = lines.slice(startIdx, endIdx).join('\n'); |
| |
| 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'); |
| |
| 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]); |
| } |
| } |
| |
| let containingFunction = undefined; |
| if (language === 'typescript' || language === 'javascript') { |
| containingFunction = await findContainingFunctionAPI(file.content, location.startLine); |
| } |
| else if (language === 'python') { |
| containingFunction = findContainingFunctionPython(lines, location.startLine); |
| } |
| |
| const context = { |
| filename: location.filename, |
| targetLines, |
| surroundingContext, |
| containingFunction, |
| imports: importLines, |
| language |
| }; |
| |
| 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; |
| } |
| } |
| |
| |
| |
| async function findContainingFunctionAPI(content, targetLine) { |
| try { |
| const project = new ts_morph_1.Project({ useInMemoryFileSystem: true }); |
| const sourceFile = project.createSourceFile('temp.ts', content); |
| |
| const lineStartPos = sourceFile.compilerNode.getPositionOfLineAndCharacter(targetLine - 1, 0); |
| const node = sourceFile.getDescendantAtPos(lineStartPos); |
| if (!node) |
| return undefined; |
| |
| 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 { |
| |
| } |
| const startLine = parent.getStartLineNumber(); |
| const endLine = parent.getEndLineNumber(); |
| const code = parent.getText(); |
| |
| 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; |
| } |
| |
| |
| |
| |
| function findContainingFunctionPython(lines, targetLine) { |
| const pyRegex = /^\s*(?:async\s+)?def\s+(\w+)/; |
| let functionStart = -1; |
| let functionName = ''; |
| |
| 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; |
| |
| 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]; |
| |
| if (checkLine.trim() === '' || checkLine.trim().startsWith('#')) |
| continue; |
| const lineIndent = checkLine.match(/^(\s*)/)?.[1]?.length || 0; |
| |
| if (lineIndent <= baseIndent && checkLine.trim()) { |
| const functionCode = lines.slice(functionStart, j).join('\n'); |
| return { |
| name: functionName, |
| startLine: functionStart + 1, |
| endLine: j, |
| code: functionCode |
| }; |
| } |
| } |
| |
| const functionCode = lines.slice(functionStart).join('\n'); |
| return { |
| name: functionName, |
| startLine: functionStart + 1, |
| endLine: lines.length, |
| code: functionCode |
| }; |
| } |
| |
| |
| |
| 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'); |
| } |
| |