Release ArcKit
Drive the ArcKit release flow end to end. This skill is the executable companion to
docs/RELEASING.md — that doc is the source of truth; if the two
disagree, trust RELEASING.md and update this skill.
The target version is $ARGUMENTS (e.g. /release 5.10.0). If no version was given, ask
for it before doing anything — never guess a version.
Preconditions
Confirm all of these before touching version files:
- The feature PR is merged to
main— releases are cut frommain, never from a branch. - Working tree is on
mainand clean:git checkout main && git pull && git status. - The version
$ARGUMENTSis valid semver (X.Y.Z) and greater than the current plugin version (cat plugins/arckit-claude/VERSION) and CLI version (cat VERSION). GH_TOKENis set in the environment (needed bypush-extensions.sh).
If any precondition fails, stop and surface it. Do not work around it.
Release flow
Run these in order. Each script is idempotent or safe to re-run except the git tag/git push
steps. Pause after step 6 (the commit) and after step 8 before tagging to let the user confirm.
bash# 1. Edit CHANGELOGs by hand — both of them: # CHANGELOG.md (CLI) and plugins/arckit-claude/CHANGELOG.md (plugin). # Preview what shipped since the last tag to seed the entries: ./scripts/generate-release-notes.sh # 2. Bump every version file in lockstep (15 locations: VERSION files, # manifests, README badges, docs, plugin.json): ./scripts/bump-version.sh X.Y.Z # 3. Regenerate Codex/OpenCode/Gemini/Copilot/Paperclip formats from the # plugin source — MUST run after the bump so the extensions carry the # new version: python scripts/converter.py # 4. Validate generated extension outputs before committing: pytest tests/codex/test_codex_extension.py \ tests/gemini tests/opencode tests/copilot \ tests/vibe/test_vibe_extension.py \ tests/paperclip/test_commands_json.py \ tests/plugin/test_release_process.py # 5. Sanity-check the tree (no stray edits, counts/versions consistent): git status && git diff --stat # 6. Commit the bump — a clean tree is required for `claude plugin tag`: git add -A && git commit -m "chore: bump version to X.Y.Z" # 7. Validate EVERY plugin manifest against the marketplace entry. # Discover plugins dynamically — do NOT hardcode the list (it grows). # Search from `plugins`, not `.`: from the repo root the manifests sit at # depth 4, so `find . -maxdepth 3` matches nothing and the loop below # "passes" having validated not one plugin. mapfile -t manifests < <(find plugins -maxdepth 3 -path '*/.claude-plugin/plugin.json' | sort) (( ${#manifests[@]} > 0 )) || { echo "No plugin manifests found — check the find path"; exit 1; } echo "Validating ${#manifests[@]} plugin manifests..." for manifest in "${manifests[@]}"; do dir=${manifest%/.claude-plugin/plugin.json} # PATH — `claude plugin tag` rejects the name claude plugin tag "$dir" --dry-run || { echo "VERSION DRIFT: $dir"; exit 1; } done # 8. (optional) Prune orphaned plugin deps: claude plugin prune --dry-run # 9. Tag the umbrella release and push — this triggers # .github/workflows/release.yml, which creates the GitHub Release. # Push the tag BY NAME: `git push --tags` pushes every local tag, and # published a stray `pre-rebase-*` backup tag during v6.8.0. git tag -a vX.Y.Z -m "vX.Y.Z" git push && git push origin vX.Y.Z # 10. Create native per-plugin tags (arckit--vX.Y.Z, arckit-uae--vX.Y.Z, …). # Auto-discovers plugins; idempotent (skips existing tags): ./scripts/tag-plugins.sh X.Y.Z # 11. Push each distribution to its standalone GitHub repo # (tractorjuice/arckit-claude, arckit-gemini, arckit-codex, …). # The claude target publishes the full Claude marketplace repo: core at # plugins/arckit, overlays under plugins/... paths, only the marketplace, # README and LICENSE at the root. This also creates or preserves each # repo's vX.Y.Z tag and GitHub Release: ./scripts/push-extensions.sh
After step 11, confirm the GitHub Release was created (the release.yml workflow runs on the
vX.Y.Z tag push). Also confirm every standalone repo has a vX.Y.Z tag and GitHub
Release, then report the release URLs and which standalone repos were pushed.
Then write the release article for the major features — docs/articles/YYYY-MM-DD-<slug>.md
plus a hero generator script, force-added past the drafts-folder gitignore. No Markdown
tables — LinkedIn and Medium do not render them; write comparisons as prose. See
"Release article" in RELEASING.md for the full conventions (voice, hero style, community
block, contributor credits).
Common Gotchas
The highest-signal failures — collected from real releases. Read these before running anything.
- Releasing from a feature branch. Releases are cut from
mainafter the PR is merged. Tagging a branch ships an unmerged tree. Alwaysgit checkout main && git pullfirst. - Forgetting the converter (step 3).
bump-version.shupdates the plugin source, but the Codex/OpenCode/Gemini/Copilot/Paperclip copies are generated. Skipconverter.pyand the extensions ship the old version. The converter must run after the bump and before the commit, so the regenerated files are included. - Skipping extension tests. Run the step 4 extension suite after
converter.py. It validates Codex, Gemini, OpenCode, Copilot, Vibe, Paperclip, release inventory, version alignment, and platform-specific command rewrites before anything is tagged. - Releasing from a Mac.
bump-version.shruns on stock macOS (BSD sed, bash 3.2) and asserts every substitution lands — adoes not contain "…" after editerror is pattern drift, not a sed-flavour problem.tag-plugins.shandpush-extensions.shstill need bash ≥ 4:brew install bash, then/opt/homebrew/bin/bash scripts/…. Full macOS notes (uv commands for the converter and tests, thehttp.postBufferfix forprotocol error: bad line lengthpushes,GH_TOKENviagh auth token) live in RELEASING.md's "Releasing from macOS" section. - Hardcoding the plugin list. The Claude marketplace now ships 16 plugins (core plus
regional, sector, method, agent-architecture, tooling, and supplier overlays) and keeps growing. Older examples listed
only 7, silently skipping newer plugins. Discover plugins dynamically (step 7) -- this is the
exact bug that shipped
arckit-uk-nhsuntagged mid-v5.4.0, which is whytag-plugins.shnow auto-discovers. Never copy a static plugin array. - A
forloop over an empty glob exits 0. Step 7's discovery searched from.while the manifests sit at depth 4, so-maxdepth 3matched nothing and the validation reported a clean pass having checked not one plugin -- through every release from v6.0.0 to v6.7.4. Dynamic discovery only helps if you assert it found something, hence the count check and theValidating N plugin manifests...line. Treat any loop whose body might never run as unvalidated until it prints what it covered. claude plugin tagtakes a PATH, not a plugin name. Passing thenamefromplugin.json(arckit) resolves relative to the repo root and fails✘ Path not found: /…/arckit. Use the plugin directory:plugins/arckit-claude. This compounded the bug above -- had the glob ever matched, every iteration would have failed anyway.- Validate before
tag-plugins.sh, never after. Once the nativename--vX.Y.Ztags exist, the dry-run fails withTag "…" already exists locally-- correct behaviour, not version drift, but it will send you hunting for a problem that isn't there. claude plugin tagneeds a clean tree. Run it after the commit (step 6), not before, or it errors on the dirty working tree.claude plugin tagis--dry-runonly here. It createsname--vX.Y.Zstyle tags that do not matchrelease.yml'sv[0-9]+.[0-9]+.[0-9]+trigger. We use it solely for its validation side effect (cross-checkingplugin.jsonvsmarketplace.json). The real release tag is thegit tag -a vX.Y.Zin step 8.- Two CHANGELOGs, not one.
CHANGELOG.mdis the CLI changelog;plugins/arckit-claude/CHANGELOG.mdis the plugin changelog. Both need an entry. They are human-authored —generate-release-notes.shonly previews what changed, it does not write them. - CLI and plugin versions are independent but bumped together.
bump-version.shmoves both to the same number by design; don't try to skew them. push-extensions.shneedsGH_TOKENand skips repos that don't yet exist on GitHub — a "skipped" line is not an error for a brand-new extension, but double-check it's not skipping a repo that should exist. Theclaudetarget writes the fullarckit-claudemarketplace repo, including the public-but-proprietaryplugins/uk/gcloud/overlay and its license exception. It now creates/preserves standalone repovX.Y.Ztags and GitHub Releases; useARCKIT_SKIP_EXTENSION_RELEASES=1only when intentionally doing a commit-only sync.- Tables in release articles. LinkedIn and Medium do not render Markdown tables — a
comparison pasted there lands as a wall of pipes. Write it as prose. Articles live in the
gitignored
docs/articles/drafts folder; force-add the keepers (git add -f). - Articles are for users, not maintainers. Lead each section with what changed for the reader and why it matters; no file names, hook names, schema or regex talk in the body — say what a mechanism does, not what it is called. Hero labels follow the same rule. The first draft of the commerce-skills article (#845) had to be rewritten for this; the maintainer detail lives in the CHANGELOG and the PR body, which the article can link to.
- The core plugin publishes to
plugins/arckit, not the repo root. Until 6.16.2 it sat at the root, so its plugin folder was the whole repo: the Claude plugin directory's validator timed out on it and every install carried a copy of all the overlays. Anything in the core that needs an overlay's files must look beside the core (${CLAUDE_PLUGIN_ROOT}/../../arckit-*/*/in the install cache,${CLAUDE_PLUGIN_ROOT}/../in a repo checkout), never inside it. - Do not put release numbers in extension READMEs. Extension release identity lives in
VERSIONfiles, manifests, Git tags, and GitHub Releases. README-pinned versions drift and are blocked bytests/plugin/test_release_process.py. - Order is load-bearing. bump → convert → extension tests → commit → validate → tag → tag-plugins → push-extensions. Re-running an earlier step after a later one (e.g. editing files after the commit) means the tag no longer points at the released tree. If you edit after committing, redo from the commit.
Reference
docs/RELEASING.md— full release documentation and rationalescripts/bump-version.sh— version bump across all filesscripts/generate-release-notes.sh— changelog preview from git logscripts/tag-plugins.sh— native per-plugin tags (auto-discovers)scripts/push-extensions.sh— pushes distribution dirs to standalone repos.github/workflows/release.yml— creates the GitHub Release onv*tag push


