From 130f9278d79c35f5eb99781fb5ea2578587b0c37 Mon Sep 17 00:00:00 2001 From: leex279 Date: Thu, 9 Oct 2025 20:37:15 +0200 Subject: [PATCH] feat: Add AI-powered release notes generator with Claude MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .github/RELEASE_NOTES_SETUP.md | 434 ++++++++++++++++++++++++++++ .github/test-release-notes.sh | 310 ++++++++++++++++++++ .github/workflows/release-notes.yml | 314 ++++++++++++++++++++ .gitignore | 3 + 4 files changed, 1061 insertions(+) create mode 100644 .github/RELEASE_NOTES_SETUP.md create mode 100644 .github/test-release-notes.sh create mode 100644 .github/workflows/release-notes.yml diff --git a/.github/RELEASE_NOTES_SETUP.md b/.github/RELEASE_NOTES_SETUP.md new file mode 100644 index 00000000..02093381 --- /dev/null +++ b/.github/RELEASE_NOTES_SETUP.md @@ -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-.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 diff --git a/.github/test-release-notes.sh b/.github/test-release-notes.sh new file mode 100644 index 00000000..cdccd8c5 --- /dev/null +++ b/.github/test-release-notes.sh @@ -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/ +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}" diff --git a/.github/workflows/release-notes.yml b/.github/workflows/release-notes.yml new file mode 100644 index 00000000..5b89afc5 --- /dev/null +++ b/.github/workflows/release-notes.yml @@ -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 diff --git a/.gitignore b/.gitignore index 96c7a645..51227923 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ temp/ UAT/ .DS_Store + +# Local release notes testing +release-notes-*.md