name: plastic-releasing description: Use when merging a feature branch to main and tagging a release, bumping the version, or when the user says "release", "tag", or "ship it"
Releasing
Merge, bump, tag, push. Annotated tags with changelogs. Semantic versioning. Project configuration drives the workflow - no hardcoded assumptions.
Checklist
- Read project config
- All tests pass (or verification skipped per config)
- Merge feature branch to main
- Bump version in configured version files
- Commit version bump
- Create annotated tag
- Push to remote with tags
- Run post-push actions (GitHub release, npm publish, etc.)
- Verify release sync (npm dist-tag, GitHub "Latest", git tag all show the new version)
- Complete active intent
- Clean up the intent's worktrees (merge-then-remove)
Workflow
0. Read Project Config
Before anything else, determine which project we are releasing and load its config.
- Read
~/.plastic/projects.yml- find the project whosepathmatches the current working directory. - Extract the project slug (the key under
projects:). - Read
~/.plastic/projects/{slug}/project.yml- this contains therelease:section.
Expected release: keys in project.yml:
release:
verify: "bin/rails test" # command to run before release
version_file: package.json # single file containing the version
version_files: # multiple files (overrides version_file)
- package.json # list EVERY file carrying the version;
- .claude-plugin/plugin.json # they must all be bumped together or they drift
- .claude-plugin/marketplace.json
tag_format: "v{{version}}" # tag naming pattern ({{version}} is replaced)
on_green: # actions to run after push succeeds
- github_release
- npm_publish
on_complete: commit_and_push # what to do with the version bump commit
on_red: stop # what to do if verification fails
Fallback: If no project.yml exists or it has no release: section, fall back to asking the user for each step - verify command, version files, tag format, and post-push actions.
1. Verify Tests Pass
Run the verification command from release.verify in project.yml:
# Example: release.verify = "ruby -Itest test/*_test.rb"
<verify-command-from-config>
- If
release.verifyis present: run it. All checks must pass before proceeding. - If
release.verifyis absent or empty: skip verification. Log that no verify command is configured. - If
release.on_redisstop: abort the release on failure. - If
release.on_redisfix_and_retry: ask the user to fix and re-run.
2. Determine Version Bump
| Change type | Bump | Example |
|---|---|---|
| Breaking changes | Major | 0.x.0 → 1.0.0 |
| New features | Minor | 0.3.0 → 0.4.0 |
| Bug fixes only | Patch | 0.4.0 → 0.4.1 |
Pre-1.0: minor bumps for features, patch for fixes. No major until stable.
3. Merge Feature Branch
git checkout main
git merge <branch-name> --no-ff -m "feat: merge intent [ID] - [description]"
Always --no-ff to preserve branch history in the merge commit.
Worktree-isolated intents (intent 73c3). When the intent was delivered in a Plastic
worktree (the bridge has a provisioned worktree block), its code lives on the branch
plastic/{id}--{slug} inside <repo>/.claude/worktrees/{id}--{slug}, not on a hand-made
feature branch. The merge-then-remove of that worktree is handled together with cleanup in
step 9, which merges plastic/{id}--{slug} into the default branch BEFORE removing the
worktree. If you already merged here by hand, step 9 is a clean no-op merge ("Already up to
date") and proceeds straight to removal. Do not delete the worktree before its branch is
merged, or the work is lost.
4. Bump Version
Determine which files to update from project.yml:
- If
release.version_filesis set: update ALL listed files (they must stay in sync). - Else if
release.version_fileis set: update that single file. - Else: ask the user which files contain the version.
Update the version string in each file, then commit:
git add <version-files>
git commit -m "chore: bump version to X.Y.Z - [one-line summary]"
5. Create Annotated Tag
Read release.tag_format from project.yml to determine the tag name:
- If set (e.g.
"v{{version}}"): replace{{version}}with the new version string. - If not set: default to
vX.Y.Z.
Generate the changelog from commits since the last tag:
git log $(git describe --tags --abbrev=0)..HEAD --oneline --no-merges | grep -E "^[a-f0-9]+ (feat|fix|refactor):"
Create the tag with a multi-line message:
git tag -a <tag-name> -m "<tag-name> - [release name]
- [changelog bullet points from feat/fix/refactor commits]"
6. Push
git push origin main --tags
7. Post-Push Actions
Read release.on_green from project.yml. This is a list of actions to run after a successful push. Execute each in order:
github_release
Create a GitHub release from the tag:
gh release create <tag-name> --title "<tag-name> - [release name]" --latest --generate-notes --notes-start-tag <previous-tag>
--latest is REQUIRED. Pre-release (alpha/beta) tags are NOT auto-promoted to the "Latest"
badge by GitHub, so without it the Releases page keeps showing an older version as Latest while
the newest tag sits below it (a real sync drift we hit on the alpha line). Pass --latest on
every release so the newest one always carries the badge. Do NOT pass --prerelease unless you
specifically want the release hidden from Latest.
For the first release (no previous tag), write notes manually with --notes "..." instead.
npm_publish
Publish the package to npm with the appropriate dist-tag:
# Alpha pre-release (version contains -alpha):
npm publish --access public --tag alpha
# Beta pre-release (version contains -beta):
npm publish --access public --tag beta
# Stable release (no pre-release suffix, >= 1.0.0):
npm publish --access public
The dist-tag is derived from the version string in package.json:
- Contains
-alpha→--tag alpha - Contains
-beta→--tag beta - No pre-release suffix → no
--tagflag (publishes tolatest)
Other values
If on_green contains an action not listed above, log it:
[releasing] Action "<action>" is configured but not yet implemented. Skipping.
If on_green is empty or absent: skip post-push actions entirely.
Verify sync (always, after the post-push actions)
A release is not done until all three surfaces show the SAME newest version. Confirm:
npm view <package> dist-tags # channel tag (alpha/beta/latest) -> new version
gh release list --limit 1 # newest release is the new tag AND marked "Latest"
git ls-remote --tags origin | grep <tag-name> # the tag reached the remote
If the GitHub "Latest" badge is on an older tag (the common drift), fix it without re-releasing:
gh release edit <tag-name> --latest
8. Complete Active Intent
A release IS a delivery. The active intent that drove this work must be completed as part of the release process. This is NOT optional.
- Read
~/.plastic/INDEX.md→ find active intent(s) related to this release - For each active intent being delivered:
a. Write
outcome.mdwith detailed results b. Write## Outcomesummary in the intent file (reference the release tag) c. Update## Insightswith final observations d. Move from## Activeto## Completedin INDEX.md (with today's date) e. Update clusters to show_(completed)_ - Auto-commit:
cd ~/.plastic && git add . && git commit -m "feat: complete intent <ID> - delivered in <tag-name>"
If no active intent exists for this release, that itself is a problem - work happened outside the intent system. Log it and move on, but flag it.
9. Clean Up the Intent's Worktrees (merge-then-remove)
A release is the merge-then-remove path for the intent's worktrees (intent 73c3). This is the
one place the merge-vs-remove policy lands on "merge": the intent's code branch
(plastic/{id}--{slug}) is merged back into the repo's default branch BEFORE the worktree is
removed, so the integrated work is never lost. (The disarm path in plastic-auto, by contrast,
is a plain remove because no release is merging the branch.)
Drive it through Worktree.finish with merge: true, which merges the code branch, then
removes both worktrees (code + paired store), prunes both repos, and clears the worktree block
from the bridge:
ruby -r ~/.plastic/scripts/lib/worktree -r ~/.plastic/scripts/lib/bridge -e \
'b = Bridge.read(ENV["CLAUDE_CODE_SESSION_ID"]); Worktree.finish(b, merge: true) if b'
finish is fail-open and idempotent: a conflicting merge is aborted and logged (the worktree
is still removed rather than stranded), and a second call with the block already cleared is a
no-op. Honor the worktree-cleanup rule: never leave an orphaned worktree, and run git worktree prune in the affected repo if you hit a stale reference.
Conventions
- Annotated tags only -
git tag -a, never lightweight tags - Tag format - driven by
release.tag_formatin project.yml (default:vX.Y.Z) - Tag message - first line:
<tag> - [short name], then blank line, then bullet changelog - Commit prefixes -
feat:,fix:,refactor:,chore:,docs:(conventional commits) - Hyphens, never em-dashes - in tag names, release titles, and commit messages, use a hyphen (
-). Never an em-dash. - Latest badge - always
gh release create --latest; the newest release must carry GitHub's "Latest" badge. - Version files - driven by project.yml; list and bump EVERY file carrying the version (they drift otherwise)
- Verify sync - after pushing, confirm npm dist-tag, GitHub "Latest", and the git tag all show the new version
- Branch cleanup - delete merged feature branches:
git branch -d <branch>
Promotion
To promote a release across channels, use --promote:
plastic-releasing --promote beta # promotes current alpha → beta
plastic-releasing --promote stable # promotes current beta → stable
Promotion rules:
- Linear only: alpha → beta → stable. Cannot skip channels.
--promote beta: reads version frompackage.json, changes-alpha.Nsuffix to-beta.1, publishes with--tag beta.--promote stable: reads version frompackage.json, strips pre-release suffix entirely (e.g.,1.0.0-beta.3→1.0.0), publishes tolatest.- Version files are bumped and committed as in a normal release.
- An annotated tag is created for the promoted version.
Retroactive Tagging
For repos without prior tags, tag historical releases:
git tag -a v0.1.0 <commit-sha> -m "v0.1.0 - [description]"
Use git log --oneline to find the right commits (look for version bump commits or major feature merges).
References
- Read
references/deprecations.mdfor the full deprecation process, severity levels, deprecations.yml schema, and dismissal rules when adding or managing deprecations