diff --git a/.claude/skills/creating-version-notes/SKILL.md b/.claude/skills/creating-version-notes/SKILL.md new file mode 100644 index 000000000..d7cef5134 --- /dev/null +++ b/.claude/skills/creating-version-notes/SKILL.md @@ -0,0 +1,198 @@ +--- +name: creating-version-notes +description: Use when preparing, updating, or reviewing the release documentation for a Struts release or release candidate on any maintenance line (6.x, 7.x) - the Version Notes page on the cwiki, its Migration Guide entry, and the GitHub release notes. +--- + +# Creating Version Notes + +## Overview + +A Version Notes page answers one question for a user deciding whether to upgrade: **what changed in this release, and what will break.** Almost all of it is a mechanical rendering of a JIRA fix version onto fixed boilerplate. + +**Core principle:** the mechanical parts must be *derived*, never retyped; the two judgement parts — Breaking changes, and how a security fix is described — are the only places you author prose. + +**One skill covers every maintenance line.** 6.x and 7.x pages share an identical structure. The line changes the data (version, prior page, JIRA ids), never the process. + +## The Iron Rule + +``` +START FROM THE TEMPLATE. NEVER CLONE THE PREVIOUS VERSION NOTES PAGE. +``` + +Cloning is how the published pages acquired their defects, and it fails differently every time: + +| Page | Inherited defect | +|---|---| +| Version Notes 6.9.0 | Issue Detail links **"JIRA Release Notes 6.8.0"** — label and `version=` id both from 6.8.0 | +| Version Notes 6.10.0 | Issue List links **"Struts 6.9.0 DONE"** — label names the previous release, against a `filter=` id different from the one the 6.9.0 page used | +| Both series | Maven Dependency code macro carries `ac:name=""` instead of `ac:name="language"` | + +Half-updated links are the signature failure: the number gets fixed and the label doesn't, or the reverse. They survive review because the link still works — it just points at, or claims to be, the wrong release. + +**[`version-notes-template.md`](version-notes-template.md) is the source of truth**: field guidance, storage-format skeleton with those defects corrected, and the pre-publication checklist. + +## Collect every input before writing + +Each row is derived from a named source. A value you cannot source is a visible placeholder, never a guess. + +| Input | Where it comes from | +|---|---| +| Version | The release being voted or announced | +| Parent page | Always **Migration Guide** (page id `13981`) — every Version Notes page is a child of it | +| Prior notes page title | The previous **released** version in the same series — see below | +| JIRA version id | Numeric id behind `ReleaseNote.jspa?version=` — from the WW project's versions, **not** the version name | +| DONE filter id | The saved JIRA filter for this release; a new release needs a new filter | +| Issue list | `project = WW AND fixVersion = `, grouped by type | +| Breaking changes | Authored — see below | +| Staging Repository block | An explicit decision — see below | + +## The issue list + +Group under `

