|
| 1 | +--- |
| 2 | +name: Release Highlights |
| 3 | +description: Generate AI-powered release highlights and prepend to draft release |
| 4 | +on: |
| 5 | + workflow_run: |
| 6 | + workflows: ["Release"] |
| 7 | + types: [completed] |
| 8 | +permissions: |
| 9 | + contents: write |
| 10 | + pull-requests: read |
| 11 | +engine: copilot |
| 12 | +timeout-minutes: 10 |
| 13 | +network: |
| 14 | + allowed: |
| 15 | + - defaults |
| 16 | +safe-outputs: |
| 17 | + update-release: |
| 18 | +steps: |
| 19 | + - name: Setup release data |
| 20 | + env: |
| 21 | + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 22 | + run: | |
| 23 | + set -e |
| 24 | +
|
| 25 | + # Only proceed if the triggering workflow succeeded |
| 26 | + if [ "${{ github.event.workflow_run.conclusion }}" != "success" ]; then |
| 27 | + echo "Release workflow did not succeed. Skipping." |
| 28 | + exit 1 |
| 29 | + fi |
| 30 | +
|
| 31 | + mkdir -p /tmp/release-data |
| 32 | +
|
| 33 | + # Find the latest draft release |
| 34 | + gh api "repos/${{ github.repository }}/releases" \ |
| 35 | + --jq '[.[] | select(.draft)] | .[0]' > /tmp/release-data/current_release.json |
| 36 | +
|
| 37 | + RELEASE_TAG=$(jq -r '.tag_name' /tmp/release-data/current_release.json) |
| 38 | + RELEASE_ID=$(jq -r '.id' /tmp/release-data/current_release.json) |
| 39 | +
|
| 40 | + if [ "$RELEASE_TAG" = "null" ] || [ -z "$RELEASE_TAG" ]; then |
| 41 | + echo "No draft release found." |
| 42 | + exit 1 |
| 43 | + fi |
| 44 | +
|
| 45 | + echo "Draft release: $RELEASE_TAG (ID: $RELEASE_ID)" |
| 46 | + echo "RELEASE_TAG=$RELEASE_TAG" >> "$GITHUB_ENV" |
| 47 | + echo "RELEASE_ID=$RELEASE_ID" >> "$GITHUB_ENV" |
| 48 | +
|
| 49 | + # Find previous non-draft release |
| 50 | + PREV_RELEASE_TAG=$(gh api "repos/${{ github.repository }}/releases" \ |
| 51 | + --jq '[.[] | select(.draft | not)] | .[0].tag_name // empty') |
| 52 | + echo "PREV_RELEASE_TAG=$PREV_RELEASE_TAG" >> "$GITHUB_ENV" |
| 53 | +
|
| 54 | + if [ -n "$PREV_RELEASE_TAG" ]; then |
| 55 | + echo "Previous release: $PREV_RELEASE_TAG" |
| 56 | +
|
| 57 | + # Get commits between tags via API |
| 58 | + gh api "repos/${{ github.repository }}/compare/${PREV_RELEASE_TAG}...${RELEASE_TAG}" \ |
| 59 | + --jq '.commits | [.[] | {sha: .sha[:8], message: (.commit.message | split("\n") | .[0]), author: .author.login}]' \ |
| 60 | + > /tmp/release-data/commits.json |
| 61 | +
|
| 62 | + COMMIT_COUNT=$(jq length /tmp/release-data/commits.json) |
| 63 | + echo "✓ Found $COMMIT_COUNT commits" |
| 64 | +
|
| 65 | + # Get merged PRs between releases |
| 66 | + PREV_DATE=$(gh api "repos/${{ github.repository }}/releases/tags/${PREV_RELEASE_TAG}" --jq '.published_at') |
| 67 | + CURR_DATE=$(jq -r '.created_at' /tmp/release-data/current_release.json) |
| 68 | +
|
| 69 | + gh pr list \ |
| 70 | + --state merged \ |
| 71 | + --limit 100 \ |
| 72 | + --json number,title,author,labels,mergedAt,url \ |
| 73 | + --jq "[.[] | select(.mergedAt >= \"$PREV_DATE\" and .mergedAt <= \"$CURR_DATE\")]" \ |
| 74 | + > /tmp/release-data/pull_requests.json |
| 75 | +
|
| 76 | + PR_COUNT=$(jq length /tmp/release-data/pull_requests.json) |
| 77 | + echo "✓ Found $PR_COUNT pull requests" |
| 78 | + else |
| 79 | + echo "No previous release found. This is the first release." |
| 80 | + echo "[]" > /tmp/release-data/commits.json |
| 81 | + echo "[]" > /tmp/release-data/pull_requests.json |
| 82 | + fi |
| 83 | +
|
| 84 | + echo "✓ Setup complete" |
| 85 | +--- |
| 86 | + |
| 87 | +# Release Highlights Generator |
| 88 | + |
| 89 | +Generate an engaging release highlights summary for **${{ github.repository }}** release `${RELEASE_TAG}`. |
| 90 | + |
| 91 | +## Data Available |
| 92 | + |
| 93 | +All data is pre-fetched in `/tmp/release-data/`: |
| 94 | +- `current_release.json` - Draft release metadata (tag, name, existing body with auto-generated notes) |
| 95 | +- `commits.json` - Commits since `${PREV_RELEASE_TAG}` (sha, message, author) |
| 96 | +- `pull_requests.json` - Merged PRs between releases (may be empty if changes were direct commits) |
| 97 | + |
| 98 | +## Workflow |
| 99 | + |
| 100 | +### 1. Load Data |
| 101 | + |
| 102 | +```bash |
| 103 | +# View release metadata |
| 104 | +cat /tmp/release-data/current_release.json | jq '{tag_name, name, created_at}' |
| 105 | + |
| 106 | +# List commits |
| 107 | +cat /tmp/release-data/commits.json | jq -r '.[] | "- \(.message) (\(.sha)) by @\(.author)"' |
| 108 | + |
| 109 | +# List PRs (may be empty) |
| 110 | +cat /tmp/release-data/pull_requests.json | jq -r '.[] | "- #\(.number): \(.title) by @\(.author.login)"' |
| 111 | +``` |
| 112 | + |
| 113 | +### 2. Categorize & Prioritize |
| 114 | + |
| 115 | +Group changes by category (omit categories with no items): |
| 116 | +- **⚠️ Breaking Changes** - Requires user action (ALWAYS list first if present) |
| 117 | +- **✨ New Features** - User-facing capabilities |
| 118 | +- **🐛 Bug Fixes** - Issue resolutions |
| 119 | +- **⚡ Performance** - Speed/efficiency improvements |
| 120 | +- **🔧 Internal** - Refactoring, dependencies (usually omit from highlights) |
| 121 | + |
| 122 | +Use both commit messages and PR titles to determine categories. |
| 123 | +Skip internal/refactoring changes unless they have user impact. |
| 124 | + |
| 125 | +### 3. Write Highlights |
| 126 | + |
| 127 | +Structure: |
| 128 | +```markdown |
| 129 | +## 🌟 Release Highlights |
| 130 | + |
| 131 | +[1-2 sentence summary of the release theme/focus] |
| 132 | + |
| 133 | +### ⚠️ Breaking Changes |
| 134 | +[If any - list FIRST with migration guidance] |
| 135 | + |
| 136 | +### ✨ What's New |
| 137 | +[Key features with user benefit] |
| 138 | + |
| 139 | +### 🐛 Bug Fixes & Improvements |
| 140 | +[Notable fixes - focus on user impact] |
| 141 | +``` |
| 142 | + |
| 143 | +**Writing Guidelines:** |
| 144 | +- Lead with benefits: "Driver deletion is now 2x faster" not "Optimized delete loop" |
| 145 | +- Be specific about what changed and why it matters to users |
| 146 | +- Keep it concise and scannable (users grasp key changes in 30 seconds) |
| 147 | +- Use professional, enthusiastic tone |
| 148 | +- This is a Windows desktop application — write from the end-user perspective |
| 149 | + |
| 150 | +### 4. Handle Special Cases |
| 151 | + |
| 152 | +**First Release** (no `${PREV_RELEASE_TAG}`): |
| 153 | +```markdown |
| 154 | +## 🎉 First Release |
| 155 | +Welcome to the inaugural release! This version includes the following capabilities: |
| 156 | +[List primary features] |
| 157 | +``` |
| 158 | + |
| 159 | +**Maintenance Release** (no user-facing changes): |
| 160 | +```markdown |
| 161 | +## 🔧 Maintenance Release |
| 162 | +Dependency updates and internal improvements to keep things running smoothly. |
| 163 | +``` |
| 164 | + |
| 165 | +### 5. Update Release |
| 166 | + |
| 167 | +**CRITICAL**: You MUST call the `update_release` MCP tool to prepend highlights to the draft release. |
| 168 | + |
| 169 | +**✅ CORRECT - Call the MCP tool directly:** |
| 170 | +``` |
| 171 | +safeoutputs/update_release( |
| 172 | + tag="${RELEASE_TAG}", |
| 173 | + operation="prepend", |
| 174 | + body="## 🌟 Release Highlights\n\n[Your complete markdown highlights here]" |
| 175 | +) |
| 176 | +``` |
| 177 | + |
| 178 | +**❌ INCORRECT - DO NOT:** |
| 179 | +- Write JSON files manually |
| 180 | +- Use bash to simulate tool calls |
| 181 | +- Create scripts that write to outputs |
| 182 | + |
| 183 | +**Important**: If no action is needed after completing your analysis, you **MUST** call the `noop` safe-output tool: |
| 184 | +``` |
| 185 | +safeoutputs/noop(message="No action needed: [brief explanation]") |
| 186 | +``` |
0 commit comments