Repository navigation
chore(release): replace AI release scripts with a Claude Code release skill #1300
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,54 @@ | ||||||||||||||||
| --- | ||||||||||||||||
| name: release | ||||||||||||||||
| description: Cut a Kosli CLI release - suggest the next semver tag from the changes since the last tag, draft the release notes with the user, then tag and push via `make release`. Use when asked to "release", "cut a release", "tag a new version", or "draft release notes". | ||||||||||||||||
| --- | ||||||||||||||||
|
|
||||||||||||||||
| ## Overview | ||||||||||||||||
|
|
||||||||||||||||
| A release is an annotated tag on `main`. Its message becomes the top of the GitHub Release: | ||||||||||||||||
| `release.yml` reads it and hands it to GoReleaser, which appends the "What's Changed" list | ||||||||||||||||
| of merged PRs. This skill does the judgment work (version bump, notes) and leaves the | ||||||||||||||||
| mechanics to `make release tag=vX.Y.Z`, which creates the annotated tag from | ||||||||||||||||
| `dist/release_notes.md` and pushes it. `make release` is the only way the tag gets | ||||||||||||||||
| created; see the Releasing section of `CLAUDE.md` for why. | ||||||||||||||||
|
|
||||||||||||||||
| ## Steps | ||||||||||||||||
|
|
||||||||||||||||
| **1. Check the starting point.** Run `git fetch origin --tags`. Confirm the current | ||||||||||||||||
| branch is `main`, it matches `origin/main`, and the working tree has no tracked changes. | ||||||||||||||||
| Find the base with `git describe --tags --abbrev=0`. If any check fails, stop and tell | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
| the user what to fix. Done when `main` == `origin/main` and the base tag is known. | ||||||||||||||||
|
|
||||||||||||||||
| **2. Classify every change since the base.** List commits with | ||||||||||||||||
| `git log --oneline <base>..HEAD`, then read the diffs of the ones that touch `cmd/` or | ||||||||||||||||
| `internal/` (`git show <sha> -- cmd internal`). Only user-facing CLI changes count: | ||||||||||||||||
| commands, flags, output, defaults, error behaviour, and fixes a user would notice. | ||||||||||||||||
| Tests, `testdata/`, docs, `charts/`, `.github/`, `bin/`, scripts, the Makefile and | ||||||||||||||||
| dependency bumps without behaviour change do not. Done when every commit is marked | ||||||||||||||||
| user-facing or not, with a one-line reason. | ||||||||||||||||
|
|
||||||||||||||||
| **3. Pick the bump.** Base it only on the user-facing changes: | ||||||||||||||||
|
|
||||||||||||||||
| - **major**: a removed or renamed command or flag, or a changed default. | ||||||||||||||||
| - **minor**: a new command, subcommand, flag or feature. | ||||||||||||||||
| - **patch**: bug fixes, refactors, dependency updates, or no user-facing changes. | ||||||||||||||||
|
|
||||||||||||||||
| Compute the next tag from the base (`v2.48.2` + minor -> `v2.49.0`) and confirm it does | ||||||||||||||||
| not exist yet with `git tag -l <next>`. A pre-release tag (`v2.49.0-rc1`) only when the | ||||||||||||||||
| user asks for one. | ||||||||||||||||
|
|
||||||||||||||||
| **4. Draft `dist/release_notes.md`.** Write user-facing CLI changes only, as concise | ||||||||||||||||
| bullets under `#` headings, using only the headings that have entries, in this order: | ||||||||||||||||
| `# Breaking changes`, `# New features`, `# Improvements`, `# Bug fixes`. Each bullet | ||||||||||||||||
| names the command or flag and what changed for the user. When there are no user-facing | ||||||||||||||||
| changes, the file is a single line with no heading: `No user-facing CLI changes.` | ||||||||||||||||
| Create `dist/` if needed and overwrite any file already there. | ||||||||||||||||
|
|
||||||||||||||||
| **5. Review with the user.** Show the suggested tag, the reasoning for the bump (the | ||||||||||||||||
| user-facing changes that drove it), and the notes. Apply every edit they ask for to | ||||||||||||||||
| `dist/release_notes.md` and show the result again. Done when the user explicitly | ||||||||||||||||
| approves both the tag and the notes. | ||||||||||||||||
|
|
||||||||||||||||
| **6. Tag and push.** Pushing the tag starts the release pipeline, so ask once more: | ||||||||||||||||
| "Create and push `<tag>`?". On a yes, run `make release tag=<tag>`. Then report the run | ||||||||||||||||
| with `gh run list --workflow release.yml --limit 1`. | ||||||||||||||||
|
Comment on lines
+52
to
+54
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two small issues with the last step:
Suggested change
|
||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| .PHONY: help build clean clean-cache deps fmt lint vet docker ldflags ensure_golangci-lint check_dirty add_test_tag build_release ensure_network ensure_gotestsum test_setup test_integration test_integration_full test_integration_single licenses upgrade-deps helm-lint helm-docs docs release suggest-version-ai | ||
| .PHONY: help build clean clean-cache deps fmt lint vet docker ldflags ensure_golangci-lint check_dirty add_test_tag build_release ensure_network ensure_gotestsum test_setup test_integration test_integration_full test_integration_single licenses upgrade-deps helm-lint helm-docs docs release | ||
| .DEFAULT: help | ||
| .DEFAULT_GOAL := help | ||
| .DELETE_ON_ERROR: | ||
|
|
@@ -216,32 +216,15 @@ docs: build ## Generate CLI reference docs (use CMD="attest snyk" for one comman | |
| @./kosli docs --dir client_reference $(CMD) | ||
| @echo "Docs written to client_reference/" | ||
|
|
||
| # Suggest next semver and changelog using Claude. | ||
| # Writes changelog to dist/release_notes.md, used as the annotated tag's body. | ||
| # Requires: jq, curl, op (1Password CLI). API key from 1Password via op. | ||
| # Usage: make suggest-version-ai [BASE_REF=v1.2.3] | ||
| suggest-version-ai: ## Suggest next version using AI | ||
| @command -v jq >/dev/null 2>&1 || (echo "Install jq (e.g. brew install jq)" && exit 1) | ||
| @command -v curl >/dev/null 2>&1 || (echo "Install curl (e.g. brew install curl)" && exit 1) | ||
| @bin/suggest-version-ai.sh $(BASE_REF) -o dist/release_notes.md | ||
|
|
||
| # Release: without tag → suggest version + changelog, then interactive edit & confirm, then tag and push. | ||
| # With tag → escape hatch: create annotated tag (body = dist/release_notes.md if present), push. No AI, no prompt. | ||
| # Creates an annotated tag (body = dist/release_notes.md if present) and pushes it. | ||
| # Release notes are carried in the tag message so GitHub Actions can pass them to GoReleaser. | ||
| release: ## Cut a new release and push next tag | ||
| # --cleanup=verbatim stops git stripping the notes' markdown '#' headings as comments. | ||
| # To draft the version and notes with Claude, use the release skill (/release in Claude Code). | ||
| release: ## Cut a new release and push the tag: make release tag=vX.Y.Z | ||
| @current=$$(git branch --show-current 2>/dev/null || git rev-parse --abbrev-ref HEAD); \ | ||
| if [ "$$current" != "main" ]; then echo "ERROR: release must be run from main branch (current: $$current)"; exit 1; fi; \ | ||
| if [ -z "$(tag)" ]; then \ | ||
| command -v jq >/dev/null 2>&1 || (echo "Install jq (e.g. brew install jq)" && exit 1); \ | ||
| command -v curl >/dev/null 2>&1 || (echo "Install curl (e.g. brew install curl)" && exit 1); \ | ||
| bin/suggest-version-ai.sh -o dist/release_notes.md; \ | ||
| if [ ! -f dist/suggested_version ]; then \ | ||
| echo "Suggestion failed or no previous tag. Use: make release tag=vX.Y.Z"; exit 1; \ | ||
| fi; \ | ||
| bin/release-interactive.sh; \ | ||
| else \ | ||
| git remote update; \ | ||
| git status -uno | grep --silent "Your branch is up to date" || (echo "ERROR: your branch is NOT up to date with remote" && exit 1); \ | ||
| ([ -f dist/release_notes.md ] && git tag -a $(tag) --cleanup=verbatim -F dist/release_notes.md) || git tag -a $(tag) -m"$(tag)"; \ | ||
| git push origin $(tag); \ | ||
| fi | ||
| if [ -z "$(tag)" ]; then echo "Usage: make release tag=vX.Y.Z (or run /release in Claude Code to draft version and notes)"; exit 1; fi; \ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: this check comes after the |
||
| git remote update; \ | ||
| git status -uno | grep --silent "Your branch is up to date" || (echo "ERROR: your branch is NOT up to date with remote" && exit 1); \ | ||
| ([ -f dist/release_notes.md ] && git tag -a $(tag) --cleanup=verbatim -F dist/release_notes.md) || git tag -a $(tag) -m"$(tag)"; \ | ||
| git push origin $(tag) | ||
This file was deleted.
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something to consider: the last step of this skill pushes a release tag, and the description triggers on loose phrases like "draft release notes" and "tag a new version". If you want it to run only on an explicit
/release, adddisable-model-invocation: trueto the frontmatter. Steps 5 and 6 already ask for confirmation twice, so this is optional. It does mean Claude won't load the skill by itself, though, which goes against the "or ask Claude to cut a release" path in the PR description. Your call.