Skip to content

Commit 900e837

Browse files
committed
chore: add release-notes skill
Replace the Python-based release note generation script that was run on release with a Claude-native skill. This transition enables automated release note categorization and GitHub release management directly through Claude without relying on external AI API dependencies. The issue is that the gemini api was timing out and was not that smart at categorization, when the skills offer more versatility. Signed-off-by: Chmouel Boudjnah <chmouel@redhat.com>
1 parent 6af8b34 commit 900e837

File tree

5 files changed

+373
-39
lines changed

5 files changed

+373
-39
lines changed
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
---
2+
name: release-notes
3+
description: This skill should be used when the user asks to "create release note", "generate release notes", "release notes", "release changelog", "update GitHub release", or wants to generate categorized release notes between tags. Gathers PR/commit data via gh CLI, categorizes changes, and outputs formatted release notes. The user can optionally specify a release version (e.g. "create release note v0.31.0") to bypass auto-detection.
4+
version: 0.1.0
5+
---
6+
7+
# Release Notes Generation
8+
9+
Generate categorized release notes for Pipelines-as-Code releases by gathering PR/commit data between two git tags and producing formatted markdown output.
10+
11+
## Purpose
12+
13+
Replace the `hack/generate-releasenotes.py` + Gemini workflow with a Claude-native skill that:
14+
15+
- Auto-detects current and previous tags
16+
- Gathers PR and commit data via `gh` CLI
17+
- Uses Claude itself for intelligent categorization (no external AI API needed)
18+
- Outputs release notes matching the project's established format
19+
- Optionally updates the GitHub release
20+
21+
## Workflow
22+
23+
### Step 1: Pull latest tags
24+
25+
```bash
26+
git pull origin --tags
27+
```
28+
29+
Ensure all tags are available locally before detection.
30+
31+
### Step 2: Detect repository info
32+
33+
```bash
34+
gh repo view --json owner,name
35+
```
36+
37+
Extract `owner` and `repo` name for API calls.
38+
39+
### Step 3: Detect tags
40+
41+
**If the user specified a version** (e.g. "create release note v0.31.0"), use that directly as the current tag — skip auto-detection. Validate that the tag exists locally:
42+
43+
```bash
44+
git tag --list 'v0.31.0'
45+
```
46+
47+
If the tag doesn't exist locally, error and ask the user to verify.
48+
49+
**Otherwise, auto-detect current tag:**
50+
51+
```bash
52+
git tag --points-at HEAD
53+
```
54+
55+
Filter for `v*` prefixed tags. If no tag points at HEAD, list recent tags and ask the user which tag to use:
56+
57+
```bash
58+
git tag --list 'v*' --sort=-version:refname | head -10
59+
```
60+
61+
**Previous tag:**
62+
63+
```bash
64+
git tag --list 'v*' --sort=-version:refname
65+
```
66+
67+
Find the entry immediately after the current tag in the version-sorted list.
68+
69+
**CRITICAL**: Confirm both tags with the user before proceeding. Display:
70+
71+
```text
72+
Current tag: v0.31.0
73+
Previous tag: v0.30.0
74+
75+
Proceed with these tags? (y/n)
76+
```
77+
78+
### Step 4: Verify prerequisites
79+
80+
Check `gh` authentication:
81+
82+
```bash
83+
gh auth status
84+
```
85+
86+
If not authenticated, instruct the user to run `gh auth login`.
87+
88+
**Validate that both tags exist on GitHub:**
89+
90+
```bash
91+
gh api repos/{owner}/{repo}/git/ref/tags/{current_tag}
92+
gh api repos/{owner}/{repo}/git/ref/tags/{previous_tag}
93+
```
94+
95+
If either tag does not exist on GitHub, **stop and report the error** — do not proceed.
96+
97+
**Check GitHub release status for the current tag:**
98+
99+
```bash
100+
gh release view {current_tag} --json isDraft,isPrerelease,tagName
101+
```
102+
103+
- If **no release exists** for the tag: warn the user, but allow proceeding (notes can be generated for preview, but cannot be uploaded).
104+
- If the release is a **draft** (`isDraft: true`): proceed normally — this is the expected state for generating release notes.
105+
- If the release is **published** (not draft, not prerelease): **ask for explicit confirmation** before proceeding, since updating will override an already-published release:
106+
107+
```text
108+
⚠️ The release for {current_tag} is already published (not a draft).
109+
Updating it will override the existing release notes.
110+
Are you sure you want to proceed? (y/n)
111+
```
112+
113+
Only continue if the user confirms.
114+
115+
### Step 5: Gather PR/commit data
116+
117+
Use `gh api` to collect data between the two tags:
118+
119+
**Compare commits:**
120+
121+
```bash
122+
gh api repos/{owner}/{repo}/compare/{previous_tag}...{current_tag} --jq '.commits[].sha'
123+
```
124+
125+
**For each commit, find associated PRs:**
126+
127+
```bash
128+
gh api repos/{owner}/{repo}/commits/{sha}/pulls
129+
```
130+
131+
**Deduplicate by PR number.** For each PR/commit, extract:
132+
133+
- PR number, title, body, author, URL, labels
134+
- JIRA tickets using regex pattern: `SRVKP-\d+` (search in PR title, PR body, and commit message)
135+
136+
For commits not associated with any PR, include them as standalone commit entries with their SHA, message, author, and URL.
137+
138+
**IMPORTANT**: This step involves many API calls. Process commits in reasonable batches and report progress to the user.
139+
140+
### Step 6: Categorize changes
141+
142+
Using the gathered PR/commit data, categorize each entry into exactly these sections (skip empty ones):
143+
144+
- `## ✨ Major changes and Features`
145+
- `## 🐛 Bug Fixes`
146+
- `## 📚 Documentation Updates`
147+
- `## ⚙️ Chores`
148+
149+
Use the entry format specified in `references/release-notes-format.md`.
150+
151+
**Categorization guidelines:**
152+
153+
- New capabilities, enhancements, significant behavior changes → Features
154+
- Bug fixes, error corrections, regression fixes → Bug Fixes
155+
- Documentation-only changes (docs/, README, comments) → Documentation Updates
156+
- Dependencies, CI/CD, refactoring, formatting, test-only changes → Chores
157+
- Within each section, entries with JIRA tickets go FIRST, before entries without JIRA tickets
158+
159+
**Internal vs user-facing detection:**
160+
161+
Changes that match ANY of these patterns are internal and belong in Chores, NOT Features — regardless of `feat:` prefix:
162+
163+
- CI/CD pipeline changes (`.tekton/`, `.github/workflows/`, Makefile targets)
164+
- Release infrastructure (release scripts, release pipeline tasks, goreleaser config)
165+
- Test infrastructure (test helpers, e2e framework, test configuration)
166+
- Build system changes (Dockerfile, ko config, build scripts)
167+
- Developer tooling (linter config, pre-commit hooks, code generation)
168+
- Internal refactoring that doesn't change user-visible behavior
169+
170+
Only classify as Features when the change is **visible to end users**: new CLI flags, new API fields, new provider capabilities, new webhook behaviors, new configuration options, new user-facing commands.
171+
172+
### Step 7: Assemble complete release notes
173+
174+
Combine all sections in this order:
175+
176+
1. **Header** (see `references/release-notes-format.md` for template)
177+
2. **Categorized sections** from Step 6
178+
3. **Installation section** (see `references/release-notes-format.md` for template)
179+
4. **GitHub auto-generated changelog**:
180+
181+
```bash
182+
gh api repos/{owner}/{repo}/releases/generate-notes -f tag_name="{current_tag}" -f previous_tag_name="{previous_tag}"
183+
```
184+
185+
Extract the `body` field from the response.
186+
187+
### Step 8: Output, save, and optional GitHub release update
188+
189+
1. **Always write** the complete release notes to `/tmp/release-notes-{current_tag}.md` and tell the user the file path.
190+
2. **Display** the complete release notes to the user.
191+
3. **Ask** if they want to update the GitHub release:
192+
193+
```text
194+
Release notes saved to /tmp/release-notes-{current_tag}.md
195+
196+
Would you like to update the GitHub release for {current_tag} with these notes? (y/n)
197+
```
198+
199+
1. If yes, update via:
200+
201+
```bash
202+
gh release edit {current_tag} --notes-file /tmp/release-notes-{current_tag}.md
203+
```
204+
205+
If the release has a TODO placeholder (matching pattern `TODO: XXXXX.*?see older releases for some example`), replace only that placeholder in the existing release body rather than overwriting the entire body.
206+
207+
### Step 9: Slack announcement (optional)
208+
209+
After Step 8, ask the user if they want a Slack announcement message.
210+
211+
If yes:
212+
213+
1. Generate a friendly summary with a few emojis (not excessive), listing the top 5-7 most important features and/or bug fixes. Max 7 items total.
214+
2. Save to `/tmp/release-notes-slack-{current_tag}.txt` and tell the user the file path so they can copy-paste.
215+
3. Append the GitHub release URL at the end of the message.
216+
217+
## Error Handling
218+
219+
| Scenario | Action |
220+
| --- | --- |
221+
| No tag at HEAD | List recent tags, ask user to pick one |
222+
| User-specified tag doesn't exist locally | Error and ask user to verify the tag name |
223+
| Tag doesn't exist on GitHub | Stop and report error — do not proceed |
224+
| `gh` not authenticated | Instruct user to run `gh auth login` |
225+
| No previous tag found | Ask user to provide one manually |
226+
| No GitHub release for tag | Warn but allow generating notes for preview |
227+
| Release is a draft | Proceed normally (expected state) |
228+
| Release is already published | Ask for explicit confirmation before overriding |
229+
| API rate limiting | Report the error, suggest waiting or using a different token |
230+
231+
## User Confirmation Requirements
232+
233+
**CRITICAL**: Always confirm tags before gathering data. Always confirm before updating a GitHub release. Never update a release without explicit user approval.
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Release Notes Format Reference
2+
3+
## Entry Format
4+
5+
Each release note entry MUST follow this exact format. The Link and Jira lines MUST be indented with two spaces so they render as nested sub-bullets:
6+
7+
```markdown
8+
* **Bold title:** One-sentence description of the change.
9+
* Link: <PR_OR_COMMIT_URL>
10+
* Jira: [SRVKP-XXXX](https://issues.redhat.com/browse/SRVKP-XXXX)
11+
```
12+
13+
### Rules
14+
15+
- The first bullet MUST start with `*` (no indent) with a bold title followed by a colon and a description.
16+
- The Link line MUST start with `* Link:` (two-space indent) with the PR or commit URL.
17+
- The Jira line MUST start with `* Jira:` (two-space indent). Include it ONLY if the entry has JIRA tickets. If there are multiple tickets, list each as a separate markdown link comma-separated.
18+
- Within each section, list entries that have JIRA tickets FIRST, before entries without JIRA tickets.
19+
- Do NOT add a Contributors section.
20+
21+
## Header Template
22+
23+
```markdown
24+
# Pipelines as Code version {tag}
25+
26+
OpenShift Pipelines as Code {tag} has been released 🥳
27+
```
28+
29+
## Section Headers
30+
31+
Use exactly these section headers (skip empty ones):
32+
33+
```markdown
34+
## ✨ Major changes and Features
35+
## 🐛 Bug Fixes
36+
## 📚 Documentation Updates
37+
## ⚙️ Chores
38+
```
39+
40+
## Installation Section Template
41+
42+
```markdown
43+
## Installation
44+
45+
To install this version you can install the release.yaml with [`kubectl`](https://kubernetes.io/docs/tasks/tools/) for your platform :
46+
47+
### Openshift
48+
49+
\`\`\`shell
50+
kubectl apply -f https://github.com/{owner}/{repo}/releases/download/{tag}/release.yaml
51+
\`\`\`
52+
53+
### Kubernetes
54+
55+
\`\`\`shell
56+
kubectl apply -f https://github.com/{owner}/{repo}/releases/download/{tag}/release.k8s.yaml
57+
\`\`\`
58+
59+
### Documentation
60+
61+
The documentation for this release is available here :
62+
63+
https://release-{tag_dashed}.pipelines-as-code.pages.dev
64+
```
65+
66+
Where `{tag_dashed}` is the tag with dots replaced by dashes (e.g., `v0.31.0``v0-31-0`).
67+
68+
## JIRA Ticket Format
69+
70+
JIRA tickets matching `SRVKP-\d+` should be linked as:
71+
72+
```markdown
73+
[SRVKP-XXXX](https://issues.redhat.com/browse/SRVKP-XXXX)
74+
```
75+
76+
Multiple tickets are comma-separated:
77+
78+
```markdown
79+
[SRVKP-1234](https://issues.redhat.com/browse/SRVKP-1234), [SRVKP-5678](https://issues.redhat.com/browse/SRVKP-5678)
80+
```
81+
82+
## Complete Example
83+
84+
```markdown
85+
# Pipelines as Code version v0.31.0
86+
87+
OpenShift Pipelines as Code v0.31.0 has been released 🥳
88+
89+
## ✨ Major changes and Features
90+
91+
* **Cache changed files in Gitea provider:** Improved performance by caching changed files in the Gitea provider, reducing API calls during pipeline runs.
92+
* Link: https://github.com/openshift-pipelines/pipelines-as-code/pull/2145
93+
* Jira: [SRVKP-1234](https://issues.redhat.com/browse/SRVKP-1234)
94+
* **Add concurrency support for pipeline runs:** Introduced concurrency controls allowing users to limit parallel pipeline executions per repository.
95+
* Link: https://github.com/openshift-pipelines/pipelines-as-code/pull/2130
96+
97+
## 🐛 Bug Fixes
98+
99+
* **Pin actions/checkout to a specific hash:** Fixed CI reliability by pinning the checkout action to a known-good commit hash.
100+
* Link: https://github.com/openshift-pipelines/pipelines-as-code/pull/2140
101+
* Jira: [SRVKP-5678](https://issues.redhat.com/browse/SRVKP-5678)
102+
103+
## ⚙️ Chores
104+
105+
* **Bump actions/download-artifact from 7.0.0 to 8.0.0:** Updated CI dependency to latest version.
106+
* Link: https://github.com/openshift-pipelines/pipelines-as-code/pull/2138
107+
108+
## Installation
109+
110+
To install this version you can install the release.yaml with [`kubectl`](https://kubernetes.io/docs/tasks/tools/) for your platform :
111+
112+
### Openshift
113+
114+
\`\`\`shell
115+
kubectl apply -f https://github.com/openshift-pipelines/pipelines-as-code/releases/download/v0.31.0/release.yaml
116+
\`\`\`
117+
118+
### Kubernetes
119+
120+
\`\`\`shell
121+
kubectl apply -f https://github.com/openshift-pipelines/pipelines-as-code/releases/download/v0.31.0/release.k8s.yaml
122+
\`\`\`
123+
124+
### Documentation
125+
126+
The documentation for this release is available here :
127+
128+
https://release-v0-31-0.pipelines-as-code.pages.dev
129+
130+
## What's Changed
131+
<!-- GitHub auto-generated changelog goes here -->
132+
```

0 commit comments

Comments
 (0)