diff --git a/.opencode/skills/update-changelog/SKILL.md b/.opencode/skills/update-changelog/SKILL.md index 889e77c3e6..5752b65aa3 100644 --- a/.opencode/skills/update-changelog/SKILL.md +++ b/.opencode/skills/update-changelog/SKILL.md @@ -7,7 +7,7 @@ description: Update the project CHANGES.md with issues from a given GitHub miles Update `CHANGES.md` with entries for all issues and PRs in a given GitHub milestone. Each entry references the user-facing issue (not the PR) as the -primary link, with the fix PR on a sub-line. +primary link, with the fix PR inline on the same line. ## When to Use @@ -19,7 +19,8 @@ primary link, with the fix PR on a sub-line. ## Prerequisites - `gh` CLI authenticated (`gh auth status`) -- Read access to the penpot/penpot repository +- Python 3.8+ +- `tools/gh.py` helper script available ## Workflow @@ -28,58 +29,64 @@ primary link, with the fix PR on a sub-line. The version is typically a semver string like `2.15.3`. Confirm with the user if not specified. -### 2. Fetch all issues and PRs in the milestone +### 2. Fetch all issues in the milestone -Find the milestone number: +Use the helper script. It uses GraphQL for efficient single-pass fetching +(closing PRs are included in the same query — no N+1): ```bash -gh api repos/penpot/penpot/milestones --paginate \ - --jq '.[] | select(.title=="") | {number: .number, title: .title, open_issues: .open_issues, closed_issues: .closed_issues}' +# All closed issues (default) +python3 tools/gh.py issues "2.16.0" + +# Include open issues too +python3 tools/gh.py issues "2.16.0" --state all + +# Exclude entries that should not go in the changelog +python3 tools/gh.py issues "2.16.0" --exclude "release blocker,no changelog" ``` -Then fetch all items: +**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 + +The script outputs JSON with each entry containing `number`, `title`, `state`, +`labels`, and `closing_prs` (the PRs that fix each issue). + +### 3. Identify missing entries (optional) + +If updating from an existing `CHANGES.md`, find issues in the milestone that +are NOT yet referenced in the changelog: ```bash -MILESTONE_NUMBER= -gh api "repos/penpot/penpot/issues?milestone=$MILESTONE_NUMBER&state=all&per_page=100" \ - --jq '.[] | {number: .number, title: .title, state: .state, labels: [.labels[].name], pull_request: .pull_request != null}' +python3 tools/gh.py issues "2.16.0" --exclude "release blocker,no changelog" --compare CHANGES.md ``` -### 3. Identify issue ↔ PR relationships +This returns a filtered JSON array with only the missing issues. -For each item, determine the relationship: +### 4. Fetch additional PR details when needed -- **Issue** (`pull_request: false`): This is the user-facing issue. It - becomes the primary link in the changelog. -- **PR** (`pull_request: true`): Check if it has `Fixes #` in its - body to find which issue it closes. - -To find the linked issue for a PR: +When you need more context for specific PRs (e.g. to find the PR author for +community contribution attribution, or to read the PR body for +"Fixes/Closes #NNN" patterns): ```bash -gh pr view --repo penpot/penpot \ - --json body,closingIssuesReferences --jq '{closingIssues: [.closingIssuesReferences[].number]}' +# One or more PR numbers +python3 tools/gh.py prs 9179 9204 9311 + +# From a file +python3 tools/gh.py prs --file prs.txt + +# From stdin +cat prs.txt | python3 tools/gh.py prs --stdin ``` -**Only closed issues are included.** An issue must have `state: "closed"` to -appear in the changelog. Open/unresolved issues are omitted, even if they are -tracked in the milestone. +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. -**Pairing rules:** +### 5. Categorize entries -| Pattern | Changelog format | -|---------|-----------------| -| Closed issue + one or more PRs fix it | Primary link = issue, sub-line with PRs comma-separated | -| PR exists with no linked issue | If a corresponding closed issue exists in the same milestone, link the issue. Otherwise, skip the entry (the issue must be the changelog unit). | -| Closed issue with no fix PR in milestone | Link the issue directly, without a PR sub-line. | - -### 4. Categorize entries - -Check the labels on each issue/PR: - -```bash -gh issue view --repo penpot/penpot --json labels --jq '[.labels[].name]' -``` +Check the labels on each issue to determine which section it belongs to: | Label / Title prefix | Changelog section | |----------------------|-------------------| @@ -90,19 +97,32 @@ gh issue view --repo penpot/penpot --json labels --jq '[.labels[].name] **Community contribution attribution:** If the issue or its fix PR has the `community contribution` label, add an attribution `(by @)` on the changelog entry line, **before** the GitHub issue/PR references. -Fetch the author: + +The attribution should reference the **PR author**, not the issue author. +The `prs` subcommand includes the `author` field — use that: ```bash -gh issue view --repo penpot/penpot --json author --jq '.author.login' +python3 tools/gh.py prs | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['author'])" ``` Placement in the entry line: ```markdown -- Fix description of the bug (by @username) [Github #](...) - (PR: [#](...)) +- Fix description of the bug (by @username) [#](...) (PR: [#](...)) ``` -### 5. Read the current CHANGES.md +**Only closed issues are included.** An issue must have `state: "closed"` to +appear in the changelog. Open/unresolved issues are omitted, even if they are +tracked in the milestone. + +**Pairing rules:** + +| Pattern | Changelog format | +|---------|-----------------| +| Closed issue + one or more PRs fix it | Primary link = issue, PR inline comma-separated | +| PR exists with no linked issue | If a corresponding closed issue exists in the same milestone, link the issue. Otherwise, skip the entry (the issue must be the changelog unit). | +| Closed issue with no fix PR in milestone | Link the issue directly, without a PR reference. | + +### 6. Read the current CHANGES.md Read the top of `CHANGES.md` to understand the existing format and find the insertion point (newest version goes at the top, after the `# CHANGELOG` @@ -115,30 +135,29 @@ Key format rules from the existing file: ### :bug: Bugs fixed -- Fix description of the bug [Github #](https://github.com/penpot/penpot/issues/) - (PR: [#](https://github.com/penpot/penpot/pull/)) -- Fix another bug (by @contributor) [Github #](https://github.com/penpot/penpot/issues/) - (PR: [#](https://github.com/penpot/penpot/pull/)) +- Fix description of the bug [#](https://github.com/penpot/penpot/issues/) (PR: [#](https://github.com/penpot/penpot/pull/)) +- Fix another bug (by @contributor) [#](https://github.com/penpot/penpot/issues/) (PR: [#](https://github.com/penpot/penpot/pull/)) ### :sparkles: New features & Enhancements -- Add new feature description [Github #](https://github.com/penpot/penpot/issues/) - (PR: [#](https://github.com/penpot/penpot/pull/)) +- Add new feature description [#](https://github.com/penpot/penpot/issues/) (PR: [#](https://github.com/penpot/penpot/pull/)) ``` Format details: - Entries start with `- ` followed by a short description in imperative mood - Primary link is **always the issue** (user-facing artifact) -- PR references are on an indented sub-line: ` (PR: [#]())` - If an issue has multiple fix PRs, they are comma-separated on one line: - ` (PR: [#](), [#]())` +- PR references are inline on the same line: `(PR: [#]())` + If an issue has multiple fix PRs, they are comma-separated: + `(PR: [#](), [#]())` - The description should describe the fix/feature from the user's perspective -- Community contributions get `(by @)` **before** the GitHub link +- Community contributions get `(by @)` **before** the issue link - Sections are separated by a blank line between the last entry and the next section title - Only include a section if there are entries for it +- When an entry already exists in an earlier version section, it must be removed + from the current version to avoid duplicates -### 6. Build the description text +### 7. Build the description text Derive the description from the issue title, not the PR title. Strip leading emoji prefixes (`:bug:`, `:sparkles:`, `:tada:`) and focus on the @@ -152,13 +171,13 @@ Examples: | `Comment content is not sanitized before rendering, enabling stored XSS` | `Sanitize comment content on rendering` | | `Custom uploaded font family names are not sanitized` | `Sanitize font family names on custom uploaded fonts` | -### 7. Insert the section into CHANGES.md +### 8. Insert the section into CHANGES.md Insert the new version section right after the `# CHANGELOG` header (before the previous version entry). Use the `edit` tool with enough context to make a unique match. -### 8. Verify +### 9. Verify Read the top of `CHANGES.md` and confirm: - The version header is correct @@ -174,17 +193,15 @@ Read the top of `CHANGES.md` and confirm: ### :bug: Bugs fixed -- [Github #](https://github.com/penpot/penpot/issues/) - (PR: [#](https://github.com/penpot/penpot/pull/)) -- (by @contributor) [Github #](https://github.com/penpot/penpot/issues/) - (PR: [#](https://github.com/penpot/penpot/pull/)) +- [#](https://github.com/penpot/penpot/issues/) (PR: [#](https://github.com/penpot/penpot/pull/)) +- (by @contributor) [#](https://github.com/penpot/penpot/issues/) (PR: [#](https://github.com/penpot/penpot/pull/)) ``` ## Key Principles - **Issue = changelog unit.** The primary link always points to the user-facing issue, not the implementation PR. -- **PR = implementation detail.** Reference the PR on a sub-line so readers +- **PR = implementation detail.** Reference the PR inline so readers can find the code changes. - **Latest version first.** New sections are inserted at the top of the changelog, below the `# CHANGELOG` header. @@ -192,10 +209,24 @@ Read the top of `CHANGES.md` and confirm: what broke and what was fixed, not internal implementation details. - **Community attribution.** When the issue or fix PR has the `community contribution` label, add `(by @)` on the entry line - between the description and the GitHub link. + between the description and the issue link. Use the **PR author** (not the + 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. - **Multiple PRs per issue.** If multiple PRs fix the same issue, list them - comma-separated on the same sub-line: `(PR: [#A](url), [#B](url))`. + comma-separated inline: `(PR: [#A](url), [#B](url))`. +- **Duplicate removal.** If an entry already exists in a prior version section, + remove it from the current version. Check for text-level duplicates (after + stripping links and attributions) across version sections. +- **Taiga references.** If a changelog entry references a Taiga URL + (`tree.taiga.io`), attempt to find a corresponding GitHub issue via the + Taiga description text or by searching GitHub PRs that reference the Taiga + URL. Replace the Taiga reference with the GitHub issue link and add the PR + reference if applicable. - **Re-fetch before editing.** Milestones can change — always re-fetch issues before making edits, don't rely on cached data. +- **Use `tools/gh.py`.** Prefer the helper script over raw `gh api` calls for + milestone issue listing and PR detail fetching. It handles GraphQL + pagination, batching, and label filtering automatically.