From 6aa3045b5e4728a096a466dd2b480e175460b40f Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Thu, 26 Mar 2026 16:22:34 -0700 Subject: [PATCH 01/65] Add release notes agentic workflow and skill references Cron-driven GitHub Agentic Workflow that maintains release notes branches for .NET preview/RC/GA releases. Architecture: - dotnet-release generate changes tool produces changes.json (deterministic, from VMR source-manifest.json diffs) - Agent reads changes.json and writes curated markdown for high-value features - One PR per release milestone, maintained incrementally - Respects human edits on the branch Skill references (6 docs, ~700 lines total): - quality-bar.md: what good release notes look like - vmr-structure.md: VMR branches, tags, source-manifest.json - changes-schema.md: changes.json schema reference - component-mapping.md: components, product slugs, output files - format-template.md: markdown document structure - editorial-rules.md: tone, attribution, naming Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/SKILL.md | 24 ++++ .../references/changes-schema.md | 136 ++++++++++++++++++ .../references/component-mapping.md | 82 +++++++++++ .../references/editorial-rules.md | 83 +++++++++++ .../references/format-template.md | 71 +++++++++ .../release-notes/references/quality-bar.md | 68 +++++++++ .../release-notes/references/vmr-structure.md | 107 ++++++++++++++ .github/workflows/release-notes.md | 128 +++++++++++++++++ 8 files changed, 699 insertions(+) create mode 100644 .github/skills/release-notes/SKILL.md create mode 100644 .github/skills/release-notes/references/changes-schema.md create mode 100644 .github/skills/release-notes/references/component-mapping.md create mode 100644 .github/skills/release-notes/references/editorial-rules.md create mode 100644 .github/skills/release-notes/references/format-template.md create mode 100644 .github/skills/release-notes/references/quality-bar.md create mode 100644 .github/skills/release-notes/references/vmr-structure.md create mode 100644 .github/workflows/release-notes.md diff --git a/.github/skills/release-notes/SKILL.md b/.github/skills/release-notes/SKILL.md new file mode 100644 index 0000000000..30df5227d7 --- /dev/null +++ b/.github/skills/release-notes/SKILL.md @@ -0,0 +1,24 @@ +--- +name: release-notes +description: Generate and maintain .NET release notes. Uses the VMR source-manifest.json and dotnet-release tool to identify what shipped, then writes curated markdown for high-value features. Designed to run as a cron-driven agentic workflow that maintains PR branches per release milestone. +compatibility: Requires GitHub MCP server or gh CLI for cross-repo queries. Uses dotnet-release generate changes for structured change data. +--- + +# .NET Release Notes + +Generate and maintain release notes for .NET preview, RC, and GA releases. + +## How it works + +1. The `dotnet-release generate changes` tool diffs `source-manifest.json` between VMR release refs to produce `changes.json` — a comprehensive manifest of all PRs/commits that shipped +2. The agent reads `changes.json` and writes curated markdown release notes for high-value features +3. Output is one PR per release milestone in dotnet/core, maintained incrementally + +## Reference documents + +- [quality-bar.md](references/quality-bar.md) — what good release notes look like +- [vmr-structure.md](references/vmr-structure.md) — VMR branches, tags, source-manifest.json +- [changes-schema.md](references/changes-schema.md) — the changes.json schema +- [component-mapping.md](references/component-mapping.md) — components, product slugs, output files +- [format-template.md](references/format-template.md) — markdown document structure +- [editorial-rules.md](references/editorial-rules.md) — tone, attribution, naming diff --git a/.github/skills/release-notes/references/changes-schema.md b/.github/skills/release-notes/references/changes-schema.md new file mode 100644 index 0000000000..2a501472f4 --- /dev/null +++ b/.github/skills/release-notes/references/changes-schema.md @@ -0,0 +1,136 @@ +# changes.json Schema + +Reference for the `changes.json` file produced by `dotnet-release generate changes`. One file per release milestone. + +Based on the [changes schema proposal](https://gist.github.com/richlander/a2bf9beb6f09cf9ba7ecf80f5b51784e). + +## Overview + +`changes.json` is a comprehensive, machine-readable manifest of every PR and commit that shipped in a release. It is the companion to the editorial markdown release notes — the JSON tells you **everything that shipped**, the markdown tells you **what matters**. + +It also serves as a companion to `cve.json` — both files use the same `commits{}` structure and `repo@shortcommit` key format, enabling cross-file joins. + +## File location + +```text +release-notes/{major.minor}/preview/{previewN}/changes.json # previews +release-notes/{major.minor}/{major.minor.patch}/changes.json # patches +``` + +## Top-level structure + +| Field | Type | Description | +| ----- | ---- | ----------- | +| `release_version` | string | e.g., `"11.0.0-preview.3"` | +| `release_date` | string | ISO 8601, e.g., `"2026-04-08"` | +| `changes` | array | The change entries | +| `commits` | object | Normalized commit metadata, keyed by `repo@shortcommit` | + +## Change entry fields + +| Field | Type | Description | +| ----- | ---- | ----------- | +| `id` | int | GitHub PR number; `0` if no public PR | +| `repo` | string | Short repository name (e.g., `"runtime"`) | +| `product` | string | Product slug from taxonomy (e.g., `"dotnet-runtime"`) | +| `package` | string | NuGet package name when applicable (e.g., `"System.Text.Json"`) | +| `title` | string | PR title; `""` if not available | +| `url` | string | Public GitHub PR URL; `""` if non-public | +| `commit` | string | Key into top-level `commits{}` dict | +| `is_security` | bool | `true` if this is a security change | + +Both `product` and `package` may be present. At least one SHOULD be present. + +## Commit entry fields (values in `commits{}`) + +| Field | Type | Description | +| ----- | ---- | ----------- | +| `repo` | string | Short repository name | +| `branch` | string | Branch the commit landed on | +| `hash` | string | Full 40-character commit hash | +| `org` | string | GitHub organization (e.g., `"dotnet"`) | +| `url` | string | `.diff`-form commit URL | + +## Conventions + +- **No nulls** — every field is always present. Use `""` for missing strings, `0` for missing integers. +- **Naming** — `snake_case_lower` for JSON fields, `kebab-case-lower` for file names and repo slugs. +- **Public URLs only** — every URL must resolve publicly. +- **Commit URLs use `.diff` form** — for machine consumption. +- **Product slugs** come from the `products.json` taxonomy file (e.g., `dotnet-runtime`, `dotnet-aspnetcore`, `dotnet-sdk`). + +## Example + +```json +{ + "release_version": "11.0.0-preview.3", + "release_date": "2026-04-08", + "changes": [ + { + "id": 112345, + "repo": "runtime", + "product": "dotnet-runtime", + "package": "System.Text.Json", + "title": "Add JsonSerializerOptions.Web preset", + "url": "https://github.com/dotnet/runtime/pull/112345", + "commit": "runtime@b2d5fa8", + "is_security": false + }, + { + "id": 54321, + "repo": "aspnetcore", + "product": "dotnet-aspnetcore", + "package": "", + "title": "Add MapStaticAssets middleware", + "url": "https://github.com/dotnet/aspnetcore/pull/54321", + "commit": "aspnetcore@f45f3c9", + "is_security": false + } + ], + "commits": { + "runtime@b2d5fa8": { + "repo": "runtime", + "branch": "main", + "hash": "b2d5fa8ca2f9c2d7e8f1f0a9d3d0f08e31c0ad5f", + "org": "dotnet", + "url": "https://github.com/dotnet/runtime/commit/b2d5fa8ca2f9c2d7e8f1f0a9d3d0f08e31c0ad5f.diff" + }, + "aspnetcore@f45f3c9": { + "repo": "aspnetcore", + "branch": "main", + "hash": "f45f3c916df91e3fb175c85ba4a4f58ec1f77ef0", + "org": "dotnet", + "url": "https://github.com/dotnet/aspnetcore/commit/f45f3c916df91e3fb175c85ba4a4f58ec1f77ef0.diff" + } + } +} +``` + +## Querying changes.json + +```bash +# All changes +jq -r '.changes[] | .title' changes.json + +# Changes by product +jq -r '.changes[] | select(.product == "dotnet-runtime") | .title' changes.json + +# Changes affecting a NuGet package +jq -r '.changes[] | select(.package == "System.Text.Json") | .title' changes.json + +# Security changes +jq -r '.changes[] | select(.is_security) | .title' changes.json + +# Cross-file join with cve.json (shared commit key format) +jq -r '.changes[] | select(.is_security) | .commit' changes.json +# → use these keys to look up CVE IDs in cve.json's cve_commits{} +``` + +## Relationship to markdown release notes + +`changes.json` is the **input** to the editorial process. The markdown release notes are a curated subset: + +- `changes.json` has an entry for every PR that shipped +- Markdown only covers features worth calling out +- The agent reads `changes.json` to know what shipped, then decides what to write about +- If a feature isn't in `changes.json`, it must not appear in the markdown diff --git a/.github/skills/release-notes/references/component-mapping.md b/.github/skills/release-notes/references/component-mapping.md new file mode 100644 index 0000000000..5c72bf93db --- /dev/null +++ b/.github/skills/release-notes/references/component-mapping.md @@ -0,0 +1,82 @@ +# Component Mapping + +Maps VMR source paths to .NET components, their source repositories, product taxonomy slugs, and release notes output files. + +## Path-to-component mapping + +Uses `source-manifest.json` `path` values to identify components. The `dotnet-release generate changes` tool uses this mapping to populate the `product` field in `changes.json`. + +| Manifest Path | Component | Source Repo | Product Slug | Release Notes File | +| ------------- | --------- | ----------- | ------------ | ------------------ | +| `runtime` | .NET Libraries | `dotnet/runtime` | `dotnet-runtime` | `libraries.md` | +| `runtime` | .NET Runtime | `dotnet/runtime` | `dotnet-runtime` | `runtime.md` | +| `aspnetcore` | ASP.NET Core | `dotnet/aspnetcore` | `dotnet-aspnetcore` | `aspnetcore.md` | +| `razor` | ASP.NET Core (Razor) | `dotnet/razor` | `dotnet-aspnetcore` | `aspnetcore.md` | +| `sdk` | .NET SDK | `dotnet/sdk` | `dotnet-sdk` | `sdk.md` | +| `templating` | .NET SDK (Templating) | `dotnet/templating` | `dotnet-sdk` | `sdk.md` | +| `msbuild` | MSBuild | `dotnet/msbuild` | `dotnet-msbuild` | `msbuild.md` | +| `winforms` | Windows Forms | `dotnet/winforms` | `dotnet-winforms` | `winforms.md` | +| `wpf` | WPF | `dotnet/wpf` | `dotnet-wpf` | `wpf.md` | +| `efcore` | EF Core | `dotnet/efcore` | `dotnet-efcore` | `efcore.md` | +| `roslyn` | C# / Visual Basic | `dotnet/roslyn` | `dotnet-roslyn` | `csharp.md` | +| `fsharp` | F# | `dotnet/fsharp` | `dotnet-fsharp` | `fsharp.md` | +| `nuget-client` | NuGet | `nuget/nuget.client` | `dotnet-nuget` | `nuget.md` | + +### Runtime sub-component classification + +The `runtime` manifest entry covers both Libraries and Runtime. When writing markdown, classify PRs by the files they changed: + +| VMR Path Prefix | Sub-component | Output File | +| --------------- | ------------- | ----------- | +| `src/runtime/src/libraries/` | Libraries | `libraries.md` | +| `src/runtime/src/coreclr/` | Runtime (CoreCLR) | `runtime.md` | +| `src/runtime/src/mono/` | Runtime (Mono) | `runtime.md` | +| `src/runtime/src/native/` | Runtime (Native) | `runtime.md` | + +### Components that share output files + +- **Razor → ASP.NET Core** — `dotnet/razor` PRs go in `aspnetcore.md` +- **Templating → SDK** — `dotnet/templating` PRs go in `sdk.md` +- **Roslyn** — covers both C# and Visual Basic. Check PR labels/titles to determine language. Produce `csharp.md` (and `visualbasic.md` if VB-specific features exist). + +### Infrastructure components (skip for release notes) + +These appear in `source-manifest.json` but rarely produce user-facing changes: + +| Manifest Path | Repo | Notes | +| ------------- | ---- | ----- | +| `arcade` | `dotnet/arcade` | Build infrastructure | +| `cecil` | `dotnet/cecil` | IL manipulation library (internal) | +| `command-line-api` | `dotnet/command-line-api` | CLI parsing (internal) | +| `deployment-tools` | `dotnet/deployment-tools` | Deployment tooling | +| `diagnostics` | `dotnet/diagnostics` | Diagnostic tools | +| `emsdk` | `dotnet/emsdk` | Emscripten SDK | +| `scenario-tests` | `dotnet/scenario-tests` | Test infrastructure | +| `source-build-reference-packages` | `dotnet/source-build-reference-packages` | Source build | +| `sourcelink` | `dotnet/sourcelink` | Source Link | +| `symreader` | `dotnet/symreader` | Symbol reader | +| `windowsdesktop` | `dotnet/windowsdesktop` | Metapackage | +| `vstest` | `microsoft/vstest` | Test platform | +| `xdt` | `dotnet/xdt` | XML transforms | + +These components appear in `changes.json` for completeness but typically don't warrant markdown release notes. + +## Expected output files per preview + +Every preview should produce these files (stubs for components with no noteworthy changes): + +```text +README.md # Index/TOC linking to all component files +libraries.md # System.* BCL APIs +runtime.md # CoreCLR, Mono, GC, JIT +aspnetcore.md # ASP.NET Core, Blazor, SignalR +sdk.md # CLI, build, project system, NuGet +efcore.md # Entity Framework Core +csharp.md # C# language features +fsharp.md # F# language and compiler +winforms.md # Windows Forms +wpf.md # WPF +msbuild.md # MSBuild +nuget.md # NuGet client +changes.json # Machine-readable change manifest +``` diff --git a/.github/skills/release-notes/references/editorial-rules.md b/.github/skills/release-notes/references/editorial-rules.md new file mode 100644 index 0000000000..74a6e916f2 --- /dev/null +++ b/.github/skills/release-notes/references/editorial-rules.md @@ -0,0 +1,83 @@ +# Editorial Rules + +Tone, attribution, and content guidelines for .NET release notes. + +## Tone + +- **Positive** — highlight what's new, don't dwell on what was missing + - ✅ `ProcessExitStatus provides a unified representation of how a process terminated.` + - ❌ `Previously, there was no way to determine how a process terminated.` +- When context about the prior state is needed, keep it brief — one clause, then pivot to the new capability + +## Entry naming + +- Prefer a **brief description** of what the feature does over the API name alone + - ✅ `## Support for Zstandard compression` + - ✅ `## Faster time zone conversions` + - ❌ `## ZstandardStream` + - ❌ `## TimeZoneInfo performance` +- Keep headings concise — 3–8 words + +## Benchmarks + +- Use **exact data** from PR descriptions — never round, approximate, or paraphrase +- State what was measured and the hardware/workload context +- Include specific before/after measurements when compelling +- Do **not** embed full BenchmarkDotNet tables — summarize in prose + +## Feature ordering + +Order features by **customer impact**: + +1. Major new capabilities — especially those with high community reaction counts +2. Meaningful improvements to existing capabilities +3. Smaller additions + +Use PR and issue reaction counts as a signal, but apply judgment — a niche feature with 100 reactions may still be less impactful than a broadly useful one with 10. + +## Community attribution + +### Inline + +When a documented feature was contributed externally: + +```markdown +Thank you [@username](https://github.com/username) for this contribution! +``` + +### Community contributors section + +At the bottom of each component's notes, list ALL external contributors — not just those with documented features. Use the `community-contribution` label to identify them. + +```markdown +## Community contributors + +Thank you contributors! ❤️ + +- [@username](https://github.com///pulls?q=is%3Apr+is%3Amerged+author%3Ausername) +``` + +## Bug fixes section + +After features but before community contributors, include a grouped bug fix summary when there are noteworthy fixes: + +```markdown +## Bug fixes + +- **System.Net.Http** + - Fixed authenticated proxy credential handling ([dotnet/runtime#123363](https://github.com/dotnet/runtime/issues/123363)) +- **System.Collections** + - Fixed integer overflow in ImmutableArray range validation ([dotnet/runtime#124042](https://github.com/dotnet/runtime/pull/124042)) +``` + +Group by namespace/area. Don't include test-only, CI, or infra fixes. + +## Preview-to-preview feedback fixes + +Include a bug fix when ALL of these apply: + +1. The issue was filed after the previous preview shipped +2. It was reported by someone outside the team +3. A fix shipped in the current preview + +Frame positively: "Based on community feedback, X now does Y." diff --git a/.github/skills/release-notes/references/format-template.md b/.github/skills/release-notes/references/format-template.md new file mode 100644 index 0000000000..ce33e2dbd5 --- /dev/null +++ b/.github/skills/release-notes/references/format-template.md @@ -0,0 +1,71 @@ +# Format Template + +Standard document structure for .NET release notes markdown files. + +## Document structure + +```markdown +# in .NET - Release Notes + +.NET includes new features & enhancements: + +- [Feature Name](#anchor) +- [Feature Name](#anchor) + + updates in .NET : + +- [What's new in .NET ](https://learn.microsoft.com/dotnet/core/whats-new/dotnet-/overview) documentation + +## Feature Name + + ([/#NNNNN](https://github.com///pull/NNNNN)). + +## Bug fixes + +- **Category** — Fix description + +## Community contributors + +- [@username](https://github.com/username) +``` + +## Section rules + +1. **TOC at top** — every feature gets a linked entry +2. **One paragraph of context** — what the feature does and why it matters, with PR/issue links +3. **Code sample** — show the feature in use +4. **Feature ordering** — highest customer impact first + +## Issue and PR references + +Always use markdown links with the `{org}/{repo}#{number}` format: + +- ✅ `[dotnet/runtime#124264](https://github.com/dotnet/runtime/pull/124264)` +- ❌ `dotnet/runtime#124264` (bare reference) + +## Minimal stub + +For components with no noteworthy changes: + +```markdown +# in .NET - Release Notes + +There are no new features or improvements in in this release. +``` + +## Example entry + +```markdown +## Finding Certificates By Thumbprints Other Than SHA-1 + +A new method on `X509Certificate2Collection` accepts the name of the hash algorithm to use +for thumbprint matching ([dotnet/runtime#NNNNN](https://github.com/dotnet/runtime/pull/NNNNN)). +``` + +Code sample example: + +```csharp +X509Certificate2Collection coll = store.Certificates.FindByThumbprint( + HashAlgorithmName.SHA256, thumbprint); +return coll.SingleOrDefault(); +``` diff --git a/.github/skills/release-notes/references/quality-bar.md b/.github/skills/release-notes/references/quality-bar.md new file mode 100644 index 0000000000..2b80126d1d --- /dev/null +++ b/.github/skills/release-notes/references/quality-bar.md @@ -0,0 +1,68 @@ +# Quality Bar + +What good .NET release notes look like. This is the north star — when in doubt, refer back here. + +## Two outputs, two standards + +### changes.json — comprehensive and mechanical + +Every PR that shipped gets an entry. No editorial judgment — if the `dotnet-release generate changes` tool found it in the source-manifest diff, it goes in. This is the machine-readable record of what shipped. + +Quality criteria: + +- **Complete** — every merged PR in the commit range is represented +- **Accurate** — PR numbers, titles, URLs, and commit hashes are correct +- **Classified** — `product` and `package` fields are populated where applicable +- **Joinable** — commit keys match the format used in `cve.json` for cross-file queries + +### Markdown release notes — curated and editorial + +Not every PR deserves a writeup. The markdown covers features users will care about. + +Quality criteria: + +- **High fidelity** — everything documented actually ships in this release +- **High value** — focused on what matters, not exhaustive +- **Useful** — a developer can read a section and start using the feature +- **Honest** — no marketing fluff, no exaggeration, no invented benchmarks + +## What to include in markdown + +Include a feature if it gives users something **new to try**, something that **works better**, or something they **asked for**: + +- New capabilities users can take advantage of +- Measurable improvements to performance, reliability, or usability +- High community demand (reaction counts on backing issues/PRs) +- Behavior changes users need to be aware of +- Preview-to-preview fixes for community-reported issues + +## What to exclude from markdown + +- Internal refactoring with no user-facing change +- Test-only changes +- Build/infrastructure changes +- Backports from servicing branches +- Features that are not independently useful yet (still need unshipped APIs) +- Anything not confirmed by `changes.json` (i.e., not in the source-manifest diff) + +## The fidelity rule + +**If it's not in `changes.json`, don't write about it.** + +The `changes.json` file is generated from the VMR's `source-manifest.json` diff — it reflects exactly what code moved between release points. This is the single source of truth. Don't document features based on PR titles alone, roadmap promises, or what "should" ship. Only document what the tool confirms actually shipped. + +## Every feature entry needs WHY and HOW + +A release note that just names a feature is useless. Every entry must answer: + +1. **Why does this matter?** — what problem does it solve, what scenario does it enable? +2. **How do I use it?** — a code sample showing the feature in action + +If you can't write a code sample for a feature, question whether it's user-facing enough for release notes. + +## Tone + +- Positive — highlight what's new, don't dwell on what was missing +- Direct — one paragraph of context, then show the code +- Precise — exact benchmark numbers only, never approximations +- Respectful of reader time — concise descriptions, no padding diff --git a/.github/skills/release-notes/references/vmr-structure.md b/.github/skills/release-notes/references/vmr-structure.md new file mode 100644 index 0000000000..3fbd258168 --- /dev/null +++ b/.github/skills/release-notes/references/vmr-structure.md @@ -0,0 +1,107 @@ +# VMR Structure + +How the .NET Virtual Monolithic Repository (VMR) at `dotnet/dotnet` works, and how to use it for release notes generation. + +## What is the VMR? + +The VMR (`dotnet/dotnet`) contains the source code for the entire .NET product — runtime, libraries, ASP.NET Core, SDK, compilers, and more. It's assembled from ~25 component repositories via an automated process called **codeflow**. + +## source-manifest.json — the bill of materials + +The file `src/source-manifest.json` is the key to everything. It lists every component repository included in the VMR, with the exact commit SHA that was incorporated: + +```json +{ + "repositories": [ + { + "path": "runtime", + "remoteUri": "https://github.com/dotnet/runtime", + "commitSha": "d3439749b652966f8283f2d01c972bc8c4dc3ec3" + }, + { + "path": "aspnetcore", + "remoteUri": "https://github.com/dotnet/aspnetcore", + "commitSha": "655f41d52f2fc75992eac41496b8e9cc119e1b54" + } + ] +} +``` + +### Using source-manifest.json for release notes + +By comparing `source-manifest.json` at two release points, you get: + +1. **Which components changed** — repos where `commitSha` differs +2. **Exact commit ranges** — the old SHA and new SHA in each source repo +3. **Which components didn't change** — these get minimal stubs, no investigation needed +4. **Source repo URLs** — for querying PRs via the GitHub compare API + +The `dotnet-release generate changes` tool automates this: it fetches the manifest at both refs, diffs them, queries GitHub for PRs in each commit range, and outputs `changes.json`. + +## Branch naming + +### Long-lived release branches + +```text +release/{major}.0.1xx # GA release train (e.g., release/10.0.1xx) +release/{major}.0.1xx-preview{N} # Preview release train (e.g., release/11.0.1xx-preview3) +``` + +### Tags + +Tags come in pairs (runtime and SDK versions) pointing to the same commit: + +```text +v{major}.0.0-preview.{N}.{build} # Runtime version tag +v{major}.0.100-preview.{N}.{build} # SDK version tag +``` + +Examples: + +- `v11.0.0-preview.2.26159.112` (runtime) +- `v11.0.100-preview.2.26159.112` (SDK) + +### Automation branches (ephemeral) + +```text +release-pr-{major}.0.100-{label}.{build} # Release staging PR +darc-release/{band}-{guid} # Codeflow dependency update +``` + +These are created by automation (Maestro/Darc) and are not manually managed. A `release-pr-*` branch signals an imminent release — it contains the version bumps and tooling updates for the release build. + +## Codeflow + +Code flows between component repos and the VMR via automated PRs: + +- **Forward flow** (repo → VMR): component repos push source changes into the VMR +- **Backflow** (VMR → repo): VMR-level changes flow back to component repos + +Codeflow PRs come from `dotnet-maestro[bot]` with titles like `[{branch}] Source code updates from dotnet/{repo}`. The `github-merge-flow.jsonc` files in some components define the merge chain (e.g., `release/10.0.1xx → release/10.0.2xx → release/10.0.3xx → main`). + +## Finding the previous release tag + +To determine what's new in a preview, you need the previous release's VMR tag. Find it by reading the previous preview's `release.json` in this repo: + +```text +release-notes/{major}.0/preview/{previous-preview}/release.json +→ .release.runtime.version (e.g., "11.0.0-preview.2.26159.112") +``` + +Map that version to a VMR tag: `v11.0.0-preview.2.26159.112`. + +## Version metadata + +The VMR's `eng/Versions.props` tracks the current version: + +- `PreReleaseVersionLabel` — e.g., `preview` +- `PreReleaseVersionIteration` — e.g., `3` (for preview 3) + +## What's NOT in the VMR + +Some .NET components are not in the VMR and won't appear in `source-manifest.json`: + +- .NET MAUI (`dotnet/maui`) +- Container images (`dotnet/dotnet-docker`) + +These are currently out of scope for automated release notes generation. diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md new file mode 100644 index 0000000000..373377b861 --- /dev/null +++ b/.github/workflows/release-notes.md @@ -0,0 +1,128 @@ +--- +on: + schedule: daily around 9:00 utc-8 + workflow_dispatch: + inputs: + preview: + description: "Target preview (e.g., preview3). Leave empty for auto-detection." + required: false + type: string + skip-if-match: + query: "is:pr is:open label:release-notes in:title \"[release-notes]\" author:app/github-actions" + max: 5 +permissions: + contents: read + pull-requests: read + issues: read +runtimes: + dotnet: + version: "9.0" +network: + allowed: + - defaults + - dotnet +safe-outputs: + create-pull-request: + title-prefix: "[release-notes] " + labels: [release-notes, automated] + draft: true + max: 3 + push-to-pull-request-branch: + title-prefix: "[release-notes] " + labels: [release-notes, automated] + max: 3 + add-comment: + max: 10 + target: "*" +tools: + github: + toolsets: [issues, pull_requests, contents, code_search] + bash: + - dotnet + - dotnet-release + - git + - jq +--- + +# .NET Release Notes Maintenance + +You maintain release notes for .NET preview, RC, and GA releases in this repository (dotnet/core). You run daily on a cron schedule. Your outputs are pull requests containing two things per release milestone: + +1. **`changes.json`** — a comprehensive, machine-readable manifest of all PRs and commits that shipped, generated by the `dotnet-release generate changes` tool +2. **Markdown release notes** — curated, editorial content covering high-value features + +## Your principles + +- **High fidelity** — only document what actually ships. The VMR (`dotnet/dotnet`) is the source of truth. The `dotnet-release generate changes` tool uses `src/source-manifest.json` diffs between release refs to determine what changed. Trust its output. +- **High value** — bias toward features users will care about. Not every PR deserves a release note. Focus on new capabilities, significant improvements, and things users asked for. Skip infra, test-only, and internal refactoring PRs. +- **Never document non-shipping features** — if it's not in `changes.json`, it didn't ship. Don't write about it. +- **Respect human edits** — when updating an existing PR branch, check what humans have changed. Preserve their work. Only add or update sections they haven't touched. +- **Incremental improvement** — drafts get better over time. Early in a preview cycle, the notes may be rough. As the release stabilizes, refine them. + +## Reference documents + +Read these files from `.github/skills/release-notes/references/` for detailed guidance: + +- **quality-bar.md** — what good release notes look like +- **vmr-structure.md** — how the VMR works, branch naming, source-manifest.json +- **changes-schema.md** — the changes.json schema +- **component-mapping.md** — VMR paths → components → product slugs → output files +- **format-template.md** — markdown document structure +- **editorial-rules.md** — tone, attribution, naming conventions + +## What to do each run + +### 1. Detect active milestones + +Check the VMR (`dotnet/dotnet`) for the current development train. Look for: + +- Release branches: `release/{major}.0.1xx-{label}` (e.g., `release/11.0.1xx-preview3`) +- Release tags: `v{major}.0.0-{label}.{build}` (e.g., `v11.0.0-preview.3.26168.106`) +- Release-PR branches: `release-pr-{major}.0.100-{label}.{build}` (signals an imminent release) + +Determine which milestone(s) need release notes by comparing against open PRs in this repo with the `[release-notes]` title prefix. + +### 2. Generate changes.json + +For each milestone that needs work, run the `dotnet-release generate changes` tool: + +```bash +dotnet-release generate changes \ + --vmr-repo dotnet/dotnet \ + --base-ref \ + --target-ref \ + --output release-notes/{major}.0/preview/{previewN}/changes.json +``` + +Find the previous release tag by reading `release-notes/{major}.0/preview/{previous}/release.json` → `.release.runtime.version` and mapping to a VMR tag (`v{version}`). + +### 3. Write or update markdown release notes + +Using `changes.json` as your input: + +- Group changes by component (the `product` field maps to output files via component-mapping.md) +- For each component with changes, identify which PRs are worth writing about +- Write feature descriptions following format-template.md and editorial-rules.md +- For components with no noteworthy changes, produce a minimal stub + +When updating an existing branch: + +- `git diff` the branch to see what humans have changed since your last push +- Do NOT overwrite sections that humans have edited +- Add new sections for newly discovered features +- Update `changes.json` (this is always regenerated fresh from the tool) + +### 4. Create or update the PR + +- If no PR exists for this milestone: create a new branch and PR +- If a PR exists: push updates to the existing branch +- Comment on the PR summarizing what changed in this update + +### 5. Respond to feedback + +If humans have commented on the PR since your last run, read their feedback and incorporate it. Common requests: + +- "Add more detail about X" — expand the section +- "This feature isn't ready" — remove or stub it +- "Wrong component" — move the entry +- "Needs a code sample" — add one From 3d196a22bca0d9368ea979b432840ee0a1dcbd7c Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Fri, 27 Mar 2026 08:18:07 -0700 Subject: [PATCH 02/65] Add release notes system design document MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Describes the three-layer architecture (tool → agent → workflow), the source-manifest.json mechanism, branch lifecycle, and design rationale. Links from SKILL.md for discoverability. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/DESIGN.md | 213 +++++++++++++++++++++++++ .github/skills/release-notes/SKILL.md | 4 + 2 files changed, 217 insertions(+) create mode 100644 .github/skills/release-notes/DESIGN.md diff --git a/.github/skills/release-notes/DESIGN.md b/.github/skills/release-notes/DESIGN.md new file mode 100644 index 0000000000..4b1041a8a6 --- /dev/null +++ b/.github/skills/release-notes/DESIGN.md @@ -0,0 +1,213 @@ +# Release Notes System — Design + +How the automated release notes system for .NET works, why it's designed this way, and how the pieces connect. + +## Problem + +.NET ships ~25 component repositories assembled into a single product via the VMR (`dotnet/dotnet`). Release notes need to reflect exactly what ships — not what's planned, not what's in progress, not what was reverted. Historically, release notes are written manually by gathering information from component teams. This is slow, error-prone, and tends to miss features or include things that didn't actually ship. + +## Goals + +1. **High fidelity** — only document what ships. The VMR `source-manifest.json` is the source of truth for what code is included in a release. +2. **High value** — bias toward features users care about. Not every merged PR is worth a release note. +3. **Never document non-shipping features** — if it's not in the VMR diff, it didn't ship. +4. **Incremental** — drafts improve over time as previews mature and humans provide feedback. +5. **Human-in-the-loop** — humans edit the release notes branch and comment on the PR. The system adapts to their input rather than overwriting it. + +## Architecture + +The system has three layers, each with a distinct responsibility: + +```text +┌─────────────────────────────────────────────────┐ +│ Agentic Workflow (cron) │ +│ .github/workflows/release-notes.md │ +│ Orchestration: branch lifecycle, PR mgmt, │ +│ human interaction, scheduling │ +├─────────────────────────────────────────────────┤ +│ AI Agent (editorial) │ +│ Reads changes.json → writes markdown │ +│ Judgment: which PRs matter, how to describe │ +│ them, code samples, feature grouping │ +├─────────────────────────────────────────────────┤ +│ dotnet-release tool (deterministic) │ +│ `dotnet-release generate changes` │ +│ Data: source-manifest diff → GitHub API → │ +│ changes.json │ +└─────────────────────────────────────────────────┘ +``` + +### Why three layers? + +- **The tool** handles mechanical data collection. It's deterministic — same inputs always produce the same output. It can be tested, debugged, and improved independently. +- **The agent** handles editorial judgment. It decides which PRs are worth writing about and how to describe them. This is inherently fuzzy and benefits from AI. +- **The workflow** handles orchestration. It knows when to run, what branches to manage, how to interact with humans, and how to preserve their edits. + +## Layer 1 — The Tool + +[`dotnet-release`](https://github.com/richlander/dotnet-release) is a .NET global tool. The `generate changes` command produces `changes.json`: + +```bash +dotnet-release generate changes \ + --base \ + --head \ + --version "11.0.0-preview.3" \ + --date "2026-04-08" \ + --labels \ + --output release-notes/11.0/preview/preview3/changes.json +``` + +### What the tool does + +1. **Reads `src/source-manifest.json`** at both refs via local git — this is the VMR bill of materials listing every component repo with its exact commit SHA +2. **Diffs the manifests** to identify which components changed and their commit ranges +3. **Queries GitHub compare API** for each changed component to enumerate merged PRs in the commit range +4. **Maps components to product slugs** using a built-in taxonomy (e.g., `runtime` → `dotnet-runtime`) +5. **Cross-references CVEs** when `--cve-repo` is provided (loads `cve.json` from the `release-index` branch) +6. **Fetches PR labels** when `--labels` is provided (useful for agent categorization) +7. **Outputs `changes.json`** following the [changes schema](references/changes-schema.md) + +### What the tool does NOT do + +- Editorial judgment (which PRs are important) +- Markdown generation +- Branch management or PR creation + +### Source-manifest.json — the key mechanism + +The VMR's `src/source-manifest.json` is a JSON bill of materials: + +```json +{ + "repositories": [ + { + "path": "runtime", + "remoteUri": "https://github.com/dotnet/runtime", + "commitSha": "d3439749b652966f8283f2d01c972bc8c4dc3ec3" + } + ] +} +``` + +By comparing this file at two release points, the tool gets exact per-component commit ranges. This replaces any need to parse VMR sync commits, trace codeflow PRs, or calculate fork points. One JSON diff gives everything. + +Example (Preview 1 → Preview 2): the tool found 1,389 PRs across 20 changed repos. + +## Layer 2 — The Agent + +The AI agent reads `changes.json` and writes markdown release notes. Its guidance comes from reference documents in `.github/skills/release-notes/references/`: + +| Document | Purpose | +| -------- | ------- | +| [quality-bar.md](references/quality-bar.md) | North star — fidelity, value, WHY+HOW | +| [vmr-structure.md](references/vmr-structure.md) | VMR branches, tags, source-manifest.json | +| [changes-schema.md](references/changes-schema.md) | The changes.json schema | +| [component-mapping.md](references/component-mapping.md) | Components → product slugs → output files | +| [format-template.md](references/format-template.md) | Markdown document structure | +| [editorial-rules.md](references/editorial-rules.md) | Tone, attribution, naming | + +These are **goal-oriented**, not procedural. They describe what good release notes look like, not the exact steps to produce them. The agent figures out the HOW. + +### Agent responsibilities + +- **Triage** — read `changes.json` and identify which PRs are worth writing about +- **Write** — produce markdown release notes for high-value features, following the quality bar +- **Verify** — use `dotnet-inspect` against nightly builds when needed to confirm public API changes +- **Respect edits** — diff the PR branch to see what humans have changed and preserve their work +- **Respond** — read PR comments and incorporate human feedback + +## Layer 3 — The Agentic Workflow + +A [GitHub Agentic Workflow](https://github.github.com/gh-aw/) defined in `.github/workflows/release-notes.md`. It runs on a daily cron schedule and manages the full lifecycle: + +### Branch lifecycle + +The workflow detects preview milestones by checking the VMR (`dotnet/dotnet`) for: + +- Release branches: `release/{major}.0.1xx-preview{N}` +- Release tags: `v{major}.0.0-preview.{N}.{build}` +- Release-PR branches: `release-pr-*` (signals an imminent release) + +For each detected milestone: + +1. **No PR exists** → create branch, run tool, write initial drafts, open draft PR +2. **PR exists, source changed** → regenerate `changes.json`, add/update markdown sections +3. **PR exists, human edited** → preserve human edits, only touch untouched sections + +### Safe outputs + +The workflow uses these safe-outputs (the only ways it can modify state): + +- `create-pull-request` — create new release notes PRs +- `push-to-pull-request-branch` — update existing PR branches +- `add-comment` — comment on PRs with update summaries + +### Schedule + +- Previews ship monthly, February through October (typically patch Tuesday) +- RC1 in ~September, RC2 in ~October, GA in November +- The workflow runs daily but only does meaningful work when the VMR state has changed + +## Output files + +Each release milestone produces these files in `release-notes/{major.minor}/preview/{previewN}/`: + +| File | Source | Description | +| ---- | ------ | ----------- | +| `changes.json` | Tool | Every PR that shipped — comprehensive, machine-readable | +| `README.md` | Agent | Index/TOC linking to component files | +| `libraries.md` | Agent | System.\* BCL APIs (from `dotnet/runtime`) | +| `runtime.md` | Agent | CoreCLR, Mono, GC, JIT (from `dotnet/runtime`) | +| `aspnetcore.md` | Agent | ASP.NET Core, Blazor, SignalR | +| `sdk.md` | Agent | CLI, project system, templating | +| `efcore.md` | Agent | Entity Framework Core | +| `csharp.md` | Agent | C# language features | +| `fsharp.md` | Agent | F# language and compiler | +| `winforms.md` | Agent | Windows Forms | +| `wpf.md` | Agent | WPF | +| `msbuild.md` | Agent | MSBuild | +| `nuget.md` | Agent | NuGet client | + +Components with no noteworthy changes get a minimal stub file. + +## What's out of scope + +- **MAUI** (`dotnet/maui`) — not in the VMR, not in `source-manifest.json` +- **Container images** (`dotnet/dotnet-docker`) — not in the VMR +- **Servicing releases** — separate process, handled by existing automation +- **Security advisories** — the tool can cross-reference CVEs but the agent doesn't author security bulletins + +## Design decisions + +### Why source-manifest.json instead of VMR git log? + +The VMR git log contains codeflow sync commits, dependency updates, and merge commits that are noisy and hard to trace back to meaningful PRs. `source-manifest.json` gives clean per-component commit ranges that map directly to GitHub compare API queries. + +### Why a separate tool instead of agent-only? + +The data collection step (manifest diff → PR enumeration) is mechanical and deterministic. Making it a standalone tool means: + +- It can be tested independently +- Output is reproducible (same refs → same `changes.json`) +- The agent gets structured input instead of having to make dozens of API calls itself +- Humans can run it locally to inspect what shipped + +### Why goal-oriented reference docs instead of procedural scripts? + +The previous iteration had 16 reference documents encoding a rigid 9-step pipeline. It was brittle — any change to the process required updating multiple docs, and the agent followed the steps mechanically without understanding the goals. Goal-oriented docs let the agent adapt to unexpected situations (e.g., a component that reorganized its repo structure). + +### Why one PR per preview? + +Each preview is a coherent release milestone with its own set of features. Maintaining one long-lived branch per preview allows: + +- Humans to edit the branch directly +- Incremental improvement as the preview matures +- Clear history of how the notes evolved +- Easy review before the preview ships + +## Open questions + +1. **dotnet-inspect in Actions** — the agent needs `dotnet-inspect` to verify public API changes against nightly builds. Availability as a global tool in GitHub Actions runners needs confirmation. +2. **Cross-repo tokens** — the workflow runs in `dotnet/core` but reads from `dotnet/dotnet` and ~20 component repos. The GitHub token scope and any required app permissions need to be configured. +3. **Multiple previews in flight** — can the agent handle updating notes for Preview N while Preview N+1's branch has just appeared? The workflow should handle this gracefully. +4. **Package mapping** — the `package` field in `changes.json` (which NuGet package a PR affects) is hard to automate reliably. May need PR labels or heuristics based on changed file paths. diff --git a/.github/skills/release-notes/SKILL.md b/.github/skills/release-notes/SKILL.md index 30df5227d7..aee914a5da 100644 --- a/.github/skills/release-notes/SKILL.md +++ b/.github/skills/release-notes/SKILL.md @@ -14,6 +14,10 @@ Generate and maintain release notes for .NET preview, RC, and GA releases. 2. The agent reads `changes.json` and writes curated markdown release notes for high-value features 3. Output is one PR per release milestone in dotnet/core, maintained incrementally +## Design + +- [DESIGN.md](DESIGN.md) — architecture, rationale, and how all the pieces connect + ## Reference documents - [quality-bar.md](references/quality-bar.md) — what good release notes look like From b94d89f05da74ffa05a9b1a29da3acf2b84c2962 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 14:58:47 -0700 Subject: [PATCH 03/65] Update release notes skill docs to match actual dotnet-release CLI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tested dotnet-release v1.1.0 generate changes against the VMR (P1→P2: 1,389 changes across 21 repos). Updated docs to reflect the actual tool output: - Remove aspirational product/package fields from changes-schema.md (tool outputs repo, not product slugs) - Add labels field (optional, via --labels flag) - Fix workflow CLI syntax (positional path, --base/--head) - Update component-mapping.md: agent does repo→file routing - Remove resolved open question about package mapping - Update example JSON and jq queries to match real output Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/DESIGN.md | 10 ++--- .../references/changes-schema.md | 26 +++++------- .../references/component-mapping.md | 40 +++++++++---------- .github/workflows/release-notes.md | 15 ++++--- 4 files changed, 42 insertions(+), 49 deletions(-) diff --git a/.github/skills/release-notes/DESIGN.md b/.github/skills/release-notes/DESIGN.md index 4b1041a8a6..ca737314db 100644 --- a/.github/skills/release-notes/DESIGN.md +++ b/.github/skills/release-notes/DESIGN.md @@ -62,10 +62,9 @@ dotnet-release generate changes \ 1. **Reads `src/source-manifest.json`** at both refs via local git — this is the VMR bill of materials listing every component repo with its exact commit SHA 2. **Diffs the manifests** to identify which components changed and their commit ranges 3. **Queries GitHub compare API** for each changed component to enumerate merged PRs in the commit range -4. **Maps components to product slugs** using a built-in taxonomy (e.g., `runtime` → `dotnet-runtime`) +4. **Fetches PR labels** when `--labels` is provided (useful for agent categorization) 5. **Cross-references CVEs** when `--cve-repo` is provided (loads `cve.json` from the `release-index` branch) -6. **Fetches PR labels** when `--labels` is provided (useful for agent categorization) -7. **Outputs `changes.json`** following the [changes schema](references/changes-schema.md) +6. **Outputs `changes.json`** following the [changes schema](references/changes-schema.md) ### What the tool does NOT do @@ -91,7 +90,7 @@ The VMR's `src/source-manifest.json` is a JSON bill of materials: By comparing this file at two release points, the tool gets exact per-component commit ranges. This replaces any need to parse VMR sync commits, trace codeflow PRs, or calculate fork points. One JSON diff gives everything. -Example (Preview 1 → Preview 2): the tool found 1,389 PRs across 20 changed repos. +Example (Preview 1 → Preview 2): the tool found 1,389 PRs across 21 changed repos. ## Layer 2 — The Agent @@ -110,7 +109,7 @@ These are **goal-oriented**, not procedural. They describe what good release not ### Agent responsibilities -- **Triage** — read `changes.json` and identify which PRs are worth writing about +- **Triage** — read `changes.json` and identify which PRs are worth writing about. Use the [component mapping](references/component-mapping.md) to route changes from `repo` to the correct output files. - **Write** — produce markdown release notes for high-value features, following the quality bar - **Verify** — use `dotnet-inspect` against nightly builds when needed to confirm public API changes - **Respect edits** — diff the PR branch to see what humans have changed and preserve their work @@ -210,4 +209,3 @@ Each preview is a coherent release milestone with its own set of features. Maint 1. **dotnet-inspect in Actions** — the agent needs `dotnet-inspect` to verify public API changes against nightly builds. Availability as a global tool in GitHub Actions runners needs confirmation. 2. **Cross-repo tokens** — the workflow runs in `dotnet/core` but reads from `dotnet/dotnet` and ~20 component repos. The GitHub token scope and any required app permissions need to be configured. 3. **Multiple previews in flight** — can the agent handle updating notes for Preview N while Preview N+1's branch has just appeared? The workflow should handle this gracefully. -4. **Package mapping** — the `package` field in `changes.json` (which NuGet package a PR affects) is hard to automate reliably. May need PR labels or heuristics based on changed file paths. diff --git a/.github/skills/release-notes/references/changes-schema.md b/.github/skills/release-notes/references/changes-schema.md index 2a501472f4..a77c4e972c 100644 --- a/.github/skills/release-notes/references/changes-schema.md +++ b/.github/skills/release-notes/references/changes-schema.md @@ -32,14 +32,13 @@ release-notes/{major.minor}/{major.minor.patch}/changes.json # patches | ----- | ---- | ----------- | | `id` | int | GitHub PR number; `0` if no public PR | | `repo` | string | Short repository name (e.g., `"runtime"`) | -| `product` | string | Product slug from taxonomy (e.g., `"dotnet-runtime"`) | -| `package` | string | NuGet package name when applicable (e.g., `"System.Text.Json"`) | | `title` | string | PR title; `""` if not available | | `url` | string | Public GitHub PR URL; `""` if non-public | | `commit` | string | Key into top-level `commits{}` dict | | `is_security` | bool | `true` if this is a security change | +| `labels` | array | PR labels (only present when `--labels` is used) | -Both `product` and `package` may be present. At least one SHOULD be present. +The `repo` field corresponds to the VMR manifest path. Use the [component mapping](component-mapping.md) to map repos to product areas and output files. ## Commit entry fields (values in `commits{}`) @@ -57,20 +56,17 @@ Both `product` and `package` may be present. At least one SHOULD be present. - **Naming** — `snake_case_lower` for JSON fields, `kebab-case-lower` for file names and repo slugs. - **Public URLs only** — every URL must resolve publicly. - **Commit URLs use `.diff` form** — for machine consumption. -- **Product slugs** come from the `products.json` taxonomy file (e.g., `dotnet-runtime`, `dotnet-aspnetcore`, `dotnet-sdk`). ## Example ```json { - "release_version": "11.0.0-preview.3", - "release_date": "2026-04-08", + "release_version": "11.0.0-preview.2", + "release_date": "", "changes": [ { "id": 112345, "repo": "runtime", - "product": "dotnet-runtime", - "package": "System.Text.Json", "title": "Add JsonSerializerOptions.Web preset", "url": "https://github.com/dotnet/runtime/pull/112345", "commit": "runtime@b2d5fa8", @@ -79,8 +75,6 @@ Both `product` and `package` may be present. At least one SHOULD be present. { "id": 54321, "repo": "aspnetcore", - "product": "dotnet-aspnetcore", - "package": "", "title": "Add MapStaticAssets middleware", "url": "https://github.com/dotnet/aspnetcore/pull/54321", "commit": "aspnetcore@f45f3c9", @@ -90,14 +84,14 @@ Both `product` and `package` may be present. At least one SHOULD be present. "commits": { "runtime@b2d5fa8": { "repo": "runtime", - "branch": "main", + "branch": "", "hash": "b2d5fa8ca2f9c2d7e8f1f0a9d3d0f08e31c0ad5f", "org": "dotnet", "url": "https://github.com/dotnet/runtime/commit/b2d5fa8ca2f9c2d7e8f1f0a9d3d0f08e31c0ad5f.diff" }, "aspnetcore@f45f3c9": { "repo": "aspnetcore", - "branch": "main", + "branch": "", "hash": "f45f3c916df91e3fb175c85ba4a4f58ec1f77ef0", "org": "dotnet", "url": "https://github.com/dotnet/aspnetcore/commit/f45f3c916df91e3fb175c85ba4a4f58ec1f77ef0.diff" @@ -112,11 +106,11 @@ Both `product` and `package` may be present. At least one SHOULD be present. # All changes jq -r '.changes[] | .title' changes.json -# Changes by product -jq -r '.changes[] | select(.product == "dotnet-runtime") | .title' changes.json +# Changes by repo +jq -r '.changes[] | select(.repo == "runtime") | .title' changes.json -# Changes affecting a NuGet package -jq -r '.changes[] | select(.package == "System.Text.Json") | .title' changes.json +# Count changes per repo +jq -r '[.changes[] | .repo] | group_by(.) | map({repo: .[0], count: length}) | sort_by(-.count)[]' changes.json # Security changes jq -r '.changes[] | select(.is_security) | .title' changes.json diff --git a/.github/skills/release-notes/references/component-mapping.md b/.github/skills/release-notes/references/component-mapping.md index 5c72bf93db..eb0e0f5bdd 100644 --- a/.github/skills/release-notes/references/component-mapping.md +++ b/.github/skills/release-notes/references/component-mapping.md @@ -1,26 +1,24 @@ # Component Mapping -Maps VMR source paths to .NET components, their source repositories, product taxonomy slugs, and release notes output files. - -## Path-to-component mapping - -Uses `source-manifest.json` `path` values to identify components. The `dotnet-release generate changes` tool uses this mapping to populate the `product` field in `changes.json`. - -| Manifest Path | Component | Source Repo | Product Slug | Release Notes File | -| ------------- | --------- | ----------- | ------------ | ------------------ | -| `runtime` | .NET Libraries | `dotnet/runtime` | `dotnet-runtime` | `libraries.md` | -| `runtime` | .NET Runtime | `dotnet/runtime` | `dotnet-runtime` | `runtime.md` | -| `aspnetcore` | ASP.NET Core | `dotnet/aspnetcore` | `dotnet-aspnetcore` | `aspnetcore.md` | -| `razor` | ASP.NET Core (Razor) | `dotnet/razor` | `dotnet-aspnetcore` | `aspnetcore.md` | -| `sdk` | .NET SDK | `dotnet/sdk` | `dotnet-sdk` | `sdk.md` | -| `templating` | .NET SDK (Templating) | `dotnet/templating` | `dotnet-sdk` | `sdk.md` | -| `msbuild` | MSBuild | `dotnet/msbuild` | `dotnet-msbuild` | `msbuild.md` | -| `winforms` | Windows Forms | `dotnet/winforms` | `dotnet-winforms` | `winforms.md` | -| `wpf` | WPF | `dotnet/wpf` | `dotnet-wpf` | `wpf.md` | -| `efcore` | EF Core | `dotnet/efcore` | `dotnet-efcore` | `efcore.md` | -| `roslyn` | C# / Visual Basic | `dotnet/roslyn` | `dotnet-roslyn` | `csharp.md` | -| `fsharp` | F# | `dotnet/fsharp` | `dotnet-fsharp` | `fsharp.md` | -| `nuget-client` | NuGet | `nuget/nuget.client` | `dotnet-nuget` | `nuget.md` | +## Repo-to-component mapping + +Uses the `repo` field from `changes.json` (which matches `source-manifest.json` `path` values) to identify components. + +| Manifest Path | Component | Source Repo | Release Notes File | +| ------------- | --------- | ----------- | ------------------ | +| `runtime` | .NET Libraries | `dotnet/runtime` | `libraries.md` | +| `runtime` | .NET Runtime | `dotnet/runtime` | `runtime.md` | +| `aspnetcore` | ASP.NET Core | `dotnet/aspnetcore` | `aspnetcore.md` | +| `razor` | ASP.NET Core (Razor) | `dotnet/razor` | `aspnetcore.md` | +| `sdk` | .NET SDK | `dotnet/sdk` | `sdk.md` | +| `templating` | .NET SDK (Templating) | `dotnet/templating` | `sdk.md` | +| `msbuild` | MSBuild | `dotnet/msbuild` | `msbuild.md` | +| `winforms` | Windows Forms | `dotnet/winforms` | `winforms.md` | +| `wpf` | WPF | `dotnet/wpf` | `wpf.md` | +| `efcore` | EF Core | `dotnet/efcore` | `efcore.md` | +| `roslyn` | C# / Visual Basic | `dotnet/roslyn` | `csharp.md` | +| `fsharp` | F# | `dotnet/fsharp` | `fsharp.md` | +| `nuget-client` | NuGet | `nuget/nuget.client` | `nuget.md` | ### Runtime sub-component classification diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index 373377b861..0998e53a92 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -84,13 +84,16 @@ Determine which milestone(s) need release notes by comparing against open PRs in ### 2. Generate changes.json -For each milestone that needs work, run the `dotnet-release generate changes` tool: +For each milestone that needs work, clone the VMR and run `dotnet-release generate changes`: ```bash -dotnet-release generate changes \ - --vmr-repo dotnet/dotnet \ - --base-ref \ - --target-ref \ +git clone --filter=blob:none https://github.com/dotnet/dotnet /tmp/dotnet + +dotnet-release generate changes /tmp/dotnet \ + --base \ + --head \ + --version "" \ + --labels \ --output release-notes/{major}.0/preview/{previewN}/changes.json ``` @@ -100,7 +103,7 @@ Find the previous release tag by reading `release-notes/{major}.0/preview/{previ Using `changes.json` as your input: -- Group changes by component (the `product` field maps to output files via component-mapping.md) +- Group changes by repo (the `repo` field maps to output files via component-mapping.md) - For each component with changes, identify which PRs are worth writing about - Write feature descriptions following format-template.md and editorial-rules.md - For components with no noteworthy changes, produce a minimal stub From c8ba518727ad8c84a4f664148d05ae1dfec64256 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 15:05:59 -0700 Subject: [PATCH 04/65] Scope to dotnet org repos; skip microsoft/vstest for now vstest is the only microsoft org repo in the VMR manifest and is already on the infra skip list. SAML token requirements for the microsoft org can be addressed later. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/DESIGN.md | 1 + .github/skills/release-notes/references/component-mapping.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/skills/release-notes/DESIGN.md b/.github/skills/release-notes/DESIGN.md index ca737314db..c229d99e79 100644 --- a/.github/skills/release-notes/DESIGN.md +++ b/.github/skills/release-notes/DESIGN.md @@ -173,6 +173,7 @@ Components with no noteworthy changes get a minimal stub file. - **MAUI** (`dotnet/maui`) — not in the VMR, not in `source-manifest.json` - **Container images** (`dotnet/dotnet-docker`) — not in the VMR +- **Non-dotnet-org repos** — repos under other GitHub orgs (e.g., `microsoft/vstest`) are skipped for now due to SAML token scope. Only `dotnet` org repos are queried. This can be expanded later. - **Servicing releases** — separate process, handled by existing automation - **Security advisories** — the tool can cross-reference CVEs but the agent doesn't author security bulletins diff --git a/.github/skills/release-notes/references/component-mapping.md b/.github/skills/release-notes/references/component-mapping.md index eb0e0f5bdd..70204ae09b 100644 --- a/.github/skills/release-notes/references/component-mapping.md +++ b/.github/skills/release-notes/references/component-mapping.md @@ -54,7 +54,7 @@ These appear in `source-manifest.json` but rarely produce user-facing changes: | `sourcelink` | `dotnet/sourcelink` | Source Link | | `symreader` | `dotnet/symreader` | Symbol reader | | `windowsdesktop` | `dotnet/windowsdesktop` | Metapackage | -| `vstest` | `microsoft/vstest` | Test platform | +| `vstest` | `microsoft/vstest` | Test platform (microsoft org — skipped) | | `xdt` | `dotnet/xdt` | XML transforms | These components appear in `changes.json` for completeness but typically don't warrant markdown release notes. From 3439740bbad22bdc7a3047cb97a1e359c9550e7d Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 15:11:34 -0700 Subject: [PATCH 05/65] Fix schema docs: product field exists, add labels analysis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tested --labels flag: 776/1389 changes got labels (56% coverage). Best coverage: runtime (94%), roslyn (96%), winforms/wpf (100%). Lower coverage on infra-heavy repos as expected. Corrected the schema docs — the product field IS output by the tool (repo-level mapping), just empty for infra repos. Added product back to the schema with accurate description. Added labels field documentation and updated examples/queries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/DESIGN.md | 7 ++++--- .github/skills/release-notes/references/changes-schema.md | 8 +++++++- .github/workflows/release-notes.md | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/skills/release-notes/DESIGN.md b/.github/skills/release-notes/DESIGN.md index c229d99e79..0ef297ad5d 100644 --- a/.github/skills/release-notes/DESIGN.md +++ b/.github/skills/release-notes/DESIGN.md @@ -62,9 +62,10 @@ dotnet-release generate changes \ 1. **Reads `src/source-manifest.json`** at both refs via local git — this is the VMR bill of materials listing every component repo with its exact commit SHA 2. **Diffs the manifests** to identify which components changed and their commit ranges 3. **Queries GitHub compare API** for each changed component to enumerate merged PRs in the commit range -4. **Fetches PR labels** when `--labels` is provided (useful for agent categorization) -5. **Cross-references CVEs** when `--cve-repo` is provided (loads `cve.json` from the `release-index` branch) -6. **Outputs `changes.json`** following the [changes schema](references/changes-schema.md) +4. **Maps repos to product slugs** using a built-in taxonomy (e.g., `runtime` → `dotnet-runtime`; infra repos get no product) +5. **Fetches PR labels** when `--labels` is provided (useful for agent categorization) +6. **Cross-references CVEs** when `--cve-repo` is provided (loads `cve.json` from the `release-index` branch) +7. **Outputs `changes.json`** following the [changes schema](references/changes-schema.md) ### What the tool does NOT do diff --git a/.github/skills/release-notes/references/changes-schema.md b/.github/skills/release-notes/references/changes-schema.md index a77c4e972c..f8a3a78dad 100644 --- a/.github/skills/release-notes/references/changes-schema.md +++ b/.github/skills/release-notes/references/changes-schema.md @@ -32,13 +32,14 @@ release-notes/{major.minor}/{major.minor.patch}/changes.json # patches | ----- | ---- | ----------- | | `id` | int | GitHub PR number; `0` if no public PR | | `repo` | string | Short repository name (e.g., `"runtime"`) | +| `product` | string | Product slug (e.g., `"dotnet-runtime"`); `""` for infra repos | | `title` | string | PR title; `""` if not available | | `url` | string | Public GitHub PR URL; `""` if non-public | | `commit` | string | Key into top-level `commits{}` dict | | `is_security` | bool | `true` if this is a security change | | `labels` | array | PR labels (only present when `--labels` is used) | -The `repo` field corresponds to the VMR manifest path. Use the [component mapping](component-mapping.md) to map repos to product areas and output files. +The `product` field is derived from the repo-level [component mapping](component-mapping.md). Infra repos like `arcade` and `symreader` have an empty product. The `repo` field always matches the VMR manifest path. ## Commit entry fields (values in `commits{}`) @@ -67,6 +68,7 @@ The `repo` field corresponds to the VMR manifest path. Use the [component mappin { "id": 112345, "repo": "runtime", + "product": "dotnet-runtime", "title": "Add JsonSerializerOptions.Web preset", "url": "https://github.com/dotnet/runtime/pull/112345", "commit": "runtime@b2d5fa8", @@ -75,6 +77,7 @@ The `repo` field corresponds to the VMR manifest path. Use the [component mappin { "id": 54321, "repo": "aspnetcore", + "product": "dotnet-aspnetcore", "title": "Add MapStaticAssets middleware", "url": "https://github.com/dotnet/aspnetcore/pull/54321", "commit": "aspnetcore@f45f3c9", @@ -106,6 +109,9 @@ The `repo` field corresponds to the VMR manifest path. Use the [component mappin # All changes jq -r '.changes[] | .title' changes.json +# Changes by product +jq -r '.changes[] | select(.product == "dotnet-runtime") | .title' changes.json + # Changes by repo jq -r '.changes[] | select(.repo == "runtime") | .title' changes.json diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index 0998e53a92..bff45d2c94 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -103,7 +103,7 @@ Find the previous release tag by reading `release-notes/{major}.0/preview/{previ Using `changes.json` as your input: -- Group changes by repo (the `repo` field maps to output files via component-mapping.md) +- Group changes by product (the `product` field maps to output files via component-mapping.md) - For each component with changes, identify which PRs are worth writing about - Write feature descriptions following format-template.md and editorial-rules.md - For components with no noteworthy changes, produce a minimal stub From 9af3d860e29f358561c57c2b1ed54d9e5a7c4caa Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 15:34:12 -0700 Subject: [PATCH 06/65] Make milestone detection deterministic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace heuristic branch-scanning with a deterministic algorithm using three data sources: 1. releases.json → what has shipped 2. VMR eng/Versions.props on main → current development iteration 3. VMR tags → base refs for generating diffs Decision: if Versions.props iteration > latest shipped → that's the target. Exact commands provided in workflow steps 1-2. Updated DESIGN.md (milestone detection section), vmr-structure.md (version metadata + base tag discovery), and the workflow file (deterministic steps 1 and 2 with concrete examples). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/DESIGN.md | 17 ++-- .../release-notes/references/vmr-structure.md | 38 ++++++--- .github/workflows/release-notes.md | 77 +++++++++++++++---- 3 files changed, 103 insertions(+), 29 deletions(-) diff --git a/.github/skills/release-notes/DESIGN.md b/.github/skills/release-notes/DESIGN.md index 0ef297ad5d..95d0f1b65d 100644 --- a/.github/skills/release-notes/DESIGN.md +++ b/.github/skills/release-notes/DESIGN.md @@ -120,19 +120,24 @@ These are **goal-oriented**, not procedural. They describe what good release not A [GitHub Agentic Workflow](https://github.github.com/gh-aw/) defined in `.github/workflows/release-notes.md`. It runs on a daily cron schedule and manages the full lifecycle: -### Branch lifecycle +### Milestone detection -The workflow detects preview milestones by checking the VMR (`dotnet/dotnet`) for: +The workflow determines which milestone needs release notes using three data sources: -- Release branches: `release/{major}.0.1xx-preview{N}` -- Release tags: `v{major}.0.0-preview.{N}.{build}` -- Release-PR branches: `release-pr-*` (signals an imminent release) +1. **`releases.json`** (this repo) — the list of shipped releases. The latest entry tells you the most recently shipped preview and provides the baseline. +2. **`eng/Versions.props`** (VMR `main` branch) — `PreReleaseVersionIteration` tells you which milestone `main` is currently building. +3. **VMR tags** — `v{major}.0.0-preview.{N}.{build}` tags identify the exact commit that shipped each preview, providing the `--base` ref for generating diffs. -For each detected milestone: +The decision is deterministic: if the iteration on `main` is ahead of the latest shipped release, that's the target milestone. If they match, there's nothing new to do. + +### PR lifecycle + +For the target milestone: 1. **No PR exists** → create branch, run tool, write initial drafts, open draft PR 2. **PR exists, source changed** → regenerate `changes.json`, add/update markdown sections 3. **PR exists, human edited** → preserve human edits, only touch untouched sections +4. **Milestone just shipped** (new tag appeared) → final regeneration with `--head ` for exact shipped content ### Safe outputs diff --git a/.github/skills/release-notes/references/vmr-structure.md b/.github/skills/release-notes/references/vmr-structure.md index 3fbd258168..fa16a48375 100644 --- a/.github/skills/release-notes/references/vmr-structure.md +++ b/.github/skills/release-notes/references/vmr-structure.md @@ -79,23 +79,41 @@ Code flows between component repos and the VMR via automated PRs: Codeflow PRs come from `dotnet-maestro[bot]` with titles like `[{branch}] Source code updates from dotnet/{repo}`. The `github-merge-flow.jsonc` files in some components define the merge chain (e.g., `release/10.0.1xx → release/10.0.2xx → release/10.0.3xx → main`). -## Finding the previous release tag +## Finding the base tag -To determine what's new in a preview, you need the previous release's VMR tag. Find it by reading the previous preview's `release.json` in this repo: +To determine what's new in a preview, you need the previous release's VMR tag as the `--base` ref. Two reliable methods: -```text -release-notes/{major}.0/preview/{previous-preview}/release.json -→ .release.runtime.version (e.g., "11.0.0-preview.2.26159.112") +### Method 1: From VMR tags (preferred) + +List tags and find the latest one for the shipped iteration: + +```bash +git tag -l 'v11.0.0-preview.*' --sort=-v:refname | head -5 +# → v11.0.0-preview.2.26159.112 ← base tag for preview 3 +# → v11.0.0-preview.1.26104.118 ``` -Map that version to a VMR tag: `v11.0.0-preview.2.26159.112`. +### Method 2: From releases.json -## Version metadata +Read the runtime version from `releases.json` and map to a tag: + +```bash +jq -r '.releases[0].runtime.version' release-notes/11.0/releases.json +# → 11.0.0-preview.2.26159.112 → tag: v11.0.0-preview.2.26159.112 +``` + +## Version metadata — milestone detection + +The VMR's `eng/Versions.props` on `main` tracks the current development milestone: + +```xml +preview +3 +``` -The VMR's `eng/Versions.props` tracks the current version: +This tells you that `main` is currently building **preview.3**. Combined with `releases.json` (which tracks what has shipped) and VMR tags (which provide base refs), this gives a deterministic signal for which milestone needs release notes. -- `PreReleaseVersionLabel` — e.g., `preview` -- `PreReleaseVersionIteration` — e.g., `3` (for preview 3) +**Key insight**: when a preview ships, a tag is created. Then `main`'s `PreReleaseVersionIteration` is bumped to the next number. So comparing the iteration against the latest shipped release always tells you the current target. ## What's NOT in the VMR diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index bff45d2c94..7a35602e81 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -72,32 +72,83 @@ Read these files from `.github/skills/release-notes/references/` for detailed gu ## What to do each run -### 1. Detect active milestones +### 1. Determine the target milestone -Check the VMR (`dotnet/dotnet`) for the current development train. Look for: +Use three data sources to deterministically identify which milestone needs release notes. -- Release branches: `release/{major}.0.1xx-{label}` (e.g., `release/11.0.1xx-preview3`) -- Release tags: `v{major}.0.0-{label}.{build}` (e.g., `v11.0.0-preview.3.26168.106`) -- Release-PR branches: `release-pr-{major}.0.100-{label}.{build}` (signals an imminent release) +#### a. What has shipped (this repo) -Determine which milestone(s) need release notes by comparing against open PRs in this repo with the `[release-notes]` title prefix. +Read `releases.json` to get the latest shipped release: -### 2. Generate changes.json +```bash +jq -r '.releases[0] | "\(.["release-version"]) \(.["release-date"])"' release-notes/11.0/releases.json +# → "11.0.0-preview.2 2026-03-10" +``` + +Extract the shipped preview number: `preview.2` → iteration `2`. -For each milestone that needs work, clone the VMR and run `dotnet-release generate changes`: +#### b. What's being developed (VMR) + +Clone the VMR (or reuse a cached clone) and read `eng/Versions.props` on `main`: ```bash git clone --filter=blob:none https://github.com/dotnet/dotnet /tmp/dotnet +git -C /tmp/dotnet show main:eng/Versions.props | grep -E 'PreReleaseVersionLabel|PreReleaseVersionIteration' +# → preview +# → 3 +``` + +This tells you the milestone that `main` is currently building: **preview.3**. + +#### c. Find the base tag (VMR tags) + +List VMR tags to find the tag for the latest shipped release: + +```bash +git -C /tmp/dotnet tag -l 'v11.0.0-preview.*' --sort=-v:refname +# → v11.0.0-preview.2.26159.112 +# → v11.0.0-preview.1.26104.118 +``` + +The first tag matching the shipped iteration (preview.2) is the `--base` ref. +#### d. Decision logic + +```text +current_iteration = PreReleaseVersionIteration from Versions.props +latest_shipped = latest preview number from releases.json + +If current_iteration > latest_shipped: + → Target: preview.{current_iteration} + → Base ref: VMR tag for latest shipped (e.g., v11.0.0-preview.2.26159.112) + → Head ref: main + → Action: create or update release notes + +If current_iteration == latest_shipped: + → The current milestone just shipped and main hasn't been bumped yet. + → No new milestone to target. Exit. +``` + +When the target milestone has just shipped (a new tag appeared since the last run), do a final regeneration using `--head ` instead of `--head main` to capture the exact shipped content. + +### 2. Generate changes.json + +Using the base tag and head ref determined in step 1: + +```bash dotnet-release generate changes /tmp/dotnet \ - --base \ - --head \ - --version "" \ + --base v11.0.0-preview.2.26159.112 \ + --head main \ + --version "11.0.0-preview.3" \ --labels \ - --output release-notes/{major}.0/preview/{previewN}/changes.json + --output release-notes/11.0/preview/preview3/changes.json ``` -Find the previous release tag by reading `release-notes/{major}.0/preview/{previous}/release.json` → `.release.runtime.version` and mapping to a VMR tag (`v{version}`). +Create the output directory if it doesn't exist: + +```bash +mkdir -p release-notes/11.0/preview/preview3 +``` ### 3. Write or update markdown release notes From 1c255c5356f13df5492c473df95081c30fd77fdc Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 15:46:37 -0700 Subject: [PATCH 07/65] Add head-ref selection logic: check for target tag before using main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Once main is bumped to the next iteration, it contains preview N+1 work. The agent must check if a VMR tag exists for the target milestone: - No tag → in development, use --head main - Tag exists → finalized, use --head Updated workflow step 1e and step 2 with both code paths. Added Head ref selection section to DESIGN.md explaining why this matters. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/DESIGN.md | 9 +++++++++ .github/workflows/release-notes.md | 25 +++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/.github/skills/release-notes/DESIGN.md b/.github/skills/release-notes/DESIGN.md index 95d0f1b65d..b2d73303e4 100644 --- a/.github/skills/release-notes/DESIGN.md +++ b/.github/skills/release-notes/DESIGN.md @@ -130,6 +130,15 @@ The workflow determines which milestone needs release notes using three data sou The decision is deterministic: if the iteration on `main` is ahead of the latest shipped release, that's the target milestone. If they match, there's nothing new to do. +### Head ref selection + +Once the target milestone is identified, check whether it has a VMR tag: + +- **No tag** → milestone is in development. Use `--head main` for draft release notes. +- **Tag exists** → milestone has been finalized. Use `--head ` for exact shipped content. + +This is critical: once `main` is bumped to the next iteration, it contains work for the *next* preview. Using `main` at that point would produce incorrect release notes for the previous milestone. + ### PR lifecycle For the target milestone: diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index 7a35602e81..fa17d5bc92 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -121,7 +121,6 @@ latest_shipped = latest preview number from releases.json If current_iteration > latest_shipped: → Target: preview.{current_iteration} → Base ref: VMR tag for latest shipped (e.g., v11.0.0-preview.2.26159.112) - → Head ref: main → Action: create or update release notes If current_iteration == latest_shipped: @@ -129,7 +128,18 @@ If current_iteration == latest_shipped: → No new milestone to target. Exit. ``` -When the target milestone has just shipped (a new tag appeared since the last run), do a final regeneration using `--head ` instead of `--head main` to capture the exact shipped content. +#### e. Determine the head ref + +Check whether the target milestone has been tagged (finalized) in the VMR: + +```bash +git -C /tmp/dotnet tag -l "v11.0.0-preview.${target_iteration}.*" --sort=-v:refname | head -1 +``` + +- **No tag found** → the milestone is still in development. Use `--head main`. +- **Tag found** → the milestone has been finalized. Use `--head ` to capture the exact shipped content. + +This matters because once the target milestone ships and `main` is bumped to the next iteration, `main` contains preview N+1 work. Always check for a tag before using `main` as the head ref. ### 2. Generate changes.json @@ -144,6 +154,17 @@ dotnet-release generate changes /tmp/dotnet \ --output release-notes/11.0/preview/preview3/changes.json ``` +If the target milestone has a tag, use the tag instead of `main`: + +```bash +dotnet-release generate changes /tmp/dotnet \ + --base v11.0.0-preview.2.26159.112 \ + --head v11.0.0-preview.3.26210.100 \ + --version "11.0.0-preview.3" \ + --labels \ + --output release-notes/11.0/preview/preview3/changes.json +``` + Create the output directory if it doesn't exist: ```bash From 73d556cd457b08d6a58148af149973b2394f3def Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 16:16:35 -0700 Subject: [PATCH 08/65] Move general docs link to README only; add component-specific links The general 'What'\''s new in .NET' link belongs in README.md, not in every component file. Component files should link to their specific docs page when one exists (runtime, libraries, SDK, ASP.NET Core, C#, EF Core). Added the dotnet/docs overview source URL so the agent can discover component-specific links. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../references/format-template.md | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/.github/skills/release-notes/references/format-template.md b/.github/skills/release-notes/references/format-template.md index ce33e2dbd5..2ea2de9ba4 100644 --- a/.github/skills/release-notes/references/format-template.md +++ b/.github/skills/release-notes/references/format-template.md @@ -4,6 +4,8 @@ Standard document structure for .NET release notes markdown files. ## Document structure +### Component file + ```markdown # in .NET - Release Notes @@ -12,10 +14,6 @@ Standard document structure for .NET release notes markdown files. - [Feature Name](#anchor) - [Feature Name](#anchor) - updates in .NET : - -- [What's new in .NET ](https://learn.microsoft.com/dotnet/core/whats-new/dotnet-/overview) documentation - ## Feature Name ([/#NNNNN](https://github.com///pull/NNNNN)). @@ -29,6 +27,40 @@ Standard document structure for .NET release notes markdown files. - [@username](https://github.com/username) ``` +### README.md (index file) + +The README.md links to all component files and includes the general docs link. Component files do NOT include the general "What's new" link — that goes in the README only. + +```markdown +# .NET - Release Notes + +- [Libraries](libraries.md) +- [Runtime](runtime.md) +- [ASP.NET Core](aspnetcore.md) +... + +.NET updates: + +- [What's new in .NET ](https://learn.microsoft.com/dotnet/core/whats-new/dotnet-/overview) +``` + +### Component-specific docs links + +Some components have their own "What's new" page on learn.microsoft.com. Include these in the relevant component file when they exist. Discover them from the docs overview source: + +`https://github.com/dotnet/docs/raw/refs/heads/main/docs/core/whats-new/dotnet-{major}/overview.md` + +Known component docs links: + +| Component | Docs URL | +| --------- | -------- | +| Runtime | `https://learn.microsoft.com/dotnet/core/whats-new/dotnet-{major}/runtime` | +| Libraries | `https://learn.microsoft.com/dotnet/core/whats-new/dotnet-{major}/libraries` | +| SDK | `https://learn.microsoft.com/dotnet/core/whats-new/dotnet-{major}/sdk` | +| ASP.NET Core | `https://learn.microsoft.com/aspnet/core/release-notes/aspnetcore-{major}` | +| C# | `https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-{lang-version}` | +| EF Core | `https://learn.microsoft.com/ef/core/what-is-new/ef-core-{major}.0/whatsnew` | + ## Section rules 1. **TOC at top** — every feature gets a linked entry From 83f06d66c4a2b8e365b3e973462f450d4a552fca Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 18:01:57 -0700 Subject: [PATCH 09/65] Add dotnet_commit field to schema (dotnet-release v1.2.0) v1.2.0 now maps every source-repo change to its VMR codeflow commit. The new dotnet_commit field on each change entry keys into the commits dict for the dotnet/dotnet commit. Multiple source PRs typically share the same VMR commit (codeflow batches). Updated schema docs, example JSON, and field descriptions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../release-notes/references/changes-schema.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/skills/release-notes/references/changes-schema.md b/.github/skills/release-notes/references/changes-schema.md index f8a3a78dad..00bd544815 100644 --- a/.github/skills/release-notes/references/changes-schema.md +++ b/.github/skills/release-notes/references/changes-schema.md @@ -35,12 +35,15 @@ release-notes/{major.minor}/{major.minor.patch}/changes.json # patches | `product` | string | Product slug (e.g., `"dotnet-runtime"`); `""` for infra repos | | `title` | string | PR title; `""` if not available | | `url` | string | Public GitHub PR URL; `""` if non-public | -| `commit` | string | Key into top-level `commits{}` dict | +| `commit` | string | Key into top-level `commits{}` dict (source repo commit) | +| `dotnet_commit` | string | Key into top-level `commits{}` dict (VMR commit that brought this change in) | | `is_security` | bool | `true` if this is a security change | | `labels` | array | PR labels (only present when `--labels` is used) | The `product` field is derived from the repo-level [component mapping](component-mapping.md). Infra repos like `arcade` and `symreader` have an empty product. The `repo` field always matches the VMR manifest path. +The `dotnet_commit` field links to the VMR codeflow commit in `dotnet/dotnet` that synced this change. Multiple source PRs typically map to the same VMR commit (codeflow batches changes). + ## Commit entry fields (values in `commits{}`) | Field | Type | Description | @@ -72,6 +75,7 @@ The `product` field is derived from the repo-level [component mapping](component "title": "Add JsonSerializerOptions.Web preset", "url": "https://github.com/dotnet/runtime/pull/112345", "commit": "runtime@b2d5fa8", + "dotnet_commit": "dotnet@a1b2c3d", "is_security": false }, { @@ -81,6 +85,7 @@ The `product` field is derived from the repo-level [component mapping](component "title": "Add MapStaticAssets middleware", "url": "https://github.com/dotnet/aspnetcore/pull/54321", "commit": "aspnetcore@f45f3c9", + "dotnet_commit": "dotnet@a1b2c3d", "is_security": false } ], @@ -98,11 +103,20 @@ The `product` field is derived from the repo-level [component mapping](component "hash": "f45f3c916df91e3fb175c85ba4a4f58ec1f77ef0", "org": "dotnet", "url": "https://github.com/dotnet/aspnetcore/commit/f45f3c916df91e3fb175c85ba4a4f58ec1f77ef0.diff" + }, + "dotnet@a1b2c3d": { + "repo": "dotnet", + "branch": "", + "hash": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0", + "org": "dotnet", + "url": "https://github.com/dotnet/dotnet/commit/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0.diff" } } } ``` +Note that both changes share the same `dotnet_commit` — they were synced to the VMR in a single codeflow batch. + ## Querying changes.json ```bash From 9e0ed309cbc4627444db80c1310e1440cb4a2817 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 20:24:10 -0700 Subject: [PATCH 10/65] Update schema docs for dotnet-release v2.0.0 - commit field is now VMR commit (was source repo) - dotnet_commit removed (replaced by commit) - local_repo_commit added for source repo commit - product field absent for infra repos (not empty string) - Updated example and jq queries Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../references/changes-schema.md | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/skills/release-notes/references/changes-schema.md b/.github/skills/release-notes/references/changes-schema.md index 00bd544815..931b62245c 100644 --- a/.github/skills/release-notes/references/changes-schema.md +++ b/.github/skills/release-notes/references/changes-schema.md @@ -32,17 +32,17 @@ release-notes/{major.minor}/{major.minor.patch}/changes.json # patches | ----- | ---- | ----------- | | `id` | int | GitHub PR number; `0` if no public PR | | `repo` | string | Short repository name (e.g., `"runtime"`) | -| `product` | string | Product slug (e.g., `"dotnet-runtime"`); `""` for infra repos | +| `product` | string | Product slug (e.g., `"dotnet-runtime"`); absent for infra repos | | `title` | string | PR title; `""` if not available | | `url` | string | Public GitHub PR URL; `""` if non-public | -| `commit` | string | Key into top-level `commits{}` dict (source repo commit) | -| `dotnet_commit` | string | Key into top-level `commits{}` dict (VMR commit that brought this change in) | +| `commit` | string | Key into top-level `commits{}` dict — the VMR (`dotnet/dotnet`) codeflow commit | | `is_security` | bool | `true` if this is a security change | +| `local_repo_commit` | string | Key into `commits{}` — the source repo commit (e.g., `"runtime@c5d5be4"`) | | `labels` | array | PR labels (only present when `--labels` is used) | -The `product` field is derived from the repo-level [component mapping](component-mapping.md). Infra repos like `arcade` and `symreader` have an empty product. The `repo` field always matches the VMR manifest path. +The `product` field is derived from the repo-level [component mapping](component-mapping.md). Infra repos like `arcade` and `symreader` have no `product` field. The `repo` field always matches the VMR manifest path. -The `dotnet_commit` field links to the VMR codeflow commit in `dotnet/dotnet` that synced this change. Multiple source PRs typically map to the same VMR commit (codeflow batches changes). +The `commit` field is the VMR codeflow commit in `dotnet/dotnet` that synced this change. Multiple source PRs typically map to the same VMR commit (codeflow batches changes). The `url` field links to the source-repo PR — together these provide both the product view (commit) and the development view (url). ## Commit entry fields (values in `commits{}`) @@ -56,7 +56,7 @@ The `dotnet_commit` field links to the VMR codeflow commit in `dotnet/dotnet` th ## Conventions -- **No nulls** — every field is always present. Use `""` for missing strings, `0` for missing integers. +- **No nulls** — required fields are always present. Optional fields (`product`, `labels`, `local_repo_commit`) may be absent. Use `""` for missing strings, `0` for missing integers. - **Naming** — `snake_case_lower` for JSON fields, `kebab-case-lower` for file names and repo slugs. - **Public URLs only** — every URL must resolve publicly. - **Commit URLs use `.diff` form** — for machine consumption. @@ -65,7 +65,7 @@ The `dotnet_commit` field links to the VMR codeflow commit in `dotnet/dotnet` th ```json { - "release_version": "11.0.0-preview.2", + "release_version": "11.0.0-preview.3", "release_date": "", "changes": [ { @@ -74,9 +74,9 @@ The `dotnet_commit` field links to the VMR codeflow commit in `dotnet/dotnet` th "product": "dotnet-runtime", "title": "Add JsonSerializerOptions.Web preset", "url": "https://github.com/dotnet/runtime/pull/112345", - "commit": "runtime@b2d5fa8", - "dotnet_commit": "dotnet@a1b2c3d", - "is_security": false + "commit": "dotnet@a1b2c3d", + "is_security": false, + "local_repo_commit": "runtime@b2d5fa8" }, { "id": 54321, @@ -84,29 +84,29 @@ The `dotnet_commit` field links to the VMR codeflow commit in `dotnet/dotnet` th "product": "dotnet-aspnetcore", "title": "Add MapStaticAssets middleware", "url": "https://github.com/dotnet/aspnetcore/pull/54321", - "commit": "aspnetcore@f45f3c9", - "dotnet_commit": "dotnet@a1b2c3d", - "is_security": false + "commit": "dotnet@a1b2c3d", + "is_security": false, + "local_repo_commit": "aspnetcore@f45f3c9" } ], "commits": { "runtime@b2d5fa8": { "repo": "runtime", - "branch": "", + "branch": "main", "hash": "b2d5fa8ca2f9c2d7e8f1f0a9d3d0f08e31c0ad5f", "org": "dotnet", "url": "https://github.com/dotnet/runtime/commit/b2d5fa8ca2f9c2d7e8f1f0a9d3d0f08e31c0ad5f.diff" }, "aspnetcore@f45f3c9": { "repo": "aspnetcore", - "branch": "", + "branch": "main", "hash": "f45f3c916df91e3fb175c85ba4a4f58ec1f77ef0", "org": "dotnet", "url": "https://github.com/dotnet/aspnetcore/commit/f45f3c916df91e3fb175c85ba4a4f58ec1f77ef0.diff" }, "dotnet@a1b2c3d": { "repo": "dotnet", - "branch": "", + "branch": "main", "hash": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0", "org": "dotnet", "url": "https://github.com/dotnet/dotnet/commit/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0.diff" @@ -115,7 +115,7 @@ The `dotnet_commit` field links to the VMR codeflow commit in `dotnet/dotnet` th } ``` -Note that both changes share the same `dotnet_commit` — they were synced to the VMR in a single codeflow batch. +Note that both changes share the same `commit` — they were synced to the VMR in a single codeflow batch. The `local_repo_commit` values differ since each originated from a different source repository. ## Querying changes.json @@ -136,7 +136,7 @@ jq -r '[.changes[] | .repo] | group_by(.) | map({repo: .[0], count: length}) | s jq -r '.changes[] | select(.is_security) | .title' changes.json # Cross-file join with cve.json (shared commit key format) -jq -r '.changes[] | select(.is_security) | .commit' changes.json +jq -r '.changes[] | select(.is_security) | .local_repo_commit' changes.json # → use these keys to look up CVE IDs in cve.json's cve_commits{} ``` From 1a10de4e4aecda5264d8a25cfa51dd13b473e268 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 20:38:51 -0700 Subject: [PATCH 11/65] Evolve to multi-milestone live system with human interaction - Multi-milestone: discover all active milestones (P4 and P5 simultaneously) - Re-validate base/head refs every nightly run (tags appear, branches create, main bumps) - Branch-per-milestone on dotnet/core (release-notes/11.0-preview4, etc.) - Human interaction: respect edits, respond to PR comments, ask when unsure - Handle transitions: main bumps, new tags, release branches, PR merges - Increased safe-output limits for multi-milestone support - Updated DESIGN.md with multi-milestone discovery, human interaction model Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/DESIGN.md | 105 ++++++++++---- .github/workflows/release-notes.md | 183 +++++++++++++------------ 2 files changed, 169 insertions(+), 119 deletions(-) diff --git a/.github/skills/release-notes/DESIGN.md b/.github/skills/release-notes/DESIGN.md index b2d73303e4..11b5c6dccf 100644 --- a/.github/skills/release-notes/DESIGN.md +++ b/.github/skills/release-notes/DESIGN.md @@ -118,49 +118,97 @@ These are **goal-oriented**, not procedural. They describe what good release not ## Layer 3 — The Agentic Workflow -A [GitHub Agentic Workflow](https://github.github.com/gh-aw/) defined in `.github/workflows/release-notes.md`. It runs on a daily cron schedule and manages the full lifecycle: +A [GitHub Agentic Workflow](https://github.github.com/gh-aw/) defined in `.github/workflows/release-notes.md`. It runs nightly and manages the full lifecycle. This is a **multi-master live system** — the agent, component teams, and release managers all edit concurrently. -### Milestone detection +### Multi-milestone discovery -The workflow determines which milestone needs release notes using three data sources: +Multiple milestones can be active simultaneously. For example: -1. **`releases.json`** (this repo) — the list of shipped releases. The latest entry tells you the most recently shipped preview and provides the baseline. -2. **`eng/Versions.props`** (VMR `main` branch) — `PreReleaseVersionIteration` tells you which milestone `main` is currently building. -3. **VMR tags** — `v{major}.0.0-preview.{N}.{build}` tags identify the exact commit that shipped each preview, providing the `--base` ref for generating diffs. +```text +Latest shipped: Preview 3 (in releases.json) +VMR main: Preview 5 (Versions.props iteration=5) +VMR release/P4: Preview 4 (release branch exists, no tag yet) -The decision is deterministic: if the iteration on `main` is ahead of the latest shipped release, that's the target milestone. If they match, there's nothing new to do. +→ Active milestones: Preview 4 (from release branch) AND Preview 5 (from main) +→ Each gets its own branch and PR on dotnet/core +``` -### Head ref selection +The workflow discovers all milestones between `latest_shipped + 1` and `main_iteration`, checks for VMR tags and release branches, and creates a branch+PR for each. -Once the target milestone is identified, check whether it has a VMR tag: +### Head ref selection (per milestone) -- **No tag** → milestone is in development. Use `--head main` for draft release notes. -- **Tag exists** → milestone has been finalized. Use `--head ` for exact shipped content. +Each milestone needs its own base and head ref. This is re-validated every run because refs can change: -This is critical: once `main` is bumped to the next iteration, it contains work for the *next* preview. Using `main` at that point would produce incorrect release notes for the previous milestone. +| Milestone state | Base ref | Head ref | +| --------------- | -------- | -------- | +| Has VMR tag (finalized) | Tag for N-1 | Tag for N | +| Has release branch (stabilizing) | Tag for N-1 | Release branch tip | +| Only on main (in development) | Tag for N-1 | main | -### PR lifecycle +**Critical**: never use `main` for milestone N if `main` has moved to N+1. Check the iteration in `Versions.props` every run. -For the target milestone: +### Branch lifecycle on dotnet/core -1. **No PR exists** → create branch, run tool, write initial drafts, open draft PR -2. **PR exists, source changed** → regenerate `changes.json`, add/update markdown sections -3. **PR exists, human edited** → preserve human edits, only touch untouched sections -4. **Milestone just shipped** (new tag appeared) → final regeneration with `--head ` for exact shipped content +```text +Branch: release-notes/11.0-preview4 +PR: [release-notes] .NET 11 Preview 4 -### Safe outputs +Branch: release-notes/11.0-preview5 +PR: [release-notes] .NET 11 Preview 5 +``` + +Each branch is long-lived — it's created on the first run and updated nightly until the PR is merged (after the preview ships). + +### Human interaction model + +This is the most delicate part of the system. The branches are shared workspaces: -The workflow uses these safe-outputs (the only ways it can modify state): +**Respecting edits:** +- Before writing, diff the branch to identify human commits +- Files humans have edited are partially off-limits — only add new sections, never overwrite their changes +- When a file has mixed agent + human content, be surgical — touch only agent-authored sections -- `create-pull-request` — create new release notes PRs -- `push-to-pull-request-branch` — update existing PR branches -- `add-comment` — comment on PRs with update summaries +**Responding to comments:** +- Read all PR comments and review threads since the last run +- Classify: actionable feedback, questions, disagreements, resolved +- For actionable items: make the change and confirm +- For questions: answer or escalate +- For disagreements: cross-check `changes.json` and explain findings +- When intent is unclear: ask for clarification via comment +- This is a conversation. Engage, don't ignore. + +**Handling conflicts:** +- If a human and the agent both changed the same section, the human's version wins +- If the agent is unsure whether a human edit was intentional, ask via PR comment +- Never force-push or rewrite human commits + +### PR lifecycle + +| State | Action | +| ----- | ------ | +| No PR for milestone | Create branch, generate content, open draft PR | +| PR exists, source changed | Regenerate `changes.json`, update/add markdown sections | +| PR exists, human edited | Preserve edits, only update untouched sections | +| New tag appeared | Final regen with `--head `, note finalization | +| Main bumped | Switch earlier milestone's head ref to release branch/tag | +| PR merged | Skip on future runs | +| PR closed | Don't reopen, log and move on | + +### Schedule and transitions + +- Runs nightly (~9am Pacific) +- Previews ship monthly, Feb–Oct (typically patch Tuesday) +- RC1 ~September, RC2 ~October, GA November +- Only does meaningful work when VMR state has changed +- Each run re-validates all refs (tags appear, branches are created, main bumps) + +### Safe outputs -### Schedule +The only ways the workflow can modify state: -- Previews ship monthly, February through October (typically patch Tuesday) -- RC1 in ~September, RC2 in ~October, GA in November -- The workflow runs daily but only does meaningful work when the VMR state has changed +- `create-pull-request` — create new release notes PRs (max 5, for multiple milestones) +- `push-to-pull-request-branch` — update existing PR branches (max 5) +- `add-comment` — comment on PRs with updates, replies to feedback, questions (max 20) ## Output files @@ -224,4 +272,5 @@ Each preview is a coherent release milestone with its own set of features. Maint 1. **dotnet-inspect in Actions** — the agent needs `dotnet-inspect` to verify public API changes against nightly builds. Availability as a global tool in GitHub Actions runners needs confirmation. 2. **Cross-repo tokens** — the workflow runs in `dotnet/core` but reads from `dotnet/dotnet` and ~20 component repos. The GitHub token scope and any required app permissions need to be configured. -3. **Multiple previews in flight** — can the agent handle updating notes for Preview N while Preview N+1's branch has just appeared? The workflow should handle this gracefully. +3. **Conflict resolution heuristics** — when the agent and a human both changed the same section between runs, the human wins. But how granular is "a section"? Need to define this precisely (per-heading? per-file?). +4. **RC/GA milestone naming** — the multi-milestone logic assumes `previewN` naming. RC and GA milestones use different naming (`rc1`, `rc2`, `ga`). The workflow needs to handle the transition gracefully. diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index fa17d5bc92..d6012972ff 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -3,13 +3,10 @@ on: schedule: daily around 9:00 utc-8 workflow_dispatch: inputs: - preview: - description: "Target preview (e.g., preview3). Leave empty for auto-detection." + milestone: + description: "Target milestone (e.g., preview4). Leave empty for auto-detection." required: false type: string - skip-if-match: - query: "is:pr is:open label:release-notes in:title \"[release-notes]\" author:app/github-actions" - max: 5 permissions: contents: read pull-requests: read @@ -26,13 +23,13 @@ safe-outputs: title-prefix: "[release-notes] " labels: [release-notes, automated] draft: true - max: 3 + max: 5 push-to-pull-request-branch: title-prefix: "[release-notes] " labels: [release-notes, automated] - max: 3 + max: 5 add-comment: - max: 10 + max: 20 target: "*" tools: github: @@ -46,18 +43,21 @@ tools: # .NET Release Notes Maintenance -You maintain release notes for .NET preview, RC, and GA releases in this repository (dotnet/core). You run daily on a cron schedule. Your outputs are pull requests containing two things per release milestone: +You maintain release notes for .NET preview, RC, and GA releases in this repository (dotnet/core). You run nightly. This is a **multi-master live system** — humans edit branches and leave PR comments at any time. You must respect their changes and engage with their feedback. -1. **`changes.json`** — a comprehensive, machine-readable manifest of all PRs and commits that shipped, generated by the `dotnet-release generate changes` tool -2. **Markdown release notes** — curated, editorial content covering high-value features +Your outputs are pull requests — one per active milestone — each containing: + +1. **`changes.json`** — a comprehensive manifest of all PRs/commits that shipped, generated by `dotnet-release generate changes` +2. **Markdown release notes** — curated editorial content covering high-value features ## Your principles -- **High fidelity** — only document what actually ships. The VMR (`dotnet/dotnet`) is the source of truth. The `dotnet-release generate changes` tool uses `src/source-manifest.json` diffs between release refs to determine what changed. Trust its output. -- **High value** — bias toward features users will care about. Not every PR deserves a release note. Focus on new capabilities, significant improvements, and things users asked for. Skip infra, test-only, and internal refactoring PRs. -- **Never document non-shipping features** — if it's not in `changes.json`, it didn't ship. Don't write about it. -- **Respect human edits** — when updating an existing PR branch, check what humans have changed. Preserve their work. Only add or update sections they haven't touched. -- **Incremental improvement** — drafts get better over time. Early in a preview cycle, the notes may be rough. As the release stabilizes, refine them. +- **High fidelity** — only document what actually ships. The VMR (`dotnet/dotnet`) and its `src/source-manifest.json` are the source of truth. Trust `dotnet-release generate changes` output. +- **High value** — bias toward features users care about. Skip infra, test-only, and internal refactoring. +- **Never document non-shipping features** — if it's not in `changes.json`, it didn't ship. +- **Respect human edits** — this is a shared workspace. Humans edit branch content directly. Diff before writing and preserve everything they've touched. When in doubt, ask via PR comment. +- **Engage with comments** — read PR comments and review threads. Some are actionable, some need discussion. Respond and iterate. +- **Incremental improvement** — early drafts are rough. Each nightly run improves them. ## Reference documents @@ -72,132 +72,133 @@ Read these files from `.github/skills/release-notes/references/` for detailed gu ## What to do each run -### 1. Determine the target milestone +### 1. Discover active milestones -Use three data sources to deterministically identify which milestone needs release notes. +Multiple milestones can be active simultaneously. For example, if `main` has moved to Preview 5 and Preview 4 has a release branch but hasn't shipped yet, both need release notes. #### a. What has shipped (this repo) -Read `releases.json` to get the latest shipped release: - ```bash jq -r '.releases[0] | "\(.["release-version"]) \(.["release-date"])"' release-notes/11.0/releases.json -# → "11.0.0-preview.2 2026-03-10" +# → "11.0.0-preview.3 2026-04-08" ``` -Extract the shipped preview number: `preview.2` → iteration `2`. - -#### b. What's being developed (VMR) +The shipped preview number is the **floor**. Everything above it may need work. -Clone the VMR (or reuse a cached clone) and read `eng/Versions.props` on `main`: +#### b. What's building on main (VMR) ```bash git clone --filter=blob:none https://github.com/dotnet/dotnet /tmp/dotnet git -C /tmp/dotnet show main:eng/Versions.props | grep -E 'PreReleaseVersionLabel|PreReleaseVersionIteration' -# → preview -# → 3 ``` -This tells you the milestone that `main` is currently building: **preview.3**. +This tells you the milestone `main` is building (e.g., iteration `5`). -#### c. Find the base tag (VMR tags) - -List VMR tags to find the tag for the latest shipped release: +#### c. What VMR tags and release branches exist ```bash +# Tags — each represents a shipped or finalized milestone git -C /tmp/dotnet tag -l 'v11.0.0-preview.*' --sort=-v:refname -# → v11.0.0-preview.2.26159.112 -# → v11.0.0-preview.1.26104.118 + +# Release branches — each represents an in-flight milestone being stabilized +git -C /tmp/dotnet branch -r -l 'origin/release/11.0.1xx-preview*' ``` -The first tag matching the shipped iteration (preview.2) is the `--base` ref. +#### d. Build the milestone list -#### d. Decision logic +For each iteration N where `latest_shipped < N <= main_iteration`: -```text -current_iteration = PreReleaseVersionIteration from Versions.props -latest_shipped = latest preview number from releases.json +1. **Check for a VMR tag** (`v11.0.0-preview.N.*`) — if found, this milestone has been finalized +2. **Check for a VMR release branch** (`release/11.0.1xx-previewN`) — if found, this milestone is stabilizing +3. **Check for existing release notes directory** in this repo (`release-notes/11.0/preview/previewN/`) +4. **Check for an existing PR** on this repo (search for `[release-notes]` PRs matching the milestone) -If current_iteration > latest_shipped: - → Target: preview.{current_iteration} - → Base ref: VMR tag for latest shipped (e.g., v11.0.0-preview.2.26159.112) - → Action: create or update release notes +Each milestone gets its own branch and PR on this repo. -If current_iteration == latest_shipped: - → The current milestone just shipped and main hasn't been bumped yet. - → No new milestone to target. Exit. -``` +#### e. Determine base and head refs per milestone -#### e. Determine the head ref +For each active milestone N: -Check whether the target milestone has been tagged (finalized) in the VMR: +| Scenario | Base ref | Head ref | +| -------- | -------- | -------- | +| N has VMR tag | Tag for N-1 | Tag for N | +| N has release branch, no tag | Tag for N-1 | release branch tip | +| N is only on main | Tag for N-1 | main | -```bash -git -C /tmp/dotnet tag -l "v11.0.0-preview.${target_iteration}.*" --sort=-v:refname | head -1 -``` +**Critical rule**: never use `main` as the head ref for milestone N if `main` has already moved to N+1. Check `Versions.props` iteration on `main`. If it's > N, use the release branch or tag for N instead. -- **No tag found** → the milestone is still in development. Use `--head main`. -- **Tag found** → the milestone has been finalized. Use `--head ` to capture the exact shipped content. +### 2. For each active milestone -This matters because once the target milestone ships and `main` is bumped to the next iteration, `main` contains preview N+1 work. Always check for a tag before using `main` as the head ref. +Process milestones in order (lowest to highest). Each gets its own branch and PR. -### 2. Generate changes.json +#### a. Regenerate changes.json -Using the base tag and head ref determined in step 1: +Always regenerate — the content may have changed since yesterday. ```bash +mkdir -p release-notes/11.0/preview/preview4 dotnet-release generate changes /tmp/dotnet \ - --base v11.0.0-preview.2.26159.112 \ + --base v11.0.0-preview.3.26210.100 \ --head main \ - --version "11.0.0-preview.3" \ + --version "11.0.0-preview.4" \ --labels \ - --output release-notes/11.0/preview/preview3/changes.json + --output release-notes/11.0/preview/preview4/changes.json ``` -If the target milestone has a tag, use the tag instead of `main`: +#### b. Check for human edits on the branch -```bash -dotnet-release generate changes /tmp/dotnet \ - --base v11.0.0-preview.2.26159.112 \ - --head v11.0.0-preview.3.26210.100 \ - --version "11.0.0-preview.3" \ - --labels \ - --output release-notes/11.0/preview/preview3/changes.json -``` - -Create the output directory if it doesn't exist: +If a PR branch already exists: ```bash -mkdir -p release-notes/11.0/preview/preview3 +# What has changed on the branch since we last pushed? +git log --oneline --author!=github-actions origin/release-notes/11.0-preview4 ``` -### 3. Write or update markdown release notes +Identify which markdown files humans have edited. For those files, diff them to understand what changed. Do NOT overwrite human-edited sections. Only add new sections or update sections the agent previously wrote that no human has touched. + +#### c. Write or update markdown -Using `changes.json` as your input: +Using `changes.json` and the reference documents: -- Group changes by product (the `product` field maps to output files via component-mapping.md) -- For each component with changes, identify which PRs are worth writing about +- Route changes to output files via `product` field and component-mapping.md +- For each component: identify which PRs are worth writing about - Write feature descriptions following format-template.md and editorial-rules.md -- For components with no noteworthy changes, produce a minimal stub +- Components with no noteworthy changes get a minimal stub + +#### d. Read and respond to PR comments + +Check all comments and review threads on the PR since the last run: + +- **Actionable feedback** (e.g., "add detail about X", "this is wrong", "wrong component") → make the change and reply confirming +- **Questions** (e.g., "is this the right framing?") → answer if you can, or flag it for a human +- **Disagreements** (e.g., "I don't think this shipped") → cross-check against `changes.json`. If the commenter is right, fix it. If unclear, reply explaining what you found and ask for clarification +- **Resolved threads** → skip + +When unsure about a human's intent, ask. Use `add-comment` to reply. This is a conversation, not a one-shot generation. + +#### e. Create or update the PR + +- **No PR exists** → create branch `release-notes/11.0-preview4`, commit, open draft PR +- **PR exists** → push updates to the existing branch, comment summarizing what changed -When updating an existing branch: +PR title format: `[release-notes] .NET 11 Preview 4` -- `git diff` the branch to see what humans have changed since your last push -- Do NOT overwrite sections that humans have edited -- Add new sections for newly discovered features -- Update `changes.json` (this is always regenerated fresh from the tool) +PR body should summarize: milestone, number of changes, which component files were written/updated, and any open questions or items needing human review. -### 4. Create or update the PR +### 3. Handle transitions -- If no PR exists for this milestone: create a new branch and PR -- If a PR exists: push updates to the existing branch -- Comment on the PR summarizing what changed in this update +Things change between runs. Handle these gracefully: -### 5. Respond to feedback +- **Main bumps to next iteration** — the previous milestone's head ref changes from `main` to its release branch or tag. Regenerate with the correct ref. +- **New tag appears** — a milestone was finalized. Do a final regeneration with `--head ` to capture exactly what shipped. Note in the PR that this is now final. +- **Release branch appears** — a milestone is stabilizing. Switch the head ref from `main` to the release branch. +- **PR was merged** — the milestone is done. Skip it on future runs. +- **PR was closed** — something went wrong. Don't reopen. Log it and move on. -If humans have commented on the PR since your last run, read their feedback and incorporate it. Common requests: +### 4. Daily summary -- "Add more detail about X" — expand the section -- "This feature isn't ready" — remove or stub it -- "Wrong component" — move the entry -- "Needs a code sample" — add one +At the end of each run, leave a comment on each active PR noting: +- What was regenerated or updated +- How many new changes appeared since yesterday +- Whether the head ref changed +- Any comments that still need human attention From 887240c7ce39eb53a3b111b924447ed984632602 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 20:41:04 -0700 Subject: [PATCH 12/65] Compile agentic workflow; fix toolset names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix invalid toolsets: contents→repos, code_search→search - Add compiled release-notes.lock.yml - Add .github/aw/actions-lock.json (gh aw artifact) - Add linguist-generated gitattributes for lock files Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .gitattributes | 4 +- .github/aw/actions-lock.json | 14 + .github/workflows/release-notes.lock.yml | 1204 ++++++++++++++++++++++ .github/workflows/release-notes.md | 2 +- 4 files changed, 1222 insertions(+), 2 deletions(-) create mode 100644 .github/aw/actions-lock.json create mode 100644 .github/workflows/release-notes.lock.yml diff --git a/.gitattributes b/.gitattributes index a1e1e97ac2..b678750c67 100644 --- a/.gitattributes +++ b/.gitattributes @@ -60,4 +60,6 @@ #*.pdf diff=astextplain #*.PDF diff=astextplain #*.rtf diff=astextplain -#*.RTF diff=astextplain \ No newline at end of file +#*.RTF diff=astextplain + +.github/workflows/*.lock.yml linguist-generated=true merge=ours \ No newline at end of file diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json new file mode 100644 index 0000000000..c051b35c6c --- /dev/null +++ b/.github/aw/actions-lock.json @@ -0,0 +1,14 @@ +{ + "entries": { + "actions/github-script@v8": { + "repo": "actions/github-script", + "version": "v8", + "sha": "ed597411d8f924073f98dfc5c65a23a2325f34cd" + }, + "github/gh-aw-actions/setup@v0.65.1": { + "repo": "github/gh-aw-actions/setup", + "version": "v0.65.1", + "sha": "1831bcd4f397da99da6e6e6b65687557a1a37ac7" + } + } +} diff --git a/.github/workflows/release-notes.lock.yml b/.github/workflows/release-notes.lock.yml new file mode 100644 index 0000000000..a3ed446b4b --- /dev/null +++ b/.github/workflows/release-notes.lock.yml @@ -0,0 +1,1204 @@ +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by gh-aw (v0.65.1). DO NOT EDIT. +# +# To update this file, edit the corresponding .md file and run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"a0071795baea7e76942a9dd783e4782052b6383b85e49cb0592fd9af149a33a6","compiler_version":"v0.65.1","strict":true,"agent_id":"copilot"} + +name: ".NET Release Notes Maintenance" +"on": + schedule: + - cron: "29 16 * * *" + # Friendly format: daily around 9:00 utc-8 (scattered) + workflow_dispatch: + inputs: + aw_context: + default: "" + description: Agent caller context (used internally by Agentic Workflows). + required: false + type: string + milestone: + description: Target milestone (e.g., preview4). Leave empty for auto-detection. + required: false + type: string + +permissions: {} + +concurrency: + group: "gh-aw-${{ github.workflow }}" + +run-name: ".NET Release Notes Maintenance" + +jobs: + activation: + runs-on: ubuntu-slim + permissions: + contents: read + outputs: + comment_id: "" + comment_repo: "" + lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} + model: ${{ steps.generate_aw_info.outputs.model }} + secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@1831bcd4f397da99da6e6e6b65687557a1a37ac7 # v0.65.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + - name: Generate agentic run info + id: generate_aw_info + env: + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} + GH_AW_INFO_VERSION: "latest" + GH_AW_INFO_AGENT_VERSION: "latest" + GH_AW_INFO_CLI_VERSION: "v0.65.1" + GH_AW_INFO_WORKFLOW_NAME: ".NET Release Notes Maintenance" + GH_AW_INFO_EXPERIMENTAL: "false" + GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" + GH_AW_INFO_STAGED: "false" + GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","dotnet"]' + GH_AW_INFO_FIREWALL_ENABLED: "true" + GH_AW_INFO_AWF_VERSION: "v0.25.5" + GH_AW_INFO_AWMG_VERSION: "" + GH_AW_INFO_FIREWALL_TYPE: "squid" + GH_AW_COMPILED_STRICT: "true" + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); + await main(core, context); + - name: Validate COPILOT_GITHUB_TOKEN secret + id: validate-secret + run: ${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + - name: Checkout .github and .agents folders + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + sparse-checkout: | + .github + .agents + sparse-checkout-cone-mode: true + fetch-depth: 1 + - name: Check workflow file timestamps + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_WORKFLOW_FILE: "release-notes.lock.yml" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Check compile-agentic version + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_COMPILED_VERSION: "v0.65.1" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); + await main(); + - name: Create prompt with built-in context + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + # poutine:ignore untrusted_checkout_exec + run: | + bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh + { + cat << 'GH_AW_PROMPT_092d204882fce517_EOF' + + GH_AW_PROMPT_092d204882fce517_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_092d204882fce517_EOF' + + Tools: add_comment(max:20), create_pull_request(max:5), push_to_pull_request_branch(max:5), missing_tool, missing_data, noop + GH_AW_PROMPT_092d204882fce517_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md" + cat << 'GH_AW_PROMPT_092d204882fce517_EOF' + + + The following GitHub context information is available for this workflow: + {{#if __GH_AW_GITHUB_ACTOR__ }} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if __GH_AW_GITHUB_REPOSITORY__ }} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if __GH_AW_GITHUB_WORKSPACE__ }} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} + - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} + - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} + - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} + - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{/if}} + {{#if __GH_AW_GITHUB_RUN_ID__ }} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + + + GH_AW_PROMPT_092d204882fce517_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" + cat << 'GH_AW_PROMPT_092d204882fce517_EOF' + + {{#runtime-import .github/workflows/release-notes.md}} + GH_AW_PROMPT_092d204882fce517_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + + const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, + GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash ${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash ${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh + - name: Upload activation artifact + if: success() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: activation + path: | + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/aw-prompts/prompt.txt + retention-days: 1 + + agent: + needs: activation + runs-on: ubuntu-latest + permissions: + contents: read + issues: read + pull-requests: read + concurrency: + group: "gh-aw-copilot-${{ github.workflow }}" + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_WORKFLOW_ID_SANITIZED: releasenotes + outputs: + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + inference_access_error: ${{ steps.detect-inference-error.outputs.inference_access_error || 'false' }} + model: ${{ needs.activation.outputs.model }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@1831bcd4f397da99da6e6e6b65687557a1a37ac7 # v0.65.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + - name: Set runtime paths + id: set-runtime-paths + run: | + echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" >> "$GITHUB_OUTPUT" + echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" >> "$GITHUB_OUTPUT" + echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" >> "$GITHUB_OUTPUT" + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Setup .NET + uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 + with: + dotnet-version: '9.0' + - name: Create gh-aw temp directory + run: bash ${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh + - name: Configure gh CLI for GitHub Enterprise + run: bash ${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh + env: + GH_TOKEN: ${{ github.token }} + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Checkout PR branch + id: checkout-pr + if: | + github.event.pull_request || github.event.issue.pull_request + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Install GitHub Copilot CLI + run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest + - name: Install AWF binary + run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.5 + - name: Determine automatic lockdown mode for GitHub MCP Server + id: determine-automatic-lockdown + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + with: + script: | + const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); + await determineAutomaticLockdown(github, context, core); + - name: Download container images + run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.5 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.5 ghcr.io/github/gh-aw-firewall/squid:0.25.5 ghcr.io/github/gh-aw-mcpg:v0.2.10 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine + - name: Write Safe Outputs Config + run: | + mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_e0765ad82515efc5_EOF' + {"add_comment":{"max":20,"target":"*"},"create_pull_request":{"draft":true,"labels":["release-notes","automated"],"max":5,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS"],"protected_path_prefixes":[".github/",".agents/"],"title_prefix":"[release-notes] "},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"if_no_changes":"warn","labels":["release-notes","automated"],"max":5,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS"],"protected_path_prefixes":[".github/",".agents/"],"title_prefix":"[release-notes] "}} + GH_AW_SAFE_OUTPUTS_CONFIG_e0765ad82515efc5_EOF + - name: Write Safe Outputs Tools + run: | + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_11b0293352d901bb_EOF' + { + "description_suffixes": { + "add_comment": " CONSTRAINTS: Maximum 20 comment(s) can be added. Target: *.", + "create_pull_request": " CONSTRAINTS: Maximum 5 pull request(s) can be created. Title will be prefixed with \"[release-notes] \". Labels [\"release-notes\" \"automated\"] will be automatically added. PRs will be created as drafts.", + "push_to_pull_request_branch": " CONSTRAINTS: Maximum 5 push(es) can be made. The target pull request title must start with \"[release-notes] \"." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_SAFE_OUTPUTS_TOOLS_META_11b0293352d901bb_EOF + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_92a5e54bdfae304b_EOF' + { + "add_comment": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "item_number": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + } + } + }, + "create_pull_request": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "branch": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "draft": { + "type": "boolean" + }, + "labels": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "push_to_pull_request_branch": { + "defaultMax": 1, + "fields": { + "branch": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "pull_request_number": { + "issueOrPRNumber": true + } + } + } + } + GH_AW_SAFE_OUTPUTS_VALIDATION_92a5e54bdfae304b_EOF + node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs + - name: Generate Safe Outputs MCP Server Config + id: safe-outputs-config + run: | + # Generate a secure random API key (360 bits of entropy, 40+ chars) + # Mask immediately to prevent timing vulnerabilities + API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${API_KEY}" + + PORT=3001 + + # Set outputs for next steps + { + echo "safe_outputs_api_key=${API_KEY}" + echo "safe_outputs_port=${PORT}" + } >> "$GITHUB_OUTPUT" + + echo "Safe Outputs MCP server will run on port ${PORT}" + + - name: Start Safe Outputs MCP HTTP Server + id: safe-outputs-start + env: + DEBUG: '*' + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + run: | + # Environment variables are set above to prevent template injection + export DEBUG + export GH_AW_SAFE_OUTPUTS_PORT + export GH_AW_SAFE_OUTPUTS_API_KEY + export GH_AW_SAFE_OUTPUTS_TOOLS_PATH + export GH_AW_SAFE_OUTPUTS_CONFIG_PATH + export GH_AW_MCP_LOG_DIR + + bash ${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh + + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }} + GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p /tmp/gh-aw/mcp-config + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="80" + export MCP_GATEWAY_DOMAIN="host.docker.internal" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + export MCP_GATEWAY_API_KEY + export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" + mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" + export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" + export DEBUG="*" + + export GH_AW_ENGINE="copilot" + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.10' + + mkdir -p /home/runner/.copilot + cat << GH_AW_MCP_CONFIG_d7bc14dc74bfc877_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh + { + "mcpServers": { + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v0.32.0", + "env": { + "GITHUB_HOST": "\${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "issues,pull_requests,repos,search" + }, + "guard-policies": { + "allow-only": { + "min-integrity": "$GITHUB_MCP_GUARD_MIN_INTEGRITY", + "repos": "$GITHUB_MCP_GUARD_REPOS" + } + } + }, + "safeoutputs": { + "type": "http", + "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + }, + "guard-policies": { + "write-sink": { + "accept": [ + "*" + ] + } + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_d7bc14dc74bfc877_EOF + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw + - name: Clean git credentials + continue-on-error: true + run: bash ${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + # --allow-tool github + # --allow-tool safeoutputs + # --allow-tool shell(cat) + # --allow-tool shell(date) + # --allow-tool shell(dotnet-release) + # --allow-tool shell(dotnet:*) + # --allow-tool shell(echo) + # --allow-tool shell(git add:*) + # --allow-tool shell(git branch:*) + # --allow-tool shell(git checkout:*) + # --allow-tool shell(git commit:*) + # --allow-tool shell(git merge:*) + # --allow-tool shell(git rm:*) + # --allow-tool shell(git status) + # --allow-tool shell(git switch:*) + # --allow-tool shell(git:*) + # --allow-tool shell(grep) + # --allow-tool shell(head) + # --allow-tool shell(jq) + # --allow-tool shell(ls) + # --allow-tool shell(pwd) + # --allow-tool shell(sort) + # --allow-tool shell(tail) + # --allow-tool shell(uniq) + # --allow-tool shell(wc) + # --allow-tool shell(yq) + # --allow-tool write + timeout-minutes: 20 + run: | + set -o pipefail + touch /tmp/gh-aw/agent-step-summary.md + # shellcheck disable=SC1003 + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.5 --skip-pull --enable-api-proxy \ + -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(dotnet-release)'\'' --allow-tool '\''shell(dotnet:*)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(git add:*)'\'' --allow-tool '\''shell(git branch:*)'\'' --allow-tool '\''shell(git checkout:*)'\'' --allow-tool '\''shell(git commit:*)'\'' --allow-tool '\''shell(git merge:*)'\'' --allow-tool '\''shell(git rm:*)'\'' --allow-tool '\''shell(git status)'\'' --allow-tool '\''shell(git switch:*)'\'' --allow-tool '\''shell(git:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} + GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_PHASE: agent + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_VERSION: v0.65.1 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Detect inference access error + id: detect-inference-error + if: always() + continue-on-error: true + run: bash ${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: | + # Copy Copilot session state files to logs folder for artifact collection + # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them + SESSION_STATE_DIR="$HOME/.copilot/session-state" + LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs" + + if [ -d "$SESSION_STATE_DIR" ]; then + echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR" + mkdir -p "$LOGS_DIR" + cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true + echo "Session state files copied successfully" + else + echo "No session-state directory found at $SESSION_STATE_DIR" + fi + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash ${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Append agent step summary + if: always() + run: bash ${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh + - name: Copy Safe Outputs + if: always() + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + run: | + mkdir -p /tmp/gh-aw + cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_ALLOWED_DOMAINS: "*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); + await main(); + - name: Print firewall logs + if: always() + continue-on-error: true + env: + AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs + run: | + # Fix permissions on firewall logs so they can be uploaded as artifacts + # AWF runs with sudo, creating files owned by root + sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true + # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) + if command -v awf &> /dev/null; then + awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" + else + echo 'AWF binary not installed, skipping firewall log summary' + fi + - name: Write agent output placeholder if missing + if: always() + run: | + if [ ! -f /tmp/gh-aw/agent_output.json ]; then + echo '{"items":[]}' > /tmp/gh-aw/agent_output.json + fi + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: agent + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/agent/ + /tmp/gh-aw/safeoutputs.jsonl + /tmp/gh-aw/agent_output.json + /tmp/gh-aw/aw-*.patch + /tmp/gh-aw/aw-*.bundle + if-no-files-found: ignore + - name: Upload firewall audit logs + if: always() + continue-on-error: true + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: firewall-audit-logs + path: | + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/sandbox/firewall/audit/ + if-no-files-found: ignore + + conclusion: + needs: + - activation + - agent + - detection + - safe_outputs + if: always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true') + runs-on: ubuntu-slim + permissions: + contents: write + discussions: write + issues: write + pull-requests: write + concurrency: + group: "gh-aw-conclusion-release-notes" + cancel-in-progress: false + outputs: + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@1831bcd4f397da99da6e6e6b65687557a1a37ac7 # v0.65.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Process No-Op Messages + id: noop + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: ".NET Release Notes Maintenance" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/noop.cjs'); + await main(); + - name: Record Missing Tool + id: missing_tool + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: ".NET Release Notes Maintenance" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Handle Agent Failure + id: handle_agent_failure + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: ".NET Release Notes Maintenance" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_WORKFLOW_ID: "release-notes" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} + GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }} + GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }} + GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} + GH_AW_GROUP_REPORTS: "false" + GH_AW_FAILURE_REPORT_AS_ISSUE: "true" + GH_AW_TIMEOUT_MINUTES: "20" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + - name: Handle No-Op Message + id: handle_noop_message + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: ".NET Release Notes Maintenance" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + await main(); + - name: Handle Create Pull Request Error + id: handle_create_pr_error + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: ".NET Release Notes Maintenance" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_create_pr_error.cjs'); + await main(); + + detection: + needs: agent + if: > + always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + runs-on: ubuntu-latest + outputs: + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@1831bcd4f397da99da6e6e6b65687557a1a37ac7 # v0.65.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + # --- Threat Detection --- + - name: Download container images + run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.5 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.5 ghcr.io/github/gh-aw-firewall/squid:0.25.5 + - name: Check if detection needed + id: detection_guard + if: always() + env: + OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }} + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + run: | + if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then + echo "run_detection=true" >> "$GITHUB_OUTPUT" + echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" + else + echo "run_detection=false" >> "$GITHUB_OUTPUT" + echo "Detection skipped: no agent outputs or patches to analyze" + fi + - name: Clear MCP configuration for detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + rm -f /tmp/gh-aw/mcp-config/mcp-servers.json + rm -f /home/runner/.copilot/mcp-config.json + rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" + - name: Prepare threat detection files + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true + for f in /tmp/gh-aw/aw-*.patch; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + for f in /tmp/gh-aw/aw-*.bundle; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + echo "Prepared threat detection files:" + ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true + - name: Setup threat detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + WORKFLOW_NAME: ".NET Release Notes Maintenance" + WORKFLOW_DESCRIPTION: "No description provided" + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs'); + await main(); + - name: Ensure threat-detection directory and log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection + touch /tmp/gh-aw/threat-detection/detection.log + - name: Install GitHub Copilot CLI + run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest + - name: Install AWF binary + run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.5 + - name: Execute GitHub Copilot CLI + if: always() && steps.detection_guard.outputs.run_detection == 'true' + id: detection_agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + touch /tmp/gh-aw/agent-step-summary.md + # shellcheck disable=SC1003 + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.5 --skip-pull --enable-api-proxy \ + -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} + GH_AW_PHASE: detection + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_VERSION: v0.65.1 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Upload threat detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: detection + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + - name: Parse and conclude threat detection + id: detection_conclusion + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + + safe_outputs: + needs: + - activation + - agent + - detection + if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' + runs-on: ubuntu-slim + permissions: + contents: write + discussions: write + issues: write + pull-requests: write + timeout-minutes: 15 + env: + GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/release-notes" + GH_AW_ENGINE_ID: "copilot" + GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} + GH_AW_WORKFLOW_ID: "release-notes" + GH_AW_WORKFLOW_NAME: ".NET Release Notes Maintenance" + outputs: + code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} + code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} + comment_id: ${{ steps.process_safe_outputs.outputs.comment_id }} + comment_url: ${{ steps.process_safe_outputs.outputs.comment_url }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + created_pr_number: ${{ steps.process_safe_outputs.outputs.created_pr_number }} + created_pr_url: ${{ steps.process_safe_outputs.outputs.created_pr_url }} + process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} + push_commit_sha: ${{ steps.process_safe_outputs.outputs.push_commit_sha }} + push_commit_url: ${{ steps.process_safe_outputs.outputs.push_commit_url }} + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@1831bcd4f397da99da6e6e6b65687557a1a37ac7 # v0.65.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Download patch artifact + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Checkout repository + if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') || (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch') + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.base_ref || github.event.pull_request.base.ref || github.ref_name || github.event.repository.default_branch }} + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + persist-credentials: false + fetch-depth: 1 + - name: Configure Git credentials + if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') || (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch') + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GIT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GIT_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_ALLOWED_DOMAINS: "*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":20,\"target\":\"*\"},\"create_pull_request\":{\"draft\":true,\"labels\":[\"release-notes\",\"automated\"],\"max\":5,\"max_patch_size\":1024,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"AGENTS.md\"],\"protected_path_prefixes\":[\".github/\",\".agents/\"],\"title_prefix\":\"[release-notes] \"},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"labels\":[\"release-notes\",\"automated\"],\"max\":5,\"max_patch_size\":1024,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"AGENTS.md\"],\"protected_path_prefixes\":[\".github/\",\".agents/\"],\"title_prefix\":\"[release-notes] \"}}" + GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); + await main(); + - name: Upload Safe Output Items + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: safe-output-items + path: /tmp/gh-aw/safe-output-items.jsonl + if-no-files-found: ignore + diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index d6012972ff..8bfd68e08c 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -33,7 +33,7 @@ safe-outputs: target: "*" tools: github: - toolsets: [issues, pull_requests, contents, code_search] + toolsets: [issues, pull_requests, repos, search] bash: - dotnet - dotnet-release From 4d7add2b80bc6c3c1097dfe6a5ebe9a72398331b Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 20:45:43 -0700 Subject: [PATCH 13/65] Remove stale gist link from changes schema docs The inline example in this file is the authoritative reference. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/references/changes-schema.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/skills/release-notes/references/changes-schema.md b/.github/skills/release-notes/references/changes-schema.md index 931b62245c..fb45bdf277 100644 --- a/.github/skills/release-notes/references/changes-schema.md +++ b/.github/skills/release-notes/references/changes-schema.md @@ -2,8 +2,6 @@ Reference for the `changes.json` file produced by `dotnet-release generate changes`. One file per release milestone. -Based on the [changes schema proposal](https://gist.github.com/richlander/a2bf9beb6f09cf9ba7ecf80f5b51784e). - ## Overview `changes.json` is a comprehensive, machine-readable manifest of every PR and commit that shipped in a release. It is the companion to the editorial markdown release notes — the JSON tells you **everything that shipped**, the markdown tells you **what matters**. From 5105869da1893fd00db810de3ff1061703a95fb3 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 20:57:08 -0700 Subject: [PATCH 14/65] Add curated examples reference doc Six examples from .NET 9/10 preview release notes demonstrating three styles: - Short: exception handler diagnostics, container insecure registries - Medium: extension operators (C#), PipeReader JSON support - Long: post-quantum cryptography, OpenIdConnect PAR Added to reference docs list in workflow and DESIGN.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/DESIGN.md | 1 + .../release-notes/references/examples.md | 62 +++++++++++++++++++ .github/workflows/release-notes.md | 1 + 3 files changed, 64 insertions(+) create mode 100644 .github/skills/release-notes/references/examples.md diff --git a/.github/skills/release-notes/DESIGN.md b/.github/skills/release-notes/DESIGN.md index 11b5c6dccf..1a81fc9138 100644 --- a/.github/skills/release-notes/DESIGN.md +++ b/.github/skills/release-notes/DESIGN.md @@ -105,6 +105,7 @@ The AI agent reads `changes.json` and writes markdown release notes. Its guidanc | [component-mapping.md](references/component-mapping.md) | Components → product slugs → output files | | [format-template.md](references/format-template.md) | Markdown document structure | | [editorial-rules.md](references/editorial-rules.md) | Tone, attribution, naming | +| [examples.md](references/examples.md) | Curated examples — short, medium, long-form styles | These are **goal-oriented**, not procedural. They describe what good release notes look like, not the exact steps to produce them. The agent figures out the HOW. diff --git a/.github/skills/release-notes/references/examples.md b/.github/skills/release-notes/references/examples.md new file mode 100644 index 0000000000..4828389f83 --- /dev/null +++ b/.github/skills/release-notes/references/examples.md @@ -0,0 +1,62 @@ +# Release Notes Examples + +Good examples of feature descriptions from previous .NET release notes, demonstrating different lengths and approaches. Use these as models — match the length to the importance and complexity of the feature. + +The public-facing "What's new in .NET" docs pages are the curated, polished versions. These repo-based release notes have more flexibility — descriptions can be as long as they need to be, as long as they're interesting. + +## Short — focused explanation, no code + +Best for: behavior changes, configuration options, minor improvements. + +### Example: Configure suppressing exception handler diagnostics + +> From [.NET 10 Preview 7 — ASP.NET Core](../../release-notes/10.0/preview/preview7/aspnetcore.md) + +A new configuration option, a behavior change, and the rationale — all in three sentences. No code needed because the value is in understanding the *why*, not the API shape. + +### Example: Container publishing improvements for insecure registries + +> From [.NET 9 Preview 7 — SDK](../../release-notes/9.0/preview/preview7/sdk.md) + +Problem/solution framing, community attribution (`[@tmds](https://github.com/tmds)`), then a tight bullet list of requirements. Shows how to credit contributors naturally. + +## Medium — context plus code sample + +Best for: new APIs, language features, things developers will type. + +### Example: Extension operators + +> From [.NET 10 Preview 7 — C#](../../release-notes/10.0/preview/preview7/csharp.md) + +One sentence of context, then a code block showing the new syntax, then bullet rules. The code *is* the feature — showing it immediately is the right call. + +### Example: PipeReader support in System.Text.Json + +> From [.NET 10 Preview 7 — ASP.NET Core](../../release-notes/10.0/preview/preview7/aspnetcore.md) + +A behavioral change that's mostly invisible but has an edge case. Explains what changed, who might be affected, the workaround, and the proper fix. Good template for "most people won't notice, but if you do, here's what to do." + +## Long — deep-dive with multiple sections + +Best for: major features, security capabilities, things users have been asking for. + +### Example: Post-Quantum Cryptography Updates + +> From [.NET 10 Preview 7 — Libraries](../../release-notes/10.0/preview/preview7/libraries.md) + +Subheadings (ML-DSA, Composite ML-DSA), diff-style before/after code, multiple API examples. The diff format (`-` old / `+` new) is especially effective for showing simplification. + +### Example: OpenIdConnectHandler support for Pushed Authorization Requests (PAR) + +> From [.NET 9 Preview 7 — ASP.NET Core](../../release-notes/9.0/preview/preview7/aspnetcore.md) + +Community contribution with extensive quoted motivation from the contributor. The block quote explains *why* this matters (security, compliance, standards). Then practical: default behavior, how to disable, how to require. Good template for features with regulatory or security significance. + +## Principles these examples demonstrate + +1. **Length matches importance** — a config option gets 3 sentences; a cryptography overhaul gets subheadings and multiple code blocks +2. **Lead with what changed** — don't bury the lede with background paragraphs +3. **Code shows, prose explains** — when there's an API, show it; use prose for the why/when +4. **Attribution is natural** — community contributions get a mention with a GitHub link, not a separate "contributors" section +5. **Diff format for improvements** — when a feature simplifies existing code, `diff` blocks make the improvement immediately visible +6. **Workarounds are welcome** — if a change might break someone, say so and give the escape hatch diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index 8bfd68e08c..544b5b1714 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -69,6 +69,7 @@ Read these files from `.github/skills/release-notes/references/` for detailed gu - **component-mapping.md** — VMR paths → components → product slugs → output files - **format-template.md** — markdown document structure - **editorial-rules.md** — tone, attribution, naming conventions +- **examples.md** — curated examples from previous releases showing short, medium, and long-form styles ## What to do each run From accd29f13ccac72472e193a14eb76b41db6edf29 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 20:59:21 -0700 Subject: [PATCH 15/65] Add JIT/runtime examples to release notes reference Three styles of JIT feature descriptions: - Array Enumeration De-Abstraction (progressive benchmarks, optimization narrative) - Struct Arguments codegen (before/after assembly comparison) - Loop Optimizations (metric-heavy prose, no code) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../release-notes/references/examples.md | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/skills/release-notes/references/examples.md b/.github/skills/release-notes/references/examples.md index 4828389f83..e7f1c4cafe 100644 --- a/.github/skills/release-notes/references/examples.md +++ b/.github/skills/release-notes/references/examples.md @@ -40,6 +40,24 @@ A behavioral change that's mostly invisible but has an edge case. Explains what Best for: major features, security capabilities, things users have been asking for. +### Example: Array Enumeration De-Abstraction + +> From [.NET 10 Preview 2 — Runtime](../../release-notes/10.0/preview/preview2/runtime.md) + +A progressive JIT optimization story told through three benchmark tables. Starts with a simple case (transparent type), escalates to a harder case (opaque type behind interface), and walks through each optimization layer (inlining, PGO, guarded devirtualization, conditional escape analysis). Each benchmark shows measurable improvement. The narrative structure — problem, partial solution, harder problem, better solution — keeps the reader engaged. + +### Example: Improved Code Generation for Struct Arguments + +> From [.NET 10 Preview 6 — Runtime](../../release-notes/10.0/preview/preview6/runtime.md) + +Before/after assembly comparison showing a concrete codegen improvement. Starts with C# code, shows the current optimal case, introduces the pathological case with assembly output, then shows the fix. Three `asm` blocks tell the whole story — the reader can literally count the instructions eliminated. + +### Example: JIT Loop Optimizations + +> From [.NET 9 Preview 1 — Runtime](../../release-notes/9.0/preview/preview1/runtime.md) + +No code at all — just crisp prose with percentages. Explains three optimization categories (hoisting, cloning, alignment) with one-paragraph descriptions and measured improvement rates. Good template for infrastructure improvements where the value is breadth of impact rather than a single dramatic before/after. + ### Example: Post-Quantum Cryptography Updates > From [.NET 10 Preview 7 — Libraries](../../release-notes/10.0/preview/preview7/libraries.md) @@ -59,4 +77,6 @@ Community contribution with extensive quoted motivation from the contributor. Th 3. **Code shows, prose explains** — when there's an API, show it; use prose for the why/when 4. **Attribution is natural** — community contributions get a mention with a GitHub link, not a separate "contributors" section 5. **Diff format for improvements** — when a feature simplifies existing code, `diff` blocks make the improvement immediately visible -6. **Workarounds are welcome** — if a change might break someone, say so and give the escape hatch +6. **Assembly comparisons for JIT work** — before/after `asm` blocks let readers count the instructions eliminated +7. **Progressive benchmarks tell a story** — multiple benchmark tables showing incremental improvement are more compelling than a single number +8. **Workarounds are welcome** — if a change might break someone, say so and give the escape hatch From f340d3ebc653f2cf8c7332f534ffc12fcf020b21 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 21:00:46 -0700 Subject: [PATCH 16/65] Add ask-for-help guidance: benchmarks, samples, domain context The agent should recognize when a feature description needs content it can't generate (benchmark data, definitive code samples, domain expertise) and post PR comments requesting it from feature authors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/references/examples.md | 3 ++- .github/workflows/release-notes.md | 12 +++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/skills/release-notes/references/examples.md b/.github/skills/release-notes/references/examples.md index e7f1c4cafe..26afe3a6c8 100644 --- a/.github/skills/release-notes/references/examples.md +++ b/.github/skills/release-notes/references/examples.md @@ -79,4 +79,5 @@ Community contribution with extensive quoted motivation from the contributor. Th 5. **Diff format for improvements** — when a feature simplifies existing code, `diff` blocks make the improvement immediately visible 6. **Assembly comparisons for JIT work** — before/after `asm` blocks let readers count the instructions eliminated 7. **Progressive benchmarks tell a story** — multiple benchmark tables showing incremental improvement are more compelling than a single number -8. **Workarounds are welcome** — if a change might break someone, say so and give the escape hatch +8. **Ask for what you can't generate** — benchmark data, definitive samples, and domain context come from humans. A placeholder with a request is better than a fabrication +9. **Workarounds are welcome** — if a change might break someone, say so and give the escape hatch diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index 544b5b1714..17417cf53f 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -166,7 +166,17 @@ Using `changes.json` and the reference documents: - Write feature descriptions following format-template.md and editorial-rules.md - Components with no noteworthy changes get a minimal stub -#### d. Read and respond to PR comments +#### d. Ask for what you can't generate + +Some features need content that only humans can provide — benchmark data, definitive code samples, or domain-specific context. When you identify a feature that would benefit from this: + +- **Benchmark data** — if a JIT or performance feature would be better told with numbers, post a comment tagging the PR author asking for benchmark results. Write a placeholder section noting the optimization and what it improves, and flag it as needing data. +- **Code samples** — if you can't confidently generate a correct, idiomatic sample (e.g., complex API interactions, platform-specific patterns), ask the feature author for one. A description without a sample is better than an incorrect sample. +- **Domain expertise** — if a feature's significance isn't clear from the PR title and diff alone, ask. "Can you describe the user scenario this improves?" is a valid comment. + +Frame these as suggestions, not demands. For example: "This JIT improvement in loop unrolling looks significant. Benchmark data showing the before/after would help tell the story — could you share numbers or point me to a benchmark?" + +#### e. Read and respond to PR comments Check all comments and review threads on the PR since the last run: From 68d022ccc664fb264e3782666385c0829c0eeb24 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 21:05:36 -0700 Subject: [PATCH 17/65] Split examples by component for token efficiency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace monolithic examples.md with per-component files: - examples/README.md — principles index, component directory - examples/runtime.md — 3 JIT examples with inlined content - examples/aspnetcore.md — 3 examples (short/medium/long) - examples/csharp.md — extension operators - examples/sdk.md — container insecure registries - examples/libraries.md — post-quantum cryptography Each file includes the actual example text and links back to the original source file. Agent loads only the relevant component file when writing, saving context tokens. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/DESIGN.md | 2 +- .../references/examples/README.md | 23 ++++ .../references/examples/aspnetcore.md | 79 ++++++++++++ .../references/examples/csharp.md | 32 +++++ .../references/examples/libraries.md | 55 +++++++++ .../references/examples/runtime.md | 113 ++++++++++++++++++ .../release-notes/references/examples/sdk.md | 20 ++++ .github/workflows/release-notes.md | 2 +- 8 files changed, 324 insertions(+), 2 deletions(-) create mode 100644 .github/skills/release-notes/references/examples/README.md create mode 100644 .github/skills/release-notes/references/examples/aspnetcore.md create mode 100644 .github/skills/release-notes/references/examples/csharp.md create mode 100644 .github/skills/release-notes/references/examples/libraries.md create mode 100644 .github/skills/release-notes/references/examples/runtime.md create mode 100644 .github/skills/release-notes/references/examples/sdk.md diff --git a/.github/skills/release-notes/DESIGN.md b/.github/skills/release-notes/DESIGN.md index 1a81fc9138..5956ec6b37 100644 --- a/.github/skills/release-notes/DESIGN.md +++ b/.github/skills/release-notes/DESIGN.md @@ -105,7 +105,7 @@ The AI agent reads `changes.json` and writes markdown release notes. Its guidanc | [component-mapping.md](references/component-mapping.md) | Components → product slugs → output files | | [format-template.md](references/format-template.md) | Markdown document structure | | [editorial-rules.md](references/editorial-rules.md) | Tone, attribution, naming | -| [examples.md](references/examples.md) | Curated examples — short, medium, long-form styles | +| [examples/](references/examples/README.md) | Curated examples by component — short, medium, long-form styles | These are **goal-oriented**, not procedural. They describe what good release notes look like, not the exact steps to produce them. The agent figures out the HOW. diff --git a/.github/skills/release-notes/references/examples/README.md b/.github/skills/release-notes/references/examples/README.md new file mode 100644 index 0000000000..47db436c9f --- /dev/null +++ b/.github/skills/release-notes/references/examples/README.md @@ -0,0 +1,23 @@ +# Release Notes Examples + +Curated examples from previous .NET release notes, organized by component. Load only the file relevant to the component you're writing. + +| File | Component | Styles shown | +|------|-----------|-------------| +| [runtime.md](examples/runtime.md) | JIT, GC, CoreCLR | Benchmark narrative, assembly comparison, metric-heavy prose | +| [aspnetcore.md](examples/aspnetcore.md) | ASP.NET Core, Blazor | Short behavior change, medium workaround, long community feature | +| [csharp.md](examples/csharp.md) | C# language | Code-first with rule bullets | +| [sdk.md](examples/sdk.md) | SDK, CLI, containers | Problem/solution with community attribution | +| [libraries.md](examples/libraries.md) | BCL, System.* APIs | Subheadings, diff blocks, multiple API examples | + +## Principles + +1. **Length matches importance** — a config option gets 3 sentences; a cryptography overhaul gets subheadings and multiple code blocks +2. **Lead with what changed** — don't bury the lede with background paragraphs +3. **Code shows, prose explains** — when there's an API, show it; use prose for the why/when +4. **Attribution is natural** — community contributions get a mention with a GitHub link, not a separate "contributors" section +5. **Diff format for improvements** — when a feature simplifies existing code, `diff` blocks make the improvement immediately visible +6. **Assembly comparisons for JIT work** — before/after `asm` blocks let readers count the instructions eliminated +7. **Progressive benchmarks tell a story** — multiple benchmark tables showing incremental improvement are more compelling than a single number +8. **Ask for what you can't generate** — benchmark data, definitive samples, and domain context come from humans. A placeholder with a request is better than a fabrication +9. **Workarounds are welcome** — if a change might break someone, say so and give the escape hatch diff --git a/.github/skills/release-notes/references/examples/aspnetcore.md b/.github/skills/release-notes/references/examples/aspnetcore.md new file mode 100644 index 0000000000..ebb859d826 --- /dev/null +++ b/.github/skills/release-notes/references/examples/aspnetcore.md @@ -0,0 +1,79 @@ +# ASP.NET Core Examples + +## Configure suppressing exception handler diagnostics + +> Source: [.NET 10 Preview 7 — ASP.NET Core](../../../../release-notes/10.0/preview/preview7/aspnetcore.md) + +Short — three sentences covering the new option, the use case, and a behavior change. No code needed. + +> A new configuration option has been added to the [ASP.NET Core exception handler middleware](https://learn.microsoft.com/aspnet/core/fundamentals/error-handling#exception-handler-page) to control diagnostic output: `ExceptionHandlerOptions.SuppressDiagnosticsCallback`. This callback is passed context about the request and exception, allowing you to add logic that determines whether the middleware should write exception logs and other telemetry. +> +> This setting is useful when you know an exception is transient, or has been handled by the exception handler middleware, and don't want the error logs written to your observability platform. +> +> Additionally, the middleware's default behavior has changed: it no longer writes exception diagnostics for exceptions handled by `IExceptionHandler`. Based on user feedback, logging handled exceptions at the error level was often undesirable when `IExceptionHandler.TryHandleAsync` returned `true`. + +**Why it works**: concise problem/solution. The reader immediately knows what changed, why, and whether they're affected. + +--- + +## Use PipeReader support in System.Text.Json + +> Source: [.NET 10 Preview 7 — ASP.NET Core](../../../../release-notes/10.0/preview/preview7/aspnetcore.md) + +Medium — behavioral change with workaround guidance and two code samples (quick fix and optimal fix). + +> MVC, Minimal APIs, and the `HttpRequestJsonExtensions.ReadFromJsonAsync` methods have all been updated to use the new PipeReader support in System.Text.Json without requiring any code changes from applications. +> +> For the majority of applications this should have no impact on behavior. However, if the application is using a custom `JsonConverter`, there is a chance that the converter doesn't handle `Utf8JsonReader.HasValueSequence` correctly. This can result in missing data and errors like `ArgumentOutOfRangeException` when deserializing. +> +> The quick workaround (especially if you don't own the custom `JsonConverter` being used) is to set the `"Microsoft.AspNetCore.UseStreamBasedJsonParsing"` AppContext switch to `true`. This should be a temporary workaround and the `JsonConverter`(s) should be updated to support `HasValueSequence`. + +```csharp +public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) +{ + var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; + // previous code +} +``` + +**Why it works**: acknowledges most users won't notice, identifies who *will* be affected, gives an immediate workaround, then the proper fix. Good template for "mostly invisible but has an edge case." + +--- + +## OpenIdConnectHandler support for Pushed Authorization Requests (PAR) + +> Source: [.NET 9 Preview 7 — ASP.NET Core](../../../../release-notes/9.0/preview/preview7/aspnetcore.md) + +Long — community contribution with extensive quoted motivation, configuration example, and operational guidance. + +> We'd like to thank @josephdecock from @DuendeSoftware for adding Pushed Authorization Requests (PAR) to ASP.NET Core's `OpenIdConnectHandler`. Joe described the background and motivation for enabling PAR in [his API proposal](https://github.com/dotnet/aspnetcore/issues/51686) as follows: +> +> > Pushed Authorization Requests (PAR) is a relatively new [OAuth standard](https://datatracker.ietf.org/doc/html/rfc9126) that improves the security of OAuth and OIDC flows by moving authorization parameters from the front channel to the back channel (that is, from redirect URLs in the browser to direct machine to machine http calls on the back end). +> > +> > This prevents an attacker in the browser from: +> > +> > - Seeing authorization parameters (which could leak PII) and from +> > - Tampering with those parameters (e.g., the attacker could change the scope of access being requested). +> > +> > The use of PAR is encouraged by the [FAPI working group](https://openid.net/wg/fapi/) within the OpenID Foundation. + +> PAR is now enabled by default if the identity provider's discovery document advertises support for it. This change should provide enhanced security for providers that support PAR. If this causes problems, disable PAR via `OpenIdConnectOptions.PushedAuthorizationBehavior`: + +```csharp +builder.Services + .AddAuthentication(options => + { + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; + }) + .AddCookie() + .AddOpenIdConnect(oidcOptions => + { + // The default value is PushedAuthorizationBehavior.UseIfAvailable. + oidcOptions.PushedAuthorizationBehavior = PushedAuthorizationBehavior.Disable; + }); +``` + +> To ensure that authentication only succeeds if PAR is used, use `PushedAuthorizationBehavior.Require`. + +**Why it works**: the block quote lets the contributor explain the significance in their own words — more credible than the agent paraphrasing. Then practical: default behavior, how to disable, how to require. Good template for features with security or compliance significance. diff --git a/.github/skills/release-notes/references/examples/csharp.md b/.github/skills/release-notes/references/examples/csharp.md new file mode 100644 index 0000000000..5e84e5b4be --- /dev/null +++ b/.github/skills/release-notes/references/examples/csharp.md @@ -0,0 +1,32 @@ +# C# Examples + +## Extension operators + +> Source: [.NET 10 Preview 7 — C#](../../../../release-notes/10.0/preview/preview7/csharp.md) + +Medium — one sentence of context, then a code block showing the new syntax, then rule bullets. Code-first because the syntax *is* the feature. + +> The new extension blocks now include support for *operators*, with the exception of implicit and explicit conversion operators. You can declare operators in extension blocks, as shown in the following example: + +```csharp +public static class Operators +{ + extension(TElement[] source) where TElement : INumber + { + public static TElement[] operator *(TElement[] vector, TElement scalar) { ... } + public static TElement[] operator *(TElement scalar, TElement[] vector) { ... } + public void operator *=(TElement scalar) { ... } + public static bool operator ==(TElement[] left, TElement[] right) { ... } + public static bool operator !=(TElement[] left, TElement[] right) { ... } + } +} +``` + +> Several of the rules for extension operators are demonstrated in the preceding example: +> +> - At least one of the operands must be the *extended type*, `TElement[]` in the preceding example. +> - For operators that require pair-wise declarations, such as `==` and `!=`, both operators must be declared in the same static class. +> - Instance compound assignment operators, and instance increment and decrement operators are expected to mutate the current instance. +> - Extension operators, like other extension members, can't use the `abstract`, `virtual`, `override`, or `sealed` modifiers. + +**Why it works**: shows the code immediately — language features need to be seen, not just described. The rule bullets follow naturally as "here's what you need to know." No motivation section needed because C# developers will already understand why extension operators are useful. diff --git a/.github/skills/release-notes/references/examples/libraries.md b/.github/skills/release-notes/references/examples/libraries.md new file mode 100644 index 0000000000..6046373b12 --- /dev/null +++ b/.github/skills/release-notes/references/examples/libraries.md @@ -0,0 +1,55 @@ +# Libraries Examples + +## Post-Quantum Cryptography Updates + +> Source: [.NET 10 Preview 7 — Libraries](../../../../release-notes/10.0/preview/preview7/libraries.md) + +Long — subheadings, diff-style before/after, multiple API examples spanning two related features. + +> ### ML-DSA +> +> The `System.Security.Cryptography.MLDsa` class gained ease-of-use updates in this release, allowing some common code patterns to be simplified: + +```diff +private static byte[] SignData(string privateKeyPath, ReadOnlySpan data) +{ + using (MLDsa signingKey = MLDsa.ImportFromPem(File.ReadAllBytes(privateKeyPath))) + { +- byte[] signature = new byte[signingKey.Algorithm.SignatureSizeInBytes]; +- signingKey.SignData(data, signature); ++ return signingKey.SignData(data); +- return signature; + } +} +``` + +> Additionally, this release added support for HashML-DSA, which we call "PreHash" to help distinguish it from "pure" ML-DSA. + +```csharp +private static byte[] SignPreHashSha3_256(MLDsa signingKey, ReadOnlySpan data) +{ + const string Sha3_256Oid = "2.16.840.1.101.3.4.2.8"; + return signingKey.SignPreHash(SHA3_256.HashData(data), Sha3_256Oid); +} +``` + +> ### Composite ML-DSA +> +> This release also introduces new types to support ietf-lamps-pq-composite-sigs (currently at draft 7), and an implementation of the primitive methods for RSA variants. + +```csharp +var algorithm = CompositeMLDsaAlgorithm.MLDsa65WithRSA4096Pss; +using var privateKey = CompositeMLDsa.GenerateKey(algorithm); + +byte[] data = [42]; +byte[] signature = privateKey.SignData(data); + +using var publicKey = CompositeMLDsa.ImportCompositeMLDsaPublicKey( + algorithm, privateKey.ExportCompositeMLDsaPublicKey()); +Console.WriteLine(publicKey.VerifyData(data, signature)); // True + +signature[0] ^= 1; // Tamper with signature +Console.WriteLine(publicKey.VerifyData(data, signature)); // False +``` + +**Why it works**: the `diff` block for ML-DSA immediately shows the simplification — red lines removed, green line added. Subheadings organize related-but-distinct features. The Composite ML-DSA sample is a complete, runnable scenario (generate → sign → verify → tamper → verify-fails). diff --git a/.github/skills/release-notes/references/examples/runtime.md b/.github/skills/release-notes/references/examples/runtime.md new file mode 100644 index 0000000000..06e91c6576 --- /dev/null +++ b/.github/skills/release-notes/references/examples/runtime.md @@ -0,0 +1,113 @@ +# Runtime Examples + +## Array Enumeration De-Abstraction + +> Source: [.NET 10 Preview 2 — Runtime](../../../../release-notes/10.0/preview/preview2/runtime.md) + +Progressive benchmark narrative. Starts simple, escalates to harder cases, walks through each optimization layer with measured results. + +```csharp +public class ArrayDeAbstraction +{ + static readonly int[] array = new int[512]; + + [Benchmark(Baseline = true)] + public int foreach_static_readonly_array() + { + int sum = 0; + foreach (int i in array) sum += i; + return sum; + } + + [Benchmark] + public int foreach_static_readonly_array_via_interface() + { + IEnumerable o = array; + int sum = 0; + foreach (int i in o) sum += i; + return sum; + } +} +``` + +| Method | Mean | Ratio | Allocated | +|------------------------------------------------------------- |-----------:|------:|----------:| +| foreach_static_readonly_array (.NET 9) | 150.8 ns | 1.00 | - | +| foreach_static_readonly_array_via_interface (.NET 9) | 851.8 ns | 5.65 | 32 B | + +Thanks to improvements to the JIT's inlining, stack allocation, and loop cloning abilities (all of which are detailed in [dotnet/runtime #108913](https://github.com/dotnet/runtime/issues/108913)), the object allocation is gone, and runtime impact has been reduced substantially: + +| Method | Mean | Ratio | Allocated | +|------------------------------------------------------------- |-----------:|------:|----------:| +| foreach_static_readonly_array (.NET 9) | 150.8 ns | 1.00 | - | +| foreach_static_readonly_array_via_interface (.NET 10) | 280.0 ns | 1.86 | - | + +**Why it works**: multiple benchmark tables tell a progressive story — problem, partial fix, harder problem, better fix. Each table shows measurable improvement. The reader follows the JIT's reasoning. + +--- + +## Improved Code Generation for Struct Arguments + +> Source: [.NET 10 Preview 6 — Runtime](../../../../release-notes/10.0/preview/preview6/runtime.md) + +Before/after assembly comparison. Shows the C# code, the optimal case, the pathological case, and the fix — all through `asm` blocks. + +```csharp +struct Point +{ + public int X; + public int Y; + public Point(int x, int y) { X = x; Y = y; } +} + +[MethodImpl(MethodImplOptions.NoInlining)] +private static void Consume(Point p) => Console.WriteLine(p.X + p.Y); + +private static void Main() +{ + Point p = new Point(10, 20); + Consume(p); +} +``` + +Before — struct fields spill to stack then reload: + +```asm +Program:Main() (FullOpts): + push rax + mov dword ptr [rsp], 10 + mov dword ptr [rsp+0x04], 20 + mov rdi, qword ptr [rsp] + call [Program:Consume(Program+Point)] + nop + add rsp, 8 + ret +``` + +After — fields packed directly into a register: + +```asm +Program:Main() (FullOpts): + mov rdi, 0x140000000A + tail.jmp [Program:Consume(Program+Point)] +``` + +**Why it works**: the reader can count the instructions eliminated. No prose needed to explain the improvement — the assembly speaks for itself. + +--- + +## JIT Loop Optimizations + +> Source: [.NET 9 Preview 1 — Runtime](../../../../release-notes/9.0/preview/preview1/runtime.md) + +Metric-heavy prose, no code. Three optimization categories with measured improvements. + +> RyuJIT already supports multiple powerful loop optimizations, and we plan to expand these capabilities for .NET 9. For Preview 1, we've focused on improving the applicability of existing optimizations by refactoring how loops are represented in RyuJIT. This new graph-based representation is simpler and more effective than the old lexical representation, enabling RyuJIT to recognize -- and thus optimize -- more loops. +> +> Here's a quick breakdown of the improvements: +> +> - **Loop hoisting** -- finds expressions that don't change in value as the containing loop iterates, and moves (or "hoists") the expressions to above the loop so they evaluate at most once. In our test collections, we saw up to 35.8% more hoisting performed with the new loop representation. +> - **Loop cloning** -- determines if a conditional check (like a bounds check on an array) inside a loop can be safely eliminated for some of its iterations, and creates a "fast" copy of the loop without the check. With the new loop representation, we saw up to 7.3% more loop cloning. +> - **Loop alignment** -- improves instruction cache performance by adjusting the offset of a loop to begin at a cache line. With the new loop representation, we saw about 5% more loops aligned across our test collections. + +**Why it works**: sells infrastructure improvements through percentages. No code needed because the value is breadth of impact, not a single dramatic before/after. diff --git a/.github/skills/release-notes/references/examples/sdk.md b/.github/skills/release-notes/references/examples/sdk.md new file mode 100644 index 0000000000..84d034b265 --- /dev/null +++ b/.github/skills/release-notes/references/examples/sdk.md @@ -0,0 +1,20 @@ +# SDK Examples + +## Container publishing improvements for insecure registries + +> Source: [.NET 9 Preview 7 — SDK](../../../../release-notes/9.0/preview/preview7/sdk.md) + +Short — problem/solution framing with community attribution and a tight requirements list. + +> The SDK's built-in container publishing support can publish images to container registries, but until this release those registries were required to be secured - they needed HTTPS support and valid certificates for the .NET SDK to work. +> Container engines can usually be configured to work with insecure registries as well - meaning registries that do not have TLS configured, or have TLS configured with a certificate that is invalid from the perspective of the container engine. This is a valid use case, but our tooling didn't support this mode of communication. +> +> In this release, [@tmds](https://github.com/tmds) enabled the SDK [to communicate with insecure registries](https://github.com/dotnet/sdk/pull/41506). +> +> Requirements (depending on your environment): +> +> * [Configure the Docker CLI to mark a registry as insecure](https://docs.docker.com/reference/cli/dockerd/#insecure-registries) +> * [Configure Podman to mark a registry as insecure](https://podman-desktop.io/docs/containers/registries) +> * Use the `DOTNET_CONTAINER_INSECURE_REGISTRIES` environment variable to pass a semicolon-delimited list of registry domains to treat as insecure + +**Why it works**: the community contributor is credited naturally mid-sentence. The requirements list gives actionable next steps without over-explaining. The reader knows exactly what changed and what to do. diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index 17417cf53f..f2a01eae94 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -69,7 +69,7 @@ Read these files from `.github/skills/release-notes/references/` for detailed gu - **component-mapping.md** — VMR paths → components → product slugs → output files - **format-template.md** — markdown document structure - **editorial-rules.md** — tone, attribution, naming conventions -- **examples.md** — curated examples from previous releases showing short, medium, and long-form styles +- **examples/** — curated examples from previous releases, organized by component ([README](examples/README.md) has principles) ## What to do each run From d0251eb65e4cb11946d8dcec7dd3417ddb37b1dc Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 21:12:44 -0700 Subject: [PATCH 18/65] Reformat examples: content first, metadata in footer blocks New format per file: ## Heading (H2) Actual content (copy/paste from original) --- Source: link to original Commentary: why it was selected Why it works: what makes it effective --- Clear separation between the example content and the editorial commentary. Content reads exactly as it would in release notes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../references/examples/aspnetcore.md | 67 ++++++++++--------- .../references/examples/csharp.md | 27 ++++---- .../references/examples/libraries.md | 23 ++++--- .../references/examples/runtime.md | 49 ++++++++------ .../release-notes/references/examples/sdk.md | 27 ++++---- 5 files changed, 104 insertions(+), 89 deletions(-) diff --git a/.github/skills/release-notes/references/examples/aspnetcore.md b/.github/skills/release-notes/references/examples/aspnetcore.md index ebb859d826..3d0fc280f9 100644 --- a/.github/skills/release-notes/references/examples/aspnetcore.md +++ b/.github/skills/release-notes/references/examples/aspnetcore.md @@ -2,31 +2,28 @@ ## Configure suppressing exception handler diagnostics -> Source: [.NET 10 Preview 7 — ASP.NET Core](../../../../release-notes/10.0/preview/preview7/aspnetcore.md) +A new configuration option has been added to the [ASP.NET Core exception handler middleware](https://learn.microsoft.com/aspnet/core/fundamentals/error-handling#exception-handler-page) to control diagnostic output: `ExceptionHandlerOptions.SuppressDiagnosticsCallback`. This callback is passed context about the request and exception, allowing you to add logic that determines whether the middleware should write exception logs and other telemetry. -Short — three sentences covering the new option, the use case, and a behavior change. No code needed. +This setting is useful when you know an exception is transient, or has been handled by the exception handler middleware, and don't want the error logs written to your observability platform. -> A new configuration option has been added to the [ASP.NET Core exception handler middleware](https://learn.microsoft.com/aspnet/core/fundamentals/error-handling#exception-handler-page) to control diagnostic output: `ExceptionHandlerOptions.SuppressDiagnosticsCallback`. This callback is passed context about the request and exception, allowing you to add logic that determines whether the middleware should write exception logs and other telemetry. -> -> This setting is useful when you know an exception is transient, or has been handled by the exception handler middleware, and don't want the error logs written to your observability platform. -> -> Additionally, the middleware's default behavior has changed: it no longer writes exception diagnostics for exceptions handled by `IExceptionHandler`. Based on user feedback, logging handled exceptions at the error level was often undesirable when `IExceptionHandler.TryHandleAsync` returned `true`. +Additionally, the middleware's default behavior has changed: it no longer writes exception diagnostics for exceptions handled by `IExceptionHandler`. Based on user feedback, logging handled exceptions at the error level was often undesirable when `IExceptionHandler.TryHandleAsync` returned `true`. -**Why it works**: concise problem/solution. The reader immediately knows what changed, why, and whether they're affected. +--- +Source: [.NET 10 Preview 7 — ASP.NET Core](../../../../release-notes/10.0/preview/preview7/aspnetcore.md) +Commentary: Short and focused — three sentences covering the new option, the use case, and a behavior change. No code needed. +Why it works: The reader immediately knows what changed, why, and whether they're affected. --- ## Use PipeReader support in System.Text.Json -> Source: [.NET 10 Preview 7 — ASP.NET Core](../../../../release-notes/10.0/preview/preview7/aspnetcore.md) +MVC, Minimal APIs, and the `HttpRequestJsonExtensions.ReadFromJsonAsync` methods have all been updated to use the new PipeReader support in System.Text.Json without requiring any code changes from applications. -Medium — behavioral change with workaround guidance and two code samples (quick fix and optimal fix). +For the majority of applications this should have no impact on behavior. However, if the application is using a custom `JsonConverter`, there is a chance that the converter doesn't handle [Utf8JsonReader.HasValueSequence](https://learn.microsoft.com/dotnet/api/system.text.json.utf8jsonreader.hasvaluesequence) correctly. This can result in missing data and errors like `ArgumentOutOfRangeException` when deserializing. -> MVC, Minimal APIs, and the `HttpRequestJsonExtensions.ReadFromJsonAsync` methods have all been updated to use the new PipeReader support in System.Text.Json without requiring any code changes from applications. -> -> For the majority of applications this should have no impact on behavior. However, if the application is using a custom `JsonConverter`, there is a chance that the converter doesn't handle `Utf8JsonReader.HasValueSequence` correctly. This can result in missing data and errors like `ArgumentOutOfRangeException` when deserializing. -> -> The quick workaround (especially if you don't own the custom `JsonConverter` being used) is to set the `"Microsoft.AspNetCore.UseStreamBasedJsonParsing"` AppContext switch to `true`. This should be a temporary workaround and the `JsonConverter`(s) should be updated to support `HasValueSequence`. +The quick workaround (especially if you don't own the custom `JsonConverter` being used) is to set the `"Microsoft.AspNetCore.UseStreamBasedJsonParsing"` [AppContext](https://learn.microsoft.com/dotnet/api/system.appcontext) switch to `true`. This should be a temporary workaround and the `JsonConverter`(s) should be updated to support `HasValueSequence`. + +To fix `JsonConverter` implementations, there is the quick fix which allocates an array from the `ReadOnlySequence`: ```csharp public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) @@ -36,28 +33,27 @@ public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSeria } ``` -**Why it works**: acknowledges most users won't notice, identifies who *will* be affected, gives an immediate workaround, then the proper fix. Good template for "mostly invisible but has an edge case." - --- +Source: [.NET 10 Preview 7 — ASP.NET Core](../../../../release-notes/10.0/preview/preview7/aspnetcore.md) +Commentary: Medium — behavioral change that's mostly invisible but has an edge case. Gives the workaround and the proper fix. +Why it works: Acknowledges most users won't notice, identifies who *will* be affected, gives an immediate workaround, then the proper fix. -## OpenIdConnectHandler support for Pushed Authorization Requests (PAR) +--- -> Source: [.NET 9 Preview 7 — ASP.NET Core](../../../../release-notes/9.0/preview/preview7/aspnetcore.md) +## `OpenIdConnectHandler` support for Pushed Authorization Requests (PAR) -Long — community contribution with extensive quoted motivation, configuration example, and operational guidance. +We'd like to thank @josephdecock from @DuendeSoftware for adding Pushed Authorization Requests (PAR) to ASP.NET Core's `OpenIdConnectHandler`. Joe described the background and motivation for enabling PAR in [his API proposal](https://github.com/dotnet/aspnetcore/issues/51686) as follows: -> We'd like to thank @josephdecock from @DuendeSoftware for adding Pushed Authorization Requests (PAR) to ASP.NET Core's `OpenIdConnectHandler`. Joe described the background and motivation for enabling PAR in [his API proposal](https://github.com/dotnet/aspnetcore/issues/51686) as follows: +> Pushed Authorization Requests (PAR) is a relatively new [OAuth standard](https://datatracker.ietf.org/doc/html/rfc9126) that improves the security of OAuth and OIDC flows by moving authorization parameters from the front channel to the back channel (that is, from redirect URLs in the browser to direct machine to machine http calls on the back end). +> +> This prevents an attacker in the browser from: > -> > Pushed Authorization Requests (PAR) is a relatively new [OAuth standard](https://datatracker.ietf.org/doc/html/rfc9126) that improves the security of OAuth and OIDC flows by moving authorization parameters from the front channel to the back channel (that is, from redirect URLs in the browser to direct machine to machine http calls on the back end). -> > -> > This prevents an attacker in the browser from: -> > -> > - Seeing authorization parameters (which could leak PII) and from -> > - Tampering with those parameters (e.g., the attacker could change the scope of access being requested). -> > -> > The use of PAR is encouraged by the [FAPI working group](https://openid.net/wg/fapi/) within the OpenID Foundation. +> - Seeing authorization parameters (which could leak PII) and from +> - Tampering with those parameters (e.g., the attacker could change the scope of access being requested). +> +> The use of PAR is encouraged by the [FAPI working group](https://openid.net/wg/fapi/) within the OpenID Foundation. -> PAR is now enabled by default if the identity provider's discovery document advertises support for it. This change should provide enhanced security for providers that support PAR. If this causes problems, disable PAR via `OpenIdConnectOptions.PushedAuthorizationBehavior`: +PAR is now enabled by default if the identity provider's discovery document advertises support for it. The identity provider's discovery document is usually found at `.well-known/openid-configuration`. This change should provide enhanced security for providers that support PAR. If this causes problems, disable PAR via `OpenIdConnectOptions.PushedAuthorizationBehavior` as follows: ```csharp builder.Services @@ -74,6 +70,13 @@ builder.Services }); ``` -> To ensure that authentication only succeeds if PAR is used, use `PushedAuthorizationBehavior.Require`. +To ensure that authentication only succeeds if PAR is used, use `PushedAuthorizationBehavior.Require`. + +This change also introduces a new `OnPushAuthorization` event to `OpenIdConnectEvents` which can be used to customize the pushed authorization request or handle it manually. Refer to the [API proposal](https://github.com/dotnet/aspnetcore/issues/51686) for more details. -**Why it works**: the block quote lets the contributor explain the significance in their own words — more credible than the agent paraphrasing. Then practical: default behavior, how to disable, how to require. Good template for features with security or compliance significance. +--- +Source: [.NET 9 Preview 7 — ASP.NET Core](../../../../release-notes/9.0/preview/preview7/aspnetcore.md) +Commentary: Long — community contribution with the contributor's own words explaining significance. Good template for features with security or compliance importance. +Why it works: The block quote lets the contributor explain the significance — more credible than paraphrasing. Then practical: default behavior, how to disable, how to require. + +--- diff --git a/.github/skills/release-notes/references/examples/csharp.md b/.github/skills/release-notes/references/examples/csharp.md index 5e84e5b4be..125c09f845 100644 --- a/.github/skills/release-notes/references/examples/csharp.md +++ b/.github/skills/release-notes/references/examples/csharp.md @@ -2,11 +2,7 @@ ## Extension operators -> Source: [.NET 10 Preview 7 — C#](../../../../release-notes/10.0/preview/preview7/csharp.md) - -Medium — one sentence of context, then a code block showing the new syntax, then rule bullets. Code-first because the syntax *is* the feature. - -> The new extension blocks now include support for *operators*, with the exception of implicit and explicit conversion operators. You can declare operators in extension blocks, as shown in the following example: +The new extension blocks now include support for *operators*, with the exception of implicit and explicit conversion operators. You can declare operators in extension blocks, as shown in the following example: ```csharp public static class Operators @@ -22,11 +18,18 @@ public static class Operators } ``` -> Several of the rules for extension operators are demonstrated in the preceding example: -> -> - At least one of the operands must be the *extended type*, `TElement[]` in the preceding example. -> - For operators that require pair-wise declarations, such as `==` and `!=`, both operators must be declared in the same static class. -> - Instance compound assignment operators, and instance increment and decrement operators are expected to mutate the current instance. -> - Extension operators, like other extension members, can't use the `abstract`, `virtual`, `override`, or `sealed` modifiers. +Several of the rules for extension operators are demonstrated in the preceding example: + +- At least one of the operands must be the *extended type*, `TElement[]` in the preceding example. +- For operators that require pair-wise declarations, such as `==` and `!=` in the preceding example, both operators must be declared in the same static class. They are not required to be in the same `extension` container. +- Instance compound assignment operators, and instance increment and decrement operators are expected to mutate the current instance. Therefore reference type parameters must be passed by value and value type parameters must be passed by `ref`. These operators can't be declared when the extended type is an unconstrained type parameter. +- Extension operators, like other extension members, can't use the `abstract`, `virtual`, `override`, or `sealed` modifiers. This is consistent with other extension members. + +Extension members are only considered for overload resolution when no applicable predefined or user defined non-extension members are found. + +--- +Source: [.NET 10 Preview 7 — C#](../../../../release-notes/10.0/preview/preview7/csharp.md) +Commentary: Code-first — one sentence of context, then the syntax, then rule bullets. Language features need to be seen, not just described. +Why it works: Shows the code immediately. The rule bullets follow naturally as "here's what you need to know." No motivation section needed. -**Why it works**: shows the code immediately — language features need to be seen, not just described. The rule bullets follow naturally as "here's what you need to know." No motivation section needed because C# developers will already understand why extension operators are useful. +--- diff --git a/.github/skills/release-notes/references/examples/libraries.md b/.github/skills/release-notes/references/examples/libraries.md index 6046373b12..0e52d39837 100644 --- a/.github/skills/release-notes/references/examples/libraries.md +++ b/.github/skills/release-notes/references/examples/libraries.md @@ -2,13 +2,9 @@ ## Post-Quantum Cryptography Updates -> Source: [.NET 10 Preview 7 — Libraries](../../../../release-notes/10.0/preview/preview7/libraries.md) +### ML-DSA -Long — subheadings, diff-style before/after, multiple API examples spanning two related features. - -> ### ML-DSA -> -> The `System.Security.Cryptography.MLDsa` class gained ease-of-use updates in this release, allowing some common code patterns to be simplified: +The `System.Security.Cryptography.MLDsa` class gained ease-of-use updates in this release, allowing some common code patterns to be simplified: ```diff private static byte[] SignData(string privateKeyPath, ReadOnlySpan data) @@ -23,7 +19,7 @@ private static byte[] SignData(string privateKeyPath, ReadOnlySpan data) } ``` -> Additionally, this release added support for HashML-DSA, which we call "PreHash" to help distinguish it from "pure" ML-DSA. +Additionally, this release added support for HashML-DSA, which we call "PreHash" to help distinguish it from "pure" ML-DSA. As the underlying specification interacts with the Object Identifier (OID) value, the SignPreHash and VerifyPreHash methods on this `[Experimental]` type take the dotted-decimal OID as a string. This may evolve as more scenarios using HashML-DSA become well-defined. ```csharp private static byte[] SignPreHashSha3_256(MLDsa signingKey, ReadOnlySpan data) @@ -33,9 +29,9 @@ private static byte[] SignPreHashSha3_256(MLDsa signingKey, ReadOnlySpan d } ``` -> ### Composite ML-DSA -> -> This release also introduces new types to support ietf-lamps-pq-composite-sigs (currently at draft 7), and an implementation of the primitive methods for RSA variants. +### Composite ML-DSA + +This release also introduces new types to support ietf-lamps-pq-composite-sigs (currently at draft 7), and an implementation of the primitive methods for RSA variants. ```csharp var algorithm = CompositeMLDsaAlgorithm.MLDsa65WithRSA4096Pss; @@ -52,4 +48,9 @@ signature[0] ^= 1; // Tamper with signature Console.WriteLine(publicKey.VerifyData(data, signature)); // False ``` -**Why it works**: the `diff` block for ML-DSA immediately shows the simplification — red lines removed, green line added. Subheadings organize related-but-distinct features. The Composite ML-DSA sample is a complete, runnable scenario (generate → sign → verify → tamper → verify-fails). +--- +Source: [.NET 10 Preview 7 — Libraries](../../../../release-notes/10.0/preview/preview7/libraries.md) +Commentary: Long — subheadings organize related-but-distinct features. Uses `diff` blocks for API simplification and complete runnable scenarios for new APIs. +Why it works: The `diff` block immediately shows the simplification (red lines removed, green line added). The Composite ML-DSA sample is a complete scenario (generate → sign → verify → tamper → verify-fails). + +--- diff --git a/.github/skills/release-notes/references/examples/runtime.md b/.github/skills/release-notes/references/examples/runtime.md index 06e91c6576..9780d4b203 100644 --- a/.github/skills/release-notes/references/examples/runtime.md +++ b/.github/skills/release-notes/references/examples/runtime.md @@ -2,9 +2,7 @@ ## Array Enumeration De-Abstraction -> Source: [.NET 10 Preview 2 — Runtime](../../../../release-notes/10.0/preview/preview2/runtime.md) - -Progressive benchmark narrative. Starts simple, escalates to harder cases, walks through each optimization layer with measured results. +Preview 1 brought enhancements to the JIT compiler's devirtualization abilities for array interface methods; this was our first step in reducing the abstraction overhead of array iteration via enumerators. Preview 2 continues this effort with improvements to many other optimizations. Consider the following benchmarks: ```csharp public class ArrayDeAbstraction @@ -30,6 +28,8 @@ public class ArrayDeAbstraction } ``` +In `foreach_static_readonly_array`, the type of `array` is transparent, so it is easy for the JIT to generate efficient code. In `foreach_static_readonly_array_via_interface`, the type of `array` is hidden behind an `IEnumerable`, introducing an object allocation and virtual calls for advancing and dereferencing the iterator. In .NET 9, this overhead impacts performance profoundly: + | Method | Mean | Ratio | Allocated | |------------------------------------------------------------- |-----------:|------:|----------:| | foreach_static_readonly_array (.NET 9) | 150.8 ns | 1.00 | - | @@ -42,15 +42,16 @@ Thanks to improvements to the JIT's inlining, stack allocation, and loop cloning | foreach_static_readonly_array (.NET 9) | 150.8 ns | 1.00 | - | | foreach_static_readonly_array_via_interface (.NET 10) | 280.0 ns | 1.86 | - | -**Why it works**: multiple benchmark tables tell a progressive story — problem, partial fix, harder problem, better fix. Each table shows measurable improvement. The reader follows the JIT's reasoning. +--- +Source: [.NET 10 Preview 2 — Runtime](../../../../release-notes/10.0/preview/preview2/runtime.md) +Commentary: Progressive benchmark narrative — the best style for JIT optimization stories. +Why it works: Multiple benchmark tables tell a story (problem → partial fix → harder problem → better fix). Each table shows measurable improvement. The reader follows the JIT's reasoning through escalating complexity. --- ## Improved Code Generation for Struct Arguments -> Source: [.NET 10 Preview 6 — Runtime](../../../../release-notes/10.0/preview/preview6/runtime.md) - -Before/after assembly comparison. Shows the C# code, the optimal case, the pathological case, and the fix — all through `asm` blocks. +.NET's JIT compiler is capable of an optimization called physical promotion, where the members of a struct are placed in registers rather than on the stack, eliminating memory accesses. This optimization is particularly useful when passing a struct to a method, and the calling convention requires the struct members to be passed in registers. Consider the following example: ```csharp struct Point @@ -70,7 +71,7 @@ private static void Main() } ``` -Before — struct fields spill to stack then reload: +Because `ints` are four bytes wide, and registers are eight bytes wide on x64, the calling convention requires us to pass the members of `Point` in one register. However, the JIT compiler's internal representation of struct members previously wasn't flexible enough to represent values that share a register. Thus, the JIT compiler would first store the values to memory, and then load the eight-byte chunk into a register: ```asm Program:Main() (FullOpts): @@ -84,7 +85,7 @@ Program:Main() (FullOpts): ret ``` -After — fields packed directly into a register: +Thanks to [dotnet/runtime #115977](https://github.com/dotnet/runtime/pull/115977), the JIT compiler can now place the promoted members of struct arguments into shared registers: ```asm Program:Main() (FullOpts): @@ -92,22 +93,28 @@ Program:Main() (FullOpts): tail.jmp [Program:Consume(Program+Point)] ``` -**Why it works**: the reader can count the instructions eliminated. No prose needed to explain the improvement — the assembly speaks for itself. +--- +Source: [.NET 10 Preview 6 — Runtime](../../../../release-notes/10.0/preview/preview6/runtime.md) +Commentary: Before/after assembly comparison — the gold standard for codegen improvements. +Why it works: The reader can count the instructions eliminated. The assembly speaks for itself — no prose needed to explain the magnitude of the improvement. --- -## JIT Loop Optimizations +## JIT: Loop Optimizations -> Source: [.NET 9 Preview 1 — Runtime](../../../../release-notes/9.0/preview/preview1/runtime.md) +RyuJIT already supports multiple powerful loop optimizations, and we plan to expand these capabilities for .NET 9. For Preview 1, we've focused on improving the applicability of existing optimizations by refactoring how loops are represented in RyuJIT. This new graph-based representation is simpler and more effective than the old lexical representation, enabling RyuJIT to recognize -- and thus optimize -- more loops. -Metric-heavy prose, no code. Three optimization categories with measured improvements. +Here's a quick breakdown of the improvements: -> RyuJIT already supports multiple powerful loop optimizations, and we plan to expand these capabilities for .NET 9. For Preview 1, we've focused on improving the applicability of existing optimizations by refactoring how loops are represented in RyuJIT. This new graph-based representation is simpler and more effective than the old lexical representation, enabling RyuJIT to recognize -- and thus optimize -- more loops. -> -> Here's a quick breakdown of the improvements: -> -> - **Loop hoisting** -- finds expressions that don't change in value as the containing loop iterates, and moves (or "hoists") the expressions to above the loop so they evaluate at most once. In our test collections, we saw up to 35.8% more hoisting performed with the new loop representation. -> - **Loop cloning** -- determines if a conditional check (like a bounds check on an array) inside a loop can be safely eliminated for some of its iterations, and creates a "fast" copy of the loop without the check. With the new loop representation, we saw up to 7.3% more loop cloning. -> - **Loop alignment** -- improves instruction cache performance by adjusting the offset of a loop to begin at a cache line. With the new loop representation, we saw about 5% more loops aligned across our test collections. +- **Loop hoisting** -- finds expressions that don't change in value as the containing loop iterates, and moves (or "hoists") the expressions to above the loop so they evaluate at most once. In our test collections, we saw up to 35.8% more hoisting performed with the new loop representation. +- **Loop cloning** -- determines if a conditional check (like a bounds check on an array) inside a loop can be safely eliminated for some of its iterations, and creates a "fast" copy of the loop without the check. With the new loop representation, we saw up to 7.3% more loop cloning. +- **Loop alignment** -- improves instruction cache performance by adjusting the offset of a loop to begin at a cache line. With the new loop representation, we saw about 5% more loops aligned across our test collections. -**Why it works**: sells infrastructure improvements through percentages. No code needed because the value is breadth of impact, not a single dramatic before/after. +This is just a snippet of the improvements RyuJIT's new loop representation brings. To take a closer look at the loop optimization work planned for .NET 9, check out [dotnet/runtime #93144](https://github.com/dotnet/runtime/issues/93144). + +--- +Source: [.NET 9 Preview 1 — Runtime](../../../../release-notes/9.0/preview/preview1/runtime.md) +Commentary: Metric-heavy prose with no code — good for infrastructure improvements where breadth of impact matters more than a single before/after. +Why it works: Percentages sell the improvement without needing code. Each bullet is self-contained. Links to the tracking issue for depth. + +--- diff --git a/.github/skills/release-notes/references/examples/sdk.md b/.github/skills/release-notes/references/examples/sdk.md index 84d034b265..fe972f4814 100644 --- a/.github/skills/release-notes/references/examples/sdk.md +++ b/.github/skills/release-notes/references/examples/sdk.md @@ -2,19 +2,20 @@ ## Container publishing improvements for insecure registries -> Source: [.NET 9 Preview 7 — SDK](../../../../release-notes/9.0/preview/preview7/sdk.md) +The SDK's built-in container publishing support can publish images to container registries, but until this release those registries were required to be secured - they needed HTTPS support and valid certificates for the .NET SDK to work. +Container engines can usually be configured to work with insecure registries as well - meaning registries that do not have TLS configured, or have TLS configured with a certificate that is invalid from the perspective of the container engine. This is a valid use case, but our tooling didn't support this mode of communication. -Short — problem/solution framing with community attribution and a tight requirements list. +In this release, [@tmds](https://github.com/tmds) enabled the SDK [to communicate with insecure registries](https://github.com/dotnet/sdk/pull/41506). -> The SDK's built-in container publishing support can publish images to container registries, but until this release those registries were required to be secured - they needed HTTPS support and valid certificates for the .NET SDK to work. -> Container engines can usually be configured to work with insecure registries as well - meaning registries that do not have TLS configured, or have TLS configured with a certificate that is invalid from the perspective of the container engine. This is a valid use case, but our tooling didn't support this mode of communication. -> -> In this release, [@tmds](https://github.com/tmds) enabled the SDK [to communicate with insecure registries](https://github.com/dotnet/sdk/pull/41506). -> -> Requirements (depending on your environment): -> -> * [Configure the Docker CLI to mark a registry as insecure](https://docs.docker.com/reference/cli/dockerd/#insecure-registries) -> * [Configure Podman to mark a registry as insecure](https://podman-desktop.io/docs/containers/registries) -> * Use the `DOTNET_CONTAINER_INSECURE_REGISTRIES` environment variable to pass a semicolon-delimited list of registry domains to treat as insecure +Requirements (depending on your environment): -**Why it works**: the community contributor is credited naturally mid-sentence. The requirements list gives actionable next steps without over-explaining. The reader knows exactly what changed and what to do. +* [Configure the Docker CLI to mark a registry as insecure](https://docs.docker.com/reference/cli/dockerd/#insecure-registries) +* [Configure Podman to mark a registry as insecure](https://podman-desktop.io/docs/containers/registries) +* Use the `DOTNET_CONTAINER_INSECURE_REGISTRIES` environment variable to pass a semicolon-delimited list of registry domains to treat as insecure + +--- +Source: [.NET 9 Preview 7 — SDK](../../../../release-notes/9.0/preview/preview7/sdk.md) +Commentary: Short problem/solution with community attribution and a tight requirements list. +Why it works: The community contributor is credited naturally mid-sentence. The requirements list gives actionable next steps without over-explaining. + +--- From c23dd4e044649bef8f60bc7ef7d7ce483e5395c4 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 21:23:05 -0700 Subject: [PATCH 19/65] Update for dotnet-release v2.1.0; fix example formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - id field is now string (repo@commit format) — globally unique - Updated schema docs and example JSON - Removed trailing blank lines in example metadata blocks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/references/changes-schema.md | 8 ++++---- .../release-notes/references/examples/aspnetcore.md | 3 --- .../skills/release-notes/references/examples/csharp.md | 1 - .../skills/release-notes/references/examples/libraries.md | 1 - .../skills/release-notes/references/examples/runtime.md | 3 --- .github/skills/release-notes/references/examples/sdk.md | 1 - 6 files changed, 4 insertions(+), 13 deletions(-) diff --git a/.github/skills/release-notes/references/changes-schema.md b/.github/skills/release-notes/references/changes-schema.md index fb45bdf277..ce095737b2 100644 --- a/.github/skills/release-notes/references/changes-schema.md +++ b/.github/skills/release-notes/references/changes-schema.md @@ -28,14 +28,14 @@ release-notes/{major.minor}/{major.minor.patch}/changes.json # patches | Field | Type | Description | | ----- | ---- | ----------- | -| `id` | int | GitHub PR number; `0` if no public PR | +| `id` | string | Globally unique identifier — `repo@shortcommit` format (e.g., `"runtime@c5d5be4"`) | | `repo` | string | Short repository name (e.g., `"runtime"`) | | `product` | string | Product slug (e.g., `"dotnet-runtime"`); absent for infra repos | | `title` | string | PR title; `""` if not available | | `url` | string | Public GitHub PR URL; `""` if non-public | | `commit` | string | Key into top-level `commits{}` dict — the VMR (`dotnet/dotnet`) codeflow commit | | `is_security` | bool | `true` if this is a security change | -| `local_repo_commit` | string | Key into `commits{}` — the source repo commit (e.g., `"runtime@c5d5be4"`) | +| `local_repo_commit` | string | Key into `commits{}` — the source repo commit (same as `id`) | | `labels` | array | PR labels (only present when `--labels` is used) | The `product` field is derived from the repo-level [component mapping](component-mapping.md). Infra repos like `arcade` and `symreader` have no `product` field. The `repo` field always matches the VMR manifest path. @@ -67,7 +67,7 @@ The `commit` field is the VMR codeflow commit in `dotnet/dotnet` that synced thi "release_date": "", "changes": [ { - "id": 112345, + "id": "runtime@b2d5fa8", "repo": "runtime", "product": "dotnet-runtime", "title": "Add JsonSerializerOptions.Web preset", @@ -77,7 +77,7 @@ The `commit` field is the VMR codeflow commit in `dotnet/dotnet` that synced thi "local_repo_commit": "runtime@b2d5fa8" }, { - "id": 54321, + "id": "aspnetcore@f45f3c9", "repo": "aspnetcore", "product": "dotnet-aspnetcore", "title": "Add MapStaticAssets middleware", diff --git a/.github/skills/release-notes/references/examples/aspnetcore.md b/.github/skills/release-notes/references/examples/aspnetcore.md index 3d0fc280f9..a8f9b9d410 100644 --- a/.github/skills/release-notes/references/examples/aspnetcore.md +++ b/.github/skills/release-notes/references/examples/aspnetcore.md @@ -12,7 +12,6 @@ Additionally, the middleware's default behavior has changed: it no longer writes Source: [.NET 10 Preview 7 — ASP.NET Core](../../../../release-notes/10.0/preview/preview7/aspnetcore.md) Commentary: Short and focused — three sentences covering the new option, the use case, and a behavior change. No code needed. Why it works: The reader immediately knows what changed, why, and whether they're affected. - --- ## Use PipeReader support in System.Text.Json @@ -37,7 +36,6 @@ public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSeria Source: [.NET 10 Preview 7 — ASP.NET Core](../../../../release-notes/10.0/preview/preview7/aspnetcore.md) Commentary: Medium — behavioral change that's mostly invisible but has an edge case. Gives the workaround and the proper fix. Why it works: Acknowledges most users won't notice, identifies who *will* be affected, gives an immediate workaround, then the proper fix. - --- ## `OpenIdConnectHandler` support for Pushed Authorization Requests (PAR) @@ -78,5 +76,4 @@ This change also introduces a new `OnPushAuthorization` event to `OpenIdConnectE Source: [.NET 9 Preview 7 — ASP.NET Core](../../../../release-notes/9.0/preview/preview7/aspnetcore.md) Commentary: Long — community contribution with the contributor's own words explaining significance. Good template for features with security or compliance importance. Why it works: The block quote lets the contributor explain the significance — more credible than paraphrasing. Then practical: default behavior, how to disable, how to require. - --- diff --git a/.github/skills/release-notes/references/examples/csharp.md b/.github/skills/release-notes/references/examples/csharp.md index 125c09f845..919754b4a6 100644 --- a/.github/skills/release-notes/references/examples/csharp.md +++ b/.github/skills/release-notes/references/examples/csharp.md @@ -31,5 +31,4 @@ Extension members are only considered for overload resolution when no applicable Source: [.NET 10 Preview 7 — C#](../../../../release-notes/10.0/preview/preview7/csharp.md) Commentary: Code-first — one sentence of context, then the syntax, then rule bullets. Language features need to be seen, not just described. Why it works: Shows the code immediately. The rule bullets follow naturally as "here's what you need to know." No motivation section needed. - --- diff --git a/.github/skills/release-notes/references/examples/libraries.md b/.github/skills/release-notes/references/examples/libraries.md index 0e52d39837..a35d1ef7ca 100644 --- a/.github/skills/release-notes/references/examples/libraries.md +++ b/.github/skills/release-notes/references/examples/libraries.md @@ -52,5 +52,4 @@ Console.WriteLine(publicKey.VerifyData(data, signature)); // False Source: [.NET 10 Preview 7 — Libraries](../../../../release-notes/10.0/preview/preview7/libraries.md) Commentary: Long — subheadings organize related-but-distinct features. Uses `diff` blocks for API simplification and complete runnable scenarios for new APIs. Why it works: The `diff` block immediately shows the simplification (red lines removed, green line added). The Composite ML-DSA sample is a complete scenario (generate → sign → verify → tamper → verify-fails). - --- diff --git a/.github/skills/release-notes/references/examples/runtime.md b/.github/skills/release-notes/references/examples/runtime.md index 9780d4b203..f57f9e4629 100644 --- a/.github/skills/release-notes/references/examples/runtime.md +++ b/.github/skills/release-notes/references/examples/runtime.md @@ -46,7 +46,6 @@ Thanks to improvements to the JIT's inlining, stack allocation, and loop cloning Source: [.NET 10 Preview 2 — Runtime](../../../../release-notes/10.0/preview/preview2/runtime.md) Commentary: Progressive benchmark narrative — the best style for JIT optimization stories. Why it works: Multiple benchmark tables tell a story (problem → partial fix → harder problem → better fix). Each table shows measurable improvement. The reader follows the JIT's reasoning through escalating complexity. - --- ## Improved Code Generation for Struct Arguments @@ -97,7 +96,6 @@ Program:Main() (FullOpts): Source: [.NET 10 Preview 6 — Runtime](../../../../release-notes/10.0/preview/preview6/runtime.md) Commentary: Before/after assembly comparison — the gold standard for codegen improvements. Why it works: The reader can count the instructions eliminated. The assembly speaks for itself — no prose needed to explain the magnitude of the improvement. - --- ## JIT: Loop Optimizations @@ -116,5 +114,4 @@ This is just a snippet of the improvements RyuJIT's new loop representation brin Source: [.NET 9 Preview 1 — Runtime](../../../../release-notes/9.0/preview/preview1/runtime.md) Commentary: Metric-heavy prose with no code — good for infrastructure improvements where breadth of impact matters more than a single before/after. Why it works: Percentages sell the improvement without needing code. Each bullet is self-contained. Links to the tracking issue for depth. - --- diff --git a/.github/skills/release-notes/references/examples/sdk.md b/.github/skills/release-notes/references/examples/sdk.md index fe972f4814..c35090bd91 100644 --- a/.github/skills/release-notes/references/examples/sdk.md +++ b/.github/skills/release-notes/references/examples/sdk.md @@ -17,5 +17,4 @@ Requirements (depending on your environment): Source: [.NET 9 Preview 7 — SDK](../../../../release-notes/9.0/preview/preview7/sdk.md) Commentary: Short problem/solution with community attribution and a tight requirements list. Why it works: The community contributor is credited naturally mid-sentence. The requirements list gives actionable next steps without over-explaining. - --- From 9c0d47ae415ebbf0ddcbd518f5b200b77859606c Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 21:24:14 -0700 Subject: [PATCH 20/65] Run every 6 hours instead of daily More frequent runs mean faster feedback loops on PR comments and quicker pickup of new VMR content. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/release-notes.lock.yml | 38 ++++++++++++------------ .github/workflows/release-notes.md | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/release-notes.lock.yml b/.github/workflows/release-notes.lock.yml index a3ed446b4b..023df3f6f2 100644 --- a/.github/workflows/release-notes.lock.yml +++ b/.github/workflows/release-notes.lock.yml @@ -21,13 +21,13 @@ # For more information: https://github.github.com/gh-aw/introduction/overview/ # # -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"a0071795baea7e76942a9dd783e4782052b6383b85e49cb0592fd9af149a33a6","compiler_version":"v0.65.1","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"4a32bbac0ad27de243b2bb6332e64fc2babf3823e6988c0a96c99bd3531deb96","compiler_version":"v0.65.1","strict":true,"agent_id":"copilot"} name: ".NET Release Notes Maintenance" "on": schedule: - - cron: "29 16 * * *" - # Friendly format: daily around 9:00 utc-8 (scattered) + - cron: "34 */6 * * *" + # Friendly format: every 6 hours (scattered) workflow_dispatch: inputs: aw_context: @@ -139,20 +139,20 @@ jobs: run: | bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh { - cat << 'GH_AW_PROMPT_092d204882fce517_EOF' + cat << 'GH_AW_PROMPT_5c70b32715b76a77_EOF' - GH_AW_PROMPT_092d204882fce517_EOF + GH_AW_PROMPT_5c70b32715b76a77_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_092d204882fce517_EOF' + cat << 'GH_AW_PROMPT_5c70b32715b76a77_EOF' Tools: add_comment(max:20), create_pull_request(max:5), push_to_pull_request_branch(max:5), missing_tool, missing_data, noop - GH_AW_PROMPT_092d204882fce517_EOF + GH_AW_PROMPT_5c70b32715b76a77_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md" - cat << 'GH_AW_PROMPT_092d204882fce517_EOF' + cat << 'GH_AW_PROMPT_5c70b32715b76a77_EOF' The following GitHub context information is available for this workflow: @@ -182,12 +182,12 @@ jobs: {{/if}} - GH_AW_PROMPT_092d204882fce517_EOF + GH_AW_PROMPT_5c70b32715b76a77_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_092d204882fce517_EOF' + cat << 'GH_AW_PROMPT_5c70b32715b76a77_EOF' {{#runtime-import .github/workflows/release-notes.md}} - GH_AW_PROMPT_092d204882fce517_EOF + GH_AW_PROMPT_5c70b32715b76a77_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -347,12 +347,12 @@ jobs: mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_e0765ad82515efc5_EOF' + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_d68c5f707ea53666_EOF' {"add_comment":{"max":20,"target":"*"},"create_pull_request":{"draft":true,"labels":["release-notes","automated"],"max":5,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS"],"protected_path_prefixes":[".github/",".agents/"],"title_prefix":"[release-notes] "},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"if_no_changes":"warn","labels":["release-notes","automated"],"max":5,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS"],"protected_path_prefixes":[".github/",".agents/"],"title_prefix":"[release-notes] "}} - GH_AW_SAFE_OUTPUTS_CONFIG_e0765ad82515efc5_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_d68c5f707ea53666_EOF - name: Write Safe Outputs Tools run: | - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_11b0293352d901bb_EOF' + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_30af8d3b559c704f_EOF' { "description_suffixes": { "add_comment": " CONSTRAINTS: Maximum 20 comment(s) can be added. Target: *.", @@ -362,8 +362,8 @@ jobs: "repo_params": {}, "dynamic_tools": [] } - GH_AW_SAFE_OUTPUTS_TOOLS_META_11b0293352d901bb_EOF - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_92a5e54bdfae304b_EOF' + GH_AW_SAFE_OUTPUTS_TOOLS_META_30af8d3b559c704f_EOF + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_1fa32dc33d42a30f_EOF' { "add_comment": { "defaultMax": 1, @@ -497,7 +497,7 @@ jobs: } } } - GH_AW_SAFE_OUTPUTS_VALIDATION_92a5e54bdfae304b_EOF + GH_AW_SAFE_OUTPUTS_VALIDATION_1fa32dc33d42a30f_EOF node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs - name: Generate Safe Outputs MCP Server Config id: safe-outputs-config @@ -565,7 +565,7 @@ jobs: export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.10' mkdir -p /home/runner/.copilot - cat << GH_AW_MCP_CONFIG_d7bc14dc74bfc877_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh + cat << GH_AW_MCP_CONFIG_8c043bf359234455_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh { "mcpServers": { "github": { @@ -606,7 +606,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_d7bc14dc74bfc877_EOF + GH_AW_MCP_CONFIG_8c043bf359234455_EOF - name: Download activation artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index f2a01eae94..1a73cc58a4 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -1,6 +1,6 @@ --- on: - schedule: daily around 9:00 utc-8 + schedule: every 6 hours workflow_dispatch: inputs: milestone: From ef90d9bc78612f8aba9efafbb6adb8860e0465e0 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 21:37:24 -0700 Subject: [PATCH 21/65] Fix commentary: callback example would benefit from code sample Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/references/examples/aspnetcore.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/skills/release-notes/references/examples/aspnetcore.md b/.github/skills/release-notes/references/examples/aspnetcore.md index a8f9b9d410..92c7fb0de6 100644 --- a/.github/skills/release-notes/references/examples/aspnetcore.md +++ b/.github/skills/release-notes/references/examples/aspnetcore.md @@ -10,7 +10,7 @@ Additionally, the middleware's default behavior has changed: it no longer writes --- Source: [.NET 10 Preview 7 — ASP.NET Core](../../../../release-notes/10.0/preview/preview7/aspnetcore.md) -Commentary: Short and focused — three sentences covering the new option, the use case, and a behavior change. No code needed. +Commentary: Short and focused — three sentences covering the new option, the use case, and a behavior change. A code sample showing the callback usage would strengthen it. Why it works: The reader immediately knows what changed, why, and whether they're affected. --- From 5016b21684462c64b70fb28f3aa609d0e62b725a Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 21:39:36 -0700 Subject: [PATCH 22/65] Replace short ASP.NET example: StatusCodeSelector with code snippet Replaces SuppressDiagnosticsCallback (prose-only) with StatusCodeSelector (one paragraph + clean code sample). Better exemplar for 'short + code'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../references/examples/aspnetcore.md | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/skills/release-notes/references/examples/aspnetcore.md b/.github/skills/release-notes/references/examples/aspnetcore.md index 92c7fb0de6..3d0c9ca07c 100644 --- a/.github/skills/release-notes/references/examples/aspnetcore.md +++ b/.github/skills/release-notes/references/examples/aspnetcore.md @@ -1,17 +1,24 @@ # ASP.NET Core Examples -## Configure suppressing exception handler diagnostics +## `ExceptionHandlerMiddleware` option to choose the status code based on the exception -A new configuration option has been added to the [ASP.NET Core exception handler middleware](https://learn.microsoft.com/aspnet/core/fundamentals/error-handling#exception-handler-page) to control diagnostic output: `ExceptionHandlerOptions.SuppressDiagnosticsCallback`. This callback is passed context about the request and exception, allowing you to add logic that determines whether the middleware should write exception logs and other telemetry. +A new option when configuring the `ExceptionHandlerMiddleware` allows app developers to choose what status code to return when an exception occurs during application request handling. The new option changes the status code being set in the `ProblemDetails` response from the `ExceptionHandlerMiddleware`. -This setting is useful when you know an exception is transient, or has been handled by the exception handler middleware, and don't want the error logs written to your observability platform. +```csharp +app.UseExceptionHandler(new ExceptionHandlerOptions +{ + StatusCodeSelector = ex => ex is TimeoutException + ? StatusCodes.Status503ServiceUnavailable + : StatusCodes.Status500InternalServerError, +}); +``` -Additionally, the middleware's default behavior has changed: it no longer writes exception diagnostics for exceptions handled by `IExceptionHandler`. Based on user feedback, logging handled exceptions at the error level was often undesirable when `IExceptionHandler.TryHandleAsync` returned `true`. +Thanks to [@latonz](https://github.com/latonz) for contributing this new option! --- -Source: [.NET 10 Preview 7 — ASP.NET Core](../../../../release-notes/10.0/preview/preview7/aspnetcore.md) -Commentary: Short and focused — three sentences covering the new option, the use case, and a behavior change. A code sample showing the callback usage would strengthen it. -Why it works: The reader immediately knows what changed, why, and whether they're affected. +Source: [.NET 9 Preview 7 — ASP.NET Core](../../../../release-notes/9.0/preview/preview7/aspnetcore.md) +Commentary: Short and focused — one paragraph of context, a tight code snippet, and community credit. +Why it works: The code speaks for itself. The reader sees exactly what the API looks like and can immediately use it. No explanation needed beyond "what" and "why." --- ## Use PipeReader support in System.Text.Json From 19c163655a27bb100ab88e0900c7ed98528a7632 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 21:40:54 -0700 Subject: [PATCH 23/65] Add active voice principle to examples guide Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/references/examples/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/skills/release-notes/references/examples/README.md b/.github/skills/release-notes/references/examples/README.md index 47db436c9f..1fc999913c 100644 --- a/.github/skills/release-notes/references/examples/README.md +++ b/.github/skills/release-notes/references/examples/README.md @@ -14,6 +14,7 @@ Curated examples from previous .NET release notes, organized by component. Load 1. **Length matches importance** — a config option gets 3 sentences; a cryptography overhaul gets subheadings and multiple code blocks 2. **Lead with what changed** — don't bury the lede with background paragraphs +3. **Prefer active voice** — "The JIT now eliminates bounds checks" not "Bounds checks are now eliminated by the JIT." Passive voice is fine occasionally but active is the strong default 3. **Code shows, prose explains** — when there's an API, show it; use prose for the why/when 4. **Attribution is natural** — community contributions get a mention with a GitHub link, not a separate "contributors" section 5. **Diff format for improvements** — when a feature simplifies existing code, `diff` blocks make the improvement immediately visible From 1d92154228bcdbb627f27b498c58a8bb2d0f4c69 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 21:43:43 -0700 Subject: [PATCH 24/65] Add style note: simplify awkward phrasing in PipeReader example Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/references/examples/aspnetcore.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/skills/release-notes/references/examples/aspnetcore.md b/.github/skills/release-notes/references/examples/aspnetcore.md index 3d0c9ca07c..a27e61d47c 100644 --- a/.github/skills/release-notes/references/examples/aspnetcore.md +++ b/.github/skills/release-notes/references/examples/aspnetcore.md @@ -43,6 +43,7 @@ public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSeria Source: [.NET 10 Preview 7 — ASP.NET Core](../../../../release-notes/10.0/preview/preview7/aspnetcore.md) Commentary: Medium — behavioral change that's mostly invisible but has an edge case. Gives the workaround and the proper fix. Why it works: Acknowledges most users won't notice, identifies who *will* be affected, gives an immediate workaround, then the proper fix. +Style note: The opening phrase "without requiring any code changes from applications" is awkward — it personifies the application. Prefer "without needing application changes." Simpler, active, shorter. --- ## `OpenIdConnectHandler` support for Pushed Authorization Requests (PAR) From 05ae36a11f6d729938e6d23966532354ad0f2da6 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 21:50:05 -0700 Subject: [PATCH 25/65] Mark PAR example as anti-pattern; add Title/Content/Credit principle The 'guest introduction' style restructures the note around attribution instead of the feature. Prefer: explain the feature, then credit at end. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/references/examples/README.md | 2 +- .github/skills/release-notes/references/examples/aspnetcore.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/skills/release-notes/references/examples/README.md b/.github/skills/release-notes/references/examples/README.md index 1fc999913c..0cb526c4ec 100644 --- a/.github/skills/release-notes/references/examples/README.md +++ b/.github/skills/release-notes/references/examples/README.md @@ -16,7 +16,7 @@ Curated examples from previous .NET release notes, organized by component. Load 2. **Lead with what changed** — don't bury the lede with background paragraphs 3. **Prefer active voice** — "The JIT now eliminates bounds checks" not "Bounds checks are now eliminated by the JIT." Passive voice is fine occasionally but active is the strong default 3. **Code shows, prose explains** — when there's an API, show it; use prose for the why/when -4. **Attribution is natural** — community contributions get a mention with a GitHub link, not a separate "contributors" section +4. **Title, Content, Credit** — explain the feature first, credit contributors at the end. Don't restructure a release note into a guest introduction. See the StatusCodeSelector example (good) vs. the PAR example (anti-pattern) in aspnetcore.md 5. **Diff format for improvements** — when a feature simplifies existing code, `diff` blocks make the improvement immediately visible 6. **Assembly comparisons for JIT work** — before/after `asm` blocks let readers count the instructions eliminated 7. **Progressive benchmarks tell a story** — multiple benchmark tables showing incremental improvement are more compelling than a single number diff --git a/.github/skills/release-notes/references/examples/aspnetcore.md b/.github/skills/release-notes/references/examples/aspnetcore.md index a27e61d47c..2e7c773666 100644 --- a/.github/skills/release-notes/references/examples/aspnetcore.md +++ b/.github/skills/release-notes/references/examples/aspnetcore.md @@ -84,4 +84,5 @@ This change also introduces a new `OnPushAuthorization` event to `OpenIdConnectE Source: [.NET 9 Preview 7 — ASP.NET Core](../../../../release-notes/9.0/preview/preview7/aspnetcore.md) Commentary: Long — community contribution with the contributor's own words explaining significance. Good template for features with security or compliance importance. Why it works: The block quote lets the contributor explain the significance — more credible than paraphrasing. Then practical: default behavior, how to disable, how to require. +Anti-pattern: The opening ("We'd like to thank...Joe described...as follows") turns the release note into a guest introduction. Prefer the **Title, Content, Credit** pattern — explain the feature first, give credit at the end (like the StatusCodeSelector example above). Credit is good; restructuring the entire note around it is not. --- From 60b1c8a470a0308620bcb1eeddf9e115fc0f54cb Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 22:02:17 -0700 Subject: [PATCH 26/65] Add style note: simplify transition to rule bullets in C# example Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/references/examples/csharp.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/skills/release-notes/references/examples/csharp.md b/.github/skills/release-notes/references/examples/csharp.md index 919754b4a6..e38234eb3d 100644 --- a/.github/skills/release-notes/references/examples/csharp.md +++ b/.github/skills/release-notes/references/examples/csharp.md @@ -31,4 +31,5 @@ Extension members are only considered for overload resolution when no applicable Source: [.NET 10 Preview 7 — C#](../../../../release-notes/10.0/preview/preview7/csharp.md) Commentary: Code-first — one sentence of context, then the syntax, then rule bullets. Language features need to be seen, not just described. Why it works: Shows the code immediately. The rule bullets follow naturally as "here's what you need to know." No motivation section needed. +Style note: Line 21 ("Several of the rules for extension operators are demonstrated in the preceding example:") is stiff. Prefer something direct like "The rules for extension operators:" or "Extension operator rules:" — the reader can see the example is right above. --- From 50518fe65ecf42f43f10877b63efa2f75a09bf19 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 22:05:37 -0700 Subject: [PATCH 27/65] Add style note: active voice for MLDsa ease-of-use intro Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/references/examples/libraries.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/skills/release-notes/references/examples/libraries.md b/.github/skills/release-notes/references/examples/libraries.md index a35d1ef7ca..30faddd3ea 100644 --- a/.github/skills/release-notes/references/examples/libraries.md +++ b/.github/skills/release-notes/references/examples/libraries.md @@ -52,4 +52,5 @@ Console.WriteLine(publicKey.VerifyData(data, signature)); // False Source: [.NET 10 Preview 7 — Libraries](../../../../release-notes/10.0/preview/preview7/libraries.md) Commentary: Long — subheadings organize related-but-distinct features. Uses `diff` blocks for API simplification and complete runnable scenarios for new APIs. Why it works: The `diff` block immediately shows the simplification (red lines removed, green line added). The Composite ML-DSA sample is a complete scenario (generate → sign → verify → tamper → verify-fails). +Style note: L7 "gained ease-of-use updates" is passive and vague. Prefer active/direct: "Common patterns using `MLDsa` are now easier:" or "Ease-of-use updates simplify common `MLDsa` patterns:". --- From f1c1a7ecef0c6eaecec3060ee61e61a376195719 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 22:09:00 -0700 Subject: [PATCH 28/65] Add principle: link PRs/issues in org/repo #number format Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/references/examples/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/skills/release-notes/references/examples/README.md b/.github/skills/release-notes/references/examples/README.md index 0cb526c4ec..729c75376c 100644 --- a/.github/skills/release-notes/references/examples/README.md +++ b/.github/skills/release-notes/references/examples/README.md @@ -22,3 +22,4 @@ Curated examples from previous .NET release notes, organized by component. Load 7. **Progressive benchmarks tell a story** — multiple benchmark tables showing incremental improvement are more compelling than a single number 8. **Ask for what you can't generate** — benchmark data, definitive samples, and domain context come from humans. A placeholder with a request is better than a fabrication 9. **Workarounds are welcome** — if a change might break someone, say so and give the escape hatch +10. **Link to PRs and issues** — use the `org/repo #number` format: `[dotnet/runtime #115977](https://github.com/dotnet/runtime/pull/115977)`. Links give readers provenance and let them dig deeper From 48940cc793a352c0863a63facaae4b9698be1bcf Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 22:10:21 -0700 Subject: [PATCH 29/65] Add principle: avoid jargon and ambiguous words 'Snippet' in a programming context suggests code. Say what you mean plainly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/references/examples/README.md | 1 + .github/skills/release-notes/references/examples/runtime.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/skills/release-notes/references/examples/README.md b/.github/skills/release-notes/references/examples/README.md index 729c75376c..4d9a0237e0 100644 --- a/.github/skills/release-notes/references/examples/README.md +++ b/.github/skills/release-notes/references/examples/README.md @@ -23,3 +23,4 @@ Curated examples from previous .NET release notes, organized by component. Load 8. **Ask for what you can't generate** — benchmark data, definitive samples, and domain context come from humans. A placeholder with a request is better than a fabrication 9. **Workarounds are welcome** — if a change might break someone, say so and give the escape hatch 10. **Link to PRs and issues** — use the `org/repo #number` format: `[dotnet/runtime #115977](https://github.com/dotnet/runtime/pull/115977)`. Links give readers provenance and let them dig deeper +11. **Avoid jargon and ambiguous words** — "snippet" in a programming context suggests code. Say what you mean plainly. If a word could be misread, pick a simpler one diff --git a/.github/skills/release-notes/references/examples/runtime.md b/.github/skills/release-notes/references/examples/runtime.md index f57f9e4629..8bc8c830e0 100644 --- a/.github/skills/release-notes/references/examples/runtime.md +++ b/.github/skills/release-notes/references/examples/runtime.md @@ -114,4 +114,5 @@ This is just a snippet of the improvements RyuJIT's new loop representation brin Source: [.NET 9 Preview 1 — Runtime](../../../../release-notes/9.0/preview/preview1/runtime.md) Commentary: Metric-heavy prose with no code — good for infrastructure improvements where breadth of impact matters more than a single before/after. Why it works: Percentages sell the improvement without needing code. Each bullet is self-contained. Links to the tracking issue for depth. +Style note: L111 "snippet" is ambiguous in a programming context — readers may expect a code snippet. Prefer plain language like "a sample of" or "some of." --- From 63e49d2d2d69bdbbeab8c4436d83b3c707f06165 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 22:14:35 -0700 Subject: [PATCH 30/65] Add style note: don't bury the lede with backward-looking passive Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/references/examples/runtime.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/skills/release-notes/references/examples/runtime.md b/.github/skills/release-notes/references/examples/runtime.md index 8bc8c830e0..8d65124ba6 100644 --- a/.github/skills/release-notes/references/examples/runtime.md +++ b/.github/skills/release-notes/references/examples/runtime.md @@ -46,6 +46,7 @@ Thanks to improvements to the JIT's inlining, stack allocation, and loop cloning Source: [.NET 10 Preview 2 — Runtime](../../../../release-notes/10.0/preview/preview2/runtime.md) Commentary: Progressive benchmark narrative — the best style for JIT optimization stories. Why it works: Multiple benchmark tables tell a story (problem → partial fix → harder problem → better fix). Each table shows measurable improvement. The reader follows the JIT's reasoning through escalating complexity. +Style note: L5 buries the lede with "Preview 1 brought enhancements to..." — passive and backward-looking. Better: "A major focus this release is reducing the abstraction overhead of array iteration via enumerators. We delivered improvements in Preview 1 for array interface methods. In this preview, we've continued on that theme." Active voice, states the goal upfront, then the progression. --- ## Improved Code Generation for Struct Arguments From 83034f7faa88d8d02cea18754b1522075f9935b4 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 22:20:16 -0700 Subject: [PATCH 31/65] Fix markdownlint: setext headings, list numbering, table spacing Add blank lines before closing --- to prevent setext heading parsing. Fix numbered list sequence in README. Fix table separator spacing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../references/examples/README.md | 20 +++++++++---------- .../references/examples/aspnetcore.md | 3 +++ .../references/examples/csharp.md | 1 + .../references/examples/libraries.md | 1 + .../references/examples/runtime.md | 3 +++ .../release-notes/references/examples/sdk.md | 1 + 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/.github/skills/release-notes/references/examples/README.md b/.github/skills/release-notes/references/examples/README.md index 4d9a0237e0..5c687aa7ee 100644 --- a/.github/skills/release-notes/references/examples/README.md +++ b/.github/skills/release-notes/references/examples/README.md @@ -3,7 +3,7 @@ Curated examples from previous .NET release notes, organized by component. Load only the file relevant to the component you're writing. | File | Component | Styles shown | -|------|-----------|-------------| +| ---- | --------- | ------------ | | [runtime.md](examples/runtime.md) | JIT, GC, CoreCLR | Benchmark narrative, assembly comparison, metric-heavy prose | | [aspnetcore.md](examples/aspnetcore.md) | ASP.NET Core, Blazor | Short behavior change, medium workaround, long community feature | | [csharp.md](examples/csharp.md) | C# language | Code-first with rule bullets | @@ -15,12 +15,12 @@ Curated examples from previous .NET release notes, organized by component. Load 1. **Length matches importance** — a config option gets 3 sentences; a cryptography overhaul gets subheadings and multiple code blocks 2. **Lead with what changed** — don't bury the lede with background paragraphs 3. **Prefer active voice** — "The JIT now eliminates bounds checks" not "Bounds checks are now eliminated by the JIT." Passive voice is fine occasionally but active is the strong default -3. **Code shows, prose explains** — when there's an API, show it; use prose for the why/when -4. **Title, Content, Credit** — explain the feature first, credit contributors at the end. Don't restructure a release note into a guest introduction. See the StatusCodeSelector example (good) vs. the PAR example (anti-pattern) in aspnetcore.md -5. **Diff format for improvements** — when a feature simplifies existing code, `diff` blocks make the improvement immediately visible -6. **Assembly comparisons for JIT work** — before/after `asm` blocks let readers count the instructions eliminated -7. **Progressive benchmarks tell a story** — multiple benchmark tables showing incremental improvement are more compelling than a single number -8. **Ask for what you can't generate** — benchmark data, definitive samples, and domain context come from humans. A placeholder with a request is better than a fabrication -9. **Workarounds are welcome** — if a change might break someone, say so and give the escape hatch -10. **Link to PRs and issues** — use the `org/repo #number` format: `[dotnet/runtime #115977](https://github.com/dotnet/runtime/pull/115977)`. Links give readers provenance and let them dig deeper -11. **Avoid jargon and ambiguous words** — "snippet" in a programming context suggests code. Say what you mean plainly. If a word could be misread, pick a simpler one +4. **Code shows, prose explains** — when there's an API, show it; use prose for the why/when +5. **Title, Content, Credit** — explain the feature first, credit contributors at the end. Don't restructure a release note into a guest introduction. See the StatusCodeSelector example (good) vs. the PAR example (anti-pattern) in aspnetcore.md +6. **Diff format for improvements** — when a feature simplifies existing code, `diff` blocks make the improvement immediately visible +7. **Assembly comparisons for JIT work** — before/after `asm` blocks let readers count the instructions eliminated +8. **Progressive benchmarks tell a story** — multiple benchmark tables showing incremental improvement are more compelling than a single number +9. **Ask for what you can't generate** — benchmark data, definitive samples, and domain context come from humans. A placeholder with a request is better than a fabrication +10. **Workarounds are welcome** — if a change might break someone, say so and give the escape hatch +11. **Link to PRs and issues** — use the `org/repo #number` format: `[dotnet/runtime #115977](https://github.com/dotnet/runtime/pull/115977)`. Links give readers provenance and let them dig deeper +12. **Avoid jargon and ambiguous words** — "snippet" in a programming context suggests code. Say what you mean plainly. If a word could be misread, pick a simpler one diff --git a/.github/skills/release-notes/references/examples/aspnetcore.md b/.github/skills/release-notes/references/examples/aspnetcore.md index 2e7c773666..0e73ab120c 100644 --- a/.github/skills/release-notes/references/examples/aspnetcore.md +++ b/.github/skills/release-notes/references/examples/aspnetcore.md @@ -19,6 +19,7 @@ Thanks to [@latonz](https://github.com/latonz) for contributing this new option! Source: [.NET 9 Preview 7 — ASP.NET Core](../../../../release-notes/9.0/preview/preview7/aspnetcore.md) Commentary: Short and focused — one paragraph of context, a tight code snippet, and community credit. Why it works: The code speaks for itself. The reader sees exactly what the API looks like and can immediately use it. No explanation needed beyond "what" and "why." + --- ## Use PipeReader support in System.Text.Json @@ -44,6 +45,7 @@ Source: [.NET 10 Preview 7 — ASP.NET Core](../../../../release-notes/10.0/prev Commentary: Medium — behavioral change that's mostly invisible but has an edge case. Gives the workaround and the proper fix. Why it works: Acknowledges most users won't notice, identifies who *will* be affected, gives an immediate workaround, then the proper fix. Style note: The opening phrase "without requiring any code changes from applications" is awkward — it personifies the application. Prefer "without needing application changes." Simpler, active, shorter. + --- ## `OpenIdConnectHandler` support for Pushed Authorization Requests (PAR) @@ -85,4 +87,5 @@ Source: [.NET 9 Preview 7 — ASP.NET Core](../../../../release-notes/9.0/previe Commentary: Long — community contribution with the contributor's own words explaining significance. Good template for features with security or compliance importance. Why it works: The block quote lets the contributor explain the significance — more credible than paraphrasing. Then practical: default behavior, how to disable, how to require. Anti-pattern: The opening ("We'd like to thank...Joe described...as follows") turns the release note into a guest introduction. Prefer the **Title, Content, Credit** pattern — explain the feature first, give credit at the end (like the StatusCodeSelector example above). Credit is good; restructuring the entire note around it is not. + --- diff --git a/.github/skills/release-notes/references/examples/csharp.md b/.github/skills/release-notes/references/examples/csharp.md index e38234eb3d..40e93fed26 100644 --- a/.github/skills/release-notes/references/examples/csharp.md +++ b/.github/skills/release-notes/references/examples/csharp.md @@ -32,4 +32,5 @@ Source: [.NET 10 Preview 7 — C#](../../../../release-notes/10.0/preview/previe Commentary: Code-first — one sentence of context, then the syntax, then rule bullets. Language features need to be seen, not just described. Why it works: Shows the code immediately. The rule bullets follow naturally as "here's what you need to know." No motivation section needed. Style note: Line 21 ("Several of the rules for extension operators are demonstrated in the preceding example:") is stiff. Prefer something direct like "The rules for extension operators:" or "Extension operator rules:" — the reader can see the example is right above. + --- diff --git a/.github/skills/release-notes/references/examples/libraries.md b/.github/skills/release-notes/references/examples/libraries.md index 30faddd3ea..caa201c306 100644 --- a/.github/skills/release-notes/references/examples/libraries.md +++ b/.github/skills/release-notes/references/examples/libraries.md @@ -53,4 +53,5 @@ Source: [.NET 10 Preview 7 — Libraries](../../../../release-notes/10.0/preview Commentary: Long — subheadings organize related-but-distinct features. Uses `diff` blocks for API simplification and complete runnable scenarios for new APIs. Why it works: The `diff` block immediately shows the simplification (red lines removed, green line added). The Composite ML-DSA sample is a complete scenario (generate → sign → verify → tamper → verify-fails). Style note: L7 "gained ease-of-use updates" is passive and vague. Prefer active/direct: "Common patterns using `MLDsa` are now easier:" or "Ease-of-use updates simplify common `MLDsa` patterns:". + --- diff --git a/.github/skills/release-notes/references/examples/runtime.md b/.github/skills/release-notes/references/examples/runtime.md index 8d65124ba6..5d7b644f54 100644 --- a/.github/skills/release-notes/references/examples/runtime.md +++ b/.github/skills/release-notes/references/examples/runtime.md @@ -47,6 +47,7 @@ Source: [.NET 10 Preview 2 — Runtime](../../../../release-notes/10.0/preview/p Commentary: Progressive benchmark narrative — the best style for JIT optimization stories. Why it works: Multiple benchmark tables tell a story (problem → partial fix → harder problem → better fix). Each table shows measurable improvement. The reader follows the JIT's reasoning through escalating complexity. Style note: L5 buries the lede with "Preview 1 brought enhancements to..." — passive and backward-looking. Better: "A major focus this release is reducing the abstraction overhead of array iteration via enumerators. We delivered improvements in Preview 1 for array interface methods. In this preview, we've continued on that theme." Active voice, states the goal upfront, then the progression. + --- ## Improved Code Generation for Struct Arguments @@ -97,6 +98,7 @@ Program:Main() (FullOpts): Source: [.NET 10 Preview 6 — Runtime](../../../../release-notes/10.0/preview/preview6/runtime.md) Commentary: Before/after assembly comparison — the gold standard for codegen improvements. Why it works: The reader can count the instructions eliminated. The assembly speaks for itself — no prose needed to explain the magnitude of the improvement. + --- ## JIT: Loop Optimizations @@ -116,4 +118,5 @@ Source: [.NET 9 Preview 1 — Runtime](../../../../release-notes/9.0/preview/pre Commentary: Metric-heavy prose with no code — good for infrastructure improvements where breadth of impact matters more than a single before/after. Why it works: Percentages sell the improvement without needing code. Each bullet is self-contained. Links to the tracking issue for depth. Style note: L111 "snippet" is ambiguous in a programming context — readers may expect a code snippet. Prefer plain language like "a sample of" or "some of." + --- diff --git a/.github/skills/release-notes/references/examples/sdk.md b/.github/skills/release-notes/references/examples/sdk.md index c35090bd91..fe972f4814 100644 --- a/.github/skills/release-notes/references/examples/sdk.md +++ b/.github/skills/release-notes/references/examples/sdk.md @@ -17,4 +17,5 @@ Requirements (depending on your environment): Source: [.NET 9 Preview 7 — SDK](../../../../release-notes/9.0/preview/preview7/sdk.md) Commentary: Short problem/solution with community attribution and a tight requirements list. Why it works: The community contributor is credited naturally mid-sentence. The requirements list gives actionable next steps without over-explaining. + --- From 36e528d5a32944d57003341bdae8994a0dced98a Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 22:25:14 -0700 Subject: [PATCH 32/65] Fix broken relative links; exclude lock files from super-linter Source links needed 5 levels up (not 4) from examples/ to repo root. Workflow link to examples/ fixed to use correct relative path. Exclude *.lock.yml from super-linter (generated by gh aw compile). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../skills/release-notes/references/examples/aspnetcore.md | 6 +++--- .github/skills/release-notes/references/examples/csharp.md | 2 +- .../skills/release-notes/references/examples/libraries.md | 2 +- .github/skills/release-notes/references/examples/runtime.md | 6 +++--- .github/skills/release-notes/references/examples/sdk.md | 2 +- .github/workflows/release-notes.md | 2 +- .github/workflows/super-linter.yml | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/skills/release-notes/references/examples/aspnetcore.md b/.github/skills/release-notes/references/examples/aspnetcore.md index 0e73ab120c..448bafe480 100644 --- a/.github/skills/release-notes/references/examples/aspnetcore.md +++ b/.github/skills/release-notes/references/examples/aspnetcore.md @@ -16,7 +16,7 @@ app.UseExceptionHandler(new ExceptionHandlerOptions Thanks to [@latonz](https://github.com/latonz) for contributing this new option! --- -Source: [.NET 9 Preview 7 — ASP.NET Core](../../../../release-notes/9.0/preview/preview7/aspnetcore.md) +Source: [.NET 9 Preview 7 — ASP.NET Core](../../../../../release-notes/9.0/preview/preview7/aspnetcore.md) Commentary: Short and focused — one paragraph of context, a tight code snippet, and community credit. Why it works: The code speaks for itself. The reader sees exactly what the API looks like and can immediately use it. No explanation needed beyond "what" and "why." @@ -41,7 +41,7 @@ public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSeria ``` --- -Source: [.NET 10 Preview 7 — ASP.NET Core](../../../../release-notes/10.0/preview/preview7/aspnetcore.md) +Source: [.NET 10 Preview 7 — ASP.NET Core](../../../../../release-notes/10.0/preview/preview7/aspnetcore.md) Commentary: Medium — behavioral change that's mostly invisible but has an edge case. Gives the workaround and the proper fix. Why it works: Acknowledges most users won't notice, identifies who *will* be affected, gives an immediate workaround, then the proper fix. Style note: The opening phrase "without requiring any code changes from applications" is awkward — it personifies the application. Prefer "without needing application changes." Simpler, active, shorter. @@ -83,7 +83,7 @@ To ensure that authentication only succeeds if PAR is used, use `PushedAuthoriza This change also introduces a new `OnPushAuthorization` event to `OpenIdConnectEvents` which can be used to customize the pushed authorization request or handle it manually. Refer to the [API proposal](https://github.com/dotnet/aspnetcore/issues/51686) for more details. --- -Source: [.NET 9 Preview 7 — ASP.NET Core](../../../../release-notes/9.0/preview/preview7/aspnetcore.md) +Source: [.NET 9 Preview 7 — ASP.NET Core](../../../../../release-notes/9.0/preview/preview7/aspnetcore.md) Commentary: Long — community contribution with the contributor's own words explaining significance. Good template for features with security or compliance importance. Why it works: The block quote lets the contributor explain the significance — more credible than paraphrasing. Then practical: default behavior, how to disable, how to require. Anti-pattern: The opening ("We'd like to thank...Joe described...as follows") turns the release note into a guest introduction. Prefer the **Title, Content, Credit** pattern — explain the feature first, give credit at the end (like the StatusCodeSelector example above). Credit is good; restructuring the entire note around it is not. diff --git a/.github/skills/release-notes/references/examples/csharp.md b/.github/skills/release-notes/references/examples/csharp.md index 40e93fed26..17089a372f 100644 --- a/.github/skills/release-notes/references/examples/csharp.md +++ b/.github/skills/release-notes/references/examples/csharp.md @@ -28,7 +28,7 @@ Several of the rules for extension operators are demonstrated in the preceding e Extension members are only considered for overload resolution when no applicable predefined or user defined non-extension members are found. --- -Source: [.NET 10 Preview 7 — C#](../../../../release-notes/10.0/preview/preview7/csharp.md) +Source: [.NET 10 Preview 7 — C#](../../../../../release-notes/10.0/preview/preview7/csharp.md) Commentary: Code-first — one sentence of context, then the syntax, then rule bullets. Language features need to be seen, not just described. Why it works: Shows the code immediately. The rule bullets follow naturally as "here's what you need to know." No motivation section needed. Style note: Line 21 ("Several of the rules for extension operators are demonstrated in the preceding example:") is stiff. Prefer something direct like "The rules for extension operators:" or "Extension operator rules:" — the reader can see the example is right above. diff --git a/.github/skills/release-notes/references/examples/libraries.md b/.github/skills/release-notes/references/examples/libraries.md index caa201c306..05fe1d5234 100644 --- a/.github/skills/release-notes/references/examples/libraries.md +++ b/.github/skills/release-notes/references/examples/libraries.md @@ -49,7 +49,7 @@ Console.WriteLine(publicKey.VerifyData(data, signature)); // False ``` --- -Source: [.NET 10 Preview 7 — Libraries](../../../../release-notes/10.0/preview/preview7/libraries.md) +Source: [.NET 10 Preview 7 — Libraries](../../../../../release-notes/10.0/preview/preview7/libraries.md) Commentary: Long — subheadings organize related-but-distinct features. Uses `diff` blocks for API simplification and complete runnable scenarios for new APIs. Why it works: The `diff` block immediately shows the simplification (red lines removed, green line added). The Composite ML-DSA sample is a complete scenario (generate → sign → verify → tamper → verify-fails). Style note: L7 "gained ease-of-use updates" is passive and vague. Prefer active/direct: "Common patterns using `MLDsa` are now easier:" or "Ease-of-use updates simplify common `MLDsa` patterns:". diff --git a/.github/skills/release-notes/references/examples/runtime.md b/.github/skills/release-notes/references/examples/runtime.md index 5d7b644f54..31bcb0bc3f 100644 --- a/.github/skills/release-notes/references/examples/runtime.md +++ b/.github/skills/release-notes/references/examples/runtime.md @@ -43,7 +43,7 @@ Thanks to improvements to the JIT's inlining, stack allocation, and loop cloning | foreach_static_readonly_array_via_interface (.NET 10) | 280.0 ns | 1.86 | - | --- -Source: [.NET 10 Preview 2 — Runtime](../../../../release-notes/10.0/preview/preview2/runtime.md) +Source: [.NET 10 Preview 2 — Runtime](../../../../../release-notes/10.0/preview/preview2/runtime.md) Commentary: Progressive benchmark narrative — the best style for JIT optimization stories. Why it works: Multiple benchmark tables tell a story (problem → partial fix → harder problem → better fix). Each table shows measurable improvement. The reader follows the JIT's reasoning through escalating complexity. Style note: L5 buries the lede with "Preview 1 brought enhancements to..." — passive and backward-looking. Better: "A major focus this release is reducing the abstraction overhead of array iteration via enumerators. We delivered improvements in Preview 1 for array interface methods. In this preview, we've continued on that theme." Active voice, states the goal upfront, then the progression. @@ -95,7 +95,7 @@ Program:Main() (FullOpts): ``` --- -Source: [.NET 10 Preview 6 — Runtime](../../../../release-notes/10.0/preview/preview6/runtime.md) +Source: [.NET 10 Preview 6 — Runtime](../../../../../release-notes/10.0/preview/preview6/runtime.md) Commentary: Before/after assembly comparison — the gold standard for codegen improvements. Why it works: The reader can count the instructions eliminated. The assembly speaks for itself — no prose needed to explain the magnitude of the improvement. @@ -114,7 +114,7 @@ Here's a quick breakdown of the improvements: This is just a snippet of the improvements RyuJIT's new loop representation brings. To take a closer look at the loop optimization work planned for .NET 9, check out [dotnet/runtime #93144](https://github.com/dotnet/runtime/issues/93144). --- -Source: [.NET 9 Preview 1 — Runtime](../../../../release-notes/9.0/preview/preview1/runtime.md) +Source: [.NET 9 Preview 1 — Runtime](../../../../../release-notes/9.0/preview/preview1/runtime.md) Commentary: Metric-heavy prose with no code — good for infrastructure improvements where breadth of impact matters more than a single before/after. Why it works: Percentages sell the improvement without needing code. Each bullet is self-contained. Links to the tracking issue for depth. Style note: L111 "snippet" is ambiguous in a programming context — readers may expect a code snippet. Prefer plain language like "a sample of" or "some of." diff --git a/.github/skills/release-notes/references/examples/sdk.md b/.github/skills/release-notes/references/examples/sdk.md index fe972f4814..d8efba4a95 100644 --- a/.github/skills/release-notes/references/examples/sdk.md +++ b/.github/skills/release-notes/references/examples/sdk.md @@ -14,7 +14,7 @@ Requirements (depending on your environment): * Use the `DOTNET_CONTAINER_INSECURE_REGISTRIES` environment variable to pass a semicolon-delimited list of registry domains to treat as insecure --- -Source: [.NET 9 Preview 7 — SDK](../../../../release-notes/9.0/preview/preview7/sdk.md) +Source: [.NET 9 Preview 7 — SDK](../../../../../release-notes/9.0/preview/preview7/sdk.md) Commentary: Short problem/solution with community attribution and a tight requirements list. Why it works: The community contributor is credited naturally mid-sentence. The requirements list gives actionable next steps without over-explaining. diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index 1a73cc58a4..c17edda8eb 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -69,7 +69,7 @@ Read these files from `.github/skills/release-notes/references/` for detailed gu - **component-mapping.md** — VMR paths → components → product slugs → output files - **format-template.md** — markdown document structure - **editorial-rules.md** — tone, attribution, naming conventions -- **examples/** — curated examples from previous releases, organized by component ([README](examples/README.md) has principles) +- **examples/** — curated examples from previous releases, organized by component ([README](../skills/release-notes/references/examples/README.md) has principles) ## What to do each run diff --git a/.github/workflows/super-linter.yml b/.github/workflows/super-linter.yml index 6e8a9bb339..5f668e62c8 100644 --- a/.github/workflows/super-linter.yml +++ b/.github/workflows/super-linter.yml @@ -35,6 +35,6 @@ jobs: VALIDATE_NATURAL_LANGUAGE: false VALIDATE_MARKDOWN_PRETTIER: false VALIDATE_JSON_PRETTIER: false - FILTER_REGEX_EXCLUDE: .github/skills/.* + FILTER_REGEX_EXCLUDE: (.github/skills/.*|.*\.lock\.yml) DEFAULT_BRANCH: main GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 0323096a9a3bd3648e600e6fff56a05342eadaf8 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 22:30:10 -0700 Subject: [PATCH 33/65] Fix remaining CI: blank line before list, README link paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add blank line before list in workflow (MD032). Fix README.md table links — files are siblings, not in examples/ subdirectory. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../skills/release-notes/references/examples/README.md | 10 +++++----- .github/workflows/release-notes.md | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/skills/release-notes/references/examples/README.md b/.github/skills/release-notes/references/examples/README.md index 5c687aa7ee..84b3eb31bb 100644 --- a/.github/skills/release-notes/references/examples/README.md +++ b/.github/skills/release-notes/references/examples/README.md @@ -4,11 +4,11 @@ Curated examples from previous .NET release notes, organized by component. Load | File | Component | Styles shown | | ---- | --------- | ------------ | -| [runtime.md](examples/runtime.md) | JIT, GC, CoreCLR | Benchmark narrative, assembly comparison, metric-heavy prose | -| [aspnetcore.md](examples/aspnetcore.md) | ASP.NET Core, Blazor | Short behavior change, medium workaround, long community feature | -| [csharp.md](examples/csharp.md) | C# language | Code-first with rule bullets | -| [sdk.md](examples/sdk.md) | SDK, CLI, containers | Problem/solution with community attribution | -| [libraries.md](examples/libraries.md) | BCL, System.* APIs | Subheadings, diff blocks, multiple API examples | +| [runtime.md](runtime.md) | JIT, GC, CoreCLR | Benchmark narrative, assembly comparison, metric-heavy prose | +| [aspnetcore.md](aspnetcore.md) | ASP.NET Core, Blazor | Short behavior change, medium workaround, long community feature | +| [csharp.md](csharp.md) | C# language | Code-first with rule bullets | +| [sdk.md](sdk.md) | SDK, CLI, containers | Problem/solution with community attribution | +| [libraries.md](libraries.md) | BCL, System.* APIs | Subheadings, diff blocks, multiple API examples | ## Principles diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index c17edda8eb..2afe2e7b89 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -209,6 +209,7 @@ Things change between runs. Handle these gracefully: ### 4. Daily summary At the end of each run, leave a comment on each active PR noting: + - What was regenerated or updated - How many new changes appeared since yesterday - Whether the head ref changed From 0991b567ba1920a485815c2c695eaf99a64708a7 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Tue, 31 Mar 2026 22:39:22 -0700 Subject: [PATCH 34/65] Remove stale monolithic examples.md (replaced by examples/ directory) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../release-notes/references/examples.md | 83 ------------------- 1 file changed, 83 deletions(-) delete mode 100644 .github/skills/release-notes/references/examples.md diff --git a/.github/skills/release-notes/references/examples.md b/.github/skills/release-notes/references/examples.md deleted file mode 100644 index 26afe3a6c8..0000000000 --- a/.github/skills/release-notes/references/examples.md +++ /dev/null @@ -1,83 +0,0 @@ -# Release Notes Examples - -Good examples of feature descriptions from previous .NET release notes, demonstrating different lengths and approaches. Use these as models — match the length to the importance and complexity of the feature. - -The public-facing "What's new in .NET" docs pages are the curated, polished versions. These repo-based release notes have more flexibility — descriptions can be as long as they need to be, as long as they're interesting. - -## Short — focused explanation, no code - -Best for: behavior changes, configuration options, minor improvements. - -### Example: Configure suppressing exception handler diagnostics - -> From [.NET 10 Preview 7 — ASP.NET Core](../../release-notes/10.0/preview/preview7/aspnetcore.md) - -A new configuration option, a behavior change, and the rationale — all in three sentences. No code needed because the value is in understanding the *why*, not the API shape. - -### Example: Container publishing improvements for insecure registries - -> From [.NET 9 Preview 7 — SDK](../../release-notes/9.0/preview/preview7/sdk.md) - -Problem/solution framing, community attribution (`[@tmds](https://github.com/tmds)`), then a tight bullet list of requirements. Shows how to credit contributors naturally. - -## Medium — context plus code sample - -Best for: new APIs, language features, things developers will type. - -### Example: Extension operators - -> From [.NET 10 Preview 7 — C#](../../release-notes/10.0/preview/preview7/csharp.md) - -One sentence of context, then a code block showing the new syntax, then bullet rules. The code *is* the feature — showing it immediately is the right call. - -### Example: PipeReader support in System.Text.Json - -> From [.NET 10 Preview 7 — ASP.NET Core](../../release-notes/10.0/preview/preview7/aspnetcore.md) - -A behavioral change that's mostly invisible but has an edge case. Explains what changed, who might be affected, the workaround, and the proper fix. Good template for "most people won't notice, but if you do, here's what to do." - -## Long — deep-dive with multiple sections - -Best for: major features, security capabilities, things users have been asking for. - -### Example: Array Enumeration De-Abstraction - -> From [.NET 10 Preview 2 — Runtime](../../release-notes/10.0/preview/preview2/runtime.md) - -A progressive JIT optimization story told through three benchmark tables. Starts with a simple case (transparent type), escalates to a harder case (opaque type behind interface), and walks through each optimization layer (inlining, PGO, guarded devirtualization, conditional escape analysis). Each benchmark shows measurable improvement. The narrative structure — problem, partial solution, harder problem, better solution — keeps the reader engaged. - -### Example: Improved Code Generation for Struct Arguments - -> From [.NET 10 Preview 6 — Runtime](../../release-notes/10.0/preview/preview6/runtime.md) - -Before/after assembly comparison showing a concrete codegen improvement. Starts with C# code, shows the current optimal case, introduces the pathological case with assembly output, then shows the fix. Three `asm` blocks tell the whole story — the reader can literally count the instructions eliminated. - -### Example: JIT Loop Optimizations - -> From [.NET 9 Preview 1 — Runtime](../../release-notes/9.0/preview/preview1/runtime.md) - -No code at all — just crisp prose with percentages. Explains three optimization categories (hoisting, cloning, alignment) with one-paragraph descriptions and measured improvement rates. Good template for infrastructure improvements where the value is breadth of impact rather than a single dramatic before/after. - -### Example: Post-Quantum Cryptography Updates - -> From [.NET 10 Preview 7 — Libraries](../../release-notes/10.0/preview/preview7/libraries.md) - -Subheadings (ML-DSA, Composite ML-DSA), diff-style before/after code, multiple API examples. The diff format (`-` old / `+` new) is especially effective for showing simplification. - -### Example: OpenIdConnectHandler support for Pushed Authorization Requests (PAR) - -> From [.NET 9 Preview 7 — ASP.NET Core](../../release-notes/9.0/preview/preview7/aspnetcore.md) - -Community contribution with extensive quoted motivation from the contributor. The block quote explains *why* this matters (security, compliance, standards). Then practical: default behavior, how to disable, how to require. Good template for features with regulatory or security significance. - -## Principles these examples demonstrate - -1. **Length matches importance** — a config option gets 3 sentences; a cryptography overhaul gets subheadings and multiple code blocks -2. **Lead with what changed** — don't bury the lede with background paragraphs -3. **Code shows, prose explains** — when there's an API, show it; use prose for the why/when -4. **Attribution is natural** — community contributions get a mention with a GitHub link, not a separate "contributors" section -5. **Diff format for improvements** — when a feature simplifies existing code, `diff` blocks make the improvement immediately visible -6. **Assembly comparisons for JIT work** — before/after `asm` blocks let readers count the instructions eliminated -7. **Progressive benchmarks tell a story** — multiple benchmark tables showing incremental improvement are more compelling than a single number -8. **Ask for what you can't generate** — benchmark data, definitive samples, and domain context come from humans. A placeholder with a request is better than a fabrication -9. **Workarounds are welcome** — if a change might break someone, say so and give the escape hatch From 3b44f7e9d34bde4a78f826b73d628a2bc0444040 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Wed, 1 Apr 2026 14:32:40 -0700 Subject: [PATCH 35/65] Add API verification guidance using dotnet-inspect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New reference doc: api-verification.md — why, when, and how to verify API names before writing about them in release notes - Updated SKILL.md: verification is now step 3 in the pipeline - Updated DESIGN.md: agent verifies before writing, not as an afterthought - Updated quality-bar.md: 'verify before you write' as a hard rule — placeholder over fabrication Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/DESIGN.md | 11 ++- .github/skills/release-notes/SKILL.md | 9 +- .../references/api-verification.md | 85 +++++++++++++++++++ .../release-notes/references/quality-bar.md | 12 +++ 4 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 .github/skills/release-notes/references/api-verification.md diff --git a/.github/skills/release-notes/DESIGN.md b/.github/skills/release-notes/DESIGN.md index 5956ec6b37..86bc6a38a1 100644 --- a/.github/skills/release-notes/DESIGN.md +++ b/.github/skills/release-notes/DESIGN.md @@ -112,8 +112,8 @@ These are **goal-oriented**, not procedural. They describe what good release not ### Agent responsibilities - **Triage** — read `changes.json` and identify which PRs are worth writing about. Use the [component mapping](references/component-mapping.md) to route changes from `repo` to the correct output files. -- **Write** — produce markdown release notes for high-value features, following the quality bar -- **Verify** — use `dotnet-inspect` against nightly builds when needed to confirm public API changes +- **Verify** — before writing about any API, use `dotnet-inspect` to confirm it exists with the correct type names, member signatures, and namespaces. See [api-verification.md](references/api-verification.md). +- **Write** — produce markdown release notes for high-value features, following the quality bar. Only use API names, type names, and code samples that have been verified. - **Respect edits** — diff the PR branch to see what humans have changed and preserve their work - **Respond** — read PR comments and incorporate human feedback @@ -271,7 +271,6 @@ Each preview is a coherent release milestone with its own set of features. Maint ## Open questions -1. **dotnet-inspect in Actions** — the agent needs `dotnet-inspect` to verify public API changes against nightly builds. Availability as a global tool in GitHub Actions runners needs confirmation. -2. **Cross-repo tokens** — the workflow runs in `dotnet/core` but reads from `dotnet/dotnet` and ~20 component repos. The GitHub token scope and any required app permissions need to be configured. -3. **Conflict resolution heuristics** — when the agent and a human both changed the same section between runs, the human wins. But how granular is "a section"? Need to define this precisely (per-heading? per-file?). -4. **RC/GA milestone naming** — the multi-milestone logic assumes `previewN` naming. RC and GA milestones use different naming (`rc1`, `rc2`, `ga`). The workflow needs to handle the transition gracefully. +1. **Cross-repo tokens** — the workflow runs in `dotnet/core` but reads from `dotnet/dotnet` and ~20 component repos. The GitHub token scope and any required app permissions need to be configured. +2. **Conflict resolution heuristics** — when the agent and a human both changed the same section between runs, the human wins. But how granular is "a section"? Need to define this precisely (per-heading? per-file?). +3. **RC/GA milestone naming** — the multi-milestone logic assumes `previewN` naming. RC and GA milestones use different naming (`rc1`, `rc2`, `ga`). The workflow needs to handle the transition gracefully. diff --git a/.github/skills/release-notes/SKILL.md b/.github/skills/release-notes/SKILL.md index aee914a5da..71aa4a80fc 100644 --- a/.github/skills/release-notes/SKILL.md +++ b/.github/skills/release-notes/SKILL.md @@ -1,7 +1,7 @@ --- name: release-notes description: Generate and maintain .NET release notes. Uses the VMR source-manifest.json and dotnet-release tool to identify what shipped, then writes curated markdown for high-value features. Designed to run as a cron-driven agentic workflow that maintains PR branches per release milestone. -compatibility: Requires GitHub MCP server or gh CLI for cross-repo queries. Uses dotnet-release generate changes for structured change data. +compatibility: Requires GitHub MCP server or gh CLI for cross-repo queries. Uses dotnet-release generate changes for structured change data. Uses dotnet-inspect for API verification. --- # .NET Release Notes @@ -11,8 +11,10 @@ Generate and maintain release notes for .NET preview, RC, and GA releases. ## How it works 1. The `dotnet-release generate changes` tool diffs `source-manifest.json` between VMR release refs to produce `changes.json` — a comprehensive manifest of all PRs/commits that shipped -2. The agent reads `changes.json` and writes curated markdown release notes for high-value features -3. Output is one PR per release milestone in dotnet/core, maintained incrementally +2. The agent reads `changes.json` and identifies high-value features worth documenting +3. The agent uses `dotnet-inspect` to verify public API names, type shapes, and member signatures before writing about them +4. The agent writes curated markdown release notes with verified API references and code samples +5. Output is one PR per release milestone in dotnet/core, maintained incrementally ## Design @@ -26,3 +28,4 @@ Generate and maintain release notes for .NET preview, RC, and GA releases. - [component-mapping.md](references/component-mapping.md) — components, product slugs, output files - [format-template.md](references/format-template.md) — markdown document structure - [editorial-rules.md](references/editorial-rules.md) — tone, attribution, naming +- [api-verification.md](references/api-verification.md) — using dotnet-inspect to verify APIs diff --git a/.github/skills/release-notes/references/api-verification.md b/.github/skills/release-notes/references/api-verification.md new file mode 100644 index 0000000000..65f981d067 --- /dev/null +++ b/.github/skills/release-notes/references/api-verification.md @@ -0,0 +1,85 @@ +# API Verification with dotnet-inspect + +Release notes reference specific .NET types, methods, properties, and enums by name. Getting these wrong is worse than omitting them — incorrect API names look authoritative but teach developers something false. Use `dotnet-inspect` to verify every API reference before including it in release notes. + +## Why this matters + +The agent reads PR titles and descriptions from `changes.json` to understand what shipped. PR titles often use informal or abbreviated names that don't match the actual public API surface. For example: + +- A PR titled "Add zstd support" doesn't tell you whether the type is `ZstdCompressionProvider`, `ZstandardCompressionProvider`, or `ZStandardResponseCompressionProvider` +- A PR titled "Add JsonContains" doesn't tell you the return type, parameter types, or whether it's `JsonContains` or `JsonContainsAsync` +- A PR titled "Rename JsonExists" doesn't confirm the new name is `JsonPathExists` vs `JsonExistsPath` vs `JsonPathExistsAsync` + +Without verification, the agent will guess — and guesses end up in code samples that don't compile. + +## How to use dotnet-inspect + +`dotnet-inspect` is available as a Copilot skill. Invoke it with `dotnet-inspect` to get the full command reference and mental model. The tool queries NuGet packages, platform libraries, and local files. + +### Installation + +The tool runs via `dnx` (like `npx` for .NET). No installation needed — just use: + +```bash +dnx dotnet-inspect -y -- +``` + +### Common verification patterns + +**Find a type by name** — when a PR mentions a new type: + +```bash +# Search across ASP.NET Core packages for the zstd type +dnx dotnet-inspect -y -- find "*Zstandard*" --aspnetcore + +# Search across all platform libraries +dnx dotnet-inspect -y -- find "*ProcessStartOptions*" --platform +``` + +**Verify a type's members** — when writing a code sample: + +```bash +# See the full API surface of a type +dnx dotnet-inspect -y -- member ZstandardCompressionProvider --aspnetcore + +# Check specific method signatures +dnx dotnet-inspect -y -- member JsonSerializer --package System.Text.Json -m Deserialize +``` + +**Verify an enum value** — when referencing specific options: + +```bash +# Check what values an enum has +dnx dotnet-inspect -y -- member CompressionLevel --platform -k field +``` + +**Diff between versions** — when documenting what's new: + +```bash +# See what APIs were added between previews +dnx dotnet-inspect -y -- diff --package Microsoft.EntityFrameworkCore@11.0.0-preview.2..11.0.0-preview.3 +``` + +## When to verify + +Verify **every** API name that appears in: + +- Prose text (e.g., "The new `ProcessStartOptions` class...") +- Code samples (every type, method, and property used) +- Before/after comparisons (both the old and new names) + +You do NOT need to verify: + +- PR numbers and URLs (these come from `changes.json` and are authoritative) +- General concepts (e.g., "Zstandard compression" as a concept vs `ZstandardCompressionProvider` as a type) +- CLI flags (e.g., `dotnet test --artifacts-path`) + +## What to do when verification fails + +If `dotnet-inspect` can't find a type, it may be: + +1. **Not yet published** — preview packages may not be on NuGet yet. Leave a `` placeholder. +2. **Named differently** — search with a broader pattern (`find "*Zstd*"` instead of the exact name). +3. **Internal** — the type may not be public. Don't document internal types in release notes. + +When in doubt, describe the feature without naming specific types and link to the PR. A correct prose description is always better than a wrong code sample. diff --git a/.github/skills/release-notes/references/quality-bar.md b/.github/skills/release-notes/references/quality-bar.md index 2b80126d1d..bbfeea1c0c 100644 --- a/.github/skills/release-notes/references/quality-bar.md +++ b/.github/skills/release-notes/references/quality-bar.md @@ -60,6 +60,18 @@ A release note that just names a feature is useless. Every entry must answer: If you can't write a code sample for a feature, question whether it's user-facing enough for release notes. +## Verify before you write + +**Never guess API names.** Before writing about any type, method, property, or enum value, verify it exists using `dotnet-inspect`. See [api-verification.md](api-verification.md) for the workflow. + +An incorrect type name in release notes is worse than a placeholder. It teaches developers something wrong and erodes trust. When you can't verify an API: + +- Use a `` placeholder +- Describe the feature in prose without naming specific types +- Link to the PR and let the reader find the API themselves + +This applies to code samples too. A fabricated code sample that uses invented types is harmful — it looks authoritative but won't compile. + ## Tone - Positive — highlight what's new, don't dwell on what was missing From 6f30d9dc98c34e33d573f2506a219dde55cd5447 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Wed, 1 Apr 2026 15:28:47 -0700 Subject: [PATCH 36/65] Add nightly SDK download workflow for API verification - Query dotnet11 NuGet feed to find the last preview.N build - Download SDK tarball from ci.dot.net with matching version - Set DOTNET_ROOT to point dotnet-inspect at the correct build - Document why stale builds give false negatives - Add PR test fallback when nightly is unavailable - Tested: confirmed AnyNewLine=2048 in P3, ProcessStartOptions correctly absent Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../references/api-verification.md | 72 +++++++++++++++++-- 1 file changed, 68 insertions(+), 4 deletions(-) diff --git a/.github/skills/release-notes/references/api-verification.md b/.github/skills/release-notes/references/api-verification.md index 65f981d067..1129270fcd 100644 --- a/.github/skills/release-notes/references/api-verification.md +++ b/.github/skills/release-notes/references/api-verification.md @@ -24,6 +24,68 @@ The tool runs via `dnx` (like `npx` for .NET). No installation needed — just u dnx dotnet-inspect -y -- ``` +### Getting a current SDK for verification + +By default, `--platform` queries the locally installed SDK. When writing release notes for Preview N, the local SDK may be Preview N-1 or older — so new APIs won't be found. **Always check what version you're querying:** + +```bash +dotnet --version # e.g., 11.0.100-preview.2.26159.112 +``` + +If the installed SDK is older than the preview you're writing about, download the correct nightly SDK build. + +### Finding the right build + +Preview releases ship from release branches, not main. By the time you're writing Preview N release notes, main may have moved to Preview N+1 or alpha. You need the **last daily build from main before the branding changed past Preview N**. + +The `dotnet11` NuGet feed publishes daily builds of `Microsoft.NETCore.App.Ref` with version strings like `11.0.0-preview.3.26179.102`. Query the feed to find the latest Preview N build: + +```bash +# Find the latest preview.3 build number from the nightly feed +curl -s "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet11/nuget/v3/flat2/microsoft.netcore.app.ref/index.json" \ + | python3 -c " +import json, sys +versions = json.load(sys.stdin)['versions'] +p3 = sorted([v for v in versions if 'preview.3' in v], + key=lambda v: [int(n) for n in v.split('.')[-2:]]) +print(p3[-1]) +" +# Output: 11.0.0-preview.3.26179.102 +``` + +The SDK tarball version matches the runtime version's build number. Download it from ci.dot.net: + +```bash +# The version from the feed query above +VERSION="11.0.100-preview.3.26179.102" + +# Download the SDK tarball +curl -Lo /tmp/dotnet-sdk-p3.tar.gz \ + "https://ci.dot.net/public/Sdk/${VERSION}/dotnet-sdk-${VERSION}-osx-arm64.tar.gz" + +# Extract to a local directory (don't overwrite the system SDK) +mkdir -p /tmp/dotnet-p3 +tar xzf /tmp/dotnet-sdk-p3.tar.gz -C /tmp/dotnet-p3 + +# Verify +/tmp/dotnet-p3/dotnet --version # 11.0.100-preview.3.26179.102 +``` + +Then point `dotnet-inspect` at it with `DOTNET_ROOT`: + +```bash +DOTNET_ROOT=/tmp/dotnet-p3 dnx dotnet-inspect -y -- find "*AnyNewLine*" --platform +DOTNET_ROOT=/tmp/dotnet-p3 dnx dotnet-inspect -y -- member RegexOptions --platform System.Text.RegularExpressions -k field +``` + +### Why this matters: stale builds give false negatives + +Without the correct build, `dotnet-inspect --platform` queries whatever SDK is installed locally. If you're on Preview 2 and writing Preview 3 notes, every new P3 API will come back "not found" — but that doesn't mean the API was reverted or doesn't exist. It means you're querying stale data. + +A false negative on a stale build is indistinguishable from a genuinely reverted API. **Always verify against a build that matches the preview you're writing about.** + +**Report the build version.** When writing release notes, note which SDK build you verified against — either as a comment in the markdown or in your working notes. For example: `` + ### Common verification patterns **Find a type by name** — when a PR mentions a new type: @@ -50,7 +112,7 @@ dnx dotnet-inspect -y -- member JsonSerializer --package System.Text.Json -m Des ```bash # Check what values an enum has -dnx dotnet-inspect -y -- member CompressionLevel --platform -k field +dnx dotnet-inspect -y -- member RegexOptions --platform -k field ``` **Diff between versions** — when documenting what's new: @@ -76,10 +138,12 @@ You do NOT need to verify: ## What to do when verification fails -If `dotnet-inspect` can't find a type, it may be: +If `dotnet-inspect` can't find a type: -1. **Not yet published** — preview packages may not be on NuGet yet. Leave a `` placeholder. +1. **Check your build version first** — run `dotnet --version` or check `DOTNET_ROOT`. If you're querying a build older than the preview you're writing about, the API may simply not be in that build. Download the correct nightly (see above). 2. **Named differently** — search with a broader pattern (`find "*Zstd*"` instead of the exact name). -3. **Internal** — the type may not be public. Don't document internal types in release notes. +3. **Reverted** — check `changes.json` for a revert PR. If the API was added and then reverted in the same preview, it didn't ship. Don't document it. +4. **Internal** — the type may not be public. Don't document internal types in release notes. +5. **Read the PR tests** — when the correct nightly build is unavailable, the PR's test files are ground truth. Tests compile and run against the actual API surface. Derive code samples from test assertions rather than guessing type names. When in doubt, describe the feature without naming specific types and link to the PR. A correct prose description is always better than a wrong code sample. From 6d81c052506a395a5606a242f9072f6fb534ecc1 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Wed, 1 Apr 2026 15:31:17 -0700 Subject: [PATCH 37/65] Simplify API verification: query NuGet feed directly, no SDK install Replace DOTNET_ROOT/tarball approach with direct --package --source queries against the dotnet11 nightly NuGet feed. Pure data, no SDK installation needed. Link to richlander/dotnet-release#42 for dotnet-release to provide build metadata automatically. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../references/api-verification.md | 98 +++++++------------ 1 file changed, 33 insertions(+), 65 deletions(-) diff --git a/.github/skills/release-notes/references/api-verification.md b/.github/skills/release-notes/references/api-verification.md index 1129270fcd..f4b56320eb 100644 --- a/.github/skills/release-notes/references/api-verification.md +++ b/.github/skills/release-notes/references/api-verification.md @@ -16,32 +16,24 @@ Without verification, the agent will guess — and guesses end up in code sample `dotnet-inspect` is available as a Copilot skill. Invoke it with `dotnet-inspect` to get the full command reference and mental model. The tool queries NuGet packages, platform libraries, and local files. -### Installation +### Running the tool -The tool runs via `dnx` (like `npx` for .NET). No installation needed — just use: +The tool runs via `dnx` (like `npx` for .NET). No installation needed: ```bash dnx dotnet-inspect -y -- ``` -### Getting a current SDK for verification +### Querying the correct build -By default, `--platform` queries the locally installed SDK. When writing release notes for Preview N, the local SDK may be Preview N-1 or older — so new APIs won't be found. **Always check what version you're querying:** +When writing release notes for Preview N, the locally installed SDK is often Preview N-1 or older. The `--platform` flag queries the local SDK, so new APIs won't be found. Instead, **query the nightly NuGet packages directly** from the `dotnet11` feed using `--package` and `--source`. -```bash -dotnet --version # e.g., 11.0.100-preview.2.26159.112 -``` - -If the installed SDK is older than the preview you're writing about, download the correct nightly SDK build. +The `dotnet-release` tool provides build metadata (package versions, feed URLs) alongside `changes.json`. Use that metadata to construct the correct queries. See [richlander/dotnet-release#42](https://github.com/richlander/dotnet-release/issues/42) for the tracking issue. -### Finding the right build - -Preview releases ship from release branches, not main. By the time you're writing Preview N release notes, main may have moved to Preview N+1 or alpha. You need the **last daily build from main before the branding changed past Preview N**. - -The `dotnet11` NuGet feed publishes daily builds of `Microsoft.NETCore.App.Ref` with version strings like `11.0.0-preview.3.26179.102`. Query the feed to find the latest Preview N build: +Until that's available, find the correct package version manually: ```bash -# Find the latest preview.3 build number from the nightly feed +# Find the latest preview.3 runtime ref pack version from the nightly feed curl -s "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet11/nuget/v3/flat2/microsoft.netcore.app.ref/index.json" \ | python3 -c " import json, sys @@ -53,75 +45,51 @@ print(p3[-1]) # Output: 11.0.0-preview.3.26179.102 ``` -The SDK tarball version matches the runtime version's build number. Download it from ci.dot.net: - -```bash -# The version from the feed query above -VERSION="11.0.100-preview.3.26179.102" - -# Download the SDK tarball -curl -Lo /tmp/dotnet-sdk-p3.tar.gz \ - "https://ci.dot.net/public/Sdk/${VERSION}/dotnet-sdk-${VERSION}-osx-arm64.tar.gz" - -# Extract to a local directory (don't overwrite the system SDK) -mkdir -p /tmp/dotnet-p3 -tar xzf /tmp/dotnet-sdk-p3.tar.gz -C /tmp/dotnet-p3 +The feed URL for .NET 11 nightly builds is documented in the VMR's [`docs/builds-table.md`](https://github.com/dotnet/dotnet/blob/main/docs/builds-table.md): -# Verify -/tmp/dotnet-p3/dotnet --version # 11.0.100-preview.3.26179.102 +```text +https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet11/nuget/v3/index.json ``` -Then point `dotnet-inspect` at it with `DOTNET_ROOT`: - -```bash -DOTNET_ROOT=/tmp/dotnet-p3 dnx dotnet-inspect -y -- find "*AnyNewLine*" --platform -DOTNET_ROOT=/tmp/dotnet-p3 dnx dotnet-inspect -y -- member RegexOptions --platform System.Text.RegularExpressions -k field -``` - -### Why this matters: stale builds give false negatives - -Without the correct build, `dotnet-inspect --platform` queries whatever SDK is installed locally. If you're on Preview 2 and writing Preview 3 notes, every new P3 API will come back "not found" — but that doesn't mean the API was reverted or doesn't exist. It means you're querying stale data. - -A false negative on a stale build is indistinguishable from a genuinely reverted API. **Always verify against a build that matches the preview you're writing about.** - -**Report the build version.** When writing release notes, note which SDK build you verified against — either as a comment in the markdown or in your working notes. For example: `` - ### Common verification patterns -**Find a type by name** — when a PR mentions a new type: +Use `--package Name@Version` with `--source` to query the correct preview build. All queries below are pure data — no SDK installation needed. + +**Find a type by name:** ```bash -# Search across ASP.NET Core packages for the zstd type -dnx dotnet-inspect -y -- find "*Zstandard*" --aspnetcore +FEED="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet11/nuget/v3/index.json" +VER="11.0.0-preview.3.26179.102" -# Search across all platform libraries -dnx dotnet-inspect -y -- find "*ProcessStartOptions*" --platform +# Search the runtime ref pack +dnx dotnet-inspect -y -- find "*AnyNewLine*" --package "Microsoft.NETCore.App.Ref@${VER}" --source "$FEED" + +# Search ASP.NET Core ref pack +dnx dotnet-inspect -y -- find "*Zstandard*" --package "Microsoft.AspNetCore.App.Ref@${VER}" --source "$FEED" ``` -**Verify a type's members** — when writing a code sample: +**Verify a type's members:** ```bash -# See the full API surface of a type -dnx dotnet-inspect -y -- member ZstandardCompressionProvider --aspnetcore - -# Check specific method signatures -dnx dotnet-inspect -y -- member JsonSerializer --package System.Text.Json -m Deserialize +dnx dotnet-inspect -y -- member RegexOptions --package "Microsoft.NETCore.App.Ref@${VER}" --source "$FEED" --library System.Text.RegularExpressions -k field ``` -**Verify an enum value** — when referencing specific options: +**Diff between versions:** ```bash -# Check what values an enum has -dnx dotnet-inspect -y -- member RegexOptions --platform -k field +dnx dotnet-inspect -y -- diff --package "Microsoft.NETCore.App.Ref@11.0.0-preview.2..11.0.0-preview.3" --source "$FEED" ``` -**Diff between versions** — when documenting what's new: +### Report what you verified against -```bash -# See what APIs were added between previews -dnx dotnet-inspect -y -- diff --package Microsoft.EntityFrameworkCore@11.0.0-preview.2..11.0.0-preview.3 +Always note the package version in a markdown comment so reviewers know the verification is grounded: + +```markdown + ``` +This makes it possible to distinguish "API doesn't exist" from "verified against a stale build." + ## When to verify Verify **every** API name that appears in: @@ -140,10 +108,10 @@ You do NOT need to verify: If `dotnet-inspect` can't find a type: -1. **Check your build version first** — run `dotnet --version` or check `DOTNET_ROOT`. If you're querying a build older than the preview you're writing about, the API may simply not be in that build. Download the correct nightly (see above). +1. **Check your package version first** — are you querying a Preview N-1 package while writing Preview N notes? Find the correct version (see above). 2. **Named differently** — search with a broader pattern (`find "*Zstd*"` instead of the exact name). 3. **Reverted** — check `changes.json` for a revert PR. If the API was added and then reverted in the same preview, it didn't ship. Don't document it. 4. **Internal** — the type may not be public. Don't document internal types in release notes. -5. **Read the PR tests** — when the correct nightly build is unavailable, the PR's test files are ground truth. Tests compile and run against the actual API surface. Derive code samples from test assertions rather than guessing type names. +5. **Read the PR tests** — the PR's test files are ground truth. Tests compile and run against the actual API surface. Derive code samples from test assertions rather than guessing type names. When in doubt, describe the feature without naming specific types and link to the PR. A correct prose description is always better than a wrong code sample. From dfe874a8d5cf4caf7abf1887f694a6952b6f3ec4 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Wed, 1 Apr 2026 19:33:35 -0700 Subject: [PATCH 38/65] Update api-verification: use dotnet-release build-metadata pipeline Replace manual feed query with dotnet-release generate build-metadata command (v2.3.0). Show real output including all ref packs. Remove --library workaround (fixed in dotnet-inspect). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../references/api-verification.md | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/.github/skills/release-notes/references/api-verification.md b/.github/skills/release-notes/references/api-verification.md index f4b56320eb..85f331757c 100644 --- a/.github/skills/release-notes/references/api-verification.md +++ b/.github/skills/release-notes/references/api-verification.md @@ -26,34 +26,46 @@ dnx dotnet-inspect -y -- ### Querying the correct build -When writing release notes for Preview N, the locally installed SDK is often Preview N-1 or older. The `--platform` flag queries the local SDK, so new APIs won't be found. Instead, **query the nightly NuGet packages directly** from the `dotnet11` feed using `--package` and `--source`. +When writing release notes for Preview N, the locally installed SDK is often Preview N-1 or older. The `--platform` flag queries the local SDK, so new APIs won't be found. Instead, **query the nightly NuGet packages directly** using `--package` and `--source`. -The `dotnet-release` tool provides build metadata (package versions, feed URLs) alongside `changes.json`. Use that metadata to construct the correct queries. See [richlander/dotnet-release#42](https://github.com/richlander/dotnet-release/issues/42) for the tracking issue. +### Step 1: Generate build metadata -Until that's available, find the correct package version manually: +`dotnet-release generate build-metadata` reads the VMR's version info and queries the nightly NuGet feed to find the correct package versions: ```bash -# Find the latest preview.3 runtime ref pack version from the nightly feed -curl -s "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet11/nuget/v3/flat2/microsoft.netcore.app.ref/index.json" \ - | python3 -c " -import json, sys -versions = json.load(sys.stdin)['versions'] -p3 = sorted([v for v in versions if 'preview.3' in v], - key=lambda v: [int(n) for n in v.split('.')[-2:]]) -print(p3[-1]) -" -# Output: 11.0.0-preview.3.26179.102 +dotnet-release generate build-metadata ~/git/dotnet \ + --base v11.0.0-preview.2.26159.112 \ + --head origin/release/11.0.1xx-preview3 \ + --output build-metadata.json ``` -The feed URL for .NET 11 nightly builds is documented in the VMR's [`docs/builds-table.md`](https://github.com/dotnet/dotnet/blob/main/docs/builds-table.md): - -```text -https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet11/nuget/v3/index.json +This produces: + +```json +{ + "version": "11.0.0-preview.3", + "base_ref": "v11.0.0-preview.2.26159.112", + "head_ref": "origin/release/11.0.1xx-preview3", + "build": { + "version": "11.0.0-preview.3.26179.102", + "sdk_version": "11.0.100-preview.3.26179.102", + "sdk_url": "https://ci.dot.net/public/Sdk/11.0.100-preview.3.26179.102/dotnet-sdk-11.0.100-preview.3.26179.102-{platform}.tar.gz" + }, + "nuget": { + "source": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet11/nuget/v3/index.json", + "packages": { + "Microsoft.NETCore.App.Ref": "11.0.0-preview.3.26179.102", + "Microsoft.AspNetCore.App.Ref": "11.0.0-preview.3.26179.102", + "Microsoft.WindowsDesktop.App.Ref": "11.0.0-preview.3.26179.102", + "Microsoft.EntityFrameworkCore": "11.0.0-preview.3.26179.102" + } + } +} ``` -### Common verification patterns +### Step 2: Verify APIs against the correct packages -Use `--package Name@Version` with `--source` to query the correct preview build. All queries below are pure data — no SDK installation needed. +Read the `nuget.source` and `nuget.packages` from `build-metadata.json` and use them directly with `dotnet-inspect`. All queries are pure data — no SDK installation needed. **Find a type by name:** @@ -71,7 +83,7 @@ dnx dotnet-inspect -y -- find "*Zstandard*" --package "Microsoft.AspNetCore.App. **Verify a type's members:** ```bash -dnx dotnet-inspect -y -- member RegexOptions --package "Microsoft.NETCore.App.Ref@${VER}" --source "$FEED" --library System.Text.RegularExpressions -k field +dnx dotnet-inspect -y -- member RegexOptions --package "Microsoft.NETCore.App.Ref@${VER}" --source "$FEED" -k field ``` **Diff between versions:** From a40133375f707e973869dee7f1ff6e1877cb8dd3 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Wed, 1 Apr 2026 20:44:44 -0700 Subject: [PATCH 39/65] Editorial rules: prefer short sentences, avoid interrupting clauses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Long sentences are fine when they flow as one thought (what → why). They're not fine when a subordinate clause interrupts the main verb. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/references/editorial-rules.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/skills/release-notes/references/editorial-rules.md b/.github/skills/release-notes/references/editorial-rules.md index 74a6e916f2..d2dd9211de 100644 --- a/.github/skills/release-notes/references/editorial-rules.md +++ b/.github/skills/release-notes/references/editorial-rules.md @@ -8,6 +8,10 @@ Tone, attribution, and content guidelines for .NET release notes. - ✅ `ProcessExitStatus provides a unified representation of how a process terminated.` - ❌ `Previously, there was no way to determine how a process terminated.` - When context about the prior state is needed, keep it brief — one clause, then pivot to the new capability +- **Prefer short, direct sentences** — If a sentence has a parenthetical clause (`which...`, `where...`, `that...`) longer than a few words, split it into two sentences. Lead with the news, follow with context. Long sentences are fine when they flow as a single continuous thought (what → why); they're not fine when a subordinate clause interrupts the main verb. + - ✅ `Runtime-async is now enabled for NativeAOT. This eliminates the state-machine overhead of async/await for ahead-of-time compiled applications.` + - ❌ `The runtime-async feature, which eliminates the state-machine overhead of async/await, is now enabled for NativeAOT.` + - ✅ `The JIT now generates ARM64 SM4 and SHA3 instructions directly, enabling hardware-accelerated implementations on capable processors.` (long but flows — one thought) ## Entry naming From 214ef9abc8c9a8af83a71f5c98c90f16b229c122 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Wed, 1 Apr 2026 20:47:45 -0700 Subject: [PATCH 40/65] Editorial rules: three-tier feature ordering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Broad interest → cluster by area → alphabetical within cluster. Replaces vague 'customer impact' guidance. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../skills/release-notes/references/editorial-rules.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/skills/release-notes/references/editorial-rules.md b/.github/skills/release-notes/references/editorial-rules.md index d2dd9211de..70cc0767a6 100644 --- a/.github/skills/release-notes/references/editorial-rules.md +++ b/.github/skills/release-notes/references/editorial-rules.md @@ -31,13 +31,13 @@ Tone, attribution, and content guidelines for .NET release notes. ## Feature ordering -Order features by **customer impact**: +Order features using three tiers, applied in order: -1. Major new capabilities — especially those with high community reaction counts -2. Meaningful improvements to existing capabilities -3. Smaller additions +1. **Broad interest first** — features most developers will care about go at the top. A new `RegexOptions` value ranks above an ARM64-only instruction set. +2. **Cluster related features** — group related items together even if they differ in importance. All Regex work in one block, all System.Text.Json work in another, all JIT work together. Readers scan by area. +3. **Alphabetical within a cluster** — when features within a cluster have roughly equal weight, alphabetical order makes them scannable. -Use PR and issue reaction counts as a signal, but apply judgment — a niche feature with 100 reactions may still be less impactful than a broadly useful one with 10. +Use PR and issue reaction counts as a signal for tier 1, but apply judgment — a niche feature with 100 reactions may still rank below a broadly useful one with 10. ## Community attribution From da285a8549b8cd20972feef82dc84ad2ef31f3e3 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Wed, 1 Apr 2026 20:54:04 -0700 Subject: [PATCH 41/65] Editorial rules: 20/80 rule, two-sentence test, value headlines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Features must matter to ≥20% of readers. Write so the other 80% understand why. If you can only write two sentences, it's probably an engineering fix — cut it. Headlines should convey value. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/references/editorial-rules.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/skills/release-notes/references/editorial-rules.md b/.github/skills/release-notes/references/editorial-rules.md index 70cc0767a6..b51f8a411e 100644 --- a/.github/skills/release-notes/references/editorial-rules.md +++ b/.github/skills/release-notes/references/editorial-rules.md @@ -29,6 +29,12 @@ Tone, attribution, and content guidelines for .NET release notes. - Include specific before/after measurements when compelling - Do **not** embed full BenchmarkDotNet tables — summarize in prose +## What to include + +- **The 20/80 rule** — at least 20% of readers need to care about a feature. Write so the other 80% understand why those 20% care and why they might, too. A feature that only matters to a narrow audience can still earn its place if the writeup makes the value legible to everyone. +- **The two-sentence test** — if you can only write two sentences about a feature, it's probably an engineering fix, not a feature. Cut it. A community contribution or breaking change can lift a borderline entry, but "fixed an internal bug that happened to be visible" is not a feature. +- **Headlines should convey value** — a heading like "GC regions on macOS" doesn't tell the reader whether this is good or bad. Prefer headings that hint at the benefit: "GC regions enabled on macOS" or "Server GC memory model now available on macOS." + ## Feature ordering Order features using three tiers, applied in order: From 7e200b8fea8353efbed21b212fda960b128c28af Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Wed, 1 Apr 2026 21:03:23 -0700 Subject: [PATCH 42/65] SKILL.md: add examples/ to reference documents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agents weren't told to read the curated examples before writing. The examples directory has 5 component files and 12 editorial principles — our best guidance on style. Now explicitly referenced. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/SKILL.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/skills/release-notes/SKILL.md b/.github/skills/release-notes/SKILL.md index 71aa4a80fc..61d2863d4e 100644 --- a/.github/skills/release-notes/SKILL.md +++ b/.github/skills/release-notes/SKILL.md @@ -29,3 +29,4 @@ Generate and maintain release notes for .NET preview, RC, and GA releases. - [format-template.md](references/format-template.md) — markdown document structure - [editorial-rules.md](references/editorial-rules.md) — tone, attribution, naming - [api-verification.md](references/api-verification.md) — using dotnet-inspect to verify APIs +- [examples/](references/examples/) — curated examples from previous releases, organized by component. **Read the examples for your component before writing.** The [examples/README.md](references/examples/README.md) lists 12 editorial principles derived from what works and what doesn't in past release notes. From 2053759ee5b195b8e9b76b2a6cdff042dc81baa0 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Wed, 1 Apr 2026 22:02:31 -0700 Subject: [PATCH 43/65] Editorial rules: don't overstate maturity in previews Say what concretely changed (enabled by default, no opt-in needed) rather than editorializing readiness (ready for production use). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/references/editorial-rules.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/skills/release-notes/references/editorial-rules.md b/.github/skills/release-notes/references/editorial-rules.md index b51f8a411e..1e96ea48a3 100644 --- a/.github/skills/release-notes/references/editorial-rules.md +++ b/.github/skills/release-notes/references/editorial-rules.md @@ -8,6 +8,9 @@ Tone, attribution, and content guidelines for .NET release notes. - ✅ `ProcessExitStatus provides a unified representation of how a process terminated.` - ❌ `Previously, there was no way to determine how a process terminated.` - When context about the prior state is needed, keep it brief — one clause, then pivot to the new capability +- **Don't editorialize beyond the facts** — state what concretely changed ("enabled by default", "no longer requires opt-in") rather than making claims you can't back up ("ready for production use", "signals maturity"). If a PR removes a preview attribute, say that. Don't interpret it as a promise. + - ✅ `Runtime-async is now enabled by default for anyone targeting net11.0.` + - ❌ `This signals that runtime-async is ready for production use.` - **Prefer short, direct sentences** — If a sentence has a parenthetical clause (`which...`, `where...`, `that...`) longer than a few words, split it into two sentences. Lead with the news, follow with context. Long sentences are fine when they flow as a single continuous thought (what → why); they're not fine when a subordinate clause interrupts the main verb. - ✅ `Runtime-async is now enabled for NativeAOT. This eliminates the state-machine overhead of async/await for ahead-of-time compiled applications.` - ❌ `The runtime-async feature, which eliminates the state-machine overhead of async/await, is now enabled for NativeAOT.` From 6eeb131155c16e3d648649f12b7b019919a38027 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Wed, 1 Apr 2026 22:10:17 -0700 Subject: [PATCH 44/65] Editorial rules: filtered features comment block When cutting features for 20/80 or two-sentence test, record them in an HTML comment in the output file. Creates a learning record for future runs and human reviewers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../references/editorial-rules.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/skills/release-notes/references/editorial-rules.md b/.github/skills/release-notes/references/editorial-rules.md index 1e96ea48a3..084f74b884 100644 --- a/.github/skills/release-notes/references/editorial-rules.md +++ b/.github/skills/release-notes/references/editorial-rules.md @@ -94,3 +94,24 @@ Include a bug fix when ALL of these apply: 3. A fix shipped in the current preview Frame positively: "Based on community feedback, X now does Y." + +## Filtered features + +When you cut a feature for failing the 20/80 rule or two-sentence test, record it in an HTML comment block in the output file. This creates a learning record — future runs and human reviewers can see what was considered and why it was excluded. + +Place the comment block immediately before the Bug fixes section: + +```html + +``` + +Good filter reasons: +- **Internal infrastructure** — "Implementation detail of the Mono → CoreCLR unification. Developers don't target the interpreter." +- **Too narrow** — "Only affects COM interop startup — very narrow audience." +- **Engineering fix, not a feature** — "Two sentences max. No user-visible behavior change beyond a perf number." +- **Provider extensibility** — "Only matters to database provider authors, not EF Core users." + +The comment is invisible to readers but preserved in the file for the next person (or agent) who reviews the notes. From 3abddf2d170b179f014e6c644728fe72c0458ba2 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Wed, 1 Apr 2026 22:21:54 -0700 Subject: [PATCH 45/65] Editorial rules: TODO for borderline entries, vet community lists - Borderline features should get a TODO comment asking for data rather than being silently included or silently cut. - Community contributor lists must be vetted: exclude -msft suffixes and other Microsoft employee indicators. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/references/editorial-rules.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/skills/release-notes/references/editorial-rules.md b/.github/skills/release-notes/references/editorial-rules.md index 084f74b884..248e358d98 100644 --- a/.github/skills/release-notes/references/editorial-rules.md +++ b/.github/skills/release-notes/references/editorial-rules.md @@ -37,6 +37,7 @@ Tone, attribution, and content guidelines for .NET release notes. - **The 20/80 rule** — at least 20% of readers need to care about a feature. Write so the other 80% understand why those 20% care and why they might, too. A feature that only matters to a narrow audience can still earn its place if the writeup makes the value legible to everyone. - **The two-sentence test** — if you can only write two sentences about a feature, it's probably an engineering fix, not a feature. Cut it. A community contribution or breaking change can lift a borderline entry, but "fixed an internal bug that happened to be visible" is not a feature. - **Headlines should convey value** — a heading like "GC regions on macOS" doesn't tell the reader whether this is good or bad. Prefer headings that hint at the benefit: "GC regions enabled on macOS" or "Server GC memory model now available on macOS." +- **TODO for borderline entries** — when a feature might deserve inclusion but you lack data to justify it (benchmark numbers, real-world impact, user demand), keep the entry but add an HTML `` comment asking for the missing information. This is better than silently including a vague claim or silently cutting something that might matter. The TODO should state what's needed and link to the PR where the data might live. ## Feature ordering @@ -62,6 +63,8 @@ Thank you [@username](https://github.com/username) for this contribution! At the bottom of each component's notes, list ALL external contributors — not just those with documented features. Use the `community-contribution` label to identify them. +**Vet the list** — the `community-contribution` label is sometimes wrong. Exclude usernames containing `-msft`, `-microsoft`, or other Microsoft suffixes. When in doubt about whether someone is a Microsoft employee, leave them out of the community list. + ```markdown ## Community contributors From e4c380d379864dc9bffdda61f6541ad6006d0501 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Wed, 1 Apr 2026 22:30:01 -0700 Subject: [PATCH 46/65] Format template: add release info and VMR refs to README README.md should include a version table and VMR base/head refs with links. Values come from build-metadata.json. Useful for tracking what was diffed and for incremental updates. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../release-notes/references/format-template.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/skills/release-notes/references/format-template.md b/.github/skills/release-notes/references/format-template.md index 2ea2de9ba4..01fb9e1739 100644 --- a/.github/skills/release-notes/references/format-template.md +++ b/.github/skills/release-notes/references/format-template.md @@ -42,8 +42,24 @@ The README.md links to all component files and includes the general docs link. C .NET updates: - [What's new in .NET ](https://learn.microsoft.com/dotnet/core/whats-new/dotnet-/overview) + +## Release information + +| | Version | +| --- | --- | +| Runtime | | +| SDK | | + +### VMR refs + +These release notes were generated from the [dotnet/dotnet](https://github.com/dotnet/dotnet) VMR: + +- **Base**: [``](https://github.com/dotnet/dotnet/tree/) +- **Head**: [``](https://github.com/dotnet/dotnet/tree/) ``` +Read the runtime version, SDK version, base ref, and head ref from `build-metadata.json`. + ### Component-specific docs links Some components have their own "What's new" page on learn.microsoft.com. Include these in the relevant component file when they exist. Discover them from the docs overview source: From 016b30d35b67750c6143c4f5f346bca97ef1b1d0 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Thu, 2 Apr 2026 03:10:32 -0700 Subject: [PATCH 47/65] =?UTF-8?q?Editorial=20rules:=20dedup=20nuance=20?= =?UTF-8?q?=E2=80=94=20state=20changes=20are=20new=20news?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A feature introduced in P1 and enabled by default in P3 is new news. Don't skip it just because the feature name appeared in a prior preview. Document the change in state, not the feature from scratch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../release-notes/references/editorial-rules.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/skills/release-notes/references/editorial-rules.md b/.github/skills/release-notes/references/editorial-rules.md index 248e358d98..186ec7d528 100644 --- a/.github/skills/release-notes/references/editorial-rules.md +++ b/.github/skills/release-notes/references/editorial-rules.md @@ -98,6 +98,18 @@ Include a bug fix when ALL of these apply: Frame positively: "Based on community feedback, X now does Y." +## Preview-to-preview deduplication + +When prior previews already documented a feature, don't repeat the same information. But **do** document significant state changes in the current preview. A feature that was introduced as opt-in in P1 and is now enabled by default in P3 is new news — document the change in state, not the feature from scratch. + +Ask: "What changed about this feature since the last preview's release notes?" If the answer is meaningful to users (enabled by default, no longer experimental, major perf improvement, new sub-features), write about that. If the answer is just "more PRs landed in the same area," skip it. + +Examples: +- ✅ "Runtime-async is now enabled by default for anyone targeting `net11.0`." (state change: opt-in → default) +- ✅ "The Regex source generator now handles alternations 3× faster." (new perf data) +- ❌ Repeating the full explanation of what runtime-async is from P1 (already documented) +- ❌ "More JIT optimizations landed this preview." (no specific news) + ## Filtered features When you cut a feature for failing the 20/80 rule or two-sentence test, record it in an HTML comment block in the output file. This creates a learning record — future runs and human reviewers can see what was considered and why it was excluded. From 7a80b7dcedaeb2af4981b783051f7955b35b07c1 Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Thu, 2 Apr 2026 22:05:38 -0700 Subject: [PATCH 48/65] Initialize Agentic Workflows agent --- .github/agents/agentic-workflows.agent.md | 178 ++++++++++++++++++++++ .github/workflows/copilot-setup-steps.yml | 26 ++++ 2 files changed, 204 insertions(+) create mode 100644 .github/agents/agentic-workflows.agent.md create mode 100644 .github/workflows/copilot-setup-steps.yml diff --git a/.github/agents/agentic-workflows.agent.md b/.github/agents/agentic-workflows.agent.md new file mode 100644 index 0000000000..ca1af04614 --- /dev/null +++ b/.github/agents/agentic-workflows.agent.md @@ -0,0 +1,178 @@ +--- +description: GitHub Agentic Workflows (gh-aw) - Create, debug, and upgrade AI-powered workflows with intelligent prompt routing +disable-model-invocation: true +--- + +# GitHub Agentic Workflows Agent + +This agent helps you work with **GitHub Agentic Workflows (gh-aw)**, a CLI extension for creating AI-powered workflows in natural language using markdown files. + +## What This Agent Does + +This is a **dispatcher agent** that routes your request to the appropriate specialized prompt based on your task: + +- **Creating new workflows**: Routes to `create` prompt +- **Updating existing workflows**: Routes to `update` prompt +- **Debugging workflows**: Routes to `debug` prompt +- **Upgrading workflows**: Routes to `upgrade-agentic-workflows` prompt +- **Creating report-generating workflows**: Routes to `report` prompt — consult this whenever the workflow posts status updates, audits, analyses, or any structured output as issues, discussions, or comments +- **Creating shared components**: Routes to `create-shared-agentic-workflow` prompt +- **Fixing Dependabot PRs**: Routes to `dependabot` prompt — use this when Dependabot opens PRs that modify generated manifest files (`.github/workflows/package.json`, `.github/workflows/requirements.txt`, `.github/workflows/go.mod`). Never merge those PRs directly; instead update the source `.md` files and rerun `gh aw compile --dependabot` to bundle all fixes +- **Analyzing test coverage**: Routes to `test-coverage` prompt — consult this whenever the workflow reads, analyzes, or reports on test coverage data from PRs or CI runs + +Workflows may optionally include: + +- **Project tracking / monitoring** (GitHub Projects updates, status reporting) +- **Orchestration / coordination** (one workflow assigning agents or dispatching and coordinating other workflows) + +## Files This Applies To + +- Workflow files: `.github/workflows/*.md` and `.github/workflows/**/*.md` +- Workflow lock files: `.github/workflows/*.lock.yml` +- Shared components: `.github/workflows/shared/*.md` +- Configuration: https://github.com/github/gh-aw/blob/v0.65.6/.github/aw/github-agentic-workflows.md + +## Problems This Solves + +- **Workflow Creation**: Design secure, validated agentic workflows with proper triggers, tools, and permissions +- **Workflow Debugging**: Analyze logs, identify missing tools, investigate failures, and fix configuration issues +- **Version Upgrades**: Migrate workflows to new gh-aw versions, apply codemods, fix breaking changes +- **Component Design**: Create reusable shared workflow components that wrap MCP servers + +## How to Use + +When you interact with this agent, it will: + +1. **Understand your intent** - Determine what kind of task you're trying to accomplish +2. **Route to the right prompt** - Load the specialized prompt file for your task +3. **Execute the task** - Follow the detailed instructions in the loaded prompt + +## Available Prompts + +### Create New Workflow +**Load when**: User wants to create a new workflow from scratch, add automation, or design a workflow that doesn't exist yet + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.65.6/.github/aw/create-agentic-workflow.md + +**Use cases**: +- "Create a workflow that triages issues" +- "I need a workflow to label pull requests" +- "Design a weekly research automation" + +### Update Existing Workflow +**Load when**: User wants to modify, improve, or refactor an existing workflow + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.65.6/.github/aw/update-agentic-workflow.md + +**Use cases**: +- "Add web-fetch tool to the issue-classifier workflow" +- "Update the PR reviewer to use discussions instead of issues" +- "Improve the prompt for the weekly-research workflow" + +### Debug Workflow +**Load when**: User needs to investigate, audit, debug, or understand a workflow, troubleshoot issues, analyze logs, or fix errors + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.65.6/.github/aw/debug-agentic-workflow.md + +**Use cases**: +- "Why is this workflow failing?" +- "Analyze the logs for workflow X" +- "Investigate missing tool calls in run #12345" + +### Upgrade Agentic Workflows +**Load when**: User wants to upgrade workflows to a new gh-aw version or fix deprecations + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.65.6/.github/aw/upgrade-agentic-workflows.md + +**Use cases**: +- "Upgrade all workflows to the latest version" +- "Fix deprecated fields in workflows" +- "Apply breaking changes from the new release" + +### Create a Report-Generating Workflow +**Load when**: The workflow being created or updated produces reports — recurring status updates, audit summaries, analyses, or any structured output posted as a GitHub issue, discussion, or comment + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.65.6/.github/aw/report.md + +**Use cases**: +- "Create a weekly CI health report" +- "Post a daily security audit to Discussions" +- "Add a status update comment to open PRs" + +### Create Shared Agentic Workflow +**Load when**: User wants to create a reusable workflow component or wrap an MCP server + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.65.6/.github/aw/create-shared-agentic-workflow.md + +**Use cases**: +- "Create a shared component for Notion integration" +- "Wrap the Slack MCP server as a reusable component" +- "Design a shared workflow for database queries" + +### Fix Dependabot PRs +**Load when**: User needs to close or fix open Dependabot PRs that update dependencies in generated manifest files (`.github/workflows/package.json`, `.github/workflows/requirements.txt`, `.github/workflows/go.mod`) + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.65.6/.github/aw/dependabot.md + +**Use cases**: +- "Fix the open Dependabot PRs for npm dependencies" +- "Bundle and close the Dependabot PRs for workflow dependencies" +- "Update @playwright/test to fix the Dependabot PR" + +### Analyze Test Coverage +**Load when**: The workflow reads, analyzes, or reports test coverage — whether triggered by a PR, a schedule, or a slash command. Always consult this prompt before designing the coverage data strategy. + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.65.6/.github/aw/test-coverage.md + +**Use cases**: +- "Create a workflow that comments coverage on PRs" +- "Analyze coverage trends over time" +- "Add a coverage gate that blocks PRs below a threshold" + +## Instructions + +When a user interacts with you: + +1. **Identify the task type** from the user's request +2. **Load the appropriate prompt** from the GitHub repository URLs listed above +3. **Follow the loaded prompt's instructions** exactly +4. **If uncertain**, ask clarifying questions to determine the right prompt + +## Quick Reference + +```bash +# Initialize repository for agentic workflows +gh aw init + +# Generate the lock file for a workflow +gh aw compile [workflow-name] + +# Debug workflow runs +gh aw logs [workflow-name] +gh aw audit + +# Upgrade workflows +gh aw fix --write +gh aw compile --validate +``` + +## Key Features of gh-aw + +- **Natural Language Workflows**: Write workflows in markdown with YAML frontmatter +- **AI Engine Support**: Copilot, Claude, Codex, or custom engines +- **MCP Server Integration**: Connect to Model Context Protocol servers for tools +- **Safe Outputs**: Structured communication between AI and GitHub API +- **Strict Mode**: Security-first validation and sandboxing +- **Shared Components**: Reusable workflow building blocks +- **Repo Memory**: Persistent git-backed storage for agents +- **Sandboxed Execution**: All workflows run in the Agent Workflow Firewall (AWF) sandbox, enabling full `bash` and `edit` tools by default + +## Important Notes + +- Always reference the instructions file at https://github.com/github/gh-aw/blob/v0.65.6/.github/aw/github-agentic-workflows.md for complete documentation +- Use the MCP tool `agentic-workflows` when running in GitHub Copilot Cloud +- Workflows must be compiled to `.lock.yml` files before running in GitHub Actions +- **Bash tools are enabled by default** - Don't restrict bash commands unnecessarily since workflows are sandboxed by the AWF +- Follow security best practices: minimal permissions, explicit network access, no template injection +- **Network configuration**: Use ecosystem identifiers (`node`, `python`, `go`, etc.) or explicit FQDNs in `network.allowed`. Bare shorthands like `npm` or `pypi` are **not** valid. See https://github.com/github/gh-aw/blob/v0.65.6/.github/aw/network.md for the full list of valid ecosystem identifiers and domain patterns. +- **Single-file output**: When creating a workflow, produce exactly **one** workflow `.md` file. Do not create separate documentation files (architecture docs, runbooks, usage guides, etc.). If documentation is needed, add a brief `## Usage` section inside the workflow file itself. diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 0000000000..12fccfe287 --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,26 @@ +name: "Copilot Setup Steps" + +# This workflow configures the environment for GitHub Copilot Agent with gh-aw MCP server +on: + workflow_dispatch: + push: + paths: + - .github/workflows/copilot-setup-steps.yml + +jobs: + # The job MUST be called 'copilot-setup-steps' to be recognized by GitHub Copilot Agent + copilot-setup-steps: + runs-on: ubuntu-latest + + # Set minimal permissions for setup steps + # Copilot Agent receives its own token with appropriate permissions + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - name: Install gh-aw extension + uses: github/gh-aw-actions/setup-cli@31130b20a8fd3ef263acbe2091267c0aace07e09 # v0.65.6 + with: + version: v0.65.6 From 9d264c1c8d77b5a1c037f68b843ca355cb6a858f Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Thu, 2 Apr 2026 22:10:22 -0700 Subject: [PATCH 49/65] Set up the select-copilot-pat integration into agentic workflows --- .github/actions/select-copilot-pat/README.md | 116 ++++++++++++++++++ .github/actions/select-copilot-pat/action.yml | 53 ++++++++ .github/agents/agentic-workflows.agent.md | 80 ++++++++++++ 3 files changed, 249 insertions(+) create mode 100644 .github/actions/select-copilot-pat/README.md create mode 100644 .github/actions/select-copilot-pat/action.yml diff --git a/.github/actions/select-copilot-pat/README.md b/.github/actions/select-copilot-pat/README.md new file mode 100644 index 0000000000..8b57f6377b --- /dev/null +++ b/.github/actions/select-copilot-pat/README.md @@ -0,0 +1,116 @@ +# Select Copilot PAT +Selects a random Copilot PAT from a numbered pool of secrets. This addresses limitations that arise from having a single PAT shared across all agentic workflows, such as rate-limiting. + +**This is a stop-gap workaround.** As soon as organization/enterprise billing is offered for agentic workflows, this approach will be removed from our workflows. + +## Repository Onboarding +To use Agentic Workflows in a dotnet org repository: + +1. Follow the instructions for [Configuring Your Repository | Agentic Authoring | GitHub Agentic Workflows][configure-repo]. +2. Copy this `select-copilot-pat` folder into the repository under `.github/actions/select-copilot-pat`, including both the `README.md` and `action.yml`. +3. Merge those additions into the repository and then follow the instructions for the PAT Creation and Usage below. + +> **Optional:** If you plan to manage secrets or workflows from the command line (e.g., `gh aw secrets set`), [install the `gh aw` CLI extension][cli-setup]: +> ```sh +> gh extension install github/gh-aw +> ``` + +## PAT Management +Team members provide PATs into the pools for the repository by adding them as repository secrets with secret names matching the pattern of `_<0-9>`, such as `COPILOT_PAT_0`. + +[Use this link to prefill the PAT creation form with the required settings][create-pat]: + +1. **Resource owner** is your **user account**, not an organization. +2. **Copilot Requests (Read)** must be the only permission granted. +3. **8-day expiration** must be used, which enforces a weekly renewal. +4. **Repository access** set to **Public repositories** only. + +The **Token Name** _does not_ need to match the secret name and is only visible to the owner of the PAT. It's recommended to use a token name indicating the PAT is used for dotnet org agentic workflows. The **Description** is also only used for your own reference. + +Team members providing PATs for workflows should set weekly recurring reminders to regenerate and update their PATs in the repository secrets. With an 8-day expiration, renewal can be done on the same day each week. + +PATs are added to repositories through the **Settings > Secrets and variables > Actions** UI, saved as **Repository secrets** and matching the `_<0-9>` naming convention. This can also be done using the GitHub CLI. + +```sh +gh aw secrets set "_<0-9>" --value "" --repo dotnet/ +``` + +## Workflow Output Attribution +Team members' PATs are _only_ used for the Copilot requests from within the agentic portion of the workflow. All outputs from the workflow use the `github-actions[bot]` account token. Issues, PRs, comments, and all other content generated by the workflow will be attributed to `github-actions[bot]`--not the team member's account or token. + +## Usage +Add the following frontmatter at the top-level of an agentic workflow. These elements are not supported through [imports][imports], so they must be copied into all workflows. + +Up to 10 `SECRET_#` environment variables can be passed to the action, numbered 0-9. Different workflows can use different pools of PATs if desired. Change the `secrets.COPILOT_PAT_0` through `secrets.COPILOT_PAT_9` secret names in both the `select-copilot-pat` step `env` values and in the `case` expression under the `engine: env` configuration. + +```yml +on: + # Add the pre-activation step of selecting a random PAT from the supplied secrets + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + name: Checkout the select-copilot-pat action folder + with: + persist-credentials: false + sparse-checkout: .github/actions/select-copilot-pat + sparse-checkout-cone-mode: true + fetch-depth: 1 + + - id: select-copilot-pat + name: Select Copilot token from pool + uses: ./.github/actions/select-copilot-pat + env: + # If the secret names are changed here, they must also be changed + # in the `engine: env` case expression + SECRET_0: ${{ secrets.COPILOT_PAT_0 }} + SECRET_1: ${{ secrets.COPILOT_PAT_1 }} + SECRET_2: ${{ secrets.COPILOT_PAT_2 }} + SECRET_3: ${{ secrets.COPILOT_PAT_3 }} + SECRET_4: ${{ secrets.COPILOT_PAT_4 }} + SECRET_5: ${{ secrets.COPILOT_PAT_5 }} + SECRET_6: ${{ secrets.COPILOT_PAT_6 }} + SECRET_7: ${{ secrets.COPILOT_PAT_7 }} + SECRET_8: ${{ secrets.COPILOT_PAT_8 }} + SECRET_9: ${{ secrets.COPILOT_PAT_9 }} + +# Add the pre-activation output of the randomly selected PAT +jobs: + pre-activation: + outputs: + copilot_pat_number: ${{ steps.select-copilot-pat.outputs.copilot_pat_number }} + +# Override the COPILOT_GITHUB_TOKEN expression used in the activation job +# Consume the PAT number from the pre-activation step and select the corresponding secret +engine: + id: copilot + env: + # We cannot use line breaks in this expression as it leads to a syntax error in the compiled workflow + # If none of the `COPILOT_PAT_#` secrets were selected, then the default COPILOT_GITHUB_TOKEN is used + COPILOT_GITHUB_TOKEN: ${{ case(needs.pre_activation.outputs.copilot_pat_number == '0', secrets.COPILOT_PAT_0, needs.pre_activation.outputs.copilot_pat_number == '1', secrets.COPILOT_PAT_1, needs.pre_activation.outputs.copilot_pat_number == '2', secrets.COPILOT_PAT_2, needs.pre_activation.outputs.copilot_pat_number == '3', secrets.COPILOT_PAT_3, needs.pre_activation.outputs.copilot_pat_number == '4', secrets.COPILOT_PAT_4, needs.pre_activation.outputs.copilot_pat_number == '5', secrets.COPILOT_PAT_5, needs.pre_activation.outputs.copilot_pat_number == '6', secrets.COPILOT_PAT_6, needs.pre_activation.outputs.copilot_pat_number == '7', secrets.COPILOT_PAT_7, needs.pre_activation.outputs.copilot_pat_number == '8', secrets.COPILOT_PAT_8, needs.pre_activation.outputs.copilot_pat_number == '9', secrets.COPILOT_PAT_9, secrets.COPILOT_GITHUB_TOKEN) }} +``` + +## References + +- [Agentic Workflows CLI Extension][cli-setup] +- [Agentic Authoring][configure-repo] +- [Authentication][authentication] +- [Agentic Workflow Imports][imports] +- [Custom Steps][steps] +- [Custom Jobs][jobs] +- [Job Outputs][job-outputs] +- [Engine Configuration][engine] +- [Engine Environment Variables][engine-vars] +- [Case Function in Workflow Expressions][case-expression] +- [Update agentic engine token handling to use user-provided secrets (github/gh-aw#18017)][secret-override] + +[cli-setup]: https://github.github.com/gh-aw/setup/cli/ +[configure-repo]: https://github.github.com/gh-aw/guides/agentic-authoring/#configuring-your-repository +[authentication]: https://github.github.com/gh-aw/reference/auth/ +[create-pat]: https://github.com/settings/personal-access-tokens/new?name=dotnet%20org%20agentic%20workflows&description=GitHub+Agentic+Workflows+-+Copilot+engine+authentication.++Used+for+dotnet+org+workflows.+MUST+be+configured+with+only+Copilot+Requests+permissions+and+user+account+as+resource+owner.+Weekly+expiration+and+required+renewal.&user_copilot_requests=read&expires_in=8 +[imports]: https://github.github.com/gh-aw/reference/imports/ +[steps]: https://github.github.com/gh-aw/reference/frontmatter/#custom-steps-steps +[jobs]: https://github.github.com/gh-aw/reference/frontmatter/#custom-jobs-jobs +[job-outputs]: https://github.github.com/gh-aw/reference/frontmatter/#job-outputs +[engine]: https://github.github.com/gh-aw/reference/frontmatter/#ai-engine-engine +[engine-vars]: https://github.github.com/gh-aw/reference/engines/#engine-environment-variables +[case-expression]: https://docs.github.com/en/actions/reference/workflows-and-actions/expressions#case +[secret-override]: https://github.com/github/gh-aw/pull/18017 diff --git a/.github/actions/select-copilot-pat/action.yml b/.github/actions/select-copilot-pat/action.yml new file mode 100644 index 0000000000..198eb0393d --- /dev/null +++ b/.github/actions/select-copilot-pat/action.yml @@ -0,0 +1,53 @@ +name: 'Select Copilot PAT from Pool' +description: > + Selects a random Copilot PAT from a numbered pool of secrets. Secrets + are passed as environment variables SECRET_0 through SECRET_9 + by the calling workflow step. + +inputs: + random-seed: + description: 'A seed number to use for the random PAT selection, for deterministic selection if needed.' + required: false + default: '' + +outputs: + copilot_pat_number: + description: 'The 0-9 secret number selected from the pool of specified secrets' + value: ${{ steps.select-pat-number.outputs.copilot_pat_number }} + +runs: + using: composite + steps: + - id: select-pat-number + shell: bash + env: + RANDOM_SEED: ${{ inputs.random-seed }} + run: | + # Collect all secret numbers with non-empty values from SECRET_0..SECRET_9 + PAT_NUMBERS=() + for i in $(seq 0 9); do + var="SECRET_${i}" + val="${!var}" + if [ -n "$val" ]; then + PAT_NUMBERS+=(${i}) + fi + done + + # If none of the secrets in the pool have values, then emit a warning and do not + # set an output value. The consumer can then fall back to using COPILOT_GITHUB_TOKEN. + if [ ${#PAT_NUMBERS[@]} -eq 0 ]; then + echo "::warning::None of the specified secrets had values (checked SECRET_0 through SECRET_9)" + exit 0 + fi + + # Select a random index using the seed if specified + if [ -n "$RANDOM_SEED" ]; then + RANDOM=$RANDOM_SEED + fi + + PAT_INDEX=$(( RANDOM % ${#PAT_NUMBERS[@]} )) + PAT_NUMBER="${PAT_NUMBERS[$PAT_INDEX]}" + echo "Selected token ${PAT_NUMBER} (index: ${PAT_INDEX}; pool size: ${#PAT_NUMBERS[@]})" + + # Set the PAT number as the output + echo "copilot_pat_number=${PAT_NUMBER}" >> "$GITHUB_OUTPUT" diff --git a/.github/agents/agentic-workflows.agent.md b/.github/agents/agentic-workflows.agent.md index ca1af04614..999c8031bc 100644 --- a/.github/agents/agentic-workflows.agent.md +++ b/.github/agents/agentic-workflows.agent.md @@ -176,3 +176,83 @@ gh aw compile --validate - Follow security best practices: minimal permissions, explicit network access, no template injection - **Network configuration**: Use ecosystem identifiers (`node`, `python`, `go`, etc.) or explicit FQDNs in `network.allowed`. Bare shorthands like `npm` or `pypi` are **not** valid. See https://github.com/github/gh-aw/blob/v0.65.6/.github/aw/network.md for the full list of valid ecosystem identifiers and domain patterns. - **Single-file output**: When creating a workflow, produce exactly **one** workflow `.md` file. Do not create separate documentation files (architecture docs, runbooks, usage guides, etc.). If documentation is needed, add a brief `## Usage` section inside the workflow file itself. + +## Repository-Specific Requirements: Copilot PAT Pool + +**⚠️ MANDATORY**: Every agentic workflow in this repository **must** include the `select-copilot-pat` integration. This selects a random Copilot PAT from a numbered pool of secrets to avoid rate-limiting from a single shared PAT. This is a stop-gap workaround until organization-level billing is offered for agentic workflows. + +These elements are **not supported through imports**, so they must be copied into the frontmatter of every agentic workflow. See `.github/actions/select-copilot-pat/README.md` for full documentation and `.github/workflows/copilot-echo.md` for a working example. + +When **creating** or **updating** an agentic workflow, always include the following three frontmatter blocks. These go between the `---` markers alongside the workflow's other frontmatter (permissions, safe-outputs, etc.). + +### Comment block (placed before the steps, inside the `on:` block) + +Always include this comment block before the steps to explain the PAT pool override: + +```yaml +# ############################################################### +# Override the COPILOT_GITHUB_TOKEN secret usage for the workflow +# with a randomly-selected token from a pool of secrets. +# +# As soon as organization-level billing is offered for Agentic +# Workflows, this stop-gap approach will be removed. +# +# See: /.github/actions/select-copilot-pat/README.md +# ############################################################### +``` + +### Block 1: Pre-activation steps (nested under `on:`) + +Add this under the `on:` key, at the same level as the trigger configuration, immediately after the comment block above: + +```yaml + # Add the pre-activation step of selecting a random PAT from the supplied secrets + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + name: Checkout the select-copilot-pat action folder + with: + persist-credentials: false + sparse-checkout: .github/actions/select-copilot-pat + sparse-checkout-cone-mode: true + fetch-depth: 1 + + - id: select-copilot-pat + name: Select Copilot token from pool + uses: ./.github/actions/select-copilot-pat + env: + SECRET_0: ${{ secrets.COPILOT_PAT_0 }} + SECRET_1: ${{ secrets.COPILOT_PAT_1 }} + SECRET_2: ${{ secrets.COPILOT_PAT_2 }} + SECRET_3: ${{ secrets.COPILOT_PAT_3 }} + SECRET_4: ${{ secrets.COPILOT_PAT_4 }} + SECRET_5: ${{ secrets.COPILOT_PAT_5 }} + SECRET_6: ${{ secrets.COPILOT_PAT_6 }} + SECRET_7: ${{ secrets.COPILOT_PAT_7 }} + SECRET_8: ${{ secrets.COPILOT_PAT_8 }} + SECRET_9: ${{ secrets.COPILOT_PAT_9 }} +``` + +### Block 2: Pre-activation job outputs (top-level `jobs:`) + +```yaml +jobs: + pre-activation: + outputs: + copilot_pat_number: ${{ steps.select-copilot-pat.outputs.copilot_pat_number }} +``` + +### Block 3: Engine configuration with PAT override (top-level `engine:`) + +```yaml +engine: + id: copilot + env: + # We cannot use line breaks in this expression as it leads to a syntax error in the compiled workflow + # If none of the `COPILOT_PAT_#` secrets were selected, then the default COPILOT_GITHUB_TOKEN is used + COPILOT_GITHUB_TOKEN: ${{ case(needs.pre_activation.outputs.copilot_pat_number == '0', secrets.COPILOT_PAT_0, needs.pre_activation.outputs.copilot_pat_number == '1', secrets.COPILOT_PAT_1, needs.pre_activation.outputs.copilot_pat_number == '2', secrets.COPILOT_PAT_2, needs.pre_activation.outputs.copilot_pat_number == '3', secrets.COPILOT_PAT_3, needs.pre_activation.outputs.copilot_pat_number == '4', secrets.COPILOT_PAT_4, needs.pre_activation.outputs.copilot_pat_number == '5', secrets.COPILOT_PAT_5, needs.pre_activation.outputs.copilot_pat_number == '6', secrets.COPILOT_PAT_6, needs.pre_activation.outputs.copilot_pat_number == '7', secrets.COPILOT_PAT_7, needs.pre_activation.outputs.copilot_pat_number == '8', secrets.COPILOT_PAT_8, needs.pre_activation.outputs.copilot_pat_number == '9', secrets.COPILOT_PAT_9, secrets.COPILOT_GITHUB_TOKEN) }} +``` + +**Important notes about the engine block:** +- The `COPILOT_GITHUB_TOKEN` `case()` expression **must** remain on a single line — line breaks cause syntax errors in the compiled workflow. +- If no `COPILOT_PAT_#` secrets are configured, the expression falls back to the default `COPILOT_GITHUB_TOKEN` secret. +- Do **not** specify `engine: copilot` as a simple string — use the object form shown above so the `env:` override can be included. From bda5c7adda6396d43d87ff1043ee2c6ed377f84f Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Thu, 2 Apr 2026 22:20:26 -0700 Subject: [PATCH 50/65] Integrate select-copilot-pat into release-notes workflow --- .github/aw/actions-lock.json | 20 ++ .github/workflows/release-notes.lock.yml | 229 ++++++++++++++--------- .github/workflows/release-notes.md | 65 ++++++- 3 files changed, 220 insertions(+), 94 deletions(-) diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index c051b35c6c..1c855ccd7d 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -1,14 +1,34 @@ { "entries": { + "actions/download-artifact@v8.0.1": { + "repo": "actions/download-artifact", + "version": "v8.0.1", + "sha": "3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c" + }, "actions/github-script@v8": { "repo": "actions/github-script", "version": "v8", "sha": "ed597411d8f924073f98dfc5c65a23a2325f34cd" }, + "actions/setup-dotnet@v5.2.0": { + "repo": "actions/setup-dotnet", + "version": "v5.2.0", + "sha": "c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7" + }, + "actions/upload-artifact@v7": { + "repo": "actions/upload-artifact", + "version": "v7", + "sha": "bbbca2ddaa5d8feaa63e36b76fdaad77386f024f" + }, "github/gh-aw-actions/setup@v0.65.1": { "repo": "github/gh-aw-actions/setup", "version": "v0.65.1", "sha": "1831bcd4f397da99da6e6e6b65687557a1a37ac7" + }, + "github/gh-aw-actions/setup@v0.65.6": { + "repo": "github/gh-aw-actions/setup", + "version": "v0.65.6", + "sha": "31130b20a8fd3ef263acbe2091267c0aace07e09" } } } diff --git a/.github/workflows/release-notes.lock.yml b/.github/workflows/release-notes.lock.yml index 023df3f6f2..b1f6cea794 100644 --- a/.github/workflows/release-notes.lock.yml +++ b/.github/workflows/release-notes.lock.yml @@ -12,7 +12,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.65.1). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.65.6). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -21,13 +21,35 @@ # For more information: https://github.github.com/gh-aw/introduction/overview/ # # -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"4a32bbac0ad27de243b2bb6332e64fc2babf3823e6988c0a96c99bd3531deb96","compiler_version":"v0.65.1","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"ba3427f47052ff36c29572b4a350513627533f527a61ed83f6a53a65954d4c85","compiler_version":"v0.65.6","strict":true,"agent_id":"copilot"} name: ".NET Release Notes Maintenance" "on": schedule: - cron: "34 */6 * * *" # Friendly format: every 6 hours (scattered) + # steps: # Steps injected into pre-activation job + # - name: Checkout the select-copilot-pat action folder + # uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + # with: + # fetch-depth: 1 + # persist-credentials: false + # sparse-checkout: .github/actions/select-copilot-pat + # sparse-checkout-cone-mode: true + # - env: + # SECRET_0: ${{ secrets.COPILOT_PAT_0 }} + # SECRET_1: ${{ secrets.COPILOT_PAT_1 }} + # SECRET_2: ${{ secrets.COPILOT_PAT_2 }} + # SECRET_3: ${{ secrets.COPILOT_PAT_3 }} + # SECRET_4: ${{ secrets.COPILOT_PAT_4 }} + # SECRET_5: ${{ secrets.COPILOT_PAT_5 }} + # SECRET_6: ${{ secrets.COPILOT_PAT_6 }} + # SECRET_7: ${{ secrets.COPILOT_PAT_7 }} + # SECRET_8: ${{ secrets.COPILOT_PAT_8 }} + # SECRET_9: ${{ secrets.COPILOT_PAT_9 }} + # id: select-copilot-pat + # name: Select Copilot token from pool + # uses: ./.github/actions/select-copilot-pat workflow_dispatch: inputs: aw_context: @@ -49,6 +71,8 @@ run-name: ".NET Release Notes Maintenance" jobs: activation: + needs: pre_activation + if: needs.pre_activation.outputs.activated == 'true' runs-on: ubuntu-slim permissions: contents: read @@ -60,7 +84,7 @@ jobs: secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@1831bcd4f397da99da6e6e6b65687557a1a37ac7 # v0.65.1 + uses: github/gh-aw-actions/setup@31130b20a8fd3ef263acbe2091267c0aace07e09 # v0.65.6 with: destination: ${{ runner.temp }}/gh-aw/actions - name: Generate agentic run info @@ -71,14 +95,14 @@ jobs: GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} GH_AW_INFO_VERSION: "latest" GH_AW_INFO_AGENT_VERSION: "latest" - GH_AW_INFO_CLI_VERSION: "v0.65.1" + GH_AW_INFO_CLI_VERSION: "v0.65.6" GH_AW_INFO_WORKFLOW_NAME: ".NET Release Notes Maintenance" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","dotnet"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.5" + GH_AW_INFO_AWF_VERSION: "v0.25.11" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -93,7 +117,7 @@ jobs: id: validate-secret run: ${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ case(needs.pre_activation.outputs.copilot_pat_number == '0', secrets.COPILOT_PAT_0, needs.pre_activation.outputs.copilot_pat_number == '1', secrets.COPILOT_PAT_1, needs.pre_activation.outputs.copilot_pat_number == '2', secrets.COPILOT_PAT_2, needs.pre_activation.outputs.copilot_pat_number == '3', secrets.COPILOT_PAT_3, needs.pre_activation.outputs.copilot_pat_number == '4', secrets.COPILOT_PAT_4, needs.pre_activation.outputs.copilot_pat_number == '5', secrets.COPILOT_PAT_5, needs.pre_activation.outputs.copilot_pat_number == '6', secrets.COPILOT_PAT_6, needs.pre_activation.outputs.copilot_pat_number == '7', secrets.COPILOT_PAT_7, needs.pre_activation.outputs.copilot_pat_number == '8', secrets.COPILOT_PAT_8, needs.pre_activation.outputs.copilot_pat_number == '9', secrets.COPILOT_PAT_9, secrets.COPILOT_GITHUB_TOKEN) }} - name: Checkout .github and .agents folders uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -116,7 +140,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: - GH_AW_COMPILED_VERSION: "v0.65.1" + GH_AW_COMPILED_VERSION: "v0.65.6" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -139,20 +163,20 @@ jobs: run: | bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh { - cat << 'GH_AW_PROMPT_5c70b32715b76a77_EOF' + cat << 'GH_AW_PROMPT_c159b561e3c758fc_EOF' - GH_AW_PROMPT_5c70b32715b76a77_EOF + GH_AW_PROMPT_c159b561e3c758fc_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_5c70b32715b76a77_EOF' + cat << 'GH_AW_PROMPT_c159b561e3c758fc_EOF' Tools: add_comment(max:20), create_pull_request(max:5), push_to_pull_request_branch(max:5), missing_tool, missing_data, noop - GH_AW_PROMPT_5c70b32715b76a77_EOF + GH_AW_PROMPT_c159b561e3c758fc_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md" - cat << 'GH_AW_PROMPT_5c70b32715b76a77_EOF' + cat << 'GH_AW_PROMPT_c159b561e3c758fc_EOF' The following GitHub context information is available for this workflow: @@ -182,12 +206,12 @@ jobs: {{/if}} - GH_AW_PROMPT_5c70b32715b76a77_EOF + GH_AW_PROMPT_c159b561e3c758fc_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_5c70b32715b76a77_EOF' + cat << 'GH_AW_PROMPT_c159b561e3c758fc_EOF' {{#runtime-import .github/workflows/release-notes.md}} - GH_AW_PROMPT_5c70b32715b76a77_EOF + GH_AW_PROMPT_c159b561e3c758fc_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -211,6 +235,7 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -229,7 +254,8 @@ jobs: GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, - GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED } }); - name: Validate prompt placeholders @@ -270,6 +296,7 @@ jobs: GH_AW_WORKFLOW_ID_SANITIZED: releasenotes outputs: checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} inference_access_error: ${{ steps.detect-inference-error.outputs.inference_access_error || 'false' }} model: ${{ needs.activation.outputs.model }} @@ -277,7 +304,7 @@ jobs: output_types: ${{ steps.collect_output.outputs.output_types }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@1831bcd4f397da99da6e6e6b65687557a1a37ac7 # v0.65.1 + uses: github/gh-aw-actions/setup@31130b20a8fd3ef263acbe2091267c0aace07e09 # v0.65.6 with: destination: ${{ runner.temp }}/gh-aw/actions - name: Set runtime paths @@ -329,7 +356,7 @@ jobs: - name: Install GitHub Copilot CLI run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest - name: Install AWF binary - run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.5 + run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.11 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -341,18 +368,18 @@ jobs: const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download container images - run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.5 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.5 ghcr.io/github/gh-aw-firewall/squid:0.25.5 ghcr.io/github/gh-aw-mcpg:v0.2.10 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine + run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.11 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.11 ghcr.io/github/gh-aw-firewall/squid:0.25.11 ghcr.io/github/gh-aw-mcpg:v0.2.11 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine - name: Write Safe Outputs Config run: | mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_d68c5f707ea53666_EOF' + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_e0d2afd7919ef324_EOF' {"add_comment":{"max":20,"target":"*"},"create_pull_request":{"draft":true,"labels":["release-notes","automated"],"max":5,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS"],"protected_path_prefixes":[".github/",".agents/"],"title_prefix":"[release-notes] "},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"if_no_changes":"warn","labels":["release-notes","automated"],"max":5,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS"],"protected_path_prefixes":[".github/",".agents/"],"title_prefix":"[release-notes] "}} - GH_AW_SAFE_OUTPUTS_CONFIG_d68c5f707ea53666_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_e0d2afd7919ef324_EOF - name: Write Safe Outputs Tools run: | - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_30af8d3b559c704f_EOF' + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_15a437faa92c99d3_EOF' { "description_suffixes": { "add_comment": " CONSTRAINTS: Maximum 20 comment(s) can be added. Target: *.", @@ -362,8 +389,8 @@ jobs: "repo_params": {}, "dynamic_tools": [] } - GH_AW_SAFE_OUTPUTS_TOOLS_META_30af8d3b559c704f_EOF - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_1fa32dc33d42a30f_EOF' + GH_AW_SAFE_OUTPUTS_TOOLS_META_15a437faa92c99d3_EOF + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_d435ee6bb8de03ce_EOF' { "add_comment": { "defaultMax": 1, @@ -497,7 +524,7 @@ jobs: } } } - GH_AW_SAFE_OUTPUTS_VALIDATION_1fa32dc33d42a30f_EOF + GH_AW_SAFE_OUTPUTS_VALIDATION_d435ee6bb8de03ce_EOF node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs - name: Generate Safe Outputs MCP Server Config id: safe-outputs-config @@ -521,6 +548,7 @@ jobs: id: safe-outputs-start env: DEBUG: '*' + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json @@ -529,6 +557,7 @@ jobs: run: | # Environment variables are set above to prevent template injection export DEBUG + export GH_AW_SAFE_OUTPUTS export GH_AW_SAFE_OUTPUTS_PORT export GH_AW_SAFE_OUTPUTS_API_KEY export GH_AW_SAFE_OUTPUTS_TOOLS_PATH @@ -562,10 +591,10 @@ jobs: export DEBUG="*" export GH_AW_ENGINE="copilot" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.10' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.11' mkdir -p /home/runner/.copilot - cat << GH_AW_MCP_CONFIG_8c043bf359234455_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh + cat << GH_AW_MCP_CONFIG_433cc36c62221f8f_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh { "mcpServers": { "github": { @@ -606,7 +635,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_8c043bf359234455_EOF + GH_AW_MCP_CONFIG_433cc36c62221f8f_EOF - name: Download activation artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -650,17 +679,17 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.5 --skip-pull --enable-api-proxy \ + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.11 --skip-pull --enable-api-proxy \ -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(dotnet-release)'\'' --allow-tool '\''shell(dotnet:*)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(git add:*)'\'' --allow-tool '\''shell(git branch:*)'\'' --allow-tool '\''shell(git checkout:*)'\'' --allow-tool '\''shell(git commit:*)'\'' --allow-tool '\''shell(git merge:*)'\'' --allow-tool '\''shell(git rm:*)'\'' --allow-tool '\''shell(git status)'\'' --allow-tool '\''shell(git switch:*)'\'' --allow-tool '\''shell(git:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ case(needs.pre_activation.outputs.copilot_pat_number == '0', secrets.COPILOT_PAT_0, needs.pre_activation.outputs.copilot_pat_number == '1', secrets.COPILOT_PAT_1, needs.pre_activation.outputs.copilot_pat_number == '2', secrets.COPILOT_PAT_2, needs.pre_activation.outputs.copilot_pat_number == '3', secrets.COPILOT_PAT_3, needs.pre_activation.outputs.copilot_pat_number == '4', secrets.COPILOT_PAT_4, needs.pre_activation.outputs.copilot_pat_number == '5', secrets.COPILOT_PAT_5, needs.pre_activation.outputs.copilot_pat_number == '6', secrets.COPILOT_PAT_6, needs.pre_activation.outputs.copilot_pat_number == '7', secrets.COPILOT_PAT_7, needs.pre_activation.outputs.copilot_pat_number == '8', secrets.COPILOT_PAT_8, needs.pre_activation.outputs.copilot_pat_number == '9', secrets.COPILOT_PAT_9, secrets.COPILOT_GITHUB_TOKEN) }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.65.1 + GH_AW_VERSION: v0.65.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_HEAD_REF: ${{ github.head_ref }} @@ -694,20 +723,7 @@ jobs: - name: Copy Copilot session state files to logs if: always() continue-on-error: true - run: | - # Copy Copilot session state files to logs folder for artifact collection - # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them - SESSION_STATE_DIR="$HOME/.copilot/session-state" - LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs" - - if [ -d "$SESSION_STATE_DIR" ]; then - echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR" - mkdir -p "$LOGS_DIR" - cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true - echo "Session state files copied successfully" - else - echo "No session-state directory found at $SESSION_STATE_DIR" - fi + run: bash ${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh - name: Stop MCP Gateway if: always() continue-on-error: true @@ -727,8 +743,18 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); await main(); env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,COPILOT_PAT_0,COPILOT_PAT_1,COPILOT_PAT_2,COPILOT_PAT_3,COPILOT_PAT_4,COPILOT_PAT_5,COPILOT_PAT_6,COPILOT_PAT_7,COPILOT_PAT_8,COPILOT_PAT_9,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + SECRET_COPILOT_PAT_0: ${{ secrets.COPILOT_PAT_0 }} + SECRET_COPILOT_PAT_1: ${{ secrets.COPILOT_PAT_1 }} + SECRET_COPILOT_PAT_2: ${{ secrets.COPILOT_PAT_2 }} + SECRET_COPILOT_PAT_3: ${{ secrets.COPILOT_PAT_3 }} + SECRET_COPILOT_PAT_4: ${{ secrets.COPILOT_PAT_4 }} + SECRET_COPILOT_PAT_5: ${{ secrets.COPILOT_PAT_5 }} + SECRET_COPILOT_PAT_6: ${{ secrets.COPILOT_PAT_6 }} + SECRET_COPILOT_PAT_7: ${{ secrets.COPILOT_PAT_7 }} + SECRET_COPILOT_PAT_8: ${{ secrets.COPILOT_PAT_8 }} + SECRET_COPILOT_PAT_9: ${{ secrets.COPILOT_PAT_9 }} SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -770,6 +796,7 @@ jobs: await main(); - name: Parse MCP Gateway logs for step summary if: always() + id: parse-mcp-gateway uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | @@ -792,6 +819,10 @@ jobs: else echo 'AWF binary not installed, skipping firewall log summary' fi + - name: Parse token usage for step summary + if: always() + continue-on-error: true + run: bash ${RUNNER_TEMP}/gh-aw/actions/parse_token_usage.sh - name: Write agent output placeholder if missing if: always() run: | @@ -849,7 +880,7 @@ jobs: total_count: ${{ steps.missing_tool.outputs.total_count }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@1831bcd4f397da99da6e6e6b65687557a1a37ac7 # v0.65.1 + uses: github/gh-aw-actions/setup@31130b20a8fd3ef263acbe2091267c0aace07e09 # v0.65.6 with: destination: ${{ runner.temp }}/gh-aw/actions - name: Download agent output artifact @@ -873,12 +904,15 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" GH_AW_WORKFLOW_NAME: ".NET Release Notes Maintenance" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/noop.cjs'); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); await main(); - name: Record Missing Tool id: missing_tool @@ -921,49 +955,20 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); await main(); - - name: Handle No-Op Message - id: handle_noop_message - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: ".NET Release Notes Maintenance" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} - GH_AW_NOOP_REPORT_AS_ISSUE: "true" - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); - await main(); - - name: Handle Create Pull Request Error - id: handle_create_pr_error - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: ".NET Release Notes Maintenance" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_create_pr_error.cjs'); - await main(); detection: needs: agent if: > always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') runs-on: ubuntu-latest + permissions: + contents: read outputs: detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} detection_success: ${{ steps.detection_conclusion.outputs.success }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@1831bcd4f397da99da6e6e6b65687557a1a37ac7 # v0.65.1 + uses: github/gh-aw-actions/setup@31130b20a8fd3ef263acbe2091267c0aace07e09 # v0.65.6 with: destination: ${{ runner.temp }}/gh-aw/actions - name: Download agent output artifact @@ -980,9 +985,14 @@ jobs: mkdir -p /tmp/gh-aw/ find "/tmp/gh-aw/" -type f -print echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Checkout repository for patch context + if: needs.agent.outputs.has_patch == 'true' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false # --- Threat Detection --- - name: Download container images - run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.5 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.5 ghcr.io/github/gh-aw-firewall/squid:0.25.5 + run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.11 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.11 ghcr.io/github/gh-aw-firewall/squid:0.25.11 - name: Check if detection needed id: detection_guard if: always() @@ -1038,7 +1048,7 @@ jobs: - name: Install GitHub Copilot CLI run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest - name: Install AWF binary - run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.5 + run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.11 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' id: detection_agentic_execution @@ -1048,15 +1058,15 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.5 --skip-pull --enable-api-proxy \ + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.11 --skip-pull --enable-api-proxy \ -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ case(needs.pre_activation.outputs.copilot_pat_number == '0', secrets.COPILOT_PAT_0, needs.pre_activation.outputs.copilot_pat_number == '1', secrets.COPILOT_PAT_1, needs.pre_activation.outputs.copilot_pat_number == '2', secrets.COPILOT_PAT_2, needs.pre_activation.outputs.copilot_pat_number == '3', secrets.COPILOT_PAT_3, needs.pre_activation.outputs.copilot_pat_number == '4', secrets.COPILOT_PAT_4, needs.pre_activation.outputs.copilot_pat_number == '5', secrets.COPILOT_PAT_5, needs.pre_activation.outputs.copilot_pat_number == '6', secrets.COPILOT_PAT_6, needs.pre_activation.outputs.copilot_pat_number == '7', secrets.COPILOT_PAT_7, needs.pre_activation.outputs.copilot_pat_number == '8', secrets.COPILOT_PAT_8, needs.pre_activation.outputs.copilot_pat_number == '9', secrets.COPILOT_PAT_9, secrets.COPILOT_GITHUB_TOKEN) }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.65.1 + GH_AW_VERSION: v0.65.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_HEAD_REF: ${{ github.head_ref }} @@ -1089,6 +1099,52 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); await main(); + pre_activation: + runs-on: ubuntu-slim + outputs: + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} + copilot_pat_number: ${{ steps.select-copilot-pat.outputs.copilot_pat_number }} + matched_command: '' + select-copilot-pat_result: ${{ steps.select-copilot-pat.outcome }} + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@31130b20a8fd3ef263acbe2091267c0aace07e09 # v0.65.6 + with: + destination: ${{ runner.temp }}/gh-aw/actions + - name: Check team membership for workflow + id: check_membership + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_REQUIRED_ROLES: "admin,maintainer,write" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); + await main(); + - name: Checkout the select-copilot-pat action folder + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1 + persist-credentials: false + sparse-checkout: .github/actions/select-copilot-pat + sparse-checkout-cone-mode: true + - name: Select Copilot token from pool + id: select-copilot-pat + uses: ./.github/actions/select-copilot-pat + env: + SECRET_0: ${{ secrets.COPILOT_PAT_0 }} + SECRET_1: ${{ secrets.COPILOT_PAT_1 }} + SECRET_2: ${{ secrets.COPILOT_PAT_2 }} + SECRET_3: ${{ secrets.COPILOT_PAT_3 }} + SECRET_4: ${{ secrets.COPILOT_PAT_4 }} + SECRET_5: ${{ secrets.COPILOT_PAT_5 }} + SECRET_6: ${{ secrets.COPILOT_PAT_6 }} + SECRET_7: ${{ secrets.COPILOT_PAT_7 }} + SECRET_8: ${{ secrets.COPILOT_PAT_8 }} + SECRET_9: ${{ secrets.COPILOT_PAT_9 }} + safe_outputs: needs: - activation @@ -1104,6 +1160,7 @@ jobs: timeout-minutes: 15 env: GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/release-notes" + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} GH_AW_WORKFLOW_ID: "release-notes" @@ -1123,7 +1180,7 @@ jobs: push_commit_url: ${{ steps.process_safe_outputs.outputs.push_commit_url }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@1831bcd4f397da99da6e6e6b65687557a1a37ac7 # v0.65.1 + uses: github/gh-aw-actions/setup@31130b20a8fd3ef263acbe2091267c0aace07e09 # v0.65.6 with: destination: ${{ runner.temp }}/gh-aw/actions - name: Download agent output artifact diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index 2afe2e7b89..3761e79c3e 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -1,12 +1,4 @@ --- -on: - schedule: every 6 hours - workflow_dispatch: - inputs: - milestone: - description: "Target milestone (e.g., preview4). Leave empty for auto-detection." - required: false - type: string permissions: contents: read pull-requests: read @@ -39,6 +31,63 @@ tools: - dotnet-release - git - jq + +on: + schedule: every 6 hours + workflow_dispatch: + inputs: + milestone: + description: "Target milestone (e.g., preview4). Leave empty for auto-detection." + required: false + type: string + + # ############################################################### + # Override the COPILOT_GITHUB_TOKEN secret usage for the workflow + # with a randomly-selected token from a pool of secrets. + # + # As soon as organization-level billing is offered for Agentic + # Workflows, this stop-gap approach will be removed. + # + # See: /.github/actions/select-copilot-pat/README.md + # ############################################################### + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + name: Checkout the select-copilot-pat action folder + with: + persist-credentials: false + sparse-checkout: .github/actions/select-copilot-pat + sparse-checkout-cone-mode: true + fetch-depth: 1 + + - id: select-copilot-pat + name: Select Copilot token from pool + uses: ./.github/actions/select-copilot-pat + env: + SECRET_0: ${{ secrets.COPILOT_PAT_0 }} + SECRET_1: ${{ secrets.COPILOT_PAT_1 }} + SECRET_2: ${{ secrets.COPILOT_PAT_2 }} + SECRET_3: ${{ secrets.COPILOT_PAT_3 }} + SECRET_4: ${{ secrets.COPILOT_PAT_4 }} + SECRET_5: ${{ secrets.COPILOT_PAT_5 }} + SECRET_6: ${{ secrets.COPILOT_PAT_6 }} + SECRET_7: ${{ secrets.COPILOT_PAT_7 }} + SECRET_8: ${{ secrets.COPILOT_PAT_8 }} + SECRET_9: ${{ secrets.COPILOT_PAT_9 }} + +# Add the pre-activation output of the randomly selected PAT +jobs: + pre-activation: + outputs: + copilot_pat_number: ${{ steps.select-copilot-pat.outputs.copilot_pat_number }} + +# Override the COPILOT_GITHUB_TOKEN expression used in the activation job +# Consume the PAT number from the pre-activation step and select the corresponding secret +engine: + id: copilot + env: + # We cannot use line breaks in this expression as it leads to a syntax error in the compiled workflow + # If none of the `COPILOT_PAT_#` secrets were selected, then the default COPILOT_GITHUB_TOKEN is used + COPILOT_GITHUB_TOKEN: ${{ case(needs.pre_activation.outputs.copilot_pat_number == '0', secrets.COPILOT_PAT_0, needs.pre_activation.outputs.copilot_pat_number == '1', secrets.COPILOT_PAT_1, needs.pre_activation.outputs.copilot_pat_number == '2', secrets.COPILOT_PAT_2, needs.pre_activation.outputs.copilot_pat_number == '3', secrets.COPILOT_PAT_3, needs.pre_activation.outputs.copilot_pat_number == '4', secrets.COPILOT_PAT_4, needs.pre_activation.outputs.copilot_pat_number == '5', secrets.COPILOT_PAT_5, needs.pre_activation.outputs.copilot_pat_number == '6', secrets.COPILOT_PAT_6, needs.pre_activation.outputs.copilot_pat_number == '7', secrets.COPILOT_PAT_7, needs.pre_activation.outputs.copilot_pat_number == '8', secrets.COPILOT_PAT_8, needs.pre_activation.outputs.copilot_pat_number == '9', secrets.COPILOT_PAT_9, secrets.COPILOT_GITHUB_TOKEN) }} --- # .NET Release Notes Maintenance From 482744fcc9f09d8823714708b4dc01fe1e4b34bb Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Thu, 2 Apr 2026 22:19:46 -0700 Subject: [PATCH 51/65] Move labeler workflow readme to avoid it being recognized as a workflow --- .github/{workflows/labeler.md => labeler-readme.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{workflows/labeler.md => labeler-readme.md} (100%) diff --git a/.github/workflows/labeler.md b/.github/labeler-readme.md similarity index 100% rename from .github/workflows/labeler.md rename to .github/labeler-readme.md From 43e2e1b437bbf5edb65fc382fc3a212e6dd12f4c Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Thu, 2 Apr 2026 23:09:07 -0700 Subject: [PATCH 52/65] Apply review feedback. Set schedule to be daily around 9am PDT. --- .github/skills/release-notes/DESIGN.md | 15 +++++----- .github/workflows/release-notes.lock.yml | 38 ++++++++++++------------ .github/workflows/release-notes.md | 8 ++--- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/.github/skills/release-notes/DESIGN.md b/.github/skills/release-notes/DESIGN.md index 86bc6a38a1..928499ea14 100644 --- a/.github/skills/release-notes/DESIGN.md +++ b/.github/skills/release-notes/DESIGN.md @@ -20,7 +20,7 @@ The system has three layers, each with a distinct responsibility: ```text ┌─────────────────────────────────────────────────┐ -│ Agentic Workflow (cron) │ +│ Agentic Workflow (cron) │ │ .github/workflows/release-notes.md │ │ Orchestration: branch lifecycle, PR mgmt, │ │ human interaction, scheduling │ @@ -119,7 +119,7 @@ These are **goal-oriented**, not procedural. They describe what good release not ## Layer 3 — The Agentic Workflow -A [GitHub Agentic Workflow](https://github.github.com/gh-aw/) defined in `.github/workflows/release-notes.md`. It runs nightly and manages the full lifecycle. This is a **multi-master live system** — the agent, component teams, and release managers all edit concurrently. +A [GitHub Agentic Workflow](https://github.github.com/gh-aw/) defined in `.github/workflows/release-notes.md`. It runs on a schedule and manages the full lifecycle. This is a **multi-master live system** — the agent, component teams, and release managers all edit concurrently. ### Multi-milestone discovery @@ -158,7 +158,7 @@ Branch: release-notes/11.0-preview5 PR: [release-notes] .NET 11 Preview 5 ``` -Each branch is long-lived — it's created on the first run and updated nightly until the PR is merged (after the preview ships). +Each branch is long-lived — it's created on the first run and updated frequently until the PR is merged (after the preview ships). ### Human interaction model @@ -197,7 +197,7 @@ This is the most delicate part of the system. The branches are shared workspaces ### Schedule and transitions -- Runs nightly (~9am Pacific) +- Runs daily (~9am Pacific) - Previews ship monthly, Feb–Oct (typically patch Tuesday) - RC1 ~September, RC2 ~October, GA November - Only does meaningful work when VMR state has changed @@ -258,7 +258,7 @@ The data collection step (manifest diff → PR enumeration) is mechanical and de ### Why goal-oriented reference docs instead of procedural scripts? -The previous iteration had 16 reference documents encoding a rigid 9-step pipeline. It was brittle — any change to the process required updating multiple docs, and the agent followed the steps mechanically without understanding the goals. Goal-oriented docs let the agent adapt to unexpected situations (e.g., a component that reorganized its repo structure). +Goal-oriented docs let the agent adapt to unexpected situations (e.g., a component that reorganized its repo structure). A procedural approach would limit the agent to mechanically execute a rigid and brittle pipeline. ### Why one PR per preview? @@ -271,6 +271,5 @@ Each preview is a coherent release milestone with its own set of features. Maint ## Open questions -1. **Cross-repo tokens** — the workflow runs in `dotnet/core` but reads from `dotnet/dotnet` and ~20 component repos. The GitHub token scope and any required app permissions need to be configured. -2. **Conflict resolution heuristics** — when the agent and a human both changed the same section between runs, the human wins. But how granular is "a section"? Need to define this precisely (per-heading? per-file?). -3. **RC/GA milestone naming** — the multi-milestone logic assumes `previewN` naming. RC and GA milestones use different naming (`rc1`, `rc2`, `ga`). The workflow needs to handle the transition gracefully. +1. **Conflict resolution heuristics** — when the agent and a human both changed the same section between runs, the human wins. But how granular is "a section"? Need to define this precisely (per-heading? per-file?). +2. **RC/GA milestone naming** — the multi-milestone logic assumes `previewN` naming. RC and GA milestones use different naming (`rc1`, `rc2`, `ga`). The workflow needs to handle the transition gracefully. diff --git a/.github/workflows/release-notes.lock.yml b/.github/workflows/release-notes.lock.yml index b1f6cea794..879089541d 100644 --- a/.github/workflows/release-notes.lock.yml +++ b/.github/workflows/release-notes.lock.yml @@ -21,13 +21,13 @@ # For more information: https://github.github.com/gh-aw/introduction/overview/ # # -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"ba3427f47052ff36c29572b4a350513627533f527a61ed83f6a53a65954d4c85","compiler_version":"v0.65.6","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"b05f1cd4127bb955da1c2791c8d67da137b28c8ed8e714499eee2d8569694e65","compiler_version":"v0.65.6","strict":true,"agent_id":"copilot"} name: ".NET Release Notes Maintenance" "on": schedule: - - cron: "34 */6 * * *" - # Friendly format: every 6 hours (scattered) + - cron: "29 15 * * *" + # Friendly format: daily around 9am PDT (scattered) # steps: # Steps injected into pre-activation job # - name: Checkout the select-copilot-pat action folder # uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd @@ -163,20 +163,20 @@ jobs: run: | bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh { - cat << 'GH_AW_PROMPT_c159b561e3c758fc_EOF' + cat << 'GH_AW_PROMPT_0583b53a137d140b_EOF' - GH_AW_PROMPT_c159b561e3c758fc_EOF + GH_AW_PROMPT_0583b53a137d140b_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_c159b561e3c758fc_EOF' + cat << 'GH_AW_PROMPT_0583b53a137d140b_EOF' Tools: add_comment(max:20), create_pull_request(max:5), push_to_pull_request_branch(max:5), missing_tool, missing_data, noop - GH_AW_PROMPT_c159b561e3c758fc_EOF + GH_AW_PROMPT_0583b53a137d140b_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md" - cat << 'GH_AW_PROMPT_c159b561e3c758fc_EOF' + cat << 'GH_AW_PROMPT_0583b53a137d140b_EOF' The following GitHub context information is available for this workflow: @@ -206,12 +206,12 @@ jobs: {{/if}} - GH_AW_PROMPT_c159b561e3c758fc_EOF + GH_AW_PROMPT_0583b53a137d140b_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_c159b561e3c758fc_EOF' + cat << 'GH_AW_PROMPT_0583b53a137d140b_EOF' {{#runtime-import .github/workflows/release-notes.md}} - GH_AW_PROMPT_c159b561e3c758fc_EOF + GH_AW_PROMPT_0583b53a137d140b_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -374,12 +374,12 @@ jobs: mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_e0d2afd7919ef324_EOF' + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_0383588c34f2e552_EOF' {"add_comment":{"max":20,"target":"*"},"create_pull_request":{"draft":true,"labels":["release-notes","automated"],"max":5,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS"],"protected_path_prefixes":[".github/",".agents/"],"title_prefix":"[release-notes] "},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"if_no_changes":"warn","labels":["release-notes","automated"],"max":5,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS"],"protected_path_prefixes":[".github/",".agents/"],"title_prefix":"[release-notes] "}} - GH_AW_SAFE_OUTPUTS_CONFIG_e0d2afd7919ef324_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_0383588c34f2e552_EOF - name: Write Safe Outputs Tools run: | - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_15a437faa92c99d3_EOF' + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_3e4b9e2d3079ffd4_EOF' { "description_suffixes": { "add_comment": " CONSTRAINTS: Maximum 20 comment(s) can be added. Target: *.", @@ -389,8 +389,8 @@ jobs: "repo_params": {}, "dynamic_tools": [] } - GH_AW_SAFE_OUTPUTS_TOOLS_META_15a437faa92c99d3_EOF - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_d435ee6bb8de03ce_EOF' + GH_AW_SAFE_OUTPUTS_TOOLS_META_3e4b9e2d3079ffd4_EOF + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_4d9f4383e68a101c_EOF' { "add_comment": { "defaultMax": 1, @@ -524,7 +524,7 @@ jobs: } } } - GH_AW_SAFE_OUTPUTS_VALIDATION_d435ee6bb8de03ce_EOF + GH_AW_SAFE_OUTPUTS_VALIDATION_4d9f4383e68a101c_EOF node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs - name: Generate Safe Outputs MCP Server Config id: safe-outputs-config @@ -594,7 +594,7 @@ jobs: export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.11' mkdir -p /home/runner/.copilot - cat << GH_AW_MCP_CONFIG_433cc36c62221f8f_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh + cat << GH_AW_MCP_CONFIG_f5439150d6a63e99_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh { "mcpServers": { "github": { @@ -635,7 +635,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_433cc36c62221f8f_EOF + GH_AW_MCP_CONFIG_f5439150d6a63e99_EOF - name: Download activation artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index 3761e79c3e..b19d56f655 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -33,7 +33,7 @@ tools: - jq on: - schedule: every 6 hours + schedule: daily around 9am PDT workflow_dispatch: inputs: milestone: @@ -92,7 +92,7 @@ engine: # .NET Release Notes Maintenance -You maintain release notes for .NET preview, RC, and GA releases in this repository (dotnet/core). You run nightly. This is a **multi-master live system** — humans edit branches and leave PR comments at any time. You must respect their changes and engage with their feedback. +You maintain release notes for .NET preview, RC, and GA releases in this repository (dotnet/core), running on a schedule to frequently identify changes in the upcoming releases. This is a **multi-master live system** — humans edit branches and leave PR comments at any time. You must respect their changes and engage with their feedback. Your outputs are pull requests — one per active milestone — each containing: @@ -183,7 +183,7 @@ Process milestones in order (lowest to highest). Each gets its own branch and PR #### a. Regenerate changes.json -Always regenerate — the content may have changed since yesterday. +Always regenerate — the content may have changed since the previous run. ```bash mkdir -p release-notes/11.0/preview/preview4 @@ -212,7 +212,7 @@ Using `changes.json` and the reference documents: - Route changes to output files via `product` field and component-mapping.md - For each component: identify which PRs are worth writing about -- Write feature descriptions following format-template.md and editorial-rules.md +- Write feature descriptions following `format-template.md` and `editorial-rules.md` - Components with no noteworthy changes get a minimal stub #### d. Ask for what you can't generate From 7ddb31b2223f3785cc8627ee549892357029e0a1 Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Thu, 2 Apr 2026 23:19:59 -0700 Subject: [PATCH 53/65] Do not run the release-notes workflow on forks unless manually dispatched --- .github/workflows/release-notes.lock.yml | 38 +++++++++++++----------- .github/workflows/release-notes.md | 2 ++ 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/.github/workflows/release-notes.lock.yml b/.github/workflows/release-notes.lock.yml index 879089541d..6910d7992b 100644 --- a/.github/workflows/release-notes.lock.yml +++ b/.github/workflows/release-notes.lock.yml @@ -21,7 +21,7 @@ # For more information: https://github.github.com/gh-aw/introduction/overview/ # # -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"b05f1cd4127bb955da1c2791c8d67da137b28c8ed8e714499eee2d8569694e65","compiler_version":"v0.65.6","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"632d40ace6be3367650a73f4cb254131628a9d1bc6bb1c52b59ccfe1f13b3fec","compiler_version":"v0.65.6","strict":true,"agent_id":"copilot"} name: ".NET Release Notes Maintenance" "on": @@ -72,7 +72,8 @@ run-name: ".NET Release Notes Maintenance" jobs: activation: needs: pre_activation - if: needs.pre_activation.outputs.activated == 'true' + if: > + needs.pre_activation.outputs.activated == 'true' && ((!github.event.repository.fork) || github.event_name == 'workflow_dispatch') runs-on: ubuntu-slim permissions: contents: read @@ -163,20 +164,20 @@ jobs: run: | bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh { - cat << 'GH_AW_PROMPT_0583b53a137d140b_EOF' + cat << 'GH_AW_PROMPT_f6c160c9a513e960_EOF' - GH_AW_PROMPT_0583b53a137d140b_EOF + GH_AW_PROMPT_f6c160c9a513e960_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_0583b53a137d140b_EOF' + cat << 'GH_AW_PROMPT_f6c160c9a513e960_EOF' Tools: add_comment(max:20), create_pull_request(max:5), push_to_pull_request_branch(max:5), missing_tool, missing_data, noop - GH_AW_PROMPT_0583b53a137d140b_EOF + GH_AW_PROMPT_f6c160c9a513e960_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md" - cat << 'GH_AW_PROMPT_0583b53a137d140b_EOF' + cat << 'GH_AW_PROMPT_f6c160c9a513e960_EOF' The following GitHub context information is available for this workflow: @@ -206,12 +207,12 @@ jobs: {{/if}} - GH_AW_PROMPT_0583b53a137d140b_EOF + GH_AW_PROMPT_f6c160c9a513e960_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_0583b53a137d140b_EOF' + cat << 'GH_AW_PROMPT_f6c160c9a513e960_EOF' {{#runtime-import .github/workflows/release-notes.md}} - GH_AW_PROMPT_0583b53a137d140b_EOF + GH_AW_PROMPT_f6c160c9a513e960_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -374,12 +375,12 @@ jobs: mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_0383588c34f2e552_EOF' + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_9fffc8fc9804009c_EOF' {"add_comment":{"max":20,"target":"*"},"create_pull_request":{"draft":true,"labels":["release-notes","automated"],"max":5,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS"],"protected_path_prefixes":[".github/",".agents/"],"title_prefix":"[release-notes] "},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"if_no_changes":"warn","labels":["release-notes","automated"],"max":5,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS"],"protected_path_prefixes":[".github/",".agents/"],"title_prefix":"[release-notes] "}} - GH_AW_SAFE_OUTPUTS_CONFIG_0383588c34f2e552_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_9fffc8fc9804009c_EOF - name: Write Safe Outputs Tools run: | - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_3e4b9e2d3079ffd4_EOF' + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_81ee74039716b11c_EOF' { "description_suffixes": { "add_comment": " CONSTRAINTS: Maximum 20 comment(s) can be added. Target: *.", @@ -389,8 +390,8 @@ jobs: "repo_params": {}, "dynamic_tools": [] } - GH_AW_SAFE_OUTPUTS_TOOLS_META_3e4b9e2d3079ffd4_EOF - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_4d9f4383e68a101c_EOF' + GH_AW_SAFE_OUTPUTS_TOOLS_META_81ee74039716b11c_EOF + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_b7d7f457f6eec6f6_EOF' { "add_comment": { "defaultMax": 1, @@ -524,7 +525,7 @@ jobs: } } } - GH_AW_SAFE_OUTPUTS_VALIDATION_4d9f4383e68a101c_EOF + GH_AW_SAFE_OUTPUTS_VALIDATION_b7d7f457f6eec6f6_EOF node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs - name: Generate Safe Outputs MCP Server Config id: safe-outputs-config @@ -594,7 +595,7 @@ jobs: export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.11' mkdir -p /home/runner/.copilot - cat << GH_AW_MCP_CONFIG_f5439150d6a63e99_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh + cat << GH_AW_MCP_CONFIG_f60514f67b5fff57_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh { "mcpServers": { "github": { @@ -635,7 +636,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_f5439150d6a63e99_EOF + GH_AW_MCP_CONFIG_f60514f67b5fff57_EOF - name: Download activation artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -1100,6 +1101,7 @@ jobs: await main(); pre_activation: + if: (!github.event.repository.fork) || github.event_name == 'workflow_dispatch' runs-on: ubuntu-slim outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index b19d56f655..096d4c7d8b 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -1,4 +1,6 @@ --- +if: (!github.event.repository.fork) || github.event_name == 'workflow_dispatch' + permissions: contents: read pull-requests: read From 1fb31f14f5c3682e1bd9448fa1f9f07176893e4a Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Thu, 2 Apr 2026 23:45:49 -0700 Subject: [PATCH 54/65] Set release-notes workflow timeout to 2 hours --- .github/workflows/release-notes.lock.yml | 38 ++++++++++++------------ .github/workflows/release-notes.md | 2 ++ 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/.github/workflows/release-notes.lock.yml b/.github/workflows/release-notes.lock.yml index 6910d7992b..71e7de6f65 100644 --- a/.github/workflows/release-notes.lock.yml +++ b/.github/workflows/release-notes.lock.yml @@ -21,7 +21,7 @@ # For more information: https://github.github.com/gh-aw/introduction/overview/ # # -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"632d40ace6be3367650a73f4cb254131628a9d1bc6bb1c52b59ccfe1f13b3fec","compiler_version":"v0.65.6","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"258a65cef4805b1365cd65928059c0f570c9ced88a7d855afb03b906893066bf","compiler_version":"v0.65.6","strict":true,"agent_id":"copilot"} name: ".NET Release Notes Maintenance" "on": @@ -164,20 +164,20 @@ jobs: run: | bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh { - cat << 'GH_AW_PROMPT_f6c160c9a513e960_EOF' + cat << 'GH_AW_PROMPT_df7ec18eb0474082_EOF' - GH_AW_PROMPT_f6c160c9a513e960_EOF + GH_AW_PROMPT_df7ec18eb0474082_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_f6c160c9a513e960_EOF' + cat << 'GH_AW_PROMPT_df7ec18eb0474082_EOF' Tools: add_comment(max:20), create_pull_request(max:5), push_to_pull_request_branch(max:5), missing_tool, missing_data, noop - GH_AW_PROMPT_f6c160c9a513e960_EOF + GH_AW_PROMPT_df7ec18eb0474082_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md" - cat << 'GH_AW_PROMPT_f6c160c9a513e960_EOF' + cat << 'GH_AW_PROMPT_df7ec18eb0474082_EOF' The following GitHub context information is available for this workflow: @@ -207,12 +207,12 @@ jobs: {{/if}} - GH_AW_PROMPT_f6c160c9a513e960_EOF + GH_AW_PROMPT_df7ec18eb0474082_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_f6c160c9a513e960_EOF' + cat << 'GH_AW_PROMPT_df7ec18eb0474082_EOF' {{#runtime-import .github/workflows/release-notes.md}} - GH_AW_PROMPT_f6c160c9a513e960_EOF + GH_AW_PROMPT_df7ec18eb0474082_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -375,12 +375,12 @@ jobs: mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_9fffc8fc9804009c_EOF' + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_a1a70c854ce3d3f5_EOF' {"add_comment":{"max":20,"target":"*"},"create_pull_request":{"draft":true,"labels":["release-notes","automated"],"max":5,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS"],"protected_path_prefixes":[".github/",".agents/"],"title_prefix":"[release-notes] "},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"if_no_changes":"warn","labels":["release-notes","automated"],"max":5,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS"],"protected_path_prefixes":[".github/",".agents/"],"title_prefix":"[release-notes] "}} - GH_AW_SAFE_OUTPUTS_CONFIG_9fffc8fc9804009c_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_a1a70c854ce3d3f5_EOF - name: Write Safe Outputs Tools run: | - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_81ee74039716b11c_EOF' + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_1a3d0610c1d608e5_EOF' { "description_suffixes": { "add_comment": " CONSTRAINTS: Maximum 20 comment(s) can be added. Target: *.", @@ -390,8 +390,8 @@ jobs: "repo_params": {}, "dynamic_tools": [] } - GH_AW_SAFE_OUTPUTS_TOOLS_META_81ee74039716b11c_EOF - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_b7d7f457f6eec6f6_EOF' + GH_AW_SAFE_OUTPUTS_TOOLS_META_1a3d0610c1d608e5_EOF + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_a75494a945553f1b_EOF' { "add_comment": { "defaultMax": 1, @@ -525,7 +525,7 @@ jobs: } } } - GH_AW_SAFE_OUTPUTS_VALIDATION_b7d7f457f6eec6f6_EOF + GH_AW_SAFE_OUTPUTS_VALIDATION_a75494a945553f1b_EOF node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs - name: Generate Safe Outputs MCP Server Config id: safe-outputs-config @@ -595,7 +595,7 @@ jobs: export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.11' mkdir -p /home/runner/.copilot - cat << GH_AW_MCP_CONFIG_f60514f67b5fff57_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh + cat << GH_AW_MCP_CONFIG_d030b8fe358a9b4a_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh { "mcpServers": { "github": { @@ -636,7 +636,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_f60514f67b5fff57_EOF + GH_AW_MCP_CONFIG_d030b8fe358a9b4a_EOF - name: Download activation artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -675,7 +675,7 @@ jobs: # --allow-tool shell(wc) # --allow-tool shell(yq) # --allow-tool write - timeout-minutes: 20 + timeout-minutes: 120 run: | set -o pipefail touch /tmp/gh-aw/agent-step-summary.md @@ -948,7 +948,7 @@ jobs: GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_GROUP_REPORTS: "false" GH_AW_FAILURE_REPORT_AS_ISSUE: "true" - GH_AW_TIMEOUT_MINUTES: "20" + GH_AW_TIMEOUT_MINUTES: "120" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index 096d4c7d8b..4cd10c017c 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -5,6 +5,7 @@ permissions: contents: read pull-requests: read issues: read + runtimes: dotnet: version: "9.0" @@ -33,6 +34,7 @@ tools: - dotnet-release - git - jq +timeout-minutes: 120 on: schedule: daily around 9am PDT From d100e656d0da24e66caeabe605d60f80611e1cf4 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Sat, 4 Apr 2026 16:35:52 -0700 Subject: [PATCH 55/65] Split release notes skill pipeline Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/api-diff/SKILL.md | 91 ++++++++--- .github/skills/generate-changes/SKILL.md | 129 ++++++++++++++++ .github/skills/generate-features/SKILL.md | 101 +++++++++++++ .github/skills/release-notes/DESIGN.md | 141 +++++++++--------- .github/skills/release-notes/SKILL.md | 17 ++- .../references/changes-schema.md | 84 ++++++----- .../references/feature-scoring.md | 40 +++++ .github/workflows/release-notes.md | 26 +++- 8 files changed, 495 insertions(+), 134 deletions(-) create mode 100644 .github/skills/generate-changes/SKILL.md create mode 100644 .github/skills/generate-features/SKILL.md create mode 100644 .github/skills/release-notes/references/feature-scoring.md diff --git a/.github/skills/api-diff/SKILL.md b/.github/skills/api-diff/SKILL.md index 1e1a24b6ce..538737b47f 100644 --- a/.github/skills/api-diff/SKILL.md +++ b/.github/skills/api-diff/SKILL.md @@ -1,30 +1,83 @@ --- name: api-diff -description: Generate an API comparison report between two .NET versions using the RunApiDiff.ps1 script. Invoke when the user asks to run, create, or generate an API diff. -disable-model-invocation: true +description: > + Download, inspect, and diff .NET APIs for a given build or release. Use + `dotnet-inspect` to verify that APIs actually exist in the shipped packages, + detect missed reverts, and generate before/after API diffs. Use + `RunApiDiff.ps1` when a full markdown diff report is needed. --- -# API Diff Generation +# API Downloading, Verification, and Diffing -Map the user's request to parameters for `release-notes/RunApiDiff.ps1` and run it. See [release-notes/RunApiDiff.md](../../../release-notes/RunApiDiff.md) for the full parameter reference. +Use this skill when you need evidence about the **actual public API surface** of a .NET build. -When no versions are mentioned, run with no parameters — the script auto-infers versions. +This is primarily for: + +- confirming whether an API really shipped in the build binaries/ref packs +- catching reverts or incomplete rollouts before they end up in release notes +- generating before/after API diffs for a release milestone + +## Preferred workflow + +### 1. Query the right build first + +Do **not** trust the locally installed SDK for preview work. Query the target build's packages directly. + +Use the process in [api-verification.md](../release-notes/references/api-verification.md): + +1. Generate or read `build-metadata.json` +2. Get `nuget.source` and the package versions for the target release +3. Run `dotnet-inspect` against those exact packages + +### 2. Verify APIs with `dotnet-inspect` + +Typical tasks: + +- **Find a type or member** to confirm the API exists +- **Compare versions** to see what changed between previews or between RC and GA +- **Validate naming** before writing prose or code samples + +Examples: + +```bash +# Find a type in the runtime ref pack +dnx dotnet-inspect -y -- find "*AnyNewLine*" \ + --package "Microsoft.NETCore.App.Ref@${VER}" \ + --source "$FEED" + +# Verify members on a type +dnx dotnet-inspect -y -- member RegexOptions \ + --package "Microsoft.NETCore.App.Ref@${VER}" \ + --source "$FEED" \ + -k field + +# Diff public APIs between two versions +dnx dotnet-inspect -y -- diff \ + --package "Microsoft.NETCore.App.Ref@11.0.0-preview.2..11.0.0-preview.3" \ + --source "$FEED" +``` + +If the API is missing from the target build, treat that as a serious signal: it may have been renamed, kept internal, or reverted. + +### 3. Use `RunApiDiff.ps1` for full diff reports + +When the user wants the markdown-ready, repo-shaped API diff output, use `release-notes/RunApiDiff.ps1`. See [release-notes/RunApiDiff.md](../../../release-notes/RunApiDiff.md) for the full parameter reference. ## Mapping natural language to parameters -| User says | Parameters | -|---|---| -| "generate the next API diff" | *(none)* | -| ".NET 10 GA vs .NET 11 Preview 1" | `-PreviousMajorMinor 10.0 -CurrentMajorMinor 11.0 -CurrentPrereleaseLabel preview.1` | -| "net9.0-preview6 to net10.0-preview5" | `-PreviousMajorMinor 9.0 -PreviousPrereleaseLabel preview.6 -CurrentMajorMinor 10.0 -CurrentPrereleaseLabel preview.5` | -| ".NET 10 RC 2 vs .NET 10 GA" | `-PreviousMajorMinor 10.0 -PreviousPrereleaseLabel rc.2 -CurrentMajorMinor 10.0` | -| "10.0.0-preview.7.25380.108 to 10.0.0-rc.1.25451.107" | `-PreviousVersion "10.0.0-preview.7.25380.108" -CurrentVersion "10.0.0-rc.1.25451.107"` | - -- **GA** or no qualifier → omit the PrereleaseLabel parameter -- **Preview N** / **previewN** → `-PrereleaseLabel preview.N` -- **RC N** / **rcN** → `-PrereleaseLabel rc.N` -- **netX.Y-previewN** (TFM format) → `-MajorMinor X.Y -PrereleaseLabel preview.N` -- Full NuGet version strings → use `-PreviousVersion` / `-CurrentVersion` directly +| User says | Parameters | +| ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| "generate the next API diff" | _(none)_ | +| ".NET 10 GA vs .NET 11 Preview 1" | `-PreviousMajorMinor 10.0 -CurrentMajorMinor 11.0 -CurrentPrereleaseLabel preview.1` | +| "net9.0-preview6 to net10.0-preview5" | `-PreviousMajorMinor 9.0 -PreviousPrereleaseLabel preview.6 -CurrentMajorMinor 10.0 -CurrentPrereleaseLabel preview.5` | +| ".NET 10 RC 2 vs .NET 10 GA" | `-PreviousMajorMinor 10.0 -PreviousPrereleaseLabel rc.2 -CurrentMajorMinor 10.0` | +| "10.0.0-preview.7.25380.108 to 10.0.0-rc.1.25451.107" | `-PreviousVersion "10.0.0-preview.7.25380.108" -CurrentVersion "10.0.0-rc.1.25451.107"` | + +- **GA** or no qualifier -> omit the PrereleaseLabel parameter +- **Preview N** / **previewN** -> `-PrereleaseLabel preview.N` +- **RC N** / **rcN** -> `-PrereleaseLabel rc.N` +- **netX.Y-previewN** (TFM format) -> `-MajorMinor X.Y -PrereleaseLabel preview.N` +- Full NuGet version strings -> use `-PreviousVersion` / `-CurrentVersion` directly - The "previous" version is always the older version; "current" is the newer one ## Running the script @@ -33,4 +86,4 @@ When no versions are mentioned, run with no parameters — the script auto-infer .\release-notes\RunApiDiff.ps1 [mapped parameters] ``` -Set an initial wait of at least 300 seconds — the script takes several minutes. Use `read_powershell` to poll for completion. The script does not print a final "done" message; it exits after generating a README.md in the output folder. After completion, summarize the results: how many diff files were generated and where. +Set an initial wait of at least 300 seconds — the script takes several minutes. After completion, summarize the results: how many diff files were generated and where. diff --git a/.github/skills/generate-changes/SKILL.md b/.github/skills/generate-changes/SKILL.md new file mode 100644 index 0000000000..b7b19040c2 --- /dev/null +++ b/.github/skills/generate-changes/SKILL.md @@ -0,0 +1,129 @@ +--- +name: generate-changes +description: > + Generate `changes.json` for a .NET release milestone by selecting the correct + VMR base/head refs and running `dotnet-release generate changes`. Handles + preview-only multi-branch targeting (`main` vs release branches vs tags) and + emits the authoritative manifest of what shipped. DO NOT USE FOR: API + verification/diffs (use api-diff), feature scoring (use generate-features), + or writing markdown release notes (use release-notes). +--- + +# Generate `changes.json` + +Produce the authoritative `changes.json` input for a preview, RC, or GA release milestone. + +This is the **VMR-aware data acquisition stage** of the release notes pipeline: + +1. Determine which milestone(s) are active +2. Resolve the correct `--base` and `--head` refs +3. Run `dotnet-release generate changes` +4. Write `changes.json` into the correct `release-notes/` folder + +## When to use + +- A new preview, RC, or GA milestone needs fresh shipped-change data +- The user wants to know whether **multiple preview milestones** are active at once +- The release notes branch should be refreshed after the VMR moved forward +- `changes.json` is missing, stale, or suspected to have the wrong ref selection + +## Preview-only branch targeting + +This is the subtle part, and it mostly matters for **previews**. + +Multiple preview milestones can be active simultaneously: + +```text +Latest shipped in this repo: Preview 3 +VMR main: Preview 5 +VMR release branch exists: Preview 4 + +→ Generate one `changes.json` for Preview 4 +→ Generate one `changes.json` for Preview 5 +``` + +For each target milestone `N`: + +| Milestone state | Base ref | Head ref | +| --------------------------------------- | ----------- | ------------------ | +| Tag exists for N | Tag for N-1 | Tag for N | +| Release branch exists for N, no tag yet | Tag for N-1 | Release branch tip | +| Only on `main` | Tag for N-1 | `main` | + +**Critical rule:** never use `main` for milestone `N` if `main` has already moved to `N+1`. + +## Inputs + +The user should provide as much of this as they know: + +- **Target release** — e.g. `.NET 11 Preview 4`, `.NET 10 RC 2`, `.NET 10 GA` +- **VMR clone path** — defaults to a local clone of `dotnet/dotnet` +- Optionally, the exact refs if they already know them + +If the user does **not** specify the milestone, infer it from: + +1. `release-notes/{version}/releases.json` in this repo +2. `eng/Versions.props` on `main` in the VMR +3. Matching preview tags and release branches in the VMR + +## Process + +### 1. Determine the floor from `releases.json` + +Find the latest shipped milestone in this repo. That tells you the lowest in-flight milestone that may need work. + +### 2. Inspect the VMR + +- Read `eng/Versions.props` on `main` to determine the current prerelease iteration +- List matching VMR tags for finalized milestones +- List matching VMR release branches for stabilizing milestones + +Use the [VMR structure reference](../release-notes/references/vmr-structure.md) for naming conventions and branch patterns. + +### 3. Resolve `--base` and `--head` + +For each active milestone: + +- `--base` is the previous shipped milestone tag +- `--head` is the milestone tag, release branch tip, or `main`, depending on what exists + +### 4. Generate the file + +```bash +dotnet-release generate changes \ + --base \ + --head \ + --version "" \ + --date "" \ + --labels \ + --output release-notes///changes.json +``` + +Examples: + +```bash +# Preview milestone +dotnet-release generate changes ~/git/dotnet \ + --base v11.0.0-preview.3.26210.100 \ + --head origin/release/11.0.1xx-preview4 \ + --version "11.0.0-preview.4" \ + --labels \ + --output release-notes/11.0/preview/preview4/changes.json + +# GA/patch milestone +dotnet-release generate changes ~/git/dotnet \ + --base v10.0.7 \ + --head v10.0.8 \ + --version "10.0.8" \ + --output release-notes/10.0/10.0.8/changes.json +``` + +## Output contract + +The output file must follow the shared schema documented in [changes-schema.md](../release-notes/references/changes-schema.md): + +- top-level `release_version`, `release_date`, `changes`, `commits` +- stable `id` values in `repo@shortcommit` format +- same authoritative source of truth used by later skills + +Once `changes.json` exists, the next step is usually `generate-features`. diff --git a/.github/skills/generate-features/SKILL.md b/.github/skills/generate-features/SKILL.md new file mode 100644 index 0000000000..23541b1b20 --- /dev/null +++ b/.github/skills/generate-features/SKILL.md @@ -0,0 +1,101 @@ +--- +name: generate-features +description: > + Generate `features.json` from `changes.json` by ranking and annotating shipped + changes. `features.json` keeps the same schema as `changes.json` and adds + optional scoring fields so release notes, docs, and blog posts can apply + different cutoffs. DO NOT USE FOR: regenerating VMR diffs (use + generate-changes) or writing final markdown (use release-notes). +--- + +# Generate `features.json` + +Create a scored, reusable feature list from `changes.json`. + +This is the **triage stage** of the release notes pipeline. It turns the comprehensive manifest of shipped changes into a ranked set of candidates for external communication. + +## Purpose + +`changes.json` answers **what shipped**. + +`features.json` answers **what is worth talking about**, while staying schema-compatible with `changes.json` so the two files can be joined, diffed, and compared easily. + +## Schema contract + +`features.json` intentionally mirrors `changes.json`: + +- same top-level fields: `release_version`, `release_date`, `changes`, `commits` +- same `id`, `repo`, `product`, and commit key structure +- same `commits{}` object so cross-file joins still work + +The only additions are **optional enrichment fields** on change entries, such as: + +| Field | Type | Purpose | +| ----------------- | ------ | ----------------------------------------- | +| `score` | number | Higher means more likely to be documented | +| `score_reason` | string | Short explanation of the score | +| `score_breakdown` | object | Optional per-dimension scoring details | + +This schema is intentionally loose and can grow as the workflow learns what it needs. + +## Default scoring guidance + +Use a consistent numeric scale within a file. The recommended starting point is **0-10**: + +| Score | Meaning | Typical outcome | +| ------ | --------------------------------------------- | -------------------------------------------------- | +| `9-10` | Marquee, broadly useful, clearly user-visible | Strong candidate for blog, docs, and release notes | +| `7-8` | Strong release-note feature | Usually document in release notes; maybe docs | +| `4-6` | Moderate or niche value | Mention if space and audience justify it | +| `1-3` | Low-signal or narrowly scoped | Usually skip in public-facing summaries | +| `0` | Infra, tests, refactoring, or pure churn | Do not document | + +See [feature-scoring.md](../release-notes/references/feature-scoring.md) for the heuristics. + +## Process + +### 1. Start from `changes.json` + +Do **not** invent features from memory, roadmaps, or PR titles found elsewhere. Everything in `features.json` must trace back to `changes.json`. + +### 2. Score what shipped + +Look for signals such as: + +- new public APIs or meaningful behavioral changes +- developer productivity wins +- performance work with clear user impact +- compatibility or migration significance +- changes that need docs or samples to be usable + +Down-rank or exclude: + +- infra and dependency churn +- test-only changes +- internal refactors with no user-facing impact +- reverts and partial work that did not survive into the build + +### 3. Use API evidence to refine the score + +If a change depends on public APIs, use `api-diff` / `dotnet-inspect` to confirm the API exists in the actual build. Missing or reverted APIs should be scored down or excluded. + +### 4. Write `features.json` + +The output typically lives next to `changes.json`: + +```text +release-notes/{major.minor}/preview/{previewN}/features.json +release-notes/{major.minor}/{major.minor.patch}/features.json +``` + +Keep the file mechanically friendly: + +- preserve the same IDs and commit keys when possible +- make `score` optional, not required +- keep `score_reason` brief and evidence-based + +## How downstream skills use it + +- **`release-notes`** uses higher-scored entries to draft markdown +- **Docs/blog workflows** can apply their own cutoffs later +- Humans can adjust scoring without changing the source-of-truth shipped data diff --git a/.github/skills/release-notes/DESIGN.md b/.github/skills/release-notes/DESIGN.md index 928499ea14..a8c3f68351 100644 --- a/.github/skills/release-notes/DESIGN.md +++ b/.github/skills/release-notes/DESIGN.md @@ -16,31 +16,31 @@ How the automated release notes system for .NET works, why it's designed this wa ## Architecture -The system has three layers, each with a distinct responsibility: +The system still has three layers, but the AI/editorial layer is now split into reusable skills with JSON handoffs: ```text -┌─────────────────────────────────────────────────┐ -│ Agentic Workflow (cron) │ -│ .github/workflows/release-notes.md │ -│ Orchestration: branch lifecycle, PR mgmt, │ -│ human interaction, scheduling │ -├─────────────────────────────────────────────────┤ -│ AI Agent (editorial) │ -│ Reads changes.json → writes markdown │ -│ Judgment: which PRs matter, how to describe │ -│ them, code samples, feature grouping │ -├─────────────────────────────────────────────────┤ -│ dotnet-release tool (deterministic) │ -│ `dotnet-release generate changes` │ -│ Data: source-manifest diff → GitHub API → │ -│ changes.json │ -└─────────────────────────────────────────────────┘ +┌──────────────────────────────────────────────────────────────┐ +│ Agentic Workflow (cron) │ +│ .github/workflows/release-notes.md │ +│ Orchestration: branch lifecycle, PR mgmt, comments, timing │ +├──────────────────────────────────────────────────────────────┤ +│ Reusable skills (editorial pipeline) │ +│ generate-changes → changes.json │ +│ generate-features → features.json │ +│ api-diff / dotnet-inspect → API evidence │ +│ release-notes → markdown │ +├──────────────────────────────────────────────────────────────┤ +│ Tooling (deterministic where possible) │ +│ dotnet-release, dotnet-inspect, GitHub APIs, local git │ +└──────────────────────────────────────────────────────────────┘ ``` -### Why three layers? +### Why split the stages? -- **The tool** handles mechanical data collection. It's deterministic — same inputs always produce the same output. It can be tested, debugged, and improved independently. -- **The agent** handles editorial judgment. It decides which PRs are worth writing about and how to describe them. This is inherently fuzzy and benefits from AI. +- **`generate-changes`** handles mechanical data collection. It's deterministic — same inputs always produce the same output. +- **`generate-features`** adds reusable triage and scoring so release notes, docs, and blogs can share the same ranked feature list. +- **`api-diff`** verifies the public API story against the actual build, which is the best defense against missed reverts. +- **`release-notes`** focuses on editorial judgment: which scored items matter, how to describe them, and how to preserve human edits. - **The workflow** handles orchestration. It knows when to run, what branches to manage, how to interact with humans, and how to preserve their edits. ## Layer 1 — The Tool @@ -70,6 +70,7 @@ dotnet-release generate changes \ ### What the tool does NOT do - Editorial judgment (which PRs are important) +- Feature scoring (`features.json`) - Markdown generation - Branch management or PR creation @@ -93,29 +94,30 @@ By comparing this file at two release points, the tool gets exact per-component Example (Preview 1 → Preview 2): the tool found 1,389 PRs across 21 changed repos. -## Layer 2 — The Agent +## Layer 2 — The Skills -The AI agent reads `changes.json` and writes markdown release notes. Its guidance comes from reference documents in `.github/skills/release-notes/references/`: +The editorial pipeline is intentionally split into small, reusable skills: -| Document | Purpose | -| -------- | ------- | -| [quality-bar.md](references/quality-bar.md) | North star — fidelity, value, WHY+HOW | -| [vmr-structure.md](references/vmr-structure.md) | VMR branches, tags, source-manifest.json | -| [changes-schema.md](references/changes-schema.md) | The changes.json schema | -| [component-mapping.md](references/component-mapping.md) | Components → product slugs → output files | -| [format-template.md](references/format-template.md) | Markdown document structure | -| [editorial-rules.md](references/editorial-rules.md) | Tone, attribution, naming | -| [examples/](references/examples/README.md) | Curated examples by component — short, medium, long-form styles | +| Document | Purpose | +| ------------------------------------------------------- | --------------------------------------------------------------- | +| [quality-bar.md](references/quality-bar.md) | North star — fidelity, value, WHY+HOW | +| [vmr-structure.md](references/vmr-structure.md) | VMR branches, tags, source-manifest.json | +| [changes-schema.md](references/changes-schema.md) | The shared `changes.json` / `features.json` schema | +| [feature-scoring.md](references/feature-scoring.md) | How to rank features for release notes, docs, and blogs | +| [component-mapping.md](references/component-mapping.md) | Components → product slugs → output files | +| [format-template.md](references/format-template.md) | Markdown document structure | +| [editorial-rules.md](references/editorial-rules.md) | Tone, attribution, naming | +| [examples/](references/examples/README.md) | Curated examples by component — short, medium, long-form styles | -These are **goal-oriented**, not procedural. They describe what good release notes look like, not the exact steps to produce them. The agent figures out the HOW. +These are **goal-oriented**, not procedural. They describe what good release notes and feature selection look like, not the exact steps to produce them. The skills figure out the HOW. -### Agent responsibilities +### Skill responsibilities -- **Triage** — read `changes.json` and identify which PRs are worth writing about. Use the [component mapping](references/component-mapping.md) to route changes from `repo` to the correct output files. -- **Verify** — before writing about any API, use `dotnet-inspect` to confirm it exists with the correct type names, member signatures, and namespaces. See [api-verification.md](references/api-verification.md). -- **Write** — produce markdown release notes for high-value features, following the quality bar. Only use API names, type names, and code samples that have been verified. -- **Respect edits** — diff the PR branch to see what humans have changed and preserve their work -- **Respond** — read PR comments and incorporate human feedback +- **`generate-changes`** — determine the correct VMR base/head refs, including preview-only multi-branch targeting, and emit `changes.json` +- **`generate-features`** — read `changes.json`, score likely features, and emit `features.json` using the same schema plus optional scoring +- **`api-diff`** — verify APIs against the actual build binaries/ref packs and generate diffs when needed +- **`release-notes`** — produce markdown release notes for high-value features, following the quality bar and only using verified API names and code samples +- **Workflow-facing behavior** — respect human edits, diff the PR branch, and incorporate PR comments and feedback ## Layer 3 — The Agentic Workflow @@ -140,11 +142,11 @@ The workflow discovers all milestones between `latest_shipped + 1` and `main_ite Each milestone needs its own base and head ref. This is re-validated every run because refs can change: -| Milestone state | Base ref | Head ref | -| --------------- | -------- | -------- | -| Has VMR tag (finalized) | Tag for N-1 | Tag for N | +| Milestone state | Base ref | Head ref | +| -------------------------------- | ----------- | ------------------ | +| Has VMR tag (finalized) | Tag for N-1 | Tag for N | | Has release branch (stabilizing) | Tag for N-1 | Release branch tip | -| Only on main (in development) | Tag for N-1 | main | +| Only on main (in development) | Tag for N-1 | main | **Critical**: never use `main` for milestone N if `main` has moved to N+1. Check the iteration in `Versions.props` every run. @@ -165,11 +167,13 @@ Each branch is long-lived — it's created on the first run and updated frequent This is the most delicate part of the system. The branches are shared workspaces: **Respecting edits:** + - Before writing, diff the branch to identify human commits - Files humans have edited are partially off-limits — only add new sections, never overwrite their changes - When a file has mixed agent + human content, be surgical — touch only agent-authored sections **Responding to comments:** + - Read all PR comments and review threads since the last run - Classify: actionable feedback, questions, disagreements, resolved - For actionable items: make the change and confirm @@ -179,21 +183,22 @@ This is the most delicate part of the system. The branches are shared workspaces - This is a conversation. Engage, don't ignore. **Handling conflicts:** + - If a human and the agent both changed the same section, the human's version wins - If the agent is unsure whether a human edit was intentional, ask via PR comment - Never force-push or rewrite human commits ### PR lifecycle -| State | Action | -| ----- | ------ | -| No PR for milestone | Create branch, generate content, open draft PR | -| PR exists, source changed | Regenerate `changes.json`, update/add markdown sections | -| PR exists, human edited | Preserve edits, only update untouched sections | -| New tag appeared | Final regen with `--head `, note finalization | -| Main bumped | Switch earlier milestone's head ref to release branch/tag | -| PR merged | Skip on future runs | -| PR closed | Don't reopen, log and move on | +| State | Action | +| ------------------------- | ------------------------------------------------------------------------- | +| No PR for milestone | Create branch, generate content, open draft PR | +| PR exists, source changed | Regenerate `changes.json` / `features.json`, update/add markdown sections | +| PR exists, human edited | Preserve edits, only update untouched sections | +| New tag appeared | Final regen with `--head `, note finalization | +| Main bumped | Switch earlier milestone's head ref to release branch/tag | +| PR merged | Skip on future runs | +| PR closed | Don't reopen, log and move on | ### Schedule and transitions @@ -215,21 +220,22 @@ The only ways the workflow can modify state: Each release milestone produces these files in `release-notes/{major.minor}/preview/{previewN}/`: -| File | Source | Description | -| ---- | ------ | ----------- | -| `changes.json` | Tool | Every PR that shipped — comprehensive, machine-readable | -| `README.md` | Agent | Index/TOC linking to component files | -| `libraries.md` | Agent | System.\* BCL APIs (from `dotnet/runtime`) | -| `runtime.md` | Agent | CoreCLR, Mono, GC, JIT (from `dotnet/runtime`) | -| `aspnetcore.md` | Agent | ASP.NET Core, Blazor, SignalR | -| `sdk.md` | Agent | CLI, project system, templating | -| `efcore.md` | Agent | Entity Framework Core | -| `csharp.md` | Agent | C# language features | -| `fsharp.md` | Agent | F# language and compiler | -| `winforms.md` | Agent | Windows Forms | -| `wpf.md` | Agent | WPF | -| `msbuild.md` | Agent | MSBuild | -| `nuget.md` | Agent | NuGet client | +| File | Source | Description | +| --------------- | ------------------- | ------------------------------------------------------- | +| `changes.json` | `generate-changes` | Every PR that shipped — comprehensive, machine-readable | +| `features.json` | `generate-features` | Same schema plus optional scoring and editorial hints | +| `README.md` | `release-notes` | Index/TOC linking to component files | +| `libraries.md` | `release-notes` | System.\* BCL APIs (from `dotnet/runtime`) | +| `runtime.md` | `release-notes` | CoreCLR, Mono, GC, JIT (from `dotnet/runtime`) | +| `aspnetcore.md` | `release-notes` | ASP.NET Core, Blazor, SignalR | +| `sdk.md` | `release-notes` | CLI, project system, templating | +| `efcore.md` | `release-notes` | Entity Framework Core | +| `csharp.md` | `release-notes` | C# language features | +| `fsharp.md` | `release-notes` | F# language and compiler | +| `winforms.md` | `release-notes` | Windows Forms | +| `wpf.md` | `release-notes` | WPF | +| `msbuild.md` | `release-notes` | MSBuild | +| `nuget.md` | `release-notes` | NuGet client | Components with no noteworthy changes get a minimal stub file. @@ -272,4 +278,5 @@ Each preview is a coherent release milestone with its own set of features. Maint ## Open questions 1. **Conflict resolution heuristics** — when the agent and a human both changed the same section between runs, the human wins. But how granular is "a section"? Need to define this precisely (per-heading? per-file?). -2. **RC/GA milestone naming** — the multi-milestone logic assumes `previewN` naming. RC and GA milestones use different naming (`rc1`, `rc2`, `ga`). The workflow needs to handle the transition gracefully. +2. **Feature grouping and schema growth** — some notable features span multiple PRs. `features.json` starts schema-compatible with `changes.json`, but may need optional grouping fields over time. +3. **RC/GA milestone naming** — the multi-milestone logic assumes `previewN` naming. RC and GA milestones use different naming (`rc1`, `rc2`, `ga`). The workflow needs to handle the transition gracefully. diff --git a/.github/skills/release-notes/SKILL.md b/.github/skills/release-notes/SKILL.md index 61d2863d4e..3aabe40d7c 100644 --- a/.github/skills/release-notes/SKILL.md +++ b/.github/skills/release-notes/SKILL.md @@ -1,19 +1,21 @@ --- name: release-notes -description: Generate and maintain .NET release notes. Uses the VMR source-manifest.json and dotnet-release tool to identify what shipped, then writes curated markdown for high-value features. Designed to run as a cron-driven agentic workflow that maintains PR branches per release milestone. -compatibility: Requires GitHub MCP server or gh CLI for cross-repo queries. Uses dotnet-release generate changes for structured change data. Uses dotnet-inspect for API verification. +description: Generate and maintain .NET release notes from `features.json`. Uses `generate-changes` for authoritative shipped-change data, `generate-features` for scoring/triage, and `api-diff`/`dotnet-inspect` for API verification before writing curated markdown for the final notes. +compatibility: Requires GitHub MCP server or gh CLI for cross-repo queries. Pairs with the generate-changes, generate-features, and api-diff skills. --- # .NET Release Notes Generate and maintain release notes for .NET preview, RC, and GA releases. +This skill is the **editorial writing stage** of the pipeline. It turns a scored `features.json` file into the markdown that ships in this repository. + ## How it works -1. The `dotnet-release generate changes` tool diffs `source-manifest.json` between VMR release refs to produce `changes.json` — a comprehensive manifest of all PRs/commits that shipped -2. The agent reads `changes.json` and identifies high-value features worth documenting -3. The agent uses `dotnet-inspect` to verify public API names, type shapes, and member signatures before writing about them -4. The agent writes curated markdown release notes with verified API references and code samples +1. `generate-changes` diffs `source-manifest.json` between VMR refs to produce `changes.json` +2. `generate-features` reads `changes.json` and emits `features.json` with optional scores +3. `api-diff` / `dotnet-inspect` verifies public APIs and catches missed reverts +4. `release-notes` writes curated markdown using the higher-value entries from `features.json` 5. Output is one PR per release milestone in dotnet/core, maintained incrementally ## Design @@ -24,7 +26,8 @@ Generate and maintain release notes for .NET preview, RC, and GA releases. - [quality-bar.md](references/quality-bar.md) — what good release notes look like - [vmr-structure.md](references/vmr-structure.md) — VMR branches, tags, source-manifest.json -- [changes-schema.md](references/changes-schema.md) — the changes.json schema +- [changes-schema.md](references/changes-schema.md) — the shared `changes.json` / `features.json` schema +- [feature-scoring.md](references/feature-scoring.md) — how to score and cut features - [component-mapping.md](references/component-mapping.md) — components, product slugs, output files - [format-template.md](references/format-template.md) — markdown document structure - [editorial-rules.md](references/editorial-rules.md) — tone, attribution, naming diff --git a/.github/skills/release-notes/references/changes-schema.md b/.github/skills/release-notes/references/changes-schema.md index ce095737b2..89cfff2b64 100644 --- a/.github/skills/release-notes/references/changes-schema.md +++ b/.github/skills/release-notes/references/changes-schema.md @@ -1,42 +1,47 @@ -# changes.json Schema +# Shared Schema for `changes.json` and `features.json` -Reference for the `changes.json` file produced by `dotnet-release generate changes`. One file per release milestone. +Reference for the `changes.json` file produced by `dotnet-release generate changes`, and for the derived `features.json` file used by downstream editorial skills. One file per release milestone. ## Overview -`changes.json` is a comprehensive, machine-readable manifest of every PR and commit that shipped in a release. It is the companion to the editorial markdown release notes — the JSON tells you **everything that shipped**, the markdown tells you **what matters**. +`changes.json` is a comprehensive, machine-readable manifest of every PR and commit that shipped in a release. `features.json` is a scored derivative that keeps the same structure while adding optional enrichment for editorial prioritization. -It also serves as a companion to `cve.json` — both files use the same `commits{}` structure and `repo@shortcommit` key format, enabling cross-file joins. +Both files also serve as companions to `cve.json` — they use the same `commits{}` structure and `repo@shortcommit` key format, enabling cross-file joins. ## File location ```text release-notes/{major.minor}/preview/{previewN}/changes.json # previews release-notes/{major.minor}/{major.minor.patch}/changes.json # patches +release-notes/{major.minor}/preview/{previewN}/features.json # previews +release-notes/{major.minor}/{major.minor.patch}/features.json # patches ``` ## Top-level structure -| Field | Type | Description | -| ----- | ---- | ----------- | -| `release_version` | string | e.g., `"11.0.0-preview.3"` | -| `release_date` | string | ISO 8601, e.g., `"2026-04-08"` | -| `changes` | array | The change entries | -| `commits` | object | Normalized commit metadata, keyed by `repo@shortcommit` | +| Field | Type | Description | +| ----------------- | ------ | ------------------------------------------------------- | +| `release_version` | string | e.g., `"11.0.0-preview.3"` | +| `release_date` | string | ISO 8601, e.g., `"2026-04-08"` | +| `changes` | array | The change or feature entries | +| `commits` | object | Normalized commit metadata, keyed by `repo@shortcommit` | ## Change entry fields -| Field | Type | Description | -| ----- | ---- | ----------- | -| `id` | string | Globally unique identifier — `repo@shortcommit` format (e.g., `"runtime@c5d5be4"`) | -| `repo` | string | Short repository name (e.g., `"runtime"`) | -| `product` | string | Product slug (e.g., `"dotnet-runtime"`); absent for infra repos | -| `title` | string | PR title; `""` if not available | -| `url` | string | Public GitHub PR URL; `""` if non-public | -| `commit` | string | Key into top-level `commits{}` dict — the VMR (`dotnet/dotnet`) codeflow commit | -| `is_security` | bool | `true` if this is a security change | -| `local_repo_commit` | string | Key into `commits{}` — the source repo commit (same as `id`) | -| `labels` | array | PR labels (only present when `--labels` is used) | +| Field | Type | Description | +| ------------------- | ------ | ---------------------------------------------------------------------------------- | +| `id` | string | Globally unique identifier — `repo@shortcommit` format (e.g., `"runtime@c5d5be4"`) | +| `repo` | string | Short repository name (e.g., `"runtime"`) | +| `product` | string | Product slug (e.g., `"dotnet-runtime"`); absent for infra repos | +| `title` | string | PR title; `""` if not available | +| `url` | string | Public GitHub PR URL; `""` if non-public | +| `commit` | string | Key into top-level `commits{}` dict — the VMR (`dotnet/dotnet`) codeflow commit | +| `is_security` | bool | `true` if this is a security change | +| `local_repo_commit` | string | Key into `commits{}` — the source repo commit (same as `id`) | +| `labels` | array | PR labels (only present when `--labels` is used) | +| `score` | number | Optional editorial score; higher means more likely to document | +| `score_reason` | string | Optional short explanation for the score | +| `score_breakdown` | object | Optional structured scoring details | The `product` field is derived from the repo-level [component mapping](component-mapping.md). Infra repos like `arcade` and `symreader` have no `product` field. The `repo` field always matches the VMR manifest path. @@ -44,17 +49,17 @@ The `commit` field is the VMR codeflow commit in `dotnet/dotnet` that synced thi ## Commit entry fields (values in `commits{}`) -| Field | Type | Description | -| ----- | ---- | ----------- | -| `repo` | string | Short repository name | -| `branch` | string | Branch the commit landed on | -| `hash` | string | Full 40-character commit hash | -| `org` | string | GitHub organization (e.g., `"dotnet"`) | -| `url` | string | `.diff`-form commit URL | +| Field | Type | Description | +| -------- | ------ | -------------------------------------- | +| `repo` | string | Short repository name | +| `branch` | string | Branch the commit landed on | +| `hash` | string | Full 40-character commit hash | +| `org` | string | GitHub organization (e.g., `"dotnet"`) | +| `url` | string | `.diff`-form commit URL | ## Conventions -- **No nulls** — required fields are always present. Optional fields (`product`, `labels`, `local_repo_commit`) may be absent. Use `""` for missing strings, `0` for missing integers. +- **No nulls** — required fields are always present. Optional fields (`product`, `labels`, `local_repo_commit`, `score`, `score_reason`, `score_breakdown`) may be absent. Use `""` for missing strings, `0` for missing integers. - **Naming** — `snake_case_lower` for JSON fields, `kebab-case-lower` for file names and repo slugs. - **Public URLs only** — every URL must resolve publicly. - **Commit URLs use `.diff` form** — for machine consumption. @@ -74,7 +79,9 @@ The `commit` field is the VMR codeflow commit in `dotnet/dotnet` that synced thi "url": "https://github.com/dotnet/runtime/pull/112345", "commit": "dotnet@a1b2c3d", "is_security": false, - "local_repo_commit": "runtime@b2d5fa8" + "local_repo_commit": "runtime@b2d5fa8", + "score": 9, + "score_reason": "Broadly useful new JSON configuration preset" }, { "id": "aspnetcore@f45f3c9", @@ -115,7 +122,7 @@ The `commit` field is the VMR codeflow commit in `dotnet/dotnet` that synced thi Note that both changes share the same `commit` — they were synced to the VMR in a single codeflow batch. The `local_repo_commit` values differ since each originated from a different source repository. -## Querying changes.json +## Querying `changes.json` ```bash # All changes @@ -135,14 +142,23 @@ jq -r '.changes[] | select(.is_security) | .title' changes.json # Cross-file join with cve.json (shared commit key format) jq -r '.changes[] | select(.is_security) | .local_repo_commit' changes.json -# → use these keys to look up CVE IDs in cve.json's cve_commits{} +# -> use these keys to look up CVE IDs in cve.json's cve_commits{} ``` +## Relationship between the files + +`changes.json` is the **source-of-truth input** to the editorial process. `features.json` is an enriched view that usually adds scoring and notes without changing the underlying identity of the shipped changes. + +- `changes.json` has an entry for every PR that shipped +- `features.json` usually preserves those entries and adds optional scoring metadata +- Both files can be joined through shared `id` and `commits{}` values + ## Relationship to markdown release notes -`changes.json` is the **input** to the editorial process. The markdown release notes are a curated subset: +The markdown release notes are a curated subset informed by these JSON files: - `changes.json` has an entry for every PR that shipped +- `features.json` helps rank which shipped changes are worth calling out - Markdown only covers features worth calling out -- The agent reads `changes.json` to know what shipped, then decides what to write about +- The agent reads `changes.json` and/or `features.json` to decide what to write about - If a feature isn't in `changes.json`, it must not appear in the markdown diff --git a/.github/skills/release-notes/references/feature-scoring.md b/.github/skills/release-notes/references/feature-scoring.md new file mode 100644 index 0000000000..218e4ee23e --- /dev/null +++ b/.github/skills/release-notes/references/feature-scoring.md @@ -0,0 +1,40 @@ +# Feature Scoring + +Use optional `score` fields in `features.json` to rank which shipped changes are worth turning into release notes, docs, or blog content. + +The score is a **decision aid**, not a hard rule. Editorial judgment still matters. + +## Recommended scale + +Use a consistent numeric scale within each file. The current default is **0-10**, where higher means "more worth documenting." + +| Score | Meaning | Typical surface | +| ------ | ----------------------------------------------------- | ------------------------------- | +| `9-10` | Marquee feature with broad appeal or major user value | Blog, docs, and release notes | +| `7-8` | Strong externally visible improvement | Release notes and possibly docs | +| `4-6` | Moderate or niche feature | Optional release notes mention | +| `1-3` | Minor or low-signal change | Usually skip | +| `0` | Noise: infra, tests, churn, refactor-only | Never document | + +## What increases a score + +- **User-visible change** — new public API, CLI switch, behavior change, workflow improvement +- **Breadth of impact** — helps many developers, not just a narrow edge case +- **Clarity** — easy to explain with a short WHY+HOW description +- **Evidence** — supported by API verification, tests, benchmarks, or a clear PR description +- **Documentation need** — developers may need guidance, migration notes, or examples + +## What lowers a score + +- Test-only or infra-only work +- Mechanical refactors with no user-facing impact +- Churn in dependencies or tooling internals +- Reverts, partial implementations, or APIs that do not appear in the actual build +- Changes too small or obscure to justify external attention + +## Good scoring behavior + +- Score based on the **shipped outcome**, not the effort involved +- Re-check scores after `api-diff` / `dotnet-inspect` confirms or disproves the public API story +- Prefer a short `score_reason` grounded in evidence over long prose +- It is okay for many entries to have low scores; `changes.json` is comprehensive, `features.json` is selective diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index 4cd10c017c..07df8e7ce5 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -101,13 +101,15 @@ You maintain release notes for .NET preview, RC, and GA releases in this reposit Your outputs are pull requests — one per active milestone — each containing: 1. **`changes.json`** — a comprehensive manifest of all PRs/commits that shipped, generated by `dotnet-release generate changes` -2. **Markdown release notes** — curated editorial content covering high-value features +2. **`features.json`** — a scored derivative of `changes.json` used to rank what is worth documenting +3. **Markdown release notes** — curated editorial content covering high-value features ## Your principles - **High fidelity** — only document what actually ships. The VMR (`dotnet/dotnet`) and its `src/source-manifest.json` are the source of truth. Trust `dotnet-release generate changes` output. - **High value** — bias toward features users care about. Skip infra, test-only, and internal refactoring. - **Never document non-shipping features** — if it's not in `changes.json`, it didn't ship. +- **Use scoring as guidance, not law** — `features.json` helps prioritize, but humans and editorial judgment still decide what makes the cut. - **Respect human edits** — this is a shared workspace. Humans edit branch content directly. Diff before writing and preserve everything they've touched. When in doubt, ask via PR comment. - **Engage with comments** — read PR comments and review threads. Some are actionable, some need discussion. Respond and iterate. - **Incremental improvement** — early drafts are rough. Each nightly run improves them. @@ -118,7 +120,8 @@ Read these files from `.github/skills/release-notes/references/` for detailed gu - **quality-bar.md** — what good release notes look like - **vmr-structure.md** — how the VMR works, branch naming, source-manifest.json -- **changes-schema.md** — the changes.json schema +- **changes-schema.md** — the shared `changes.json` / `features.json` schema +- **feature-scoring.md** — how to score and cut candidate features - **component-mapping.md** — VMR paths → components → product slugs → output files - **format-template.md** — markdown document structure - **editorial-rules.md** — tone, attribution, naming conventions @@ -199,7 +202,16 @@ dotnet-release generate changes /tmp/dotnet \ --output release-notes/11.0/preview/preview4/changes.json ``` -#### b. Check for human edits on the branch +#### b. Generate or refresh features.json + +Use `changes.json` as the source of truth and write a sibling `features.json` that preserves the same schema while adding optional scoring metadata: + +- keep the same `id` and `commits{}` values +- assign higher scores to externally meaningful, user-visible changes +- down-rank infra, churn, test-only work, and anything that appears reverted +- preserve any useful human annotations if the file already exists + +#### c. Check for human edits on the branch If a PR branch already exists: @@ -210,16 +222,16 @@ git log --oneline --author!=github-actions origin/release-notes/11.0-preview4 Identify which markdown files humans have edited. For those files, diff them to understand what changed. Do NOT overwrite human-edited sections. Only add new sections or update sections the agent previously wrote that no human has touched. -#### c. Write or update markdown +#### d. Write or update markdown -Using `changes.json` and the reference documents: +Using `features.json`, `changes.json`, and the reference documents: - Route changes to output files via `product` field and component-mapping.md - For each component: identify which PRs are worth writing about - Write feature descriptions following `format-template.md` and `editorial-rules.md` - Components with no noteworthy changes get a minimal stub -#### d. Ask for what you can't generate +#### e. Ask for what you can't generate Some features need content that only humans can provide — benchmark data, definitive code samples, or domain-specific context. When you identify a feature that would benefit from this: @@ -229,7 +241,7 @@ Some features need content that only humans can provide — benchmark data, defi Frame these as suggestions, not demands. For example: "This JIT improvement in loop unrolling looks significant. Benchmark data showing the before/after would help tell the story — could you share numbers or point me to a benchmark?" -#### e. Read and respond to PR comments +#### f. Read and respond to PR comments Check all comments and review threads on the PR since the last run: From a7a8711c74dd9d6edd5d31fb5df83f5ab693aeb8 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Sun, 5 Apr 2026 22:41:57 -0700 Subject: [PATCH 56/65] Refine release notes scoring guidance Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/editorial-scoring/SKILL.md | 105 ++++++++++++++++++ .github/skills/generate-features/SKILL.md | 45 +++++--- .github/skills/release-notes/DESIGN.md | 27 +++-- .github/skills/release-notes/SKILL.md | 10 +- .github/skills/release-notes/STATUS.md | 104 +++++++++++++++++ .../references/api-verification.md | 2 + .../references/changes-schema.md | 49 +++++--- .../references/editorial-rules.md | 15 +++ .../references/feature-scoring.md | 48 ++++++-- .../references/format-template.md | 5 + .../release-notes/references/quality-bar.md | 9 ++ .github/skills/review-release-notes/SKILL.md | 92 +++++++++++++++ .github/workflows/release-notes.md | 24 +++- 13 files changed, 476 insertions(+), 59 deletions(-) create mode 100644 .github/skills/editorial-scoring/SKILL.md create mode 100644 .github/skills/release-notes/STATUS.md create mode 100644 .github/skills/review-release-notes/SKILL.md diff --git a/.github/skills/editorial-scoring/SKILL.md b/.github/skills/editorial-scoring/SKILL.md new file mode 100644 index 0000000000..487973473c --- /dev/null +++ b/.github/skills/editorial-scoring/SKILL.md @@ -0,0 +1,105 @@ +--- +name: editorial-scoring +description: > + Apply the shared reader-centric rubric used to rank candidate features for + release notes, blog posts, and docs. Use this when you need scoring and cut + guidance independent of any one output format or task. +--- + +# Editorial Scoring + +Use this skill whenever you need to answer **"How important is this to a reader?"** + +This skill is intentionally **rubric-focused, not task-focused**. It does not generate files on its own. Instead, it provides the shared editorial calibration that other skills should reuse: + +- `generate-features` — assigns the first-pass scores +- `review-release-notes` — audits and recalibrates those scores +- `release-notes` — uses the resulting cut to decide what gets written up +- future blog/docs workflows — can reuse the same rubric with different thresholds + +## Core question + +Score from the perspective of a developer upgrading to the new release — not from the perspective of the engineer who implemented the feature. + +## Reader-centric scale + +| Score | Reader reaction | What to do with it | +| ----- | -------------------------------------------------------------------------------- | ---------------------------------------------------------- | +| `10` | "This is the first feature I'll enable or test." | Lead story | +| `8+` | "I'm going to use this when I upgrade." | Strong release-note feature | +| `6+` | "I'm glad I know about this. It will likely come in handy." | Good grouped release-note material | +| `4+` | "I can see how someone could use this. I'll look up the docs if I ever need it." | Optional mention, grouping candidate, or short paragraph | +| `2+` | "This one is a mystery to me." | Usually skip unless stronger explanation changes the score | +| `0` | "This is total gobbledygook — internal .NET engineering work." | Cut it from public-facing content | + +## 80/20 audience filter + +Default to features that make sense to roughly **80% of the audience**. + +Keep a more specialized feature for the other **20%** only when the remaining 80% can still understand why it matters and react with something like: + +> "Not for me, but I'm glad that's there for the people who need it." + +This is how foundational but initially niche work can still earn a good score. + +## Strong positive signals + +- New public APIs with obvious user value +- CLI or workflow improvements developers will notice right away +- Features that are easy to explain by anchoring them to a familiar tool or workflow +- Clear performance, reliability, or usability wins +- Features people have been asking for +- Changes that require docs, migration notes, or examples to use well + +## Strong negative signals + +- Test-only work +- Build or infrastructure churn +- Refactoring with no user-visible effect +- VMR sync noise, dependency updates, or repo automation +- Highly specialized implementation details that read like internal jargon +- Features that only matter after several stacked conditions are true (for example: uses single-file publish **and** cares deeply about startup **and** is willing to do extra training/tuning) +- PRs with very thin descriptions where the only concrete signal is an internal runtime/tooling term like cDAC +- Existing niche surfaces that are barely documented and are not part of the model's normal customer-facing understanding +- Claimed "new API" features where the actual new public API cannot be identified +- Bug fixes for features that were never really announced or are still obscure to most readers +- Old, low-engagement bugs filed internally that sat for a long time without clear external demand + +## Common scoring mistakes + +- **Technical novelty bias** — "this is clever" is not the same as "users will care" +- **API inventory mode** — listing everything from an API diff is not release-note curation +- **Effort bias** — a difficult implementation may still score low if the reader value is small +- **Insider-language inflation** — terms like JIT, LSRA, cDAC, intrinsics, or pipeline details can sound impressive but still be a `0-4` unless the value is explained clearly +- **Audience multiplication blindness** — when a feature requires several independent interests or behaviors, multiply those filters together instead of scoring the broadest prerequisite alone +- **Description vacuum optimism** — if the PR does not explain the user scenario, do not fill in the blanks with a generous story +- **Documentation blindness** — if an "existing" feature is not findable in Learn docs and does not show up as a known customer scenario, that is a signal to score it around `1`, not to assume hidden importance +- **Phantom API stories** — if you cannot identify the concrete new public API, do not give API credit based on a vague title alone +- **Promotion by association** — a fix in a glamorous subsystem is still just a `1` if it only patches an unannounced or niche feature +- **Demand inversion** — do not mistake an old internal bug with no reactions for evidence that many users need to hear about the fix +- **Fragmentation bias** — do not dismiss each small related item in isolation when several `2-4` items together form one intelligible reader story, such as a cluster of "Unsafe evolution" changes + +## Working thresholds + +- **8+** — worthy of its own section +- **6-7** — usually grouped with similar items, sometimes a short standalone section +- **0-5** — bug-fix bucket, one-liner, or cut + +Several `3-5` items in the same area can still combine into **one** worthwhile writeup. Keep the individual scores honest, then let the writing stage roll them up into a single themed section. + +## Breaking changes are separate from score + +Use score for **reader interest/value**, and use `breaking_changes: true` for **reaction/migration significance**. + +That means: + +- a **low-score breaking change** often still deserves a short end-of-notes callout +- a **high-score breaking change** may deserve both a full feature writeup and a migration note + +Do **not** inflate a narrow breaking change to `7+` just to keep it visible. + +## Canonical references + +- [`../release-notes/references/feature-scoring.md`](../release-notes/references/feature-scoring.md) — detailed scoring guidance +- [`../release-notes/references/quality-bar.md`](../release-notes/references/quality-bar.md) — what good release notes look like +- [`../release-notes/references/examples/README.md`](../release-notes/references/examples/README.md) — editorial examples and why they work diff --git a/.github/skills/generate-features/SKILL.md b/.github/skills/generate-features/SKILL.md index 23541b1b20..d11bfe8c50 100644 --- a/.github/skills/generate-features/SKILL.md +++ b/.github/skills/generate-features/SKILL.md @@ -4,8 +4,9 @@ description: > Generate `features.json` from `changes.json` by ranking and annotating shipped changes. `features.json` keeps the same schema as `changes.json` and adds optional scoring fields so release notes, docs, and blog posts can apply - different cutoffs. DO NOT USE FOR: regenerating VMR diffs (use - generate-changes) or writing final markdown (use release-notes). + different cutoffs. Uses the shared `editorial-scoring` rubric. DO NOT USE + FOR: regenerating VMR diffs (use generate-changes) or writing final markdown + (use release-notes). --- # Generate `features.json` @@ -14,6 +15,8 @@ Create a scored, reusable feature list from `changes.json`. This is the **triage stage** of the release notes pipeline. It turns the comprehensive manifest of shipped changes into a ranked set of candidates for external communication. +Use [`editorial-scoring`](../editorial-scoring/SKILL.md) as the canonical rubric. Do **not** invent a different notion of importance for this step. + ## Purpose `changes.json` answers **what shipped**. @@ -30,27 +33,31 @@ This is the **triage stage** of the release notes pipeline. It turns the compreh The only additions are **optional enrichment fields** on change entries, such as: -| Field | Type | Purpose | -| ----------------- | ------ | ----------------------------------------- | -| `score` | number | Higher means more likely to be documented | -| `score_reason` | string | Short explanation of the score | -| `score_breakdown` | object | Optional per-dimension scoring details | +- `score` (`number`) — higher means more likely to be documented +- `score_reason` (`string`) — short explanation of the score +- `score_breakdown` (`object`) — optional per-dimension scoring details +- `breaking_changes` (`bool`) — mark changes that users may need to react to, even when they are not headline items This schema is intentionally loose and can grow as the workflow learns what it needs. +`breaking_changes` is **separate from score**. A change might only be a `3` or `4` on reader interest, but still need to be carried forward as a short migration note because it can affect people upgrading. + ## Default scoring guidance Use a consistent numeric scale within a file. The recommended starting point is **0-10**: -| Score | Meaning | Typical outcome | -| ------ | --------------------------------------------- | -------------------------------------------------- | -| `9-10` | Marquee, broadly useful, clearly user-visible | Strong candidate for blog, docs, and release notes | -| `7-8` | Strong release-note feature | Usually document in release notes; maybe docs | -| `4-6` | Moderate or niche value | Mention if space and audience justify it | -| `1-3` | Low-signal or narrowly scoped | Usually skip in public-facing summaries | -| `0` | Infra, tests, refactoring, or pure churn | Do not document | +| Score | Reader reaction | Typical outcome | +| ----- | ------------------------------------------------ | -------------------------------------- | +| `10` | "This is the first feature I'll enable or test." | Lead story | +| `8+` | "I'm going to use this when I upgrade." | Strong release-note feature | +| `6+` | "I'm glad I know about this." | Good grouped release-note material | +| `4+` | "Someone will care; I can look it up later." | Optional mention or grouping candidate | +| `2+` | "This is a mystery to me." | Usually skip | +| `0` | Internal gobbledygook | Never document | + +See [`editorial-scoring`](../editorial-scoring/SKILL.md) for the shared rubric and [feature-scoring.md](../release-notes/references/feature-scoring.md) for the detailed heuristics. -See [feature-scoring.md](../release-notes/references/feature-scoring.md) for the heuristics. +Apply the **80/20 rule** from that document: prefer features that make sense to most upgraders, and only keep niche items when the broader audience can still appreciate why they matter. ## Process @@ -67,6 +74,11 @@ Look for signals such as: - performance work with clear user impact - compatibility or migration significance - changes that need docs or samples to be usable +- features whose value is obvious to a broad slice of users + +If a change is important mainly because users need to adjust to it, set `breaking_changes: true` even if the score stays low. Do **not** inflate the score just to keep the item visible. + +If several individually modest changes cluster around one theme, keep that cluster in mind as you score. Do **not** inflate each entry, but do preserve the related items so downstream writing can roll them up into one coherent feature writeup. Title prefixes and labels are often useful clues here, such as a set of `[browser]` runtime changes or multiple "Unsafe evolution" items. Down-rank or exclude: @@ -74,6 +86,7 @@ Down-rank or exclude: - test-only changes - internal refactors with no user-facing impact - reverts and partial work that did not survive into the build +- items that mostly require insider knowledge to understand why they matter ### 3. Use API evidence to refine the score @@ -97,5 +110,7 @@ Keep the file mechanically friendly: ## How downstream skills use it - **`release-notes`** uses higher-scored entries to draft markdown +- **`release-notes`** can also surface low-scored entries with `breaking_changes: true` as one-line callouts in a breaking-changes section +- **`review-release-notes`** re-checks the scores against editorial examples and trims over-scored items - **Docs/blog workflows** can apply their own cutoffs later - Humans can adjust scoring without changing the source-of-truth shipped data diff --git a/.github/skills/release-notes/DESIGN.md b/.github/skills/release-notes/DESIGN.md index a8c3f68351..8d75219500 100644 --- a/.github/skills/release-notes/DESIGN.md +++ b/.github/skills/release-notes/DESIGN.md @@ -26,9 +26,11 @@ The system still has three layers, but the AI/editorial layer is now split into ├──────────────────────────────────────────────────────────────┤ │ Reusable skills (editorial pipeline) │ │ generate-changes → changes.json │ +│ editorial-scoring → shared rubric │ │ generate-features → features.json │ │ api-diff / dotnet-inspect → API evidence │ │ release-notes → markdown │ +│ review-release-notes → trim / re-score │ ├──────────────────────────────────────────────────────────────┤ │ Tooling (deterministic where possible) │ │ dotnet-release, dotnet-inspect, GitHub APIs, local git │ @@ -38,9 +40,11 @@ The system still has three layers, but the AI/editorial layer is now split into ### Why split the stages? - **`generate-changes`** handles mechanical data collection. It's deterministic — same inputs always produce the same output. +- **`editorial-scoring`** centralizes the rubric so the generator and reviewers use the same definition of importance. - **`generate-features`** adds reusable triage and scoring so release notes, docs, and blogs can share the same ranked feature list. - **`api-diff`** verifies the public API story against the actual build, which is the best defense against missed reverts. - **`release-notes`** focuses on editorial judgment: which scored items matter, how to describe them, and how to preserve human edits. +- **`review-release-notes`** applies a harsher, reader-centric cutoff so the final draft does not drift into API-inventory territory. - **The workflow** handles orchestration. It knows when to run, what branches to manage, how to interact with humans, and how to preserve their edits. ## Layer 1 — The Tool @@ -98,25 +102,28 @@ Example (Preview 1 → Preview 2): the tool found 1,389 PRs across 21 changed re The editorial pipeline is intentionally split into small, reusable skills: -| Document | Purpose | -| ------------------------------------------------------- | --------------------------------------------------------------- | -| [quality-bar.md](references/quality-bar.md) | North star — fidelity, value, WHY+HOW | -| [vmr-structure.md](references/vmr-structure.md) | VMR branches, tags, source-manifest.json | -| [changes-schema.md](references/changes-schema.md) | The shared `changes.json` / `features.json` schema | -| [feature-scoring.md](references/feature-scoring.md) | How to rank features for release notes, docs, and blogs | -| [component-mapping.md](references/component-mapping.md) | Components → product slugs → output files | -| [format-template.md](references/format-template.md) | Markdown document structure | -| [editorial-rules.md](references/editorial-rules.md) | Tone, attribution, naming | -| [examples/](references/examples/README.md) | Curated examples by component — short, medium, long-form styles | +| Document | Purpose | +| -------------------------------------------------------------- | --------------------------------------------------------------- | +| [quality-bar.md](references/quality-bar.md) | North star — fidelity, value, WHY+HOW | +| [vmr-structure.md](references/vmr-structure.md) | VMR branches, tags, source-manifest.json | +| [changes-schema.md](references/changes-schema.md) | The shared `changes.json` / `features.json` schema | +| [../editorial-scoring/SKILL.md](../editorial-scoring/SKILL.md) | Shared reader-centric scoring rubric | +| [feature-scoring.md](references/feature-scoring.md) | How to rank features for release notes, docs, and blogs | +| [component-mapping.md](references/component-mapping.md) | Components → product slugs → output files | +| [format-template.md](references/format-template.md) | Markdown document structure | +| [editorial-rules.md](references/editorial-rules.md) | Tone, attribution, naming | +| [examples/](references/examples/README.md) | Curated examples by component — short, medium, long-form styles | These are **goal-oriented**, not procedural. They describe what good release notes and feature selection look like, not the exact steps to produce them. The skills figure out the HOW. ### Skill responsibilities - **`generate-changes`** — determine the correct VMR base/head refs, including preview-only multi-branch targeting, and emit `changes.json` +- **`editorial-scoring`** — define the shared reader-centric scoring rubric and cut thresholds - **`generate-features`** — read `changes.json`, score likely features, and emit `features.json` using the same schema plus optional scoring - **`api-diff`** — verify APIs against the actual build binaries/ref packs and generate diffs when needed - **`release-notes`** — produce markdown release notes for high-value features, following the quality bar and only using verified API names and code samples +- **`review-release-notes`** — audit the scored output against the editorial examples, demoting over-scored items and promoting missing ones - **Workflow-facing behavior** — respect human edits, diff the PR branch, and incorporate PR comments and feedback ## Layer 3 — The Agentic Workflow diff --git a/.github/skills/release-notes/SKILL.md b/.github/skills/release-notes/SKILL.md index 3aabe40d7c..a563abf481 100644 --- a/.github/skills/release-notes/SKILL.md +++ b/.github/skills/release-notes/SKILL.md @@ -1,7 +1,7 @@ --- name: release-notes -description: Generate and maintain .NET release notes from `features.json`. Uses `generate-changes` for authoritative shipped-change data, `generate-features` for scoring/triage, and `api-diff`/`dotnet-inspect` for API verification before writing curated markdown for the final notes. -compatibility: Requires GitHub MCP server or gh CLI for cross-repo queries. Pairs with the generate-changes, generate-features, and api-diff skills. +description: Generate and maintain .NET release notes from `features.json`. Uses `generate-changes` for authoritative shipped-change data, `generate-features` for scoring/triage, `editorial-scoring` for the shared rubric, `api-diff`/`dotnet-inspect` for API verification, and `review-release-notes` for a final editorial QA pass. +compatibility: Requires GitHub MCP server or gh CLI for cross-repo queries. Pairs with the generate-changes, generate-features, editorial-scoring, api-diff, and review-release-notes skills. --- # .NET Release Notes @@ -13,10 +13,11 @@ This skill is the **editorial writing stage** of the pipeline. It turns a scored ## How it works 1. `generate-changes` diffs `source-manifest.json` between VMR refs to produce `changes.json` -2. `generate-features` reads `changes.json` and emits `features.json` with optional scores +2. `generate-features` reads `changes.json` and emits `features.json` with optional scores using the shared `editorial-scoring` rubric 3. `api-diff` / `dotnet-inspect` verifies public APIs and catches missed reverts 4. `release-notes` writes curated markdown using the higher-value entries from `features.json` -5. Output is one PR per release milestone in dotnet/core, maintained incrementally +5. `review-release-notes` checks the selection against the scoring rubric and editorial examples +6. Output is one PR per release milestone in dotnet/core, maintained incrementally ## Design @@ -27,6 +28,7 @@ This skill is the **editorial writing stage** of the pipeline. It turns a scored - [quality-bar.md](references/quality-bar.md) — what good release notes look like - [vmr-structure.md](references/vmr-structure.md) — VMR branches, tags, source-manifest.json - [changes-schema.md](references/changes-schema.md) — the shared `changes.json` / `features.json` schema +- [../editorial-scoring/SKILL.md](../editorial-scoring/SKILL.md) — the reusable scoring rubric and cut guidance - [feature-scoring.md](references/feature-scoring.md) — how to score and cut features - [component-mapping.md](references/component-mapping.md) — components, product slugs, output files - [format-template.md](references/format-template.md) — markdown document structure diff --git a/.github/skills/release-notes/STATUS.md b/.github/skills/release-notes/STATUS.md new file mode 100644 index 0000000000..516d26fb7a --- /dev/null +++ b/.github/skills/release-notes/STATUS.md @@ -0,0 +1,104 @@ +# Release Notes Skill Status + +Current state of the release-notes skill split, scoring rubric, and Preview 2/3 evaluation work as of 2026-04-06. + +## Branch + +- Working branch: `release-notes-agentic-workflow` + +## Pipeline shape now + +The monolithic release-notes flow has been split into reusable stages: + +1. `generate-changes` — produce authoritative `changes.json` from VMR refs +2. `api-diff` / `dotnet-inspect` — verify public API stories and catch reverts +3. `generate-features` — produce scored `features.json` +4. `editorial-scoring` — shared rubric used by generator and reviewer +5. `review-release-notes` — critique the scored shortlist and the draft notes +6. `release-notes` — write the final markdown + +## Schema decisions + +`features.json` stays schema-compatible with `changes.json`, with optional enrichment fields: + +- `score` +- `score_reason` +- `score_breakdown` +- `breaking_changes` + +`breaking_changes` is separate from `score`: + +- a high score means broad reader value +- `breaking_changes: true` means some users may need to react, even if the item is narrow +- low-score + `breaking_changes: true` should usually become a short end-of-notes callout, not a headline feature section + +## Scoring and editorial calibration added + +The shared rubric now encodes these rules: + +- use the reader-centric `10 / 8 / 6 / 4 / 2 / 0` scale with the **80/20** audience filter +- compare new functionality to familiar tools when that helps readers place it quickly + - example: `dotnet run -e FOO=BAR` can be framed as Docker-style env-var injection +- score down features with **stacked audience gates** + - if a user must care about A, then B, then be willing to do C, the audience shrinks at each step +- score down thinly described items with internal-runtime cues like **cDAC** +- if the story depends on a **new public API**, we should be able to identify that API via API diff / `dotnet-inspect` +- if an "existing" feature is not part of the model's normal customer-facing understanding and is not easy to find in Learn docs, default to about a `1` +- bug fixes for obscure, unannounced, or low-demand features should usually stay around `1` +- old, internally filed, low-engagement bugs are evidence of low urgency, not hidden headline value +- several related low-score items can still justify **one grouped writeup** + - examples: Roslyn **Unsafe evolution** and runtime **`[browser]`** improvements + +## Preview 2 comparison result + +A fresh Preview 2 run was generated from VMR tags and compared to the human-written notes. + +Result: + +- roughly **B+** +- strong overlap on the main human-selected items +- useful gaps identified: + - some human-covered areas were underplayed or missed + - MAUI and containers sit outside the VMR-driven source path and need separate sourcing +- useful possible omissions surfaced as well, including some Roslyn, NuGet, and MSBuild items + +## Preview 3 rerun result + +Fresh rerun worktree: + +- `/tmp/core-preview3-rerun-79OjKk/release-notes/11.0/preview/preview3/features.json` + +Rerun summary: + +- `2,275` changes scored +- `5` scored `7+` +- `21` scored `5+` +- `17` flagged `breaking_changes` + +Highest-value items in the rerun included: + +- `dotnet run -e` environment-variable injection +- runtime-async dropping `RequiresPreviewFeatures` +- System.Text.Json naming/ignore additions +- file-based app `#:include` +- OpenAPI 3.2 +- `RegexOptions.AnyNewLine` + +Clusters preserved in the rerun: + +- runtime-async +- System.Text.Json +- Roslyn Unsafe evolution +- browser / WebCIL / WASM runtime improvements + +Notable low-score `breaking_changes` examples: + +- EF Core `JsonExists` -> `JsonPathExists` +- EF Core migrations behavior changes +- BackgroundService exception propagation +- Zstandard API move +- certificate-download default changes + +## Recommended next step + +Review the rerun `features.json` above with `review-release-notes`, then decide whether the current rubric is strong enough to generate markdown directly or needs one more tightening pass. diff --git a/.github/skills/release-notes/references/api-verification.md b/.github/skills/release-notes/references/api-verification.md index 85f331757c..716e4f8b26 100644 --- a/.github/skills/release-notes/references/api-verification.md +++ b/.github/skills/release-notes/references/api-verification.md @@ -127,3 +127,5 @@ If `dotnet-inspect` can't find a type: 5. **Read the PR tests** — the PR's test files are ground truth. Tests compile and run against the actual API surface. Derive code samples from test assertions rather than guessing type names. When in doubt, describe the feature without naming specific types and link to the PR. A correct prose description is always better than a wrong code sample. + +For scoring and feature selection, this is also a **quality bar**: if a change only looks interesting because it seems to add a new API, but you cannot identify that API in the public surface, score it down sharply. That usually means it is internal plumbing, a refactor, or an existing niche surface getting maintenance rather than a real release-note feature. diff --git a/.github/skills/release-notes/references/changes-schema.md b/.github/skills/release-notes/references/changes-schema.md index 89cfff2b64..81ee8c172e 100644 --- a/.github/skills/release-notes/references/changes-schema.md +++ b/.github/skills/release-notes/references/changes-schema.md @@ -28,20 +28,19 @@ release-notes/{major.minor}/{major.minor.patch}/features.json # patches ## Change entry fields -| Field | Type | Description | -| ------------------- | ------ | ---------------------------------------------------------------------------------- | -| `id` | string | Globally unique identifier — `repo@shortcommit` format (e.g., `"runtime@c5d5be4"`) | -| `repo` | string | Short repository name (e.g., `"runtime"`) | -| `product` | string | Product slug (e.g., `"dotnet-runtime"`); absent for infra repos | -| `title` | string | PR title; `""` if not available | -| `url` | string | Public GitHub PR URL; `""` if non-public | -| `commit` | string | Key into top-level `commits{}` dict — the VMR (`dotnet/dotnet`) codeflow commit | -| `is_security` | bool | `true` if this is a security change | -| `local_repo_commit` | string | Key into `commits{}` — the source repo commit (same as `id`) | -| `labels` | array | PR labels (only present when `--labels` is used) | -| `score` | number | Optional editorial score; higher means more likely to document | -| `score_reason` | string | Optional short explanation for the score | -| `score_breakdown` | object | Optional structured scoring details | +- `id` (`string`) — globally unique identifier in `repo@shortcommit` format, for example `"runtime@c5d5be4"` +- `repo` (`string`) — short repository name, for example `"runtime"` +- `product` (`string`) — product slug, for example `"dotnet-runtime"`; absent for infra repos +- `title` (`string`) — PR title; `""` if not available +- `url` (`string`) — public GitHub PR URL; `""` if non-public +- `commit` (`string`) — key into top-level `commits{}` for the VMR (`dotnet/dotnet`) codeflow commit +- `is_security` (`bool`) — `true` if this is a security change +- `local_repo_commit` (`string`) — key into `commits{}` for the source repo commit (same as `id`) +- `labels` (`array`) — PR labels, only present when `--labels` is used +- `score` (`number`) — optional editorial score; higher means more likely to document +- `score_reason` (`string`) — optional short explanation for the score +- `score_breakdown` (`object`) — optional structured scoring details +- `breaking_changes` (`bool`) — optional flag for changes users may need to react to, even if they are not headline features The `product` field is derived from the repo-level [component mapping](component-mapping.md). Infra repos like `arcade` and `symreader` have no `product` field. The `repo` field always matches the VMR manifest path. @@ -59,11 +58,25 @@ The `commit` field is the VMR codeflow commit in `dotnet/dotnet` that synced thi ## Conventions -- **No nulls** — required fields are always present. Optional fields (`product`, `labels`, `local_repo_commit`, `score`, `score_reason`, `score_breakdown`) may be absent. Use `""` for missing strings, `0` for missing integers. +- **No nulls** — required fields are always present. Optional fields (`product`, `labels`, `local_repo_commit`, `score`, `score_reason`, `score_breakdown`, `breaking_changes`) may be absent. Use `""` for missing strings, `0` for missing integers. - **Naming** — `snake_case_lower` for JSON fields, `kebab-case-lower` for file names and repo slugs. - **Public URLs only** — every URL must resolve publicly. - **Commit URLs use `.diff` form** — for machine consumption. +## Breaking changes are a separate axis + +`breaking_changes` is **not the same thing** as `score`. + +- `score` answers: **"How broadly interesting or valuable is this to readers?"** +- `breaking_changes` answers: **"Do some users need to react to this?"** + +That means a change can be: + +- **high score + `breaking_changes: true`** — a widely relevant feature that also needs migration guidance +- **low score + `breaking_changes: true`** — a niche or narrow change that still deserves a short callout near the end of the release notes + +In practice, a `score` around `0-4` with `breaking_changes: true` usually means **one line in a "Breaking changes" section**, not a full feature writeup. + ## Example ```json @@ -91,7 +104,10 @@ The `commit` field is the VMR codeflow commit in `dotnet/dotnet` that synced thi "url": "https://github.com/dotnet/aspnetcore/pull/54321", "commit": "dotnet@a1b2c3d", "is_security": false, - "local_repo_commit": "aspnetcore@f45f3c9" + "local_repo_commit": "aspnetcore@f45f3c9", + "score": 4, + "score_reason": "Narrower impact but users upgrading affected apps need to be aware of it.", + "breaking_changes": true } ], "commits": { @@ -151,6 +167,7 @@ jq -r '.changes[] | select(.is_security) | .local_repo_commit' changes.json - `changes.json` has an entry for every PR that shipped - `features.json` usually preserves those entries and adds optional scoring metadata +- `features.json` can also flag entries with `breaking_changes: true` so downstream writing can keep a short migration note even when the score is low - Both files can be joined through shared `id` and `commits{}` values ## Relationship to markdown release notes diff --git a/.github/skills/release-notes/references/editorial-rules.md b/.github/skills/release-notes/references/editorial-rules.md index 186ec7d528..7f9bc398e6 100644 --- a/.github/skills/release-notes/references/editorial-rules.md +++ b/.github/skills/release-notes/references/editorial-rules.md @@ -16,6 +16,15 @@ Tone, attribution, and content guidelines for .NET release notes. - ❌ `The runtime-async feature, which eliminates the state-machine overhead of async/await, is now enabled for NativeAOT.` - ✅ `The JIT now generates ARM64 SM4 and SHA3 instructions directly, enabling hardware-accelerated implementations on capable processors.` (long but flows — one thought) +## Familiar comparisons + +- When a feature matches a workflow developers already know from another tool, say so. A short comparison can make the value obvious faster than a longer explanation. +- Use the comparison to **anchor the mental model**, not to claim perfect parity. Say what is similar, then explain the .NET-specific behavior. +- Prefer common tools and workflows the reader is likely to know already (for example Docker, GitHub Actions, shell usage, or package managers). Skip the comparison if it feels forced or more obscure than the feature itself. + + - ✅ `dotnet run -e FOO=BAR` gives you Docker-style CLI environment-variable injection for local app runs, so you can test configuration changes without editing shell state or launch profiles. + - ❌ `dotnet run` now works just like Docker. (overstates the similarity) + ## Entry naming - Prefer a **brief description** of what the feature does over the API name alone @@ -38,6 +47,8 @@ Tone, attribution, and content guidelines for .NET release notes. - **The two-sentence test** — if you can only write two sentences about a feature, it's probably an engineering fix, not a feature. Cut it. A community contribution or breaking change can lift a borderline entry, but "fixed an internal bug that happened to be visible" is not a feature. - **Headlines should convey value** — a heading like "GC regions on macOS" doesn't tell the reader whether this is good or bad. Prefer headings that hint at the benefit: "GC regions enabled on macOS" or "Server GC memory model now available on macOS." - **TODO for borderline entries** — when a feature might deserve inclusion but you lack data to justify it (benchmark numbers, real-world impact, user demand), keep the entry but add an HTML `` comment asking for the missing information. This is better than silently including a vague claim or silently cutting something that might matter. The TODO should state what's needed and link to the PR where the data might live. +- **Breaking changes are separate from hype** — a breaking change can be important even when it is not exciting. Keep the score honest; use `breaking_changes: true` to preserve a short callout instead of inflating the item into a headline feature. +- **Clusters can be stronger than the parts** — several related low-score items can justify one section when together they tell a clear story. Keep the individual scores honest, then merge them into one writeup instead of emitting several weak mini-features. Good examples include a group of "Unsafe evolution" changes or multiple runtime entries prefixed with `[browser]`. ## Feature ordering @@ -98,6 +109,8 @@ Include a bug fix when ALL of these apply: Frame positively: "Based on community feedback, X now does Y." +If the fix is for a feature that was never really announced or is still obscure to most readers, do **not** turn that fix into a standalone feature entry. It usually belongs in the bug-fix bucket, and often scores around `1`. + ## Preview-to-preview deduplication When prior previews already documented a feature, don't repeat the same information. But **do** document significant state changes in the current preview. A feature that was introduced as opt-in in P1 and is now enabled by default in P3 is new news — document the change in state, not the feature from scratch. @@ -105,6 +118,7 @@ When prior previews already documented a feature, don't repeat the same informat Ask: "What changed about this feature since the last preview's release notes?" If the answer is meaningful to users (enabled by default, no longer experimental, major perf improvement, new sub-features), write about that. If the answer is just "more PRs landed in the same area," skip it. Examples: + - ✅ "Runtime-async is now enabled by default for anyone targeting `net11.0`." (state change: opt-in → default) - ✅ "The Regex source generator now handles alternations 3× faster." (new perf data) - ❌ Repeating the full explanation of what runtime-async is from P1 (already documented) @@ -124,6 +138,7 @@ Place the comment block immediately before the Bug fixes section: ``` Good filter reasons: + - **Internal infrastructure** — "Implementation detail of the Mono → CoreCLR unification. Developers don't target the interpreter." - **Too narrow** — "Only affects COM interop startup — very narrow audience." - **Engineering fix, not a feature** — "Two sentences max. No user-visible behavior change beyond a perf number." diff --git a/.github/skills/release-notes/references/feature-scoring.md b/.github/skills/release-notes/references/feature-scoring.md index 218e4ee23e..b1ba2f3cea 100644 --- a/.github/skills/release-notes/references/feature-scoring.md +++ b/.github/skills/release-notes/references/feature-scoring.md @@ -4,23 +4,35 @@ Use optional `score` fields in `features.json` to rank which shipped changes are The score is a **decision aid**, not a hard rule. Editorial judgment still matters. -## Recommended scale +## Reader-centric scale Use a consistent numeric scale within each file. The current default is **0-10**, where higher means "more worth documenting." -| Score | Meaning | Typical surface | -| ------ | ----------------------------------------------------- | ------------------------------- | -| `9-10` | Marquee feature with broad appeal or major user value | Blog, docs, and release notes | -| `7-8` | Strong externally visible improvement | Release notes and possibly docs | -| `4-6` | Moderate or niche feature | Optional release notes mention | -| `1-3` | Minor or low-signal change | Usually skip | -| `0` | Noise: infra, tests, churn, refactor-only | Never document | +Score from the perspective of a reader upgrading to the new release: + +| Score | Reader reaction | Typical editorial outcome | +| ----- | -------------------------------------------------------------------------------- | -------------------------------------------------------- | +| `10` | "This is the first feature I'll enable or test." | Lead story; blog, docs, and release notes | +| `8+` | "I'm going to use this when I upgrade." | Strong release-note feature; maybe docs | +| `6+` | "I'm glad I know about this. It will likely come in handy." | Good release-note material, often grouped | +| `4+` | "I can see how someone could use this. I'll look up the docs if I ever need it." | Optional mention, grouping candidate, or short paragraph | +| `2+` | "This one is a mystery to me." | Usually skip unless it becomes clearer with explanation | +| `0` | "This is total gobbledygook — internal .NET engineering work." | Never document outside internal notes | + +## Audience filter: the 80/20 rule + +Default to features that make sense to roughly **80% of the user base**. + +A feature that mainly helps the other **20%** can still score well, but only if the remaining 80% can still recognize why it matters and think: **"Not for me, but I'm glad it's there for the people who need it."** + +Foundational shifts like `Span` can still deserve high scores even before broad adoption, but they need extra explanation so the significance is legible to non-specialists. ## What increases a score - **User-visible change** — new public API, CLI switch, behavior change, workflow improvement - **Breadth of impact** — helps many developers, not just a narrow edge case - **Clarity** — easy to explain with a short WHY+HOW description +- **Familiar anchor** — easier to understand because it maps to a tool or workflow readers already know - **Evidence** — supported by API verification, tests, benchmarks, or a clear PR description - **Documentation need** — developers may need guidance, migration notes, or examples @@ -31,10 +43,28 @@ Use a consistent numeric scale within each file. The current default is **0-10** - Churn in dependencies or tooling internals - Reverts, partial implementations, or APIs that do not appear in the actual build - Changes too small or obscure to justify external attention +- **Stacked audience gates** — if the reader has to care about A, then B, then be willing to do C, the addressable audience shrinks at each step and the score should usually drop hard +- **Sparse evidence + internal hints** — if the PR barely explains the scenario and drops terms like cDAC, diagnostics plumbing, or runtime internals without a strong end-user story, treat it as near-internal by default +- **Invisible existing surface** — if the change is framed as a simplification or extension of an existing feature, but that feature is not visible in common model knowledge and not easy to find in Learn docs, default to about a `1` unless stronger customer evidence exists +- **No identifiable public API** — if the only apparent story is "there is a new API here" but you cannot point to a concrete new public type/member via API diff or `dotnet-inspect`, default to a `1` unless there is a separately strong behavior or workflow story +- **Bug fix for an unannounced feature** — if the work mostly fixes or rounds out a feature that customers were never really told about, treat it as a bug-fix-level item, often around a `1` +- **Low-demand bug history** — if the backing issue was filed internally, has little or no outside engagement, and sat for a long time, that is a signal the item likely belongs near `1` ## Good scoring behavior -- Score based on the **shipped outcome**, not the effort involved +- Score based on the **reader value of the shipped outcome**, not the effort involved +- Ask whether a typical upgrader immediately understands why the feature matters +- Use a short comparison to a familiar tool or workflow when it makes the value legible faster (for example, comparing CLI env-var injection to Docker's `-e` pattern) +- Discount aggressively for **compound niche scenarios**. "Single-file publishers who deeply care about startup and are willing to do training/tuning work" is much smaller than "people who publish single-file apps." +- Aggressively score down items that mostly read like internal plumbing or jargon +- If the strongest concrete clue is an internal term like **cDAC**, assume the feature is for runtime/tooling internals unless there is clear evidence of mainstream developer value +- For an **existing** feature area, ask two quick questions: "Would the model already recognize this as a real customer scenario?" and "Can I find it in Learn docs?" If both answers are effectively no, treat the item as a `1` by default +- For an API-centric entry, name the actual public API. If you cannot identify the new public surface area, do not score it like a real API feature +- Do not promote a **bug fix for an obscure or previously unannounced feature** into a release-note feature just because the subsystem sounds interesting +- Treat **issue provenance and engagement** as demand signals. An old bug filed by an internal architect with zero reactions is evidence of low urgency, not hidden headline value +- Consider **cluster value**, not just single-entry value. Several related `2-4` items can earn one grouped writeup when together they tell a coherent story (for example, a set of "Unsafe evolution" changes or a cluster of `[browser]` runtime improvements) +- Keep niche items only when they still impress or educate the broader audience +- A community contribution can lift a borderline item slightly, but it does **not** turn a niche optimization into a headline feature by itself - Re-check scores after `api-diff` / `dotnet-inspect` confirms or disproves the public API story - Prefer a short `score_reason` grounded in evidence over long prose - It is okay for many entries to have low scores; `changes.json` is comprehensive, `features.json` is selective diff --git a/.github/skills/release-notes/references/format-template.md b/.github/skills/release-notes/references/format-template.md index 01fb9e1739..89bb2d897f 100644 --- a/.github/skills/release-notes/references/format-template.md +++ b/.github/skills/release-notes/references/format-template.md @@ -18,6 +18,10 @@ Standard document structure for .NET release notes markdown files. ([/#NNNNN](https://github.com///pull/NNNNN)). +## Breaking changes + +- Short migration note or heads-up for narrower changes that users may need to react to + ## Bug fixes - **Category** — Fix description @@ -83,6 +87,7 @@ Known component docs links: 2. **One paragraph of context** — what the feature does and why it matters, with PR/issue links 3. **Code sample** — show the feature in use 4. **Feature ordering** — highest customer impact first +5. **Breaking changes near the end** — low-score entries with `breaking_changes: true` usually belong in a short section before Bug fixes, not as full feature sections ## Issue and PR references diff --git a/.github/skills/release-notes/references/quality-bar.md b/.github/skills/release-notes/references/quality-bar.md index bbfeea1c0c..5c585ef3a9 100644 --- a/.github/skills/release-notes/references/quality-bar.md +++ b/.github/skills/release-notes/references/quality-bar.md @@ -35,6 +35,12 @@ Include a feature if it gives users something **new to try**, something that **w - High community demand (reaction counts on backing issues/PRs) - Behavior changes users need to be aware of - Preview-to-preview fixes for community-reported issues +- Features that most readers can immediately understand the value of + +Use the **80/20 rule**: + +- Default to features that make sense to about **80% of the user base** +- Keep a more specialized feature only when the other 80% will still think, "Not for me, but I'm glad this exists" ## What to exclude from markdown @@ -44,6 +50,7 @@ Include a feature if it gives users something **new to try**, something that **w - Backports from servicing branches - Features that are not independently useful yet (still need unshipped APIs) - Anything not confirmed by `changes.json` (i.e., not in the source-manifest diff) +- Anything that reads like unexplained engineering jargon to most readers ## The fidelity rule @@ -58,6 +65,8 @@ A release note that just names a feature is useless. Every entry must answer: 1. **Why does this matter?** — what problem does it solve, what scenario does it enable? 2. **How do I use it?** — a code sample showing the feature in action +When relevant, add a **familiar comparison** to help the reader place the feature quickly. If a new CLI workflow resembles something people already know from another tool, say that briefly. For example, `dotnet run -e FOO=BAR` can be framed as Docker-style environment-variable injection for local runs. Use comparisons to clarify, not to imply exact parity. + If you can't write a code sample for a feature, question whether it's user-facing enough for release notes. ## Verify before you write diff --git a/.github/skills/review-release-notes/SKILL.md b/.github/skills/review-release-notes/SKILL.md new file mode 100644 index 0000000000..e76a677e24 --- /dev/null +++ b/.github/skills/review-release-notes/SKILL.md @@ -0,0 +1,92 @@ +--- +name: review-release-notes +description: > + Review `features.json` scores and draft release notes against the editorial + examples and the shared `editorial-scoring` rubric. Use it to identify + over-scored, under-scored, or missing features before publishing release + notes. DO NOT USE FOR: generating `changes.json` or writing the first draft. +--- + +# Review Release Notes + +Audit a scored `features.json` file and its markdown draft from the perspective of the **reader**, not the implementer. + +This is the **editorial QA stage** of the pipeline. Its job is to make sure the release notes feel curated, legible, and exciting to the right audience — not like an API inventory. + +This skill **reuses the shared rubric** from [`editorial-scoring`](../editorial-scoring/SKILL.md). It critiques the scoring; it does not invent a separate scoring philosophy. + +## When to use + +- After `generate-features` produced `features.json` +- After `release-notes` drafted the markdown +- When the selection feels too broad, too niche, or too internally focused +- When you want to compare the current draft against the examples and recalibrate the cut + +## Inputs + +Review these, in order: + +1. `changes.json` — the source of truth for what shipped +2. `features.json` — the scored candidate list, if present +3. Draft markdown files (`libraries.md`, `runtime.md`, `sdk.md`, etc.) +4. Editorial examples in `references/examples/` +5. The scoring and quality bar references: + - `../editorial-scoring/SKILL.md` + - `references/feature-scoring.md` + - `references/quality-bar.md` + +If `features.json` does not exist yet, infer the current implicit scoring from: + +- which features were promoted to headings +- how much space each feature received +- what was grouped into bug-fix buckets or omitted + +## Core review questions + +### 1. Reader-value test + +For each selected feature, ask: + +- **10** — Is this the first thing a reader will want to try? +- **8+** — Will many readers likely use this when they upgrade? +- **6+** — Will readers be glad they learned about it now? +- **4+** — Is it understandable but niche enough that docs can carry the rest? +- **2+** — Will most readers find it mysterious? +- **0** — Is this internal gobbledygook that mainly matters to the engineers who built it? + +### 2. The 80/20 rule + +Default to features that make sense to about **80% of the audience**. + +Keep a more specialized feature only when the other 80% can still appreciate why it matters, even if they won't use it directly. + +### 3. Example check + +Compare the draft against the component examples: + +- Does the length match the importance? +- Are medium-value items grouped instead of getting their own heavyweight sections? +- Are performance stories backed by evidence? +- Does the draft teach, not just enumerate? + +## Common failure modes + +- **Over-scoring** — too many `4-6` items are promoted to top-level sections +- **Under-explaining** — a real `8+` feature is present but buried or not framed clearly +- **API inventory mode** — the draft starts mirroring `api-diff` instead of telling a user story +- **Technical novelty bias** — clever implementation details outrank practical user value + +## Output + +Return a concise review with: + +1. **Most correct scoring** — the features that are best prioritized +2. **Most wrong scoring** — over-scored and under-scored items +3. **Important omissions** — items from `changes.json` / `features.json` that should likely be promoted +4. **Cut list** — what to group, compress, demote, or drop first + +As a working rule of thumb: + +- **8+** — section-worthy +- **6-7** — grouped paragraph or short section +- **0-5** — bug-fix bucket, one-liner, or cut diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index 07df8e7ce5..ae5dfeddd8 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -110,14 +110,17 @@ Your outputs are pull requests — one per active milestone — each containing: - **High value** — bias toward features users care about. Skip infra, test-only, and internal refactoring. - **Never document non-shipping features** — if it's not in `changes.json`, it didn't ship. - **Use scoring as guidance, not law** — `features.json` helps prioritize, but humans and editorial judgment still decide what makes the cut. +- **Reader value first** — use the score from the reader's point of view: 10 = "first thing I'll try", 8 = "I'll use this on upgrade", 6 = "glad to know", 4 = "maybe later", 2 = "mystery", 0 = "internal gobbledygook". +- **Prefer the 80%** — default to features that make sense to most users. Keep 20%-audience features only when the other 80% can still appreciate why they matter. - **Respect human edits** — this is a shared workspace. Humans edit branch content directly. Diff before writing and preserve everything they've touched. When in doubt, ask via PR comment. - **Engage with comments** — read PR comments and review threads. Some are actionable, some need discussion. Respond and iterate. - **Incremental improvement** — early drafts are rough. Each nightly run improves them. ## Reference documents -Read these files from `.github/skills/release-notes/references/` for detailed guidance: +Read these files and skills for detailed guidance: +- **editorial-scoring** — shared reader-centric scoring rubric and 80/20 audience filter ([skill](../skills/editorial-scoring/SKILL.md)) - **quality-bar.md** — what good release notes look like - **vmr-structure.md** — how the VMR works, branch naming, source-manifest.json - **changes-schema.md** — the shared `changes.json` / `features.json` schema @@ -211,7 +214,18 @@ Use `changes.json` as the source of truth and write a sibling `features.json` th - down-rank infra, churn, test-only work, and anything that appears reverted - preserve any useful human annotations if the file already exists -#### c. Check for human edits on the branch +#### c. Re-check the cut with the reader rubric + +Before finalizing the candidate list, ask: + +- Is this one of the **first things a reader will want to try**? +- Will many readers **use it when they upgrade**? +- Will readers at least be **glad they learned about it now**? +- Or does it mainly read like **internal engineering jargon**? + +Default to features that make sense to roughly **80% of the audience**. Specialized features are still welcome, but only when the other 80% can still understand why they matter. + +#### d. Check for human edits on the branch If a PR branch already exists: @@ -222,7 +236,7 @@ git log --oneline --author!=github-actions origin/release-notes/11.0-preview4 Identify which markdown files humans have edited. For those files, diff them to understand what changed. Do NOT overwrite human-edited sections. Only add new sections or update sections the agent previously wrote that no human has touched. -#### d. Write or update markdown +#### e. Write or update markdown Using `features.json`, `changes.json`, and the reference documents: @@ -231,7 +245,7 @@ Using `features.json`, `changes.json`, and the reference documents: - Write feature descriptions following `format-template.md` and `editorial-rules.md` - Components with no noteworthy changes get a minimal stub -#### e. Ask for what you can't generate +#### f. Ask for what you can't generate Some features need content that only humans can provide — benchmark data, definitive code samples, or domain-specific context. When you identify a feature that would benefit from this: @@ -241,7 +255,7 @@ Some features need content that only humans can provide — benchmark data, defi Frame these as suggestions, not demands. For example: "This JIT improvement in loop unrolling looks significant. Benchmark data showing the before/after would help tell the story — could you share numbers or point me to a benchmark?" -#### f. Read and respond to PR comments +#### g. Read and respond to PR comments Check all comments and review threads on the PR since the last run: From 6c2dd1c0cd3246203a909a787d55f4f7c4e3565f Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Mon, 6 Apr 2026 13:20:09 -0700 Subject: [PATCH 57/65] Clarify release-notes editorial guidance Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../release-notes/references/editorial-rules.md | 12 +++++++++--- .../release-notes/references/examples/README.md | 3 ++- .../release-notes/references/format-template.md | 12 ++++++------ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/.github/skills/release-notes/references/editorial-rules.md b/.github/skills/release-notes/references/editorial-rules.md index 7f9bc398e6..50c8bdef0e 100644 --- a/.github/skills/release-notes/references/editorial-rules.md +++ b/.github/skills/release-notes/references/editorial-rules.md @@ -11,6 +11,12 @@ Tone, attribution, and content guidelines for .NET release notes. - **Don't editorialize beyond the facts** — state what concretely changed ("enabled by default", "no longer requires opt-in") rather than making claims you can't back up ("ready for production use", "signals maturity"). If a PR removes a preview attribute, say that. Don't interpret it as a promise. - ✅ `Runtime-async is now enabled by default for anyone targeting net11.0.` - ❌ `This signals that runtime-async is ready for production use.` +- **Prefer concrete deltas over inferred outcomes** — say what changed and, if useful, the direct consequence for a real scenario. Avoid claims about trust, confidence, convenience, or behavior unless the source explicitly supports them. + - ✅ `Regex recognizes all Unicode newline sequences.` + - ✅ `Tar extraction now rejects entries that would write outside the destination directory.` + - ✅ `Tar extraction now rejects path traversal entries, helping applications avoid overwriting files outside the target folder.` + - ❌ `Compression and archive handling are easier to trust.` + - ❌ `You can extract archives without worrying about attacks.` - **Prefer short, direct sentences** — If a sentence has a parenthetical clause (`which...`, `where...`, `that...`) longer than a few words, split it into two sentences. Lead with the news, follow with context. Long sentences are fine when they flow as a single continuous thought (what → why); they're not fine when a subordinate clause interrupts the main verb. - ✅ `Runtime-async is now enabled for NativeAOT. This eliminates the state-machine overhead of async/await for ahead-of-time compiled applications.` - ❌ `The runtime-async feature, which eliminates the state-machine overhead of async/await, is now enabled for NativeAOT.` @@ -86,15 +92,15 @@ Thank you contributors! ❤️ ## Bug fixes section -After features but before community contributors, include a grouped bug fix summary when there are noteworthy fixes: +After features but before community contributors, include a grouped bug fix summary when there are noteworthy fixes. When citing the source work, use linked `org/repo #number` references with a space before `#`: ```markdown ## Bug fixes - **System.Net.Http** - - Fixed authenticated proxy credential handling ([dotnet/runtime#123363](https://github.com/dotnet/runtime/issues/123363)) + - Fixed authenticated proxy credential handling ([dotnet/runtime #123363](https://github.com/dotnet/runtime/issues/123363)) - **System.Collections** - - Fixed integer overflow in ImmutableArray range validation ([dotnet/runtime#124042](https://github.com/dotnet/runtime/pull/124042)) + - Fixed integer overflow in ImmutableArray range validation ([dotnet/runtime #124042](https://github.com/dotnet/runtime/pull/124042)) ``` Group by namespace/area. Don't include test-only, CI, or infra fixes. diff --git a/.github/skills/release-notes/references/examples/README.md b/.github/skills/release-notes/references/examples/README.md index 84b3eb31bb..79b01bbd2c 100644 --- a/.github/skills/release-notes/references/examples/README.md +++ b/.github/skills/release-notes/references/examples/README.md @@ -22,5 +22,6 @@ Curated examples from previous .NET release notes, organized by component. Load 8. **Progressive benchmarks tell a story** — multiple benchmark tables showing incremental improvement are more compelling than a single number 9. **Ask for what you can't generate** — benchmark data, definitive samples, and domain context come from humans. A placeholder with a request is better than a fabrication 10. **Workarounds are welcome** — if a change might break someone, say so and give the escape hatch -11. **Link to PRs and issues** — use the `org/repo #number` format: `[dotnet/runtime #115977](https://github.com/dotnet/runtime/pull/115977)`. Links give readers provenance and let them dig deeper +11. **Link to PRs and issues** — use the `org/repo #number` format, with a space before `#`: `[dotnet/runtime #115977](https://github.com/dotnet/runtime/pull/115977)`. Links give readers provenance and let them dig deeper 12. **Avoid jargon and ambiguous words** — "snippet" in a programming context suggests code. Say what you mean plainly. If a word could be misread, pick a simpler one +13. **Concrete beats promotional** — describe the observable change, not the feeling it might create. "Regex recognizes all Unicode newline sequences" or "Tar extraction now rejects path traversal entries" beats "compression and archive handling are easier to trust." diff --git a/.github/skills/release-notes/references/format-template.md b/.github/skills/release-notes/references/format-template.md index 89bb2d897f..d1daccc836 100644 --- a/.github/skills/release-notes/references/format-template.md +++ b/.github/skills/release-notes/references/format-template.md @@ -16,7 +16,7 @@ Standard document structure for .NET release notes markdown files. ## Feature Name - ([/#NNNNN](https://github.com///pull/NNNNN)). + ([/ #NNNNN](https://github.com///pull/NNNNN)). ## Breaking changes @@ -84,17 +84,17 @@ Known component docs links: ## Section rules 1. **TOC at top** — every feature gets a linked entry -2. **One paragraph of context** — what the feature does and why it matters, with PR/issue links +2. **One paragraph of context** — what the feature does and why it matters in concrete terms, with PR/issue links; avoid inferred feelings or marketing-style claims 3. **Code sample** — show the feature in use 4. **Feature ordering** — highest customer impact first 5. **Breaking changes near the end** — low-score entries with `breaking_changes: true` usually belong in a short section before Bug fixes, not as full feature sections ## Issue and PR references -Always use markdown links with the `{org}/{repo}#{number}` format: +Always use markdown links with the `{org}/{repo} #{number}` format, with a space before `#`: -- ✅ `[dotnet/runtime#124264](https://github.com/dotnet/runtime/pull/124264)` -- ❌ `dotnet/runtime#124264` (bare reference) +- ✅ `[dotnet/runtime #124264](https://github.com/dotnet/runtime/pull/124264)` +- ❌ `dotnet/runtime#124264` (wrong spacing and bare reference) ## Minimal stub @@ -112,7 +112,7 @@ There are no new features or improvements in in this release. ## Finding Certificates By Thumbprints Other Than SHA-1 A new method on `X509Certificate2Collection` accepts the name of the hash algorithm to use -for thumbprint matching ([dotnet/runtime#NNNNN](https://github.com/dotnet/runtime/pull/NNNNN)). +for thumbprint matching ([dotnet/runtime #NNNNN](https://github.com/dotnet/runtime/pull/NNNNN)). ``` Code sample example: From a00e63f7bf96185ff3d058d6bfeeedbeebaae3cf Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Mon, 6 Apr 2026 13:41:15 -0700 Subject: [PATCH 58/65] Prefer stronger verbs in release-note headings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/references/editorial-rules.md | 4 ++++ .github/skills/release-notes/references/examples/README.md | 1 + 2 files changed, 5 insertions(+) diff --git a/.github/skills/release-notes/references/editorial-rules.md b/.github/skills/release-notes/references/editorial-rules.md index 50c8bdef0e..1c12788809 100644 --- a/.github/skills/release-notes/references/editorial-rules.md +++ b/.github/skills/release-notes/references/editorial-rules.md @@ -38,6 +38,10 @@ Tone, attribution, and content guidelines for .NET release notes. - ✅ `## Faster time zone conversions` - ❌ `## ZstandardStream` - ❌ `## TimeZoneInfo performance` +- Prefer **specific, customer-facing verbs** over generic `gets` phrasing. Name the capability the customer now has: `offers`, `adds`, `supports`, `recognizes`, `enables`, and similar verbs are usually stronger. + - ✅ `## System.Text.Json offers more control over naming and ignore defaults` + - ✅ `## Regex recognizes all Unicode newline sequences` + - ❌ `## System.Text.Json gets more control over naming and ignore defaults` - Keep headings concise — 3–8 words ## Benchmarks diff --git a/.github/skills/release-notes/references/examples/README.md b/.github/skills/release-notes/references/examples/README.md index 79b01bbd2c..44bdafdf52 100644 --- a/.github/skills/release-notes/references/examples/README.md +++ b/.github/skills/release-notes/references/examples/README.md @@ -25,3 +25,4 @@ Curated examples from previous .NET release notes, organized by component. Load 11. **Link to PRs and issues** — use the `org/repo #number` format, with a space before `#`: `[dotnet/runtime #115977](https://github.com/dotnet/runtime/pull/115977)`. Links give readers provenance and let them dig deeper 12. **Avoid jargon and ambiguous words** — "snippet" in a programming context suggests code. Say what you mean plainly. If a word could be misread, pick a simpler one 13. **Concrete beats promotional** — describe the observable change, not the feeling it might create. "Regex recognizes all Unicode newline sequences" or "Tar extraction now rejects path traversal entries" beats "compression and archive handling are easier to trust." +14. **Prefer strong verbs over `gets`** — write the customer-facing capability directly: "System.Text.Json offers more control..." is stronger than "System.Text.Json gets more control..." From 9242e3170fb047e88361e2d3d42fc68f9c12e783 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Mon, 6 Apr 2026 15:42:26 -0700 Subject: [PATCH 59/65] Refine release-notes workflow guidance Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/DESIGN.md | 8 +- .github/skills/release-notes/SKILL.md | 6 +- .../references/changes-schema.md | 42 +++++++ .../references/editorial-rules.md | 8 ++ .../references/examples/README.md | 1 + .../references/format-template.md | 11 ++ .github/skills/review-release-notes/SKILL.md | 38 +++++++ .github/workflows/release-notes.lock.yml | 107 +++++++++++------- .github/workflows/release-notes.md | 38 ++++++- release-notes/features.json | 14 +++ 10 files changed, 227 insertions(+), 46 deletions(-) create mode 100644 release-notes/features.json diff --git a/.github/skills/release-notes/DESIGN.md b/.github/skills/release-notes/DESIGN.md index 8d75219500..0e27a93ad4 100644 --- a/.github/skills/release-notes/DESIGN.md +++ b/.github/skills/release-notes/DESIGN.md @@ -44,9 +44,15 @@ The system still has three layers, but the AI/editorial layer is now split into - **`generate-features`** adds reusable triage and scoring so release notes, docs, and blogs can share the same ranked feature list. - **`api-diff`** verifies the public API story against the actual build, which is the best defense against missed reverts. - **`release-notes`** focuses on editorial judgment: which scored items matter, how to describe them, and how to preserve human edits. -- **`review-release-notes`** applies a harsher, reader-centric cutoff so the final draft does not drift into API-inventory territory. +- **`review-release-notes`** applies a harsher, reader-centric cutoff so the final draft does not drift into API-inventory territory. The final pass should fan out to two different models so the system gets a broader editorial read before synthesis. - **The workflow** handles orchestration. It knows when to run, what branches to manage, how to interact with humans, and how to preserve their edits. +### Model strategy + +- **Claude Opus 4.6 by default** — use Claude Opus 4.6 throughout the main orchestration, scoring, and drafting flow. +- **Two-model final review** — the preferred final editorial pair is **Claude Opus 4.6 + GPT-5.4**. +- **Synthesis over voting** — overlap is a strong signal, but the shared rubric and fidelity to the actual shipped changes still outrank raw vote count. + ## Layer 1 — The Tool [`dotnet-release`](https://github.com/richlander/dotnet-release) is a .NET global tool. The `generate changes` command produces `changes.json`: diff --git a/.github/skills/release-notes/SKILL.md b/.github/skills/release-notes/SKILL.md index a563abf481..964e14a235 100644 --- a/.github/skills/release-notes/SKILL.md +++ b/.github/skills/release-notes/SKILL.md @@ -1,7 +1,7 @@ --- name: release-notes -description: Generate and maintain .NET release notes from `features.json`. Uses `generate-changes` for authoritative shipped-change data, `generate-features` for scoring/triage, `editorial-scoring` for the shared rubric, `api-diff`/`dotnet-inspect` for API verification, and `review-release-notes` for a final editorial QA pass. -compatibility: Requires GitHub MCP server or gh CLI for cross-repo queries. Pairs with the generate-changes, generate-features, editorial-scoring, api-diff, and review-release-notes skills. +description: Generate and maintain .NET release notes from `features.json`. Uses `generate-changes` for authoritative shipped-change data, `generate-features` for scoring/triage, `editorial-scoring` for the shared rubric, `api-diff`/`dotnet-inspect` for API verification, and a multi-model `review-release-notes` pass for final editorial QA. +compatibility: Requires GitHub MCP server or gh CLI for cross-repo queries. Pairs with the generate-changes, generate-features, editorial-scoring, api-diff, and review-release-notes skills. Claude Opus 4.6 is the default workflow model; the preferred final reviewer pair is Claude Opus 4.6 + GPT-5.4 for broader editorial feedback. --- # .NET Release Notes @@ -16,7 +16,7 @@ This skill is the **editorial writing stage** of the pipeline. It turns a scored 2. `generate-features` reads `changes.json` and emits `features.json` with optional scores using the shared `editorial-scoring` rubric 3. `api-diff` / `dotnet-inspect` verifies public APIs and catches missed reverts 4. `release-notes` writes curated markdown using the higher-value entries from `features.json` -5. `review-release-notes` checks the selection against the scoring rubric and editorial examples +5. `review-release-notes` runs a final multi-model editorial QA pass against the scoring rubric and examples 6. Output is one PR per release milestone in dotnet/core, maintained incrementally ## Design diff --git a/.github/skills/release-notes/references/changes-schema.md b/.github/skills/release-notes/references/changes-schema.md index 81ee8c172e..9cfd6a75ea 100644 --- a/.github/skills/release-notes/references/changes-schema.md +++ b/.github/skills/release-notes/references/changes-schema.md @@ -15,6 +15,7 @@ release-notes/{major.minor}/preview/{previewN}/changes.json # previews release-notes/{major.minor}/{major.minor.patch}/changes.json # patches release-notes/{major.minor}/preview/{previewN}/features.json # previews release-notes/{major.minor}/{major.minor.patch}/features.json # patches +release-notes/features.json # root multi-release features sidecar ``` ## Top-level structure @@ -26,6 +27,47 @@ release-notes/{major.minor}/{major.minor.patch}/features.json # patches | `changes` | array | The change or feature entries | | `commits` | object | Normalized commit metadata, keyed by `repo@shortcommit` | +## Root `release-notes/features.json` sidecar + +`release-notes/features.json` is an optional **root-level sidecar** for +multi-release or long-running features that need consistent naming, preview +callouts, and extra editorial notes across multiple milestones. + +Use it when: + +- a feature remains preview for a full release and may become stable in a later one +- the same feature may appear in multiple preview or RC milestones +- the notes should lead with a standard blockquote such as + `> This is a preview feature for .NET 11.` +- the feature has an official name, and any aliases should only help matching that feature across runs +- common casing or shorthand variants should still resolve to the same feature name + +Suggested structure: + +```json +{ + "features": [ + { + "id": "unsafe-evolution", + "official_name": "Unsafe Evolution", + "aliases": ["Memory Safety v2", "memory safety v2"], + "repos": ["roslyn", "runtime"], + "preview_in": "11.0", + "stable_in": "12.0", + "callout": "This is a preview feature for .NET 11.", + "notes": "Use the official name in headings and body copy. Aliases are for matching only. Avoid generic phrasing such as 'Unsafe code adds ...'." + } + ] +} +``` + +This root file is **separate from** the per-milestone `features.json` files +under each preview or patch directory. It is curated metadata used by the +writing stage to keep long-running features consistent across the release. + +- `official_name` is the canonical name to use in headings and prose. +- `aliases` are match-only helpers for recognition, casing variants, or prior shorthand names. + ## Change entry fields - `id` (`string`) — globally unique identifier in `repo@shortcommit` format, for example `"runtime@c5d5be4"` diff --git a/.github/skills/release-notes/references/editorial-rules.md b/.github/skills/release-notes/references/editorial-rules.md index 1c12788809..45546e4ac6 100644 --- a/.github/skills/release-notes/references/editorial-rules.md +++ b/.github/skills/release-notes/references/editorial-rules.md @@ -42,6 +42,14 @@ Tone, attribution, and content guidelines for .NET release notes. - ✅ `## System.Text.Json offers more control over naming and ignore defaults` - ✅ `## Regex recognizes all Unicode newline sequences` - ❌ `## System.Text.Json gets more control over naming and ignore defaults` +- Avoid anthropomorphic or club-like transition verbs such as `joins` when a more literal term is available. Prefer `moves to`, `is now in`, `adds support for`, or `supports`. + - ✅ `## Zstandard moved to System.IO.Compression and ZIP reads validate CRC32` + - ❌ `## Zstandard joins System.IO.Compression and ZIP reads validate CRC32` +- Use the **established feature name** when one exists, especially for long-running preview features. If `release-notes/features.json` lists an `official_name`, use that in headings and prose. Treat aliases as match-only metadata, not as the default wording. + - ✅ `## Unsafe Evolution remains a preview feature in .NET 11` + - ✅ `## Unsafe Evolution adds clearer diagnostics in Preview 3` + - ❌ `## Memory Safety v2 adds clearer diagnostics in Preview 3` + - ❌ `## Unsafe code adds clearer diagnostics and annotations` - Keep headings concise — 3–8 words ## Benchmarks diff --git a/.github/skills/release-notes/references/examples/README.md b/.github/skills/release-notes/references/examples/README.md index 44bdafdf52..574af2bfde 100644 --- a/.github/skills/release-notes/references/examples/README.md +++ b/.github/skills/release-notes/references/examples/README.md @@ -26,3 +26,4 @@ Curated examples from previous .NET release notes, organized by component. Load 12. **Avoid jargon and ambiguous words** — "snippet" in a programming context suggests code. Say what you mean plainly. If a word could be misread, pick a simpler one 13. **Concrete beats promotional** — describe the observable change, not the feeling it might create. "Regex recognizes all Unicode newline sequences" or "Tar extraction now rejects path traversal entries" beats "compression and archive handling are easier to trust." 14. **Prefer strong verbs over `gets`** — write the customer-facing capability directly: "System.Text.Json offers more control..." is stronger than "System.Text.Json gets more control..." +15. **Prefer literal transition verbs** — if something moved namespaces or assemblies, say that directly. "Zstandard moved to `System.IO.Compression`" is clearer than "Zstandard joins `System.IO.Compression`." diff --git a/.github/skills/release-notes/references/format-template.md b/.github/skills/release-notes/references/format-template.md index d1daccc836..0e8618e6c6 100644 --- a/.github/skills/release-notes/references/format-template.md +++ b/.github/skills/release-notes/references/format-template.md @@ -88,6 +88,7 @@ Known component docs links: 3. **Code sample** — show the feature in use 4. **Feature ordering** — highest customer impact first 5. **Breaking changes near the end** — low-score entries with `breaking_changes: true` usually belong in a short section before Bug fixes, not as full feature sections +6. **Preview feature callout** — when a feature is listed in `release-notes/features.json`, start its section with the standard blockquote callout from that file ## Issue and PR references @@ -115,6 +116,16 @@ A new method on `X509Certificate2Collection` accepts the name of the hash algori for thumbprint matching ([dotnet/runtime #NNNNN](https://github.com/dotnet/runtime/pull/NNNNN)). ``` +Preview-feature example: + +```markdown +## Unsafe Evolution remains a preview feature in .NET 11 + +> This is a preview feature for .NET 11. + +Preview 3 adds clearer language-version errors for updated memory-safety rules... +``` + Code sample example: ```csharp diff --git a/.github/skills/review-release-notes/SKILL.md b/.github/skills/review-release-notes/SKILL.md index e76a677e24..42978c9f9c 100644 --- a/.github/skills/review-release-notes/SKILL.md +++ b/.github/skills/review-release-notes/SKILL.md @@ -76,6 +76,44 @@ Compare the draft against the component examples: - **API inventory mode** — the draft starts mirroring `api-diff` instead of telling a user story - **Technical novelty bias** — clever implementation details outrank practical user value +## Multi-model review pattern + +For the final editorial QA pass, use this skill as a **two-reviewer parallel check** to get broader viewpoint diversity: + +Preferred set: + +1. **Claude Opus 4.6** +2. **GPT-5.4** + +Give both reviewers the same inputs and the same requested output: + +- most correct scoring +- most wrong scoring +- important omissions +- cut list + +Then synthesize the overlap and disagreements. Treat consensus as a strong signal, but do **not** turn this into a blind vote — fidelity to `changes.json`, the shared `editorial-scoring` rubric, and the repo's editorial rules still wins. + +## Reviewer checklist + +Do not ask reviewers the vague question "do you like this?" Give them the same +specific checks instead: + +1. **Which headings still sound vague, passive, anthropomorphic, or promotional?** +2. **Which sections fail the 80/20 reader-value test and should be cut, grouped, or demoted?** +3. **Which sentences infer feelings or outcomes (`trust`, `confidence`, `easier`, `better`) instead of stating the concrete change?** +4. **Which sections drift into API-inventory mode instead of teaching a user-facing story?** +5. **Which code samples or examples are weak, confusing, or unsupported by the text?** +6. **Which links, issue/PR references, or formatting details still violate house style?** +7. **What is the single highest-value rewrite still needed in the draft?** +8. **Is the wording conventional, or is it inventing non-standard phrasing or terms?** +9. **Are the subject and its adjective or adverb paired in a familiar way?** +10. **Would this phrasing seem normal in release notes for another developer platform?** +11. **If `release-notes/features.json` lists this feature, does the section begin with the standard preview blockquote?** + +Ask reviewers to answer with file + heading + issue + suggested rewrite. This +produces actionable review instead of general taste feedback. + ## Output Return a concise review with: diff --git a/.github/workflows/release-notes.lock.yml b/.github/workflows/release-notes.lock.yml index 71e7de6f65..d5496c4201 100644 --- a/.github/workflows/release-notes.lock.yml +++ b/.github/workflows/release-notes.lock.yml @@ -12,7 +12,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.65.6). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.65.1). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -21,7 +21,7 @@ # For more information: https://github.github.com/gh-aw/introduction/overview/ # # -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"258a65cef4805b1365cd65928059c0f570c9ced88a7d855afb03b906893066bf","compiler_version":"v0.65.6","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"258a65cef4805b1365cd65928059c0f570c9ced88a7d855afb03b906893066bf","compiler_version":"v0.65.1","strict":true,"agent_id":"copilot"} name: ".NET Release Notes Maintenance" "on": @@ -85,7 +85,7 @@ jobs: secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@31130b20a8fd3ef263acbe2091267c0aace07e09 # v0.65.6 + uses: github/gh-aw-actions/setup@1831bcd4f397da99da6e6e6b65687557a1a37ac7 # v0.65.1 with: destination: ${{ runner.temp }}/gh-aw/actions - name: Generate agentic run info @@ -96,14 +96,14 @@ jobs: GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} GH_AW_INFO_VERSION: "latest" GH_AW_INFO_AGENT_VERSION: "latest" - GH_AW_INFO_CLI_VERSION: "v0.65.6" + GH_AW_INFO_CLI_VERSION: "v0.65.1" GH_AW_INFO_WORKFLOW_NAME: ".NET Release Notes Maintenance" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","dotnet"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.11" + GH_AW_INFO_AWF_VERSION: "v0.25.5" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -141,7 +141,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: - GH_AW_COMPILED_VERSION: "v0.65.6" + GH_AW_COMPILED_VERSION: "v0.65.1" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -297,7 +297,6 @@ jobs: GH_AW_WORKFLOW_ID_SANITIZED: releasenotes outputs: checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} - effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} inference_access_error: ${{ steps.detect-inference-error.outputs.inference_access_error || 'false' }} model: ${{ needs.activation.outputs.model }} @@ -305,7 +304,7 @@ jobs: output_types: ${{ steps.collect_output.outputs.output_types }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@31130b20a8fd3ef263acbe2091267c0aace07e09 # v0.65.6 + uses: github/gh-aw-actions/setup@1831bcd4f397da99da6e6e6b65687557a1a37ac7 # v0.65.1 with: destination: ${{ runner.temp }}/gh-aw/actions - name: Set runtime paths @@ -357,7 +356,7 @@ jobs: - name: Install GitHub Copilot CLI run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest - name: Install AWF binary - run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.11 + run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.5 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -369,7 +368,7 @@ jobs: const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download container images - run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.11 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.11 ghcr.io/github/gh-aw-firewall/squid:0.25.11 ghcr.io/github/gh-aw-mcpg:v0.2.11 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine + run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.5 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.5 ghcr.io/github/gh-aw-firewall/squid:0.25.5 ghcr.io/github/gh-aw-mcpg:v0.2.10 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine - name: Write Safe Outputs Config run: | mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs @@ -549,7 +548,6 @@ jobs: id: safe-outputs-start env: DEBUG: '*' - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json @@ -558,7 +556,6 @@ jobs: run: | # Environment variables are set above to prevent template injection export DEBUG - export GH_AW_SAFE_OUTPUTS export GH_AW_SAFE_OUTPUTS_PORT export GH_AW_SAFE_OUTPUTS_API_KEY export GH_AW_SAFE_OUTPUTS_TOOLS_PATH @@ -592,7 +589,7 @@ jobs: export DEBUG="*" export GH_AW_ENGINE="copilot" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.11' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.10' mkdir -p /home/runner/.copilot cat << GH_AW_MCP_CONFIG_d030b8fe358a9b4a_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh @@ -680,7 +677,7 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.11 --skip-pull --enable-api-proxy \ + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.5 --skip-pull --enable-api-proxy \ -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(dotnet-release)'\'' --allow-tool '\''shell(dotnet:*)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(git add:*)'\'' --allow-tool '\''shell(git branch:*)'\'' --allow-tool '\''shell(git checkout:*)'\'' --allow-tool '\''shell(git commit:*)'\'' --allow-tool '\''shell(git merge:*)'\'' --allow-tool '\''shell(git rm:*)'\'' --allow-tool '\''shell(git status)'\'' --allow-tool '\''shell(git switch:*)'\'' --allow-tool '\''shell(git:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE @@ -690,7 +687,7 @@ jobs: GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.65.6 + GH_AW_VERSION: v0.65.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_HEAD_REF: ${{ github.head_ref }} @@ -724,7 +721,20 @@ jobs: - name: Copy Copilot session state files to logs if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh + run: | + # Copy Copilot session state files to logs folder for artifact collection + # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them + SESSION_STATE_DIR="$HOME/.copilot/session-state" + LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs" + + if [ -d "$SESSION_STATE_DIR" ]; then + echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR" + mkdir -p "$LOGS_DIR" + cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true + echo "Session state files copied successfully" + else + echo "No session-state directory found at $SESSION_STATE_DIR" + fi - name: Stop MCP Gateway if: always() continue-on-error: true @@ -797,7 +807,6 @@ jobs: await main(); - name: Parse MCP Gateway logs for step summary if: always() - id: parse-mcp-gateway uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | @@ -820,10 +829,6 @@ jobs: else echo 'AWF binary not installed, skipping firewall log summary' fi - - name: Parse token usage for step summary - if: always() - continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/parse_token_usage.sh - name: Write agent output placeholder if missing if: always() run: | @@ -881,7 +886,7 @@ jobs: total_count: ${{ steps.missing_tool.outputs.total_count }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@31130b20a8fd3ef263acbe2091267c0aace07e09 # v0.65.6 + uses: github/gh-aw-actions/setup@1831bcd4f397da99da6e6e6b65687557a1a37ac7 # v0.65.1 with: destination: ${{ runner.temp }}/gh-aw/actions - name: Download agent output artifact @@ -905,15 +910,12 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" GH_AW_WORKFLOW_NAME: ".NET Release Notes Maintenance" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_NOOP_REPORT_AS_ISSUE: "true" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + const { main } = require('${{ runner.temp }}/gh-aw/actions/noop.cjs'); await main(); - name: Record Missing Tool id: missing_tool @@ -956,20 +958,49 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); await main(); + - name: Handle No-Op Message + id: handle_noop_message + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: ".NET Release Notes Maintenance" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + await main(); + - name: Handle Create Pull Request Error + id: handle_create_pr_error + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: ".NET Release Notes Maintenance" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_create_pr_error.cjs'); + await main(); detection: needs: agent if: > always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') runs-on: ubuntu-latest - permissions: - contents: read outputs: detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} detection_success: ${{ steps.detection_conclusion.outputs.success }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@31130b20a8fd3ef263acbe2091267c0aace07e09 # v0.65.6 + uses: github/gh-aw-actions/setup@1831bcd4f397da99da6e6e6b65687557a1a37ac7 # v0.65.1 with: destination: ${{ runner.temp }}/gh-aw/actions - name: Download agent output artifact @@ -986,14 +1017,9 @@ jobs: mkdir -p /tmp/gh-aw/ find "/tmp/gh-aw/" -type f -print echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - - name: Checkout repository for patch context - if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false # --- Threat Detection --- - name: Download container images - run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.11 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.11 ghcr.io/github/gh-aw-firewall/squid:0.25.11 + run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.5 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.5 ghcr.io/github/gh-aw-firewall/squid:0.25.5 - name: Check if detection needed id: detection_guard if: always() @@ -1049,7 +1075,7 @@ jobs: - name: Install GitHub Copilot CLI run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest - name: Install AWF binary - run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.11 + run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.5 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' id: detection_agentic_execution @@ -1059,7 +1085,7 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.11 --skip-pull --enable-api-proxy \ + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.5 --skip-pull --enable-api-proxy \ -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE @@ -1067,7 +1093,7 @@ jobs: COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.65.6 + GH_AW_VERSION: v0.65.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_HEAD_REF: ${{ github.head_ref }} @@ -1110,7 +1136,7 @@ jobs: select-copilot-pat_result: ${{ steps.select-copilot-pat.outcome }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@31130b20a8fd3ef263acbe2091267c0aace07e09 # v0.65.6 + uses: github/gh-aw-actions/setup@1831bcd4f397da99da6e6e6b65687557a1a37ac7 # v0.65.1 with: destination: ${{ runner.temp }}/gh-aw/actions - name: Check team membership for workflow @@ -1162,7 +1188,6 @@ jobs: timeout-minutes: 15 env: GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/release-notes" - GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} GH_AW_WORKFLOW_ID: "release-notes" @@ -1182,7 +1207,7 @@ jobs: push_commit_url: ${{ steps.process_safe_outputs.outputs.push_commit_url }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@31130b20a8fd3ef263acbe2091267c0aace07e09 # v0.65.6 + uses: github/gh-aw-actions/setup@1831bcd4f397da99da6e6e6b65687557a1a37ac7 # v0.65.1 with: destination: ${{ runner.temp }}/gh-aw/actions - name: Download agent output artifact diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index ae5dfeddd8..aaa05532ef 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -130,6 +130,14 @@ Read these files and skills for detailed guidance: - **editorial-rules.md** — tone, attribution, naming conventions - **examples/** — curated examples from previous releases, organized by component ([README](../skills/release-notes/references/examples/README.md) has principles) +## Model strategy + +- Use **Claude Opus 4.6** as the default model throughout the workflow for orchestration, scoring, and drafting. +- For the **final `review-release-notes` pass**, prefer this **two-model reviewer set** to widen the editorial viewpoint: + - **Claude Opus 4.6** + - **GPT-5.4** +- Give both reviewers the same inputs and ask for the same output shape. Synthesize the overlap, inspect meaningful disagreements, and prefer the shared `editorial-scoring` rubric over any single model's preference. + ## What to do each run ### 1. Discover active milestones @@ -243,6 +251,7 @@ Using `features.json`, `changes.json`, and the reference documents: - Route changes to output files via `product` field and component-mapping.md - For each component: identify which PRs are worth writing about - Write feature descriptions following `format-template.md` and `editorial-rules.md` +- If `release-notes/features.json` exists, consult it before writing. For matching long-running features, use the established feature name and start the section with the standard preview blockquote from the sidecar file. - Components with no noteworthy changes get a minimal stub #### f. Ask for what you can't generate @@ -266,7 +275,34 @@ Check all comments and review threads on the PR since the last run: When unsure about a human's intent, ask. Use `add-comment` to reply. This is a conversation, not a one-shot generation. -#### e. Create or update the PR +#### h. Run the final multi-model review + +Before pushing the draft, run the `review-release-notes` stage as a **two-agent parallel review**: + +- **Reviewer 1:** Claude Opus 4.6 +- **Reviewer 2:** GPT-5.4 + +Have each reviewer critique the same draft using the same rubric, examples, and +this checklist: + +- Which headings still sound vague, passive, anthropomorphic, or promotional? +- Which sections fail the 80/20 reader-value test and should be cut, grouped, or demoted? +- Which sentences infer feelings or outcomes instead of stating the concrete change? +- Which sections drift into API-inventory mode instead of teaching a user story? +- Which code samples or examples are weak or confusing? +- Which links, issue/PR references, or formatting details still violate house style? +- What is the single highest-value rewrite still needed? +- Is the wording conventional, or is it inventing non-standard phrasing or terms? +- Are the subject and its adjective or adverb paired in a familiar way? +- Would this phrasing seem normal within release notes for another developer platform? + +Ask for file + heading + issue + suggested rewrite, not generic preference. Then: + +- apply the changes that have clear consensus +- keep a human-readable note of any major disagreement +- avoid "majority vote" thinking when it conflicts with fidelity or house style + +#### i. Create or update the PR - **No PR exists** → create branch `release-notes/11.0-preview4`, commit, open draft PR - **PR exists** → push updates to the existing branch, comment summarizing what changed diff --git a/release-notes/features.json b/release-notes/features.json new file mode 100644 index 0000000000..2a9fdc8b18 --- /dev/null +++ b/release-notes/features.json @@ -0,0 +1,14 @@ +{ + "features": [ + { + "id": "unsafe-evolution", + "official_name": "Unsafe Evolution", + "aliases": ["Memory Safety v2", "memory safety v2"], + "repos": ["roslyn", "runtime"], + "preview_in": "11.0", + "stable_in": "12.0", + "callout": "This is a preview feature for .NET 11.", + "notes": "Use the official name in headings and body copy. Keep aliases for matching only rather than as the default wording. Avoid generic phrasing such as 'Unsafe code adds ...'." + } + ] +} From ecacff188681046d12384fc2717767e92064671c Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Mon, 6 Apr 2026 15:48:11 -0700 Subject: [PATCH 60/65] Remove redundant release-notes docs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/release-notes/DESIGN.md | 295 ------------------------- .github/skills/release-notes/SKILL.md | 4 - .github/skills/release-notes/STATUS.md | 104 --------- 3 files changed, 403 deletions(-) delete mode 100644 .github/skills/release-notes/DESIGN.md delete mode 100644 .github/skills/release-notes/STATUS.md diff --git a/.github/skills/release-notes/DESIGN.md b/.github/skills/release-notes/DESIGN.md deleted file mode 100644 index 0e27a93ad4..0000000000 --- a/.github/skills/release-notes/DESIGN.md +++ /dev/null @@ -1,295 +0,0 @@ -# Release Notes System — Design - -How the automated release notes system for .NET works, why it's designed this way, and how the pieces connect. - -## Problem - -.NET ships ~25 component repositories assembled into a single product via the VMR (`dotnet/dotnet`). Release notes need to reflect exactly what ships — not what's planned, not what's in progress, not what was reverted. Historically, release notes are written manually by gathering information from component teams. This is slow, error-prone, and tends to miss features or include things that didn't actually ship. - -## Goals - -1. **High fidelity** — only document what ships. The VMR `source-manifest.json` is the source of truth for what code is included in a release. -2. **High value** — bias toward features users care about. Not every merged PR is worth a release note. -3. **Never document non-shipping features** — if it's not in the VMR diff, it didn't ship. -4. **Incremental** — drafts improve over time as previews mature and humans provide feedback. -5. **Human-in-the-loop** — humans edit the release notes branch and comment on the PR. The system adapts to their input rather than overwriting it. - -## Architecture - -The system still has three layers, but the AI/editorial layer is now split into reusable skills with JSON handoffs: - -```text -┌──────────────────────────────────────────────────────────────┐ -│ Agentic Workflow (cron) │ -│ .github/workflows/release-notes.md │ -│ Orchestration: branch lifecycle, PR mgmt, comments, timing │ -├──────────────────────────────────────────────────────────────┤ -│ Reusable skills (editorial pipeline) │ -│ generate-changes → changes.json │ -│ editorial-scoring → shared rubric │ -│ generate-features → features.json │ -│ api-diff / dotnet-inspect → API evidence │ -│ release-notes → markdown │ -│ review-release-notes → trim / re-score │ -├──────────────────────────────────────────────────────────────┤ -│ Tooling (deterministic where possible) │ -│ dotnet-release, dotnet-inspect, GitHub APIs, local git │ -└──────────────────────────────────────────────────────────────┘ -``` - -### Why split the stages? - -- **`generate-changes`** handles mechanical data collection. It's deterministic — same inputs always produce the same output. -- **`editorial-scoring`** centralizes the rubric so the generator and reviewers use the same definition of importance. -- **`generate-features`** adds reusable triage and scoring so release notes, docs, and blogs can share the same ranked feature list. -- **`api-diff`** verifies the public API story against the actual build, which is the best defense against missed reverts. -- **`release-notes`** focuses on editorial judgment: which scored items matter, how to describe them, and how to preserve human edits. -- **`review-release-notes`** applies a harsher, reader-centric cutoff so the final draft does not drift into API-inventory territory. The final pass should fan out to two different models so the system gets a broader editorial read before synthesis. -- **The workflow** handles orchestration. It knows when to run, what branches to manage, how to interact with humans, and how to preserve their edits. - -### Model strategy - -- **Claude Opus 4.6 by default** — use Claude Opus 4.6 throughout the main orchestration, scoring, and drafting flow. -- **Two-model final review** — the preferred final editorial pair is **Claude Opus 4.6 + GPT-5.4**. -- **Synthesis over voting** — overlap is a strong signal, but the shared rubric and fidelity to the actual shipped changes still outrank raw vote count. - -## Layer 1 — The Tool - -[`dotnet-release`](https://github.com/richlander/dotnet-release) is a .NET global tool. The `generate changes` command produces `changes.json`: - -```bash -dotnet-release generate changes \ - --base \ - --head \ - --version "11.0.0-preview.3" \ - --date "2026-04-08" \ - --labels \ - --output release-notes/11.0/preview/preview3/changes.json -``` - -### What the tool does - -1. **Reads `src/source-manifest.json`** at both refs via local git — this is the VMR bill of materials listing every component repo with its exact commit SHA -2. **Diffs the manifests** to identify which components changed and their commit ranges -3. **Queries GitHub compare API** for each changed component to enumerate merged PRs in the commit range -4. **Maps repos to product slugs** using a built-in taxonomy (e.g., `runtime` → `dotnet-runtime`; infra repos get no product) -5. **Fetches PR labels** when `--labels` is provided (useful for agent categorization) -6. **Cross-references CVEs** when `--cve-repo` is provided (loads `cve.json` from the `release-index` branch) -7. **Outputs `changes.json`** following the [changes schema](references/changes-schema.md) - -### What the tool does NOT do - -- Editorial judgment (which PRs are important) -- Feature scoring (`features.json`) -- Markdown generation -- Branch management or PR creation - -### Source-manifest.json — the key mechanism - -The VMR's `src/source-manifest.json` is a JSON bill of materials: - -```json -{ - "repositories": [ - { - "path": "runtime", - "remoteUri": "https://github.com/dotnet/runtime", - "commitSha": "d3439749b652966f8283f2d01c972bc8c4dc3ec3" - } - ] -} -``` - -By comparing this file at two release points, the tool gets exact per-component commit ranges. This replaces any need to parse VMR sync commits, trace codeflow PRs, or calculate fork points. One JSON diff gives everything. - -Example (Preview 1 → Preview 2): the tool found 1,389 PRs across 21 changed repos. - -## Layer 2 — The Skills - -The editorial pipeline is intentionally split into small, reusable skills: - -| Document | Purpose | -| -------------------------------------------------------------- | --------------------------------------------------------------- | -| [quality-bar.md](references/quality-bar.md) | North star — fidelity, value, WHY+HOW | -| [vmr-structure.md](references/vmr-structure.md) | VMR branches, tags, source-manifest.json | -| [changes-schema.md](references/changes-schema.md) | The shared `changes.json` / `features.json` schema | -| [../editorial-scoring/SKILL.md](../editorial-scoring/SKILL.md) | Shared reader-centric scoring rubric | -| [feature-scoring.md](references/feature-scoring.md) | How to rank features for release notes, docs, and blogs | -| [component-mapping.md](references/component-mapping.md) | Components → product slugs → output files | -| [format-template.md](references/format-template.md) | Markdown document structure | -| [editorial-rules.md](references/editorial-rules.md) | Tone, attribution, naming | -| [examples/](references/examples/README.md) | Curated examples by component — short, medium, long-form styles | - -These are **goal-oriented**, not procedural. They describe what good release notes and feature selection look like, not the exact steps to produce them. The skills figure out the HOW. - -### Skill responsibilities - -- **`generate-changes`** — determine the correct VMR base/head refs, including preview-only multi-branch targeting, and emit `changes.json` -- **`editorial-scoring`** — define the shared reader-centric scoring rubric and cut thresholds -- **`generate-features`** — read `changes.json`, score likely features, and emit `features.json` using the same schema plus optional scoring -- **`api-diff`** — verify APIs against the actual build binaries/ref packs and generate diffs when needed -- **`release-notes`** — produce markdown release notes for high-value features, following the quality bar and only using verified API names and code samples -- **`review-release-notes`** — audit the scored output against the editorial examples, demoting over-scored items and promoting missing ones -- **Workflow-facing behavior** — respect human edits, diff the PR branch, and incorporate PR comments and feedback - -## Layer 3 — The Agentic Workflow - -A [GitHub Agentic Workflow](https://github.github.com/gh-aw/) defined in `.github/workflows/release-notes.md`. It runs on a schedule and manages the full lifecycle. This is a **multi-master live system** — the agent, component teams, and release managers all edit concurrently. - -### Multi-milestone discovery - -Multiple milestones can be active simultaneously. For example: - -```text -Latest shipped: Preview 3 (in releases.json) -VMR main: Preview 5 (Versions.props iteration=5) -VMR release/P4: Preview 4 (release branch exists, no tag yet) - -→ Active milestones: Preview 4 (from release branch) AND Preview 5 (from main) -→ Each gets its own branch and PR on dotnet/core -``` - -The workflow discovers all milestones between `latest_shipped + 1` and `main_iteration`, checks for VMR tags and release branches, and creates a branch+PR for each. - -### Head ref selection (per milestone) - -Each milestone needs its own base and head ref. This is re-validated every run because refs can change: - -| Milestone state | Base ref | Head ref | -| -------------------------------- | ----------- | ------------------ | -| Has VMR tag (finalized) | Tag for N-1 | Tag for N | -| Has release branch (stabilizing) | Tag for N-1 | Release branch tip | -| Only on main (in development) | Tag for N-1 | main | - -**Critical**: never use `main` for milestone N if `main` has moved to N+1. Check the iteration in `Versions.props` every run. - -### Branch lifecycle on dotnet/core - -```text -Branch: release-notes/11.0-preview4 -PR: [release-notes] .NET 11 Preview 4 - -Branch: release-notes/11.0-preview5 -PR: [release-notes] .NET 11 Preview 5 -``` - -Each branch is long-lived — it's created on the first run and updated frequently until the PR is merged (after the preview ships). - -### Human interaction model - -This is the most delicate part of the system. The branches are shared workspaces: - -**Respecting edits:** - -- Before writing, diff the branch to identify human commits -- Files humans have edited are partially off-limits — only add new sections, never overwrite their changes -- When a file has mixed agent + human content, be surgical — touch only agent-authored sections - -**Responding to comments:** - -- Read all PR comments and review threads since the last run -- Classify: actionable feedback, questions, disagreements, resolved -- For actionable items: make the change and confirm -- For questions: answer or escalate -- For disagreements: cross-check `changes.json` and explain findings -- When intent is unclear: ask for clarification via comment -- This is a conversation. Engage, don't ignore. - -**Handling conflicts:** - -- If a human and the agent both changed the same section, the human's version wins -- If the agent is unsure whether a human edit was intentional, ask via PR comment -- Never force-push or rewrite human commits - -### PR lifecycle - -| State | Action | -| ------------------------- | ------------------------------------------------------------------------- | -| No PR for milestone | Create branch, generate content, open draft PR | -| PR exists, source changed | Regenerate `changes.json` / `features.json`, update/add markdown sections | -| PR exists, human edited | Preserve edits, only update untouched sections | -| New tag appeared | Final regen with `--head `, note finalization | -| Main bumped | Switch earlier milestone's head ref to release branch/tag | -| PR merged | Skip on future runs | -| PR closed | Don't reopen, log and move on | - -### Schedule and transitions - -- Runs daily (~9am Pacific) -- Previews ship monthly, Feb–Oct (typically patch Tuesday) -- RC1 ~September, RC2 ~October, GA November -- Only does meaningful work when VMR state has changed -- Each run re-validates all refs (tags appear, branches are created, main bumps) - -### Safe outputs - -The only ways the workflow can modify state: - -- `create-pull-request` — create new release notes PRs (max 5, for multiple milestones) -- `push-to-pull-request-branch` — update existing PR branches (max 5) -- `add-comment` — comment on PRs with updates, replies to feedback, questions (max 20) - -## Output files - -Each release milestone produces these files in `release-notes/{major.minor}/preview/{previewN}/`: - -| File | Source | Description | -| --------------- | ------------------- | ------------------------------------------------------- | -| `changes.json` | `generate-changes` | Every PR that shipped — comprehensive, machine-readable | -| `features.json` | `generate-features` | Same schema plus optional scoring and editorial hints | -| `README.md` | `release-notes` | Index/TOC linking to component files | -| `libraries.md` | `release-notes` | System.\* BCL APIs (from `dotnet/runtime`) | -| `runtime.md` | `release-notes` | CoreCLR, Mono, GC, JIT (from `dotnet/runtime`) | -| `aspnetcore.md` | `release-notes` | ASP.NET Core, Blazor, SignalR | -| `sdk.md` | `release-notes` | CLI, project system, templating | -| `efcore.md` | `release-notes` | Entity Framework Core | -| `csharp.md` | `release-notes` | C# language features | -| `fsharp.md` | `release-notes` | F# language and compiler | -| `winforms.md` | `release-notes` | Windows Forms | -| `wpf.md` | `release-notes` | WPF | -| `msbuild.md` | `release-notes` | MSBuild | -| `nuget.md` | `release-notes` | NuGet client | - -Components with no noteworthy changes get a minimal stub file. - -## What's out of scope - -- **MAUI** (`dotnet/maui`) — not in the VMR, not in `source-manifest.json` -- **Container images** (`dotnet/dotnet-docker`) — not in the VMR -- **Non-dotnet-org repos** — repos under other GitHub orgs (e.g., `microsoft/vstest`) are skipped for now due to SAML token scope. Only `dotnet` org repos are queried. This can be expanded later. -- **Servicing releases** — separate process, handled by existing automation -- **Security advisories** — the tool can cross-reference CVEs but the agent doesn't author security bulletins - -## Design decisions - -### Why source-manifest.json instead of VMR git log? - -The VMR git log contains codeflow sync commits, dependency updates, and merge commits that are noisy and hard to trace back to meaningful PRs. `source-manifest.json` gives clean per-component commit ranges that map directly to GitHub compare API queries. - -### Why a separate tool instead of agent-only? - -The data collection step (manifest diff → PR enumeration) is mechanical and deterministic. Making it a standalone tool means: - -- It can be tested independently -- Output is reproducible (same refs → same `changes.json`) -- The agent gets structured input instead of having to make dozens of API calls itself -- Humans can run it locally to inspect what shipped - -### Why goal-oriented reference docs instead of procedural scripts? - -Goal-oriented docs let the agent adapt to unexpected situations (e.g., a component that reorganized its repo structure). A procedural approach would limit the agent to mechanically execute a rigid and brittle pipeline. - -### Why one PR per preview? - -Each preview is a coherent release milestone with its own set of features. Maintaining one long-lived branch per preview allows: - -- Humans to edit the branch directly -- Incremental improvement as the preview matures -- Clear history of how the notes evolved -- Easy review before the preview ships - -## Open questions - -1. **Conflict resolution heuristics** — when the agent and a human both changed the same section between runs, the human wins. But how granular is "a section"? Need to define this precisely (per-heading? per-file?). -2. **Feature grouping and schema growth** — some notable features span multiple PRs. `features.json` starts schema-compatible with `changes.json`, but may need optional grouping fields over time. -3. **RC/GA milestone naming** — the multi-milestone logic assumes `previewN` naming. RC and GA milestones use different naming (`rc1`, `rc2`, `ga`). The workflow needs to handle the transition gracefully. diff --git a/.github/skills/release-notes/SKILL.md b/.github/skills/release-notes/SKILL.md index 964e14a235..a0728c2dfb 100644 --- a/.github/skills/release-notes/SKILL.md +++ b/.github/skills/release-notes/SKILL.md @@ -19,10 +19,6 @@ This skill is the **editorial writing stage** of the pipeline. It turns a scored 5. `review-release-notes` runs a final multi-model editorial QA pass against the scoring rubric and examples 6. Output is one PR per release milestone in dotnet/core, maintained incrementally -## Design - -- [DESIGN.md](DESIGN.md) — architecture, rationale, and how all the pieces connect - ## Reference documents - [quality-bar.md](references/quality-bar.md) — what good release notes look like diff --git a/.github/skills/release-notes/STATUS.md b/.github/skills/release-notes/STATUS.md deleted file mode 100644 index 516d26fb7a..0000000000 --- a/.github/skills/release-notes/STATUS.md +++ /dev/null @@ -1,104 +0,0 @@ -# Release Notes Skill Status - -Current state of the release-notes skill split, scoring rubric, and Preview 2/3 evaluation work as of 2026-04-06. - -## Branch - -- Working branch: `release-notes-agentic-workflow` - -## Pipeline shape now - -The monolithic release-notes flow has been split into reusable stages: - -1. `generate-changes` — produce authoritative `changes.json` from VMR refs -2. `api-diff` / `dotnet-inspect` — verify public API stories and catch reverts -3. `generate-features` — produce scored `features.json` -4. `editorial-scoring` — shared rubric used by generator and reviewer -5. `review-release-notes` — critique the scored shortlist and the draft notes -6. `release-notes` — write the final markdown - -## Schema decisions - -`features.json` stays schema-compatible with `changes.json`, with optional enrichment fields: - -- `score` -- `score_reason` -- `score_breakdown` -- `breaking_changes` - -`breaking_changes` is separate from `score`: - -- a high score means broad reader value -- `breaking_changes: true` means some users may need to react, even if the item is narrow -- low-score + `breaking_changes: true` should usually become a short end-of-notes callout, not a headline feature section - -## Scoring and editorial calibration added - -The shared rubric now encodes these rules: - -- use the reader-centric `10 / 8 / 6 / 4 / 2 / 0` scale with the **80/20** audience filter -- compare new functionality to familiar tools when that helps readers place it quickly - - example: `dotnet run -e FOO=BAR` can be framed as Docker-style env-var injection -- score down features with **stacked audience gates** - - if a user must care about A, then B, then be willing to do C, the audience shrinks at each step -- score down thinly described items with internal-runtime cues like **cDAC** -- if the story depends on a **new public API**, we should be able to identify that API via API diff / `dotnet-inspect` -- if an "existing" feature is not part of the model's normal customer-facing understanding and is not easy to find in Learn docs, default to about a `1` -- bug fixes for obscure, unannounced, or low-demand features should usually stay around `1` -- old, internally filed, low-engagement bugs are evidence of low urgency, not hidden headline value -- several related low-score items can still justify **one grouped writeup** - - examples: Roslyn **Unsafe evolution** and runtime **`[browser]`** improvements - -## Preview 2 comparison result - -A fresh Preview 2 run was generated from VMR tags and compared to the human-written notes. - -Result: - -- roughly **B+** -- strong overlap on the main human-selected items -- useful gaps identified: - - some human-covered areas were underplayed or missed - - MAUI and containers sit outside the VMR-driven source path and need separate sourcing -- useful possible omissions surfaced as well, including some Roslyn, NuGet, and MSBuild items - -## Preview 3 rerun result - -Fresh rerun worktree: - -- `/tmp/core-preview3-rerun-79OjKk/release-notes/11.0/preview/preview3/features.json` - -Rerun summary: - -- `2,275` changes scored -- `5` scored `7+` -- `21` scored `5+` -- `17` flagged `breaking_changes` - -Highest-value items in the rerun included: - -- `dotnet run -e` environment-variable injection -- runtime-async dropping `RequiresPreviewFeatures` -- System.Text.Json naming/ignore additions -- file-based app `#:include` -- OpenAPI 3.2 -- `RegexOptions.AnyNewLine` - -Clusters preserved in the rerun: - -- runtime-async -- System.Text.Json -- Roslyn Unsafe evolution -- browser / WebCIL / WASM runtime improvements - -Notable low-score `breaking_changes` examples: - -- EF Core `JsonExists` -> `JsonPathExists` -- EF Core migrations behavior changes -- BackgroundService exception propagation -- Zstandard API move -- certificate-download default changes - -## Recommended next step - -Review the rerun `features.json` above with `review-release-notes`, then decide whether the current rubric is strong enough to generate markdown directly or needs one more tightening pass. From f56f9b566adb6aa10f3864b1e555a411d8678876 Mon Sep 17 00:00:00 2001 From: Rich Lander <2608468+richlander@users.noreply.github.com> Date: Mon, 6 Apr 2026 16:21:55 -0700 Subject: [PATCH 61/65] Update .github/workflows/release-notes.md Co-authored-by: Jeff Handley --- .github/workflows/release-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index aaa05532ef..595cfe85b8 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -16,7 +16,7 @@ network: safe-outputs: create-pull-request: title-prefix: "[release-notes] " - labels: [release-notes, automated] + labels: [area-release-notes, automation] draft: true max: 5 push-to-pull-request-branch: From 0b545af6b7f7857760f4050dd923a7d005f9ec46 Mon Sep 17 00:00:00 2001 From: Rich Lander <2608468+richlander@users.noreply.github.com> Date: Mon, 6 Apr 2026 16:22:07 -0700 Subject: [PATCH 62/65] Update .github/workflows/release-notes.md Co-authored-by: Jeff Handley --- .github/workflows/release-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index 595cfe85b8..9eb7fa645a 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -21,7 +21,7 @@ safe-outputs: max: 5 push-to-pull-request-branch: title-prefix: "[release-notes] " - labels: [release-notes, automated] + labels: [area-release-notes, automation] max: 5 add-comment: max: 20 From 9faac3b4b8841f6ed4f0237b0cd111b19314aac8 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Mon, 6 Apr 2026 16:29:35 -0700 Subject: [PATCH 63/65] Deduplicate editorial rubric guidance Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/editorial-scoring/SKILL.md | 4 +++ .../references/editorial-rules.md | 2 +- .../references/feature-scoring.md | 32 +++++-------------- .../release-notes/references/quality-bar.md | 8 ++--- .github/skills/review-release-notes/SKILL.md | 23 ++++--------- 5 files changed, 24 insertions(+), 45 deletions(-) diff --git a/.github/skills/editorial-scoring/SKILL.md b/.github/skills/editorial-scoring/SKILL.md index 487973473c..a592f9cd79 100644 --- a/.github/skills/editorial-scoring/SKILL.md +++ b/.github/skills/editorial-scoring/SKILL.md @@ -17,6 +17,10 @@ This skill is intentionally **rubric-focused, not task-focused**. It does not ge - `release-notes` — uses the resulting cut to decide what gets written up - future blog/docs workflows — can reuse the same rubric with different thresholds +This is the **canonical home** for the shared scoring scale and 80/20 audience +filter. Other release-notes docs should point back here instead of restating the +rubric in full. + ## Core question Score from the perspective of a developer upgrading to the new release — not from the perspective of the engineer who implemented the feature. diff --git a/.github/skills/release-notes/references/editorial-rules.md b/.github/skills/release-notes/references/editorial-rules.md index 45546e4ac6..126418088f 100644 --- a/.github/skills/release-notes/references/editorial-rules.md +++ b/.github/skills/release-notes/references/editorial-rules.md @@ -61,7 +61,7 @@ Tone, attribution, and content guidelines for .NET release notes. ## What to include -- **The 20/80 rule** — at least 20% of readers need to care about a feature. Write so the other 80% understand why those 20% care and why they might, too. A feature that only matters to a narrow audience can still earn its place if the writeup makes the value legible to everyone. +- **Shared audience filter** — apply the 80/20 rule from `editorial-scoring`; don't redefine a competing threshold here. Keep narrower items only when the broader audience can still see why they matter. - **The two-sentence test** — if you can only write two sentences about a feature, it's probably an engineering fix, not a feature. Cut it. A community contribution or breaking change can lift a borderline entry, but "fixed an internal bug that happened to be visible" is not a feature. - **Headlines should convey value** — a heading like "GC regions on macOS" doesn't tell the reader whether this is good or bad. Prefer headings that hint at the benefit: "GC regions enabled on macOS" or "Server GC memory model now available on macOS." - **TODO for borderline entries** — when a feature might deserve inclusion but you lack data to justify it (benchmark numbers, real-world impact, user demand), keep the entry but add an HTML `` comment asking for the missing information. This is better than silently including a vague claim or silently cutting something that might matter. The TODO should state what's needed and link to the PR where the data might live. diff --git a/.github/skills/release-notes/references/feature-scoring.md b/.github/skills/release-notes/references/feature-scoring.md index b1ba2f3cea..83b7f3353e 100644 --- a/.github/skills/release-notes/references/feature-scoring.md +++ b/.github/skills/release-notes/references/feature-scoring.md @@ -1,31 +1,15 @@ # Feature Scoring -Use optional `score` fields in `features.json` to rank which shipped changes are worth turning into release notes, docs, or blog content. +Use optional `score` fields in `features.json` to rank which shipped changes are +worth turning into release notes, docs, or blog content. -The score is a **decision aid**, not a hard rule. Editorial judgment still matters. +The score is a **decision aid**, not a hard rule. Editorial judgment still +matters. -## Reader-centric scale - -Use a consistent numeric scale within each file. The current default is **0-10**, where higher means "more worth documenting." - -Score from the perspective of a reader upgrading to the new release: - -| Score | Reader reaction | Typical editorial outcome | -| ----- | -------------------------------------------------------------------------------- | -------------------------------------------------------- | -| `10` | "This is the first feature I'll enable or test." | Lead story; blog, docs, and release notes | -| `8+` | "I'm going to use this when I upgrade." | Strong release-note feature; maybe docs | -| `6+` | "I'm glad I know about this. It will likely come in handy." | Good release-note material, often grouped | -| `4+` | "I can see how someone could use this. I'll look up the docs if I ever need it." | Optional mention, grouping candidate, or short paragraph | -| `2+` | "This one is a mystery to me." | Usually skip unless it becomes clearer with explanation | -| `0` | "This is total gobbledygook — internal .NET engineering work." | Never document outside internal notes | - -## Audience filter: the 80/20 rule - -Default to features that make sense to roughly **80% of the user base**. - -A feature that mainly helps the other **20%** can still score well, but only if the remaining 80% can still recognize why it matters and think: **"Not for me, but I'm glad it's there for the people who need it."** - -Foundational shifts like `Span` can still deserve high scores even before broad adoption, but they need extra explanation so the significance is legible to non-specialists. +Use [`../../editorial-scoring/SKILL.md`](../../editorial-scoring/SKILL.md) as +the **canonical source** for the shared reader-centric scale, thresholds, and +80/20 audience filter. This file adds the more specific heuristics that tend to +matter during .NET release-note triage. ## What increases a score diff --git a/.github/skills/release-notes/references/quality-bar.md b/.github/skills/release-notes/references/quality-bar.md index 5c585ef3a9..881edbe47b 100644 --- a/.github/skills/release-notes/references/quality-bar.md +++ b/.github/skills/release-notes/references/quality-bar.md @@ -37,10 +37,10 @@ Include a feature if it gives users something **new to try**, something that **w - Preview-to-preview fixes for community-reported issues - Features that most readers can immediately understand the value of -Use the **80/20 rule**: - -- Default to features that make sense to about **80% of the user base** -- Keep a more specialized feature only when the other 80% will still think, "Not for me, but I'm glad this exists" +Apply the shared **80/20 audience filter** from +[`../../editorial-scoring/SKILL.md`](../../editorial-scoring/SKILL.md): +prioritize features most readers can immediately understand, and keep narrower +items only when the broader audience can still appreciate why they matter. ## What to exclude from markdown diff --git a/.github/skills/review-release-notes/SKILL.md b/.github/skills/review-release-notes/SKILL.md index 42978c9f9c..6cdfda1b79 100644 --- a/.github/skills/review-release-notes/SKILL.md +++ b/.github/skills/review-release-notes/SKILL.md @@ -43,24 +43,15 @@ If `features.json` does not exist yet, infer the current implicit scoring from: ## Core review questions -### 1. Reader-value test +Use the shared rubric from [`../editorial-scoring/SKILL.md`](../editorial-scoring/SKILL.md) +rather than inventing a new one here. In particular: -For each selected feature, ask: +- keep the same reader-centric `10 / 8 / 6 / 4 / 2 / 0` scale +- apply the same 80/20 audience filter +- compare the draft against the examples to see whether it still teaches instead + of merely enumerating -- **10** — Is this the first thing a reader will want to try? -- **8+** — Will many readers likely use this when they upgrade? -- **6+** — Will readers be glad they learned about it now? -- **4+** — Is it understandable but niche enough that docs can carry the rest? -- **2+** — Will most readers find it mysterious? -- **0** — Is this internal gobbledygook that mainly matters to the engineers who built it? - -### 2. The 80/20 rule - -Default to features that make sense to about **80% of the audience**. - -Keep a more specialized feature only when the other 80% can still appreciate why it matters, even if they won't use it directly. - -### 3. Example check +### Example check Compare the draft against the component examples: From d9655edd4077e195bf09ee7faa9a0b2271d92028 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Mon, 6 Apr 2026 16:34:48 -0700 Subject: [PATCH 64/65] Clarify preview and GA api-diff labels Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/api-diff/SKILL.md | 4 +-- .github/workflows/release-notes.lock.yml | 42 ++++++++++++------------ .github/workflows/release-notes.md | 2 +- release-notes/RunApiDiff.md | 19 +++++++---- 4 files changed, 36 insertions(+), 31 deletions(-) diff --git a/.github/skills/api-diff/SKILL.md b/.github/skills/api-diff/SKILL.md index 538737b47f..637a026558 100644 --- a/.github/skills/api-diff/SKILL.md +++ b/.github/skills/api-diff/SKILL.md @@ -73,8 +73,8 @@ When the user wants the markdown-ready, repo-shaped API diff output, use `releas | ".NET 10 RC 2 vs .NET 10 GA" | `-PreviousMajorMinor 10.0 -PreviousPrereleaseLabel rc.2 -CurrentMajorMinor 10.0` | | "10.0.0-preview.7.25380.108 to 10.0.0-rc.1.25451.107" | `-PreviousVersion "10.0.0-preview.7.25380.108" -CurrentVersion "10.0.0-rc.1.25451.107"` | -- **GA** or no qualifier -> omit the PrereleaseLabel parameter -- **Preview N** / **previewN** -> `-PrereleaseLabel preview.N` +- **GA** or no qualifier -> omit the `PrereleaseLabel` parameter for `RunApiDiff.ps1`; if a downstream api-diff release label is needed, use `ga` +- **Preview N** / **previewN** -> `-PrereleaseLabel preview.N` (for example, `preview.4`, not `preview4`) - **RC N** / **rcN** -> `-PrereleaseLabel rc.N` - **netX.Y-previewN** (TFM format) -> `-MajorMinor X.Y -PrereleaseLabel preview.N` - Full NuGet version strings -> use `-PreviousVersion` / `-CurrentVersion` directly diff --git a/.github/workflows/release-notes.lock.yml b/.github/workflows/release-notes.lock.yml index d5496c4201..59584556d8 100644 --- a/.github/workflows/release-notes.lock.yml +++ b/.github/workflows/release-notes.lock.yml @@ -21,7 +21,7 @@ # For more information: https://github.github.com/gh-aw/introduction/overview/ # # -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"258a65cef4805b1365cd65928059c0f570c9ced88a7d855afb03b906893066bf","compiler_version":"v0.65.1","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"49a735ce89ffcd0b73da9dda5c5b390b23e20d2fbb48ab92997d03cf56137193","compiler_version":"v0.65.1","strict":true,"agent_id":"copilot"} name: ".NET Release Notes Maintenance" "on": @@ -58,7 +58,7 @@ name: ".NET Release Notes Maintenance" required: false type: string milestone: - description: Target milestone (e.g., preview4). Leave empty for auto-detection. + description: Target milestone directory (e.g., preview4). Package/API-diff labels use preview.4; GA is ga/omitted depending on the tool. Leave empty for auto-detection. required: false type: string @@ -164,20 +164,20 @@ jobs: run: | bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh { - cat << 'GH_AW_PROMPT_df7ec18eb0474082_EOF' + cat << 'GH_AW_PROMPT_34e76750cb77576c_EOF' - GH_AW_PROMPT_df7ec18eb0474082_EOF + GH_AW_PROMPT_34e76750cb77576c_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_df7ec18eb0474082_EOF' + cat << 'GH_AW_PROMPT_34e76750cb77576c_EOF' Tools: add_comment(max:20), create_pull_request(max:5), push_to_pull_request_branch(max:5), missing_tool, missing_data, noop - GH_AW_PROMPT_df7ec18eb0474082_EOF + GH_AW_PROMPT_34e76750cb77576c_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md" - cat << 'GH_AW_PROMPT_df7ec18eb0474082_EOF' + cat << 'GH_AW_PROMPT_34e76750cb77576c_EOF' The following GitHub context information is available for this workflow: @@ -207,12 +207,12 @@ jobs: {{/if}} - GH_AW_PROMPT_df7ec18eb0474082_EOF + GH_AW_PROMPT_34e76750cb77576c_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_df7ec18eb0474082_EOF' + cat << 'GH_AW_PROMPT_34e76750cb77576c_EOF' {{#runtime-import .github/workflows/release-notes.md}} - GH_AW_PROMPT_df7ec18eb0474082_EOF + GH_AW_PROMPT_34e76750cb77576c_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -374,23 +374,23 @@ jobs: mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_a1a70c854ce3d3f5_EOF' - {"add_comment":{"max":20,"target":"*"},"create_pull_request":{"draft":true,"labels":["release-notes","automated"],"max":5,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS"],"protected_path_prefixes":[".github/",".agents/"],"title_prefix":"[release-notes] "},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"if_no_changes":"warn","labels":["release-notes","automated"],"max":5,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS"],"protected_path_prefixes":[".github/",".agents/"],"title_prefix":"[release-notes] "}} - GH_AW_SAFE_OUTPUTS_CONFIG_a1a70c854ce3d3f5_EOF + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_5bc8fc1a8a382f8b_EOF' + {"add_comment":{"max":20,"target":"*"},"create_pull_request":{"draft":true,"labels":["area-release-notes","automation"],"max":5,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS"],"protected_path_prefixes":[".github/",".agents/"],"title_prefix":"[release-notes] "},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"if_no_changes":"warn","labels":["area-release-notes","automation"],"max":5,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS"],"protected_path_prefixes":[".github/",".agents/"],"title_prefix":"[release-notes] "}} + GH_AW_SAFE_OUTPUTS_CONFIG_5bc8fc1a8a382f8b_EOF - name: Write Safe Outputs Tools run: | - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_1a3d0610c1d608e5_EOF' + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_145ed235d2c9bc9a_EOF' { "description_suffixes": { "add_comment": " CONSTRAINTS: Maximum 20 comment(s) can be added. Target: *.", - "create_pull_request": " CONSTRAINTS: Maximum 5 pull request(s) can be created. Title will be prefixed with \"[release-notes] \". Labels [\"release-notes\" \"automated\"] will be automatically added. PRs will be created as drafts.", + "create_pull_request": " CONSTRAINTS: Maximum 5 pull request(s) can be created. Title will be prefixed with \"[release-notes] \". Labels [\"area-release-notes\" \"automation\"] will be automatically added. PRs will be created as drafts.", "push_to_pull_request_branch": " CONSTRAINTS: Maximum 5 push(es) can be made. The target pull request title must start with \"[release-notes] \"." }, "repo_params": {}, "dynamic_tools": [] } - GH_AW_SAFE_OUTPUTS_TOOLS_META_1a3d0610c1d608e5_EOF - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_a75494a945553f1b_EOF' + GH_AW_SAFE_OUTPUTS_TOOLS_META_145ed235d2c9bc9a_EOF + cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_9f3b5600d910518f_EOF' { "add_comment": { "defaultMax": 1, @@ -524,7 +524,7 @@ jobs: } } } - GH_AW_SAFE_OUTPUTS_VALIDATION_a75494a945553f1b_EOF + GH_AW_SAFE_OUTPUTS_VALIDATION_9f3b5600d910518f_EOF node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs - name: Generate Safe Outputs MCP Server Config id: safe-outputs-config @@ -592,7 +592,7 @@ jobs: export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.10' mkdir -p /home/runner/.copilot - cat << GH_AW_MCP_CONFIG_d030b8fe358a9b4a_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh + cat << GH_AW_MCP_CONFIG_a482666cb00b2b3f_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh { "mcpServers": { "github": { @@ -633,7 +633,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_d030b8fe358a9b4a_EOF + GH_AW_MCP_CONFIG_a482666cb00b2b3f_EOF - name: Download activation artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -1269,7 +1269,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":20,\"target\":\"*\"},\"create_pull_request\":{\"draft\":true,\"labels\":[\"release-notes\",\"automated\"],\"max\":5,\"max_patch_size\":1024,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"AGENTS.md\"],\"protected_path_prefixes\":[\".github/\",\".agents/\"],\"title_prefix\":\"[release-notes] \"},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"labels\":[\"release-notes\",\"automated\"],\"max\":5,\"max_patch_size\":1024,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"AGENTS.md\"],\"protected_path_prefixes\":[\".github/\",\".agents/\"],\"title_prefix\":\"[release-notes] \"}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":20,\"target\":\"*\"},\"create_pull_request\":{\"draft\":true,\"labels\":[\"area-release-notes\",\"automation\"],\"max\":5,\"max_patch_size\":1024,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"AGENTS.md\"],\"protected_path_prefixes\":[\".github/\",\".agents/\"],\"title_prefix\":\"[release-notes] \"},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"labels\":[\"area-release-notes\",\"automation\"],\"max\":5,\"max_patch_size\":1024,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"AGENTS.md\"],\"protected_path_prefixes\":[\".github/\",\".agents/\"],\"title_prefix\":\"[release-notes] \"}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index 9eb7fa645a..49607ac73f 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -41,7 +41,7 @@ on: workflow_dispatch: inputs: milestone: - description: "Target milestone (e.g., preview4). Leave empty for auto-detection." + description: "Target milestone directory (e.g., preview4). Package/API-diff labels use preview.4; GA is ga/omitted depending on the tool. Leave empty for auto-detection." required: false type: string diff --git a/release-notes/RunApiDiff.md b/release-notes/RunApiDiff.md index 9a874e7f7a..e0aac975ab 100644 --- a/release-notes/RunApiDiff.md +++ b/release-notes/RunApiDiff.md @@ -26,25 +26,25 @@ When run with no arguments, the script infers the next version to diff by scanni By default the script assumes a diff will be produced for the next preview. When no version information is provided, the script scans existing `api-diff` folders in the repository to find the latest version and infers the next one in the progression (preview.1 → preview.2 → ... → preview.7 → rc.1 → rc.2 → GA → next major preview.1). When `PreviousVersion` or `CurrentVersion` is provided, the `MajorMinor` and `PrereleaseLabel` values are extracted from it automatically. | Parameter | Description | Default | -|---|---|---| +| --- | --- | --- | | `PreviousVersion` | Exact package version for the "before" comparison (e.g., `10.0.0-preview.7.25380.108`). MajorMinor and PrereleaseLabel are extracted automatically. | *(empty — inferred or searched)* | | `CurrentVersion` | Exact package version for the "after" comparison (e.g., `10.0.0-rc.1.25451.107`). MajorMinor and PrereleaseLabel are extracted automatically. | *(empty — inferred or searched)* | | `PreviousMajorMinor` | The "before" .NET major.minor version (e.g., `10.0`) | Inferred from api-diffs, extracted from `PreviousVersion`, or discovered from `PreviousNuGetFeed` | -| `PreviousPrereleaseLabel` | Prerelease label for the "before" version (e.g., `preview.7`, `rc.1`). Omit for GA. | Inferred from api-diffs, extracted from `PreviousVersion`, or discovered from `PreviousNuGetFeed` | +| `PreviousPrereleaseLabel` | Prerelease label for the "before" version (e.g., `preview.4`, `preview.7`, `rc.1`). Omit for GA when calling `RunApiDiff.ps1`; the api-diff release label itself is `ga`. | Inferred from api-diffs, extracted from `PreviousVersion`, or discovered from `PreviousNuGetFeed` | | `CurrentMajorMinor` | The "after" .NET major.minor version (e.g., `10.0`) | Inferred from api-diffs, extracted from `CurrentVersion`, or discovered from `CurrentNuGetFeed` | -| `CurrentPrereleaseLabel` | Prerelease label for the "after" version (e.g., `preview.7`, `rc.1`). Omit for GA. | Inferred from api-diffs, extracted from `CurrentVersion`, or discovered from `CurrentNuGetFeed` | +| `CurrentPrereleaseLabel` | Prerelease label for the "after" version (e.g., `preview.4`, `preview.7`, `rc.1`). Omit for GA when calling `RunApiDiff.ps1`; the api-diff release label itself is `ga`. | Inferred from api-diffs, extracted from `CurrentVersion`, or discovered from `CurrentNuGetFeed` | ### Feed Parameters | Parameter | Description | Default | -|---|---|---| +| --- | --- | --- | | `CurrentNuGetFeed` | NuGet feed URL for downloading "after" packages | `https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json` | | `PreviousNuGetFeed` | NuGet feed URL for downloading "before" packages | `https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json` | ### Path Parameters | Parameter | Description | Default | -|---|---|---| +| --- | --- | --- | | `CoreRepo` | Path to your local clone of the dotnet/core repo | Git repo root relative to the script | | `TmpFolder` | Working directory for downloaded and extracted packages | Auto-created temp directory | | `AttributesToExcludeFilePath` | Path to attributes exclusion file | `ApiDiffAttributesToExclude.txt` (same folder as script) | @@ -53,7 +53,7 @@ By default the script assumes a diff will be produced for the next preview. When ### Switches | Parameter | Description | -|---|---| +| --- | --- | | `ExcludeNetCore` | Skip the Microsoft.NETCore.App comparison | | `ExcludeAspNetCore` | Skip the Microsoft.AspNetCore.App comparison | | `ExcludeWindowsDesktop` | Skip the Microsoft.WindowsDesktop.App comparison | @@ -67,13 +67,18 @@ By default the script assumes a diff will be produced for the next preview. When .\RunApiDiff.ps1 # Specify only the current version; previous is inferred from existing api-diffs -.\RunApiDiff.ps1 -CurrentMajorMinor 11.0 -CurrentPrereleaseLabel preview.2 +.\RunApiDiff.ps1 -CurrentMajorMinor 11.0 -CurrentPrereleaseLabel preview.4 # Specify both versions explicitly .\RunApiDiff.ps1 ` -PreviousMajorMinor 10.0 -PreviousPrereleaseLabel preview.7 ` -CurrentMajorMinor 10.0 -CurrentPrereleaseLabel rc.1 +# Compare RC to GA; omit CurrentPrereleaseLabel for GA +.\RunApiDiff.ps1 ` + -PreviousMajorMinor 10.0 -PreviousPrereleaseLabel rc.2 ` + -CurrentMajorMinor 10.0 + # Use exact NuGet package versions (MajorMinor and PrereleaseLabel are extracted automatically) .\RunApiDiff.ps1 ` -PreviousVersion "10.0.0-preview.7.25380.108" ` From 11fcd8f3e046c159f7036bb196739cf9a1ff2187 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Mon, 6 Apr 2026 16:55:20 -0700 Subject: [PATCH 65/65] Rename maintainer release tool to release-notes-gen Update the workflow docs, repo skills, and locked workflow permissions to use the renamed maintainer CLI and package name while keeping dotnet-release for the public graph-query tool. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/generate-changes/SKILL.md | 10 ++-- .../references/api-verification.md | 4 +- .../references/changes-schema.md | 2 +- .../release-notes/references/quality-bar.md | 2 +- .../release-notes/references/vmr-structure.md | 2 +- .../skills/update-distro-packages/SKILL.md | 16 +++---- .github/skills/update-os-packages/SKILL.md | 18 ++++---- .../references/verify-output-example.md | 2 +- .github/skills/update-release-graph/SKILL.md | 46 +++++++++---------- .github/skills/update-supported-os/SKILL.md | 20 ++++---- .github/skills/verify-releases/SKILL.md | 42 ++++++++--------- .../references/verify-output-example.md | 8 ++-- .github/workflows/release-notes.lock.yml | 5 +- .github/workflows/release-notes.md | 8 ++-- 14 files changed, 92 insertions(+), 93 deletions(-) diff --git a/.github/skills/generate-changes/SKILL.md b/.github/skills/generate-changes/SKILL.md index b7b19040c2..80ad551a98 100644 --- a/.github/skills/generate-changes/SKILL.md +++ b/.github/skills/generate-changes/SKILL.md @@ -2,7 +2,7 @@ name: generate-changes description: > Generate `changes.json` for a .NET release milestone by selecting the correct - VMR base/head refs and running `dotnet-release generate changes`. Handles + VMR base/head refs and running `release-notes-gen generate changes`. Handles preview-only multi-branch targeting (`main` vs release branches vs tags) and emits the authoritative manifest of what shipped. DO NOT USE FOR: API verification/diffs (use api-diff), feature scoring (use generate-features), @@ -17,7 +17,7 @@ This is the **VMR-aware data acquisition stage** of the release notes pipeline: 1. Determine which milestone(s) are active 2. Resolve the correct `--base` and `--head` refs -3. Run `dotnet-release generate changes` +3. Run `release-notes-gen generate changes` 4. Write `changes.json` into the correct `release-notes/` folder ## When to use @@ -90,7 +90,7 @@ For each active milestone: ### 4. Generate the file ```bash -dotnet-release generate changes \ +release-notes-gen generate changes \ --base \ --head \ --version "" \ @@ -103,7 +103,7 @@ Examples: ```bash # Preview milestone -dotnet-release generate changes ~/git/dotnet \ +release-notes-gen generate changes ~/git/dotnet \ --base v11.0.0-preview.3.26210.100 \ --head origin/release/11.0.1xx-preview4 \ --version "11.0.0-preview.4" \ @@ -111,7 +111,7 @@ dotnet-release generate changes ~/git/dotnet \ --output release-notes/11.0/preview/preview4/changes.json # GA/patch milestone -dotnet-release generate changes ~/git/dotnet \ +release-notes-gen generate changes ~/git/dotnet \ --base v10.0.7 \ --head v10.0.8 \ --version "10.0.8" \ diff --git a/.github/skills/release-notes/references/api-verification.md b/.github/skills/release-notes/references/api-verification.md index 716e4f8b26..4d2a480b49 100644 --- a/.github/skills/release-notes/references/api-verification.md +++ b/.github/skills/release-notes/references/api-verification.md @@ -30,10 +30,10 @@ When writing release notes for Preview N, the locally installed SDK is often Pre ### Step 1: Generate build metadata -`dotnet-release generate build-metadata` reads the VMR's version info and queries the nightly NuGet feed to find the correct package versions: +`release-notes-gen generate build-metadata` reads the VMR's version info and queries the nightly NuGet feed to find the correct package versions: ```bash -dotnet-release generate build-metadata ~/git/dotnet \ +release-notes-gen generate build-metadata ~/git/dotnet \ --base v11.0.0-preview.2.26159.112 \ --head origin/release/11.0.1xx-preview3 \ --output build-metadata.json diff --git a/.github/skills/release-notes/references/changes-schema.md b/.github/skills/release-notes/references/changes-schema.md index 9cfd6a75ea..4168fe9ac2 100644 --- a/.github/skills/release-notes/references/changes-schema.md +++ b/.github/skills/release-notes/references/changes-schema.md @@ -1,6 +1,6 @@ # Shared Schema for `changes.json` and `features.json` -Reference for the `changes.json` file produced by `dotnet-release generate changes`, and for the derived `features.json` file used by downstream editorial skills. One file per release milestone. +Reference for the `changes.json` file produced by `release-notes-gen generate changes`, and for the derived `features.json` file used by downstream editorial skills. One file per release milestone. ## Overview diff --git a/.github/skills/release-notes/references/quality-bar.md b/.github/skills/release-notes/references/quality-bar.md index 881edbe47b..5920c05076 100644 --- a/.github/skills/release-notes/references/quality-bar.md +++ b/.github/skills/release-notes/references/quality-bar.md @@ -6,7 +6,7 @@ What good .NET release notes look like. This is the north star — when in doubt ### changes.json — comprehensive and mechanical -Every PR that shipped gets an entry. No editorial judgment — if the `dotnet-release generate changes` tool found it in the source-manifest diff, it goes in. This is the machine-readable record of what shipped. +Every PR that shipped gets an entry. No editorial judgment — if the `release-notes-gen generate changes` tool found it in the source-manifest diff, it goes in. This is the machine-readable record of what shipped. Quality criteria: diff --git a/.github/skills/release-notes/references/vmr-structure.md b/.github/skills/release-notes/references/vmr-structure.md index fa16a48375..c583885f66 100644 --- a/.github/skills/release-notes/references/vmr-structure.md +++ b/.github/skills/release-notes/references/vmr-structure.md @@ -36,7 +36,7 @@ By comparing `source-manifest.json` at two release points, you get: 3. **Which components didn't change** — these get minimal stubs, no investigation needed 4. **Source repo URLs** — for querying PRs via the GitHub compare API -The `dotnet-release generate changes` tool automates this: it fetches the manifest at both refs, diffs them, queries GitHub for PRs in each commit range, and outputs `changes.json`. +The `release-notes-gen generate changes` tool automates this: it fetches the manifest at both refs, diffs them, queries GitHub for PRs in each commit range, and outputs `changes.json`. ## Branch naming diff --git a/.github/skills/update-distro-packages/SKILL.md b/.github/skills/update-distro-packages/SKILL.md index f0b0c76039..7f151acca7 100644 --- a/.github/skills/update-distro-packages/SKILL.md +++ b/.github/skills/update-distro-packages/SKILL.md @@ -34,24 +34,24 @@ release-notes/{version}/distros/ ## Prerequisites -The `dotnet-release` tool must be installed for markdown generation and package availability queries. +The `release-notes-gen` tool must be installed for markdown generation and package availability queries. The public `dotnet-release` tool is now for browsing release data and CVEs. ```bash # Check if already installed -dotnet-release --version +release-notes-gen --version ``` If not installed: ```bash -dotnet tool install -g Dotnet.Release.Tools \ +dotnet tool install -g ReleaseNotes.Gen \ --add-source https://nuget.pkg.github.com/richlander/index.json ``` If already installed, update to latest: ```bash -dotnet tool update -g Dotnet.Release.Tools \ +dotnet tool update -g ReleaseNotes.Gen \ --add-source https://nuget.pkg.github.com/richlander/index.json ``` @@ -214,7 +214,7 @@ Confirm every Linux distro in `supported-os.json` has a corresponding file. Regenerate `dotnet-dependencies.md` from the JSON files: ```bash -dotnet-release generate dotnet-dependencies {version} release-notes +release-notes-gen generate dotnet-dependencies {version} release-notes ``` This produces `release-notes/{version}/dotnet-dependencies.md` with copy-pasteable install commands for each distro and release. Never hand-edit this file — it is generated from the JSON. @@ -246,7 +246,7 @@ Requires `PKGS_ORG_TOKEN` to be set. ```bash export PKGS_ORG_TOKEN= -dotnet-release query distro-packages --dotnet-version {version} --output /tmp/distro-packages.json +release-notes-gen query distro-packages --dotnet-version {version} --output /tmp/distro-packages.json ``` This queries pkgs.org and supplemental feeds (Ubuntu backports via Launchpad, Homebrew, NixOS) and writes a JSON file with package availability per distro. @@ -400,8 +400,8 @@ Show the user a summary of which distros+releases have packages and from which f After any JSON changes, regenerate both markdown files: ```bash -dotnet-release generate dotnet-dependencies {version} release-notes -dotnet-release generate dotnet-packages {version} release-notes +release-notes-gen generate dotnet-dependencies {version} release-notes +release-notes-gen generate dotnet-packages {version} release-notes ``` - `dotnet-dependencies.md` — what OS packages .NET requires (from dependency data) diff --git a/.github/skills/update-os-packages/SKILL.md b/.github/skills/update-os-packages/SKILL.md index 0a0ec19778..3f1fcea9d8 100644 --- a/.github/skills/update-os-packages/SKILL.md +++ b/.github/skills/update-os-packages/SKILL.md @@ -2,7 +2,7 @@ name: update-os-packages description: > Audit and update os-packages.json/md files that document required Linux - packages for each .NET release. Uses the dotnet-release tool to verify + packages for each .NET release. Uses the release-notes-gen tool to verify package names against distro archives and regenerate markdown. USE FOR: adding packages for new distro versions, fixing incorrect package names, periodic package audits. DO NOT USE FOR: supported-os.json changes (use @@ -26,15 +26,15 @@ The scope of `os-packages.json` is broader than `supported-os.json`. It includes ## Prerequisites -The `dotnet-release` tool must be installed. Packages are published to [GitHub Packages](https://github.com/richlander/dotnet-release/packages). +The `release-notes-gen` tool must be installed. The public `dotnet-release` tool is now for browsing release data and CVEs. Packages are published to [GitHub Packages](https://github.com/richlander/dotnet-release/packages). ```bash # GitHub Packages requires authentication — use a GitHub token (PAT or GITHUB_TOKEN) -dotnet tool install -g Dotnet.Release.Tools \ +dotnet tool install -g ReleaseNotes.Gen \ --add-source https://nuget.pkg.github.com/richlander/index.json # Verify -dotnet-release --help +release-notes-gen --help ``` > **Note:** GitHub Packages requires authentication even for public repositories. If you get a 401 error, configure credentials for the source: @@ -63,17 +63,17 @@ The user provides: Run the verify command for each .NET version to audit: ```bash -dotnet-release verify os-packages release-notes +release-notes-gen verify os-packages release-notes ``` Examples: ```bash # Check 10.0 against local files -dotnet-release verify os-packages 10.0 release-notes +release-notes-gen verify os-packages 10.0 release-notes # Check against live data on GitHub (no local clone needed) -dotnet-release verify os-packages 10.0 +release-notes-gen verify os-packages 10.0 ``` **Interpret the exit code:** @@ -164,7 +164,7 @@ Delete the release object from the `releases` array for that distribution. After updating the JSON, regenerate the markdown file: ```bash -dotnet-release generate os-packages release-notes +release-notes-gen generate os-packages release-notes ``` This overwrites `os-packages.md` with content derived from the updated JSON. @@ -186,7 +186,7 @@ CI runs markdownlint via super-linter. If linting fails, fix the generator or Ma 1. Run verify again to confirm issues are resolved: ```bash - dotnet-release verify os-packages release-notes + release-notes-gen verify os-packages release-notes ``` Expect exit code 0 (or only skipped distros remaining). diff --git a/.github/skills/update-os-packages/references/verify-output-example.md b/.github/skills/update-os-packages/references/verify-output-example.md index 09079259f0..b7a9cbd520 100644 --- a/.github/skills/update-os-packages/references/verify-output-example.md +++ b/.github/skills/update-os-packages/references/verify-output-example.md @@ -1,6 +1,6 @@ # Verify Output Example -Example output from `dotnet-release verify os-packages 10.0 release-notes`: +Example output from `release-notes-gen verify os-packages 10.0 release-notes`: ````markdown # .NET 10.0 — OS Packages Verification diff --git a/.github/skills/update-release-graph/SKILL.md b/.github/skills/update-release-graph/SKILL.md index 6f410aaa5f..057eea3005 100644 --- a/.github/skills/update-release-graph/SKILL.md +++ b/.github/skills/update-release-graph/SKILL.md @@ -60,7 +60,7 @@ release-notes/ ### Graph generators -The `dotnet-release` tool includes four graph generation commands: +The `release-notes-gen` tool includes four graph generation commands: | Command | Generates | From | |---------|-----------|------| @@ -72,7 +72,7 @@ The `dotnet-release` tool includes four graph generation commands: All accept the same arguments: ``` -dotnet-release generate [output-dir] [--url-root ] +release-notes-gen generate [output-dir] [--url-root ] ``` ## When to use @@ -85,16 +85,16 @@ dotnet-release generate [output-dir] [--url-root ] ## Prerequisites -### dotnet-release +### release-notes-gen -The `dotnet-release` tool handles both graph generation and legacy file operations. Packages are published to [GitHub Packages](https://github.com/richlander/dotnet-release/packages). +The `release-notes-gen` tool handles both graph generation and legacy file operations. The public `dotnet-release` tool is for navigating release data and CVEs. Packages are published to [GitHub Packages](https://github.com/richlander/dotnet-release/packages). ```bash -dotnet tool install -g Dotnet.Release.Tools \ +dotnet tool install -g ReleaseNotes.Gen \ --add-source https://nuget.pkg.github.com/richlander/index.json # Verify — should show graph generation commands -dotnet-release --help +release-notes-gen --help ``` > **Note:** GitHub Packages requires authentication even for public repositories. If you get a 401 error, configure credentials for the source: @@ -156,27 +156,27 @@ The simplest approach is `generate indexes` which runs all three generators in s ```bash # Generate all graph files in one shot -dotnet-release generate indexes release-notes +release-notes-gen generate indexes release-notes ``` Or run each generator individually (order matters: version-index → timeline-index → llms-index): ```bash -dotnet-release generate version-index release-notes -dotnet-release generate timeline-index release-notes -dotnet-release generate llms-index release-notes +release-notes-gen generate version-index release-notes +release-notes-gen generate timeline-index release-notes +release-notes-gen generate llms-index release-notes ``` **Custom URL root** (for PR review before merging to release-index): ```bash -dotnet-release generate indexes release-notes --url-root https://raw.githubusercontent.com/dotnet/core/ +release-notes-gen generate indexes release-notes --url-root https://raw.githubusercontent.com/dotnet/core/ ``` **Separate output directory** (to inspect output without overwriting source): ```bash -dotnet-release generate indexes release-notes /tmp/graph-output +release-notes-gen generate indexes release-notes /tmp/graph-output ``` The default URL root is `https://raw.githubusercontent.com/dotnet/core/refs/heads/release-index/release-notes/`. @@ -185,21 +185,21 @@ The default URL root is `https://raw.githubusercontent.com/dotnet/core/refs/head ```bash # Regenerate releases-index.json from releases.json files -dotnet-release generate releases-index release-notes +release-notes-gen generate releases-index release-notes # Regenerate releases.md -dotnet-release generate releases release-notes +release-notes-gen generate releases release-notes ``` ### 4. Validate ```bash # Verify release links and hashes (can take minutes — do not cancel) -dotnet-release verify releases release-notes +release-notes-gen verify releases release-notes # Or for a specific version -dotnet-release verify releases {ver} release-notes +release-notes-gen verify releases {ver} release-notes # Skip hash verification for faster iteration -dotnet-release verify releases release-notes --skip-hash +release-notes-gen verify releases release-notes --skip-hash # Lint generated markdown npx markdownlint --config .github/linters/.markdown-lint.yml release-notes/releases.md @@ -290,7 +290,7 @@ Create the `release.json` in the patch directory. ### 2. Regenerate -Same as the patch release process — run `dotnet-release generate indexes release-notes` → legacy files. +Same as the patch release process — run `release-notes-gen generate indexes release-notes` → legacy files. ### 3. Verify the new version appears @@ -375,8 +375,8 @@ Immutable files use only `prev-*` links (no `next`). Mutable files use `latest-* - Never hand-edit generated files (`index.json`, `manifest.json`, `llms.json`, downloads files, timeline indexes) - `_manifest.json` (with underscore prefix) is the source; `manifest.json` (without prefix) is the generated output - Similarly, `_llms.json` is optional source overrides; `llms.json` is the generated output -- `releases-index.json` (legacy flat format) is generated by `dotnet-release generate releases-index` -- `releases.md` is generated by `dotnet-release generate releases` +- `releases-index.json` (legacy flat format) is generated by `release-notes-gen generate releases-index` +- `releases.md` is generated by `release-notes-gen generate releases` - The generators are idempotent — running them on unchanged source data produces identical output - When running individually, order matters: `version-index` → `timeline-index` → `llms-index`; `generate indexes` handles this automatically - All `_links.*.href` values are absolute URLs; the base URL is controlled by `--url-root` @@ -389,10 +389,10 @@ Immutable files use only `prev-*` links (no `next`). Mutable files use `latest-* |---------|------------| | Hand-editing `index.json` or other generated files | Edit source data and re-run the generators | | Hand-editing `manifest.json` | Edit `_manifest.json` and re-run `VersionIndex` | -| Hand-editing `releases-index.json` | Run `dotnet-release generate releases-index release-notes` | -| Hand-editing `releases.md` | Run `dotnet-release generate releases release-notes` | +| Hand-editing `releases-index.json` | Run `release-notes-gen generate releases-index release-notes` | +| Hand-editing `releases.md` | Run `release-notes-gen generate releases release-notes` | | Running generators in wrong order | Use `generate indexes` (handles order automatically) or run: version-index → timeline-index → llms-index | | Missing `_manifest.json` for a version | The generators warn but fall back to `releases.json`; create `_manifest.json` for accurate lifecycle data | | Missing `release.json` for a patch | Patch detail index will be incomplete; ensure every patch has a `release.json` | -| Editing source data without regenerating | Always run `dotnet-release generate indexes release-notes` after changing source files | +| Editing source data without regenerating | Always run `release-notes-gen generate indexes release-notes` after changing source files | | Forgetting `--url-root` for PR review | Links will use the default release-index branch URL; pass `--url-root` with a commit SHA for verifiable links in PRs | diff --git a/.github/skills/update-supported-os/SKILL.md b/.github/skills/update-supported-os/SKILL.md index e958954780..b6c4c4556f 100644 --- a/.github/skills/update-supported-os/SKILL.md +++ b/.github/skills/update-supported-os/SKILL.md @@ -2,7 +2,7 @@ name: update-supported-os description: > Audit and update supported-os.json/md files to reflect current OS version - support. Uses the dotnet-release tool for automated verification against + support. Uses the release-notes-gen tool for automated verification against upstream lifecycle data and markdown regeneration. USE FOR: adding new OS versions, moving EOL versions to unsupported, periodic support matrix audits. DO NOT USE FOR: os-packages.json changes (use update-os-packages skill), @@ -23,17 +23,17 @@ Audit and update `supported-os.json` files in this repository. These files decla The following tools must be installed: -### dotnet-release +### release-notes-gen -The `dotnet-release` tool is used to verify and generate supported OS files. Packages are published to [GitHub Packages](https://github.com/richlander/dotnet-release/packages). +The `release-notes-gen` tool is used to verify and generate supported OS files. The public `dotnet-release` tool is now for browsing release data and CVEs. Packages are published to [GitHub Packages](https://github.com/richlander/dotnet-release/packages). ```bash # GitHub Packages requires authentication — use a GitHub token (PAT or GITHUB_TOKEN) -dotnet tool install -g Dotnet.Release.Tools \ +dotnet tool install -g ReleaseNotes.Gen \ --add-source https://nuget.pkg.github.com/richlander/index.json # Verify -dotnet-release --help +release-notes-gen --help ``` > **Note:** GitHub Packages requires authentication even for public repositories. If you get a 401 error, configure credentials for the source: @@ -75,17 +75,17 @@ The user provides: Run the verify command for each .NET version to audit: ```bash -dotnet-release verify supported-os release-notes +release-notes-gen verify supported-os release-notes ``` Examples: ```bash # Check 10.0 against local files -dotnet-release verify supported-os 10.0 release-notes +release-notes-gen verify supported-os 10.0 release-notes # Check against live data on GitHub (no local clone needed) -dotnet-release verify supported-os 10.0 +release-notes-gen verify supported-os 10.0 ``` **Interpret the exit code:** @@ -145,7 +145,7 @@ For each confirmed change, edit `release-notes//supported-os.json`: After updating the JSON, regenerate the markdown file: ```bash -dotnet-release generate supported-os release-notes +release-notes-gen generate supported-os release-notes ``` This overwrites `supported-os.md` with content derived from the updated JSON. @@ -171,7 +171,7 @@ Check if any newly added distro versions need entries in `os-packages.json`. If 1. Run verify again to confirm issues are resolved: ```bash - dotnet-release verify supported-os release-notes + release-notes-gen verify supported-os release-notes ``` Remaining items are acceptable if they are: diff --git a/.github/skills/verify-releases/SKILL.md b/.github/skills/verify-releases/SKILL.md index 732cbee18f..759f43fcee 100644 --- a/.github/skills/verify-releases/SKILL.md +++ b/.github/skills/verify-releases/SKILL.md @@ -2,7 +2,7 @@ name: verify-releases description: > Validate releases and release links: URL liveness, file hashes, CDN - latest.version files, and aka.ms redirect targets. Uses dotnet-release verify + latest.version files, and aka.ms redirect targets. Uses release-notes-gen verify and generate commands against the local release-notes directory. USE FOR: validate the latest release, validate release links, validating that all download links return HTTP 200, verifying SHA512 hashes match downloaded @@ -16,21 +16,21 @@ description: > # Verify Releases -Validate .NET release data in `release-notes/` using the `dotnet-release` CLI tool. This skill checks that download URLs are live, file hashes match, CDN latest.version files are current, and aka.ms redirects resolve correctly. +Validate .NET release data in `release-notes/` using the `release-notes-gen` CLI tool. This skill checks that download URLs are live, file hashes match, CDN latest.version files are current, and aka.ms redirects resolve correctly. ## Prerequisites -### dotnet-release +### release-notes-gen -The `dotnet-release` tool is published to [GitHub Packages](https://github.com/richlander/dotnet-release/packages). +The `release-notes-gen` tool is published to [GitHub Packages](https://github.com/richlander/dotnet-release/packages). The public `dotnet-release` tool is now for browsing release data and CVEs. ```bash # Install -dotnet tool install -g Dotnet.Release.Tools \ +dotnet tool install -g ReleaseNotes.Gen \ --add-source https://nuget.pkg.github.com/richlander/index.json # Verify — must show "verify releases" in usage output -dotnet-release +release-notes-gen ``` > **Note:** GitHub Packages requires authentication even for public repositories. If you get a 401 error, configure credentials: @@ -46,7 +46,7 @@ dotnet-release **Version check:** If the tool usage output does not include `verify releases` in its command list, the installed version is too old. Update with: ```bash -dotnet tool update -g Dotnet.Release.Tools \ +dotnet tool update -g ReleaseNotes.Gen \ --add-source https://nuget.pkg.github.com/richlander/index.json ``` @@ -57,7 +57,7 @@ dotnet tool update -g Dotnet.Release.Tools \ Downloads every binary and verifies SHA512 hashes against `releases.json`. This is the most thorough check and takes several minutes. ```bash -dotnet-release verify releases release-notes +release-notes-gen verify releases release-notes ``` ### Verify all supported versions (quick — skip hashes) @@ -65,20 +65,20 @@ dotnet-release verify releases release-notes Checks URL liveness, CDN latest.version, and aka.ms redirects only. Much faster — typically under 30 seconds. ```bash -dotnet-release verify releases release-notes --skip-hash +release-notes-gen verify releases release-notes --skip-hash ``` ### Verify a specific major version ```bash -dotnet-release verify releases 10.0 release-notes -dotnet-release verify releases 10.0 release-notes --skip-hash +release-notes-gen verify releases 10.0 release-notes +release-notes-gen verify releases 10.0 release-notes --skip-hash ``` ### Verify a specific patch release ```bash -dotnet-release verify releases 10.0.5 release-notes +release-notes-gen verify releases 10.0.5 release-notes ``` ## What gets verified @@ -112,13 +112,13 @@ dotnet-release verify releases 10.0.5 release-notes ### 1. Check tool version -Confirm `dotnet-release` is installed and has the `verify releases` command: +Confirm `release-notes-gen` is installed and has the `verify releases` command: ```bash -dotnet-release +release-notes-gen ``` -The usage output must include `dotnet-release verify releases [version] [path] [--skip-hash]`. If it does not, update the tool (see Prerequisites). +The usage output must include `release-notes-gen verify releases [version] [path] [--skip-hash]`. If it does not, update the tool (see Prerequisites). ### 2. Run verification @@ -126,13 +126,13 @@ For a standard validation (recommended for release sign-off): ```bash cd ~/git/core -dotnet-release verify releases release-notes +release-notes-gen verify releases release-notes ``` For a quick check during development: ```bash -dotnet-release verify releases release-notes --skip-hash +release-notes-gen verify releases release-notes --skip-hash ``` ### 3. Interpret results @@ -156,8 +156,8 @@ Present a summary table with per-version results: ## .NET Release Link Verification Report **Date:** YYYY-MM-DD -**Tool:** `dotnet-release` vX.Y.Z -**Command:** `dotnet-release verify releases release-notes` +**Tool:** `release-notes-gen` vX.Y.Z +**Command:** `release-notes-gen verify releases release-notes` | Version | Latest Release | Download URLs | SHA512 Hashes | CDN latest.version | aka.ms | Status | |---------|---------------|---------------|---------------|-------------------|--------|--------| @@ -177,10 +177,10 @@ After verifying releases, you may also want to regenerate the legacy index and m ```bash # Regenerate releases-index.json -dotnet-release generate releases-index release-notes +release-notes-gen generate releases-index release-notes # Regenerate releases.md -dotnet-release generate releases release-notes +release-notes-gen generate releases release-notes # Lint the generated markdown npx markdownlint --config .github/linters/.markdown-lint.yml release-notes/releases.md diff --git a/.github/skills/verify-releases/references/verify-output-example.md b/.github/skills/verify-releases/references/verify-output-example.md index f1348e443b..37ee9a21a0 100644 --- a/.github/skills/verify-releases/references/verify-output-example.md +++ b/.github/skills/verify-releases/references/verify-output-example.md @@ -2,7 +2,7 @@ ## Full verification (with hashes) -Command: `dotnet-release verify releases release-notes` +Command: `release-notes-gen verify releases release-notes` ``` Verifying release links for all supported versions in /Users/rich/git/core/release-notes... @@ -64,7 +64,7 @@ Exit code: `0` ## Quick verification (skip hashes) -Command: `dotnet-release verify releases release-notes --skip-hash` +Command: `release-notes-gen verify releases release-notes --skip-hash` ``` Verifying release links for all supported versions in /Users/rich/git/core/release-notes... @@ -115,8 +115,8 @@ Exit code: `0` ## .NET Release Link Verification Report **Date:** 2026-03-27 -**Tool:** `dotnet-release` v1.1.0 -**Command:** `dotnet-release verify releases release-notes` +**Tool:** `release-notes-gen` v1.1.0 +**Command:** `release-notes-gen verify releases release-notes` | Version | Latest Release | Download URLs | SHA512 Hashes | CDN latest.version | aka.ms | Status | |---------|---------------|---------------|---------------|-------------------|--------|--------| diff --git a/.github/workflows/release-notes.lock.yml b/.github/workflows/release-notes.lock.yml index 59584556d8..8a258cabeb 100644 --- a/.github/workflows/release-notes.lock.yml +++ b/.github/workflows/release-notes.lock.yml @@ -649,7 +649,7 @@ jobs: # --allow-tool safeoutputs # --allow-tool shell(cat) # --allow-tool shell(date) - # --allow-tool shell(dotnet-release) + # --allow-tool shell(release-notes-gen) # --allow-tool shell(dotnet:*) # --allow-tool shell(echo) # --allow-tool shell(git add:*) @@ -678,7 +678,7 @@ jobs: touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.5 --skip-pull --enable-api-proxy \ - -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(dotnet-release)'\'' --allow-tool '\''shell(dotnet:*)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(git add:*)'\'' --allow-tool '\''shell(git branch:*)'\'' --allow-tool '\''shell(git checkout:*)'\'' --allow-tool '\''shell(git commit:*)'\'' --allow-tool '\''shell(git merge:*)'\'' --allow-tool '\''shell(git rm:*)'\'' --allow-tool '\''shell(git status)'\'' --allow-tool '\''shell(git switch:*)'\'' --allow-tool '\''shell(git:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(release-notes-gen)'\'' --allow-tool '\''shell(dotnet:*)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(git add:*)'\'' --allow-tool '\''shell(git branch:*)'\'' --allow-tool '\''shell(git checkout:*)'\'' --allow-tool '\''shell(git commit:*)'\'' --allow-tool '\''shell(git merge:*)'\'' --allow-tool '\''shell(git rm:*)'\'' --allow-tool '\''shell(git status)'\'' --allow-tool '\''shell(git switch:*)'\'' --allow-tool '\''shell(git:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_GITHUB_TOKEN: ${{ case(needs.pre_activation.outputs.copilot_pat_number == '0', secrets.COPILOT_PAT_0, needs.pre_activation.outputs.copilot_pat_number == '1', secrets.COPILOT_PAT_1, needs.pre_activation.outputs.copilot_pat_number == '2', secrets.COPILOT_PAT_2, needs.pre_activation.outputs.copilot_pat_number == '3', secrets.COPILOT_PAT_3, needs.pre_activation.outputs.copilot_pat_number == '4', secrets.COPILOT_PAT_4, needs.pre_activation.outputs.copilot_pat_number == '5', secrets.COPILOT_PAT_5, needs.pre_activation.outputs.copilot_pat_number == '6', secrets.COPILOT_PAT_6, needs.pre_activation.outputs.copilot_pat_number == '7', secrets.COPILOT_PAT_7, needs.pre_activation.outputs.copilot_pat_number == '8', secrets.COPILOT_PAT_8, needs.pre_activation.outputs.copilot_pat_number == '9', secrets.COPILOT_PAT_9, secrets.COPILOT_GITHUB_TOKEN) }} @@ -1285,4 +1285,3 @@ jobs: name: safe-output-items path: /tmp/gh-aw/safe-output-items.jsonl if-no-files-found: ignore - diff --git a/.github/workflows/release-notes.md b/.github/workflows/release-notes.md index 49607ac73f..49095f4fd0 100644 --- a/.github/workflows/release-notes.md +++ b/.github/workflows/release-notes.md @@ -31,7 +31,7 @@ tools: toolsets: [issues, pull_requests, repos, search] bash: - dotnet - - dotnet-release + - release-notes-gen - git - jq timeout-minutes: 120 @@ -100,13 +100,13 @@ You maintain release notes for .NET preview, RC, and GA releases in this reposit Your outputs are pull requests — one per active milestone — each containing: -1. **`changes.json`** — a comprehensive manifest of all PRs/commits that shipped, generated by `dotnet-release generate changes` +1. **`changes.json`** — a comprehensive manifest of all PRs/commits that shipped, generated by `release-notes-gen generate changes` 2. **`features.json`** — a scored derivative of `changes.json` used to rank what is worth documenting 3. **Markdown release notes** — curated editorial content covering high-value features ## Your principles -- **High fidelity** — only document what actually ships. The VMR (`dotnet/dotnet`) and its `src/source-manifest.json` are the source of truth. Trust `dotnet-release generate changes` output. +- **High fidelity** — only document what actually ships. The VMR (`dotnet/dotnet`) and its `src/source-manifest.json` are the source of truth. Trust `release-notes-gen generate changes` output. - **High value** — bias toward features users care about. Skip infra, test-only, and internal refactoring. - **Never document non-shipping features** — if it's not in `changes.json`, it didn't ship. - **Use scoring as guidance, not law** — `features.json` helps prioritize, but humans and editorial judgment still decide what makes the cut. @@ -205,7 +205,7 @@ Always regenerate — the content may have changed since the previous run. ```bash mkdir -p release-notes/11.0/preview/preview4 -dotnet-release generate changes /tmp/dotnet \ +release-notes-gen generate changes /tmp/dotnet \ --base v11.0.0-preview.3.26210.100 \ --head main \ --version "11.0.0-preview.4" \