feat: Add AI-powered release notes generator with Claude

Implements automated release notes generation using Claude AI for releases and branch comparisons.

## Features

- **GitHub Action Workflow**: Automatically generates release notes when tags are pushed or releases are created
- **Local Testing Script**: Test release note generation locally before pushing
- **Branch Comparison Support**: Compare stable vs main branches or any two refs (tags, branches, commits)
- **Smart Branch Resolution**: Automatically resolves local/remote branches (e.g., stable → origin/stable)
- **Comprehensive Release Notes**: Includes features, improvements, bug fixes, technical changes, statistics, and contributors

## Files Added

- `.github/workflows/release-notes.yml` - GitHub Action for automated release notes
- `.github/RELEASE_NOTES_SETUP.md` - Complete setup guide and usage documentation
- `.github/test-release-notes.sh` - Local testing script with branch comparison support
- `.gitignore` - Exclude local release notes test files

## Usage

### Local Testing
```bash
export ANTHROPIC_API_KEY="sk-ant-..."
./.github/test-release-notes.sh                    # Compare origin/stable..main
./.github/test-release-notes.sh stable main        # Explicit branches
./.github/test-release-notes.sh v1.0.0 v2.0.0      # Compare tags
```

### GitHub Action
1. Add `ANTHROPIC_API_KEY` to repository secrets
2. Push a tag: `git tag v1.0.0 && git push origin v1.0.0`
3. Release notes are automatically generated and added to the GitHub release

## Technical Details

- Uses Claude Sonnet 4 for intelligent content analysis
- Properly escapes JSON using jq for robust handling of special characters
- Supports multiple comparison formats: tags, branches, commit hashes
- Cost: ~$0.003 per release (~$0.036/year for monthly releases)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
leex279
2025-10-09 20:37:15 +02:00
parent a580fdfe66
commit 130f9278d7
4 changed files with 1061 additions and 0 deletions

434
.github/RELEASE_NOTES_SETUP.md vendored Normal file
View File

