AgentStack
SKILL unreviewed MIT Self-run

Changelog Generator

skill-seaworld008-commonly-used-high-value-skills-changelog-generator · by seaworld008

Parse conventional commits, determine semantic version bumps, and generate structured changelogs in Keep a Changelog format. Supports monorepo changelogs, GitHub Releases integration, and separates user-facing from developer changelogs.

No reviews yet
0 installs
15 views
0.0% view→install

Install

$ agentstack add skill-seaworld008-commonly-used-high-value-skills-changelog-generator

Open-source listing — not yet scanned by AgentStack. Follow the source repository for install instructions.

Security review

⚠ Flagged

1 finding(s); flagged for manual review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures
  • high Dangerous shell/eval execution.

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution Used
  • Environment & secrets No
  • Dynamic code execution Used

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Changelog Generator? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Changelog Generator

Tier: POWERFUL Category: Engineering Domain: Release Management / Documentation


Overview

Parse conventional commits, determine semantic version bumps, and generate structured changelogs in Keep a Changelog format. Supports monorepo changelogs, GitHub Releases integration, and separates user-facing from developer changelogs.

Core Capabilities

  • Conventional commit parsing — feat, fix, chore, docs, refactor, perf, test, build, ci
  • SemVer bump determination — breaking change → major, feat → minor, fix → patch
  • Keep a Changelog format — Added, Changed, Deprecated, Removed, Fixed, Security
  • Monorepo support — per-package changelogs with shared version strategy
  • GitHub/GitLab Releases — auto-create release with changelog body
  • Audience-aware output — user-facing (what changed) vs developer (why + technical details)

When to Use

  • Before every release to generate the CHANGELOG.md entry
  • Setting up automated changelog generation in CI
  • Converting git log into readable release notes for GitHub Releases
  • Maintaining monorepo changelogs for individual packages
  • Generating internal release notes for the engineering team

Conventional Commits Reference

(): 

[optional body]

[optional footer(s)]

Types and SemVer impact

| Type | Changelog section | SemVer bump | |------|------------------|-------------| | feat | Added | minor | | fix | Fixed | patch | | perf | Changed | patch | | refactor | Changed (internal) | patch | | docs | — (omit or include) | patch | | chore | — (omit) | patch | | test | — (omit) | patch | | build | — (omit) | patch | | ci | — (omit) | patch | | security | Security | patch | | deprecated | Deprecated | minor | | remove | Removed | major (if breaking) | | BREAKING CHANGE: footer | — (major bump) | major | | ! after type | — (major bump) | major |

Examples

feat(auth): add OAuth2 login with Google
fix(api): correct pagination offset calculation
feat!: rename /users endpoint to /accounts (BREAKING)
perf(db): add index on users.email column
security: patch XSS vulnerability in comment renderer
docs: update API reference for v2 endpoints

Changelog Generation Script

#!/usr/bin/env bash
# generate-changelog.sh — generate CHANGELOG entry for the latest release

set -euo pipefail

CURRENT_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
PREVIOUS_TAG=$(git describe --tags --abbrev=0 "${CURRENT_TAG}^" 2>/dev/null || echo "")
DATE=$(date +%Y-%m-%d)

if [ -z "$CURRENT_TAG" ]; then
  echo "No tags found. Create a tag first: git tag v1.0.0"
  exit 1
fi

RANGE="${PREVIOUS_TAG:+${PREVIOUS_TAG}..}${CURRENT_TAG}"
echo "Generating changelog for: $RANGE"

# Parse commits
ADDED=""
CHANGED=""
DEPRECATED=""
REMOVED=""
FIXED=""
SECURITY=""
BREAKING=""

while IFS= read -r line; do
  # Skip empty lines
  [ -z "$line" ] && continue
  
  # Detect type
  if [[ "$line" =~ ^feat(\([^)]+\))?\!:\ (.+)$ ]]; then
    desc="${BASH_REMATCH[2]}"
    BREAKING="${BREAKING}- **BREAKING** ${desc}\n"
    ADDED="${ADDED}- ${desc}\n"
  elif [[ "$line" =~ ^feat(\([^)]+\))?:\ (.+)$ ]]; then
    ADDED="${ADDED}- ${BASH_REMATCH[2]}\n"
  elif [[ "$line" =~ ^fix(\([^)]+\))?:\ (.+)$ ]]; then
    FIXED="${FIXED}- ${BASH_REMATCH[2]}\n"
  elif [[ "$line" =~ ^perf(\([^)]+\))?:\ (.+)$ ]]; then
    CHANGED="${CHANGED}- ${BASH_REMATCH[2]}\n"
  elif [[ "$line" =~ ^security(\([^)]+\))?:\ (.+)$ ]]; then
    SECURITY="${SECURITY}- ${BASH_REMATCH[2]}\n"
  elif [[ "$line" =~ ^deprecated(\([^)]+\))?:\ (.+)$ ]]; then
    DEPRECATED="${DEPRECATED}- ${BASH_REMATCH[2]}\n"
  elif [[ "$line" =~ ^remove(\([^)]+\))?:\ (.+)$ ]]; then
    REMOVED="${REMOVED}- ${BASH_REMATCH[2]}\n"
  elif [[ "$line" =~ ^refactor(\([^)]+\))?:\ (.+)$ ]]; then
    CHANGED="${CHANGED}- ${BASH_REMATCH[2]}\n"
  fi
