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:
@@ -1,8 +1,10 @@
|
|||||||
#!/usr/bin/env python3
|
#!/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 argparse
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
|
|
||||||
@@ -20,31 +22,71 @@ SECTIONS = {
|
|||||||
"revert": "Reverts",
|
"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:
|
def parse_args() -> argparse.Namespace:
|
||||||
parser = argparse.ArgumentParser(description="Generate changelog from git commits.")
|
parser = argparse.ArgumentParser(
|
||||||
parser.add_argument("--from", dest="from_ref", default="HEAD~50")
|
description="Generate changelog from git commits or piped input.",
|
||||||
parser.add_argument("--to", dest="to_ref", default="HEAD")
|
epilog="Examples:\n"
|
||||||
parser.add_argument("--format", choices=["markdown", "text"], default="markdown")
|
" %(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()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
def get_git_log(from_ref: str, to_ref: str) -> list[str]:
|
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}"
|
commit_range = f"{from_ref}..{to_ref}"
|
||||||
cmd = ["git", "log", "--pretty=format:%s", commit_range]
|
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()]
|
lines = [line.strip() for line in result.stdout.splitlines() if line.strip()]
|
||||||
return lines
|
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]]:
|
def group_commits(subjects: list[str]) -> dict[str, list[str]]:
|
||||||
grouped = defaultdict(list)
|
grouped: dict[str, list[str]] = defaultdict(list)
|
||||||
grouped["other"] = []
|
|
||||||
|
|
||||||
for subject in subjects:
|
for subject in subjects:
|
||||||
commit_type = "other"
|
commit_type = "other"
|
||||||
for prefix in SECTIONS:
|
for prefix in SECTIONS:
|
||||||
if subject.startswith(f"{prefix}:"):
|
if subject.startswith(f"{prefix}:") or subject.startswith(f"{prefix}("):
|
||||||
commit_type = prefix
|
commit_type = prefix
|
||||||
break
|
break
|
||||||
grouped[commit_type].append(subject)
|
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:
|
def render_text(grouped: dict[str, list[str]]) -> str:
|
||||||
out = []
|
out: list[str] = []
|
||||||
ordered_types = list(SECTIONS.keys()) + ["other"]
|
ordered_types = list(SECTIONS.keys()) + ["other"]
|
||||||
for commit_type in ordered_types:
|
for commit_type in ordered_types:
|
||||||
commits = grouped.get(commit_type, [])
|
commits = grouped.get(commit_type, [])
|
||||||
@@ -84,7 +126,18 @@ def render_text(grouped: dict[str, list[str]]) -> str:
|
|||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
args = parse_args()
|
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)
|
grouped = group_commits(subjects)
|
||||||
|
|
||||||
if args.format == "markdown":
|
if args.format == "markdown":
|
||||||
|
|||||||
Reference in New Issue
Block a user