Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/spell-check/excludes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
\.xz$
\.zip$
^\.github/actions/spell-check/
^\.github/skills/
^\.github/workflows/spelling\d*\.yml$
^\.gitmodules$
^\Q.pipelines/ESRPSigning_core.json\E$
Expand Down
152 changes: 152 additions & 0 deletions .github/skills/changelog-generator/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
---
name: changelog-generator
description: Automatically creates user-facing changelogs from git commits by analyzing commit history, categorizing changes, and transforming technical commits into clear, customer-friendly release notes. Turns hours of manual changelog writing into minutes of automated generation.
---

# Changelog Generator

This skill transforms technical git commits into polished, user-friendly changelogs that your customers and users will actually understand and appreciate.

## When to Use This Skill

- Preparing release notes for a new version
- Generating changelog between two tags/commits
- Creating draft release notes from recent commits

## Quick Start

```
Create a changelog from commits since v0.96.1
```

```
Create release notes for version 0.97.0 starting from tag v0.96.1
```

---

## Workflow Overview

```
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 1. Fetch │───▶│ 2. Filter │───▶│ 3. Categorize│───▶│ 4. Generate │
│ Commits │ │ & Dedupe │ │ by Module │ │ Descriptions │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
│ │ │ │
▼ ▼ ▼ ▼
📄 github-api 📄 commit- 📄 module- 📄 user-facing-
reference.md filtering.md mapping.md description.md
```

## Sub-Skills Reference

This skill is composed of the following sub-skills.

**⚠️ IMPORTANT: Do NOT read all sub-skills at once!**
- Only read a sub-skill when you reach that step in the workflow
- This saves context window and improves accuracy
- Use `read_file` to load a sub-skill only when needed

| Step | Sub-Skill | When to Read |
|------|-----------|--------------|
| Fetch data | [github-api-reference.md](sub-skills/github-api-reference.md) | When fetching commits/PRs |
| Filter commits | [commit-filtering.md](sub-skills/commit-filtering.md) | When checking if commit should be skipped |
| Categorize | [module-mapping.md](sub-skills/module-mapping.md) | When determining which module a PR belongs to |
| Generate text | [user-facing-description.md](sub-skills/user-facing-description.md) | When writing the changelog entry text |
| Attribution | [contributor-attribution.md](sub-skills/contributor-attribution.md) | When checking if author needs thanks |
| Large releases | [progress-tracking.md](sub-skills/progress-tracking.md) | Only if processing 50+ commits |

---

## Step-by-Step Summary

### Step 1: Get Commit Range
```powershell
# Count commits between tags
gh api repos/microsoft/PowerToys/compare/v0.96.0...v0.96.1 --jq '.commits | length'
```
👉 Details: [github-api-reference.md](sub-skills/github-api-reference.md)

### Step 2: Filter Commits
- Skip commits already in start tag
- Skip cherry-picks and backports
- Deduplicate by PR number

👉 Details: [commit-filtering.md](sub-skills/commit-filtering.md)

### Step 3: For Each PR, Generate Entry
1. Get PR title, body, files, labels
2. Determine module from file paths or labels
3. Check if user-facing (skip internal changes)
4. Transform to user-friendly description
5. Add contributor attribution if needed

👉 Details: [user-facing-description.md](sub-skills/user-facing-description.md), [module-mapping.md](sub-skills/module-mapping.md), [contributor-attribution.md](sub-skills/contributor-attribution.md)

### Step 4: Checkpoint (if 50+ commits)
- Save progress after every 15-20 commits
- Track processed PRs for deduplication
- Enable resume from interruption

👉 Details: [progress-tracking.md](sub-skills/progress-tracking.md)

### Step 5: Format Output
```markdown
## ✨ What's new
**Version X.XX (Month Year)**

**✨ Highlights**
- [Most impactful change 1]
- [Most impactful change 2]

### [Module Name - Alphabetical]
- [Description]. Thanks [@contributor](https://github.com/contributor)!

### Development
- [Internal changes]
```

---

## Example Output

```markdown
## ✨ What's new
**Version 0.96.1 (December 2025)**

**✨ Highlights**
- Advanced Paste now supports multiple AI providers.
- PowerRename can extract photo metadata for renaming.

### Advanced Paste
- Added support for Azure OpenAI, Google Gemini, Mistral, and more.

### Awake
- The countdown timer now stays accurate over long periods. Thanks [@daverayment](https://github.com/daverayment)!

### Development
- Resolved build warnings in Command Palette projects.
```

