Skip to content

release-notes: fix --all timeout and migrate all pages to the metadata block#4174

Merged
mattleibow merged 6 commits into
mainfrom
mattleibow/changelog-generation-plan
Jun 16, 2026
Merged

release-notes: fix --all timeout and migrate all pages to the metadata block#4174
mattleibow merged 6 commits into
mainfrom
mattleibow/changelog-generation-plan

Conversation

@mattleibow

Copy link
Copy Markdown
Contributor

Why

The update-release-notes workflow timed out after 15 minutes on its first --all run after the unified docs-automation system landed (#4171, 08ac410ead). This PR fixes the timeout and does the one-time migration that makes future runs cheap.

What changed

1. Fast-path: compute PR effort only after the skip check

_write_page computed per-PR effort (commit count + working days, ~1s/PR fetching refs/pull/N/head) for every page before the _is_content_unchanged check. The 4.148 page alone (162 PRs) took ~150s; across 66 pages an all-unchanged run blew past the 15-min agent cap.

  • Split the effort loop out of get_prs_from_diff (now a cheap git-log parse) into a new add_pr_effort().
  • Call it from _write_page only after the unchanged check passes.

Measured: 4.148 page ~170s → ~0.12s when skipped; a fully-migrated --all now runs in ~30s instead of timing out. (+22/−8 in the script.)

2. One-time migration: regenerate + re-polish every page

Only 7 of 65 pages carried the RAW PR DATA metadata block the skip check relies on; the other 58 were legacy hand-curated pages that could never be skipped. This PR runs the full --all regen so every page now carries the block, then re-polishes all 61 pages from their own raw PR data per TEMPLATE.md (Highlights, grouped New Features with Skia milestone bumps first, Bug Fixes, Platform Support, Community Contributors, Links). Backport-bot authors (github-actions[bot]) were dropped from contributor tables.

3. Page renames / additions

  • 4.148.0-unreleased.md4.148.0.md (now that release/4.148.0-rc.1 exists)
  • new 4.150.0.md (Skia m150 preview) and 1.54.1.1.md

Result

With every page migrated, future workflow runs skip all unchanged pages in seconds and only re-polish the 0–2 pages that actually changed — so the website release notes stay current with no manual effort.

Verification

  • python3 -m py_compile on the script
  • ✅ All 61 pages have the raw block + # Version heading + polished body; no leftover <!-- AI: --> skeletons
  • ✅ Second --all run skips 68/68 real pages in ~30s (proves migration + fast-path)
  • ✅ No orphaned *-unreleased references in TOC/index; 4.148.0 & 4.150.0 listed

Note: some community handles are derived from commit-email local-parts (_login_from_email fallback) and may not be exact GitHub logins — this is pre-existing behavior from #4171, not introduced here. Happy to follow up with gh-based login resolution if wanted.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

…a block

The `update-release-notes` workflow timed out after 15 minutes on its first
`--all` run after the unified docs-automation system landed (08ac410). Two
problems caused it, both fixed here.

~~ Fast-path: compute PR effort only after the skip check ~~

`_write_page` called `get_prs_from_diff`, which computed per-PR "effort"
(commit count + working days) for every PR by fetching `refs/pull/N/head` —
about 1s of network per PR. That ran for EVERY page BEFORE the
`_is_content_unchanged` check, so an all-unchanged `--all` still paid the full
cost: the 4.148 page alone (162 PRs) took ~150s, and across 66 pages the run
blew past the agent's 15-minute cap.

Split the effort loop out of `get_prs_from_diff` (now a cheap git-log parse)
into a new `add_pr_effort()`, and call it from `_write_page` only AFTER the
unchanged check passes. Pages that skip now pay zero effort cost. Measured: the
4.148 page went from ~170s to ~0.12s when skipped, and a fully-migrated `--all`
now completes in ~30s instead of timing out.

~~ One-time migration: regenerate + re-polish every page ~~

Only 7 of 65 pages carried the machine-readable `RAW PR DATA` metadata block
(prs/diff/status/supersede) that the skip check compares; the other 58 were
legacy hand-curated pages with no block, so they could never be skipped. This
commit runs the full `--all` regeneration so every page now carries the block,
then re-polishes all 61 pages from their own raw PR data following
TEMPLATE.md — Highlights, grouped New Features (Skia milestone bumps first),
Bug Fixes, Platform Support, Community Contributors, and Links. Backport-bot
authors (github-actions[bot]) were dropped from the contributor tables.

With every page migrated, future workflow runs skip all unchanged pages in
seconds and only re-polish the 0-2 pages that actually changed.

Also renames `4.148.0-unreleased.md` to `4.148.0.md` (now that
`release/4.148.0-rc.1` exists) and adds `4.150.0.md` and `1.54.1.1.md`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

📦 Try the packages from this PR

Warning

Do not run these scripts without first reviewing the code in this PR.

Step 1 — Download the packages

bash / macOS / Linux:

curl -fsSL https://raw.githubusercontent.com/mono/SkiaSharp/main/scripts/get-skiasharp-pr.sh | bash -s -- 4174

PowerShell / Windows:

iex "& { $(irm https://raw.githubusercontent.com/mono/SkiaSharp/main/scripts/get-skiasharp-pr.ps1) } 4174"

Step 2 — Add the local NuGet source

dotnet nuget add source ~/.skiasharp/hives/pr-4174/packages --name skiasharp-pr-4174
More options
Option Description
--successful-only / -SuccessfulOnly Only use successful builds
--force / -Force Overwrite previously downloaded packages
--list / -List List available artifacts without downloading
--build-id ID / -BuildId ID Download from a specific build

Or download manually from Azure Pipelines — look for the nuget artifact on the build for this PR.

Remove the source when you're done:

dotnet nuget remove source skiasharp-pr-4174

…ttribution)

