Skip to content
This repository was archived by the owner on May 6, 2026. It is now read-only.

Commit 5269e67

Browse files
yysjasmineclaude
andcommitted
release: generate notes from git log, with RELEASE_NOTES.md override
Shared scripts/build-release-notes.sh emits Markdown release notes for a given tag, bucketing commits by their prefix (feat / fix / docs / installer / release / ci / vendor / chore / other). If a hand-written RELEASE_NOTES.md exists at repo root it's used verbatim instead — useful when a particular release deserves a curated summary. Both the CI workflow and scripts/release.sh now call this shared generator, so automated and manual releases produce identical notes. GitHub's built-in generate_release_notes was a bad fit: it synthesizes from PR titles, and this repo commits directly to main. Also set fetch-depth: 0 + fetch-tags: true on the release job's checkout so `git log <prev>..HEAD` actually has history to walk. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6aa8d4e commit 5269e67

3 files changed

Lines changed: 117 additions & 8 deletions

File tree

.github/workflows/release.yml

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,15 @@ jobs:
7777
needs: [build-windows, build-macos]
7878
runs-on: ubuntu-latest
7979
steps:
80-
# Checkout so we can grab linux/install.sh for the Linux download.
81-
# Nothing to build for Linux — install.sh bootstraps itself by
82-
# cloning agent_pack on the user's machine, same shape as the
83-
# curl | bash one-liner in the README.
80+
# Checkout full history + tags so build-release-notes.sh below can
81+
# diff against the previous tag. linux/install.sh is also grabbed
82+
# from this checkout and staged as a release asset (no build step —
83+
# it bootstraps itself on the user's machine, same shape as the
84+
# curl | bash one-liner in the README).
8485
- uses: actions/checkout@v4
86+
with:
87+
fetch-depth: 0
88+
fetch-tags: true
8589

8690
- name: Download Windows artifact
8791
uses: actions/download-artifact@v4
@@ -120,12 +124,22 @@ jobs:
120124
# Cover all three platforms' downloads.
121125
run: sha256sum AgentPack-* > SHA256SUMS
122126

127+
- name: Build release notes
128+
# Delegates to scripts/build-release-notes.sh — same script is used
129+
# by scripts/release.sh for local releases, so CI and manual
130+
# releases produce identical notes. Honors a hand-written
131+
# RELEASE_NOTES.md at repo root when present.
132+
run: |
133+
./scripts/build-release-notes.sh "${{ steps.tag.outputs.tag }}" > RELEASE_NOTES_BODY.md
134+
echo "---- Release notes ----"
135+
cat RELEASE_NOTES_BODY.md
136+
123137
- name: Create / update Release
124138
uses: softprops/action-gh-release@v2
125139
with:
126140
tag_name: ${{ steps.tag.outputs.tag }}
127141
name: Agent Pack ${{ steps.tag.outputs.tag }}
128-
generate_release_notes: true
142+
body_path: RELEASE_NOTES_BODY.md
129143
# fail_on_unmatched_files keeps us honest if a build artifact
130144
# silently went missing before upload.
131145
fail_on_unmatched_files: true