` per issue type, in this order, omitting any type with no issues: + +**Bug → New Feature → Improvement → Task → Dependency** + +Within a section, order by issue key ascending. Each entry is `[WW-XXXX] - `. + +**Reconcile against what actually merged.** The JIRA query is the starting point, not the answer. Two mismatches to check: + +- A ticket marked fixed whose change did not make the release branch — it must not be listed. +- Work that shipped under a ticket assigned to a different fix version — the notes under-report the release. + +**Reconcile through the ticket's linked PR, reading the files it changed.** Do not grep commit subjects, and do not go looking for the class named in the ticket title: a title often names the *symptom* while the fix lives elsewhere. WW-5630 reads "Performance Issue SecurityMemberAccess" and was fixed in `ConfigParseUtil`; searching for the former concludes, wrongly, that the backport is missing. Squash-merges also rewrite hashes, so the merge commit id from the PR need not appear on the branch. + +**Untick eted patch-level dependency bumps are not a gap.** Dependabot PRs for patch updates are merged directly and deliberately get no ticket, so they get no entry — there is nothing to link. Expect the pom to show a higher patch version than the ticket text says: 6.11.0 shipped jackson 2.22.1 while WW-5648 reads "2.21.4 to 2.22.0". That is correct, not an omission. Minor and major bumps do get a ticket and do get listed. + +Where a ticket's summary was written for triage rather than for users, the page may carry a clearer summary — but then it is authored text, and the link must still resolve to that ticket. + +## Only released versions belong in the chain + +The prior-notes link forms a chain through the series, and it **skips versions that were cut but never released**. Version Notes 7.2.1 links back to 7.1.1, not to the withdrawn 7.2.0. + +When a release is superseded before it ships, its content does not disappear — the successor absorbs it. 7.2.1 carries the Breaking changes for the whole 7.2.x cycle. Check what the predecessor covered before assuming your issue list is complete. + +This is the same discipline `creating-security-bulletins` applies to Affected Software, for the same reason: naming a version that never reached users misdirects everyone downstream. + +## Breaking changes + +Present only when the release has them — a maintenance release usually does not. This section is **authored prose, not a ticket dump**: one item per change, each stating what an application must now do differently, with its ticket(s) linked at the end. + +The register is the upgrade decision, not the implementation. From 7.2.1: + +> `CookieInterceptor` now applies `@StrutsParameter` authorization to cookie values and deprecates the 4-arg `populateCookieValueIntoStack(...)` in favor of a new 5-arg overload taking the action, so un-annotated setters stop receiving cookies and subclass overrides must migrate. + +Name the type or setting a user must act on, say what stops working, and say what replaces it. + +## Security fixes in a release + +A release usually ships before its bulletin publishes and before a CVE exists. The Version Notes then list a **public, neutrally-framed** ticket for a defect whose advisory is still restricted. + +- List the ticket as you would any other. It is already public; omitting it under-reports the release. +- **Do not add security framing the bulletin has not published yet** — no severity, no attack description, no S2-XXX or CVE number that has not been assigned and published. +- Once the bulletin is public, the notes may link it. + +**REQUIRED BACKGROUND:** where the wording of a security-relevant entry is in question, `creating-security-bulletins` governs what may be said and when. + +## The Staging Repository block + +**Include it.** The block points readers at ASF Nexus staging so they can test the artifacts before the vote closes, and it stays on the page afterwards. + +Older 6.x pages (6.9.0, 6.10.0) lack it while the 7.x pages carry it. That is an artefact of cloning within each series, not a difference between the lines — 6.11.0 carries it. + +## Link the new page from the Migration Guide + +The page is not finished when it is created. **[Migration Guide](https://cwiki.apache.org/confluence/spaces/WW/pages/13981/Migration+Guide) (id `13981`) is both the parent page and the index**, and a Version Notes page that is not listed there is unreachable by anyone browsing. + +Add an entry at the **top** of the list under the `

