UNPKG

@tosin2013/mcp-shrimp-task-manager

Version:

Enhanced MCP Shrimp Task Manager with comprehensive LLM integration. A task management tool built for AI Agents, emphasizing chain-of-thought, reflection, and style consistency. Features real GPT-4 ↔ MCP tools communication, comprehensive testing pipeline

1,108 lines 43.1 kB
/** * Codebase Analyzer for the Idea Honing Tool * * This component analyzes the repository structure and identifies components and dependencies. */ import fsPromises from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; import { getAllProjectRules, determineRelevantRules } from './project-rules-integrator.js'; // Get the base directory for the repository const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const REPO_ROOT = path.resolve(__dirname, '../../../../'); // Define file types for analysis const FILE_TYPES = { JAVASCRIPT: ['.js'], TYPESCRIPT: ['.ts', '.tsx'], REACT: ['.jsx', '.tsx'], HTML: ['.html', '.htm'], CSS: ['.css', '.scss', '.sass', '.less'], JSON: ['.json'], MARKDOWN: ['.md'], PYTHON: ['.py'], JAVA: ['.java'], GO: ['.go'], RUST: ['.rs'], RUBY: ['.rb'], PHP: ['.php'], C_SHARP: ['.cs'], C_CPP: ['.c', '.cpp', '.h', '.hpp'], SHELL: ['.sh', '.bash'], YAML: ['.yml', '.yaml'], TOML: ['.toml'], OTHER: [] }; // Define common ignore patterns const IGNORE_PATTERNS = [ 'node_modules', 'dist', 'build', '.git', '.github', '.vscode', '.idea', 'coverage', 'tmp', 'temp', '__pycache__', '*.min.js', '*.min.css', '*.map' ]; /** * Analyzes the repository structure * * @param config - Configuration for the analysis * @returns Promise that resolves with the analysis result */ export async function analyzeRepository(config = {}) { try { // Set default configuration values const rootDir = config.rootDir || REPO_ROOT; const includeDirs = config.includeDirs || []; const excludeDirs = config.excludeDirs || IGNORE_PATTERNS; const includeFileTypes = config.includeFileTypes || []; const maxDepth = config.maxDepth || Infinity; const maxFiles = config.maxFiles || Infinity; const analyzeDependencies = config.analyzeDependencies !== false; const focusKeywords = config.focusKeywords || []; console.time('Repository analysis'); // Scan the repository structure console.time('Directory scan'); const files = await scanDirectory(rootDir, { includeDirs, excludeDirs, includeFileTypes, maxDepth, maxFiles }); console.timeEnd('Directory scan'); // Run component identification and dependency detection in parallel const [components, dependencies, impactedFiles] = await Promise.all([ // Identify components (async () => { console.time('Component identification'); const result = identifyComponents(files, rootDir); console.timeEnd('Component identification'); return result; })(), // Detect dependencies if enabled (async () => { if (!analyzeDependencies) return []; console.time('Dependency detection'); const result = await detectDependencies(files, rootDir); console.timeEnd('Dependency detection'); return result; })(), // Identify impacted files based on focus keywords (async () => { console.time('Impact assessment'); const result = await identifyImpactedFiles(files, focusKeywords); console.timeEnd('Impact assessment'); return result; })() ]); // Apply smart focusing to components based on impacted files console.time('Smart focusing'); const focusedComponents = applySmartFocusing(components, impactedFiles, dependencies); console.timeEnd('Smart focusing'); // Get project rules console.time('Project rules integration'); let projectRules = []; try { // Get all project rules const allRules = await getAllProjectRules(); // Create a mock specification for rule matching const mockSpecification = { id: 'temp', title: focusKeywords.join(' '), sections: [ { id: 'overview', title: 'Overview', content: focusKeywords.join(' '), order: 0 } ], metadata: { authors: [], status: 'draft', tags: focusKeywords, relatedSpecs: [] }, version: 1, changeHistory: [], projectId: '', featureId: '', createdAt: new Date(), updatedAt: new Date() }; // Determine relevant rules projectRules = determineRelevantRules(mockSpecification, allRules); } catch (error) { console.error('Error integrating project rules:', error); } console.timeEnd('Project rules integration'); // Generate the analysis result const analysisResult = { affectedComponents: focusedComponents, impactedFiles, dependencies, projectRules, suggestedQuestions: generateSuggestedQuestions(focusedComponents, impactedFiles, dependencies, projectRules), analysisConfidence: calculateConfidence(focusedComponents, impactedFiles, dependencies, focusKeywords, projectRules.length > 0) }; console.timeEnd('Repository analysis'); return analysisResult; } catch (error) { throw new Error(`Failed to analyze repository: ${error instanceof Error ? error.message : String(error)}`); } } /** * Applies smart focusing to components based on impacted files * * @param components - All identified components * @param impactedFiles - Impacted files * @param dependencies - Dependencies between files * @returns Focused components with updated impact levels */ function applySmartFocusing(components, impactedFiles, dependencies) { // Create a map of components by path const componentMap = new Map(); for (const component of components) { componentMap.set(component.path, component); } // Create a set of impacted file paths const impactedFilePaths = new Set(impactedFiles.map(file => file.path)); // Create a map of file paths to their components const fileToComponentMap = new Map(); for (const component of components) { for (const file of impactedFiles) { if (file.path.startsWith(component.path)) { fileToComponentMap.set(file.path, component.path); } } } // Count impacted files per component const componentImpactCounts = new Map(); for (const file of impactedFiles) { const componentPath = fileToComponentMap.get(file.path); if (componentPath) { componentImpactCounts.set(componentPath, (componentImpactCounts.get(componentPath) || 0) + 1); } } // Create a dependency graph const dependencyGraph = new Map(); for (const dep of dependencies) { if (!dependencyGraph.has(dep.source)) { dependencyGraph.set(dep.source, new Set()); } dependencyGraph.get(dep.source).add(dep.target); } // Identify components that depend on impacted files const dependentComponents = new Set(); for (const [source, targets] of dependencyGraph.entries()) { for (const target of targets) { if (impactedFilePaths.has(target)) { // Find the component that contains this source file for (const component of components) { if (source.startsWith(component.path)) { dependentComponents.add(component.path); break; } } } } } // Update impact levels based on the analysis const focusedComponents = []; for (const component of components) { // Clone the component const focusedComponent = { ...component }; // Determine impact level if (componentImpactCounts.has(component.path)) { const impactCount = componentImpactCounts.get(component.path); if (impactCount > 3) { focusedComponent.impactLevel = 'high'; } else if (impactCount > 1) { focusedComponent.impactLevel = 'medium'; } else { focusedComponent.impactLevel = 'low'; } } else if (dependentComponents.has(component.path)) { // Components that depend on impacted files focusedComponent.impactLevel = 'medium'; } else { // Components not directly impacted focusedComponent.impactLevel = 'low'; } focusedComponents.push(focusedComponent); } // Sort components by impact level return focusedComponents.sort((a, b) => { const impactOrder = { high: 0, medium: 1, low: 2 }; return impactOrder[a.impactLevel] - impactOrder[b.impactLevel]; }); } /** * Scans a directory recursively to find files * * @param dirPath - Path to the directory to scan * @param options - Scan options * @param currentDepth - Current recursion depth * @param fileList - Accumulated list of files * @returns Promise that resolves with the list of files */ async function scanDirectory(dirPath, options, currentDepth = 0, fileList = []) { // Check if we've reached the maximum depth if (currentDepth > options.maxDepth) { return fileList; } // Check if we've reached the maximum number of files if (fileList.length >= options.maxFiles) { return fileList; } try { // Read the directory contents const entries = await fsPromises.readdir(dirPath, { withFileTypes: true }); // Process each entry for (const entry of entries) { const entryPath = path.join(dirPath, entry.name); // Skip excluded directories if (options.excludeDirs.some(pattern => { if (pattern.includes('*')) { const regex = new RegExp(pattern.replace(/\*/g, '.*')); return regex.test(entry.name); } return entry.name === pattern; })) { continue; } // Process directories if (entry.isDirectory()) { // Include only specified directories if provided if (options.includeDirs.length > 0 && !options.includeDirs.some(dir => entryPath.includes(dir))) { continue; } // Recursively scan subdirectories await scanDirectory(entryPath, options, currentDepth + 1, fileList); } // Process files else if (entry.isFile()) { // Include only specified file types if provided if (options.includeFileTypes.length > 0) { const ext = path.extname(entry.name).toLowerCase(); if (!options.includeFileTypes.includes(ext)) { continue; } } // Add the file to the list fileList.push(entryPath); // Check if we've reached the maximum number of files if (fileList.length >= options.maxFiles) { break; } } } return fileList; } catch (error) { console.error(`Error scanning directory ${dirPath}: ${error}`); return fileList; } } /** * Identifies components in the repository * * @param files - List of files in the repository * @param rootDir - Root directory of the repository * @returns Array of identified components */ function identifyComponents(files, rootDir) { // Group files by directory const dirGroups = {}; for (const file of files) { const dir = path.dirname(file); if (!dirGroups[dir]) { dirGroups[dir] = []; } dirGroups[dir].push(file); } // Identify components based on directory structure const components = []; // Process each directory group for (const [dir, dirFiles] of Object.entries(dirGroups)) { // Skip directories with too few files if (dirFiles.length < 2) { continue; } // Determine component type based on file types const fileTypes = dirFiles.map(file => path.extname(file).toLowerCase()); const hasTypeScript = fileTypes.some(ext => FILE_TYPES.TYPESCRIPT.includes(ext)); const hasReact = fileTypes.some(ext => FILE_TYPES.REACT.includes(ext)); const hasPython = fileTypes.some(ext => FILE_TYPES.PYTHON.includes(ext)); // Generate component description let description = 'Component based on directory structure'; if (hasTypeScript) description += ', using TypeScript'; if (hasReact) description += ', using React'; if (hasPython) description += ', using Python'; // Create the component const relativePath = path.relative(rootDir, dir); const component = { name: path.basename(dir), path: relativePath, description, impactLevel: 'low' // Default impact level }; components.push(component); } return components; } /** * Detects dependencies between files * * @param files - List of files in the repository * @param rootDir - Root directory of the repository * @returns Promise that resolves with the detected dependencies */ async function detectDependencies(files, rootDir) { const dependencies = []; // Create a map of files for faster lookup const fileMap = new Set(files); // Process each file for (const file of files) { const ext = path.extname(file).toLowerCase(); // Skip non-code files if (![...FILE_TYPES.JAVASCRIPT, ...FILE_TYPES.TYPESCRIPT, ...FILE_TYPES.PYTHON, ...FILE_TYPES.JAVA, ...FILE_TYPES.GO, ...FILE_TYPES.RUST, ...FILE_TYPES.RUBY, ...FILE_TYPES.PHP].includes(ext)) { continue; } try { // Read the file content const content = await fsPromises.readFile(file, 'utf-8'); // Detect imports based on file type if ([...FILE_TYPES.JAVASCRIPT, ...FILE_TYPES.TYPESCRIPT].includes(ext)) { // Detect JavaScript/TypeScript imports await detectJsImports(file, content, rootDir, fileMap, dependencies); // Detect class inheritance and usage detectJsClassRelations(file, content, rootDir, dependencies); // Detect function calls detectJsFunctionCalls(file, content, rootDir, dependencies); } else if (FILE_TYPES.PYTHON.includes(ext)) { // Detect Python imports detectPythonImports(file, content, rootDir, dependencies); // Detect Python class inheritance detectPythonClassRelations(file, content, rootDir, dependencies); } else if (FILE_TYPES.JAVA.includes(ext)) { // Detect Java imports detectJavaImports(file, content, rootDir, dependencies); } else if (FILE_TYPES.GO.includes(ext)) { // Detect Go imports detectGoImports(file, content, rootDir, dependencies); } else if (FILE_TYPES.RUST.includes(ext)) { // Detect Rust imports detectRustImports(file, content, rootDir, dependencies); } } catch (error) { console.error(`Error detecting dependencies in ${file}: ${error}`); } } // Detect transitive dependencies const transitiveDependencies = detectTransitiveDependencies(dependencies); dependencies.push(...transitiveDependencies); // Detect circular dependencies const circularDependencies = detectCircularDependencies(dependencies); // Add circular dependency information for (const circular of circularDependencies) { dependencies.push({ source: circular[0], target: circular[circular.length - 1], type: 'data', description: `Circular dependency detected: ${circular.join(' -> ')} -> ${circular[0]}` }); } return dependencies; } /** * Detects JavaScript/TypeScript imports */ async function detectJsImports(file, content, rootDir, fileMap, dependencies) { // Detect ES6 imports const importRegex = /import\s+(?:{[^}]*}|\*\s+as\s+[^\s,]+|[^\s,]+)\s+from\s+['"]([^'"]+)['"]/g; let match; while ((match = importRegex.exec(content)) !== null) { const importPath = match[1]; // Skip external dependencies if (!importPath.startsWith('.') && !importPath.startsWith('/')) { continue; } // Resolve the import path let resolvedPath; try { if (importPath.startsWith('.')) { resolvedPath = path.resolve(path.dirname(file), importPath); } else { resolvedPath = path.resolve(rootDir, importPath.substring(1)); } // Add extensions if needed if (!path.extname(resolvedPath)) { // Try with different extensions for (const ext of [...FILE_TYPES.JAVASCRIPT, ...FILE_TYPES.TYPESCRIPT]) { const testPath = `${resolvedPath}${ext}`; if (fileMap.has(testPath)) { resolvedPath = testPath; break; } } // Try with index files if (!fileMap.has(resolvedPath)) { for (const ext of [...FILE_TYPES.JAVASCRIPT, ...FILE_TYPES.TYPESCRIPT]) { const indexPath = path.join(resolvedPath, `index${ext}`); if (fileMap.has(indexPath)) { resolvedPath = indexPath; break; } } } } // Skip if the resolved path is not in the file map if (!fileMap.has(resolvedPath)) { continue; } // Create the dependency const dependency = { source: path.relative(rootDir, file), target: path.relative(rootDir, resolvedPath), type: 'imports', description: `${path.basename(file)} imports from ${path.basename(resolvedPath)}` }; dependencies.push(dependency); } catch (error) { console.error(`Error resolving import path ${importPath} in ${file}: ${error}`); } } // Detect CommonJS requires const requireRegex = /(?:const|let|var)\s+(?:{[^}]*}|[^\s,]+)\s+=\s+require\(['"]([^'"]+)['"]\)/g; while ((match = requireRegex.exec(content)) !== null) { const importPath = match[1]; // Skip external dependencies if (!importPath.startsWith('.') && !importPath.startsWith('/')) { continue; } // Resolve the import path (similar to above) let resolvedPath; try { if (importPath.startsWith('.')) { resolvedPath = path.resolve(path.dirname(file), importPath); } else { resolvedPath = path.resolve(rootDir, importPath.substring(1)); } // Add extensions if needed (similar to above) if (!path.extname(resolvedPath)) { for (const ext of [...FILE_TYPES.JAVASCRIPT, ...FILE_TYPES.TYPESCRIPT]) { const testPath = `${resolvedPath}${ext}`; if (fileMap.has(testPath)) { resolvedPath = testPath; break; } } } // Skip if the resolved path is not in the file map if (!fileMap.has(resolvedPath)) { continue; } // Create the dependency const dependency = { source: path.relative(rootDir, file), target: path.relative(rootDir, resolvedPath), type: 'imports', description: `${path.basename(file)} requires ${path.basename(resolvedPath)}` }; dependencies.push(dependency); } catch (error) { console.error(`Error resolving require path ${importPath} in ${file}: ${error}`); } } } /** * Detects JavaScript/TypeScript class inheritance and usage */ function detectJsClassRelations(file, content, rootDir, dependencies) { // Detect class inheritance const extendsRegex = /class\s+([^\s]+)\s+extends\s+([^\s{]+)/g; let match; while ((match = extendsRegex.exec(content)) !== null) { const childClass = match[1]; const parentClass = match[2]; // Create the dependency const dependency = { source: path.relative(rootDir, file), target: parentClass, // We don't know the file, just the class name type: 'extends', description: `${childClass} extends ${parentClass}` }; dependencies.push(dependency); } // Detect class instantiation const newRegex = /new\s+([A-Z][a-zA-Z0-9_]*)\(/g; while ((match = newRegex.exec(content)) !== null) { const className = match[1]; // Create the dependency const dependency = { source: path.relative(rootDir, file), target: className, // We don't know the file, just the class name type: 'uses', description: `${path.basename(file)} instantiates ${className}` }; dependencies.push(dependency); } } /** * Detects JavaScript/TypeScript function calls */ function detectJsFunctionCalls(file, content, rootDir, dependencies) { // Detect function calls const functionCallRegex = /\b([a-zA-Z][a-zA-Z0-9_]*)\s*\(/g; let match; while ((match = functionCallRegex.exec(content)) !== null) { const functionName = match[1]; // Skip common functions and keywords if (['if', 'for', 'while', 'switch', 'catch', 'function', 'return', 'console', 'require', 'import'].includes(functionName)) { continue; } // Create the dependency const dependency = { source: path.relative(rootDir, file), target: functionName, // We don't know the file, just the function name type: 'uses', description: `${path.basename(file)} calls function ${functionName}` }; dependencies.push(dependency); } } /** * Detects Python imports */ function detectPythonImports(file, content, rootDir, dependencies) { // Detect Python imports const importRegex = /(?:from\s+([^\s]+)\s+import|import\s+([^\s,]+))/g; let match; while ((match = importRegex.exec(content)) !== null) { const importPath = match[1] || match[2]; // Skip standard library imports if (!importPath.includes('.')) { continue; } // Create the dependency const dependency = { source: path.relative(rootDir, file), target: importPath.replace(/\./g, '/'), type: 'imports', description: `${path.basename(file)} imports ${importPath}` }; dependencies.push(dependency); } } /** * Detects Python class inheritance */ function detectPythonClassRelations(file, content, rootDir, dependencies) { // Detect class inheritance const classRegex = /class\s+([^\s(]+)\s*\(([^)]+)\):/g; let match; while ((match = classRegex.exec(content)) !== null) { const childClass = match[1]; const parentClasses = match[2].split(',').map(c => c.trim()); for (const parentClass of parentClasses) { // Skip object (base class) if (parentClass === 'object') { continue; } // Create the dependency const dependency = { source: path.relative(rootDir, file), target: parentClass, // We don't know the file, just the class name type: 'extends', description: `${childClass} inherits from ${parentClass}` }; dependencies.push(dependency); } } } /** * Detects Java imports */ function detectJavaImports(file, content, rootDir, dependencies) { // Detect Java imports const importRegex = /import\s+([^;]+);/g; let match; while ((match = importRegex.exec(content)) !== null) { const importPath = match[1].trim(); // Create the dependency const dependency = { source: path.relative(rootDir, file), target: importPath.replace(/\./g, '/'), type: 'imports', description: `${path.basename(file)} imports ${importPath}` }; dependencies.push(dependency); } } /** * Detects Go imports */ function detectGoImports(file, content, rootDir, dependencies) { // Detect Go imports const importRegex = /import\s+\(\s*((?:"[^"]+"\s*)+)\s*\)/g; let match; while ((match = importRegex.exec(content)) !== null) { const importBlock = match[1]; const importPaths = importBlock.match(/"([^"]+)"/g); if (importPaths) { for (const importPath of importPaths) { const cleanPath = importPath.replace(/"/g, ''); // Create the dependency const dependency = { source: path.relative(rootDir, file), target: cleanPath, type: 'imports', description: `${path.basename(file)} imports ${cleanPath}` }; dependencies.push(dependency); } } } } /** * Detects Rust imports */ function detectRustImports(file, content, rootDir, dependencies) { // Detect Rust imports const useRegex = /use\s+([^;]+);/g; let match; while ((match = useRegex.exec(content)) !== null) { const importPath = match[1].trim(); // Create the dependency const dependency = { source: path.relative(rootDir, file), target: importPath.replace(/::/g, '/'), type: 'imports', description: `${path.basename(file)} uses ${importPath}` }; dependencies.push(dependency); } } /** * Detects transitive dependencies * * @param dependencies - Direct dependencies * @returns Array of transitive dependencies */ function detectTransitiveDependencies(dependencies) { const transitiveDependencies = []; // Build a dependency graph const graph = {}; for (const dep of dependencies) { if (!graph[dep.source]) { graph[dep.source] = new Set(); } graph[dep.source].add(dep.target); } // Find transitive dependencies using depth-first search for (const source in graph) { const visited = new Set(); const stack = []; // Add direct dependencies to the stack for (const target of graph[source]) { stack.push(target); } while (stack.length > 0) { const current = stack.pop(); // Skip if already visited if (visited.has(current)) { continue; } visited.add(current); // Skip if not in the graph if (!graph[current]) { continue; } // Add transitive dependencies to the stack for (const target of graph[current]) { // Skip direct dependencies if (graph[source] && graph[source].has(target)) { continue; } // Skip if already visited if (visited.has(target)) { continue; } stack.push(target); // Add transitive dependency transitiveDependencies.push({ source, target, type: 'data', description: `${source} indirectly depends on ${target} through ${current}` }); } } } return transitiveDependencies; } /** * Detects circular dependencies * * @param dependencies - All dependencies * @returns Array of circular dependency chains */ function detectCircularDependencies(dependencies) { const circularDependencies = []; // Build a dependency graph const graph = {}; for (const dep of dependencies) { if (!graph[dep.source]) { graph[dep.source] = []; } if (!graph[dep.target]) { graph[dep.target] = []; } // Add the dependency if it doesn't already exist if (!graph[dep.source].includes(dep.target)) { graph[dep.source].push(dep.target); } } // Find circular dependencies using depth-first search for (const source in graph) { const visited = new Set(); const path = []; function dfs(node) { // Skip if already in the current path (circular dependency) if (path.includes(node)) { const cycle = path.slice(path.indexOf(node)); cycle.push(node); // Complete the cycle // Check if this cycle is already in the results const cycleStr = cycle.join(','); if (!circularDependencies.some(c => c.join(',') === cycleStr)) { circularDependencies.push(cycle); } return; } // Skip if already visited if (visited.has(node)) { return; } visited.add(node); path.push(node); // Visit neighbors for (const neighbor of graph[node] || []) { dfs(neighbor); } path.pop(); } dfs(source); } return circularDependencies; } /** * Identifies files that may be impacted by the proposed changes * * @param files - List of files in the repository * @param keywords - Keywords to focus the analysis on * @returns Array of impacted files */ async function identifyImpactedFiles(files, keywords) { // If no keywords are provided, return an empty array if (!keywords || keywords.length === 0) { return []; } const impactedFiles = []; const fileContentCache = {}; // Process each file for (const file of files) { let impactScore = 0; const reasons = []; // Check if the file path contains any of the keywords const pathMatchedKeywords = keywords.filter(keyword => file.toLowerCase().includes(keyword.toLowerCase())); if (pathMatchedKeywords.length > 0) { impactScore += pathMatchedKeywords.length * 2; // Higher weight for path matches reasons.push(`File path contains keywords: ${pathMatchedKeywords.join(', ')}`); } // Check file extension to determine if it's a code file const ext = path.extname(file).toLowerCase(); const isCodeFile = [ ...FILE_TYPES.JAVASCRIPT, ...FILE_TYPES.TYPESCRIPT, ...FILE_TYPES.PYTHON, ...FILE_TYPES.JAVA, ...FILE_TYPES.GO, ...FILE_TYPES.RUST, ...FILE_TYPES.RUBY, ...FILE_TYPES.PHP, ...FILE_TYPES.C_SHARP, ...FILE_TYPES.C_CPP ].includes(ext); // Check file content for keywords if it's a code file if (isCodeFile) { try { // Read the file content (or use cached content) if (!fileContentCache[file]) { fileContentCache[file] = await fsPromises.readFile(file, 'utf-8'); } const content = fileContentCache[file]; // Check for keyword matches in content const contentMatchedKeywords = keywords.filter(keyword => content.toLowerCase().includes(keyword.toLowerCase())); if (contentMatchedKeywords.length > 0) { impactScore += contentMatchedKeywords.length; reasons.push(`File content contains keywords: ${contentMatchedKeywords.join(', ')}`); // Check for keyword matches in function/class names (higher relevance) const functionMatches = detectFunctionMatches(content, contentMatchedKeywords); if (functionMatches.length > 0) { impactScore += functionMatches.length * 3; // Higher weight for function matches reasons.push(`File contains relevant functions/classes: ${functionMatches.join(', ')}`); } } } catch (error) { console.error(`Error reading file ${file}: ${error}`); } } // Add the file to the impacted files list if it has a non-zero impact score if (impactScore > 0) { // Determine impact level let impactLevel = 'low'; if (impactScore > 5) { impactLevel = 'high'; } else if (impactScore > 2) { impactLevel = 'medium'; } // Create the file impact const fileImpact = { path: file, reason: reasons.join('; '), suggestedChanges: impactScore > 3 ? 'May need significant modifications' : 'May need minor modifications' }; impactedFiles.push(fileImpact); } } // Sort impacted files by relevance (impact score) return impactedFiles.sort((a, b) => { // Calculate impact scores based on reasons const scoreA = calculateImpactScore(a.reason); const scoreB = calculateImpactScore(b.reason); return scoreB - scoreA; }); } /** * Calculates an impact score based on the reason string * * @param reason - Reason string * @returns Impact score */ function calculateImpactScore(reason) { let score = 0; // Path matches are more significant if (reason.includes('File path contains keywords')) { score += 5; } // Function/class matches are very significant if (reason.includes('File contains relevant functions/classes')) { score += 10; } // Content matches are somewhat significant if (reason.includes('File content contains keywords')) { score += 3; } // Count the number of keywords matched const keywordMatches = (reason.match(/keywords:/g) || []).length; score += keywordMatches * 2; return score; } /** * Detects function or class names that match keywords * * @param content - File content * @param keywords - Keywords to match * @returns Array of matched function or class names */ function detectFunctionMatches(content, keywords) { const matches = []; // Match function declarations const functionRegex = /function\s+([a-zA-Z0-9_]+)/g; let match; while ((match = functionRegex.exec(content)) !== null) { const functionName = match[1]; // Check if the function name contains any of the keywords if (keywords.some(keyword => functionName.toLowerCase().includes(keyword.toLowerCase()))) { matches.push(functionName); } } // Match class declarations const classRegex = /class\s+([a-zA-Z0-9_]+)/g; while ((match = classRegex.exec(content)) !== null) { const className = match[1]; // Check if the class name contains any of the keywords if (keywords.some(keyword => className.toLowerCase().includes(keyword.toLowerCase()))) { matches.push(className); } } // Match method declarations const methodRegex = /(?:public|private|protected)?\s+(?:static\s+)?(?:async\s+)?(?:function\s+)?([a-zA-Z0-9_]+)\s*\(/g; while ((match = methodRegex.exec(content)) !== null) { const methodName = match[1]; // Skip constructor if (methodName === 'constructor') { continue; } // Check if the method name contains any of the keywords if (keywords.some(keyword => methodName.toLowerCase().includes(keyword.toLowerCase()))) { matches.push(methodName); } } return [...new Set(matches)]; // Remove duplicates } /** * Generates suggested questions based on the analysis results * * @param components - Identified components * @param impactedFiles - Impacted files * @param dependencies - Detected dependencies * @param projectRules - Relevant project rules * @returns Array of suggested questions */ function generateSuggestedQuestions(components, impactedFiles, dependencies, projectRules = []) { const questions = []; // Questions about components if (components.length > 0) { const highImpactComponents = components.filter(c => c.impactLevel === 'high'); if (highImpactComponents.length > 0) { questions.push(`The analysis identified ${highImpactComponents.length} high-impact components. How will you ensure these components are properly updated?`); } else { questions.push(`Which of the ${components.length} identified components will be most affected by this change?`); } questions.push('Are there any components missing from the analysis that should be considered?'); } else { questions.push('What components will be created or modified for this feature?'); } // Questions about impacted files if (impactedFiles.length > 0) { questions.push(`The analysis identified ${impactedFiles.length} potentially impacted files. Are there any critical files missing from this list?`); // Check for files with suggested changes const filesWithSuggestedChanges = impactedFiles.filter(f => f.suggestedChanges); if (filesWithSuggestedChanges.length > 0) { questions.push(`${filesWithSuggestedChanges.length} files may need significant modifications. What specific changes will be made to these files?`); } } else { questions.push('What existing files will need to be modified for this feature?'); } // Questions about dependencies if (dependencies.length > 0) { // Check for circular dependencies const circularDeps = dependencies.filter(d => d.description.includes('Circular dependency')); if (circularDeps.length > 0) { questions.push(`The analysis detected ${circularDeps.length} circular dependencies. How will these be addressed?`); } else { questions.push('Are there any additional dependencies that need to be considered?'); } } else { questions.push('What dependencies will this feature have on existing code?'); } // Questions about project rules if (projectRules.length > 0) { questions.push(`The analysis identified ${projectRules.length} relevant project rules. How will you ensure compliance with these rules?`); // Group rules by section const rulesBySection = {}; for (const rule of projectRules) { rulesBySection[rule.sectionId] = (rulesBySection[rule.sectionId] || 0) + 1; } // Ask about sections with multiple rules for (const [sectionId, count] of Object.entries(rulesBySection)) { if (count > 1) { questions.push(`The "${sectionId}" section has ${count} applicable rules. What specific considerations are needed for this section?`); } } } else { questions.push('Are there any project rules or guidelines that should be considered for this implementation?'); } // General questions questions.push('What are the potential risks or challenges in implementing this feature?'); questions.push('Are there any performance considerations for this implementation?'); questions.push('What testing approach would be most appropriate for this feature?'); return questions; } /** * Calculates the confidence score for the analysis * * @param components - Identified components * @param impactedFiles - Impacted files * @param dependencies - Detected dependencies * @param focusKeywords - Keywords used to focus the analysis * @param hasProjectRules - Whether project rules were successfully integrated * @returns Confidence score (0-1) */ function calculateConfidence(components, impactedFiles, dependencies, focusKeywords, hasProjectRules = false) { // Base confidence let confidence = 0.5; // Adjust based on components if (components.length > 0) { confidence += 0.1; // Bonus for high-impact components const highImpactComponents = components.filter(c => c.impactLevel === 'high'); if (highImpactComponents.length > 0) { confidence += 0.05; } } // Adjust based on impacted files if (impactedFiles.length > 0) { confidence += 0.1; // Bonus for files with suggested changes const filesWithSuggestedChanges = impactedFiles.filter(f => f.suggestedChanges); if (filesWithSuggestedChanges.length > 0) { confidence += 0.05; } } // Adjust based on dependencies if (dependencies.length > 0) { confidence += 0.1; // Bonus for transitive and circular dependency detection const transitiveDeps = dependencies.filter(d => d.description.includes('indirectly depends')); const circularDeps = dependencies.filter(d => d.description.includes('Circular dependency')); if (transitiveDeps.length > 0 || circularDeps.length > 0) { confidence += 0.05; } } // Adjust based on focus keywords if (focusKeywords.length > 0) { confidence += 0.1; } else { confidence -= 0.2; // Significant penalty for no focus keywords } // Adjust based on project rules integration if (hasProjectRules) { confidence += 0.1; } // Ensure the confidence is between 0 and 1 return Math.max(0, Math.min(1, confidence)); } //# sourceMappingURL=codebase-analyzer.js.map