From ship
Generic release workflow for repositories without a dedicated release tool. Handles version bump, changelog, test, tag, push, GitHub release, and optional publish.
npx claudepluginhub agent-sh/ship --plugin shipThis skill uses the workspace's default tool permissions.
Generic release workflow used as a fallback when no dedicated release tool (semantic-release, release-it, goreleaser, etc.) is configured. Called by the release-agent after discovery.
Creates isolated Git worktrees for feature branches with prioritized directory selection, gitignore safety checks, auto project setup for Node/Python/Rust/Go, and baseline verification.
Executes implementation plans in current session by dispatching fresh subagents per independent task, with two-stage reviews: spec compliance then code quality.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
Generic release workflow used as a fallback when no dedicated release tool (semantic-release, release-it, goreleaser, etc.) is configured. Called by the release-agent after discovery.
Parse from $ARGUMENTS:
| Argument | Values | Default | Description |
|---|---|---|---|
| bump | patch, minor, major | patch | Semver bump level |
--dry-run | flag | - | Show plan without executing |
--skip-publish | flag | - | Tag and release but don't publish |
--skip-changelog | flag | - | Skip changelog update |
--profile=JSON | string | - | Discovery profile from release-agent |
# Must be on main/master branch
CURRENT_BRANCH=$(git branch --show-current)
MAIN_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "main")
if [ "$CURRENT_BRANCH" != "$MAIN_BRANCH" ]; then
echo "[ERROR] Must be on $MAIN_BRANCH to release (currently on $CURRENT_BRANCH)"
exit 1
fi
# Must be clean working tree
if [ -n "$(git status --porcelain)" ]; then
echo "[ERROR] Working tree is dirty. Commit or stash changes first."
exit 1
fi
# Pull latest
git pull origin "$MAIN_BRANCH"
Use the profile to determine where the version lives.
npm (package.json):
CURRENT_VERSION=$(node -e "console.log(require('./package.json').version)")
cargo (Cargo.toml):
CURRENT_VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
python (pyproject.toml):
CURRENT_VERSION=$(grep -m1 'version' pyproject.toml | sed 's/.*"\(.*\)"/\1/')
go (tags only):
CURRENT_VERSION=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "0.0.0")
maven (pom.xml):
CURRENT_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout | sed 's/-SNAPSHOT//')
const [major, minor, patch] = currentVersion.split('.').map(Number);
const newVersion = bump === 'major' ? `${major + 1}.0.0`
: bump === 'minor' ? `${major}.${minor + 1}.0`
: `${major}.${minor}.${patch + 1}`;
Use the tag prefix from the profile (default v).
If --dry-run, display the plan and stop:
Release Plan
Ecosystem: {ecosystem}
Current version: {currentVersion}
New version: {newVersion} ({bump} bump)
Tag: {tagPrefix}{newVersion}
Commits since last tag: {count}
Changelog: {will update | skip}
Publish: {command | skip}
[DRY RUN] No changes made
npm:
npm version $NEW_VERSION --no-git-tag-version
cargo (single crate):
Use the Edit tool to replace version = "{CURRENT}" with version = "{NEW}" in Cargo.toml, then run cargo check to update Cargo.lock.
cargo workspace: Bump workspace version in root Cargo.toml. Individual crates matching the old version should also be bumped.
python (pyproject.toml):
Use the Edit tool to replace version = "{CURRENT}" with version = "{NEW}" in pyproject.toml.
go: No manifest to bump (version is tag-only).
maven:
mvn versions:set -DnewVersion=$NEW_VERSION -DgenerateBackupPoms=false
Other: Use sed or the appropriate ecosystem tool to update the version field.
Skip if --skip-changelog.
Generate commit log since last tag:
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -n "$LAST_TAG" ]; then
COMMITS=$(git log $LAST_TAG..HEAD --oneline --no-merges)
else
COMMITS=$(git log --oneline --no-merges -20)
fi
If a changelog file exists (from profile), prepend a new version section:
## {tagPrefix}{newVersion} - {YYYY-MM-DD}
{Categorized commit summaries}
Categorize by conventional commit prefix:
feat: -> Addedfix: -> Fixedrefactor: / perf: -> Changeddocs: -> DocumentationDetect or use the test command from the profile:
| Ecosystem | Test Command |
|---|---|
| npm | npm test |
| cargo | cargo test |
| python | pytest or python -m pytest |
| go | go test ./... |
| maven | mvn test |
| gradle | gradle test |
If tests fail: revert version bump and abort.
# Revert on failure
git checkout -- .
echo "[ERROR] Tests failed - aborting release. Version bump reverted."
git add -A
git commit -m "release: {tagPrefix}{newVersion}"
git tag -a "{tagPrefix}{newVersion}" -m "Release {tagPrefix}{newVersion}"
git push origin $MAIN_BRANCH
git push origin "{tagPrefix}{newVersion}"
gh release create "{tagPrefix}{newVersion}" \
--title "{tagPrefix}{newVersion}" \
--generate-notes \
--latest
Skip if --skip-publish or if ecosystem is tag-only (go, packagist, swift).
| Ecosystem | Publish Command |
|---|---|
| npm | npm publish --access public |
| cargo | cargo publish |
| python | python -m build && twine upload dist/* |
| maven | mvn deploy |
| gradle | gradle publish |
| rubygems | gem build && gem push *.gem |
| nuget | dotnet pack && dotnet nuget push |
| dart | dart pub publish |
| hex | mix hex.publish |
For cargo workspace: publish crates in dependency order.
[OK] Released {tagPrefix}{newVersion}
Version bumped: {currentVersion} -> {newVersion}
Changelog: {updated | skipped}
Tag: {tagPrefix}{newVersion} pushed
GitHub release: https://github.com/{owner}/{repo}/releases/tag/{tagPrefix}{newVersion}
Published: {registry | skipped | tag-only}
v)gh release create --generate-notes