From 9bc3ef8a9088e7d3e477d6abce3df2388d2b03db Mon Sep 17 00:00:00 2001 From: akash-vijay-kv Date: Wed, 4 Nov 2026 14:12:40 +0530 Subject: [PATCH 1/2] chore: Add GitHub workflows for pre-commit checks and draft release notes generation --- .github/scripts/generate-release-notes.sh | 127 ++++++++++++++++++++++ .github/workflows/pre-commit.yml | 36 ++++++ .github/workflows/release-notes.yml | 66 +++++++++++ 3 files changed, 229 insertions(+) create mode 100755 .github/scripts/generate-release-notes.sh create mode 100644 .github/workflows/pre-commit.yml create mode 100644 .github/workflows/release-notes.yml diff --git a/.github/scripts/generate-release-notes.sh b/.github/scripts/generate-release-notes.sh new file mode 100755 index 00000000..3e5ba152 --- /dev/null +++ b/.github/scripts/generate-release-notes.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash +# Builds "What's Changed" release notes from the commits between the previous +# published release and TARGET_REF, then creates or refreshes a DRAFT release. +# +# Commits are read straight from the compare API rather than GitHub's +# auto-generated notes, because changes often land on main without a PR and the +# auto-generator only lists merged PRs. +# +# Environment: +# REPO owner/name (required) +# TARGET_REF commit SHA (or tag, for PRINT_ONLY replays) the release points at (required) +# VERSION version to release, without the "v" (required) +# PREVIOUS_TAG tag to diff against; defaults to the latest published, non-prerelease release +# PRINT_ONLY=1 print the notes and exit without creating or editing a release +# GITHUB_STEP_SUMMARY set by Actions; notes and skipped commits are appended to it +set -euo pipefail + +VERSION_PATTERN='^[0-9]+\.[0-9]+\.[0-9]+([a-z0-9.-]*)?$' +TAG_PATTERN='^[A-Za-z0-9._-]+$' +# A housekeeping commit is a `chore:` (optionally ticket-prefixed) whose subject +# mentions the version or changelog, e.g. "chore: Bump version to 1.2.4 ...". +HOUSEKEEPING_PATTERN='^(\[[^\]]+\]\s*)?chore(\([^)]*\))?:.*(version|changelog)' + +fail() { + echo "::error::$1" >&2 + exit 1 +} + +: "${REPO:?REPO is required}" +: "${TARGET_REF:?TARGET_REF is required}" +: "${VERSION:?VERSION is required}" +PRINT_ONLY="${PRINT_ONLY:-0}" + +[[ "$VERSION" =~ $VERSION_PATTERN ]] || fail "Version '$VERSION' is not a valid version (expected e.g. 1.2.5)." +TAG="v${VERSION}" + +if [[ -z "${PREVIOUS_TAG:-}" ]]; then + PREVIOUS_TAG="$(gh release list -R "$REPO" --exclude-drafts --exclude-pre-releases --limit 1 \ + --json tagName --jq '.[0].tagName // ""')" + [[ -n "$PREVIOUS_TAG" ]] || fail "No published release found to diff against; set previous_tag." +fi +[[ "$PREVIOUS_TAG" =~ $TAG_PATTERN ]] || fail "Previous tag '$PREVIOUS_TAG' contains unexpected characters." +gh api "repos/${REPO}/git/ref/tags/${PREVIOUS_TAG}" --silent \ + || fail "Previous tag '$PREVIOUS_TAG' does not exist in ${REPO}." +[[ "$PREVIOUS_TAG" != "$TAG" ]] || fail "Previous tag and release tag are both '$TAG'." + +# The compare API returns at most 250 commits per page set; --paginate walks them. +COMMITS_JSON="$(gh api --paginate "repos/${REPO}/compare/${PREVIOUS_TAG}...${TARGET_REF}?per_page=100" \ + --jq '.commits[]' | jq -s '.')" + +# One object per non-merge commit: its first message line and how to credit it. +ENTRIES_JSON="$(jq --arg housekeeping "$HOUSEKEEPING_PATTERN" ' + map(select((.parents | length) <= 1)) + | map( + (.commit.message | split("\n")[0]) as $subject + | { + subject: $subject, + sha: .sha[0:7], + skipped: ($subject | test($housekeeping; "i")), + line: ( + "* " + + ($subject + | sub("^\\[[^\\]]+\\]\\s*"; "") + | sub("\\s*\\(#(?[0-9]+)\\)$"; " in #\(.pr)")) + + " by " + + (if .author.login then "@" + .author.login else .commit.author.name end) + ) + } + )' <<<"$COMMITS_JSON")" + +BULLETS="$(jq -r 'map(select(.skipped | not)) | .[].line' <<<"$ENTRIES_JSON")" +SKIPPED="$(jq -r 'map(select(.skipped)) | .[] | "- `\(.sha)` \(.subject)"' <<<"$ENTRIES_JSON")" + +[[ -n "$BULLETS" ]] || fail "Nothing to release since ${PREVIOUS_TAG}: no commits left after skipping housekeeping." + +NOTES_FILE="$(mktemp)" +trap 'rm -f "$NOTES_FILE"' EXIT +cat >"$NOTES_FILE" <&2 +fi + +if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then + { + echo "# Draft release notes for ${TAG} (since ${PREVIOUS_TAG})" + echo + cat "$NOTES_FILE" + echo + echo "### Skipped as version/changelog housekeeping" + echo "${SKIPPED:-_None_}" + } >>"$GITHUB_STEP_SUMMARY" +fi + +[[ "$PRINT_ONLY" == "1" ]] && exit 0 + +# Draft releases are invisible to /releases/tags/{tag}, so scan the release list. +# Drafts and recent releases sort first, so the first 100 are enough. +EXISTING_JSON="$(gh api "repos/${REPO}/releases?per_page=100" \ + | jq --arg tag "$TAG" 'map(select(.tag_name == $tag)) | map({id, draft})')" + +if jq -e 'any(.[]; .draft | not)' <<<"$EXISTING_JSON" >/dev/null; then + fail "Release ${TAG} is already published; refusing to modify it." +fi + +DRAFT_COUNT="$(jq 'length' <<<"$EXISTING_JSON")" +case "$DRAFT_COUNT" in + 0) + gh release create "$TAG" -R "$REPO" --draft --target "$TARGET_REF" --title "$TAG" --notes-file "$NOTES_FILE" + ;; + 1) + DRAFT_ID="$(jq -r '.[0].id' <<<"$EXISTING_JSON")" + gh api -X PATCH "repos/${REPO}/releases/${DRAFT_ID}" \ + -F "body=@${NOTES_FILE}" -f "target_commitish=${TARGET_REF}" --jq '.html_url' + echo "Updated existing draft ${TAG}." + ;; + *) + fail "Found ${DRAFT_COUNT} drafts for ${TAG}; delete the extras and re-run." + ;; +esac diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 00000000..eee85758 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,36 @@ +name: pre-commit + +on: + pull_request: + branches: [main, develop] + types: [opened, synchronize, reopened] + +permissions: + contents: read + +concurrency: + group: pre-commit-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + pre-commit: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version-file: .python-version + + # Same version poetry.lock pins, so CI matches `poetry run pre-commit`. + - name: Install pre-commit + run: python -m pip install pre-commit==4.2.0 + + - uses: actions/cache@v4 + with: + path: ~/.cache/pre-commit + key: pre-commit-${{ runner.os }}-${{ hashFiles('.python-version', '.pre-commit-config.yaml') }} + + - name: Run pre-commit on all files + run: pre-commit run --all-files --show-diff-on-failure diff --git a/.github/workflows/release-notes.yml b/.github/workflows/release-notes.yml new file mode 100644 index 00000000..535eaeb0 --- /dev/null +++ b/.github/workflows/release-notes.yml @@ -0,0 +1,66 @@ +name: Generate release notes (draft) + +on: + workflow_dispatch: + inputs: + version: + description: "Version to release, without the v (blank = [project].version in pyproject.toml)" + required: false + type: string + previous_tag: + description: "Tag to compare against (blank = latest published, non-prerelease release)" + required: false + type: string + +permissions: + contents: write + +concurrency: + group: release-notes + cancel-in-progress: false + +jobs: + draft-release-notes: + runs-on: ubuntu-latest + timeout-minutes: 10 + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + steps: + - name: Allow only maintainers and admins + env: + ACTOR: ${{ github.actor }} + run: | + role="$(gh api "repos/${REPO}/collaborators/${ACTOR}/permission" --jq '.role_name')" + case "$role" in + admin|maintain) echo "${ACTOR} has role '${role}'; continuing." ;; + *) echo "::error::${ACTOR} has role '${role}'. Only maintain or admin may generate release notes."; exit 1 ;; + esac + + - name: Allow only the main branch + run: | + if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then + echo "::error::Run this workflow from main (got ${GITHUB_REF})." + exit 1 + fi + + - uses: actions/checkout@v4 + + - name: Resolve version + id: version + env: + INPUT_VERSION: ${{ inputs.version }} + run: | + if [[ -n "$INPUT_VERSION" ]]; then + version="$INPUT_VERSION" + else + version="$(python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')" + fi + echo "version=${version}" >> "$GITHUB_OUTPUT" + + - name: Create or update draft release + env: + VERSION: ${{ steps.version.outputs.version }} + PREVIOUS_TAG: ${{ inputs.previous_tag }} + TARGET_REF: ${{ github.sha }} + run: .github/scripts/generate-release-notes.sh From f586b5f0dfc7bbc830d77a7c634b6a44fbe78af3 Mon Sep 17 00:00:00 2001 From: akash-vijay-kv Date: Wed, 4 Nov 2026 14:16:02 +0530 Subject: [PATCH 2/2] chore: Remove unused import of trace from opentelemetry in test_codex_instrumentation.py --- tests/test_codex_instrumentation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_codex_instrumentation.py b/tests/test_codex_instrumentation.py index 68b62b38..e4a36490 100644 --- a/tests/test_codex_instrumentation.py +++ b/tests/test_codex_instrumentation.py @@ -15,7 +15,6 @@ from typing import Any, Dict, Iterator, List, Optional import pytest -from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import SimpleSpanProcessor from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter