From 4b03404dd185c5ccc2fd442c4f5076240039e055 Mon Sep 17 00:00:00 2001 From: Talgat Ryshmanov Date: Tue, 31 Mar 2026 16:02:21 -0400 Subject: [PATCH] docs: make release prep use PR-based changelog finalization --- .agents/skills/cut-release/SKILL.md | 30 ++++++++++++++++--- CHANGELOG.md | 6 ++-- CONTRIBUTING.md | 2 +- docs/map.md | 2 +- .../trust/changelog-and-release-versioning.md | 23 +++++++++++--- docs/trust/release-integrity.md | 2 +- testinfra/hygiene/release_version_test.go | 26 ++++++++++++++-- 7 files changed, 75 insertions(+), 16 deletions(-) diff --git a/.agents/skills/cut-release/SKILL.md b/.agents/skills/cut-release/SKILL.md index 9b8bfa6..978ba14 100644 --- a/.agents/skills/cut-release/SKILL.md +++ b/.agents/skills/cut-release/SKILL.md @@ -12,9 +12,8 @@ Execute this workflow for: "cut release", "ship vX.Y.Z", "push tag and monitor r - Repository: `.` - Tag source branch: `main` only. -- No pre-release branch creation. -- No pre-release PR creation. -- Branch and PR flow is used only for hotfixes after failed checks. +- Allow one short-lived release-prep branch and PR only for finalized changelog publication before tagging. +- Branch and PR flow is otherwise used only for hotfixes after failed checks. - Deterministic changelog finalization is allowed only through the release helper scripts below. - Default posture: when a release or post-release blocker is actionable from repo code, workflow config, docs, or install-path behavior, fix it in-loop and continue instead of stopping at the first failure. @@ -43,6 +42,7 @@ Execute this workflow for: "cut release", "ship vX.Y.Z", "push tag and monitor r - Tag must always be created and pushed from `main`. - `main` must be fast-forward synced with `origin/main` before each tag push. +- Do not bypass branch protection on `main`; use the release-prep PR path when changelog finalization changes files. - No force-push to tags. - No destructive git commands. - No commit amend unless explicitly requested. @@ -66,7 +66,29 @@ Execute this workflow for: "cut release", "ship vX.Y.Z", "push tag and monitor r 6. Finalize the changelog for the resolved version before any tag checks: - `python3 scripts/finalize_release_changelog.py --release-version --json` - `python3 scripts/validate_release_changelog.py --release-version --json` -- if `CHANGELOG.md` changes, review the diff, commit the finalized changelog on `main`, and continue from that committed release-prep state before tagging +- if `CHANGELOG.md` changes: + - create release-prep branch from current `main`: + - `git checkout -b codex/release-prep-` + - commit only the finalized changelog there: + - `git add CHANGELOG.md` + - `git commit -m "chore: finalize changelog for "` + - push branch: + - `git push -u origin codex/release-prep-` + - open release-prep PR using EOF body: + - `gh pr create --base main --head codex/release-prep- --title "chore: finalize changelog for " --body-file - <<'EOF'` + - include: problem, root cause, fix, validation + - `EOF` + - monitor PR CI to green (`CI_TIMEOUT_MIN`) + - required Wrkr PR checks: `fast-lane`, `scan-contract`, `wave-sequence`, `windows-smoke` + - also monitor `CodeQL` when present + - use non-interactive watch or polling such as: + - `gh pr checks --watch --interval 10` + - merge the release-prep PR after green: + - `gh pr merge --rebase --delete-branch` + - sync `main` to the merged commit before continuing: + - `git checkout main` + - `git pull --ff-only origin main` + - rerun `python3 scripts/validate_release_changelog.py --release-version --json` on merged `main` 7. Ensure target tag does not already exist locally or remotely. 8. Ensure release prerequisites are available: - `gh auth status` diff --git a/CHANGELOG.md b/CHANGELOG.md index 1190dfe..b83b70b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and ### Changed -- (none yet) +- [semver:patch] Release prep now lands finalized changelog updates through a short-lived release-prep PR before tagging when `main` is protected. ### Deprecated @@ -33,8 +33,8 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and ## Changelog maintenance process 1. Update `## [Unreleased]` in every PR that changes user-visible behavior, contracts, or governance process. -2. Before release tagging, run `python3 scripts/finalize_release_changelog.py --json` to promote releasable `Unreleased` entries into a dated versioned section and commit that changelog update in the same release-prep commit that will be tagged. -3. Validate the prepared release changelog with `python3 scripts/validate_release_changelog.py --release-version vX.Y.Z --json` on that release-prep commit before or during the tag workflow. +2. Before release tagging, run `python3 scripts/finalize_release_changelog.py --json` to promote releasable `Unreleased` entries into a dated versioned section and publish that change through a short-lived release-prep PR. +3. Validate the prepared release changelog with `python3 scripts/validate_release_changelog.py --release-version vX.Y.Z --json` on merged `main` before or during the tag workflow. 4. Keep entries concise and operator-facing: what changed, why it matters, and any migration/action notes. 5. Link release notes and tag artifacts to the finalized changelog section. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b10b224..522d79e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -88,7 +88,7 @@ This path is sufficient for most CLI/runtime changes and does not require Node. 6. If docs are touched, follow [`docs/map.md`](docs/map.md) and run docs validation bundle. 7. For user-visible changes, update [`CHANGELOG.md`](CHANGELOG.md) under `Unreleased`. Public contract wording changes in `README.md`, command help, `docs/`, `product/`, or docs-site projections count even when JSON, exit codes, and schemas stay unchanged. - Maintainers finalize `Unreleased` into a versioned section immediately before tagging with `python3 scripts/finalize_release_changelog.py --json` and commit that release-prep changelog update before creating the tag. + Maintainers finalize `Unreleased` into a versioned section immediately before tagging with `python3 scripts/finalize_release_changelog.py --json`, publish that change through a short-lived release-prep PR, merge it to `main`, and only then create the tag from merged `main`. 8. For `product/` or `.agents/skills/` changes, confirm policy conformance per [`docs/governance/content-visibility.md`](docs/governance/content-visibility.md). Issue/PR templates: diff --git a/docs/map.md b/docs/map.md index c7efb97..2fc9d7e 100644 --- a/docs/map.md +++ b/docs/map.md @@ -22,7 +22,7 @@ Edit canonical documentation in repository markdown first (`README.md` + `docs/` README first-screen or quickstart changes should also update the affected docs-site LLM projection files (`docs-site/public/llms.txt`, `docs-site/public/llm/*.md`) in the same change. If the Wrkr README uses the landing-page Variant B contract, install and OSS trust/support details may live in canonical docs (`docs/install/*`, `docs/README.md`, `docs/trust/*`) instead of the README footer. Public contract wording changes should update `CHANGELOG.md` under `Unreleased` in the same change, even when runtime JSON, exit-code, and schema contracts stay unchanged. -Maintainers should finalize `Unreleased` with `python3 scripts/finalize_release_changelog.py --json` before cutting a release tag and commit that prepared changelog update so the tag points at the finalized versioned section. +Maintainers should finalize `Unreleased` with `python3 scripts/finalize_release_changelog.py --json` before cutting a release tag, land that prepared changelog update through a release-prep PR, and tag the merged `main` commit so the tag points at the finalized versioned section. ## Required validation bundle diff --git a/docs/trust/changelog-and-release-versioning.md b/docs/trust/changelog-and-release-versioning.md index 28d3464..f2737fa 100644 --- a/docs/trust/changelog-and-release-versioning.md +++ b/docs/trust/changelog-and-release-versioning.md @@ -18,8 +18,8 @@ Instead, maintainers and implementation agents stage operator-facing release not 1. Planning decides whether each story needs a changelog entry. 2. Implementation updates `CHANGELOG.md` `## [Unreleased]` for stories that require it. 3. Release prep resolves the next version from `Unreleased`. -4. Release prep finalizes the changelog into a dated versioned section. -5. The finalized changelog change is committed before tagging. +4. Release prep finalizes the changelog into a dated versioned section on a short-lived release-prep branch. +5. That release-prep PR is merged to `main`. 6. The tag-triggered release workflow validates that the tagged commit's changelog matches the tag. 7. `Unreleased` is empty again, so the next release only considers new changes. @@ -146,7 +146,22 @@ Validate the prepared release version: python3 scripts/validate_release_changelog.py --release-version vX.Y.Z --json ``` -Then commit the finalized changelog update on the release-prep commit before creating the tag. +Then publish that finalized changelog update through a release-prep PR before creating the tag: + +```bash +git checkout -b codex/release-prep-vX.Y.Z +git add CHANGELOG.md +git commit -m "chore: finalize changelog for vX.Y.Z" +git push -u origin codex/release-prep-vX.Y.Z +gh pr create --base main --head codex/release-prep-vX.Y.Z --title "chore: finalize changelog for vX.Y.Z" --body-file - <<'EOF' +... +EOF +gh pr checks --watch --interval 10 +gh pr merge --rebase --delete-branch +git checkout main +git pull --ff-only origin main +python3 scripts/validate_release_changelog.py --release-version vX.Y.Z --json +``` ### 4. After finalization @@ -262,7 +277,7 @@ It now requires: - resolving the version from changelog state - finalizing the changelog - validating the prepared version -- committing that changelog-prep state before tagging +- landing that changelog-prep state through a release-prep PR before tagging ### `.agents/skills/adhoc-plan/SKILL.md` and `.agents/skills/backlog-plan/SKILL.md` diff --git a/docs/trust/release-integrity.md b/docs/trust/release-integrity.md index 0482aba..dd829dd 100644 --- a/docs/trust/release-integrity.md +++ b/docs/trust/release-integrity.md @@ -70,7 +70,7 @@ python3 scripts/finalize_release_changelog.py --json python3 scripts/validate_release_changelog.py --release-version v1.0.0 --json ``` -The finalizer promotes releasable `Unreleased` entries into `## [vX.Y.Z] - YYYY-MM-DD`, adds a hidden semver hint for CI validation, and resets `Unreleased` to the canonical empty template so the next release only considers new entries. Commit that changelog update before creating the tag; the tag workflow validates the changelog content from the tagged commit itself. +The finalizer promotes releasable `Unreleased` entries into `## [vX.Y.Z] - YYYY-MM-DD`, adds a hidden semver hint for CI validation, and resets `Unreleased` to the canonical empty template so the next release only considers new entries. Publish that changelog update through a short-lived release-prep PR before creating the tag; the tag workflow validates the changelog content from the tagged commit itself. For the full changelog ownership model, planning/implementation handoff, and file/script reference, see [`docs/trust/changelog-and-release-versioning.md`](changelog-and-release-versioning.md). diff --git a/testinfra/hygiene/release_version_test.go b/testinfra/hygiene/release_version_test.go index 348029d..0b0d5c4 100644 --- a/testinfra/hygiene/release_version_test.go +++ b/testinfra/hygiene/release_version_test.go @@ -381,6 +381,10 @@ func TestCutReleaseSkillReferencesDeterministicResolver(t *testing.T) { "python3 scripts/resolve_release_version.py --json", "python3 scripts/finalize_release_changelog.py --release-version --json", "python3 scripts/validate_release_changelog.py --release-version --json", + "release-prep PR", + "git checkout -b codex/release-prep-", + "gh pr create --base main --head codex/release-prep-", + "gh pr merge", "[semver:major]", "[semver:minor]", "[semver:patch]", @@ -450,13 +454,31 @@ func fixtureChangelog(entries map[string][]string) string { "## Changelog maintenance process", "", "1. Update `## [Unreleased]` in every PR that changes user-visible behavior, contracts, or governance process.", - "2. Before release tagging, run `python3 scripts/finalize_release_changelog.py --json` to promote releasable `Unreleased` entries into a dated versioned section.", - "3. Validate the prepared release changelog with `python3 scripts/validate_release_changelog.py --release-version vX.Y.Z --json` before or during the tag workflow.", + "2. Before release tagging, run `python3 scripts/finalize_release_changelog.py --json` to promote releasable `Unreleased` entries into a dated versioned section and land that change through a release-prep PR.", + "3. Validate the prepared release changelog with `python3 scripts/validate_release_changelog.py --release-version vX.Y.Z --json` on merged main before or during the tag workflow.", ) return strings.Join(lines, "\n") } +func TestReleaseDocsReferenceReleasePrepPRFlow(t *testing.T) { + t.Parallel() + + repoRoot := mustFindRepoRoot(t) + for _, relPath := range []string{ + "CHANGELOG.md", + "CONTRIBUTING.md", + "docs/map.md", + "docs/trust/changelog-and-release-versioning.md", + "docs/trust/release-integrity.md", + } { + content := mustReadFile(t, filepath.Join(repoRoot, relPath)) + if !strings.Contains(content, "release-prep PR") { + t.Fatalf("expected %s to mention release-prep PR flow", relPath) + } + } +} + func addUnreleasedEntry(t *testing.T, repoRoot string, section string, entry string) { t.Helper()