mirror of
https://github.com/penpot/penpot.git
synced 2026-05-20 07:23:42 +00:00
📎 Update the 'update-changelog' opencode skill
This commit is contained in:
parent
5b7c732449
commit
d9bcc1431c
@ -45,12 +45,12 @@ python3 tools/gh.py issues "2.16.0" --state all
|
||||
python3 tools/gh.py issues "2.16.0" --exclude "release blocker,no changelog"
|
||||
```
|
||||
|
||||
**Label exclusion rules:**
|
||||
- `release blocker` — Internal release-blocking bugs not relevant to end users
|
||||
- `no changelog` — Chore/refactor work that doesn't need a changelog entry
|
||||
**Exclusion rules:**
|
||||
- `no changelog` label — Chore/refactor work that doesn't need a changelog entry
|
||||
- `Task` issue type — Internal chores are not user-facing; filter these out after fetching
|
||||
|
||||
The script outputs JSON with each entry containing `number`, `title`, `state`,
|
||||
`labels`, and `closing_prs` (the PRs that fix each issue).
|
||||
`issue_type`, `labels`, and `closing_prs` (the PRs that fix each issue).
|
||||
|
||||
### 3. Identify missing entries (optional)
|
||||
|
||||
@ -84,34 +84,27 @@ The `prs` command returns JSON with `number`, `title`, `body`, `state`,
|
||||
`merged_at`, `author`, `labels`, and `closing_issues`. PRs are fetched in
|
||||
batches of 50 via GraphQL to stay within API limits.
|
||||
|
||||
### 5. Categorize entries
|
||||
### 5. Categorize entries — strictly by issue type, never by labels or emoji
|
||||
|
||||
Use the **Issue Type** field (GitHub's native issue type, accessible via GraphQL
|
||||
`issueType { name }`) to determine which section an entry belongs to.
|
||||
**Do not** use labels or title emoji prefixes as the source of truth — they are
|
||||
often inaccurate or missing.
|
||||
Use the **Issue Type** field (GitHub's native issue type, exposed as
|
||||
`issue_type` in the `gh.py` JSON output) to determine which section an entry
|
||||
belongs to.
|
||||
|
||||
| Issue Type (`issueType.name`) | Changelog section |
|
||||
|------------------------------|-------------------|
|
||||
> **⚠️ CRITICAL: Never use labels or title emoji prefixes for categorization.**
|
||||
> Labels like `bug` and `enhancement`, as well as title prefixes like `:bug:`
|
||||
> and `:sparkles:`, are frequently inaccurate, missing, or contradictory to the
|
||||
> actual issue type. The `issue_type` field from `gh.py` is the single source
|
||||
> of truth.
|
||||
|
||||
| `issue_type` value | Changelog section |
|
||||
|--------------------|-------------------|
|
||||
| `Bug` | `### :bug: Bugs fixed` |
|
||||
| `Feature` or `Enhancement` | `### :sparkles: New features & Enhancements` |
|
||||
| No type set | Fetch the issue and check its labels as a fallback: `bug` label → bugs section, otherwise default to enhancements |
|
||||
| `Task` | **Exclude** — internal chores are not user-facing |
|
||||
| `null` (not set) | Check labels as a fallback: `bug` label → bugs, otherwise enhancements |
|
||||
|
||||
To fetch Issue Types for all issues in a milestone efficiently, use a single
|
||||
GraphQL query with aliases rather than N+1 REST calls:
|
||||
|
||||
```graphql
|
||||
query {
|
||||
repository(owner: "penpot", name: "penpot") {
|
||||
i123: issue(number: 123) {
|
||||
number state milestone { number } issueType { name }
|
||||
}
|
||||
i456: issue(number: 456) {
|
||||
number state milestone { number } issueType { name }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
The `gh.py` issues command already includes `issue_type` in every entry's
|
||||
output. **No separate GraphQL query is needed.**
|
||||
|
||||
**Community contribution attribution:** If the issue or its fix PR has the
|
||||
`community contribution` label, add an attribution `(by @<github_username>)`
|
||||
@ -224,7 +217,7 @@ Read the top of `CHANGES.md` and confirm:
|
||||
can find the code changes.
|
||||
- **Latest version first.** New sections are inserted at the top of the
|
||||
changelog, below the `# CHANGELOG` header.
|
||||
- **Issue Type determines section.** Use GitHub's `issueType` field (Bug → `:bug:`, Feature/Enhancement → `:sparkles:`) to categorize entries. Ignore labels and title emoji prefixes — they are unreliable for categorization.
|
||||
- **Issue Type determines section — exclusively.** Use the `issue_type` field from `gh.py` output (Bug → `:bug:`, Feature/Enhancement → `:sparkles:`). **Do not** use labels (`bug`, `enhancement`) or title emoji prefixes (`:bug:`, `:sparkles:`) — they are frequently wrong or contradictory. The `issue_type` is the single source of truth.
|
||||
- **User-facing descriptions.** Write from the user's perspective — describe
|
||||
what broke and what was fixed, not internal implementation details.
|
||||
- **Community attribution.** When the issue or fix PR has the
|
||||
@ -233,8 +226,9 @@ Read the top of `CHANGES.md` and confirm:
|
||||
issue author) for the attribution.
|
||||
- **Only closed issues.** An issue must have `state: "closed"` to appear in
|
||||
the changelog. Open unresolved issues are omitted.
|
||||
- **Excluded labels.** Issues with `release blocker` or `no changelog` labels
|
||||
must be excluded from the changelog.
|
||||
- **Excluded issues.** Issues with `no changelog` label must be excluded.
|
||||
Issues with `issue_type: "Task"` must also be excluded — they are internal
|
||||
chores, not user-facing changes.
|
||||
- **Multiple PRs per issue.** If multiple PRs fix the same issue, list them
|
||||
comma-separated inline: `(PR: [#A](url), [#B](url))`.
|
||||
- **Duplicate removal.** If an entry already exists in a prior version section,
|
||||
|
||||
21
tools/gh.py
21
tools/gh.py
@ -80,14 +80,15 @@ query($owner: String!, $repo: String!, $milestone: Int!, $cursor: String) {
|
||||
issues(first: 100, after: $cursor, states: __STATES__) {
|
||||
totalCount
|
||||
pageInfo { hasNextPage endCursor }
|
||||
nodes {
|
||||
... on Issue {
|
||||
number
|
||||
title
|
||||
state
|
||||
labels(first: 20) { nodes { name } }
|
||||
closedByPullRequestsReferences(first: 5) { nodes { number } }
|
||||
}
|
||||
nodes {
|
||||
... on Issue {
|
||||
number
|
||||
title
|
||||
state
|
||||
issueType { name }
|
||||
labels(first: 20) { nodes { name } }
|
||||
closedByPullRequestsReferences(first: 5) { nodes { number } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -120,7 +121,7 @@ def fetch_milestone_issues(milestone_num: int, states: str) -> list[dict]:
|
||||
states: GraphQL states enum array literal, e.g. ``"[CLOSED]"`` or ``"[OPEN CLOSED]"``
|
||||
|
||||
Returns:
|
||||
List of {number, title, state, labels: [str], closing_prs: [int]}
|
||||
List of {number, title, state, issue_type: str|None, labels: [str], closing_prs: [int]}
|
||||
"""
|
||||
query = GQL_ISSUES_QUERY.replace("__STATES__", states)
|
||||
all_nodes: list[dict] = []
|
||||
@ -140,10 +141,12 @@ def fetch_milestone_issues(milestone_num: int, states: str) -> list[dict]:
|
||||
for node in issues["nodes"]:
|
||||
if node is None:
|
||||
continue
|
||||
issue_type = node.get("issueType")
|
||||
all_nodes.append({
|
||||
"number": node["number"],
|
||||
"title": node["title"],
|
||||
"state": node["state"],
|
||||
"issue_type": issue_type["name"] if issue_type else None,
|
||||
"labels": [lbl["name"] for lbl in node["labels"]["nodes"]],
|
||||
"closing_prs": [pr["number"] for pr in node["closedByPullRequestsReferences"]["nodes"]],
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user