fix(product): resolve merge conflicts and improve changelog generator

- Resolve plugin.json conflict: keep version 2.1.2, update to 12 skills
- Resolve CLAUDE.md conflict: merge detailed tool docs with new skills
- Improve changelog_generator.py: add --stdin, --demo modes, graceful
  error when git unavailable, support scoped prefixes (feat(scope):)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Reza Rezvani
2026-03-11 15:00:48 +01:00
parent be61f84e36
commit d18c63d2aa

View File

@@ -1,8 +1,10 @@
#!/usr/bin/env python3
"""Generate changelog sections from git log using conventional commit prefixes."""
"""Generate changelog sections from git log or piped commit messages using conventional commit prefixes."""
import argparse
import shutil
import subprocess
import sys
from collections import defaultdict
@@ -20,31 +22,71 @@ SECTIONS = {
"revert": "Reverts",
}
DEMO_COMMITS = [
"feat: add user dashboard with analytics widgets",
"feat: implement dark mode toggle",
"fix: resolve crash on empty CSV import",
"fix: correct timezone offset in calendar view",
"docs: update API reference for v2 endpoints",
"refactor: extract shared validation into utils module",
"chore: bump dependencies to latest patch versions",
"perf: optimize database queries for user listing",
]
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Generate changelog from git commits.")
parser.add_argument("--from", dest="from_ref", default="HEAD~50")
parser.add_argument("--to", dest="to_ref", default="HEAD")
parser.add_argument("--format", choices=["markdown", "text"], default="markdown")
parser = argparse.ArgumentParser(
description="Generate changelog from git commits or piped input.",
epilog="Examples:\n"
" %(prog)s --from v1.0.0 --to HEAD\n"
" git log --pretty=format:%%s v1.0..HEAD | %(prog)s --stdin\n"
" %(prog)s --demo\n",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument("--from", dest="from_ref", default="HEAD~50",
help="Start ref for git log (default: HEAD~50)")
parser.add_argument("--to", dest="to_ref", default="HEAD",
help="End ref for git log (default: HEAD)")
parser.add_argument("--format", choices=["markdown", "text"], default="markdown",
help="Output format (default: markdown)")
parser.add_argument("--stdin", action="store_true",
help="Read commit subjects from stdin instead of git log")
parser.add_argument("--demo", action="store_true",
help="Run with sample data (no git required)")
return parser.parse_args()
def get_git_log(from_ref: str, to_ref: str) -> list[str]:
"""Get commit subjects from git log. Requires git on PATH and a git repo."""
if not shutil.which("git"):
print("Error: git not found on PATH. Use --stdin or --demo instead.", file=sys.stderr)
sys.exit(1)
commit_range = f"{from_ref}..{to_ref}"
cmd = ["git", "log", "--pretty=format:%s", commit_range]
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
except subprocess.TimeoutExpired:
print("Error: git log timed out.", file=sys.stderr)
sys.exit(1)
if result.returncode != 0:
print(f"Error: git log failed: {result.stderr.strip()}", file=sys.stderr)
sys.exit(1)
lines = [line.strip() for line in result.stdout.splitlines() if line.strip()]
return lines
def read_stdin() -> list[str]:
"""Read commit subjects from stdin, one per line."""
return [line.strip() for line in sys.stdin if line.strip()]
def group_commits(subjects: list[str]) -> dict[str, list[str]]:
grouped = defaultdict(list)
grouped["other"] = []
grouped: dict[str, list[str]] = defaultdict(list)
for subject in subjects:
commit_type = "other"
for prefix in SECTIONS:
if subject.startswith(f"{prefix}:"):
if subject.startswith(f"{prefix}:") or subject.startswith(f"{prefix}("):
commit_type = prefix
break
grouped[commit_type].append(subject)
@@ -68,7 +110,7 @@ def render_markdown(grouped: dict[str, list[str]]) -> str:
def render_text(grouped: dict[str, list[str]]) -> str:
out = []
out: list[str] = []
ordered_types = list(SECTIONS.keys()) + ["other"]
for commit_type in ordered_types:
commits = grouped.get(commit_type, [])
@@ -84,7 +126,18 @@ def render_text(grouped: dict[str, list[str]]) -> str:
def main() -> int:
args = parse_args()
subjects = get_git_log(args.from_ref, args.to_ref)
if args.demo:
subjects = DEMO_COMMITS
elif args.stdin:
subjects = read_stdin()
else:
subjects = get_git_log(args.from_ref, args.to_ref)
if not subjects:
print("No commits found.", file=sys.stderr)
return 0
grouped = group_commits(subjects)
if args.format == "markdown":