import { DiffProviderPort, DocumentationPort, OutputPort, Diff, CodeBlock } from '../ports'; import { DocSuggestion } from '../entities/DocSuggestion'; import * as fs from 'fs'; export interface GenerateDocsOptions { readonly baseRef?: string; readonly headRef?: string; readonly includeStaged?: boolean; readonly includeUnstaged?: boolean; readonly filePatterns?: string[]; readonly excludePatterns?: string[]; readonly outputFormat?: 'console' | 'markdown' | 'json' | 'html' | 'file'; readonly outputPath?: string; readonly docType?: 'function' | 'class' | 'interface' | 'module' | 'api' | 'readme' | 'changelog' | 'architecture'; readonly format?: 'markdown' | 'jsdoc' | 'tsdoc' | 'asciidoc' | 'rst' | 'plain'; readonly language?: string; readonly includeExamples?: boolean; readonly includeParameters?: boolean; readonly includeReturnTypes?: boolean; readonly includeSeeAlso?: boolean; readonly style?: 'formal' | 'casual' | 'technical' | 'beginner'; } export interface GenerateDocsUseCase { execute(options?: GenerateDocsOptions): Promise; } export class GenerateDocsUseCaseImpl implements GenerateDocsUseCase { constructor( private readonly diffProvider: DiffProviderPort, private readonly documentationPort: DocumentationPort, private readonly outputPort: OutputPort ) {} async execute(options: GenerateDocsOptions = {}): Promise { try { // Get diffs const diffs = await this.diffProvider.getDiffs({ baseRef: options.baseRef, headRef: options.headRef, includeStaged: options.includeStaged, includeUnstaged: options.includeUnstaged, filePatterns: options.filePatterns, excludePatterns: options.excludePatterns, }); // Prioritize file patterns when provided for documentation generation if (options.filePatterns && options.filePatterns.length > 0) { // Create mock diffs for file pattern analysis const mockDiffs: Diff[] = options.filePatterns.map(filePath => { // Read the actual file content let fileContent = ''; try { if (fs.existsSync(filePath)) { fileContent = fs.readFileSync(filePath, 'utf-8'); } } catch (error) { console.warn(`Warning: Could not read file ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`); } return { filePath, language: this.getLanguageFromFile(filePath), hunks: [], linesAdded: 0, linesRemoved: 0, isBinary: false, oldMode: '100644', newMode: '100644', oldFile: filePath, newFile: filePath, oldContent: '', newContent: fileContent, isNewFile: false, isDeletedFile: false, }; }); // Convert diffs to code blocks const codeBlocks = this.convertDiffsToCodeBlocks(mockDiffs); // Generate documentation based on type return this.generateDocumentation(codeBlocks, options); } else if (diffs.length > 0) { // Convert diffs to code blocks const codeBlocks = this.convertDiffsToCodeBlocks(diffs); // Generate documentation based on type const docSuggestions = await this.generateDocumentation(codeBlocks, options); // Output the suggestions await this.outputPort.displayDocSuggestions(docSuggestions, { format: options.outputFormat || 'console', outputPath: options.outputPath, }); return docSuggestions; } else { throw new Error('No changes found to generate documentation for'); } } catch (error) { await this.outputPort.displayError(`Documentation generation failed: ${error instanceof Error ? error.message : 'Unknown error'}`); throw error; } } private getLanguageFromFile(filePath: string): string { const extension = filePath.split('.').pop()?.toLowerCase(); switch (extension) { case 'ts': case 'tsx': return 'typescript'; case 'js': case 'jsx': return 'javascript'; case 'dart': return 'dart'; case 'py': return 'python'; case 'java': return 'java'; case 'cpp': case 'cc': case 'cxx': return 'cpp'; case 'c': return 'c'; case 'cs': return 'csharp'; case 'go': return 'go'; case 'rs': return 'rust'; case 'php': return 'php'; case 'rb': return 'ruby'; case 'swift': return 'swift'; case 'kt': return 'kotlin'; default: return 'text'; } } private convertDiffsToCodeBlocks(diffs: Diff[]): any[] { const codeBlocks: any[] = []; for (const diff of diffs) { if (diff.isBinary) { continue; } // Focus on new content for documentation generation if (diff.newContent) { codeBlocks.push({ content: diff.newContent, language: diff.language, startLine: 1, endLine: diff.newContent.split('\n').length, filePath: diff.filePath, }); } } return codeBlocks; } private async generateDocumentation(codeBlocks: any[], options: GenerateDocsOptions): Promise { // Detect language from code blocks if not specified const detectedLanguage = options.language || (codeBlocks.length > 0 ? codeBlocks[0].language : 'typescript'); const docOptions = { format: options.format || 'markdown', language: detectedLanguage, includeExamples: options.includeExamples !== false, includeParameters: options.includeParameters !== false, includeReturnTypes: options.includeReturnTypes !== false, includeSeeAlso: options.includeSeeAlso !== false, style: options.style || 'technical', }; switch (options.docType) { case 'readme': return this.generateReadmeDocumentation(options); case 'changelog': return this.generateChangelogDocumentation(options); case 'architecture': return this.generateArchitectureDocumentation(codeBlocks, docOptions); case 'api': return this.generateApiDocumentation(codeBlocks, docOptions); default: return this.generateGeneralDocumentation(codeBlocks, docOptions); } } private async generateGeneralDocumentation(codeBlocks: CodeBlock[], docOptions: any): Promise { return this.documentationPort.generateDocumentationForMultiple(codeBlocks, docOptions); } private async generateApiDocumentation(codeBlocks: CodeBlock[], docOptions: any): Promise { return this.documentationPort.generateApiDocumentation(codeBlocks, docOptions); } private async generateArchitectureDocumentation(codeBlocks: CodeBlock[], docOptions: any): Promise { return this.documentationPort.generateArchitectureDocumentation(codeBlocks, docOptions); } private async generateReadmeDocumentation(options: GenerateDocsOptions): Promise { // This would typically gather project information from package.json, git, etc. const projectInfo = { name: 'AI Developer Assistant', description: 'A modular, open-source AI Developer Assistant for automating and improving the software development lifecycle', language: options.language || 'typescript', features: [ 'Code review and analysis', 'Security scanning', 'Test generation', 'Documentation generation', 'Refactoring suggestions', 'Commit message generation', ], installation: 'npm install -g ai-developer-assistant', usage: 'ai-dev review --help', }; const docOptions = { format: options.format || 'markdown', language: options.language || 'typescript', includeExamples: options.includeExamples !== false, includeParameters: options.includeParameters !== false, includeReturnTypes: options.includeReturnTypes !== false, includeSeeAlso: options.includeSeeAlso !== false, style: options.style || 'technical', }; return this.documentationPort.generateReadmeDocumentation(projectInfo, docOptions); } private async generateChangelogDocumentation(options: GenerateDocsOptions): Promise { // This would typically gather commit information from git const commits = [ { hash: 'abc123', message: 'Initial implementation of AI Developer Assistant', date: new Date(), author: 'AI Assistant Team', }, { hash: 'def456', message: 'Add security scanning capabilities', date: new Date(), author: 'AI Assistant Team', }, ]; const docOptions = { format: options.format || 'markdown', language: options.language || 'typescript', includeExamples: options.includeExamples !== false, includeParameters: options.includeParameters !== false, includeReturnTypes: options.includeReturnTypes !== false, includeSeeAlso: options.includeSeeAlso !== false, style: options.style || 'technical', }; return this.documentationPort.generateChangelog(commits, docOptions); } }