@@ -0,0 +1,434 @@
# AI-Generated Release Notes Setup
This repository uses Claude AI to automatically generate comprehensive release notes when you create a new release.
## How It Works
The workflow triggers when:
- You push a new tag (e.g., `v1.0.0`)
- You create a GitHub release
- You manually trigger it via workflow dispatch
Claude AI analyzes:
- Commit messages since the last release
- Merged pull requests
- File changes by component (frontend, backend, docs)
- Contributors
Then generates structured release notes with:
- Overview and key changes
- Feature additions, improvements, and bug fixes
- Technical changes by component
- Statistics and contributor acknowledgments
- Breaking changes (important for beta!)
## Testing Locally First (Recommended!)
Before setting up the GitHub Action, you can test the release notes generation locally:
### Prerequisites
```bash
# Install required tools (if not already installed)
sudo apt install jq curl # Linux
# or
brew install jq curl # macOS
# Install GitHub CLI (optional, for PR detection)
# See: https://github.com/cli/cli#installation
```
### Run Local Test
```bash
# 1. Export your Anthropic API key
export ANTHROPIC_API_KEY="sk-ant-api03-..."
# 2. Run the test script
# Compare origin/stable vs main branches (default - shows unreleased changes)
./.github/test-release-notes.sh
# Or specify branches explicitly (automatically handles remote branches)
./.github/test-release-notes.sh stable main # Will use origin/stable if no local stable
./.github/test-release-notes.sh origin/stable main # Explicit remote branch
# Or use range syntax
./.github/test-release-notes.sh stable..main
# Or compare tags for a release
./.github/test-release-notes.sh v1.0.0 v2.0.0
# Or test a single tag (compares with previous tag)
./.github/test-release-notes.sh v0.1.0
```
### What the Local Test Does
1. **Gathers git data**: Commits, file changes, and PRs (if gh CLI available)
2. **Calls Claude API**: Generates release notes using the same prompt as the workflow
3. **Saves output**: Creates `release-notes-<tag>.md` in current directory
4. **Shows preview**: Displays the generated notes in your terminal
### Example Output
```bash
$ ./.github/test-release-notes.sh v0.2.0
🤖 Local Release Notes Generator Test
==========================================
Current tag: v0.2.0
Previous tag: v0.1.0
📝 Gathering commits...
Found 42 commits
📊 Analyzing file changes...
Files summary: 28 files changed, 1547 insertions(+), 423 deletions(-)
🔀 Looking for merged PRs...
Found 8 merged PRs
🤖 Generating release notes with Claude...
✅ Release notes generated successfully!
📄 Output saved to: release-notes-v0.2.0.md
==========================================
Preview:
==========================================
[Generated release notes appear here]
==========================================
✅ Done!
```
### Testing Different Scenarios
```bash
# 🔥 MOST COMMON: See what's new in main vs stable (unreleased changes)
./.github/test-release-notes.sh
# Output: release-notes-origin-stable..main.md
# Or with explicit arguments
./.github/test-release-notes.sh stable main
# Output: release-notes-origin-stable..main.md (auto-resolves to origin/stable)
# Test your first release (compares with initial commit)
./.github/test-release-notes.sh v0.1.0
# Test a release between two specific tags
./.github/test-release-notes.sh v1.0.0 v2.0.0
# Test what would be in next release (current branch vs stable)
git checkout main
./.github/test-release-notes.sh stable main
```
### Typical Workflow: Stable vs Main
For projects with separate `stable` (production) and `main` (development) branches:
```bash
# 1. See what's ready to release (compare branches)
export ANTHROPIC_API_KEY="sk-ant-..."
./.github/test-release-notes.sh stable main
# Or explicitly use remote branch: ./.github/test-release-notes.sh origin/stable main
# 2. Review the generated notes
cat release-notes-origin-stable..main.md
# 3. When ready to release, fetch latest and merge main to stable
git fetch origin
git checkout -b stable origin/stable # Create local tracking branch if needed
git merge main
git push origin stable
# 4. Create a release tag
git tag v1.0.0
git push origin v1.0.0
# 5. The GitHub Action will automatically generate release notes
# (You can also manually create the release with the generated notes)
gh release create v1.0.0 --title "Release v1.0.0" --notes-file release-notes-origin-stable..main.md
```
## Setup Instructions
### 1. Create Anthropic API Key
1. Go to [Anthropic Console](https://console.anthropic.com/)
2. Create a new API key
3. Copy the key (starts with `sk-ant-...`)
### 2. Add GitHub Secret
1. Go to your repository's Settings
2. Navigate to **Secrets and variables****Actions**
3. Click **New repository secret**
4. Name: `ANTHROPIC_API_KEY`
5. Value: Paste your Anthropic API key
6. Click **Add secret**
### 3. Test the Workflow
#### Option A: Create a Release via GitHub UI
1. Go to **Releases** in your repository
2. Click **Draft a new release**
3. Choose or create a tag (e.g., `v0.1.0`)
4. Click **Publish release**
5. The workflow will automatically run and update the release notes
#### Option B: Push a Tag via Git
```bash
# Create and push a new tag
git tag v0.1.0
git push origin v0.1.0
# The workflow will automatically create a release with AI-generated notes
```
#### Option C: Manual Trigger
1. Go to **Actions** tab
2. Select "AI-Generated Release Notes" workflow
3. Click **Run workflow**
4. Enter the tag name (e.g., `v0.1.0`)
5. Click **Run workflow**
## Usage Examples
### Creating Your First Release
```bash
# Tag your current state
git tag v0.1.0-beta
# Push the tag
git push origin v0.1.0-beta
# Check Actions tab - release notes will be generated automatically
```
### Creating Subsequent Releases
```bash
# Make your changes and commits
git add .
git commit -m "feat: Add AI-powered search feature"
git push
# When ready to release
git tag v0.2.0-beta
git push origin v0.2.0-beta
# Release notes will compare v0.2.0-beta with v0.1.0-beta
```
## What Gets Generated
The AI generates release notes in this structure:
```markdown
# 🚀 Release v0.2.0
## 📝 Overview
[Summary of the release]
## ✨ What's New
### Major Features
- [New features]
### Improvements
- [Enhancements]
### Bug Fixes
- [Fixes]
## 🔧 Technical Changes
### Backend (Python/FastAPI)
- [Backend changes]
### Frontend (React/TypeScript)
- [Frontend changes]
### Infrastructure
- [Infrastructure updates]
## 📊 Statistics
- Commits: X
- Pull Requests: Y
- Files Changed: Z
- Contributors: N
## 🙏 Contributors
[List of contributors]
## ⚠️ Breaking Changes
[Any breaking changes]
## 🔗 Links
- Full Changelog: [link]
```
## Customization
### Modify the Prompt
Edit `.github/workflows/release-notes.yml` and change the prompt in the "Generate release notes with Claude" step to adjust:
- Tone and style
- Structure
- Focus areas
- Level of detail
### Change Claude Model
In the workflow file, you can change the model:
```yaml
"model": "claude-sonnet-4-20250514" # Latest Sonnet
# or
"model": "claude-3-7-sonnet-20250219" # Sonnet 3.7
# or
"model": "claude-opus-4-20250514" # Opus 4 (more detailed)
```
### Adjust Token Limit
Increase `max_tokens` for longer release notes:
```yaml
"max_tokens": 4096 # Default
# or
"max_tokens": 8192 # For more detailed notes
```
## Troubleshooting
### Workflow Fails with "ANTHROPIC_API_KEY not found"
- Ensure you've added the secret in repository settings
- Secret name must be exactly `ANTHROPIC_API_KEY`
- Secret must be a valid Anthropic API key
### Empty or Incomplete Release Notes
- Check if commits exist between tags
- Verify git history is complete (workflow uses `fetch-depth: 0`)
- Check Actions logs for API errors
### API Rate Limits
- Anthropic has generous rate limits for API keys
- For very frequent releases, consider caching or batching
## Cost Estimation
Claude API pricing (as of 2025):
- Sonnet 4: ~$0.003 per release (assuming ~4K tokens)
- Each release generation costs less than a penny
For a project with monthly releases: ~$0.036/year
## Best Practices
### Write Good Commit Messages
The AI works better with clear commits:
```bash
# ✅ Good
git commit -m "feat: Add vector search with pgvector"
git commit -m "fix: Resolve race condition in crawling service"
git commit -m "docs: Update API documentation"
# ❌ Less helpful
git commit -m "updates"
git commit -m "fix stuff"
git commit -m "wip"
```
### Use Conventional Commits
The workflow benefits from conventional commit format:
- `feat:` - New features
- `fix:` - Bug fixes
- `docs:` - Documentation
- `refactor:` - Code refactoring
- `test:` - Tests
- `chore:` - Maintenance
### Tag Semantically
Use semantic versioning:
- `v1.0.0` - Major release
- `v1.1.0` - Minor release (new features)
- `v1.1.1` - Patch release (bug fixes)
- `v0.1.0-beta` - Beta releases
## Manual Editing
You can always edit the generated release notes:
1. Go to the release page
2. Click **Edit release**
3. Modify the notes as needed
4. Click **Update release**
## Workflow Outputs
The workflow provides:
- Updated GitHub release with AI-generated notes
- Artifact with release notes (kept for 90 days)
- Comments on related PRs linking to the release
- Summary in Actions tab
## Advanced: Using with Pre-releases
```bash
# Create a pre-release
git tag v0.2.0-rc.1
git push origin v0.2.0-rc.1
# Mark as pre-release in GitHub UI or via gh CLI
gh release create v0.2.0-rc.1 --prerelease
```
## Integration with Other Tools
### Notify Slack/Discord
Add a notification step after release creation:
```yaml
- name: Notify team
run: |
curl -X POST YOUR_WEBHOOK_URL \
-H 'Content-Type: application/json' \
-d '{"text":"Release ${{ steps.get_tag.outputs.tag }} published!"}'
```
### Update Changelog File
Append to CHANGELOG.md:
```yaml
- name: Update changelog
run: |
cat release_notes.md >> CHANGELOG.md
git add CHANGELOG.md
git commit -m "docs: Update changelog for ${{ steps.get_tag.outputs.tag }}"
git push
```
## Support
If you encounter issues:
1. Check the workflow logs in Actions tab
2. Verify your API key is valid
3. Ensure git history is available
4. Open an issue with workflow logs attached

310
.github/test-release-notes.sh vendored Normal file
View File

@@ -0,0 +1,310 @@
#!/bin/bash
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${BLUE}🤖 Local Release Notes Generator Test${NC}"
echo "=========================================="
echo
# Check for required tools
command -v jq >/dev/null 2>&1 || { echo -e "${RED}❌ jq is required but not installed. Install with: apt install jq${NC}" >&2; exit 1; }
command -v curl >/dev/null 2>&1 || { echo -e "${RED}❌ curl is required but not installed.${NC}" >&2; exit 1; }
# Check for API key
if [ -z "$ANTHROPIC_API_KEY" ]; then
echo -e "${RED}❌ ANTHROPIC_API_KEY environment variable not set${NC}"
echo -e "${YELLOW}Export your key first: export ANTHROPIC_API_KEY=sk-ant-...${NC}"
exit 1
fi
# Parse arguments for branch/tag comparison
if [ -n "$2" ]; then
# Two arguments: compare from $1 to $2
PREVIOUS_TAG="$1"
CURRENT_TAG="$2"
echo -e "${GREEN}Comparing: $PREVIOUS_TAG$CURRENT_TAG${NC}"
IS_FIRST_RELEASE="false"
elif [ -n "$1" ]; then
# One argument - could be a tag or a range
if [[ "$1" == *".."* ]]; then
# Range format: stable..main
PREVIOUS_TAG="${1%%..*}"
CURRENT_TAG="${1##*..}"
echo -e "${GREEN}Comparing range: $PREVIOUS_TAG$CURRENT_TAG${NC}"
else
# Single tag/branch - compare with previous tag or initial commit
CURRENT_TAG="$1"
PREVIOUS_TAG=$(git tag --sort=-v:refname | grep -A1 "^${CURRENT_TAG}$" | tail -1)
if [ -z "$PREVIOUS_TAG" ] || [ "$PREVIOUS_TAG" == "$CURRENT_TAG" ]; then
PREVIOUS_TAG=$(git rev-list --max-parents=0 HEAD)
IS_FIRST_RELEASE="true"
echo -e "${YELLOW}First release - comparing against initial commit${NC}"
else
IS_FIRST_RELEASE="false"
echo -e "${GREEN}Current: $CURRENT_TAG, Previous: $PREVIOUS_TAG${NC}"
fi
fi
IS_FIRST_RELEASE="false"
else
# No arguments - default to origin/stable..main comparison
PREVIOUS_TAG="origin/stable"
CURRENT_TAG="main"
echo -e "${BLUE}No arguments provided - comparing branches: origin/stable → main${NC}"
echo -e "${YELLOW}Usage:${NC}"
echo -e "${YELLOW} $0 # Compare origin/stable..main (default)${NC}"
echo -e "${YELLOW} $0 origin/stable main # Compare two branches${NC}"
echo -e "${YELLOW} $0 origin/stable..main # Range syntax${NC}"
echo -e "${YELLOW} $0 v1.0.0 # Compare with previous tag${NC}"
echo -e "${YELLOW} $0 v1.0.0 v2.0.0 # Compare two tags${NC}"
echo
IS_FIRST_RELEASE="false"
fi
# Normalize branch/tag references (handle remote branches)
# If a branch name doesn't exist locally, try origin/<branch>
normalize_ref() {
local ref="$1"
# If it already has origin/ prefix or is a commit hash, use as-is
if [[ "$ref" == origin/* ]] || git rev-parse -q --verify "$ref" >/dev/null 2>&1; then
echo "$ref"
return
fi
# Try local branch first
if git rev-parse -q --verify "$ref" >/dev/null 2>&1; then
echo "$ref"
return
fi
# Try as remote branch
if git rev-parse -q --verify "origin/$ref" >/dev/null 2>&1; then
echo "origin/$ref"
return
fi
# Return original if nothing works (will fail later with clear error)
echo "$ref"
}
PREVIOUS_TAG=$(normalize_ref "$PREVIOUS_TAG")
CURRENT_TAG=$(normalize_ref "$CURRENT_TAG")
echo -e "${GREEN}Comparing: ${PREVIOUS_TAG}${CURRENT_TAG}${NC}"
echo
# Get commit messages
echo -e "${BLUE}📝 Gathering commits...${NC}"
COMMITS=$(git log ${PREVIOUS_TAG}..${CURRENT_TAG} --pretty=format:"- %s (%h) by %an" --no-merges)
COMMIT_COUNT=$(echo "$COMMITS" | wc -l)
echo -e "${GREEN}Found $COMMIT_COUNT commits${NC}"
# Get file changes
echo -e "${BLUE}📊 Analyzing file changes...${NC}"
FILES_CHANGED=$(git diff ${PREVIOUS_TAG}..${CURRENT_TAG} --stat | tail -1)
# Detailed changes by component
CHANGES_FRONTEND=$(git diff ${PREVIOUS_TAG}..${CURRENT_TAG} --stat -- archon-ui-main/ | head -20)
CHANGES_BACKEND=$(git diff ${PREVIOUS_TAG}..${CURRENT_TAG} --stat -- python/ | head -20)
CHANGES_DOCS=$(git diff ${PREVIOUS_TAG}..${CURRENT_TAG} --stat -- '*.md' PRPs/ | head -10)
FILE_CHANGES="### File Changes by Component
**Frontend:**
$CHANGES_FRONTEND
**Backend:**
$CHANGES_BACKEND
**Documentation:**
$CHANGES_DOCS"
echo -e "${GREEN}Files summary: $FILES_CHANGED${NC}"
# Get merged PRs (using gh CLI if available)
echo -e "${BLUE}🔀 Looking for merged PRs...${NC}"
if command -v gh >/dev/null 2>&1; then
PREV_DATE=$(git log -1 --format=%ai ${PREVIOUS_TAG})
PRS=$(gh pr list \
--state merged \
--limit 100 \
--json number,title,mergedAt,author,url \
--jq --arg date "$PREV_DATE" \
'.[] | select(.mergedAt >= $date) | "- #\(.number): \(.title) by @\(.author.login) - \(.url)"' \
2>/dev/null || echo "No PRs found or unable to fetch")
PR_COUNT=$(echo "$PRS" | grep -c '^-' || echo "0")
echo -e "${GREEN}Found $PR_COUNT merged PRs${NC}"
else
PRS="No PRs fetched (gh CLI not available)"
echo -e "${YELLOW}gh CLI not available - skipping PR detection${NC}"
fi
echo
# Get repository info
REPO_FULL=$(git config --get remote.origin.url | sed 's/.*github.com[:/]\(.*\)\.git/\1/')
REPO_OWNER=$(echo "$REPO_FULL" | cut -d'/' -f1)
REPO_NAME=$(echo "$REPO_FULL" | cut -d'/' -f2)
# Create prompt for Claude
echo -e "${BLUE}🤖 Generating release notes with Claude...${NC}"
# Build the prompt content
PROMPT_CONTENT="You are writing release notes for Archon V2 Beta, a local-first AI knowledge management system.
## Release Information
**Version:** ${CURRENT_TAG}
**Previous Version:** ${PREVIOUS_TAG}
**Commits:** ${COMMIT_COUNT}
**Is First Release:** ${IS_FIRST_RELEASE}
## Commits
\`\`\`
${COMMITS}
\`\`\`
## Pull Requests Merged
\`\`\`
${PRS}
\`\`\`
## File Changes
\`\`\`
${FILE_CHANGES}
\`\`\`
## Instructions
Generate comprehensive release notes following this structure:
# 🚀 Release ${CURRENT_TAG}
## 📝 Overview
[2-3 sentence summary of this release]
## ✨ What's New
### Major Features
- [List major new features with brief descriptions]
### Improvements
- [List improvements and enhancements]
### Bug Fixes
- [List bug fixes]
## 🔧 Technical Changes
### Backend (Python/FastAPI)
- [Notable backend changes]
### Frontend (React/TypeScript)
- [Notable frontend changes]
### Infrastructure
- [Docker, CI/CD, deployment changes]
## 📊 Statistics
- **Commits:** ${COMMIT_COUNT}
- **Pull Requests:** [Count from PRs list]
- **Files Changed:** [From file stats]
- **Contributors:** [Unique authors from commits]
## 🙏 Contributors
Thanks to everyone who contributed to this release:
[List unique contributors with @ mentions]
## 📚 Documentation
[If documentation changes, list them]
## ⚠️ Breaking Changes
[List any breaking changes - this is beta software, so breaking changes are expected]
## 🔗 Links
- **Full Changelog:** https://github.com/${REPO_FULL}/compare/${PREVIOUS_TAG}...${CURRENT_TAG}
- **Installation Guide:** [Link to docs]
---
**Note:** This is a beta release. Features may change rapidly. Report issues at: https://github.com/${REPO_FULL}/issues
---
Write in a professional yet enthusiastic tone. Focus on user-facing changes. Be specific but concise."
# Create the request using jq to properly escape JSON
jq -n \
--arg model "claude-sonnet-4-20250514" \
--arg content "$PROMPT_CONTENT" \
'{
model: $model,
max_tokens: 4096,
temperature: 0.7,
messages: [
{
role: "user",
content: $content
}
]
}' > /tmp/claude_request.json
# Call Claude API
RESPONSE=$(curl -s https://api.anthropic.com/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d @/tmp/claude_request.json)
# Check for errors
if echo "$RESPONSE" | jq -e '.error' >/dev/null 2>&1; then
echo -e "${RED}❌ API Error:${NC}"
echo "$RESPONSE" | jq '.error'
exit 1
fi
# Extract release notes
RELEASE_NOTES=$(echo "$RESPONSE" | jq -r '.content[0].text')
if [ -z "$RELEASE_NOTES" ] || [ "$RELEASE_NOTES" == "null" ]; then
echo -e "${RED}❌ Failed to extract release notes from response${NC}"
echo "Response:"
echo "$RESPONSE" | jq .
exit 1
fi
# Save to file
# Create safe filename from branch/tag names
SAFE_FROM=$(echo "$PREVIOUS_TAG" | tr '/' '-')
SAFE_TO=$(echo "$CURRENT_TAG" | tr '/' '-')
OUTPUT_FILE="release-notes-${SAFE_FROM}..${SAFE_TO}.md"
echo "$RELEASE_NOTES" > "$OUTPUT_FILE"
echo -e "${GREEN}✅ Release notes generated successfully!${NC}"
echo
echo -e "${BLUE}📄 Output saved to: ${OUTPUT_FILE}${NC}"
echo
echo "=========================================="
echo -e "${YELLOW}Preview:${NC}"
echo "=========================================="
cat "$OUTPUT_FILE"
echo
echo "=========================================="
echo -e "${GREEN}✅ Done!${NC}"
echo
echo "To create a GitHub release with these notes:"
echo -e "${YELLOW}gh release create ${CURRENT_TAG} --title 'Release ${CURRENT_TAG}' --notes-file ${OUTPUT_FILE}${NC}"

314
.github/workflows/release-notes.yml vendored Normal file
View File

@@ -0,0 +1,314 @@
name: AI-Generated Release Notes
on:
release:
types: [created, published]
push:
tags:
- 'v*.*.*'
workflow_dispatch:
inputs:
tag_name:
description: 'Tag name (e.g., v1.0.0)'
required: true
type: string
permissions:
contents: write
pull-requests: read
jobs:
generate-release-notes:
name: Generate Release Notes with Claude
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full git history
- name: Get release tag
id: get_tag
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "tag=${{ inputs.tag_name }}" >> $GITHUB_OUTPUT
elif [ "${{ github.event_name }}" == "release" ]; then
echo "tag=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT
else
echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
fi
- name: Get previous tag
id: prev_tag
run: |
CURRENT_TAG="${{ steps.get_tag.outputs.tag }}"
PREVIOUS_TAG=$(git tag --sort=-v:refname | grep -A1 "^${CURRENT_TAG}$" | tail -1)
if [ -z "$PREVIOUS_TAG" ] || [ "$PREVIOUS_TAG" == "$CURRENT_TAG" ]; then
# First release or no previous tag - use initial commit
PREVIOUS_TAG=$(git rev-list --max-parents=0 HEAD)
echo "previous_tag=$PREVIOUS_TAG" >> $GITHUB_OUTPUT
echo "is_first_release=true" >> $GITHUB_OUTPUT
else
echo "previous_tag=$PREVIOUS_TAG" >> $GITHUB_OUTPUT
echo "is_first_release=false" >> $GITHUB_OUTPUT
fi
- name: Get commit messages
id: commits
run: |
CURRENT_TAG="${{ steps.get_tag.outputs.tag }}"
PREVIOUS_TAG="${{ steps.prev_tag.outputs.previous_tag }}"
# Get commit history
COMMITS=$(git log ${PREVIOUS_TAG}..${CURRENT_TAG} --pretty=format:"- %s (%h) by %an" --no-merges)
# Save to file to preserve formatting
echo "$COMMITS" > commits.txt
# Get stats
COMMIT_COUNT=$(echo "$COMMITS" | wc -l)
echo "commit_count=$COMMIT_COUNT" >> $GITHUB_OUTPUT
- name: Get changed files summary
id: files
run: |
CURRENT_TAG="${{ steps.get_tag.outputs.tag }}"
PREVIOUS_TAG="${{ steps.prev_tag.outputs.previous_tag }}"
# Get file change statistics
FILES_CHANGED=$(git diff ${PREVIOUS_TAG}..${CURRENT_TAG} --stat | tail -1)
echo "files_summary=$FILES_CHANGED" >> $GITHUB_OUTPUT
# Get detailed changes by component
echo "### File Changes by Component" > changes.txt
echo "" >> changes.txt
echo "**Frontend:**" >> changes.txt
git diff ${PREVIOUS_TAG}..${CURRENT_TAG} --stat -- archon-ui-main/ | head -20 >> changes.txt
echo "" >> changes.txt
echo "**Backend:**" >> changes.txt
git diff ${PREVIOUS_TAG}..${CURRENT_TAG} --stat -- python/ | head -20 >> changes.txt
echo "" >> changes.txt
echo "**Documentation:**" >> changes.txt
git diff ${PREVIOUS_TAG}..${CURRENT_TAG} --stat -- '*.md' PRPs/ | head -10 >> changes.txt
- name: Get closed PRs
id: prs
env:
GH_TOKEN: ${{ github.token }}
run: |
CURRENT_TAG="${{ steps.get_tag.outputs.tag }}"
PREVIOUS_TAG="${{ steps.prev_tag.outputs.previous_tag }}"
# Get date of previous tag
PREV_DATE=$(git log -1 --format=%ai ${PREVIOUS_TAG})
# Get merged PRs since previous tag
gh pr list \
--state merged \
--limit 100 \
--json number,title,mergedAt,author,url \
--jq --arg date "$PREV_DATE" \
'.[] | select(.mergedAt >= $date) | "- #\(.number): \(.title) by @\(.author.login) - \(.url)"' \
> prs.txt || echo "No PRs found" > prs.txt
- name: Generate release notes with Claude
id: claude
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
# Read all gathered information
COMMITS=$(cat commits.txt)
FILE_CHANGES=$(cat changes.txt)
PRS=$(cat prs.txt)
# Build the prompt content
PROMPT_CONTENT="You are writing release notes for Archon V2 Beta, a local-first AI knowledge management system.
## Release Information
**Version:** ${{ steps.get_tag.outputs.tag }}
**Previous Version:** ${{ steps.prev_tag.outputs.previous_tag }}
**Commits:** ${{ steps.commits.outputs.commit_count }}
**Is First Release:** ${{ steps.prev_tag.outputs.is_first_release }}
## Commits
\`\`\`
${COMMITS}
\`\`\`
## Pull Requests Merged
\`\`\`
${PRS}
\`\`\`
## File Changes
\`\`\`
${FILE_CHANGES}
\`\`\`
## Instructions
Generate comprehensive release notes following this structure:
# 🚀 Release ${{ steps.get_tag.outputs.tag }}
## 📝 Overview
[2-3 sentence summary of this release]
## ✨ What's New
### Major Features
- [List major new features with brief descriptions]
### Improvements
- [List improvements and enhancements]
### Bug Fixes
- [List bug fixes]
## 🔧 Technical Changes
### Backend (Python/FastAPI)
- [Notable backend changes]
### Frontend (React/TypeScript)
- [Notable frontend changes]
### Infrastructure
- [Docker, CI/CD, deployment changes]
## 📊 Statistics
- **Commits:** ${{ steps.commits.outputs.commit_count }}
- **Pull Requests:** [Count from PRs list]
- **Files Changed:** [From file stats]
- **Contributors:** [Unique authors from commits]
## 🙏 Contributors
Thanks to everyone who contributed to this release:
[List unique contributors with @ mentions]
## 📚 Documentation
[If documentation changes, list them]
## ⚠️ Breaking Changes
[List any breaking changes - this is beta software, so breaking changes are expected]
## 🔗 Links
- **Full Changelog:** https://github.com/${{ github.repository }}/compare/${{ steps.prev_tag.outputs.previous_tag }}...${{ steps.get_tag.outputs.tag }}
- **Installation Guide:** [Link to docs]
---
**Note:** This is a beta release. Features may change rapidly. Report issues at: https://github.com/${{ github.repository }}/issues
---
Write in a professional yet enthusiastic tone. Focus on user-facing changes. Be specific but concise."
# Create the request using jq to properly escape JSON
jq -n \
--arg model "claude-sonnet-4-20250514" \
--arg content "$PROMPT_CONTENT" \
'{
model: $model,
max_tokens: 4096,
temperature: 0.7,
messages: [
{
role: "user",
content: $content
}
]
}' > request.json
# Call Claude API
RESPONSE=$(curl -s https://api.anthropic.com/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d @request.json)
# Extract release notes from response
echo "$RESPONSE" | jq -r '.content[0].text' > release_notes.md
# Check if generation was successful
if [ ! -s release_notes.md ] || grep -q "error" release_notes.md; then
echo "❌ Failed to generate release notes"
echo "Response: $RESPONSE"
exit 1
fi
echo "✅ Release notes generated successfully"
- name: Create or update GitHub release
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="${{ steps.get_tag.outputs.tag }}"
NOTES=$(cat release_notes.md)
# Check if release already exists
if gh release view "$TAG" &>/dev/null; then
echo "Release $TAG exists, updating notes..."
gh release edit "$TAG" --notes-file release_notes.md
else
echo "Creating new release $TAG..."
gh release create "$TAG" \
--title "Release $TAG" \
--notes-file release_notes.md \
--draft=false \
--latest
fi
- name: Upload release notes as artifact
uses: actions/upload-artifact@v4
with:
name: release-notes-${{ steps.get_tag.outputs.tag }}
path: release_notes.md
retention-days: 90
- name: Comment on related PRs
if: steps.prev_tag.outputs.is_first_release == 'false'
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="${{ steps.get_tag.outputs.tag }}"
# Get PR numbers from commits in this release
CURRENT_TAG="${{ steps.get_tag.outputs.tag }}"
PREVIOUS_TAG="${{ steps.prev_tag.outputs.previous_tag }}"
# Extract PR numbers from commit messages
PR_NUMBERS=$(git log ${PREVIOUS_TAG}..${CURRENT_TAG} --oneline |
grep -oP '#\K\d+' || true)
if [ -n "$PR_NUMBERS" ]; then
for PR in $PR_NUMBERS; do
echo "Adding release comment to PR #$PR"
gh pr comment "$PR" \
--body "🎉 This pull request has been included in release [$TAG](https://github.com/${{ github.repository }}/releases/tag/$TAG)!" \
|| echo "Could not comment on PR #$PR (might be closed)"
done
fi
- name: Create summary
if: always()
run: |
echo "# 🎉 Release Notes Generation Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Release Tag:** ${{ steps.get_tag.outputs.tag }}" >> $GITHUB_STEP_SUMMARY
echo "**Previous Tag:** ${{ steps.prev_tag.outputs.previous_tag }}" >> $GITHUB_STEP_SUMMARY
echo "**Commits:** ${{ steps.commits.outputs.commit_count }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "## Generated Release Notes" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
cat release_notes.md >> $GITHUB_STEP_SUMMARY

3
.gitignore vendored
View File

@@ -12,3 +12,6 @@ temp/
UAT/
.DS_Store
# Local release notes testing
release-notes-*.md