From b1423aa9c5a4ccb040219f300cbc1a6d62f66a69 Mon Sep 17 00:00:00 2001 From: Manfred Riem <15701806+mnriem@users.noreply.github.com> Date: Mon, 2 Mar 2026 12:11:12 -0600 Subject: [PATCH 1/6] 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. --- .github/workflows/RELEASE-PROCESS.md | 186 ++++++++++++++++++ .github/workflows/release-trigger.yml | 128 ++++++++++++ .github/workflows/release.yml | 48 ++--- .github/workflows/scripts/simulate-release.sh | 167 ++++++++++++++++ CHANGELOG.md | 39 ++++ pyproject.toml | 2 +- 6 files changed, 539 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/RELEASE-PROCESS.md create mode 100644 .github/workflows/release-trigger.yml create mode 100755 .github/workflows/scripts/simulate-release.sh diff --git a/.github/workflows/RELEASE-PROCESS.md b/.github/workflows/RELEASE-PROCESS.md new file mode 100644 index 0000000000..58b4365115 --- /dev/null +++ b/.github/workflows/RELEASE-PROCESS.md @@ -0,0 +1,186 @@ +# Release Process + +This document describes the automated release process for Spec Kit. + +## Overview + +The release process is split into two workflows to ensure version consistency: + +1. **Release Trigger Workflow** (`release-trigger.yml`) - Manages versioning and triggers release +2. **Release Workflow** (`release.yml`) - Builds and publishes artifacts + +This separation ensures that git tags always point to commits with the correct version in `pyproject.toml`. + +## Before Creating a Release + +**Important**: Write clear, descriptive commit messages! + +### How CHANGELOG.md Works + +The CHANGELOG is **automatically generated** from your git commit messages: + +1. **During Development**: Write clear, descriptive commit messages: + ```bash + git commit -m "feat: Add new authentication feature" + git commit -m "fix: Resolve timeout issue in API client (#123)" + git commit -m "docs: Update installation instructions" + ``` + +2. **When Releasing**: The release trigger workflow automatically: + - Finds all commits since the last release tag + - Formats them as changelog entries + - Inserts them into CHANGELOG.md + - Commits the updated changelog before creating the new tag + +### Commit Message Best Practices + +Good commit messages make good changelogs: +- **Be descriptive**: "Add user authentication" not "Update files" +- **Reference issues/PRs**: Include `(#123)` for automated linking +- **Use conventional commits** (optional): `feat:`, `fix:`, `docs:`, `chore:` +- **Keep it concise**: One line is ideal, details go in commit body + +**Example commits that become good changelog entries:** +``` +fix: prepend YAML frontmatter to Cursor .mdc files (#1699) +feat: add generic agent support with customizable command directories (#1639) +docs: document dual-catalog system for extensions (#1689) +``` + +## Creating a Release + +### Option 1: Auto-Increment (Recommended for patches) + +1. Go to **Actions** → **Release Trigger** +2. Click **Run workflow** +3. Leave the version field **empty** +4. Click **Run workflow** + +The workflow will: +- Auto-increment the patch version (e.g., `0.1.10` → `0.1.11`) +- Update `pyproject.toml` +- Convert `[Unreleased]` section in CHANGELOG.md to the new version +- Add a new empty `[Unreleased]` section +- Commit changes +- Create and push git tag +- Trigger the release workflow automatically + +### Option 2: Manual Version (For major/minor bumps) + +1. Go to **Actions** → **Release Trigger** +2. Click **Run workflow** +3. Enter the desired version (e.g., `0.2.0` or `v0.2.0`) +4. Click **Run workflow** + +The workflow will: +- Use your specified version +- Update `pyproject.toml` +- Convert `[Unreleased]` section in CHANGELOG.md to the new version +- Add a new empty `[Unreleased]` section +- Commit changes +- Create and push git tag +- Trigger the release workflow automatically + +## What Happens Next + +Once the release trigger workflow completes: + +1. The git tag is pushed to GitHub +2. The **Release Workflow** is automatically triggered +3. Release artifacts are built for all supported agents +4. A GitHub Release is created with all assets +5. Release notes are generated from PR titles + +## Workflow Details + +### Release Trigger Workflow + +**File**: `.github/workflows/release-trigger.yml` + +**Trigger**: Manual (`workflow_dispatch`) + +**Permissions Required**: `contents: write` + +**Steps**: +1. Checkout repository +2. Determine version (manual or auto-increment) +3. Check if tag already exists (prevents duplicates) +4. Update `pyproject.toml` +5. Update `CHANGELOG.md` +6. Commit changes +7. Create and push tag + +### Release Workflow + +**File**: `.github/workflows/release.yml` + +**Trigger**: Tag push (`v*`) + +**Permissions Required**: `contents: write` + +**Steps**: +1. Checkout repository at tag +2. Extract version from tag name +3. Check if release already exists +4. Build release package variants (all agents × shell/powershell) +5. Generate release notes from commits +6. Create GitHub Release with all assets + +## Version Constraints + +- Tags must follow format: `v{MAJOR}.{MINOR}.{PATCH}` +- Example valid versions: `v0.1.11`, `v0.2.0`, `v1.0.0` +- Auto-increment only bumps patch version +- Cannot create duplicate tags (workflow will fail) + +## Benefits of This Approach + +✅ **Version Consistency**: Git tags point to commits with matching `pyproject.toml` version + +✅ **Single Source of Truth**: Version set once, used everywhere + +✅ **Prevents Drift**: No more manual version synchronization needed + +✅ **Clean Separation**: Versioning logic separate from artifact building + +✅ **Flexibility**: Supports both auto-increment and manual versioning + +## Troubleshooting + +### No Commits Since Last Release + +If you run the release trigger workflow when there are no new commits since the last tag: +- The workflow will still succeed +- The CHANGELOG will show "- Initial release" if it's the first release +- Or it will be empty if there are no commits +- Consider adding meaningful commits before releasing + +**Best Practice**: Use descriptive commit messages - they become your changelog! + +### Tag Already Exists + +If you see "Error: Tag vX.Y.Z already exists!", you need to: +- Choose a different version number, or +- Delete the existing tag if it was created in error + +### Release Workflow Didn't Trigger + +Check that: +- The release trigger workflow completed successfully +- The tag was pushed (check repository tags) +- The release workflow is enabled in Actions settings + +### Version Mismatch + +If `pyproject.toml` doesn't match the latest tag: +- Run the release trigger workflow to sync versions +- Or manually update `pyproject.toml` and push changes before running the release trigger + +## Legacy Behavior (Pre-v0.1.10) + +Before this change, the release workflow: +- Created tags automatically on main branch pushes +- Updated `pyproject.toml` AFTER creating the tag +- Resulted in tags pointing to commits with outdated versions + +This has been fixed in v0.1.10+. diff --git a/.github/workflows/release-trigger.yml b/.github/workflows/release-trigger.yml new file mode 100644 index 0000000000..e3ff80d3a4 --- /dev/null +++ b/.github/workflows/release-trigger.yml @@ -0,0 +1,128 @@ +name: Release Trigger + +on: + workflow_dispatch: + inputs: + version: + description: 'Version to release (e.g., 0.1.11). Leave empty to auto-increment patch version.' + required: false + type: string + +jobs: + bump-version: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Determine version + id: version + run: | + if [[ -n "${{ github.event.inputs.version }}" ]]; then + # Manual version specified + VERSION="${{ github.event.inputs.version }}" + # Remove 'v' prefix if present + VERSION=${VERSION#v} + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "tag=v$VERSION" >> $GITHUB_OUTPUT + echo "Using manual version: $VERSION" + else + # Auto-increment patch version + LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") + echo "Latest tag: $LATEST_TAG" + + # Extract version number and increment + VERSION=$(echo $LATEST_TAG | sed 's/v//') + IFS='.' read -ra VERSION_PARTS <<< "$VERSION" + MAJOR=${VERSION_PARTS[0]:-0} + MINOR=${VERSION_PARTS[1]:-0} + PATCH=${VERSION_PARTS[2]:-0} + + # Increment patch version + PATCH=$((PATCH + 1)) + NEW_VERSION="$MAJOR.$MINOR.$PATCH" + + echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT + echo "tag=v$NEW_VERSION" >> $GITHUB_OUTPUT + echo "Auto-incremented version: $NEW_VERSION" + fi + + - name: Check if tag already exists + run: | + if git rev-parse "${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then + echo "Error: Tag ${{ steps.version.outputs.tag }} already exists!" + exit 1 + fi + + - name: Update pyproject.toml + run: | + sed -i "s/version = \".*\"/version = \"${{ steps.version.outputs.version }}\"/" pyproject.toml + echo "Updated pyproject.toml to version ${{ steps.version.outputs.version }}" + + - name: Update CHANGELOG.md + run: | + if [ -f "CHANGELOG.md" ]; then + DATE=$(date +%Y-%m-%d) + + # Get the previous tag to compare commits + PREVIOUS_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + + echo "Generating changelog from commits..." + if [[ -n "$PREVIOUS_TAG" ]]; then + echo "Changes since $PREVIOUS_TAG" + + # Get commits since last tag, format as bullet points + # Extract PR numbers and format nicely + COMMITS=$(git log --oneline "$PREVIOUS_TAG"..HEAD --no-merges --pretty=format:"- %s" 2>/dev/null || echo "- Initial release") + else + echo "No previous tag found - this is the first release" + COMMITS="- Initial release" + fi + + # Create new changelog entry + { + head -n 8 CHANGELOG.md + echo "" + echo "## [${{ steps.version.outputs.version }}] - $DATE" + echo "" + echo "### Changed" + echo "" + echo "$COMMITS" + echo "" + tail -n +9 CHANGELOG.md + } > CHANGELOG.md.tmp + mv CHANGELOG.md.tmp CHANGELOG.md + + echo "✅ Updated CHANGELOG.md with commits since $PREVIOUS_TAG" + else + echo "No CHANGELOG.md found" + fi + + - name: Commit version bump + run: | + git add pyproject.toml CHANGELOG.md + git commit -m "chore: bump version to ${{ steps.version.outputs.version }}" + echo "Changes committed" + + - name: Create and push tag + run: | + git tag -a "${{ steps.version.outputs.tag }}" -m "Release ${{ steps.version.outputs.tag }}" + git push origin main + git push origin "${{ steps.version.outputs.tag }}" + echo "Tag ${{ steps.version.outputs.tag }} created and pushed" + + - name: Summary + run: | + echo "✅ Version bumped to ${{ steps.version.outputs.version }}" + echo "✅ Tag ${{ steps.version.outputs.tag }} created and pushed" + echo "🚀 Release workflow will now build artifacts automatically" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 39e0d8531a..bf0a3d5561 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,68 +2,56 @@ name: Create Release on: push: - branches: [ main ] - paths: - - 'memory/**' - - 'scripts/**' - - 'src/**' - - 'templates/**' - - '.github/workflows/**' - workflow_dispatch: + tags: + - 'v*' jobs: release: runs-on: ubuntu-latest permissions: contents: write - pull-requests: write steps: - name: Checkout repository uses: actions/checkout@v6 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - - name: Get latest tag - id: get_tag + + - name: Extract version from tag + id: version run: | - chmod +x .github/workflows/scripts/get-next-version.sh - .github/workflows/scripts/get-next-version.sh + VERSION=${GITHUB_REF#refs/tags/} + echo "tag=$VERSION" >> $GITHUB_OUTPUT + echo "Building release for $VERSION" + - name: Check if release already exists id: check_release run: | chmod +x .github/workflows/scripts/check-release-exists.sh - .github/workflows/scripts/check-release-exists.sh ${{ steps.get_tag.outputs.new_version }} + .github/workflows/scripts/check-release-exists.sh ${{ steps.version.outputs.tag }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Create release package variants if: steps.check_release.outputs.exists == 'false' run: | chmod +x .github/workflows/scripts/create-release-packages.sh - .github/workflows/scripts/create-release-packages.sh ${{ steps.get_tag.outputs.new_version }} + .github/workflows/scripts/create-release-packages.sh ${{ steps.version.outputs.tag }} + - name: Generate release notes if: steps.check_release.outputs.exists == 'false' id: release_notes run: | chmod +x .github/workflows/scripts/generate-release-notes.sh - .github/workflows/scripts/generate-release-notes.sh ${{ steps.get_tag.outputs.new_version }} ${{ steps.get_tag.outputs.latest_tag }} + # Get the previous tag for changelog generation + PREVIOUS_TAG=$(git describe --tags --abbrev=0 ${{ steps.version.outputs.tag }}^ 2>/dev/null || echo "") + .github/workflows/scripts/generate-release-notes.sh ${{ steps.version.outputs.tag }} "$PREVIOUS_TAG" + - name: Create GitHub Release if: steps.check_release.outputs.exists == 'false' run: | chmod +x .github/workflows/scripts/create-github-release.sh - .github/workflows/scripts/create-github-release.sh ${{ steps.get_tag.outputs.new_version }} + .github/workflows/scripts/create-github-release.sh ${{ steps.version.outputs.tag }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Update version in pyproject.toml (for release artifacts only) - if: steps.check_release.outputs.exists == 'false' - run: | - chmod +x .github/workflows/scripts/update-version.sh - .github/workflows/scripts/update-version.sh ${{ steps.get_tag.outputs.new_version }} - - name: Commit version bump to main - if: steps.check_release.outputs.exists == 'false' - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add pyproject.toml - git diff --cached --quiet || git commit -m "chore: bump version to ${{ steps.get_tag.outputs.new_version }} [skip ci]" - git push diff --git a/.github/workflows/scripts/simulate-release.sh b/.github/workflows/scripts/simulate-release.sh new file mode 100755 index 0000000000..9854e69ade --- /dev/null +++ b/.github/workflows/scripts/simulate-release.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash +set -euo pipefail + +# simulate-release.sh +# Simulate the release process locally without pushing to GitHub +# Usage: simulate-release.sh [version] +# If version is omitted, auto-increments patch version + +# Colors for output +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +echo -e "${BLUE}🧪 Simulating Release Process Locally${NC}" +echo "======================================" +echo "" + +# Step 1: Determine version +if [[ -n "${1:-}" ]]; then + VERSION="${1#v}" + TAG="v$VERSION" + echo -e "${GREEN}📝 Using manual version: $VERSION${NC}" +else + LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") + echo -e "${BLUE}Latest tag: $LATEST_TAG${NC}" + + VERSION=$(echo $LATEST_TAG | sed 's/v//') + IFS='.' read -ra VERSION_PARTS <<< "$VERSION" + MAJOR=${VERSION_PARTS[0]:-0} + MINOR=${VERSION_PARTS[1]:-0} + PATCH=${VERSION_PARTS[2]:-0} + + PATCH=$((PATCH + 1)) + VERSION="$MAJOR.$MINOR.$PATCH" + TAG="v$VERSION" + echo -e "${GREEN}📝 Auto-incremented to: $VERSION${NC}" +fi + +echo "" + +# Step 2: Check if tag exists +if git rev-parse "$TAG" >/dev/null 2>&1; then + echo -e "${RED}❌ Error: Tag $TAG already exists!${NC}" + echo " Please use a different version or delete the tag first." + exit 1 +fi +echo -e "${GREEN}✓ Tag $TAG is available${NC}" + +# Step 3: Backup current state +echo "" +echo -e "${YELLOW}💾 Creating backup of current state...${NC}" +BACKUP_DIR=$(mktemp -d) +cp pyproject.toml "$BACKUP_DIR/pyproject.toml.bak" +cp CHANGELOG.md "$BACKUP_DIR/CHANGELOG.md.bak" +echo -e "${GREEN}✓ Backup created at: $BACKUP_DIR${NC}" + +# Step 4: Update pyproject.toml +echo "" +echo -e "${YELLOW}📝 Updating pyproject.toml...${NC}" +sed -i.tmp "s/version = \".*\"/version = \"$VERSION\"/" pyproject.toml +rm -f pyproject.toml.tmp +echo -e "${GREEN}✓ Updated pyproject.toml to version $VERSION${NC}" + +# Step 5: Update CHANGELOG.md +echo "" +echo -e "${YELLOW}📝 Updating CHANGELOG.md...${NC}" +DATE=$(date +%Y-%m-%d) + +# Get the previous tag to compare commits +PREVIOUS_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + +if [[ -n "$PREVIOUS_TAG" ]]; then + echo " Generating changelog from commits since $PREVIOUS_TAG" + # Get commits since last tag, format as bullet points + COMMITS=$(git log --oneline "$PREVIOUS_TAG"..HEAD --no-merges --pretty=format:"- %s" 2>/dev/null || echo "- Initial release") +else + echo " No previous tag found - this is the first release" + COMMITS="- Initial release" +fi + +# Create temp file with new entry +{ + head -n 8 CHANGELOG.md + echo "" + echo "## [$VERSION] - $DATE" + echo "" + echo "### Changed" + echo "" + echo "$COMMITS" + echo "" + tail -n +9 CHANGELOG.md +} > CHANGELOG.md.tmp +mv CHANGELOG.md.tmp CHANGELOG.md +echo -e "${GREEN}✓ Updated CHANGELOG.md with commits since $PREVIOUS_TAG${NC}" + +# Step 6: Show what would be committed +echo "" +echo -e "${YELLOW}📋 Changes that would be committed:${NC}" +git diff pyproject.toml CHANGELOG.md + +# Step 7: Create temporary tag (no push) +echo "" +echo -e "${YELLOW}🏷️ Creating temporary local tag...${NC}" +git tag -a "$TAG" -m "Simulated release $TAG" 2>/dev/null || true +echo -e "${GREEN}✓ Tag $TAG created locally${NC}" + +# Step 8: Simulate release artifact creation +echo "" +echo -e "${YELLOW}📦 Simulating release package creation...${NC}" +echo " (Running create-release-packages.sh in dry-run mode)" +echo "" + +# Check if script exists and is executable +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [[ -x "$SCRIPT_DIR/create-release-packages.sh" ]]; then + # Just show what would be created, don't actually create + echo -e "${BLUE}Packages that would be created:${NC}" + echo " - spec-kit-template-copilot-sh-$TAG.zip" + echo " - spec-kit-template-copilot-ps-$TAG.zip" + echo " - spec-kit-template-claude-sh-$TAG.zip" + echo " - spec-kit-template-claude-ps-$TAG.zip" + echo " - spec-kit-template-gemini-sh-$TAG.zip" + echo " - spec-kit-template-gemini-ps-$TAG.zip" + echo " - spec-kit-template-cursor-agent-sh-$TAG.zip" + echo " - spec-kit-template-cursor-agent-ps-$TAG.zip" + echo " ... (40 total packages)" +else + echo -e "${RED}⚠️ create-release-packages.sh not found or not executable${NC}" +fi + +# Step 9: Simulate release notes generation +echo "" +echo -e "${YELLOW}📄 Simulating release notes generation...${NC}" +echo "" +PREVIOUS_TAG=$(git describe --tags --abbrev=0 $TAG^ 2>/dev/null || echo "") +if [[ -n "$PREVIOUS_TAG" ]]; then + echo -e "${BLUE}Changes since $PREVIOUS_TAG:${NC}" + git log --oneline "$PREVIOUS_TAG".."$TAG" | head -n 10 + echo "" +else + echo -e "${BLUE}No previous tag found - this would be the first release${NC}" +fi + +# Step 10: Summary +echo "" +echo -e "${GREEN}🎉 Simulation Complete!${NC}" +echo "======================================" +echo "" +echo -e "${BLUE}Summary:${NC}" +echo " Version: $VERSION" +echo " Tag: $TAG" +echo " Backup: $BACKUP_DIR" +echo "" +echo -e "${YELLOW}⚠️ SIMULATION ONLY - NO CHANGES PUSHED${NC}" +echo "" +echo -e "${BLUE}Next steps:${NC}" +echo " 1. Review the changes above" +echo " 2. To keep changes: git add pyproject.toml CHANGELOG.md && git commit" +echo " 3. To discard changes: git checkout pyproject.toml CHANGELOG.md && git tag -d $TAG" +echo " 4. To restore from backup: cp $BACKUP_DIR/* ." +echo "" +echo -e "${BLUE}To run the actual release:${NC}" +echo " Go to: https://github.com/github/spec-kit/actions/workflows/release-trigger.yml" +echo " Click 'Run workflow' and enter version: $VERSION" +echo "" diff --git a/CHANGELOG.md b/CHANGELOG.md index 741ce5d0cb..c38c0f226b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,45 @@ Recent changes to the Specify CLI and templates are documented here. 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). +## [0.1.10] - 2026-03-02 + +### Fixed + +- **Version Sync Issue (#1721)**: Fixed version mismatch between `pyproject.toml` and git release tags + - Split release process into two workflows: `release-trigger.yml` for version management and `release.yml` for artifact building + - Version bump now happens BEFORE tag creation, ensuring tags point to commits with correct version + - Supports both manual version specification and auto-increment (patch version) + - Git tags now accurately reflect the version in `pyproject.toml` at that commit + - Prevents confusion when installing from source + +## [0.1.9] - 2026-02-28 + +### Changed + +- Updated dependency: bumped astral-sh/setup-uv from 6 to 7 + +## [0.1.8] - 2026-02-28 + +### Changed + +- Updated dependency: bumped actions/setup-python from 5 to 6 + +## [0.1.7] - 2026-02-27 + +### Changed + +- Updated outdated GitHub Actions versions +- Documented dual-catalog system for extensions + +### Fixed + +- Fixed version command in documentation + +### Added + +- Added Cleanup Extension to README +- Added retrospective extension to community catalog + ## [0.1.6] - 2026-02-23 ### Fixed diff --git a/pyproject.toml b/pyproject.toml index 5f6a2eb7ab..d0fc64b03e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "specify-cli" -version = "0.1.6" +version = "0.1.10" description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)." requires-python = ">=3.11" dependencies = [ From f16853d2df66beb0da8b46b4dc10e3858d75a75e Mon Sep 17 00:00:00 2001 From: Manfred Riem <15701806+mnriem@users.noreply.github.com> Date: Mon, 2 Mar 2026 12:28:34 -0600 Subject: [PATCH 2/6] Update .github/workflows/RELEASE-PROCESS.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/RELEASE-PROCESS.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/RELEASE-PROCESS.md b/.github/workflows/RELEASE-PROCESS.md index 58b4365115..85500a40be 100644 --- a/.github/workflows/RELEASE-PROCESS.md +++ b/.github/workflows/RELEASE-PROCESS.md @@ -59,8 +59,7 @@ docs: document dual-catalog system for extensions (#1689) The workflow will: - Auto-increment the patch version (e.g., `0.1.10` → `0.1.11`) - Update `pyproject.toml` -- Convert `[Unreleased]` section in CHANGELOG.md to the new version -- Add a new empty `[Unreleased]` section +- Update `CHANGELOG.md` by adding a new section for the release based on commits since the last tag - Commit changes - Create and push git tag - Trigger the release workflow automatically From e435ab88aa3ffe8e9d33a9938ee1b98c7aa465be Mon Sep 17 00:00:00 2001 From: Manfred Riem <15701806+mnriem@users.noreply.github.com> Date: Mon, 2 Mar 2026 12:29:18 -0600 Subject: [PATCH 3/6] Update .github/workflows/scripts/simulate-release.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/scripts/simulate-release.sh | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/.github/workflows/scripts/simulate-release.sh b/.github/workflows/scripts/simulate-release.sh index 9854e69ade..a3960d0317 100755 --- a/.github/workflows/scripts/simulate-release.sh +++ b/.github/workflows/scripts/simulate-release.sh @@ -109,23 +109,17 @@ echo -e "${GREEN}✓ Tag $TAG created locally${NC}" # Step 8: Simulate release artifact creation echo "" echo -e "${YELLOW}📦 Simulating release package creation...${NC}" -echo " (Running create-release-packages.sh in dry-run mode)" +echo " (High-level simulation only; packaging script is not executed)" echo "" # Check if script exists and is executable SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [[ -x "$SCRIPT_DIR/create-release-packages.sh" ]]; then - # Just show what would be created, don't actually create - echo -e "${BLUE}Packages that would be created:${NC}" - echo " - spec-kit-template-copilot-sh-$TAG.zip" - echo " - spec-kit-template-copilot-ps-$TAG.zip" - echo " - spec-kit-template-claude-sh-$TAG.zip" - echo " - spec-kit-template-claude-ps-$TAG.zip" - echo " - spec-kit-template-gemini-sh-$TAG.zip" - echo " - spec-kit-template-gemini-ps-$TAG.zip" - echo " - spec-kit-template-cursor-agent-sh-$TAG.zip" - echo " - spec-kit-template-cursor-agent-ps-$TAG.zip" - echo " ... (40 total packages)" + echo -e "${BLUE}In a real release, the following command would be run to create packages:${NC}" + echo " $SCRIPT_DIR/create-release-packages.sh \"$TAG\"" + echo "" + echo "This simulation does not enumerate individual package files to avoid" + echo "drifting from the actual behavior of create-release-packages.sh." else echo -e "${RED}⚠️ create-release-packages.sh not found or not executable${NC}" fi From d2aac8d60d3fc70d83ede3440c23195736ca0dea Mon Sep 17 00:00:00 2001 From: Manfred Riem <15701806+mnriem@users.noreply.github.com> Date: Mon, 2 Mar 2026 12:29:45 -0600 Subject: [PATCH 4/6] Update .github/workflows/release.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bf0a3d5561..2e29592cc0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,6 +45,10 @@ jobs: chmod +x .github/workflows/scripts/generate-release-notes.sh # Get the previous tag for changelog generation PREVIOUS_TAG=$(git describe --tags --abbrev=0 ${{ steps.version.outputs.tag }}^ 2>/dev/null || echo "") + # Default to v0.0.0 if no previous tag is found (e.g., first release) + if [ -z "$PREVIOUS_TAG" ]; then + PREVIOUS_TAG="v0.0.0" + fi .github/workflows/scripts/generate-release-notes.sh ${{ steps.version.outputs.tag }} "$PREVIOUS_TAG" - name: Create GitHub Release From cc2754cbf541a332d812a688d314a7e10556db7d Mon Sep 17 00:00:00 2001 From: Manfred Riem <15701806+mnriem@users.noreply.github.com> Date: Mon, 2 Mar 2026 12:37:35 -0600 Subject: [PATCH 5/6] Update .github/workflows/release-trigger.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/release-trigger.yml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-trigger.yml b/.github/workflows/release-trigger.yml index e3ff80d3a4..ba7d422015 100644 --- a/.github/workflows/release-trigger.yml +++ b/.github/workflows/release-trigger.yml @@ -110,10 +110,18 @@ jobs: - name: Commit version bump run: | - git add pyproject.toml CHANGELOG.md - git commit -m "chore: bump version to ${{ steps.version.outputs.version }}" - echo "Changes committed" + if [ -f "CHANGELOG.md" ]; then + git add pyproject.toml CHANGELOG.md + else + git add pyproject.toml + fi + if git diff --cached --quiet; then + echo "No changes to commit" + else + git commit -m "chore: bump version to ${{ steps.version.outputs.version }}" + echo "Changes committed" + fi - name: Create and push tag run: | git tag -a "${{ steps.version.outputs.tag }}" -m "Release ${{ steps.version.outputs.tag }}" From 88e50a10ec1b7b725641a05ca139a669c888cb06 Mon Sep 17 00:00:00 2001 From: Manfred Riem <15701806+mnriem@users.noreply.github.com> Date: Mon, 2 Mar 2026 12:48:11 -0600 Subject: [PATCH 6/6] fix: harden release-trigger against shell injection and fix stale docs - Pass workflow_dispatch version input via env: instead of direct interpolation into shell script, preventing potential injection attacks - Validate version input against strict semver regex before use - Fix RELEASE-PROCESS.md Option 2 still referencing [Unreleased] section handling that no longer exists in the workflow --- .github/workflows/RELEASE-PROCESS.md | 3 +-- .github/workflows/release-trigger.yml | 15 ++++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/RELEASE-PROCESS.md b/.github/workflows/RELEASE-PROCESS.md index 85500a40be..6d0aaa8594 100644 --- a/.github/workflows/RELEASE-PROCESS.md +++ b/.github/workflows/RELEASE-PROCESS.md @@ -74,8 +74,7 @@ The workflow will: The workflow will: - Use your specified version - Update `pyproject.toml` -- Convert `[Unreleased]` section in CHANGELOG.md to the new version -- Add a new empty `[Unreleased]` section +- Update `CHANGELOG.md` by adding a new section for the release based on commits since the last tag - Commit changes - Create and push git tag - Trigger the release workflow automatically diff --git a/.github/workflows/release-trigger.yml b/.github/workflows/release-trigger.yml index ba7d422015..dd16152c50 100644 --- a/.github/workflows/release-trigger.yml +++ b/.github/workflows/release-trigger.yml @@ -27,12 +27,17 @@ jobs: - name: Determine version id: version + env: + INPUT_VERSION: ${{ github.event.inputs.version }} run: | - if [[ -n "${{ github.event.inputs.version }}" ]]; then - # Manual version specified - VERSION="${{ github.event.inputs.version }}" - # Remove 'v' prefix if present - VERSION=${VERSION#v} + if [[ -n "$INPUT_VERSION" ]]; then + # Manual version specified - strip optional v prefix + VERSION="${INPUT_VERSION#v}" + # Validate strict semver format to prevent injection + if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: Invalid version format '$VERSION'. Must be X.Y.Z (e.g. 1.2.3 or v1.2.3)" + exit 1 + fi echo "version=$VERSION" >> $GITHUB_OUTPUT echo "tag=v$VERSION" >> $GITHUB_OUTPUT echo "Using manual version: $VERSION"