From d28f312b817ad6470d29f6db5836f35ca0df00d7 Mon Sep 17 00:00:00 2001
From: Kai Tao <69313318+vanzue@users.noreply.github.com>
Date: Wed, 4 Mar 2026 16:40:08 +0800
Subject: [PATCH] Copilot Skills: Release note generation skill should also
quote co-authors (#45819)
## Summary of the Pull Request
Release note generation skill add support for quoting co-authors
## PR Checklist
- [ ] Closes: #xxx
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
## Detailed Description of the Pull Request / Additional comments
## Validation Steps Performed
Result may vary because of uncertainty of LLM, while I can get this
result during my tests
---
.../skills/release-note-generation/SKILL.md | 7 +-
.../references/SampleOutput.md | 10 +-
.../references/step1-collection.md | 31 +++++
.../references/step4-summarization.md | 8 +-
.../scripts/dump-prs-since-commit.ps1 | 127 +++++++++++++-----
5 files changed, 138 insertions(+), 45 deletions(-)
diff --git a/.github/skills/release-note-generation/SKILL.md b/.github/skills/release-note-generation/SKILL.md
index e27c347b4d..39fc3f0740 100644
--- a/.github/skills/release-note-generation/SKILL.md
+++ b/.github/skills/release-note-generation/SKILL.md
@@ -33,7 +33,7 @@ Generated Files/ReleaseNotes/
## Prerequisites
-- GitHub CLI (`gh`) installed and authenticated
+- **GitHub CLI (`gh`) installed and authenticated** — The collection script uses `gh pr view` and `gh api graphql` to fetch PR metadata and co-author information. Run `gh auth status` to verify; if not logged in, run `gh auth login` first. See [Step 1.0.0](./references/step1-collection.md) for details.
- MCP Server: github-mcp-server installed
- GitHub Copilot code review enabled for the org/repo
@@ -49,6 +49,10 @@ Generated Files/ReleaseNotes/
```
┌────────────────────────────────┐
+│ 1.0 Verify gh auth + MemberList │
+└────────────────────────────────┘
+ ↓
+┌────────────────────────────────┐
│ 1.1 Collect PRs (stable range) │
└────────────────────────────────┘
↓
@@ -85,6 +89,7 @@ Generated Files/ReleaseNotes/
| Step | Action | Details |
|------|--------|---------|
+| 1.0 | Verify prerequisites | `gh auth status` must pass; generate MemberList.md |
| 1.1 | Collect PRs | From previous release tag on `stable` branch → `sorted_prs.csv` |
| 1.2 | Assign Milestones | Ensure all PRs have correct milestone |
| 2.1–2.4 | Label PRs | Auto-suggest + human label low-confidence |
diff --git a/.github/skills/release-note-generation/references/SampleOutput.md b/.github/skills/release-note-generation/references/SampleOutput.md
index f10c65ab0a..aefb181c1e 100644
--- a/.github/skills/release-note-generation/references/SampleOutput.md
+++ b/.github/skills/release-note-generation/references/SampleOutput.md
@@ -1,9 +1,9 @@
- - Added mouse button actions so you can choose what left, right, or middle click does. Thanks [@PesBandi](https://github.com/PesBandi)!
+ - Added mouse button actions so you can choose what left, right, or middle click does in [#1234](https://github.com/microsoft/PowerToys/pull/1234) by [@PesBandi](https://github.com/PesBandi)
- - Aligned window styling with current Windows theme for a cleaner look. Thanks [@sadirano](https://github.com/sadirano)!
+ - Aligned window styling with current Windows theme for a cleaner look in [#1235](https://github.com/microsoft/PowerToys/pull/1235) by [@sadirano](https://github.com/sadirano)
- - Ensured screen readers are notified when the selected item in the list changes for better accessibility.
+ - Ensured screen readers are notified when the selected item in the list changes for better accessibility in [#1236](https://github.com/microsoft/PowerToys/pull/1236)
- - Implemented configurable UI test pipeline that can use pre-built official releases instead of building everything from scratch, reducing test execution time from 2+ hours.
+ - Implemented configurable UI test pipeline that can use pre-built official releases instead of building everything from scratch, reducing test execution time from 2+ hours in [#1237](https://github.com/microsoft/PowerToys/pull/1237)
- - Fixed Alt+Left Arrow navigation not working when search box contains text. Thanks [@jiripolasek](https://github.com/jiripolasek)!
\ No newline at end of file
+ - Fixed Alt+Left Arrow navigation not working when search box contains text in [#1238](https://github.com/microsoft/PowerToys/pull/1238) by [@jiripolasek](https://github.com/jiripolasek)
\ No newline at end of file
diff --git a/.github/skills/release-note-generation/references/step1-collection.md b/.github/skills/release-note-generation/references/step1-collection.md
index a084b17f1d..31b73419ee 100644
--- a/.github/skills/release-note-generation/references/step1-collection.md
+++ b/.github/skills/release-note-generation/references/step1-collection.md
@@ -1,6 +1,7 @@
# Step 1: Collection and Milestones
## 1.0 To-do
+- 1.0.0 Verify GitHub CLI authentication (REQUIRED)
- 1.0.1 Generate MemberList.md (REQUIRED)
- 1.1 Collect PRs
- 1.2 Assign Milestones (REQUIRED)
@@ -20,6 +21,34 @@
---
+## 1.0.0 Verify GitHub CLI Authentication (REQUIRED)
+
+⚠️ **BLOCKING:** The collection script requires an authenticated `gh` CLI to fetch PR metadata and co-author information via GitHub's GraphQL API. Without authentication, PR data and `NeedThanks` attribution will be incomplete.
+
+### Check authentication status
+
+```powershell
+gh auth status
+```
+
+**If authenticated:** You'll see `Logged in to github.com account `. Proceed to 1.0.1.
+
+**If NOT authenticated:** Run the login flow before continuing:
+
+```powershell
+# Interactive login (opens browser for OAuth)
+gh auth login --hostname github.com --web
+
+# Or use a personal access token
+gh auth login --with-token <<< "YOUR_GITHUB_TOKEN"
+```
+
+**Required scopes:** `repo` (for reading PR data and assigning milestones)
+
+After login, verify again with `gh auth status` and confirm exit code 0.
+
+---
+
## 1.0.1 Generate MemberList.md (REQUIRED)
Create `Generated Files/ReleaseNotes/MemberList.md` from the **PowerToys core team** section in [COMMUNITY.md](../../../COMMUNITY.md).
@@ -80,6 +109,8 @@ The script detects both merge commits (`Merge pull request #12345`) and squash c
**Output** (in `Generated Files/ReleaseNotes/`):
- `milestone_prs.json` - raw PR data
- `sorted_prs.csv` - sorted PR list with columns: Id, Title, Labels, Author, Url, Body, CopilotSummary, NeedThanks
+ - `Author`: Comma-separated list of all contributors (PR opener + co-authors from commit trailers)
+ - `NeedThanks`: Comma-separated list of external contributors to thank (excludes core team members from MemberList.md). Empty string means no thanks needed.
---
diff --git a/.github/skills/release-note-generation/references/step4-summarization.md b/.github/skills/release-note-generation/references/step4-summarization.md
index b14c47490f..afb5fe86bd 100644
--- a/.github/skills/release-note-generation/references/step4-summarization.md
+++ b/.github/skills/release-note-generation/references/step4-summarization.md
@@ -16,7 +16,7 @@ For each CSV in `Generated Files/ReleaseNotes/grouped_csv/`, create a markdown f
- Use the “Verb-ed + Scenario + Impact” sentence structure—make readers think, “That’s exactly what I need” or “Yes, that’s an awesome fix.”; The "impact" can be end-user focused (written to convey user excitement) or technical (performance/stability) when user-facing impact is minimal.
- If nothing special on impact or unclear impact, mark as needing human summary
- Source from Title, Body, and CopilotSummary (prefer CopilotSummary when available)
-- If the column `NeedThanks` in CSV is `True`, append: `Thanks [@Author](https://github.com/Author)!`
+- The `NeedThanks` column contains a comma-separated list of external contributor usernames who should be thanked (empty = no thanks needed, all authors are core team). For each non-empty `NeedThanks` value, append thanks for **every** listed contributor: `Thanks [@user1](https://github.com/user1)!` for a single contributor, or `Thanks [@user1](https://github.com/user1) and [@user2](https://github.com/user2)!` for two, or `Thanks [@user1](https://github.com/user1), [@user2](https://github.com/user2), and [@user3](https://github.com/user3)!` for three or more.
- Do NOT include PR numbers in bullet lines
- Do NOT mention “security” or “privacy” issues, since these are not known and could be leveraged by attackers in earlier versions. Instead, describe the user-facing scenario, usage, or impact.
- If confidence < 70%, write: `Human Summary Needed: `
@@ -72,13 +72,13 @@ Some items in the Development section may overlap and should be moved to the Mod
## Advanced Paste
-- Wrapped paste option lists in a single ScrollViewer
-- Added image input handling for AI-powered transformations
+- Wrapped paste option lists in a single ScrollViewer in [#5678](https://github.com/microsoft/PowerToys/pull/5678)
+- Added image input handling for AI-powered transformations in [#5679](https://github.com/microsoft/PowerToys/pull/5679)
...
## Awake
-- Fixed timed mode expiration. Thanks [@daverayment](https://github.com/daverayment)!
+- Fixed timed mode expiration in [#5680](https://github.com/microsoft/PowerToys/pull/5680) by [@daverayment](https://github.com/daverayment)
...
---
diff --git a/.github/skills/release-note-generation/scripts/dump-prs-since-commit.ps1 b/.github/skills/release-note-generation/scripts/dump-prs-since-commit.ps1
index 011b37fdd1..b4b8b485af 100644
--- a/.github/skills/release-note-generation/scripts/dump-prs-since-commit.ps1
+++ b/.github/skills/release-note-generation/scripts/dump-prs-since-commit.ps1
@@ -42,30 +42,7 @@ param(
[string]$OutputJson = "milestone_prs.json"
)
-<#
-.SYNOPSIS
- Dump merged PR information whose merge commits are reachable from EndCommit but not from StartCommit.
-.DESCRIPTION
- Uses git rev-list to compute commits in the (StartCommit, EndCommit] range, extracts PR numbers from merge commit messages,
- queries GitHub (gh CLI) for details, then outputs a CSV.
-
- PR merge commit messages in PowerToys generally contain patterns like:
- Merge pull request #12345 from ...
-
-.EXAMPLE
- pwsh ./dump-prs-since-commit.ps1 -StartCommit 0123abcd -Branch stable
-
-.EXAMPLE
- pwsh ./dump-prs-since-commit.ps1 -StartCommit 0123abcd -EndCommit 89ef7654 -OutputCsv changes.csv
-
-.NOTES
- Requires: gh CLI authenticated; git available in working directory (must be inside PowerToys repo clone).
- CopilotSummary behavior:
- - Attempts to locate the latest GitHub Copilot authored review (preferred).
- - If no review is found, lazily fetches PR comments to look for a Copilot-authored comment.
- - Normalizes whitespace and strips newlines. Empty when no Copilot activity detected.
- - Run with -Verbose to see whether the summary came from a 'review' or 'comment' source.
-#>
+# (See top-level synopsis above for full documentation)
function Write-Info($msg) { Write-Host $msg -ForegroundColor Cyan }
function Write-Warn($msg) { Write-Host $msg -ForegroundColor Yellow }
@@ -151,11 +128,11 @@ catch {
}
Write-Info "Collecting commits between $startSha..$endSha (excluding start, including end)."
- # Get list of commits reachable from end but not from start.
- # IMPORTANT: In PowerShell, the .. operator creates a numeric/char range. If $startSha and $endSha look like hex strings,
- # `$startSha..$endSha` must be passed as a single string argument.
- $rangeArg = "$startSha..$endSha"
- $commitList = git rev-list $rangeArg
+# Get list of commits reachable from end but not from start.
+# IMPORTANT: In PowerShell, the .. operator creates a numeric/char range. If $startSha and $endSha look like hex strings,
+# `$startSha..$endSha` must be passed as a single string argument.
+$rangeArg = "$startSha..$endSha"
+$commitList = git rev-list $rangeArg
# Normalize list (filter out empty strings)
$normalizedCommits = $commitList | Where-Object { $_ -and $_.Trim() -ne '' }
@@ -210,6 +187,63 @@ $prNumbers = $mergeCommits | Select-Object -ExpandProperty Pr -Unique | Sort-Obj
Write-Info ("Found {0} unique PRs: {1}" -f $prNumbers.Count, ($prNumbers -join ', '))
Write-DebugMsg ("Total merge commits examined: {0}" -f $mergeCommits.Count)
+# Build a map of PR number → list of commit SHAs (for co-author extraction)
+$prToCommits = @{}
+foreach ($mc in $mergeCommits) {
+ if (-not $prToCommits.ContainsKey($mc.Pr)) {
+ $prToCommits[$mc.Pr] = @()
+ }
+ $prToCommits[$mc.Pr] += $mc.Sha
+}
+
+<#
+.SYNOPSIS
+ Get all authors (including co-authors) for a set of commits via GitHub GraphQL API.
+.DESCRIPTION
+ Uses the Commit.authors field in GitHub's GraphQL API which natively includes
+ co-authors (from Co-authored-by trailers). Returns GitHub usernames (login)
+ without any email parsing — GitHub resolves the association for us.
+
+ NOTE: For squash merges this captures all co-authors correctly because GitHub
+ preserves Co-authored-by trailers in the squash commit. For traditional merge
+ commits, only the merger's author is returned — co-authors on individual PR
+ commits are not traversed. This is acceptable because PowerToys primarily uses
+ squash merging.
+#>
+function Get-CommitAuthors {
+ param(
+ [string[]]$CommitShas,
+ [string]$RepoFullName = "microsoft/PowerToys"
+ )
+ $parts = $RepoFullName -split '/'
+ $owner = $parts[0]
+ $repoName = $parts[1]
+ $allAuthors = @()
+
+ foreach ($sha in $CommitShas) {
+ try {
+ $query = "{ repository(owner: `"$owner`", name: `"$repoName`") { object(expression: `"$sha`") { ... on Commit { authors(first: 20) { nodes { user { login } name } } } } } }"
+ $result = gh api graphql -f query="$query" 2>$null | ConvertFrom-Json
+ $nodes = $result.data.repository.object.authors.nodes
+ if ($nodes) {
+ foreach ($node in $nodes) {
+ if ($node.user -and $node.user.login) {
+ $allAuthors += $node.user.login
+ } else {
+ # User without a GitHub account (rare) — use display name as fallback
+ Write-DebugMsg "Commit $sha has an author without GitHub account: $($node.name)"
+ }
+ }
+ }
+ }
+ catch {
+ Write-DebugMsg "GraphQL authors query failed for commit ${sha}: $_"
+ }
+ }
+
+ return $allAuthors | Select-Object -Unique
+}
+
# Query GitHub for each PR
$prDetails = @()
function Get-CopilotSummaryFromPrJson {
@@ -307,22 +341,45 @@ foreach ($pr in $prNumbers) {
$bodyValue = if ($json.body) { ($json.body -replace "`r", '') -replace "`n", ' ' } else { '' }
$bodyValue = $bodyValue -replace '\s+', ' '
- # Determine if author needs thanks (not in member list)
+ # Collect all contributors: PR author + co-authors from commit messages
$authorLogin = $json.author.login
- $needThanks = $true
- if ($memberList.Count -gt 0 -and $authorLogin) {
- $needThanks = -not ($memberList -contains $authorLogin)
+ $allContributors = @($authorLogin)
+
+ # Extract all authors (including co-authors) from associated commits via GitHub GraphQL API
+ if ($prToCommits.ContainsKey([int]$pr)) {
+ $commitAuthors = Get-CommitAuthors -CommitShas $prToCommits[[int]$pr] -RepoFullName $Repo
+ if ($commitAuthors) {
+ $allContributors += $commitAuthors
+ }
}
+ # Deduplicate contributors (case-insensitive)
+ $allContributors = $allContributors | Where-Object { $_ } | Sort-Object -Unique
+
+ # Filter to only external contributors (not in member list) for thanks
+ $externalContributors = @()
+ if ($memberList.Count -gt 0) {
+ $externalContributors = $allContributors | Where-Object { -not ($memberList -contains $_) }
+ } else {
+ $externalContributors = $allContributors
+ }
+
+ # Author column: all contributors (comma-separated)
+ $authorField = ($allContributors -join ', ')
+
+ # NeedThanks column: comma-separated list of external contributors who
+ # deserve thanks attribution. Empty string means no thanks needed.
+ $needThanksField = ($externalContributors -join ', ')
+
$prDetails += [PSCustomObject]@{
Id = $json.number
Title = $json.title
Labels = $labelNames
- Author = $authorLogin
+ Author = $authorField
Url = $json.url
Body = $bodyValue
CopilotSummary = $copilot.Summary
- NeedThanks = $needThanks
+ NeedThanks = $needThanksField
}
}
catch {