Draft release notes #3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION. All rights reserved. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| # | |
| # SPDX-License-Identifier: Apache-2.0 | |
| # Generates release notes for an OSMO version using Claude Code, then opens | |
| # (or updates) a PR adding releases/X.Y.Z.md on main. | |
| # | |
| # Inputs come from git history between the previous release tag and the | |
| # current X.Y.Z tag (or main HEAD if the tag does not yet exist). The system | |
| # prompt is .github/release-notes-template.md. | |
| name: Draft release notes | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Release version (e.g. 6.3.0)' | |
| required: true | |
| model: | |
| description: 'LLM model name on NVIDIA gateway' | |
| default: 'aws/anthropic/claude-opus-4-7' | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| concurrency: | |
| # Serialize per-version so re-running for the same version replaces the | |
| # in-flight PR rather than racing. | |
| group: draft-release-notes-${{ inputs.version }} | |
| cancel-in-progress: true | |
| jobs: | |
| draft: | |
| environment: testbot-generate | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.SVC_OSMO_CI_TOKEN }} | |
| - name: Configure git | |
| run: | | |
| git config user.name "svc-osmo-ci" | |
| git config user.email "svc-osmo-ci@users.noreply.github.com" | |
| - name: Resolve tag range | |
| id: tags | |
| env: | |
| VERSION: ${{ inputs.version }} | |
| run: | | |
| set -euo pipefail | |
| if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| echo "::error::version must be MAJOR.MINOR.PATCH (got: $VERSION)" | |
| exit 1 | |
| fi | |
| # Largest semver tag strictly less than VERSION wins. sort -V handles | |
| # the ordering; awk filters out the requested version and any non- | |
| # semver tags. | |
| prev_tag=$(git tag --list '[0-9]*.[0-9]*.[0-9]*' \ | |
| | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' \ | |
| | awk -v v="$VERSION" '$0 != v' \ | |
| | sort -V \ | |
| | awk -v v="$VERSION" ' | |
| { | |
| split($0, a, "."); | |
| split(v, b, "."); | |
| if (a[1] < b[1] || | |
| (a[1] == b[1] && a[2] < b[2]) || | |
| (a[1] == b[1] && a[2] == b[2] && a[3] < b[3])) { | |
| print $0; | |
| } | |
| } | |
| ' \ | |
| | tail -n1) | |
| if [[ -z "$prev_tag" ]]; then | |
| echo "::error::no previous semver tag less than $VERSION" | |
| exit 1 | |
| fi | |
| echo "prev_tag=$prev_tag" | |
| # Upper bound: prefer the X.Y.Z tag if it exists (set by the GitLab | |
| # nominate pipeline). Otherwise use main HEAD, but only if main's | |
| # version.yaml matches the requested version. | |
| if git rev-parse --verify --quiet "refs/tags/$VERSION" >/dev/null; then | |
| current_ref="refs/tags/$VERSION" | |
| echo "Using tag $VERSION as upper bound" | |
| else | |
| file_version=$(yq -r '"\(.major).\(.minor).\(.revision)"' \ | |
| src/lib/utils/version.yaml 2>/dev/null || echo "") | |
| if [[ "$file_version" != "$VERSION" ]]; then | |
| echo "::error::tag $VERSION does not exist and version.yaml on main is '$file_version' (need '$VERSION'). Bump version on main first or wait for nominate." | |
| exit 1 | |
| fi | |
| current_ref="HEAD" | |
| echo "Tag $VERSION not found; using main HEAD (version.yaml matches)" | |
| fi | |
| { | |
| echo "prev_tag=$prev_tag" | |
| echo "current_ref=$current_ref" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Build input bundle | |
| id: bundle | |
| env: | |
| PREV_TAG: ${{ steps.tags.outputs.prev_tag }} | |
| CURRENT_REF: ${{ steps.tags.outputs.current_ref }} | |
| run: | | |
| set -euo pipefail | |
| bundle="$RUNNER_TEMP/release-notes-input.md" | |
| { | |
| echo "## Range" | |
| echo | |
| echo "From: \`$PREV_TAG\` → To: \`$CURRENT_REF\`" | |
| echo | |
| echo "## git log --no-merges (with bodies)" | |
| echo | |
| echo '```' | |
| git log --no-merges --pretty=format:'%h %s%n%b' "$PREV_TAG..$CURRENT_REF" | |
| echo | |
| echo '```' | |
| echo | |
| echo "## git diff --stat" | |
| echo | |
| echo '```' | |
| git diff --stat "$PREV_TAG..$CURRENT_REF" | |
| echo '```' | |
| echo | |
| echo "## Per-file diffs (user-facing surfaces)" | |
| for path in \ | |
| src/cli \ | |
| src/service/core/auth \ | |
| src/service/core/workflow \ | |
| src/service/core/data \ | |
| src/service/core/app \ | |
| src/service/core/config \ | |
| src/service/core/profile \ | |
| src/utils/roles \ | |
| src/lib/data \ | |
| src/ui ; do | |
| if git diff --quiet "$PREV_TAG..$CURRENT_REF" -- "$path" 2>/dev/null; then | |
| continue | |
| fi | |
| echo | |
| echo "### $path" | |
| echo | |
| echo '```diff' | |
| git diff "$PREV_TAG..$CURRENT_REF" -- "$path" || true | |
| echo '```' | |
| done | |
| } > "$bundle" | |
| echo "bundle_path=$bundle" >> "$GITHUB_OUTPUT" | |
| echo "::group::Input bundle (first 200 lines)" | |
| head -200 "$bundle" | |
| echo "::endgroup::" | |
| - name: Generate release notes | |
| id: generate | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.NVIDIA_NIM_KEY }} | |
| ANTHROPIC_BASE_URL: https://inference-api.nvidia.com | |
| ANTHROPIC_MODEL: ${{ inputs.model }} | |
| DISABLE_PROMPT_CACHING: "1" | |
| CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS: "1" | |
| VERSION: ${{ inputs.version }} | |
| BUNDLE: ${{ steps.bundle.outputs.bundle_path }} | |
| run: | | |
| set -euo pipefail | |
| template=".github/release-notes-template.md" | |
| if [[ ! -f "$template" ]]; then | |
| echo "::error::missing $template" | |
| exit 1 | |
| fi | |
| out="$RUNNER_TEMP/release-notes.md" | |
| prompt=$(cat <<EOF | |
| $(cat "$template") | |
| ---- | |
| Generate release notes for OSMO $VERSION following the template above. | |
| Output ONLY the markdown body of the release notes file — no | |
| surrounding commentary, no code fences. Start with the RC notice | |
| block (this is a pre-release variant) and end with the Getting OSMO | |
| section verbatim from the template. | |
| Inputs from git history follow: | |
| $(cat "$BUNDLE") | |
| EOF | |
| ) | |
| npx @anthropic-ai/claude-code@2.1.91 --print \ | |
| --model "$ANTHROPIC_MODEL" \ | |
| --allowedTools "Read,Glob,Grep" \ | |
| --max-turns 20 \ | |
| "$prompt" > "$out" | |
| if [[ ! -s "$out" ]]; then | |
| echo "::error::Claude returned empty output" | |
| exit 1 | |
| fi | |
| mkdir -p releases | |
| cp "$out" "releases/${VERSION}.md" | |
| echo "wrote releases/${VERSION}.md ($(wc -c < "releases/${VERSION}.md") bytes)" | |
| echo "::group::Generated release notes" | |
| cat "releases/${VERSION}.md" | |
| echo "::endgroup::" | |
| - name: Open or update PR | |
| env: | |
| GH_TOKEN: ${{ secrets.SVC_OSMO_CI_TOKEN }} | |
| VERSION: ${{ inputs.version }} | |
| run: | | |
| set -euo pipefail | |
| branch="release-notes/${VERSION}" | |
| title="release notes: ${VERSION}" | |
| git checkout -B "$branch" | |
| git add "releases/${VERSION}.md" | |
| if git diff --cached --quiet; then | |
| echo "no changes to commit; release notes already up to date" | |
| exit 0 | |
| fi | |
| git commit -m "$title" | |
| git push --force-with-lease origin "$branch" | |
| if existing=$(gh pr list --head "$branch" --state open --json number --jq '.[0].number' 2>/dev/null) && [[ -n "$existing" ]]; then | |
| echo "Updated existing PR #$existing on branch $branch" | |
| gh pr comment "$existing" --body "Refreshed by workflow run ${{ github.run_id }}." | |
| else | |
| gh pr create \ | |
| --title "$title" \ | |
| --body "Release notes for OSMO ${VERSION}, generated by the draft-release-notes workflow. Review the content for accuracy before merging." \ | |
| --base main \ | |
| --head "$branch" | |
| fi |