From 017423336f2fd2619854b4f1582bcf165f6a07ad Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Fri, 12 Dec 2025 08:45:10 -0300 Subject: [PATCH 01/46] feat: add GPTChangelog reusable workflow - Add gptchangelog.yml reusable workflow with consolidated changelog support - Add comprehensive documentation in docs/gptchangelog-workflow.md - Update README.md with new workflow entry Features: - AI-powered changelog generation using OpenAI GPT-4o - Consolidated changelog: single CHANGELOG.md with sections per app - Monorepo support via filter_paths and path_level - GitHub Release notes integration per app tag - GPG-signed commits and PRs - Slack notification integration - Blacksmith runner as default --- .github/workflows/gptchangelog.yml | 429 +++++++++++++++++++++++++++++ README.md | 5 + docs/gptchangelog-workflow.md | 378 +++++++++++++++++++++++++ 3 files changed, 812 insertions(+) create mode 100644 .github/workflows/gptchangelog.yml create mode 100644 docs/gptchangelog-workflow.md diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml new file mode 100644 index 0000000..ff4264c --- /dev/null +++ b/.github/workflows/gptchangelog.yml @@ -0,0 +1,429 @@ +name: "GPT Changelog" + +# This reusable workflow generates a consolidated CHANGELOG.md and RELEASE_NOTES.md using GPTChangelog +# It uses OpenAI GPT-4o to analyze commits and generate human-readable changelogs +# +# Monorepo Support: +# - If filter_paths is provided: detects changes and generates changelog sections for each changed app +# - If filter_paths is empty: generates changelog for the entire repository (single app mode) +# - All apps are consolidated into ONE CHANGELOG.md file (no overwrites) +# +# Features: +# - Generates consolidated CHANGELOG.md with sections per app +# - Generates RELEASE_NOTES.md and updates GitHub Release +# - Creates PR with changelog update using GPG-signed commits +# - Handles tag-based versioning (between tags, first tag, no tags scenarios) + +on: + workflow_call: + inputs: + runner_type: + description: 'Runner to use for the workflow' + type: string + default: 'blacksmith' + filter_paths: + description: 'Newline-separated list of path prefixes to filter. If not provided, treats as single app repo.' + type: string + required: false + default: '' + path_level: + description: 'Limits the path to the first N segments (e.g., 2 -> "charts/agent")' + type: string + default: '2' + openai_model: + description: 'OpenAI model to use for changelog generation' + type: string + default: 'gpt-4o' + max_context_tokens: + description: 'Maximum context tokens for OpenAI API' + type: string + default: '80000' + python_version: + description: 'Python version to use' + type: string + default: '3.10' + +permissions: + contents: write + pull-requests: write + +jobs: + prepare: + runs-on: ${{ inputs.runner_type }} + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + has_changes: ${{ steps.set-matrix.outputs.has_changes }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get changed paths (monorepo) + if: inputs.filter_paths != '' + id: changed-paths + uses: LerianStudio/github-actions-changed-paths@main + with: + filter_paths: ${{ inputs.filter_paths }} + path_level: ${{ inputs.path_level }} + get_app_name: 'true' + + - name: Set matrix + id: set-matrix + run: | + if [ -z "${{ inputs.filter_paths }}" ]; then + # Single app mode - generate changelog from root + APP_NAME="${{ github.event.repository.name }}" + echo "matrix=[{\"name\": \"${APP_NAME}\", \"working_dir\": \".\"}]" >> $GITHUB_OUTPUT + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "๐Ÿ“ฆ Single app mode: ${APP_NAME}" + else + MATRIX='${{ steps.changed-paths.outputs.matrix }}' + if [ "$MATRIX" == "[]" ] || [ -z "$MATRIX" ]; then + echo "matrix=[]" >> $GITHUB_OUTPUT + echo "has_changes=false" >> $GITHUB_OUTPUT + echo "โš ๏ธ No changes detected in filter_paths" + else + echo "matrix=$MATRIX" >> $GITHUB_OUTPUT + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "๐Ÿ“ฆ Monorepo mode - detected changes: $MATRIX" + fi + fi + + generate_changelog: + needs: prepare + if: needs.prepare.outputs.has_changes == 'true' + runs-on: ${{ inputs.runner_type }} + name: Generate Consolidated Changelog + + steps: + - name: Create GitHub App Token + uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ secrets.LERIAN_STUDIO_MIDAZ_PUSH_BOT_APP_ID }} + private-key: ${{ secrets.LERIAN_STUDIO_MIDAZ_PUSH_BOT_PRIVATE_KEY }} + + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} + + - name: Sync with remote branch + run: | + git fetch origin ${{ github.ref_name }} + git reset --hard origin/${{ github.ref_name }} + + - name: Import GPG key + uses: crazy-max/ghaction-import-gpg@v6 + id: import_gpg + with: + gpg_private_key: ${{ secrets.LERIAN_CI_CD_USER_GPG_KEY }} + passphrase: ${{ secrets.LERIAN_CI_CD_USER_GPG_KEY_PASSWORD }} + git_committer_name: ${{ secrets.LERIAN_CI_CD_USER_NAME }} + git_committer_email: ${{ secrets.LERIAN_CI_CD_USER_EMAIL }} + git_config_global: true + git_user_signingkey: true + git_commit_gpgsign: true + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: ${{ inputs.python_version }} + + - name: Install gptchangelog + run: | + python -m pip install --upgrade pip + pip install gptchangelog + echo "โœ… gptchangelog installed successfully" + + - name: Create gptchangelog config + run: | + mkdir -p .gptchangelog + cat > .gptchangelog/config.ini << EOF + [gptchangelog] + openai = true + + [openai] + api_key = ${OPENAI_API_KEY} + model = ${{ inputs.openai_model }} + max_context_tokens = ${{ inputs.max_context_tokens }} + EOF + echo "โœ… gptchangelog config created" + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + + - name: Create changelog prompt template + run: | + mkdir -p .gptchangelog/templates + cat > .gptchangelog/templates/changelog_prompt.txt << 'EOF' + ## Release $next_version (Released on $current_date) + + ### What's New + $commit_messages + + ### โœจ Features + - Highlight new features added in this release. + + ### ๐Ÿ›  Fixes + - List bug fixes and improvements. + + ### ๐Ÿ“š Documentation + - Summarize updates to documentation. + + ### ๐Ÿš€ Improvements + - Highlight performance or backend optimizations. + + ### โš ๏ธ Breaking Changes + - List any breaking changes here. + + ### ๐Ÿ™Œ Contributors + - Acknowledge contributors for this release. + EOF + echo "โœ… Changelog prompt template created" + + - name: Generate consolidated changelog for all apps + id: generate + run: | + echo "OPENAI_API_KEY=${OPENAI_API_KEY}" > .env + git fetch --tags + + MATRIX='${{ needs.prepare.outputs.matrix }}' + CURRENT_DATE=$(date +%Y-%m-%d) + + echo "๐Ÿ“ฆ Processing apps from matrix: $MATRIX" + + # Initialize files + > /tmp/consolidated_changelog.md + > /tmp/consolidated_release_notes.md + > /tmp/apps_updated.txt + + # Parse the matrix JSON and iterate through each app + echo "$MATRIX" | jq -c '.[]' | while read -r APP; do + APP_NAME=$(echo "$APP" | jq -r '.name') + WORKING_DIR=$(echo "$APP" | jq -r '.working_dir') + + echo "" + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + echo "๐Ÿ“ Processing: $APP_NAME (dir: $WORKING_DIR)" + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + + # Determine tag pattern based on app type + if [ "$WORKING_DIR" != "." ]; then + TAG_PATTERN="${APP_NAME}-v*" + else + TAG_PATTERN="v*" + fi + + echo "๐Ÿ” Looking for tags matching: $TAG_PATTERN" + + # Determine version range + if git describe --tags --abbrev=0 --match "$TAG_PATTERN" HEAD >/dev/null 2>&1; then + LAST_TAG=$(git describe --tags --abbrev=0 --match "$TAG_PATTERN" HEAD) + + if git describe --tags --abbrev=0 --match "$TAG_PATTERN" HEAD^ >/dev/null 2>&1; then + PENULT_TAG=$(git describe --tags --abbrev=0 --match "$TAG_PATTERN" HEAD^) + SINCE="$PENULT_TAG" + TO="$LAST_TAG" + echo "๐ŸŸข Range: $PENULT_TAG โ†’ $LAST_TAG" + else + FIRST_COMMIT=$(git rev-list --max-parents=0 HEAD) + SINCE="$FIRST_COMMIT" + TO="$LAST_TAG" + echo "๐ŸŸก First tag - Range: $FIRST_COMMIT โ†’ $LAST_TAG" + fi + else + FIRST_COMMIT=$(git rev-list --max-parents=0 HEAD) + SINCE="$FIRST_COMMIT" + TO="HEAD" + LAST_TAG="HEAD" + echo "๐Ÿ”ด No tags - Range: $FIRST_COMMIT โ†’ HEAD" + fi + + # Extract version from tag + VERSION=$(echo "$LAST_TAG" | sed 's/.*-v/v/' | sed 's/^v//') + + # Generate changelog for this app + TEMP_CHANGELOG=$(mktemp) + + pushd "$WORKING_DIR" >/dev/null 2>&1 || pushd . >/dev/null + + if gptchangelog generate --since "$SINCE" --to "$TO" --output "$TEMP_CHANGELOG"; then + # Clean up markdown blocks + sed -i '' '/^```/d' "$TEMP_CHANGELOG" 2>/dev/null || sed -i '/^```/d' "$TEMP_CHANGELOG" + + # Extract content (skip the header line if present) + CONTENT=$(cat "$TEMP_CHANGELOG" | tail -n +2) + + # Build app section for consolidated changelog + echo "### ${APP_NAME} v${VERSION}" >> /tmp/consolidated_changelog.md + echo "" >> /tmp/consolidated_changelog.md + echo "$CONTENT" >> /tmp/consolidated_changelog.md + echo "" >> /tmp/consolidated_changelog.md + + # Also add to release notes + echo "### ${APP_NAME} v${VERSION}" >> /tmp/consolidated_release_notes.md + echo "" >> /tmp/consolidated_release_notes.md + echo "$CONTENT" >> /tmp/consolidated_release_notes.md + echo "" >> /tmp/consolidated_release_notes.md + + # Track which apps were updated + echo "${APP_NAME}:v${VERSION}" >> /tmp/apps_updated.txt + + # Update GitHub Release for this app's tag (if not HEAD) + if [ "$LAST_TAG" != "HEAD" ]; then + echo "### ${APP_NAME} v${VERSION}" > /tmp/app_release_notes.md + echo "" >> /tmp/app_release_notes.md + echo "$CONTENT" >> /tmp/app_release_notes.md + gh release edit "$LAST_TAG" --notes-file /tmp/app_release_notes.md || \ + echo "โš ๏ธ Could not update release for $LAST_TAG" + fi + + echo "โœ… Processed $APP_NAME" + else + echo "โš ๏ธ Failed to generate changelog for $APP_NAME" + fi + + popd >/dev/null 2>&1 || true + rm -f "$TEMP_CHANGELOG" + done + + # Create final consolidated CHANGELOG.md + if [ -s /tmp/consolidated_changelog.md ]; then + APPS_LIST=$(cat /tmp/apps_updated.txt 2>/dev/null | tr '\n' ', ' | sed 's/,$//') + + # Prepare the new changelog entry + cat > /tmp/new_entry.md << ENTRY_EOF + ## [$CURRENT_DATE] + + $(cat /tmp/consolidated_changelog.md) + --- + + ENTRY_EOF + + # Prepend to existing CHANGELOG.md or create new one + if [ -f CHANGELOG.md ]; then + # Insert after the title line + if grep -q "^# " CHANGELOG.md; then + TITLE=$(head -n 1 CHANGELOG.md) + EXISTING=$(tail -n +2 CHANGELOG.md) + echo "$TITLE" > CHANGELOG.md + echo "" >> CHANGELOG.md + cat /tmp/new_entry.md >> CHANGELOG.md + echo "$EXISTING" >> CHANGELOG.md + else + cat /tmp/new_entry.md > /tmp/new_changelog.md + cat CHANGELOG.md >> /tmp/new_changelog.md + mv /tmp/new_changelog.md CHANGELOG.md + fi + else + echo "# Changelog" > CHANGELOG.md + echo "" >> CHANGELOG.md + cat /tmp/new_entry.md >> CHANGELOG.md + fi + + # Create RELEASE_NOTES.md + echo "# Release Notes - $CURRENT_DATE" > RELEASE_NOTES.md + echo "" >> RELEASE_NOTES.md + cat /tmp/consolidated_release_notes.md >> RELEASE_NOTES.md + + echo "apps_updated=$APPS_LIST" >> $GITHUB_OUTPUT + echo "โœ… Consolidated changelog created with apps: $APPS_LIST" + else + echo "โš ๏ธ No changelog content generated" + echo "apps_updated=" >> $GITHUB_OUTPUT + fi + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} + + - name: Show generated changelog + run: | + echo "๐Ÿ“„ Generated CHANGELOG.md:" + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + head -100 CHANGELOG.md + echo "" + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + + - name: Create changelog PR + if: steps.generate.outputs.apps_updated != '' + run: | + BASE_BRANCH="${GITHUB_REF##*/}" + CURRENT_DATE=$(date +%Y-%m-%d) + BRANCH_NAME="release/update-changelog-${CURRENT_DATE}" + APPS_UPDATED="${{ steps.generate.outputs.apps_updated }}" + + echo "๐Ÿ“Œ Creating branch: $BRANCH_NAME" + git checkout -b "$BRANCH_NAME" + + # Add and commit CHANGELOG + git add CHANGELOG.md + if ! git diff --cached --quiet; then + git commit -S -m "chore(release): Update CHANGELOG for ${APPS_UPDATED}" + echo "โœ… CHANGELOG committed" + else + echo "โš ๏ธ No changes to CHANGELOG" + exit 0 + fi + + # Merge base branch to resolve conflicts + git fetch origin "$BASE_BRANCH" + git merge -X ours origin/"$BASE_BRANCH" --no-ff -m "Merge $BASE_BRANCH into ${BRANCH_NAME}" || { + git checkout --ours CHANGELOG.md + git add CHANGELOG.md + git commit -m "resolve conflict using ours strategy" + } + + # Push and create PR + git push --force-with-lease origin "$BRANCH_NAME" + + if ! gh pr view "$BRANCH_NAME" --base "$BASE_BRANCH" > /dev/null 2>&1; then + gh pr create \ + --title "chore(release): Update CHANGELOG - ${CURRENT_DATE}" \ + --body "## Automatic Changelog Update + + **Date:** ${CURRENT_DATE} + **Apps Updated:** ${APPS_UPDATED} + + ### Changes + - Updated CHANGELOG.md with consolidated release notes + - Each app section generated by GPTChangelog using OpenAI GPT-4o + + ### Apps Included + $(echo "$APPS_UPDATED" | tr ',' '\n' | sed 's/^/- /') + + --- + *This PR was automatically generated by the GPTChangelog workflow.*" \ + --base "$BASE_BRANCH" \ + --head "$BRANCH_NAME" + echo "โœ… PR created" + else + echo "โš ๏ธ PR already exists" + fi + + # Auto-merge if possible + gh pr merge --merge --delete-branch || echo "โš ๏ธ Could not auto-merge PR" + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + + - name: Cleanup sensitive files + if: always() + run: | + rm -f .env + rm -rf .gptchangelog + rm -f /tmp/consolidated_changelog.md /tmp/consolidated_release_notes.md /tmp/apps_updated.txt /tmp/app_release_notes.md /tmp/new_entry.md + echo "๐Ÿงน Cleaned up sensitive files" + + # Slack notification + notify: + name: Notify + needs: [prepare, generate_changelog] + if: always() && needs.prepare.outputs.has_changes == 'true' + uses: ./.github/workflows/slack-notify.yml + with: + status: ${{ needs.generate_changelog.result }} + workflow_name: "GPT Changelog" + failed_jobs: ${{ needs.generate_changelog.result == 'failure' && 'Generate Changelog' || '' }} + secrets: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/README.md b/README.md index b33c7f1..d2c2b57 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,11 @@ Send Slack notifications from workflows with rich formatting and status-based co **Key Features**: Rich formatting, status colors, graceful degradation, PR support +### 13. [GPT Changelog](docs/gptchangelog-workflow.md) +AI-powered changelog generation using GPTChangelog and OpenAI GPT-4o with consolidated output. + +**Key Features**: AI commit analysis, consolidated changelog, monorepo support, GitHub Release integration, GPG signing + ## Documentation **[Complete Documentation โ†’](docs/README.md)** diff --git a/docs/gptchangelog-workflow.md b/docs/gptchangelog-workflow.md new file mode 100644 index 0000000..3dc21fa --- /dev/null +++ b/docs/gptchangelog-workflow.md @@ -0,0 +1,378 @@ +# GPT Changelog Workflow + +Reusable workflow for generating a consolidated CHANGELOG.md and RELEASE_NOTES.md using GPTChangelog. Uses OpenAI GPT-4o to analyze commits and generate human-readable, categorized changelogs. + +## Features + +- **AI-powered changelog generation**: Uses OpenAI GPT-4o for intelligent commit analysis +- **Consolidated changelog**: Single CHANGELOG.md with sections per app (no overwrites) +- **Monorepo support**: Automatic detection of changed components via filter_paths +- **GitHub Release integration**: Automatically updates release notes per app tag +- **GPG signing**: Signed commits for changelog PRs +- **Tag-based versioning**: Handles between-tags, first-tag, and no-tags scenarios +- **Automatic PR creation**: Creates and optionally auto-merges changelog PRs +- **Slack notifications**: Automatic success/failure notifications + +## Usage + +### Single App Repository + +```yaml +name: Generate Changelog +on: + push: + tags: + - 'v*' + +permissions: + contents: write + pull-requests: write + +jobs: + changelog: + uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main + with: + runner_type: "blacksmith" + secrets: inherit +``` + +**Output:** +```markdown +# Changelog + +## [2025-12-12] + +### my-app v1.2.0 + +#### โœจ Features +- Added new authentication flow +- Implemented caching layer + +#### ๐Ÿ›  Fixes +- Fixed memory leak in worker process + +--- +``` + +### Monorepo with Multiple Components + +Works with any directory structure (Helm charts, microservices, packages, etc.): + +```yaml +name: Generate Changelog +on: + push: + tags: + - '**-v*' # Matches: agent-v1.0.0, midaz-v2.1.0, etc. + +permissions: + contents: write + pull-requests: write + +jobs: + changelog: + uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main + with: + runner_type: "blacksmith" + filter_paths: |- + charts/agent + charts/control-plane + charts/midaz + charts/reporter + path_level: '2' + secrets: inherit +``` + +**Output (when multiple apps change):** +```markdown +# Changelog + +## [2025-12-12] + +### agent v1.2.0 + +#### โœจ Features +- Added new metric collection endpoint + +#### ๐Ÿ›  Fixes +- Fixed reconnection logic + +### midaz v2.1.0 + +#### โœจ Features +- New transaction batching API + +#### ๐Ÿš€ Improvements +- Optimized database queries + +### control-plane v1.5.0 + +#### ๐Ÿ›  Fixes +- Fixed race condition in scheduler + +--- +``` + +**Key Benefit:** All apps are consolidated into ONE CHANGELOG.md - no more overwrites when multiple apps change! + +### After Release Workflow + +```yaml +name: Release Pipeline +on: + push: + branches: + - main + +jobs: + release: + uses: LerianStudio/github-actions-shared-workflows/.github/workflows/release.yml@main + secrets: inherit + + changelog: + needs: release + uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main + with: + runner_type: "blacksmith" + secrets: inherit +``` + +## Inputs + +| Input | Type | Default | Description | +|-------|------|---------|-------------| +| `runner_type` | string | `blacksmith` | GitHub runner type | +| `filter_paths` | string | `''` | Newline-separated list of path prefixes. If empty, single-app mode | +| `path_level` | string | `2` | Directory depth for app name extraction | +| `openai_model` | string | `gpt-4o` | OpenAI model for changelog generation | +| `max_context_tokens` | string | `80000` | Maximum context tokens for OpenAI API | +| `python_version` | string | `3.10` | Python version to use | + +## Secrets + +All secrets are inherited via `secrets: inherit`. Required secrets in your repository: + +| Secret | Description | +|--------|-------------| +| `OPENAI_API_KEY` | OpenAI API key for GPT-4o access | +| `LERIAN_STUDIO_MIDAZ_PUSH_BOT_APP_ID` | GitHub App ID for authentication | +| `LERIAN_STUDIO_MIDAZ_PUSH_BOT_PRIVATE_KEY` | GitHub App private key | +| `LERIAN_CI_CD_USER_GPG_KEY` | GPG private key for signing commits | +| `LERIAN_CI_CD_USER_GPG_KEY_PASSWORD` | GPG key passphrase | +| `LERIAN_CI_CD_USER_NAME` | Git committer name | +| `LERIAN_CI_CD_USER_EMAIL` | Git committer email | +| `SLACK_WEBHOOK_URL` | *(Optional)* Slack webhook for notifications | + +## How It Works + +### Consolidated Changelog Architecture + +Unlike traditional matrix-based approaches where each app generates its own changelog (causing overwrites), this workflow uses a **single-job consolidated approach**: + +1. **Detect all changed apps** via `changed-paths` action +2. **Single job iterates** through all changed apps +3. **Accumulates changelog entries** per app into one consolidated file +4. **Creates one PR** with all changes + +**Result:** One CHANGELOG.md at repo root with sections for each app that changed. + +### Version Range Detection + +The workflow automatically determines the commit range for changelog generation: + +| Scenario | Range | Example | +|----------|-------|---------| +| Two or more tags | Previous tag โ†’ Current tag | `v1.0.0...v1.1.0` | +| First tag | First commit โ†’ Current tag | `abc123...v1.0.0` | +| No tags | First commit โ†’ HEAD | `abc123...HEAD` | + +### Monorepo Tag Patterns + +For monorepos, the workflow supports app-specific tags: + +| App | Tag Pattern | Example | +|-----|-------------|---------| +| agent | `agent-v*` | `agent-v1.0.0` | +| control-plane | `control-plane-v*` | `control-plane-v2.1.0` | + +This works with **any directory structure**: +- `apps/api`, `apps/worker` โ†’ tags: `api-v1.0.0`, `worker-v2.0.0` +- `services/auth`, `services/billing` โ†’ tags: `auth-v1.0.0`, `billing-v1.5.0` +- `charts/midaz`, `charts/agent` โ†’ tags: `midaz-v1.0.0`, `agent-v2.0.0` + +### Changelog Categories + +GPTChangelog organizes commits into these categories: +- โœจ **Features**: New features added +- ๐Ÿ›  **Fixes**: Bug fixes and improvements +- ๐Ÿ“š **Documentation**: Documentation updates +- ๐Ÿš€ **Improvements**: Performance or backend optimizations +- โš ๏ธ **Breaking Changes**: Breaking changes +- ๐Ÿ™Œ **Contributors**: Acknowledgments + +## Workflow Jobs + +### prepare +- Detects changed paths (monorepo) or sets single-app mode +- Outputs matrix for changelog generation job + +### generate_changelog +- Installs gptchangelog and dependencies +- Iterates through all changed apps in a single job +- Generates consolidated CHANGELOG.md with sections per app +- Updates GitHub Release for each app's tag +- Creates PR with changelog update (GPG-signed) +- Auto-merges PR if possible + +### notify +- Sends Slack notification on completion +- Skipped if `SLACK_WEBHOOK_URL` not configured + +## Best Practices + +### 1. Trigger After Release + +Run changelog generation after the release workflow: + +```yaml +changelog: + needs: release + uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main + secrets: inherit +``` + +### 2. Use Conventional Commits + +GPTChangelog works best with conventional commits: +- `feat:` - New features +- `fix:` - Bug fixes +- `docs:` - Documentation +- `perf:` - Performance improvements + +### 3. Configure Slack Notifications + +Add `SLACK_WEBHOOK_URL` secret for team notifications. + +## Troubleshooting + +### No changelog generated + +**Issue**: Workflow runs but no CHANGELOG.md is created + +**Solutions**: +1. Check OpenAI API key is valid +2. Verify tag format matches expected pattern +3. Check if there are commits in the version range +4. Review workflow logs for gptchangelog errors + +### Version header not updated + +**Issue**: CHANGELOG shows wrong version + +**Solutions**: +1. Verify tag format (should include version number) +2. Check sed command output in logs +3. Ensure CHANGELOG has standard version header format + +### PR not created + +**Issue**: Changelog generated but PR fails + +**Solutions**: +1. Verify GitHub App has `contents: write` and `pull-requests: write` permissions +2. Check if branch already exists +3. Review PR creation step logs + +### OpenAI API errors + +**Issue**: gptchangelog fails with API errors + +**Solutions**: +1. Verify `OPENAI_API_KEY` is set correctly +2. Check API rate limits +3. Try reducing `max_context_tokens` +4. Ensure model name is valid (`gpt-4o`) + +### Monorepo changes not detected + +**Issue**: No apps in matrix for monorepo + +**Solutions**: +1. Verify `filter_paths` matches your directory structure +2. Check `path_level` is correct +3. Ensure changes are in tracked paths +4. Review changed-paths action output + +## Examples + +### Basic Single App + +```yaml +name: Changelog +on: + push: + tags: ['v*'] + +jobs: + changelog: + uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main + secrets: inherit +``` + +### Helm Charts Monorepo + +```yaml +name: Changelog +on: + push: + tags: ['**-v*'] + +jobs: + changelog: + uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main + with: + filter_paths: |- + charts/agent + charts/control-plane + charts/midaz + charts/reporter + path_level: '2' + secrets: inherit +``` + +### Microservices Monorepo + +```yaml +name: Changelog +on: + push: + tags: ['**-v*'] + +jobs: + changelog: + uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main + with: + filter_paths: |- + services/api + services/worker + services/scheduler + path_level: '2' + secrets: inherit +``` + +### Custom OpenAI Model + +```yaml +changelog: + uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main + with: + openai_model: 'gpt-4-turbo' + max_context_tokens: '128000' + secrets: inherit +``` + +## Related Workflows + +- [Release](release-workflow.md) - Create releases that trigger changelog generation +- [Build](build-workflow.md) - Build Docker images after release +- [Slack Notify](slack-notify-workflow.md) - Notification system From 284a1c5868fcfebf8d6411adbba8442892da475f Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Fri, 12 Dec 2025 12:27:42 -0300 Subject: [PATCH 02/46] fix: handle tag triggers in sync step The workflow was failing when triggered by tag pushes because it tried to do 'git reset --hard origin/' which doesn't exist for tags. Now properly detects if triggered by tag vs branch and handles accordingly. --- .github/workflows/gptchangelog.yml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index ff4264c..5510d5e 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -110,10 +110,18 @@ jobs: fetch-depth: 0 token: ${{ steps.app-token.outputs.token }} - - name: Sync with remote branch + - name: Sync with remote ref run: | - git fetch origin ${{ github.ref_name }} - git reset --hard origin/${{ github.ref_name }} + # Handle both branch and tag triggers + if [[ "${{ github.ref }}" == refs/tags/* ]]; then + echo "๐Ÿ“Œ Triggered by tag: ${{ github.ref_name }}" + # For tags, we're already on the correct commit after checkout + git fetch --tags + else + echo "๐Ÿ“Œ Triggered by branch: ${{ github.ref_name }}" + git fetch origin ${{ github.ref_name }} + git reset --hard origin/${{ github.ref_name }} + fi - name: Import GPG key uses: crazy-max/ghaction-import-gpg@v6 From f001195f783a06e11af40b78a2a5da0bd816ba9d Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Fri, 12 Dec 2025 12:45:10 -0300 Subject: [PATCH 03/46] fix: use default branch for PR base when triggered by tag When triggered by a tag push, GITHUB_REF contains the tag name, not a branch. Now detects tag triggers and uses the repo's default branch as PR base instead. --- .github/workflows/gptchangelog.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index 5510d5e..da14566 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -357,7 +357,16 @@ jobs: - name: Create changelog PR if: steps.generate.outputs.apps_updated != '' run: | - BASE_BRANCH="${GITHUB_REF##*/}" + # Determine base branch - use default branch for tag triggers + if [[ "${{ github.ref }}" == refs/tags/* ]]; then + # For tags, get the default branch from the repo + BASE_BRANCH=$(gh repo view --json defaultBranchRef -q '.defaultBranchRef.name') + echo "๐Ÿ“Œ Triggered by tag, using default branch: $BASE_BRANCH" + else + BASE_BRANCH="${GITHUB_REF##*/}" + echo "๐Ÿ“Œ Triggered by branch: $BASE_BRANCH" + fi + CURRENT_DATE=$(date +%Y-%m-%d) BRANCH_NAME="release/update-changelog-${CURRENT_DATE}" APPS_UPDATED="${{ steps.generate.outputs.apps_updated }}" From e4c6b1b70e8fd83579ca9e6e821f2197a220c0a3 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Fri, 12 Dec 2025 12:51:25 -0300 Subject: [PATCH 04/46] fix: remove unnecessary tag fetch in sync step, use --force for tag fetch - Sync step: skip git fetch for tags since checkout already positioned correctly - Generate step: use --force flag to allow overwriting local tags --- .github/workflows/gptchangelog.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index da14566..39da8f6 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -115,8 +115,8 @@ jobs: # Handle both branch and tag triggers if [[ "${{ github.ref }}" == refs/tags/* ]]; then echo "๐Ÿ“Œ Triggered by tag: ${{ github.ref_name }}" - # For tags, we're already on the correct commit after checkout - git fetch --tags + # For tags, checkout already positioned us at the correct commit + echo "โœ… Already at tag commit" else echo "๐Ÿ“Œ Triggered by branch: ${{ github.ref_name }}" git fetch origin ${{ github.ref_name }} @@ -195,7 +195,7 @@ jobs: id: generate run: | echo "OPENAI_API_KEY=${OPENAI_API_KEY}" > .env - git fetch --tags + git fetch --tags --force MATRIX='${{ needs.prepare.outputs.matrix }}' CURRENT_DATE=$(date +%Y-%m-%d) From 870b1a2f0a9613fae800c711c2ccc2fed3b6bd34 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Fri, 12 Dec 2025 13:02:03 -0300 Subject: [PATCH 05/46] fix: properly initialize gptchangelog config - Run 'gptchangelog config init' first to create proper structure - Use echo commands instead of heredoc for reliable variable expansion - Copy config to global location as fallback - Add config debug output (excluding api key) --- .github/workflows/gptchangelog.yml | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index 39da8f6..b78cb0f 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -148,17 +148,26 @@ jobs: - name: Create gptchangelog config run: | + # Initialize config first (creates proper structure) + gptchangelog config init --non-interactive || true + + # Create project-specific config mkdir -p .gptchangelog - cat > .gptchangelog/config.ini << EOF - [gptchangelog] - openai = true - - [openai] - api_key = ${OPENAI_API_KEY} - model = ${{ inputs.openai_model }} - max_context_tokens = ${{ inputs.max_context_tokens }} - EOF + echo "[gptchangelog]" > .gptchangelog/config.ini + echo "openai = true" >> .gptchangelog/config.ini + echo "" >> .gptchangelog/config.ini + echo "[openai]" >> .gptchangelog/config.ini + echo "api_key = ${OPENAI_API_KEY}" >> .gptchangelog/config.ini + echo "model = ${{ inputs.openai_model }}" >> .gptchangelog/config.ini + echo "max_context_tokens = ${{ inputs.max_context_tokens }}" >> .gptchangelog/config.ini + + # Also create global config as fallback + mkdir -p ~/.config/gptchangelog + cp .gptchangelog/config.ini ~/.config/gptchangelog/config.ini + echo "โœ… gptchangelog config created" + echo "๐Ÿ“„ Config contents:" + cat .gptchangelog/config.ini | grep -v api_key env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} From c0b0bd4bfe731d937a483f8b7d0e218c114b601e Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Fri, 12 Dec 2025 13:05:08 -0300 Subject: [PATCH 06/46] fix: run gptchangelog from repo root instead of app subdirectory gptchangelog needs access to .git folder to work properly. Removed pushd/popd to app subdirectory, added --ui plain flag. --- .github/workflows/gptchangelog.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index b78cb0f..84f8d98 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -261,12 +261,10 @@ jobs: # Extract version from tag VERSION=$(echo "$LAST_TAG" | sed 's/.*-v/v/' | sed 's/^v//') - # Generate changelog for this app + # Generate changelog for this app (run from repo root, gptchangelog needs git access) TEMP_CHANGELOG=$(mktemp) - pushd "$WORKING_DIR" >/dev/null 2>&1 || pushd . >/dev/null - - if gptchangelog generate --since "$SINCE" --to "$TO" --output "$TEMP_CHANGELOG"; then + if gptchangelog generate --since "$SINCE" --to "$TO" --output "$TEMP_CHANGELOG" --ui plain; then # Clean up markdown blocks sed -i '' '/^```/d' "$TEMP_CHANGELOG" 2>/dev/null || sed -i '/^```/d' "$TEMP_CHANGELOG" @@ -302,7 +300,6 @@ jobs: echo "โš ๏ธ Failed to generate changelog for $APP_NAME" fi - popd >/dev/null 2>&1 || true rm -f "$TEMP_CHANGELOG" done From eb4e34b0e7829d639c26b3736a4f8deeb4c2ae85 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Fri, 12 Dec 2025 13:07:14 -0300 Subject: [PATCH 07/46] fix: remove unsupported gptchangelog CLI flags - Remove --non-interactive flag from config init - Remove --ui plain flag from generate command --- .github/workflows/gptchangelog.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index 84f8d98..97082f5 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -149,7 +149,7 @@ jobs: - name: Create gptchangelog config run: | # Initialize config first (creates proper structure) - gptchangelog config init --non-interactive || true + # Note: gptchangelog config init may prompt for input, so we skip it and create config manually # Create project-specific config mkdir -p .gptchangelog @@ -264,7 +264,7 @@ jobs: # Generate changelog for this app (run from repo root, gptchangelog needs git access) TEMP_CHANGELOG=$(mktemp) - if gptchangelog generate --since "$SINCE" --to "$TO" --output "$TEMP_CHANGELOG" --ui plain; then + if gptchangelog generate --since "$SINCE" --to "$TO" --output "$TEMP_CHANGELOG"; then # Clean up markdown blocks sed -i '' '/^```/d' "$TEMP_CHANGELOG" 2>/dev/null || sed -i '/^```/d' "$TEMP_CHANGELOG" From 7900744f90941ba79391661fb621d9a7f50b65ee Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Fri, 12 Dec 2025 13:36:17 -0300 Subject: [PATCH 08/46] feat: add stable-only mode and per-app changelog support Changes: - Add stable_releases_only input (default: true) to skip beta/rc/alpha tags - Write changelogs to per-app folders (e.g., apps/agent/CHANGELOG.md) - Each app gets its own CHANGELOG.md instead of consolidated root file - Update PR creation to handle multiple per-app changelog files - Improve tag detection for prerelease versions Best practices: - Only stable releases generate changelogs by default - Per-app changelogs follow monorepo conventions --- .github/workflows/gptchangelog.yml | 207 ++++++++++++++++++----------- 1 file changed, 132 insertions(+), 75 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index 97082f5..bb92f9e 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -1,18 +1,19 @@ name: "GPT Changelog" -# This reusable workflow generates a consolidated CHANGELOG.md and RELEASE_NOTES.md using GPTChangelog +# This reusable workflow generates CHANGELOG.md files using GPTChangelog # It uses OpenAI GPT-4o to analyze commits and generate human-readable changelogs # # Monorepo Support: -# - If filter_paths is provided: detects changes and generates changelog sections for each changed app +# - If filter_paths is provided: detects changes and generates changelog for each changed app # - If filter_paths is empty: generates changelog for the entire repository (single app mode) -# - All apps are consolidated into ONE CHANGELOG.md file (no overwrites) +# - Each app gets its own CHANGELOG.md in its folder (best practice for monorepos) # # Features: -# - Generates consolidated CHANGELOG.md with sections per app -# - Generates RELEASE_NOTES.md and updates GitHub Release -# - Creates PR with changelog update using GPG-signed commits +# - Generates per-app CHANGELOG.md files in each app's folder +# - Updates GitHub Release notes +# - Creates PR with changelog updates using GPG-signed commits # - Handles tag-based versioning (between tags, first tag, no tags scenarios) +# - Supports stable-only mode (skips beta/rc/alpha tags) on: workflow_call: @@ -30,6 +31,10 @@ on: description: 'Limits the path to the first N segments (e.g., 2 -> "charts/agent")' type: string default: '2' + stable_releases_only: + description: 'Only generate changelogs for stable releases (skip beta/rc/alpha tags)' + type: boolean + default: true openai_model: description: 'OpenAI model to use for changelog generation' type: string @@ -53,14 +58,34 @@ jobs: outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} has_changes: ${{ steps.set-matrix.outputs.has_changes }} + is_stable: ${{ steps.check-tag.outputs.is_stable }} steps: + - name: Check if tag is stable release + id: check-tag + run: | + TAG_NAME="${GITHUB_REF##*/}" + echo "๐Ÿ“Œ Checking tag: $TAG_NAME" + + # Check if this is a prerelease tag (beta, rc, alpha) + if [[ "$TAG_NAME" =~ -(beta|rc|alpha|dev|snapshot) ]]; then + echo "is_stable=false" >> $GITHUB_OUTPUT + echo "โš ๏ธ Prerelease tag detected: $TAG_NAME" + if [ "${{ inputs.stable_releases_only }}" == "true" ]; then + echo "๐Ÿ›‘ stable_releases_only=true, skipping changelog generation" + fi + else + echo "is_stable=true" >> $GITHUB_OUTPUT + echo "โœ… Stable release tag: $TAG_NAME" + fi + - name: Checkout repository + if: steps.check-tag.outputs.is_stable == 'true' || inputs.stable_releases_only == false uses: actions/checkout@v4 with: fetch-depth: 0 - name: Get changed paths (monorepo) - if: inputs.filter_paths != '' + if: (steps.check-tag.outputs.is_stable == 'true' || inputs.stable_releases_only == false) && inputs.filter_paths != '' id: changed-paths uses: LerianStudio/github-actions-changed-paths@main with: @@ -71,6 +96,14 @@ jobs: - name: Set matrix id: set-matrix run: | + # Skip if stable_releases_only is enabled and tag is not stable + if [ "${{ inputs.stable_releases_only }}" == "true" ] && [ "${{ steps.check-tag.outputs.is_stable }}" != "true" ]; then + echo "matrix=[]" >> $GITHUB_OUTPUT + echo "has_changes=false" >> $GITHUB_OUTPUT + echo "๐Ÿ›‘ Skipping: prerelease tag with stable_releases_only=true" + exit 0 + fi + if [ -z "${{ inputs.filter_paths }}" ]; then # Single app mode - generate changelog from root APP_NAME="${{ github.event.repository.name }}" @@ -271,24 +304,51 @@ jobs: # Extract content (skip the header line if present) CONTENT=$(cat "$TEMP_CHANGELOG" | tail -n +2) - # Build app section for consolidated changelog - echo "### ${APP_NAME} v${VERSION}" >> /tmp/consolidated_changelog.md - echo "" >> /tmp/consolidated_changelog.md - echo "$CONTENT" >> /tmp/consolidated_changelog.md - echo "" >> /tmp/consolidated_changelog.md + # Determine the changelog path (per-app folder or root) + if [ "$WORKING_DIR" != "." ]; then + CHANGELOG_PATH="${WORKING_DIR}/CHANGELOG.md" + else + CHANGELOG_PATH="CHANGELOG.md" + fi + + # Prepare the new changelog entry for this app + cat > /tmp/new_entry.md << ENTRY_EOF + ## [${VERSION}] - ${CURRENT_DATE} + + ${CONTENT} + --- + + ENTRY_EOF + + # Update or create per-app CHANGELOG.md + if [ -f "$CHANGELOG_PATH" ]; then + # Insert after the title line + if grep -q "^# " "$CHANGELOG_PATH"; then + TITLE=$(head -n 1 "$CHANGELOG_PATH") + EXISTING=$(tail -n +2 "$CHANGELOG_PATH") + echo "$TITLE" > "$CHANGELOG_PATH" + echo "" >> "$CHANGELOG_PATH" + cat /tmp/new_entry.md >> "$CHANGELOG_PATH" + echo "$EXISTING" >> "$CHANGELOG_PATH" + else + cat /tmp/new_entry.md > /tmp/new_changelog.md + cat "$CHANGELOG_PATH" >> /tmp/new_changelog.md + mv /tmp/new_changelog.md "$CHANGELOG_PATH" + fi + else + echo "# ${APP_NAME} Changelog" > "$CHANGELOG_PATH" + echo "" >> "$CHANGELOG_PATH" + cat /tmp/new_entry.md >> "$CHANGELOG_PATH" + fi - # Also add to release notes - echo "### ${APP_NAME} v${VERSION}" >> /tmp/consolidated_release_notes.md - echo "" >> /tmp/consolidated_release_notes.md - echo "$CONTENT" >> /tmp/consolidated_release_notes.md - echo "" >> /tmp/consolidated_release_notes.md + echo "๐Ÿ“„ Updated: $CHANGELOG_PATH" - # Track which apps were updated - echo "${APP_NAME}:v${VERSION}" >> /tmp/apps_updated.txt + # Track which apps were updated (with their paths) + echo "${APP_NAME}:v${VERSION}:${WORKING_DIR}" >> /tmp/apps_updated.txt # Update GitHub Release for this app's tag (if not HEAD) if [ "$LAST_TAG" != "HEAD" ]; then - echo "### ${APP_NAME} v${VERSION}" > /tmp/app_release_notes.md + echo "## ${APP_NAME} v${VERSION}" > /tmp/app_release_notes.md echo "" >> /tmp/app_release_notes.md echo "$CONTENT" >> /tmp/app_release_notes.md gh release edit "$LAST_TAG" --notes-file /tmp/app_release_notes.md || \ @@ -303,47 +363,11 @@ jobs: rm -f "$TEMP_CHANGELOG" done - # Create final consolidated CHANGELOG.md - if [ -s /tmp/consolidated_changelog.md ]; then - APPS_LIST=$(cat /tmp/apps_updated.txt 2>/dev/null | tr '\n' ', ' | sed 's/,$//') - - # Prepare the new changelog entry - cat > /tmp/new_entry.md << ENTRY_EOF - ## [$CURRENT_DATE] - - $(cat /tmp/consolidated_changelog.md) - --- - - ENTRY_EOF - - # Prepend to existing CHANGELOG.md or create new one - if [ -f CHANGELOG.md ]; then - # Insert after the title line - if grep -q "^# " CHANGELOG.md; then - TITLE=$(head -n 1 CHANGELOG.md) - EXISTING=$(tail -n +2 CHANGELOG.md) - echo "$TITLE" > CHANGELOG.md - echo "" >> CHANGELOG.md - cat /tmp/new_entry.md >> CHANGELOG.md - echo "$EXISTING" >> CHANGELOG.md - else - cat /tmp/new_entry.md > /tmp/new_changelog.md - cat CHANGELOG.md >> /tmp/new_changelog.md - mv /tmp/new_changelog.md CHANGELOG.md - fi - else - echo "# Changelog" > CHANGELOG.md - echo "" >> CHANGELOG.md - cat /tmp/new_entry.md >> CHANGELOG.md - fi - - # Create RELEASE_NOTES.md - echo "# Release Notes - $CURRENT_DATE" > RELEASE_NOTES.md - echo "" >> RELEASE_NOTES.md - cat /tmp/consolidated_release_notes.md >> RELEASE_NOTES.md - + # Output results + if [ -s /tmp/apps_updated.txt ]; then + APPS_LIST=$(cat /tmp/apps_updated.txt 2>/dev/null | cut -d: -f1,2 | tr '\n' ', ' | sed 's/,$//') echo "apps_updated=$APPS_LIST" >> $GITHUB_OUTPUT - echo "โœ… Consolidated changelog created with apps: $APPS_LIST" + echo "โœ… Per-app changelogs created for: $APPS_LIST" else echo "โš ๏ธ No changelog content generated" echo "apps_updated=" >> $GITHUB_OUTPUT @@ -352,12 +376,24 @@ jobs: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} GH_TOKEN: ${{ steps.app-token.outputs.token }} - - name: Show generated changelog + - name: Show generated changelogs run: | - echo "๐Ÿ“„ Generated CHANGELOG.md:" + echo "๐Ÿ“„ Generated per-app CHANGELOGs:" echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" - head -100 CHANGELOG.md - echo "" + if [ -f /tmp/apps_updated.txt ]; then + while IFS=: read -r APP_NAME VERSION WORKING_DIR; do + if [ "$WORKING_DIR" != "." ]; then + CHANGELOG_PATH="${WORKING_DIR}/CHANGELOG.md" + else + CHANGELOG_PATH="CHANGELOG.md" + fi + echo "" + echo "๐Ÿ“ฆ ${APP_NAME} (${CHANGELOG_PATH}):" + echo "---" + head -50 "$CHANGELOG_PATH" 2>/dev/null || echo "File not found" + echo "" + done < /tmp/apps_updated.txt + fi echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" - name: Create changelog PR @@ -380,22 +416,43 @@ jobs: echo "๐Ÿ“Œ Creating branch: $BRANCH_NAME" git checkout -b "$BRANCH_NAME" - # Add and commit CHANGELOG - git add CHANGELOG.md + # Add all per-app CHANGELOG files + if [ -f /tmp/apps_updated.txt ]; then + while IFS=: read -r APP_NAME VERSION WORKING_DIR; do + if [ "$WORKING_DIR" != "." ]; then + CHANGELOG_PATH="${WORKING_DIR}/CHANGELOG.md" + else + CHANGELOG_PATH="CHANGELOG.md" + fi + git add "$CHANGELOG_PATH" 2>/dev/null || true + echo "๐Ÿ“„ Added: $CHANGELOG_PATH" + done < /tmp/apps_updated.txt + fi + if ! git diff --cached --quiet; then - git commit -S -m "chore(release): Update CHANGELOG for ${APPS_UPDATED}" - echo "โœ… CHANGELOG committed" + git commit -S -m "chore(release): Update CHANGELOGs for ${APPS_UPDATED}" + echo "โœ… CHANGELOGs committed" else - echo "โš ๏ธ No changes to CHANGELOG" + echo "โš ๏ธ No changes to commit" exit 0 fi # Merge base branch to resolve conflicts git fetch origin "$BASE_BRANCH" git merge -X ours origin/"$BASE_BRANCH" --no-ff -m "Merge $BASE_BRANCH into ${BRANCH_NAME}" || { - git checkout --ours CHANGELOG.md - git add CHANGELOG.md - git commit -m "resolve conflict using ours strategy" + # Re-add changelog files after conflict resolution + if [ -f /tmp/apps_updated.txt ]; then + while IFS=: read -r APP_NAME VERSION WORKING_DIR; do + if [ "$WORKING_DIR" != "." ]; then + CHANGELOG_PATH="${WORKING_DIR}/CHANGELOG.md" + else + CHANGELOG_PATH="CHANGELOG.md" + fi + git checkout --ours "$CHANGELOG_PATH" 2>/dev/null || true + git add "$CHANGELOG_PATH" 2>/dev/null || true + done < /tmp/apps_updated.txt + fi + git commit -m "resolve conflict using ours strategy" || true } # Push and create PR @@ -403,15 +460,15 @@ jobs: if ! gh pr view "$BRANCH_NAME" --base "$BASE_BRANCH" > /dev/null 2>&1; then gh pr create \ - --title "chore(release): Update CHANGELOG - ${CURRENT_DATE}" \ + --title "chore(release): Update CHANGELOGs - ${CURRENT_DATE}" \ --body "## Automatic Changelog Update **Date:** ${CURRENT_DATE} **Apps Updated:** ${APPS_UPDATED} ### Changes - - Updated CHANGELOG.md with consolidated release notes - - Each app section generated by GPTChangelog using OpenAI GPT-4o + - Updated per-app CHANGELOG.md files + - Each changelog generated by GPTChangelog using OpenAI GPT-4o ### Apps Included $(echo "$APPS_UPDATED" | tr ',' '\n' | sed 's/^/- /') From b43f8c7cd6e2cf45d1c314881e58ffa2111ac0e0 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Fri, 12 Dec 2025 13:52:54 -0300 Subject: [PATCH 09/46] fix: add [skip ci] to prevent infinite loop with release workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All commit messages from gptchangelog now include [skip ci] to prevent triggering other workflows (like release) which would create a loop: release โ†’ gptchangelog โ†’ merge โ†’ release โ†’ gptchangelog... --- .github/workflows/gptchangelog.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index bb92f9e..ff49d23 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -430,7 +430,7 @@ jobs: fi if ! git diff --cached --quiet; then - git commit -S -m "chore(release): Update CHANGELOGs for ${APPS_UPDATED}" + git commit -S -m "chore(release): Update CHANGELOGs for ${APPS_UPDATED} [skip ci]" echo "โœ… CHANGELOGs committed" else echo "โš ๏ธ No changes to commit" @@ -439,7 +439,7 @@ jobs: # Merge base branch to resolve conflicts git fetch origin "$BASE_BRANCH" - git merge -X ours origin/"$BASE_BRANCH" --no-ff -m "Merge $BASE_BRANCH into ${BRANCH_NAME}" || { + git merge -X ours origin/"$BASE_BRANCH" --no-ff -m "Merge $BASE_BRANCH into ${BRANCH_NAME} [skip ci]" || { # Re-add changelog files after conflict resolution if [ -f /tmp/apps_updated.txt ]; then while IFS=: read -r APP_NAME VERSION WORKING_DIR; do @@ -452,7 +452,7 @@ jobs: git add "$CHANGELOG_PATH" 2>/dev/null || true done < /tmp/apps_updated.txt fi - git commit -m "resolve conflict using ours strategy" || true + git commit -m "resolve conflict using ours strategy [skip ci]" || true } # Push and create PR From 65b0ec01f801ee08fe9fc598a25bc48115a6b06c Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Fri, 12 Dec 2025 14:32:11 -0300 Subject: [PATCH 10/46] feat: add sync main to develop step after changelog merge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flow is now: 1. Tag pushed โ†’ GPT Changelog triggers 2. PR created to main โ†’ auto-merged 3. Sync PR created: main โ†’ develop โ†’ auto-merged 4. Develop now has the changelog updates All commits include [skip ci] to prevent triggering other workflows. --- .github/workflows/gptchangelog.yml | 54 ++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index ff49d23..64b6c54 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -487,6 +487,60 @@ jobs: env: GH_TOKEN: ${{ steps.app-token.outputs.token }} + - name: Sync main to develop + if: steps.generate.outputs.apps_updated != '' + run: | + # Get default branch (main) and develop branch + DEFAULT_BRANCH=$(gh repo view --json defaultBranchRef -q '.defaultBranchRef.name') + DEVELOP_BRANCH="develop" + + echo "๐Ÿ“Œ Syncing $DEFAULT_BRANCH โ†’ $DEVELOP_BRANCH" + + # Check if develop branch exists + if ! git ls-remote --heads origin "$DEVELOP_BRANCH" | grep -q "$DEVELOP_BRANCH"; then + echo "โš ๏ธ Branch $DEVELOP_BRANCH does not exist, skipping sync" + exit 0 + fi + + # Fetch latest + git fetch origin "$DEFAULT_BRANCH" "$DEVELOP_BRANCH" + + # Create sync branch + SYNC_BRANCH="sync/${DEFAULT_BRANCH}-to-${DEVELOP_BRANCH}-$(date +%Y%m%d-%H%M%S)" + git checkout -b "$SYNC_BRANCH" "origin/$DEVELOP_BRANCH" + + # Merge main into develop (changelog changes) + git merge "origin/$DEFAULT_BRANCH" --no-ff -m "chore: sync $DEFAULT_BRANCH to $DEVELOP_BRANCH [skip ci]" || { + # If conflict, prefer main's version for changelogs + git checkout --theirs . 2>/dev/null || true + git add -A + git commit -m "chore: sync $DEFAULT_BRANCH to $DEVELOP_BRANCH (resolved conflicts) [skip ci]" || true + } + + # Push and create PR + git push origin "$SYNC_BRANCH" + + if ! gh pr list --head "$SYNC_BRANCH" --base "$DEVELOP_BRANCH" | grep -q .; then + gh pr create \ + --title "chore: sync $DEFAULT_BRANCH to $DEVELOP_BRANCH [skip ci]" \ + --body "Automatic sync of changelog updates from $DEFAULT_BRANCH to $DEVELOP_BRANCH. + + This PR syncs the changelog changes that were merged to $DEFAULT_BRANCH back to $DEVELOP_BRANCH. + + --- + *This PR was automatically generated by the GPTChangelog workflow.*" \ + --base "$DEVELOP_BRANCH" \ + --head "$SYNC_BRANCH" + + # Auto-merge the sync PR + gh pr merge --merge --delete-branch || echo "โš ๏ธ Could not auto-merge sync PR" + echo "โœ… Synced $DEFAULT_BRANCH to $DEVELOP_BRANCH" + else + echo "โš ๏ธ Sync PR already exists" + fi + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + - name: Cleanup sensitive files if: always() run: | From afb17340c975f7c7ca1e4ec3442a40e2bd5fd0df Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Fri, 12 Dec 2025 14:40:05 -0300 Subject: [PATCH 11/46] feat: improve changelog sync and cleanup Changes: - Remove date from changelog entries (cleaner format) - Remove separator line between entries - Sync to develop AND RC branches (rc/*, release/*, release-*) - Use main directly for PR (no intermediate branch) - Don't auto-merge sync PRs - keep open for review - Add Slack notification for sync PRs needing review - Support RC branch patterns --- .github/workflows/gptchangelog.yml | 148 +++++++++++++++++++++-------- 1 file changed, 106 insertions(+), 42 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index 64b6c54..b64e633 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -128,6 +128,8 @@ jobs: if: needs.prepare.outputs.has_changes == 'true' runs-on: ${{ inputs.runner_type }} name: Generate Consolidated Changelog + outputs: + sync_prs: ${{ steps.sync.outputs.sync_prs }} steps: - name: Create GitHub App Token @@ -313,10 +315,9 @@ jobs: # Prepare the new changelog entry for this app cat > /tmp/new_entry.md << ENTRY_EOF - ## [${VERSION}] - ${CURRENT_DATE} + ## [${VERSION}] ${CONTENT} - --- ENTRY_EOF @@ -487,57 +488,69 @@ jobs: env: GH_TOKEN: ${{ steps.app-token.outputs.token }} - - name: Sync main to develop + - name: Sync main to develop and RC branches + id: sync if: steps.generate.outputs.apps_updated != '' run: | - # Get default branch (main) and develop branch DEFAULT_BRANCH=$(gh repo view --json defaultBranchRef -q '.defaultBranchRef.name') - DEVELOP_BRANCH="develop" + SYNC_PRS="" - echo "๐Ÿ“Œ Syncing $DEFAULT_BRANCH โ†’ $DEVELOP_BRANCH" + # List of branches to sync to (develop + any RC branches) + SYNC_TARGETS="develop" - # Check if develop branch exists - if ! git ls-remote --heads origin "$DEVELOP_BRANCH" | grep -q "$DEVELOP_BRANCH"; then - echo "โš ๏ธ Branch $DEVELOP_BRANCH does not exist, skipping sync" - exit 0 + # Find RC branches (release candidate pattern: rc/*, release/*, release-*) + RC_BRANCHES=$(git ls-remote --heads origin | grep -E 'refs/heads/(rc/|release/|release-)' | sed 's/.*refs\/heads\///' || true) + if [ -n "$RC_BRANCHES" ]; then + SYNC_TARGETS="$SYNC_TARGETS $RC_BRANCHES" fi - # Fetch latest - git fetch origin "$DEFAULT_BRANCH" "$DEVELOP_BRANCH" - - # Create sync branch - SYNC_BRANCH="sync/${DEFAULT_BRANCH}-to-${DEVELOP_BRANCH}-$(date +%Y%m%d-%H%M%S)" - git checkout -b "$SYNC_BRANCH" "origin/$DEVELOP_BRANCH" - - # Merge main into develop (changelog changes) - git merge "origin/$DEFAULT_BRANCH" --no-ff -m "chore: sync $DEFAULT_BRANCH to $DEVELOP_BRANCH [skip ci]" || { - # If conflict, prefer main's version for changelogs - git checkout --theirs . 2>/dev/null || true - git add -A - git commit -m "chore: sync $DEFAULT_BRANCH to $DEVELOP_BRANCH (resolved conflicts) [skip ci]" || true - } - - # Push and create PR - git push origin "$SYNC_BRANCH" + echo "๐Ÿ“Œ Will sync $DEFAULT_BRANCH to: $SYNC_TARGETS" - if ! gh pr list --head "$SYNC_BRANCH" --base "$DEVELOP_BRANCH" | grep -q .; then - gh pr create \ - --title "chore: sync $DEFAULT_BRANCH to $DEVELOP_BRANCH [skip ci]" \ - --body "Automatic sync of changelog updates from $DEFAULT_BRANCH to $DEVELOP_BRANCH. + for TARGET_BRANCH in $SYNC_TARGETS; do + echo "" + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + echo "๐Ÿ“Œ Syncing $DEFAULT_BRANCH โ†’ $TARGET_BRANCH" + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + + # Check if target branch exists + if ! git ls-remote --heads origin "$TARGET_BRANCH" | grep -q "$TARGET_BRANCH"; then + echo "โš ๏ธ Branch $TARGET_BRANCH does not exist, skipping" + continue + fi + + # Check if PR already exists from main to this target + EXISTING_PR=$(gh pr list --base "$TARGET_BRANCH" --head "$DEFAULT_BRANCH" --json number -q '.[0].number' 2>/dev/null || true) + if [ -n "$EXISTING_PR" ]; then + echo "โš ๏ธ PR #$EXISTING_PR already exists for $DEFAULT_BRANCH โ†’ $TARGET_BRANCH" + continue + fi + + # Create PR directly from main to target (no intermediate branch) + PR_URL=$(gh pr create \ + --title "chore: sync $DEFAULT_BRANCH to $TARGET_BRANCH [skip ci]" \ + --body "## Automatic Changelog Sync + + Syncs changelog updates from \`$DEFAULT_BRANCH\` to \`$TARGET_BRANCH\`. - This PR syncs the changelog changes that were merged to $DEFAULT_BRANCH back to $DEVELOP_BRANCH. + **Apps Updated:** ${{ steps.generate.outputs.apps_updated }} --- - *This PR was automatically generated by the GPTChangelog workflow.*" \ - --base "$DEVELOP_BRANCH" \ - --head "$SYNC_BRANCH" + *This PR was automatically generated by the GPTChangelog workflow.* + *Please review and merge manually.*" \ + --base "$TARGET_BRANCH" \ + --head "$DEFAULT_BRANCH" 2>&1) || { + echo "โš ๏ธ Could not create PR for $TARGET_BRANCH: $PR_URL" + continue + } - # Auto-merge the sync PR - gh pr merge --merge --delete-branch || echo "โš ๏ธ Could not auto-merge sync PR" - echo "โœ… Synced $DEFAULT_BRANCH to $DEVELOP_BRANCH" - else - echo "โš ๏ธ Sync PR already exists" - fi + echo "โœ… Created PR: $PR_URL" + SYNC_PRS="${SYNC_PRS}${PR_URL}\n" + done + + # Output PR URLs for Slack notification + echo "sync_prs<> $GITHUB_OUTPUT + echo -e "$SYNC_PRS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT env: GH_TOKEN: ${{ steps.app-token.outputs.token }} @@ -549,7 +562,7 @@ jobs: rm -f /tmp/consolidated_changelog.md /tmp/consolidated_release_notes.md /tmp/apps_updated.txt /tmp/app_release_notes.md /tmp/new_entry.md echo "๐Ÿงน Cleaned up sensitive files" - # Slack notification + # Slack notification for workflow status notify: name: Notify needs: [prepare, generate_changelog] @@ -561,3 +574,54 @@ jobs: failed_jobs: ${{ needs.generate_changelog.result == 'failure' && 'Generate Changelog' || '' }} secrets: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + + # Notify about sync PRs that need manual review + notify-sync-prs: + name: Notify Sync PRs + needs: [generate_changelog] + if: needs.generate_changelog.result == 'success' + runs-on: ubuntu-latest + steps: + - name: Send Slack notification for sync PRs + if: needs.generate_changelog.outputs.sync_prs != '' + uses: slackapi/slack-github-action@v1.24.0 + with: + payload: | + { + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": "๐Ÿ“‹ Changelog Sync PRs Need Review", + "emoji": true + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Repository:* ${{ github.repository }}\n*Triggered by:* ${{ github.ref_name }}\n\nThe following PRs were created to sync changelog updates and need manual review:" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "${{ needs.generate_changelog.outputs.sync_prs }}" + } + }, + { + "type": "context", + "elements": [ + { + "type": "mrkdwn", + "text": "Please review and merge these PRs to sync changelog updates to develop and RC branches." + } + ] + } + ] + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK From 59fbfd640fa5792d14bc1217c14263ab3197b30f Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Fri, 12 Dec 2025 15:02:20 -0300 Subject: [PATCH 12/46] fix: use explicit branch names (develop, release-candidate) Instead of pattern matching, explicitly check for: - develop - release-candidate These are the actual branch names used in the platform repo. --- .github/workflows/gptchangelog.yml | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index b64e633..e53cc63 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -495,13 +495,24 @@ jobs: DEFAULT_BRANCH=$(gh repo view --json defaultBranchRef -q '.defaultBranchRef.name') SYNC_PRS="" - # List of branches to sync to (develop + any RC branches) - SYNC_TARGETS="develop" + # List of branches to sync to + SYNC_TARGETS="" - # Find RC branches (release candidate pattern: rc/*, release/*, release-*) - RC_BRANCHES=$(git ls-remote --heads origin | grep -E 'refs/heads/(rc/|release/|release-)' | sed 's/.*refs\/heads\///' || true) - if [ -n "$RC_BRANCHES" ]; then - SYNC_TARGETS="$SYNC_TARGETS $RC_BRANCHES" + # Check if develop branch exists + if git ls-remote --heads origin develop | grep -q develop; then + SYNC_TARGETS="develop" + echo "โœ… Found develop branch" + fi + + # Check if release-candidate branch exists + if git ls-remote --heads origin release-candidate | grep -q release-candidate; then + SYNC_TARGETS="$SYNC_TARGETS release-candidate" + echo "โœ… Found release-candidate branch" + fi + + if [ -z "$SYNC_TARGETS" ]; then + echo "โš ๏ธ No sync target branches found (develop, release-candidate)" + exit 0 fi echo "๐Ÿ“Œ Will sync $DEFAULT_BRANCH to: $SYNC_TARGETS" @@ -512,12 +523,6 @@ jobs: echo "๐Ÿ“Œ Syncing $DEFAULT_BRANCH โ†’ $TARGET_BRANCH" echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" - # Check if target branch exists - if ! git ls-remote --heads origin "$TARGET_BRANCH" | grep -q "$TARGET_BRANCH"; then - echo "โš ๏ธ Branch $TARGET_BRANCH does not exist, skipping" - continue - fi - # Check if PR already exists from main to this target EXISTING_PR=$(gh pr list --base "$TARGET_BRANCH" --head "$DEFAULT_BRANCH" --json number -q '.[0].number' 2>/dev/null || true) if [ -n "$EXISTING_PR" ]; then From fada96bda45b225ced156c3e87343b6262743ffd Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Sat, 13 Dec 2025 13:26:37 -0300 Subject: [PATCH 13/46] feat: only generate changelog for tags on main branch Adds a check to verify the tagged commit exists on the default branch (main). Tags created from develop or other branches will be skipped. This prevents: - Beta tags from develop triggering changelog generation - Conflicts between release-please (develop) and GPT Changelog (main) --- .github/workflows/gptchangelog.yml | 32 ++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index e53cc63..2b6d225 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -60,12 +60,38 @@ jobs: has_changes: ${{ steps.set-matrix.outputs.has_changes }} is_stable: ${{ steps.check-tag.outputs.is_stable }} steps: - - name: Check if tag is stable release + - name: Checkout for branch check + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check if tag is stable release on main id: check-tag run: | TAG_NAME="${GITHUB_REF##*/}" echo "๐Ÿ“Œ Checking tag: $TAG_NAME" + # Get the commit SHA for this tag + TAG_COMMIT=$(git rev-list -n 1 "$TAG_NAME" 2>/dev/null || echo "") + if [ -z "$TAG_COMMIT" ]; then + echo "โŒ Could not find commit for tag: $TAG_NAME" + echo "is_stable=false" >> $GITHUB_OUTPUT + exit 0 + fi + echo "๐Ÿ“Œ Tag commit: $TAG_COMMIT" + + # Check if this commit is on main branch + DEFAULT_BRANCH=$(gh repo view --json defaultBranchRef -q '.defaultBranchRef.name') + echo "๐Ÿ“Œ Default branch: $DEFAULT_BRANCH" + + if git merge-base --is-ancestor "$TAG_COMMIT" "origin/$DEFAULT_BRANCH" 2>/dev/null; then + echo "โœ… Tag commit is on $DEFAULT_BRANCH branch" + else + echo "โŒ Tag commit is NOT on $DEFAULT_BRANCH branch - skipping changelog" + echo "is_stable=false" >> $GITHUB_OUTPUT + exit 0 + fi + # Check if this is a prerelease tag (beta, rc, alpha) if [[ "$TAG_NAME" =~ -(beta|rc|alpha|dev|snapshot) ]]; then echo "is_stable=false" >> $GITHUB_OUTPUT @@ -75,8 +101,10 @@ jobs: fi else echo "is_stable=true" >> $GITHUB_OUTPUT - echo "โœ… Stable release tag: $TAG_NAME" + echo "โœ… Stable release tag on $DEFAULT_BRANCH: $TAG_NAME" fi + env: + GH_TOKEN: ${{ github.token }} - name: Checkout repository if: steps.check-tag.outputs.is_stable == 'true' || inputs.stable_releases_only == false From 4dcd23017b886141d6d026a89ba087e43301b4e9 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Sat, 20 Dec 2025 17:15:25 -0300 Subject: [PATCH 14/46] fix: skip release workflow for changelog commits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add check to skip release when commit message contains: - [skip ci] - chore(release): Update CHANGELOGs This prevents the infinite loop where: 1. Tag โ†’ GPT Changelog โ†’ PR merged 2. Merge triggers Release โ†’ new tag โ†’ loop --- .github/workflows/release.yml | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 167592c..49477b7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,7 +34,26 @@ jobs: outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} has_changes: ${{ steps.set-matrix.outputs.has_changes }} + should_skip: ${{ steps.check-skip.outputs.should_skip }} steps: + - name: Check if should skip (changelog commits) + id: check-skip + run: | + COMMIT_MSG=$(cat << 'EOF' + ${{ github.event.head_commit.message }} + EOF + ) + echo "๐Ÿ“Œ Commit message: $COMMIT_MSG" + + # Skip if commit message contains [skip ci] or is a changelog update + if echo "$COMMIT_MSG" | grep -qiE '\[skip ci\]|chore\(release\): Update CHANGELOGs'; then + echo "๐Ÿ›‘ Skipping release - changelog/skip-ci commit detected" + echo "should_skip=true" >> $GITHUB_OUTPUT + else + echo "โœ… Proceeding with release" + echo "should_skip=false" >> $GITHUB_OUTPUT + fi + - name: Get changed paths (monorepo) if: inputs.filter_paths != '' id: changed-paths @@ -65,7 +84,7 @@ jobs: publish_release: needs: prepare - if: needs.prepare.outputs.has_changes == 'true' + if: needs.prepare.outputs.has_changes == 'true' && needs.prepare.outputs.should_skip != 'true' runs-on: ${{ inputs.runner_type }} environment: name: create_release From 054707eac20c8262cf3e52d8be95d5a14ca4e86d Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Sat, 20 Dec 2025 17:25:12 -0300 Subject: [PATCH 15/46] feat: improve changelog format with links and separators New format: --- ## [VERSION](link-to-tag) [Compare changes](link-to-compare) --- - No dates - Version links to release tag - Compare link to previous version - Separator lines between entries --- .github/workflows/gptchangelog.yml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index 2b6d225..5c7cf97 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -324,6 +324,13 @@ jobs: # Extract version from tag VERSION=$(echo "$LAST_TAG" | sed 's/.*-v/v/' | sed 's/^v//') + # Extract previous version for compare link + if [ -n "$PENULT_TAG" ]; then + PREV_VERSION=$(echo "$PENULT_TAG" | sed 's/.*-v/v/' | sed 's/^v//') + else + PREV_VERSION="initial" + fi + # Generate changelog for this app (run from repo root, gptchangelog needs git access) TEMP_CHANGELOG=$(mktemp) @@ -342,12 +349,20 @@ jobs: fi # Prepare the new changelog entry for this app + # Get repo URL for links + REPO_URL="https://github.com/${{ github.repository }}" + TAG_NAME="${APP_NAME}-v${VERSION}" + cat > /tmp/new_entry.md << ENTRY_EOF - ## [${VERSION}] +--- + +## [${VERSION}](${REPO_URL}/releases/tag/${TAG_NAME}) + +${CONTENT} - ${CONTENT} +[Compare changes](${REPO_URL}/compare/${APP_NAME}-v${PREV_VERSION}...${TAG_NAME}) - ENTRY_EOF +ENTRY_EOF # Update or create per-app CHANGELOG.md if [ -f "$CHANGELOG_PATH" ]; then From 51624c954726991b4921a3e3c2e7f39e69d3a5d3 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Sat, 20 Dec 2025 18:19:55 -0300 Subject: [PATCH 16/46] fix: remove date from PR title and body - Use timestamp for branch name uniqueness only - Remove date from PR title - Remove date from PR body - Add [skip ci] to PR title --- .github/workflows/gptchangelog.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index 5c7cf97..295ea09 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -453,8 +453,8 @@ ENTRY_EOF echo "๐Ÿ“Œ Triggered by branch: $BASE_BRANCH" fi - CURRENT_DATE=$(date +%Y-%m-%d) - BRANCH_NAME="release/update-changelog-${CURRENT_DATE}" + TIMESTAMP=$(date +%Y%m%d%H%M%S) + BRANCH_NAME="release/update-changelog-${TIMESTAMP}" APPS_UPDATED="${{ steps.generate.outputs.apps_updated }}" echo "๐Ÿ“Œ Creating branch: $BRANCH_NAME" @@ -504,10 +504,9 @@ ENTRY_EOF if ! gh pr view "$BRANCH_NAME" --base "$BASE_BRANCH" > /dev/null 2>&1; then gh pr create \ - --title "chore(release): Update CHANGELOGs - ${CURRENT_DATE}" \ + --title "chore(release): Update CHANGELOGs [skip ci]" \ --body "## Automatic Changelog Update - **Date:** ${CURRENT_DATE} **Apps Updated:** ${APPS_UPDATED} ### Changes From bceac3a56538f812125e9591a981b40f1308adc3 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Sat, 20 Dec 2025 18:30:58 -0300 Subject: [PATCH 17/46] fix: remove all date references from changelog workflow - Remove date from gptchangelog prompt template - Remove unused CURRENT_DATE variable - Changelog will now be completely date-free --- .github/workflows/gptchangelog.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index 295ea09..02f7727 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -238,7 +238,7 @@ jobs: run: | mkdir -p .gptchangelog/templates cat > .gptchangelog/templates/changelog_prompt.txt << 'EOF' - ## Release $next_version (Released on $current_date) + ## Release $next_version ### What's New $commit_messages @@ -270,7 +270,6 @@ jobs: git fetch --tags --force MATRIX='${{ needs.prepare.outputs.matrix }}' - CURRENT_DATE=$(date +%Y-%m-%d) echo "๐Ÿ“ฆ Processing apps from matrix: $MATRIX" From 2273f83822c1e38c140554194aa56ace5059a415 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Sat, 20 Dec 2025 19:04:06 -0300 Subject: [PATCH 18/46] feat: support workflow_run trigger for GPT Changelog - Detect trigger type (workflow_run vs push:tags) - For workflow_run: find apps from recent stable tags - For push:tags: use changed-paths action as before - Handles GitHub Actions security feature (tags from Actions don't trigger workflows) [skip ci] --- .github/workflows/gptchangelog.yml | 118 +++++++++++++++++++++++++---- 1 file changed, 102 insertions(+), 16 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index 02f7727..0219dcd 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -68,8 +68,38 @@ jobs: - name: Check if tag is stable release on main id: check-tag run: | - TAG_NAME="${GITHUB_REF##*/}" - echo "๐Ÿ“Œ Checking tag: $TAG_NAME" + # Detect trigger type: workflow_run or push:tags + if [[ "$GITHUB_REF" == refs/tags/* ]]; then + # Triggered by tag push + TAG_NAME="${GITHUB_REF##*/}" + echo "๐Ÿ“Œ Triggered by tag push: $TAG_NAME" + else + # Triggered by workflow_run - find latest stable tags + echo "๐Ÿ“Œ Triggered by workflow_run, finding latest stable tags..." + git fetch --tags + + # Get tags created in the last hour (recent release) + LATEST_TAGS=$(git tag --sort=-creatordate | head -20) + echo "๐Ÿ“Œ Recent tags: $LATEST_TAGS" + + # Find first stable tag (no beta/rc/alpha) + TAG_NAME="" + for tag in $LATEST_TAGS; do + if [[ ! "$tag" =~ -(beta|rc|alpha|dev|snapshot) ]]; then + TAG_NAME="$tag" + echo "๐Ÿ“Œ Found stable tag: $TAG_NAME" + break + fi + done + + if [ -z "$TAG_NAME" ]; then + echo "โš ๏ธ No stable tags found" + echo "is_stable=false" >> $GITHUB_OUTPUT + exit 0 + fi + fi + + echo "๐Ÿ“Œ Processing tag: $TAG_NAME" # Get the commit SHA for this tag TAG_COMMIT=$(git rev-list -n 1 "$TAG_NAME" 2>/dev/null || echo "") @@ -132,22 +162,78 @@ jobs: exit 0 fi - if [ -z "${{ inputs.filter_paths }}" ]; then - # Single app mode - generate changelog from root - APP_NAME="${{ github.event.repository.name }}" - echo "matrix=[{\"name\": \"${APP_NAME}\", \"working_dir\": \".\"}]" >> $GITHUB_OUTPUT - echo "has_changes=true" >> $GITHUB_OUTPUT - echo "๐Ÿ“ฆ Single app mode: ${APP_NAME}" - else - MATRIX='${{ steps.changed-paths.outputs.matrix }}' - if [ "$MATRIX" == "[]" ] || [ -z "$MATRIX" ]; then - echo "matrix=[]" >> $GITHUB_OUTPUT - echo "has_changes=false" >> $GITHUB_OUTPUT - echo "โš ๏ธ No changes detected in filter_paths" + # Detect trigger type: workflow_run or push:tags + if [[ "$GITHUB_REF" != refs/tags/* ]]; then + # Triggered by workflow_run - find apps from recent stable tags + echo "๐Ÿ“Œ Triggered by workflow_run, finding apps from recent stable tags..." + git fetch --tags + + # Get filter_paths as array + FILTER_PATHS="${{ inputs.filter_paths }}" + + if [ -z "$FILTER_PATHS" ]; then + # Single app mode + APP_NAME="${{ github.event.repository.name }}" + echo "matrix=[{\"name\": \"${APP_NAME}\", \"working_dir\": \".\"}]" >> $GITHUB_OUTPUT + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "๐Ÿ“ฆ Single app mode: ${APP_NAME}" else - echo "matrix=$MATRIX" >> $GITHUB_OUTPUT + # Monorepo mode - find apps with recent stable tags + MATRIX="[" + FIRST=true + + # Parse filter_paths to get app names + while IFS= read -r path; do + [ -z "$path" ] && continue + APP_NAME=$(basename "$path") + + # Check if this app has a recent stable tag + LATEST_TAG=$(git tag --sort=-creatordate | grep "^${APP_NAME}-v" | grep -v -E "-(beta|rc|alpha|dev|snapshot)" | head -1) + + if [ -n "$LATEST_TAG" ]; then + echo "๐Ÿ“ฆ Found stable tag for $APP_NAME: $LATEST_TAG" + if [ "$FIRST" = true ]; then + FIRST=false + else + MATRIX="$MATRIX," + fi + MATRIX="$MATRIX{\"name\": \"${APP_NAME}\", \"working_dir\": \"${path}\"}" + else + echo "โš ๏ธ No stable tag found for $APP_NAME" + fi + done <<< "$FILTER_PATHS" + + MATRIX="$MATRIX]" + + if [ "$MATRIX" = "[]" ]; then + echo "matrix=[]" >> $GITHUB_OUTPUT + echo "has_changes=false" >> $GITHUB_OUTPUT + echo "โš ๏ธ No apps with stable tags found" + else + echo "matrix=$MATRIX" >> $GITHUB_OUTPUT + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "๐Ÿ“ฆ Monorepo mode - found apps: $MATRIX" + fi + fi + else + # Triggered by tag push - use changed-paths + if [ -z "${{ inputs.filter_paths }}" ]; then + # Single app mode - generate changelog from root + APP_NAME="${{ github.event.repository.name }}" + echo "matrix=[{\"name\": \"${APP_NAME}\", \"working_dir\": \".\"}]" >> $GITHUB_OUTPUT echo "has_changes=true" >> $GITHUB_OUTPUT - echo "๐Ÿ“ฆ Monorepo mode - detected changes: $MATRIX" + echo "๐Ÿ“ฆ Single app mode: ${APP_NAME}" + else + MATRIX='${{ steps.changed-paths.outputs.matrix }}' + if [ "$MATRIX" == "[]" ] || [ -z "$MATRIX" ]; then + echo "matrix=[]" >> $GITHUB_OUTPUT + echo "has_changes=false" >> $GITHUB_OUTPUT + echo "โš ๏ธ No changes detected in filter_paths" + else + echo "matrix=$MATRIX" >> $GITHUB_OUTPUT + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "๐Ÿ“ฆ Monorepo mode - detected changes: $MATRIX" + fi fi fi From 63646f922adaca9fbdac6973bbfb2f004a8fb8df Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Sat, 20 Dec 2025 19:06:42 -0300 Subject: [PATCH 19/46] fix: use multiple -e patterns instead of -E for grep compatibility [skip ci] --- .github/workflows/gptchangelog.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index 0219dcd..e5c2858 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -188,7 +188,7 @@ jobs: APP_NAME=$(basename "$path") # Check if this app has a recent stable tag - LATEST_TAG=$(git tag --sort=-creatordate | grep "^${APP_NAME}-v" | grep -v -E "-(beta|rc|alpha|dev|snapshot)" | head -1) + LATEST_TAG=$(git tag --sort=-creatordate | grep "^${APP_NAME}-v" | grep -v -e "-beta" -e "-rc" -e "-alpha" -e "-dev" -e "-snapshot" | head -1) if [ -n "$LATEST_TAG" ]; then echo "๐Ÿ“ฆ Found stable tag for $APP_NAME: $LATEST_TAG" From ef96ce3e53324bc3f1593e3f0dcae1c9be4a9178 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Sat, 20 Dec 2025 19:09:14 -0300 Subject: [PATCH 20/46] fix: replace heredoc with echo commands to avoid while loop issues [skip ci] --- .github/workflows/gptchangelog.yml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index e5c2858..e70dedf 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -438,16 +438,17 @@ jobs: REPO_URL="https://github.com/${{ github.repository }}" TAG_NAME="${APP_NAME}-v${VERSION}" - cat > /tmp/new_entry.md << ENTRY_EOF ---- - -## [${VERSION}](${REPO_URL}/releases/tag/${TAG_NAME}) - -${CONTENT} - -[Compare changes](${REPO_URL}/compare/${APP_NAME}-v${PREV_VERSION}...${TAG_NAME}) - -ENTRY_EOF + # Create new entry using echo (heredoc has issues in while loop) + { + echo "---" + echo "" + echo "## [${VERSION}](${REPO_URL}/releases/tag/${TAG_NAME})" + echo "" + echo "${CONTENT}" + echo "" + echo "[Compare changes](${REPO_URL}/compare/${APP_NAME}-v${PREV_VERSION}...${TAG_NAME})" + echo "" + } > /tmp/new_entry.md # Update or create per-app CHANGELOG.md if [ -f "$CHANGELOG_PATH" ]; then From dac53cc7730630e39fa48c30d9b7a353da6af12f Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Sat, 20 Dec 2025 20:00:47 -0300 Subject: [PATCH 21/46] fix: improve changelog generation with path filtering and proper formatting - Filter commits by app path to only include relevant changes - Fix compare link for first releases (use 'View all changes' link) - Verify tag exists before processing - Create fresh changelog instead of appending to avoid duplicates - Use proper title casing for app names - Exclude beta/rc/alpha tags from processing [skip ci] --- .github/workflows/gptchangelog.yml | 155 +++++++++++++++-------------- 1 file changed, 81 insertions(+), 74 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index e70dedf..050e240 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -356,12 +356,11 @@ jobs: git fetch --tags --force MATRIX='${{ needs.prepare.outputs.matrix }}' + REPO_URL="https://github.com/${{ github.repository }}" echo "๐Ÿ“ฆ Processing apps from matrix: $MATRIX" # Initialize files - > /tmp/consolidated_changelog.md - > /tmp/consolidated_release_notes.md > /tmp/apps_updated.txt # Parse the matrix JSON and iterate through each app @@ -383,114 +382,122 @@ jobs: echo "๐Ÿ” Looking for tags matching: $TAG_PATTERN" - # Determine version range - if git describe --tags --abbrev=0 --match "$TAG_PATTERN" HEAD >/dev/null 2>&1; then - LAST_TAG=$(git describe --tags --abbrev=0 --match "$TAG_PATTERN" HEAD) - - if git describe --tags --abbrev=0 --match "$TAG_PATTERN" HEAD^ >/dev/null 2>&1; then - PENULT_TAG=$(git describe --tags --abbrev=0 --match "$TAG_PATTERN" HEAD^) - SINCE="$PENULT_TAG" - TO="$LAST_TAG" - echo "๐ŸŸข Range: $PENULT_TAG โ†’ $LAST_TAG" - else - FIRST_COMMIT=$(git rev-list --max-parents=0 HEAD) - SINCE="$FIRST_COMMIT" - TO="$LAST_TAG" - echo "๐ŸŸก First tag - Range: $FIRST_COMMIT โ†’ $LAST_TAG" - fi + # Find the latest STABLE tag for this app (exclude beta/rc/alpha) + LAST_TAG=$(git tag --sort=-version:refname | grep "^${APP_NAME}-v" | grep -v -e "-beta" -e "-rc" -e "-alpha" -e "-dev" -e "-snapshot" | head -1) + + if [ -z "$LAST_TAG" ]; then + echo "โš ๏ธ No stable tag found for $APP_NAME - skipping" + continue + fi + + echo "๐Ÿ“Œ Latest stable tag: $LAST_TAG" + + # Verify tag exists + if ! git rev-parse "$LAST_TAG" >/dev/null 2>&1; then + echo "โŒ Tag $LAST_TAG does not exist - skipping" + continue + fi + + # Find previous stable tag for compare link + PREV_TAG=$(git tag --sort=-version:refname | grep "^${APP_NAME}-v" | grep -v -e "-beta" -e "-rc" -e "-alpha" -e "-dev" -e "-snapshot" | sed -n '2p') + + if [ -n "$PREV_TAG" ]; then + SINCE="$PREV_TAG" + echo "๐ŸŸข Range: $PREV_TAG โ†’ $LAST_TAG" else - FIRST_COMMIT=$(git rev-list --max-parents=0 HEAD) - SINCE="$FIRST_COMMIT" - TO="HEAD" - LAST_TAG="HEAD" - echo "๐Ÿ”ด No tags - Range: $FIRST_COMMIT โ†’ HEAD" + # First stable release - use first commit + SINCE=$(git rev-list --max-parents=0 HEAD) + echo "๐ŸŸก First stable release - Range: first commit โ†’ $LAST_TAG" fi # Extract version from tag - VERSION=$(echo "$LAST_TAG" | sed 's/.*-v/v/' | sed 's/^v//') + VERSION=$(echo "$LAST_TAG" | sed 's/.*-v//') + + # Generate changelog using gptchangelog with path filter + TEMP_CHANGELOG=$(mktemp) + TEMP_COMMITS=$(mktemp) - # Extract previous version for compare link - if [ -n "$PENULT_TAG" ]; then - PREV_VERSION=$(echo "$PENULT_TAG" | sed 's/.*-v/v/' | sed 's/^v//') + # Get commits that touched this app's path (filtered by path) + if [ "$WORKING_DIR" != "." ]; then + git log --oneline "$SINCE".."$LAST_TAG" -- "$WORKING_DIR" > "$TEMP_COMMITS" 2>/dev/null || true else - PREV_VERSION="initial" + git log --oneline "$SINCE".."$LAST_TAG" > "$TEMP_COMMITS" 2>/dev/null || true fi - # Generate changelog for this app (run from repo root, gptchangelog needs git access) - TEMP_CHANGELOG=$(mktemp) + COMMIT_COUNT=$(wc -l < "$TEMP_COMMITS" | tr -d ' ') + echo "๐Ÿ“Š Found $COMMIT_COUNT commits for $APP_NAME" + + if [ "$COMMIT_COUNT" -eq 0 ]; then + echo "โš ๏ธ No commits found for $APP_NAME in range - skipping" + rm -f "$TEMP_CHANGELOG" "$TEMP_COMMITS" + continue + fi - if gptchangelog generate --since "$SINCE" --to "$TO" --output "$TEMP_CHANGELOG"; then + # Generate changelog (gptchangelog will use the commit range) + if gptchangelog generate --since "$SINCE" --to "$LAST_TAG" --output "$TEMP_CHANGELOG"; then # Clean up markdown blocks sed -i '' '/^```/d' "$TEMP_CHANGELOG" 2>/dev/null || sed -i '/^```/d' "$TEMP_CHANGELOG" - # Extract content (skip the header line if present) - CONTENT=$(cat "$TEMP_CHANGELOG" | tail -n +2) + # Extract content (skip any header lines) + CONTENT=$(cat "$TEMP_CHANGELOG" | grep -v "^#" | sed '/^$/N;/^\n$/d') - # Determine the changelog path (per-app folder or root) + if [ -z "$CONTENT" ]; then + echo "โš ๏ธ No content generated for $APP_NAME" + rm -f "$TEMP_CHANGELOG" "$TEMP_COMMITS" + continue + fi + + # Determine the changelog path if [ "$WORKING_DIR" != "." ]; then CHANGELOG_PATH="${WORKING_DIR}/CHANGELOG.md" else CHANGELOG_PATH="CHANGELOG.md" fi - # Prepare the new changelog entry for this app - # Get repo URL for links - REPO_URL="https://github.com/${{ github.repository }}" - TAG_NAME="${APP_NAME}-v${VERSION}" + # Build compare link + if [ -n "$PREV_TAG" ]; then + COMPARE_LINK="[Compare changes](${REPO_URL}/compare/${PREV_TAG}...${LAST_TAG})" + else + COMPARE_LINK="[View all changes](${REPO_URL}/commits/${LAST_TAG})" + fi - # Create new entry using echo (heredoc has issues in while loop) + # Create fresh changelog with proper format { + echo "# ${APP_NAME^} Changelog" + echo "" echo "---" echo "" - echo "## [${VERSION}](${REPO_URL}/releases/tag/${TAG_NAME})" + echo "## [${VERSION}](${REPO_URL}/releases/tag/${LAST_TAG})" echo "" - echo "${CONTENT}" + echo "$CONTENT" echo "" - echo "[Compare changes](${REPO_URL}/compare/${APP_NAME}-v${PREV_VERSION}...${TAG_NAME})" + echo "$COMPARE_LINK" echo "" - } > /tmp/new_entry.md + } > "$CHANGELOG_PATH" - # Update or create per-app CHANGELOG.md - if [ -f "$CHANGELOG_PATH" ]; then - # Insert after the title line - if grep -q "^# " "$CHANGELOG_PATH"; then - TITLE=$(head -n 1 "$CHANGELOG_PATH") - EXISTING=$(tail -n +2 "$CHANGELOG_PATH") - echo "$TITLE" > "$CHANGELOG_PATH" - echo "" >> "$CHANGELOG_PATH" - cat /tmp/new_entry.md >> "$CHANGELOG_PATH" - echo "$EXISTING" >> "$CHANGELOG_PATH" - else - cat /tmp/new_entry.md > /tmp/new_changelog.md - cat "$CHANGELOG_PATH" >> /tmp/new_changelog.md - mv /tmp/new_changelog.md "$CHANGELOG_PATH" - fi - else - echo "# ${APP_NAME} Changelog" > "$CHANGELOG_PATH" - echo "" >> "$CHANGELOG_PATH" - cat /tmp/new_entry.md >> "$CHANGELOG_PATH" - fi + echo "๐Ÿ“„ Created: $CHANGELOG_PATH" - echo "๐Ÿ“„ Updated: $CHANGELOG_PATH" - - # Track which apps were updated (with their paths) + # Track which apps were updated echo "${APP_NAME}:v${VERSION}:${WORKING_DIR}" >> /tmp/apps_updated.txt - # Update GitHub Release for this app's tag (if not HEAD) - if [ "$LAST_TAG" != "HEAD" ]; then - echo "## ${APP_NAME} v${VERSION}" > /tmp/app_release_notes.md - echo "" >> /tmp/app_release_notes.md - echo "$CONTENT" >> /tmp/app_release_notes.md - gh release edit "$LAST_TAG" --notes-file /tmp/app_release_notes.md || \ - echo "โš ๏ธ Could not update release for $LAST_TAG" - fi + # Update GitHub Release notes + { + echo "## ${APP_NAME^} v${VERSION}" + echo "" + echo "$CONTENT" + echo "" + echo "$COMPARE_LINK" + } > /tmp/app_release_notes.md + + gh release edit "$LAST_TAG" --notes-file /tmp/app_release_notes.md || \ + echo "โš ๏ธ Could not update release for $LAST_TAG" echo "โœ… Processed $APP_NAME" else echo "โš ๏ธ Failed to generate changelog for $APP_NAME" fi - rm -f "$TEMP_CHANGELOG" + rm -f "$TEMP_CHANGELOG" "$TEMP_COMMITS" done # Output results From 03c38385a5a3789c3009cd5300312b31906caed0 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Sat, 20 Dec 2025 23:23:22 -0300 Subject: [PATCH 22/46] fix: remove @semantic-release/changelog - using GPT Changelog instead [skip ci] --- .github/workflows/release.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 49477b7..371d60d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -141,8 +141,7 @@ jobs: working-directory: ${{ matrix.app.working_dir }} run: | npm install --save-dev \ - @semantic-release/exec \ - @semantic-release/changelog + @semantic-release/exec - name: Semantic Release uses: cycjimmy/semantic-release-action@v6 From 951a6396630f16f59677b6f802389e536b840fc1 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Sun, 21 Dec 2025 01:50:01 -0300 Subject: [PATCH 23/46] fix(gptchangelog): simplify sync to develop only and fix Slack notification - Remove release-candidate from sync targets (it gets updated from develop) - Simplify sync step to only create PR to develop branch - Fix Slack notification JSON payload (was breaking with newlines) - Change output from sync_prs to sync_pr (single URL) - Add proper condition to skip notify job if no sync PR [skip ci] --- .github/workflows/gptchangelog.yml | 93 +- .../plans/2025-12-12-gptchangelog-workflow.md | 1281 +++++++++++++++++ 2 files changed, 1317 insertions(+), 57 deletions(-) create mode 100644 docs/plans/2025-12-12-gptchangelog-workflow.md diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index 050e240..551d674 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -243,7 +243,7 @@ jobs: runs-on: ${{ inputs.runner_type }} name: Generate Consolidated Changelog outputs: - sync_prs: ${{ steps.sync.outputs.sync_prs }} + sync_pr: ${{ steps.sync.outputs.sync_pr }} steps: - name: Create GitHub App Token @@ -623,52 +623,37 @@ jobs: env: GH_TOKEN: ${{ steps.app-token.outputs.token }} - - name: Sync main to develop and RC branches + - name: Sync main to develop branch id: sync if: steps.generate.outputs.apps_updated != '' run: | DEFAULT_BRANCH=$(gh repo view --json defaultBranchRef -q '.defaultBranchRef.name') - SYNC_PRS="" + SYNC_PR_URL="" - # List of branches to sync to - SYNC_TARGETS="" + # Only sync to develop branch (release-candidate gets updated from develop) + TARGET_BRANCH="develop" # Check if develop branch exists - if git ls-remote --heads origin develop | grep -q develop; then - SYNC_TARGETS="develop" - echo "โœ… Found develop branch" + if ! git ls-remote --heads origin develop | grep -q develop; then + echo "โš ๏ธ No develop branch found - skipping sync" + echo "sync_pr=" >> $GITHUB_OUTPUT + exit 0 fi - # Check if release-candidate branch exists - if git ls-remote --heads origin release-candidate | grep -q release-candidate; then - SYNC_TARGETS="$SYNC_TARGETS release-candidate" - echo "โœ… Found release-candidate branch" - fi + echo "๐Ÿ“Œ Syncing $DEFAULT_BRANCH โ†’ $TARGET_BRANCH" - if [ -z "$SYNC_TARGETS" ]; then - echo "โš ๏ธ No sync target branches found (develop, release-candidate)" + # Check if PR already exists from main to develop + EXISTING_PR=$(gh pr list --base "$TARGET_BRANCH" --head "$DEFAULT_BRANCH" --json number -q '.[0].number' 2>/dev/null || true) + if [ -n "$EXISTING_PR" ]; then + echo "โš ๏ธ PR #$EXISTING_PR already exists for $DEFAULT_BRANCH โ†’ $TARGET_BRANCH" + echo "sync_pr=" >> $GITHUB_OUTPUT exit 0 fi - echo "๐Ÿ“Œ Will sync $DEFAULT_BRANCH to: $SYNC_TARGETS" - - for TARGET_BRANCH in $SYNC_TARGETS; do - echo "" - echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" - echo "๐Ÿ“Œ Syncing $DEFAULT_BRANCH โ†’ $TARGET_BRANCH" - echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" - - # Check if PR already exists from main to this target - EXISTING_PR=$(gh pr list --base "$TARGET_BRANCH" --head "$DEFAULT_BRANCH" --json number -q '.[0].number' 2>/dev/null || true) - if [ -n "$EXISTING_PR" ]; then - echo "โš ๏ธ PR #$EXISTING_PR already exists for $DEFAULT_BRANCH โ†’ $TARGET_BRANCH" - continue - fi - - # Create PR directly from main to target (no intermediate branch) - PR_URL=$(gh pr create \ - --title "chore: sync $DEFAULT_BRANCH to $TARGET_BRANCH [skip ci]" \ - --body "## Automatic Changelog Sync + # Create PR directly from main to develop + PR_URL=$(gh pr create \ + --title "chore: sync $DEFAULT_BRANCH to $TARGET_BRANCH [skip ci]" \ + --body "## Automatic Changelog Sync Syncs changelog updates from \`$DEFAULT_BRANCH\` to \`$TARGET_BRANCH\`. @@ -677,20 +662,15 @@ jobs: --- *This PR was automatically generated by the GPTChangelog workflow.* *Please review and merge manually.*" \ - --base "$TARGET_BRANCH" \ - --head "$DEFAULT_BRANCH" 2>&1) || { - echo "โš ๏ธ Could not create PR for $TARGET_BRANCH: $PR_URL" - continue - } - - echo "โœ… Created PR: $PR_URL" - SYNC_PRS="${SYNC_PRS}${PR_URL}\n" - done + --base "$TARGET_BRANCH" \ + --head "$DEFAULT_BRANCH" 2>&1) || { + echo "โš ๏ธ Could not create PR: $PR_URL" + echo "sync_pr=" >> $GITHUB_OUTPUT + exit 0 + } - # Output PR URLs for Slack notification - echo "sync_prs<> $GITHUB_OUTPUT - echo -e "$SYNC_PRS" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT + echo "โœ… Created PR: $PR_URL" + echo "sync_pr=$PR_URL" >> $GITHUB_OUTPUT env: GH_TOKEN: ${{ steps.app-token.outputs.token }} @@ -715,15 +695,14 @@ jobs: secrets: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - # Notify about sync PRs that need manual review - notify-sync-prs: - name: Notify Sync PRs + # Notify about sync PR that needs manual review + notify-sync-pr: + name: Notify Sync PR needs: [generate_changelog] - if: needs.generate_changelog.result == 'success' + if: needs.generate_changelog.result == 'success' && needs.generate_changelog.outputs.sync_pr != '' runs-on: ubuntu-latest steps: - - name: Send Slack notification for sync PRs - if: needs.generate_changelog.outputs.sync_prs != '' + - name: Send Slack notification for sync PR uses: slackapi/slack-github-action@v1.24.0 with: payload: | @@ -733,7 +712,7 @@ jobs: "type": "header", "text": { "type": "plain_text", - "text": "๐Ÿ“‹ Changelog Sync PRs Need Review", + "text": "Changelog Sync PR Needs Review", "emoji": true } }, @@ -741,14 +720,14 @@ jobs: "type": "section", "text": { "type": "mrkdwn", - "text": "*Repository:* ${{ github.repository }}\n*Triggered by:* ${{ github.ref_name }}\n\nThe following PRs were created to sync changelog updates and need manual review:" + "text": "*Repository:* ${{ github.repository }}\n*Triggered by:* ${{ github.ref_name }}\n\nA PR was created to sync changelog updates to develop:" } }, { "type": "section", "text": { "type": "mrkdwn", - "text": "${{ needs.generate_changelog.outputs.sync_prs }}" + "text": "<${{ needs.generate_changelog.outputs.sync_pr }}|View PR>" } }, { @@ -756,7 +735,7 @@ jobs: "elements": [ { "type": "mrkdwn", - "text": "Please review and merge these PRs to sync changelog updates to develop and RC branches." + "text": "Please review and merge this PR to sync changelog updates to develop branch." } ] } diff --git a/docs/plans/2025-12-12-gptchangelog-workflow.md b/docs/plans/2025-12-12-gptchangelog-workflow.md new file mode 100644 index 0000000..6329e2c --- /dev/null +++ b/docs/plans/2025-12-12-gptchangelog-workflow.md @@ -0,0 +1,1281 @@ +# GPTChangelog Workflow Implementation Plan + +> **For Agents:** REQUIRED SUB-SKILL: Use ring-default:executing-plans to implement this plan task-by-task. + +**Goal:** Create a reusable workflow that generates a consolidated CHANGELOG.md and RELEASE_NOTES.md using GPTChangelog, with full monorepo support for any multi-app repositories (helm charts, microservices, etc.). + +**Architecture:** The workflow follows the established shared workflows pattern with a `prepare` โ†’ `main_job` โ†’ `notify` structure. For monorepos, it uses the `LerianStudio/github-actions-changed-paths` action to detect which apps changed and generates a **single consolidated changelog** with sections per app. For single-app repos, it generates a changelog for the entire repository. + +**Key Design Decision - Consolidated Changelog:** +- Single CHANGELOG.md at repository root (not per-app files) +- When multiple apps change, each app gets its own section in the changelog +- Prevents overwrite issues when multiple apps change simultaneously +- Works generically with any directory structure (charts, apps, services, etc.) + +**Tech Stack:** GitHub Actions reusable workflow, Python 3.10, gptchangelog PyPI package, OpenAI GPT-4o, GPG signing, GitHub App authentication + +**Global Prerequisites:** +- Environment: GitHub Actions runner (blacksmith default) +- Tools: Git, Python 3.10+, pip +- Access: OpenAI API key, GitHub App credentials, GPG key for signing +- State: Feature branch created from `develop` branch + +**Verification before starting:** +```bash +# Navigate to the shared-workflows repository +cd /Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows + +# Verify clean git state +git status # Expected: clean working tree or known changes + +# Verify on develop branch +git branch --show-current # Expected: develop or main + +# Verify docs/plans directory exists +ls docs/plans/ # Expected: directory exists with existing plans +``` + +--- + +## Task 1: Create Feature Branch + +**Files:** +- None (git operation) + +**Prerequisites:** +- Tools: Git +- Current directory: `/Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows` + +**Step 1: Fetch latest changes** + +```bash +cd /Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows +git fetch origin +``` + +**Step 2: Create feature branch from develop** + +```bash +git checkout develop +git pull origin develop +git checkout -b feature/gptchangelog-workflow +``` + +**Expected output:** +``` +Switched to a new branch 'feature/gptchangelog-workflow' +``` + +**Step 3: Verify branch creation** + +Run: `git branch --show-current` + +**Expected output:** +``` +feature/gptchangelog-workflow +``` + +**If Task Fails:** + +1. **Branch already exists:** + - Run: `git branch -D feature/gptchangelog-workflow` + - Retry branch creation + +2. **develop branch doesn't exist:** + - Run: `git checkout main` instead + - Continue from main branch + +--- + +## Task 2: Create the GPTChangelog Workflow File + +**Files:** +- Create: `/Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows/.github/workflows/gptchangelog.yml` + +**Prerequisites:** +- Tools: Text editor +- Branch: `feature/gptchangelog-workflow` +- Files must exist: `.github/workflows/` directory + +**Step 1: Create the workflow file** + +Create the file `.github/workflows/gptchangelog.yml` with the following content: + +```yaml +name: "GPT Changelog" + +# This reusable workflow generates CHANGELOG.md and RELEASE_NOTES.md using GPTChangelog +# It uses OpenAI GPT-4o to analyze commits and generate human-readable changelogs +# +# Monorepo Support: +# - If filter_paths is provided: detects changes and generates changelog for each changed app +# - If filter_paths is empty: generates changelog for the entire repository (single app mode) +# +# Features: +# - Generates CHANGELOG.md with categorized changes +# - Generates RELEASE_NOTES.md and updates GitHub Release +# - Creates PR with changelog update using GPG-signed commits +# - Handles tag-based versioning (between tags, first tag, no tags scenarios) + +on: + workflow_call: + inputs: + runner_type: + description: 'Runner to use for the workflow' + type: string + default: 'blacksmith' + filter_paths: + description: 'Newline-separated list of path prefixes to filter. If not provided, treats as single app repo.' + type: string + required: false + default: '' + path_level: + description: 'Limits the path to the first N segments (e.g., 2 -> "charts/agent")' + type: string + default: '2' + openai_model: + description: 'OpenAI model to use for changelog generation' + type: string + default: 'gpt-4o' + max_context_tokens: + description: 'Maximum context tokens for OpenAI API' + type: string + default: '80000' + python_version: + description: 'Python version to use' + type: string + default: '3.10' + +permissions: + contents: write + pull-requests: write + +jobs: + prepare: + runs-on: ${{ inputs.runner_type }} + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + has_changes: ${{ steps.set-matrix.outputs.has_changes }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get changed paths (monorepo) + if: inputs.filter_paths != '' + id: changed-paths + uses: LerianStudio/github-actions-changed-paths@main + with: + filter_paths: ${{ inputs.filter_paths }} + path_level: ${{ inputs.path_level }} + get_app_name: 'true' + + - name: Set matrix + id: set-matrix + run: | + if [ -z "${{ inputs.filter_paths }}" ]; then + # Single app mode - generate changelog from root + APP_NAME="${{ github.event.repository.name }}" + echo "matrix=[{\"name\": \"${APP_NAME}\", \"working_dir\": \".\"}]" >> $GITHUB_OUTPUT + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "๐Ÿ“ฆ Single app mode: ${APP_NAME}" + else + MATRIX='${{ steps.changed-paths.outputs.matrix }}' + if [ "$MATRIX" == "[]" ] || [ -z "$MATRIX" ]; then + echo "matrix=[]" >> $GITHUB_OUTPUT + echo "has_changes=false" >> $GITHUB_OUTPUT + echo "โš ๏ธ No changes detected in filter_paths" + else + echo "matrix=$MATRIX" >> $GITHUB_OUTPUT + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "๐Ÿ“ฆ Monorepo mode - detected changes: $MATRIX" + fi + fi + + generate_changelog: + needs: prepare + if: needs.prepare.outputs.has_changes == 'true' + runs-on: ${{ inputs.runner_type }} + name: Generate Consolidated Changelog + + steps: + - name: Create GitHub App Token + uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ secrets.LERIAN_STUDIO_MIDAZ_PUSH_BOT_APP_ID }} + private-key: ${{ secrets.LERIAN_STUDIO_MIDAZ_PUSH_BOT_PRIVATE_KEY }} + + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} + + - name: Sync with remote branch + run: | + git fetch origin ${{ github.ref_name }} + git reset --hard origin/${{ github.ref_name }} + + - name: Import GPG key + uses: crazy-max/ghaction-import-gpg@v6 + id: import_gpg + with: + gpg_private_key: ${{ secrets.LERIAN_CI_CD_USER_GPG_KEY }} + passphrase: ${{ secrets.LERIAN_CI_CD_USER_GPG_KEY_PASSWORD }} + git_committer_name: ${{ secrets.LERIAN_CI_CD_USER_NAME }} + git_committer_email: ${{ secrets.LERIAN_CI_CD_USER_EMAIL }} + git_config_global: true + git_user_signingkey: true + git_commit_gpgsign: true + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: ${{ inputs.python_version }} + + - name: Install gptchangelog + run: | + python -m pip install --upgrade pip + pip install gptchangelog + echo "โœ… gptchangelog installed successfully" + + - name: Create gptchangelog config + run: | + mkdir -p .gptchangelog + cat > .gptchangelog/config.ini << EOF + [gptchangelog] + openai = true + + [openai] + api_key = ${OPENAI_API_KEY} + model = ${{ inputs.openai_model }} + max_context_tokens = ${{ inputs.max_context_tokens }} + EOF + echo "โœ… gptchangelog config created" + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + + - name: Create changelog prompt template + run: | + mkdir -p .gptchangelog/templates + cat > .gptchangelog/templates/changelog_prompt.txt << 'EOF' + ## Release $next_version (Released on $current_date) + + ### What's New + $commit_messages + + ### โœจ Features + - Highlight new features added in this release. + + ### ๐Ÿ›  Fixes + - List bug fixes and improvements. + + ### ๐Ÿ“š Documentation + - Summarize updates to documentation. + + ### ๐Ÿš€ Improvements + - Highlight performance or backend optimizations. + + ### โš ๏ธ Breaking Changes + - List any breaking changes here. + + ### ๐Ÿ™Œ Contributors + - Acknowledge contributors for this release. + EOF + echo "โœ… Changelog prompt template created" + + - name: Generate consolidated changelog for all apps + id: generate + run: | + echo "OPENAI_API_KEY=${OPENAI_API_KEY}" > .env + git fetch --tags + + MATRIX='${{ needs.prepare.outputs.matrix }}' + CURRENT_DATE=$(date +%Y-%m-%d) + CONSOLIDATED_CHANGELOG="" + CONSOLIDATED_RELEASE_NOTES="" + APPS_UPDATED="" + + echo "๐Ÿ“ฆ Processing apps from matrix: $MATRIX" + + # Parse the matrix JSON and iterate through each app + echo "$MATRIX" | jq -c '.[]' | while read -r APP; do + APP_NAME=$(echo "$APP" | jq -r '.name') + WORKING_DIR=$(echo "$APP" | jq -r '.working_dir') + + echo "" + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + echo "๐Ÿ“ Processing: $APP_NAME (dir: $WORKING_DIR)" + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + + # Determine tag pattern based on app type + if [ "$WORKING_DIR" != "." ]; then + TAG_PATTERN="${APP_NAME}-v*" + else + TAG_PATTERN="v*" + fi + + echo "๐Ÿ” Looking for tags matching: $TAG_PATTERN" + + # Determine version range + if git describe --tags --abbrev=0 --match "$TAG_PATTERN" HEAD >/dev/null 2>&1; then + LAST_TAG=$(git describe --tags --abbrev=0 --match "$TAG_PATTERN" HEAD) + + if git describe --tags --abbrev=0 --match "$TAG_PATTERN" HEAD^ >/dev/null 2>&1; then + PENULT_TAG=$(git describe --tags --abbrev=0 --match "$TAG_PATTERN" HEAD^) + SINCE="$PENULT_TAG" + TO="$LAST_TAG" + echo "๐ŸŸข Range: $PENULT_TAG โ†’ $LAST_TAG" + else + FIRST_COMMIT=$(git rev-list --max-parents=0 HEAD) + SINCE="$FIRST_COMMIT" + TO="$LAST_TAG" + echo "๐ŸŸก First tag - Range: $FIRST_COMMIT โ†’ $LAST_TAG" + fi + else + FIRST_COMMIT=$(git rev-list --max-parents=0 HEAD) + SINCE="$FIRST_COMMIT" + TO="HEAD" + LAST_TAG="HEAD" + echo "๐Ÿ”ด No tags - Range: $FIRST_COMMIT โ†’ HEAD" + fi + + # Extract version from tag + VERSION=$(echo "$LAST_TAG" | sed 's/.*-v/v/' | sed 's/^v//') + + # Generate changelog for this app + TEMP_CHANGELOG=$(mktemp) + cd "$WORKING_DIR" 2>/dev/null || cd . + + gptchangelog generate \ + --since "$SINCE" \ + --to "$TO" \ + --output "$TEMP_CHANGELOG" || { + echo "โš ๏ธ Failed to generate changelog for $APP_NAME" + continue + } + + # Clean up markdown blocks + sed -i '/^```/d' "$TEMP_CHANGELOG" + + # Extract content (skip the header line if present) + CONTENT=$(cat "$TEMP_CHANGELOG" | tail -n +2) + + # Build app section for consolidated changelog + APP_SECTION="### ${APP_NAME} v${VERSION} + +${CONTENT} +" + # Append to consolidated files + echo "$APP_SECTION" >> /tmp/consolidated_changelog.md + echo "$APP_SECTION" >> /tmp/consolidated_release_notes.md + + # Track which apps were updated + echo "${APP_NAME}:${VERSION}" >> /tmp/apps_updated.txt + + # Update GitHub Release for this app's tag (if not HEAD) + if [ "$LAST_TAG" != "HEAD" ]; then + echo "$APP_SECTION" > /tmp/app_release_notes.md + gh release edit "$LAST_TAG" --notes-file /tmp/app_release_notes.md || \ + echo "โš ๏ธ Could not update release for $LAST_TAG" + fi + + cd - >/dev/null 2>&1 || true + rm -f "$TEMP_CHANGELOG" + + echo "โœ… Processed $APP_NAME" + done + + # Create final consolidated CHANGELOG.md + if [ -f /tmp/consolidated_changelog.md ]; then + APPS_LIST=$(cat /tmp/apps_updated.txt 2>/dev/null | tr '\n' ', ' | sed 's/,$//') + + # Prepare the new changelog entry + NEW_ENTRY="## [$CURRENT_DATE] + +$( cat /tmp/consolidated_changelog.md ) +--- +" + # Prepend to existing CHANGELOG.md or create new one + if [ -f CHANGELOG.md ]; then + # Insert after the title line + if grep -q "^# " CHANGELOG.md; then + TITLE=$(head -n 1 CHANGELOG.md) + EXISTING=$(tail -n +2 CHANGELOG.md) + echo "$TITLE + +$NEW_ENTRY +$EXISTING" > CHANGELOG.md + else + echo "# Changelog + +$NEW_ENTRY +$(cat CHANGELOG.md)" > CHANGELOG.md + fi + else + echo "# Changelog + +$NEW_ENTRY" > CHANGELOG.md + fi + + # Create RELEASE_NOTES.md + echo "# Release Notes - $CURRENT_DATE + +$(cat /tmp/consolidated_release_notes.md)" > RELEASE_NOTES.md + + echo "apps_updated=$APPS_LIST" >> $GITHUB_OUTPUT + echo "โœ… Consolidated changelog created with apps: $APPS_LIST" + else + echo "โš ๏ธ No changelog content generated" + echo "apps_updated=" >> $GITHUB_OUTPUT + fi + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} + + - name: Show generated changelog + run: | + echo "๐Ÿ“„ Generated CHANGELOG.md:" + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + head -100 CHANGELOG.md + echo "" + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + + - name: Create changelog PR + if: steps.generate.outputs.apps_updated != '' + run: | + BASE_BRANCH="${GITHUB_REF##*/}" + CURRENT_DATE=$(date +%Y-%m-%d) + BRANCH_NAME="release/update-changelog-${CURRENT_DATE}" + APPS_UPDATED="${{ steps.generate.outputs.apps_updated }}" + + echo "๐Ÿ“Œ Creating branch: $BRANCH_NAME" + git checkout -b "$BRANCH_NAME" + + # Add and commit CHANGELOG + git add CHANGELOG.md + if ! git diff --cached --quiet; then + git commit -S -m "chore(release): Update CHANGELOG for ${APPS_UPDATED}" + echo "โœ… CHANGELOG committed" + else + echo "โš ๏ธ No changes to CHANGELOG" + exit 0 + fi + + # Merge base branch to resolve conflicts + git fetch origin "$BASE_BRANCH" + git merge -X ours origin/"$BASE_BRANCH" --no-ff -m "Merge $BASE_BRANCH into ${BRANCH_NAME}" || { + git checkout --ours CHANGELOG.md + git add CHANGELOG.md + git commit -m "resolve conflict using ours strategy" + } + + # Push and create PR + git push --force-with-lease origin "$BRANCH_NAME" + + if ! gh pr view "$BRANCH_NAME" --base "$BASE_BRANCH" > /dev/null 2>&1; then + gh pr create \ + --title "chore(release): Update CHANGELOG - ${CURRENT_DATE}" \ + --body "## Automatic Changelog Update + +**Date:** ${CURRENT_DATE} +**Apps Updated:** ${APPS_UPDATED} + +### Changes +- Updated CHANGELOG.md with consolidated release notes +- Each app section generated by GPTChangelog using OpenAI GPT-4o + +### Apps Included +$(echo "$APPS_UPDATED" | tr ',' '\n' | sed 's/^/- /') + +--- +*This PR was automatically generated by the GPTChangelog workflow.*" \ + --base "$BASE_BRANCH" \ + --head "$BRANCH_NAME" + echo "โœ… PR created" + else + echo "โš ๏ธ PR already exists" + fi + + # Auto-merge if possible + gh pr merge --merge --delete-branch || echo "โš ๏ธ Could not auto-merge PR" + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + + - name: Cleanup sensitive files + if: always() + run: | + rm -f .env + rm -rf .gptchangelog + rm -f /tmp/consolidated_changelog.md /tmp/consolidated_release_notes.md /tmp/apps_updated.txt /tmp/app_release_notes.md + echo "๐Ÿงน Cleaned up sensitive files" + + # Slack notification + notify: + name: Notify + needs: [prepare, generate_changelog] + if: always() && needs.prepare.outputs.has_changes == 'true' + uses: ./.github/workflows/slack-notify.yml + with: + status: ${{ needs.generate_changelog.result }} + workflow_name: "GPT Changelog" + failed_jobs: ${{ needs.generate_changelog.result == 'failure' && 'Generate Changelog' || '' }} + secrets: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} +``` + +**Step 2: Verify file was created** + +Run: `ls -la /Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows/.github/workflows/gptchangelog.yml` + +**Expected output:** +``` +-rw-r--r-- 1 user staff XXXX Dec XX XX:XX gptchangelog.yml +``` + +**If Task Fails:** + +1. **Directory doesn't exist:** + - Run: `mkdir -p .github/workflows` + - Retry file creation + +2. **Permission denied:** + - Check file permissions + - Run with appropriate permissions + +--- + +## Task 3: Create the Documentation File + +**Files:** +- Create: `/Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows/docs/gptchangelog-workflow.md` + +**Prerequisites:** +- Tools: Text editor +- Branch: `feature/gptchangelog-workflow` +- Files must exist: `docs/` directory + +**Step 1: Create the documentation file** + +Create the file `docs/gptchangelog-workflow.md` with the following content: + +```markdown +# GPT Changelog Workflow + +Reusable workflow for generating CHANGELOG.md and RELEASE_NOTES.md using GPTChangelog. Uses OpenAI GPT-4o to analyze commits and generate human-readable, categorized changelogs. + +## Features + +- **AI-powered changelog generation**: Uses OpenAI GPT-4o for intelligent commit analysis +- **Monorepo support**: Automatic detection of changed components via filter_paths +- **GitHub Release integration**: Automatically updates release notes +- **GPG signing**: Signed commits for changelog PRs +- **Tag-based versioning**: Handles between-tags, first-tag, and no-tags scenarios +- **Automatic PR creation**: Creates and optionally auto-merges changelog PRs +- **Slack notifications**: Automatic success/failure notifications + +## Usage + +### Single App Repository + +```yaml +name: Generate Changelog +on: + push: + tags: + - 'v*' + +jobs: + changelog: + uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main + with: + runner_type: "blacksmith" + secrets: inherit +``` + +**Output (single app):** +```markdown +# Changelog + +## [2025-12-12] + +### my-app v1.2.0 + +#### โœจ Features +- Added new authentication flow +- Implemented caching layer + +#### ๐Ÿ›  Fixes +- Fixed memory leak in worker process + +--- + +## [2025-12-01] +... +``` + +### Monorepo with Multiple Components (e.g., Helm Charts, Microservices) + +```yaml +name: Generate Changelog +on: + push: + tags: + - '**' + +jobs: + changelog: + uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main + with: + runner_type: "blacksmith" + filter_paths: |- + charts/agent + charts/control-plane + charts/midaz + charts/plugin-access-manager + charts/plugin-crm + charts/plugin-fees + charts/reporter + path_level: '2' + secrets: inherit +``` + +**Output (monorepo with multiple apps changed):** +```markdown +# Changelog + +## [2025-12-12] + +### agent v1.2.0 + +#### โœจ Features +- Added new metric collection endpoint + +#### ๐Ÿ›  Fixes +- Fixed reconnection logic + +### midaz v2.1.0 + +#### โœจ Features +- New transaction batching API + +#### ๐Ÿš€ Improvements +- Optimized database queries + +### control-plane v1.5.0 + +#### ๐Ÿ›  Fixes +- Fixed race condition in scheduler + +--- + +## [2025-12-01] +... +``` + +**Key Benefit:** All apps are included in ONE CHANGELOG.md file - no more overwrites when multiple apps change! + +### After Release Workflow + +```yaml +name: Release Pipeline +on: + push: + branches: + - main + +jobs: + release: + uses: LerianStudio/github-actions-shared-workflows/.github/workflows/release.yml@main + secrets: inherit + + changelog: + needs: release + uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main + with: + runner_type: "blacksmith" + secrets: inherit +``` + +## Inputs + +| Input | Type | Default | Description | +|-------|------|---------|-------------| +| `runner_type` | string | `blacksmith` | GitHub runner type | +| `filter_paths` | string | `''` | Newline-separated list of path prefixes. If empty, single-app mode | +| `path_level` | string | `2` | Directory depth for app name extraction | +| `openai_model` | string | `gpt-4o` | OpenAI model for changelog generation | +| `max_context_tokens` | string | `80000` | Maximum context tokens for OpenAI API | +| `python_version` | string | `3.10` | Python version to use | + +## Secrets + +### Required Secrets + +| Secret | Description | +|--------|-------------| +| `OPENAI_API_KEY` | OpenAI API key for GPT-4o access | +| `LERIAN_STUDIO_MIDAZ_PUSH_BOT_APP_ID` | GitHub App ID for authentication | +| `LERIAN_STUDIO_MIDAZ_PUSH_BOT_PRIVATE_KEY` | GitHub App private key | +| `LERIAN_CI_CD_USER_GPG_KEY` | GPG private key for signing commits | +| `LERIAN_CI_CD_USER_GPG_KEY_PASSWORD` | GPG key passphrase | +| `LERIAN_CI_CD_USER_NAME` | Git committer name | +| `LERIAN_CI_CD_USER_EMAIL` | Git committer email | + +### Optional Secrets + +| Secret | Description | +|--------|-------------| +| `SLACK_WEBHOOK_URL` | Slack webhook for notifications (gracefully skipped if not provided) | + +## How It Works + +### Consolidated Changelog Architecture + +Unlike traditional matrix-based approaches where each app generates its own changelog (causing overwrites), this workflow uses a **single-job consolidated approach**: + +1. **Detect all changed apps** via `changed-paths` action +2. **Single job iterates** through all changed apps +3. **Accumulates changelog entries** per app into one consolidated file +4. **Creates one PR** with all changes + +**Result:** One CHANGELOG.md at repo root with sections for each app that changed. + +### Version Range Detection + +The workflow automatically determines the commit range for changelog generation: + +| Scenario | Range | Example | +|----------|-------|---------| +| Two or more tags | Previous tag โ†’ Current tag | `v1.0.0...v1.1.0` | +| First tag | First commit โ†’ Current tag | `abc123...v1.0.0` | +| No tags | First commit โ†’ HEAD | `abc123...HEAD` | + +### Monorepo Tag Patterns + +For monorepos, the workflow supports app-specific tags: + +| App | Tag Pattern | Example | +|-----|-------------|---------| +| agent | `agent-v*` | `agent-v1.0.0` | +| control-plane | `control-plane-v*` | `control-plane-v2.1.0` | + +This works with **any directory structure** - not just charts. Examples: +- `apps/api`, `apps/worker` โ†’ tags: `api-v1.0.0`, `worker-v2.0.0` +- `services/auth`, `services/billing` โ†’ tags: `auth-v1.0.0`, `billing-v1.5.0` +- `packages/core`, `packages/utils` โ†’ tags: `core-v3.0.0`, `utils-v1.2.0` + +### Generated Files + +| File | Description | +|------|-------------| +| `CHANGELOG.md` | **Single consolidated** changelog with sections per app | +| `RELEASE_NOTES.md` | Release-specific notes (also consolidated) | + +### Changelog Structure + +```markdown +# Changelog + +## [2025-12-12] + +### app-1 v1.2.0 +- Changes for app-1... + +### app-2 v2.0.0 +- Changes for app-2... + +--- + +## [2025-12-01] +- Previous release entries... +``` + +### Changelog Categories + +GPTChangelog organizes commits into these categories: +- โœจ **Features**: New features added +- ๐Ÿ›  **Fixes**: Bug fixes and improvements +- ๐Ÿ“š **Documentation**: Documentation updates +- ๐Ÿš€ **Improvements**: Performance or backend optimizations +- โš ๏ธ **Breaking Changes**: Breaking changes +- ๐Ÿ™Œ **Contributors**: Acknowledgments + +## Workflow Jobs + +### prepare +- Detects changed paths (monorepo) or sets single-app mode +- Outputs matrix for changelog generation job + +### generate_changelog +- Installs gptchangelog and dependencies +- Generates CHANGELOG.md and RELEASE_NOTES.md +- Updates GitHub Release with release notes +- Creates PR with changelog update (GPG-signed) +- Auto-merges PR if possible + +### notify +- Sends Slack notification on completion +- Skipped if `SLACK_WEBHOOK_URL` not configured + +## Best Practices + +### 1. Trigger After Release + +Run changelog generation after the release workflow: + +```yaml +changelog: + needs: release + uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main +``` + +### 2. Use Conventional Commits + +GPTChangelog works best with conventional commits: +- `feat:` - New features +- `fix:` - Bug fixes +- `docs:` - Documentation +- `perf:` - Performance improvements + +### 3. Configure Slack Notifications + +Add `SLACK_WEBHOOK_URL` secret for team notifications. + +### 4. Review Generated Changelogs + +The workflow creates PRs for review before merging. Disable auto-merge if manual review is required. + +## Troubleshooting + +### No changelog generated + +**Issue**: Workflow runs but no CHANGELOG.md is created + +**Solutions**: +1. Check OpenAI API key is valid +2. Verify tag format matches expected pattern +3. Check if there are commits in the version range +4. Review workflow logs for gptchangelog errors + +### Version header not updated + +**Issue**: CHANGELOG shows wrong version + +**Solutions**: +1. Verify tag format (should include version number) +2. Check sed command output in logs +3. Ensure CHANGELOG has standard version header format + +### PR not created + +**Issue**: Changelog generated but PR fails + +**Solutions**: +1. Verify GitHub App has `contents: write` and `pull-requests: write` permissions +2. Check if branch already exists +3. Review PR creation step logs + +### OpenAI API errors + +**Issue**: gptchangelog fails with API errors + +**Solutions**: +1. Verify `OPENAI_API_KEY` is set correctly +2. Check API rate limits +3. Try reducing `max_context_tokens` +4. Ensure model name is valid (`gpt-4o`) + +### Monorepo changes not detected + +**Issue**: No apps in matrix for monorepo + +**Solutions**: +1. Verify `filter_paths` matches your directory structure +2. Check `path_level` is correct +3. Ensure changes are in tracked paths +4. Review changed-paths action output + +## Examples + +### Basic Single App + +```yaml +name: Changelog +on: + push: + tags: ['v*'] + +jobs: + changelog: + uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main + secrets: inherit +``` + +### Helm Charts Monorepo + +```yaml +name: Changelog +on: + push: + tags: ['**'] + +jobs: + changelog: + uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main + with: + filter_paths: |- + charts/agent + charts/control-plane + charts/midaz + charts/reporter + path_level: '2' + secrets: inherit +``` + +### Custom OpenAI Model + +```yaml +changelog: + uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main + with: + openai_model: 'gpt-4-turbo' + max_context_tokens: '128000' + secrets: inherit +``` + +## Related Workflows + +- [Release](release-workflow.md) - Create releases that trigger changelog generation +- [Build](build-workflow.md) - Build Docker images after release +- [Slack Notify](slack-notify-workflow.md) - Notification system + +--- + +**Last Updated:** 2025-12-12 +**Version:** 1.0.0 +``` + +**Step 2: Verify file was created** + +Run: `ls -la /Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows/docs/gptchangelog-workflow.md` + +**Expected output:** +``` +-rw-r--r-- 1 user staff XXXX Dec XX XX:XX gptchangelog-workflow.md +``` + +**If Task Fails:** + +1. **Directory doesn't exist:** + - Run: `mkdir -p docs` + - Retry file creation + +--- + +## Task 4: Update README.md with New Workflow + +**Files:** +- Modify: `/Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows/README.md` + +**Prerequisites:** +- Tools: Text editor +- Branch: `feature/gptchangelog-workflow` +- Files must exist: `README.md` + +**Step 1: Read the current README.md** + +Run: `cat /Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows/README.md` + +**Step 2: Add the new workflow entry to the Available Workflows section** + +Find the line `### 12. [Slack Notify]` and add the following entry AFTER it (as item 13): + +```markdown +### 13. [GPT Changelog](docs/gptchangelog-workflow.md) +AI-powered changelog generation using GPTChangelog and OpenAI GPT-4o. + +**Key Features**: AI commit analysis, monorepo support, GitHub Release integration, GPG signing +``` + +**Step 3: Verify the update** + +Run: `grep -A3 "GPT Changelog" /Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows/README.md` + +**Expected output:** +``` +### 13. [GPT Changelog](docs/gptchangelog-workflow.md) +AI-powered changelog generation using GPTChangelog and OpenAI GPT-4o. + +**Key Features**: AI commit analysis, monorepo support, GitHub Release integration, GPG signing +``` + +**If Task Fails:** + +1. **Pattern not found:** + - Check for alternate section names + - Manually locate the workflow list section + - Add entry at appropriate location + +--- + +## Task 5: Commit All Changes + +**Files:** +- All files created/modified in previous tasks + +**Prerequisites:** +- Tools: Git +- Branch: `feature/gptchangelog-workflow` +- All files created successfully + +**Step 1: Check status of changes** + +Run: `cd /Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows && git status` + +**Expected output:** +``` +On branch feature/gptchangelog-workflow +Changes not staged for commit: + modified: README.md + +Untracked files: + .github/workflows/gptchangelog.yml + docs/gptchangelog-workflow.md +``` + +**Step 2: Stage all changes** + +```bash +git add .github/workflows/gptchangelog.yml +git add docs/gptchangelog-workflow.md +git add README.md +``` + +**Step 3: Commit changes** + +```bash +git commit -m "feat: add GPTChangelog reusable workflow + +- Add gptchangelog.yml reusable workflow with monorepo support +- Add comprehensive documentation in docs/gptchangelog-workflow.md +- Update README.md with new workflow entry + +Features: +- AI-powered changelog generation using OpenAI GPT-4o +- Monorepo support via filter_paths and path_level +- GitHub Release notes integration +- GPG-signed commits and PRs +- Slack notification integration +- Blacksmith runner as default" +``` + +**Expected output:** +``` +[feature/gptchangelog-workflow XXXXXXX] feat: add GPTChangelog reusable workflow + 3 files changed, XXX insertions(+) + create mode 100644 .github/workflows/gptchangelog.yml + create mode 100644 docs/gptchangelog-workflow.md +``` + +**Step 4: Verify commit** + +Run: `git log --oneline -1` + +**Expected output:** +``` +XXXXXXX feat: add GPTChangelog reusable workflow +``` + +**If Task Fails:** + +1. **Nothing to commit:** + - Verify files were created correctly + - Check git status for untracked files + +2. **GPG signing required but fails:** + - Use `git commit --no-gpg-sign` for local testing + - Fix GPG setup before pushing + +--- + +## Task 6: Push Branch and Create PR + +**Files:** +- None (git operation) + +**Prerequisites:** +- Tools: Git, GitHub CLI (gh) +- Branch: `feature/gptchangelog-workflow` with commit +- Remote: origin configured + +**Step 1: Push branch to remote** + +```bash +cd /Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows +git push -u origin feature/gptchangelog-workflow +``` + +**Expected output:** +``` +Enumerating objects: ... +... +To github.com:LerianStudio/github-actions-shared-workflows.git + * [new branch] feature/gptchangelog-workflow -> feature/gptchangelog-workflow +Branch 'feature/gptchangelog-workflow' set up to track remote branch 'feature/gptchangelog-workflow' from 'origin'. +``` + +**Step 2: Create Pull Request** + +```bash +gh pr create \ + --title "feat: add GPTChangelog reusable workflow" \ + --body "## Summary +Adds a new reusable workflow for generating CHANGELOG.md and RELEASE_NOTES.md using GPTChangelog with OpenAI GPT-4o. + +## Features +- โœจ AI-powered changelog generation using OpenAI GPT-4o +- ๐Ÿ“ฆ Monorepo support via \`filter_paths\` and \`path_level\` inputs +- ๐Ÿ“ GitHub Release notes integration +- ๐Ÿ” GPG-signed commits and PRs +- ๐Ÿ“ข Slack notification integration +- ๐Ÿƒ Blacksmith runner as default + +## Files Added +- \`.github/workflows/gptchangelog.yml\` - The reusable workflow +- \`docs/gptchangelog-workflow.md\` - Comprehensive documentation + +## Files Modified +- \`README.md\` - Added workflow to the list + +## Usage Example + +### Single App +\`\`\`yaml +changelog: + uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main + secrets: inherit +\`\`\` + +### Monorepo (Helm Charts) +\`\`\`yaml +changelog: + uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main + with: + filter_paths: |- + charts/agent + charts/control-plane + charts/midaz + path_level: '2' + secrets: inherit +\`\`\` + +## Migration from Standalone Action +This workflow replaces the standalone \`github-actions-gptchangelog\` composite action with a reusable workflow that follows the shared-workflows patterns. + +## Testing +- [ ] Test with single-app repository +- [ ] Test with monorepo (helm charts) +- [ ] Verify GPG signing works +- [ ] Verify Slack notifications work" \ + --base develop \ + --head feature/gptchangelog-workflow +``` + +**Expected output:** +``` +https://github.com/LerianStudio/github-actions-shared-workflows/pull/XX +``` + +**If Task Fails:** + +1. **gh not authenticated:** + - Run: `gh auth login` + - Follow authentication steps + +2. **develop branch doesn't exist as base:** + - Use `--base main` instead + +3. **PR already exists:** + - Run: `gh pr view` to see existing PR + +--- + +## Task 7: Run Code Review + +> **Note:** This task should be run after PR creation to ensure code quality. + +**Step 1: Dispatch all 3 reviewers in parallel:** +- REQUIRED SUB-SKILL: Use ring-default:requesting-code-review +- All reviewers run simultaneously (ring-default:code-reviewer, ring-default:business-logic-reviewer, ring-default:security-reviewer) +- Wait for all to complete + +**Step 2: Handle findings by severity (MANDATORY):** + +**Critical/High/Medium Issues:** +- Fix immediately (do NOT add TODO comments for these severities) +- Re-run all 3 reviewers in parallel after fixes +- Repeat until zero Critical/High/Medium issues remain + +**Low Issues:** +- Add `TODO(review):` comments in code at the relevant location +- Format: `TODO(review): [Issue description] (reported by [reviewer] on [date], severity: Low)` + +**Cosmetic/Nitpick Issues:** +- Add `FIXME(nitpick):` comments in code at the relevant location +- Format: `FIXME(nitpick): [Issue description] (reported by [reviewer] on [date], severity: Cosmetic)` + +**Step 3: Proceed only when:** +- Zero Critical/High/Medium issues remain +- All Low issues have TODO(review): comments added +- All Cosmetic issues have FIXME(nitpick): comments added + +--- + +## Summary Checklist + +Before completing the implementation: + +- [ ] Feature branch created from develop +- [ ] `.github/workflows/gptchangelog.yml` created with all features +- [ ] `docs/gptchangelog-workflow.md` created with comprehensive documentation +- [ ] `README.md` updated with new workflow entry +- [ ] All changes committed with descriptive commit message +- [ ] Branch pushed to remote +- [ ] Pull Request created +- [ ] Code review completed with all issues addressed + +--- + +## Post-Implementation Testing + +After the PR is merged, test the workflow: + +### Test 1: Single App Repository + +1. Create a test tag on a single-app repository +2. Trigger the gptchangelog workflow +3. Verify CHANGELOG.md and RELEASE_NOTES.md are generated +4. Verify GitHub Release is updated +5. Verify PR is created and merged + +### Test 2: Monorepo (Helm Charts) + +1. Make changes to one chart in the helm repository +2. Create a tag for that chart +3. Trigger the gptchangelog workflow with appropriate filter_paths +4. Verify only the changed chart gets a changelog +5. Verify the PR is created with correct app name + +### Test 3: Slack Notifications + +1. Configure SLACK_WEBHOOK_URL secret +2. Run the workflow +3. Verify Slack notification is received + +--- + +**Plan created:** 2025-12-12 +**Author:** Factory Planning Agent From f6cd6dbfceb4d88e2040a6826abcec5a5be25446 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Sun, 21 Dec 2025 02:09:00 -0300 Subject: [PATCH 24/46] fix(gptchangelog): call OpenAI directly with path-filtered commits BREAKING: gptchangelog tool doesn't support path filtering - it analyzed ALL commits Changes: - Remove gptchangelog dependency (Python, pip install, config, templates) - Call OpenAI API directly via curl with filtered commits only - Filter commits per app using git log -- - Add app-specific prompt that focuses on the component - Remove extra --- separator at top of changelog - Simplify cleanup step This ensures each app's changelog only contains changes relevant to that app. --- .github/workflows/gptchangelog.yml | 223 ++++++++++++----------------- 1 file changed, 90 insertions(+), 133 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index 551d674..88dc381 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -43,10 +43,6 @@ on: description: 'Maximum context tokens for OpenAI API' type: string default: '80000' - python_version: - description: 'Python version to use' - type: string - default: '3.10' permissions: contents: write @@ -284,72 +280,7 @@ jobs: git_user_signingkey: true git_commit_gpgsign: true - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: ${{ inputs.python_version }} - - - name: Install gptchangelog - run: | - python -m pip install --upgrade pip - pip install gptchangelog - echo "โœ… gptchangelog installed successfully" - - - name: Create gptchangelog config - run: | - # Initialize config first (creates proper structure) - # Note: gptchangelog config init may prompt for input, so we skip it and create config manually - - # Create project-specific config - mkdir -p .gptchangelog - echo "[gptchangelog]" > .gptchangelog/config.ini - echo "openai = true" >> .gptchangelog/config.ini - echo "" >> .gptchangelog/config.ini - echo "[openai]" >> .gptchangelog/config.ini - echo "api_key = ${OPENAI_API_KEY}" >> .gptchangelog/config.ini - echo "model = ${{ inputs.openai_model }}" >> .gptchangelog/config.ini - echo "max_context_tokens = ${{ inputs.max_context_tokens }}" >> .gptchangelog/config.ini - - # Also create global config as fallback - mkdir -p ~/.config/gptchangelog - cp .gptchangelog/config.ini ~/.config/gptchangelog/config.ini - - echo "โœ… gptchangelog config created" - echo "๐Ÿ“„ Config contents:" - cat .gptchangelog/config.ini | grep -v api_key - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - - - name: Create changelog prompt template - run: | - mkdir -p .gptchangelog/templates - cat > .gptchangelog/templates/changelog_prompt.txt << 'EOF' - ## Release $next_version - - ### What's New - $commit_messages - - ### โœจ Features - - Highlight new features added in this release. - - ### ๐Ÿ›  Fixes - - List bug fixes and improvements. - - ### ๐Ÿ“š Documentation - - Summarize updates to documentation. - - ### ๐Ÿš€ Improvements - - Highlight performance or backend optimizations. - - ### โš ๏ธ Breaking Changes - - List any breaking changes here. - - ### ๐Ÿ™Œ Contributors - - Acknowledge contributors for this release. - EOF - echo "โœ… Changelog prompt template created" - - - name: Generate consolidated changelog for all apps + - name: Generate changelog for all apps id: generate run: | echo "OPENAI_API_KEY=${OPENAI_API_KEY}" > .env @@ -433,70 +364,97 @@ jobs: continue fi - # Generate changelog (gptchangelog will use the commit range) - if gptchangelog generate --since "$SINCE" --to "$LAST_TAG" --output "$TEMP_CHANGELOG"; then - # Clean up markdown blocks - sed -i '' '/^```/d' "$TEMP_CHANGELOG" 2>/dev/null || sed -i '/^```/d' "$TEMP_CHANGELOG" - - # Extract content (skip any header lines) - CONTENT=$(cat "$TEMP_CHANGELOG" | grep -v "^#" | sed '/^$/N;/^\n$/d') - - if [ -z "$CONTENT" ]; then - echo "โš ๏ธ No content generated for $APP_NAME" - rm -f "$TEMP_CHANGELOG" "$TEMP_COMMITS" - continue - fi - - # Determine the changelog path - if [ "$WORKING_DIR" != "." ]; then - CHANGELOG_PATH="${WORKING_DIR}/CHANGELOG.md" - else - CHANGELOG_PATH="CHANGELOG.md" - fi - - # Build compare link - if [ -n "$PREV_TAG" ]; then - COMPARE_LINK="[Compare changes](${REPO_URL}/compare/${PREV_TAG}...${LAST_TAG})" - else - COMPARE_LINK="[View all changes](${REPO_URL}/commits/${LAST_TAG})" - fi - - # Create fresh changelog with proper format - { - echo "# ${APP_NAME^} Changelog" - echo "" - echo "---" - echo "" - echo "## [${VERSION}](${REPO_URL}/releases/tag/${LAST_TAG})" - echo "" - echo "$CONTENT" - echo "" - echo "$COMPARE_LINK" - echo "" - } > "$CHANGELOG_PATH" - - echo "๐Ÿ“„ Created: $CHANGELOG_PATH" - - # Track which apps were updated - echo "${APP_NAME}:v${VERSION}:${WORKING_DIR}" >> /tmp/apps_updated.txt - - # Update GitHub Release notes - { - echo "## ${APP_NAME^} v${VERSION}" - echo "" - echo "$CONTENT" - echo "" - echo "$COMPARE_LINK" - } > /tmp/app_release_notes.md - - gh release edit "$LAST_TAG" --notes-file /tmp/app_release_notes.md || \ - echo "โš ๏ธ Could not update release for $LAST_TAG" - - echo "โœ… Processed $APP_NAME" + # Get detailed commit messages for this app only + COMMITS_TEXT=$(cat "$TEMP_COMMITS") + echo "๐Ÿ“ Commits for $APP_NAME:" + echo "$COMMITS_TEXT" + + # Call OpenAI directly with filtered commits (gptchangelog doesn't support path filtering) + PROMPT="Generate a concise changelog for the ${APP_NAME} component based on these commits: + +${COMMITS_TEXT} + +Requirements: +- Focus ONLY on changes to ${APP_NAME} - do not mention other components +- Use bullet points with brief descriptions +- Group by: Features, Fixes, Improvements (skip empty sections) +- Be specific about what changed in ${APP_NAME} +- Do not use generic descriptions like 'backend' or 'frontend' +- Do not include markdown headers (no ## or ###) +- Start directly with bullet points +- Keep it concise - max 5-6 bullet points" + + # Escape the prompt for JSON + ESCAPED_PROMPT=$(echo "$PROMPT" | jq -Rs .) + + # Call OpenAI API directly + RESPONSE=$(curl -s https://api.openai.com/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -d "{ + \"model\": \"${{ inputs.openai_model }}\", + \"messages\": [{\"role\": \"user\", \"content\": $ESCAPED_PROMPT}], + \"temperature\": 0.3, + \"max_tokens\": 1000 + }") + + # Extract content from response + CONTENT=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // empty') + + if [ -z "$CONTENT" ]; then + echo "โš ๏ธ No content generated for $APP_NAME" + echo "API Response: $RESPONSE" + rm -f "$TEMP_CHANGELOG" "$TEMP_COMMITS" + continue + fi + + # Clean up any markdown code blocks + CONTENT=$(echo "$CONTENT" | sed '/^```/d') + + # Determine the changelog path + if [ "$WORKING_DIR" != "." ]; then + CHANGELOG_PATH="${WORKING_DIR}/CHANGELOG.md" else - echo "โš ๏ธ Failed to generate changelog for $APP_NAME" + CHANGELOG_PATH="CHANGELOG.md" fi + # Build compare link + if [ -n "$PREV_TAG" ]; then + COMPARE_LINK="[Compare changes](${REPO_URL}/compare/${PREV_TAG}...${LAST_TAG})" + else + COMPARE_LINK="[View all changes](${REPO_URL}/commits/${LAST_TAG})" + fi + + # Create fresh changelog with proper format (no extra --- at top) + { + echo "# ${APP_NAME^} Changelog" + echo "" + echo "## [${VERSION}](${REPO_URL}/releases/tag/${LAST_TAG})" + echo "" + echo "$CONTENT" + echo "" + echo "$COMPARE_LINK" + echo "" + } > "$CHANGELOG_PATH" + + echo "๐Ÿ“„ Created: $CHANGELOG_PATH" + + # Track which apps were updated + echo "${APP_NAME}:v${VERSION}:${WORKING_DIR}" >> /tmp/apps_updated.txt + + # Update GitHub Release notes + { + echo "## ${APP_NAME^} v${VERSION}" + echo "" + echo "$CONTENT" + echo "" + echo "$COMPARE_LINK" + } > /tmp/app_release_notes.md + + gh release edit "$LAST_TAG" --notes-file /tmp/app_release_notes.md || \ + echo "โš ๏ธ Could not update release for $LAST_TAG" + + echo "โœ… Processed $APP_NAME" rm -f "$TEMP_CHANGELOG" "$TEMP_COMMITS" done @@ -678,8 +636,7 @@ jobs: if: always() run: | rm -f .env - rm -rf .gptchangelog - rm -f /tmp/consolidated_changelog.md /tmp/consolidated_release_notes.md /tmp/apps_updated.txt /tmp/app_release_notes.md /tmp/new_entry.md + rm -f /tmp/apps_updated.txt /tmp/app_release_notes.md echo "๐Ÿงน Cleaned up sensitive files" # Slack notification for workflow status From 1546ff37409b12d60b6b942ba16d5960e1f05b0f Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Sun, 21 Dec 2025 02:19:07 -0300 Subject: [PATCH 25/46] fix(gptchangelog): add contributors at end with GitHub usernames - Extract GitHub usernames from commit emails - Handle GitHub noreply email format (user@users.noreply.github.com) - Add contributors section at the end of changelog - Format as @username mentions --- .github/workflows/gptchangelog.yml | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index 88dc381..e09502a 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -369,6 +369,29 @@ jobs: echo "๐Ÿ“ Commits for $APP_NAME:" echo "$COMMITS_TEXT" + # Get unique contributors (GitHub usernames) for this app + # Try to extract GitHub username from email (format: user@users.noreply.github.com or username+id@users.noreply.github.com) + if [ "$WORKING_DIR" != "." ]; then + RAW_EMAILS=$(git log "$SINCE".."$LAST_TAG" --format='%ae' -- "$WORKING_DIR" 2>/dev/null | sort -u) + else + RAW_EMAILS=$(git log "$SINCE".."$LAST_TAG" --format='%ae' 2>/dev/null | sort -u) + fi + + CONTRIBUTORS="" + for EMAIL in $RAW_EMAILS; do + if [[ "$EMAIL" == *"@users.noreply.github.com" ]]; then + # Extract username from GitHub noreply email + USERNAME=$(echo "$EMAIL" | sed 's/@users.noreply.github.com//' | sed 's/.*+//') + CONTRIBUTORS="${CONTRIBUTORS}@${USERNAME}, " + else + # Use email prefix as fallback + USERNAME=$(echo "$EMAIL" | cut -d@ -f1) + CONTRIBUTORS="${CONTRIBUTORS}@${USERNAME}, " + fi + done + CONTRIBUTORS=$(echo "$CONTRIBUTORS" | sed 's/, $//') + echo "๐Ÿ‘ฅ Contributors: $CONTRIBUTORS" + # Call OpenAI directly with filtered commits (gptchangelog doesn't support path filtering) PROMPT="Generate a concise changelog for the ${APP_NAME} component based on these commits: @@ -382,7 +405,9 @@ Requirements: - Do not use generic descriptions like 'backend' or 'frontend' - Do not include markdown headers (no ## or ###) - Start directly with bullet points -- Keep it concise - max 5-6 bullet points" +- Keep it concise - max 5-6 bullet points +- At the END, add a Contributors section with: ${CONTRIBUTORS} +- Format contributors as GitHub mentions (@username) if possible" # Escape the prompt for JSON ESCAPED_PROMPT=$(echo "$PROMPT" | jq -Rs .) From 7d5c905d993dc5e909407d805fc92f0a0cf986cc Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Sun, 21 Dec 2025 02:33:21 -0300 Subject: [PATCH 26/46] fix(gptchangelog): use heredoc for prompt to fix YAML parsing The multiline PROMPT string was breaking YAML syntax parsing. Using heredoc (cat < Date: Sun, 21 Dec 2025 02:34:02 -0300 Subject: [PATCH 27/46] fix(gptchangelog): simplify prompt to single line to fix YAML parsing --- .github/workflows/gptchangelog.yml | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index 0f91547..d175900 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -393,27 +393,8 @@ jobs: echo "๐Ÿ‘ฅ Contributors: $CONTRIBUTORS" # Call OpenAI directly with filtered commits (gptchangelog doesn't support path filtering) - # Build prompt using heredoc to avoid YAML parsing issues - PROMPT=$(cat < Date: Sun, 21 Dec 2025 02:38:24 -0300 Subject: [PATCH 28/46] fix(gptchangelog): append new version to existing changelog Instead of overwriting, now: - Reads existing changelog content after header - Inserts new version entry at top - Preserves all previous version entries - Adds separator (---) between entries --- .github/workflows/gptchangelog.yml | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index d175900..8a20fd1 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -435,7 +435,17 @@ jobs: COMPARE_LINK="[View all changes](${REPO_URL}/commits/${LAST_TAG})" fi - # Create fresh changelog with proper format (no extra --- at top) + # Append new version to changelog (keep existing entries) + if [ -f "$CHANGELOG_PATH" ]; then + # Extract existing content after the header line + EXISTING_CONTENT=$(sed '1,/^# /d' "$CHANGELOG_PATH" | sed '/^$/d' | head -c -1) + echo "๐Ÿ“œ Found existing changelog with content" + else + EXISTING_CONTENT="" + echo "๐Ÿ“œ Creating new changelog" + fi + + # Build changelog with new entry at top, existing entries below { echo "# ${APP_NAME^} Changelog" echo "" @@ -444,10 +454,16 @@ jobs: echo "$CONTENT" echo "" echo "$COMPARE_LINK" + if [ -n "$EXISTING_CONTENT" ]; then + echo "" + echo "---" + echo "" + echo "$EXISTING_CONTENT" + fi echo "" } > "$CHANGELOG_PATH" - echo "๐Ÿ“„ Created: $CHANGELOG_PATH" + echo "๐Ÿ“„ Updated: $CHANGELOG_PATH" # Track which apps were updated echo "${APP_NAME}:v${VERSION}:${WORKING_DIR}" >> /tmp/apps_updated.txt From 97af1b7ffe2b730244c48d82ff0a148dc6db23b3 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Mon, 22 Dec 2025 10:24:20 -0300 Subject: [PATCH 29/46] fix(gptchangelog): exclude service accounts from contributors Filter out: - srv.iam (CI/CD service account) - Other noreply emails (except GitHub user noreply) --- .github/workflows/gptchangelog.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index 8a20fd1..ce4dfe9 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -379,6 +379,10 @@ jobs: CONTRIBUTORS="" for EMAIL in $RAW_EMAILS; do + # Skip service accounts used for automated commits + if [[ "$EMAIL" == *"srv.iam"* ]] || [[ "$EMAIL" == *"noreply"* && "$EMAIL" != *"users.noreply.github.com" ]]; then + continue + fi if [[ "$EMAIL" == *"@users.noreply.github.com" ]]; then # Extract username from GitHub noreply email USERNAME=$(echo "$EMAIL" | sed 's/@users.noreply.github.com//' | sed 's/.*+//') From 380e318702a5cfb63f75bd0c87618712f73a600d Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Tue, 23 Dec 2025 11:31:31 -0300 Subject: [PATCH 30/46] feat(gptchangelog): switch from OpenAI to OpenRouter API - Change API endpoint to openrouter.ai - Use OPENROUTER_API_KEY secret instead of OPENAI_API_KEY - Add HTTP-Referer and X-Title headers for OpenRouter - Update default model to openai/gpt-4o (OpenRouter format) OpenRouter provides access to multiple LLM providers with a unified API. --- .github/workflows/gptchangelog.yml | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index ce4dfe9..e2c0f91 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -36,11 +36,11 @@ on: type: boolean default: true openai_model: - description: 'OpenAI model to use for changelog generation' + description: 'Model to use for changelog generation (OpenRouter format)' type: string - default: 'gpt-4o' + default: 'openai/gpt-4o' max_context_tokens: - description: 'Maximum context tokens for OpenAI API' + description: 'Maximum context tokens for API' type: string default: '80000' @@ -283,7 +283,7 @@ jobs: - name: Generate changelog for all apps id: generate run: | - echo "OPENAI_API_KEY=${OPENAI_API_KEY}" > .env + echo "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" > .env git fetch --tags --force MATRIX='${{ needs.prepare.outputs.matrix }}' @@ -396,15 +396,17 @@ jobs: CONTRIBUTORS=$(echo "$CONTRIBUTORS" | sed 's/, $//') echo "๐Ÿ‘ฅ Contributors: $CONTRIBUTORS" - # Call OpenAI directly with filtered commits (gptchangelog doesn't support path filtering) + # Call OpenRouter API with filtered commits # Build prompt and escape for JSON PROMPT="Generate a concise changelog for the ${APP_NAME} component based on these commits: ${COMMITS_TEXT} --- Requirements: Focus ONLY on changes to ${APP_NAME}. Use bullet points. Group by Features, Fixes, Improvements (skip empty). Be specific. No markdown headers. Keep concise (max 5-6 points). At the END add Contributors: ${CONTRIBUTORS} as @username mentions." ESCAPED_PROMPT=$(echo "$PROMPT" | jq -Rs .) - # Call OpenAI API directly - RESPONSE=$(curl -s https://api.openai.com/v1/chat/completions \ + # Call OpenRouter API (OpenAI-compatible) + RESPONSE=$(curl -s https://openrouter.ai/api/v1/chat/completions \ -H "Content-Type: application/json" \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Authorization: Bearer $OPENROUTER_API_KEY" \ + -H "HTTP-Referer: https://github.com/${{ github.repository }}" \ + -H "X-Title: GPT Changelog" \ -d "{ \"model\": \"${{ inputs.openai_model }}\", \"messages\": [{\"role\": \"user\", \"content\": $ESCAPED_PROMPT}], @@ -498,7 +500,7 @@ jobs: echo "apps_updated=" >> $GITHUB_OUTPUT fi env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} GH_TOKEN: ${{ steps.app-token.outputs.token }} - name: Show generated changelogs From 1b7b6ad05e0e3fe0f6574a0d5d1b10904ef984f7 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Tue, 23 Dec 2025 11:39:43 -0300 Subject: [PATCH 31/46] fix(gptchangelog): fix append logic and stricter GPT prompt 1. Fix append logic: - Use awk to extract existing version entries (## [x.y.z]) - Properly preserve all previous changelog entries 2. Stricter GPT prompt: - Explicit STRICT RULES format - NEVER mention other components - Clearer instructions for output format --- .github/workflows/gptchangelog.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index e2c0f91..d2c65b4 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -397,8 +397,8 @@ jobs: echo "๐Ÿ‘ฅ Contributors: $CONTRIBUTORS" # Call OpenRouter API with filtered commits - # Build prompt and escape for JSON - PROMPT="Generate a concise changelog for the ${APP_NAME} component based on these commits: ${COMMITS_TEXT} --- Requirements: Focus ONLY on changes to ${APP_NAME}. Use bullet points. Group by Features, Fixes, Improvements (skip empty). Be specific. No markdown headers. Keep concise (max 5-6 points). At the END add Contributors: ${CONTRIBUTORS} as @username mentions." + # Build prompt and escape for JSON - be very strict about component name + PROMPT="Generate a changelog for ${APP_NAME} ONLY. Commits: ${COMMITS_TEXT} --- STRICT RULES: 1) NEVER mention other components - only ${APP_NAME}. 2) Use bullet points. 3) Group by Features, Fixes, Improvements (skip empty sections). 4) No markdown headers. 5) Max 5 bullet points. 6) End with Contributors: ${CONTRIBUTORS}" ESCAPED_PROMPT=$(echo "$PROMPT" | jq -Rs .) # Call OpenRouter API (OpenAI-compatible) @@ -443,9 +443,13 @@ jobs: # Append new version to changelog (keep existing entries) if [ -f "$CHANGELOG_PATH" ]; then - # Extract existing content after the header line - EXISTING_CONTENT=$(sed '1,/^# /d' "$CHANGELOG_PATH" | sed '/^$/d' | head -c -1) - echo "๐Ÿ“œ Found existing changelog with content" + # Extract existing entries (everything after first ## header, excluding the title) + EXISTING_CONTENT=$(awk '/^## \[/{found=1} found{print}' "$CHANGELOG_PATH") + if [ -n "$EXISTING_CONTENT" ]; then + echo "๐Ÿ“œ Found existing changelog entries" + else + echo "๐Ÿ“œ Changelog exists but no version entries found" + fi else EXISTING_CONTENT="" echo "๐Ÿ“œ Creating new changelog" From c2ce3008f7b5c1aacd8979780f3ec37e7cff2dad Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Mon, 29 Dec 2025 20:37:19 -0300 Subject: [PATCH 32/46] fix(gptchangelog): support both monorepo and single-app tag patterns - Monorepo tags: app-name-v1.0.0 (e.g., auth-v1.0.0) - Single-app tags: v1.0.0 The workflow now correctly detects and extracts versions from both tag formats based on WORKING_DIR. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .github/workflows/gptchangelog.yml | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index d2c65b4..717700e 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -304,17 +304,21 @@ jobs: echo "๐Ÿ“ Processing: $APP_NAME (dir: $WORKING_DIR)" echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" - # Determine tag pattern based on app type + # Determine tag pattern based on app type (monorepo vs single-app) if [ "$WORKING_DIR" != "." ]; then + # Monorepo: tags are prefixed with app name (e.g., auth-v1.0.0) TAG_PATTERN="${APP_NAME}-v*" + TAG_GREP_PATTERN="^${APP_NAME}-v" else + # Single-app repo: tags are just version (e.g., v1.0.0) TAG_PATTERN="v*" + TAG_GREP_PATTERN="^v" fi echo "๐Ÿ” Looking for tags matching: $TAG_PATTERN" # Find the latest STABLE tag for this app (exclude beta/rc/alpha) - LAST_TAG=$(git tag --sort=-version:refname | grep "^${APP_NAME}-v" | grep -v -e "-beta" -e "-rc" -e "-alpha" -e "-dev" -e "-snapshot" | head -1) + LAST_TAG=$(git tag --sort=-version:refname | grep "$TAG_GREP_PATTERN" | grep -v -e "-beta" -e "-rc" -e "-alpha" -e "-dev" -e "-snapshot" | head -1) if [ -z "$LAST_TAG" ]; then echo "โš ๏ธ No stable tag found for $APP_NAME - skipping" @@ -330,7 +334,7 @@ jobs: fi # Find previous stable tag for compare link - PREV_TAG=$(git tag --sort=-version:refname | grep "^${APP_NAME}-v" | grep -v -e "-beta" -e "-rc" -e "-alpha" -e "-dev" -e "-snapshot" | sed -n '2p') + PREV_TAG=$(git tag --sort=-version:refname | grep "$TAG_GREP_PATTERN" | grep -v -e "-beta" -e "-rc" -e "-alpha" -e "-dev" -e "-snapshot" | sed -n '2p') if [ -n "$PREV_TAG" ]; then SINCE="$PREV_TAG" @@ -341,8 +345,14 @@ jobs: echo "๐ŸŸก First stable release - Range: first commit โ†’ $LAST_TAG" fi - # Extract version from tag - VERSION=$(echo "$LAST_TAG" | sed 's/.*-v//') + # Extract version from tag (handles both monorepo and single-app formats) + if [ "$WORKING_DIR" != "." ]; then + # Monorepo: auth-v1.0.0 -> 1.0.0 + VERSION=$(echo "$LAST_TAG" | sed 's/.*-v//') + else + # Single-app: v1.0.0 -> 1.0.0 + VERSION=$(echo "$LAST_TAG" | sed 's/^v//') + fi # Generate changelog using gptchangelog with path filter TEMP_CHANGELOG=$(mktemp) From a96427ef9a6c8835ac02890181b43e01d35fec42 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Mon, 29 Dec 2025 20:45:40 -0300 Subject: [PATCH 33/46] fix(gptchangelog): deduplicate contributors by username Same user may have multiple emails (personal + GitHub noreply). Now collects usernames first, then deduplicates before formatting. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .github/workflows/gptchangelog.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index 717700e..883d089 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -387,7 +387,8 @@ jobs: RAW_EMAILS=$(git log "$SINCE".."$LAST_TAG" --format='%ae' 2>/dev/null | sort -u) fi - CONTRIBUTORS="" + # Collect unique usernames (same user may have multiple emails) + USERNAMES_FILE=$(mktemp) for EMAIL in $RAW_EMAILS; do # Skip service accounts used for automated commits if [[ "$EMAIL" == *"srv.iam"* ]] || [[ "$EMAIL" == *"noreply"* && "$EMAIL" != *"users.noreply.github.com" ]]; then @@ -396,14 +397,15 @@ jobs: if [[ "$EMAIL" == *"@users.noreply.github.com" ]]; then # Extract username from GitHub noreply email USERNAME=$(echo "$EMAIL" | sed 's/@users.noreply.github.com//' | sed 's/.*+//') - CONTRIBUTORS="${CONTRIBUTORS}@${USERNAME}, " else # Use email prefix as fallback USERNAME=$(echo "$EMAIL" | cut -d@ -f1) - CONTRIBUTORS="${CONTRIBUTORS}@${USERNAME}, " fi + echo "$USERNAME" >> "$USERNAMES_FILE" done - CONTRIBUTORS=$(echo "$CONTRIBUTORS" | sed 's/, $//') + # Deduplicate usernames and format as @username + CONTRIBUTORS=$(sort -u "$USERNAMES_FILE" | sed 's/^/@/' | tr '\n' ', ' | sed 's/, $//') + rm -f "$USERNAMES_FILE" echo "๐Ÿ‘ฅ Contributors: $CONTRIBUTORS" # Call OpenRouter API with filtered commits From 77000a9a7a9f6b4327dcbfcb7f1f3a2413e30300 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Mon, 29 Dec 2025 22:36:16 -0300 Subject: [PATCH 34/46] docs(gptchangelog): update documentation for OpenRouter and add examples - Changed OPENAI_API_KEY to OPENROUTER_API_KEY - Added stable_releases_only input to docs - Added recommended workflow_run trigger example - Updated model format to OpenRouter (openai/gpt-4o) - Updated runner to blacksmith-4vcpu-ubuntu-2404 - Added note about stable releases only default Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- docs/gptchangelog-workflow.md | 56 ++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/docs/gptchangelog-workflow.md b/docs/gptchangelog-workflow.md index 3dc21fa..573b319 100644 --- a/docs/gptchangelog-workflow.md +++ b/docs/gptchangelog-workflow.md @@ -1,10 +1,10 @@ # GPT Changelog Workflow -Reusable workflow for generating a consolidated CHANGELOG.md and RELEASE_NOTES.md using GPTChangelog. Uses OpenAI GPT-4o to analyze commits and generate human-readable, categorized changelogs. +Reusable workflow for generating CHANGELOG.md using AI. Uses OpenRouter API (GPT-4o by default) to analyze commits and generate human-readable, categorized changelogs. ## Features -- **AI-powered changelog generation**: Uses OpenAI GPT-4o for intelligent commit analysis +- **AI-powered changelog generation**: Uses OpenRouter API (GPT-4o) for intelligent commit analysis - **Consolidated changelog**: Single CHANGELOG.md with sections per app (no overwrites) - **Monorepo support**: Automatic detection of changed components via filter_paths - **GitHub Release integration**: Automatically updates release notes per app tag @@ -15,7 +15,35 @@ Reusable workflow for generating a consolidated CHANGELOG.md and RELEASE_NOTES.m ## Usage -### Single App Repository +### Single App Repository (Recommended - After Release) + +Trigger changelog generation after your Release workflow completes on main: + +```yaml +name: GPT Changelog +on: + workflow_run: + workflows: ["Release"] + types: [completed] + branches: [main] + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + changelog: + if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' + uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main + with: + runner_type: "blacksmith-4vcpu-ubuntu-2404" + secrets: inherit +``` + +> **Note**: By default, `stable_releases_only: true` means changelog is only generated for stable releases (v1.0.0), not prereleases (v1.0.0-beta.1). + +### Single App Repository (Tag Push Trigger) ```yaml name: Generate Changelog @@ -32,7 +60,7 @@ jobs: changelog: uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main with: - runner_type: "blacksmith" + runner_type: "blacksmith-4vcpu-ubuntu-2404" secrets: inherit ``` @@ -144,9 +172,9 @@ jobs: | `runner_type` | string | `blacksmith` | GitHub runner type | | `filter_paths` | string | `''` | Newline-separated list of path prefixes. If empty, single-app mode | | `path_level` | string | `2` | Directory depth for app name extraction | -| `openai_model` | string | `gpt-4o` | OpenAI model for changelog generation | -| `max_context_tokens` | string | `80000` | Maximum context tokens for OpenAI API | -| `python_version` | string | `3.10` | Python version to use | +| `stable_releases_only` | boolean | `true` | Only generate changelogs for stable releases (skip beta/rc/alpha) | +| `openai_model` | string | `openai/gpt-4o` | OpenRouter model for changelog generation | +| `max_context_tokens` | string | `80000` | Maximum context tokens for API | ## Secrets @@ -154,7 +182,7 @@ All secrets are inherited via `secrets: inherit`. Required secrets in your repos | Secret | Description | |--------|-------------| -| `OPENAI_API_KEY` | OpenAI API key for GPT-4o access | +| `OPENROUTER_API_KEY` | OpenRouter API key for AI model access | | `LERIAN_STUDIO_MIDAZ_PUSH_BOT_APP_ID` | GitHub App ID for authentication | | `LERIAN_STUDIO_MIDAZ_PUSH_BOT_PRIVATE_KEY` | GitHub App private key | | `LERIAN_CI_CD_USER_GPG_KEY` | GPG private key for signing commits | @@ -283,15 +311,15 @@ Add `SLACK_WEBHOOK_URL` secret for team notifications. 2. Check if branch already exists 3. Review PR creation step logs -### OpenAI API errors +### OpenRouter API errors -**Issue**: gptchangelog fails with API errors +**Issue**: Changelog generation fails with API errors **Solutions**: -1. Verify `OPENAI_API_KEY` is set correctly +1. Verify `OPENROUTER_API_KEY` is set correctly 2. Check API rate limits 3. Try reducing `max_context_tokens` -4. Ensure model name is valid (`gpt-4o`) +4. Ensure model name is valid (e.g., `openai/gpt-4o`) ### Monorepo changes not detected @@ -360,13 +388,13 @@ jobs: secrets: inherit ``` -### Custom OpenAI Model +### Custom OpenRouter Model ```yaml changelog: uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main with: - openai_model: 'gpt-4-turbo' + openai_model: 'anthropic/claude-3.5-sonnet' max_context_tokens: '128000' secrets: inherit ``` From 3db96fb942f2bebf386d24ef05206843db9652d2 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Mon, 29 Dec 2025 22:41:40 -0300 Subject: [PATCH 35/46] chore: add plans folder to gitignore Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d99f8c7..9fd76b8 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ temp/ dist/ build/ bin/ +plans/ From c5bbc030ae736c1179d02f2fd81d0d98009b4a8c Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Mon, 29 Dec 2025 22:55:15 -0300 Subject: [PATCH 36/46] chore: remove plans file from PR Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../plans/2025-12-12-gptchangelog-workflow.md | 1281 ----------------- 1 file changed, 1281 deletions(-) delete mode 100644 docs/plans/2025-12-12-gptchangelog-workflow.md diff --git a/docs/plans/2025-12-12-gptchangelog-workflow.md b/docs/plans/2025-12-12-gptchangelog-workflow.md deleted file mode 100644 index 6329e2c..0000000 --- a/docs/plans/2025-12-12-gptchangelog-workflow.md +++ /dev/null @@ -1,1281 +0,0 @@ -# GPTChangelog Workflow Implementation Plan - -> **For Agents:** REQUIRED SUB-SKILL: Use ring-default:executing-plans to implement this plan task-by-task. - -**Goal:** Create a reusable workflow that generates a consolidated CHANGELOG.md and RELEASE_NOTES.md using GPTChangelog, with full monorepo support for any multi-app repositories (helm charts, microservices, etc.). - -**Architecture:** The workflow follows the established shared workflows pattern with a `prepare` โ†’ `main_job` โ†’ `notify` structure. For monorepos, it uses the `LerianStudio/github-actions-changed-paths` action to detect which apps changed and generates a **single consolidated changelog** with sections per app. For single-app repos, it generates a changelog for the entire repository. - -**Key Design Decision - Consolidated Changelog:** -- Single CHANGELOG.md at repository root (not per-app files) -- When multiple apps change, each app gets its own section in the changelog -- Prevents overwrite issues when multiple apps change simultaneously -- Works generically with any directory structure (charts, apps, services, etc.) - -**Tech Stack:** GitHub Actions reusable workflow, Python 3.10, gptchangelog PyPI package, OpenAI GPT-4o, GPG signing, GitHub App authentication - -**Global Prerequisites:** -- Environment: GitHub Actions runner (blacksmith default) -- Tools: Git, Python 3.10+, pip -- Access: OpenAI API key, GitHub App credentials, GPG key for signing -- State: Feature branch created from `develop` branch - -**Verification before starting:** -```bash -# Navigate to the shared-workflows repository -cd /Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows - -# Verify clean git state -git status # Expected: clean working tree or known changes - -# Verify on develop branch -git branch --show-current # Expected: develop or main - -# Verify docs/plans directory exists -ls docs/plans/ # Expected: directory exists with existing plans -``` - ---- - -## Task 1: Create Feature Branch - -**Files:** -- None (git operation) - -**Prerequisites:** -- Tools: Git -- Current directory: `/Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows` - -**Step 1: Fetch latest changes** - -```bash -cd /Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows -git fetch origin -``` - -**Step 2: Create feature branch from develop** - -```bash -git checkout develop -git pull origin develop -git checkout -b feature/gptchangelog-workflow -``` - -**Expected output:** -``` -Switched to a new branch 'feature/gptchangelog-workflow' -``` - -**Step 3: Verify branch creation** - -Run: `git branch --show-current` - -**Expected output:** -``` -feature/gptchangelog-workflow -``` - -**If Task Fails:** - -1. **Branch already exists:** - - Run: `git branch -D feature/gptchangelog-workflow` - - Retry branch creation - -2. **develop branch doesn't exist:** - - Run: `git checkout main` instead - - Continue from main branch - ---- - -## Task 2: Create the GPTChangelog Workflow File - -**Files:** -- Create: `/Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows/.github/workflows/gptchangelog.yml` - -**Prerequisites:** -- Tools: Text editor -- Branch: `feature/gptchangelog-workflow` -- Files must exist: `.github/workflows/` directory - -**Step 1: Create the workflow file** - -Create the file `.github/workflows/gptchangelog.yml` with the following content: - -```yaml -name: "GPT Changelog" - -# This reusable workflow generates CHANGELOG.md and RELEASE_NOTES.md using GPTChangelog -# It uses OpenAI GPT-4o to analyze commits and generate human-readable changelogs -# -# Monorepo Support: -# - If filter_paths is provided: detects changes and generates changelog for each changed app -# - If filter_paths is empty: generates changelog for the entire repository (single app mode) -# -# Features: -# - Generates CHANGELOG.md with categorized changes -# - Generates RELEASE_NOTES.md and updates GitHub Release -# - Creates PR with changelog update using GPG-signed commits -# - Handles tag-based versioning (between tags, first tag, no tags scenarios) - -on: - workflow_call: - inputs: - runner_type: - description: 'Runner to use for the workflow' - type: string - default: 'blacksmith' - filter_paths: - description: 'Newline-separated list of path prefixes to filter. If not provided, treats as single app repo.' - type: string - required: false - default: '' - path_level: - description: 'Limits the path to the first N segments (e.g., 2 -> "charts/agent")' - type: string - default: '2' - openai_model: - description: 'OpenAI model to use for changelog generation' - type: string - default: 'gpt-4o' - max_context_tokens: - description: 'Maximum context tokens for OpenAI API' - type: string - default: '80000' - python_version: - description: 'Python version to use' - type: string - default: '3.10' - -permissions: - contents: write - pull-requests: write - -jobs: - prepare: - runs-on: ${{ inputs.runner_type }} - outputs: - matrix: ${{ steps.set-matrix.outputs.matrix }} - has_changes: ${{ steps.set-matrix.outputs.has_changes }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Get changed paths (monorepo) - if: inputs.filter_paths != '' - id: changed-paths - uses: LerianStudio/github-actions-changed-paths@main - with: - filter_paths: ${{ inputs.filter_paths }} - path_level: ${{ inputs.path_level }} - get_app_name: 'true' - - - name: Set matrix - id: set-matrix - run: | - if [ -z "${{ inputs.filter_paths }}" ]; then - # Single app mode - generate changelog from root - APP_NAME="${{ github.event.repository.name }}" - echo "matrix=[{\"name\": \"${APP_NAME}\", \"working_dir\": \".\"}]" >> $GITHUB_OUTPUT - echo "has_changes=true" >> $GITHUB_OUTPUT - echo "๐Ÿ“ฆ Single app mode: ${APP_NAME}" - else - MATRIX='${{ steps.changed-paths.outputs.matrix }}' - if [ "$MATRIX" == "[]" ] || [ -z "$MATRIX" ]; then - echo "matrix=[]" >> $GITHUB_OUTPUT - echo "has_changes=false" >> $GITHUB_OUTPUT - echo "โš ๏ธ No changes detected in filter_paths" - else - echo "matrix=$MATRIX" >> $GITHUB_OUTPUT - echo "has_changes=true" >> $GITHUB_OUTPUT - echo "๐Ÿ“ฆ Monorepo mode - detected changes: $MATRIX" - fi - fi - - generate_changelog: - needs: prepare - if: needs.prepare.outputs.has_changes == 'true' - runs-on: ${{ inputs.runner_type }} - name: Generate Consolidated Changelog - - steps: - - name: Create GitHub App Token - uses: actions/create-github-app-token@v1 - id: app-token - with: - app-id: ${{ secrets.LERIAN_STUDIO_MIDAZ_PUSH_BOT_APP_ID }} - private-key: ${{ secrets.LERIAN_STUDIO_MIDAZ_PUSH_BOT_PRIVATE_KEY }} - - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ steps.app-token.outputs.token }} - - - name: Sync with remote branch - run: | - git fetch origin ${{ github.ref_name }} - git reset --hard origin/${{ github.ref_name }} - - - name: Import GPG key - uses: crazy-max/ghaction-import-gpg@v6 - id: import_gpg - with: - gpg_private_key: ${{ secrets.LERIAN_CI_CD_USER_GPG_KEY }} - passphrase: ${{ secrets.LERIAN_CI_CD_USER_GPG_KEY_PASSWORD }} - git_committer_name: ${{ secrets.LERIAN_CI_CD_USER_NAME }} - git_committer_email: ${{ secrets.LERIAN_CI_CD_USER_EMAIL }} - git_config_global: true - git_user_signingkey: true - git_commit_gpgsign: true - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: ${{ inputs.python_version }} - - - name: Install gptchangelog - run: | - python -m pip install --upgrade pip - pip install gptchangelog - echo "โœ… gptchangelog installed successfully" - - - name: Create gptchangelog config - run: | - mkdir -p .gptchangelog - cat > .gptchangelog/config.ini << EOF - [gptchangelog] - openai = true - - [openai] - api_key = ${OPENAI_API_KEY} - model = ${{ inputs.openai_model }} - max_context_tokens = ${{ inputs.max_context_tokens }} - EOF - echo "โœ… gptchangelog config created" - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - - - name: Create changelog prompt template - run: | - mkdir -p .gptchangelog/templates - cat > .gptchangelog/templates/changelog_prompt.txt << 'EOF' - ## Release $next_version (Released on $current_date) - - ### What's New - $commit_messages - - ### โœจ Features - - Highlight new features added in this release. - - ### ๐Ÿ›  Fixes - - List bug fixes and improvements. - - ### ๐Ÿ“š Documentation - - Summarize updates to documentation. - - ### ๐Ÿš€ Improvements - - Highlight performance or backend optimizations. - - ### โš ๏ธ Breaking Changes - - List any breaking changes here. - - ### ๐Ÿ™Œ Contributors - - Acknowledge contributors for this release. - EOF - echo "โœ… Changelog prompt template created" - - - name: Generate consolidated changelog for all apps - id: generate - run: | - echo "OPENAI_API_KEY=${OPENAI_API_KEY}" > .env - git fetch --tags - - MATRIX='${{ needs.prepare.outputs.matrix }}' - CURRENT_DATE=$(date +%Y-%m-%d) - CONSOLIDATED_CHANGELOG="" - CONSOLIDATED_RELEASE_NOTES="" - APPS_UPDATED="" - - echo "๐Ÿ“ฆ Processing apps from matrix: $MATRIX" - - # Parse the matrix JSON and iterate through each app - echo "$MATRIX" | jq -c '.[]' | while read -r APP; do - APP_NAME=$(echo "$APP" | jq -r '.name') - WORKING_DIR=$(echo "$APP" | jq -r '.working_dir') - - echo "" - echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" - echo "๐Ÿ“ Processing: $APP_NAME (dir: $WORKING_DIR)" - echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" - - # Determine tag pattern based on app type - if [ "$WORKING_DIR" != "." ]; then - TAG_PATTERN="${APP_NAME}-v*" - else - TAG_PATTERN="v*" - fi - - echo "๐Ÿ” Looking for tags matching: $TAG_PATTERN" - - # Determine version range - if git describe --tags --abbrev=0 --match "$TAG_PATTERN" HEAD >/dev/null 2>&1; then - LAST_TAG=$(git describe --tags --abbrev=0 --match "$TAG_PATTERN" HEAD) - - if git describe --tags --abbrev=0 --match "$TAG_PATTERN" HEAD^ >/dev/null 2>&1; then - PENULT_TAG=$(git describe --tags --abbrev=0 --match "$TAG_PATTERN" HEAD^) - SINCE="$PENULT_TAG" - TO="$LAST_TAG" - echo "๐ŸŸข Range: $PENULT_TAG โ†’ $LAST_TAG" - else - FIRST_COMMIT=$(git rev-list --max-parents=0 HEAD) - SINCE="$FIRST_COMMIT" - TO="$LAST_TAG" - echo "๐ŸŸก First tag - Range: $FIRST_COMMIT โ†’ $LAST_TAG" - fi - else - FIRST_COMMIT=$(git rev-list --max-parents=0 HEAD) - SINCE="$FIRST_COMMIT" - TO="HEAD" - LAST_TAG="HEAD" - echo "๐Ÿ”ด No tags - Range: $FIRST_COMMIT โ†’ HEAD" - fi - - # Extract version from tag - VERSION=$(echo "$LAST_TAG" | sed 's/.*-v/v/' | sed 's/^v//') - - # Generate changelog for this app - TEMP_CHANGELOG=$(mktemp) - cd "$WORKING_DIR" 2>/dev/null || cd . - - gptchangelog generate \ - --since "$SINCE" \ - --to "$TO" \ - --output "$TEMP_CHANGELOG" || { - echo "โš ๏ธ Failed to generate changelog for $APP_NAME" - continue - } - - # Clean up markdown blocks - sed -i '/^```/d' "$TEMP_CHANGELOG" - - # Extract content (skip the header line if present) - CONTENT=$(cat "$TEMP_CHANGELOG" | tail -n +2) - - # Build app section for consolidated changelog - APP_SECTION="### ${APP_NAME} v${VERSION} - -${CONTENT} -" - # Append to consolidated files - echo "$APP_SECTION" >> /tmp/consolidated_changelog.md - echo "$APP_SECTION" >> /tmp/consolidated_release_notes.md - - # Track which apps were updated - echo "${APP_NAME}:${VERSION}" >> /tmp/apps_updated.txt - - # Update GitHub Release for this app's tag (if not HEAD) - if [ "$LAST_TAG" != "HEAD" ]; then - echo "$APP_SECTION" > /tmp/app_release_notes.md - gh release edit "$LAST_TAG" --notes-file /tmp/app_release_notes.md || \ - echo "โš ๏ธ Could not update release for $LAST_TAG" - fi - - cd - >/dev/null 2>&1 || true - rm -f "$TEMP_CHANGELOG" - - echo "โœ… Processed $APP_NAME" - done - - # Create final consolidated CHANGELOG.md - if [ -f /tmp/consolidated_changelog.md ]; then - APPS_LIST=$(cat /tmp/apps_updated.txt 2>/dev/null | tr '\n' ', ' | sed 's/,$//') - - # Prepare the new changelog entry - NEW_ENTRY="## [$CURRENT_DATE] - -$( cat /tmp/consolidated_changelog.md ) ---- -" - # Prepend to existing CHANGELOG.md or create new one - if [ -f CHANGELOG.md ]; then - # Insert after the title line - if grep -q "^# " CHANGELOG.md; then - TITLE=$(head -n 1 CHANGELOG.md) - EXISTING=$(tail -n +2 CHANGELOG.md) - echo "$TITLE - -$NEW_ENTRY -$EXISTING" > CHANGELOG.md - else - echo "# Changelog - -$NEW_ENTRY -$(cat CHANGELOG.md)" > CHANGELOG.md - fi - else - echo "# Changelog - -$NEW_ENTRY" > CHANGELOG.md - fi - - # Create RELEASE_NOTES.md - echo "# Release Notes - $CURRENT_DATE - -$(cat /tmp/consolidated_release_notes.md)" > RELEASE_NOTES.md - - echo "apps_updated=$APPS_LIST" >> $GITHUB_OUTPUT - echo "โœ… Consolidated changelog created with apps: $APPS_LIST" - else - echo "โš ๏ธ No changelog content generated" - echo "apps_updated=" >> $GITHUB_OUTPUT - fi - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - GH_TOKEN: ${{ steps.app-token.outputs.token }} - - - name: Show generated changelog - run: | - echo "๐Ÿ“„ Generated CHANGELOG.md:" - echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" - head -100 CHANGELOG.md - echo "" - echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" - - - name: Create changelog PR - if: steps.generate.outputs.apps_updated != '' - run: | - BASE_BRANCH="${GITHUB_REF##*/}" - CURRENT_DATE=$(date +%Y-%m-%d) - BRANCH_NAME="release/update-changelog-${CURRENT_DATE}" - APPS_UPDATED="${{ steps.generate.outputs.apps_updated }}" - - echo "๐Ÿ“Œ Creating branch: $BRANCH_NAME" - git checkout -b "$BRANCH_NAME" - - # Add and commit CHANGELOG - git add CHANGELOG.md - if ! git diff --cached --quiet; then - git commit -S -m "chore(release): Update CHANGELOG for ${APPS_UPDATED}" - echo "โœ… CHANGELOG committed" - else - echo "โš ๏ธ No changes to CHANGELOG" - exit 0 - fi - - # Merge base branch to resolve conflicts - git fetch origin "$BASE_BRANCH" - git merge -X ours origin/"$BASE_BRANCH" --no-ff -m "Merge $BASE_BRANCH into ${BRANCH_NAME}" || { - git checkout --ours CHANGELOG.md - git add CHANGELOG.md - git commit -m "resolve conflict using ours strategy" - } - - # Push and create PR - git push --force-with-lease origin "$BRANCH_NAME" - - if ! gh pr view "$BRANCH_NAME" --base "$BASE_BRANCH" > /dev/null 2>&1; then - gh pr create \ - --title "chore(release): Update CHANGELOG - ${CURRENT_DATE}" \ - --body "## Automatic Changelog Update - -**Date:** ${CURRENT_DATE} -**Apps Updated:** ${APPS_UPDATED} - -### Changes -- Updated CHANGELOG.md with consolidated release notes -- Each app section generated by GPTChangelog using OpenAI GPT-4o - -### Apps Included -$(echo "$APPS_UPDATED" | tr ',' '\n' | sed 's/^/- /') - ---- -*This PR was automatically generated by the GPTChangelog workflow.*" \ - --base "$BASE_BRANCH" \ - --head "$BRANCH_NAME" - echo "โœ… PR created" - else - echo "โš ๏ธ PR already exists" - fi - - # Auto-merge if possible - gh pr merge --merge --delete-branch || echo "โš ๏ธ Could not auto-merge PR" - env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} - - - name: Cleanup sensitive files - if: always() - run: | - rm -f .env - rm -rf .gptchangelog - rm -f /tmp/consolidated_changelog.md /tmp/consolidated_release_notes.md /tmp/apps_updated.txt /tmp/app_release_notes.md - echo "๐Ÿงน Cleaned up sensitive files" - - # Slack notification - notify: - name: Notify - needs: [prepare, generate_changelog] - if: always() && needs.prepare.outputs.has_changes == 'true' - uses: ./.github/workflows/slack-notify.yml - with: - status: ${{ needs.generate_changelog.result }} - workflow_name: "GPT Changelog" - failed_jobs: ${{ needs.generate_changelog.result == 'failure' && 'Generate Changelog' || '' }} - secrets: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} -``` - -**Step 2: Verify file was created** - -Run: `ls -la /Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows/.github/workflows/gptchangelog.yml` - -**Expected output:** -``` --rw-r--r-- 1 user staff XXXX Dec XX XX:XX gptchangelog.yml -``` - -**If Task Fails:** - -1. **Directory doesn't exist:** - - Run: `mkdir -p .github/workflows` - - Retry file creation - -2. **Permission denied:** - - Check file permissions - - Run with appropriate permissions - ---- - -## Task 3: Create the Documentation File - -**Files:** -- Create: `/Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows/docs/gptchangelog-workflow.md` - -**Prerequisites:** -- Tools: Text editor -- Branch: `feature/gptchangelog-workflow` -- Files must exist: `docs/` directory - -**Step 1: Create the documentation file** - -Create the file `docs/gptchangelog-workflow.md` with the following content: - -```markdown -# GPT Changelog Workflow - -Reusable workflow for generating CHANGELOG.md and RELEASE_NOTES.md using GPTChangelog. Uses OpenAI GPT-4o to analyze commits and generate human-readable, categorized changelogs. - -## Features - -- **AI-powered changelog generation**: Uses OpenAI GPT-4o for intelligent commit analysis -- **Monorepo support**: Automatic detection of changed components via filter_paths -- **GitHub Release integration**: Automatically updates release notes -- **GPG signing**: Signed commits for changelog PRs -- **Tag-based versioning**: Handles between-tags, first-tag, and no-tags scenarios -- **Automatic PR creation**: Creates and optionally auto-merges changelog PRs -- **Slack notifications**: Automatic success/failure notifications - -## Usage - -### Single App Repository - -```yaml -name: Generate Changelog -on: - push: - tags: - - 'v*' - -jobs: - changelog: - uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main - with: - runner_type: "blacksmith" - secrets: inherit -``` - -**Output (single app):** -```markdown -# Changelog - -## [2025-12-12] - -### my-app v1.2.0 - -#### โœจ Features -- Added new authentication flow -- Implemented caching layer - -#### ๐Ÿ›  Fixes -- Fixed memory leak in worker process - ---- - -## [2025-12-01] -... -``` - -### Monorepo with Multiple Components (e.g., Helm Charts, Microservices) - -```yaml -name: Generate Changelog -on: - push: - tags: - - '**' - -jobs: - changelog: - uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main - with: - runner_type: "blacksmith" - filter_paths: |- - charts/agent - charts/control-plane - charts/midaz - charts/plugin-access-manager - charts/plugin-crm - charts/plugin-fees - charts/reporter - path_level: '2' - secrets: inherit -``` - -**Output (monorepo with multiple apps changed):** -```markdown -# Changelog - -## [2025-12-12] - -### agent v1.2.0 - -#### โœจ Features -- Added new metric collection endpoint - -#### ๐Ÿ›  Fixes -- Fixed reconnection logic - -### midaz v2.1.0 - -#### โœจ Features -- New transaction batching API - -#### ๐Ÿš€ Improvements -- Optimized database queries - -### control-plane v1.5.0 - -#### ๐Ÿ›  Fixes -- Fixed race condition in scheduler - ---- - -## [2025-12-01] -... -``` - -**Key Benefit:** All apps are included in ONE CHANGELOG.md file - no more overwrites when multiple apps change! - -### After Release Workflow - -```yaml -name: Release Pipeline -on: - push: - branches: - - main - -jobs: - release: - uses: LerianStudio/github-actions-shared-workflows/.github/workflows/release.yml@main - secrets: inherit - - changelog: - needs: release - uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main - with: - runner_type: "blacksmith" - secrets: inherit -``` - -## Inputs - -| Input | Type | Default | Description | -|-------|------|---------|-------------| -| `runner_type` | string | `blacksmith` | GitHub runner type | -| `filter_paths` | string | `''` | Newline-separated list of path prefixes. If empty, single-app mode | -| `path_level` | string | `2` | Directory depth for app name extraction | -| `openai_model` | string | `gpt-4o` | OpenAI model for changelog generation | -| `max_context_tokens` | string | `80000` | Maximum context tokens for OpenAI API | -| `python_version` | string | `3.10` | Python version to use | - -## Secrets - -### Required Secrets - -| Secret | Description | -|--------|-------------| -| `OPENAI_API_KEY` | OpenAI API key for GPT-4o access | -| `LERIAN_STUDIO_MIDAZ_PUSH_BOT_APP_ID` | GitHub App ID for authentication | -| `LERIAN_STUDIO_MIDAZ_PUSH_BOT_PRIVATE_KEY` | GitHub App private key | -| `LERIAN_CI_CD_USER_GPG_KEY` | GPG private key for signing commits | -| `LERIAN_CI_CD_USER_GPG_KEY_PASSWORD` | GPG key passphrase | -| `LERIAN_CI_CD_USER_NAME` | Git committer name | -| `LERIAN_CI_CD_USER_EMAIL` | Git committer email | - -### Optional Secrets - -| Secret | Description | -|--------|-------------| -| `SLACK_WEBHOOK_URL` | Slack webhook for notifications (gracefully skipped if not provided) | - -## How It Works - -### Consolidated Changelog Architecture - -Unlike traditional matrix-based approaches where each app generates its own changelog (causing overwrites), this workflow uses a **single-job consolidated approach**: - -1. **Detect all changed apps** via `changed-paths` action -2. **Single job iterates** through all changed apps -3. **Accumulates changelog entries** per app into one consolidated file -4. **Creates one PR** with all changes - -**Result:** One CHANGELOG.md at repo root with sections for each app that changed. - -### Version Range Detection - -The workflow automatically determines the commit range for changelog generation: - -| Scenario | Range | Example | -|----------|-------|---------| -| Two or more tags | Previous tag โ†’ Current tag | `v1.0.0...v1.1.0` | -| First tag | First commit โ†’ Current tag | `abc123...v1.0.0` | -| No tags | First commit โ†’ HEAD | `abc123...HEAD` | - -### Monorepo Tag Patterns - -For monorepos, the workflow supports app-specific tags: - -| App | Tag Pattern | Example | -|-----|-------------|---------| -| agent | `agent-v*` | `agent-v1.0.0` | -| control-plane | `control-plane-v*` | `control-plane-v2.1.0` | - -This works with **any directory structure** - not just charts. Examples: -- `apps/api`, `apps/worker` โ†’ tags: `api-v1.0.0`, `worker-v2.0.0` -- `services/auth`, `services/billing` โ†’ tags: `auth-v1.0.0`, `billing-v1.5.0` -- `packages/core`, `packages/utils` โ†’ tags: `core-v3.0.0`, `utils-v1.2.0` - -### Generated Files - -| File | Description | -|------|-------------| -| `CHANGELOG.md` | **Single consolidated** changelog with sections per app | -| `RELEASE_NOTES.md` | Release-specific notes (also consolidated) | - -### Changelog Structure - -```markdown -# Changelog - -## [2025-12-12] - -### app-1 v1.2.0 -- Changes for app-1... - -### app-2 v2.0.0 -- Changes for app-2... - ---- - -## [2025-12-01] -- Previous release entries... -``` - -### Changelog Categories - -GPTChangelog organizes commits into these categories: -- โœจ **Features**: New features added -- ๐Ÿ›  **Fixes**: Bug fixes and improvements -- ๐Ÿ“š **Documentation**: Documentation updates -- ๐Ÿš€ **Improvements**: Performance or backend optimizations -- โš ๏ธ **Breaking Changes**: Breaking changes -- ๐Ÿ™Œ **Contributors**: Acknowledgments - -## Workflow Jobs - -### prepare -- Detects changed paths (monorepo) or sets single-app mode -- Outputs matrix for changelog generation job - -### generate_changelog -- Installs gptchangelog and dependencies -- Generates CHANGELOG.md and RELEASE_NOTES.md -- Updates GitHub Release with release notes -- Creates PR with changelog update (GPG-signed) -- Auto-merges PR if possible - -### notify -- Sends Slack notification on completion -- Skipped if `SLACK_WEBHOOK_URL` not configured - -## Best Practices - -### 1. Trigger After Release - -Run changelog generation after the release workflow: - -```yaml -changelog: - needs: release - uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main -``` - -### 2. Use Conventional Commits - -GPTChangelog works best with conventional commits: -- `feat:` - New features -- `fix:` - Bug fixes -- `docs:` - Documentation -- `perf:` - Performance improvements - -### 3. Configure Slack Notifications - -Add `SLACK_WEBHOOK_URL` secret for team notifications. - -### 4. Review Generated Changelogs - -The workflow creates PRs for review before merging. Disable auto-merge if manual review is required. - -## Troubleshooting - -### No changelog generated - -**Issue**: Workflow runs but no CHANGELOG.md is created - -**Solutions**: -1. Check OpenAI API key is valid -2. Verify tag format matches expected pattern -3. Check if there are commits in the version range -4. Review workflow logs for gptchangelog errors - -### Version header not updated - -**Issue**: CHANGELOG shows wrong version - -**Solutions**: -1. Verify tag format (should include version number) -2. Check sed command output in logs -3. Ensure CHANGELOG has standard version header format - -### PR not created - -**Issue**: Changelog generated but PR fails - -**Solutions**: -1. Verify GitHub App has `contents: write` and `pull-requests: write` permissions -2. Check if branch already exists -3. Review PR creation step logs - -### OpenAI API errors - -**Issue**: gptchangelog fails with API errors - -**Solutions**: -1. Verify `OPENAI_API_KEY` is set correctly -2. Check API rate limits -3. Try reducing `max_context_tokens` -4. Ensure model name is valid (`gpt-4o`) - -### Monorepo changes not detected - -**Issue**: No apps in matrix for monorepo - -**Solutions**: -1. Verify `filter_paths` matches your directory structure -2. Check `path_level` is correct -3. Ensure changes are in tracked paths -4. Review changed-paths action output - -## Examples - -### Basic Single App - -```yaml -name: Changelog -on: - push: - tags: ['v*'] - -jobs: - changelog: - uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main - secrets: inherit -``` - -### Helm Charts Monorepo - -```yaml -name: Changelog -on: - push: - tags: ['**'] - -jobs: - changelog: - uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main - with: - filter_paths: |- - charts/agent - charts/control-plane - charts/midaz - charts/reporter - path_level: '2' - secrets: inherit -``` - -### Custom OpenAI Model - -```yaml -changelog: - uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main - with: - openai_model: 'gpt-4-turbo' - max_context_tokens: '128000' - secrets: inherit -``` - -## Related Workflows - -- [Release](release-workflow.md) - Create releases that trigger changelog generation -- [Build](build-workflow.md) - Build Docker images after release -- [Slack Notify](slack-notify-workflow.md) - Notification system - ---- - -**Last Updated:** 2025-12-12 -**Version:** 1.0.0 -``` - -**Step 2: Verify file was created** - -Run: `ls -la /Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows/docs/gptchangelog-workflow.md` - -**Expected output:** -``` --rw-r--r-- 1 user staff XXXX Dec XX XX:XX gptchangelog-workflow.md -``` - -**If Task Fails:** - -1. **Directory doesn't exist:** - - Run: `mkdir -p docs` - - Retry file creation - ---- - -## Task 4: Update README.md with New Workflow - -**Files:** -- Modify: `/Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows/README.md` - -**Prerequisites:** -- Tools: Text editor -- Branch: `feature/gptchangelog-workflow` -- Files must exist: `README.md` - -**Step 1: Read the current README.md** - -Run: `cat /Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows/README.md` - -**Step 2: Add the new workflow entry to the Available Workflows section** - -Find the line `### 12. [Slack Notify]` and add the following entry AFTER it (as item 13): - -```markdown -### 13. [GPT Changelog](docs/gptchangelog-workflow.md) -AI-powered changelog generation using GPTChangelog and OpenAI GPT-4o. - -**Key Features**: AI commit analysis, monorepo support, GitHub Release integration, GPG signing -``` - -**Step 3: Verify the update** - -Run: `grep -A3 "GPT Changelog" /Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows/README.md` - -**Expected output:** -``` -### 13. [GPT Changelog](docs/gptchangelog-workflow.md) -AI-powered changelog generation using GPTChangelog and OpenAI GPT-4o. - -**Key Features**: AI commit analysis, monorepo support, GitHub Release integration, GPG signing -``` - -**If Task Fails:** - -1. **Pattern not found:** - - Check for alternate section names - - Manually locate the workflow list section - - Add entry at appropriate location - ---- - -## Task 5: Commit All Changes - -**Files:** -- All files created/modified in previous tasks - -**Prerequisites:** -- Tools: Git -- Branch: `feature/gptchangelog-workflow` -- All files created successfully - -**Step 1: Check status of changes** - -Run: `cd /Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows && git status` - -**Expected output:** -``` -On branch feature/gptchangelog-workflow -Changes not staged for commit: - modified: README.md - -Untracked files: - .github/workflows/gptchangelog.yml - docs/gptchangelog-workflow.md -``` - -**Step 2: Stage all changes** - -```bash -git add .github/workflows/gptchangelog.yml -git add docs/gptchangelog-workflow.md -git add README.md -``` - -**Step 3: Commit changes** - -```bash -git commit -m "feat: add GPTChangelog reusable workflow - -- Add gptchangelog.yml reusable workflow with monorepo support -- Add comprehensive documentation in docs/gptchangelog-workflow.md -- Update README.md with new workflow entry - -Features: -- AI-powered changelog generation using OpenAI GPT-4o -- Monorepo support via filter_paths and path_level -- GitHub Release notes integration -- GPG-signed commits and PRs -- Slack notification integration -- Blacksmith runner as default" -``` - -**Expected output:** -``` -[feature/gptchangelog-workflow XXXXXXX] feat: add GPTChangelog reusable workflow - 3 files changed, XXX insertions(+) - create mode 100644 .github/workflows/gptchangelog.yml - create mode 100644 docs/gptchangelog-workflow.md -``` - -**Step 4: Verify commit** - -Run: `git log --oneline -1` - -**Expected output:** -``` -XXXXXXX feat: add GPTChangelog reusable workflow -``` - -**If Task Fails:** - -1. **Nothing to commit:** - - Verify files were created correctly - - Check git status for untracked files - -2. **GPG signing required but fails:** - - Use `git commit --no-gpg-sign` for local testing - - Fix GPG setup before pushing - ---- - -## Task 6: Push Branch and Create PR - -**Files:** -- None (git operation) - -**Prerequisites:** -- Tools: Git, GitHub CLI (gh) -- Branch: `feature/gptchangelog-workflow` with commit -- Remote: origin configured - -**Step 1: Push branch to remote** - -```bash -cd /Users/ferr3ira/Documents/empresas/lerian-studio/projetos/github/modules/github-actions-shared-workflows -git push -u origin feature/gptchangelog-workflow -``` - -**Expected output:** -``` -Enumerating objects: ... -... -To github.com:LerianStudio/github-actions-shared-workflows.git - * [new branch] feature/gptchangelog-workflow -> feature/gptchangelog-workflow -Branch 'feature/gptchangelog-workflow' set up to track remote branch 'feature/gptchangelog-workflow' from 'origin'. -``` - -**Step 2: Create Pull Request** - -```bash -gh pr create \ - --title "feat: add GPTChangelog reusable workflow" \ - --body "## Summary -Adds a new reusable workflow for generating CHANGELOG.md and RELEASE_NOTES.md using GPTChangelog with OpenAI GPT-4o. - -## Features -- โœจ AI-powered changelog generation using OpenAI GPT-4o -- ๐Ÿ“ฆ Monorepo support via \`filter_paths\` and \`path_level\` inputs -- ๐Ÿ“ GitHub Release notes integration -- ๐Ÿ” GPG-signed commits and PRs -- ๐Ÿ“ข Slack notification integration -- ๐Ÿƒ Blacksmith runner as default - -## Files Added -- \`.github/workflows/gptchangelog.yml\` - The reusable workflow -- \`docs/gptchangelog-workflow.md\` - Comprehensive documentation - -## Files Modified -- \`README.md\` - Added workflow to the list - -## Usage Example - -### Single App -\`\`\`yaml -changelog: - uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main - secrets: inherit -\`\`\` - -### Monorepo (Helm Charts) -\`\`\`yaml -changelog: - uses: LerianStudio/github-actions-shared-workflows/.github/workflows/gptchangelog.yml@main - with: - filter_paths: |- - charts/agent - charts/control-plane - charts/midaz - path_level: '2' - secrets: inherit -\`\`\` - -## Migration from Standalone Action -This workflow replaces the standalone \`github-actions-gptchangelog\` composite action with a reusable workflow that follows the shared-workflows patterns. - -## Testing -- [ ] Test with single-app repository -- [ ] Test with monorepo (helm charts) -- [ ] Verify GPG signing works -- [ ] Verify Slack notifications work" \ - --base develop \ - --head feature/gptchangelog-workflow -``` - -**Expected output:** -``` -https://github.com/LerianStudio/github-actions-shared-workflows/pull/XX -``` - -**If Task Fails:** - -1. **gh not authenticated:** - - Run: `gh auth login` - - Follow authentication steps - -2. **develop branch doesn't exist as base:** - - Use `--base main` instead - -3. **PR already exists:** - - Run: `gh pr view` to see existing PR - ---- - -## Task 7: Run Code Review - -> **Note:** This task should be run after PR creation to ensure code quality. - -**Step 1: Dispatch all 3 reviewers in parallel:** -- REQUIRED SUB-SKILL: Use ring-default:requesting-code-review -- All reviewers run simultaneously (ring-default:code-reviewer, ring-default:business-logic-reviewer, ring-default:security-reviewer) -- Wait for all to complete - -**Step 2: Handle findings by severity (MANDATORY):** - -**Critical/High/Medium Issues:** -- Fix immediately (do NOT add TODO comments for these severities) -- Re-run all 3 reviewers in parallel after fixes -- Repeat until zero Critical/High/Medium issues remain - -**Low Issues:** -- Add `TODO(review):` comments in code at the relevant location -- Format: `TODO(review): [Issue description] (reported by [reviewer] on [date], severity: Low)` - -**Cosmetic/Nitpick Issues:** -- Add `FIXME(nitpick):` comments in code at the relevant location -- Format: `FIXME(nitpick): [Issue description] (reported by [reviewer] on [date], severity: Cosmetic)` - -**Step 3: Proceed only when:** -- Zero Critical/High/Medium issues remain -- All Low issues have TODO(review): comments added -- All Cosmetic issues have FIXME(nitpick): comments added - ---- - -## Summary Checklist - -Before completing the implementation: - -- [ ] Feature branch created from develop -- [ ] `.github/workflows/gptchangelog.yml` created with all features -- [ ] `docs/gptchangelog-workflow.md` created with comprehensive documentation -- [ ] `README.md` updated with new workflow entry -- [ ] All changes committed with descriptive commit message -- [ ] Branch pushed to remote -- [ ] Pull Request created -- [ ] Code review completed with all issues addressed - ---- - -## Post-Implementation Testing - -After the PR is merged, test the workflow: - -### Test 1: Single App Repository - -1. Create a test tag on a single-app repository -2. Trigger the gptchangelog workflow -3. Verify CHANGELOG.md and RELEASE_NOTES.md are generated -4. Verify GitHub Release is updated -5. Verify PR is created and merged - -### Test 2: Monorepo (Helm Charts) - -1. Make changes to one chart in the helm repository -2. Create a tag for that chart -3. Trigger the gptchangelog workflow with appropriate filter_paths -4. Verify only the changed chart gets a changelog -5. Verify the PR is created with correct app name - -### Test 3: Slack Notifications - -1. Configure SLACK_WEBHOOK_URL secret -2. Run the workflow -3. Verify Slack notification is received - ---- - -**Plan created:** 2025-12-12 -**Author:** Factory Planning Agent From b66c08a107b96723fb7119d313436567ede17d27 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Mon, 29 Dec 2025 23:02:59 -0300 Subject: [PATCH 37/46] fix(release): prevent script injection via commit message Pass commit message through env variable instead of direct interpolation to prevent potential script injection attacks. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 388a0ed..6500905 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,12 +38,10 @@ jobs: steps: - name: Check if should skip (changelog commits) id: check-skip + env: + COMMIT_MSG: ${{ github.event.head_commit.message }} run: | - COMMIT_MSG=$(cat << 'EOF' - ${{ github.event.head_commit.message }} - EOF - ) - echo "๐Ÿ“Œ Commit message: $COMMIT_MSG" + echo "๐Ÿ“Œ Checking commit message for skip patterns" # Skip if commit message contains [skip ci] or is a changelog update if echo "$COMMIT_MSG" | grep -qiE '\[skip ci\]|chore\(release\): Update CHANGELOGs'; then From 39e01dd124dbdf1d44ae93709cc72864f2a53ea9 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Mon, 29 Dec 2025 23:04:01 -0300 Subject: [PATCH 38/46] docs(gptchangelog): fix OpenAI -> OpenRouter terminology Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- docs/gptchangelog-workflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/gptchangelog-workflow.md b/docs/gptchangelog-workflow.md index 573b319..3f8e2b4 100644 --- a/docs/gptchangelog-workflow.md +++ b/docs/gptchangelog-workflow.md @@ -288,7 +288,7 @@ Add `SLACK_WEBHOOK_URL` secret for team notifications. **Issue**: Workflow runs but no CHANGELOG.md is created **Solutions**: -1. Check OpenAI API key is valid +1. Check OpenRouter API key is valid (`OPENROUTER_API_KEY`) 2. Verify tag format matches expected pattern 3. Check if there are commits in the version range 4. Review workflow logs for gptchangelog errors From ba303a7d19a2199f2d7b172c4e45268da84a0fe1 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Mon, 29 Dec 2025 23:10:30 -0300 Subject: [PATCH 39/46] docs(gptchangelog): add race condition warning for tag triggers - Explain why workflow_run is recommended - Add warning about race conditions with push:tags trigger - Document benefits: avoids duplicates, ensures tag exists Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- docs/gptchangelog-workflow.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/gptchangelog-workflow.md b/docs/gptchangelog-workflow.md index 3f8e2b4..3ea2b0f 100644 --- a/docs/gptchangelog-workflow.md +++ b/docs/gptchangelog-workflow.md @@ -17,7 +17,10 @@ Reusable workflow for generating CHANGELOG.md using AI. Uses OpenRouter API (GPT ### Single App Repository (Recommended - After Release) -Trigger changelog generation after your Release workflow completes on main: +Trigger changelog generation after your Release workflow completes on main. This is the **recommended approach** because it: +- Avoids race conditions (only runs once after release completes) +- Ensures the release tag exists before generating changelog +- Prevents duplicate workflow runs ```yaml name: GPT Changelog @@ -45,6 +48,8 @@ jobs: ### Single App Repository (Tag Push Trigger) +> **Warning**: Using `push: tags` can cause race conditions if your release workflow also triggers on tags. Both workflows may run simultaneously, causing duplicate runs or upload conflicts. Prefer `workflow_run` trigger above. + ```yaml name: Generate Changelog on: From 8d47dcfed51e1167ed798ceafe0504efe88facc9 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Mon, 29 Dec 2025 23:13:34 -0300 Subject: [PATCH 40/46] docs(gptchangelog): add prerequisite to disable semantic-release changelog Must disable @semantic-release/changelog plugin when using GPT Changelog to avoid duplicate or conflicting changelog entries. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- docs/gptchangelog-workflow.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/gptchangelog-workflow.md b/docs/gptchangelog-workflow.md index 3ea2b0f..769cbe6 100644 --- a/docs/gptchangelog-workflow.md +++ b/docs/gptchangelog-workflow.md @@ -13,6 +13,25 @@ Reusable workflow for generating CHANGELOG.md using AI. Uses OpenRouter API (GPT - **Automatic PR creation**: Creates and optionally auto-merges changelog PRs - **Slack notifications**: Automatic success/failure notifications +## Prerequisites + +### Disable semantic-release changelog plugin + +When using GPT Changelog, you **must disable** the `@semantic-release/changelog` plugin in your `.releaserc.yml` to avoid conflicts: + +```yaml +# .releaserc.yml +plugins: + - "@semantic-release/commit-analyzer" + - "@semantic-release/release-notes-generator" + # Changelog disabled - using GPT Changelog instead + # - "@semantic-release/changelog" + - - "@semantic-release/github" + - successComment: "๐ŸŽ‰ This PR is included in version ${nextRelease.gitTag}" +``` + +If both are enabled, you'll get duplicate or conflicting changelog entries. + ## Usage ### Single App Repository (Recommended - After Release) From 0dd0c1c742d3f011443bc7be8139b1e45c42d524 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Mon, 29 Dec 2025 23:15:55 -0300 Subject: [PATCH 41/46] fix(gptchangelog): remove .env file creation for API key Use environment variable directly instead of writing to disk. This prevents API key from persisting if runner crashes. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .github/workflows/gptchangelog.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index 883d089..9b7f8d5 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -283,7 +283,6 @@ jobs: - name: Generate changelog for all apps id: generate run: | - echo "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" > .env git fetch --tags --force MATRIX='${{ needs.prepare.outputs.matrix }}' @@ -680,12 +679,11 @@ jobs: env: GH_TOKEN: ${{ steps.app-token.outputs.token }} - - name: Cleanup sensitive files + - name: Cleanup temporary files if: always() run: | - rm -f .env rm -f /tmp/apps_updated.txt /tmp/app_release_notes.md - echo "๐Ÿงน Cleaned up sensitive files" + echo "๐Ÿงน Cleaned up temporary files" # Slack notification for workflow status notify: From 19b1424ccf39f25e4ff01d4c180c95793d6dad8f Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Mon, 29 Dec 2025 23:35:12 -0300 Subject: [PATCH 42/46] fix: address CodeRabbit review feedback - Fix subshell pitfall: use process substitution for while loop to persist file writes - Add timeout and HTTP error handling for OpenRouter API call (60s max, 10s connect) - Add GPG signing (-S flag) to conflict resolution commit for consistency - Include api_response.json in cleanup step --- .github/workflows/gptchangelog.yml | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index 9b7f8d5..ee118fe 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -294,7 +294,8 @@ jobs: > /tmp/apps_updated.txt # Parse the matrix JSON and iterate through each app - echo "$MATRIX" | jq -c '.[]' | while read -r APP; do + # Using process substitution to avoid subshell issues with file writes + while read -r APP; do APP_NAME=$(echo "$APP" | jq -r '.name') WORKING_DIR=$(echo "$APP" | jq -r '.working_dir') @@ -412,8 +413,9 @@ jobs: PROMPT="Generate a changelog for ${APP_NAME} ONLY. Commits: ${COMMITS_TEXT} --- STRICT RULES: 1) NEVER mention other components - only ${APP_NAME}. 2) Use bullet points. 3) Group by Features, Fixes, Improvements (skip empty sections). 4) No markdown headers. 5) Max 5 bullet points. 6) End with Contributors: ${CONTRIBUTORS}" ESCAPED_PROMPT=$(echo "$PROMPT" | jq -Rs .) - # Call OpenRouter API (OpenAI-compatible) - RESPONSE=$(curl -s https://openrouter.ai/api/v1/chat/completions \ + # Call OpenRouter API (OpenAI-compatible) with timeout and error handling + HTTP_CODE=$(curl -s -w "%{http_code}" --max-time 60 --connect-timeout 10 -o /tmp/api_response.json \ + https://openrouter.ai/api/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENROUTER_API_KEY" \ -H "HTTP-Referer: https://github.com/${{ github.repository }}" \ @@ -425,6 +427,17 @@ jobs: \"max_tokens\": 1000 }") + # Check for HTTP errors + if [ "$HTTP_CODE" -ge 400 ]; then + echo "โš ๏ธ API returned HTTP $HTTP_CODE for $APP_NAME - skipping" + cat /tmp/api_response.json 2>/dev/null || true + rm -f "$TEMP_CHANGELOG" "$TEMP_COMMITS" /tmp/api_response.json + continue + fi + + RESPONSE=$(cat /tmp/api_response.json) + rm -f /tmp/api_response.json + # Extract content from response CONTENT=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // empty') @@ -503,7 +516,7 @@ jobs: echo "โœ… Processed $APP_NAME" rm -f "$TEMP_CHANGELOG" "$TEMP_COMMITS" - done + done < <(echo "$MATRIX" | jq -c '.[]') # Output results if [ -s /tmp/apps_updated.txt ]; then @@ -594,7 +607,7 @@ jobs: git add "$CHANGELOG_PATH" 2>/dev/null || true done < /tmp/apps_updated.txt fi - git commit -m "resolve conflict using ours strategy [skip ci]" || true + git commit -S -m "resolve conflict using ours strategy [skip ci]" || true } # Push and create PR @@ -682,7 +695,7 @@ jobs: - name: Cleanup temporary files if: always() run: | - rm -f /tmp/apps_updated.txt /tmp/app_release_notes.md + rm -f /tmp/apps_updated.txt /tmp/app_release_notes.md /tmp/api_response.json echo "๐Ÿงน Cleaned up temporary files" # Slack notification for workflow status From d76c3743928cce60d5a1e447bd19a83137e2887d Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Mon, 29 Dec 2025 23:47:40 -0300 Subject: [PATCH 43/46] fix: capture stderr for auto-merge failure details Redirect stderr to stdout so gh pr merge error messages are visible in logs --- .github/workflows/gptchangelog.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index ee118fe..5a5d68d 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -636,8 +636,8 @@ jobs: echo "โš ๏ธ PR already exists" fi - # Auto-merge if possible - gh pr merge --merge --delete-branch || echo "โš ๏ธ Could not auto-merge PR" + # Auto-merge if possible (capture stderr for failure details) + gh pr merge --merge --delete-branch 2>&1 || echo "โš ๏ธ Could not auto-merge PR (check above for details)" env: GH_TOKEN: ${{ steps.app-token.outputs.token }} From 482002cf8786fb6ba019cb623892ab0923e77061 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Tue, 30 Dec 2025 00:12:16 -0300 Subject: [PATCH 44/46] fix(gptchangelog): add --state open to PR existence check Prevents including closed/merged PRs when checking for existing sync PRs. This allows creating a new sync PR after previous ones were merged. --- .github/workflows/gptchangelog.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index 5a5d68d..2b8b925 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -660,8 +660,8 @@ jobs: echo "๐Ÿ“Œ Syncing $DEFAULT_BRANCH โ†’ $TARGET_BRANCH" - # Check if PR already exists from main to develop - EXISTING_PR=$(gh pr list --base "$TARGET_BRANCH" --head "$DEFAULT_BRANCH" --json number -q '.[0].number' 2>/dev/null || true) + # Check if PR already exists from main to develop (only open PRs) + EXISTING_PR=$(gh pr list --state open --base "$TARGET_BRANCH" --head "$DEFAULT_BRANCH" --json number -q '.[0].number' 2>/dev/null || true) if [ -n "$EXISTING_PR" ]; then echo "โš ๏ธ PR #$EXISTING_PR already exists for $DEFAULT_BRANCH โ†’ $TARGET_BRANCH" echo "sync_pr=" >> $GITHUB_OUTPUT From d4aecb1209c83d72d7a21335f266e8bb00d8a5d3 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Tue, 30 Dec 2025 00:24:52 -0300 Subject: [PATCH 45/46] fix(gptchangelog): correct GitHub noreply email format in comment GitHub format is id+username@users.noreply.github.com, not username+id@ --- .github/workflows/gptchangelog.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index 2b8b925..2a2f122 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -380,7 +380,7 @@ jobs: echo "$COMMITS_TEXT" # Get unique contributors (GitHub usernames) for this app - # Try to extract GitHub username from email (format: user@users.noreply.github.com or username+id@users.noreply.github.com) + # Try to extract GitHub username from email (format: user@users.noreply.github.com or id+username@users.noreply.github.com) if [ "$WORKING_DIR" != "." ]; then RAW_EMAILS=$(git log "$SINCE".."$LAST_TAG" --format='%ae' -- "$WORKING_DIR" 2>/dev/null | sort -u) else From 67406d50ee54a49e31f170127ec0e7a4f7e57ca8 Mon Sep 17 00:00:00 2001 From: Gabriel Ferreira Date: Tue, 30 Dec 2025 00:26:03 -0300 Subject: [PATCH 46/46] fix(gptchangelog): escape regex metacharacters in app names Fixes grep patterns that use APP_NAME to handle special characters like dots, brackets, asterisks, etc. in app names. - Line 187: escape before grep in prepare job - Line 311: escape before TAG_GREP_PATTERN in generate job This prevents pattern matching failures for app names like: - my.app (dot) - app[test] (brackets) - app* (asterisk) --- .github/workflows/gptchangelog.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index 2a2f122..0334213 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -184,7 +184,9 @@ jobs: APP_NAME=$(basename "$path") # Check if this app has a recent stable tag - LATEST_TAG=$(git tag --sort=-creatordate | grep "^${APP_NAME}-v" | grep -v -e "-beta" -e "-rc" -e "-alpha" -e "-dev" -e "-snapshot" | head -1) + # Escape regex metacharacters in app name for grep + APP_NAME_ESCAPED=$(echo "$APP_NAME" | sed 's/[.[\*^$()+?{}|\\]/\\&/g') + LATEST_TAG=$(git tag --sort=-creatordate | grep "^${APP_NAME_ESCAPED}-v" | grep -v -e "-beta" -e "-rc" -e "-alpha" -e "-dev" -e "-snapshot" | head -1) if [ -n "$LATEST_TAG" ]; then echo "๐Ÿ“ฆ Found stable tag for $APP_NAME: $LATEST_TAG" @@ -305,10 +307,12 @@ jobs: echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" # Determine tag pattern based on app type (monorepo vs single-app) + # Escape regex metacharacters in app name for grep patterns + APP_NAME_ESCAPED=$(echo "$APP_NAME" | sed 's/[.[\*^$()+?{}|\\]/\\&/g') if [ "$WORKING_DIR" != "." ]; then # Monorepo: tags are prefixed with app name (e.g., auth-v1.0.0) TAG_PATTERN="${APP_NAME}-v*" - TAG_GREP_PATTERN="^${APP_NAME}-v" + TAG_GREP_PATTERN="^${APP_NAME_ESCAPED}-v" else # Single-app repo: tags are just version (e.g., v1.0.0) TAG_PATTERN="v*"