mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 09:46:54 +02:00
Copilot Skills: Release note generation skill should also quote co-authors (#45819)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Release note generation skill add support for quoting co-authors <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [ ] Closes: #xxx <!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) --> - [ ] **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 <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed Result may vary because of uncertainty of LLM, while I can get this result during my tests <img width="696" height="147" alt="image" src="https://github.com/user-attachments/assets/9d20670f-b9fb-4630-b6b4-f94c2a5d2284" />
This commit is contained in:
@@ -33,7 +33,7 @@ Generated Files/ReleaseNotes/
|
|||||||
|
|
||||||
## Prerequisites
|
## 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
|
- MCP Server: github-mcp-server installed
|
||||||
- GitHub Copilot code review enabled for the org/repo
|
- 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) │
|
│ 1.1 Collect PRs (stable range) │
|
||||||
└────────────────────────────────┘
|
└────────────────────────────────┘
|
||||||
↓
|
↓
|
||||||
@@ -85,6 +89,7 @@ Generated Files/ReleaseNotes/
|
|||||||
|
|
||||||
| Step | Action | Details |
|
| 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.1 | Collect PRs | From previous release tag on `stable` branch → `sorted_prs.csv` |
|
||||||
| 1.2 | Assign Milestones | Ensure all PRs have correct milestone |
|
| 1.2 | Assign Milestones | Ensure all PRs have correct milestone |
|
||||||
| 2.1–2.4 | Label PRs | Auto-suggest + human label low-confidence |
|
| 2.1–2.4 | Label PRs | Auto-suggest + human label low-confidence |
|
||||||
|
|||||||
@@ -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)!
|
- 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)
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
# Step 1: Collection and Milestones
|
# Step 1: Collection and Milestones
|
||||||
|
|
||||||
## 1.0 To-do
|
## 1.0 To-do
|
||||||
|
- 1.0.0 Verify GitHub CLI authentication (REQUIRED)
|
||||||
- 1.0.1 Generate MemberList.md (REQUIRED)
|
- 1.0.1 Generate MemberList.md (REQUIRED)
|
||||||
- 1.1 Collect PRs
|
- 1.1 Collect PRs
|
||||||
- 1.2 Assign Milestones (REQUIRED)
|
- 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 <username>`. 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)
|
## 1.0.1 Generate MemberList.md (REQUIRED)
|
||||||
|
|
||||||
Create `Generated Files/ReleaseNotes/MemberList.md` from the **PowerToys core team** section in [COMMUNITY.md](../../../COMMUNITY.md).
|
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/`):
|
**Output** (in `Generated Files/ReleaseNotes/`):
|
||||||
- `milestone_prs.json` - raw PR data
|
- `milestone_prs.json` - raw PR data
|
||||||
- `sorted_prs.csv` - sorted PR list with columns: Id, Title, Labels, Author, Url, Body, CopilotSummary, NeedThanks
|
- `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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
- 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
|
- If nothing special on impact or unclear impact, mark as needing human summary
|
||||||
- Source from Title, Body, and CopilotSummary (prefer CopilotSummary when available)
|
- 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 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.
|
- 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: <PR full link>`
|
- If confidence < 70%, write: `Human Summary Needed: <PR full link>`
|
||||||
@@ -72,13 +72,13 @@ Some items in the Development section may overlap and should be moved to the Mod
|
|||||||
|
|
||||||
## Advanced Paste
|
## Advanced Paste
|
||||||
|
|
||||||
- Wrapped paste option lists in a single ScrollViewer
|
- 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
|
- Added image input handling for AI-powered transformations in [#5679](https://github.com/microsoft/PowerToys/pull/5679)
|
||||||
...
|
...
|
||||||
|
|
||||||
## Awake
|
## 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)
|
||||||
...
|
...
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -42,30 +42,7 @@ param(
|
|||||||
[string]$OutputJson = "milestone_prs.json"
|
[string]$OutputJson = "milestone_prs.json"
|
||||||
)
|
)
|
||||||
|
|
||||||
<#
|
# (See top-level synopsis above for full documentation)
|
||||||
.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.
|
|
||||||
#>
|
|
||||||
|
|
||||||
function Write-Info($msg) { Write-Host $msg -ForegroundColor Cyan }
|
function Write-Info($msg) { Write-Host $msg -ForegroundColor Cyan }
|
||||||
function Write-Warn($msg) { Write-Host $msg -ForegroundColor Yellow }
|
function Write-Warn($msg) { Write-Host $msg -ForegroundColor Yellow }
|
||||||
@@ -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-Info ("Found {0} unique PRs: {1}" -f $prNumbers.Count, ($prNumbers -join ', '))
|
||||||
Write-DebugMsg ("Total merge commits examined: {0}" -f $mergeCommits.Count)
|
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
|
# Query GitHub for each PR
|
||||||
$prDetails = @()
|
$prDetails = @()
|
||||||
function Get-CopilotSummaryFromPrJson {
|
function Get-CopilotSummaryFromPrJson {
|
||||||
@@ -307,22 +341,45 @@ foreach ($pr in $prNumbers) {
|
|||||||
$bodyValue = if ($json.body) { ($json.body -replace "`r", '') -replace "`n", ' ' } else { '' }
|
$bodyValue = if ($json.body) { ($json.body -replace "`r", '') -replace "`n", ' ' } else { '' }
|
||||||
$bodyValue = $bodyValue -replace '\s+', ' '
|
$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
|
$authorLogin = $json.author.login
|
||||||
$needThanks = $true
|
$allContributors = @($authorLogin)
|
||||||
if ($memberList.Count -gt 0 -and $authorLogin) {
|
|
||||||
$needThanks = -not ($memberList -contains $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]@{
|
$prDetails += [PSCustomObject]@{
|
||||||
Id = $json.number
|
Id = $json.number
|
||||||
Title = $json.title
|
Title = $json.title
|
||||||
Labels = $labelNames
|
Labels = $labelNames
|
||||||
Author = $authorLogin
|
Author = $authorField
|
||||||
Url = $json.url
|
Url = $json.url
|
||||||
Body = $bodyValue
|
Body = $bodyValue
|
||||||
CopilotSummary = $copilot.Summary
|
CopilotSummary = $copilot.Summary
|
||||||
NeedThanks = $needThanks
|
NeedThanks = $needThanksField
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
|||||||
Reference in New Issue
Block a user