UNPKG

@aaswe/codebase-ai

Version:

AI-Assisted Software Engineering (AASWE) - Rich codebase context for IDE LLMs

811 lines 33.5 kB
"use strict"; /** * Project Analysis Service * * Core orchestrator for automatic project analysis and TTL generation. * This service coordinates between AST analysis, RDF generation, and knowledge management * to provide comprehensive project understanding and context generation. */ 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; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProjectAnalysisService = void 0; const events_1 = require("events"); const fs = __importStar(require("fs/promises")); const path = __importStar(require("path")); const logger_1 = __importDefault(require("../../utils/logger")); const CodeIngestionService_1 = require("../layer1/code-ingestion/CodeIngestionService"); const ModuleKnowledgeManager_1 = require("../layer1/module-knowledge/ModuleKnowledgeManager"); const TypeScriptAnalyzer_1 = require("../layer1/ast-analyzer/TypeScriptAnalyzer"); const PythonAnalyzer_1 = require("../layer1/ast-analyzer/PythonAnalyzer"); const JavaAnalyzer_1 = require("../layer1/ast-analyzer/JavaAnalyzer"); const GoAnalyzer_1 = require("../layer1/ast-analyzer/GoAnalyzer"); const RustAnalyzer_1 = require("../layer1/ast-analyzer/RustAnalyzer"); const CppAnalyzer_1 = require("../layer1/ast-analyzer/CppAnalyzer"); /** * Project Analysis Service * * Provides comprehensive project analysis capabilities including: * - Project structure detection and analysis * - Multi-language code analysis and AST generation * - Automatic TTL knowledge file generation * - Real-time file watching and incremental updates * - Integration with knowledge management system */ class ProjectAnalysisService extends events_1.EventEmitter { codeIngestionService; moduleKnowledgeManager; analyzers; config; isInitialized = false; constructor(config) { super(); this.config = { outputDirectory: '.aaswe/knowledge', languages: ['typescript', 'javascript', 'python', 'java', 'go', 'rust', 'cpp'], includePatterns: ['**/*.ts', '**/*.js', '**/*.py', '**/*.java', '**/*.go', '**/*.rs', '**/*.cpp', '**/*.hpp'], excludePatterns: [ '**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**', '**/*.test.*', '**/*.spec.*', '**/coverage/**', '**/.next/**', '**/.nuxt/**', '**/venv/**', '**/env/**', '**/.venv/**', '**/.env/**', '**/__pycache__/**', '**/*.pyc', '**/.pytest_cache/**', '**/target/**', '**/vendor/**' ], generateTTL: true, enableWatching: true, preserveBusinessContext: true, analysisDepth: 'detailed', ...config }; // Initialize analyzers this.analyzers = new Map(); this.setupAnalyzers(); // Initialize services this.moduleKnowledgeManager = new ModuleKnowledgeManager_1.ModuleKnowledgeManager({ autoValidate: true, preserveBusinessContext: this.config.preserveBusinessContext || true, enableConflictResolution: true, enableLLMPreview: true }); // Use TypeScript analyzer as primary for code ingestion const primaryAnalyzer = this.analyzers.get('typescript') || this.analyzers.get('javascript'); this.codeIngestionService = new CodeIngestionService_1.CodeIngestionService(primaryAnalyzer, { supportedLanguages: this.config.languages || ['typescript', 'javascript'], defaultExcludePatterns: this.config.excludePatterns || [] }); this.setupEventHandlers(); } /** * Initialize the Project Analysis Service */ async initialize() { if (this.isInitialized) { return; } try { logger_1.default.info('Initializing Project Analysis Service', { rootPath: this.config.rootPath, languages: this.config.languages, analysisDepth: this.config.analysisDepth }); // Initialize sub-services await this.moduleKnowledgeManager.initialize(); await this.codeIngestionService.initialize(); // Ensure output directory exists if (this.config.outputDirectory) { await fs.mkdir(path.resolve(this.config.rootPath, this.config.outputDirectory), { recursive: true }); } this.isInitialized = true; logger_1.default.info('Project Analysis Service initialized successfully'); } catch (error) { logger_1.default.error('Failed to initialize Project Analysis Service', { error }); throw error; } } /** * Analyze entire project and generate TTL files */ async analyzeProject() { this.ensureInitialized(); const analysisId = `analysis_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const startTime = new Date(); logger_1.default.info('Starting comprehensive project analysis', { analysisId, rootPath: this.config.rootPath, analysisDepth: this.config.analysisDepth }); try { // Phase 1: Project Structure Discovery logger_1.default.info('Phase 1: Discovering project structure'); const projectStructure = await this.discoverProjectStructure(); this.emit('structureDiscovered', { analysisId, structure: projectStructure }); // Phase 2: File Discovery and Filtering logger_1.default.info('Phase 2: Discovering and filtering source files'); const sourceFiles = await this.discoverSourceFiles(); // Phase 3: Code Analysis logger_1.default.info('Phase 3: Analyzing source code', { fileCount: sourceFiles.length }); const analysisResults = await this.analyzeSourceFiles(sourceFiles, analysisId); // Phase 4: TTL Generation (if enabled) let ttlResults = []; if (this.config.generateTTL) { logger_1.default.info('Phase 4: Generating TTL knowledge files'); ttlResults = await this.generateTTLFiles(analysisResults, analysisId); } // Phase 5: Setup Watching (if enabled) if (this.config.enableWatching) { logger_1.default.info('Phase 5: Setting up file watching'); await this.setupProjectWatching(); } const endTime = new Date(); const duration = endTime.getTime() - startTime.getTime(); // Compile final results const result = { projectPath: this.config.rootPath, analysisId, startTime, endTime, duration, summary: this.compileSummary(analysisResults, ttlResults), files: analysisResults.map(result => ({ filePath: result.filePath, language: result.language, status: result.success ? 'success' : 'error', ttlGenerated: result.ttlGenerated || false, error: result.error })), errors: analysisResults .filter(result => !result.success) .map(result => ({ filePath: result.filePath, error: result.error || 'Unknown error', phase: 'analysis' })), warnings: [], recommendations: this.generateRecommendations(projectStructure, analysisResults) }; logger_1.default.info('Project analysis completed successfully', { analysisId, duration, totalFiles: result.summary.totalFiles, analyzedFiles: result.summary.analyzedFiles, ttlFilesGenerated: result.summary.ttlFilesGenerated }); this.emit('analysisCompleted', result); return result; } catch (error) { const endTime = new Date(); const duration = endTime.getTime() - startTime.getTime(); logger_1.default.error('Project analysis failed', { analysisId, error, duration }); const errorResult = { projectPath: this.config.rootPath, analysisId, startTime, endTime, duration, summary: { totalFiles: 0, analyzedFiles: 0, skippedFiles: 0, errorFiles: 1, ttlFilesGenerated: 0, languageBreakdown: {} }, files: [], errors: [{ filePath: this.config.rootPath, error: error instanceof Error ? error.message : 'Unknown error', phase: 'discovery' }], warnings: [], recommendations: [] }; this.emit('analysisFailed', { analysisId, error, result: errorResult }); return errorResult; } } /** * Analyze specific files (incremental analysis) */ async analyzeFiles(filePaths) { this.ensureInitialized(); const analysisId = `incremental_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const startTime = new Date(); logger_1.default.info('Starting incremental file analysis', { analysisId, fileCount: filePaths.length, files: filePaths }); try { // Filter and validate file paths const validFiles = await this.validateFilePaths(filePaths); // Analyze files const analysisResults = await this.analyzeSourceFiles(validFiles, analysisId); // Generate TTL files if enabled let ttlResults = []; if (this.config.generateTTL) { ttlResults = await this.generateTTLFiles(analysisResults, analysisId); } const endTime = new Date(); const duration = endTime.getTime() - startTime.getTime(); const result = { projectPath: this.config.rootPath, analysisId, startTime, endTime, duration, summary: this.compileSummary(analysisResults, ttlResults), files: analysisResults.map(result => ({ filePath: result.filePath, language: result.language, status: result.success ? 'success' : 'error', ttlGenerated: result.ttlGenerated || false, error: result.error })), errors: analysisResults .filter(result => !result.success) .map(result => ({ filePath: result.filePath, error: result.error || 'Unknown error', phase: 'analysis' })), warnings: [], recommendations: [] }; logger_1.default.info('Incremental analysis completed', { analysisId, duration, analyzedFiles: result.summary.analyzedFiles }); this.emit('incrementalAnalysisCompleted', result); return result; } catch (error) { logger_1.default.error('Incremental analysis failed', { analysisId, error }); throw error; } } /** * Get project structure information */ async getProjectStructure() { this.ensureInitialized(); return await this.discoverProjectStructure(); } /** * Get analysis status and metrics */ getAnalysisMetrics() { return { codeIngestion: this.codeIngestionService.getMetrics(), knowledgeFiles: { totalFiles: 0, // Would be populated from module knowledge manager validFiles: 0, businessContextEnhanced: 0 }, analyzers: Array.from(this.analyzers.keys()), lastAnalysis: new Date() }; } /** * Shutdown the service */ async shutdown() { logger_1.default.info('Shutting down Project Analysis Service'); try { await this.codeIngestionService.shutdown(); this.removeAllListeners(); this.isInitialized = false; logger_1.default.info('Project Analysis Service shutdown completed'); } catch (error) { logger_1.default.error('Error during Project Analysis Service shutdown', { error }); throw error; } } // Private methods setupAnalyzers() { this.analyzers.set('typescript', new TypeScriptAnalyzer_1.TypeScriptAnalyzer()); this.analyzers.set('javascript', new TypeScriptAnalyzer_1.TypeScriptAnalyzer()); // TypeScript analyzer handles JS too this.analyzers.set('python', new PythonAnalyzer_1.PythonAnalyzer()); this.analyzers.set('java', new JavaAnalyzer_1.JavaAnalyzer()); this.analyzers.set('go', new GoAnalyzer_1.GoAnalyzer()); this.analyzers.set('rust', new RustAnalyzer_1.RustAnalyzer()); this.analyzers.set('cpp', new CppAnalyzer_1.CppAnalyzer()); } setupEventHandlers() { // Code ingestion events this.codeIngestionService.on('analysisCompleted', (result) => { this.emit('codeAnalysisCompleted', result); }); this.codeIngestionService.on('jobCompleted', (job) => { this.emit('analysisJobCompleted', job); }); this.codeIngestionService.on('jobFailed', (job) => { this.emit('analysisJobFailed', job); }); // Module knowledge events this.moduleKnowledgeManager.on('file_updated', (event) => { this.emit('ttlFileUpdated', event); }); this.moduleKnowledgeManager.on('file_validated', (event) => { this.emit('ttlFileValidated', event); }); } async discoverProjectStructure() { const rootPath = this.config.rootPath; try { // Discover package files const packageFiles = await this.discoverPackageFiles(rootPath); // Discover source files const sourceFiles = await this.discoverSourceFiles(); // Discover directories const directories = await this.discoverDirectories(rootPath); // Determine project type const projectType = this.determineProjectType(packageFiles, sourceFiles); // Detect frameworks const frameworks = this.detectFrameworks(packageFiles, sourceFiles); // Detect build tools const buildTools = this.detectBuildTools(packageFiles); return { rootPath, packageFiles, sourceFiles, directories, projectType, frameworks, buildTools }; } catch (error) { logger_1.default.error('Failed to discover project structure', { rootPath, error }); throw error; } } async discoverPackageFiles(rootPath) { const packageFiles = []; const packageFilePatterns = [ { pattern: 'package.json', type: 'package.json' }, { pattern: 'requirements.txt', type: 'requirements.txt' }, { pattern: 'pom.xml', type: 'pom.xml' }, { pattern: 'go.mod', type: 'go.mod' }, { pattern: 'Cargo.toml', type: 'Cargo.toml' }, { pattern: 'CMakeLists.txt', type: 'CMakeLists.txt' } ]; for (const { pattern, type } of packageFilePatterns) { const filePath = path.join(rootPath, pattern); try { await fs.access(filePath); const dependencies = await this.extractDependencies(filePath, type); packageFiles.push({ path: filePath, type, dependencies }); } catch { // File doesn't exist, continue } } return packageFiles; } async discoverSourceFiles() { const { glob } = await Promise.resolve().then(() => __importStar(require('glob'))); const sourceFiles = []; for (const pattern of this.config.includePatterns) { try { const files = await glob(pattern, { cwd: this.config.rootPath, ignore: this.config.excludePatterns || [], absolute: true }); for (const filePath of files) { try { const stats = await fs.stat(filePath); const language = this.getLanguageFromFile(filePath); sourceFiles.push({ path: filePath, language, size: stats.size, lastModified: stats.mtime }); } catch (error) { logger_1.default.warn('Failed to stat source file', { filePath, error }); } } } catch (error) { logger_1.default.warn('Failed to glob pattern', { pattern, error }); } } return sourceFiles; } async discoverDirectories(rootPath) { const directories = []; try { const entries = await fs.readdir(rootPath, { withFileTypes: true }); for (const entry of entries) { if (entry.isDirectory() && !entry.name.startsWith('.')) { const dirPath = path.join(rootPath, entry.name); const type = this.classifyDirectory(entry.name); const fileCount = await this.countFilesInDirectory(dirPath); directories.push({ path: dirPath, type, fileCount }); } } } catch (error) { logger_1.default.warn('Failed to discover directories', { rootPath, error }); } return directories; } async analyzeSourceFiles(sourceFiles, analysisId) { const results = []; const batchSize = 10; // Process files in batches logger_1.default.info('Analyzing source files', { totalFiles: sourceFiles.length, batchSize, analysisId }); for (let i = 0; i < sourceFiles.length; i += batchSize) { const batch = sourceFiles.slice(i, i + batchSize); const batchPromises = batch.map(async (file) => { try { const analyzer = this.analyzers.get(file.language); if (!analyzer) { return { filePath: file.path, language: file.language, success: false, error: `No analyzer available for language: ${file.language}` }; } logger_1.default.debug('Analyzing file', { filePath: file.path, language: file.language }); const analysisResult = await analyzer.analyzeFile(file.path); return { filePath: file.path, language: file.language, success: true, analysisResult, ttlGenerated: false }; } catch (error) { logger_1.default.error('Failed to analyze file', { filePath: file.path, error }); return { filePath: file.path, language: file.language, success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } }); const batchResults = await Promise.all(batchPromises); results.push(...batchResults); // Emit progress this.emit('analysisProgress', { analysisId, processed: Math.min(i + batchSize, sourceFiles.length), total: sourceFiles.length, currentBatch: batchResults }); } return results; } async generateTTLFiles(analysisResults, analysisId) { const ttlResults = []; logger_1.default.info('Generating TTL knowledge files', { fileCount: analysisResults.filter(r => r.success).length, analysisId }); for (const result of analysisResults) { if (!result.success || !result.analysisResult) { continue; } try { const ttlResult = await this.moduleKnowledgeManager.updateKnowledgeFileFromCode(result.filePath, result.analysisResult); if (ttlResult.success) { result.ttlGenerated = true; ttlResults.push({ sourceFile: result.filePath, ttlFile: ttlResult.data?.filePath, success: true }); } else { ttlResults.push({ sourceFile: result.filePath, success: false, error: ttlResult.error }); } } catch (error) { logger_1.default.error('Failed to generate TTL file', { sourceFile: result.filePath, error }); ttlResults.push({ sourceFile: result.filePath, success: false, error: error instanceof Error ? error.message : 'Unknown error' }); } } return ttlResults; } async setupProjectWatching() { try { // Add the project as a repository to the code ingestion service await this.codeIngestionService.addRepository({ name: path.basename(this.config.rootPath), path: this.config.rootPath, enableFileWatcher: true, includePatterns: this.config.includePatterns || [], excludePatterns: this.config.excludePatterns || [], languages: this.config.languages || [] }); logger_1.default.info('Project watching setup completed', { rootPath: this.config.rootPath }); } catch (error) { logger_1.default.error('Failed to setup project watching', { error }); throw error; } } async validateFilePaths(filePaths) { const validFiles = []; for (const filePath of filePaths) { try { const stats = await fs.stat(filePath); const language = this.getLanguageFromFile(filePath); if (this.config.languages.includes(language)) { validFiles.push({ path: filePath, language, size: stats.size, lastModified: stats.mtime }); } } catch (error) { logger_1.default.warn('Invalid file path', { filePath, error }); } } return validFiles; } compileSummary(analysisResults, ttlResults) { const languageBreakdown = {}; for (const result of analysisResults) { languageBreakdown[result.language] = (languageBreakdown[result.language] || 0) + 1; } return { totalFiles: analysisResults.length, analyzedFiles: analysisResults.filter(r => r.success).length, skippedFiles: 0, errorFiles: analysisResults.filter(r => !r.success).length, ttlFilesGenerated: ttlResults.filter(r => r.success).length, languageBreakdown }; } generateRecommendations(structure, analysisResults) { const recommendations = []; // Check for missing package files if (structure.packageFiles.length === 0) { recommendations.push('Consider adding a package management file (package.json, requirements.txt, etc.)'); } // Check for test coverage const hasTests = analysisResults.some(r => r.filePath.includes('test') || r.filePath.includes('spec')); if (!hasTests) { recommendations.push('Consider adding test files to improve code quality and documentation'); } // Check for documentation const hasReadme = structure.sourceFiles.some(f => path.basename(f.path).toLowerCase().includes('readme')); if (!hasReadme) { recommendations.push('Consider adding a README.md file to document your project'); } return recommendations; } // Utility methods getLanguageFromFile(filePath) { const ext = path.extname(filePath).toLowerCase(); const languageMap = { '.ts': 'typescript', '.tsx': 'typescript', '.js': 'javascript', '.jsx': 'javascript', '.py': 'python', '.java': 'java', '.go': 'go', '.rs': 'rust', '.cpp': 'cpp', '.hpp': 'cpp', '.cc': 'cpp', '.cxx': 'cpp' }; return languageMap[ext] || 'unknown'; } async extractDependencies(filePath, type) { try { const content = await fs.readFile(filePath, 'utf-8'); switch (type) { case 'package.json': const pkg = JSON.parse(content); return [ ...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.devDependencies || {}) ]; case 'requirements.txt': return content.split('\n') .map(line => line.trim()) .filter(line => line && !line.startsWith('#')) .map(line => line.split('==')[0].split('>=')[0].split('<=')[0]); default: return []; } } catch (error) { logger_1.default.warn('Failed to extract dependencies', { filePath, type, error }); return []; } } determineProjectType(packageFiles, sourceFiles) { // Simple heuristics for project type detection const hasWebFiles = sourceFiles.some(f => f.path.includes('html') || f.path.includes('css') || f.path.includes('react') || f.path.includes('vue')); const hasApiFiles = sourceFiles.some(f => f.path.includes('api') || f.path.includes('server') || f.path.includes('route')); const hasCliFiles = sourceFiles.some(f => f.path.includes('cli') || f.path.includes('bin')); // Check package files for additional context const hasWebDependencies = packageFiles.some(pkg => pkg.dependencies.some(dep => ['react', 'vue', 'angular', 'svelte'].includes(dep.toLowerCase()))); if ((hasWebFiles || hasWebDependencies) && hasApiFiles) return 'mixed'; if (hasWebFiles || hasWebDependencies) return 'web'; if (hasApiFiles) return 'api'; if (hasCliFiles) return 'cli'; return 'library'; } detectFrameworks(packageFiles, sourceFiles) { const frameworks = []; // Check package dependencies for (const pkgFile of packageFiles) { const deps = pkgFile.dependencies.join(' ').toLowerCase(); if (deps.includes('react')) frameworks.push('React'); if (deps.includes('vue')) frameworks.push('Vue'); if (deps.includes('angular')) frameworks.push('Angular'); if (deps.includes('express')) frameworks.push('Express'); if (deps.includes('fastapi')) frameworks.push('FastAPI'); if (deps.includes('django')) frameworks.push('Django'); if (deps.includes('spring')) frameworks.push('Spring'); } // Check source files for framework indicators const sourceContent = sourceFiles.map(f => f.path.toLowerCase()).join(' '); if (sourceContent.includes('react') && !frameworks.includes('React')) frameworks.push('React'); if (sourceContent.includes('vue') && !frameworks.includes('Vue')) frameworks.push('Vue'); if (sourceContent.includes('angular') && !frameworks.includes('Angular')) frameworks.push('Angular'); return [...new Set(frameworks)]; } detectBuildTools(packageFiles) { const buildTools = []; for (const pkgFile of packageFiles) { switch (pkgFile.type) { case 'package.json': buildTools.push('npm/yarn'); break; case 'pom.xml': buildTools.push('Maven'); break; case 'go.mod': buildTools.push('Go Modules'); break; case 'Cargo.toml': buildTools.push('Cargo'); break; case 'CMakeLists.txt': buildTools.push('CMake'); break; } } return buildTools; } classifyDirectory(dirName) { const name = dirName.toLowerCase(); if (name.includes('test') || name.includes('spec')) return 'test'; if (name.includes('src') || name.includes('lib')) return 'source'; if (name.includes('config') || name.includes('conf')) return 'config'; if (name.includes('doc') || name.includes('readme')) return 'docs'; if (name.includes('build') || name.includes('dist')) return 'build'; return 'other'; } ensureInitialized() { if (!this.isInitialized) { throw new Error('Project Analysis Service not initialized. Call initialize() first.'); } } async countFilesInDirectory(dirPath) { try { const entries = await fs.readdir(dirPath, { withFileTypes: true }); let count = 0; for (const entry of entries) { if (entry.isFile()) { count++; } else if (entry.isDirectory() && !entry.name.startsWith('.')) { count += await this.countFilesInDirectory(path.join(dirPath, entry.name)); } } return count; } catch (error) { logger_1.default.warn('Failed to count files in directory', { dirPath, error }); return 0; } } } exports.ProjectAnalysisService = ProjectAnalysisService; //# sourceMappingURL=ProjectAnalysisService.js.map