---

## Tips

1. **Propose highlights** after all entries are generated
2. **Check PR body** when title is unclear
3. **Thank external contributors** - see [contributor-attribution.md](sub-skills/contributor-attribution.md)
4. **Use progress tracking** for large releases - see [progress-tracking.md](sub-skills/progress-tracking.md)
5. **Save output** to `release-change-note-draft.md`

---

## Troubleshooting

| Problem | Solution |
|---------|----------|
| Lost progress mid-generation | Read `release-notes-progress.md` to resume |
| Duplicate entries | Check processed PR list, dedupe by PR number |
| Commit already released | Use `git merge-base --is-ancestor` to verify |
| API rate limited | Check `gh api rate_limit`, wait or use token |

👉 See sub-skills for detailed troubleshooting.
107 changes: 107 additions & 0 deletions .github/skills/changelog-generator/sub-skills/commit-filtering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Commit Filtering Rules

This sub-skill defines rules for filtering commits to include in the changelog.

## Understanding the Branch Model

PowerToys uses a release branch model where fixes are cherry-picked from main:

```
main: A---B---C---D---E---F---G---H (HEAD)
\
release: X---Y---Z (v0.96.1 tag)
(X, Y are cherry-picks of C, E from main)
```

**Key insight:** When comparing `v0.96.1...main`:
- The release tag (v0.96.1) is on a **different branch** than main
- GitHub compare finds the merge-base and returns commits on main after that point
- Commits C and E appear in the results even though they were cherry-picked to release as X and Y
- **The SHAs are different**, so SHA-based filtering won't work!

## ⚠️ CRITICAL: Filter by PR Number, Not SHA

Since cherry-picks have different SHAs, you **MUST** check by PR number:

```powershell
# Extract PR number from commit message and check if it exists in the release tag
$prNumber = "43785"
$startTag = "v0.96.1"

# Search the release branch for this PR number in commit messages
$cherryPicked = git log $startTag --oneline --grep="#$prNumber"
if ($cherryPicked) {
Write-Host "SKIP: PR #$prNumber was cherry-picked to $startTag"
} else {
Write-Host "INCLUDE: PR #$prNumber is new since $startTag"
}
```

## Complete Filtering Workflow

```powershell
$startTag = "v0.96.1" # Release tag (on release branch)
$endRef = "main" # Target (main branch)

# Step 1: Get all commits from main since the merge-base with release tag
$commits = gh api "repos/microsoft/PowerToys/compare/$startTag...$endRef" `
--jq '.commits[] | {sha: .sha, message: .commit.message}' | ConvertFrom-Json

# Step 2: Build list of PR numbers already in the release tag
$releasePRs = git log $startTag --oneline | Select-String -Pattern '#(\d+)' -AllMatches |
ForEach-Object { $_.Matches.Groups[1].Value } | Sort-Object -Unique

Write-Host "PRs already in $startTag : $($releasePRs.Count)"

# Step 3: Filter commits - skip if PR was cherry-picked to release
$newCommits = @()
foreach ($commit in $commits) {
if ($commit.message -match '#(\d+)') {
$prNumber = $matches[1]
if ($releasePRs -contains $prNumber) {
Write-Host "SKIP: PR #$prNumber already in $startTag (cherry-picked)"
continue
}
}
$newCommits += $commit
}

