skills/curiouslearner/changelog-generator/SKILL.md
Automatically generate changelogs from git commits following conventional commits, semantic versi...
npx skillsauth add aiskillstore/marketplace changelog-generatorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Automatically generate changelogs from git commits following conventional commits, semantic versioning, and best practices.
You are a changelog generation expert. When invoked:
Analyze Commit History:
Generate Changelog Entries:
Format Output:
Version Management:
Breaking Change: Any commit with BREAKING CHANGE: in body or ! after type (major version bump)
@changelog-generator
@changelog-generator --since v1.2.0
@changelog-generator --unreleased
@changelog-generator --version 2.0.0
@changelog-generator --format keep-a-changelog
@changelog-generator --include-authors
# 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.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- New feature X for improved user experience
- Support for configuration option Y
### Changed
- Updated dependency Z to version 2.0
- Improved performance of data processing
### Deprecated
- Function `oldMethod()` - use `newMethod()` instead
### Removed
- Removed deprecated API endpoint `/api/v1/old`
### Fixed
- Fixed memory leak in cache implementation
- Corrected timezone handling in date formatter
### Security
- Fixed XSS vulnerability in user input handling
- Updated crypto library to address CVE-2024-1234
## [1.5.0] - 2024-01-15
### Added
- User authentication with OAuth2
- Export functionality for reports
- Dark mode theme support
### Changed
- Redesigned dashboard UI
- Optimized database queries
### Fixed
- Fixed bug in pagination logic
- Resolved CORS issues with API
## [1.4.2] - 2024-01-10
### Fixed
- Critical bug in payment processing
- Memory leak in WebSocket connections
### Security
- Patched authentication bypass vulnerability
## [1.4.1] - 2024-01-05
### Fixed
- Hotfix for broken deployment script
- Fixed typo in error messages
## [1.4.0] - 2024-01-01
### Added
- Real-time notifications
- File upload with drag and drop
- Advanced search filters
### Changed
- Migrated from REST to GraphQL
- Updated UI components library
### Deprecated
- Old REST API endpoints (will be removed in 2.0)
[Unreleased]: https://github.com/user/repo/compare/v1.5.0...HEAD
[1.5.0]: https://github.com/user/repo/compare/v1.4.2...v1.5.0
[1.4.2]: https://github.com/user/repo/compare/v1.4.1...v1.4.2
[1.4.1]: https://github.com/user/repo/compare/v1.4.0...v1.4.1
[1.4.0]: https://github.com/user/repo/releases/tag/v1.4.0
#!/bin/bash
# generate-changelog.sh - Generate changelog from git commits
VERSION=${1:-"Unreleased"}
PREV_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
echo "# Changelog"
echo ""
echo "## [$VERSION] - $(date +%Y-%m-%d)"
echo ""
# Get commits since last tag
if [ -z "$PREV_TAG" ]; then
COMMITS=$(git log --pretty=format:"%s|||%h|||%an" --reverse)
else
COMMITS=$(git log ${PREV_TAG}..HEAD --pretty=format:"%s|||%h|||%an" --reverse)
fi
# Arrays for different categories
declare -a features=()
declare -a fixes=()
declare -a breaking=()
declare -a docs=()
declare -a chores=()
declare -a other=()
# Parse commits
while IFS='|||' read -r message hash author; do
case "$message" in
feat:*|feat\(*\):*)
features+=("- ${message#feat*: } ([${hash}](../../commit/${hash}))")
;;
fix:*|fix\(*\):*)
fixes+=("- ${message#fix*: } ([${hash}](../../commit/${hash}))")
;;
*BREAKING*|*\!:*)
breaking+=("- ${message} ([${hash}](../../commit/${hash}))")
;;
docs:*)
docs+=("- ${message#docs: } ([${hash}](../../commit/${hash}))")
;;
chore:*|build:*|ci:*)
chores+=("- ${message#*: } ([${hash}](../../commit/${hash}))")
;;
*)
other+=("- ${message} ([${hash}](../../commit/${hash}))")
;;
esac
done <<< "$COMMITS"
# Output sections
if [ ${#breaking[@]} -gt 0 ]; then
echo "### ⚠️ BREAKING CHANGES"
echo ""
printf '%s\n' "${breaking[@]}"
echo ""
fi
if [ ${#features[@]} -gt 0 ]; then
echo "### Added"
echo ""
printf '%s\n' "${features[@]}"
echo ""
fi
if [ ${#fixes[@]} -gt 0 ]; then
echo "### Fixed"
echo ""
printf '%s\n' "${fixes[@]}"
echo ""
fi
if [ ${#docs[@]} -gt 0 ]; then
echo "### Documentation"
echo ""
printf '%s\n' "${docs[@]}"
echo ""
fi
if [ ${#chores[@]} -gt 0 ]; then
echo "### Internal"
echo ""
printf '%s\n' "${chores[@]}"
echo ""
fi
if [ ${#other[@]} -gt 0 ]; then
echo "### Other Changes"
echo ""
printf '%s\n' "${other[@]}"
echo ""
fi
# Install
npm install -g conventional-changelog-cli
# Generate changelog
conventional-changelog -p angular -i CHANGELOG.md -s
# For first release
conventional-changelog -p angular -i CHANGELOG.md -s -r 0
# With specific version
conventional-changelog -p angular -i CHANGELOG.md -s --release-count 0 \
--tag-prefix v --preset angular
package.json configuration:
{
"scripts": {
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
"version": "npm run changelog && git add CHANGELOG.md"
},
"devDependencies": {
"conventional-changelog-cli": "^4.1.0"
}
}
# Install
npm install -D standard-version
# Generate changelog and bump version
npx standard-version
# Preview without committing
npx standard-version --dry-run
# First release
npx standard-version --first-release
# Specific version
npx standard-version --release-as minor
npx standard-version --release-as 1.1.0
# Pre-release
npx standard-version --prerelease alpha
package.json:
{
"scripts": {
"release": "standard-version",
"release:minor": "standard-version --release-as minor",
"release:major": "standard-version --release-as major",
"release:alpha": "standard-version --prerelease alpha"
},
"standard-version": {
"types": [
{"type": "feat", "section": "Features"},
{"type": "fix", "section": "Bug Fixes"},
{"type": "chore", "hidden": true},
{"type": "docs", "section": "Documentation"},
{"type": "style", "hidden": true},
{"type": "refactor", "section": "Code Refactoring"},
{"type": "perf", "section": "Performance Improvements"},
{"type": "test", "hidden": true}
]
}
}
.github/workflows/release.yml:
name: Release
on:
push:
branches:
- main
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: google-github-actions/release-please-action@v3
id: release
with:
release-type: node
package-name: my-package
- uses: actions/checkout@v3
if: ${{ steps.release.outputs.release_created }}
- uses: actions/setup-node@v3
if: ${{ steps.release.outputs.release_created }}
with:
node-version: 18
registry-url: 'https://registry.npmjs.org'
- run: npm ci
if: ${{ steps.release.outputs.release_created }}
- run: npm publish
if: ${{ steps.release.outputs.release_created }}
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
#!/usr/bin/env node
// generate-changelog.js
const { execSync } = require('child_process');
const fs = require('fs');
const COMMIT_PATTERN = /^(\w+)(\([\w-]+\))?(!)?:\s(.+)$/;
const TYPES = {
feat: { section: 'Added', bump: 'minor' },
fix: { section: 'Fixed', bump: 'patch' },
docs: { section: 'Documentation', bump: null },
style: { section: 'Style', bump: null },
refactor: { section: 'Changed', bump: null },
perf: { section: 'Performance', bump: 'patch' },
test: { section: 'Tests', bump: null },
build: { section: 'Build System', bump: null },
ci: { section: 'CI', bump: null },
chore: { section: 'Chores', bump: null },
revert: { section: 'Reverts', bump: 'patch' },
};
function getCommitsSinceTag(tag) {
const cmd = tag
? `git log ${tag}..HEAD --pretty=format:"%H|||%s|||%b|||%an|||%ae|||%ai"`
: `git log --pretty=format:"%H|||%s|||%b|||%an|||%ae|||%ai"`;
try {
const output = execSync(cmd, { encoding: 'utf-8' });
return output.split('\n').filter(Boolean);
} catch (error) {
return [];
}
}
function parseCommit(commitLine) {
const [hash, subject, body, author, email, date] = commitLine.split('|||');
const match = subject.match(COMMIT_PATTERN);
if (!match) {
return {
hash,
subject,
body,
author,
email,
date,
type: 'other',
scope: null,
breaking: false,
description: subject,
};
}
const [, type, scope, breaking, description] = match;
return {
hash,
subject,
body,
author,
email,
date,
type,
scope: scope ? scope.slice(1, -1) : null,
breaking: Boolean(breaking) || body.includes('BREAKING CHANGE'),
description,
};
}
function groupCommits(commits) {
const groups = {};
const breaking = [];
for (const commit of commits) {
if (commit.breaking) {
breaking.push(commit);
}
const typeInfo = TYPES[commit.type] || { section: 'Other Changes' };
const section = typeInfo.section;
if (!groups[section]) {
groups[section] = [];
}
groups[section].push(commit);
}
return { groups, breaking };
}
function generateMarkdown(version, date, groups, breaking, options = {}) {
const lines = [];
lines.push(`## [${version}] - ${date}`);
lines.push('');
// Breaking changes first
if (breaking.length > 0) {
lines.push('### ⚠️ BREAKING CHANGES');
lines.push('');
for (const commit of breaking) {
lines.push(`- **${commit.description}** ([${commit.hash.slice(0, 7)}](../../commit/${commit.hash}))`);
if (commit.body) {
const breakingNote = commit.body.match(/BREAKING CHANGE:\s*(.+)/);
if (breakingNote) {
lines.push(` ${breakingNote[1]}`);
}
}
}
lines.push('');
}
// Other sections
const sectionOrder = [
'Added',
'Changed',
'Deprecated',
'Removed',
'Fixed',
'Security',
'Performance',
'Documentation',
];
for (const section of sectionOrder) {
if (groups[section] && groups[section].length > 0) {
lines.push(`### ${section}`);
lines.push('');
const commits = groups[section];
const grouped = {};
// Group by scope if present
for (const commit of commits) {
const key = commit.scope || '_default';
if (!grouped[key]) grouped[key] = [];
grouped[key].push(commit);
}
for (const [scope, scopeCommits] of Object.entries(grouped)) {
if (scope !== '_default') {
lines.push(`#### ${scope}`);
lines.push('');
}
for (const commit of scopeCommits) {
let line = `- ${commit.description}`;
if (options.includeHash) {
line += ` ([${commit.hash.slice(0, 7)}](../../commit/${commit.hash}))`;
}
if (options.includeAuthor) {
line += ` - @${commit.author}`;
}
lines.push(line);
}
}
lines.push('');
}
}
return lines.join('\n');
}
function getLatestTag() {
try {
return execSync('git describe --tags --abbrev=0', { encoding: 'utf-8' }).trim();
} catch (error) {
return null;
}
}
function suggestVersion(breaking, groups) {
const latestTag = getLatestTag();
if (!latestTag) return '1.0.0';
const [major, minor, patch] = latestTag.replace('v', '').split('.').map(Number);
if (breaking.length > 0) {
return `${major + 1}.0.0`;
}
const hasFeatures = groups['Added'] && groups['Added'].length > 0;
if (hasFeatures) {
return `${major}.${minor + 1}.0`;
}
return `${major}.${minor}.${patch + 1}`;
}
// Main execution
function main() {
const args = process.argv.slice(2);
const options = {
includeHash: !args.includes('--no-hash'),
includeAuthor: args.includes('--author'),
version: args.find(a => a.startsWith('--version='))?.split('=')[1],
since: args.find(a => a.startsWith('--since='))?.split('=')[1],
};
const latestTag = options.since || getLatestTag();
const commits = getCommitsSinceTag(latestTag);
const parsed = commits.map(parseCommit);
const { groups, breaking } = groupCommits(parsed);
const version = options.version || suggestVersion(breaking, groups);
const date = new Date().toISOString().split('T')[0];
const changelog = generateMarkdown(version, date, groups, breaking, options);
// Read existing changelog or create new
let existingChangelog = '';
try {
existingChangelog = fs.readFileSync('CHANGELOG.md', 'utf-8');
} catch (error) {
existingChangelog = '# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n';
}
// Insert new version after header
const lines = existingChangelog.split('\n');
const headerEnd = lines.findIndex(l => l.startsWith('## '));
const newLines = [
...lines.slice(0, headerEnd === -1 ? lines.length : headerEnd),
changelog,
...lines.slice(headerEnd === -1 ? lines.length : headerEnd),
];
fs.writeFileSync('CHANGELOG.md', newLines.join('\n'));
console.log(`✓ Generated changelog for version ${version}`);
console.log(` - ${commits.length} commits processed`);
console.log(` - ${breaking.length} breaking changes`);
console.log(` - Suggested version: ${version}`);
}
if (require.main === module) {
main();
}
module.exports = { parseCommit, groupCommits, generateMarkdown };
Usage:
# Make executable
chmod +x generate-changelog.js
# Run
./generate-changelog.js
# With options
./generate-changelog.js --version=2.0.0 --author --since=v1.5.0
#!/usr/bin/env python3
# generate_changelog.py
import re
import subprocess
from datetime import datetime
from collections import defaultdict
from typing import List, Dict, Tuple
COMMIT_PATTERN = re.compile(r'^(\w+)(\([\w-]+\))?(!)?:\s(.+)$')
TYPES = {
'feat': {'section': 'Added', 'bump': 'minor'},
'fix': {'section': 'Fixed', 'bump': 'patch'},
'docs': {'section': 'Documentation', 'bump': None},
'style': {'section': 'Style', 'bump': None},
'refactor': {'section': 'Changed', 'bump': None},
'perf': {'section': 'Performance', 'bump': 'patch'},
'test': {'section': 'Tests', 'bump': None},
'build': {'section': 'Build System', 'bump': None},
'ci': {'section': 'CI', 'bump': None},
'chore': {'section': 'Chores', 'bump': None},
}
def get_commits_since_tag(tag: str = None) -> List[str]:
cmd = ['git', 'log', '--pretty=format:%H|||%s|||%b|||%an|||%ae|||%ai']
if tag:
cmd.insert(2, f'{tag}..HEAD')
try:
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
return [line for line in result.stdout.split('\n') if line]
except subprocess.CalledProcessError:
return []
def parse_commit(commit_line: str) -> Dict:
parts = commit_line.split('|||')
if len(parts) < 6:
return None
hash_val, subject, body, author, email, date = parts
match = COMMIT_PATTERN.match(subject)
if not match:
return {
'hash': hash_val,
'subject': subject,
'body': body,
'author': author,
'type': 'other',
'scope': None,
'breaking': False,
'description': subject,
}
type_val, scope, breaking, description = match.groups()
return {
'hash': hash_val,
'subject': subject,
'body': body,
'author': author,
'type': type_val,
'scope': scope[1:-1] if scope else None,
'breaking': bool(breaking) or 'BREAKING CHANGE' in body,
'description': description,
}
def group_commits(commits: List[Dict]) -> Tuple[Dict, List]:
groups = defaultdict(list)
breaking = []
for commit in commits:
if commit['breaking']:
breaking.append(commit)
type_info = TYPES.get(commit['type'], {'section': 'Other Changes'})
section = type_info['section']
groups[section].append(commit)
return dict(groups), breaking
def generate_markdown(version: str, groups: Dict, breaking: List) -> str:
lines = [f"## [{version}] - {datetime.now().strftime('%Y-%m-%d')}", ""]
if breaking:
lines.append("### ⚠️ BREAKING CHANGES")
lines.append("")
for commit in breaking:
lines.append(f"- **{commit['description']}** ([{commit['hash'][:7]}](../../commit/{commit['hash']}))")
lines.append("")
section_order = ['Added', 'Changed', 'Fixed', 'Security', 'Performance', 'Documentation']
for section in section_order:
if section in groups and groups[section]:
lines.append(f"### {section}")
lines.append("")
for commit in groups[section]:
lines.append(f"- {commit['description']} ([{commit['hash'][:7]}](../../commit/{commit['hash']}))")
lines.append("")
return '\n'.join(lines)
def main():
commits = get_commits_since_tag()
parsed = [parse_commit(c) for c in commits if parse_commit(c)]
groups, breaking = group_commits(parsed)
version = input("Enter version number (or press Enter for auto): ").strip() or "1.0.0"
changelog = generate_markdown(version, groups, breaking)
print(changelog)
# Optionally write to file
with open('CHANGELOG.md', 'r+') as f:
content = f.read()
f.seek(0, 0)
f.write(changelog + '\n\n' + content)
if __name__ == '__main__':
main()
# .github/workflows/release.yml
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Generate changelog
id: changelog
run: |
# Get previous tag
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
# Generate changelog
if [ -z "$PREV_TAG" ]; then
CHANGELOG=$(git log --pretty=format:"- %s (%h)" --reverse)
else
CHANGELOG=$(git log ${PREV_TAG}..HEAD --pretty=format:"- %s (%h)" --reverse)
fi
# Set output
echo "changelog<<EOF" >> $GITHUB_OUTPUT
echo "$CHANGELOG" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
body: |
## Changes in this release
${{ steps.changelog.outputs.changelog }}
draft: false
prerelease: false
development
Apple Human Interface Guidelines for content display components. Use this skill when the user asks about charts component, collection view, image view, web view, color well, image well, activity view, lockup, data visualization, content display, displaying images, rendering web content, color pickers, or presenting collections of items in Apple apps. Also use when the user says how should I display charts, what's the best way to show images, should I use a web view, how do I build a grid of items, what component shows media, or how do I present a share sheet. Cross-references: hig-foundations for color/typography/accessibility, hig-patterns for data visualization patterns, hig-components-layout for structural containers, hig-platforms for platform-specific component behavior.
tools
Automate HelpDesk tasks via Rube MCP (Composio): list tickets, manage views, use canned responses, and configure custom fields. Always search tools first for current schemas.
testing
Expert Haskell engineer specializing in advanced type systems, pure functional design, and high-reliability software. Use PROACTIVELY for type-level programming, concurrency, and architecture guidance.
tools
GraphQL gives clients exactly the data they need - no more, no less. One endpoint, typed schema, introspection. But the flexibility that makes it powerful also makes it dangerous. Without proper controls, clients can craft queries that bring down your server. This skill covers schema design, resolvers, DataLoader for N+1 prevention, federation for microservices, and client integration with Apollo/urql. Key insight: GraphQL is a contract. The schema is the API documentation. Design it carefully.