diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 79d043ae..531ffe68 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -128,6 +128,72 @@ jobs: herdr-macos-aarch64/herdr-macos-aarch64 body_path: RELEASE_NOTES.md + close-released-issues: + needs: release + runs-on: ubuntu-latest + continue-on-error: true + permissions: + contents: read + issues: write + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Close issues referenced by released commits + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + + VERSION="${GITHUB_REF_NAME#v}" + CURRENT_COMMIT="$(git rev-list -n 1 "$GITHUB_REF_NAME")" + PREVIOUS_TAG="$(git describe --first-parent --tags --match 'v[0-9]*' --abbrev=0 "${CURRENT_COMMIT}^" 2>/dev/null || true)" + + if [ -z "$PREVIOUS_TAG" ]; then + echo "No previous release tag found; skipping issue close." + exit 0 + fi + + echo "Scanning released commits in $PREVIOUS_TAG..$GITHUB_REF_NAME for refs # lines." + mapfile -t ISSUES < <( + git log --format=%b "$PREVIOUS_TAG..$CURRENT_COMMIT" \ + | perl -ne 'print "$1\n" if /^\s*refs\s+#([0-9]+)\s*$/i' \ + | sort -nu + ) + + if [ "${#ISSUES[@]}" -eq 0 ]; then + echo "No released issue refs found." + exit 0 + fi + + RELEASE_URL="https://github.com/${GITHUB_REPOSITORY}/releases/tag/v${VERSION}" + + for issue in "${ISSUES[@]}"; do + echo "Checking #$issue" + if ! data="$(gh api "repos/${GITHUB_REPOSITORY}/issues/${issue}")"; then + echo "::warning::Could not read issue #$issue; skipping." + continue + fi + + if jq -e 'has("pull_request")' <<<"$data" >/dev/null; then + echo "Skipping #$issue because it is a pull request." + continue + fi + + if [ "$(jq -r '.state' <<<"$data")" != "open" ]; then + echo "Skipping #$issue because it is not open." + continue + fi + + if ! gh issue close "$issue" --repo "$GITHUB_REPOSITORY" --reason completed --comment "Released in [v${VERSION}](${RELEASE_URL})."; then + echo "::warning::Could not close issue #$issue." + continue + fi + done + update-latest-json: needs: release runs-on: ubuntu-latest diff --git a/.pi/prompts/pre-release-audit.md b/.pi/prompts/pre-release-audit.md index 40ab4309..3b54f6d1 100644 --- a/.pi/prompts/pre-release-audit.md +++ b/.pi/prompts/pre-release-audit.md @@ -20,9 +20,9 @@ Process: ```bash git log --first-parent --reverse --format='%H%x09%s' ..HEAD ``` - - Also inspect full commits when needed: + - Also inspect full commits and commit bodies when needed: ```bash - git log --reverse --format='%H%x09%s' ..HEAD + git log --reverse --format='%H%x09%s%n%b' ..HEAD ``` 3. Detect merged PRs if any. @@ -44,11 +44,16 @@ Process: - formatting-only changes - comment-only/doc-only changes unless they materially affect users -6. Audit `.pi/docs/CHANGELOG.md`. +6. Audit `.pi/docs/CHANGELOG.md` and issue references. - Treat root `CHANGELOG.md` as the latest released changelog. - Treat `.pi/docs/CHANGELOG.md` as the next-release changelog. - Compare meaningful user-facing changes in the commit range against `.pi/docs/CHANGELOG.md`. - Flag missing entries for new features, bug fixes, removals, breaking changes, defaults, compatibility changes, user-visible command/config/API behavior, and security-relevant changes. + - Inspect commit bodies for issue reference lines in the form `refs #`. + - Flag normal commits that use GitHub closing keywords like `fixes #`, `closes #`, or `resolves #`, because they close issues before release when they land on `master`. + - For each shipped issue reference, check whether the changelog has a matching user-facing entry that mentions `#` when appropriate. + - Do not require or add GitHub closing keywords like `fixes #`, `closes #`, or `resolves #` to changelog entries or release notes. + - List shipped issue references under `Issue references to close after release:` so the release operator can verify what release CI will close after the GitHub Release is published. - Flag stale entries that do not appear to correspond to shipped changes in the range. - Flag entries that are too implementation-focused or unclear for end users. - Preserve the existing changelog style and sections: `Added`, `Changed`, `Fixed`, `Removed`, and `Breaking Changes` when applicable. @@ -82,6 +87,7 @@ Output format: - `Direct commits included:` - `Excluded as housekeeping:` - `Next-release changelog audit:` +- `Issue references to close after release:` - `Next-release docs audit:` - `Root finalization status:` - `Required changes before release:` diff --git a/AGENTS.md b/AGENTS.md index 3f025ac2..f5258d5f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -57,7 +57,13 @@ Unit tests live next to the code (`#[cfg(test)] mod tests`). If you add behavior - Before release, copy the approved `.pi/docs/` versions into the root public docs. `just release` blocks until each root public doc and `CHANGELOG.md` are identical to their `.pi/docs/` counterparts. - Keep website copy and config examples aligned with the latest published release unless the user explicitly asks for prerelease docs. - Put local PRDs, planning notes, and exploratory specs under `.prd/`; that directory is ignored and locally controlled. -- When a commit fully fixes a GitHub issue, include `fixes #` in the commit body so GitHub closes it on merge. +- When a normal feature or fix commit relates to a GitHub issue, add a commit body line `refs #` after the subject. Use this shape: + ```text + fix: handle pane focus + + refs #82 + ``` + Do not use GitHub closing keywords like `fixes #`, `closes #`, or `resolves #` in normal commits, because `master` contains unreleased work and those keywords close issues before release. Release CI scans `refs #` body lines between release tags and closes the referenced issues after the GitHub Release is created. - Rust: no `unwrap()` in production code. `tracing` for logging. `#[allow]` only with a comment explaining why. - Don't bypass checks. If tests fail, fix them before committing. - Don't add dependencies without a reason. Check if the existing deps cover it first.