ワンクリックで
prepare-release
Prepare a markymark release — version bump, quality gates, PR, tag, and release notes with human checkpoints
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Prepare a markymark release — version bump, quality gates, PR, tag, and release notes with human checkpoints
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | prepare-release |
| description | Prepare a markymark release — version bump, quality gates, PR, tag, and release notes with human checkpoints |
Prepare a markymark release through a conversational 5-phase workflow with human checkpoints between each phase. The agent automates tedious parts (commit classification, version bumping, Cargo.lock regeneration, release notes) while the human makes all decisions (version number, PR approval, merge, tagging).
Use this skill when:
dev to mainBefore starting, verify:
# Ensure tags are fetched (needed for changelog range)
git fetch --tags
# Check for git-cliff (optional, has fallback)
command -v git-cliff && echo "git-cliff available" || echo "git-cliff not installed — will use git log fallback"
Lefthook pre-commit hooks will run on the version bump commit: fmt check, clippy, cargo-audit, gitleaks, zig build. These are protective gates, not obstacles.
| Location | Format | Example |
|---|---|---|
Cargo.toml workspace.package.version | X.Y.Z (no v prefix) | 0.5.0 |
markymark-plugin/.claude-plugin/plugin.json version | X.Y.Z (no v prefix) | 0.5.0 |
| Git tag | vX.Y.Z (with v prefix) | v0.5.0 |
Cargo.lock internal crate entries | X.Y.Z (auto-generated) | 0.5.0 |
Goal: Classify commits since last release and propose a semver version.
Find the last release tag:
git fetch --tags
git tag --list 'v*' --sort=-version:refname | head -1
List commits since last tag:
git log $(git tag --list 'v*' --sort=-version:refname | head -1)..HEAD --oneline
Classify commits by conventional commit type:
feat = new feature (minor bump)fix = bug fix (patch bump)BREAKING CHANGE or ! after type = major bumprefactor, perf, docs, test, chore, ci, style = patch bump (no user-facing change)Propose semver bump based on the highest-priority commit type:
feat -> minorGenerate changelog preview:
If git-cliff is available:
git-cliff --unreleased --strip header
If git-cliff is NOT available (fallback):
git log $(git tag --list 'v*' --sort=-version:refname | head -1)..HEAD --pretty=format:"- %s (%h)" --reverse
Check for non-conventional commits (filtered by git-cliff's filter_unconventional = true):
# Count total vs conventional
TOTAL=$(git log $(git tag --list 'v*' --sort=-version:refname | head -1)..HEAD --oneline | wc -l)
echo "Total commits: $TOTAL"
# If using git-cliff, compare against changelog entry count
If significant commits were filtered, warn the human.
Present to human:
## Release Assessment
Last release: vX.Y.Z
Commits since: N
### Commit Classification
- Features: N
- Bug Fixes: N
- Refactoring: N
- Documentation: N
- Other: N
### Proposed Version: X.Y.Z -> A.B.C (MINOR/PATCH/MAJOR bump)
### Changelog Preview
[changelog content]
Please confirm the version number or specify a different one.
Goal: Bump version in all files, run quality gates, commit.
Critical ordering: Edit files -> build -> quality gates -> commit. NEVER commit before build succeeds.
Edit Cargo.toml (root workspace version):
[workspace.package]
version = "A.B.C"
Edit all inter-crate dependency versions in each crate's Cargo.toml:
markymark- dependencies with version = "OLD" across all crate Cargo.toml filesversion = "A.B.C" while preserving path, optional, and features attributesmarkymark-index/Cargo.toml, markymark-parser/Cargo.toml, markymark-lsp/Cargo.toml, markymark-cli/Cargo.toml, markymark-mcp/Cargo.tomlgrep to find all instances: grep -rn 'markymark-.*version = "' */Cargo.tomlEdit markymark-plugin/.claude-plugin/plugin.json version field:
"version" value. Do not reformat, reorder keys, or modify other fields.Validate plugin.json syntax:
python3 -c "import json; json.load(open('markymark-plugin/.claude-plugin/plugin.json')); print('plugin.json: valid')"
Cross-crate package version assertion (all crate package versions and plugin.json must match):
CARGO_VER=$(cargo metadata --format-version 1 --no-deps | python3 -c "
import json, sys
pkgs = [p for p in json.load(sys.stdin)['packages'] if p['name'].startswith('markymark')]
versions = set(p['version'] for p in pkgs)
assert len(versions) == 1, f'Version mismatch across crates: {versions}'
print(versions.pop())")
PLUGIN_VER=$(python3 -c "import json; print(json.load(open('markymark-plugin/.claude-plugin/plugin.json'))['version'])")
echo "Cargo.toml version: $CARGO_VER"
echo "plugin.json version: $PLUGIN_VER"
[ "$CARGO_VER" = "$PLUGIN_VER" ] && echo "Versions match" || echo "ERROR: Version mismatch!"
Note: This checks crate package versions and plugin.json. Inter-crate dependency version = "..." fields (step 2) are validated by cargo build in step 6 — if they're wrong, the build fails with "failed to select a version for the requirement".
Rebuild to regenerate Cargo.lock:
cargo build
This MUST use cargo build (not cargo check) because cargo check does not reliably update Cargo.lock for all workspace members.
If build fails:
git checkout -- Cargo.toml markymark-*/Cargo.toml markymark-plugin/.claude-plugin/plugin.jsonValidate Cargo.lock regeneration (dynamic, not hardcoded):
cargo metadata --format-version 1 --no-deps | python3 -c "
import json, sys
pkgs = [p for p in json.load(sys.stdin)['packages'] if p['name'].startswith('markymark')]
print(f'{len(pkgs)} internal crates: {sorted(p[\"name\"] for p in pkgs)}')
versions = set(p['version'] for p in pkgs)
assert len(versions) == 1, f'Version mismatch: {versions}'
print(f'All at version {versions.pop()}')"
Also verify Cargo.lock actually changed:
git diff --stat Cargo.lock | head -3
If Cargo.lock is unchanged after a version bump, something went wrong.
Run full quality gates (all must pass before committing):
# Format check
cargo fmt --all -- --check
# Lint
cargo clippy --workspace --all-targets -- -D warnings
# All tests
cargo test --workspace
# Smoke tests
cargo test -p markymark-cli --test smoke_lsp --test smoke_mcp
# E2E protocol tests
cargo test -p markymark-cli --test lsp_methods --test mcp_methods -- --nocapture
# Plugin hook tests
bash markymark-plugin/tests/test_hooks.sh
If quality gates fail:
Validate RELEASING.md publish order against current cargo metadata:
cargo metadata --format-version 1 --no-deps | python3 -c "
import json, sys
meta = json.load(sys.stdin)
for p in sorted(meta['packages'], key=lambda x: x['name']):
if not p['name'].startswith('markymark'): continue
deps = [d['name'] for d in p['dependencies']
if d['name'].startswith('markymark') and d.get('kind') is None]
print(f\"{p['name']}: {deps if deps else '(none)'}\")"
Compare the output against the publish order listed in RELEASING.md. If they differ, update RELEASING.md as part of this commit.
Commit ALL version-bumped files in one commit:
git add Cargo.toml Cargo.lock markymark-plugin/.claude-plugin/plugin.json
git add markymark-*/Cargo.toml # inter-crate dependency versions
# Also add RELEASING.md if it was updated in step 10
git commit -m "$(cat <<'EOF'
chore(release): bump version to A.B.C
EOF
)"
The version bump commit must contain ONLY version-related files. No unrelated changes.
If pre-commit hooks fail: The commit did NOT happen. Fix the issue, re-stage, and create a NEW commit attempt. Do NOT use --amend.
git push origin dev
## Version Bump Complete
Version bumped to A.B.C across:
- Cargo.toml (workspace version)
- plugin.json
- Cargo.lock (N internal crates updated)
- RELEASING.md (if publish order changed)
Quality gates: All passing
Commit: [hash]
Pushed to: origin/dev
Please review the changes. When ready, I'll create the PR.
Goal: Create a dev -> main PR with changelog body.
Check for unexpected commits (race condition guard):
git pull --rebase origin dev
git log origin/main..HEAD --oneline
If there are commits beyond the version bump (and any pre-existing fix commits), STOP and alert the human. Do NOT create a PR with unexpected content.
Generate PR body:
If git-cliff is available:
git-cliff --latest --strip header
If git-cliff is NOT available (fallback):
git log $(git tag --list 'v*' --sort=-version:refname | head -1)..HEAD --pretty=format:"- %s (%h)" --reverse
Create PR:
gh pr create --base main --head dev \
--title "Release vA.B.C" \
--body "$(cat <<'EOF'
## Release vA.B.C
[changelog content from step 2]
## Checklist
- [ ] Changelog reviewed
- [ ] Version numbers correct (Cargo.toml, plugin.json, Cargo.lock)
- [ ] Quality gates passing
- [ ] Ready to merge
EOF
)"
Agent NEVER merges the PR (Project Rule #7). The human merges all PRs.
## PR Created
PR: [URL]
Base: main <- dev
Title: Release vA.B.C
The PR is ready for your review. Please:
1. Merge it when satisfied
2. Tag the release: `git tag vA.B.C && git push origin vA.B.C`
3. Wait for CI to complete (the tag push triggers a GitHub Actions release workflow that creates the GitHub Release with auto-generated notes)
4. Tell me when CI is done — I'll then refine the release notes
Goal: Tag the release on main after the human has merged the PR.
This phase is performed by the human, not the agent. The agent cannot checkout main in a worktree environment (where main is checked out in a different worktree). The human tags from their main worktree or bare repo.
# From the main worktree (or any checkout of main):
git checkout main
git pull origin main
git tag vA.B.C
git push origin vA.B.C
Important: The tag push triggers a GitHub Actions release workflow that creates the GitHub Release with auto-generated git-cliff notes. Phase 5 requires that release to exist — gh release view vA.B.C will return "release not found" until the workflow completes. Do NOT attempt Phase 5 until the human confirms CI is done.
The agent verifies the tag AND release exist:
git fetch --tags
git log --oneline -1 vA.B.C
gh release view vA.B.C --json name,tagName --jq '"\(.name) — \(.tagName)"'
Goal: Replace the auto-generated git-cliff release notes with a curated, narrative version.
The CI release workflow (triggered by the tag push) creates a GitHub Release with auto-generated notes from git-cliff. These are a raw commit dump with internal issue IDs and no narrative structure. This phase replaces them with human-quality release notes.
Read the auto-generated release notes:
gh release view vA.B.C --json body --jq '.body'
Fetch PR review comments for additional context (Copilot summary, CodeRabbit findings):
# Look up the release PR number (dev -> main, most recent merged)
PR_NUMBER=$(gh pr list --base main --head dev --state merged --limit 1 --json number --jq '.[0].number')
if [ -z "$PR_NUMBER" ]; then
echo "Warning: No merged dev->main PR found. Skipping review comments."
else
gh api repos/sethyanow/markymark/pulls/$PR_NUMBER/reviews --jq '.[].body'
fi
Draft curated release notes following this structure:
## vA.B.C — [Release Title: 2-4 word theme]
[1-2 paragraph narrative describing the major theme of this release.
What changed architecturally? What's the user-visible impact?]
### Highlights
- **[Theme 1]** — [1-2 sentence summary with architectural context]
- **[Theme 2]** — [1-2 sentence summary]
### New Features
- [Feature description] ([commit](link))
### [Thematic Bug Fix Groups]
Group fixes by theme (Soundness, FFI, LSP, Parser, etc.) rather than
listing them flat. Each group gets a heading like:
- "Soundness & Memory Safety"
- "FFI Hardening"
- "LSP Fixes"
- "Parser Fixes"
### Testing
- [Notable test additions]
### Refactoring
- [Notable structural changes]
### Infrastructure
- [Build/release/CI changes]
**Full diff:** [vPREV...vA.B.C](https://github.com/sethyanow/markymark/compare/vPREV...vA.B.C)
**Release PR:** [#N](https://github.com/sethyanow/markymark/pull/N)
Writing guidelines:
Present draft to human for approval before publishing.
Publish the curated notes:
gh release edit vA.B.C --notes "$(cat <<'ENDOFNOTES'
[approved release notes content]
ENDOFNOTES
)"
Verify the update:
gh release view vA.B.C --json name,tagName --jq '"\(.name) — \(.tagName)"'
If something goes wrong at any phase:
| Phase | Rollback |
|---|---|
| Phase 2 (before commit) | git checkout -- Cargo.toml Cargo.lock markymark-plugin/.claude-plugin/plugin.json markymark-*/Cargo.toml |
| Phase 2 (after commit, before push) | git reset HEAD~1 (undoes commit, keeps changes staged) |
| Phase 3 (PR created) | Close the PR via gh pr close [number] |
| Phase 4 (tag pushed) | git tag -d vA.B.C && git push origin :refs/tags/vA.B.C (delete local and remote tag) |
| Phase 5 (notes published) | Re-run gh release edit vA.B.C --notes "..." with corrected content |
git checkout -- Cargo.toml markymark-plugin/.claude-plugin/plugin.json)--amendgit log origin/main..HEAD --oneline shows unexpected commits