From 7e4fab307887b7c839a25684227e84e81ccc97a8 Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Fri, 22 May 2026 13:00:50 -0300 Subject: [PATCH 1/2] chore(release): add /release slash command for weekly ritual Adds a Claude Code slash command that runs the Friday release flow: auto-detects semver bump, generates the GitHub release via the native generate-notes API, drafts a customer-facing email in Cal.com style (themed prose, no internal references), runs the humanizer skill on the email prose, and saves mirrors to releases//. --- .claude/commands/release.md | 212 ++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 .claude/commands/release.md diff --git a/.claude/commands/release.md b/.claude/commands/release.md new file mode 100644 index 00000000..4bbaee40 --- /dev/null +++ b/.claude/commands/release.md @@ -0,0 +1,212 @@ +--- +description: Friday release ritual — create git tag, GitHub release (auto-generated changelog), and a customer-facing email draft (Cal.com style) +argument-hint: "[major|minor|patch]" +allowed-tools: Bash, Write, Read, Skill +--- + +You are running the Friday release ritual for TryPost. Three artifacts are produced: + +1. A git tag (semver) +2. A GitHub release with the **auto-generated** changelog (PR list + authors via GitHub's native generator — flat, technical, for developers) +3. A **customer-facing email draft** in Cal.com style (themed prose, end-user voice, no commit/PR references) + +Plus local mirrors in `releases//`. + +**Always confirm with the user before any push/tag/release.** + +## Context (auto-loaded) + +- Current branch: !`git branch --show-current` +- Working tree: !`git status --porcelain` +- Latest tag: !`git describe --tags --abbrev=0 2>/dev/null || echo "(none)"` +- Repo (owner/name): !`gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || echo "(no gh)"` +- Local vs origin/main: !`git fetch --quiet origin main 2>/dev/null; git rev-list --left-right --count HEAD...origin/main 2>/dev/null || echo "0 0"` +- Commits since latest tag (or all if no tag): !`LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null); if [ -z "$LAST_TAG" ]; then git log --pretty=format:"%H%x09%s" --reverse; else git log "$LAST_TAG"..HEAD --pretty=format:"%H%x09%s" --reverse; fi` + +User bump override (optional): $ARGUMENTS + +## Workflow + +### Step 1 — Pre-flight checks + +Stop and tell the user if any of these fail: + +- Current branch must be `main`. Else: ask user to `git checkout main`. +- Working tree must be clean. Else: ask user to commit/stash. +- Local in sync with `origin/main` (rev-list count `0 0`). Else: ask user to pull/push. +- Commits-since-tag list must be non-empty. Else: "Nada novo desde a última tag." + +### Step 2 — Determine next version + +Parse the commit list: + +| Trigger | Bump | +|---|---| +| `!:` in subject OR `BREAKING CHANGE:` in body | **major** | +| Any `feat(...)` / `feat:` | **minor** | +| Else (fix, chore, docs, refactor, perf, test, ci, build, style, merge commits) | **patch** | + +If `$ARGUMENTS` is `major`/`minor`/`patch`, override the auto-detection. + +Compute next version from latest tag: strip leading `v`, bump component, zero lower components, re-prefix `v`. Example: `v0.3.2` + minor → `v0.4.0`. + +If **no previous tag** exists, default to `v0.1.0` (still respect user override). + +### Step 3 — Preview the changelog (GitHub native format) + +Use GitHub's release-notes generator API to produce the changelog **without creating anything yet**: + +```bash +gh api -X POST "repos/{OWNER}/{REPO}/releases/generate-notes" \ + -f tag_name="" \ + -f target_commitish="main" \ + -f previous_tag_name="" \ + --jq '.body' +``` + +For the first release ever (no previous tag), omit the `previous_tag_name` flag — GitHub falls back to the initial commit. + +The body already contains: +- ` by @ in #` lines +- "New Contributors" section when applicable +- `Full Changelog: ...` compare link + +**Do not modify it.** The GitHub-native format is the goal. + +### Step 4 — Draft the customer email (Cal.com style) + +This email is for **end users of TryPost** — non-developers, paying customers, trial users. It must **NOT** reference: commits, PRs, authors, SHAs, conventional commit scopes, version control concepts, internal class names, file paths. + +Read the commits only as **internal source material**. Translate to user-facing language. + +#### Structure + +```markdown +--- +subject: "Changelog: TryPost , , ..." +--- + +# Changelog: TryPost , , ... + +By Paulo Castellano • Release + +Hello! Welcome to this week's update. Here's what's new in TryPost. + +## + +<2-4 sentences of concrete narrative — what changed, why a user should care, what they'll notice. No marketing puffery.> + +## + + + +## + + + +## New features + +- +- <...> + +## Fixes + +- +- <...> + +Cheers, +Paulo Castellano from TryPost.it +``` + +#### Theme grouping (AI clusters by user impact) + +Read all commits since the last tag and cluster into **2-3 user-facing themes**. Use whatever frame makes the changes feel coherent to a customer, not to a developer. + +**Good themes** (end-user framing): +- "Trial protection" — bundles billing/Stripe Radar work +- "Reliable Facebook posting" — bundles Facebook fixes +- "Faster scheduling" — bundles queue/post improvements +- "Better post editor" — bundles UI changes to the post composer + +**Bad themes** (internal framing — never use these): +- "Refactoring" +- "Dependency updates" +- "Feature commits" / "Fix commits" +- "Backend improvements" + +If there are fewer than 3 themeable groups, use 2 or just 1. Don't pad. Internal-only changes (chore, CI, refactor, deps) usually shouldn't appear at all — fold the user-visible ones into "Fixes" with a user-voice rewrite, drop the rest. + +#### Bullet rules for "New features" / "Fixes" + +Rewrite each item in **user voice**, not commit voice: + +- ❌ "fix(facebook): send Graph API requests as form-urlencoded" +- ✅ "Fixed an issue where multi-image Facebook posts could fail to publish" + +- ❌ "feat(billing): charge one-time trial setup fee at Stripe Checkout" +- ✅ (Probably its own theme, not a bullet — billing is a big user-facing topic) + +- ❌ "chore(deps): bump axios to 1.13.5" +- ✅ (Skip entirely — pure internal) + +If a commit has no user-visible effect, **omit it**. Don't pad the email. + +#### Subject line + +Pattern: `Changelog: TryPost , , ...` + +Cap around 80 chars. If themes don't fit, shorten to the 2 most impactful + "and more...". + +### Step 5 — Humanize the email prose + +Run the email body through the `humanizer` skill before previewing: + +1. Invoke the `Skill` tool with `skill: humanizer` and pass the draft email body plus this context: *"This is a customer-facing changelog email for TryPost (social media scheduler SaaS). Tone: developer founder writing to early users on a Friday — warm, specific, no marketing puffery. Cal.com style. Keep the existing structure (subject frontmatter, section headers, bullets, signature). Do not strip section headers or the 'Cheers, Paulo Castellano from TryPost.it' signature."* +2. Replace the draft email body with the humanized version. + +**Do NOT humanize:** +- The changelog from Step 3 (flat commit list, no prose). +- The subject line frontmatter. +- The literal signature `Cheers,\nPaulo Castellano from TryPost.it` — keep it exact. + +The humanizer skill itself covers all patterns. Trust it. + +### Step 6 — Confirm with the user + +Show: +1. **Proposed version** + bump type with reason (e.g., "minor — there's a `feat(billing)` in the commits"). +2. **Changelog preview** (Step 3 output). +3. **Email preview**: subject line + full body (post-humanizer). +4. **Files that will be created/pushed**: + - Tag `` (pushed to origin) + - GitHub release `` + - `releases//changelog.md` + - `releases//email.md` + +Then ask in Portuguese: **"Crio a tag, publico o release e salvo os arquivos?"** + +Do **not** proceed without explicit yes. + +### Step 7 — Execute + +After confirmation, in this exact order: + +1. Create local directory: `mkdir -p releases/` +2. Write `releases//changelog.md` with the Step 3 content (raw GitHub markdown). +3. Write `releases//email.md` with frontmatter + humanized body. +4. Create annotated tag: `git tag -a -m "Release "` +5. Push tag: `git push origin ` +6. Create the GitHub release using the changelog file as body: + ```bash + gh release create --title "" --notes-file releases//changelog.md + ``` +7. Report to the user: + - GitHub release URL (from `gh` output) + - Local paths: `releases//changelog.md`, `releases//email.md` + - Reminder: *"Os arquivos em `releases//` não foram commitados. Commit depois se quiser preservar o histórico no repo."* + +### On failure + +- `git push origin ` fails: report the exact error, leave the local tag in place, do not retry destructively. +- `gh release create` fails: the tag is already pushed; tell the user they can recreate manually with `gh release create --title "" --notes-file releases//changelog.md`. +- `Skill` or `Write` failure during artifact prep: report and stop. Do not push the tag without the artifacts being prepared. From ff7ca65dc278ea29a8416faa969c27911fbaf3a1 Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Fri, 22 May 2026 13:07:01 -0300 Subject: [PATCH 2/2] chore(release): switch to sequential versioning with rollover at 9 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces semver-by-commit-type detection with sequential numbering. Patch increments on every release; rolls over to minor at patch=10; rolls over to major at minor=10. No manual override — every release is the next number in sequence. --- .claude/commands/release.md | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/.claude/commands/release.md b/.claude/commands/release.md index 4bbaee40..b3c74a94 100644 --- a/.claude/commands/release.md +++ b/.claude/commands/release.md @@ -1,6 +1,5 @@ --- description: Friday release ritual — create git tag, GitHub release (auto-generated changelog), and a customer-facing email draft (Cal.com style) -argument-hint: "[major|minor|patch]" allowed-tools: Bash, Write, Read, Skill --- @@ -23,8 +22,6 @@ Plus local mirrors in `releases//`. - Local vs origin/main: !`git fetch --quiet origin main 2>/dev/null; git rev-list --left-right --count HEAD...origin/main 2>/dev/null || echo "0 0"` - Commits since latest tag (or all if no tag): !`LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null); if [ -z "$LAST_TAG" ]; then git log --pretty=format:"%H%x09%s" --reverse; else git log "$LAST_TAG"..HEAD --pretty=format:"%H%x09%s" --reverse; fi` -User bump override (optional): $ARGUMENTS - ## Workflow ### Step 1 — Pre-flight checks @@ -38,19 +35,28 @@ Stop and tell the user if any of these fail: ### Step 2 — Determine next version -Parse the commit list: +TryPost uses **sequential numbering with rollover at 9** — not standard semver. Do not parse conventional commits to choose the bump. Every release is the next sequential number, whatever the commits look like. -| Trigger | Bump | -|---|---| -| `!:` in subject OR `BREAKING CHANGE:` in body | **major** | -| Any `feat(...)` / `feat:` | **minor** | -| Else (fix, chore, docs, refactor, perf, test, ci, build, style, merge commits) | **patch** | +1. If no previous tag exists → next version = **`v1.0.0`** (first release ever). +2. Otherwise, parse the latest tag as `vMAJOR.MINOR.PATCH` and increment by these rules: + - `patch += 1` + - If `patch` reaches `10`: set `patch = 0`, `minor += 1` + - If `minor` reaches `10`: set `minor = 0`, `major += 1` +3. Re-prefix with `v`. -If `$ARGUMENTS` is `major`/`minor`/`patch`, override the auto-detection. +Examples: -Compute next version from latest tag: strip leading `v`, bump component, zero lower components, re-prefix `v`. Example: `v0.3.2` + minor → `v0.4.0`. +| From | To | +|---|---| +| (no tag) | v1.0.0 | +| v1.0.0 | v1.0.1 | +| v1.0.8 | v1.0.9 | +| v1.0.9 | v1.1.0 | +| v1.5.7 | v1.5.8 | +| v1.9.8 | v1.9.9 | +| v1.9.9 | v2.0.0 | -If **no previous tag** exists, default to `v0.1.0` (still respect user override). +There is no manual override — the next version is whatever the rule above produces. If a release needs a different version for some special reason, the user must create the tag manually outside this command. ### Step 3 — Preview the changelog (GitHub native format) @@ -174,7 +180,7 @@ The humanizer skill itself covers all patterns. Trust it. ### Step 6 — Confirm with the user Show: -1. **Proposed version** + bump type with reason (e.g., "minor — there's a `feat(billing)` in the commits"). +1. **Proposed version** (e.g., `v1.0.9 → v1.1.0` — sequential rollover at 9). 2. **Changelog preview** (Step 3 output). 3. **Email preview**: subject line + full body (post-humanizer). 4. **Files that will be created/pushed**: