📚 docs(changelog): Updated CHANGELOG format and added ChangelogGenerator class documentation

This commit is contained in:
sankalp committed 2025-06-04 14:04:36 +05:45
1 parent d54e83941c
commit 984ec2f9ed
2 files changed
+230 -53

No files matched your search

+40 -34
View File
@@ -4,54 +4,60 @@
### ✨ Added
- - Updated changelog format and added ChangelogGenerator class documentation 📚
- - Updated `README.md` file with the new commit message format and ChangelogGenerator class usage 📝
- ## Added
- - ✨ docs(changelog): Add ChangelogGenerator class documentation
- - 🐛 docs(changelog): Updated changelog format and added ChangelogGenerator class documentation
- ### ✨ Added
- - - Updated changelog format and added ChangelogGenerator class documentation 📚
- - - Updated `README.md` file with the new commit message format and ChangelogGenerator class usage 📝
- - ## Added
- - - ✨ docs(changelog): Add ChangelogGenerator class documentation
- - - 🐛 docs(changelog): Updated changelog format and added ChangelogGenerator class documentation
### 🔄 Changed
- # Changelog
- Changes for version 0.1.0:
- - Updated commit message and changelog generation using AI 👨‍💻
- - Updated `GitService` to return exit code from `_run_git_command` and improved `get_commit_history` method signature and docstring 📝
- - Updated changelog format for clarity and consistency 📚
- - Updated `LICENSE` 📄
- ### Changed
- - 🔄 refactor(generator): Update commit message and changelog generation using AI
- - 🔄 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
- - 📚 Update `README.md` file
- - 📚 Update `LICENSE`
- ### 🔄 Changed
- - # Changelog
- - Changes for version 0.1.0:
- - - Updated commit message and changelog generation using AI 👨‍💻
- - - Updated `GitService` to return exit code from `_run_git_command` and improved `get_commit_history` method signature and docstring 📝
- - - Updated changelog format for clarity and consistency 📚
- - - Updated `LICENSE` 📄
- - ### Changed
- - - 🔄 refactor(generator): Update commit message and changelog generation using AI
- - - 🔄 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
- - - 📚 Update `README.md` file
- - - 📚 Update `LICENSE`
### 🐛 Fixed
- ### Fixed
### 📝 Documentation
- 📝 Documentation:
- ### 🐛 Fixed
- - ### Fixed
### 🔧 Maintenance
- ## 0.1.0
- 🔧 Maintenance:
- - Cleaned up user prompt text in commit message generation
- Initial:
- - Initial commit 🚀
- - 🔄 refactor(git.py): Remove unused variable in `_run_git_command` calls
- ### Initial Commits
- - 🚀 Initial commit
- - 🗑️ Not applicable for this version
- - 🔒 Not applicable for this version
- ## v0.1.0
- ### 📝 Documentation
- - 📝 Documentation:
- ### 🔧 Maintenance
- - ## 0.1.0
- - 🔧 Maintenance:
- - - Cleaned up user prompt text in commit message generation
- - Initial:
- - - Initial commit 🚀
- - - 🔄 refactor(git.py): Remove unused variable in `_run_git_command` calls
- - ### Initial Commits
- - - 🚀 Initial commit
- - - 🗑️ Not applicable for this version
- - - 🔒 Not applicable for this version
### 🗑️ Removed
- - Removed unused variable in `_run_git_command` calls 🗑️
- ### Removed
- ### 🗑️ Removed
- - - Removed unused variable in `_run_git_command` calls 🗑️
- - ### Removed
### 🔒 Security
- ### Security
- ### 🔒 Security
- - ### Security
+190 -19
View File
@@ -2,10 +2,10 @@
Commit message and changelog generation using AI.
"""
from typing import List, Dict, Optional, Tuple
from typing import Dict, List, Optional
from ..shared.ai import AIService
from ..shared.config import Config
from concurrent.futures import ThreadPoolExecutor
class CommitGenerator(AIService):
@@ -108,6 +108,123 @@ Output ONLY the commit message in the correct format (with emoji)."""
return message
def _parse_analysis_result(self, analysis: str) -> bool:
"""Parse and validate the analysis result from AI.
Args:
analysis: Raw analysis output from AI
Returns:
bool: True if changes should be grouped, False otherwise
Raises:
ValueError: If analysis result cannot be reliably determined
"""
# Normalize the analysis text
analysis = analysis.strip().upper()
# Direct matches
if "GROUP" in analysis and "SEPARATE" not in analysis:
return True
if "SEPARATE" in analysis and "GROUP" not in analysis:
return False
# Check for strong indicators in the explanation
group_indicators = [
"RELATED",
"SAME FEATURE",
"SAME FIX",
"SAME SCOPE",
"SAME PURPOSE",
"SHOULD BE GROUPED",
"COMBINED",
]
separate_indicators = [
"UNRELATED",
"DIFFERENT",
"SEPARATE",
"INDEPENDENT",
"SHOULD BE SEPARATE",
"DISTINCT",
]
# Count indicators
group_count = sum(1 for ind in group_indicators if ind in analysis)
separate_count = sum(1 for ind in separate_indicators if ind in analysis)
# If we have a clear majority, use that
if group_count > separate_count and group_count > 0:
return True
if separate_count > group_count and separate_count > 0:
return False
# If we can't determine reliably, raise an error
raise ValueError(
f"Could not reliably determine grouping from analysis: {analysis}"
)
def _validate_commit_message(self, message: str) -> str:
"""Validate and clean up a generated commit message.
Args:
message: Raw commit message from AI
Returns:
str: Cleaned and validated commit message
"""
# Remove any markdown code blocks
message = message.replace("```", "").strip()
# Remove any explanatory text
lines = [line.strip() for line in message.split("\n") if line.strip()]
for line in lines:
# Skip lines that look like explanations
if any(
skip in line.lower()
for skip in [
"based on",
"changes in",
"can be written",
"commit message",
"following",
"these changes",
"the changes",
"here's",
"this is",
]
):
continue
# Skip lines that look like code or commands
if line.startswith("`") or line.startswith("/") or ":" in line:
continue
message = line
break
else:
message = lines[0] if lines else "🔧 chore: update code"
# Ensure proper emoji prefix
if not any(
emoji in message for emoji in ["✨", "🐛", "📚", "💅", "♻️", "✅", "🔧"]
):
if message.startswith("feat"):
message = "✨ " + message
elif message.startswith("fix"):
message = "🐛 " + message
elif message.startswith("docs"):
message = "📚 " + message
elif message.startswith("style"):
message = "💅 " + message
elif message.startswith("refactor"):
message = "♻️ " + message
elif message.startswith("test"):
message = "✅ " + message
elif message.startswith("chore"):
message = "🔧 " + message
else:
message = "🔧 " + message # fallback
return message
def generate_batch_messages(
self, diffs: Dict[str, str], temperature: Optional[float] = None
) -> Dict[str, str]:
@@ -128,7 +245,8 @@ Output ONLY the commit message in the correct format (with emoji)."""
if len(diffs) == 1:
file_path, diff = next(iter(diffs.items()))
return {file_path: self.generate_commit_message(diff, temperature)}
message = self.generate_commit_message(diff, temperature)
return {file_path: self._validate_commit_message(message)}
# First, analyze the diffs to determine if they should be grouped
system_prompt = """You are an expert at analyzing code changes and determining their relationships.
@@ -148,13 +266,20 @@ Include a brief explanation of your reasoning."""
f"File: {file_path}\nChanges:\n{diff}" for file_path, diff in diffs.items()
)
analysis = self.generate_completion(
system_prompt,
f"Analyze these changes:\n\n{analysis_input}",
temperature=0.1, # Lower temperature for more consistent analysis
)
should_group = "GROUP" in analysis.upper()
try:
analysis = self.generate_completion(
system_prompt,
f"Analyze these changes:\n\n{analysis_input}",
temperature=0.1, # Lower temperature for more consistent analysis
)
should_group = self._parse_analysis_result(analysis)
except Exception as e:
print(
f"Warning: Failed to analyze changes, defaulting to separate messages: {
e
}"
)
should_group = False
if should_group:
# Generate a single commit message for all changes
@@ -166,6 +291,7 @@ Include a brief explanation of your reasoning."""
)
message = self.generate_commit_message(structured_diff, temperature)
message = self._validate_commit_message(message)
return {file_path: message for file_path in diffs.keys()}
except Exception as e:
# Fallback to individual messages if combined generation fails
@@ -173,16 +299,61 @@ Include a brief explanation of your reasoning."""
should_group = False
if not should_group:
# Generate individual messages for each file
# Generate individual messages for each file in parallel
import time
from concurrent.futures import ThreadPoolExecutor
from queue import Queue
from threading import Lock
# Rate limiting setup
RATE_LIMIT = 5 # requests per second
rate_limit_queue = Queue()
rate_limit_lock = Lock()
last_request_time = time.time()
def rate_limited_generate(file_path: str, diff: str) -> tuple[str, str]:
"""Generate commit message with rate limiting."""
nonlocal last_request_time
with rate_limit_lock:
# Calculate time to wait
current_time = time.time()
time_since_last = current_time - last_request_time
if time_since_last < 1.0 / RATE_LIMIT:
time.sleep(1.0 / RATE_LIMIT - time_since_last)
try:
message = self.generate_commit_message(diff, temperature)
message = self._validate_commit_message(message)
last_request_time = time.time()
return file_path, message
except Exception as e:
print(
f"Warning: Failed to generate message for {file_path}: {e}"
)
return file_path, "🔧 chore: update code"
# Use ThreadPoolExecutor with a reasonable number of workers
max_workers = min(len(diffs), 10) # Cap at 10 concurrent workers
results = {}
for file_path, diff in diffs.items():
try:
message = self.generate_commit_message(diff, temperature)
results[file_path] = message
except Exception as e:
# Fallback to a basic message if generation fails
print(f"Warning: Failed to generate message for {file_path}: {e}")
results[file_path] = "🔧 chore: update code"
with ThreadPoolExecutor(max_workers=max_workers) as executor:
# Submit all tasks
future_to_file = {
executor.submit(rate_limited_generate, file_path, diff): file_path
for file_path, diff in diffs.items()
}
# Collect results as they complete
for future in future_to_file:
try:
file_path, message = future.result()
results[file_path] = message
except Exception as e:
file_path = future_to_file[future]
print(f"Warning: Failed to process {file_path}: {e}")
results[file_path] = "🔧 chore: update code"
return results
def generate_changelog(