Write-Host "New commits to process: $($newCommits.Count)"
```

## Why SHA-Based Methods Don't Work Here

| Method | Works for same branch? | Works for cross-branch (cherry-picks)? |
|--------|------------------------|----------------------------------------|
| `git merge-base --is-ancestor` | ✅ Yes | ❌ No - different SHAs |
| `git tag --contains` | ✅ Yes | ❌ No - tag is on different branch |
| GitHub Compare API | ✅ Yes | ❌ No - returns commits by SHA |
| **PR number matching** | ✅ Yes | ✅ **Yes** |

## Skip Rules Summary

| Priority | Condition | Action |
|----------|-----------|--------|
| 1 | PR number found in `git log $startTag --grep="#$prNumber"` | **SKIP** - cherry-picked |
| 2 | Same PR number already processed in this run | **SKIP** - duplicate |
| 3 | Bot author (dependabot, etc.) | **SKIP** - unless user-visible |
| 4 | Internal-only change (CI, tests, refactor) | Move to **Development** section |

## User-Facing vs Non-User-Facing

**Include in changelog:**
- New features and capabilities
- Bug fixes that affect users
- UI/UX improvements
- Performance improvements users would notice
- Breaking changes or behavior modifications
- Security fixes

**Exclude from changelog (put in Development section):**
- Internal refactoring
- CI/CD changes
- Code style fixes
- Test additions/modifications
- Documentation-only changes
- Dependency updates (unless user-visible impact)
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Contributor Attribution Rules

This sub-skill defines when and how to credit contributors in changelog entries.

## Attribution Rules

- Check [COMMUNITY.md](../../../COMMUNITY.md) for the "PowerToys core team" section
- If author is **NOT** in core team → Add `Thanks [@username](https://github.com/username)!`
- If author **IS** in core team → No attribution needed
- Microsoft employees working on PowerToys as their job → No attribution needed

## Core Team Members

Current core team members (from COMMUNITY.md) - do NOT thank these:

```
@craigloewen-msft, @niels9001, @dhowett, @yeelam-gordon, @jamrobot,
@lei9444, @shuaiyuanxx, @moooyo, @haoliuu, @chenmy77, @chemwolf6922,
@yaqingmi, @zhaoqpcn, @urnotdfs, @zhaopy536, @wang563681252, @vanzue,
@zadjii-msft, @khmyznikov, @chatasweetie, @MichaelJolley, @Jaylyn-Barbee,
@zateutsch, @crutkas
```

## Check Author Script

```powershell
$coreTeam = @(
'craigloewen-msft', 'niels9001', 'dhowett', 'yeelam-gordon', 'jamrobot',
'lei9444', 'shuaiyuanxx', 'moooyo', 'haoliuu', 'chenmy77', 'chemwolf6922',
'yaqingmi', 'zhaoqpcn', 'urnotdfs', 'zhaopy536', 'wang563681252', 'vanzue',
'zadjii-msft', 'khmyznikov', 'chatasweetie', 'MichaelJolley', 'Jaylyn-Barbee',
'zateutsch', 'crutkas'
)

function Get-Attribution {
param([string]$author)

if ($coreTeam -contains $author) {
return $null # No attribution needed
}
return "Thanks [@$author](https://github.com/$author)!"
}

# Usage
$author = gh pr view 12345 --repo microsoft/PowerToys --json author --jq '.author.login'
$attribution = Get-Attribution $author
if ($attribution) {
Write-Host "Add: $attribution"
} else {
Write-Host "No attribution needed (core team member)"
}
```

## Attribution Format

**With attribution:**
```markdown
- The Awake countdown timer now stays accurate over long periods. Thanks [@daverayment](https://github.com/daverayment)!
```

**Without attribution (core team):**
```markdown
- Added new feature to Command Palette for opening settings.
```

## Special Cases

1. **Co-authored commits**: Credit the primary author (first in list)
2. **Bot accounts** (dependabot, etc.): No attribution
3. **Former core team members**: Check if they were core team at time of PR
4. **Multiple PRs by same external contributor**: Thank them on each entry

## High-Impact Community Members

These contributors have made significant ongoing contributions and are recognized in COMMUNITY.md.
**ALWAYS thank these contributors** - they are NOT core team and deserve recognition:

```
@davidegiacometti, @htcfreek, @daverayment, @jiripolasek
```

Check COMMUNITY.md for the full up-to-date list under "High impact community members" section.

## Updated Check Author Script

```powershell
$coreTeam = @(
'craigloewen-msft', 'niels9001', 'dhowett', 'yeelam-gordon', 'jamrobot',
'lei9444', 'shuaiyuanxx', 'moooyo', 'haoliuu', 'chenmy77', 'chemwolf6922',
'yaqingmi', 'zhaoqpcn', 'urnotdfs', 'zhaopy536', 'wang563681252', 'vanzue',
'zadjii-msft', 'khmyznikov', 'chatasweetie', 'MichaelJolley', 'Jaylyn-Barbee',
'zateutsch', 'crutkas'
)

# High-impact community members - ALWAYS thank these!
$highImpactCommunity = @(
'davidegiacometti', 'htcfreek', 'daverayment', 'jiripolasek'
)

function Get-Attribution {
param([string]$author)

# Core team and bots don't need thanks
if ($coreTeam -contains $author -or $author -match '\[bot\]$') {
return $null
}
# Everyone else (including high-impact community) gets thanked
return "Thanks [@$author](https://github.com/$author)!"
}
```
Loading
Loading