changelog-gen
Generates structured changelogs from git history using conventional commit parsing. Reads commits since the last git tag, categorizes them into Added, Fixed, Changed, Breaking Changes, and Internal sections, then prepends a formatted entry to CHANGELOG.md. Falls back to AI-based categorization when commits do not follow conventional format. Triggered when the user asks to generate a changelog, update CHANGELOG, prepare release notes, or review what changed since the last release.
changelog-gen
Generate a structured CHANGELOG entry from git commit history using conventional commit conventions.
When to Trigger
Activate this skill when the user:
- Asks to generate a changelog or update CHANGELOG.md
- Wants to prepare release notes
- Asks "what changed since last release" or "summarize recent commits"
- Is invoked by the
npm-publishskill during the release pipeline
Commit Parsing
Fetch Commits Since Last Tag
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null)
If a tag exists:
git log ${LAST_TAG}..HEAD --oneline --no-merges
If no tags exist (first release), use all commits:
git log --oneline --no-merges
Categorize by Conventional Commit Prefix
| Prefix | Changelog Section |
|---|---|
feat: / feat(scope): | Added |
fix: / fix(scope): | Fixed |
refactor: / perf: | Changed |
docs: | Documentation |
BREAKING CHANGE: or !: in subject | Breaking Changes |
chore: / ci: / build: / test: | Internal |
Rules:
- Strip the prefix and scope from the description (e.g.,
feat(auth): add OAuth flowbecomesAdd OAuth flow) - Capitalize the first letter of each entry
- Append the short commit hash in parentheses:
(a1b2c3d) - If a commit body contains
BREAKING CHANGE:, promote it to the Breaking Changes section regardless of prefix
Non-Conventional Fallback
If fewer than 50% of commits follow conventional format, switch to AI categorization:
- For each commit, read the diff:
git show --stat <hash> - Categorize based on the files changed and diff content:
- New files added to
src/-> Added - Modified test files -> Internal
- Modified docs -> Documentation
- Bug-related keywords in message (fix, resolve, patch, correct) -> Fixed
- Everything else -> Changed
- New files added to
- Flag in the output that AI categorization was used
Output Format
## [X.Y.Z] --- YYYY-MM-DD
### Breaking Changes
- Description of breaking change and migration steps (hash)
### Added
- New feature description (hash)
### Fixed
- Bug fix description (hash)
### Changed
- Refactor or performance improvement description (hash)
### Documentation
- Documentation update description (hash)
### Internal
- Chore, CI, build, or test change description (hash)
Rules:
- Omit any section that has zero entries
- Use the version from
package.jsonif available, otherwise prompt the user - Date is always today in
YYYY-MM-DDformat - Use
---as the date separator (em dash style)
Actions
1. Generate Entry
Build the changelog entry following the output format above. Display it to the user for review before writing.
2. Prepend to CHANGELOG.md
If CHANGELOG.md exists, insert the new entry immediately after the # Changelog header line (preserving any preamble text between the header and the first version entry).
If CHANGELOG.md does not exist, create it:
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [X.Y.Z] --- YYYY-MM-DD
...
3. Write Temp File for GitHub Release
Write just the current version's entry (without the ## [version] header) to /tmp/CHANGELOG_ENTRY.md for use by the npm-publish skill or gh release create.
Edge Cases
| Scenario | Behavior |
|---|---|
| No git tags exist | Use all commits from repo inception |
| No commits since last tag | Report "No changes since vX.Y.Z" and exit |
| No conventional commits | Use AI categorization with a warning |
| Merge commits | Skip by default (--no-merges) |
| Single commit since last tag | Still generate a full entry |
| Monorepo with multiple packages | Filter commits by path if user specifies a package directory |
Integration
This skill is designed to be invoked standalone or as part of the npm-publish pipeline. When called from npm-publish, the version number is already determined and should be passed in rather than read from package.json.