diff --git a/.agents/skills/greptimedb-release-note/SKILL.md b/.agents/skills/greptimedb-release-note/SKILL.md index 893b99445ce..026ad90cd8d 100644 --- a/.agents/skills/greptimedb-release-note/SKILL.md +++ b/.agents/skills/greptimedb-release-note/SKILL.md @@ -1,6 +1,6 @@ --- name: greptimedb-release-note -description: Generate a GreptimeDB release changelog with git cliff (correct range, subtract already-released patch PRs, rebuild contributors, add human-curated highlights), output to a file, and prepare the docs-repo blog PR. Use when asked to write/generate a GreptimeDB release note or changelog. +description: Generate a GreptimeDB release changelog (minor, patch, or alpha/beta/rc pre-release) with git cliff (correct range, subtract already-released patch PRs, rebuild contributors, add human-curated highlights and upgrade notes), output to a file, and prepare the docs-repo blog PR. Use when asked to write/generate a GreptimeDB release note or changelog. --- # GreptimeDB Release Note / Changelog @@ -27,41 +27,54 @@ a token it still runs, but may be rate-limited / incomplete. ## 2. Determine the previous version (skip nightlies) -From `gh release list --repo GreptimeTeam/greptimedb`, ignore `*-nightly-*`, `-rc.*`, `-beta.*`, -and build-suffixed tags; focus on formal `vX.Y.Z`. +From `gh release list --repo GreptimeTeam/greptimedb`, ignore `*-nightly-*` and build-suffixed +tags (e.g. `v1.3.0-alpha.1-3c0e2a8d5-20260928-1790639248`). - Patch `vX.Y.Z` (Z>0): previous = `vX.Y.(Z-1)`. - New minor `vX.Y.0`: previous = the latest `vX.(Y-1).*` (for `v1.0.0`, the biggest `0.x`). +- Pre-release `vX.Y.Z-{alpha,beta,rc}.N`: previous = the latest pre-release tag of the same + `vX.Y.Z` (e.g. `v1.3.0-beta.1` for `v1.3.0-beta.2`). If there is none (the first alpha), + see "First pre-release" in §3: the previous minor's `.0` may not be published yet, and since + v1.2 a `.0` tag sits on `release/vX.Y`, not on `main`. Double-check with the user. ## 3. Pick the range and generate -Key topology fact: a **minor tag** (e.g. `v1.0.0`) is an ancestor of `main`; **patch tags** -(`v1.0.1`, `v1.0.2`) live on the `release/v1.0` branch and are **NOT** ancestors of `main` -(they are cherry-picks with different SHAs). Verify with +Key topology fact: **patch tags** (`v1.0.1`, `v1.0.2`) live on `release/vX.Y` and are **NOT** +ancestors of `main` (they are cherry-picks with different SHAs). Up to v1.1 the `.0` tag was +the point where `release/vX.Y` left `main`; from v1.2 the branch is cut before the `.0`, and +pre-releases and the `.0` itself are tagged on the branch with backports. Verify with `git merge-base --is-ancestor /main`. -### New minor (`X.Y.0`, cut from main) -Note the two different tags here: the `git cliff` **base is the previous minor `.0` tag** -(e.g. `v1.0.0`, an ancestor of `main`) — **not** the "previous version" from §2 (the latest -patch, e.g. `v1.0.2`), which is only used below to decide which patch PRs to subtract. +### New minor (`X.Y.0`) +The note compares against the whole previous line: everything new since `release/vX.(Y-1)` +left `main`, minus what any published `vX.(Y-1).*` release (pre-releases, `.0`, patches) +already shipped. The line's own pre-releases (`vX.Y.0-beta.*`) are not subtracted; the `.0` +note covers the whole minor. -Base = the **previous minor tag** (e.g. `v1.0.0`); tip = the release commit (= `release/vX.Y` -tip, usually `/main`): +Base = the **previous minor tag** (e.g. `v1.1.0`); tip = the tagged `vX.Y.0` commit, resolved as +in "Pre-release" below. Split the range at the point where `release/vX.Y` left `main`: ``` -GITHUB_TOKEN=$(gh auth token) git cliff .. --tag vX.Y.0 \ - -o /path/CHANGELOG-vX.Y.0.md +bp=$(git merge-base /release/vX.Y /main) ``` -`cliff.toml`'s `ignore_tags` folds in-range nightly tags into the single section. +- `..$bp` is on `main`; generate it with `git cliff`: + ``` + GITHUB_TOKEN=$(gh auth token) git cliff ..$bp --tag vX.Y.0 \ + -o /path/CHANGELOG-vX.Y.0.md + ``` + `cliff.toml`'s `ignore_tags` folds in-range nightly tags into the single section. +- `$bp..` is the release branch: backports, squashed picks, and commits without a PR. + `git cliff` fails on such commits (`Filter call 'sort' failed`), so build this part as a + pre-release: collect the original PRs with the loop in "Pre-release" and add their bullets. + List the commits that carry no PR with + `git log --format='%h %s' $bp.. | grep -vE '\(#[0-9]+\)$'`; drop mechanical ones, and + for a manual port of a `main` PR (the body says `cherry picked from commit` a SHA that is not + on `main`), find that PR by title and cite it. -**Then subtract PRs already shipped in the intermediate patch releases** (`vX.(Y-1).1`, -`.2`, …) — their main-branch commits are inside the range and would duplicate, and the -audience cares about what's new vs the latest patch. Collect the PR set from the patch release -bodies and remove matching lines: -``` -gh release view vX.(Y-1).Z --repo GreptimeTeam/greptimedb | grep -oE 'pull/[0-9]+' -``` -Remove every changelog bullet whose `#NNNN` is in that combined set (a small Python script is -the reliable way — match `pull/)` on lines starting with `*`). +**Then subtract PRs already shipped in published `vX.(Y-1).*` releases**: their `main` commits +are inside the range. Build the set as for the first pre-release (below): the PR links in +every published `vX.(Y-1).*` release body, plus the `(#N)` line endings in the history of the +newest published `vX.(Y-1).*` tag. Remove every changelog bullet whose `#NNNN` is in that set (a +small Python script is the reliable way — match `pull/)` on lines starting with `*`). ### Patch (`X.Y.Z`, Z>0, cherry-picks on the release branch) The previous patch tag is an ancestor of the release branch, so a plain range works **when the @@ -99,11 +112,78 @@ user which they prefer**): If the branch history is otherwise messy, the same "identify the picked PRs and keep only those" rule applies. +### First pre-release (`X.Y.0-alpha.1`, cut from main) +The base is the commit where the previous line's release branch left `main`. It exists when the +alpha is cut, even if `vX.(Y-1).0` does not: +``` +base=$(git merge-base /release/vX.(Y-1) ) +``` +If there is no `release/vX.(Y-1)`, use the newest published release tag that is an ancestor of +the tag (`git merge-base --is-ancestor `). Run `git cliff ..`, then +subtract every PR that already shipped in a published `vX.(Y-1).*` release, pre-releases +included. Take the union of the PR links in those release bodies and the `(#N)` line endings in +the history of the newest published `vX.(Y-1).*` tag: +``` +gh release view --repo GreptimeTeam/greptimedb | grep -oE 'pull/[0-9]+' +git log --format=%B | grep -oE '\(#[0-9]+\)$' +``` +Either source alone misses PRs: a release body can omit a backport that its tag contains, and a +backport commit can name only the backport PR. + +### Pre-release (`X.Y.Z-{alpha,beta,rc}.N` after an earlier pre-release) +**The tip is the tagged commit, not the release branch tip.** Resolve it from the remote and +write this SHA in the docs PR body (§8) as what the note was checked against: +``` +git ls-remote --tags 'vX.Y.Z-beta.N*' +``` +Use the `refs/tags/vX.Y.Z-beta.N` line (its `^{}` line if the tag is annotated), not the +build-suffixed tags it also lists. PRs that land on `release/vX.Y` after the tag +(`git log --oneline ../release/vX.Y`) belong to the next release, even if they are +already merged when you write the note. + +The range `..` usually mixes two kinds of commits: individual backports +(`... [Backport release/vX.Y] (#NNNN)`) and squashed pick commits (`chore: backport selected +changes for ... (#NNNN)`). In both, the original PRs are the `(#NN)` at the end of body lines; +a backport commit can also carry prerequisite PRs. Collect the PR set, falling back to the +subject for commits whose body lists none: +``` +for c in $(git rev-list ..); do + prs=$(git log -1 --format=%b "$c" | grep -oE '\(#[0-9]+\)$') + [ -n "$prs" ] || prs=$(git log -1 --format=%s "$c" | grep -oE '\(#[0-9]+\)$') + echo "$prs" +done | tr -dc '0-9\n' | sort -un +``` +List the bullets under the **original** PRs (title and author of the PR merged to `main`), not +the backport PRs. Build them with patch (a) or (b) above; for (b), fetch +`gh api repos/GreptimeTeam/greptimedb/pulls/ --jq '{number,title,author:.user.login,merged_at}'`, +group by the `cliff.toml` parsers (first match wins), order by `merged_at`, and put `!` titles +under Breaking changes (§6). + +A squashed pick PR can also contain release-only commits that have no PR. List them: +``` +gh api repos/GreptimeTeam/greptimedb/pulls//commits --paginate \ + --jq '.[].commit.message | split("\n")[0]' | grep -vE '\(#[0-9]+\)$' +``` +Mechanical ones (version bump, toolchain or lint adaptations; example: +`fix: adapt release OptionMap iteration to Rust 1.98`) get no bullet. A release-only +commit that changes behavior gets a bullet in its group. + > Flaky-network note for (a): `git cliff` fetches the repo's full commit history from the GitHub > API for author enrichment (paginates back thousands of commits, even for a tiny range). On a > flaky connection it can time out mid-pagination; it caches pages, so just **re-run until it > completes** — each retry resumes from the cache. If it keeps failing, fall back to (b). +### Groups (all paths) +`git cliff` drops PRs whose title isn't a conventional commit (`filter_unconventional`). List +them and add the user-facing ones by hand: +``` +git log --format=%s | grep -E '\(#[0-9]+\)$' | grep -vE '^[a-z]+(\([^)]*\))?!?: ' +``` +Use only the groups in `cliff.toml`. `git cliff` prints a `### Build` section for `build:` commits +because no parser matches them; move a user-facing `build(deps)` fix to Bug Fixes and the rest to +Miscellaneous Tasks. Keep each bullet list free of blank lines; a blank line turns it into a +loose list that renders with different spacing. + ## 4. Rebuild contributor sections after subtracting `cliff` computes `New Contributors` / `All Contributors` over the full range, so after removing @@ -114,6 +194,21 @@ lines, recompute from the **remaining** commit bullets: in-range PR shipped in a patch). Do this in the same script that subtracts the PRs. +When the bullets are built by hand (§3 (b), pre-releases), `git cliff` computes no New +Contributors, and GitHub's generated notes don't either (they only see the backport PRs). An +author is new if none of their merged PRs shipped before, i.e. none appears in the history of the +base tag: +``` +git log --format=%B | grep -oE '\(#[0-9]+\)$' | tr -dc '0-9\n' | sort -u > /tmp/shipped.txt +gh search prs --repo GreptimeTeam/greptimedb --author --merged --limit 1000 \ + --json number --jq '.[].number' | sort -u > /tmp/author.txt +comm -12 /tmp/author.txt /tmp/shipped.txt # empty => new contributor +``` +Run it for every non-bot author in the release, and cite their earliest-merged PR in this +release. "Earliest merged PR is in range" is not the same test: a contributor whose first PR +merged on `main` but was not picked into any release is still new when a later PR of theirs +ships. + ## 5. Verify Cross-check the result against `git log ..`: no subtracted PR remains, and no @@ -122,17 +217,71 @@ line (ask the user; some prefer to keep it). ## 6. Human-curated sections (insert after the `Release date:` line) +**Style.** Follow the Google developer documentation style guide: sentence-case headings, active voice, present +tense for behavior, American spelling, serial comma, code font for identifiers, bold only for UI +names. Keep it short: +- Intro: two sentences at most, what the release covers and its main theme. +- Highlight: one paragraph of up to four sentences, plus an example if it earns one (see below). +- Upgrade note or breaking change: one bullet, one or two sentences: what changes, who is + affected, what to do. +- Every sentence states a fact the reader acts on or checks. Delete sentences that restate the + heading or the previous sentence, transitions, and summaries. +- No marketing words (`significantly`, `powerful`, `seamless`, `robust`, `enhanced`), no vague + verbs (`supports`, `handles`, `improves`; say what happens), no `several`/`many` when the number + is known. Keep the source's hedges; don't add new ones. + +Example: +> Bad: This release significantly enhances write reliability by introducing a robust retry +> mechanism that seamlessly handles transient failures. +> +> Good: In distributed clusters, the frontend retries a failed write up to three times with +> exponential backoff. Set `max_retries = 0` to disable retries. + Mirror past release bodies (`gh release view v1.0.0 --repo GreptimeTeam/greptimedb`): -- **Short intro**, terse, engineer-written tone (no marketing adjectives). -- **`### 👍 Highlights`** — *few, deep* highlights, each with a **working example** - (SQL / TOML config). To get examples right, **read the highlight PR and the related docs - PR/page** in the docs repo (`GreptimeTeam/docs`, often checked out locally) — verify syntax - in `docs/reference/sql/*.md`, `config/*.example.toml`, `config/config.md`. Do **not** mention - implementation details, tiny features, or unfinished/experimental-but-not-ready features. - Always have the user review and edit the highlights; iterate. +- **Short intro** (see Style). +- **`### 👍 Highlights`** — *few, deep* highlights, ranked by user impact. Fixes for silently + wrong query results, lost or invisible data, and security issues rank with new features and + can be highlights. + - Write a fix as its symptom and the affected deployments or usage, not the PR title. Example: + "with no traffic, `errors / requests > 0.05` was true, so the alert fired". + - State whether a feature is on by default. If an example config uses the default values, say + so. + - Give an example (SQL / TOML config) only where it helps readers act. Don't pick highlights, + or the example, by what is easy to demonstrate. + - One highlight covers one change or one user-visible problem. Don't merge unrelated PRs into + one entry because they touch the same component. + - Sources: the PR body's sections on impact, compatibility, and behavior changes, plus the + code and config it touches, not the title alone. Verify examples against the related docs + PR/page in the docs repo (`GreptimeTeam/docs`, often checked out locally), + `docs/reference/sql/*.md`, `config/*.example.toml`, `config/config.md`. + Do **not** mention implementation details, tiny features, or unfinished/experimental-but-not-ready + features. Always have the user review and edit the highlights; iterate. - **Dashboard** subsection — don't just bump the version. Read the dashboard PRs in the bundled release (`gh release view --repo GreptimeTeam/dashboard`; then the linked PRs) and - describe the user-facing change. + describe the user-facing change. Use the names the UI shows, not the PR titles; check them in + the dashboard's locale strings at the bundled tag: + ``` + gh api 'repos/GreptimeTeam/dashboard/contents/src/locale/en-US/menu.ts?ref=' \ + --jq .content | base64 -d + ``` + Screenshots need absolute `https://` URLs: the body is copied to the GitHub release, where + relative and site-root (`/img/...`) paths don't resolve. +- **`### Upgrade notes`** and **`### Breaking changes`** — check every PR in the release for: + `!` titles; `Compatibility`, `Behavior change`, `Breaking` paragraphs in the body (often a + plain `Compatibility:` line, not a heading); unchecked "API changes are backward compatible" / + "Schema or data changes are backward compatible" boxes; new config options and their defaults; + changed defaults; persisted format changes; rolling-upgrade order; new hard limits. These + signals set reading order, not a filter: a limit can sit in plain prose with every box + checked, so read the body of every user-facing `feat`/`fix` PR. Examples of what is easy to + miss: a 512 MiB cap on decompressed request bodies (#9264); a default step for stepless + subqueries (#9481). + - **Upgrade notes**: steps operators take — upgrade order, index rebuilds, settings to review. + - **Breaking changes**: behavior that used to work and now errors or returns different + results. Put the `!` PR bullets here too, after these entries (see `v1.3.0-beta.1`). +- **Benchmark results** (optional) — state the baseline version (often the previous formal + release, not the previous beta), data size, cache state, and hardware. Don't attribute results + to single PRs. Leave out results the benchmark marks as not for publication, and smoke-level + runs. ## 7. Output @@ -144,7 +293,8 @@ it**. The release runbook deletes it after the release + docs PR are done. The release note is also published as a blog post in **`GreptimeTeam/docs`**. - **Ask the user for their local `GreptimeTeam/docs` checkout path**; if they have none, offer to clone (`git clone git@github.com:GreptimeTeam/docs.git `). -- File: `blog/release-X-Y-Z.md` (version with dashes; see `blog/release-1-0-0.md`). +- File: `blog/release-X-Y-Z.md` (version with dashes; see `blog/release-1-0-0.md`). Pre-releases + use dashes throughout: `blog/release-1-3-0-beta-2.md`. - Content = docs frontmatter (below) followed by the GitHub release body, **keeping the `# vX.Y.Z` H1 directly under the frontmatter**. The blog frontmatter has no `title:`, so Docusaurus uses that H1 as the page title and sidebar label — omitting it makes the page @@ -156,11 +306,13 @@ The release note is also published as a blog post in **`GreptimeTeam/docs`**. description: GreptimeDB vX.Y.Z Changelog date: YYYY-MM-DD --- - # vX.Y.Z Release date: ... ``` + The docs CI check `check-release-note-sync` requires the post minus its frontmatter to equal + the GitHub release body byte for byte, so the H1 goes on the line right after the closing + `---`, with no blank line. - The docs working tree may have unrelated WIP — **don't disturb it**: use a `git worktree` off `origin/main`: ``` @@ -169,8 +321,19 @@ The release note is also published as a blog post in **`GreptimeTeam/docs`**. ``` - **Read and follow the current docs repo PR template** (`.github/pull_request_template.md`) from the checkout used for the release. - Fill in every required section and leave reviewer-owned checklist boxes unchecked; - do not rely on hard-coded section names from an older template. + Fill in every required section; do not rely on hard-coded section names from an older + template. The checklist items are the author's to confirm: check the ones you did, and for one + that doesn't apply, check it and say why. For pre-releases, state the tagged SHA (§3) under + Verification. +- **Build the site before opening the PR.** A new worktree has no `node_modules`. Install with + the pnpm version pinned in `package.json` (`packageManager`), as CI does: + ``` + cd /tmp/docs-release-note + pm=$(jq -r .packageManager package.json) # e.g. pnpm@8.6.0 + npx -y "$pm" install --frozen-lockfile + npx -y "$pm" build + ``` + If the build fails, fix it or stop; don't mark the PR ready for review. - Commit with sign-off, push, open a **draft** PR, then remove the worktree: ``` git -C /tmp/docs-release-note add blog/release-X-Y-Z.md