PR Changelog Generator

Automatically generate release notes and changelogs from merged pull requests.

This workflow analyzes PRs, categorizes changes, and creates user-facing release notes with proper formatting.

Implementation

1import { relay } from "@relayplane/workflows";
2
3const result = await relay
4 .workflow("pr-changelog")
5
6 // Step 1: Categorize pull requests
7 .step("categorize-prs")
8 .with("openai:gpt-4o")
9 .prompt(`Categorize these merged pull requests:
10
11{{pullRequests}}
12
13Categories:
14- ๐Ÿš€ Features: New functionality
15- ๐Ÿ› Bug Fixes: Defect repairs
16- โšก Performance: Speed/efficiency improvements
17- ๐Ÿ”’ Security: Security patches
18- ๐Ÿ“š Documentation: Docs updates
19- ๐Ÿงน Chores: Refactoring, dependencies, CI/CD
20- โš ๏ธ Breaking Changes: API changes requiring migration
21
22For each PR:
23- Category (one primary)
24- User-facing: yes/no
25- Breaking change: yes/no
26- Summary (1 sentence, non-technical language)
27
28Return as JSON array.`)
29
30 // Step 2: Generate user-facing descriptions
31 .step("write-descriptions")
32 .with("anthropic:claude-3.5-sonnet")
33 .depends("categorize-prs")
34 .prompt(`Write user-facing descriptions for these changes:
35
36{{categorize-prs.output}}
37
38For each user-facing change:
39- Translate technical jargon to user benefits
40- Focus on "what" not "how"
41- Be specific about impact
42- Keep to 1-2 sentences
43- Use active voice
44
45Example:
46โŒ "Refactored API client to use connection pooling"
47โœ… "API requests are now 3x faster with improved connection handling"
48
49Skip internal/chores unless significant.`)
50
51 // Step 3: Identify breaking changes
52 .step("breaking-changes")
53 .with("anthropic:claude-3.5-sonnet")
54 .depends("categorize-prs")
55 .prompt(`Document breaking changes and migration steps:
56
57{{categorize-prs.output}}
58
59For each breaking change:
60- What changed (API, behavior, etc.)
61- Why we made this change
62- Migration steps (specific code examples)
63- Deprecation timeline if applicable
64
65Format for developer documentation.`)
66
67 // Step 4: Generate changelog
68 .step("generate-changelog")
69 .with("anthropic:claude-3.5-sonnet")
70 .depends("categorize-prs", "write-descriptions", "breaking-changes")
71 .prompt(`Create release changelog:
72
73Categorized PRs: {{categorize-prs.output}}
74Descriptions: {{write-descriptions.output}}
75Breaking Changes: {{breaking-changes.output}}
76
77Version: {{version}}
78Release Date: {{releaseDate}}
79
80Format:
81# Release {{version}} - {{releaseDate}}
82
83## โš ๏ธ Breaking Changes
84[If any, with migration guides]
85
86## ๐Ÿš€ New Features
87- Feature 1 (#PR_NUMBER)
88- Feature 2 (#PR_NUMBER)
89
90## ๐Ÿ› Bug Fixes
91- Fix description (#PR_NUMBER)
92
93## โšก Performance Improvements
94- Improvement (#PR_NUMBER)
95
96## ๐Ÿ“š Documentation
97- Doc updates (#PR_NUMBER)
98
99## ๐Ÿ™ Contributors
100[@username1, @username2]
101
102Use emoji, link to PR numbers, credit contributors.
103Exclude chores/internal changes.`)
104
105 .run({
106 pullRequests: mergedPRs.map(pr => ({
107 number: pr.number,
108 title: pr.title,
109 body: pr.body,
110 author: pr.author,
111 labels: pr.labels,
112 })),
113 version: "v3.2.0",
114 releaseDate: new Date().toISOString().split('T')[0],
115 });
116
117// Save to CHANGELOG.md
118const changelog = result.steps["generate-changelog"].output;
119await prependToFile("CHANGELOG.md", changelog + "\n\n");
120
121// Post to GitHub Releases
122await github.repos.createRelease({
123 owner: repo.owner,
124 repo: repo.name,
125 tag_name: "v3.2.0",
126 name: "Release v3.2.0",
127 body: changelog,
128});

Automated Release Flow