A full Opus 4.8 review pass over all 61 regenerated pages (one reviewer per
batch, cross-checking each page against TEMPLATE.md and its own raw PR data)
found and fixed real quality problems the first polish pass introduced:

  * Hallucinated content on 0-PR pages — several historical pages with no PR
    data (1.49.0, 1.53.2, 1.54.0, 1.58.0, 1.58.1.1, 1.60.2, 1.60.3) had
    invented Skia milestones, APIs, code samples, and bug fixes citing issue
    numbers absent from the raw data. Replaced with honest servicing notes.
  * Fabricated PR references — e.g. 2.88.0 cited #1733 (belongs to 3.0.0) and
    2.88.5/2.88.8 invented features/contributors not in their raw data.
  * Bot credits in prose — github-actions[bot]/dependabot[bot]/@github/@Copilot
    backport credits were stripped from Highlights, feature bullets, and
    Bug Fixes on 2.88.9, 3.116.1, 3.119.1, 3.119.2, 2.88.0 (the no-bots rule
    only the visible prose had leaked, the tables were already clean).
  * Misc accuracy — duplicate PR listings (1.68.1 #929), a miscategorized
    breaking change (2.80.0 enum restore is additive), wrong platform emoji
    (Tizen 🤖→📦), a typo (macioS), and an overstated "community drove
    everything" claim (3.119.2).

One verified handle correction landed too: 2.80.3 @jerome.laban →
@jeromelaban (confirmed via gh). Raw PR-data blocks were preserved verbatim
throughout; only the polished prose below each block changed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

📖 Documentation Preview

The documentation for this PR has been deployed and is available at:

🔗 View Staging Site
🔗 View Staging Docs
🔗 View Staging Gallery (Blazor)
🔗 View Staging Gallery (Uno Platform)
🔗 View Staging SkiaFiddle

This preview will be updated automatically when you push new commits to this PR.


This comment is automatically updated by the documentation staging workflow.

mattleibow and others added 4 commits June 16, 2026 21:56
Previously generate-release-notes.py derived contributor handles from the
local-part of the commit author email (email.split("@")[0]). For GitHub
noreply emails that happens to work, but for everyone else it produced broken
or — worse — wrong @mentions:

  jerome.laban@unoplatform.com  -> @jerome.laban   (dead link)
  sebastien.pouliot@gmail.com   -> @sebastien.pouliot (dead link)
  ...@...                       -> @jon / @martin  (pings unrelated real users)

A GitHub login does not exist anywhere in git history, so the local-part is
just a guess. In a community project that means we were failing to credit
contributors and, in the @jon/@martin cases, notifying the wrong people.

Resolve logins authoritatively instead:

  1. Prefer the login embedded in a GitHub noreply email (offline, exact, and
     preserves repo conventions like @Copilot and the *[bot] suffixes).
  2. For every remaining PR, resolve the author login via the GitHub GraphQL
     API in batches of 50, cached in scripts/pr-authors.json so future runs
     stay warm and offline. Unresolvable authors fall back to a plain name
     with no @ and no link rather than a wrong mention.

  - _login_from_email now returns the noreply login or None (no more guessing).
  - New resolver: load/save_author_cache, _graphql_pr_authors (uses check=False
    because `gh api graphql` exits non-zero when any aliased PR in a batch is
    missing yet still returns valid data for the rest), resolve_pr_authors.
  - get_prs_from_diff also captures the author display name for the fallback.
  - format_pr_list renders "by @login" when known, else "by name".
  - _write_page/cmd_branch/cmd_all gain --force to regenerate unchanged pages.

scripts/pr-authors.json is committed with 921 resolved authors (0 unresolved)
so the very first CI run needs no API calls for historical PRs.

All existing release pages are regenerated with corrected raw blocks, and the
already-reviewed prose is migrated in place: only the broken handle tokens and
their profile URLs are rewritten (longest-token-first, boundary-safe so
@jerome never clobbers @jeromelaban and @github-actions is left untouched).
No prose wording from the prior review pass is otherwise changed.

Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
versions.json (version-comparison/supersession config) and pr-authors.json
(the GitHub-login cache) are both data files for the documentation-generation
systems. Their only consumers are:

  - the release-notes generator (.agents/skills/release-notes/.../generate-release-notes.py)
  - the api-diff/docs Cake infra (scripts/infra/docs/docs.cake)

Both already anchor on the repo root, and docs.cake itself lives under
scripts/infra/docs/, so colocate the data with the code that produces the docs
instead of leaving it in the generic top-level scripts/ directory.

  - git mv scripts/versions.json    -> scripts/infra/docs/versions.json
  - git mv scripts/pr-authors.json  -> scripts/infra/docs/pr-authors.json
  - Update the two code paths (VERSIONS_JSON_PATH and _AUTHOR_CACHE_PATH in the
    generator; the ROOT_PATH-relative path in docs.cake).
  - Update the prose/links that reference the old locations: writing-docs.md,
    api-diff.yml, SKILL.md, and inline docstrings/comments.

Verified with a full `--all` run: the generator loads both files from the new
paths, all 68 existing pages are skipped unchanged, and TOC/index regenerate
identically. No release-note content changes.

Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Deliberately remove the generated 4.148.0 and 4.150.0 release pages, their
TOC.yml/index.md navigation entries, and the 134 corresponding author-cache
entries (921 -> 787) from scripts/infra/docs/pr-authors.json.

This exercises the full create/update path end to end after merge: the
update-release-notes workflow's `--all` run should recreate both pages from the
release/4.148.0-rc.1 and release/4.150.0-preview.1 branches, re-resolve their PR
authors via the GitHub GraphQL API (the cache is now cold for those PRs),
repopulate the cache, and re-link both versions in TOC.yml and index.md.

The removed PRs are unique to the 4.148/4.150 diff ranges, so no other
(skipped) page depends on those cache entries.

Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The api_diff cache job listed scripts/versions.json explicitly, but that file
moved to scripts/infra/docs/versions.json in 6b3189c and is already covered
by the sibling scripts/infra/docs/** glob on the same line. Remove the dead
path so the config has no reference to a file that no longer exists.

repo-deps.py validate still passes (0 uncovered); versions.json and the
relocated pr-authors.json are both hashed via scripts/infra/docs/** into the
api_diff cache key.

Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mattleibow
mattleibow merged commit 2ccf968 into main Jun 16, 2026
3 of 4 checks passed
@mattleibow
mattleibow deleted the mattleibow/changelog-generation-plan branch June 16, 2026 22:08
mattleibow added a commit that referenced this pull request Jun 17, 2026
…lup (R1/R2/R3)

The versions.json supersession migration (#4171/#4174) correctly made
"which version supersedes which" deterministic via versions.json, but its
move from per-push branch runs to a bulk `--all` pass silently regressed
three documented behaviours. Pages were migrated in #4174 without the
script emitting the data those pages used to contain, so the features were
dropped on the next regeneration instead of failing loudly.

Three regressions, each restored from a deterministic source (not heuristics):

R1 — in-flight naming. 4.148 and 4.150 were written as terminal
`{version}.md` and the cleanup pass then deleted the real
`{version}-unreleased.md`. Root cause: `--all` picks the rc.1/preview.1
branch as canonical for the version. Fixed with a terminal-vs-in-flight
rule in `_page_filename` (suffix-less `release/X.Y.Z` branch exists, or
`status: superseded` in versions.json => `{version}.md`; otherwise
`{version}-unreleased.md`) plus an ownership filter in `cmd_all` so the
line head (main / `release/X.Y.x`) owns the `-unreleased` page and the
matching rc/preview branch is deferred instead of colliding.

R2 — in-flight `.x` rollup was a servicing-delta instead of the full
cumulative rollup. `determine_diff_range` now honors the versions.json
`compare_to` first (4.148.0 -> 3.119.4) and only does a servicing delta
once the `.0` has actually shipped a stable tag.

R3 — the trailing `## Preview N (date)` sections were lost. The original
3.119.2.md (#3763) and TEMPLATE.md have them and SKILL rules 9/10 mandate
them, but #4174 deleted them on regen because the script never emitted
preview data. `collect_preview_milestones` now enumerates them from the
published prerelease git tags within the page's diff range (deduped to the
latest build per milestone, dated, with chained compare links). A page
rolls up a skipped predecessor minor's previews too, so the 4.148 page
lists the 4.147 previews that match its rolled-up PRs.

Two separate deterministic sources, no magic: versions.json answers "which
version supersedes which"; published tags answer "which previews shipped
and when". Documented in code comments (R1/R2/R3) and a new SKILL.md
"Page naming & preview rollup" section so this can't silently regress on
the next migration. Validated with an offline `--all` dry-run: correct
`-unreleased` filenames, 4.148 rolls up 4.147 p1/p2/p3 + rc1, no bare
4.148.0.md/4.150.0.md, 3.119.x unchanged, and the 3.119.2 compare links
reproduce the original page exactly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mattleibow added a commit that referenced this pull request Jun 17, 2026
…lup (R1/R2/R3) (#4179)

release-notes: restore in-flight -unreleased naming + per-preview rollup (R1/R2/R3) (#4179)

The versions.json supersession migration (#4171/#4174) correctly made
"which version supersedes which" deterministic via versions.json, but its
move from per-push branch runs to a bulk `--all` pass silently regressed
three documented behaviours. #4174 migrated the pages without the script
emitting the data those pages used to contain, so the features were dropped
on the next regeneration instead of failing loudly.

Three regressions, each restored from a deterministic source (not heuristics):

R1 — in-flight naming. 4.148 and 4.150 were written as terminal
`{version}.md` and the cleanup pass then deleted the real
`{version}-unreleased.md`. Root cause: `--all` picks the rc.1/preview.1
branch as canonical for the version. Fixed with a terminal-vs-in-flight
rule in `_page_filename` (a suffix-less `release/X.Y.Z` branch exists, or
`status: superseded` in versions.json => `{version}.md`; otherwise
`{version}-unreleased.md`) plus an ownership filter in `cmd_all` so the
line head (main / `release/X.Y.x`) owns the `-unreleased` page and the
matching rc/preview branch is deferred instead of colliding.

R2 — in-flight `.x` rollup was a servicing delta instead of the full
cumulative rollup. `determine_diff_range` now honors the versions.json
`compare_to` first (4.148.0 -> 3.119.4) and only does a servicing delta
once the `.0` has actually shipped a stable tag.

R3 — the trailing `## Preview N (date)` sections were lost. The original
3.119.2.md (#3763) and TEMPLATE.md have them and SKILL rules 9/10 mandate
them, but #4174 deleted them on regen because the script never emitted
preview data. `collect_preview_milestones` now enumerates them from the
published prerelease git tags within the page's diff range (deduped to the
latest build per milestone, dated, with chained compare links). A page
rolls up a skipped predecessor minor's previews too, so the 4.148 page
lists the 4.147 previews that match its rolled-up PRs.

Two separate deterministic sources, no magic: versions.json answers "which
version supersedes which"; published tags answer "which previews shipped
and when". Documented in code comments (R1/R2/R3) and a new SKILL.md
"Page naming & preview rollup" section so this can't silently regress on
the next migration. Validated with an offline `--all` dry-run: correct
`-unreleased` filenames, 4.148 rolls up 4.147 p1/p2/p3 + rc1, no bare
4.148.0.md/4.150.0.md, 3.119.x unchanged, and the 3.119.2 compare links
reproduce the original page exactly.

Co-authored-by: Matthew Leibowitz <mattleibow@live.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mattleibow added a commit that referenced this pull request Jun 17, 2026
A version's "released" and "unreleased" states are orthogonal: a version
can be on NuGet as a shipped prerelease (e.g. 4.150.0-preview.1) AND have
newer, not-yet-released commits on its head branch (main). The original
two-file model gave each of those its own page — a full-rollup released
`{version}.md` and a small-delta `{version}-unreleased.md` — and they
coexisted while the version was in flight.

A five-PR refactor stack (#4174, #4177, #4179) collapsed this into a
single page per version:

  * #4174 added `--all` + `cleanup_stale_unreleased`, which deleted
    `{v}-unreleased.md` whenever `{v}.md` existed — killing the
    coexisting in-flight delta page.
  * #4179 (R2) forced `main`/`.x` heads to honor `versions.json`
    `compare_to`, turning the small "what may ship next" delta into a
    full rollup.
  * #4179 (R1) remapped prerelease branches to `-unreleased` and
    deferred them, collapsing released + unreleased onto one file.

Restore the original delta semantics while keeping the genuinely-good
additions from the stack (versions.json supersession + compare_to on the
RELEASED rollup pages, preview-milestone rollup, contributor credit,
local skia links, `--all` idempotency):

  * `_page_filename`: versioned branch (incl. -rc/-preview) -> released
    `{version}.md`; `main`/`.x` head -> `{version}-unreleased.md`.
  * `determine_diff_range` (main + servicing `.x`): drop the versions.json
    `compare_to` override on heads so the unreleased page is again the
    small delta from the last release to head, not a rollup. `compare_to`
    still drives the released rollup pages produced by versioned branches.
  * `cmd_all`: process main + servicing + ALL canonical versioned branches;
    no deferral (distinct filenames never collide).
  * `cleanup_stale_unreleased`: delete `{v}-unreleased.md` only once the
    line advances to a strictly-higher version in the same minor — keeps
    the in-flight released+unreleased pair, still prunes obsolete deltas
    (e.g. 3.119.4-unreleased once 3.119.5 appears).
  * `_write_page`: an unreleased head with an empty delta emits no page
    (and prunes any stale one) — nothing to ship next.

Net effect on the 4.x line: 4.148.0.md / 4.150.0.md are full released
rollups (with preview milestones + supersede banners), 4.150.0-unreleased.md
is the small `release/4.150.0-preview.1..main` delta, and 4.148.0 has no
unreleased page because release/4.148.x has zero commits past the rc.
Verified by running the script for real (`--all`): it emitted exactly
those pages and listed only the changed ones under "Files to polish".

Keep the page model where it belongs — in the SCRIPT. The script owns ALL
file creation, naming, diff ranges, supersession, and cleanup; the AI/skill
only polishes the prose of the files the script lists. The detailed model
now lives in the script's module + function docstrings, and SKILL.md is
trimmed to a clear "division of responsibility" instead of duplicating
(and drifting from) the naming rules.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mattleibow added a commit that referenced this pull request Jun 17, 2026
A version's "released" and "unreleased" states are orthogonal: a version
can be on NuGet as a shipped prerelease (e.g. 4.150.0-preview.1) AND have
newer, not-yet-released commits on its head branch (main). The original
two-file model gave each of those its own page — a full-rollup released
`{version}.md` and a small-delta `{version}-unreleased.md` — and they
coexisted while the version was in flight.

A five-PR refactor stack (#4174, #4177, #4179) collapsed this into a
single page per version:

  * #4174 added `--all` + `cleanup_stale_unreleased`, which deleted
    `{v}-unreleased.md` whenever `{v}.md` existed — killing the
    coexisting in-flight delta page.
  * #4179 (R2) forced `main`/`.x` heads to honor `versions.json`
    `compare_to`, turning the small "what may ship next" delta into a
    full rollup.
  * #4179 (R1) remapped prerelease branches to `-unreleased` and
    deferred them, collapsing released + unreleased onto one file.

Restore the original delta semantics while keeping the genuinely-good
additions from the stack (versions.json supersession + compare_to on the
RELEASED rollup pages, preview-milestone rollup, contributor credit,
local skia links, `--all` idempotency):

  * `_page_filename`: versioned branch (incl. -rc/-preview) -> released
    `{version}.md`; `main`/`.x` head -> `{version}-unreleased.md`.
  * `determine_diff_range` (main + servicing `.x`): drop the versions.json
    `compare_to` override on heads so the unreleased page is again the
    small delta from the last release to head, not a rollup. `compare_to`
    still drives the released rollup pages produced by versioned branches.
  * `cmd_all`: process main + servicing + ALL canonical versioned branches;
    no deferral (distinct filenames never collide).
  * `cleanup_stale_unreleased`: delete `{v}-unreleased.md` only once the
    line advances to a strictly-higher version in the same minor — keeps
    the in-flight released+unreleased pair, still prunes obsolete deltas
    (e.g. 3.119.4-unreleased once 3.119.5 appears).
  * `_write_page`: an unreleased head with an empty delta emits no page
    (and prunes any stale one) — nothing to ship next.

Net effect on the 4.x line: 4.148.0.md / 4.150.0.md are full released
rollups (with preview milestones + supersede banners), 4.150.0-unreleased.md
is the small `release/4.150.0-preview.1..main` delta, and 4.148.0 has no
unreleased page because release/4.148.x has zero commits past the rc.
Verified by running the script for real (`--all`): it emitted exactly
those pages and listed only the changed ones under "Files to polish".

Keep the page model where it belongs — in the SCRIPT. The script owns ALL
file creation, naming, diff ranges, supersession, and cleanup; the AI/skill
only polishes the prose of the files the script lists. The detailed model
now lives in the script's module + function docstrings, and SKILL.md is
trimmed to a clear "division of responsibility" instead of duplicating
(and drifting from) the naming rules.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mattleibow added a commit that referenced this pull request Jun 17, 2026
A version's "released" and "unreleased" states are orthogonal: a version
can be on NuGet as a shipped prerelease (e.g. 4.150.0-preview.1) AND have
newer, not-yet-released commits on its head branch (main). The original
two-file model gave each of those its own page — a full-rollup released
`{version}.md` and a small-delta `{version}-unreleased.md` — and they
coexisted while the version was in flight.

A five-PR refactor stack (#4174, #4177, #4179) collapsed this into a
single page per version:

  * #4174 added `--all` + `cleanup_stale_unreleased`, which deleted
    `{v}-unreleased.md` whenever `{v}.md` existed — killing the
    coexisting in-flight delta page.
  * #4179 (R2) forced `main`/`.x` heads to honor `versions.json`
    `compare_to`, turning the small "what may ship next" delta into a
    full rollup.
  * #4179 (R1) remapped prerelease branches to `-unreleased` and
    deferred them, collapsing released + unreleased onto one file.

Restore the original delta semantics while keeping the genuinely-good
additions from the stack (versions.json supersession + compare_to on the
RELEASED rollup pages, preview-milestone rollup, contributor credit,
local skia links, `--all` idempotency):

  * `_page_filename`: versioned branch (incl. -rc/-preview) -> released
    `{version}.md`; `main`/`.x` head -> `{version}-unreleased.md`.
  * `determine_diff_range` (main + servicing `.x`): drop the versions.json
    `compare_to` override on heads so the unreleased page is again the
    small delta from the last release to head, not a rollup. `compare_to`
    still drives the released rollup pages produced by versioned branches.
  * `cmd_all`: process main + servicing + ALL canonical versioned branches;
    no deferral (distinct filenames never collide).
  * `cleanup_stale_unreleased`: delete `{v}-unreleased.md` only once the
    line advances to a strictly-higher version in the same minor — keeps
    the in-flight released+unreleased pair, still prunes obsolete deltas
    (e.g. 3.119.4-unreleased once 3.119.5 appears).
  * `_write_page`: an unreleased head with an empty delta emits no page
    (and prunes any stale one) — nothing to ship next.

Net effect on the 4.x line: 4.148.0.md / 4.150.0.md are full released
rollups (with preview milestones + supersede banners), 4.150.0-unreleased.md
is the small `release/4.150.0-preview.1..main` delta, and 4.148.0 has no
unreleased page because release/4.148.x has zero commits past the rc.
Verified by running the script for real (`--all`): it emitted exactly
those pages and listed only the changed ones under "Files to polish".

Keep the page model where it belongs — in the SCRIPT. The script owns ALL
file creation, naming, diff ranges, supersession, and cleanup; the AI/skill
only polishes the prose of the files the script lists. The detailed model
now lives in the script's module + function docstrings, and SKILL.md is
trimmed to a clear "division of responsibility" instead of duplicating
(and drifting from) the naming rules.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mattleibow added a commit that referenced this pull request Jun 17, 2026
A version's "released" and "unreleased" states are orthogonal: a version
can be on NuGet as a shipped prerelease (e.g. 4.150.0-preview.1) AND have
newer, not-yet-released commits on its head branch (main). The original
two-file model gave each of those its own page — a full-rollup released
`{version}.md` and a small-delta `{version}-unreleased.md` — and they
coexisted while the version was in flight.

A five-PR refactor stack (#4174, #4177, #4179) collapsed this into a
single page per version:

  * #4174 added `--all` + `cleanup_stale_unreleased`, which deleted
    `{v}-unreleased.md` whenever `{v}.md` existed — killing the
    coexisting in-flight delta page.
  * #4179 (R2) forced `main`/`.x` heads to honor `versions.json`
    `compare_to`, turning the small "what may ship next" delta into a
    full rollup.
  * #4179 (R1) remapped prerelease branches to `-unreleased` and
    deferred them, collapsing released + unreleased onto one file.

Restore the original delta semantics while keeping the genuinely-good
additions from the stack (versions.json supersession + compare_to on the
RELEASED rollup pages, preview-milestone rollup, contributor credit,
local skia links, `--all` idempotency):

  * `_page_filename`: versioned branch (incl. -rc/-preview) -> released
    `{version}.md`; `main`/`.x` head -> `{version}-unreleased.md`.
  * `determine_diff_range` (main + servicing `.x`): drop the versions.json
    `compare_to` override on heads so the unreleased page is again the
    small delta from the last release to head, not a rollup. `compare_to`
    still drives the released rollup pages produced by versioned branches.
  * `cmd_all`: process main + servicing + ALL canonical versioned branches;
    no deferral (distinct filenames never collide).
  * `cleanup_stale_unreleased`: delete `{v}-unreleased.md` only once the
    line advances to a strictly-higher version in the same minor — keeps
    the in-flight released+unreleased pair, still prunes obsolete deltas
    (e.g. 3.119.4-unreleased once 3.119.5 appears).
  * `_write_page`: an unreleased head with an empty delta emits no page
    (and prunes any stale one) — nothing to ship next.

Bucket PRs per preview so the AI can summarize each milestone directly.
A released rollup spans several prerelease tags; a single flat PR list
left the AI guessing which work shipped in which preview. The script now
partitions the PRs into per-preview buckets via git ancestry — each PR
under the EARLIEST milestone tag whose range contains its commit, i.e.
the preview it first shipped in (`bucket_prs_by_milestone`). The buckets
exhaustively cover the range (every PR lands in exactly one), so they ARE
the full list — no separate flat list to drift. The AI renders one
`## Preview N` section per bucket and merges them for the Highlights.
Verified: 4.148.0.md partitions its 162 PRs as RC1 35 + P3 37 + P2 27 +
P1 63 = 162 (no leftover); 4.150.0.md as Preview 1 = 2; the
4.150.0-unreleased.md delta has no previews and stays a flat 7-PR list.

Keep the page model where it belongs — in the SCRIPT. The script owns ALL
file creation, naming, diff ranges, supersession, cleanup, and PR
bucketing; the AI/skill only polishes prose and, on any anomaly (missing
or unexpected page, bad data), STOPS and reports rather than editing the
script or working around it. The model lives in the script's module +
function docstrings; SKILL.md is trimmed to a "division of responsibility"
plus how to consume the pre-bucketed PR data, instead of duplicating (and
drifting from) the naming rules.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant