Git Release
Cross-Platform AI Agent Skill This skill works with any AI agent platform that supports the skills.sh standard.
Release Manager
Create semantic version releases with automated changelog generation from conventional commits, version file updates, and GitHub release publishing.
Quality Guidelines
CRITICAL: Release operations are high-consequence and irreversible once pushed:
- Verify every change - Analyze actual commits, not assumptions
- Confirm version bump - Ensure the detected semver bump matches the change scope
- Validate changelog - Every entry must correspond to a real commit
- User approval required - Always confirm before executing the release
Workflow
Phase 1: Collect Commits Since Last Tag
- Find the latest tag:
git describe --tags --abbrev=0 2>/dev/null || echo "none"
- If no tags exist, collect all commits on the current branch
- If a tag exists, collect commits since that tag
- Collect commits:
# With existing tag
git log <last-tag>..HEAD --format="%H %s" --no-merges
# Without existing tag (first release)
git log --format="%H %s" --no-merges
3. **Validate preconditions**:
- Working tree is clean: `git status --porcelain`
- On the expected branch (main/master or release branch)
- Remote is up to date: `git fetch origin && git log HEAD..origin/$(git branch --show-current) --oneline`
- If there are no commits since the last tag, abort with a clear message
### Phase 2: Auto-Detect Version Bump (Use Parallel Analysis for Large Changelogs)
For releases with >20 commits, spawn a parallel agent for analysis:
Agent - Commit Classification:
- prompt: "Classify these git commits by conventional commit type. For each commit, identify: type (feat/fix/docs/etc.), scope, whether it has a BREAKING CHANGE footer or ! marker. Return a structured summary with counts per type and list any breaking changes."
- agent-type: "general-purpose"
- Parse each commit using conventional commit format:
- Extract type:
feat,fix,docs,style,refactor,perf,test,build,ci,chore,revert - Extract scope (optional): text in parentheses after type
- Detect breaking changes:
!after type/scope ORBREAKING CHANGE:in commit body - For non-conventional commits, classify as
other
- Determine version bump following semver rules:
- MAJOR (
X.0.0): Any commit with breaking change indicator (!orBREAKING CHANGE:footer) - MINOR (
x.Y.0): Anyfeattype commit (without breaking change) - PATCH (
x.y.Z): Anyfixtype commit, or only non-feature changes (docs,refactor,perf,chore, etc.) - The highest-priority bump wins (major > minor > patch)
- Calculate new version:
- Parse last tag as semver (strip leading
vif present) - If no previous tag, start from
0.1.0(first feature release) or1.0.0if user specifies - Apply the detected bump
- Respect
--major,--minor, or--patchoverride from arguments
- Display version summary:
Current version: v1.2.3
Detected bump: minor (2 features, 5 fixes, 3 chores)
New version: v1.3.0
Breaking changes: none
### Phase 3: Generate CHANGELOG Entry
1. **Read existing CHANGELOG.md** (if it exists) to understand the current format and preserve it
2. **Group commits by type** using this order and heading format:
```markdown
## [1.3.0](https://github.com/owner/repo/compare/v1.2.3...v1.3.0) (YYYY-MM-DD)
### Breaking Changes
- **scope:** description ([hash](url))
### Features
- **scope:** description ([hash](url))
### Bug Fixes
- **scope:** description ([hash](url))
### Performance
- **scope:** description ([hash](url))
### Documentation
- **scope:** description ([hash](url))
### Other Changes
- **scope:** description ([hash](url))
Type-to-heading mapping:
- Breaking changes (any type with `!` or `BREAKING CHANGE:`) → **Breaking Changes**
- `feat` → **Features**
- `fix` → **Bug Fixes**
- `perf` → **Performance**
- `docs` → **Documentation**
- `refactor`, `style`, `test`, `build`, `ci`, `chore`, `revert`, `other` → **Other Changes**
Only include sections that have entries. Omit empty sections.
3. **Generate comparison URL**:
```bash
# Get remote URL for links
gh repo view --json url -q .url 2>/dev/null || git remote get-url origin
4. **Construct the changelog entry**:
- Use short commit hashes (7 chars) linked to the full commit URL
- If scope exists, bold it: `**scope:** description`
- If no scope: just the description
- Date format: `YYYY-MM-DD`
5. **Prepend to CHANGELOG.md**:
- If CHANGELOG.md exists, insert after the `# Changelog` header (preserve existing entries)
- If CHANGELOG.md does not exist, create it with a `# Changelog` header followed by the entry
- Maintain a blank line between the header and first entry, and between entries
### Phase 4: User Approval
**CRITICAL**: Always present the full release plan and wait for explicit approval before executing.
1. **Display release summary**:
=== Release Summary ===
Version: v1.2.3 → v1.3.0 (minor) Tag: v1.3.0 Commits: 12 commits since v1.2.3 Branch: main
Changelog preview: ─────────────────────
1.3.0 (2025-01-15)
Features
- auth: add OAuth2 login support (abc1234)
- api: add rate limiting endpoint (def5678)
Bug Fixes
- api: resolve null pointer in user endpoint (ghi9012) ─────────────────────
Version files to update:
- package.json (1.2.3 → 1.3.0)
- pyproject.toml (1.2.3 → 1.3.0)
Actions:
- Update version files
- Update CHANGELOG.md
- Create git commit: "chore(release): v1.3.0"
- Create git tag: v1.3.0
- Push commit and tag to origin
- Create GitHub release with changelog
- Ask for confirmation using interactive clarification:
- Option 1: "Proceed with release" — Execute all actions
- Option 2: "Change version" — Allow manual version override
- Option 3: "Dry run only" — Show what would happen without executing
- Option 4: "Abort" — Cancel the release
- Handle responses:
- Proceed: Continue to Phase 5
- Change version: Ask for the desired version, recalculate, re-display summary
- Dry run: Display the summary and all file changes that would be made, then stop
- Abort: Exit cleanly with "Release cancelled."
Phase 5: Execute Release
Execute all release actions in strict order. Stop immediately if any step fails.
- Update version files (detect and update all that exist):
package.json: Update"version": "x.y.z"fieldpackage-lock.json: Update"version": "x.y.z"at root levelpyproject.toml: Updateversion = "x.y.z"under[project]or[tool.poetry]Cargo.toml: Updateversion = "x.y.z"under[package]VERSIONorVERSION.txt: Replace entire file contentsetup.cfg: Updateversion = x.y.zunder[metadata]build.gradle/build.gradle.kts: Updateversion = "x.y.z"- Other version files: Skip unknown formats, notify user
- Write CHANGELOG.md:
- Prepend the generated changelog entry (from Phase 3)
- Preserve all existing content
- Create release commit:
git add -A
git commit -m "chore(release): v<new-version>"
4. **Create annotated tag**:
```bash
git tag -a v<new-version> -m "Release v<new-version>"
5. **Push commit and tag**:
```bash
git push origin $(git branch --show-current)
git push origin v<new-version>
6. **Create GitHub release** (unless `--no-github` flag is set):
```bash
gh release create v<new-version> \
--title "v<new-version>" \
--notes-file /tmp/release-notes-<timestamp>.md \
--latest
- Use the changelog entry (without the
## [version]header) as release notes - Write release notes to a temp file first to avoid shell escaping issues
- Display completion summary:
Release v1.3.0 completed successfully!
- Commit: abc1234 chore(release): v1.3.0
- Tag: v1.3.0
- GitHub: https://github.com/owner/repo/releases/tag/v1.3.0
- Changelog: Updated CHANGELOG.md
## Argument Parsing
Parse optional arguments from `command arguments`:
- `--major`: Force a major version bump (overrides auto-detection)
- `--minor`: Force a minor version bump (overrides auto-detection)
- `--patch`: Force a patch version bump (overrides auto-detection)
- `--dry-run` or `-n`: Show what would happen without making changes
- `--no-github`: Skip GitHub release creation (only local tag + changelog)
When force flags conflict (e.g., `--major --minor`), use the highest: major > minor > patch.
## Edge Cases
- **No conventional commits**: If commits don't follow conventional format, default to `patch` bump and list all commits under **Other Changes**
- **Pre-release versions** (e.g., `0.x.y`): Follow semver pre-1.0 rules — breaking changes bump minor, features bump minor, fixes bump patch
- **Monorepo**: If multiple `package.json` files exist, only update the root one. Warn the user about other version files found
- **Dirty working tree**: Abort with a clear message asking user to commit or stash changes first
- **No remote**: If `git push` fails due to no remote, skip push and GitHub release, warn user
- **Tag already exists**: If the computed tag already exists, abort and suggest using a force flag or choosing a different version
## Important Notes
- **Conventional Commits**: This skill works best with conventional commits (from the git-commit skill)
- **Tag Format**: Always uses `v` prefix (e.g., `v1.3.0`) unless existing tags use a different convention
- **CHANGELOG Format**: Follows [Keep a Changelog](https://keepachangelog.com/) conventions
- **Semver**: Strictly follows [Semantic Versioning 2.0.0](https://semver.org/)
- **No --no-verify**: Never skip git hooks during the release commit
- **Atomic Operations**: If any step in Phase 5 fails, clearly report which step failed and what manual cleanup may be needed
## Examples
```bash
# Auto-detect version bump from commits
git-release
# Force a major version bump
git-release --major
# Preview without making changes
git-release --dry-run
# Release without creating GitHub release
git-release --no-github
# Force minor bump, dry run
git-release --minor --dry-run