From c5bf81bc06b3ae5f72752331e37dfa30be2cb453 Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Wed, 4 Jun 2025 13:31:03 +0545 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 39 ++++++--- devtools/changelog/generator.py | 140 ++++++++++++++++++++------------ devtools/commitgen/generator.py | 41 ++++++---- 3 files changed, 140 insertions(+), 80 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28c7e29..de973b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,20 +1,33 @@ +# Changelog + ## 0.1.0 -Version 0.1.0 - Initial Release +### Added -📝 Documentation: -- Updated ChangelogGenerator class documentation in docs(changelog) -- Updated changelog format for clarity and consistency in docs(changelog) -- Added ChangelogGenerator class documentation in docs(changelog) -- Updated LICENSE +* ✨ docs(changelog): Add ChangelogGenerator class documentation -🔧 Maintenance: -- Removed unused variable in _run\_git\_command calls in refactor(git.py) -- Updated GitService to return exit code from _run\_git\_command and improved get\_commit\_history method signature and docstring in refactor(devtools/shared/git.py) +### Changed -📚 Documentation: -- Updated changelog format in docs(changelog) +* 🔄 refactor(generator): Update commit message and changelog generation using AI +* 🔄 refactor(git.py): Remove unused variable in `_run_git_command` calls +* 🔄 refactor(devtools/shared/git.py): Update GitService to return exit code from `_run_git_command` and improve `get_commit_history` method signature and docstring +* 🔄 docs(changelog): Updated changelog format for clarity and consistency -🔒 Security: -- Initial commit +### Fixed + +* 🐛 docs(changelog): Updated changelog format and added ChangelogGenerator class documentation + +### Initial Commits + +* 🚀 Initial commit +* 📚 Update `README.md` file +* 📚 Update `LICENSE` + +### Removed + +* 🗑️ Not applicable for this version + +### Security + +* 🔒 Not applicable for this version diff --git a/devtools/changelog/generator.py b/devtools/changelog/generator.py index 618401e..d9349d5 100644 --- a/devtools/changelog/generator.py +++ b/devtools/changelog/generator.py @@ -1,50 +1,68 @@ """ Changelog generation using AI. """ + from typing import List, Dict, Optional from ..shared.ai import AIService from ..shared.config import Config from ..shared.git import GitService +import re + class ChangelogGenerator(AIService): """AI-powered changelog generator.""" - + def __init__(self, config: Config, git_service: Optional[GitService] = None): """Initialize changelog generator. - + Args: config: Configuration object git_service: Optional GitService instance """ super().__init__(config) self.git_service = git_service - + def get_commits(self, from_tag: str) -> List[Dict[str, str]]: """Get commits since a tag.""" if not self.git_service: raise ValueError("GitService not provided") return self.git_service.get_commits_since_tag(from_tag) - + def get_commits_since_date(self, days: int) -> List[Dict[str, str]]: """Get commits since a date.""" if not self.git_service: raise ValueError("GitService not provided") return self.git_service.get_commits_since_date(days) - + def get_last_n_commits(self, n: int) -> List[Dict[str, str]]: """Get last N commits.""" if not self.git_service: raise ValueError("GitService not provided") return self.git_service.get_commit_history(limit=n) - - def generate_changelog(self, version: str, changes: List[Dict[str, str]], temperature: Optional[float] = None) -> str: + + def _extract_pr_reference(self, message: str) -> Optional[str]: + """Extract PR reference from commit message if it exists.""" + # Look for patterns like (#123) or (#123, #456) + pr_pattern = r"\(#(\d+)(?:,\s*#\d+)*\)" + match = re.search(pr_pattern, message) + if match: + # Return the full match including parentheses + return match.group(0) + return None + + def generate_changelog( + self, + version: str, + changes: List[Dict[str, str]], + temperature: Optional[float] = None, + ) -> str: """Generate a changelog from a list of changes. - + Args: version: Version number for the changelog changes: List of changes, each with 'message' and 'hash' temperature: AI temperature for generation - + Returns: Generated changelog content """ @@ -62,25 +80,30 @@ class ChangelogGenerator(AIService): - 📝 Documentation - 🔧 Maintenance - 🗑️ Removed - - 🔒 Security""" - - # Format changes for the prompt - changes_text = "\n".join( - f"{change['message']}" - for change in changes - ) - - user_prompt = f"Generate a changelog for version {version} with these changes:\n\n{changes_text}" - + - 🔒 Security + 6. Only include PR references if they exist in the original commit message + 7. Keep the changelog clean and professional""" + + # Format changes for the prompt, preserving PR references + changes_text = "\n".join(f"{change['message']}" for change in changes) + + user_prompt = f"Generate a changelog for version { + version + } with these changes:\n\n{changes_text}" + return self.generate_completion( - system_prompt, - user_prompt, - temperature=temperature + system_prompt, user_prompt, temperature=temperature ) - - def update_changelog_file(self, version: str, changes: List[Dict[str, str]], output_file: str, temperature: Optional[float] = None) -> None: + + def update_changelog_file( + self, + version: str, + changes: List[Dict[str, str]], + output_file: str, + temperature: Optional[float] = None, + ) -> None: """Generate and update a changelog file. - + Args: version: Version number for the changelog changes: List of changes, each with 'message' and 'hash' @@ -88,41 +111,55 @@ class ChangelogGenerator(AIService): temperature: AI temperature for generation """ changelog_content = self.generate_changelog(version, changes, temperature) - + # Read existing changelog if it exists try: with open(output_file, "r") as f: existing_content = f.read() except FileNotFoundError: existing_content = "" - - # Add new changelog entry at the top - new_content = f"## {version}\n\n{changelog_content}\n\n" + + # Create new version entry + new_version_entry = f"## {version}\n\n{changelog_content}\n\n" + if existing_content: - new_content += existing_content - + # Check if version already exists + version_pattern = f"## {version}\n" + if version_pattern in existing_content: + # Replace existing version content + pattern = f"{version_pattern}.*?(?=## |$)" + new_content = re.sub( + pattern, new_version_entry, existing_content, flags=re.DOTALL + ) + else: + # Add new version at the top + new_content = new_version_entry + existing_content + else: + # Create new changelog + new_content = f"# Changelog\n\n{new_version_entry}" + # Write updated changelog with open(output_file, "w") as f: f.write(new_content) - + def parse_changes_from_text(self, text: str) -> List[Dict[str, str]]: """Parse changes from text input. - + Args: text: Text containing changes - + Returns: List of changes with type and description """ changes = [] current_type = None current_description = [] - + for line in text.splitlines(): line = line.strip() if not line: continue - + # Check if line starts with a type marker type_markers = { "✨": "Added", @@ -132,32 +169,33 @@ class ChangelogGenerator(AIService): "📝": "Documentation", "🔧": "Maintenance", "🗑️": "Removed", - "🔒": "Security" + "🔒": "Security", } - + for marker, type_ in type_markers.items(): if line.startswith(marker): # Save previous change if exists if current_type and current_description: - changes.append({ - "type": current_type, - "description": " ".join(current_description) - }) - + changes.append( + { + "type": current_type, + "description": " ".join(current_description), + } + ) + # Start new change current_type = type_ - current_description = [line[len(marker):].strip()] + current_description = [line[len(marker) :].strip()] break else: # Continue current change if current_type: current_description.append(line) - + # Add last change if exists if current_type and current_description: - changes.append({ - "type": current_type, - "description": " ".join(current_description) - }) - - return changes \ No newline at end of file + changes.append( + {"type": current_type, "description": " ".join(current_description)} + ) + + return changes diff --git a/devtools/commitgen/generator.py b/devtools/commitgen/generator.py index 7fb7c95..1bde25e 100644 --- a/devtools/commitgen/generator.py +++ b/devtools/commitgen/generator.py @@ -20,12 +20,12 @@ class CommitGenerator(AIService): """Generate a commit message from a diff.""" system_prompt = """You are an expert at writing clear, concise, and meaningful git commit messages. Follow these rules strictly: -1. Format: type(scope): description +2. Format: type(scope): description - type: One of feat, fix, docs, style, refactor, test, chore - scope: Optional, describes the section of the codebase affected - description: Clear, concise explanation of the change -2. Types and their emojis: +3. Types and their emojis: - feat: ✨ (new feature or enhancement) - fix: 🐛 (bug fix) - docs: 📚 (documentation changes) @@ -34,17 +34,19 @@ class CommitGenerator(AIService): - test: ✅ (adding or modifying tests) - chore: 🔧 (maintenance tasks, dependencies, etc) -3. Guidelines: - - Keep the description under 72 characters +4. Guidelines: + - Keep the description under 73 characters - Use imperative mood ("add" not "added") - Don't end with a period - Focus on the "why" not the "what" - Be specific about the change - Use present tense - Start with a verb + - Don't include pull request references in the main message + - Keep the message clean and focused on the change itself -4. Examples: - - ✨ feat(auth): add OAuth2 authentication +5. Examples: + - ✨ feat(auth): add OAuth3 authentication - 🐛 fix(api): handle null response from server - 📚 docs(readme): update installation instructions - 💅 style(ui): format button component @@ -52,25 +54,32 @@ class CommitGenerator(AIService): - ✅ test(api): add unit tests for user endpoints - 🔧 chore(deps): update dependencies -Analyze the changes carefully and write a commit message that accurately reflects the modifications.""" +Analyze the changes carefully and write a commit message that accurately reflects the modifications. Focus on the actual changes, not on pull request numbers or references.""" user_prompt = f"""Analyze these changes and write a conventional commit message: {diff} Focus on: -1. What type of change this is (feat, fix, etc.) -2. What part of the codebase is affected (scope) -3. What the change accomplishes (description) +2. What type of change this is (feat, fix, etc.) +3. What part of the codebase is affected (scope) +4. What the change accomplishes (description) -Write a single-line commit message following the conventional format.""" +Write a single-line commit message following the conventional format. Do not include pull request references in the main message.""" message = self.generate_completion( system_prompt, user_prompt, temperature=temperature ) # Clean up the response to ensure it's a valid commit message - message = message.split("\n")[0].strip() + message = message.split("\n")[1].strip() + + # Remove any pull request references from the main message + message = ( + message.split("(")[1].strip() + if "(" in message and "#" in message + else message + ) # Ensure the message starts with an emoji if not any( @@ -102,10 +111,10 @@ Write a single-line commit message following the conventional format.""" """Generate a changelog from a list of commits.""" system_prompt = """You are a helpful AI that generates changelogs. Follow these rules: - 1. Group changes by type (Added, Changed, Fixed, etc.) - 2. Keep descriptions concise but informative - 3. Use past tense - 4. Start each entry with a verb""" + 2. Group changes by type (Added, Changed, Fixed, etc.) + 3. Keep descriptions concise but informative + 4. Use past tense + 5. Start each entry with a verb""" user_prompt = ( f"Generate a changelog for version {version} with these commits:\n\n"