scripts/build-release-notes.sh

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#!/usr/bin/env bash
2+
# build-release-notes.sh — Emit release notes for <tag> to stdout.
3+
#
4+
# Usage: build-release-notes.sh <tag>
5+
#
6+
# Resolution order:
7+
# 1. If ./RELEASE_NOTES.md exists at repo root, print it verbatim and exit.
8+
# Use this to override for releases that deserve a hand-crafted summary.
9+
# 2. Otherwise synthesize Markdown from `git log <prev>..HEAD`, bucketing
10+
# commits by the "type:" prefix we already use (feat / fix / docs / ci
11+
# / installer / release / vendor / chore). Unprefixed commits fall
12+
# into "Other".
13+
# 3. If no previous v* tag exists, treat this as the first release and
14+
# list the entire history.
15+
#
16+
# Callers:
17+
# - .github/workflows/release.yml (CI release notes)
18+
# - scripts/release.sh (local `gh release create` uploader)
19+
#
20+
# Exits non-zero only on unexpected git failures; an empty commit range
21+
# still produces a valid (if short) notes body.
22+
23+
set -euo pipefail
24+
25+
[ $# -ge 1 ] || { echo "Usage: $0 <tag>" >&2; exit 2; }
26+
TAG="$1"
27+
28+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
29+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
30+
REPO_SLUG="${GITHUB_REPOSITORY:-SenseTime-FVG/agent_pack}"
31+
32+
# Hand-written override wins.
33+
if [ -f "$PROJECT_ROOT/RELEASE_NOTES.md" ]; then
34+
cat "$PROJECT_ROOT/RELEASE_NOTES.md"
35+
exit 0
36+
fi
37+
38+
cd "$PROJECT_ROOT"
39+
40+
# Previous tag = newest v* tag that isn't the one we're releasing. Empty
41+
# string means "no prior release — walk the whole history".
42+
prev="$(git tag --list 'v*' --sort=-v:refname | grep -v "^${TAG}$" | head -n1 || true)"
43+
44+
if [ -n "$prev" ]; then
45+
range="${prev}..HEAD"
46+
header="Changes since [$prev](https://github.com/${REPO_SLUG}/releases/tag/$prev)"
47+
else
48+
range="HEAD"
49+
header="Initial release."
50+
fi
51+
52+
echo "$header"
53+
echo
54+
55+
# Bucket recognized prefixes into titled sections, in a stable order so
56+
# diffs between consecutive releases look consistent.
57+
for group in \
58+
"feat|Features" \
59+
"fix|Bug Fixes" \
60+
"docs|Documentation" \
61+
"installer|Installer" \
62+
"release|Release" \
63+
"ci|CI" \
64+
"vendor|Vendored Changes" \
65+
"chore|Chores"; do
66+
prefix="${group%|*}"
67+
title="${group#*|}"
68+
body="$(git log --no-merges --pretty=format:"- %s (%h)" "$range" \
69+
| grep -E "^- ${prefix}(\([^)]+\))?:" || true)"
70+
if [ -n "$body" ]; then
71+
echo "### $title"
72+
echo
73+
echo "$body"
74+
echo
75+
fi
76+
done
77+
78+
# Anything that didn't match a known bucket. Useful when someone commits
79+
# without a prefix — it still shows up, just under "Other".
80+
other="$(git log --no-merges --pretty=format:"- %s (%h)" "$range" \
81+
| grep -vE "^- (feat|fix|docs|installer|release|ci|vendor|chore)(\([^)]+\))?:" || true)"
82+
if [ -n "$other" ]; then
83+
echo "### Other"
84+
echo
85+
echo "$other"
86+
echo
87+
fi

scripts/release.sh

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,16 +122,24 @@ else
122122
git push origin "$TAG"
123123
fi
124124

125-
# Default release notes come from the git log since the previous tag; if
126-
# the caller wants custom notes they pass --notes-file themselves.
125+
# Default release notes are produced by scripts/build-release-notes.sh —
126+
# same generator the CI workflow uses, so local and CI releases look
127+
# identical. The caller can still override with --notes-file pointing at
128+
# their own file, in which case we skip generation.
127129
HAS_NOTES=0
128130
for f in "${GH_FLAGS[@]}"; do
129131
case "$f" in
130132
--notes|--notes-file|--generate-notes) HAS_NOTES=1 ;;
131133
esac
132134
done
135+
NOTES_TMP=""
133136
if [ "$HAS_NOTES" -eq 0 ]; then
134-
GH_FLAGS+=(--generate-notes)
137+
NOTES_TMP="$(mktemp -t agent-pack-release-notes.XXXXXX)"
138+
# shellcheck disable=SC2064
139+
trap "rm -f '$NOTES_TMP'" EXIT
140+
note "Building release notes via scripts/build-release-notes.sh"
141+
"$SCRIPT_DIR/build-release-notes.sh" "$TAG" > "$NOTES_TMP"
142+
GH_FLAGS+=(--notes-file "$NOTES_TMP")
135143
fi
136144

137145
note "Creating release $TAG with ${#FILES[@]} asset(s)"

0 commit comments

Comments
 (0)