UNPKG

codecrucible-synth

Version:

Production-Ready AI Development Platform with Multi-Voice Synthesis, Smithery MCP Integration, Enterprise Security, and Zero-Timeout Reliability

385 lines (334 loc) 11.2 kB
/** * Lazy Project Intelligence System - Performance Optimized * Iteration 4: Optimize memory usage and performance */ import { EventEmitter } from 'events'; import { basename } from 'path'; import { Logger } from '../logger.js'; import { unifiedCache } from '../cache/unified-cache-system.js'; import { ProjectIntelligenceSystem, ProjectIntelligence, AnalysisOptions, } from './project-intelligence-system.js'; export interface LazyProjectIntelligence { basic: BasicProjectInfo; full?: ProjectIntelligence; loaded: boolean; loading: boolean; } export interface BasicProjectInfo { name: string; type: 'library' | 'application' | 'service' | 'unknown'; language: string; hasPackageJson: boolean; hasTestDir: boolean; estimatedSize: 'small' | 'medium' | 'large' | 'huge'; fileCount: number; } export interface PerformanceMetrics { initTime: number; analysisTime: number; memoryUsage: number; cacheHits: number; cacheMisses: number; } export class LazyProjectIntelligenceSystem extends EventEmitter { private logger: Logger; private fullSystem: ProjectIntelligenceSystem; private loadingPromises: Map<string, Promise<ProjectIntelligence>> = new Map(); private cleanupInterval?: NodeJS.Timeout; private metrics: PerformanceMetrics = { initTime: 0, analysisTime: 0, memoryUsage: 0, cacheHits: 0, cacheMisses: 0, }; constructor() { super(); this.logger = new Logger('LazyProjectIntelligence'); this.fullSystem = new ProjectIntelligenceSystem(); // Cleanup timer to prevent memory leaks this.cleanupInterval = setInterval(() => this.cleanupCache(), 300000); // 5 minutes // TODO: Store interval ID and call clearInterval in cleanup } /** * Quick initialization with basic project info only */ async quickAnalysis(rootPath: string): Promise<BasicProjectInfo> { const startTime = Date.now(); try { const basic = await this.extractBasicInfo(rootPath); // Cache the basic info const cacheKey = `lazy-intel-basic:${rootPath}`; await unifiedCache.set( cacheKey, { basic, loaded: false, loading: false, }, { ttl: 3600000, tags: ['lazy-project-intelligence', 'basic-info'] } ); this.metrics.initTime = Date.now() - startTime; this.logger.info(`Quick analysis completed in ${this.metrics.initTime}ms`); return basic; } catch (error) { this.logger.warn(`Quick analysis failed: ${error}`); return this.getDefaultBasicInfo(rootPath); } } /** * Get full project intelligence (lazy loaded) */ async getFullIntelligence(rootPath: string, force = false): Promise<ProjectIntelligence | null> { const cacheKey = `lazy-intel-basic:${rootPath}`; const cached = await unifiedCache.get<LazyProjectIntelligence>(cacheKey); // Return cached full intelligence if available if (cached?.value?.full && !force) { this.metrics.cacheHits++; return cached.value.full; } // Check if already loading if (this.loadingPromises.has(rootPath)) { return await this.loadingPromises.get(rootPath)!; } this.metrics.cacheMisses++; // Start loading const loadingPromise = this.loadFullIntelligence(rootPath); this.loadingPromises.set(rootPath, loadingPromise); try { const intelligence = await loadingPromise; // Update cache const existingCached = await unifiedCache.get<LazyProjectIntelligence>(cacheKey); if (existingCached?.value) { const updated = { ...existingCached.value, full: intelligence, loaded: true, loading: false, }; await unifiedCache.set(cacheKey, updated, { ttl: 3600000, tags: ['lazy-project-intelligence', 'full-intel'], }); } this.emit('intelligence:loaded', { rootPath, intelligence }); return intelligence; } finally { this.loadingPromises.delete(rootPath); } } /** * Load full intelligence in background without blocking */ async preloadIntelligence(rootPath: string): Promise<void> { const cacheKey = `lazy-intel-basic:${rootPath}`; const cached = await unifiedCache.get<LazyProjectIntelligence>(cacheKey); if (!cached?.value || this.loadingPromises.has(rootPath)) { return; } if (cached.value.loaded || cached.value.loading) { return; } const updated = { ...cached.value, loading: true }; await unifiedCache.set(cacheKey, updated, { ttl: 3600000, tags: ['lazy-project-intelligence'], }); // Load in background setImmediate(async () => { try { await this.getFullIntelligence(rootPath); } catch (error) { this.logger.warn(`Background preload failed: ${error}`); const cachedData = await unifiedCache.get<LazyProjectIntelligence>(cacheKey); if (cachedData?.value) { const updated = { ...cachedData.value, loading: false }; await unifiedCache.set(cacheKey, updated, { ttl: 3600000, tags: ['lazy-project-intelligence'], }); } } }); } /** * Extract basic project information quickly */ private async extractBasicInfo(rootPath: string): Promise<BasicProjectInfo> { const { readdir, stat, readFile } = await import('fs/promises'); const { join, basename } = await import('path'); let name = basename(rootPath); let type: BasicProjectInfo['type'] = 'unknown'; let language = 'Unknown'; let hasPackageJson = false; let hasTestDir = false; let fileCount = 0; try { // Quick directory scan (non-recursive) const entries = await readdir(rootPath, { withFileTypes: true }); fileCount = entries.length; // Look for key indicators for (const entry of entries) { const entryName = entry.name.toLowerCase(); if (entry.isFile()) { // Package files if (entryName === 'package.json') { hasPackageJson = true; try { const packageContent = await readFile(join(rootPath, entry.name), 'utf8'); const packageJson = JSON.parse(packageContent); name = packageJson.name || name; // Determine type from package.json if (packageJson.main || packageJson.bin) { type = packageJson.bin ? 'application' : 'library'; } } catch (error) { // Ignore parsing errors } } // Language detection if (entryName.endsWith('.ts') || entryName.endsWith('.tsx')) { language = 'TypeScript'; } else if (entryName.endsWith('.js') || entryName.endsWith('.jsx')) { language = language === 'Unknown' ? 'JavaScript' : language; } else if (entryName.endsWith('.py')) { language = language === 'Unknown' ? 'Python' : language; } else if (entryName.endsWith('.rs')) { language = 'Rust'; } else if (entryName.endsWith('.go')) { language = 'Go'; } } else if (entry.isDirectory()) { // Test directories if (entryName === 'test' || entryName === 'tests' || entryName === '__tests__') { hasTestDir = true; } // Service indicators if (entryName === 'api' || entryName === 'server' || entryName === 'service') { type = type === 'unknown' ? 'service' : type; } } } // Estimate project size const estimatedSize = this.estimateProjectSize(fileCount); return { name, type, language, hasPackageJson, hasTestDir, estimatedSize, fileCount, }; } catch (error) { this.logger.warn(`Basic info extraction failed: ${error}`); return this.getDefaultBasicInfo(rootPath); } } /** * Load full intelligence using the main system */ private async loadFullIntelligence(rootPath: string): Promise<ProjectIntelligence> { const startTime = Date.now(); const options: AnalysisOptions = { force: false, maxDepth: 4, // Reduced depth for performance skipLargeFiles: true, maxFileSize: 512 * 1024, // 512KB limit }; const intelligence = await this.fullSystem.analyzeProject(rootPath, options); this.metrics.analysisTime = Date.now() - startTime; this.metrics.memoryUsage = process.memoryUsage().heapUsed; return intelligence; } /** * Estimate project size based on file count */ private estimateProjectSize(fileCount: number): BasicProjectInfo['estimatedSize'] { if (fileCount < 20) return 'small'; if (fileCount < 100) return 'medium'; if (fileCount < 500) return 'large'; return 'huge'; } /** * Get default basic info when analysis fails */ private getDefaultBasicInfo(rootPath: string): BasicProjectInfo { return { name: basename(rootPath), type: 'unknown', language: 'Unknown', hasPackageJson: false, hasTestDir: false, estimatedSize: 'medium', fileCount: 0, }; } /** * Cleanup old cache entries - now handled by unified cache TTL */ private cleanupCache(): void { // Cache cleanup is now handled automatically by unified cache TTL // This method is kept for compatibility but no longer needed } /** * Get cached basic info */ async getBasicInfo(rootPath: string): Promise<BasicProjectInfo | null> { const cacheKey = `lazy-intel-basic:${rootPath}`; const cached = await unifiedCache.get<LazyProjectIntelligence>(cacheKey); return cached?.value?.basic || null; } /** * Check if full intelligence is loaded */ async isFullyLoaded(rootPath: string): Promise<boolean> { const cacheKey = `lazy-intel-basic:${rootPath}`; const cached = await unifiedCache.get<LazyProjectIntelligence>(cacheKey); return cached?.value?.loaded || false; } /** * Check if currently loading */ async isLoading(rootPath: string): Promise<boolean> { const cacheKey = `lazy-intel-basic:${rootPath}`; const cached = await unifiedCache.get<LazyProjectIntelligence>(cacheKey); return cached?.value?.loading || this.loadingPromises.has(rootPath); } /** * Get performance metrics */ getMetrics(): PerformanceMetrics { return { ...this.metrics }; } /** * Clear all caches */ async clearCache(): Promise<void> { await unifiedCache.clearByTags(['lazy-project-intelligence']); this.loadingPromises.clear(); await this.fullSystem.clearCache(); this.metrics = { initTime: 0, analysisTime: 0, memoryUsage: 0, cacheHits: 0, cacheMisses: 0, }; } /** * Shutdown and cleanup */ async shutdown(): Promise<void> { if (this.cleanupInterval) { clearInterval(this.cleanupInterval); this.cleanupInterval = undefined; } await this.clearCache(); this.removeAllListeners(); } } export default LazyProjectIntelligenceSystem;