Release
Bump versions and tag plugins for release in the solopreneur multi-plugin marketplace. Detects which sub-plugins changed since their last tag, checks whether the root README is stale relative to those changes (offering one proposal at a time), asks the user for per-plugin patch/minor bumps, composes an outward `CHANGELOG.md` entry from the per-plugin diff (every release, user-reviewed), commits the bumps + changelog in a single commit, creates double-dash git tags, and pushes commit + tags atomically. Optionally creates one GitHub Release on `solopreneur--v<new>` for user-visible milestones (skip for routine bumps), and offers build-in-public social drafts (X English + Threads 正體中文) composed from the release's CHANGELOG section. Use when the user says "release", "ship release", "/release", "tag and release", or wants to publish bumped plugins after merging changes to main.From its SKILL.md
npx -y skills add hanamizuki/solopreneur --skill releaseAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
SKILL.md
20.9 KB, as published. Nobody here has run it
Release
Manual release flow for the solopreneur multi-plugin marketplace.
Implements the release rule documented in CLAUDE.md at the repo root.
This skill is interactive. Confirm each decision with the user. Do not autopilot version bumps or release publishing.
Pre-flight
-
Confirm cwd is the repo root or a worktree of it. Run from the directory containing
.claude-plugin/marketplace.json,skills/, andsrc/. -
Confirm working tree is clean:
git status --porcelainIf unclean, stop and ask the user to commit or stash first. Don't release on top of uncommitted work. The new commit's content depends on the working tree.
-
Confirm we're on
main(the typical release path is "merge → release"):git branch --show-currentIf not on main, ask the user to confirm the branch is intentional.
-
Pull latest:
git fetch origin && git pull --ff-onlyIf
--ff-onlyfails (local has commits), stop and ask the user.
Step 1: Detect per-plugin changes since last tag
For each sub-plugin, find its last <plugin>--v* tag and compare against HEAD.
Plugin names match the first-level directory names under both canonical roots.
PLUGINS=(solopreneur designer marketer ios-dev android-dev ai-engineer neo4j-dev)
for p in "${PLUGINS[@]}"; do
LAST_TAG=$(git tag -l "${p}--v*" --sort=-v:refname | head -1)
# Transitional fallback: the v0.5.x → v0.5.3 rename dropped the `solo-`
# prefix from sub-plugins. The first /release run after that rename has
# no `<bare>--v*` tag yet, so look for the legacy `solo-<bare>--v*` tag
# to avoid a misleading "NO TAG YET" message. Once the new tag exists,
# this fallback never fires again.
if [ -z "$LAST_TAG" ] && [ "$p" != "solopreneur" ]; then
LAST_TAG=$(git tag -l "solo-${p}--v*" --sort=-v:refname | head -1)
fi
if [ -z "$LAST_TAG" ]; then
echo "=== $p: NO TAG YET (first release) ==="
git log --oneline -- "skills/$p/" "src/$p/"
continue
fi
CHANGED=$(git diff --name-only "$LAST_TAG"..HEAD -- "skills/$p/" "src/$p/" | wc -l | tr -d ' ')
if [ "$CHANGED" -gt 0 ]; then
echo "=== $p: $CHANGED files changed since $LAST_TAG ==="
git log --oneline "$LAST_TAG"..HEAD -- "skills/$p/" "src/$p/"
else
echo "=== $p: no changes since $LAST_TAG ==="
fi
done
Also check whether marketplace.json changed in a way that affects a plugin
entry (per the rule in CLAUDE.md):
git diff "$LAST_RELEASE_BASELINE"..HEAD -- .claude-plugin/marketplace.json
Use the most recent tag of any plugin as $LAST_RELEASE_BASELINE. If the
diff touches a plugin entry's name / source / description / license,
flag those plugins for a bump even if their skills/<name>/ and src/<name>/
didn't change.
Step 1.5: README sync check
Before asking for version bumps, look at whether the changes since
$LAST_RELEASE_BASELINE make the root README.md stale. Skip this step
entirely if the diff only touches internals (no surface changes).
What might make README stale
Apply judgment — these are signals, not rules:
- A plugin's
descriptioninmarketplace.jsonorsrc/<p>/plugin.jsonchanged, and the README's row / paragraph for that plugin still quotes the old text - A new plugin directory was added under
skills/andsrc/, and the README's plugin table doesn't include it - A new in-house skill landed in a plugin, and the README's "Bundled skills" section for that plugin enumerates skills explicitly (so an addition is user-visible)
- A new agent was added under
src/<p>/agents/, and the README mentions agents per plugin - Counts mentioned in the README (e.g. "23 vendored skills") drifted
Examples of cases that don't need a README change:
- Bug fix or refactor inside a skill with no description change
- Vendored skill re-sync with no upstream content drift
- New skill landed but README only says "ships N+ vendored skills" without enumerating, and N is still an accurate floor
Workflow
-
Pull the changed-files list scoped to surface signals:
git diff --name-only "$LAST_RELEASE_BASELINE"..HEAD | grep -E \ '(^src/[^/]+/plugin\.json|\.claude-plugin/marketplace\.json|^skills/[^/]+/[^/]+/SKILL\.md|^src/[^/]+/agents/.*\.md|^README\.md)$' -
Read
README.mdplus each changed surface file. Compare. For each plugin whose surface changed, decide: does the README still describe this plugin accurately? -
For every potential README change, ask the user as a single question:
"README line N currently says:
<current>. Based on the diff, suggest:<proposed>. (a) apply (b) skip (c) let me edit" -
Apply accepted changes inline via
Edit. For "let me edit", read the user's replacement text and apply that instead. -
If any README updates were applied, commit them as a separate commit BEFORE the version-bump commit (so the README change has its own reviewable history):
git add README.md git commit -m "docs: sync README with plugin changes for upcoming release"The Step 5
git push --follow-tagswill publish this commit together with the bump commit and tags. -
If nothing needs updating, proceed straight to Step 2.
Step 2: Decide per-plugin bump
Present the change list to the user, one plugin at a time, with the commit log scoped to that plugin. For each plugin with changes, ask:
patch(e.g. 0.3.0 → 0.3.1): bug fix / docs / refactorminor(e.g. 0.3.0 → 0.4.0): new skill, agent, user-visible behavior; pre-1.0 minor may be breakingskip: don't release this plugin yetabort: stop the whole release
Use the table in CLAUDE.md § "Release rule" as the source of truth.
Default suggestions from the LLM are useful. Propose the bump you'd pick based on the commit messages, then confirm with the user.
Step 2.5: Compose CHANGELOG
Every release — not just milestones — gets a public-facing, plain-language
note in the repo-root CHANGELOG.md. You author it from the actual
per-plugin diff, distilled into outward, user-readable content. This is
not a restatement of the Step 2 bump reasons and not a raw git
log. The user is the reviewer/approver, not the typist.
Cost is intentional. This makes
/releaseheavier — you must read and comprehend each bumped plugin's diff every release. Accepted on purpose: a public changelog deserves human-grade narrative, not boilerplate. Do not shortcut it back into reason-restatement.
Format — Keep a Changelog style. If CHANGELOG.md is absent, create
it with a one-time header note: "Versions before <this release's earliest-touched version> predate this changelog — see git tags / GitHub
Releases." Prepend each release as a dated section with a per-plugin
subsection for only the plugins bumped this cycle:
## <YYYY-MM-DD>
### <plugin> <old> → <new>
<plain outward description — what this means for someone who installs
this plugin>
(#<PR>, #<PR>) — thanks @<external-contributor>!
Compose — for each bumped plugin:
-
git diff "<plugin>--v<old>".."HEAD" -- "skills/<plugin>/" "src/<plugin>/"(per-plugin scope; baseline = that plugin's previous tag, the sameLAST_TAGresolved in Step 1). -
Comprehend the diff and distill into plain outward text. Condense vendored re-sync churn (e.g. "re-synced N vendored skills from upstream") — never enumerate files. Describe real features in user language.
-
Attach the mechanical scaffold automatically: the date header, the
<old> → <new>subsection heading, and PR-number links parsed from(#NN)in the merge commits across<old>..HEAD. -
Resolve external contributors. For each PR number collected in step 3, look up its author:
gh pr view <NN> --json author --jq .author.loginDrop the repo maintainer (
gh api user --jq .login) and dedupe across the plugin's PRs. If any external contributors remain, append— thanks @<handle>!(or— thanks @<h1>, @<h2>!) to the line carrying the PR references. No externals → omit the thanks line entirely; never render an empty "thanks " tail.
Review gate — present the full drafted section to the user:
"CHANGELOG draft for this release: <draft> (a) apply (b) I edit a section (c) rewrite a plugin's section"
Apply accepted text. For (b)/(c), take the user's replacement and re-present
until they pick (a). Do not stage or commit yet — CHANGELOG.md is
committed together with the bumps in Step 3.
Step 3: Apply bumps in one commit
For each plugin marked for bump, edit src/<name>/plugin.json's version
field. That is the only version source; never edit generated manifests.
If every Claude marketplace source still uses ./plugins/<name>, this is the
one-time symmetric-layout cutover release. Require all seven plugins to receive
at least a patch bump, then change every source to
./plugins/claude/<name>. If the sources are mixed between layouts, stop; a
partial cutover is invalid. Once all sources are symmetric, later releases
leave them unchanged.
The cutover release must also, in the same commit, retire the compatibility bridge everywhere it is described or implemented — otherwise the repo ships prose describing directories that no longer exist and branches that can never be taken again.
Order matters here, and getting it wrong strands the bridge silently. The
generator deletes plugins/<name>/ and .codex/plugins/ from its symmetric
branch; retiring that code before a regeneration has actually removed them
leaves committed, still-installable copies behind. Do it in exactly this order:
- Flip every marketplace source to
./plugins/claude/<name>(above), then run./scripts/generate-plugin-packages.sh. This is the step that removes the compatibility trees. - Confirm they are gone —
ls plugins/must show onlyclaudeandcodex, and.codex/pluginsmust not exist../scripts/validate-plugin-packages.shalso fails on a leftover once the marketplace is symmetric. - Only now delete the dead code and prose. All five files, or the release
cannot validate —
validate-plugin-packages.shruns the fixture unconditionally, so a fixture still asserting the bridge fails the whole gate even when the cutover itself is perfect (measured:error: filtered-publication fixture failed at line 66):scripts/generate-plugin-packages.sh— thelayout == legacybranches (LEGACY_CODEX_ROOT, its copy loop, and thequeue_output ""sentinels that were removing those trees).scripts/validate-plugin-packages.sh— itslayout == legacybranch.scripts/tests/test-codex-filtered-publication.sh— the legacy phase: the./.codex/plugins/solopreneurmarketplace assertion and the$FILTER_HOMEinstall that follows it, the "must not strand its flat legacy bridge" case (which requiresplugins/solopreneurto exist), the now-vacuous[[ ! -d ... ]]pair in the cutover block, theFILTER_HOMEdeclaration and itstrapentry, and the word "legacy" in the closing message. The fixture copies the repo's marketplace, so once that is symmetric its legacy phase can never pass again.CLAUDE.md— the compatibility-bridge paragraph ("Until the first release after this migration…").README.md— the "temporary generated copies" sentence in the Repository layout section.
- Re-run the generator and the validator once more to prove the retired code
changed nothing. Verified on a throwaway cutover tree: with all five edits
applied,
./scripts/validate-plugin-packages.shexits 0.
Step 1.5's README-sync heuristics key on descriptions, new plugins, new skills and counts, so none of them fire on this; it has to be done here.
Then regenerate both platform packages. The generator also switches the Codex marketplace path and removes the temporary legacy copies when the Claude marketplace has entered symmetric mode:
./scripts/generate-plugin-packages.sh
Stage all bumped source manifests, both generated package trees, both
marketplaces, and .codex/agents/
together with the CHANGELOG.md update from Step 2.5 and commit them in
one commit, so the Step 4 tags point at a commit that already contains the
changelog (zero gap).
Use the subject + body two--m form below — NOT a
-m "$(cat <<EOF ...)" heredoc. The product-repo worktree guard hook
sanctions a release commit on main only when chore(release): appears
immediately after the -m quote; a heredoc / command-substitution between
-m and the message hides the prefix and the commit gets blocked. With the
direct form the subject matches, and CHANGELOG.md (markdown) plus the
plugin.json bumps land in one commit on main:
git add -- src/*/plugin.json .claude-plugin/marketplace.json .agents/plugins/marketplace.json CHANGELOG.md
git add -A -- plugins .codex
# Cutover release ONLY — the five bridge-retirement edits from Step 3 above.
# They are not covered by any pathspec on the two lines above, so without this
# the tag commit ships a symmetric marketplace with the bridge code still in it.
# Skip this line on an ordinary release; those files are untouched then.
git add -- scripts/generate-plugin-packages.sh scripts/validate-plugin-packages.sh \
scripts/tests/test-codex-filtered-publication.sh CLAUDE.md README.md
git commit \
-m "chore(release): <one-line summary of what's shipping>" \
-m "<plugin1> v<old>→v<new>: <reason>
<plugin2> v<old>→v<new>: <reason>
..."
Commit subject convention: chore(release): <summary>. The body lists each
plugin's bump and one-line reason. Useful when the same release ships
multiple plugin bumps. CHANGELOG.md rides in this same commit — it is
never a separate commit and never lands after the tags.
Step 4: Create tags
For each bumped plugin, create an annotated tag pointing at the bump commit:
for p in "${BUMPED[@]}"; do
VERSION=$(jq -r .version "src/$p/plugin.json")
git tag -a "${p}--v${VERSION}" -m "${p} v${VERSION}: <one-line>"
done
Use the same one-line summary the user provided in Step 2 for each plugin. The double-dash format is mandatory. Claude Code's dependency resolver parses it.
Step 5: Push atomically
git push --follow-tags
--follow-tags only pushes annotated tags reachable from the pushed commit,
exactly what we want here. If push splits or rejects:
- Rejected (non-fast-forward): something landed on origin/main between pre-flight and now. Stop, ask user to investigate. Do not force-push.
- Tag push failed but commit pushed: retry tags only with
git push origin <tag1> <tag2> .... Do not let the gap persist. Installs in the gap will hitno-matching-tag.
Step 6: GitHub Release (optional, milestones only)
Ask the user: "Is this a milestone worth a GitHub Release?"
Reference for "milestone" (matches superpowers' tag:release ≈ 6:1 cadence):
- New sub-plugin shipped (e.g. a
rustplugin joins the family) - Breaking change users need to know about
- New skill family or major feature
- Coordinated multi-plugin release with a coherent theme
For routine patch bumps or single-skill additions, skip the Release. Tags alone are enough; Claude Code only needs tags for resolution.
If the user says yes:
-
The Release attaches to one tag. Use
solopreneur--v<new>as the umbrella, even if core didn't change much. (Repo's "Latest release" surface should look like a single coherent version, not six per drop.)If
solopreneurcore wasn't bumped this cycle, attach to whichever plugin's tag best represents the milestone, but warn the user that the umbrella tag is typically core. -
Do not re-author notes. The outward note was already written and user-reviewed in Step 2.5 and lives in
CHANGELOG.md. The Release reuses that release's CHANGELOG section verbatim plus a link back to the full file — no second hand-authoring, no--generate-notes. If the milestone needs extra framing (breaking-change callout, MIGRATION link), prepend a short headline above the copied section; do not rewrite the per-plugin bodies. -
Extract this release's section from
CHANGELOG.md(the top dated## <YYYY-MM-DD>block) and create the Release with it:gh release create "solopreneur--v<new>" \ --title "v<new>" \ --notes-file <(cat <<'EOF' <this release's CHANGELOG.md section, verbatim> --- Full changelog: <repo URL>/blob/main/CHANGELOG.md EOF )
Step 7: Build-in-public drafts (optional)
Ask the user: "Want social post drafts for this release?" One X post (English) and one Threads post (正體中文). Offer it every release; the user skips freely on routine bumps.
If yes, the source material is this release's CHANGELOG section — already user-reviewed in Step 2.5. Do not re-research the diff, and do not post anything anywhere: deliver draft text only, publishing is manual.
Shared rules
- Pick ONE angle for the main post — the most build-in-public-honest item (a deletion, a default flip, a milestone number). Everything else goes to a self-reply. Never enumerate the whole changelog in the main post.
- Put one concrete number in the hook (lines deleted, skill count, version).
- Leave some details out on purpose. Questions in the replies are the goal (an author reply-back weighs ~150× a like on X), so keep an answer or two in reserve instead of pre-empting everything.
X draft (English) — release-announcement skeleton:
- Main tweet ≤ 280 chars:
<name> <version> is out!+ the angle with its number + a short closer (≤ 1 emoji). No external link and no hashtags in the main tweet — the repo link rides in the self-reply. - Self-reply: remaining highlights in plain prose, then the repo URL.
- @mention an upstream author only when the release genuinely touches their work and they plausibly engage; never orphan-mention.
- If the
marketer:x-writingskill is available, run its quality checklist on the draft before delivering.
Threads draft (正體中文)
- Same two-post shape, ≤ 500 chars per post. The link may sit in the reply; Threads does not throttle external links the way X does.
- Rewrite the angle natively in Traditional Chinese — do not translate the English tweet sentence by sentence. Short lines, no em-dashes, no AI-tell connectors (然而/因此/總而言之).
- Drop X-only @handles unless the person's Threads handle is known; refer to upstream work by name instead.
- If a personal Chinese-voice skill is available (e.g.
hana-writing), apply it on top of these rules.
Present both drafts together for edit/approve. This step changes no files and happens after the push, so aborting it never affects the release.
Final report
Released: <list of <plugin>@<version> shipped>
Tags pushed: <list>
GitHub Release: <URL or "skipped">
Social drafts: <"X + Threads delivered" or "skipped">
Commit: <SHA>
Notes
- Per-plugin independence: only bump plugins whose files actually changed (or whose marketplace entry changed). Don't bump everyone every release.
- Docs-only changes at repo root (README.md, MIGRATION.md, CLAUDE.md,
LICENSE, CHANGELOG.md, this
.claude/skills/directory): no plugin bump needed.CHANGELOG.mdis a release output, not plugin content — it rides in the bump commit but never itself triggers a bump. - CHANGELOG cost is deliberate. Step 2.5 makes every release heavier: you read and comprehend each bumped plugin's diff to write an outward, plain-language note. This is an accepted tradeoff — a public changelog is worth human-grade narrative. Do not regress it into restating the Step 2 bump reasons or dumping the git log.
- Pre-1.0 semver: minor bumps may be breaking. 1.0.0 is reserved for the first stable API cut.
- Worktree caveat: If the user is releasing from a worktree on main, the
push will go through fine. Just make sure
git pull --ff-onlysucceeds before bumping. - Don't
--no-verify: this repo doesn't have pre-commit hooks today, but if any are added later, never bypass them in a release.