Repository navigation
chore: generate release notes with Claude in Simplified Technical English - #792
Conversation
…lish GitHub's generated notes only list PR titles, so a release did not say what changed for the user. The release recipe now asks claude -p (Sonnet 5.5) to summarize the commits since the previous tag in ASD-STE100 Simplified Technical English and keeps the PR list and changelog link below. Notes are written before the version bump, so a failure stops the release before anything is pushed. `just release notes [tag]` previews the notes for a tag or for unreleased commits, and `just release renote <tag>` replaces the notes of a published release. The gh wrappers use plain gh when GH_TOKEN is set, because op plugin run needs an interactive prompt.
|
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
| cd "{{ root }}" | ||
| tag="{{ tag }}" |
There was a problem hiding this comment.
[important · security] Do not interpolate tag arguments into shell source
Just substitutes {{ tag }} before Bash parses this line, so a tag argument such as $(touch /tmp/marker) runs the command before the tag validation. The same pattern occurs in renote and in _notes for tag-derived from and to values; a fetched tag containing shell syntax reaches those assignments too. Pass these values as data rather than inserting them into Bash source.
Suggested fix
Use shell-safe argument passing for tag, from, and to throughout these recipes, including the nested just calls and the gh release edit argument; avoid direct {{ ... }} substitution into the script.
Prompt for a coding agent
In .just/release.just at lines 71, 94, 96, 103, and 104, and the renote-to-notes invocation, change the `notes`, `renote`, and `_notes` recipes to pass tag, from, and to values as shell-safe data instead of interpolating `{{ tag }}`, `{{ from }}`, or `{{ to }}` into Bash source. Ensure a tag containing shell metacharacters is rejected or handled literally without executing commands.
| cd "{{ root }}" | ||
| tag="{{ tag }}" | ||
| if test -z "$tag"; then | ||
| from="$(git describe --tags --abbrev=0 HEAD)" |
There was a problem hiding this comment.
[important · correctness] Handle previews before the first release tag exists
The release path explicitly supports a repository with no published release by using v0.0.0, but notes without an argument exits at git describe when there are no tags. Thus the advertised unreleased-notes preview cannot be used for the first release, although _notes already has sentinel-range handling.
| from="$(git describe --tags --abbrev=0 HEAD)" | |
| from="$(git describe --tags --abbrev=0 HEAD 2>/dev/null || echo v0.0.0)" |
Prompt for a coding agent
In .just/release.just line 73, update the `notes` recipe's no-tag branch to fall back to `v0.0.0` when `git describe` finds no tag, matching `_release` and `_notes`, so first-release notes can be previewed.
| repo="$(gh repo view --json nameWithOwner -q .nameWithOwner)" | ||
| previous=() | ||
| if test "$from" != "v0.0.0"; then previous=(-f previous_tag_name="$from"); fi | ||
| changes="$(gh api "repos/$repo/releases/generate-notes" -f tag_name="$to" "${previous[@]}" -q .body)" |
There was a problem hiding this comment.
[important · correctness] Pin GitHub's generated notes to the selected local target
For an unreleased target, this request supplies only a new tag_name; GitHub generates its PR list against its default branch while commits comes from the local HEAD. Running just release notes on a feature branch therefore combines a feature-branch Claude summary with a default-branch PR list and changelog. Send the selected commit as target_commitish so both parts describe the same target.
Suggested fix
Resolve the local target commit ($to if it exists, otherwise HEAD) and include it as target_commitish in the generate-notes API request; retain previous_tag_name for the start of the range.
Prompt for a coding agent
In .just/release.just around lines 111-119, update `_notes` to resolve the selected local target to a commit SHA and pass it as `target_commitish` to `gh api repos/$repo/releases/generate-notes`. Keep the existing previous-tag handling so preview notes on a branch and release notes use the same endpoint as the local git log.
|
| repo="$(gh repo view --json nameWithOwner -q .nameWithOwner)" | ||
| previous=() | ||
| if test "$from" != "v0.0.0"; then previous=(-f previous_tag_name="$from"); fi | ||
| changes="$(gh api "repos/$repo/releases/generate-notes" -f tag_name="$to" "${previous[@]}" -q .body)" |
There was a problem hiding this comment.
just release notes reads commits from local HEAD, but this GitHub request does not pass target_commitish. If the local branch differs from the remote default branch, the summary and appended PR list can describe different changes. Use the same commit for both parts when GitHub can resolve it; otherwise omit the GitHub list from the local preview.
| else | ||
| gh() { command gh "$@"; } | ||
| fi | ||
| command -v claude >/dev/null || { echo "claude is required to write release notes" >&2; exit 1; } |
There was a problem hiding this comment.
The release commands now require an installed and authenticated Claude CLI, but the release guide does not explain that setup. Maintainers following the guide discover the requirement only when their release stops. Document the required setup, the selected model, and the notes and renote commands.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
GitHub's generated notes only list PR titles, so a release did not say
what changed for the user. The release recipe now asks claude -p
(Sonnet 5.5) to summarize the commits since the previous tag in ASD-STE100
Simplified Technical English and keeps the PR list and changelog link
below. Notes are written before the version bump, so a failure stops the
release before anything is pushed.
just release notes [tag]previews the notes for a tag or for unreleasedcommits, and
just release renote <tag>replaces the notes of a publishedrelease. The gh wrappers use plain gh when GH_TOKEN is set, because
op plugin run needs an interactive prompt.