From d838490ca215dc13466443deab99ee120d7ba691 Mon Sep 17 00:00:00 2001 From: Alexander Gil Date: Wed, 20 May 2026 15:13:33 +0200 Subject: [PATCH] chore: add opencode release skill and improve release script - Add .opencode/skills/release/SKILL.md with full release workflow - Add .opencode/ with package.json and .gitignore - Improve .ci/release.sh with clean state checks, unshallow handling, automatic release branch creation and push - Simplify AGENTS.md release section to reference the skill --- .ci/release.sh | 66 ++++++-- .opencode/skills/release/SKILL.md | 269 ++++++++++++++++++++++++++++++ AGENTS.md | 49 +----- 3 files changed, 331 insertions(+), 53 deletions(-) create mode 100644 .opencode/skills/release/SKILL.md diff --git a/.ci/release.sh b/.ci/release.sh index 8b54a3c..05cc4f6 100755 --- a/.ci/release.sh +++ b/.ci/release.sh @@ -1,35 +1,75 @@ #!/bin/bash -set -e +set -euo pipefail ensure_full_history() { if git rev-parse --is-shallow-repository 2>/dev/null | grep -q "true"; then - echo "Shallow clone detected. Fetching full history and tags..." + echo "Shallow clone detected. Fetching full history..." git fetch --unshallow --quiet fi - if [ -z "$(git tag -l)" ]; then - echo "No tags found. Fetching tags..." - git fetch --tags --quiet + echo "Fetching tags..." + git fetch --tags --quiet +} + +ensure_clean_state() { + if ! git diff --quiet || ! git diff --cached --quiet; then + echo "Working tree is not clean. Commit or stash changes first." + exit 1 + fi + + BRANCH=$(git rev-parse --abbrev-ref HEAD) + if [ "$BRANCH" != "master" ]; then + echo "Not on master branch. Current branch: $BRANCH" + exit 1 + fi + + git pull origin master --quiet + + if [ "$(git rev-list --count origin/master..HEAD 2>/dev/null || echo 1)" -ne 0 ]; then + echo "There are commits ahead of origin/master. Push or merge them first." + echo "CHANGELOG template needs master commit ID." + exit 1 fi } +echo "=== Checking repository state ===" +ensure_clean_state ensure_full_history -if ! [ "$(git rev-list --count origin/master..HEAD 2>/dev/null || echo 1)" -eq 0 ]; then - echo "There are commits in this branch. Please merge them first." - echo "CHANGELOG template needs master commit ID." - exit 1 +echo "" +echo "=== Recent commits since last release ===" +LATEST_TAG=$(git tag --sort=-creatordate | head -1) +if [ -n "$LATEST_TAG" ]; then + git log "$LATEST_TAG"..HEAD --oneline +else + git log --oneline -10 fi -# bump version +echo "" +echo "=== Bumping version ===" vim ./Cargo.toml -# update lock file +VERSION=$(sed -n 's/^version = "\(.*\)"/\1/p' ./Cargo.toml | head -n1) +echo "New version: $VERSION" + +echo "" +echo "=== Updating lock file ===" cargo update -p timer-cli +echo "" +echo "=== Updating changelog ===" make update-changelog +echo "" +echo "=== Committing release ===" git add . -VERSION=$(sed -n 's/^version = "\(.*\)"/\1/p' ./Cargo.toml | head -n1) git commit -m "release: Version $VERSION" -echo "After merging the PR, tag and release are automatically done" +echo "" +echo "=== Creating release branch and pushing ===" +git checkout -b "release/v$VERSION" +git push -u origin "release/v$VERSION" + +echo "" +echo "Release v$VERSION prepared successfully." +echo "Create a PR to merge release/v$VERSION into master." +echo "After merge, tag and GitHub release are created automatically." diff --git a/.opencode/skills/release/SKILL.md b/.opencode/skills/release/SKILL.md new file mode 100644 index 0000000..e4a5ffa --- /dev/null +++ b/.opencode/skills/release/SKILL.md @@ -0,0 +1,269 @@ +--- +name: release +description: Prepare and publish a new release. Use when the user asks to release, cut a release, or publish a new version. +--- + +## Purpose + +Release a new version of Timer CLI using the release script and CI pipeline. + +## When to use + +Use this skill when: +- The user asks to release a new version +- The user asks to cut a release or publish +- The user asks to tag a new version + +## Prerequisites + +Before releasing, verify: + +1. **Working tree is clean** — no uncommitted changes +2. **You are on `master`** — releases only happen from master +3. **Local master is up to date with origin/master** +4. **No commits ahead of origin/master** (output of `git rev-list --count origin/master..HEAD` must be 0) +5. **Repository is not a shallow clone** — git-cliff needs full history for accurate changelogs + +Check with: +```bash +git status --short +git rev-parse --abbrev-ref HEAD +git pull origin master +git rev-list --count origin/master..HEAD +git tag --sort=-creatordate | head -3 +git log --oneline v..HEAD +``` + +## Version Decision Guide + +Use Semantic Versioning (MAJOR.MINOR.PATCH). Determine the bump type by analyzing commits since the last release. + +### Major Version (X.0.0) + +Bump MAJOR when: +- Breaking changes to CLI interface or arguments +- Breaking changes to output format +- Removal of previously supported time formats +- Commit message contains `BREAKING CHANGE:` or `!` (e.g., `feat!: ...`) + +### Minor Version (0.X.0) + +Bump MINOR when: +- New features added (`feat:` commits) +- New CLI options or subcommands +- New time format support +- Backward-compatible enhancements + +### Patch Version (0.0.X) + +Bump PATCH when: +- Bug fixes (`fix:` commits) +- Documentation updates (`docs:` commits) +- Internal refactoring (`refactor:` commits) +- Dependency updates (`chore(deps):` commits) + +### Decision Process + +1. Run: `git log v..HEAD --oneline` +2. Check commit messages for: + - `!` or `BREAKING CHANGE:` -> MAJOR + - `feat:` -> MINOR + - `fix:`, `docs:`, `refactor:`, etc. -> PATCH +3. If multiple types, use the highest precedence (MAJOR > MINOR > PATCH) + +## Release Process + +### Step 1: Verify Clean State + +Ensure you're on master with no uncommitted changes, up to date with origin, and no commits ahead: + +```bash +git checkout master +git pull origin master +git status # Should show "nothing to commit, working tree clean" +``` + +Verify no commits ahead of origin/master: + +```bash +git rev-list --count origin/master..HEAD # Should output 0 +``` + +**Unshallow check** — shallow clones produce incomplete changelogs: + +```bash +git rev-parse --is-shallow-repository +``` + +If this outputs `true`, unshallow the repo before proceeding: + +```bash +git fetch --unshallow origin +git fetch --tags origin +``` + +### Step 2: Determine Version + +1. Get current version: + ```bash + grep '^version =' Cargo.toml | head -1 + ``` + +2. Review commits since last release: + ```bash + git log v..HEAD --oneline + ``` + +3. Decide on MAJOR, MINOR, or PATCH bump based on the Version Decision Guide above. + +### Step 3: Create Release Branch + +```bash +git checkout -b release/v +``` + +### Step 4: Update Version + +Edit `Cargo.toml` and update the version: + +```toml +version = "" +``` + +### Step 5: Update Lock File + +```bash +cargo update -p timer-cli +``` + +### Step 6: Update Changelog + +Generate the changelog using git-cliff: + +```bash +make update-changelog +``` + +This runs: `git cliff -t v -u -p CHANGELOG.md` + +The changelog is generated from conventional commits and grouped by type (Added, Fixed, Documentation, Build, Refactor, Styling, Testing, Chore). Release commits are excluded. + +### Step 7: Commit Changes + +Stage and commit all changes: + +```bash +git add . +VERSION=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1) +git commit -m "release: Version $VERSION" +``` + +### Step 8: Push Branch and Create PR + +```bash +git push -u origin release/v +``` + +Create a pull request to merge into master. + +### Step 9: After Merge + +After the PR is merged to master, tagging and releasing is done **automatically** by CI: + +1. `auto-tag.yaml` reads the new version from `CHANGELOG.md` +2. Creates a signed GPG tag `v` +3. Pushes the tag to GitHub + +The tag then triggers: +- `rust.yml`: Builds for macOS and Linux, runs tests, publishes to crates.io, creates GitHub Release with changelog +- `aur-publish.yml`: Publishes AUR packages (`timer-rs` and `timer-rs-bin`) + +Monitor with: +```bash +gh run list --limit 5 +``` + +**Note:** Do not manually create tags — CI handles this automatically. + +## What NOT to Do + +| Mistake | Why it's wrong | Fix | +|---------|---------------|-----| +| Manually editing CHANGELOG.md | git-cliff generates it from conventional commits | Use `make update-changelog` | +| Creating git tags manually | `auto-tag.yaml` creates signed tags automatically | Just push to master | +| Releasing from a feature branch | Changelog generation needs master commit IDs | Checkout master first | +| Releasing with dirty working tree | Script will fail or produce incomplete release | Commit or stash changes first | +| Skipping the unshallow check | Shallow clones produce incomplete changelogs | Always check and unshallow if needed | +| Using `--amend` on a commit | May amend the wrong parent commit after hook failures | Just commit again normally | + +## Troubleshooting + +### "There are commits ahead of origin/master" +Merge or push them first before starting the release. + +### Shallow clone detected +```bash +git fetch --unshallow origin +git fetch --tags origin +``` + +### git-cliff not installed +```bash +cargo install git-cliff +``` + +### Auto-tag workflow didn't trigger +Ensure: +- The CHANGELOG.md has a new version entry as the first `## [v...]` heading +- The tag doesn't already exist: `git tag -l | grep ` +- The `PAT` and `GPG_PRIVATE_KEY` secrets are configured in GitHub + +### Commit failed due to pre-commit hooks +Do NOT use `--amend`. Simply stage the changes and commit again: +```bash +git add . +git commit -m "release: Version " +``` + +## Key Files + +| File | Role | +|------|------| +| `Cargo.toml` | Package version (single source of truth) | +| `cliff.toml` | git-cliff configuration for changelog generation | +| `CHANGELOG.md` | Generated changelog (auto-tag reads version from here) | +| `.github/workflows/auto-tag.yaml` | Creates signed GPG tag on push to master | +| `.github/workflows/rust.yml` | Builds, tests, publishes to crates.io and GitHub on tag | +| `.github/workflows/aur-publish.yml` | Publishes AUR packages on tag | +| `Makefile` | `update-changelog` target | +| `.ci/release.sh` | Full release script (can be run manually) | + +## Checklist + +- [ ] On master branch, clean working tree +- [ ] Pulled latest from origin/master +- [ ] No commits ahead of origin/master +- [ ] Repository is not a shallow clone (or has been unshallowed) +- [ ] Determined version bump type (MAJOR/MINOR/PATCH) +- [ ] Created release branch `release/v` +- [ ] Updated version in `Cargo.toml` +- [ ] Ran `cargo update -p timer-cli` +- [ ] Ran `make update-changelog` +- [ ] Committed with message `release: Version ` +- [ ] Pushed and created PR +- [ ] After merge: CI automatically creates tag and release + +## Quick Reference + +| Step | Command | +|------|---------| +| Check current version | `grep '^version =' Cargo.toml \| head -1` | +| View recent commits | `git log v..HEAD --oneline` | +| Check commits ahead | `git rev-list --count origin/master..HEAD` | +| Check shallow clone | `git rev-parse --is-shallow-repository` | +| Unshallow repo | `git fetch --unshallow origin && git fetch --tags origin` | +| Update lock file | `cargo update -p timer-cli` | +| Update changelog | `make update-changelog` | +| Commit | `git commit -m "release: Version "` | +| Monitor CI | `gh run list --limit 5` | +| Run release script | `.ci/release.sh` | diff --git a/AGENTS.md b/AGENTS.md index 4d45211..a087808 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -238,48 +238,17 @@ When adding new features: ## Release Workflow -When running a release (`/release` command), follow these mandatory steps: +Use the **release skill** (`.opencode/skills/release/SKILL.md`) for detailed release instructions. The release script (`.ci/release.sh`) automates the full process. -### 1. Unshallow Git Repository (CRITICAL) +Key steps: +1. Verify clean state (master, no uncommitted changes, up to date) +2. **Unshallow repo if needed** — critical for git-cliff changelog accuracy +3. Determine version bump (MAJOR/MINOR/PATCH) +4. Create release branch, update `Cargo.toml`, run `cargo update -p timer-cli` +5. Run `make update-changelog` +6. Commit, push, create PR -**MUST be done before running git-cliff.** Without full git history, git-cliff will produce incomplete changelog. - -```bash -# Check if repository is shallow -git rev-parse --is-shallow-repository - -# If "true", unshallow immediately -git fetch --unshallow --quiet -git fetch --tags --quiet -``` - -### 2. Version Bump - -Edit `Cargo.toml` to bump the version number (patch/minor/major as appropriate). - -### 3. Update Lock File - -```bash -cargo update -p timer-cli -``` - -### 4. Update CHANGELOG - -```bash -make update-changelog -``` - -This runs `git cliff -t v -u -p CHANGELOG.md`. - -### 5. Commit and Create PR - -```bash -git add . -git commit -m "release: Version " -git checkout -b release/v -git push origin release/v -git-api pr create --title "release: Version " --body "..." -``` +After merge, CI auto-tags and publishes to crates.io, GitHub Releases, and AUR. ## Git Rules