plastic-releasing
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"
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
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"
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
Wrap, finish, close, or mark an intent Done, delivered or abandoned. Use when completing or abandoning an intent, when a checklist reaches 100 percent and Exec is finished, or when asked to "wrap this up".
Autonomous intent delivery - agent takes over How and Exec. Use when user says "auto", "take it from here", "deliver this", or when brainstorming-grill-me concludes and user confirms autonomous execution. Requires an active intent in INDEX.md.
Use when diagnosing Plastic installation health, after updates, or when something seems broken. Runs checks and reports findings with fix options.
Use when updating Plastic. Runs the `update` verb, which reads the installed VERSION, derives its channel, queries npm dist-tags, and advances to the next version on that channel (or switches channel with a flag).
Use when completing or reviewing intents, reorganizing the index, or when the intent store needs maintenance. Examples: <example>Context: User has finished implementing a feature. user: "This intent is done, clean up the index" assistant: "I'll use the intent-curator to update the intent status and reorganize INDEX.md" <commentary>Intent lifecycle change triggers curator for index maintenance.</commentary></example> <example>Context: The intent store has grown and clusters need review. user: "Organize the intents" assistant: "I'll use the intent-curator to review clusters, flag orphans, and suggest connections" <commentary>Periodic maintenance of the Zettelkasten structure.</commentary></example>
Board a session onto an intent: take the lock FIRST, confirm savepoint state, ask auto or guided ONCE, then resume at the latest delivered station and run the cycle to Done. Use on `continuing --intent {id}`, when a new intent is registered and the user asks to work it, or when the user picks an intent to work. Requires the intent in INDEX `## Active`.
| 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" |
| user-invocable | true |
Merge, bump, tag, push. Annotated tags with changelogs. Semantic versioning. Project configuration drives the workflow - no hardcoded assumptions.
Before anything else, determine which project we are releasing and load its config.
~/.plastic/projects.yml - find the project whose path matches the current working directory.projects:).~/.plastic/projects/{slug}/project.yml - this contains the release: 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.
Run the verification command from release.verify in project.yml:
# Example: release.verify = "ruby -Itest test/*_test.rb"
<verify-command-from-config>
release.verify is present: run it. All checks must pass before proceeding.release.verify is absent or empty: skip verification. Log that no verify command is configured.release.on_red is stop: abort the release on failure.release.on_red is fix_and_retry: ask the user to fix and re-run.| 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.
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). A worktree-delivered intent's code lives on
plastic/{id}--{slug}, merged together with cleanup in step 9, not on a hand-made feature
branch. Do not delete the worktree before its branch is merged, or the work is lost. For
the full rationale and the already-merged-by-hand no-op case, read
references/promotion-and-tagging.md.
Stable-cut guard. Before touching any version file for a stable (no pre-release suffix,
latest) cut, run the guard in scripts/lib/release_guard.rb:
require "./scripts/lib/release_guard"
result = ReleaseGuard.check(
package_json: "package.json",
plugin_json: ".claude-plugin/plugin.json",
marketplace_json: ".claude-plugin/marketplace.json",
stable: true
)
raise "release guard failed: #{result.mismatches} #{result.prerelease_suffix}" unless result.ok?
If it reports a mismatch or a pre-release-suffix violation, stop and resolve it before bumping
any file. For a beta or alpha cut, pass stable: false; only version-file agreement is checked,
a pre-release suffix is expected. Read references/release-lines.md for the stable-line
guarantees this guard protects.
Determine which files to update from project.yml:
release.version_files is set: update ALL listed files (they must stay in sync).release.version_file is set: update that single file.Update the version string in each file, then commit:
Cut the CHANGELOG entry. Before committing, edit CHANGELOG.md at the repo root so
the changelog change rides this same version-bump commit and reaches the tag. Write one
line in the existing shape:
`<version>` - shipped <date>; collected <intent-id> (<one-line summary>)
Prepend it as the first bullet under ## Released (newest-first). If this version was
sitting under ## Unreleased, move it out of that section and into ## Released. Keep
the line intent-centric narrative (which intents the cut collected and why), NOT commit
detail: step 5's tag-message changelog and step 7's gh release create --generate-notes
already own the commit-level detail, so do not duplicate it here.
git add <version-files> CHANGELOG.md
git commit -m "chore: bump version to X.Y.Z - [one-line summary]"
Read release.tag_format from project.yml to determine the tag name:
"v{{version}}"): replace {{version}} with the new version string.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]"
git push origin main --tags
Read release.on_green from project.yml. This is a list of actions to run after a successful push. Execute each in order:
github_releaseCreate 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_publishPublish 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:
-alpha → --tag alpha-beta → --tag beta--tag flag (publishes to latest)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.
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
This step now runs BEFORE step 9's end-intent call (intent 188, D7): scripts/end-intent
gained its own step 5 that disarms (releases the worktree, clears delivery.lock) as part
of every close. Its plain-remove shape does not merge, so if end-intent ran first on a
release, its step 5 would remove the worktree WITHOUT merging the code branch first,
stranding the integrated work (Worktree.finish returns early once the worktree block it
needs is gone, per worktree.rb's own "no-op if nothing was provisioned" contract).
Running this merge-then-remove step first means the worktree is already gone by the time
step 9 runs, so end-intent's own disarm becomes a harmless no-op for the worktree
(nothing left to remove), while for the FIRST time on this path it also clears the delivery
lock correctly (G5): before intent 188 this path left the lock stranded, exactly the class
of bug closed by the End-tail enforcement work.
This is the release branch of plastic-intent-ending's Step 5 disarm (merge: true), not a
separate concern: a release is the merge-then-remove path for the intent's worktree (intent
73c3), so the intent's code branch is merged back into the default branch BEFORE the worktree
is removed. Drive it through Worktree.finish with merge: true, which merges the code
branch, then removes the worktree, prunes the repo, and clears the worktree block from the
bridge:
ruby -r ~/.plastic/scripts/lib/worktree -r ~/.plastic/scripts/lib/bridge -e \
'b = Bridge.discover_bridge(session: ENV["CLAUDE_CODE_SESSION_ID"], cwd: Dir.pwd); Worktree.finish(b, merge: true) if b'
(Uses discover_bridge, not a bare session-keyed Bridge.read, because a session can own more
than one live bridge now (intent 131) and discover_bridge resolves the right one for this cwd.)
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. For why this is the one place the
merge-vs-remove policy lands on merge, and the fail-open/idempotent guarantees of finish,
read references/promotion-and-tagging.md.
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. The mechanical close (outcome/INDEX/savepoint/commit, AND disarm since intent 188) is plastic-intent-ending's job, not this skill's: run its backing script rather than restating that prose here.
~/.plastic/INDEX.md (or the project's INDEX.md) - find active intent(s) related to this release.outcome.md (never leave the scaffold placeholder), disposition: delivered, referencing the release tag.
b. Update ## Insights with final observations.
c. Run the mechanical close (scripts/end-intent's steps 1-5): this stamps the intent file's ## Outcome summary, moves the INDEX.md line to ## Completed (dated today, with a rich entry description via --index-note), appends the savepoint Done bookend, commits the store, and disarms (releases the worktree - already gone from step 8 above - and clears delivery.lock), all in one call:
ruby ~/.plastic/scripts/end-intent --store <store_path> --id <ID> --disposition delivered \
--session "$CLAUDE_CODE_SESSION_ID" \
--outcome-summary "delivered in <tag-name>: <one-line summary>" \
--index-note "<tag-name>, <mode/tier>; <what shipped>; <suite result>"
A non-zero exit needs attention: 4 means a live foreign session holds the lock (back
off), 5 means the code worktree is still dirty (should not happen here, since step 8
already removed it; investigate before overriding with --discard-worktree-changes),
3 means disarm ran but the lock is still present (run /plastic-doctor check the lock status).
d. Update clusters to show _(completed)_ (the store-curating skill's job on its next pass).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.
Two lanes get code to a release, on top of the workflow above.
latest. This is the
workflow in the steps above, unchanged. Use it for additive, suite-verifiable,
low-blast-radius work.beta, publish to the npm beta channel, verify in
real use, then merge to main and cut stable. Use it for work that changes operational
substrate, or carries data, migration, lock, or state-format risk, or that a hermetic suite
cannot fully validate on its own.Stable-line guarantees. An external latest user can rely on:
main is always green and releasable; no pending revert awaiting re-land sits on main.latest, and the newest release
always carries the GitHub "Latest" badge.scripts/lib/release_guard.rb (see Bump
Version above).latest is stable, beta is the verification line, alpha is
experimental.Read references/release-lines.md for the full lane-routing detail, the version-line map, and
the intent-41 re-land playbook.
git tag -a, never lightweight tagsrelease.tag_format in project.yml (default: vX.Y.Z)<tag> - [short name], then blank line, then bullet changelogfeat:, fix:, refactor:, chore:, docs: (conventional commits)-). Never an em-dash.gh release create --latest; the newest release must carry GitHub's "Latest" badge.git branch -d <branch>references/release-lines.md for the two release lanes, the stable-line guarantees,
the version-line map, and the intent-41 re-land playbook before starting any release--promote beta/--promote stable) or
tagging a historical release retroactively, read references/promotion-and-tagging.md
for the exact commands and rules firstreferences/deprecations.md for the full deprecation process, severity levels, deprecations.yml schema, and dismissal rules when adding or managing deprecations