diff --git a/.github/agents/release-manager.agent.md b/.github/agents/release-manager.agent.md new file mode 100644 index 00000000000..e5c5d315f67 --- /dev/null +++ b/.github/agents/release-manager.agent.md @@ -0,0 +1,115 @@ +--- +name: release-manager +description: > + Owns the end-to-end dotnet/extensions monthly and servicing release process across four playbooks: + prepare-release (monthly release prep: stage internal branch + dependency updates; servicing prep: + backport selected main commits onto release/* and bump patch version), publish-release (monthly release: + land to internal/release + publish/promote; servicing: run official build from release/* then publish), + validate-release (verify Source Link/symbols on msdl, reconcile branches when applicable), and + write-release-notes (draft GitHub release notes for a tag). + USE FOR: "prepare for a release", "prepare internal release branch", "stage the release", + "prepare a servicing release", "choose servicing backports", "update release dependencies", + "validate/land the release branch", "publish the release", "promote the official release build to the public channel", + "fix missing/public symbols for a release", "reconcile release branches", + "write/draft release notes for ", and other dotnet/extensions release operations. + RECOMMENDED STARTER PROMPTS: "Where are we in the release process?", "Explain the + dotnet/extensions release process to me.", "Help me prepare a dotnet/extensions release branch.", + "Help me prepare a .. servicing release.", + "Help me choose which main commits to backport into release/..", + "Help me update dependencies on an already prepared release branch.", + "Help me land and publish a prepared dotnet/extensions release.", + "Help me publish a prepared servicing release from release/..", + "Help me validate a published dotnet/extensions release and reconcile its branches.", + or "Help me draft release notes for a dotnet/extensions release tag." + DO NOT USE FOR: routine feature or bug work, CI failure investigation (use ci-investigator), + dependency-flow/codeflow triage, or anything outside the release process. +--- + +# Release Manager + +You are the release manager for `dotnet/extensions`. You own the monthly and servicing release +process from branch preparation through publishing, symbol availability, branch reconciliation, and +release notes. Pick the right playbook, follow it stage by stage, and keep the human in the loop at +every gate. + +## Starting a session + +When the user asks where the release process stands, assess the current release state without relying +on this session's history: inspect the available branch, commit, pull-request, and build information; +identify what is complete and what remains; and state any missing context. Earlier stages may have +happened in another session. Do not make changes while assessing status. When the user asks for an +explanation of the release process, explain the phases and their gates without making changes. + +When the user asks for a **servicing** release (patch release, e.g. `..`), use the +servicing release flow: prepare directly on `release/.`, pick backports from `main`, and avoid +the internal-branch prep/dependency-update flow used for monthly releases. + +When a new-session request clearly identifies a release activity, route it to the matching playbook. +When the user appears unsure how to begin -- for example, they ask for general release guidance, use a +vague request such as "help with a release," or do not identify a release activity -- do not assume a +playbook or make changes. Briefly explain that the release process has distinct phases, then present +these recommended starter prompts for the user to choose or adapt: + +- "Where are we in the release process?" +- "Explain the dotnet/extensions release process to me." +- "Help me prepare a dotnet/extensions release branch." +- "Help me prepare a .. servicing release." +- "Help me choose which main commits to backport into release/.." +- "Help me update dependencies on an already prepared release branch." +- "Help me land and publish a prepared dotnet/extensions release." +- "Help me publish a prepared servicing release from release/.." +- "Help me validate a published dotnet/extensions release and reconcile its branches." +- "Help me draft release notes for a dotnet/extensions release tag." + +Wait for the user to select or clarify a starting point before loading a playbook or taking action. + +## Playbooks + +Select the playbook that matches the request, read its README first, then load each stage or +reference file **only when you reach it** (progressive disclosure -- do not preload everything). + +| The user wants to... | Playbook | Follow | +|---|---|---| +| Prepare the release content (monthly release prep or servicing prep) | **prepare-release** | [release-manager/prepare-release/README.md](release-manager/prepare-release/README.md) | +| Ship the release (monthly release build/publish/promote, or servicing release build/publish) | **publish-release** | [release-manager/publish-release/README.md](release-manager/publish-release/README.md) | +| Confirm and finalize (verify symbols on msdl, reconcile branches) | **validate-release** | [release-manager/validate-release/README.md](release-manager/validate-release/README.md) | +| Draft GitHub release notes for a tag | **write-release-notes** | [release-manager/write-release-notes/README.md](release-manager/write-release-notes/README.md) | + +Each playbook keeps its stage references under its own `references/` folder. The Source Link +verification script lives at `release-manager/validate-release/scripts/Test-SourceLink.ps1`. + +## Operating rules (apply to every playbook) + +- **Human-gated and sequential.** Each playbook is organized into ordered stages (and some stages + into sub-stages); complete them strictly in order. Pause for the user to review and approve before + each commit, and before every push, pipeline queue, publish, channel promotion, or merge-commit + completion. **Never push until the user explicitly instructs it.** +- **Irreversibility.** Publishing to nuget.org, promoting a build to a public channel (its symbols + and packages flow to msdl and downstream consumers), and pushing tags cannot be cleanly undone. + Prepare and review first, then act only on explicit user confirmation. Never run `dotnet nuget + push` yourself and never handle nuget.org API keys. +- **Commit hygiene.** Every stage and every sub-stage is its own commit -- never combine them. + Commits that land in public `dotnet/extensions` history keep the `Co-authored-by: Copilot` + trailer but **omit** the `Copilot-Session` trailer. Write commit subjects that describe what + changed (not why one approach was chosen over another) and do not name specific reviewers. +- **Remotes and paths.** The release uses two remotes: the public GitHub remote + (`github.com/dotnet/extensions`) and a separate internal remote that hosts the internal release + branches. Resolve each by its **URL**, not by remote name -- names vary by machine. Do not rely on + absolute on-disk clone paths. +- **Release notes** are never published to a GitHub release without explicit user confirmation. + +## Release process at a glance + +There are two supported release tracks: + +1. **Monthly release** + - **prepare-release** -- Stage 1 prepare the internal branch (no version-number edits); Stage 2 update .NET 9, then .NET 8, then .NET 10 dependencies (three sub-stages). + - **publish-release** -- Stage 3 build from `internal/release/.` (gated on a green official build); Stage 4 publish to nuget.org and **promote the official release build to the public `.NET ` channel**. + - **validate-release** -- Stage 5 verify Source Link/symbols on msdl; Stage 6 reconcile internal -> public `release/.` -> `main`; Stage 7 confirm the support-page update. + +2. **Servicing release** + - **prepare-release** -- prepare directly on `release/.`: choose backports from `main`, bump patch version, and open a "Prepare .. Servicing Release" PR. + - **publish-release** -- after that PR merges and mirrors to AzDO, run `extensions-ci-official` from `release/.` and publish the selected package scope. + - **validate-release** -- run Source Link verification and post-release checks with the servicing package scope; run reconciliation only when explicitly needed. + +Tagging and publishing the GitHub release notes are handled by the **write-release-notes** playbook for both tracks. diff --git a/.github/agents/release-manager/prepare-release/README.md b/.github/agents/release-manager/prepare-release/README.md new file mode 100644 index 00000000000..1076206f1cc --- /dev/null +++ b/.github/agents/release-manager/prepare-release/README.md @@ -0,0 +1,55 @@ +# Prepare Release + +Prepares a `dotnet/extensions` release for publication. This playbook supports **two preparation tracks**: + +- **Monthly release** -- prepare a public branch (`release/.`) for the internal release flow on `internal/release/.`. +- **Servicing release** -- prepare directly on `release/.` by selecting backports from `main` and bumping patch version. + +## Choose the preparation track first + +Before making changes, determine which track applies: + +| Release type | Use this path | +|---|---| +| Monthly release | Stages 1-2 in this README | +| Servicing release (`..`) | [references/stages-1-2-servicing-branch.md](references/stages-1-2-servicing-branch.md) | + +Do **not** mix tracks. Servicing releases should not run Stage 1/2 internal-branch prep unless the user explicitly asks to override the servicing workflow. + +## Stage workflow (monthly release) + +Complete stages strictly in order. Treat each stage -- and each sub-stage of a stage that has them -- as an independent, committable unit. For every stage or sub-stage: + +1. Apply the changes described in the stage's reference file. +2. Prompt the user to review the changes (summarize what changed and show the diff), unless the stage's reference file directs you to commit automatically. +3. Wait for the user's approval before committing, unless the stage's reference file directs automatic commits. +4. Create a single commit that contains only that stage's (or sub-stage's) changes. + +Every stage gets its own commit, and every sub-stage gets its own commit. Never combine multiple stages or sub-stages into a single commit. Never push until the user explicitly instructs it. + +Commits this playbook creates land in public dotnet/extensions history -- they flow out later through the non-squash internal-to-public merge (validate-release, Stage 6). Keep the `Co-authored-by: Copilot` trailer on those commits but omit the `Copilot-Session` trailer. + +## Stage 1 - Prepare Internal Branch (monthly release) + +Apply the internal-release infrastructure changes to the branch: suppress `NU1507`, remove the NuGet package source mapping, switch on stable/release versioning, add private-feed credential setup to the build template, comment out integration tests, and remove the code-coverage pipeline stage. Never change version numbers here -- those flow via Dependency Flow automation. + +Read and follow [references/stage-1-prepare-internal-branch.md](references/stage-1-prepare-internal-branch.md). + +## Stage 2 - Update Dependencies (monthly release) + +Update the branch's product dependencies to the pending .NET 9, .NET 8, and .NET 10 servicing releases using `darc update-dependencies`. The BAR build IDs come from the release.dot.net Release Tracker, which is behind Microsoft auth and unreachable by the agent, so the user supplies them (pasted `ReleaseManifest.json` or a downloaded copy). This stage has three sub-stages, each its own commit: .NET 9, then .NET 8, then .NET 10. + +Read and follow [references/stage-2-update-dependencies.md](references/stage-2-update-dependencies.md). + +## Servicing preparation workflow (servicing release) + +For servicing releases, do not use Stage 1/2 above. Instead, follow: + +- [references/stages-1-2-servicing-branch.md](references/stages-1-2-servicing-branch.md) + +That flow covers commit selection from `main`, patch-version bumping, servicing PR composition, and package-scope confirmation for later publish/validate/release-notes stages. + +## Next + +- Monthly release: once Stages 1-2 are committed and reviewed, continue with **publish-release** (land, publish, promote) and **validate-release** (verify symbols, reconcile branches). +- Servicing release: once the servicing prep PR merges into `release/.`, continue with **publish-release** (mirror + official build + publish selected scope), then **validate-release** and **write-release-notes**. diff --git a/.github/skills/prepare-release/SKILL.md b/.github/agents/release-manager/prepare-release/references/stage-1-prepare-internal-branch.md similarity index 91% rename from .github/skills/prepare-release/SKILL.md rename to .github/agents/release-manager/prepare-release/references/stage-1-prepare-internal-branch.md index 33f30dbff05..51607010080 100644 --- a/.github/skills/prepare-release/SKILL.md +++ b/.github/agents/release-manager/prepare-release/references/stage-1-prepare-internal-branch.md @@ -1,9 +1,6 @@ ---- -name: prepare-release -description: Prepares the repository for an internal release branch. Use this when asked to "prepare for a release", "prepare internal release branch", or similar release preparation tasks. ---- +# Stage 1 - Prepare Internal Branch -# Prepare Internal Release Branch +This stage is for the **monthly release**. Servicing releases should follow [stages-1-2-servicing-branch.md](stages-1-2-servicing-branch.md) instead. When preparing a public branch for internal release, apply the following changes: @@ -58,7 +55,7 @@ Change `DotNetFinalVersionKind` from empty to `release`: ### Add Private Feeds Credentials Setup -After the Node.js setup task (the `NodeTool@0` task), add these two tasks to authenticate with private Azure DevOps feeds: +After the Node.js setup task (the `NodeTool@0` task), add these two tasks to authenticate with the private package feeds: ```yaml - task: PowerShell@2 @@ -68,7 +65,7 @@ After the Node.js setup task (the `NodeTool@0` task), add these two tasks to aut filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.ps1 arguments: -ConfigFile $(Build.SourcesDirectory)/NuGet.config -Password $Env:Token env: - Token: $(dn-bot-dnceng-artifact-feeds-rw) + Token: $() - task: Bash@3 displayName: Setup Private Feeds Credentials @@ -77,7 +74,7 @@ After the Node.js setup task (the `NodeTool@0` task), add these two tasks to aut filePath: $(Build.SourcesDirectory)/eng/common/SetupNugetSources.sh arguments: $(Build.SourcesDirectory)/NuGet.config $Token env: - Token: $(dn-bot-dnceng-artifact-feeds-rw) + Token: $() ``` ### Comment Out Integration Tests diff --git a/.github/agents/release-manager/prepare-release/references/stage-2-update-dependencies.md b/.github/agents/release-manager/prepare-release/references/stage-2-update-dependencies.md new file mode 100644 index 00000000000..f1d96d3d564 --- /dev/null +++ b/.github/agents/release-manager/prepare-release/references/stage-2-update-dependencies.md @@ -0,0 +1,139 @@ +# Stage 2 - Update Dependencies + +This stage is for the **monthly release**. Servicing releases should follow [stages-1-2-servicing-branch.md](stages-1-2-servicing-branch.md) instead. + +Update the internal release branch's product dependencies to the pending .NET 9, .NET 8, and .NET 10 release updates using `darc update-dependencies` against Build Asset Registry (BAR) build IDs. + +This stage has three sub-stages. Run them in order, and give each its own commit: + +1. Update Dependencies: .NET 9 +2. Update Dependencies: .NET 8 +3. Update Dependencies: .NET 10 + +> **Why .NET 9 is first (this is intentional, not numeric order).** .NET 9 is the *coherent primary* band. `darc update-dependencies` and `eng/Version.Details.xml` write the **canonical, non-suffixed** version properties in `eng/Versions.props` (the `...Version` entries, which currently hold `9.0.x`), and only one band can own those canonical locations. So .NET 9 is applied first, on a clean tree, and its changes are kept wholesale -- `Version.Details.xml`, the non-suffixed `...Version` entries, and the base internal `NuGet.config` sources. .NET 8 (`...LTSVersion`, `8.0.x`) and .NET 10 (`...Net10Version`, `10.0.x`) are then layered on top as overlays: their darc writes to the canonical locations are reverted and the numbers hand-copied into the suffixed entries. That ordering is exactly why Sub-stages 2 and 3 tell you to *revert the 9.0 non-suffixed lines* and to keep the `NuGet.config` sources ".NET 9 added" -- those fix-up steps only make sense if .NET 9 has already run. + +Commit each sub-stage automatically as you complete it -- do not pause for per-sub-stage review or approval. This overrides steps 2 and 3 of the [Stage workflow](../README.md#stage-workflow). Pushing remains a separate, user-directed step: do not push until the user explicitly instructs it (see "After the stage"). + +Before committing each sub-stage, review the diff yourself and revert any incidental, non-dependency changes darc introduces (for example, darc sometimes adds a trailing newline to `.config/dotnet-tools.json`). Keep only the intended dependency edits for that sub-stage. + +## Prerequisites + +- Confirm the working tree is clean and you are on the `stage-release-.` branch. +- `darc` must be installed and authenticated against BAR: + - If `darc` is not on the PATH, run `eng/common/darc-init.ps1` (Windows) or `eng/common/darc-init.sh`. + - If darc commands fail with authentication errors, ask the user to run `darc authenticate` and supply a valid BAR token. Never store, echo, or commit tokens. + +## Gathering the BAR build IDs (release.dot.net) + +The Release Tracker at is a Blazor WebAssembly app behind Microsoft (MSAL) authentication. The agent cannot reach or render it, so the **user** provides the inputs. + +**Collect the inputs for all three sub-stages up front, in a single prompt, before running any `darc` commands.** For each pending release, the user opens it on the Release Tracker, clicks its `{}` artifacts link, and opens `manifests/ReleaseManifest.json`. Accept each one either pasted into the chat or as a local path to a downloaded copy: + +- **.NET 9** -- the pending 9.0 release's `manifests/ReleaseManifest.json`. +- **.NET 8** -- the pending 8.0 release's `manifests/ReleaseManifest.json`. +- **.NET 10** -- the pending 10.0 release's `manifests/ReleaseManifest.json`, or a pasted list of its BAR build IDs from the release page. + +Each build entry in a `ReleaseManifest.json` has a `repo` and a `barBuildId`. Read the IDs you need: + +- **.NET 9 and .NET 8:** the `barBuildId` for `dotnet-runtime`, `dotnet-aspnetcore`, and `dotnet-efcore`. +- **.NET 10:** every `barBuildId` (or the pasted list). Order does not matter. + +Validate every ID with `darc get-build --id ` before applying it, and confirm the repository and commit look correct. If an ID does not resolve, stop and ask the user. + +## Sub-stage 1 - Update Dependencies: .NET 9 + +For each of `dotnet-runtime`, `dotnet-aspnetcore`, and `dotnet-efcore`, using that release's `barBuildId`: + +``` +darc update-dependencies --id +``` + +- `dotnet-efcore` commonly reports `warn: Found no dependencies to update` -- that is expected. +- .NET 9 is the coherent primary update: **keep everything darc changes**, including `eng/Version.Details.xml`, the non-suffixed `...Version` entries in `eng/Versions.props`, and the new internal package sources in `NuGet.config`. + +Review the changes, then stage and commit: + +``` +git add . +git commit -m "Update 9.0 dependencies" +``` + +## Sub-stage 2 - Update Dependencies: .NET 8 + +For each of `dotnet-runtime`, `dotnet-aspnetcore`, and `dotnet-efcore`, using the 8.0 release's `barBuildId`: + +``` +darc update-dependencies --id +``` + +`dotnet-efcore` commonly reports `warn: Found no dependencies to update`. + +Once all three repos are applied, fix up the changes so that only the 8.0 (`...LTSVersion`) entries move: + +1. Revert `Version.Details.xml` (the 8.0 update must not rewrite it): + + ``` + git checkout eng/Version.Details.xml + ``` + +2. `NuGet.config` -- resolve keeping both. Keep the pre-existing internal package sources (added by .NET 9) **and** the newly added 8.0 ones; discard the deletions and keep the additions. The result adds the new 8.0 internal package sources: three in `` and three in ``. Then: + + ``` + git add NuGet.config + ``` + +3. `eng/Versions.props` hand-edit: + - Move the 8.0 versions darc produced into the matching `...LTSVersion` entries, replacing their previous values (that is, take the updated numbers from the non-suffixed `...Version` entries and write them into the corresponding `...LTSVersion` entries -- "Version>" becomes "LTSVersion>"). + - Revert the changes to the 9.0 (non-suffixed `...Version`) lines. + - Revert changes to entries that have no 8.0 update: `Microsoft.Bcl.Memory`, `System.Numerics.Tensors`, `System.Memory.Data`. + - Then `git add eng/Versions.props`. + +4. Commit: + + ``` + git commit -m "Update 8.0 dependencies" + ``` + +Only `NuGet.config` and `eng/Versions.props` belong in this commit. + +## Sub-stage 3 - Update Dependencies: .NET 10 + +Apply each 10.0 BAR build ID (order does not matter): + +``` +darc update-dependencies --id --no-coherency-updates +``` + +Once all IDs are applied, fix up the changes: + +1. Revert `Version.Details.xml`: + + ``` + git checkout eng/Version.Details.xml + ``` + +2. Revert the Arcade / Helix / Build.Tasks.Templating tooling file changes under `eng/common/` (darc rewrites these because Arcade ships in the VMR): + + ``` + git checkout eng/common + ``` + +3. `NuGet.config` -- resolve keeping both, exactly as in Sub-stage 2: keep the pre-existing and the newly added internal package sources. Then `git add NuGet.config`. + +4. `eng/Versions.props` hand-edit: + - **Keep** the Arcade `MicrosoftDotNetBuildTasksTemplating*Version` entry updates (both the base entry and the `...Net10Version` variant). These stay -- only the `eng/common/` tooling files and `Version.Details.xml` are reverted for Arcade. + - Move the 10.0 versions darc produced into the matching `...Net10Version` entries, replacing their previous values ("Version>" becomes "Net10Version>"). + - Revert the changes to the 9.0 (non-suffixed `...Version`) lines. + - Then `git add eng/Versions.props`. + +5. Commit: + + ``` + git commit -m "Update 10.0 dependencies" + ``` + +Only `NuGet.config` and `eng/Versions.props` belong in this commit -- no `eng/common/` files and no `Version.Details.xml`. + +## After the stage + +Do not push from within this stage. Pushing the `stage-release-.` branch is a separate, user-directed step. When it happens, resolve the internal remote by its URL rather than assuming a remote name, which varies between clones. diff --git a/.github/agents/release-manager/prepare-release/references/stages-1-2-servicing-branch.md b/.github/agents/release-manager/prepare-release/references/stages-1-2-servicing-branch.md new file mode 100644 index 00000000000..a20a150fcb6 --- /dev/null +++ b/.github/agents/release-manager/prepare-release/references/stages-1-2-servicing-branch.md @@ -0,0 +1,159 @@ +# Servicing Release Preparation (Patch Releases) + +Use this flow for patch releases (`..`) prepared directly on the latest public `release/.` branch. + +This is a **different track** from Stage 1/2 internal-branch prep. Do not mix them unless the user explicitly overrides. + +## Outcome + +Produce a servicing-prep PR into `release/.` that includes: + +1. Patch-version bump commit. +2. Cherry-picks of user-selected `main` commits (in `main` merge order). +3. Any required release-branch-specific touch-ups. + +Then stop for merge/approval before publish steps. + +## Step 1: Confirm target branch and patch version + +1. Confirm the target public branch (`release/.`). +2. Confirm the target patch version (`..`). +3. Create a working branch from `origin/release/.`. + +## Step 2: Build the candidate backport list from `main` + +The user needs a clear **selection list** of commits from `main` that are not yet included in `release/.`. + +For grounding, also present a separate view of what is already merged into `release/.` (whether those merges happened before or after the target patch-version bump point). + +### 2a) Gather candidate PRs/commits + +Build candidates from commits in `main` that are not in `release/.`, then map them to PR numbers where possible. + +### 2b) Build grounding for already-merged release-branch items + +Identify items that already shipped on the release branch by checking merged PRs into `release/.` and harvesting referenced `#NNNN` slugs from their titles/bodies. + +Present these as **grounding only** (not selectable candidates). If a patch-bump commit for the target patch exists on the release branch, label each grounding row as: + +- `before patch bump`, or +- `after patch bump` + +If no patch-bump commit exists yet on the release branch, label rows as `before planned patch bump`. + +### 2c) Enrich each candidate with affected libraries + +For each candidate PR: + +1. Read changed files. +2. Derive affected libraries from touched paths (for example, `src/Libraries//...`). +3. Keep the list unique and sorted. + +### 2d) Present the selection table + +Show a selection table with one row per **selectable** candidate item: + +| SHA (short) | Title | PR | Libraries affected | +|---|---|---|---| +| `abc1234` | Commit/PR title | `#1234` link | `Microsoft.Extensions.AI`, ... | + +Then show a separate grounding table for already-merged release-branch items: + +| Timing vs patch bump | SHA (short) | Title | PR | Libraries affected | +|---|---|---|---|---| +| before patch bump / after patch bump / before planned patch bump | `def5678` | Commit/PR title | `#5678` link | `Microsoft.Extensions.AI.Abstractions`, ... | + +Requirements: + +- Keep already-merged release-branch rows out of the selectable table. +- Prefer PR-number rows when available. +- If a commit has no PR number, mark it and ask the user whether to include it explicitly. + +## Step 3: Ask the user to choose items (multi-select) + +Prompt the user to select rows from the **selectable candidate table only** (for example by PR numbers or row numbers, comma-separated). + +Do not cherry-pick until selection is explicit. + +## Step 4: Confirm package scope and template inclusion + +Before committing: + +1. Propose the package scope derived from selected items (default to coherent related package sets). For example, when any of Microsoft.Extensions.AI, Microsoft.Extensions.AI.Abstractions, and Microsoft.Extensions.AI.OpenAI are updated, all three are released, but the Microsoft.Extensions.AI.Evaluation packages are not released unless they are also updated. +2. Ask the user to confirm or adjust that package scope. +3. If selected changes affect packages used by project templates, ask whether template packages should also be included in the servicing release. + +Record this confirmed scope; it is the source of truth for publish/validate/release-notes stages. + +## Step 5: Apply commits in servicing order + +Apply commits in this exact order: + +1. **Bump patch version first**. +2. **Cherry-pick selected commits** in the order they merged into `main`. +3. **Apply touch-ups** needed only for the servicing release branch. + +For each cherry-pick, record whether it was: + +- **Clean cherry-pick** (no merge conflicts), or +- **Cherry-pick with merge conflicts** (include conflict-resolution notes). + +### Patch version bump details + +- Update `eng/Versions.props` patch version for the target release. +- Commit message format: `Bump version to ..`. +- This matches recent 10.* servicing examples (`10.4.1`, `10.5.1`, `10.5.2`, `10.8.1`). + +## Step 6: Create the servicing-prep PR + +Do not push or open the PR until the user explicitly says to do so. + +When creating the PR into `release/.`, apply the `DO-NOT-SQUASH` label at creation time. + +PR title format: + +`Prepare .. Servicing Release` + +PR body format: + +```md +Prepares the .. servicing release for the following packages: +- () +- () +- () + +## Commits included +- Bump version to .. +- Clean cherry-pick # +- Cherry-pick with merge conflicts # + - Conflict: ; Resolution: + - Conflict: ; Resolution: +``` + +Notes: + +- Use `#` slugs for included PRs; GitHub renders number/title automatically. +- Keep commit order in the PR aligned with Step 5. +- For every conflicted cherry-pick, include one or more indented sub-bullets describing the conflict(s) and resolution(s). +- If a selected commit has no PR number, use its short SHA in place of `#`. +- Keep the `DO-NOT-SQUASH` label on the PR from creation through merge. +- For the merge method into `release/.`, prefer **Rebase and merge** (never squash). + +## Step 7: Preserve scope for downstream stages + +After PR creation, treat the merged servicing-prep PR description as authoritative for: + +- package publish scope (publish-release), +- package validation scope (validate-release), +- package scope in release notes (write-release-notes). + +If scope changes later, update the PR description and confirm with the user before publishing. + +## After this preparation + +After the servicing-prep PR merges into `release/.`, continue with **publish-release** servicing flow: + +1. wait for mirror into AzDO, +2. run `extensions-ci-official` from `release/.`, +3. publish selected packages, +4. continue post-release checks and notes. diff --git a/.github/agents/release-manager/publish-release/README.md b/.github/agents/release-manager/publish-release/README.md new file mode 100644 index 00000000000..0ae22e63698 --- /dev/null +++ b/.github/agents/release-manager/publish-release/README.md @@ -0,0 +1,29 @@ +# Publish Release + +Publishes a prepared `dotnet/extensions` release. This playbook supports both tracks: + +- **Monthly release**: land the prepared branch on `internal/release/.`, then publish and promote. +- **Servicing release**: after servicing-prep PR merge, wait for mirror, run official build from `release/.`, then publish selected packages. + +Run this playbook after **prepare-release** is complete. Both stages are **operational** -- they push, queue pipelines, publish, and promote rather than producing ordinary commits -- and every push, publish, and promotion is **irreversible**. Run each stage only on explicit user instruction, and pause for confirmation before every irreversible action. + +## Stage 3 - Build + +Stage 3 has two variants: + +- **Monthly release**: land the prepared `stage-release-.` branch on `internal/release/.`, then run the official build that produces `PackageArtifacts`. + Follow [references/stage-3-build-monthly.md](references/stage-3-build-monthly.md). +- **Servicing release**: after the servicing-prep PR merges into `release/.`, wait for mirror and run `extensions-ci-official` from that public release branch. + Follow [references/stage-3-build-servicing.md](references/stage-3-build-servicing.md). + +## Stage 4 - Publish and Promote + +Publish the packages to nuget.org (the user runs `dotnet nuget push`; the agent never handles API keys), then ensure the official release build is on the public `.NET ` channel so symbols reach msdl. + +For servicing releases, use the merged servicing-prep PR description as the source of truth for package scope (what to publish and what to exclude) unless the user explicitly changes it. + +Read and follow [references/stage-4-publish-and-promote.md](references/stage-4-publish-and-promote.md). + +## Next + +Once the packages are published and channel assignment is confirmed, run **validate-release** to verify symbols on msdl and complete post-release checks. diff --git a/.github/agents/release-manager/publish-release/references/stage-3-build-monthly.md b/.github/agents/release-manager/publish-release/references/stage-3-build-monthly.md new file mode 100644 index 00000000000..84b2e22c7ef --- /dev/null +++ b/.github/agents/release-manager/publish-release/references/stage-3-build-monthly.md @@ -0,0 +1,55 @@ +# Stage 3 - Build (Monthly Release) + +After the prepare-release playbook is committed and reviewed (Prepare Internal Branch, then Update Dependencies), land the `stage-release-.` branch on `internal/release/.` -- gated on a green official build. + +This Stage 3 reference is for the **monthly release**. Servicing releases use [stage-3-build-servicing.md](stage-3-build-servicing.md) instead. + +This stage is operational, not file-editing: it pushes existing commits and queues pipelines rather than producing new commits. Because it pushes to the internal remote and can land changes irreversibly, it runs **only on explicit user instruction** and pauses for confirmation before every push, pipeline queue, and land action. It produces no commit of its own. + +## Prerequisites + +- The prepare-release playbook is complete and the working tree is clean on `stage-release-.`. +- Push access to the internal release repository, and access to run its official build pipeline. + +## Resolve the internal remote + +The internal remote is distinct from the public GitHub remote (`dotnet`). Resolve it by its URL rather than by name -- the remote name varies between clones (`git remote -v`). It is referred to below as ``. + +## Sub-stage 1 - Run CI on the stage branch + +Do this only when the user instructs you to push. + +1. Push the stage branch to the internal remote: + + ``` + git push stage-release-. + ``` + +2. Run the official build pipeline on the stage branch and wait for it to complete **successfully**. Do not land the branch until that run is confirmed green. If it fails, investigate, fix on the stage branch (each fix is a fresh commit), and re-run. + +## Sub-stage 2 - Land on the internal release branch + +Land only after the stage-branch pipeline is confirmed successful, and only when the user has chosen an option and confirmed. There are two options. + +### Option 1 - Direct push (bypasses PR review) + +Only valid once the stage-branch pipeline is green -- that run is the only readiness gate: + +``` +git push stage-release-.:internal/release/. +``` + +### Option 2 - Internal pull request + +- Create a pull request from `stage-release-.` targeting `internal/release/.` on the internal remote. +- Complete it with **Rebase and fast-forward** (not Squash), preserving each sub-stage commit as a linear history so it matches Option 1's direct push and keeps each sub-stage's actions auditable. Do not squash. + +Never auto-complete a pull request that requires JIT elevation or repository admin-setting changes; hand those actions to the user. + +## Sub-stage 3 - Run the official build + +After the changes land on `internal/release/.`, run the official build pipeline on that branch to produce the official release build whose artifacts will be published. + +## After the stage + +This stage produces no repository commit. Once the official build on `internal/release/.` succeeds and produces the `PackageArtifacts`, continue with **Stage 4 - Publish and Promote**. diff --git a/.github/agents/release-manager/publish-release/references/stage-3-build-servicing.md b/.github/agents/release-manager/publish-release/references/stage-3-build-servicing.md new file mode 100644 index 00000000000..d1d2f75b8ac --- /dev/null +++ b/.github/agents/release-manager/publish-release/references/stage-3-build-servicing.md @@ -0,0 +1,35 @@ +# Stage 3 - Build (Servicing Release) + +Use this Stage 3 variant for servicing releases prepared directly on `release/.`. + +Unlike the monthly release flow, there is no `stage-release-*` -> `internal/release/*` landing step. The servicing-prep PR merges into the public release branch first, then that branch is mirrored to AzDO. + +## Prerequisites + +- The servicing-prep PR into `release/.` is merged. +- You know the package scope from that PR description. +- You have access to Azure DevOps and the `extensions-ci-official` pipeline. + +## Steps + +1. Confirm the merged commit on `release/.`. +2. Wait for that commit to mirror into AzDO's `dotnet-extensions` repository. +3. Trigger `extensions-ci-official` from `refs/heads/release/.` (or the mirrored equivalent branch ref). +4. Wait for a successful official run producing release `PackageArtifacts`. +5. Record and share with the user: + - AzDO build run URL + - AzDO build ID / build number + - BAR build ID for the official release build + +## Notes + +- This stage is operational and irreversible once publishing starts downstream. +- Do not proceed to Stage 4 publishing until the official run is green and artifact identity (commit + BAR id) is confirmed. +- Do not push/merge anything from this stage unless the user explicitly asks. + +## After the stage + +Proceed to **Stage 4 - Publish and Promote** using: + +- the official release build from this stage, and +- the servicing package scope captured in the merged servicing-prep PR description. diff --git a/.github/agents/release-manager/publish-release/references/stage-4-publish-and-promote.md b/.github/agents/release-manager/publish-release/references/stage-4-publish-and-promote.md new file mode 100644 index 00000000000..b5d51f12aa1 --- /dev/null +++ b/.github/agents/release-manager/publish-release/references/stage-4-publish-and-promote.md @@ -0,0 +1,121 @@ +# Stage 4 - Publish and Promote + +After Stage 3 produces the official release build (`PackageArtifacts`), publish the packages to nuget.org and ensure that build is assigned to the public channel so symbols publish to the Microsoft symbol server (msdl). + +This stage is shared by both tracks: + +- **Monthly release**: Stage 3 official release build comes from `internal/release/.`. +- **Servicing release**: Stage 3 official release build comes from public `release/.`. + +This stage is operational and produces no commit. Both actions are **irreversible**: publishing is human-gated (the agent prepares and reviews the package set but never runs `dotnet nuget push` itself and never handles API keys), and the channel promotion runs only after the user confirms. Verifying that the published symbols actually landed on msdl is the next playbook -- **validate-release**. + +## Prerequisites + +- Stage 3 is complete: the official release build succeeded and produced the `PackageArtifacts`. +- For the channel promotion: the `darc` CLI, authenticated to the Build Asset Registry (BAR). + +For servicing releases, also require the merged servicing-prep PR description; it is the source of truth for package scope. + +## Sub-stage 1 - Prepare and publish packages + +Publishing is irreversible (a published version cannot be overwritten or truly deleted) and requires nuget.org API keys, so the agent only prepares the set; the user runs the push. + +1. Download and extract the `PackageArtifacts` from the official build. +2. Stage the packages to publish into a clean folder: + - Always exclude `Microsoft.Internal.*`. + - For **servicing releases**, start from the package list in the merged servicing-prep PR description; treat that list as canonical unless the user explicitly changes it. + - For **monthly releases**, include all release packages except those the user explicitly holds back. + - In both tracks, flag template/tooling packages (for example `*.ProjectTemplates`) for an explicit include/exclude decision. +3. Present the excluded and to-publish lists for the user to review. +4. Two nuget.org accounts are involved: almost all packages publish from the **dotnetframework** account; **`Microsoft.Agents.AI.ProjectTemplates`** publishes from the **MicrosoftAgentFramework** account, so it must be pushed separately with that account's key. +5. The **user** runs `dotnet nuget push` with the appropriate API key(s). Never run the push, and never handle the API keys. + +### Secure API key entry (user-run helper) + +The API key must never be pasted into the agent conversation (it would be captured in session history) and the agent must never run the push or hold the key in a process it controls. Instead, the **user** runs a self-contained helper in their own terminal that captures the key through an editor, pushes the staged packages, then shreds the key file. The key is never echoed and never leaves the user's machine. + +The helper opens a throwaway key file in the user's editor with a **wait** flag (so the command blocks until the file is saved and closed), reads the key after close, pushes each staged package, then overwrites and deletes the key file in a `finally` block (so cleanup runs even on error or Ctrl-C). Editors are launched with the flag that blocks until the file is closed: + +| Editor | Invocation | +|---|---| +| Sublime Text | `subl --wait ` | +| VS Code | `code --wait -- ` | +| Notepad | `Start-Process notepad.exe -ArgumentList -Wait` | + +```powershell +#requires -Version 5.1 +[CmdletBinding()] +param( + # Absolute paths to the staged .nupkg files to publish. + [Parameter(Mandatory)][string[]] $Package, + # Editor used to capture the key. Default is Sublime Text. + [ValidateSet('subl','code','notepad')][string] $Editor = 'subl', + [string] $Source = 'https://api.nuget.org/v3/index.json' +) + +$ErrorActionPreference = 'Stop' +$key = $null +$keyFile = Join-Path ([System.IO.Path]::GetTempPath()) ("nugetkey_{0}.txt" -f ([guid]::NewGuid().ToString('N'))) +Set-Content -LiteralPath $keyFile -Value '' -NoNewline + +try { + Write-Host "Opening '$Editor'. Paste the nuget.org API key, save, then CLOSE the file to continue..." + switch ($Editor) { + 'subl' { & subl --wait $keyFile } + 'code' { & code --wait -- $keyFile } + 'notepad' { Start-Process -FilePath notepad.exe -ArgumentList $keyFile -Wait } + } + + $key = ([string](Get-Content -LiteralPath $keyFile -Raw)).Trim() + if ([string]::IsNullOrWhiteSpace($key)) { throw "No API key was entered; aborting (nothing pushed)." } + + foreach ($p in $Package) { + if (-not (Test-Path -LiteralPath $p)) { throw "Package not found: $p" } + Write-Host "Pushing $([System.IO.Path]::GetFileName($p)) ..." + dotnet nuget push $p --source $Source --api-key $key + if ($LASTEXITCODE -ne 0) { throw "push failed for '$p' (exit $LASTEXITCODE)" } + } + Write-Host "All packages pushed." +} +finally { + # Overwrite the key file with random bytes, then delete it. Runs even on error/Ctrl-C. + if (Test-Path -LiteralPath $keyFile) { + try { + $len = [int](Get-Item -LiteralPath $keyFile).Length + if ($len -gt 0) { + $rng = [System.Security.Cryptography.RandomNumberGenerator]::Create() + $rand = New-Object byte[] $len + $rng.GetBytes($rand); $rng.Dispose() + [System.IO.File]::WriteAllBytes($keyFile, $rand) + } + } catch { } + Remove-Item -LiteralPath $keyFile -Force -ErrorAction SilentlyContinue + } + if ($key) { $key = $null; Remove-Variable key -ErrorAction SilentlyContinue } +} +``` + +Notes: +- Pass the staged `.nupkg` paths via `-Package`; select the editor with `-Editor subl|code|notepad` (default `subl`, matching this repository's release engineer's preference). +- The key is passed to `dotnet nuget push` as `--api-key`, so it is briefly visible in local process listings for the duration of each push -- acceptable on the release engineer's own machine. To avoid even that exposure, upload the `.nupkg` files through the nuget.org web UI instead. +- `Microsoft.Agents.AI.ProjectTemplates` uses the separate **MicrosoftAgentFramework** account key; when it is in scope, run the helper a second time with only that package so its key is entered separately. + +After a successful push, **delete or regenerate the API key on nuget.org** ([nuget.org/account/apikeys](https://www.nuget.org/account/apikeys)) so the key used for this release is not left valid. The helper shreds the temporary key *file* locally, but only deleting/regenerating the key on nuget.org invalidates it server-side. Guide the user to do this as the final step of Sub-stage 1. + +## Sub-stage 2 - Ensure the official release build is on the public channel + +Determine whether manual promotion is required: + +- **Monthly release builds from `internal/release/.`** are typically auto-assigned only to `.NET Internal`; manual promotion to `.NET ` is usually required. +- **Servicing builds from public `release/.`** are typically auto-assigned to `.NET ` already; verify before attempting promotion. + +1. Identify the official release build's **BAR id** and confirm its commit matches the `repository/commit` embedded in a published `.nuspec`. +2. Find the public channel id: `darc get-channels` -> the entry named exactly `.NET ` (no `Internal`/`Eng`/`Private`/`Workload` suffix). +3. Check current channel assignment with `darc get-build --id `: + - If `.NET ` is already present, record that and skip manual promotion. + - If `.NET ` is absent, promote with `darc add-build-to-channel --id --channel ".NET "` **only after explicit user confirmation**. +4. If promotion was required, wait for it to complete, then confirm `.NET ` appears in `darc get-build --id `. + +## After the stage + +This stage produces no repository commit. Next, run the **validate-release** playbook: verify Source Link and symbols on msdl (Stage 5), reconcile the branches (Stage 6), and confirm the support-page listing (Stage 7). diff --git a/.github/agents/release-manager/validate-release/README.md b/.github/agents/release-manager/validate-release/README.md new file mode 100644 index 00000000000..77ac7938429 --- /dev/null +++ b/.github/agents/release-manager/validate-release/README.md @@ -0,0 +1,33 @@ +# Validate Release + +Confirms a published `dotnet/extensions` release is correct and finalizes it: verify that the published symbols are on the Microsoft symbol server (msdl) with working Source Link, then reconcile the internal, public, and `main` branches. + +Run this playbook after **publish-release**. + +- Stage 5 is automated symbol verification. +- Stage 6 stages reconciliation merges when needed (pushing and PR completion are left to the user). +- Stage 7 confirms the support-page listing. + +For servicing releases prepared directly on public `release/.`, Stage 6 is often unnecessary because commits were backported from `main` into the release branch up front. In that case, run Stage 5 and Stage 7, and run Stage 6 only if the user explicitly asks for additional branch-flow follow-up. + +## Stage 5 - Verify Source Link and Symbols + +Run the Source Link sweep against the published packages until every library package reports `valid` on msdl. This is the **release sign-off gate** -- a persistent `symbols-not-indexed` result means the official release build never reached the public `.NET ` channel (see publish-release, Stage 4). + +Read and follow [references/stage-5-verify-source-link.md](references/stage-5-verify-source-link.md). + +## Stage 6 - Reconcile Branches + +Merge the internal release branch back out to the public `release/.` branch (discarding the internal-only infrastructure changes), then merge that public branch into `main` (reverting the stable-versioning flip). Each merge lands as a merge commit (never squashed); the agent stages the merges and shows the diff, but pushing and completing the merge-commit PRs (which need elevation) are user-directed. + +Read and follow [references/stage-6-reconcile-branches.md](references/stage-6-reconcile-branches.md). + +## Stage 7 - Confirm the Support-Page Update + +The .NET Platform Extensions support page is maintained by a partner team. Confirm that a pull request updating it has been opened for this release, review it, and flag any newly published or recently-stabilized packages that are missing from the list. + +Read and follow [references/stage-7-support-page.md](references/stage-7-support-page.md). + +## Done + +These three final stages complete the branch-level release and its verification. Tagging and publishing the GitHub release notes are the **write-release-notes** playbook. diff --git a/.github/agents/release-manager/validate-release/references/stage-5-verify-source-link.md b/.github/agents/release-manager/validate-release/references/stage-5-verify-source-link.md new file mode 100644 index 00000000000..a779e77a1e4 --- /dev/null +++ b/.github/agents/release-manager/validate-release/references/stage-5-verify-source-link.md @@ -0,0 +1,34 @@ +# Stage 5 - Verify Source Link and Symbols + +The publish-release playbook published the packages to nuget.org and promoted/assigned the official release build to the public `.NET ` channel. Only after that assignment do symbols publish to the Microsoft symbol server (msdl). Verify Source Link and symbol-server availability -- this is the **release sign-off gate**. + +## Prerequisites + +- publish-release is complete: packages published to nuget.org and the official release build assigned/promoted to the public `.NET ` channel. +- The `sourcelink` and `dotnet-symbol` global tools (`dotnet tool install -g sourcelink`; `dotnet tool install -g dotnet-symbol`). +- The package scope for this release is known: + - Monthly release: the full release package set. + - Servicing release: the package list from the merged servicing-prep PR description (unless the user explicitly changed scope at publish time). + +## Verify + +Run the Source Link sweep against the folder containing the published packages for this release scope: + +``` +./.github/agents/release-manager/validate-release/scripts/Test-SourceLink.ps1 -PackageDir +``` + +For each `.nupkg` the script extracts a lib DLL, pulls the matching PDB from the Microsoft symbol server (msdl) via `dotnet-symbol`, and runs `sourcelink test`. Each package reports one of: + +- `valid` -- Source Link resolved and the symbols were on msdl. +- `sourcelink-FAILED` -- symbols found, but Source Link did not validate (investigate). +- `symbols-not-indexed` -- the PDB is not yet on msdl (still "Validating..." on nuget.info; re-run later). +- `no-lib-dll` -- template/tooling package with no `lib/**/*.dll` (expected). + +Indexing on msdl lags the promotion/channel assignment, so `symbols-not-indexed` immediately afterward is expected -- **re-run until every published library package in scope is `valid`**. If packages stay `symbols-not-indexed` well after publish, re-confirm the official release build is actually on the public `.NET ` channel (`darc get-build --id ` should list `.NET `). Investigate any `sourcelink-FAILED`. + +**Do not sign off the release until every published library package in scope reports `valid`.** + +## After the stage + +Once every library package is `valid`, the release symbols are public. Continue with **Stage 6 - Reconcile Branches**. diff --git a/.github/agents/release-manager/validate-release/references/stage-6-reconcile-branches.md b/.github/agents/release-manager/validate-release/references/stage-6-reconcile-branches.md new file mode 100644 index 00000000000..80a238837cc --- /dev/null +++ b/.github/agents/release-manager/validate-release/references/stage-6-reconcile-branches.md @@ -0,0 +1,94 @@ +# Stage 6 - Reconcile Branches + +After Stage 5 verifies the published symbols, reconcile the branches: merge the internal release branch back out to the public release branch, then merge the public release branch into `main`. This is the final release activity. + +This stage is designed for the **monthly release**. For servicing releases prepared directly on public `release/.`, Stage 6 is usually skipped unless the user explicitly asks for additional branch-flow reconciliation. + +Like the publish-release stages, this stage is operational: it stages merges and creates commits on throwaway `merge/*` branches, but it does **not** push or complete pull requests on its own. The agent stages each merge, applies the required file "doctoring", commits, and shows the diff for review; **pushing and completing the PRs (which need JIT elevation / admin settings) are user-directed**, and each merge PR must land as a merge commit (never squashed). + +Run each sub-stage only when the user instructs it. Sub-stage 2 depends on Sub-stage 1's PR having already merged into the public release branch. + +## Prerequisites + +- Stage 4 complete: packages published and the release is public. +- A clone with the public GitHub remote (referred to here as `dotnet`) and up-to-date `internal/release/.`, `release/.`, and `main` branches. Fetch all three first. + +## Sub-stage 1 - Merge internal release branch into the public release branch + +Carry the version bumps and internal product changes out to the public `release/.` branch while discarding the internal-only infrastructure changes (the Stage 1 prep). + +1. From `release/.`, create the merge branch (do not use a `release/` prefix -- it is protected): + + ``` + git switch release/. + git switch -c merge/. + ``` + +2. Stage the merge without committing: + + ``` + git merge --no-ff --no-commit internal/release/. + ``` + +3. Discard the internal-only infrastructure changes so they do not reach the public branch -- restore these to the public release-branch version: + + ``` + git checkout HEAD -- Directory.Build.props NuGet.config azure-pipelines.yml eng/pipelines/templates/BuildAndTest.yml + ``` + +4. Keep everything else staged, in particular the versioning changes (`eng/Version.Details.xml`, `eng/Versions.props`) and any internal-only product changes / backports. + +5. Compare against the last five releases to check for anomalies vs. consistency. Look at the corresponding merge PRs from the previous five monthly releases (their titles vary -- e.g. "Merge published release into release/X.Y", "Merging internal changes into the release/X.Y branch", "Merge internal changes from X.Y"). Find them and list their changed files with `gh`: + + ``` + gh pr list --repo dotnet/extensions --base release/. --state merged --json number,title + gh pr view --repo dotnet/extensions --json files + ``` + + Compare this merge's staged file set (`git diff --cached --stat`) against them and flag anomalies to the user before committing: + - `eng/Version.Details.xml` and `eng/Versions.props` appear in every release and must be here too (including the stabilization flip). + - The internal-only infrastructure files (`Directory.Build.props`, `NuGet.config`, `azure-pipelines.yml`, `eng/pipelines/templates/BuildAndTest.yml`) are absent in every recent release and must be absent here too. + - Tooling files such as `.github/agents/**` and `.github/skills/**` are excluded by recent releases -- flag if present. + - The remaining files should be version bumps plus intentional product/test backports. Flag anything unexpected: an unusually large or empty change set, missing versioning, leaked infrastructure, or files no prior release touched. + +6. Review the staged diff: it should be version bumps + product changes, with **no** infrastructure changes. Then commit: + + ``` + git commit -m "Merge published release into release/." + ``` + +7. Pushing (user-directed) to `dotnet` and opening the PR into `release/.` -- title "Merge published release into release/.", label `DO-NOT-SQUASH`, description "Merge using a merge commit. Do not squash." Do not enable auto-merge (squash). Completing the PR (JIT elevation, allow merge commits) is done by the user. + +## Sub-stage 2 - Merge the public release branch into main + +Do this only after Sub-stage 1's PR has merged into `release/.`. It reverts the stable-versioning flip so `main` does not produce stable/release versions. + +1. Fetch, then from `main` create the merge branch: + + ``` + git switch main + git switch -c merge/.-to-main + ``` + +2. Stage the merge without committing: + + ``` + git merge --no-ff --no-commit release/. + ``` + +3. In `eng/Versions.props`, revert only the package-stabilization change (keep all version numbers and all `eng/Version.Details.xml` changes): + - Set `StabilizePackageVersion` back to `false`. + - Set `DotNetFinalVersionKind` back to empty (``). + - `git add eng/Versions.props`. + +4. Review the staged diff, then commit: + + ``` + git commit -m "Merge release/. into main" + ``` + +5. Pushing (user-directed) to `dotnet` and opening the PR into `main` -- title "Merge release/. into main", label `DO-NOT-SQUASH`, description "Merge using a merge commit. Do not squash." Do not enable auto-merge (squash). The user completes the PR. + +## After the stage + +This stage produces merge commits on `merge/*` branches but does not push or complete the pull requests. Tagging and publishing the release notes are handled by the **write-release-notes** playbook. Next, confirm the support-page listing (Stage 7). diff --git a/.github/agents/release-manager/validate-release/references/stage-7-support-page.md b/.github/agents/release-manager/validate-release/references/stage-7-support-page.md new file mode 100644 index 00000000000..b244a695b6f --- /dev/null +++ b/.github/agents/release-manager/validate-release/references/stage-7-support-page.md @@ -0,0 +1,20 @@ +# Stage 7 - Confirm the Support-Page Update + +The [.NET Platform Extensions support policy page](https://dotnet.microsoft.com/en-us/platform/support/policy/extensions) lists the supported packages and their current versions. It is maintained by a **partner team**, so this stage is a **review**, not an edit: confirm their update lands and is complete. + +## Steps + +1. **Find the pull request** into [`dotnet/website`](https://github.com/dotnet/website) that updates the extensions support page for this release (it edits `website/src/netlandingpage/Pages/platform/support/policy/extensions.cshtml`). Search open and recently-merged PRs. +2. **Review it** for this release: + - The supported version and its release date are updated to this release. + - The previous minor version has moved to the out-of-support table with the correct end-of-support date. +3. **Check the package list** against what shipped. Compare the page's listed packages to the packages published in this release, and flag any that are missing -- in particular: + - **Newly published** packages (new in this release). + - **Recently-stabilized** packages (previously preview, now stable as of this release). + + Exclude preview-only packages, which are not listed. +4. If the pull request is missing or incomplete, raise it with the partner team. + +## After the stage + +This is the final validation step. The release is complete once its symbols are public (Stage 5), the branches are reconciled (Stage 6), and the support-page listing is confirmed. diff --git a/.github/agents/release-manager/validate-release/scripts/Test-SourceLink.ps1 b/.github/agents/release-manager/validate-release/scripts/Test-SourceLink.ps1 new file mode 100644 index 00000000000..e944c0c0815 --- /dev/null +++ b/.github/agents/release-manager/validate-release/scripts/Test-SourceLink.ps1 @@ -0,0 +1,61 @@ +<# +.SYNOPSIS + Validates Source Link + symbol-server availability for published NuGet packages, + the same two things nuget.info ("Valid with Symbol Server") checks. + +.DESCRIPTION + For each .nupkg: extracts a lib DLL, pulls the matching PDB from the Microsoft symbol + server (msdl) via dotnet-symbol, then runs `sourcelink test` on the PDB. + + Results: + valid Source Link resolved and symbols were on the server + sourcelink-FAILED Symbols found, but Source Link did not validate + symbols-not-indexed PDB not yet on the symbol server (still "Validating..." on nuget.info) + no-lib-dll Package has no lib/**/*.dll (template/tooling package) + + Requires: dotnet tool install -g sourcelink ; dotnet tool install -g dotnet-symbol + +.EXAMPLE + ./Test-SourceLink.ps1 -PackageDir C:\path\to\published\packages +#> +param( + [string]$PackageDir = '.', + [string]$SymbolServer = 'https://msdl.microsoft.com/download/symbols/', + [int]$Max = 0 # 0 = all packages +) + +$ErrorActionPreference = 'Continue' +$dotnetTools = Join-Path $HOME '.dotnet/tools' +if (Test-Path $dotnetTools) { + $env:PATH = "$env:PATH$([IO.Path]::PathSeparator)$dotnetTools" +} + +$tmp = Join-Path ([IO.Path]::GetTempPath()) ('srclink-' + [guid]::NewGuid().ToString('N')) +New-Item -ItemType Directory -Path $tmp | Out-Null + +$pkgs = Get-ChildItem -Path $PackageDir -Filter '*.nupkg' +if ($Max -gt 0) { $pkgs = $pkgs | Select-Object -First $Max } + +$results = foreach ($pkg in $pkgs) { + $ed = Join-Path $tmp ([IO.Path]::GetFileNameWithoutExtension($pkg.Name)) + Expand-Archive -Path $pkg.FullName -DestinationPath $ed -Force + + $dll = Get-ChildItem -Path (Join-Path $ed 'lib') -Filter '*.dll' -Recurse -ErrorAction SilentlyContinue | + Sort-Object { if ($_.Directory.Name -eq 'net8.0') { 0 } else { 1 } } | Select-Object -First 1 + if (-not $dll) { [pscustomobject]@{ Package = $pkg.Name; Result = 'no-lib-dll' }; continue } + + $sd = Join-Path $ed 'sym'; New-Item -ItemType Directory -Path $sd | Out-Null + Copy-Item $dll.FullName $sd + dotnet-symbol --symbols --microsoft-symbol-server --server-path $SymbolServer --output $sd (Join-Path $sd $dll.Name) 2>&1 | Out-Null + $pdb = Get-ChildItem -Path $sd -Filter '*.pdb' | Select-Object -First 1 + if (-not $pdb) { [pscustomobject]@{ Package = $pkg.Name; Result = 'symbols-not-indexed' }; continue } + + sourcelink test $pdb.FullName 2>&1 | Out-Null + $res = if ($LASTEXITCODE -eq 0) { 'valid' } else { 'sourcelink-FAILED' } + [pscustomobject]@{ Package = $pkg.Name; Result = $res } +} + +$results | Sort-Object Result, Package | Format-Table -AutoSize +Write-Output '' +$results | Group-Object Result | ForEach-Object { "{0,-22} {1}" -f $_.Name, $_.Count } +Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue diff --git a/.github/skills/write-release-notes/SKILL.md b/.github/agents/release-manager/write-release-notes/README.md similarity index 86% rename from .github/skills/write-release-notes/SKILL.md rename to .github/agents/release-manager/write-release-notes/README.md index 0455eef8e0d..f3330d1927a 100644 --- a/.github/skills/write-release-notes/SKILL.md +++ b/.github/agents/release-manager/write-release-notes/README.md @@ -1,15 +1,8 @@ ---- -name: write-release-notes -description: 'Draft release notes for a dotnet/extensions release. Gathers merged PRs, assigns them to packages by file path, categorizes by area and impact, tracks experimental API changes, and produces formatted markdown suitable for a GitHub release. Handles both monthly full releases and targeted intra-month patch releases.' -agent: 'agent' -tools: ['github/*', 'sql', 'ask_user'] ---- - # Release Notes -Draft release notes for a `dotnet/extensions` release. This skill gathers merged PRs between two tags, maps them to affected packages by examining changed file paths, categorizes entries by area and impact, audits experimental API changes, and produces concise markdown suitable for a GitHub release. +Draft release notes for a `dotnet/extensions` release. This playbook gathers merged PRs between two tags, maps them to affected packages by examining changed file paths, categorizes entries by area and impact, audits experimental API changes, and produces concise markdown suitable for a GitHub release. -> **User confirmation required: This skill NEVER publishes a GitHub release without explicit user confirmation.** The user must review and approve the draft before any release is created. +> **User confirmation required: This playbook NEVER publishes a GitHub release without explicit user confirmation.** The user must review and approve the draft before any release is created. ## Context @@ -20,7 +13,7 @@ The `dotnet/extensions` repository ships NuGet packages across many functional a The repository does not follow Semantic Versioning. Major versions align with annual .NET releases, minor versions increment monthly, and patch versions are for intra-month fixes. -The repository makes heavy use of `[Experimental]` attributes. Experimental diagnostic IDs are documented in [`docs/list-of-diagnostics.md`](../../docs/list-of-diagnostics.md). Breaking changes to experimental APIs are expected and acceptable. Graduation of experimental APIs to stable is a noteworthy positive event. +The repository makes heavy use of `[Experimental]` attributes. Experimental diagnostic IDs are documented in [`docs/list-of-diagnostics.md`](../../../../docs/list-of-diagnostics.md). Breaking changes to experimental APIs are expected and acceptable. Graduation of experimental APIs to stable is a noteworthy positive event. The repository uses `release/` branches (e.g. `release/10.4`) where release tags are associated with commits on those branches. When determining the commit range for a release, ensure the previous and target tags are resolved against the appropriate release branch history. @@ -30,6 +23,7 @@ The repository uses `release/` branches (e.g. `release/10.4`) where release tags - **Do not run linters, formatters, or validators** on the output. - **Maximize parallel tool calls.** Fetch multiple PR and issue details in a single response. - **Package assignment is file-path-driven.** Determine which packages a PR affects by examining which `src/Libraries/{PackageName}/` paths it touches. See [references/package-areas.md](references/package-areas.md) for the mapping. Use `area-*` labels only as a fallback. +- **For servicing releases, use the servicing-prep PR description as scope input.** If a merged PR titled `Prepare .. Servicing Release` exists, treat its package list and commit list as the default source of truth for publish/validate/release-notes scope unless the user explicitly overrides it. ## Process @@ -45,7 +39,7 @@ The user may provide: Once the range is established: 1. Determine if this is a **full release** (minor version bump) or **patch release** (patch version bump) based on the version numbers. -2. For patch releases, ask the user which packages are included (or infer from the PRs). +2. For patch releases, ask the user which packages are included (or infer from the PRs). If a servicing-prep PR exists, start from its package list and confirm any overrides. 3. Get the merge date range for PR collection. ### Step 2: Collect and Enrich PRs @@ -84,7 +78,7 @@ Follow [references/experimental-features.md](references/experimental-features.md Build the package version information: 1. For **full releases**: all packages ship at the same version. Note the version number but do not generate a per-package table — it would be repetitive with no value. -2. For **patch releases**: build a table of only the affected packages and their version numbers. +2. For **patch releases**: build a table of only the affected packages and their version numbers. If a servicing-prep PR exists, seed this table from that PR's package list before applying user overrides. 3. Present the version information to the user for confirmation. The user may adjust which packages are included in a patch release. ### Step 6: Draft Release Notes diff --git a/.github/skills/write-release-notes/references/categorize-entries.md b/.github/agents/release-manager/write-release-notes/references/categorize-entries.md similarity index 97% rename from .github/skills/write-release-notes/references/categorize-entries.md rename to .github/agents/release-manager/write-release-notes/references/categorize-entries.md index 6e570167c07..237be07ab49 100644 --- a/.github/skills/write-release-notes/references/categorize-entries.md +++ b/.github/agents/release-manager/write-release-notes/references/categorize-entries.md @@ -11,7 +11,7 @@ For each candidate PR, assign one of these categories based on the primary inten | What's Changed | `changed` | Features, bug fixes, API improvements, performance, breaking changes | | Documentation Updates | `docs` | PRs whose sole purpose is documentation | | Test Improvements | `tests` | Adding, fixing, or improving tests | -| Repository Infrastructure Updates | `infra` | CI/CD, dependency bumps, version bumps, build system, skills | +| Repository Infrastructure Updates | `infra` | CI/CD, dependency bumps, version bumps, build system, agents, workflows and skills | **Decision rules:** - If a PR modifies files under `src/Libraries/` or `src/Generators/` or `src/Analyzers/`, it is `changed` (even if it also touches docs or tests) @@ -59,7 +59,7 @@ These categories are **not** grouped by package area. They appear as flat lists ## Full vs. patch release considerations -### Full monthly release +### Monthly release - All areas with changes get their own heading - All four category sections appear (omit empty ones) - Include the "Experimental API Changes" section if any experimental changes were detected diff --git a/.github/skills/write-release-notes/references/collect-prs.md b/.github/agents/release-manager/write-release-notes/references/collect-prs.md similarity index 69% rename from .github/skills/write-release-notes/references/collect-prs.md rename to .github/agents/release-manager/write-release-notes/references/collect-prs.md index de21ba88d89..7b3419990fb 100644 --- a/.github/skills/write-release-notes/references/collect-prs.md +++ b/.github/agents/release-manager/write-release-notes/references/collect-prs.md @@ -133,10 +133,18 @@ For each candidate PR, check whether it was already included in any prior releas 1. **Check against the prior release tag**: `git merge-base --is-ancestor `. If the PR's merge commit is an ancestor of the previous release tag, it shipped in that release — exclude it. 2. **Check against the prior release branch HEAD**: The release branch may have advanced beyond the release tag (e.g. `release/10.3` may contain commits merged after `v10.3.0` was tagged but before `v10.3.1` or the branch was abandoned). Check: `git merge-base --is-ancestor /release/10.3`. If reachable, the PR was part of that release branch's content — exclude it. 3. **Check the prior release notes body**: Fetch the GitHub release for the previous tag and check if the PR number appears in the release notes body. This catches PRs that were explicitly covered. +4. **Harvest `main`-PR references from every PR merged into the prior release branch**: Backport PRs name the `main` PR number(s) they carry -- in the PR **title** (a trailing `(#NNNN)`) and/or **body** (`Backport of #NNNN`, or a checklist/list of numbers). This is the reliable signal, and it handles both shapes of backport: + - **Single-PR backport**: `[release/10.7] Fix ToolJson.AdditionalProperties to accept sub-schema objects` (#7547) references its `main` twin #7546 in the body. + - **Aggregate backport**: `Stage an MEAI 10.4.1 release` (#7402) references **thirteen** `main` PRs in its body (`#7285, #7340, #7369, #7373, #7379, #7381, #7382, #7387, #7389, #7394, #7398, #7399, #7400`) and has **no `[release/...]` title twin at all**. Title-matching would miss every one of them. -> **Why this matters:** A PR can be merged into a `release/` branch, ship in that release's packages, but never appear in that release's notes (e.g. a late-breaking fix). When that PR is later merged into `main`, it appears in the date-range search for the next release. Without branch-aware deduplication, it would be incorrectly included in the new release notes. + Build a "shipped-via-backport" set: list every PR merged into the prior release branch (`gh pr list --repo dotnet/extensions --base release/. --state merged --limit 200 --json number,title,body`), then extract every `#NNNN` reference from each PR's **title and body**. Exclude any candidate whose PR number appears in that set. These are `main` PRs whose changes shipped in the prior release even though the `main` PR's own merge commit is new to the current release's lineage, so the ancestry checks in 1-2 miss them. +5. **Fallback -- title equivalence**: For the rare backport that omits the `(#NNNN)` reference, fall back to matching titles. Normalize each candidate's title (strip any leading `[release/x.y] ` prefix and any trailing `(#NNNN)`), then look for a PR merged into the prior release branch with a matching normalized title (`gh pr list --repo dotnet/extensions --base release/. --state merged --search " in:title"`), or a title match against the prior release notes body. Treat a match as an exclusion. -This step is critical and must run before marking PRs as candidates. +> **Why this matters:** A change already shipped in the prior release can reappear in the next release's date-range search in two ways: +> - A PR merged directly into a `release/` branch ships in that release but may never appear in its notes; when the branch later merges into `main` it surfaces in the next release's search. The ancestry checks (1-2) catch this. +> - A change is **backported**: it merges into `main` as its own PR (new to the next release's lineage, so ancestry misses it) and separately ships in the prior release via a backport PR merged into that release branch. The backport PR names the `main` PR number(s) it carries -- **one** number for a single-PR backport, **many** for an aggregate backport -- so harvesting those references (4) is what excludes the `main` half. The notes-body number check (3) and title fallback (5) are backups for the rare backport that doesn't cite its source PR numbers. + +Steps 1-5 are critical and must **all** run before marking PRs as candidates. The ancestry checks (1-2) alone will let the `main` half of a backport slip through -- e.g. #7546 when its backport #7547 already shipped in 10.7, or any of the thirteen `main` PRs carried by an aggregate backport like #7402. Harvesting `main`-PR references (4) is the primary defense; the notes-body (3) and title (5) checks are fallbacks for backports that don't cite their source PR numbers. ## Exclusion filters diff --git a/.github/skills/write-release-notes/references/editorial-rules.md b/.github/agents/release-manager/write-release-notes/references/editorial-rules.md similarity index 99% rename from .github/skills/write-release-notes/references/editorial-rules.md rename to .github/agents/release-manager/write-release-notes/references/editorial-rules.md index 9efe10704b7..5c2aa24b24c 100644 --- a/.github/skills/write-release-notes/references/editorial-rules.md +++ b/.github/agents/release-manager/write-release-notes/references/editorial-rules.md @@ -91,7 +91,7 @@ PRs that maintain the development environment: - CI/CD workflow changes - Dependency updates (Dependabot) - Build system changes -- Copilot instructions and skill updates +- Copilot instructions, agent, and skill updates PRs that touch test code should never be categorized as Infrastructure. diff --git a/.github/skills/write-release-notes/references/experimental-features.md b/.github/agents/release-manager/write-release-notes/references/experimental-features.md similarity index 100% rename from .github/skills/write-release-notes/references/experimental-features.md rename to .github/agents/release-manager/write-release-notes/references/experimental-features.md diff --git a/.github/skills/write-release-notes/references/format-template.md b/.github/agents/release-manager/write-release-notes/references/format-template.md similarity index 99% rename from .github/skills/write-release-notes/references/format-template.md rename to .github/agents/release-manager/write-release-notes/references/format-template.md index 93f4a18debd..3a50562ba4c 100644 --- a/.github/skills/write-release-notes/references/format-template.md +++ b/.github/agents/release-manager/write-release-notes/references/format-template.md @@ -1,6 +1,6 @@ # Release Notes Format Template -## Full monthly release +## Monthly release Use this template when all packages ship together (e.g. v10.3.0 → v10.4.0). diff --git a/.github/skills/write-release-notes/references/package-areas.md b/.github/agents/release-manager/write-release-notes/references/package-areas.md similarity index 100% rename from .github/skills/write-release-notes/references/package-areas.md rename to .github/agents/release-manager/write-release-notes/references/package-areas.md diff --git a/.github/skills/write-release-notes/references/sql-storage.md b/.github/agents/release-manager/write-release-notes/references/sql-storage.md similarity index 100% rename from .github/skills/write-release-notes/references/sql-storage.md rename to .github/agents/release-manager/write-release-notes/references/sql-storage.md