done  "$TEMP"
  
  if [ -f CHANGELOG.md ]; then
    # Insert after the first line (# Changelog header)
    head -n 1 CHANGELOG.md >> "$TEMP"
    echo "" >> "$TEMP"
    printf "$OUTPUT" >> "$TEMP"
    tail -n +2 CHANGELOG.md >> "$TEMP"
  else
    echo "# Changelog" > CHANGELOG.md
    echo "All notable changes to this project will be documented here." >> CHANGELOG.md
    echo "" >> CHANGELOG.md
    cat "$TEMP" >> CHANGELOG.md
  fi
  
  mv "$TEMP" CHANGELOG.md
  echo "✅ CHANGELOG.md updated"
fi

Python Changelog Generator (more robust)

#!/usr/bin/env python3
"""generate_changelog.py — parse conventional commits and emit Keep a Changelog"""

import subprocess
import re
import sys
from datetime import date
from dataclasses import dataclass, field
from typing import Optional

COMMIT_RE = re.compile(
    r"^(?Pfeat|fix|perf|refactor|docs|test|chore|build|ci|security|deprecated|remove)"
    r"(?:\((?P[^)]+)\))?(?P!)?: (?P.+)$"
)

SECTION_MAP = {
    "feat": "Added",
    "fix": "Fixed",
    "perf": "Changed",
    "refactor": "Changed",
    "security": "Security",
    "deprecated": "Deprecated",
    "remove": "Removed",
}

@dataclass
class Commit:
    type: str
    scope: Optional[str]
    breaking: bool
    desc: str
    body: str = ""
    sha: str = ""

@dataclass
class ChangelogEntry:
    version: str
    date: str
    added: list[str] = field(default_factory=list)
    changed: list[str] = field(default_factory=list)
    deprecated: list[str] = field(default_factory=list)
    removed: list[str] = field(default_factory=list)
    fixed: list[str] = field(default_factory=list)
    security: list[str] = field(default_factory=list)
    breaking: list[str] = field(default_factory=list)

def get_commits(from_tag: str, to_tag: str) -> list[Commit]:
    range_spec = f"{from_tag}..{to_tag}" if from_tag else to_tag
    result = subprocess.run(
        ["git", "log", range_spec, "--pretty=format:%H|%s|%b", "--no-merges"],
        capture_output=True, text=True, check=True
    )
    
    commits = []
    for line in result.stdout.splitlines():
        if not line.strip():
            continue
        parts = line.split("|", 2)
        sha = parts[0] if len(parts) > 0 else ""
        subject = parts[1] if len(parts) > 1 else ""
        body = parts[2] if len(parts) > 2 else ""
        
        m = COMMIT_RE.match(subject)
        if m:
            commits.append(Commit(
                type=m.group("type"),
                scope=m.group("scope"),
                breaking=m.group("breaking") == "!" or "BREAKING CHANGE" in body,
                desc=m.group("desc"),
                body=body,
                sha=sha[:8],
            ))
    
    return commits

def determine_bump(commits: list[Commit], current_version: str) -> str:
    parts = current_version.lstrip("v").split(".")
    major, minor, patch = int(parts[0]), int(parts[1]), int(parts[2])
    
    has_breaking = any(c.breaking for c in commits)
    has_feat = any(c.type == "feat" for c in commits)
    
    if has_breaking:
        return f"v{major + 1}.0.0"
    elif has_feat:
        return f"v{major}.{minor + 1}.0"
    else:
        return f"v{major}.{minor}.{patch + 1}"

def build_entry(commits: list[Commit], version: str) -> ChangelogEntry:
    entry = ChangelogEntry(version=version, date=date.today().isoformat())
    
    for c in commits:
        scope_prefix = f"**{c.scope}**: " if c.scope else ""
        desc = f"{scope_prefix}{c.desc}"
        
        if c.breaking:
            entry.breaking.append(desc)
        
        section = SECTION_MAP.get(c.type)
        if section == "Added":
            entry.added.append(desc)
        elif section == "Fixed":
            entry.fixed.append(desc)
        elif section == "Changed":
            entry.changed.append(desc)
        elif section == "Security":
            entry.security.append(desc)
        elif section == "Deprecated":
            entry.deprecated.append(desc)
        elif section == "Removed":
            entry.removed.append(desc)
    
    return entry

