Generic release automation for projects using Keep a Changelog + GitHub. Single confirmation gate then end-to-end automation: bump detection, CHANGELOG promotion, PR/main merge, tag, GitHub Release. Trigger: release, version bump, publish. Do NOT load for: implementation, review, planning, setup.
Instrucciones de origen · Vista previa de solo lectura
name
harness-release
description
Generic release automation for projects using Keep a Changelog + GitHub. Single confirmation gate then end-to-end automation: bump detection, CHANGELOG promotion, PR/main merge, tag, GitHub Release. Trigger: release, version bump, publish. Do NOT load for: implementation, review, planning, setup.
description-en
Generic release automation for projects using Keep a Changelog + GitHub. Single confirmation gate then end-to-end automation: bump detection, CHANGELOG promotion, PR/main merge, tag, GitHub Release. Trigger: release, version bump, publish. Do NOT load for: implementation, review, planning, setup.
Generic release automation skill for any project using Keep a Changelog + GitHub.
Design principle: Single confirmation gate. The user sees and approves the full plan once. After approval, execution continues uninterrupted: file rewrite → commit → branch push → PR create/update → merge to default branch → tag on default branch → GitHub Release.
Definition of release complete: A release is not complete just by "creating a tag and GitHub Release." Complete means: the target work and release bump are merged into the default branch (usually main), the release tag points to a commit reachable from the default branch, and the GitHub Release is published against that tag.
Literal invocation note: The entry point for this skill uses literal commands as-is: harness-release, /release, /release patch, /release --dry-run.
Bare invocation contract
if $ARGUMENTS == "":
→ Interpret as "commit work done so far and complete through PR/main merge before releasing," and run Review Gate detection
→ Auto-proceed to Step 0 (Review Gate) only when exactly one target work can be determined
→ When target is unclear or no review state exists, present options with AskUserQuestion before proceeding
Always output the following literal marker in the first response on bare invocation:
The last line of skill output must always include this literal:
↑Claude will summarize this result. Press Enter to continue, or enter a new prompt for a different instruction.
This is an explicit instruction for the UX problem where text displayed via <local-command-stdout> makes users feel it has "stopped" (patterns.md P35).
harness-release / /release alone means "commit work done so far and release through PR/main merge."
The older phrasing "commit work done so far and release" has the same intent, but the completion condition must always include PR/main merge.
Do not stop with "No tasks" or "Waiting for instructions."
On bare release, run the Review Gate and Work Commit Gate before the normal release preflight.
Check git status --porcelain and git log @{upstream}..HEAD / main..HEAD to identify the target "work done so far"
Check .claude/state/review-result.json and .claude/state/review-approved.json to verify whether the target work has an APPROVEd review
When no APPROVEd review exists, confirm with AskUserQuestion
When user selects "Start from review," launch harness-review and do not proceed to release until APPROVE is returned
When harness-review returns REQUEST_CHANGES, hold the release, fix with harness-work, then re-run harness-review. Loop until APPROVE.
After harness-review returns APPROVE, create a work commit for the working tree
After the working tree is clean, proceed to normal release preflight / confirmation gate / PR merge / tag / GitHub Release
Review Gate AskUserQuestion
When running harness-release and review approval cannot be confirmed, do not release by guessing.
Present the following Ask:
question: "harness-release will commit work done so far and release, but no APPROVE review was found for this work. How would you like to proceed?"
options:
- label: "Start from review (Recommended)"
description: "Run harness-review and proceed to commit/release only if APPROVE is returned."
- label: "release dry-run"
description: "No file changes; check only the release plan and missing gates."
- label: "Cancel"
description: "Stop without review or release."
When user selects "Start from review," start harness-review in the same session.
Target determination in harness-review follows harness-review's own bare review contract.
If review is APPROVE, return to the harness-release Work Commit Gate.
If review is REQUEST_CHANGES, hold release, fix with harness-work, then re-run harness-review.
This post-fix re-review loop continues until APPROVE.
Only return to the user in these cases:
Fix requires decisions about spec source of truth / Plans.md / API / permission / migration / billing etc., and AskUserQuestion is needed
Multiple fix approaches exist where choice affects user value or compatibility
User selected release dry-run or Cancel in Ask
Do not use REQUEST_CHANGES alone as a final stop reason.
Work Commit Gate
On bare release, when there are uncommitted changes in the working tree, create a reviewed work commit separately from the release version bump commit first.
Generate commit message briefly from review summary / Plans.md task / branch name.
When unclear, present 2-3 commit message candidates with AskUserQuestion.
After creating the work commit, verify or update commit_hash in .claude/state/review-result.json, then proceed to release preflight.
After entering normal release preflight, treat dirty working tree as failure as before.
Do not proceed to version bump / tag / GitHub Release with a dirty tree.
Quick Reference
/release # Review gate → commit → PR/main merge → release work done so far
/release patch # Explicitly specify bump as patch
/release minor # Explicitly specify bump as minor
/release major # Explicitly specify bump as major
/release --dry-run # Display plan only, do not execute
pyproject.toml (Python, [project] or [tool.poetry])
Cargo.toml (Rust, [package])
gh CLI installed and authenticated
Git remote origin points to GitHub
For Claude Code plugin projects: claude CLI supports plugin tag
When these are not met, Preflight detects and aborts.
prUrlTemplate multi-host review URL is recognized as a future candidate, but this skill's release automation still uses gh CLI and GitHub remote as the primary path. Auto-retrieval of owner / branch / release asset / CI metadata has large host-by-host differences, so Phase 56.2.3 remains docs-only.
Single gate flow
[Bare release only: work review/commit pre-step]
↓
0. Review Gate (AskUserQuestion → harness-review if no review)
0.5 Work Commit Gate (commit review-APPROVEd work separately from release bump)
↓
[Pre-Gate: information collection only, no file changes]
↓
1. Preflight (working tree clean / CHANGELOG / gh etc. checks)
2. Version file auto-detection
3. Read current version
4. Claude plugin tag preflight (plugin projects only)
5. Parse [Unreleased] content → estimate bump level
6. Calculate new version
7. Create CHANGELOG diff draft (in memory)
8. Create GitHub Release notes draft (in memory)
★━━━━━━ Single Confirmation Gate ━━━━━━★
Present full plan to user once:
- Detected version file
- Current version → new version
- Bump determination reason (e.g., "minor because ### Added is in [Unreleased]")
- CHANGELOG change preview
- GitHub Release notes draft
- Files to commit
- Final actions (branch push + PR merge + tag + release publish)
User response:
"yes" → Proceed to Post-Gate
"<fix instruction>" → Regenerate draft per instruction, re-confirm
"cancel/no" → Do nothing and exit
★━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━★
↓
[Post-Gate: after approval, no interruption]
9. Rewrite version file
10. Rewrite CHANGELOG.md ([Unreleased] → [X.Y.Z] promotion + compare link)
11. git add + commit
12. Release branch push
13. PR create/update
14. Merge to default branch
15. Fetch/checkout default branch; verify release commit is reachable
16. Claude plugin tag validation + tag (plugin projects only)
17. GitHub Release semver tag (only for projects that need it)
18. git push origin <default-branch> --tags
19. gh release create vX.Y.Z
20. Completion report
This working tree clean check is the gate for normal release preflight.
When wanting to commit "work done so far" on bare release, complete the Review Gate and Work Commit Gate before this check.
Do not abort and finish just because of an unreviewed dirty tree at this check.
scripts/release-preflight.sh also detects mirror drift in opencode/, skills-codex/, codex/.codex/skills/ before tag creation. When node scripts/build-opencode.js generates a diff, stop the release, commit that diff, then proceed to tag.
2. Version file auto-detection
Search in this priority order; use the first found as authoritative:
import os, json, re
import tomllib # Python 3.11+defdetect_version_file():
if os.path.exists("VERSION"):
withopen("VERSION") as f:
return ("VERSION", f.read().strip(), None)
if os.path.exists("package.json"):
withopen("package.json") as f:
data = json.load(f)
return ("package.json", data["version"], None)
if os.path.exists("pyproject.toml"):
withopen("pyproject.toml", "rb") as f:
data = tomllib.load(f)
if"project"in data:
return ("pyproject.toml", data["project"]["version"], "[project]")
if"tool"in data and"poetry"in data["tool"]:
return ("pyproject.toml", data["tool"]["poetry"]["version"], "[tool.poetry]")
if os.path.exists("Cargo.toml"):
withopen("Cargo.toml", "rb") as f:
data = tomllib.load(f)
return ("Cargo.toml", data["package"]["version"], "[package]")
raise RuntimeError("No supported version file found")
For projects with .claude-plugin/plugin.json, create a Claude plugin release tag separately from the normal GitHub Release tag.
In short: before assembling git tag -a manually, pass Claude Code's own plugin validation and then create the {plugin-name}--v{version} tag.
Pre-Gate does not rewrite files; verify the following. Read version sync with structured parser, not grep/sed:
command -v claude >/dev/null || { echo"claude CLI not found"; exit 1; }
claude plugin validate .claude-plugin/plugin.json
HARNESS_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-.}"
python3 "${HARNESS_PLUGIN_ROOT}/scripts/check-release-version-sync.py" --root .
claude plugin tag .claude-plugin --dry-run
${HARNESS_PLUGIN_ROOT}/scripts/check-release-version-sync.py reads all existing release surfaces and determines canonical in the order VERSION > package.json > .claude-plugin/plugin.json > .codex-plugin/plugin.json.
Then it does not proceed to tag/release if any of the following have mismatches or are missing:
VERSION
package.json.version
.claude-plugin/plugin.json.version
.codex-plugin/plugin.json.version
.claude-plugin/marketplace.json.metadata.version
.claude-plugin/marketplace.json.plugins[].version (each plugin entry in the array)
On mismatch, show which surface differs from canonical or which field is missing/invalid.
Use --json for machine processing or CI:
Cutting a tag with VERSION and .claude-plugin/plugin.json version out of sync
Proceeding to release workflow with stale package.json / marketplace entry version
Getting stuck on plugin install/update side after skipping plugin manifest/marketplace entry validation
--dry-run shows the tag name claude plugin tag would create and the internal git tag -a / push equivalent command. Include the command shown here in the Confirmation Gate plan.
4. Bump auto-estimation
Parse headings directly under [Unreleased] to determine bump level:
Headings in [Unreleased]
Estimated bump
Contains ### Breaking Changes or ### Removed
major
Contains ### Added (no Removed/Breaking)
minor
Only ### Fixed / ### Changed / ### Security
patch
Empty section
error: no release target
When user explicitly specifies /release patch|minor|major, that takes priority.
Details: bump-detection.md
5. CHANGELOG draft creation (in memory)
Calculate the following; do not write yet:
Cut out body of ## [Unreleased]
Create the form with ## [<new>] - YYYY-MM-DD inserted between ## [Unreleased] and ## [<previous>]
When an existing PR exists, update its body and merge without creating a new one. When repository policy requires squash merge, verify that release bump content (version files + CHANGELOG + source commits) is included in the default branch — not just the release commit hash.
Create the tag after this Gate is complete, against HEAD of the default branch or a commit reachable from the release commit. Do not create a GitHub Release with a tag pointing to a commit that exists only on the release branch.
Claude plugin project tag creation
For projects with .claude-plugin/plugin.json, verify version sync on the default branch once more after PR/main merge, then create the plugin tag:
HARNESS_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-.}"
python3 "${HARNESS_PLUGIN_ROOT}/scripts/check-release-version-sync.py" --root .
claude plugin tag .claude-plugin --dry-run
claude plugin tag .claude-plugin --push --remote origin
The tag claude plugin tag creates is in {plugin-name}--v{version} format. For projects where existing GitHub Release workflows assume a vX.Y.Z tag, also create git tag -a v<new> separately from the plugin tag. Let claude plugin tag handle distribution tags; treat the GitHub Release semver tag as a compatible release automation surface.
--dry-run mode
Run all Pre-Gate steps and display content through Confirmation Gate, but stop at the gate without proceeding to Post-Gate.
For Claude plugin projects, even in dry-run, execute python3 "${HARNESS_PLUGIN_ROOT}/scripts/check-release-version-sync.py" --root . and claude plugin tag .claude-plugin --dry-run to show the actual plugin tag name to be created and push targets. If any version surfaces in VERSION / package.json / .claude-plugin/plugin.json / .codex-plugin/plugin.json / .claude-plugin/marketplace.json are mismatched or missing, stop at dry-run time.
Environment variables
For per-project adjustments:
Variable
Description
HARNESS_RELEASE_PROJECT_ROOT
Repository root (default: $(pwd))
HARNESS_RELEASE_BRANCH
Branch to push (default: current branch)
HARNESS_RELEASE_DEFAULT_BRANCH
Default branch for PR merge target (default: main)
HARNESS_RELEASE_HEALTHCHECK_CMD
Additional command to run in Preflight
HARNESS_RELEASE_SKIP_GH
1 skips GitHub Release creation
CHANGELOG writing rules
The [Unreleased] section must always have one of these subsections:
## [Unreleased]### Added ← minor### Changed ← patch### Deprecated ← minor### Removed ← major### Fixed ← patch### Security ← patch### Breaking Changes ← major (non-standard in KaCL but common)
This skill parses these headings mechanically, so heading variations (### Fix / ### Bug Fixes etc.) cannot be recognized. Use standard KaCL headings.
Related skills
harness-release-internal - Harness-specific preflight/finalization run additionally for claude-code-harness itself (not distributed)
harness-plan - Plans.md management
harness-review - Code review before release
Design philosophy
Single gate: User makes one judgment per release. Multiple mini-confirmations become rubber-stamps and lose meaning.
Draft everything upfront: No "reconsideration" after entering Post-Gate. Assemble all drafts before the gate.
Main merge is the completion condition: Release tag / GitHub Release only created after default branch merge. Branch-only releases are treated as incomplete.
Failures are transparent: On mid-execution failure, do not attempt auto-rollback; present the current state to the user for judgment.
Project-independent: No assumptions about VERSION file format, mirrors, residue checks etc. specific to certain environments. Harness-specific processing for the main harness is separated into harness-release-internal.