import * as path from 'path'; import yaml from 'js-yaml'; import { ProjectInfo, ContextConfig, TemplateData, FileInfo, ConventionInfo, } from '../types'; import { ProjectTypeDetector } from '../analyzers/projectTypeDetector'; import { DependencyAnalyzer } from '../analyzers/dependencyAnalyzer'; import { ConventionExtractor } from '../analyzers/conventionExtractor'; import { FileUtils } from '../utils/fileUtils'; import { ConfigLoader } from '../config/configLoader'; export class ContextGenerator { private projectTypeDetector: ProjectTypeDetector; private dependencyAnalyzer: DependencyAnalyzer; private conventionExtractor: ConventionExtractor; private fileUtils: FileUtils; constructor() { this.projectTypeDetector = new ProjectTypeDetector(); this.dependencyAnalyzer = new DependencyAnalyzer(); this.conventionExtractor = new ConventionExtractor(); this.fileUtils = FileUtils.getInstance(); } async generate( dir: string, config?: ContextConfig ): Promise { const resolvedDir = this.fileUtils.resolveDirectory(dir); const contextConfig = config ?? await ConfigLoader.load(); if (contextConfig.respectGitignore) { await this.fileUtils.loadGitignore(resolvedDir); this.fileUtils.addCustomPatterns(contextConfig.excludes); } const files = await this.fileUtils.getFiles( resolvedDir, contextConfig.includes, contextConfig.excludes ); const projectType = await this.projectTypeDetector.detect(resolvedDir); const dependencies = await this.dependencyAnalyzer.analyze( resolvedDir, contextConfig.includeDevDependencies ); let conventions: ConventionInfo | null = null; if (contextConfig.analyzeConventions) { conventions = await this.conventionExtractor.extract(resolvedDir, files) ?? null; } return { projectType, language: projectType.primaryLanguage, framework: projectType.frameworks[0] || null, dependencies, conventions, fileCount: files.length, analysisDate: new Date().toISOString(), }; } async generateWithFiles( dir: string, config?: ContextConfig ): Promise { const projectInfo = await this.generate(dir, config); const contextConfig = config ?? await ConfigLoader.load(); const resolvedDir = this.fileUtils.resolveDirectory(dir); const files = await this.fileUtils.getFiles( resolvedDir, contextConfig.includes, contextConfig.excludes ); const fileInfos: FileInfo[] = []; for (const file of files) { try { const size = await this.fileUtils.getFileSize(file); fileInfos.push({ path: path.relative(resolvedDir, file), size, type: this.fileUtils.getFileExtension(file), language: this.detectLanguage(file), }); } catch { // Skip files that can't be read } } return { projectInfo, files: fileInfos, config: contextConfig, generatedAt: new Date().toISOString(), }; } async generateJson(dir: string, config?: ContextConfig): Promise { const data = await this.generateWithFiles(dir, config); return JSON.stringify(data, null, 2); } async generateYaml(dir: string, config?: ContextConfig): Promise { const data = await this.generateWithFiles(dir, config); return yaml.dump(data, { indent: 2, lineWidth: -1 }); } async saveContext( dir: string, outputPath: string, format: 'json' | 'yaml', config?: ContextConfig ): Promise { let content: string; let finalPath = outputPath; if (format === 'json') { content = await this.generateJson(dir, config); if (!finalPath.endsWith('.json')) { finalPath = `${finalPath}.json`; } } else { content = await this.generateYaml(dir, config); if (!finalPath.endsWith('.yaml') && !finalPath.endsWith('.yml')) { finalPath = `${finalPath}.yaml`; } } await this.fileUtils.writeFile(finalPath, content); } private detectLanguage(filePath: string): string { const ext = this.fileUtils.getFileExtension(filePath); const languageMap: Record = { '.ts': 'TypeScript', '.tsx': 'TypeScript', '.js': 'JavaScript', '.jsx': 'JavaScript', '.py': 'Python', '.go': 'Go', '.rs': 'Rust', '.java': 'Java', '.c': 'C', '.cpp': 'C++', '.h': 'C', '.hpp': 'C++', '.cs': 'C#', '.rb': 'Ruby', '.php': 'PHP', '.swift': 'Swift', }; return languageMap[ext] || 'Unknown'; } }