def render_entry(entry: ChangelogEntry) -> str:
    lines = [f"## [{entry.version}] - {entry.date}", ""]
    
    sections = [
        ("⚠ BREAKING CHANGES", entry.breaking),
        ("Security", entry.security),
        ("Added", entry.added),
        ("Changed", entry.changed),
        ("Deprecated", entry.deprecated),
        ("Removed", entry.removed),
        ("Fixed", entry.fixed),
    ]
    
    for title, items in sections:
        if items:
            lines.append(f"### {title}")
            for item in items:
                lines.append(f"- {item}")
            lines.append("")
    
    return "\n".join(lines)

if __name__ == "__main__":
    tags = subprocess.run(
        ["git", "tag", "--sort=-version:refname"],
        capture_output=True, text=True
    ).stdout.splitlines()
    
    current_tag = tags[0] if tags else ""
    previous_tag = tags[1] if len(tags) > 1 else ""
    
    if not current_tag:
        print("No tags found. Create a tag first.")
        sys.exit(1)
    
    commits = get_commits(previous_tag, current_tag)
    entry = build_entry(commits, current_tag)
    print(render_entry(entry))

Monorepo Changelog Strategy

For repos with multiple packages (e.g., pnpm workspaces, nx, turborepo):

# packages/api/CHANGELOG.md     — API package only
# packages/ui/CHANGELOG.md      — UI package only
# CHANGELOG.md                  — Root (affects all)

# Filter commits by package path
git log v1.2.0..v1.3.0 --pretty=format:"%s" -- packages/api/

With Changesets (recommended for monorepos):

# Install changesets
pnpm add -D @changesets/cli
pnpm changeset init

# Developer workflow: create a changeset for each PR
pnpm changeset
# → prompts for: which packages changed, bump type, description

# On release branch: version all packages
pnpm changeset version

# Publish and create GitHub release
pnpm changeset publish

GitHub Releases Integration

#!/usr/bin/env bash
# create-github-release.sh

set -euo pipefail

VERSION=$(git describe --tags --abbrev=0)
NOTES=$(python3 generate_changelog.py)

# Using GitHub CLI
gh release create "$VERSION" \
  --title "Release $VERSION" \
  --notes "$NOTES" \
  --verify-tag

# Or via API
curl -s -X POST \
  -H "Authorization: Bearer $GITHUB_TOKEN" \
  -H "Content-Type: application/json" \
  "https://api.github.com/repos/${REPO}/releases" \
  -d "$(jq -n \
    --arg tag "$VERSION" \
    --arg name "Release $VERSION" \
    --arg body "$NOTES" \
    '{tag_name: $tag, name: $name, body: $body, draft: false}')"

User-Facing vs Developer Changelog

User-facing (product changelog)

  • Plain language, no jargon
  • Focus on what changed, not how
  • Skip: refactor, test, chore, ci, docs
  • Include: feat, fix, security, perf (if user-visible)
## Version 2.3.0 — March 1, 2026

**New:** You can now log in with Google.
**Fixed:** Dashboard no longer freezes when loading large datasets.
**Improved:** Search results load 3x faster.

Developer changelog (CHANGELOG.md)

  • Technical details, scope, SemVer impact
  • Include all breaking changes with migration notes
  • Reference PR numbers and issue IDs
## [2.3.0] - 2026-03-01

### Added
- **auth**: OAuth2 Google login via passport-google (#234)
- **api**: GraphQL subscriptions for real-time updates (#241)

### Fixed
- **dashboard**: resolve infinite re-render on large datasets (closes #228)

### Performance
- **search**: switch from Elasticsearch to Typesense, P99 latency -67% (#239)

GitHub Actions — Automated Changelog CI

name: Release

on:
  push:
    tags: ['v*']

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Full history for git log

      - name: Generate changelog
        id: changelog
        run: |
          NOTES=$(python3 scripts/generate_changelog.py)
          echo "notes> $GITHUB_OUTPUT
          echo "$NOTES" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          body: ${{ steps.changelog.outputs.notes }}
          generate_release_notes: false

Common Pitfalls

  • --depth=1 in CI — git log needs full history; use fetch-depth: 0
  • Merge commits polluting log — always use --no-merges
  • No conventional commits discipline — enforce with commitlint in CI
  • Missing previous tag — handle first-release case (no previous tag)
  • Version in multiple places — single source of truth; read from git tag, not package.json

Best Practices

  1. commitlint in CI — enforce conventional commits before merge
  2. Tag before generating — tag the release commit first, then generate
  3. Separate user/dev changelog — product team wants plain English
  4. Keep a link section[2.3.0]: https://github.com/org/repo/compare/v2.2.0...v2.3.0
  5. Automate but review — generate in CI, human reviews before publish

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.