Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Commit Filtering Rules

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

## Filter Out Commits Already in Start Tag

**CRITICAL: Skip commits that are already included in the start tag to avoid duplicates.**

This happens when:
- Cherry-picks from main to stable branch
- Backported fixes that were already released
- Commits included in a previous release

### Method 1: Git Merge-Base (Local, Most Reliable)

```powershell
$startTag = "v0.96.0"
$commitSha = "abc1234"

# Check if commit is ancestor of (already included in) start tag
# Returns exit code 0 if true, 1 if false
git merge-base --is-ancestor $commitSha $startTag
if ($LASTEXITCODE -eq 0) {
Write-Host "SKIP: Commit $commitSha already in $startTag"
} else {
Write-Host "PROCESS: Commit $commitSha is new"
}
```

### Method 2: GitHub API Compare (Auto-Filtered)

```powershell
# The compare API already handles this - it returns only commits
# that are in the "head" but NOT in the "base"
$compare = gh api repos/microsoft/PowerToys/compare/v0.96.0...v0.96.1 | ConvertFrom-Json

# These commits are guaranteed to be NEW (not in v0.96.0)
$newCommits = $compare.commits
Write-Host "Found $($newCommits.Count) new commits not in start tag"
```

### Method 3: Check Tags Containing Commit

```powershell
$commitSha = "abc1234"
$tagsContaining = git tag --contains $commitSha
if ($tagsContaining -match "v0\.96\.0") {
Write-Host "SKIP: Commit already released in v0.96.0"
}
```

## Batch Filter Script

```powershell
$startTag = "v0.96.0"
$endTag = "v0.96.1"

# Get commits from compare (already filtered - only new commits)
$newCommits = gh api repos/microsoft/PowerToys/compare/$startTag...$endTag --jq '.commits[] | {sha: .sha, message: .commit.message}' | ConvertFrom-Json

# Additional validation: filter out any merge commits that reference old PRs
$filteredCommits = $newCommits | Where-Object {
$sha = $_.sha
# Double-check: ensure commit is not ancestor of start tag
git merge-base --is-ancestor $sha $startTag 2>$null
$LASTEXITCODE -ne 0 # Keep only if NOT an ancestor
}

Write-Host "After filtering: $($filteredCommits.Count) commits to process"
```

## Skip Rules Summary

| Condition | Action |
|-----------|--------|
| `git merge-base --is-ancestor $sha $startTag` returns 0 | SKIP - already in start tag |
| Commit message contains "cherry-pick" + old PR number | SKIP - likely backport |
| PR was merged before start tag date | SKIP - old PR |
| Same PR number already processed | SKIP - duplicate |

## 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