Skip to content

Commit b1423aa

Browse files
committed
fix: split release process to sync pyproject.toml version with git tags (#1721)
- Split release workflow into two: release-trigger.yml and release.yml - release-trigger.yml: Updates pyproject.toml, generates changelog from commits, creates tag - release.yml: Triggered by tag push, builds artifacts, creates GitHub release - Ensures git tags point to commits with correct version in pyproject.toml - Auto-generates changelog from commit messages since last tag - Supports manual version input or auto-increment patch version - Added simulate-release.sh for local testing without pushing - Added comprehensive RELEASE-PROCESS.md documentation - Updated pyproject.toml to v0.1.10 to sync with latest release This fixes the version mismatch issue where tags pointed to commits with outdated pyproject.toml versions, preventing confusion when installing from source.
1 parent b55d00b commit b1423aa

File tree

6 files changed

+539
-31
lines changed

6 files changed

+539
-31
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# Release Process
2+
3+
This document describes the automated release process for Spec Kit.
4+
5+
## Overview
6+
7+
The release process is split into two workflows to ensure version consistency:
8+
9+
1. **Release Trigger Workflow** (`release-trigger.yml`) - Manages versioning and triggers release
10+
2. **Release Workflow** (`release.yml`) - Builds and publishes artifacts
11+
12+
This separation ensures that git tags always point to commits with the correct version in `pyproject.toml`.
13+
14+
## Before Creating a Release
15+
16+
**Important**: Write clear, descriptive commit messages!
17+
18+
### How CHANGELOG.md Works
19+
20+
The CHANGELOG is **automatically generated** from your git commit messages:
21+
22+
1. **During Development**: Write clear, descriptive commit messages:
23+
```bash
24+
git commit -m "feat: Add new authentication feature"
25+
git commit -m "fix: Resolve timeout issue in API client (#123)"
26+
git commit -m "docs: Update installation instructions"
27+
```
28+
29+
2. **When Releasing**: The release trigger workflow automatically:
30+
- Finds all commits since the last release tag
31+
- Formats them as changelog entries
32+
- Inserts them into CHANGELOG.md
33+
- Commits the updated changelog before creating the new tag
34+
35+
### Commit Message Best Practices
36+
37+
Good commit messages make good changelogs:
38+
- **Be descriptive**: "Add user authentication" not "Update files"
39+
- **Reference issues/PRs**: Include `(#123)` for automated linking
40+
- **Use conventional commits** (optional): `feat:`, `fix:`, `docs:`, `chore:`
41+
- **Keep it concise**: One line is ideal, details go in commit body
42+
43+
**Example commits that become good changelog entries:**
44+
```
45+
fix: prepend YAML frontmatter to Cursor .mdc files (#1699)
46+
feat: add generic agent support with customizable command directories (#1639)
47+
docs: document dual-catalog system for extensions (#1689)
48+
```
49+
50+
## Creating a Release
51+
52+
### Option 1: Auto-Increment (Recommended for patches)
53+
54+
1. Go to **Actions****Release Trigger**
55+
2. Click **Run workflow**
56+
3. Leave the version field **empty**
57+
4. Click **Run workflow**
58+
59+
The workflow will:
60+
- Auto-increment the patch version (e.g., `0.1.10``0.1.11`)
61+
- Update `pyproject.toml`
62+
- Convert `[Unreleased]` section in CHANGELOG.md to the new version
63+
- Add a new empty `[Unreleased]` section
64+
- Commit changes
65+
- Create and push git tag
66+
- Trigger the release workflow automatically
67+
68+
### Option 2: Manual Version (For major/minor bumps)
69+
70+
1. Go to **Actions****Release Trigger**
71+
2. Click **Run workflow**
72+
3. Enter the desired version (e.g., `0.2.0` or `v0.2.0`)
73+
4. Click **Run workflow**
74+
75+
The workflow will:
76+
- Use your specified version
77+
- Update `pyproject.toml`
78+
- Convert `[Unreleased]` section in CHANGELOG.md to the new version
79+
- Add a new empty `[Unreleased]` section
80+
- Commit changes
81+
- Create and push git tag
82+
- Trigger the release workflow automatically
83+
84+
## What Happens Next
85+
86+
Once the release trigger workflow completes:
87+
88+
1. The git tag is pushed to GitHub
89+
2. The **Release Workflow** is automatically triggered
90+
3. Release artifacts are built for all supported agents
91+
4. A GitHub Release is created with all assets
92+
5. Release notes are generated from PR titles
93+
94+
## Workflow Details
95+
96+
### Release Trigger Workflow
97+
98+
**File**: `.github/workflows/release-trigger.yml`
99+
100+
**Trigger**: Manual (`workflow_dispatch`)
101+
102+
**Permissions Required**: `contents: write`
103+
104+
**Steps**:
105+
1. Checkout repository
106+
2. Determine version (manual or auto-increment)
107+
3. Check if tag already exists (prevents duplicates)
108+
4. Update `pyproject.toml`
109+
5. Update `CHANGELOG.md`
110+
6. Commit changes
111+
7. Create and push tag
112+
113+
### Release Workflow
114+
115+
**File**: `.github/workflows/release.yml`
116+
117+
**Trigger**: Tag push (`v*`)
118+
119+
**Permissions Required**: `contents: write`
120+
121+
**Steps**:
122+
1. Checkout repository at tag
123+
2. Extract version from tag name
124+
3. Check if release already exists
125+
4. Build release package variants (all agents × shell/powershell)
126+
5. Generate release notes from commits
127+
6. Create GitHub Release with all assets
128+
129+
## Version Constraints
130+
131+
- Tags must follow format: `v{MAJOR}.{MINOR}.{PATCH}`
132+
- Example valid versions: `v0.1.11`, `v0.2.0`, `v1.0.0`
133+
- Auto-increment only bumps patch version
134+
- Cannot create duplicate tags (workflow will fail)
135+
136+
## Benefits of This Approach
137+
138+
**Version Consistency**: Git tags point to commits with matching `pyproject.toml` version
139+
140+
**Single Source of Truth**: Version set once, used everywhere
141+
142+
**Prevents Drift**: No more manual version synchronization needed
143+
144+
**Clean Separation**: Versioning logic separate from artifact building
145+
146+
**Flexibility**: Supports both auto-increment and manual versioning
147+
148+
## Troubleshooting
149+
150+
### No Commits Since Last Release
151+
152+
If you run the release trigger workflow when there are no new commits since the last tag:
153+
- The workflow will still succeed
154+
- The CHANGELOG will show "- Initial release" if it's the first release
155+
- Or it will be empty if there are no commits
156+
- Consider adding meaningful commits before releasing
157+
158+
**Best Practice**: Use descriptive commit messages - they become your changelog!
159+
160+
### Tag Already Exists
161+
162+
If you see "Error: Tag vX.Y.Z already exists!", you need to:
163+
- Choose a different version number, or
164+
- Delete the existing tag if it was created in error
165+
166+
### Release Workflow Didn't Trigger
167+
168+
Check that:
169+
- The release trigger workflow completed successfully
170+
- The tag was pushed (check repository tags)
171+
- The release workflow is enabled in Actions settings
172+
173+
### Version Mismatch
174+
175+
If `pyproject.toml` doesn't match the latest tag:
176+
- Run the release trigger workflow to sync versions
177+
- Or manually update `pyproject.toml` and push changes before running the release trigger
178+
179+
## Legacy Behavior (Pre-v0.1.10)
180+
181+
Before this change, the release workflow:
182+
- Created tags automatically on main branch pushes
183+
- Updated `pyproject.toml` AFTER creating the tag
184+
- Resulted in tags pointing to commits with outdated versions
185+
186+
This has been fixed in v0.1.10+.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
name: Release Trigger
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
version:
7+
description: 'Version to release (e.g., 0.1.11). Leave empty to auto-increment patch version.'
8+
required: false
9+
type: string
10+
11+
jobs:
12+
bump-version:
13+
runs-on: ubuntu-latest
14+
permissions:
15+
contents: write
16+
steps:
17+
- name: Checkout repository
18+
uses: actions/checkout@v6
19+
with:
20+
fetch-depth: 0
21+
token: ${{ secrets.GITHUB_TOKEN }}
22+
23+
- name: Configure Git
24+
run: |
25+
git config user.name "github-actions[bot]"
26+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
27+
28+
- name: Determine version
29+
id: version
30+
run: |
31+
if [[ -n "${{ github.event.inputs.version }}" ]]; then
32+
# Manual version specified
33+
VERSION="${{ github.event.inputs.version }}"
34+
# Remove 'v' prefix if present
35+
VERSION=${VERSION#v}
36+
echo "version=$VERSION" >> $GITHUB_OUTPUT
37+
echo "tag=v$VERSION" >> $GITHUB_OUTPUT
38+
echo "Using manual version: $VERSION"
39+
else
40+
# Auto-increment patch version
41+
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
42+
echo "Latest tag: $LATEST_TAG"
43+
44+
# Extract version number and increment
45+
VERSION=$(echo $LATEST_TAG | sed 's/v//')
46+
IFS='.' read -ra VERSION_PARTS <<< "$VERSION"
47+
MAJOR=${VERSION_PARTS[0]:-0}
48+
MINOR=${VERSION_PARTS[1]:-0}
49+
PATCH=${VERSION_PARTS[2]:-0}
50+
51+
# Increment patch version
52+
PATCH=$((PATCH + 1))
53+
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
54+
55+
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
56+
echo "tag=v$NEW_VERSION" >> $GITHUB_OUTPUT
57+
echo "Auto-incremented version: $NEW_VERSION"
58+
fi
59+
60+
- name: Check if tag already exists
61+
run: |
62+
if git rev-parse "${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then
63+
echo "Error: Tag ${{ steps.version.outputs.tag }} already exists!"
64+
exit 1
65+
fi
66+
67+
- name: Update pyproject.toml
68+
run: |
69+
sed -i "s/version = \".*\"/version = \"${{ steps.version.outputs.version }}\"/" pyproject.toml
70+
echo "Updated pyproject.toml to version ${{ steps.version.outputs.version }}"
71+
72+
- name: Update CHANGELOG.md
73+
run: |
74+
if [ -f "CHANGELOG.md" ]; then
75+
DATE=$(date +%Y-%m-%d)
76+
77+
# Get the previous tag to compare commits
78+
PREVIOUS_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
79+
80+
echo "Generating changelog from commits..."
81+
if [[ -n "$PREVIOUS_TAG" ]]; then
82+
echo "Changes since $PREVIOUS_TAG"
83+
84+
# Get commits since last tag, format as bullet points
85+
# Extract PR numbers and format nicely
86+
COMMITS=$(git log --oneline "$PREVIOUS_TAG"..HEAD --no-merges --pretty=format:"- %s" 2>/dev/null || echo "- Initial release")
87+
else
88+
echo "No previous tag found - this is the first release"
89+
COMMITS="- Initial release"
90+
fi
91+
92+
# Create new changelog entry
93+
{
94+
head -n 8 CHANGELOG.md
95+
echo ""
96+
echo "## [${{ steps.version.outputs.version }}] - $DATE"
97+
echo ""
98+
echo "### Changed"
99+
echo ""
100+
echo "$COMMITS"
101+
echo ""
102+
tail -n +9 CHANGELOG.md
103+
} > CHANGELOG.md.tmp
104+
mv CHANGELOG.md.tmp CHANGELOG.md
105+
106+
echo "✅ Updated CHANGELOG.md with commits since $PREVIOUS_TAG"
107+
else
108+
echo "No CHANGELOG.md found"
109+
fi
110+
111+
- name: Commit version bump
112+
run: |
113+
git add pyproject.toml CHANGELOG.md
114+
git commit -m "chore: bump version to ${{ steps.version.outputs.version }}"
115+
echo "Changes committed"
116+
117+
- name: Create and push tag
118+
run: |
119+
git tag -a "${{ steps.version.outputs.tag }}" -m "Release ${{ steps.version.outputs.tag }}"
120+
git push origin main
121+
git push origin "${{ steps.version.outputs.tag }}"
122+
echo "Tag ${{ steps.version.outputs.tag }} created and pushed"
123+
124+
- name: Summary
125+
run: |
126+
echo "✅ Version bumped to ${{ steps.version.outputs.version }}"
127+
echo "✅ Tag ${{ steps.version.outputs.tag }} created and pushed"
128+
echo "🚀 Release workflow will now build artifacts automatically"

0 commit comments

Comments
 (0)