` for the matching line — `Version Notes 7.x`, `Version Notes 6.x`, and so on. The lists are newest-first, and the entry is a page link carrying no body text: + +```xml +
  • +``` + +**Update the section, not the whole page.** `confluence_update_page_section` on the exact heading replaces only that section's body; its boundary is the next `

    `, so the section body includes the `

    ` migration-guide link that follows the list. Supply that `

    ` and its paragraph in the replacement content or they are dropped. + +**Verify against raw storage, not the diff.** A version diff of this page renders empty even for a real change, because the markdown view discards `ac:link` bodies. Fetch the new version with `convert_to_markdown=false` and confirm the new entry is present, the prior entries survive in order, and the trailing `

    ` appears exactly once. + +## The GitHub release notes + +A release also has a GitHub release at the `STRUTS_X_Y_Z` tag, kept as a **pre-release** while the vote runs. GitHub's generated body is a starting point that needs two corrections before it is fit to publish. + +### Check the range before anything else + +The generated body ends with `**Full Changelog**: .../compare/...`. **Confirm `` is the immediately preceding release on this line.** GitHub picks it by tag reachability, and Struts release branches get renamed and re-imported, so older tags are frequently *not* ancestors of the new one and the heuristic reaches too far back. + +For 6.11.0 it chose `STRUTS_6_8_0` and produced ~101 entries, 88 of which had already shipped in 6.9.0 and 6.10.0. + +Get the real change set from git, which works even across unrelated histories: + +```bash +git log --format='%h %s' STRUTS_6_10_0..STRUTS_6_11_0 +``` + +Drop every generated entry outside that range and correct the Full Changelog link to the right previous tag. Drop `## New Contributors` too when the contribution it cites falls outside the range. + +### Split the entries + +Two sections, `### Dependencies` nested under `## What's Changed`, before any `## New Contributors`: + +| Entry | Section | +|---|---| +| Carries a `WW-XXXX` ticket — whoever authored it | `## What's Changed` | +| A human PR that is not a dependency change (ci, chore, release prep) | `## What's Changed` | +| A dependency bump with **no** ticket | `### Dependencies` | + +**The discriminator is the ticket, not the author.** A Dependabot PR carrying a ticket stays in What's Changed, because a ticketed bump is release content and appears in the Version Notes Dependency section. A human PR that is purely a dependency change (`Removes unused jaxb-core dependency`) belongs under Dependencies. Both cases occur in the 6.9.0 release. + +Preserve the generated relative order within each section, and keep the entry lines byte-identical — they carry the author and PR links GitHub rendered. + +### Applying it + +```bash +gh release view STRUTS_X_Y_Z --json body -q .body > original.md # keep, so it can be restored +gh release edit STRUTS_X_Y_Z --prerelease --notes-file new.md +``` + +Pass `--prerelease` on the edit so a release still under vote is not silently promoted. + +## Re-read the page immediately before you write to it + +Confluence has no conflict warning. Fetch the current version immediately before every write and compare the version number against the one you read; if it advanced, re-read, merge onto the newer content, and write that. + +After writing, diff against the version you meant to build on. The diff should show only your intended change. + +## Red Flags — STOP + +- Starting from a copy of the previous release's page +- A version number or JIRA id typed rather than derived +- A link whose label and its id name different releases +- The prior-notes link pointing at a version that was cut but never released +- Publishing the issue list straight from JIRA without reconciling against the release branch +- Concluding a backport is missing from a commit-subject grep, or from the class named in the ticket title +- Treating an untick eted patch dependency bump as a reconciliation gap +- A severity, CVE, or S2-XXX reference on the page that has not been published +- Breaking changes assembled by pasting ticket summaries +- Creating the page without adding it to the Migration Guide index +- Trusting an empty version diff on the Migration Guide as proof the edit landed +- Publishing GitHub release notes without checking which tag the Full Changelog compares against +- Splitting the GitHub sections by author instead of by whether the entry carries a ticket +- Editing a GitHub release under vote without `--prerelease` +- Writing from page content read earlier in the session without re-fetching + +## Common Mistakes + +| Mistake | Reality | +|---|---| +| "Copying last release's page is faster" | It is how "JIRA Release Notes 6.8.0" shipped on the 6.9.0 page. Copy the template. | +| "I updated the link, it's fine" | Check the label too. Every observed defect is a half-updated link. | +| "`version=` takes the version number" | It takes JIRA's numeric version id. Look it up. | +| "The DONE filter can be reused" | A reused filter shows the previous release's issues under this release's heading. | +| "JIRA is the release contents" | JIRA is the claim. The release branch is the fact. Reconcile. | +| "No commit mentions the ticket, so it wasn't backported" | Read the linked PR's changed files. Titles name symptoms, and squash-merges rewrite hashes. | +| "The pom version doesn't match the ticket, that's a gap" | Patch bumps ship untick eted by design. Only ticketed bumps get an entry. | +| "The page is created, so the work is done" | It is invisible until listed on the Migration Guide. | +| "The version diff is empty, so nothing changed" | The diff renders markdown, which drops `ac:link` bodies. Check raw storage. | +| "GitHub generated the changelog, so the range is right" | It guesses the previous tag by reachability. Renamed branches make it reach too far back. Verify with `git log PREV..THIS`. | +| "Dependabot authored it, so it goes under Dependencies" | Ticketed bumps stay in What's Changed. The ticket decides, not the author. | +| "The fix is public, so I can describe the vulnerability" | The ticket being public does not publish the advisory. Neutral framing until the bulletin ships. | +| "Breaking changes are the tickets typed as breaking" | They are the changes that break an application. Author them. | +| "7.x needs different handling from 6.x" | Same structure, same process. Only the data differs. | diff --git a/.claude/skills/creating-version-notes/version-notes-template.md b/.claude/skills/creating-version-notes/version-notes-template.md new file mode 100644 index 000000000..04b438810 --- /dev/null +++ b/.claude/skills/creating-version-notes/version-notes-template.md @@ -0,0 +1,127 @@ +# Version Notes Template + +The canonical skeleton and per-field guidance for a Struts **Version Notes X.Y.Z** page +on the [Apache Struts 2 Wiki](https://cwiki.apache.org/confluence/spaces/WW) (space `WW`). +Companion to [`SKILL.md`](SKILL.md), which covers *how* to establish the values; +this file covers *what the page contains*. + +**This file is the source of truth.** Start every page from the skeleton below, never +from a copy of the previous release's page — see the Iron Rule in `SKILL.md`. + +## Fields + +| Field | What goes in it | +|---|---| +| Version | The release being announced, e.g. `6.11.0`. Appears in the intro sentence, the page title, the Maven snippet, and both JIRA link labels. | +| Parent page | Always `Migration Guide`, page id `13981`. Create the page as its child, and add it to that page's index — see `SKILL.md`. | +| Prior notes page | Title of the previous **released** version's page in the same series, e.g. `Version Notes 6.10.0`. Skip versions that were cut but never released. | +| JIRA version id | The numeric id for `ReleaseNote.jspa?version=`. Obtain from the WW project's versions — it is not the version name. `6.10.0` is `12357065`, `7.2.1` is `12355751`. | +| DONE filter id | Saved-filter id for `issues/?filter=`, labelled `Struts X.Y.Z DONE`. Each release needs its own; a reused id lists the wrong release. | +| TODO filter id | Constant across releases: `12351174`, labelled `Struts x.x.x TODO`. | +| Issue sections | One `

    ` per issue type present, ordered **Bug → New Feature → Improvement → Task → Dependency**, entries sorted by key ascending. | +| Breaking changes | Optional. Authored prose, one `
  • ` per change. Omit the section entirely when the release has none. | +| Staging Repository | Always included, on every line — see `SKILL.md`. | + +## Corrected storage format + +Three defects present in the published pages are fixed here. Keep them fixed: + +1. **`ac:name="language"` on the code macros.** The published Maven Dependency and + Staging Repository macros carry `ac:name=""` with the value `xml`, which is a + malformed parameter. The Archetype Catalog macro on the same pages has it right. +2. **No `ac:macro-id` attributes.** The published pages share hard-coded macro ids + across releases and across series because they were cloned. Omit the attribute and + let Confluence assign one on save. +3. **No trailing empty `
    `s.** Every published page ends with two empty divs + carrying inline `font-size: 24.0px` styling. They render as stray whitespace. + +```xml +

    These are the notes for the Struts version X.Y.Z distribution.

    +

    For prior notes in this release series, see

    +

    + +

    Maven users

    +

    If you are a Maven user, you might want to get started using the .

    + + Maven Dependency + xml + + org.apache.struts + struts2-core + X.Y.Z + +]]> + +

    You can also use Struts Archetype Catalog like below

    + + text + Struts Archetype Catalog + + + + + Staging Repository + xml + + + apache.nexus + ASF Nexus Staging + https://repository.apache.org/content/groups/staging/ + +]]> + + + +

    Breaking changes

    +
      +
    • WHAT AN APPLICATION MUST NOW DO DIFFERENTLY, AND WHAT REPLACES THE OLD BEHAVIOUR [WW-XXXX].
    • +
    + +

    Bug

    + + +

    Issue Detail

    + + +

    Issue List

    + + +

    Other resources

    + +``` + +Repeat the issue `

    ` block per type present, in the order given above. +`projectId=12311041` is the WW project and is constant. Note `&` in the +`ReleaseNote.jspa` URL — a bare `&` is invalid in storage format. + +## Before publishing + +- [ ] Every placeholder is replaced, and no guidance text survives on the page. +- [ ] Page title is `Version Notes X.Y.Z` and the intro names the same version. +- [ ] Prior-notes link resolves, and names the previous **released** version. +- [ ] Maven snippet version matches the release. +- [ ] `ReleaseNote.jspa` label and its `version=` id are the same release. +- [ ] `DONE` filter label and its `filter=` id are the same release. +- [ ] Issue list reconciled against the release branch via each ticket's linked PR, not taken from JIRA alone. +- [ ] Issue types ordered Bug → New Feature → Improvement → Task → Dependency; empty types omitted. +- [ ] Breaking changes authored, or the section omitted because there are none. +- [ ] Staging Repository block present. +- [ ] No unpublished severity, CVE, or S2-XXX reference anywhere on the page. +- [ ] Page created as a child of Migration Guide (`13981`). +- [ ] **Listed at the top of the matching `Version Notes N.x` section on the Migration Guide**, and that edit verified against raw storage — the version diff renders empty even when the change landed. +- [ ] Page re-fetched immediately before every write. + +## GitHub release notes + +- [ ] Original generated body saved before editing, so it can be restored. +- [ ] Full Changelog compares against the **immediately preceding release** on this line, verified with `git log PREV..THIS` — GitHub's guess is often wrong after a branch rename. +- [ ] Entries outside that range removed, including a `## New Contributors` block citing one. +- [ ] Entries split by **ticket, not author**: ticketed → `## What's Changed`; untick eted dependency bumps → `### Dependencies`. +- [ ] Generated order and entry text preserved within each section. +- [ ] `gh release edit` passed `--prerelease` while the vote is open.