"use strict"; /** * Issue Planning System for Prix AI * * A multi-stage selective retrieval pipeline for generating implementation plans * Optimized for low token usage, fast execution, and actionable engineering plans. */ 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.generatePlan = exports.formatPlanAsComment = exports.expandContext = exports.retrieveFiles = exports.predictRelevantFiles = exports.clearRepoCache = exports.mapRepositoryStructure = exports.IGNORED_PATTERNS = exports.SENSITIVE_FILE_PATTERNS = exports.generateAgenticPlanningPrompt = exports.generateCompactFallback = exports.extractRelevantSnippet = exports.validateFileExistsInTree = exports.heuristicFileSelection = exports.ISSUE_MODE_PROMPTS = exports.detectIssueMode = exports.generateFileSummary = exports.smartTruncate = exports.TOKEN_BUDGET = exports.TIER_LIMITS = void 0; const octokit_1 = require("./octokit"); const generated_filter_1 = require("./generated-filter"); // ============================================================================= // TIER LIMITS (REDESIGNED FOR TOKEN EFFICIENCY) // ============================================================================= exports.TIER_LIMITS = { free: { maxPhases: 1, maxFiles: 5, maxChars: 30000, maxRetrievalRounds: 1, enableContextExpansion: false, enableAdvancedAnalysis: false }, starter: { maxPhases: 2, maxFiles: 10, maxChars: 75000, maxRetrievalRounds: 2, enableContextExpansion: true, enableAdvancedAnalysis: false }, pro: { maxPhases: 5, maxFiles: 15, maxChars: 100000, maxRetrievalRounds: 3, enableContextExpansion: true, enableAdvancedAnalysis: true } }; // ============================================================================= // HARD TOKEN BUDGET SYSTEM (NEW) // ============================================================================= exports.TOKEN_BUDGET = { MAX_CONTEXT_TOKENS: 3500, MAX_RETRIEVED_FILES: 5, MAX_FILE_CHARS: 2000, MAX_SNIPPET_LINES: 80, MAX_RETRIES: 1, MAX_SUMMARY_CHARS: 300, MAX_SYMBOLS_PER_FILE: 10, MAX_EXPANDED_TOKENS: 5000, MAX_EXPANSION_DEPTH: 1, CACHE_TTL_MS: 60 * 60 * 1000, // 1 hour TTL MAX_OUTPUT_TOKENS: 1800 // Hard limit on AI output to prevent rambling }; // ============================================================================= // SMART TRUNCATION (NEW) // ============================================================================= /** * Smart truncation that cuts at nearest function boundary, export, class, or JSX block * instead of blindly truncating at character limit which can cut syntax midway */ const smartTruncate = (content, maxLength) => { if (content.length <= maxLength) { return content; } const truncated = content.substring(0, maxLength); // Look for the nearest safe boundary before the truncation point const boundaries = [ /\nexport\s+(?:const|function|class|interface)\s+\w+/g, // Export statements /\nfunction\s+\w+\s*\(/g, // Function declarations /\nconst\s+\w+\s*=\s*(?:async\s+)?\(?/g, // Const function assignments /\nclass\s+\w+/g, // Class declarations /\ninterface\s+\w+/g, // Interface declarations /\n\/\*\*[\s\S]*?\*\//g, // JSDoc comments /\n\/\/.*$/gm, // Single-line comments /\n\s*\n/g // Blank lines ]; let bestMatchIndex = -1; let bestMatch = ''; // Search for the last occurrence of any boundary before the truncation point for (const boundary of boundaries) { let match; const regex = new RegExp(boundary.source, boundary.flags); while ((match = regex.exec(truncated)) !== null) { if (match.index > bestMatchIndex && match.index < maxLength - 100) { bestMatchIndex = match.index; bestMatch = match[0]; } } } // If we found a good boundary, truncate there if (bestMatchIndex > 0) { return (content.substring(0, bestMatchIndex) + '\n... [truncated at boundary]'); } // Fallback: try to truncate at the last newline const lastNewline = truncated.lastIndexOf('\n'); if (lastNewline > maxLength - 200) { return content.substring(0, lastNewline) + '\n... [truncated]'; } // Final fallback: blind truncation return truncated + '\n... [truncated]'; }; exports.smartTruncate = smartTruncate; /** * Generate semantic summary of a file using AST analysis with ts-morph * Extracts JSX tags, React hooks, exports, fetch calls, route handlers, state usage, props, API calls, context usage */ const generateFileSummary = async (content, path) => { const lines = content.split('\n'); const symbols = []; const dependencies = []; const uiElements = []; const dataFlow = []; const reactHooks = []; const apiCalls = []; const contextUsage = []; try { // Try to use ts-morph for TypeScript/JavaScript files if (path.endsWith('.ts') || path.endsWith('.tsx') || path.endsWith('.js') || path.endsWith('.jsx')) { const tsMorph = await Promise.resolve().then(() => __importStar(require('ts-morph'))); const { Project, SyntaxKind } = tsMorph; const project = new Project({ useInMemoryFileSystem: true }); // Create a temporary source file const sourceFile = project.createSourceFile(path, content); // Extract exports const exportDeclarations = sourceFile.getExportDeclarations(); for (const exportDecl of exportDeclarations) { const moduleSpecifier = exportDecl.getModuleSpecifierValue(); if (moduleSpecifier) dependencies.push(moduleSpecifier); } // Extract functions and classes const functions = sourceFile.getFunctions(); const classes = sourceFile.getClasses(); const interfaces = sourceFile.getInterfaces(); functions.forEach((fn) => { const name = fn.getName(); if (name) symbols.push(name); // Check for async functions (potential API calls) if (fn.isAsync()) { dataFlow.push(`async function: ${name}`); } // Check for fetch calls inside functions fn.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((callExpr) => { const expr = callExpr.getExpression().getText(); if (expr === 'fetch' || expr.includes('axios') || expr.includes('request')) { apiCalls.push(`${name} calls ${expr}`); } }); }); classes.forEach((cls) => { const name = cls.getName(); if (name) symbols.push(name); // Check for React components (extends Component or has JSX) const isReactComponent = cls.getExtends()?.getText().includes('Component') || sourceFile.getDescendantsOfKind(SyntaxKind.JsxElement).length > 0; if (isReactComponent) { dataFlow.push(`React component: ${name}`); } }); interfaces.forEach((iface) => { const name = iface.getName(); if (name) symbols.push(name); }); // Extract React hooks sourceFile .getDescendantsOfKind(SyntaxKind.CallExpression) .forEach((callExpr) => { const expr = callExpr.getExpression().getText(); if (expr.startsWith('use') && expr.length > 3 && expr[3] === expr[3].toUpperCase()) { reactHooks.push(expr); } }); // Extract JSX tags (for React components) if (path.endsWith('.tsx') || path.endsWith('.jsx')) { const jsxElements = sourceFile.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement); const jsxOpeningElements = sourceFile.getDescendantsOfKind(SyntaxKind.JsxOpeningElement); const allJsxElements = [...jsxElements, ...jsxOpeningElements]; const uniqueTags = new Set(); allJsxElements.forEach((jsx) => { const tagName = jsx.getTagNameNode().getText(); // Only include custom components (capitalized) or common UI elements if (tagName[0] === tagName[0].toUpperCase() || [ 'Button', 'Input', 'Form', 'Modal', 'Card', 'Nav', 'Header', 'Footer', 'Sidebar' ].includes(tagName)) { uniqueTags.add(tagName); } }); uiElements.push(...Array.from(uniqueTags)); } // Extract context usage sourceFile .getDescendantsOfKind(SyntaxKind.CallExpression) .forEach((callExpr) => { const expr = callExpr.getExpression().getText(); if (expr.includes('Context') || expr.includes('createContext') || expr.includes('useContext')) { contextUsage.push(expr); } }); // Extract variable declarations for state management sourceFile.getVariableStatements().forEach((varStmt) => { varStmt.getDeclarations().forEach((decl) => { const name = decl.getName(); const type = decl.getTypeNode()?.getText(); if (type?.includes('useState') || type?.includes('useReducer')) { dataFlow.push(`state: ${name}`); } }); }); // Dispose ts-morph project to prevent memory leak try { project.removeSourceFile(sourceFile); } catch { } } } catch (error) { // Fallback to regex-based extraction if ts-morph fails console.log('[SUMMARY] ts-morph failed, using fallback for:', path, error); // Extract symbols (component names, functions, exports) for (const line of lines) { const exportMatch = line.match(/export\s+(?:const|function|class|interface)\s+(\w+)/); if (exportMatch) symbols.push(exportMatch[1]); const componentMatch = line.match(/function\s+(\w+)\s*\(|const\s+(\w+)\s*=/); if (componentMatch) { const name = componentMatch[1] || componentMatch[2]; if (name && name[0] === name[0].toUpperCase()) symbols.push(name); } // Extract React hooks const hookMatch = line.match(/(use\w+)\(/); if (hookMatch) reactHooks.push(hookMatch[1]); // Extract fetch calls if (line.includes('fetch(') || line.includes('axios.') || line.includes('request(')) { apiCalls.push('API call detected'); } } } // Determine semantic role let semanticRole = 'module'; const filename = path.split('/').pop() || path; const lowerPath = path.toLowerCase(); if (lowerPath.includes('/components/') || lowerPath.includes('/component/')) { semanticRole = 'ui-component'; } else if (lowerPath.includes('/pages/') || lowerPath.includes('/page/')) { semanticRole = 'page'; } else if (lowerPath.includes('/hooks/') || filename.includes('use')) { semanticRole = 'react-hook'; } else if (lowerPath.includes('/api/') || lowerPath.includes('/routes/')) { semanticRole = 'api-handler'; } else if (lowerPath.includes('/utils/') || lowerPath.includes('/helpers/')) { semanticRole = 'utility'; } else if (lowerPath.includes('/services/') || lowerPath.includes('/lib/')) { semanticRole = 'service'; } else if (filename.includes('api') || filename.includes('route')) { semanticRole = 'api-handler'; } else if (reactHooks.length > 0) { semanticRole = 'react-hook'; } else if (uiElements.length > 0) { semanticRole = 'ui-component'; } // Build comprehensive summary const summaryParts = []; if (semanticRole !== 'module') { summaryParts.push(`${semanticRole.replace('-', ' ')}`); } if (symbols.length > 0) { summaryParts.push(`exports: ${symbols.slice(0, 5).join(', ')}`); } if (reactHooks.length > 0) { summaryParts.push(`hooks: ${reactHooks.slice(0, 3).join(', ')}`); } if (uiElements.length > 0) { summaryParts.push(`UI: ${uiElements.slice(0, 3).join(', ')}`); } if (apiCalls.length > 0) { summaryParts.push(`API calls`); } if (contextUsage.length > 0) { summaryParts.push(`context usage`); } if (dataFlow.length > 0) { summaryParts.push(`manages state/data`); } const summary = summaryParts.length > 0 ? summaryParts.join(' | ') : `File (${lines.length} lines)`; return { path, summary: summary.substring(0, exports.TOKEN_BUDGET.MAX_SUMMARY_CHARS), symbols: symbols.slice(0, exports.TOKEN_BUDGET.MAX_SYMBOLS_PER_FILE), estimatedLines: lines.length, semanticRole, dependencies: dependencies.slice(0, 10), uiElements: uiElements.slice(0, 10), dataFlow: dataFlow.slice(0, 5) }; }; exports.generateFileSummary = generateFileSummary; const detectIssueMode = (issueTitle, issueBody) => { const text = (issueTitle + ' ' + issueBody).toLowerCase(); if (text.includes('button') || text.includes('component') || text.includes('layout') || text.includes('responsive') || text.includes('mobile') || text.includes('nav')) { return 'ui'; } if (text.includes('api') || text.includes('endpoint') || text.includes('auth') || text.includes('database') || text.includes('server') || text.includes('backend')) { return 'backend'; } if (text.includes('slow') || text.includes('performance') || text.includes('optimize') || text.includes('bundle') || text.includes('render') || text.includes('cache')) { return 'performance'; } if (text.includes('security') || text.includes('vulnerability') || text.includes('inject') || text.includes('xss') || text.includes('csrf') || text.includes('auth')) { return 'security'; } return 'general'; }; exports.detectIssueMode = detectIssueMode; exports.ISSUE_MODE_PROMPTS = { ui: 'Senior frontend engineer. Focus on components, layouts, interactions.', backend: 'Senior backend engineer. Focus on APIs, auth, database.', performance: 'Performance specialist. Focus on bundle size, caching, queries.', security: 'Security engineer. Focus on injection, auth, sanitization.', general: 'Senior full-stack engineer. Provide implementation plan.' }; const heuristicFileSelection = (issueTitle, issueBody, repoFiles, issueMode) => { const text = (issueTitle + ' ' + issueBody).toLowerCase(); const keywords = text .split(/\s+/) .filter(w => w.length > 3) .slice(0, 5); const scoredFiles = repoFiles.map(filePath => { const lowerPath = filePath.toLowerCase(); let score = 0; const reasons = []; // Score 1: Keyword match in filename/path for (const keyword of keywords) { if (lowerPath.includes(keyword)) { score += 10; reasons.push(`keyword match: ${keyword}`); } } // Score 2: Folder relevance based on issue mode if (issueMode === 'ui') { if (lowerPath.includes('/components/') || lowerPath.includes('/component/')) { score += 15; reasons.push('ui component folder'); } if (lowerPath.includes('/pages/') || lowerPath.includes('/page/')) { score += 12; reasons.push('ui page folder'); } if (lowerPath.includes('/layouts/') || lowerPath.includes('/layout/')) { score += 10; reasons.push('layout folder'); } if (lowerPath.includes('button') || lowerPath.includes('nav') || lowerPath.includes('header')) { score += 8; reasons.push('ui-related keyword'); } } else if (issueMode === 'backend') { if (lowerPath.includes('api')) { score += 15; reasons.push('api folder'); } if (lowerPath.includes('auth')) { score += 12; reasons.push('auth folder'); } if (lowerPath.includes('db') || lowerPath.includes('model')) { score += 10; reasons.push('database folder'); } if (lowerPath.includes('controller') || lowerPath.includes('service')) { score += 8; reasons.push('backend folder'); } } else if (issueMode === 'performance') { if (lowerPath.includes('config') || lowerPath.includes('webpack') || lowerPath.includes('vite')) { score += 15; reasons.push('config folder'); } if (lowerPath.includes('cache')) { score += 12; reasons.push('cache folder'); } } else if (issueMode === 'security') { if (lowerPath.includes('auth') || lowerPath.includes('middleware')) { score += 15; reasons.push('security-critical folder'); } if (lowerPath.includes('validation')) { score += 12; reasons.push('validation folder'); } } // Score 3: Filename patterns (root files, index files) const filename = filePath.split('/').pop() || ''; if (filename === 'index.ts' || filename === 'index.js' || filename === 'index.tsx') { score += 5; reasons.push('index file'); } if (!filePath.includes('/')) { score += 3; reasons.push('root file'); } // Score 4: File extension relevance if (issueMode === 'ui' && (filePath.endsWith('.tsx') || filePath.endsWith('.jsx'))) { score += 5; reasons.push('react component extension'); } if (issueMode === 'backend' && (filePath.endsWith('.ts') || filePath.endsWith('.js'))) { score += 3; reasons.push('backend extension'); } return { path: filePath, score, reasons }; }); // Sort by score (highest first) and return top files scoredFiles.sort((a, b) => b.score - a.score); const topFiles = scoredFiles .filter(f => f.score > 0) // Only return files with positive scores .slice(0, exports.TOKEN_BUDGET.MAX_RETRIEVED_FILES) .map(f => f.path); console.log('[HEURISTIC SELECTION] Top files:', topFiles.map((path, i) => ({ rank: i + 1, path, score: scoredFiles.find(f => f.path === path)?.score, reasons: scoredFiles.find(f => f.path === path)?.reasons }))); return topFiles; }; exports.heuristicFileSelection = heuristicFileSelection; // ============================================================================= // HALLUCINATION PROTECTION (NEW) // ============================================================================= const validateFileExistsInTree = (filePath, repoFiles) => { const normalizedPath = filePath.replace(/^\//, '').replace(/\\/g, '/'); return repoFiles.some(f => f.replace(/^\//, '').replace(/\\/g, '/') === normalizedPath); }; exports.validateFileExistsInTree = validateFileExistsInTree; // ============================================================================= // TARGETED SNIPPET RETRIEVAL (NEW) // ============================================================================= const extractRelevantSnippet = (content, searchTerm, maxLines = exports.TOKEN_BUDGET.MAX_SNIPPET_LINES) => { const lines = content.split('\n'); const relevantLines = []; const lowerSearchTerm = searchTerm.toLowerCase(); for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (line.toLowerCase().includes(lowerSearchTerm)) { // Include context lines around the match const start = Math.max(0, i - 3); const end = Math.min(lines.length, i + maxLines / 2); for (let j = start; j < end; j++) { if (!relevantLines.includes(lines[j])) { relevantLines.push(lines[j]); } } if (relevantLines.length >= maxLines) break; } } return relevantLines.slice(0, maxLines).join('\n'); }; exports.extractRelevantSnippet = extractRelevantSnippet; // ============================================================================= // COMPACT FALLBACK MODE (NEW) // ============================================================================= const generateCompactFallback = (issueTitle, relevantFiles, issueMode) => { const mode = issueMode; let suggestedAreas = []; if (mode === 'ui') { suggestedAreas = [ 'components', 'layouts', 'navigation', 'shared UI elements' ]; } else if (mode === 'backend') { suggestedAreas = ['API routes', 'controllers', 'models', 'middleware']; } else if (mode === 'performance') { suggestedAreas = [ 'rendering logic', 'database queries', 'bundle configuration', 'caching' ]; } else if (mode === 'security') { suggestedAreas = [ 'input validation', 'authentication flows', 'API endpoints', 'data sanitization' ]; } else { suggestedAreas = relevantFiles.slice(0, 3); } return `## Compact Implementation Plan **Likely Affected Areas:** ${suggestedAreas.map(area => `- ${area}`).join('\n')} **Suggested Tasks:** - Analyze the issue requirements - Identify necessary code changes - Update affected files - Test the implementation - Verify no regressions **Suggested Search Terms:** ${relevantFiles .slice(0, 3) .map(f => f.split('/').pop()) .join(', ')} *Generated by Prix AI - Compact Fallback Mode*`; }; exports.generateCompactFallback = generateCompactFallback; // ============================================================================= // AGENTIC PROMPT GENERATION (NEW) // ============================================================================= const generateAgenticPlanningPrompt = (issueTitle, issueBody, fileSummaries, issueMode) => { const modePrompt = exports.ISSUE_MODE_PROMPTS[issueMode]; const summaryContext = fileSummaries .map(f => `- ${f.path}: ${f.summary}`) .join('\n'); return `${modePrompt} Issue: ${issueTitle} Description: ${issueBody} Files: ${summaryContext} IMPORTANT: Return your response as markdown only. DO NOT use markdown tables. Use headings and bullet lists instead. DO NOT return JSON. Return plain markdown format. Include these sections in order: 1. Understanding (brief explanation of the issue) 2. Affected files (list) 3. Implementation phases (as headings with bullet lists, not tables) - For EACH phase, include a code block with the specific AI prompt to execute that phase - Format: "### Phase X: [Title]" then tasks/risks, then "\`\`\`" followed by the AI prompt for that phase 4. Risks (bullet list) 5. Testing checklist (bullet list) 6. Combined AI Prompt (final code block with a comprehensive prompt that combines all phases) CRITICAL - AI Prompt Format: - Each phase MUST have its own code block with a specific, detailed AI prompt to execute just that phase - The phase prompt should be actionable and specific (e.g., "You are a code scanner. Go through the codebase and find all static buttons...") - At the end, include a "### Combined AI Prompt" section with a single comprehensive prompt that combines all phases - The combined prompt should allow the user to solve the entire issue in one go by pasting it to an AI Example phase prompt format: \`\`\` You are a senior frontend developer. Your task is to [specific task]. Go through the codebase and [specific actions]. List your findings to the user. \`\`\` Example combined prompt format: \`\`\` You are a senior full-stack developer. Your task is to [overall goal]. Phase 1: [phase 1 task] Phase 2: [phase 2 task] [Additional context and instructions] \`\`\` Be concise. Focus on actionable tasks.`; }; exports.generateAgenticPlanningPrompt = generateAgenticPlanningPrompt; // ============================================================================= // SECURITY FILTERS // ============================================================================= exports.SENSITIVE_FILE_PATTERNS = [ /\.env$/, /\.env\./, /credentials/, /secret/, /private.*key/, /\.pem$/, /\.key$/, /password/, /token/, /\.aws/, /\.ssh/ ]; exports.IGNORED_PATTERNS = [ /node_modules/, /dist/, /build/, /\.next/, /coverage/, /\.min\.js$/, /\.bundle\.js$/, /\.generated\./, /vendor/, /\.map$/, /\.snap$/, /__pycache__/, /\.pyc$/, /\.egg-info/, /target/, /out/, /\.cache/, /\.vscode/, /\.idea/, /\.DS_Store$/, /Thumbs\.db$/ ]; const repoStructureCache = new Map(); /** * Generate cache key from owner, repo, and ref */ const getCacheKey = (owner, repo, ref) => { return `${owner}/${repo}/${ref}`; }; /** * Check if cache entry is valid (not expired) */ const isCacheValid = (entry) => { const now = Date.now(); return now - entry.timestamp < exports.TOKEN_BUDGET.CACHE_TTL_MS; }; /** * Collect lightweight repository structure without loading file contents * Uses caching to avoid expensive GitHub API calls */ const mapRepositoryStructure = async (owner, repo, ref = 'HEAD') => { const cacheKey = getCacheKey(owner, repo, ref); // Check cache first const cachedEntry = repoStructureCache.get(cacheKey); if (cachedEntry && isCacheValid(cachedEntry)) { console.log('[REPO CACHE] Using cached structure for', cacheKey); return cachedEntry.structure; } console.log('[REPO CACHE] Cache miss or expired for', cacheKey, '- fetching from GitHub'); const structure = { folders: [], files: [], extensions: [], packageJson: null, frameworkHints: [], rootFiles: [] }; try { // Get repository tree recursively (limited depth for performance) const { data: tree } = await octokit_1.octokit.git.getTree({ owner, repo, tree_sha: ref, recursive: 'true' }); if (!tree.tree) { return structure; } const extensions = new Set(); const folders = new Set(); for (const item of tree.tree) { // Skip ignored patterns if (shouldIgnorePath(item.path)) { continue; } if (item.type === 'blob') { structure.files.push(item.path); // Track root files if (!item.path.includes('/')) { structure.rootFiles.push(item.path); } // Extract extension const ext = item.path.split('.').pop(); if (ext && ext !== item.path) { extensions.add(`.${ext}`); } // Load package.json if found if (item.path === 'package.json') { try { const { data: file } = await octokit_1.octokit.repos.getContent({ owner, repo, path: 'package.json', ref }); if (!Array.isArray(file) && file.type === 'file' && file.content) { structure.packageJson = JSON.parse(Buffer.from(file.content, 'base64').toString()); structure.frameworkHints = detectFrameworkHints(structure.packageJson); } } catch (e) { // Ignore package.json parse errors } } } else if (item.type === 'tree') { folders.add(item.path); } } structure.extensions = Array.from(extensions); structure.folders = Array.from(folders).slice(0, 100); // Limit folders // Cache the result repoStructureCache.set(cacheKey, { structure, timestamp: Date.now() }); console.log('[REPO CACHE] Cached structure for', cacheKey, 'with', structure.files.length, 'files'); } catch (error) { console.error('Error mapping repository structure:', error.message); } return structure; }; exports.mapRepositoryStructure = mapRepositoryStructure; /** * Clear repository structure cache (call on push events to invalidate) */ const clearRepoCache = (owner, repo) => { if (owner && repo) { // Clear specific repo cache for (const key of repoStructureCache.keys()) { if (key.startsWith(`${owner}/${repo}/`)) { repoStructureCache.delete(key); console.log('[REPO CACHE] Cleared cache for', key); } } } else { // Clear all cache repoStructureCache.clear(); console.log('[REPO CACHE] Cleared all cache'); } }; exports.clearRepoCache = clearRepoCache; /** * Detect framework from package.json dependencies */ const detectFrameworkHints = (packageJson) => { const hints = []; const deps = { ...packageJson.dependencies, ...packageJson.devDependencies }; if (deps.react) hints.push('React'); if (deps.next) hints.push('Next.js'); if (deps.vue) hints.push('Vue'); if (deps.angular || deps['@angular/core']) hints.push('Angular'); if (deps.express) hints.push('Express'); if (deps['@nestjs/core'] || deps.nestjs) hints.push('NestJS'); if (deps.fastify) hints.push('Fastify'); if (deps.django || deps['djangorestframework']) hints.push('Django'); if (deps.flask) hints.push('Flask'); if (deps.rails) hints.push('Rails'); if (deps['@prisma/client'] || deps.prisma) hints.push('Prisma'); if (deps.typeorm) hints.push('TypeORM'); if (deps.mongoose) hints.push('Mongoose'); if (deps.sequelize) hints.push('Sequelize'); if (deps.drizzle) hints.push('Drizzle'); if (deps.postgres || deps.pg) hints.push('PostgreSQL'); if (deps.redis) hints.push('Redis'); if (deps.mongodb || deps.mongoose) hints.push('MongoDB'); return hints; }; /** * Check if a path should be ignored */ const shouldIgnorePath = (path) => { // CRITICAL: Use global generated file filter first if ((0, generated_filter_1.isGeneratedFile)(path)) { return true; } // Security check for (const pattern of exports.SENSITIVE_FILE_PATTERNS) { if (pattern.test(path)) { return true; } } // Standard ignores for (const pattern of exports.IGNORED_PATTERNS) { if (pattern.test(path)) { return true; } } return false; }; // ============================================================================= // STAGE 2: RELEVANT FILE PREDICTION // ============================================================================= /** * Use LLM to predict relevant files based on issue and repository structure */ const predictRelevantFiles = async (issueTitle, issueBody, structure, bot, limits) => { try { // Build context for LLM const structureSummary = buildStructureSummary(structure); const prompt = `You are an expert code reviewer. Given a GitHub issue and repository structure, identify the most relevant files for implementing a solution. ISSUE: Title: ${issueTitle} Body: ${issueBody} REPOSITORY STRUCTURE: ${structureSummary} FRAMEWORKS: ${structure.frameworkHints.join(', ') || 'Unknown'} TASK: Identify the most relevant files for this issue. Return a JSON array with: - path: file path - confidence: "high" | "medium" | "low" - reasoning: brief explanation Prioritize: 1. Files directly related to the issue 2. Core implementation files 3. Configuration/routing files 4. Test files (if relevant) Limit to ${limits.maxFiles} files maximum. Return ONLY valid JSON array, no markdown formatting.`; const [response] = await bot.chat(prompt, { max_tokens: exports.TOKEN_BUDGET.MAX_OUTPUT_TOKENS }); // Log planning-specific token usage console.log('TOKEN_USAGE', { action: 'ISSUE_PLANNING', model: bot.getCurrentModel(), planningStage: 'file_prediction', promptLength: prompt.length, responseLength: response.length, timestamp: new Date().toISOString() }); // Parse response const predictions = parseLLMPredictions(response); // Apply limits return predictions.slice(0, limits.maxFiles); } catch (error) { console.error('Error predicting relevant files:', error.message); return []; } }; exports.predictRelevantFiles = predictRelevantFiles; /** * Build concise summary of repository structure */ const buildStructureSummary = (structure) => { const lines = []; lines.push(`Total files: ${structure.files.length}`); lines.push(`Extensions: ${structure.extensions.slice(0, 10).join(', ')}`); lines.push(`Main folders: ${structure.folders.slice(0, 15).join(', ')}`); if (structure.rootFiles.length > 0) { lines.push(`Root files: ${structure.rootFiles.join(', ')}`); } return lines.join('\n'); }; /** * Parse LLM predictions from response */ const parseLLMPredictions = (response) => { try { // Try to extract JSON from response const jsonMatch = response.match(/\[[\s\S]*\]/); if (!jsonMatch) { return []; } const parsed = JSON.parse(jsonMatch[0]); return Array.isArray(parsed) ? parsed : []; } catch (error) { console.error('Error parsing LLM predictions:', error); return []; } }; // ============================================================================= // STAGE 3: SELECTIVE FILE RETRIEVAL // ============================================================================= /** * Retrieve file contents with hard limits */ const retrieveFiles = async (predictions, owner, repo, ref, limits) => { const retrievedFiles = []; let totalChars = 0; // Sort by confidence (high first) const sorted = predictions.sort((a, b) => { const confidenceOrder = { high: 0, medium: 1, low: 2 }; return confidenceOrder[a.confidence] - confidenceOrder[b.confidence]; }); for (const prediction of sorted) { // CRITICAL: Skip generated files before even fetching if ((0, generated_filter_1.isGeneratedFile)(prediction.path)) { (0, generated_filter_1.logIgnoredFile)(prediction.path, 0); continue; } // Check limits if (retrievedFiles.length >= limits.maxFiles) { console.log(`Reached max files limit: ${limits.maxFiles}`); break; } if (totalChars >= limits.maxChars) { console.log(`Reached max chars limit: ${limits.maxChars}`); break; } try { const { data: file } = await octokit_1.octokit.repos.getContent({ owner, repo, path: prediction.path, ref }); if (Array.isArray(file) || file.type !== 'file' || !file.content) { continue; } const content = Buffer.from(file.content, 'base64').toString(); const size = content.length; // CRITICAL: Check diff size before adding if ((0, generated_filter_1.isDiffTooLarge)(content)) { console.log(`Diff too large for ${prediction.path}, skipping (${size} chars)`); (0, generated_filter_1.logIgnoredFile)(prediction.path, size); continue; } // Check if adding this file would exceed char limit if (totalChars + size > limits.maxChars) { // Truncate if needed const remainingChars = limits.maxChars - totalChars; if (remainingChars > 1000) { retrievedFiles.push({ path: prediction.path, content: content.substring(0, remainingChars), size: remainingChars }); totalChars = limits.maxChars; } break; } retrievedFiles.push({ path: prediction.path, content, size }); totalChars += size; } catch (error) { console.error(`Error retrieving file ${prediction.path}:`, error.message); } } // CRITICAL: Log token savings (0, generated_filter_1.logTokenSavings)(); return retrievedFiles; }; exports.retrieveFiles = retrieveFiles; // ============================================================================= // STAGE 4: CONTEXT EXPANSION // ============================================================================= /** * Expand context by analyzing imports and dependencies */ const expandContext = async (files, owner, repo, ref, limits, structure) => { if (!limits.enableContextExpansion) { return files; } const expandedFiles = [...files]; const importedPaths = new Set(); let totalExpandedChars = 0; // Extract imports from retrieved files (depth 1 only - don't recursively expand) for (const file of files) { const imports = extractImports(file.content, file.path); for (const imp of imports) { importedPaths.add(imp); } } // Try to resolve and retrieve imported files with safety limits for (const importPath of Array.from(importedPaths).slice(0, 2)) { if (expandedFiles.length >= limits.maxFiles) break; // Check if adding more files would exceed expanded token budget if (totalExpandedChars >= exports.TOKEN_BUDGET.MAX_EXPANDED_TOKENS) { console.log('[CONTEXT EXPANSION] Reached MAX_EXPANDED_TOKENS limit'); break; } const resolvedPath = resolveImportPath(importPath, structure); if (!resolvedPath) continue; try { const { data: file } = await octokit_1.octokit.repos.getContent({ owner, repo, path: resolvedPath, ref }); if (!Array.isArray(file) && file.type === 'file' && file.content) { const content = Buffer.from(file.content, 'base64').toString(); const fileSize = content.length; // Check if this file would exceed the token budget if (totalExpandedChars + fileSize > exports.TOKEN_BUDGET.MAX_EXPANDED_TOKENS) { console.log('[CONTEXT EXPANSION] File would exceed token budget, skipping:', resolvedPath); continue; } expandedFiles.push({ path: resolvedPath, content, size: fileSize }); totalExpandedChars += fileSize; console.log('[CONTEXT EXPANSION] Added file:', resolvedPath, 'size:', fileSize, 'total:', totalExpandedChars); } } catch (error) { // File not found or inaccessible, skip } } console.log('[CONTEXT EXPANSION] Summary', { originalFiles: files.length, expandedFiles: expandedFiles.length, totalExpandedChars, maxExpandedTokens: exports.TOKEN_BUDGET.MAX_EXPANDED_TOKENS }); return expandedFiles; }; exports.expandContext = expandContext; /** * Extract import statements from file content */ const extractImports = (content, filePath) => { const imports = []; const lines = content.split('\n'); for (const line of lines) { // TypeScript/JavaScript imports const importMatch = line.match(/import.*from ['"]([^'"]+)['"]/); if (importMatch) { imports.push(importMatch[1]); } // Python imports const pythonMatch = line.match(/^(?:from|import)\s+(\S+)/); if (pythonMatch) { imports.push(pythonMatch[1]); } // Ruby requires const rubyMatch = line.match(/require\s+['"]([^'"]+)['"]/); if (rubyMatch) { imports.push(rubyMatch[1]); } } return imports; }; /** * Resolve import path to actual file path */ const resolveImportPath = (importPath, structure) => { // Handle relative imports if (importPath.startsWith('./') || importPath.startsWith('../')) { return importPath; } // Handle absolute imports (check against known files) const candidates = structure.files.filter(f => f.endsWith(importPath) || f.endsWith(`${importPath}.ts`) || f.endsWith(`${importPath}.js`) || f.endsWith(`${importPath}.tsx`) || f.endsWith(`${importPath}.jsx`)); return candidates[0] || null; }; // PLAN FORMATTING // ============================================================================= /** * Generate a combined AI prompt from phases * This creates a comprehensive prompt that combines all phases for one-shot execution */ const generateCombinedPromptFromPhases = (phases, understanding) => { const lines = []; lines.push('You are a senior full-stack developer. Your task is to implement the following issue.'); lines.push(''); lines.push('Issue Context:'); lines.push(understanding); lines.push(''); lines.push('Please execute the implementation in the following phases:'); lines.push(''); phases.forEach((phase, index) => { lines.push(`Phase ${index + 1}: ${phase.title}`); lines.push(`Purpose: ${phase.purpose}`); if (phase.tasks.length > 0) { lines.push('Tasks:'); phase.tasks.forEach(task => lines.push(`- ${task}`)); } lines.push(''); }); lines.push('Instructions:'); lines.push('- Go through each phase systematically'); lines.push('- Make the necessary code changes'); lines.push('- Test your changes'); lines.push('- Report your progress and any issues encountered'); lines.push(''); lines.push('Begin implementation now.'); return lines.join('\n'); }; /** * Format implementation plan as GitHub comment * Preserves raw markdown from AI with light header/footer */ const formatPlanAsComment = (plan) => { // If raw markdown is available, use it directly (preferred) if (plan.rawMarkdown) { const header = '# 🧠 Implementation Plan\n\n'; const footer = '\n\n---\n\n> If you are facing any issues, report to us at [support@prixai.xyz](mailto:support@prixai.xyz) or [https://www.prixai.xyz/feedback](https://www.prixai.xyz/feedback)\n\n*Generated by Prix AI — Autonomous Code Planning*\n\nšŸš€ [prixai.xyz](https://prixai.xyz)'; // Ensure the raw markdown has a combined AI prompt section let processedMarkdown = plan.rawMarkdown; // If the AI didn't generate a combined prompt, generate one from phase prompts if (!processedMarkdown.includes('### Combined AI Prompt') && plan.phases.length > 0) { const combinedPrompt = generateCombinedPromptFromPhases(plan.phases, plan.understanding); processedMarkdown += '\n\n### Combined AI Prompt\n\n```\n' + combinedPrompt + '\n```'; } const formatted = header + processedMarkdown + footer; console.log('FORMATTER_OUTPUT', { mode: 'raw_markdown', length: formatted.length, rawLength: plan.rawMarkdown.length }); return formatted; } // Fallback: rebuild from parsed object (legacy mode) const lines = []; lines.push('# 🧠 Implementation Plan'); lines.push(''); // Complexity Badge const complexityEmoji = plan.complexity === 'Low' ? '🟢' : plan.complexity === 'Medium' ? '🟔' : 'šŸ”“'; lines.push(`**Complexity:** ${complexityEmoji} ${plan.complexity}`); lines.push(''); // Estimated Scope lines.push('## šŸ“Š Estimated Scope'); lines.push(''); lines.push('| Metric | Value |'); lines.push('|--------|-------|'); lines.push(`| Files Affected | ${plan.estimatedScope.filesAffected} |`); lines.push(`| Effort | ${plan.estimatedScope.effort} |`); lines.push(`| Risk Level | ${plan.estimatedScope.riskLevel} |`); lines.push(''); // Understanding lines.push('## šŸŽÆ Understanding'); lines.push(''); lines.push(plan.understanding); lines.push(''); // Relevant Areas if (plan.relevantAreas.length > 0) { lines.push('## šŸ“ Relevant Areas'); lines.push(''); for (const area of plan.relevantAreas) { lines.push(`- \`${area}\``); } lines.push(''); } // Phases for (const phase of plan.phases) { lines.push(`## šŸ“‹ Phase — ${phase.title}`); lines.push(''); lines.push(`**Purpose:** ${phase.purpose}`); lines.push(''); if (phase.tasks.length > 0) { lines.push('**Tasks:**'); for (const task of phase.tasks) { lines.push(`- ${task}`); } lines.push(''); } if (phase.risks.length > 0) { lines.push('**Risks:**'); for (const risk of phase.risks) { lines.push(`- āš ļø ${risk}`); } lines.push(''); } } // Testing Checklist if (plan.testingChecklist.length > 0) { lines.push('## āœ… Testing Checklist'); lines.push(''); for (const item of plan.testingChecklist) { lines.push(`- [ ] ${item}`); } lines.push(''); } // AI Prompts if (plan.aiPrompts.length > 0) { lines.push('## šŸ¤– Suggested AI Prompts'); lines.push(''); for (let i = 0; i < plan.aiPrompts.length; i++) { lines.push(`### Prompt ${i + 1}`); lines.push(''); lines.push('```'); lines.push(plan.aiPrompts[i]); lines.push('```'); lines.push(''); } } // Footer lines.push('---'); lines.push(''); lines.push('> If you are facing any issues, report to us at [support@prixai.xyz](mailto:support@prixai.xyz) or [https://www.prixai.xyz/feedback](https://www.prixai.xyz/feedback)'); lines.push(''); lines.push('*Generated by Prix AI — Autonomous Code Planning*'); lines.push(''); lines.push('šŸš€ [prixai.xyz](https://prixai.xyz)'); const formatted = lines.join('\n'); console.log('FORMATTER_OUTPUT', { mode: 'legacy', length: formatted.length, phases: plan.phases.length }); return formatted; }; exports.formatPlanAsComment = formatPlanAsComment; const generatePlan = async (options, bot) => { // CRITICAL: Reset filter metrics at start of new plan generation (0, generated_filter_1.resetFilterMetrics)(); const startTime = Date.now(); const limits = exports.TIER_LIMITS[options.tier]; const metrics = { filesRetrieved: 0, tokenEstimate: 0, retrievalRounds: 0, executionDuration: 0, tier: options.tier }; let retryCount = 0; const maxRetries = exports.TOKEN_BUDGET.MAX_RETRIES; let structure = null; try { // Detect issue mode for specialized handling const issueMode = (0, exports.detectIssueMode)(options.issueTitle, options.issueBody); console.log('[ISSUE MODE]', { mode: issueMode, title: options.issueTitle }); // Stage 1: Repository Mapping structure = await (0, exports.mapRepositoryStructure)(options.owner, options.repo, options.ref); // Stage 2: Relevant File Prediction (TOKEN OPTIMIZATION: use heuristic for simple issues) let filteredPredictions; // Check if issue is simple enough for heuristic retrieval (skip expensive LLM) const isSimpleIssue = options.issueTitle.length < 120 && structure.files.length < 50 && !options.issueBody.toLowerCase().includes('architecture') && !options.issueBody.toLowerCase().includes('complex'); if (isSimpleIssue) { console.log('[HEURISTIC RETRIEVAL] Using heuristic file selection for simple issue'); const heuristicPaths = (0, exports.heuristicFileSelection)(options.issueTitle, options.issueBody, structure.files, issueMode); filteredPredictions = heuristicPaths.map(path => ({ path, confidence: 'medium', reasoning: 'Heuristic selection based on issue keywords' })); } else { console.log('[LLM RETRIEVAL] Using AI file selection for complex issue'); const predictions = await (0, exports.predictRelevantFiles)(options.issueTitle, options.issueBody, structure, bot, limits); filteredPredictions = predictions.slice(0, exports.TOKEN_BUDGET.MAX_RETRIEVED_FILES); } // Stage 3: Selective File Retrieval with Hallucination Protection (PARALLEL) const validPredictions = filteredPredictions.filter(prediction => structure && (0, exports.validateFileExistsInTree)(prediction.path, structure.files)); // Limit to max files before parallel retrieval const predictionsToRetrieve = validPredictions.slice(0, exports.TOKEN_BUDGET.MAX_RETRIEVED_FILES); console.log('[PARALLEL RETRIEVAL] Fetching', predictionsToRetrieve.length, 'files in parallel'); // Parallel file retrieval using Promise.allSettled const retrievalPromises = predictionsToRetrieve.map(prediction => retrieveSingleFile(prediction.path, options.owner, options.repo, options.ref).catch(err => { console.warn('[FILE RETRIEVAL ERROR]', { path: prediction.path, error: err.message }); return null; })); const results = await Promise.allSettled(retrievalPromises); // Process results const files = []; for (const result of results) { if (result.status === 'fulfilled' && result.value) { const file = result.value; // Smart truncate content to respect token budget (cuts at function boundaries) if (file.content.length > exports.TOKEN_BUDGET.MAX_FILE_CHARS) { file.content = (0, exports.smartTruncate)(file.content, exports.TOKEN_BUDGET.MAX_FILE_CHARS); } files.push(file); metrics.filesRetrieved++; } } console.log('[PARALLEL RETRIEVAL] Successfully retrieved', files.length, 'files'); metrics.retrievalRounds = 1; // Stage 4: Generate Semantic Summaries instead of full content dumps const fileSummaries = []; for (const file of files) { const summary = await (0, exports.generateFileSummary)(file.content, file.path); fileSummaries.push(summary); } // Stage 5: Generate Agentic Planning Prompt const planningPrompt = (0, exports.generateAgenticPlanningPrompt)(options.issueTitle, options.issueBody, fileSummaries, issueMode); // Estimate tokens from actual prompt length (more accurate than summaries) metrics.tokenEstimate = Math.ceil(planningPrompt.length / 3.5); // Stage 6: Implementation Plan Generation with Retry Protection let plan; try { plan = await generateImplementationPlanWithPrompt(planningPrompt, structure, bot, limits); } catch (planErr) { console.error('[PLAN_GENERATION_RETRY]', { error: planErr.message, stack: planErr.stack, attempt: retryCount + 1 }); if (retryCount < maxRetries) { retryCount++; console.warn('[RETRY] Retrying plan generation', { attempt: retryCount }); // Use compact fallback on retry const relevantFilePaths = fileSummaries.map(f => f.path); const fallbackContent = (0, exports.generateCompactFallback)(options.issueTitle, relevantFilePaths, issueMode); plan = parseFallbackPlan(fallbackContent); } else { // Final fallback console.error('[PLAN GENERATION FAILED] Using compact fallback'); const relevantFilePaths = fileSummaries.map(f => f.path); const fallbackContent = (0, exports.generateCompactFallback)(options.issueTitle, relevantFilePaths, issueMode); plan = parseFallbackPlan(fallbackContent); } } metrics.executionDuration = Date.now() - startTime; return { plan, metrics }; } catch (error) { metrics.executionDuration = Date.now() - startTime; console.error('[PLAN GENERATION ERROR]', { error: error.message, stack: error.stack }); // Final fallback on any error - use generic file list if structure not available const relevantFilePaths = structure?.files?.slice(0, 5) || [ 'src/', 'components/', 'lib/' ]; const issueMode = (0, exports.detectIssueMode)(options.issueTitle, options.issueBody); const fallbackContent = (0, exports.generateCompactFallback)(options.issueTitle, relevantFilePaths, issueMode); const plan = parseFallbackPlan(fallbackContent); return { plan, metrics }; } }; exports.generatePlan = generatePlan; // ============================================================================= // HELPER: Generate Implementation Plan with Agentic Prompt // ============================================================================= const generateImplementationPlanWithPrompt = async (prompt, structure, bot, limits) => { const [response] = await bot.chat(prompt, { max_tokens: exports.TOKEN_BUDGET.MAX_OUTPUT_TOKENS }); // Log planning-specific token usage for main plan generation console.log('TOKEN_USAGE', { action: 'ISSUE_PLANNING', model: bot.getCurrentModel(), planningStage: 'plan_generation', promptLength: prompt.length, responseLength: response.length, timestamp: new Date().toISOString() }); // CRITICAL: Log raw AI response before any processing if (!response) { console.log('RAW_AI_RESPONSE: Null response received'); return { understanding: 'Failed to generate plan due to null response', phases: [], estimatedScope: { filesAffected: 0, effort: 'Unknown', riskLevel: 'Low' }, complexity: 'Medium', relevantAreas: [], testingChecklist: [], aiPrompts: [] }; } console.log('RAW_AI_RESPONSE', { length: response.length, preview: response.substring(0, 500) }); console.log('RAW_RESPONSE_FULL', response); // Parse the LLM response into ImplementationPlan const plan = parsePlanResponse(response); console.log('PARSED_PLAN', { phases: plan.phases.length, understanding: plan.understanding.substring(0, 200) }); return plan; }; // ============================================================================= // HELPER: Parse Plan Response // ============================================================================= const parsePlanResponse = (response) => { const cleanedMarkdown = response .trim() .replace(/\r\n/g, '\n') .replace(/\r/g, '\n') .replace(/\n{3,}/g, '\n\n'); console.log('RAW_MARKDOWN_PARSED', { length: cleanedMarkdown.length, preview: cleanedMarkdown.substring(0, 300) }); return { complexity: 'Medium', estimatedScope: { filesAffected: 5, effort: 'Medium', riskLevel: 'Medium' }, understanding: cleanedMarkdown, relevantAreas: [], phases: [], testingChecklist: [], aiPrompts: [], rawMarkdown: cleanedMarkdown }; }; // ============================================================================= // HELPER: Parse Fallback Plan // ============================================================================= const parseFallbackPlan = (fallbackContent) => { const phase = { title: 'Implementation', purpose: 'Execute the suggested tasks', tasks: [ 'Analyze the issue requirements', 'Identify necessary code changes', 'Update affected files', 'Test the implementation' ], risks: ['Potential for regressions', 'Breaking changes in dependencies'] }; const combinedPrompt = `You are a senior full-stack developer. Your task is to implement the following issue. Issue Context: ${fallbackContent} Phase 1: Implementation Purpose: Execute the suggested tasks Tasks: - Analyze the issue requirements - Identify necessary code changes - Update affected files - Test the implementation Instructions: - Go through each phase systematically - Make the necessary code changes - Test your changes - Report your progress and any issues encountered Begin implementation now.`; const rawMarkdown = `## Understanding ${fallbackContent} ## Phase 1: Implementation **Purpose:** Execute the suggested tasks **Tasks:** - Analyze the issue requirements - Identify necessary code changes - Update affected files - Test the implementation **Risks:** - āš ļø Potential for regressions - āš ļø Breaking changes in dependencies ### Phase 1 AI Prompt \`\`\` You are a senior full-stack developer. Your task is to analyze the issue requirements and identify necessary code changes. Go through the codebase and list all files that need to be modified along with the specific changes required. Present your findings to the user in a clear, organized manner. \`\`\` ## Combined AI Prompt \`\`\` ${combinedPrompt} \`\`\` ## Testing Checklist - [ ] Verify the fix works as expected - [ ] Check for regressions - [ ] Test edge cases`; return { complexity: 'Low', estimatedScope: { filesAffected: 5, effort: 'Medium', riskLevel: 'Low' }, understanding: fallbackContent, relevantAreas: [], phases: [phase], testingChecklist: [ 'Verify the fix works as expected', 'Check for regressions', 'Test edge cases' ], aiPrompts: [ 'Help me implement the changes described in the issue', 'Review my implementation for any issues' ], rawMarkdown }; }; // ============================================================================= // HELPER: Retrieve Single File // ============================================================================= const retrieveSingleFile = async (path, owner, repo, ref) => { const { data } = await octokit_1.octokit.repos.getContent({ owner, repo, path, ref }); const content = Buffer.from(data.content, 'base64').toString('utf-8'); return { path, content, size: content.length }; }; //# sourceMappingURL=issue-planner.js.map