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 image --- .../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 {