diff --git a/app/src/git_commit_generator/changelog_generator.py b/app/src/git_commit_generator/changelog_generator.py index aab0756..b35c362 100644 --- a/app/src/git_commit_generator/changelog_generator.py +++ b/app/src/git_commit_generator/changelog_generator.py @@ -1,10 +1,13 @@ """Changelog generation from git history.""" from datetime import datetime -from typing import Optional +from typing import TYPE_CHECKING, Optional from git_commit_generator.config import Config, get_config -from git_commit_generator.git_utils import GitUtils, get_git_utils -from git_commit_generator.ollama_client import OllamaClient, get_ollama_client +from git_commit_generator.ollama_client import OllamaClient +from git_commit_generator.git_utils import GitUtils + +if TYPE_CHECKING: + pass class ChangelogGenerator: @@ -51,55 +54,13 @@ class ChangelogGenerator: if not commits: raise ValueError("No commits found in repository.") + formatted = self._format_commits_for_prompt(commits) prompt = self.config.read_prompt("changelog.txt") - if not prompt: - prompt = self._get_default_changelog_prompt() - - commits_text = self._format_commits_for_prompt(commits) changelog = self.ollama_client.generate_changelog( - commits=commits_text, + commits=formatted, prompt=prompt, - model=self.config.ollama_model, ) - - changelog = self._clean_changelog(changelog) - - if output_path: - self._write_changelog(output_path, changelog) - - return changelog - - def _get_default_changelog_prompt(self) -> str: - """Get default changelog prompt.""" - return """Generate a changelog in markdown format from the following commits. -Group by type (feat, fix, docs, etc.) and format properly.""" - - def _format_commits_for_prompt(self, commits: list[dict]) -> str: - """Format commits for the LLM prompt.""" - lines = [] - for commit in commits: - lines.append( - f"[{commit['hash']}] {commit['message']} ({commit['author']}, {commit['date']})" - ) - return "\n".join(lines) - - def _clean_changelog(self, changelog: str) -> str: - """Clean and normalize changelog.""" - lines = changelog.strip().split("\n") - - cleaned_lines = [] - for line in lines: - line = line.strip() - if line and not line.startswith("```"): - cleaned_lines.append(line) - - return "\n".join(cleaned_lines) - - def _write_changelog(self, path: str, changelog: str) -> None: - """Write changelog to file.""" - with open(path, "w") as f: - f.write(f"# Changelog\n\nGenerated on {datetime.now().isoformat()}\n\n") - f.write(changelog) + return self._clean_changelog(changelog) def generate_simple( self, @@ -111,19 +72,18 @@ Group by type (feat, fix, docs, etc.) and format properly.""" commits = self.git_utils.get_conventional_commits(since=since, limit=limit) if not commits: - raise ValueError("No conventional commits found in repository.") + raise ValueError("No conventional commits found.") grouped = self._group_commits_by_type(commits) - changelog = self._format_simple_changelog(grouped) if output_path: - self._write_changelog(output_path, changelog) + Path(output_path).write_text(changelog) return changelog - def _group_commits_by_type(self, commits: list[dict]) -> dict: - """Group commits by their type.""" + def _group_commits_by_type(self, commits: list[dict]) -> dict[str, list[dict]]: + """Group commits by type.""" grouped = {} for commit in commits: commit_type = commit.get("type", "chore") @@ -132,29 +92,43 @@ Group by type (feat, fix, docs, etc.) and format properly.""" grouped[commit_type].append(commit) return grouped - def _format_simple_changelog(self, grouped: dict) -> str: + def _format_simple_changelog(self, grouped: dict[str, list[dict]]) -> str: """Format grouped commits into markdown changelog.""" - lines = ["# Changelog\n"] + lines = ["# Changelog", ""] - type_order = [ - "feat", "fix", "perf", "refactor", "docs", - "style", "test", "build", "ci", "chore", "revert" - ] + for commit_type, commits in grouped.items(): + type_name, _ = self.CHANGE_TYPES.get( + commit_type, ("Other", "white") + ) + lines.append(f"## {type_name}") + for commit in commits: + scope = commit.get("scope", "") + description = commit.get("description", "") + if scope: + lines.append(f"**{commit_type}({scope}):** {description}") + else: + lines.append(f"**{commit_type}:** {description}") + lines.append("") - for commit_type in type_order: - if commit_type in grouped: - type_name, _ = self.CHANGE_TYPES.get( - commit_type, (commit_type.title(), "white") - ) - lines.append(f"\n## {type_name}\n") - for commit in grouped[commit_type]: - scope = commit.get("scope", "") - description = commit.get("description", "") - if scope: - lines.append(f"- **{commit_type}({scope}):** {description}") - else: - lines.append(f"- **{commit_type}:** {description}") + return "\n".join(lines) + def _format_commits_for_prompt(self, commits: list[dict]) -> str: + """Format commits for LLM prompt.""" + return "\n".join( + f"[{c.get('hash', 'unknown')}] {c.get('message', '')} ({c.get('author', 'unknown')}, {c.get('date', 'unknown')})" + for c in commits + ) + + def _clean_changelog(self, raw: str) -> str: + """Clean up LLM-generated changelog.""" + lines = [] + for line in raw.split("\n"): + line = line.strip() + if line.startswith("```"): + continue + if "```" in line: + continue + lines.append(line) return "\n".join(lines)