| name | release |
| description | Config-driven release pipeline: version bump, CHANGELOG update, git tagging, GitHub Release, registry publishing. Reads .factory/release-config.yaml. Supports bootstrap (init), release, and dry-run modes.
|
| argument-hint | [init | <version> | --dry-run] |
Release Pipeline
When This Skill Runs
When the user wants to cut a release, bootstrap release config for a new repo,
or preview what a release would do. Works with any project type — Claude Code
plugins, Rust crates, Node.js packages, Python packages, Go modules.
Defer to RELEASING.md when present (project canonical procedure)
If a RELEASING.md exists at the project root, read it first and use it
as the authoritative procedure for this project. This skill provides the
generic invokable shell; RELEASING.md provides the project-specific details
(branch conventions, merge strategy, marketplace publish, recovery procedures).
Procedure:
-
Check for RELEASING.md at the project root.
-
If it exists:
- Announce:
**Release Pipeline** — RELEASING.md found at project root. Deferring to its canonical procedure.
- Read RELEASING.md in full before any action.
- Honor every "Mandatory invariants" entry. Violating them is what
historically broke vsdd-factory's marketplace publish.
- Follow the "Step-by-step: cutting a release" section literally; the
shell snippets are tested.
- Return RELEASING.md's recovery procedures verbatim if you encounter
a known failure mode. STOP and surface to the human if you encounter
anything not in RELEASING.md's recovery section.
- Skip the rest of this SKILL.md unless RELEASING.md explicitly defers
a step back to the generic skill.
-
If RELEASING.md does NOT exist:
-
Announce: **Release Pipeline** — no RELEASING.md found at project root. The canonical release procedure should be documented before cutting a release.
-
Prompt the human:
This project doesn't have a RELEASING.md at the root, which is
the documented source of truth for how releases are cut. Without
one, this skill will fall back to a generic config-driven release
flow which may not match your project's actual release conventions
(branch naming, merge strategy, marketplace publish, etc.).
Recommended: create RELEASING.md before cutting this release.
Use the example at
vsdd-factory/RELEASING.md
as a starting template and adapt to your project's conventions.
Choose:
- Pause and create RELEASING.md now (recommended for first-time releases or any project with a non-trivial release flow).
- Proceed with the generic config-driven flow (use only if you've already validated the generic flow works for this project).
-
If the human chooses (1), help them draft RELEASING.md by reading
.github/workflows/release.yml, .factory/release-config.yaml,
and CHANGELOG.md to extract the project's actual release behavior,
then write a draft for human review.
-
If the human chooses (2), proceed with the generic flow below and
consider it operating without a safety net — prepare to halt if
anything looks wrong.
Announce at Start
After the RELEASING.md check above, if you're proceeding with the generic flow,
say verbatim:
Release Pipeline — reading release config from .factory/release-config.yaml.
Factory Worktree Handling
Before reading the release config, ensure .factory/ is available:
- If
.factory/release-config.yaml exists → proceed (worktree already mounted)
- If
.factory/ directory does not exist:
- Check if
factory-artifacts branch exists: git branch -a | grep factory-artifacts
- If yes:
git worktree add .factory origin/factory-artifacts
- If no: trigger Bootstrap Mode (creates the branch)
- If
.factory/ exists but release-config.yaml is missing → trigger Bootstrap Mode
Mode Detection
Parse $ARGUMENTS:
init → Bootstrap Mode
--dry-run → Dry Run Mode
- Anything else (version string or empty) → Release Mode
Bootstrap Mode (/release init)
Create a release config for a repo that doesn't have one yet.
Step 1: Scan for Project Markers
Search the repo root for:
| Marker | Project Type | Version Location | Publish Command |
|---|
Cargo.toml | Rust | [package].version | cargo publish |
package.json | Node.js | version | npm publish |
pyproject.toml | Python | [project].version | twine upload dist/* |
plugins/*/.claude-plugin/plugin.json | Claude Code plugin | version | none |
go.mod | Go | git tags only | none |
Also detect:
- Test scripts:
run-all.sh, Makefile with test target, CI workflow files
- CHANGELOG:
CHANGELOG.md, CHANGELOG, CHANGES.md
- CI workflows:
.github/workflows/*.yml
- Version badges in README: regex
version-([0-9.]+)
- Marketplace:
.claude-plugin/marketplace.json with version field
Step 2: Propose Config
Generate a release-config.yaml based on detected markers. Present it to the
user with explanations of each section.
Step 3: Confirm and Write
Ask the user to review and confirm. On approval:
- If
factory-artifacts branch does not exist, create it:
git checkout --orphan factory-artifacts
git rm -rf .
git commit --allow-empty -m "factory: initialize factory-artifacts branch"
git checkout main
git worktree add .factory factory-artifacts
- Write
release-config.yaml to .factory/
- Commit on
factory-artifacts: chore: add release config
- Push
factory-artifacts
Release Mode (/release [version])
Step 1: Read Config
Read .factory/release-config.yaml. Validate schema: 1.
Step 2: Determine Version
- If user provided explicit version (e.g.,
/release 1.2.0) → use it
- If
.factory/stories/ exists with story files containing type frontmatter:
- Any
type: feat → MINOR bump
- Only
type: fix → PATCH bump
- Any
breaking_change: true → MAJOR bump
- Otherwise → ask: "Current version is X.Y.Z. What bump? (major / minor / patch / or type explicit version)"
Get current version from the first entry in packages[0].version_sources.
Present proposed version and ask for confirmation before proceeding.
Step 3: Quality Gates
Read quality_gates.mode:
If standard: Skip to Step 4.
If vsdd-partial or vsdd-full: Check each enabled gate:
| Gate | Config Key | Check |
|---|
| Convergence | require_convergence | .factory/ contains convergence report with >= min_convergence_dimensions dimensions CONVERGED |
| Holdout | require_holdout | Holdout satisfaction >= min_holdout_satisfaction |
| Formal verification | require_formal_verification | .factory/ contains passing formal verification report |
| Adversarial passes | require_adversarial_passes | Adversarial review completed >= N passes |
| Human approval | require_human_approval | Ask user: "Quality gates passed. Approve release of vX.Y.Z? (yes/no)" |
If any gate fails, report which gate failed, the expected vs actual value,
and abort. Do not proceed to version bumping.
Step 4: Pre-release Checks
Run each command in pre_release list:
Running pre-release check: "BATS test suite"...
✓ BATS test suite passed
Running pre-release check: "Shellcheck hooks"...
✓ Shellcheck hooks passed
If any check fails, report the failure output and abort.
Step 5: Bump Versions
For each entry in packages[].version_sources and global_version_sources:
format: json → use jq to update the field at path
format: toml → use sed or toml tool to update the field at path
format: yaml → use yq to update the field at path
format: regex → use sed to replace the captured group in path pattern
Report each file updated:
Bumped plugins/vsdd-factory/.claude-plugin/plugin.json → 0.10.3
Bumped README.md badge → 0.10.3
Bumped .claude-plugin/marketplace.json → 0.10.3
Step 6: Update CHANGELOG
Check if CHANGELOG already has an entry for this version:
Step 7: Commit
git add -A
git commit -m "chore: release vX.Y.Z"
Step 8: Tag
git tag -a vX.Y.Z -m "vX.Y.Z: <first line of CHANGELOG entry>"
Step 9: Push
Ask for confirmation: "Push commit + tag to origin? (yes/no)"
git push origin main
git push origin vX.Y.Z
Step 10: Wait for CI
If ci_workflow is set in config:
gh run list --limit 3
gh run watch <run-id> --exit-status
Report result: "Release workflow passed ✓" or "Release workflow FAILED — check ".
Step 11: Verify GitHub Release
gh release view vX.Y.Z
If the release was created by CI, verify it has CHANGELOG content (not
fallback text). If no CI created the release, create it:
gh release create vX.Y.Z --title "vX.Y.Z: <title>" --notes-file /tmp/notes.md
Step 12: Publish (if configured)
For each package with publish config:
cd <package.path>
<publish.pre_publish commands>
<publish.command>
Report result for each package.
Dry Run Mode (/release --dry-run)
Execute the full Release Mode flow but only print what would happen:
DRY RUN — no changes will be made.
1. Version bump: <CURRENT> → <NEXT> (MINOR)
2. Files to update:
- plugins/vsdd-factory/.claude-plugin/plugin.json: "version" → "<NEXT>"
- README.md: badge → <NEXT>
- .claude-plugin/marketplace.json: "plugins[0].version" → "<NEXT>"
3. CHANGELOG: would generate entry from N commits since v<CURRENT>
4. Commit: "chore: release v<NEXT>"
5. Tag: v<NEXT>
6. Push: main + v<NEXT>
7. CI workflow: .github/workflows/release.yml would be triggered
8. Publish: none (no publish config)
Quality gates: standard (pre_release checks only)
Pre-release checks that would run:
- BATS test suite
- Shellcheck hooks
- Plugin structure validation
- Lobster workflow parsing
Error Handling
- If
release-config.yaml has unknown schema version → abort with message suggesting skill update
- If
quality_gates.mode is unrecognized → abort with list of valid modes
- If a pre-release check fails → show output, abort, suggest fixing and re-running
- If version bump fails (file not found, bad format) → abort with specifics
- If git push fails → suggest checking remote access, do not retry
- If CI fails → show link to failed run, do not re-tag