UNPKG

contaigents

Version:

Modular AI Content Ecosystem with Audio Generation

267 lines (266 loc) 9.45 kB
import { FileService } from '../services/fileService.js'; import path from 'path'; import fs from 'fs/promises'; /** * Manages project context and understanding */ export class ProjectContext { constructor() { this.contextCache = new Map(); this.fileService = new FileService(); } /** * Initialize project context for a given path */ async initializeContext(projectPath) { const normalizedPath = path.resolve(projectPath); // Check cache first if (this.contextCache.has(normalizedPath)) { const cached = this.contextCache.get(normalizedPath); // Return cached if less than 5 minutes old if (Date.now() - cached.metadata.lastAnalyzed < 5 * 60 * 1000) { return cached; } } try { // Build file tree const fileTree = await this.buildFileTree(normalizedPath); // Analyze project metadata const metadata = await this.analyzeProjectMetadata(normalizedPath, fileTree); // Find dependencies const dependencies = await this.findFileDependencies(fileTree); const context = { projectPath: normalizedPath, fileTree, dependencies, metadata, knowledgeBase: [] }; // Cache the context this.contextCache.set(normalizedPath, context); return context; } catch (error) { console.error('Error initializing project context:', error); throw error; } } /** * Build enhanced file tree with metadata */ async buildFileTree(projectPath) { const tree = await this.fileService.getFileTree(); return this.enhanceFileTree(tree, projectPath); } /** * Enhance file tree with metadata */ async enhanceFileTree(nodes, basePath) { const enhanced = []; for (const node of nodes) { try { const fullPath = path.join(basePath, node.path); const stats = await fs.stat(fullPath); const enhancedNode = { path: node.path, name: node.name, type: node.type, size: stats.size, lastModified: stats.mtime.getTime() }; if (node.type === 'file') { enhancedNode.metadata = await this.analyzeFileMetadata(fullPath); } if (node.children) { enhancedNode.children = await this.enhanceFileTree(node.children, basePath); } enhanced.push(enhancedNode); } catch (error) { // Skip files that can't be accessed console.warn(`Could not access ${node.path}:`, error); } } return enhanced; } /** * Analyze file metadata */ async analyzeFileMetadata(filePath) { try { const content = await this.fileService.readFile(filePath); const ext = path.extname(filePath).toLowerCase(); const metadata = { contentType: this.getContentType(ext), lastAnalyzed: Date.now() }; // Count lines and words for text files if (this.isTextFile(ext)) { const lines = content.split('\n'); metadata.lineCount = lines.length; metadata.wordCount = content.split(/\s+/).filter(word => word.length > 0).length; // Detect language for code files if (this.isCodeFile(ext)) { metadata.language = this.detectLanguage(ext); } // Generate summary for markdown files if (ext === '.md' || ext === '.mdx') { metadata.summary = this.generateMarkdownSummary(content); } } return metadata; } catch (error) { return { contentType: 'unknown', lastAnalyzed: Date.now() }; } } /** * Analyze project metadata */ async analyzeProjectMetadata(projectPath, fileTree) { const projectName = path.basename(projectPath); // Analyze file types to determine project type const fileTypes = this.analyzeFileTypes(fileTree); const projectType = this.determineProjectType(fileTypes); // Look for package.json, README, etc. for more info const description = await this.findProjectDescription(projectPath); return { name: projectName, description, type: projectType, lastAnalyzed: Date.now() }; } /** * Find file dependencies */ async findFileDependencies(fileTree) { const dependencies = []; // Simple dependency detection for now for (const file of this.flattenFileTree(fileTree)) { if (file.type === 'file' && file.metadata?.language) { const deps = await this.findFileDependenciesInFile(file); dependencies.push(...deps); } } return dependencies; } /** * Find dependencies in a single file */ async findFileDependenciesInFile(file) { const dependencies = []; try { const content = await this.fileService.readFile(file.path); // Look for import statements, requires, includes, etc. const importPatterns = [ /import.*from\s+['"]([^'"]+)['"]/g, // ES6 imports /require\(['"]([^'"]+)['"]\)/g, // CommonJS requires /\[([^\]]+)\]\([^)]+\)/g, // Markdown links ]; for (const pattern of importPatterns) { let match; while ((match = pattern.exec(content)) !== null) { dependencies.push({ source: file.path, target: match[1], type: 'import', strength: 0.8 }); } } } catch (error) { // Skip files that can't be read } return dependencies; } /** * Helper methods */ getContentType(ext) { const types = { '.md': 'markdown', '.mdx': 'markdown', '.js': 'javascript', '.ts': 'typescript', '.py': 'python', '.json': 'json', '.txt': 'text', '.html': 'html', '.css': 'css' }; return types[ext] || 'unknown'; } isTextFile(ext) { const textExts = ['.md', '.mdx', '.js', '.ts', '.py', '.json', '.txt', '.html', '.css', '.yml', '.yaml']; return textExts.includes(ext); } isCodeFile(ext) { const codeExts = ['.js', '.ts', '.py', '.html', '.css', '.json']; return codeExts.includes(ext); } detectLanguage(ext) { const languages = { '.js': 'javascript', '.ts': 'typescript', '.py': 'python', '.html': 'html', '.css': 'css', '.json': 'json' }; return languages[ext] || 'unknown'; } generateMarkdownSummary(content) { const lines = content.split('\n'); const firstHeading = lines.find(line => line.startsWith('#')); const firstParagraph = lines.find(line => line.trim().length > 0 && !line.startsWith('#')); return [firstHeading, firstParagraph].filter(Boolean).join(' - ').substring(0, 200); } analyzeFileTypes(fileTree) { const types = {}; for (const file of this.flattenFileTree(fileTree)) { if (file.type === 'file' && file.metadata?.contentType) { types[file.metadata.contentType] = (types[file.metadata.contentType] || 0) + 1; } } return types; } determineProjectType(fileTypes) { const total = Object.values(fileTypes).reduce((sum, count) => sum + count, 0); const markdownRatio = (fileTypes.markdown || 0) / total; const codeRatio = ((fileTypes.javascript || 0) + (fileTypes.typescript || 0) + (fileTypes.python || 0)) / total; if (markdownRatio > 0.7) return 'documentation'; if (codeRatio > 0.5) return 'code'; if (markdownRatio > 0.3) return 'content'; return 'mixed'; } async findProjectDescription(projectPath) { try { const readmePath = path.join(projectPath, 'README.md'); const content = await this.fileService.readFile(readmePath); const lines = content.split('\n'); const descLine = lines.find(line => line.trim().length > 0 && !line.startsWith('#')); return descLine?.substring(0, 200); } catch { return undefined; } } flattenFileTree(nodes) { const flattened = []; for (const node of nodes) { flattened.push(node); if (node.children) { flattened.push(...this.flattenFileTree(node.children)); } } return flattened; } }