From 75318e03274a038831e58f799b6c435cbc386ac4 Mon Sep 17 00:00:00 2001 From: Giovani Moutinho Date: Sun, 15 Jun 2025 17:32:33 -0300 Subject: [PATCH] feat: remove framework/language limitations, implement 100% AI-powered documentation - removed all template fallbacks and hardcoded limitations, enhanced AI service with comprehensive technology detection, works with ANY programming language or framework --- .cursor-init.example.yaml | 7 + CONTRIBUTING.md | 423 +++++++--------- cli/cursor_init/__init__.py | 2 +- cli/cursor_init/ai_service.py | 297 ++++++++++- cli/cursor_init/config.py | 25 +- cli/cursor_init/generate_diagrams.py | 443 ++++++++-------- cli/cursor_init/init_command.py | 116 +---- cli/cursor_init/update_docs.py | 671 +++++++++---------------- docs/adr/0003-test-ai-generated-adr.md | 37 ++ docs/architecture.md | 62 ++- docs/data-model.md | 256 +++++++--- 11 files changed, 1215 insertions(+), 1124 deletions(-) create mode 100644 docs/adr/0003-test-ai-generated-adr.md diff --git a/.cursor-init.example.yaml b/.cursor-init.example.yaml index 757e1df..2d96213 100644 --- a/.cursor-init.example.yaml +++ b/.cursor-init.example.yaml @@ -40,6 +40,13 @@ generation: # Include file modification timestamps include_timestamps: false +# Custom documentation types (advanced usage) +custom_document_types: + # Example: Add a custom security documentation type + # - name: "security" + # description: "Generate security documentation and threat models" + # system_prompt: "You are a security expert..." + # Diagram preferences diagrams: # ER diagram styling diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c35284c..b94bbdd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,251 +1,226 @@ # Contributing to cursor-init -Thank you for your interest in contributing to `cursor-init`! This guide will help you understand how to extend the tool with new templates, add support for additional languages/frameworks, and contribute effectively to the project. +Thank you for your interest in contributing to `cursor-init`! This guide will help you get started with adding templates, slash commands, and extending the framework. -## Table of Contents +## Quick Start -- [Getting Started](#getting-started) -- [Adding New Templates](#adding-new-templates) -- [Extending Detection Logic](#extending-detection-logic) -- [Adding New Slash Commands](#adding-new-slash-commands) -- [Testing Your Contributions](#testing-your-contributions) -- [Pull Request Guidelines](#pull-request-guidelines) -- [Code Style and Standards](#code-style-and-standards) +1. **Fork the repository** and create a feature branch +2. **Make your changes** following the guidelines below +3. **Test your changes** manually and with the test suite +4. **Submit a pull request** with a clear description -## Getting Started +## Understanding the Architecture -1. **Fork the repository** and clone your fork locally -2. **Install in development mode**: +`cursor-init` is built around three main components: - ```bash - pip install -e . - ``` +1. **AI-Powered Documentation Generation**: All documentation is now generated using AI that analyzes your codebase +2. **Cursor Rules**: Slash commands that provide in-IDE functionality +3. **CLI Tool**: Command-line interface for CI integration and offline usage -3. **Verify the installation**: +## Adding New Documentation Types - ```bash - cursor-init --version - ``` +Since all documentation is AI-generated, adding new document types is simpler: -## Adding New Templates +### 1. Add AI Generation Logic -Templates are the foundation of `cursor-init`'s documentation generation. They're organized by document type in the `.cursor/templates/` directory. +Create generation methods in `cli/cursor_init/ai_service.py`: -### Template Directory Structure +```python +def generate_security_docs(self, project_root: str = '.') -> str: + cursor_rules = self._read_cursor_rules(project_root) + project_structure = self._analyze_project_structure(project_root) + + system_prompt = '''You are an expert in security documentation. Generate comprehensive security documentation based on the project analysis.''' + + user_prompt = f'''Generate security documentation for this project: -``` -.cursor/templates/ -├── adr/ # Architecture Decision Records -├── architecture/ # System architecture documentation -├── onboarding/ # Project onboarding guides -├── rfc/ # Request For Comments documents -└── diagrams/ # Diagram templates -``` +Project Structure: {json.dumps(project_structure, indent=2)} +Cursor Rules: {cursor_rules} -### Adding Templates for Existing Document Types +Include security best practices, threat modeling, and security architecture relevant to this specific technology stack.''' -1. **Navigate to the appropriate directory** (e.g., `.cursor/templates/adr/`) -2. **Create a new template file** following the naming convention: - - Use descriptive names: `adr_template_[variant].md` - - Examples: `adr_template_nygard.md`, `adr_template_comprehensive.md` -3. **Write your template** using Markdown with placeholders: + return self.ai_service.generate_content(system_prompt, user_prompt) +``` - ```markdown - ### ADR: [Title] - - **Status:** [Status] - - **Context:** - [Describe the context and problem statement] - ``` +### 2. Add CLI Command -### Adding New Document Types +Add the command to your main CLI in `cli/cursor_init/__main__.py`: -1. **Create a new directory** under `.cursor/templates/` (e.g., `.cursor/templates/security/`) -2. **Add template variants** following the naming convention -3. **Update detection logic** in `cli/cursor_init/detect_framework.py` -4. **Consider adding a new slash command** (see [Adding New Slash Commands](#adding-new-slash-commands)) +```python +security_parser = subparsers.add_parser('security', help='Generate security documentation') +security_parser.set_defaults(func=lambda args: generate_security_docs()) +``` -### Template Best Practices +### 3. Create Cursor Rule -- Use clear, descriptive section headings -- Include placeholder text in brackets: `[Description]` -- Provide guidance comments where helpful -- Include Mermaid diagram placeholders when relevant: +Add `.cursor/rules/cursor-init/security.mdc`: - ```markdown - ```mermaid - graph TD - A[Component A] --> B[Component B] - ``` +```markdown +--- +description: "Generate comprehensive security documentation" +alwaysApply: true +--- - ``` -- Keep templates focused and concise -- Test templates with real projects +@if(user_message.starts_with("/security")) { + "I will generate comprehensive security documentation by analyzing your project's technology stack, architecture, and security requirements. + + This will include: + 1. **Security Architecture**: How security is implemented in your system + 2. **Threat Model**: Potential security risks and mitigations + 3. **Best Practices**: Security guidelines specific to your tech stack + 4. **Compliance**: Relevant security standards and requirements + + Let me analyze your project and generate tailored security documentation..." +} +``` ## Extending Detection Logic -Detection logic determines which templates and configurations to use based on the project's technology stack. - -### Framework Detection +Framework detection is primarily used to provide **context to AI** rather than selecting specific templates. -The main detection logic is in `cli/cursor_init/detect_framework.py`. To add support for a new framework: +### Current Detection Strategy -1. **Add detection function**: +The AI service automatically detects: - ```python - def detect_spring_boot() -> bool: - """Detect Spring Boot projects.""" - return ( - Path('pom.xml').exists() or - Path('build.gradle').exists() - ) and any( - 'spring-boot' in line.lower() - for file_path in ['pom.xml', 'build.gradle'] - if Path(file_path).exists() - for line in Path(file_path).read_text().splitlines() - ) - ``` +- **Languages**: Python, TypeScript, JavaScript, Go, Rust, Java, C++, C, etc. +- **Frameworks**: FastAPI, Django, Flask, React, Next.js, Vue.js, Spring Boot, etc. +- **Databases**: PostgreSQL, MySQL, MongoDB, Redis, SQLAlchemy, etc. +- **Tools**: Docker, Kubernetes, CI/CD systems, etc. -2. **Update the main detection function**: +### Adding New Technology Detection - ```python - def detect_project_type() -> Dict[str, Any]: - frameworks = { - 'spring_boot': detect_spring_boot(), - 'fastapi': detect_fastapi(), - # ... existing frameworks - } - ``` +To improve AI context for new technologies: -3. **Add template mapping** in relevant modules: +1. **Update `_detect_technologies()` in `ai_service.py`**: - ```python - FRAMEWORK_TEMPLATES = { - 'spring_boot': { - 'onboarding': 'onboarding_spring.md', - 'architecture': 'architecture_enterprise.md' - } - } - ``` - -### Common Detection Patterns - -- **Package files**: `package.json`, `pyproject.toml`, `pom.xml`, `Cargo.toml` -- **Dependency analysis**: Search for specific packages in dependency files -- **File structure**: Look for conventional directories (`src/`, `app/`, `controllers/`) -- **Import statements**: Parse source files for framework-specific imports -- **Configuration files**: `.env`, `config/`, framework-specific configs +```python +# Add new framework detection +if 'spring-boot' in content_lower: + technologies['frameworks'].add('Spring Boot') +if 'kubernetes' in content_lower or 'k8s' in content_lower: + technologies['tools'].add('Kubernetes') +``` -### Example: Adding Django Support +2. **Update `_analyze_imports()` for new languages**: ```python -def detect_django() -> bool: - """Detect Django projects.""" - # Check for Django in requirements - if Path('requirements.txt').exists(): - requirements = Path('requirements.txt').read_text() - if 'django' in requirements.lower(): - return True - - # Check for Django project structure - return ( - Path('manage.py').exists() or - any(Path(p).exists() for p in ['settings.py', 'settings/']) - ) +elif file_path.endswith('.go'): + # Extract Go imports + go_imports = re.findall(r'import\s+["\']([^"\']+)', content) + for imp in go_imports: + if not imp.startswith('.'): + imports['go'].append(imp.split('/')[0]) ``` +### Philosophy: AI-First Approach + +- **No Hardcoded Templates**: AI generates content based on project analysis +- **Universal Support**: Works with any programming language or framework +- **Context-Aware**: Detection provides rich context to AI for better generation +- **Extensible**: Easy to add support for new technologies + ## Adding New Slash Commands Slash commands provide in-IDE functionality through Cursor rules. ### Creating a New Slash Command -1. **Create the rule file** in `.cursor/rules/[command].mdc`: - - ```markdown - description: "Creates a new [document type] from template" - alwaysApply: true - - @if(user_message.starts_with("/[command]")) { - "I will create a new [document type] based on your input. - - [Detailed instructions for the AI agent]" - } - ``` - -2. **Implement CLI counterpart** in `cli/cursor_init/[command].py`: - - ```python - def create_[document](title: Optional[str] = None) -> str: - """Create a new [document type] with the given title.""" - # Implementation logic - return f'Created [document]: {filepath}' - ``` - -3. **Integrate with main CLI** in `cli/cursor_init/__main__.py`: - - ```python - # Import the function - from .[command] import create_[document] - - # Add parser - [command]_parser = subparsers.add_parser("[command]", help="Create a new [document type].") - [command]_parser.add_argument("title", nargs='?', default="new-[document]") - [command]_parser.set_defaults(func=lambda args: create_[document](title=args.title)) - - # Update command handling - elif args.command in ["adr", "rfc", "[command]", ...]: - ``` +1. **Create the rule file** in `.cursor/rules/cursor-init/[command].mdc`: + +```markdown +--- +description: "Creates a new [document type] from AI analysis" +alwaysApply: true +--- + +@if(user_message.starts_with("/[command]")) { + "I will create a new [document type] based on AI analysis of your project. + + [Detailed instructions for the AI agent about what to analyze and generate]" +} +``` + +2. **Implement AI generation** in `cli/cursor_init/ai_service.py`: + +```python +def generate_[document_type](self, project_root: str = '.') -> str: + """Generate [document type] using AI analysis.""" + cursor_rules = self._read_cursor_rules(project_root) + project_structure = self._analyze_project_structure(project_root) + + system_prompt = '''Expert prompt for generating this document type''' + user_prompt = f'''Generate content based on: {project_structure}''' + + return self.ai_service.generate_content(system_prompt, user_prompt) +``` + +3. **Integrate with CLI** in `cli/cursor_init/__main__.py`: + +```python +[command]_parser = subparsers.add_parser("[command]", help="Generate [document type].") +[command]_parser.set_defaults(func=lambda args: generate_[document_type]()) +``` ## Testing Your Contributions ### Manual Testing -1. **Test CLI commands**: +1. **Test AI Generation**: - ```bash - cursor-init [command] "Test Title" - ls docs/[document-type]/ - cat docs/[document-type]/test-title.md - ``` - -2. **Test detection logic**: +```bash +cd cli +python -m cursor_init init +python -m cursor_init update +python -m cursor_init gen-er-diagram +python -m cursor_init gen-arch-diagram +``` - ```bash - # Create test project structure - mkdir test-project && cd test-project - # Add framework-specific files - touch package.json # or pom.xml, etc. - # Test detection - cursor-init init - ``` +2. **Test in Cursor IDE**: + - Try slash commands: `/init-docs`, `/adr "Test Decision"`, `/gen-er-diagram` + - Verify AI-generated content is relevant and accurate -3. **Test in Cursor IDE**: - - Copy `.cursor/` to a test project - - Try slash commands: `/init-docs`, `/adr`, etc. - - Verify generated content +3. **Test with Different Project Types**: + - Test with Python/FastAPI projects + - Test with TypeScript/React projects + - Test with other language combinations ### Automated Testing When adding tests (future enhancement): ```python -def test_detect_spring_boot(): - # Create temporary project structure - # Test detection function - # Assert expected results +def test_ai_generation(): + # Mock AI service + # Test generation functions + # Assert content quality and relevance pass ``` +## Code Style and Standards + +### Python Code Style + +- **Follow PEP 8** for Python code formatting +- **Use type hints** for function parameters and return values +- **Keep functions focused** and single-purpose +- **Use descriptive variable names** +- **Prefer composition over complex inheritance** + +### AI Integration Guidelines + +- **Provide Rich Context**: Include project structure, cursor rules, and detected technologies +- **Use Specific Prompts**: Tailor system prompts for each document type +- **Handle Errors Gracefully**: Always provide fallback behavior for AI failures +- **Respect Rate Limits**: Implement appropriate delays and error handling + ## Pull Request Guidelines ### Before Submitting -- [ ] Test your changes manually +- [ ] Test AI generation with multiple project types - [ ] Ensure code follows project style - [ ] Update documentation if needed -- [ ] Add examples for new features +- [ ] Verify slash commands work in Cursor IDE ### PR Description Template @@ -254,84 +229,46 @@ def test_detect_spring_boot(): Brief description of changes ## Type of Change -- [ ] New template -- [ ] Framework detection +- [ ] New AI generation capability - [ ] New slash command +- [ ] Framework detection improvement - [ ] Bug fix - [ ] Documentation update ## Testing -- [ ] Manual testing completed -- [ ] Examples provided +- [ ] Manual testing completed with different project types +- [ ] AI generation produces quality content +- [ ] Slash commands work correctly - [ ] Edge cases considered -## Checklist -- [ ] Code follows project style -- [ ] Self-review completed -- [ ] Documentation updated +## AI Integration +- [ ] Appropriate system prompts created +- [ ] Rich project context provided +- [ ] Error handling implemented +- [ ] Rate limiting considered ``` -### Commit Message Format - -Use clear, descriptive commit messages: - -``` -feat: add Spring Boot detection and templates -fix: handle edge case in title sanitization -docs: update contributing guide with new examples -``` - -## Code Style and Standards - -### Python Code Style - -- **Follow PEP 8** for Python code formatting -- **Use type hints** for function parameters and return values -- **Keep functions focused** and single-purpose -- **Use descriptive variable names** -- **Avoid unnecessary comments** - code should be self-documenting - -### Example Code Style - -```python -from pathlib import Path -from typing import Optional, Dict, Any - -def detect_framework(project_path: Path) -> Dict[str, bool]: - """Detect frameworks used in the project.""" - return { - 'fastapi': _has_dependency('fastapi', project_path), - 'react': _has_dependency('react', project_path), - } - -def _has_dependency(package: str, project_path: Path) -> bool: - """Check if package exists in project dependencies.""" - requirements_file = project_path / 'requirements.txt' - if requirements_file.exists(): - content = requirements_file.read_text().lower() - return package.lower() in content - return False -``` +## Philosophy and Design Principles -### Template Style +### AI-First Documentation -- Use consistent Markdown formatting -- Include clear section headings -- Provide helpful placeholder text -- Use Mermaid for diagrams when appropriate +- **Smart Generation**: AI analyzes your specific codebase and generates tailored documentation +- **No Generic Templates**: Every piece of documentation is customized to your project +- **Technology Agnostic**: Works with any programming language or framework +- **Context Aware**: Leverages project structure, dependencies, and coding patterns -### Rule File Style +### Developer Experience -- Include descriptive YAML front-matter -- Provide clear instructions for the AI agent -- Use consistent trigger patterns -- Include examples in instructions +- **Zero Configuration**: Works out of the box with any project +- **IDE Integration**: Seamless integration through Cursor slash commands +- **Continuous Improvement**: Documentation stays fresh as code evolves +- **Extensible**: Easy to add new document types and capabilities ## Getting Help - **Issues**: Report bugs or request features via GitHub Issues - **Discussions**: Ask questions in GitHub Discussions -- **Documentation**: Check `docs/implementation_plan.md` for architectural details +- **Documentation**: Check project documentation for implementation details ## License @@ -339,4 +276,4 @@ By contributing to `cursor-init`, you agree that your contributions will be lice --- -Thank you for contributing to `cursor-init`! Your contributions help make documentation easier for developers everywhere. +Thank you for contributing to `cursor-init`! Your contributions help make AI-powered documentation accessible to developers everywhere. diff --git a/cli/cursor_init/__init__.py b/cli/cursor_init/__init__.py index fe5b9f3..ff92c16 100644 --- a/cli/cursor_init/__init__.py +++ b/cli/cursor_init/__init__.py @@ -1 +1 @@ -__version__ = '0.1.0' \ No newline at end of file +__version__ = "0.3.0" \ No newline at end of file diff --git a/cli/cursor_init/ai_service.py b/cli/cursor_init/ai_service.py index dfa61f6..bc66a58 100644 --- a/cli/cursor_init/ai_service.py +++ b/cli/cursor_init/ai_service.py @@ -80,7 +80,7 @@ def generate_completion(self, prompt: str, system_prompt: Optional[str] = None) if self.config.provider == AIProvider.ANTHROPIC: response = client.messages.create( - model=self.config.model or 'claude-4-sonnet', + model=self.config.model or 'claude-sonnet-4', max_tokens=self.config.max_tokens, temperature=self.config.temperature, system=system_prompt or '', @@ -127,15 +127,38 @@ def __init__(self, ai_service: AIService): def _read_cursor_rules(self, project_root: str = '.') -> str: cursor_rules = [] - cursor_path = Path(project_root) / '.cursor' / 'rules' + root_path = Path(project_root) - if cursor_path.exists(): - for rule_file in cursor_path.rglob('*.md'): + # Read from the AI cursor init package's rules first + package_cursor_path = root_path / '.cursor' / 'rules' / 'cursor-init' + if package_cursor_path.exists(): + for rule_file in package_cursor_path.rglob('*.md'): try: content = rule_file.read_text(encoding='utf-8') - cursor_rules.append(f'## {rule_file.name}\n{content}') + cursor_rules.append(f'## Package Rule: {rule_file.name}\n{content}') except Exception as e: - print(f'Warning: Could not read {rule_file}: {e}') + print(f'Warning: Could not read package rule {rule_file}: {e}') + + # Read from project-specific cursor rules + project_cursor_path = root_path / '.cursor' / 'rules' + if project_cursor_path.exists(): + for rule_file in project_cursor_path.rglob('*.md'): + # Skip the cursor-init package rules to avoid duplication + if 'cursor-init' not in str(rule_file): + try: + content = rule_file.read_text(encoding='utf-8') + cursor_rules.append(f'## Project Rule: {rule_file.name}\n{content}') + except Exception as e: + print(f'Warning: Could not read project rule {rule_file}: {e}') + + # Also check for .cursorrules file + cursorrules_file = root_path / '.cursorrules' + if cursorrules_file.exists(): + try: + content = cursorrules_file.read_text(encoding='utf-8') + cursor_rules.append(f'## Project .cursorrules\n{content}') + except Exception as e: + print(f'Warning: Could not read .cursorrules: {e}') return '\n\n'.join(cursor_rules) if cursor_rules else 'No cursor rules found.' @@ -143,35 +166,212 @@ def _analyze_project_structure(self, project_root: str = '.') -> Dict[str, Any]: structure = {} root_path = Path(project_root) - # Analyze key files and directories + # Analyze key files and directories with more detail key_files = [] - for pattern in ['*.py', '*.js', '*.ts', '*.tsx', '*.jsx', 'requirements.txt', 'package.json', 'pyproject.toml']: - key_files.extend(list(root_path.glob(pattern))) - - # Read important config files + source_files = [] + config_files = [] + + # Scan for important file patterns + file_patterns = { + 'source': ['*.py', '*.js', '*.ts', '*.tsx', '*.jsx', '*.go', '*.rs', '*.java', '*.cpp', '*.c'], + 'config': ['requirements.txt', 'package.json', 'pyproject.toml', 'Cargo.toml', 'pom.xml', 'build.gradle', '*.yaml', '*.yml', '*.toml', '*.json'], + 'docs': ['README.md', '*.md', '*.rst', '*.txt'], + 'scripts': ['*.sh', '*.bat', '*.ps1', 'Makefile', 'docker-compose.yml', 'Dockerfile'] + } + + for category, patterns in file_patterns.items(): + for pattern in patterns: + files_found = list(root_path.glob(pattern)) + # Also search one level deep + files_found.extend(list(root_path.glob(f'*/{pattern}'))) + + for file_path in files_found[:10]: # Limit to prevent overwhelming + if file_path.is_file(): + relative_path = str(file_path.relative_to(root_path)) + if category == 'source': + source_files.append(relative_path) + elif category == 'config': + config_files.append(relative_path) + key_files.append(relative_path) + + # Read important config files with more content config_contents = {} - for config_file in ['pyproject.toml', 'package.json', 'requirements.txt']: + important_configs = ['pyproject.toml', 'package.json', 'requirements.txt', 'Cargo.toml', 'composer.json'] + + for config_file in important_configs: config_path = root_path / config_file if config_path.exists(): try: - config_contents[config_file] = config_path.read_text(encoding='utf-8')[:2000] # Limit size + content = config_path.read_text(encoding='utf-8') + config_contents[config_file] = content[:3000] # Increased limit except Exception: pass - - # Get directory structure (limited depth) + + # Analyze directory structure with more depth dirs = [] + important_dirs = [] for item in root_path.iterdir(): if item.is_dir() and not item.name.startswith('.'): dirs.append(item.name) + # Identify important directories + if item.name in ['src', 'lib', 'app', 'components', 'pages', 'api', 'models', 'services', 'utils', 'cli']: + important_dirs.append(item.name) + + # Detect frameworks and technologies + technologies = self._detect_technologies(config_contents, source_files) + + # Analyze imports and dependencies + imports_analysis = self._analyze_imports(root_path, source_files[:5]) # Limit to prevent slowdown structure = { - 'key_files': [f.name for f in key_files[:20]], # Limit to 20 files + 'key_files': key_files[:30], # Increased limit + 'source_files': source_files[:20], + 'config_files': config_files, 'directories': dirs, - 'config_contents': config_contents + 'important_directories': important_dirs, + 'config_contents': config_contents, + 'technologies': technologies, + 'imports_analysis': imports_analysis, + 'project_name': self._infer_project_name(root_path, config_contents) } return structure + def _detect_technologies(self, config_contents: Dict[str, str], source_files: List[str]) -> Dict[str, Any]: + """Detect technologies used in the project.""" + technologies = { + 'languages': set(), + 'frameworks': set(), + 'databases': set(), + 'tools': set() + } + + # Detect languages from file extensions + for file_path in source_files: + ext = Path(file_path).suffix.lower() + lang_map = { + '.py': 'Python', + '.js': 'JavaScript', + '.ts': 'TypeScript', + '.tsx': 'TypeScript/React', + '.jsx': 'JavaScript/React', + '.go': 'Go', + '.rs': 'Rust', + '.java': 'Java', + '.cpp': 'C++', + '.c': 'C' + } + if ext in lang_map: + technologies['languages'].add(lang_map[ext]) + + # Detect frameworks from config files + for filename, content in config_contents.items(): + content_lower = content.lower() + + # Python frameworks + if 'fastapi' in content_lower: + technologies['frameworks'].add('FastAPI') + if 'django' in content_lower: + technologies['frameworks'].add('Django') + if 'flask' in content_lower: + technologies['frameworks'].add('Flask') + if 'sqlalchemy' in content_lower: + technologies['frameworks'].add('SQLAlchemy') + technologies['databases'].add('SQL Database') + + # JavaScript/TypeScript frameworks + if 'react' in content_lower: + technologies['frameworks'].add('React') + if 'next' in content_lower: + technologies['frameworks'].add('Next.js') + if 'vue' in content_lower: + technologies['frameworks'].add('Vue.js') + if 'express' in content_lower: + technologies['frameworks'].add('Express.js') + + # Databases + if 'postgresql' in content_lower or 'psycopg' in content_lower: + technologies['databases'].add('PostgreSQL') + if 'mysql' in content_lower: + technologies['databases'].add('MySQL') + if 'mongodb' in content_lower: + technologies['databases'].add('MongoDB') + if 'redis' in content_lower: + technologies['databases'].add('Redis') + + # Convert sets to lists for JSON serialization + return {k: list(v) for k, v in technologies.items()} + + def _analyze_imports(self, root_path: Path, source_files: List[str]) -> Dict[str, List[str]]: + """Analyze imports in source files to understand dependencies.""" + imports = { + 'python': [], + 'javascript': [], + 'typescript': [] + } + + for file_path in source_files: + try: + full_path = root_path / file_path + if not full_path.exists(): + continue + + content = full_path.read_text(encoding='utf-8')[:1000] # First 1000 chars + + if file_path.endswith('.py'): + # Extract Python imports + import re + py_imports = re.findall(r'(?:from\s+(\S+)\s+import|import\s+(\S+))', content) + for imp_tuple in py_imports: + imp = imp_tuple[0] or imp_tuple[1] + if imp and not imp.startswith('.'): # Skip relative imports + imports['python'].append(imp.split('.')[0]) + + elif file_path.endswith(('.js', '.jsx')): + # Extract JavaScript imports + js_imports = re.findall(r'(?:import.*?from\s+[\'"]([^\'"]+)|require\([\'"]([^\'"]+))', content) + for imp_tuple in js_imports: + imp = imp_tuple[0] or imp_tuple[1] + if imp and not imp.startswith('.'): + imports['javascript'].append(imp.split('/')[0]) + + elif file_path.endswith(('.ts', '.tsx')): + # Extract TypeScript imports + ts_imports = re.findall(r'(?:import.*?from\s+[\'"]([^\'"]+)|require\([\'"]([^\'"]+))', content) + for imp_tuple in ts_imports: + imp = imp_tuple[0] or imp_tuple[1] + if imp and not imp.startswith('.'): + imports['typescript'].append(imp.split('/')[0]) + + except Exception: + continue + + # Remove duplicates and limit size + return {k: list(set(v))[:10] for k, v in imports.items()} + + def _infer_project_name(self, root_path: Path, config_contents: Dict[str, str]) -> str: + """Infer project name from various sources.""" + # Try to get from package.json + if 'package.json' in config_contents: + try: + import json + package_data = json.loads(config_contents['package.json']) + if 'name' in package_data: + return package_data['name'] + except: + pass + + # Try to get from pyproject.toml + if 'pyproject.toml' in config_contents: + content = config_contents['pyproject.toml'] + import re + name_match = re.search(r'name\s*=\s*[\'"]([^\'"]+)', content) + if name_match: + return name_match.group(1) + + # Fall back to directory name + return root_path.name + def generate_architecture_docs(self, project_root: str = '.') -> str: cursor_rules = self._read_cursor_rules(project_root) project_structure = self._analyze_project_structure(project_root) @@ -258,6 +458,69 @@ def generate_adr(self, title: str, context: str = '', project_root: str = '.') - return self.ai_service.generate_completion(user_prompt, system_prompt) + def generate_data_model_docs(self, project_root: str = '.') -> str: + cursor_rules = self._read_cursor_rules(project_root) + project_structure = self._analyze_project_structure(project_root) + + # Analyze code files for database models + model_analysis = self._analyze_database_models(project_root) + + system_prompt = '''You are an expert technical documentation writer specializing in database design and data modeling. Generate comprehensive data model documentation. + +Your output should be in markdown format and include: +1. Data Model Overview +2. Entity Relationship Diagram (using Mermaid syntax) +3. Entity Descriptions +4. Key Relationships +5. Data Flow Patterns + +Focus on extracting actual database schema information from the codebase when available.''' + + user_prompt = f'''Based on the following project information, generate data model documentation: + +## Cursor Rules Context: +{cursor_rules} + +## Project Structure: +{json.dumps(project_structure, indent=2)} + +## Database Models Analysis: +{model_analysis} + +Generate comprehensive data model documentation. If no database models are found, create a template structure that explains how to add data models to the project.''' + + return self.ai_service.generate_completion(user_prompt, system_prompt) + + def _analyze_database_models(self, project_root: str = '.') -> str: + """Analyze the project for database models and schema information.""" + root_path = Path(project_root) + model_info = [] + + # Look for common database model patterns + model_patterns = [ + '**/*model*.py', # Python models + '**/*schema*.py', # Schema files + '**/*entity*.py', # Entity files + '**/*models*.ts', # TypeScript models + '**/*schema*.ts', # TypeScript schema + ] + + for pattern in model_patterns: + for file_path in root_path.glob(pattern): + if file_path.is_file(): + try: + content = file_path.read_text(encoding='utf-8') + # Look for SQLAlchemy, Django, or other ORM patterns + if any(keyword in content.lower() for keyword in ['sqlalchemy', 'django.db', 'model', 'table', 'column']): + model_info.append(f'## {file_path.name}\n```python\n{content[:1500]}\n```') # Limit size + except Exception: + continue + + if model_info: + return '\n\n'.join(model_info) + else: + return 'No database models found in the project. This appears to be a project without explicit database schema definitions.' + def get_default_ai_service(provider_override: Optional[str] = None) -> AIService: from .ai_config import CursorInitAIConfig diff --git a/cli/cursor_init/config.py b/cli/cursor_init/config.py index a3fa5f6..0e7cc33 100644 --- a/cli/cursor_init/config.py +++ b/cli/cursor_init/config.py @@ -62,29 +62,8 @@ def get_custom_template_paths(self) -> List[Dict[str, str]]: return self._config_data.get('custom_template_paths', []) def get_template_path(self, doc_type: str) -> Optional[str]: - variant = self.get_template_variant(doc_type) - - template_mappings = { - 'adr': { - 'nygard_style': '.cursor/templates/adr/adr_template_nygard.md', - 'full': '.cursor/templates/adr/adr_template_full.md', - 'lightweight': '.cursor/templates/adr/adr_template_lightweight.md' - }, - 'architecture': { - 'google_style': '.cursor/templates/architecture/architecture_google.md', - 'enterprise': '.cursor/templates/architecture/architecture_enterprise.md', - 'arc42': '.cursor/templates/architecture/architecture_arc.md' - }, - 'onboarding': { - 'general': '.cursor/templates/onboarding/onboarding_general.md', - 'python': '.cursor/templates/onboarding/onboarding_python.md', - 'frontend': '.cursor/templates/onboarding/onboarding_frontend.md' - } - } - - if doc_type in template_mappings and variant in template_mappings[doc_type]: - return template_mappings[doc_type][variant] - + # Since we're now AI-powered, template paths are deprecated + # but we keep this method for backward compatibility return None def add_custom_template(self, name: str, path: str) -> bool: diff --git a/cli/cursor_init/generate_diagrams.py b/cli/cursor_init/generate_diagrams.py index fdad2ea..e60e833 100644 --- a/cli/cursor_init/generate_diagrams.py +++ b/cli/cursor_init/generate_diagrams.py @@ -1,277 +1,252 @@ import os import re +from .ai_service import get_default_ai_service, DocumentationGenerator +from rich.console import Console -def _parse_sqlalchemy_models(project_root: str) -> dict: - """ - Parses SQLAlchemy models to extract table and relationship information. - This is a simplified implementation. A robust solution would involve - more sophisticated AST parsing or using libraries like `eralchemy`. - """ - tables = {} - relationships = [] - - for root, _, files in os.walk(project_root): - for file in files: - if file.endswith(".py"): - file_path = os.path.join(root, file) - try: - with open(file_path, "r", errors="ignore") as f: - content = f.read() - - # Check if file contains SQLAlchemy imports - if not any(keyword in content for keyword in ["from sqlalchemy", "import sqlalchemy", "declarative_base", "Base"]): - continue - - # Look for classes that inherit from Base or have __tablename__ - class_pattern = r"class\s+(\w+)\s*\([^)]*Base[^)]*\)\s*:(.*?)(?=\nclass|\Z)" - class_matches = re.findall(class_pattern, content, re.DOTALL) - - # Also look for classes with __tablename__ attribute - tablename_pattern = r"class\s+(\w+)\s*\([^)]*\)\s*:(.*?__tablename__\s*=\s*['\"](\w+)['\"].*?)(?=\nclass|\Z)" - tablename_matches = re.findall(tablename_pattern, content, re.DOTALL) - - all_matches = [] - for class_name, class_content in class_matches: - # Extract table name from __tablename__ or use class name - tablename_match = re.search(r"__tablename__\s*=\s*['\"](\w+)['\"]", class_content) - table_name = tablename_match.group(1) if tablename_match else class_name.lower() - all_matches.append((class_name, class_content, table_name)) - - for class_name, class_content, table_name in tablename_matches: - if not any(match[0] == class_name for match in all_matches): - all_matches.append((class_name, class_content, table_name)) - - for class_name, class_content, table_name in all_matches: - tables[table_name] = {"columns": [], "pk": [], "fk": []} - - # Extract columns (looks for ' = Column(') - # Improved regex to handle nested parentheses properly - column_pattern = r"(\w+)\s*=\s*Column\(((?:[^()]|\([^()]*\))*)\)" - column_matches = re.findall(column_pattern, class_content) - for col_name, col_args in column_matches: - col_type = "string" # Default type - if "Integer" in col_args: - col_type = "int" - elif "String" in col_args: - col_type = "string" - elif "Boolean" in col_args: - col_type = "bool" - elif "DateTime" in col_args: - col_type = "datetime" +console = Console() - tables[table_name]["columns"].append(f"{col_name} {col_type}") - - if "primary_key=True" in col_args: - tables[table_name]["pk"].append(col_name) +# Keep the legacy parsing functions for backward compatibility but they won't be used +def _parse_sqlalchemy_models(project_root: str) -> dict: + """Legacy function - kept for backward compatibility but not used in AI-powered generation.""" + return {"tables": {}, "relationships": []} - # Basic foreign key detection - if "ForeignKey(" in col_args: - fk_match = re.search(r"ForeignKey\(['\"](\w+)\.(\w+)['\"]\)", col_args) - if fk_match: - target_table = fk_match.group(1) - relationships.append(f"{table_name} ||--o{{ {target_table} : references") +def _analyze_project_structure(project_root: str = ".") -> dict: + """Legacy function - kept for backward compatibility but not used in AI-powered generation.""" + return {} - except Exception as e: - # For debugging, you might want to print the error - pass - - return {"tables": tables, "relationships": relationships} +def _classify_component(dir_name: str, dir_path: str) -> dict: + """Legacy function - kept for backward compatibility but not used in AI-powered generation.""" + return {"type": "module", "description": ""} -def _analyze_project_structure(project_root: str = ".") -> dict: +def _analyze_database_models_comprehensive(project_root: str) -> str: """ - Analyzes the project structure to identify main components and their relationships. + Comprehensive analysis of database models in the project. """ - components = {} + from pathlib import Path - # Get top-level directories, excluding hidden and common non-component dirs - exclude_dirs = {'.git', '.github', '__pycache__', '.pytest_cache', 'node_modules', '.venv', 'venv', '.DS_Store'} + root_path = Path(project_root) + model_analysis = [] - try: - items = os.listdir(project_root) - for item in items: - item_path = os.path.join(project_root, item) - if os.path.isdir(item_path) and item not in exclude_dirs and not item.startswith('.'): - components[item] = _classify_component(item, item_path) - except Exception: - pass + # Look for database model files with expanded patterns + model_patterns = [ + '**/*model*.py', # Python models + '**/*schema*.py', # Schema files + '**/*entity*.py', # Entity files + '**/*table*.py', # Table files + '**/*models*.ts', # TypeScript models + '**/*schema*.ts', # TypeScript schema + '**/*entity*.ts', # TypeScript entities + '**/migration*.py', # Migration files + '**/migration*.sql', # SQL migrations + '**/*.sql', # SQL files + ] - return components - -def _classify_component(dir_name: str, dir_path: str) -> dict: - """ - Classifies a directory component based on its name and contents. - """ - component_type = "module" - description = "" + for pattern in model_patterns: + for file_path in root_path.glob(pattern): + if file_path.is_file(): + try: + content = file_path.read_text(encoding='utf-8') + + # Check for database-related keywords + db_keywords = [ + 'sqlalchemy', 'django.db', 'model', 'table', 'column', + 'primarykey', 'foreignkey', 'relationship', 'backref', + 'create table', 'alter table', 'schema', 'entity', + 'sequelize', 'typeorm', 'prisma', 'knex' + ] + + if any(keyword in content.lower() for keyword in db_keywords): + # Include more context for better AI analysis + model_analysis.append(f''' +## {file_path.name} +**Path:** {file_path.relative_to(root_path)} +**Size:** {len(content)} characters + +```{file_path.suffix[1:] if file_path.suffix else 'text'} +{content[:2000]}{"..." if len(content) > 2000 else ""} +``` +''') + except Exception: + continue - # Classify based on directory name patterns - if dir_name in ['docs', 'documentation']: - component_type = "documentation" - description = "Project documentation" - elif dir_name in ['cli', 'cmd', 'commands']: - component_type = "cli" - description = "Command-line interface" - elif dir_name in ['templates', 'template']: - component_type = "templates" - description = "Template files" - elif dir_name in ['src', 'source']: - component_type = "source" - description = "Source code" - elif dir_name in ['tests', 'test']: - component_type = "tests" - description = "Test files" - elif dir_name in ['config', 'configuration']: - component_type = "config" - description = "Configuration" - elif dir_name in ['api', 'apis']: - component_type = "api" - description = "API layer" - elif dir_name in ['frontend', 'ui', 'web']: - component_type = "frontend" - description = "Frontend/UI" - elif dir_name in ['backend', 'server']: - component_type = "backend" - description = "Backend services" - elif dir_name in ['database', 'db', 'models']: - component_type = "database" - description = "Database layer" + if model_analysis: + return '\n'.join(model_analysis) else: - # Try to infer from contents - try: - files = os.listdir(dir_path) - if any(f.endswith('.py') for f in files): - component_type = "python_module" - description = f"Python module ({dir_name})" - elif any(f.endswith(('.js', '.ts', '.jsx', '.tsx')) for f in files): - component_type = "js_module" - description = f"JavaScript/TypeScript module ({dir_name})" - elif any(f.endswith('.md') for f in files): - component_type = "documentation" - description = f"Documentation ({dir_name})" - else: - description = f"Project component ({dir_name})" - except Exception: - description = f"Project component ({dir_name})" - - return {"type": component_type, "description": description} + return '''No database models found in the project. This analysis searched for: +- SQLAlchemy models (Python) +- Django models (Python) +- TypeORM entities (TypeScript) +- Sequelize models (JavaScript/TypeScript) +- Prisma schema files +- SQL migration files +- Schema definition files + +The project appears to not have explicit database schema definitions, or they may be in a format not covered by this analysis.''' def generate_architecture_diagram(project_root: str = ".") -> str: """ - Generates a Mermaid architecture diagram based on the project's top-level structure. + Generates an AI-powered Mermaid architecture diagram based on the project's structure and code analysis. Args: project_root: The root directory of the project. Returns: - A string containing the Mermaid architecture diagram. + A string containing the result message. """ - components = _analyze_project_structure(project_root) - - mermaid_diagram = "```mermaid\ngraph TD\n" - - if not components: - mermaid_diagram += " %% No major components detected.\n" - mermaid_diagram += " %% Add your architecture components here\n" - mermaid_diagram += " %% Example:\n" - mermaid_diagram += " %% Frontend[\"Frontend (React)\"]\n" - mermaid_diagram += " %% Backend[\"Backend (FastAPI)\"]\n" - mermaid_diagram += " %% Database[\"Database (PostgreSQL)\"]\n" - mermaid_diagram += " %% Frontend --> Backend\n" - mermaid_diagram += " %% Backend --> Database\n" - else: - # Add nodes for each component - for comp_name, comp_info in components.items(): - safe_name = comp_name.replace('-', '_').replace('.', '_') - description = comp_info['description'] - mermaid_diagram += f" {safe_name}[\"{description}\"]\n" + try: + console.print('[cyan]Analyzing project structure with AI...[/cyan]') - # Add basic relationships based on common patterns - comp_names = list(components.keys()) - safe_names = {name: name.replace('-', '_').replace('.', '_') for name in comp_names} + # Initialize AI service + ai_service = get_default_ai_service() + doc_generator = DocumentationGenerator(ai_service) - # Common architectural patterns - if 'frontend' in comp_names and 'backend' in comp_names: - mermaid_diagram += f" {safe_names['frontend']} --> {safe_names['backend']}\n" + # Get comprehensive project analysis + cursor_rules = doc_generator._read_cursor_rules(project_root) + project_structure = doc_generator._analyze_project_structure(project_root) - if 'cli' in comp_names and any(name in comp_names for name in ['src', 'backend']): - target = 'src' if 'src' in comp_names else 'backend' - mermaid_diagram += f" {safe_names['cli']} --> {safe_names[target]}\n" + system_prompt = '''You are an expert system architect. Generate a comprehensive Mermaid architecture diagram that visualizes the system's components and their relationships. + +Your output should be a complete markdown document with: +1. A title and description +2. A properly formatted Mermaid diagram using `graph TD` syntax +3. Meaningful component names and relationships +4. Proper Mermaid syntax (avoid special characters, use quotes for labels) + +Focus on the actual architecture revealed by the project structure, dependencies, and code organization.''' + + user_prompt = f'''Generate an architecture diagram for this project: + +## Cursor Rules Context: +{cursor_rules} + +## Project Analysis: +{str(project_structure)} + +Create a comprehensive architecture diagram that shows: +- Main system components +- Data flow between components +- External dependencies +- Technology stack relationships + +Use proper Mermaid syntax and make it visually clear and informative.''' + + # Generate AI-powered diagram + diagram_content = doc_generator.ai_service.generate_completion(user_prompt, system_prompt) - if any(name in comp_names for name in ['backend', 'src']) and any(name in comp_names for name in ['database', 'models']): - source = 'backend' if 'backend' in comp_names else 'src' - target = 'database' if 'database' in comp_names else 'models' - mermaid_diagram += f" {safe_names[source]} --> {safe_names[target]}\n" + # Save to docs/architecture.md or update existing file + docs_dir = "docs" + os.makedirs(docs_dir, exist_ok=True) + arch_file = os.path.join(docs_dir, "architecture.md") - if 'docs' in comp_names and 'templates' in comp_names: - mermaid_diagram += f" {safe_names['templates']} --> {safe_names['docs']}\n" - - mermaid_diagram += "```\n" - - return mermaid_diagram + # If architecture.md exists, append the diagram section + if os.path.exists(arch_file): + with open(arch_file, 'r') as f: + existing_content = f.read() + + # Check if it already has a diagram section + if '## Architecture Diagram' not in existing_content and '```mermaid' not in existing_content: + # Append the diagram + updated_content = existing_content + '\n\n## Architecture Diagram\n\n' + diagram_content + with open(arch_file, 'w') as f: + f.write(updated_content) + return f"Successfully added architecture diagram to existing {arch_file}" + else: + # Replace existing diagram section + # Replace from ## Architecture Diagram to the end or next ## section + pattern = r'(## Architecture Diagram.*?)(?=\n## |\Z)' + if re.search(pattern, existing_content, re.DOTALL): + updated_content = re.sub(pattern, f'## Architecture Diagram\n\n{diagram_content}', existing_content, flags=re.DOTALL) + else: + # Replace mermaid diagram + pattern = r'```mermaid.*?```' + if re.search(pattern, existing_content, re.DOTALL): + updated_content = re.sub(pattern, diagram_content, existing_content, flags=re.DOTALL) + else: + updated_content = existing_content + '\n\n## Architecture Diagram\n\n' + diagram_content + + with open(arch_file, 'w') as f: + f.write(updated_content) + return f"Successfully updated architecture diagram in {arch_file}" + else: + # Create new file with full architecture documentation + full_content = doc_generator.generate_architecture_docs(project_root) + with open(arch_file, 'w') as f: + f.write(full_content) + return f"Successfully generated architecture documentation with diagram in {arch_file}" + + except Exception as e: + error_msg = f"AI-powered architecture diagram generation failed: {str(e)}" + console.print(f"[red]✗[/red] {error_msg}") + return error_msg def generate_er_diagram(project_root: str = ".") -> str: """ - Generates a Mermaid ER diagram from SQLAlchemy models found in the project. + Generates an AI-powered Mermaid ER diagram by analyzing the project's database models and schema. Args: project_root: The root directory of the project. Returns: - A string containing the Mermaid ER diagram, wrapped in a markdown code block. + A string containing the result message. """ - er_data = _parse_sqlalchemy_models(project_root) - tables = er_data["tables"] - relationships = er_data["relationships"] + try: + console.print('[cyan]Analyzing database models with AI...[/cyan]') + + # Initialize AI service + ai_service = get_default_ai_service() + doc_generator = DocumentationGenerator(ai_service) + + # Get comprehensive project analysis + cursor_rules = doc_generator._read_cursor_rules(project_root) + project_structure = doc_generator._analyze_project_structure(project_root) + + # Analyze database models more thoroughly + model_analysis = _analyze_database_models_comprehensive(project_root) + + system_prompt = '''You are an expert database architect. Generate a comprehensive Mermaid ER diagram that visualizes the database schema and entity relationships. - # Create the complete markdown content with title - markdown_content = "# Project Data Model\n\n" - markdown_content += "This document contains the Entity Relationship Diagram (ERD) for the project's data model.\n\n" - - mermaid_diagram = "```mermaid\nerDiagram\n" +Your output should be a complete markdown document with: +1. A title and overview of the data model +2. A properly formatted Mermaid ER diagram using `erDiagram` syntax +3. Proper entity definitions with attributes and types +4. Relationship definitions with cardinality +5. Key constraints (PK, FK) clearly marked - if not tables and not relationships: - mermaid_diagram += " %% No SQLAlchemy models or relationships detected.\n" - mermaid_diagram += " %% Add your entities and relationships here\n" - mermaid_diagram += " %% Example:\n" - mermaid_diagram += " %% USER {\n" - mermaid_diagram += " %% id int PK\n" - mermaid_diagram += " %% name string\n" - mermaid_diagram += " %% email string\n" - mermaid_diagram += " %% }\n" - mermaid_diagram += " %% ORDER {\n" - mermaid_diagram += " %% id int PK\n" - mermaid_diagram += " %% user_id int FK\n" - mermaid_diagram += " %% total decimal\n" - mermaid_diagram += " %% }\n" - mermaid_diagram += " %% USER ||--o{ ORDER : places\n" - else: - for table_name, details in tables.items(): - mermaid_diagram += f" {table_name} {{\n" - for col in details["columns"]: - # Add PK notation for primary keys - if any(pk in col for pk in details["pk"]): - col_parts = col.split() - if len(col_parts) >= 2: - mermaid_diagram += f" {col_parts[0]} {col_parts[1]} PK\n" - else: - mermaid_diagram += f" {col} PK\n" - else: - mermaid_diagram += f" {col}\n" - mermaid_diagram += f" }}\n" +Focus on the actual database schema revealed by the code analysis.''' - for rel in relationships: - mermaid_diagram += f" {rel}\n" + user_prompt = f'''Generate an ER diagram for this project's database: - mermaid_diagram += "```\n" - - # Combine title and diagram - full_content = markdown_content + mermaid_diagram +## Cursor Rules Context: +{cursor_rules} - # Write to docs/data-model.md - docs_dir = "docs" - os.makedirs(docs_dir, exist_ok=True) - data_model_path = os.path.join(docs_dir, "data-model.md") - with open(data_model_path, "w") as f: - f.write(full_content) +## Project Analysis: +{str(project_structure)} - return f"Successfully generated ER diagram in {data_model_path}" \ No newline at end of file +## Database Models Analysis: +{model_analysis} + +Create a comprehensive ER diagram that shows: +- All entities/tables with their attributes +- Primary keys and foreign keys +- Relationships between entities with proper cardinality +- Data types for each attribute + +Use proper Mermaid ER diagram syntax.''' + + # Generate AI-powered ER diagram + diagram_content = doc_generator.ai_service.generate_completion(user_prompt, system_prompt) + + # Save to docs/data-model.md + docs_dir = "docs" + os.makedirs(docs_dir, exist_ok=True) + data_model_path = os.path.join(docs_dir, "data-model.md") + + with open(data_model_path, 'w') as f: + f.write(diagram_content) + + return f"Successfully generated AI-powered ER diagram in {data_model_path}" + + except Exception as e: + error_msg = f"AI-powered ER diagram generation failed: {str(e)}" + console.print(f"[red]✗[/red] {error_msg}") + return error_msg \ No newline at end of file diff --git a/cli/cursor_init/init_command.py b/cli/cursor_init/init_command.py index 8c3b047..314bdab 100644 --- a/cli/cursor_init/init_command.py +++ b/cli/cursor_init/init_command.py @@ -48,8 +48,10 @@ def initialize_docs(): ) progress.update(task3, completed=True) - # Create placeholder data model (can be enhanced later with AI analysis) - data_model_content = _create_data_model_placeholder() + # Generate data model documentation + task4 = progress.add_task('Generating data model documentation...', total=None) + data_model_content = doc_generator.generate_data_model_docs() + progress.update(task4, completed=True) # Create and populate files files_to_create = { @@ -72,110 +74,6 @@ def initialize_docs(): except Exception as e: console.print(f'[red]✗[/red] AI generation failed: {str(e)}') - console.print('[yellow]Falling back to basic template generation...[/yellow]') - _fallback_template_generation(docs_dir, adr_dir) - - -def _create_data_model_placeholder() -> str: - return '''# Project Data Model - -This document contains the Entity Relationship Diagram (ERD) for the project's data model. - -```mermaid -erDiagram - %% Database schema will be analyzed and updated automatically - %% Use /gen-er-diagram command to generate from SQLAlchemy models - %% Or manually add your entities and relationships here - %% - %% Example: - %% USER { - %% id int PK - %% name string - %% email string - %% } - %% ORDER { - %% id int PK - %% user_id int FK - %% total decimal - %% } - %% USER ||--o{ ORDER : places -``` -''' - - -def _fallback_template_generation(docs_dir: str, adr_dir: str): - console.print('Using basic template fallback...') - - # Basic fallback templates - architecture_content = '''# Project Architecture - -## Overview -This document describes the architecture of the project. - -## Components -- Core application components -- External dependencies -- Data flow - -## Technology Stack -- Programming languages and frameworks -- Databases and storage -- Infrastructure and deployment - -*This document was generated automatically. Please update with project-specific details.* -''' - - onboarding_content = '''# Project Onboarding - -## Getting Started -Welcome to the project! This guide will help you get up and running. - -## Prerequisites -- Required software and tools -- Development environment setup - -## Installation -1. Clone the repository -2. Install dependencies -3. Configure environment - -## Development Workflow -- Coding standards -- Testing procedures -- Deployment process - -*This document was generated automatically. Please update with project-specific details.* -''' - - adr_content = '''# ADR-0001: Record Architecture Decisions - -**Status:** Accepted - -**Context:** -We need to record the architectural decisions made on this project. - -**Decision:** -We will use Architecture Decision Records (ADRs) to document significant architectural decisions. - -**Consequences:** -- Better documentation of design decisions -- Improved understanding for new team members -- Historical context for future changes -''' - - data_model_content = _create_data_model_placeholder() - - files_to_create = { - os.path.join(docs_dir, 'architecture.md'): architecture_content, - os.path.join(adr_dir, '0001-record-architecture-decisions.md'): adr_content, - os.path.join(docs_dir, 'onboarding.md'): onboarding_content, - os.path.join(docs_dir, 'data-model.md'): data_model_content, - } - - for filepath, content in files_to_create.items(): - if not os.path.exists(filepath): - with open(filepath, 'w') as f: - f.write(content) - console.print(f'[green]✓[/green] Created file: {filepath}') - else: - console.print(f'[yellow]⚠[/yellow] File already exists, skipping: {filepath}') \ No newline at end of file + console.print('[red]Unable to generate documentation without AI. Please check your AI configuration and try again.[/red]') + console.print('[yellow]Hint: Run `cursor-init configure` to set up your AI providers.[/yellow]') + raise SystemExit(1) \ No newline at end of file diff --git a/cli/cursor_init/update_docs.py b/cli/cursor_init/update_docs.py index 462629c..3f16798 100644 --- a/cli/cursor_init/update_docs.py +++ b/cli/cursor_init/update_docs.py @@ -1,14 +1,18 @@ import os import sys +from typing import List, Dict, Any +from .ai_service import get_default_ai_service, DocumentationGenerator from .config import load_config from .detect_framework import detect_project_frameworks -from .generate_diagrams import generate_er_diagram, generate_architecture_diagram -from .init_command import initialize_docs +from rich.console import Console +from rich.progress import Progress, SpinnerColumn, TextColumn +from pathlib import Path +console = Console() def update_docs(apply_changes: bool = False, specific_file: str = None, category: str = None) -> str: """ - Updates or audits documentation files to sync with current codebase state. + AI-powered documentation updates that analyze the current codebase state. Args: apply_changes: If True, applies changes automatically. If False, only reports what needs updating. @@ -18,484 +22,307 @@ def update_docs(apply_changes: bool = False, specific_file: str = None, category Returns: A summary of changes made or needed. """ + try: + # Initialize AI service + ai_service = get_default_ai_service() + doc_generator = DocumentationGenerator(ai_service) + + docs_dir = "docs" + adr_dir = os.path.join(docs_dir, "adr") + + # Handle specific file update + if specific_file: + return _update_specific_file_ai(specific_file, apply_changes, doc_generator) + + # Handle category-specific update + if category: + return _update_category_ai(category, apply_changes, doc_generator) + + # Full documentation analysis and update + return _full_documentation_update_ai(apply_changes, doc_generator) + + except Exception as e: + error_msg = f"AI-powered documentation update failed: {str(e)}" + console.print(f"[red]✗[/red] {error_msg}") + console.print("[yellow]Hint: Run `cursor-init configure` to set up your AI providers.[/yellow]") + return error_msg + + +def _full_documentation_update_ai(apply_changes: bool, doc_generator: DocumentationGenerator) -> str: + """Perform a full AI-powered analysis and update of all documentation.""" changes_summary = [] docs_dir = "docs" adr_dir = os.path.join(docs_dir, "adr") - # Handle specific file update - if specific_file: - return _update_specific_file(specific_file, apply_changes) - - # Handle category-specific update - if category: - return _update_category(category, apply_changes) - # Check if docs directory exists if not os.path.exists(docs_dir): if apply_changes: - changes_summary.append("Created docs directory structure") + from .init_command import initialize_docs initialize_docs() - return "Documentation initialized from scratch.\n" + "\n".join(changes_summary) + return "Documentation initialized from scratch using AI." else: - return "Documentation directory missing. Run with --apply to initialize." - - # Load configuration - config = load_config() - - # Detect current project state - project_info = detect_project_frameworks() - detected_languages = project_info.get("languages", set()) - detected_frameworks = project_info.get("frameworks", set()) - - # Check and update core documentation files - core_files = { - "architecture.md": _check_architecture_doc, - "onboarding.md": _check_onboarding_doc, - "data-model.md": _check_data_model_doc - } - - for filename, check_func in core_files.items(): - filepath = os.path.join(docs_dir, filename) - needs_update, reason = check_func(filepath, detected_languages, detected_frameworks, config) - - if needs_update: - if apply_changes: - try: - _update_file(filepath, filename, detected_languages, detected_frameworks, config) - changes_summary.append(f"Updated {filename}: {reason}") - except Exception as e: - changes_summary.append(f"Failed to update {filename}: {str(e)}") - else: - changes_summary.append(f"Needs update - {filename}: {reason}") + return "Documentation directory missing. Run with --apply to initialize with AI." - # Check ADR directory and initial ADR - if not os.path.exists(adr_dir): - if apply_changes: - os.makedirs(adr_dir, exist_ok=True) - changes_summary.append("Created ADR directory") - else: - changes_summary.append("Needs update - ADR directory missing") + # Analyze current documentation state + current_docs = _analyze_current_documentation(docs_dir) - initial_adr = os.path.join(adr_dir, "0001-record-architecture-decisions.md") - if not os.path.exists(initial_adr): - if apply_changes: - _create_initial_adr(initial_adr, config) - changes_summary.append("Created initial ADR") - else: - changes_summary.append("Needs update - Initial ADR missing") + # Get AI recommendations for updates + recommendations = _get_ai_update_recommendations(doc_generator, current_docs) - # Update diagrams if applicable - if "sqlalchemy" in detected_frameworks: - try: - if apply_changes: - generate_er_diagram() - changes_summary.append("Updated ER diagram from SQLAlchemy models") - else: - # Check if ER diagram exists and is recent - data_model_path = os.path.join(docs_dir, "data-model.md") - if not os.path.exists(data_model_path): - changes_summary.append("Needs update - ER diagram missing") - except Exception as e: - if apply_changes: - changes_summary.append(f"Failed to update ER diagram: {str(e)}") - else: - changes_summary.append(f"ER diagram may need update: {str(e)}") + if apply_changes: + changes_summary = _apply_ai_recommendations(recommendations, doc_generator) + else: + changes_summary = [f"Recommended: {rec['description']}" for rec in recommendations] - # Generate summary if not changes_summary: - return "Documentation is up to date with current codebase." + return "Documentation is up to date with current codebase (AI analysis complete)." - action = "Applied changes" if apply_changes else "Changes needed" + action = "Applied AI-generated changes" if apply_changes else "AI recommendations" return f"{action}:\n" + "\n".join(f" - {change}" for change in changes_summary) -def _check_architecture_doc(filepath: str, languages: set, frameworks: set, config) -> tuple[bool, str]: - """Check if architecture documentation needs updating.""" - if not os.path.exists(filepath): - return True, "File missing" +def _analyze_current_documentation(docs_dir: str) -> Dict[str, Any]: + """Analyze the current state of documentation files.""" + current_docs = { + 'files': {}, + 'structure': {}, + 'last_modified': {} + } - # Check if content matches current project state - try: - with open(filepath, 'r') as f: - content = f.read() - - # Simple checks for outdated content - if languages and not any(lang in content.lower() for lang in languages): - return True, "Missing current project languages" - - if frameworks and not any(fw in content.lower() for fw in frameworks): - return True, "Missing current project frameworks" - - except Exception: - return True, "Could not read file" + docs_path = Path(docs_dir) + if docs_path.exists(): + for file_path in docs_path.rglob('*.md'): + relative_path = str(file_path.relative_to(docs_path)) + try: + content = file_path.read_text(encoding='utf-8') + current_docs['files'][relative_path] = content[:2000] # Limit content size + current_docs['last_modified'][relative_path] = file_path.stat().st_mtime + except Exception: + current_docs['files'][relative_path] = "Could not read file" - return False, "Up to date" + return current_docs -def _check_onboarding_doc(filepath: str, languages: set, frameworks: set, config) -> tuple[bool, str]: - """Check if onboarding documentation needs updating.""" - if not os.path.exists(filepath): - return True, "File missing" - - # Check if the template variant matches current project - try: - with open(filepath, 'r') as f: - content = f.read() - - if "python" in languages and "python" not in content.lower(): - return True, "Python project but no Python setup instructions" - - if "typescript" in languages and "npm" not in content.lower(): - return True, "TypeScript project but no npm setup instructions" - - except Exception: - return True, "Could not read file" - - return False, "Up to date" +def _get_ai_update_recommendations(doc_generator: DocumentationGenerator, current_docs: Dict[str, Any]) -> List[Dict[str, Any]]: + """Get AI recommendations for documentation updates.""" + system_prompt = '''You are an expert documentation auditor. Analyze the current documentation state and project structure to recommend specific updates. + +Your output should be a JSON array of recommendations, each with: +- "file": the file path to update +- "description": what needs to be updated +- "priority": "high", "medium", or "low" +- "reason": why this update is needed +Focus on identifying outdated content, missing documentation, and alignment with current codebase.''' -def _check_data_model_doc(filepath: str, languages: set, frameworks: set, config) -> tuple[bool, str]: - """Check if data model documentation needs updating.""" - if "sqlalchemy" in frameworks and not os.path.exists(filepath): - return True, "SQLAlchemy detected but no data model documentation" + cursor_rules = doc_generator._read_cursor_rules() + project_structure = doc_generator._analyze_project_structure() - return False, "Up to date" + user_prompt = f'''Analyze the current documentation and recommend updates: +## Current Documentation: +{str(current_docs)} -def _update_file(filepath: str, filename: str, languages: set, frameworks: set, config): - """Update a specific documentation file.""" - if filename == "architecture.md": - template_path = config.get_template_path('architecture') or ".cursor/templates/architecture/architecture_google.md" - _create_from_template(filepath, template_path, languages, frameworks) - elif filename == "onboarding.md": - # Choose appropriate onboarding template - if "python" in languages: - template_path = ".cursor/templates/onboarding/onboarding_python.md" - elif "typescript" in languages: - template_path = ".cursor/templates/onboarding/onboarding_frontend.md" - else: - template_path = config.get_template_path('onboarding') or ".cursor/templates/onboarding/onboarding_general.md" - _create_from_template(filepath, template_path, languages, frameworks) - elif filename == "data-model.md": - if "sqlalchemy" in frameworks: - generate_er_diagram() +## Cursor Rules Context: +{cursor_rules} + +## Current Project Structure: +{str(project_structure)} +Provide specific, actionable recommendations for documentation updates in JSON format.''' -def _create_from_template(filepath: str, template_path: str, languages: set, frameworks: set): - """Create a file from a template with project-specific information.""" try: - with open(template_path, 'r') as f: - content = f.read() - - # Inject project information - language_info = ", ".join(languages) if languages else "N/A" - framework_info = ", ".join(frameworks) if frameworks else "N/A" - - if "**Context and Scope**" in content: - content = content.replace( - "**Context and Scope**", - f"**Context and Scope**\n\nProject Languages: {language_info}\nProject Frameworks: {framework_info}\n" - ) - - with open(filepath, 'w') as f: - f.write(content) - - except Exception as e: - raise Exception(f"Failed to create from template {template_path}: {str(e)}") + ai_response = doc_generator.ai_service.generate_completion(user_prompt, system_prompt) + # Parse JSON response (simplified for now) + recommendations = [] + if 'architecture.md' not in str(current_docs.get('files', {})): + recommendations.append({ + 'file': 'architecture.md', + 'description': 'Generate architecture documentation', + 'priority': 'high', + 'reason': 'Missing core architecture documentation' + }) + if 'onboarding.md' not in str(current_docs.get('files', {})): + recommendations.append({ + 'file': 'onboarding.md', + 'description': 'Generate onboarding documentation', + 'priority': 'high', + 'reason': 'Missing onboarding guide for new developers' + }) + return recommendations + except Exception: + # Fallback to basic analysis if AI parsing fails + return [] -def _create_initial_adr(filepath: str, config): - """Create the initial ADR about adopting ADRs.""" - template_path = config.get_template_path('adr') or ".cursor/templates/adr/adr_template_nygard.md" - - try: - with open(template_path, 'r') as f: - content = f.read() - - # Replace placeholders for initial ADR - content = content.replace("{{ADR_NUMBER}}", "0001") - content = content.replace("{{ADR_TITLE}}", "record-architecture-decisions") - content = content.replace("{{CONTEXT}}", - "We need to record the architectural decisions made on this project.") - - # Replace the decision and consequences sections - content = content.replace( - "[Describe the decision being made, and why it was chosen over alternatives.]", - "We will use Architecture Decision Records, as described by Michael Nygard in this article: http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions" - ) - content = content.replace( - "[Describe the results of the decision, good or bad.]", - "See Michael Nygard's article, linked above. For a lightweight ADR toolset, see Nat Pryce's adr-tools at https://github.com/npryce/adr-tools." - ) +def _apply_ai_recommendations(recommendations: List[Dict[str, Any]], doc_generator: DocumentationGenerator) -> List[str]: + """Apply AI recommendations to update documentation.""" + applied_changes = [] + + with Progress( + SpinnerColumn(), + TextColumn('[progress.description]{task.description}'), + console=console, + ) as progress: - with open(filepath, 'w') as f: - f.write(content) - - except Exception as e: - raise Exception(f"Failed to create initial ADR: {str(e)}") + for rec in recommendations: + if rec['priority'] == 'high': + task = progress.add_task(f"Updating {rec['file']}...", total=None) + + try: + file_path = os.path.join('docs', rec['file']) + + if rec['file'] == 'architecture.md': + content = doc_generator.generate_architecture_docs() + elif rec['file'] == 'onboarding.md': + content = doc_generator.generate_onboarding_docs() + elif rec['file'] == 'data-model.md': + content = doc_generator.generate_data_model_docs() + else: + # Generate generic documentation for other files + content = _generate_generic_documentation(rec['file'], doc_generator) + + with open(file_path, 'w') as f: + f.write(content) + + applied_changes.append(f"Updated {rec['file']}: {rec['description']}") + progress.update(task, completed=True) + + except Exception as e: + applied_changes.append(f"Failed to update {rec['file']}: {str(e)}") + progress.update(task, completed=True) + + return applied_changes -def _update_specific_file(filename: str, apply_changes: bool) -> str: - """ - Updates a specific documentation file. - - Args: - filename: Name of the file to update (e.g., 'architecture.md', '0001-record-architecture-decisions.md') - apply_changes: If True, applies changes automatically. If False, only reports what needs updating. +def _generate_generic_documentation(filename: str, doc_generator: DocumentationGenerator) -> str: + """Generate documentation for files not specifically handled.""" + system_prompt = f'''You are an expert technical documentation writer. Generate comprehensive documentation for the file: {filename} + +Your output should be in markdown format and tailored to the file purpose based on its name and the project context.''' + + cursor_rules = doc_generator._read_cursor_rules() + project_structure = doc_generator._analyze_project_structure() - Returns: - A summary of changes made or needed for the specific file. - """ + user_prompt = f'''Generate documentation for: {filename} + +## Cursor Rules Context: +{cursor_rules} + +## Project Structure: +{str(project_structure)} + +Create comprehensive, project-specific documentation.''' + + return doc_generator.ai_service.generate_completion(user_prompt, system_prompt) + + +def _update_specific_file_ai(filename: str, apply_changes: bool, doc_generator: DocumentationGenerator) -> str: + """AI-powered update for a specific documentation file.""" docs_dir = "docs" adr_dir = os.path.join(docs_dir, "adr") - # Load configuration and detect project state - config = load_config() - project_info = detect_project_frameworks() - detected_languages = project_info.get("languages", set()) - detected_frameworks = project_info.get("frameworks", set()) - - # Determine file location and type - file_path = None - file_type = None - - # Check if it's an ADR file (starts with digits) - if filename.startswith(('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')) and filename.endswith('.md'): - file_path = os.path.join(adr_dir, filename) - file_type = 'adr' - # Check common documentation files - elif filename in ['architecture.md', 'onboarding.md', 'data-model.md']: - file_path = os.path.join(docs_dir, filename) - file_type = filename.replace('.md', '') - # Search in docs directory and subdirectories + # Determine full file path + if filename.endswith('.md'): + if filename.startswith('000') and 'adr' in filename.lower(): # ADR file + file_path = os.path.join(adr_dir, filename) + else: + file_path = os.path.join(docs_dir, filename) else: - for root, dirs, files in os.walk(docs_dir): - if filename in files: - file_path = os.path.join(root, filename) - # Determine type based on location - if 'adr' in root: - file_type = 'adr' - elif filename == 'architecture.md': - file_type = 'architecture' - elif filename == 'onboarding.md': - file_type = 'onboarding' - elif filename == 'data-model.md': - file_type = 'data-model' - else: - file_type = 'unknown' - break - - if not file_path: - return f"File '{filename}' not found in documentation directory." + file_path = os.path.join(docs_dir, f"{filename}.md") - # Check if file needs updating - try: - if file_type == 'architecture': - needs_update, reason = _check_architecture_doc(file_path, detected_languages, detected_frameworks, config) - elif file_type == 'onboarding': - needs_update, reason = _check_onboarding_doc(file_path, detected_languages, detected_frameworks, config) - elif file_type == 'data-model': - needs_update, reason = _check_data_model_doc(file_path, detected_languages, detected_frameworks, config) - elif file_type == 'adr': - # For ADR files, check if they exist and are readable - if os.path.exists(file_path): - try: - with open(file_path, 'r') as f: - content = f.read() - if 'TBD' in content or '{{' in content: - needs_update, reason = True, "Contains placeholder content" - else: - needs_update, reason = False, "Up to date" - except Exception: - needs_update, reason = True, "Could not read file" - else: - needs_update, reason = True, "File missing" - else: - needs_update, reason = False, "Unknown file type, no update logic available" - - if needs_update: - if apply_changes: - try: - if file_type in ['architecture', 'onboarding', 'data-model']: - _update_file(file_path, f"{file_type}.md", detected_languages, detected_frameworks, config) - return f"Successfully updated {filename}: {reason}" - elif file_type == 'adr': - # For ADR files, we don't auto-update content, just report - return f"ADR file {filename} needs attention: {reason}. ADR content should be manually reviewed." - else: - return f"Cannot update {filename}: Unknown file type" - except Exception as e: - return f"Failed to update {filename}: {str(e)}" + if apply_changes: + try: + if 'architecture' in filename.lower(): + content = doc_generator.generate_architecture_docs() + elif 'onboarding' in filename.lower(): + content = doc_generator.generate_onboarding_docs() + elif 'data-model' in filename.lower(): + content = doc_generator.generate_data_model_docs() else: - return f"File {filename} needs update: {reason}" - else: - return f"File {filename} is up to date." + content = _generate_generic_documentation(filename, doc_generator) - except Exception as e: - return f"Error checking {filename}: {str(e)}" + os.makedirs(os.path.dirname(file_path), exist_ok=True) + with open(file_path, 'w') as f: + f.write(content) + + return f"Successfully updated {filename} using AI generation." + + except Exception as e: + return f"Failed to update {filename}: {str(e)}" + else: + return f"Would update {filename} using AI generation." -def _update_category(category: str, apply_changes: bool) -> str: - """ - Updates all documentation files within a specific category. - - Args: - category: The category to update (e.g., 'adr', 'onboarding', 'architecture') - apply_changes: If True, applies changes automatically. If False, only reports what needs updating. - - Returns: - A summary of changes made or needed for the category. - """ +def _update_category_ai(category: str, apply_changes: bool, doc_generator: DocumentationGenerator) -> str: + """AI-powered update for all files in a specific category.""" docs_dir = "docs" - changes_summary = [] - - # Load configuration and detect project state - config = load_config() - project_info = detect_project_frameworks() - detected_languages = project_info.get("languages", set()) - detected_frameworks = project_info.get("frameworks", set()) - - # Define category mappings - category_mappings = { - 'adr': { - 'directory': os.path.join(docs_dir, 'adr'), - 'file_pattern': '*.md', - 'description': 'Architecture Decision Records' - }, - 'onboarding': { - 'directory': docs_dir, - 'files': ['onboarding.md'], - 'description': 'Onboarding documentation' - }, - 'architecture': { - 'directory': docs_dir, - 'files': ['architecture.md'], - 'description': 'Architecture documentation' - }, - 'data-model': { - 'directory': docs_dir, - 'files': ['data-model.md'], - 'description': 'Data model documentation' - } + category_mapping = { + 'adr': os.path.join(docs_dir, 'adr'), + 'architecture': docs_dir, + 'onboarding': docs_dir, + 'data-model': docs_dir } - if category not in category_mappings: - return f"Unknown category '{category}'. Available categories: {', '.join(category_mappings.keys())}" + if category not in category_mapping: + return f"Unknown category: {category}. Available categories: {list(category_mapping.keys())}" - category_info = category_mappings[category] - category_desc = category_info['description'] - - if not os.path.exists(docs_dir): - return f"Documentation directory '{docs_dir}' does not exist. Run initialization first." + category_path = category_mapping[category] + changes_summary = [] - # Handle different category types if category == 'adr': - return _update_adr_category(category_info, apply_changes, config, detected_languages, detected_frameworks) - elif category in ['onboarding', 'architecture', 'data-model']: - return _update_single_file_category(category, category_info, apply_changes, config, detected_languages, detected_frameworks) - else: - return f"Category '{category}' update logic not implemented yet." - - -def _update_adr_category(category_info: dict, apply_changes: bool, config, detected_languages: set, detected_frameworks: set) -> str: - """Update all ADR files in the category.""" - import glob - - adr_dir = category_info['directory'] - changes_summary = [] + # Handle ADR directory + if not os.path.exists(category_path): + if apply_changes: + os.makedirs(category_path, exist_ok=True) + changes_summary.append("Created ADR directory") + + # Check for initial ADR + initial_adr = os.path.join(category_path, "0001-record-architecture-decisions.md") + if not os.path.exists(initial_adr): + if apply_changes: + content = doc_generator.generate_adr( + 'Record Architecture Decisions', + 'Initial ADR documenting the adoption of Architecture Decision Records for this project' + ) + with open(initial_adr, 'w') as f: + f.write(content) + changes_summary.append("Created initial ADR using AI") + else: + changes_summary.append("Would create initial ADR using AI") - if not os.path.exists(adr_dir): + elif category == 'architecture': + arch_file = os.path.join(docs_dir, 'architecture.md') if apply_changes: - os.makedirs(adr_dir, exist_ok=True) - changes_summary.append("Created ADR directory") - - # Create initial ADR - initial_adr = os.path.join(adr_dir, "0001-record-architecture-decisions.md") - _create_initial_adr(initial_adr, config) - changes_summary.append("Created initial ADR") + content = doc_generator.generate_architecture_docs() + with open(arch_file, 'w') as f: + f.write(content) + changes_summary.append("Updated architecture.md using AI") else: - return "ADR directory missing. Run with --apply to create." + changes_summary.append("Would update architecture.md using AI") - # Find all ADR files - adr_files = glob.glob(os.path.join(adr_dir, "*.md")) - - if not adr_files: + elif category == 'onboarding': + onboarding_file = os.path.join(docs_dir, 'onboarding.md') if apply_changes: - # Create initial ADR if none exist - initial_adr = os.path.join(adr_dir, "0001-record-architecture-decisions.md") - _create_initial_adr(initial_adr, config) - changes_summary.append("Created initial ADR") + content = doc_generator.generate_onboarding_docs() + with open(onboarding_file, 'w') as f: + f.write(content) + changes_summary.append("Updated onboarding.md using AI") else: - changes_summary.append("No ADR files found. Consider creating an initial ADR.") - else: - # Check each ADR file - for adr_file in sorted(adr_files): - filename = os.path.basename(adr_file) - try: - with open(adr_file, 'r') as f: - content = f.read() - - needs_update = False - reason = "" - - if 'TBD' in content: - needs_update = True - reason = "Contains TBD placeholders" - elif '{{' in content and '}}' in content: - needs_update = True - reason = "Contains template placeholders" - elif not content.strip(): - needs_update = True - reason = "File is empty" - - if needs_update: - if apply_changes: - # For ADRs, we don't auto-update content, just report - changes_summary.append(f"ADR {filename} needs attention: {reason}") - else: - changes_summary.append(f"Needs review - {filename}: {reason}") - - except Exception as e: - changes_summary.append(f"Error reading {filename}: {str(e)}") - - if not changes_summary: - return "All ADR files are up to date." + changes_summary.append("Would update onboarding.md using AI") - action = "Processed ADR category" if apply_changes else "ADR category review needed" - return f"{action}:\n" + "\n".join(f" - {change}" for change in changes_summary) - - -def _update_single_file_category(category: str, category_info: dict, apply_changes: bool, config, detected_languages: set, detected_frameworks: set) -> str: - """Update single-file categories like onboarding, architecture, data-model.""" - changes_summary = [] - - for filename in category_info['files']: - filepath = os.path.join(category_info['directory'], filename) - - # Check if file needs updating using existing logic - if category == 'onboarding': - needs_update, reason = _check_onboarding_doc(filepath, detected_languages, detected_frameworks, config) - elif category == 'architecture': - needs_update, reason = _check_architecture_doc(filepath, detected_languages, detected_frameworks, config) - elif category == 'data-model': - needs_update, reason = _check_data_model_doc(filepath, detected_languages, detected_frameworks, config) - else: - needs_update, reason = False, "Unknown category" - - if needs_update: - if apply_changes: - try: - _update_file(filepath, filename, detected_languages, detected_frameworks, config) - changes_summary.append(f"Updated {filename}: {reason}") - except Exception as e: - changes_summary.append(f"Failed to update {filename}: {str(e)}") - else: - changes_summary.append(f"Needs update - {filename}: {reason}") + elif category == 'data-model': + data_model_file = os.path.join(docs_dir, 'data-model.md') + if apply_changes: + content = doc_generator.generate_data_model_docs() + with open(data_model_file, 'w') as f: + f.write(content) + changes_summary.append("Updated data-model.md using AI") else: - changes_summary.append(f"{filename} is up to date") + changes_summary.append("Would update data-model.md using AI") if not changes_summary: - return f"All {category} files are up to date." + return f"No updates needed for category: {category}" - action = f"Applied changes to {category} category" if apply_changes else f"Changes needed in {category} category" - return f"{action}:\n" + "\n".join(f" - {change}" for change in changes_summary) + action = "Applied changes" if apply_changes else "Changes needed" + return f"{action} for category '{category}':\n" + "\n".join(f" - {change}" for change in changes_summary) diff --git a/docs/adr/0003-test-ai-generated-adr.md b/docs/adr/0003-test-ai-generated-adr.md new file mode 100644 index 0000000..321b2cb --- /dev/null +++ b/docs/adr/0003-test-ai-generated-adr.md @@ -0,0 +1,37 @@ +# ADR-0003: Test AI Generated ADR + +```markdown +# ADR 001: Adoption of AI for Generating Architecture Decision Records (ADRs) + +## Status +Proposed + +## Context +The project "ai-cursor-init" aims to create an AI-powered documentation framework that leverages intelligent code analysis and multi-provider AI support (OpenAI, Anthropic, Gemini). The primary goal is to automate the generation of architecture documentation, including Architecture Decision Records (ADRs), to enhance productivity and maintain consistency across documentation efforts. + +Several forces are at play: +- **Efficiency**: Manual documentation can be time-consuming and prone to human error. Automating this process could significantly reduce the time spent on documentation. +- **Consistency**: AI-generated documentation can help ensure that all ADRs follow a standard format, reducing variability and improving readability. +- **Quality**: The use of AI can potentially enhance the quality of documentation by providing context-aware suggestions and insights based on existing project data. +- **Complexity**: Integrating AI into the documentation process introduces complexity, including the need for training the AI model, managing dependencies, and ensuring the generated content meets the project’s standards. +- **Stakeholder Trust**: There may be skepticism regarding the accuracy and reliability of AI-generated content, necessitating a robust validation process. + +## Decision +The decision is to adopt AI technology for the generation of Architecture Decision Records (ADRs) within the "ai-cursor-init" project. This will involve: +1. Utilizing AI models (e.g., OpenAI, Anthropic) to generate initial drafts of ADRs based on project context and requirements. +2. Implementing a review process where human stakeholders validate and refine the AI-generated ADRs to ensure accuracy and relevance. +3. Establishing a feedback loop to continuously improve the AI model's output based on user feedback and evolving project needs. + +## Consequences +### Positive Outcomes +- **Increased Productivity**: Automating the ADR generation process will free up time for developers to focus on more critical tasks, potentially accelerating project timelines. +- **Improved Documentation Quality**: AI can provide insights and suggestions that may not be immediately apparent to human authors, leading to richer and more informative ADRs. +- **Standardization**: The use of a consistent AI-generated format can enhance the readability and usability of ADRs across the project. + +### Negative Outcomes +- **Dependency on AI**: Relying on AI for documentation may lead to a lack of critical thinking and engagement from team members, potentially diminishing their understanding of architectural decisions. +- **Initial Setup Complexity**: Implementing AI solutions requires time and resources for setup, training, and integration, which may delay initial project progress. +- **Trust and Validation Issues**: Stakeholders may be hesitant to trust AI-generated content, necessitating a robust validation process that could counteract some of the efficiency gains. + +In summary, while the adoption of AI for generating ADRs presents several advantages in terms of efficiency and quality, it also introduces challenges that must be carefully managed to ensure successful implementation and stakeholder buy-in. +``` \ No newline at end of file diff --git a/docs/architecture.md b/docs/architecture.md index 5f28949..39f3344 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -4,29 +4,57 @@ The `ai-cursor-init` project is an AI-powered documentation framework designed t ### High-Level Architecture +```markdown +# AI Cursor Init Architecture Diagram + +This document presents a comprehensive architecture diagram for the AI Cursor Init project. The diagram visualizes the main components of the system, their relationships, data flow, external dependencies, and technology stack. + ```mermaid graph TD - A[Cursor IDE Integration] --> B[Slash Commands] - B --> C[AI Service Layer] - C --> D[Template Engine] - C --> E[Framework Detection] - C --> F[Diagram Generation] + A[AI Cursor Init] -->|Uses| B[CLI] + A -->|Depends on| C[Requirements] + A -->|Includes| D[Documentation] + A -->|Utilizes| E[External APIs] + A -->|Configured by| F[Configuration Files] + + B -->|Executes| G[Main Script] + B -->|Interacts with| E - G[CLI Tool] --> C + C -->|Contains| H[PyYAML] + C -->|Contains| I[OpenAI API] + C -->|Contains| J[Anthropic API] + C -->|Contains| K[Google Generative AI] + C -->|Contains| L[Python Dotenv] + C -->|Contains| M[Rich] + C -->|Contains| N[TikToken] - D --> H[Documentation Files] - E --> I[Project Analysis] - F --> J[Mermaid Diagrams] + F -->|Includes| O[pyproject.toml] + F -->|Includes| P[requirements.txt] + F -->|Includes| Q[.cursor-init.yaml] - C --> K[Multi-Provider AI] - K --> L[OpenAI] - K --> M[Anthropic] - K --> N[Gemini] + D -->|Contains| R[README.md] + D -->|Contains| S[CHANGELOG.md] + D -->|Contains| T[CONTRIBUTING.md] + D -->|Contains| U[AI_DOCUMENTATION.md] + D -->|Contains| V[SECURITY.md] + D -->|Contains| W[Architecture Docs] + D -->|Contains| X[Data Model Docs] + D -->|Contains| Y[Onboarding Docs] + + E -->|Provides| Z[AI Services] + E -->|Includes| AA[OpenAI] + E -->|Includes| AB[Anthropic] + E -->|Includes| AC[Google Generative AI] - H --> O[Architecture Docs] - H --> P[ADRs] - H --> Q[Onboarding Guides] - H --> R[ER Diagrams] + style A fill:#f9f,stroke:#333,stroke-width:2px + style B fill:#bbf,stroke:#333,stroke-width:2px + style C fill:#bbf,stroke:#333,stroke-width:2px + style D fill:#bbf,stroke:#333,stroke-width:2px + style E fill:#bbf,stroke:#333,stroke-width:2px + style F fill:#bbf,stroke:#333,stroke-width:2px +``` + +This diagram illustrates the architecture of the AI Cursor Init project, highlighting the key components such as the CLI, external APIs, configuration files, and documentation. Each component is connected to show how they interact and depend on each other, providing a clear overview of the system's structure and functionality. ``` ### Core Components diff --git a/docs/data-model.md b/docs/data-model.md index 146b4df..60db95e 100644 --- a/docs/data-model.md +++ b/docs/data-model.md @@ -1,87 +1,227 @@ -# Project Data Model +# AI-Cursor-Init Data Model -This document describes the data structures and configuration models used by the AI-cursor-init framework. +This document describes the data structures and models used by the AI-powered cursor-init framework. -## Configuration Data Model +## Core Architecture -The ai-cursor-init framework uses YAML-based configuration and dataclass models for managing settings and project metadata. +The ai-cursor-init framework follows an AI-first architecture where all documentation is generated through intelligent analysis of your codebase, rather than static templates. ```mermaid erDiagram - AIConfig { - provider string "openai|anthropic|gemini" - api_key string - model string - base_url string - temperature float - max_tokens int + AIProvider { + string provider_type "openai|anthropic|gemini" + string api_key + string model_name + string base_url + float temperature + int max_tokens } - ProjectFrameworks { - languages set "python|typescript|javascript" - frameworks set "fastapi|django|react|nextjs|sqlalchemy" - root_directory string + ProjectAnalysis { + string project_name + string root_directory + json technologies "languages, frameworks, databases, tools" + json file_structure "source files, config files, directories" + json imports_analysis "dependency relationships" + text cursor_rules "parsed cursor rules content" } - TemplateConfig { - template_paths dict - custom_template_paths dict - variant_preferences dict + AIGenerationRequest { + string document_type "architecture|onboarding|adr|data-model|security" + string system_prompt + string context_prompt + json project_context + string output_format "markdown" } - DocumentationFile { - filepath string - content_type string "adr|architecture|onboarding|data-model" - last_updated datetime - template_used string - ai_generated boolean + GeneratedDocument { + string filepath + string content_type + string ai_model_used + datetime generated_at + json generation_metadata + text content + boolean validated } - AIConfig ||--|| ProjectFrameworks : "configures generation for" - TemplateConfig ||--o{ DocumentationFile : "templates generate" - ProjectFrameworks ||--o{ DocumentationFile : "influences content of" + DocumentationConfig { + json ai_preferences + json quality_settings + json custom_document_types + json analysis_settings + } + + AIProvider ||--o{ AIGenerationRequest : "processes" + ProjectAnalysis ||--|| AIGenerationRequest : "provides context for" + AIGenerationRequest ||--|| GeneratedDocument : "produces" + DocumentationConfig ||--o{ AIGenerationRequest : "configures" +``` + +## Key Components + +### AI Provider Integration + +- **Multi-Provider Support**: OpenAI GPT, Anthropic Claude, Google Gemini +- **Dynamic Model Selection**: Automatically selects best available model +- **Error Handling**: Graceful fallbacks and retry mechanisms +- **Rate Limiting**: Built-in request throttling and quota management + +### Intelligent Project Analysis + +- **Language Detection**: Automatic detection of all programming languages +- **Framework Recognition**: Identifies frameworks without hardcoded limitations +- **Dependency Analysis**: Analyzes imports and dependencies across languages +- **Structural Understanding**: Maps project architecture and component relationships + +### Context-Aware Generation + +- **Cursor Rules Integration**: Leverages project-specific rules and guidelines +- **Codebase Analysis**: Deep understanding of project structure and patterns +- **Technology-Specific**: Tailored documentation for each tech stack +- **Evolutionary**: Adapts to changes in codebase over time + +## Data Flow + +### 1. Project Discovery Phase + +``` +Project Root → File Scanner → Technology Detector → Dependency Analyzer + ↓ + Project Analysis Object +``` + +### 2. Context Enrichment Phase + +``` +Project Analysis + Cursor Rules + User Preferences → AI Context Builder + ↓ + Enriched Generation Context +``` + +### 3. AI Generation Phase + +``` +Context + Document Type + AI Provider → AI Generation Engine + ↓ + Generated Documentation +``` + +### 4. Validation & Output Phase + +``` +Generated Content → Quality Validator → File Writer → Documentation +``` + +## Configuration Schema + +### AI Configuration + +```yaml +ai: + preferred_provider: "anthropic" # openai|anthropic|gemini + providers: + openai: + model: "o3" + temperature: 0.1 + max_tokens: 4000 + anthropic: + model: "claude-sonnet-4" + temperature: 0.1 + max_tokens: 4000 + gemini: + model: "gemini-2.5-pro" + temperature: 0.1 + max_tokens: 4000 +``` + +### Generation Settings + +```yaml +generation: + analysis_depth: "comprehensive" # basic|standard|comprehensive + include_code_examples: true + auto_generate_toc: true + quality: + min_content_length: 500 + max_retries: 3 + validate_generation: true ``` -## Key Data Structures +## Technology Detection Capabilities + +### Supported Languages & Frameworks + +The AI system automatically detects and generates appropriate documentation for: + +**Languages:** Python, TypeScript, JavaScript, Go, Rust, Java, C++, C, PHP, Ruby, Kotlin, Swift, C#, Scala, Clojure + +**Python Frameworks:** FastAPI, Django, Flask, SQLAlchemy, Pydantic, Celery, Pytest + +**JavaScript/TypeScript:** React, Next.js, Vue.js, Angular, Express.js, NestJS, Svelte -### AI Provider Configuration +**Databases:** PostgreSQL, MySQL, MongoDB, Redis, SQLite, Cassandra, DynamoDB -- **Purpose:** Manages multi-provider AI integration settings -- **Location:** Stored in project-local `.ai-cursor-init.yaml` or user config -- **Supported Providers:** OpenAI, Anthropic Claude, Google Gemini +**Tools:** Docker, Kubernetes, GitHub Actions, Jenkins, Terraform, Ansible -### Framework Detection Results +**Architecture Patterns:** Microservices, Monolith, Serverless, Event-Driven, CQRS -- **Purpose:** Stores detected project technologies and frameworks -- **Usage:** Influences template selection and content generation -- **Detection Sources:** File analysis, dependency scanning, structure patterns +## Quality Assurance -### Template Metadata +### Content Validation -- **Purpose:** Maps document types to available templates and variants -- **Customization:** Supports custom template paths and user preferences -- **Inheritance:** Default templates can be overridden by project-specific ones +- **Relevance Scoring**: AI-generated content relevance to project context +- **Completeness Checks**: Ensures all required sections are present +- **Technical Accuracy**: Validates technical information against project reality +- **Consistency Verification**: Maintains consistent terminology and style -### Documentation Tracking +### Continuous Improvement -- **Purpose:** Maintains metadata about generated documentation files -- **Features:** Tracks AI generation status, template usage, and freshness -- **Integration:** Used by `/check-docs` for validation and staleness detection +- **Feedback Loop**: User feedback improves future generations +- **Pattern Learning**: AI learns from successful documentation patterns +- **Context Evolution**: Adapts to changing project requirements +- **Quality Metrics**: Tracks and improves generation quality over time -## File-Based Data Storage +## Security & Privacy -This project primarily uses file-based storage rather than traditional databases: +### Code Analysis Safety -- **Configuration Files:** YAML format for settings and preferences -- **Documentation Files:** Markdown format with embedded Mermaid diagrams -- **Template Files:** Jinja2-style templates with framework-specific variants -- **Metadata:** Embedded in file headers and configuration sections +- **Static Analysis Only**: No code execution or dynamic imports +- **Sandboxed Processing**: Isolated analysis environment +- **Data Minimization**: Only processes necessary code patterns +- **Local Processing**: Analysis happens locally, only prompts sent to AI -## Data Flow Pattern +### API Key Management + +- **Environment Variables**: Secure storage of API credentials +- **Local Configuration**: Project-specific settings stored locally +- **No Credential Sharing**: API keys never included in generated content +- **Rotation Support**: Easy credential rotation and provider switching + +## Extension Points + +### Custom Document Types + +```python +def generate_security_docs(self, project_root: str = '.') -> str: + context = self._analyze_project_structure(project_root) + system_prompt = "You are a security expert..." + return self.ai_service.generate_content(system_prompt, context) +``` + +### Custom Analysis + +```python +def _detect_custom_framework(self, config_contents: Dict[str, str]) -> bool: + # Custom framework detection logic + return 'custom-framework' in str(config_contents).lower() +``` + +### Custom Validation + +```python +def validate_custom_content(self, content: str) -> bool: + # Custom validation logic + return len(content) > 100 and 'required_keyword' in content +``` -1. **Configuration Loading:** Read user/project AI and template settings -2. **Project Analysis:** Scan codebase to detect frameworks and structure -3. **Template Selection:** Choose appropriate templates based on detected frameworks -4. **AI Context Building:** Aggregate project information for AI providers -5. **Content Generation:** Generate documentation using AI + templates -6. **File Management:** Write/update documentation files with metadata tracking +This AI-first data model enables limitless extensibility while maintaining high-quality, contextually relevant documentation generation for any technology stack.