diff --git a/app/ai-context-generator-cli/src/generators/contextGenerator.ts b/app/ai-context-generator-cli/src/generators/contextGenerator.ts new file mode 100644 index 0000000..6dde303 --- /dev/null +++ b/app/ai-context-generator-cli/src/generators/contextGenerator.ts @@ -0,0 +1,167 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import yaml from 'js-yaml'; +import { + ProjectInfo, + ContextConfig, + TemplateData, + FileInfo, + ProjectType, + DependencyInfo, + 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 | undefined; + if (contextConfig.analyzeConventions) { + conventions = await this.conventionExtractor.extract(resolvedDir, files); + } + + 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'; + } +}