From 56084ef54de50b47a9bedb5e90bc5668c2c54c72 Mon Sep 17 00:00:00 2001 From: "Daniel (dB.) Doubrovkine" Date: Tue, 11 Aug 2026 20:55:47 -0400 Subject: [PATCH] Generate AI CHANGELOG entries for automated API update PRs The Update API workflow committed a single generic CHANGELOG.md line for every automated update PR: "Update API from slack-api-ref@", with no indication of what actually changed. Following the same approach as slack-ruby/slack-api-ref#87, add a GitHub Models-backed step (actions/ai-inference@v1, default GITHUB_TOKEN with models: read permission, no extra secrets needed) that summarizes the diff produced by rake slack:api:update into a short list of grouped, descriptive entries (new/removed methods, argument/validation changes, error code changes, etc.) via a structured .github/prompts/changelog-entries.prompt.yml with a JSON schema response. Each generated entry becomes its own CHANGELOG.md bullet, all tagged with the same automated PR number, replacing the old single generic line. The same list is also included under a "## Changes" heading in the PR body for reviewers. Falls back to the original generic, rev-based message if there are no changes, the AI step fails, or the response is empty/malformed, so the workflow never blocks on this step. Uses Ruby (already set up in this workflow) instead of Python for the inline CHANGELOG-rewriting script, for consistency with the rest of this Ruby project. Also locks simplecov below 1.1.0 in the Gemfile: 1.1.0 changed Result#to_hash to write a float timestamp to coverage/.resultset.json, which coverallsapp/github-action's parser (typed Int64?) fails to deserialize, breaking the Coveralls step in CI (coverallsapp/github-action#269, fix tracked upstream in coverallsapp/coverage-reporter#191). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/prompts/changelog-entries.prompt.yml | 52 +++++++++++++++ .github/workflows/update_api.yml | 68 ++++++++++++++++++-- CHANGELOG.md | 1 + Gemfile | 5 +- 4 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 .github/prompts/changelog-entries.prompt.yml diff --git a/.github/prompts/changelog-entries.prompt.yml b/.github/prompts/changelog-entries.prompt.yml new file mode 100644 index 00000000..f8f47519 --- /dev/null +++ b/.github/prompts/changelog-entries.prompt.yml @@ -0,0 +1,52 @@ +messages: + - role: system + content: |- + You write CHANGELOG entries describing an automated API update to slack-ruby-client, a + Ruby gem whose Web API endpoint methods, argument validations, specs, and bin commands + are code-generated from JSON method definitions vendored from + https://github.com/slack-ruby/slack-api-ref (via a git submodule and rake task). + + Given a diffstat and a diff of the regenerated files, respond with a JSON object: + {"entries": [string, ...]} + + Rules for entries: + - Each entry is a single short line, imperative mood, no leading bullet/dash and no + trailing period (the caller adds both). + - Group related changes together into one entry per notable change or affected method + group (e.g. one entry for "Add chat.appendStream and chat.stopStream methods", one + entry for "Add a metadata argument to chat.postEphemeral, chat.postMessage, + chat.scheduleMessage, and chat.update"), rather than one entry per file. + - Focus on developer-visible API surface changes: new/removed/renamed methods, new/ + removed/changed arguments (including required/mutually-exclusive validation changes), + new/removed error codes, and behavioral changes to generated code. + - Skip purely mechanical noise (e.g. reformatted docs/comments with no semantic change). + - Produce at most 8 entries. If there are no notable API surface changes, return a single + generic entry describing the update in one line. + - role: user + content: |- + Diffstat: + {{diff_stat}} + + Diff (may be truncated): + {{diff}} +model: openai/gpt-4o-mini +responseFormat: json_schema +jsonSchema: |- + { + "name": "changelog_entries", + "strict": true, + "schema": { + "type": "object", + "properties": { + "entries": { + "type": "array", + "items": { + "type": "string" + }, + "description": "1-8 short, imperative-mood CHANGELOG entry descriptions, no leading bullet or trailing period" + } + }, + "additionalProperties": false, + "required": ["entries"] + } + } diff --git a/.github/workflows/update_api.yml b/.github/workflows/update_api.yml index fe069e48..3eeefc0a 100644 --- a/.github/workflows/update_api.yml +++ b/.github/workflows/update_api.yml @@ -10,6 +10,7 @@ jobs: permissions: contents: write pull-requests: write + models: read steps: - uses: actions/checkout@v7 with: @@ -32,6 +33,47 @@ jobs: - name: Get slack-api-ref ref id: api-ref run: echo "api-ref=$(git rev-parse --short HEAD:lib/slack/web/api/slack-api-ref)" >> $GITHUB_OUTPUT + - name: Check for changes + id: changes + run: | + if git diff --quiet; then + echo "changed=false" >> "$GITHUB_OUTPUT" + else + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + - name: Prepare diff for AI summary + if: steps.changes.outputs.changed == 'true' + run: | + git diff --stat > /tmp/diff_stat.txt + git diff | head -c 20000 > /tmp/diff.txt + - name: Generate changelog entries with AI + if: steps.changes.outputs.changed == 'true' + id: ai + uses: actions/ai-inference@v1 + with: + prompt-file: ./.github/prompts/changelog-entries.prompt.yml + file_input: | + diff_stat: /tmp/diff_stat.txt + diff: /tmp/diff.txt + - name: Build changelog entries + id: entries + run: | + entries="" + if [ "${{ steps.changes.outputs.changed }}" = "true" ]; then + entries="$(jq -r '.entries[]? // empty' "${{ steps.ai.outputs.response-file }}" 2>/dev/null)" + fi + if [ -z "$entries" ]; then + entries="Update API from slack-api-ref@${{ steps.api-ref.outputs.api-ref }}" + fi + body_list="$(printf '%s\n' "$entries" | sed 's/^/- /')" + { + echo 'entries<> "$GITHUB_OUTPUT" - name: GitHub App token if: ${{ github.repository == 'slack-ruby/slack-ruby-client' }} id: github_app_token @@ -52,6 +94,10 @@ jobs: Update API from [slack-api-ref](https://github.com/slack-ruby/slack-api-ref). Rev: ${{ steps.api-ref.outputs.api-ref }} Date: ${{ steps.date.outputs.date }} + + ## Changes + + ${{ steps.entries.outputs.body_list }} branch: automated-api-update base: master committer: slack-ruby-ci-bot @@ -62,12 +108,24 @@ jobs: git fetch origin automated-api-update git checkout automated-api-update - name: Update CHANGELOG - uses: jacobtomlinson/gha-find-replace@v3 if: ${{ steps.cpr.outputs.pull-request-number != '' }} - with: - include: CHANGELOG.md - find: "\\* Your contribution here." - replace: "* [#${{steps.cpr.outputs.pull-request-number}}](https://github.com/slack-ruby/slack-ruby-client/pull/${{steps.cpr.outputs.pull-request-number}}): Update API from [slack-api-ref@${{ steps.api-ref.outputs.api-ref }}](https://github.com/slack-ruby/slack-api-ref/commit/${{ steps.api-ref.outputs.api-ref }}) - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot).\n* Your contribution here." + env: + PR_NUMBER: ${{ steps.cpr.outputs.pull-request-number }} + ENTRIES: ${{ steps.entries.outputs.entries }} + run: | + ruby - <<'RUBYEOF' + pr_number = ENV.fetch("PR_NUMBER") + pr_url = "https://github.com/slack-ruby/slack-ruby-client/pull/#{pr_number}" + entries = ENV.fetch("ENTRIES").each_line.map(&:strip).reject(&:empty?) + + lines = entries.map do |entry| + "* [##{pr_number}](#{pr_url}): #{entry} - [@slack-ruby-ci-bot](https://github.com/apps/slack-ruby-ci-bot)." + end.join("\n") + + content = File.read("CHANGELOG.md") + content = content.sub("* Your contribution here.", "#{lines}\n* Your contribution here.") + File.write("CHANGELOG.md", content) + RUBYEOF - name: Commit and Push if: ${{ steps.cpr.outputs.pull-request-number != '' }} run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a60b654..83b7f8f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ### 3.2.1 (Next) +* [#591](https://github.com/slack-ruby/slack-ruby-client/pull/591): Generate AI CHANGELOG entries and PR summaries for automated API update PRs, lock simplecov below 1.1.0 to avoid breaking Coveralls - [@dblock](https://github.com/dblock). * Your contribution here. ### 3.2.0 (2026/07/05) diff --git a/Gemfile b/Gemfile index ccc186e4..5f209263 100644 --- a/Gemfile +++ b/Gemfile @@ -21,7 +21,10 @@ group :test do gem 'rubocop-performance' gem 'rubocop-rake' gem 'rubocop-rspec' - gem 'simplecov' + # Lock below 1.1.0, which started writing float timestamps to + # coverage/.resultset.json, breaking coverallsapp/github-action's parser + # (coverallsapp/github-action#269, coverallsapp/coverage-reporter#191). + gem 'simplecov', '< 1.1.0' gem 'simplecov-lcov' gem 'timecop' gem 'vcr'