1// GitHub Actions workflow
2import { relay } from "@relayplane/workflows";
3import { Octokit } from "@octokit/rest";
4
5async function generateReleaseNotes(fromTag: string, toTag: string) {
6 const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
7
8 // Get all PRs merged between tags
9 const compare = await octokit.repos.compareCommits({
10 owner: "myorg",
11 repo: "myrepo",
12 base: fromTag,
13 head: toTag,
14 });
15
16 const prNumbers = compare.data.commits
17 .map(c => c.commit.message.match(/#(\d+)/)?.[1])
18 .filter(Boolean);
19
20 const prs = await Promise.all(
21 prNumbers.map(num =>
22 octokit.pulls.get({
23 owner: "myorg",
24 repo: "myrepo",
25 pull_number: parseInt(num!),
26 })
27 )
28 );
29
30 // Generate changelog
31 const changelog = await relay
32 .workflow("pr-changelog")
33 .run({
34 pullRequests: prs.map(pr => pr.data),
35 version: toTag,
36 releaseDate: new Date().toISOString().split('T')[0],
37 });
38
39 return changelog.steps["generate-changelog"].output;
40}
41
42// In CI/CD
43const notes = await generateReleaseNotes("v3.1.0", "v3.2.0");
44await createGitHubRelease({ tag: "v3.2.0", notes });

Conventional Commits Integration

1// Parse conventional commits for automatic categorization
2import { relay } from "@relayplane/workflows";
3
4async function parseConventionalCommits(commits: string[]) {
5 const categorized = commits.map(commit => {
6 const match = commit.match(/^(\w+)(\(.+\))?!?: (.+)/);
7 if (!match) return null;
8
9 const [, type, scope, description] = match;
10 const breaking = commit.includes('!:');
11
12 return {
13 type, // feat, fix, docs, chore, etc.
14 scope: scope?.replace(/[()]/g, ''),
15 description,
16 breaking,
17 };
18 }).filter(Boolean);
19
20 const prData = categorized.map(c => ({
21 title: c.description,
22 type: c.type,
23 breaking: c.breaking,
24 }));
25
26 return await relay
27 .workflow("pr-changelog")
28 .run({ pullRequests: prData, version: "v3.2.0" });
29}

Sample Output

1# Release v3.2.0 - 2024-11-18
2
3## โš ๏ธ Breaking Changes
4
5**API Authentication now requires Bearer tokens**
6Previously, API keys could be passed via query parameters. For security, we now require authentication via the \`Authorization\` header.
7
8Migration:
9\`\`\`diff
10- fetch('https://api.example.com/data?api_key=xxx')
11+ fetch('https://api.example.com/data', {
12+ headers: { 'Authorization': 'Bearer xxx' }
13+ })
14\`\`\`
15
16Timeline: Query param auth deprecated immediately, removed in v4.0.0 (Jan 2025)
17
18## ๐Ÿš€ New Features
19
20- **Multi-model workflows**: Chain steps across OpenAI, Anthropic, and Google models in a single workflow (#247)
21- **Webhook retry logic**: Automatically retry failed webhook deliveries with exponential backoff (#251)
22- **Team workspaces**: Collaborate on workflows with team members and role-based permissions (#256)
23
24## ๐Ÿ› Bug Fixes
25
26- Fixed workflow execution timeout not being respected (#243)
27- Resolved memory leak in long-running scheduled jobs (#248)
28- Corrected timezone handling for cron schedules (#253)
29
30## โšก Performance Improvements
31
32- Reduced workflow cold start time by 60% through better caching (#245)
33- Database query optimization decreased API latency by 200ms (#249)
34
35## ๐Ÿ“š Documentation
36
37- Added 25 production-ready workflow examples (#258)
38- Created migration guide for v2 to v3 upgrade (#259)
39
40## ๐Ÿ™ Contributors
41
42Thank you to @sarah-dev, @mike-engineer, @alex-designer, and 12 other contributors for making this release possible!
43
44**Full Changelog**: https://github.com/org/repo/compare/v3.1.0...v3.2.0

Benefits

  • Consistency: Same format for every release
  • Time Savings: Auto-generate in seconds vs 30+ minutes manual
  • User-Focused: Non-technical language for product updates
  • Complete: Never forget to document a change
Pro Tip: Combine with semantic versioning to automatically determine version bumps based on change types (breaking = major, feat = minor, fix = patch)