This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch vote-mail-skill
in repository https://gitbox.apache.org/repos/asf/struts.git

commit b7a9dfa10e4005507ab040b79c591d3af6ea96ac
Author: Lukasz Lenart <[email protected]>
AuthorDate: Sat Aug 8 07:28:15 2026 +0200

    docs: implementation plan for creating-release-vote-mail
    
    Co-Authored-By: Claude Opus 5 <[email protected]>
---
 .../2026-08-08-creating-release-vote-mail-skill.md | 669 +++++++++++++++++++++
 1 file changed, 669 insertions(+)

diff --git 
a/docs/superpowers/plans/2026-08-08-creating-release-vote-mail-skill.md 
b/docs/superpowers/plans/2026-08-08-creating-release-vote-mail-skill.md
new file mode 100644
index 000000000..a55718a9e
--- /dev/null
+++ b/docs/superpowers/plans/2026-08-08-creating-release-vote-mail-skill.md
@@ -0,0 +1,669 @@
+# `creating-release-vote-mail` Skill Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use 
superpowers:subagent-driven-development (recommended) or 
superpowers:executing-plans to implement this plan task-by-task. Steps use 
checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Add a `creating-release-vote-mail` skill that drafts the `[VOTE] 
Apache Struts X.Y.Z` mail as a plain-text rendering of the already-published 
Version Notes page.
+
+**Architecture:** Two files under 
`.claude/skills/creating-release-vote-mail/`, matching the shape of the three 
sibling skills in the same directory — `SKILL.md` carries judgement 
(preconditions, recipients, transform rules, failure modes) and 
`vote-mail-template.md` carries the frozen artifact (headers, slots, ASF vote 
boilerplate). A one-line handoff is added to `creating-version-notes` so the 
two chain.
+
+**Tech Stack:** Markdown skill files with YAML frontmatter; Gmail MCP 
(`create_draft`); `gh` CLI and `curl` for precondition checks.
+
+**Spec:** 
[`docs/superpowers/specs/2026-08-08-release-vote-mail-skill-design.md`](../specs/2026-08-08-release-vote-mail-skill-design.md)
+
+## Global Constraints
+
+- Skill directory: `.claude/skills/creating-release-vote-mail/`, sibling to 
`creating-version-notes`, `creating-security-bulletins`, 
`triaging-security-reports`.
+- Skill name is gerund-form kebab-case, matching every existing skill in that 
directory.
+- Frontmatter is exactly two keys, `name` and `description`, matching the 
sibling skills.
+- The skill **drafts, never sends**. Gmail `create_draft` only.
+- Recipients are `To: [email protected]`, `Bcc: 
[email protected]`. `[email protected]` must appear nowhere in 
either file except as a prohibition.
+- Sign-off is exactly `On behalf of the Apache Struts project`.
+- Staging URL is exactly 
`https://repository.apache.org/content/repositories/staging/`.
+- The ASF vote boilerplate is reproduced **byte-identical** to the archived 
mails, including `Github release` without its colon.
+- No tests ship with this skill, matching `creating-version-notes`.
+- Commits use the `docs:` prefix with no Jira ticket — this is process 
documentation, not framework code.
+- All work lands on branch `vote-mail-skill` (already created) and finishes as 
a PR. Never push to `main`.
+- Never `git add -A` in this repo; stage explicit paths and check `git diff 
--cached --name-only`.
+
+## File Structure
+
+| File | Responsibility |
+|---|---|
+| `.claude/skills/creating-release-vote-mail/vote-mail-template.md` | The 
artifact: field table, mail skeleton with slots, frozen boilerplate, pre-send 
checklist. Changes when the mail's wording changes. |
+| `.claude/skills/creating-release-vote-mail/SKILL.md` | The judgement: when 
to use, preconditions, recipient rule, transform rules, verification, Red 
Flags, Common Mistakes. Changes when a new failure mode is learned. |
+| `.claude/skills/creating-version-notes/SKILL.md` | Modified: one handoff 
line at the end of "The test-build announcement". |
+
+The split follows the standing decision recorded for `creating-version-notes`: 
mechanical reference material goes in the template file, every rule requiring 
judgement stays in `SKILL.md`, because a rule in an unloaded file is a rule 
that will not be followed.
+
+---
+
+### Task 1: The template file
+
+**Files:**
+- Create: `.claude/skills/creating-release-vote-mail/vote-mail-template.md`
+- Reference (read only, do not modify): 
`.claude/skills/creating-version-notes/version-notes-template.md`, 
`.claude/skills/creating-security-bulletins/bulletin-template.md`
+
+**Interfaces:**
+- Produces: the slot names `<X.Y.Z>`, `<X_Y_Z>`, `<SHAPE SENTENCE>` and the 
section-block names `Breaking changes`, `Deprecations`, `Issue list`. Task 2's 
`SKILL.md` refers to these by exactly these names.
+
+- [ ] **Step 1: Read the two sibling templates for house shape**
+
+Read `.claude/skills/creating-version-notes/version-notes-template.md` and
+`.claude/skills/creating-security-bulletins/bulletin-template.md`. Both open 
with a
+"Companion to `SKILL.md`" preamble, then a `## Fields` table, then the 
skeleton. Match that
+order — do not invent a new structure.
+
+- [ ] **Step 2: Write the template file**
+
+Create `.claude/skills/creating-release-vote-mail/vote-mail-template.md` with 
exactly this content:
+
+````markdown
+# Release Vote Mail Template
+
+The canonical skeleton for the `[VOTE] Apache Struts X.Y.Z` mail that opens a 
release vote
+on `[email protected]`. Companion to [`SKILL.md`](SKILL.md), which covers 
*how* to
+establish what goes in the slots; this file covers *what the mail contains*.
+
+**This file is the source of truth.** Start every vote mail from the skeleton 
below, never
+from a copy of the previous release's mail — see the Iron Rule in `SKILL.md`.
+
+## Fields
+
+| Slot | What goes in it |
+|---|---|
+| `<X.Y.Z>` | The release being voted on, dotted, e.g. `7.3.0`. Appears in the 
subject, the opening sentence, the Version Notes URL and the dist path. |
+| `<X_Y_Z>` | The same version underscored, e.g. `7_3_0`, for the `STRUTS_` 
git tag only. |
+| `<SHAPE SENTENCE>` | One sentence describing the *shape* of the issue list, 
never its individual tickets. See `SKILL.md`. |
+| Breaking changes | Optional block. The Version Notes page's items verbatim, 
`- ` prefixed. Omit the block when the page has none. |
+| Deprecations | Optional block. Same shape. Omit when the page has none. |
+| Issue list | The page's issue-type sections in page order, rendered as plain 
text. Never retyped from JIRA. |
+
+`Rejected requests` is **not** a slot. The page may carry that section; the 
mail never does.
+
+## Skeleton
+
+```
+Subject: [VOTE] Apache Struts <X.Y.Z>
+To:      [email protected]
+Bcc:     [email protected]
+
+The Apache Struts <X.Y.Z> test build is available. <SHAPE SENTENCE>
+
+Breaking changes
+
+- <page item, verbatim, ending [WW-XXXX].>
+- <page item, verbatim, ending [WW-XXXX].>
+
+Deprecations
+
+- <page item, verbatim, ending [WW-XXXX].>
+
+Bug
+[WW-XXXX] - <summary>
+[WW-XXXX] - <summary>
+
+New Feature
+[WW-XXXX] - <summary>
+
+Improvement
+[WW-XXXX] - <summary>
+
+Task
+[WW-XXXX] - <summary>
+
+Dependency
+[WW-XXXX] - <summary>
+
+Release notes:
+* https://cwiki.apache.org/confluence/display/WW/Version+Notes+<X.Y.Z>
+
+Github release
+* https://github.com/apache/struts/releases/tag/STRUTS_<X_Y_Z>
+
+Distribution:
+* https://dist.apache.org/repos/dist/dev/struts/<X.Y.Z>/
+
+Maven 2 staging repository:
+* https://repository.apache.org/content/repositories/staging/
+
+Once you have had a chance to review the test build, please respond
+with a vote on its quality:
+
+[ ] Leave at test build
+[ ] Alpha
+[ ] Beta
+[ ] General Availability (GA)
+
+Everyone who has tested the build is invited to vote. Votes by PMC
+members are considered binding. A vote passes if there are at least
+three binding +1s and more +1s than -1s.
+
+The vote will remain open for at least 72 hours, longer upon request.
+A vote can be amended at any time to upgrade or downgrade the quality
+of the release based on future experience. If an initial vote
+designates the build as "Beta", the release will be submitted for
+mirroring and announced to the user list. Once released as a public
+beta, subsequent quality votes on a build may be held on the user
+list.
+
+As always, the act of voting carries certain obligations. A binding
+vote not only states an opinion, but means that the voter is agreeing
+to help do the work.
+
+On behalf of the Apache Struts project
+Łukasz
+```
+
+## Frozen text — do not edit
+
+Everything from `Once you have had a chance to review the test build` to the 
sign-off is
+**byte-frozen**. It is the ASF vote call: the quality options voters tick, the 
binding-vote
+threshold, the 72-hour minimum, and the obligation a binding vote carries. 
Rewording any of
+it changes what the project is asking for.
+
+Three details that look like typos and are kept deliberately:
+
+| Detail | Why it stays |
+|---|---|
+| `Github release` has no trailing colon, unlike the other three link labels | 
Every archived Struts vote mail reads this way. The template records what 
ships. |
+| The staging URL is `content/repositories/staging/`, while the `[TEST]` mail 
uses `content/groups/staging/` | Both are valid Nexus endpoints. The vote 
mail's form is what past votes used; the difference is accepted, not a defect 
to reconcile. |
+| All four quality checkboxes are empty | The release manager's own `+1 
(binding)` is a separate reply. A call arriving with GA pre-ticked reads as a 
decision announced, not a vote opened. |
+
+## Pre-send checklist
+
+- [ ] All four links resolve, and the GitHub release is still flagged 
pre-release
+- [ ] Ticket ids in the mail match the page's, excluding the page's `Rejected 
requests`
+- [ ] Boilerplate byte-identical to the frozen text above
+- [ ] `To: dev@` only — `user@` absent — and `Bcc: private@` present
+- [ ] Subject is exactly `[VOTE] Apache Struts X.Y.Z`
+- [ ] All four checkboxes empty
+- [ ] Body hard-wrapped at 72 columns, continuation lines unindented
+````
+
+- [ ] **Step 3: Verify the boilerplate is byte-identical to the archived mail**
+
+Write the archived 6.10.0 boilerplate to a scratchpad file and diff the 
template's copy
+against it. Both mails carry identical boilerplate, so either is a valid 
reference.
+
+```bash
+SCRATCH=/private/tmp/claude-501/-Users-lukaszlenart-Projects-Apache-struts/322a9b9b-d830-4ab7-a7e8-2e30a7682260/scratchpad
+cat > "$SCRATCH/boilerplate-reference.txt" <<'EOF'
+Once you have had a chance to review the test build, please respond
+with a vote on its quality:
+
+[ ] Leave at test build
+[ ] Alpha
+[ ] Beta
+[ ] General Availability (GA)
+
+Everyone who has tested the build is invited to vote. Votes by PMC
+members are considered binding. A vote passes if there are at least
+three binding +1s and more +1s than -1s.
+
+The vote will remain open for at least 72 hours, longer upon request.
+A vote can be amended at any time to upgrade or downgrade the quality
+of the release based on future experience. If an initial vote
+designates the build as "Beta", the release will be submitted for
+mirroring and announced to the user list. Once released as a public
+beta, subsequent quality votes on a build may be held on the user
+list.
+
+As always, the act of voting carries certain obligations. A binding
+vote not only states an opinion, but means that the voter is agreeing
+to help do the work.
+EOF
+
+sed -n '/^Once you have had a chance/,/^to help do the work\.$/p' \
+  .claude/skills/creating-release-vote-mail/vote-mail-template.md \
+  > "$SCRATCH/boilerplate-template.txt"
+
+diff "$SCRATCH/boilerplate-reference.txt" "$SCRATCH/boilerplate-template.txt"
+```
+
+Expected: no output. Any diff means the boilerplate was retyped rather than 
copied — fix the
+template, do not adjust the reference.
+
+- [ ] **Step 4: Verify the constrained strings**
+
+```bash
+grep -n '[email protected]' 
.claude/skills/creating-release-vote-mail/vote-mail-template.md
+grep -c 'content/repositories/staging/' 
.claude/skills/creating-release-vote-mail/vote-mail-template.md
+grep -n 'On behalf of the Apache Struts project' 
.claude/skills/creating-release-vote-mail/vote-mail-template.md
+```
+
+Expected: the first prints nothing (exit 1); the second prints `2` (skeleton 
plus the
+frozen-text table row); the third prints one line.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add .claude/skills/creating-release-vote-mail/vote-mail-template.md
+git diff --cached --name-only
+git commit -m "docs: add the release vote mail template
+
+Freezes the [VOTE] Apache Struts X.Y.Z skeleton: slots, link block, and
+the ASF vote boilerplate reproduced byte-identical to the archived 6.10.0
+and 7.2.1 mails.
+
+Co-Authored-By: Claude Opus 5 <[email protected]>"
+```
+
+---
+
+### Task 2: The skill file
+
+**Files:**
+- Create: `.claude/skills/creating-release-vote-mail/SKILL.md`
+- Reference (read only): `.claude/skills/creating-version-notes/SKILL.md`
+
+**Interfaces:**
+- Consumes: from Task 1, the slot names `<X.Y.Z>`, `<X_Y_Z>`, `<SHAPE 
SENTENCE>` and the block names `Breaking changes`, `Deprecations`, `Issue 
list`; the pre-send checklist, which `SKILL.md` points at rather than 
duplicating.
+- Produces: the skill `name: creating-release-vote-mail`, which Task 3's 
handoff line names.
+
+- [ ] **Step 1: Confirm the description does not collide with the sibling**
+
+```bash
+grep -n '^description:' .claude/skills/*/SKILL.md
+```
+
+Expected: `creating-version-notes` ends its description at "the test-build 
announcement mail".
+The new description must begin where that ends and must not contain the words 
"Version Notes
+page", "Migration Guide" or "GitHub release notes" as things it *produces* — 
those are its
+inputs. Two skills claiming the same trigger is the failure this step prevents.
+
+- [ ] **Step 2: Write `SKILL.md`**
+
+Create `.claude/skills/creating-release-vote-mail/SKILL.md` with this content:
+
+````markdown
+---
+name: creating-release-vote-mail
+description: Use when opening the formal release vote for a Struts release 
candidate on any maintenance line (6.x, 7.x) - composing and drafting the 
[VOTE] Apache Struts X.Y.Z mail to dev@ once the Version Notes page, GitHub 
release and staged artifacts are published.
+---
+
+# Creating a Release Vote Mail
+
+## Overview
+
+The `[VOTE]` mail opens the formal release vote. It is four links wrapped in 
frozen ASF
+boilerplate, around a plain-text rendering of the release's Version Notes page.
+
+**Core principle:** the mail is a *rendering* of the page, not a second 
account of the
+release. Everything below the opening sentence is a transform of a published 
cwiki section,
+so the mail cannot assert something the page does not.
+
+**This is the step after 
[`creating-version-notes`](../creating-version-notes/SKILL.md).**
+That skill produces the page, the GitHub release and the `[TEST]` 
announcement; this one
+consumes all three. If they do not exist yet, you are in the wrong skill.
+
+## The Iron Rule
+
+```
+THE VERSION NOTES PAGE IS THE ONLY SOURCE FOR THE BODY.
+NEVER RETYPE THE ISSUE LIST, AND NEVER CLONE THE PREVIOUS VOTE MAIL.
+```
+
+Cloning fails the same way it fails for Version Notes pages: the version 
number gets updated
+and the surrounding text does not. The 7.2.1 and 6.10.0 mails already disagree 
on their
+sign-off, which is what cloning drift looks like before anyone notices it.
+
+[`vote-mail-template.md`](vote-mail-template.md) is the source of truth for 
the artifact —
+slots, link block, frozen boilerplate, and the pre-send checklist.
+
+## Check every precondition before drafting
+
+The mail is four links. A vote opened on a 404 burns the 72-hour window before 
anyone can
+test, and the mail cannot be recalled from a public archive.
+
+| Link | Produced by | How to check |
+|---|---|---|
+| `Version+Notes+X.Y.Z` on cwiki | `creating-version-notes` | Fetch it — it is 
also the body source |
+| `releases/tag/STRUTS_X_Y_Z` | `creating-version-notes` | `gh release view 
STRUTS_X_Y_Z --json isPrerelease,url` — must still be a prerelease |
+| `dist/dev/struts/X.Y.Z/` | The release build | `curl -sI` the directory; 
artifacts and their `.asc`/`.sha512` present |
+| Nexus staging | `mvn release` | The staging repository is open, not dropped 
or already released |
+
+A GitHub release that is no longer a prerelease means someone promoted it 
before the vote
+closed. Stop and resolve that before drafting.
+
+## Recipients
+
+```
+To:  [email protected]
+Bcc: [email protected]
+```
+
+**`user@` must not appear.** The `[TEST]` mail one step earlier goes to `dev@` 
*and* `user@`,
+which is correct for it — it asks people to test. This mail asks people to 
*vote*, and a vote
+invitation on the user list solicits votes from people whose votes are not 
binding.
+
+The subject is exactly `[VOTE] Apache Struts X.Y.Z`. No "test build", no RC 
suffix, no
+release-quality word.
+
+## The opening sentence is the only prose you author
+
+Two sentences. The first is fixed: `The Apache Struts X.Y.Z test build is 
available.` The
+second describes the *shape* of the issue list.
+
+| The page has | Second sentence |
+|---|---|
+| No Breaking changes | `With this release the following issues were 
addressed:` |
+| Breaking changes | `This release contains <what>. Also a lot of dependencies 
have been updated:` |
+
+Fill `<what>` from what the list actually holds — `a few minor breaking 
changes plus some bug
+fixes` for 7.2.1. **Never preview individual tickets here.** The list below it 
is the preview;
+a ticket named in the opener is a ticket you have decided matters more than 
its neighbours,
+which is an editorial judgement the vote mail has no business making.
+
+## Transform rules, per section
+
+| Page section | In the mail |
+|---|---|
+| Breaking changes | Verbatim, `- ` prefixed, ticket refs as bare `[WW-XXXX]` 
text |
+| Deprecations | Verbatim, same shape |
+| Rejected requests | **Omitted** |
+| Bug / New Feature / Improvement / Task / Dependency | Page order, heading 
bare on its own line, entries `[WW-XXXX] - <summary>` |
+
+**Copy Breaking changes verbatim.** Verbatim is what holds each item at the 
page's
+one-sentence form. Re-authoring them from the tickets is how the 7.2.1 items 
grew to three
+clauses each, which is longer than the rule the page itself follows.
+
+**Deprecations are in; Rejected requests are out.** A deprecation tells a 
voter what to check
+in their own application, so it belongs in front of the people testing. A 
`Won't Do` ticket
+has nothing to test — it is release documentation, and the Release notes link 
carries it.
+
+**Plain text carries no links.** A ticket reference is the bare string 
`[WW-XXXX]`, and the
+only URLs in the mail are the four link lines.
+
+**Hard-wrap at 72 columns**, continuation lines unindented. This is what keeps 
the issue list
+legible in the ASF archives and in the quoted replies voters send back.
+
+## A security summary the page truncated stays truncated
+
+Where the Version Notes page stopped a ticket summary at a clause boundary 
because its
+bulletin is unpublished, the mail stops at the same boundary.
+
+`[email protected]` is a **public, permanently archived** list. 
Re-expanding a truncated
+summary there publishes what the page deliberately withheld, to a wider 
audience than the
+page, and no correction removes it from the archive.
+
+Nothing else about the release's security posture reaches this mail either: no 
severity, no
+attack description, no S2-XXX or CVE that has not been published.
+
+**REQUIRED BACKGROUND:** where the wording of a security-relevant entry is in 
question,
+`creating-security-bulletins` governs what may be said and when.
+
+## A ticket/prose version mismatch is not an error
+
+A Dependency entry may name a lower version than the Breaking changes prose. 
7.2.1 lists
+`[WW-5536] - Bump ognl:ognl from 3.3.5 to 3.4.8` while its Breaking changes 
says OGNL went to
+3.4.11. Both are correct: the entry reproduces the ticket summary verbatim, 
the prose states
+what shipped, and patch bumps land after a ticket is titled.
+
+**Do not reconcile it in the mail.** Changing either one makes the mail 
disagree with the
+page, which is the one thing a rendering may never do.
+
+## Verify, then draft
+
+Run the pre-send checklist in 
[`vote-mail-template.md`](vote-mail-template.md). The one worth
+scripting is the ticket-set comparison — sort the `WW-` ids in your draft 
against the page's,
+excluding the page's `Rejected requests` section. Empty output, or the mail is 
not a rendering
+and you should find out which direction the difference runs before sending.
+
+## Draft it, do not send it
+
+Create a Gmail draft with `To`, `Bcc`, `Subject` and body set. **Never send.**
+
+Sending opens a binding project vote on a public list, starts the 72-hour 
clock, and commits
+the PMC to the artifacts as staged. That is the release manager's keystroke, 
not yours.
+
+## Red Flags — STOP
+
+- Starting from a copy of the previous release's vote mail
+- Retyping the issue list from JIRA instead of rendering the page
+- Re-expanding a summary the page truncated
+- Adding `[email protected]` to the recipients
+- Pre-ticking a quality level in the checkbox block
+- Drafting before the page, tag, dist path or staging repo exist
+- A severity, CVE or S2-XXX reference anywhere in the mail
+- Editing the boilerplate wording
+- "Correcting" a Dependency entry so it matches the Breaking changes prose
+- Naming individual tickets in the opening sentence
+- Sending, rather than drafting
+
+## Common Mistakes
+
+| Mistake | Reality |
+|---|---|
+| "Last release's vote mail is the fastest start" | It is how the sign-off 
drifted between 6.10.0 and 7.2.1. Start from the template. |
+| "The `[TEST]` mail went to `user@`, so this should too" | That mail asks 
people to test. This one asks people to vote, and user-list votes are not 
binding. `dev@` only. |
+| "The ticket says 3.4.8 but we shipped 3.4.11" | Both are right. The entry is 
the ticket summary verbatim; the prose is what shipped. Leave it. |
+| "The ticket is public, so I can describe the vulnerability" | A public 
ticket does not publish the advisory, and `dev@` is archived forever. Neutral 
framing until the bulletin ships. |
+| "The page is up, so I can draft" | Check the tag, the dist path and the 
staging repo too. A vote on a 404 wastes 72 hours. |
+| "I'll tick GA since that's what we want" | The release manager's `+1` is a 
separate reply. A pre-ticked call announces a decision instead of opening a 
vote. |
+| "Rejected requests are part of the release, so list them" | Voters test 
artifacts. A `Won't Do` ticket has nothing to test; the page link carries it. |
+| "It only needs the ticket numbers, I'll type them out" | Typing is the 
failure mode. Render the page. |
+````
+
+- [ ] **Step 3: Verify frontmatter and cross-references**
+
+```bash
+head -4 .claude/skills/creating-release-vote-mail/SKILL.md
+grep -c 'vote-mail-template.md' 
.claude/skills/creating-release-vote-mail/SKILL.md
+grep -n '[email protected]' 
.claude/skills/creating-release-vote-mail/SKILL.md
+```
+
+Expected: frontmatter is `---`, `name:`, `description:`, `---` and nothing 
else; the template
+is referenced at least 3 times; `user@` appears only in the prohibition and 
the two mistake
+rows that explain it — read each hit and confirm none of them is a recipient 
instruction.
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add .claude/skills/creating-release-vote-mail/SKILL.md
+git diff --cached --name-only
+git commit -m "docs: add creating-release-vote-mail skill
+
+Drafts the [VOTE] Apache Struts X.Y.Z mail as a rendering of the
+published Version Notes page. Records the dev@-only recipient rule, the
+truncated-security-summary carry-through, and the draft-never-send
+boundary.
+
+Co-Authored-By: Claude Opus 5 <[email protected]>"
+```
+
+---
+
+### Task 3: Chain it to `creating-version-notes`
+
+**Files:**
+- Modify: `.claude/skills/creating-version-notes/SKILL.md` — end of the `## 
The test-build announcement` section, immediately before `## Re-read the page 
immediately before you write to it`
+
+**Interfaces:**
+- Consumes: the skill name `creating-release-vote-mail` from Task 2.
+
+- [ ] **Step 1: Locate the insertion point**
+
+```bash
+grep -n '^## ' .claude/skills/creating-version-notes/SKILL.md
+```
+
+The handoff goes at the **end** of `## The test-build announcement`, after the 
paragraph
+beginning `Keep the security posture of the pages`.
+
+- [ ] **Step 2: Add the handoff line**
+
+Append this paragraph to that section:
+
+```markdown
+**The vote is the next step, and it is a different mail.** Once the `[TEST]` 
build has been
+announced, `creating-release-vote-mail` composes the `[VOTE] Apache Struts 
X.Y.Z` call — to
+`dev@` alone, rendered from the page this skill produced. Do not draft it from 
here: its
+recipients, subject and body differ from the announcement above.
+```
+
+- [ ] **Step 3: Verify nothing else moved**
+
+```bash
+git diff --stat .claude/skills/creating-version-notes/SKILL.md
+git diff .claude/skills/creating-version-notes/SKILL.md
+```
+
+Expected: `1 file changed, N insertions(+)` with **no deletions**. Any 
deletion means a
+neighbouring section was disturbed — this skill's own rules warn about 
swallowing an adjacent
+heading, and the same care applies to editing it.
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add .claude/skills/creating-version-notes/SKILL.md
+git diff --cached --name-only
+git commit -m "docs: point creating-version-notes at the vote mail step
+
+Co-Authored-By: Claude Opus 5 <[email protected]>"
+```
+
+---
+
+### Task 4: Dry run against the archived 6.10.0 vote
+
+This is the closest thing the skill has to a test: render a mail the project 
already sent, and
+compare. 6.10.0 is the better subject than 7.2.1 — its issue list is three 
tickets, so a diff
+is readable, and it has no Breaking changes block, which exercises the 
omission path.
+
+**Files:**
+- No repository files change. Working files go in the scratchpad.
+
+**Interfaces:**
+- Consumes: `SKILL.md` and `vote-mail-template.md` from Tasks 1 and 2.
+
+- [ ] **Step 1: Capture the archived mail as the expected output**
+
+Fetch the sent `[VOTE] Apache Struts 6.10.0` mail (Gmail thread 
`19e5fc8e0a444f92`, message
+`19e5fcd6bf35b65b`) and save its plaintext body to 
`$SCRATCH/expected-6.10.0.txt`.
+
+- [ ] **Step 2: Render a mail from the published page, following the skill**
+
+Follow `SKILL.md` end to end for version `6.10.0`, sourcing the body from
+`https://cwiki.apache.org/confluence/display/WW/Version+Notes+6.10.0`. Write 
the result to
+`$SCRATCH/rendered-6.10.0.txt` instead of creating a draft.
+
+Skip the precondition checks that cannot pass for a shipped release — the 
6.10.0 GitHub
+release is no longer a prerelease and its staging repo is long closed. Note 
which checks you
+skipped; that list is itself a finding if it is longer than those two.
+
+- [ ] **Step 3: Diff and classify every difference**
+
+```bash
+diff -u "$SCRATCH/expected-6.10.0.txt" "$SCRATCH/rendered-6.10.0.txt"
+```
+
+Expected: differences only in the sign-off (`Kind regards` → `On behalf of the 
Apache Struts
+project`, a deliberate normalisation from the spec) and in wrapping of the 
authored opener.
+
+Classify each remaining difference into exactly one bucket:
+
+| Bucket | Action |
+|---|---|
+| Deliberate normalisation named in the spec | None — expected |
+| The skill produced something wrong | Fix `SKILL.md` or the template, re-run 
from Step 2 |
+| The archived mail was wrong | Add a Common Mistakes row so the skill 
prevents it next time |
+
+- [ ] **Step 4: Verify the ticket-set check actually works**
+
+```bash
+grep -o 'WW-[0-9]*' "$SCRATCH/rendered-6.10.0.txt" | sort -u
+```
+
+Expected: exactly `WW-5623`, `WW-5628`, `WW-5629` — matching the page's three 
tickets. If the
+rendering dropped or invented one, the transform rules in Task 2 need fixing.
+
+- [ ] **Step 5: Commit any corrections**
+
+If Steps 3 or 4 required changes:
+
+```bash
+git add .claude/skills/creating-release-vote-mail/
+git diff --cached --name-only
+git commit -m "docs: correct the vote mail skill from the 6.10.0 dry run
+
+Co-Authored-By: Claude Opus 5 <[email protected]>"
+```
+
+If nothing changed, record the dry-run outcome in the PR description instead 
and move on.
+
+---
+
+### Task 5: Open the PR
+
+**Files:**
+- No file changes.
+
+- [ ] **Step 1: Review the whole branch before pushing**
+
+```bash
+git log --oneline main..vote-mail-skill
+git diff main...vote-mail-skill
+```
+
+Read the full diff. Every change on this branch gets the same review gate, 
including the
+corrections Task 4 may have added.
+
+- [ ] **Step 2: Confirm this is not a security patch**
+
+This branch adds process documentation and touches no framework code. Confirm 
with:
+
+```bash
+git diff --name-only main...vote-mail-skill
+```
+
+Expected: only paths under `.claude/skills/` and `docs/superpowers/`. Anything 
under `core/`
+or `plugins/` means something unintended was staged — stop and investigate 
before pushing.
+
+- [ ] **Step 3: Push and open the PR**
+
+```bash
+git push -u origin vote-mail-skill
+gh pr create --title "docs: add creating-release-vote-mail skill" --body 
"$(cat <<'EOF'
+Adds a `creating-release-vote-mail` skill that drafts the `[VOTE] Apache 
Struts X.Y.Z` mail
+as a plain-text rendering of the already-published Version Notes page.
+
+It is the step after `creating-version-notes`: that skill ends at the `[TEST]` 
announcement,
+this one opens the vote.
+
+- `vote-mail-template.md` freezes the skeleton and the ASF vote boilerplate, 
reproduced
+  byte-identical to the archived 6.10.0 and 7.2.1 mails.
+- `SKILL.md` records the judgement: `dev@`-only recipients, Deprecations in 
and Rejected
+  requests out, truncated security summaries carrying through unchanged, and 
drafting rather
+  than sending.
+- Validated by re-rendering the archived 6.10.0 vote mail from its published 
page.
+
+Design: `docs/superpowers/specs/2026-08-08-release-vote-mail-skill-design.md`
+
+No Jira ticket — process documentation, consistent with the 
`creating-version-notes` and
+`creating-security-bulletins` skill commits.
+
+🤖 Generated with [Claude Code](https://claude.com/claude-code)
+EOF
+)"
+```
+
+---
+
+## Self-Review
+
+**Spec coverage.** Every spec section maps to a task: identity and frontmatter 
→ Task 2 Step 2;
+preconditions table → Task 2; recipients → Tasks 1 and 2, verified in Task 1 
Step 4 and Task 2
+Step 3; opening sentence → Task 2; transform rules including 
Deprecations-in/Rejected-out →
+Task 2; security truncation → Task 2; template with frozen sign-off and 
staging URL → Task 1;
+empty checkboxes → Task 1; verification checklist → Task 1 Step 2, exercised 
in Task 4;
+Gmail-draft-never-send → Task 2; Red Flags and Common Mistakes → Task 2; "no 
tests" → Global
+Constraints, with Task 4 standing in as a dry run. The spec's position-in-flow 
diagram is what
+Task 3 implements.
+
+**Placeholders.** None. Every file's full content is given inline; every 
command is runnable;
+every "Expected:" states a concrete result.
+
+**Name consistency.** `creating-release-vote-mail` (skill name, directory, 
frontmatter, Task 3
+handoff, PR title), `vote-mail-template.md` (Task 1 creates it, Task 2 links 
it three times),
+and the slot names `<X.Y.Z>` / `<X_Y_Z>` / `<SHAPE SENTENCE>` are used 
identically in Tasks 1
+and 2. Branch `vote-mail-skill` is the same in Global Constraints and Task 5.

Reply via email to