Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions .github/workflows/spec-sync-summary.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Spec Sync — refresh PR summary

# The inline "Summarize the PR diff" step in spec-sync.yml only runs once, when a sync PR is first
# opened, so its "## What changed" block reflects just the mechanical + AI-wiring commits. When a
# human later pushes follow-up commits to the sync branch (e.g. a breaking removal the AI didn't
# wire), this workflow re-runs the shared summarizer so the description tracks the FINAL diff.
#
# The summarizer writes into a stable <!-- what-changed:start/end --> fence, so re-running REPLACES
# that block in place (idempotent) and never disturbs body text written outside the fence. Bot
# pushes (mechanical snapshot / AI wiring) are skipped — the inline step already covers those, and
# skipping avoids a double-write race at open time. Best-effort: the script degrades to leaving the
# body untouched on any hiccup (missing PR, API error), and continue-on-error is unnecessary because
# the script itself exits 0 on every non-config failure.

on:
push:
branches: ['spec-sync/**']

permissions:
contents: read
pull-requests: write

concurrency:
# One refresh per branch; a newer push supersedes an in-flight one.
group: spec-sync-summary-${{ github.ref }}
cancel-in-progress: true

jobs:
refresh-summary:
# Only recompute for human follow-up commits. The mechanical commit is authored by
# spec-sync@users.noreply.github.com and the AI-wiring commit by a *[bot] account; both are
# already summarized inline in spec-sync.yml.
if: >-
github.event.deleted != true &&
github.event.head_commit != null &&
!contains(github.event.head_commit.author.email, '[bot]') &&
github.event.head_commit.author.email != 'spec-sync@users.noreply.github.com'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0 # summarize-pr.sh diffs origin/main...HEAD

- name: Refresh the PR "What changed" summary
env:
GH_TOKEN: ${{ secrets.SPEC_SYNC_TOKEN }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
Comment thread
tian-lan-landing marked this conversation as resolved.
Outdated
run: |
set -uo pipefail
git fetch --quiet origin main

branch="${GITHUB_REF_NAME}"
owner="${GITHUB_REPOSITORY%%/*}"
# Resolve the open PR for this branch via REST (needs only `repo`, unlike `gh pr view`).
url="$(gh api "repos/${GITHUB_REPOSITORY}/pulls?head=${owner}:${branch}&state=open" --jq '.[0].html_url // ""')"
if [ -z "$url" ]; then
echo "No open PR for ${branch}; nothing to refresh."
exit 0
fi

# V2 excludes the hand-maintained /v2/workflow* surface from "What changed". Keep this in
# sync with the SUMMARY_SCOPE_NOTE on the V2 job in spec-sync.yml.
if [ "$branch" = "spec-sync/v2" ]; then
export SUMMARY_SCOPE_NOTE='Scope: this SDK intentionally does NOT implement /v2/workflow, /v2/workflow/jobs, or /v2/workflow/jobs/{job_id}; that surface is deferred and is not wired into the client. Do not describe any /v2/workflow* route as a client/SDK change. If the diff touches only workflow, say the spec snapshot was updated but no client surface changed.'
fi

./scripts/spec-sync/summarize-pr.sh "$url"
45 changes: 36 additions & 9 deletions scripts/spec-sync/summarize-pr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,42 @@ if [ -z "$summary" ]; then
exit 0
fi

# Read the current body and APPEND — never overwrite. Guard the read: a failure or empty body means
# we skip rather than clobber the static preamble with a bare summary.
body="$(gh pr view "$pr_url" --json body --jq .body)" || { echo "could not read PR body; keeping it."; exit 0; }
# Read the current body, then refresh the marked "What changed" block in it (replace-or-append, see
# below) — the rest of the body is never touched. Guard the read: a failure or empty body means we
# skip rather than clobber the static preamble with a bare summary. We go through the REST pulls
# endpoint rather than `gh pr view`/`gh pr edit`: `gh pr edit` eagerly loads org-level PR metadata
# (review-request team `slug`, assignee `login`/`name`) and fails with a `read:org` GraphQL scope
# error under SPEC_SYNC_TOKEN — a classic PAT scoped to `repo`+`workflow`. REST pull read/update
# needs only `repo`, so it works with the token we have.
rest_path="${pr_url#https://github.com/}" # <owner>/<repo>/pull/<n>
repo="${rest_path%/pull/*}" # <owner>/<repo>
num="${rest_path##*/}" # <n>
if [ "$repo" = "$rest_path" ] || ! [[ "$num" =~ ^[0-9]+$ ]]; then
echo "could not parse PR URL '$pr_url'; keeping the static body."; exit 0
fi

body="$(gh api "repos/$repo/pulls/$num" --jq '.body // ""')" || { echo "could not read PR body; keeping it."; exit 0; }
if [ -z "$body" ]; then echo "PR body empty/unreadable; keeping it."; exit 0; fi

# Avoid duplicating the section if the workflow is re-run.
if printf '%s\n' "$body" | grep -qF '## What changed'; then
echo 'What changed section already present; keeping the existing PR body.'
exit 0
fi
# Wrap the AI section in stable markers so a re-run REPLACES it in place (idempotent) rather than
# appending a second copy, and so it never disturbs body text a human wrote outside the fence. This
# is what lets a later push (see .github/workflows/spec-sync-summary.yml) refresh the summary against
# the final diff. `$summary` already had <!-- / --> stripped above, so it cannot forge the end marker.
block="$(printf '<!-- what-changed:start -->\n## What changed\n_AI-generated from the PR diff — verify against the actual changes._\n\n%s\n<!-- what-changed:end -->' "$summary")"

# Replace the existing marked block if present, else append a fresh one. perl slurps the whole body
# so multi-line markdown is handled; the replacement is an interpolated variable, inserted verbatim.
new_body="$(BODY="$body" BLOCK="$block" perl -0777 -e '
my ($b, $k) = ($ENV{BODY}, $ENV{BLOCK});
if ($b =~ /<!-- what-changed:start -->.*?<!-- what-changed:end -->/s) {
$b =~ s/<!-- what-changed:start -->.*?<!-- what-changed:end -->/$k/s;
} else {
$b =~ s/\s+\z//; # trim trailing whitespace before appending
$b .= "\n\n" . $k . "\n";
}
print $b;
')"

printf '%s\n\n## What changed\n_AI-generated from the PR diff — verify against the actual changes._\n\n%s\n' "$body" "$summary" | gh pr edit "$pr_url" --body-file -
printf '%s' "$new_body" | gh api --method PATCH "repos/$repo/pulls/$num" -F body=@- >/dev/null \
|| { echo "could not update PR body; keeping the existing body."; exit 0; }
echo "PR body updated with the What changed section."
Loading