Skip to content

Commit 8525202

Browse files
authored
Merge branch 'main' into add-retrospective-to-community-catalog
2 parents f455d10 + bfaca2c commit 8525202

18 files changed

Lines changed: 1112 additions & 76 deletions

.github/ISSUE_TEMPLATE/bug_report.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ body:
5151
id: version
5252
attributes:
5353
label: Specify CLI Version
54-
description: "Run `specify --version` or `pip show spec-kit`"
54+
description: "Run `specify version` or `pip show spec-kit`"
5555
placeholder: "e.g., 1.3.0"
5656
validations:
5757
required: true
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
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+
- Update `CHANGELOG.md` by adding a new section for the release based on commits since the last tag
63+
- Commit changes to a `chore/release-vX.Y.Z` branch
64+
- Create and push the git tag from that branch
65+
- Open a PR to merge the version bump into `main`
66+
- Trigger the release workflow automatically via the tag push
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+
- Update `CHANGELOG.md` by adding a new section for the release based on commits since the last tag
79+
- Commit changes to a `chore/release-vX.Y.Z` branch
80+
- Create and push the git tag from that branch
81+
- Open a PR to merge the version bump into `main`
82+
- Trigger the release workflow automatically via the tag push
83+
84+
## What Happens Next
85+
86+
Once the release trigger workflow completes:
87+
88+
1. A `chore/release-vX.Y.Z` branch is pushed with the version bump commit
89+
2. The git tag is pushed, pointing to that commit
90+
3. The **Release Workflow** is automatically triggered by the tag push
91+
4. Release artifacts are built for all supported agents
92+
5. A GitHub Release is created with all assets
93+
6. A PR is opened to merge the version bump branch into `main`
94+
95+
> **Note**: Merge the auto-opened PR after the release is published to keep `main` in sync.
96+
97+
## Workflow Details
98+
99+
### Release Trigger Workflow
100+
101+
**File**: `.github/workflows/release-trigger.yml`
102+
103+
**Trigger**: Manual (`workflow_dispatch`)
104+
105+
**Permissions Required**: `contents: write`
106+
107+
**Steps**:
108+
1. Checkout repository
109+
2. Determine version (manual or auto-increment)
110+
3. Check if tag already exists (prevents duplicates)
111+
4. Create `chore/release-vX.Y.Z` branch
112+
5. Update `pyproject.toml`
113+
6. Update `CHANGELOG.md` from git commits
114+
7. Commit changes
115+
8. Push branch and tag
116+
9. Open PR to merge version bump into `main`
117+
118+
### Release Workflow
119+
120+
**File**: `.github/workflows/release.yml`
121+
122+
**Trigger**: Tag push (`v*`)
123+
124+
**Permissions Required**: `contents: write`
125+
126+
**Steps**:
127+
1. Checkout repository at tag
128+
2. Extract version from tag name
129+
3. Check if release already exists
130+
4. Build release package variants (all agents × shell/powershell)
131+
5. Generate release notes from commits
132+
6. Create GitHub Release with all assets
133+
134+
## Version Constraints
135+
136+
- Tags must follow format: `v{MAJOR}.{MINOR}.{PATCH}`
137+
- Example valid versions: `v0.1.11`, `v0.2.0`, `v1.0.0`
138+
- Auto-increment only bumps patch version
139+
- Cannot create duplicate tags (workflow will fail)
140+
141+
## Benefits of This Approach
142+
143+
**Version Consistency**: Git tags point to commits with matching `pyproject.toml` version
144+
145+
**Single Source of Truth**: Version set once, used everywhere
146+
147+
**Prevents Drift**: No more manual version synchronization needed
148+
149+
**Clean Separation**: Versioning logic separate from artifact building
150+
151+
**Flexibility**: Supports both auto-increment and manual versioning
152+
153+
## Troubleshooting
154+
155+
### No Commits Since Last Release
156+
157+
If you run the release trigger workflow when there are no new commits since the last tag:
158+
- The workflow will still succeed
159+
- The CHANGELOG will show "- Initial release" if it's the first release
160+
- Or it will be empty if there are no commits
161+
- Consider adding meaningful commits before releasing
162+
163+
**Best Practice**: Use descriptive commit messages - they become your changelog!
164+
165+
### Tag Already Exists
166+
167+
If you see "Error: Tag vX.Y.Z already exists!", you need to:
168+
- Choose a different version number, or
169+
- Delete the existing tag if it was created in error
170+
171+
### Release Workflow Didn't Trigger
172+
173+
Check that:
174+
- The release trigger workflow completed successfully
175+
- The tag was pushed (check repository tags)
176+
- The release workflow is enabled in Actions settings
177+
178+
### Version Mismatch
179+
180+
If `pyproject.toml` doesn't match the latest tag:
181+
- Run the release trigger workflow to sync versions
182+
- Or manually update `pyproject.toml` and push changes before running the release trigger
183+
184+
## Legacy Behavior (Pre-v0.1.10)
185+
186+
Before this change, the release workflow:
187+
- Created tags automatically on main branch pushes
188+
- Updated `pyproject.toml` AFTER creating the tag
189+
- Resulted in tags pointing to commits with outdated versions
190+
191+
This has been fixed in v0.1.10+.

.github/workflows/docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
runs-on: ubuntu-latest
3030
steps:
3131
- name: Checkout
32-
uses: actions/checkout@v4
32+
uses: actions/checkout@v6
3333
with:
3434
fetch-depth: 0 # Fetch all history for git info
3535

.github/workflows/lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
runs-on: ubuntu-latest
1313
steps:
1414
- name: Checkout
15-
uses: actions/checkout@v4
15+
uses: actions/checkout@v6
1616

1717
- name: Run markdownlint-cli2
1818
uses: DavidAnson/markdownlint-cli2-action@v19
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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+
pull-requests: write
17+
steps:
18+
- name: Checkout repository
19+
uses: actions/checkout@v6
20+
with:
21+
fetch-depth: 0
22+
token: ${{ secrets.RELEASE_PAT }}
23+
24+
- name: Configure Git
25+
run: |
26+
git config user.name "github-actions[bot]"
27+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
28+
29+
- name: Determine version
30+
id: version
31+
env:
32+
INPUT_VERSION: ${{ github.event.inputs.version }}
33+
run: |
34+
if [[ -n "$INPUT_VERSION" ]]; then
35+
# Manual version specified - strip optional v prefix
36+
VERSION="${INPUT_VERSION#v}"
37+
# Validate strict semver format to prevent injection
38+
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
39+
echo "Error: Invalid version format '$VERSION'. Must be X.Y.Z (e.g. 1.2.3 or v1.2.3)"
40+
exit 1
41+
fi
42+
echo "version=$VERSION" >> $GITHUB_OUTPUT
43+
echo "tag=v$VERSION" >> $GITHUB_OUTPUT
44+
echo "Using manual version: $VERSION"
45+
else
46+
# Auto-increment patch version
47+
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
48+
echo "Latest tag: $LATEST_TAG"
49+
50+
# Extract version number and increment
51+
VERSION=$(echo $LATEST_TAG | sed 's/v//')
52+
IFS='.' read -ra VERSION_PARTS <<< "$VERSION"
53+
MAJOR=${VERSION_PARTS[0]:-0}
54+
MINOR=${VERSION_PARTS[1]:-0}
55+
PATCH=${VERSION_PARTS[2]:-0}
56+
57+
# Increment patch version
58+
PATCH=$((PATCH + 1))
59+
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
60+
61+
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
62+
echo "tag=v$NEW_VERSION" >> $GITHUB_OUTPUT
63+
echo "Auto-incremented version: $NEW_VERSION"
64+
fi
65+
66+
- name: Check if tag already exists
67+
run: |
68+
if git rev-parse "${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then
69+
echo "Error: Tag ${{ steps.version.outputs.tag }} already exists!"
70+
exit 1
71+
fi
72+
73+
- name: Create release branch
74+
run: |
75+
BRANCH="chore/release-${{ steps.version.outputs.tag }}"
76+
git checkout -b "$BRANCH"
77+
echo "branch=$BRANCH" >> $GITHUB_ENV
78+
79+
- name: Update pyproject.toml
80+
run: |
81+
sed -i "s/version = \".*\"/version = \"${{ steps.version.outputs.version }}\"/" pyproject.toml
82+
echo "Updated pyproject.toml to version ${{ steps.version.outputs.version }}"
83+
84+
- name: Update CHANGELOG.md
85+
run: |
86+
if [ -f "CHANGELOG.md" ]; then
87+
DATE=$(date +%Y-%m-%d)
88+
89+
# Get the previous tag to compare commits
90+
PREVIOUS_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
91+
92+
echo "Generating changelog from commits..."
93+
if [[ -n "$PREVIOUS_TAG" ]]; then
94+
echo "Changes since $PREVIOUS_TAG"
95+
COMMITS=$(git log --oneline "$PREVIOUS_TAG"..HEAD --no-merges --pretty=format:"- %s" 2>/dev/null || echo "- Initial release")
96+
else
97+
echo "No previous tag found - this is the first release"
98+
COMMITS="- Initial release"
99+
fi
100+
101+
# Create new changelog entry
102+
{
103+
head -n 8 CHANGELOG.md
104+
echo ""
105+
echo "## [${{ steps.version.outputs.version }}] - $DATE"
106+
echo ""
107+
echo "### Changed"
108+
echo ""
109+
echo "$COMMITS"
110+
echo ""
111+
tail -n +9 CHANGELOG.md
112+
} > CHANGELOG.md.tmp
113+
mv CHANGELOG.md.tmp CHANGELOG.md
114+
115+
echo "✅ Updated CHANGELOG.md with commits since $PREVIOUS_TAG"
116+
else
117+
echo "No CHANGELOG.md found"
118+
fi
119+
120+
- name: Commit version bump
121+
run: |
122+
if [ -f "CHANGELOG.md" ]; then
123+
git add pyproject.toml CHANGELOG.md
124+
else
125+
git add pyproject.toml
126+
fi
127+
128+
if git diff --cached --quiet; then
129+
echo "No changes to commit"
130+
else
131+
git commit -m "chore: bump version to ${{ steps.version.outputs.version }}"
132+
echo "Changes committed"
133+
fi
134+
135+
- name: Create and push tag
136+
run: |
137+
git tag -a "${{ steps.version.outputs.tag }}" -m "Release ${{ steps.version.outputs.tag }}"
138+
git push origin "${{ env.branch }}"
139+
git push origin "${{ steps.version.outputs.tag }}"
140+
echo "Branch ${{ env.branch }} and tag ${{ steps.version.outputs.tag }} pushed"
141+
142+
- name: Open pull request
143+
env:
144+
GITHUB_TOKEN: ${{ secrets.RELEASE_PAT }}
145+
run: |
146+
gh pr create \
147+
--base main \
148+
--head "${{ env.branch }}" \
149+
--title "chore: bump version to ${{ steps.version.outputs.version }}" \
150+
--body "Automated version bump to ${{ steps.version.outputs.version }}.
151+
152+
This PR was created by the Release Trigger workflow. The git tag \`${{ steps.version.outputs.tag }}\` has already been pushed and the release artifacts are being built.
153+
154+
Merge this PR to record the version bump and changelog update on \`main\`."
155+
156+
- name: Summary
157+
run: |
158+
echo "✅ Version bumped to ${{ steps.version.outputs.version }}"
159+
echo "✅ Tag ${{ steps.version.outputs.tag }} created and pushed"
160+
echo "✅ PR opened to merge version bump into main"
161+
echo "🚀 Release workflow is building artifacts from the tag"

0 commit comments

Comments
 (0)