Skip to content

Commit b9b8d27

Browse files
committed
chore(skill): add cut-release skill for automated semver releases
Adds a project-local skill at .claude/skills/cut-release/ that walks the agent through a full release: detect commits since the last tag, classify them with conventional-commit rules to pick the bump tier (major / minor / patch), bump every version-bearing file in sync, regenerate both lockfiles, write a curated CHANGELOG section, build the release on a dedicated branch, tag + push, and bring the release commit back to main. Split per the sub-agent-builder pattern: SKILL.md as a thin orchestrator, 9 phase files under steps/, 3 reference files for project-shape, conventional commits, and the CHANGELOG template. Phase 7 is mostly a no-op now that release.yml auto-publishes — it just watches the run and verifies isDraft=false. The manual gh release edit fallback is kept for legacy drafts and CI failures. Updates .gitignore so .claude/skills/cut-release/ is tracked while the locally-installed gitnexus skill stays ignored.
1 parent db2af9a commit b9b8d27

14 files changed

Lines changed: 901 additions & 0 deletions
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
---
2+
name: cut-release
3+
description: |
4+
Cuts a new versioned release of claude-code-trace by detecting commits since
5+
the last tag, classifying them with conventional-commit rules to decide the
6+
semver bump (major / minor / patch), bumping every version-bearing file in
7+
sync, writing a CHANGELOG entry, building the release on a dedicated branch,
8+
tagging + pushing to trigger the GitHub Actions release pipeline, and
9+
publishing the resulting draft release on the repo page. Supports BOTH
10+
whole-main releases ("everything since v0.5.0") and curated releases ("only
11+
the watcher fix, even though main has other unreleased commits") via
12+
cherry-pick onto a fresh branch off the previous tag. Use this skill
13+
whenever the user mentions cutting, tagging, bumping, or shipping a
14+
release — including phrases like "cut a release", "release v1.2.3", "tag and
15+
release", "ship the watcher fix", "bump version to next", "release for X
16+
only", "publish a release", "do a patch release", or "make a new release".
17+
Trigger even when the user does not name the version explicitly — the skill
18+
computes it. Also use proactively when the user finishes a unit of work and
19+
asks to publish it as a release rather than just merging.
20+
---
21+
22+
# Cut a Release — `claude-code-trace`
23+
24+
Turns "we should release this" into a tagged, pushed, pipeline-triggered, **publicly
25+
published** release with a curated CHANGELOG, in a way that survives the project's strict
26+
pre-commit hook chain and stays honest about what's actually shipping.
27+
28+
The skill is project-local because the steps depend on this repo's specific shape (three
29+
version files, two lockfiles, GH Actions release on `v*` tag, the spec-drift +
30+
test-reflection pre-commit hook pair).
31+
32+
---
33+
34+
## Before you start
35+
36+
Read these references on the first invocation in a session — they describe constants the
37+
phase files refer to.
38+
39+
- `${CLAUDE_SKILL_DIR}/references/project-shape.md` — version files, lockfiles, pre-commit
40+
hooks, release pipeline wiring.
41+
- `${CLAUDE_SKILL_DIR}/references/conventional-commits.md` — how commit prefixes map to
42+
semver bump level.
43+
44+
`${CLAUDE_SKILL_DIR}/references/changelog-template.md` is loaded when you reach Phase 4.
45+
46+
---
47+
48+
## Execution
49+
50+
### Phase 1 — Inspect repo state and propose scope
51+
52+
Read `${CLAUDE_SKILL_DIR}/steps/phase1-inspect-and-scope.md` and follow all instructions.
53+
54+
---
55+
56+
### Phase 2 — Build the release branch
57+
58+
Read `${CLAUDE_SKILL_DIR}/steps/phase2-build-release-branch.md` and follow all instructions.
59+
60+
---
61+
62+
### Phase 3 — Bump version files
63+
64+
Read `${CLAUDE_SKILL_DIR}/steps/phase3-bump-versions.md` and follow all instructions.
65+
66+
---
67+
68+
### Phase 4 — Write the CHANGELOG entry
69+
70+
Read `${CLAUDE_SKILL_DIR}/steps/phase4-changelog.md` and follow all instructions.
71+
72+
---
73+
74+
### Phase 5 — Verify, commit, tag
75+
76+
Read `${CLAUDE_SKILL_DIR}/steps/phase5-verify-commit-tag.md` and follow all instructions.
77+
78+
---
79+
80+
### Phase 6 — Push the tag (confirmation gate)
81+
82+
Read `${CLAUDE_SKILL_DIR}/steps/phase6-push-tag.md` and follow all instructions.
83+
84+
---
85+
86+
### Phase 7 — Publish the GitHub release
87+
88+
Read `${CLAUDE_SKILL_DIR}/steps/phase7-publish-github-release.md` and follow all
89+
instructions.
90+
91+
---
92+
93+
### Phase 8 — Bring the release commit back to `main`
94+
95+
Read `${CLAUDE_SKILL_DIR}/steps/phase8-back-to-main.md` and follow all instructions.
96+
97+
---
98+
99+
### Phase 9 — Clean up
100+
101+
Read `${CLAUDE_SKILL_DIR}/steps/phase9-cleanup.md` and follow all instructions.
102+
103+
---
104+
105+
## Safety guardrails
106+
107+
These hold across every phase. The skill must never do any of them without an explicit
108+
"yes" from the user in the current turn:
109+
110+
- `git push origin v*` — release-triggering, never reversible cleanly.
111+
- `gh release edit ... --draft=false` — flips the release public on the repo page.
112+
- `git push -f` or `--force` to any shared branch.
113+
- `git branch -D` on an unmerged branch.
114+
- `--no-verify` to bypass pre-commit hooks — fix the underlying issue instead.
115+
- Cutting a **major** bump unless the user explicitly asked for it OR a `BREAKING
116+
CHANGE:` footer / `!:` marker is present AND the user has confirmed they intend the
117+
major bump.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# CHANGELOG Template
2+
3+
The repo uses [Keep a Changelog](https://keepachangelog.com/) conventions.
4+
5+
## First-time setup
6+
7+
If `CHANGELOG.md` doesn't yet exist, create it with this header:
8+
9+
```markdown
10+
# Changelog
11+
12+
All notable changes to claude-code-trace are documented here. Versions follow
13+
[semantic versioning](https://semver.org/).
14+
```
15+
16+
Then append the per-release section below.
17+
18+
## Per-release section template
19+
20+
```markdown
21+
## [X.Y.Z] — YYYY-MM-DD
22+
23+
One paragraph framing what this release is fundamentally about. Speak to a user, not a
24+
maintainer — explain visible impact, not implementation.
25+
26+
### Added
27+
28+
- **Headline name** ([`<sha7>`](https://github.com/delexw/claude-code-trace/commit/<sha>)).
29+
Two or three sentences. Why it exists, what the user sees, any caveat. End with a
30+
pointer to verification or docs if useful.
31+
32+
### Fixed
33+
34+
- **Headline name** ([`<sha7>`](https://github.com/delexw/claude-code-trace/commit/<sha>)).
35+
What was broken, when it surfaced, how it surfaces now. Avoid jargon-only entries like
36+
"fix race condition" — say which user-visible behavior was wrong and is now right.
37+
38+
### Changed
39+
40+
- Internal refactors with no behavior change usually don't belong here. Only mention if
41+
the user has to update their own integration (e.g. config file rename).
42+
43+
### Removed
44+
45+
- Anything a user could notice as gone (CLI flag, config key, exported function from a
46+
consumed package).
47+
48+
[X.Y.Z]: https://github.com/delexw/claude-code-trace/releases/tag/vX.Y.Z
49+
```
50+
51+
## Bucket rules
52+
53+
- **Added** for `feat:` commits.
54+
- **Fixed** for `fix:` / `perf:` commits.
55+
- **Changed** for `refactor:` / config / build adjustments that the user must notice.
56+
- **Removed** for deletions of public surface.
57+
- **Breaking Changes** (new bucket, comes first, before Added) for major releases. Lead
58+
with what broke and how the user migrates.
59+
60+
Skip `chore:` / `docs:` / `test:` / `ci:` unless the user specifically asks. They
61+
clutter the CHANGELOG without informing users.
62+
63+
## Linking
64+
65+
Each bullet **must** link to its commit so readers can dig in:
66+
67+
```
68+
([`ebb2ca5`](https://github.com/delexw/claude-code-trace/commit/ebb2ca5))
69+
```
70+
71+
7-character SHAs are enough — git accepts the abbreviation.
72+
73+
The reference-link at the bottom (`[X.Y.Z]: https://...releases/tag/...`) is what GitHub
74+
uses to make the version header a clickable link in rendered markdown. Keep it.
75+
76+
## Style
77+
78+
Write substantive bullets. Read the diff if the subject is terse — "fix off-by-one" is
79+
useless to a user, "the picker no longer skips the first session card after a refresh"
80+
is what they care about. Lean on the "what was broken / what is now right" frame for
81+
Fixed, "what's new and why" for Added.
82+
83+
## After writing
84+
85+
Always format:
86+
87+
```bash
88+
npx oxfmt CHANGELOG.md
89+
```
90+
91+
oxfmt enforces consistent table / code-fence formatting that survives the project's
92+
`fmt:check` gate.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Conventional Commit → Semver Bump
2+
3+
The bump level for a release is the highest tier among the commits the user wants to
4+
ship. One `feat:` upgrades the whole release to minor; one `feat!:` or breaking-change
5+
marker upgrades it to major.
6+
7+
| Prefix or marker in commit subject / body | Bump tier | Notes |
8+
| ------------------------------------------------ | --------------------------- | ------------------------------------------------------------------------------------ |
9+
| `feat:` or `feat(scope):` | **minor** | new user-facing feature |
10+
| `fix:` `fix(scope):` | **patch** | bug fix |
11+
| `perf:` `perf(scope):` | **patch** | observable performance improvement |
12+
| `refactor:` `refactor(scope):` | **patch** | internal restructure with no behavior change |
13+
| `chore:` `docs:` `test:` `ci:` `build:` `style:` | **patch** (or "no release") | usually skip in CHANGELOG; if the whole release is only these, still call it a patch |
14+
| `!` after type — e.g. `feat!:`, `fix!:` | **major** | breaking change in API or schema |
15+
| `BREAKING CHANGE:` footer (anywhere) | **major** | always a major bump |
16+
| Anything else / no convention | judge case-by-case | read the diff, ask the user if unclear |
17+
18+
## Algorithm
19+
20+
1. List commit subjects: `git log $LAST_TAG..HEAD --pretty=format:'%H %s%n%b' --no-merges`
21+
2. For each commit in the **chosen subset** (linear or curated), classify by the table
22+
above.
23+
3. The release's bump tier = the highest tier observed.
24+
4. Compute the next version:
25+
- **major**: `X.0.0` (e.g. `0.5.1``1.0.0`, `1.2.3``2.0.0`)
26+
- **minor**: `X.Y.0` (e.g. `0.5.1``0.6.0`)
27+
- **patch**: `X.Y.Z+1` (e.g. `0.5.0``0.5.1`)
28+
5. State the proposed version and ask the user to confirm or override.
29+
30+
## Pre-1.0 caveat
31+
32+
While the version is `0.X.Y` (we are at the time of writing), the project follows
33+
"`0.minor.patch`" loosely — breaking changes can still bump only the minor instead of
34+
forcing a 1.0. Defer to the user. Don't auto-promote to `1.0.0` even when a breaking
35+
marker appears unless the user explicitly asks.
36+
37+
## `tui/package.json` track
38+
39+
The TUI's version moves independently. If the chosen subset contains commits that
40+
modified files under `tui/`, bump the TUI version by the same tier; otherwise bump only
41+
its patch number (so its lockfile stays in sync but the user-visible TUI npm version
42+
ticks gently).
43+
44+
If the subset contains **no** TUI changes at all, you may skip bumping `tui/package.json`
45+
entirely — but then mention this in the CHANGELOG explicitly so consumers know the TUI
46+
package didn't release.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Project Shape — `claude-code-trace`
2+
3+
The skill-specific facts the phase files rely on. For everything that's already in the
4+
codebase, this file points at the source of truth rather than repeating it.
5+
6+
## Read these first if you don't know the codebase
7+
8+
- `AGENTS.md` (symlinked to `CLAUDE.md`) — toolchain commands (`npm run check`, `oxfmt`,
9+
`oxlint`, etc.) and the "format + lint + test before committing" rule.
10+
- `.claude/hooks/pre-commit-spec.sh` and `.claude/hooks/pre-commit.sh` — header comments
11+
describe exactly what each hook checks and how the per-session flag bypass works.
12+
- `.github/workflows/release.yml` — the release pipeline: tag pattern, build matrix,
13+
notes extraction, publish step.
14+
- `.claude/settings.json` — the matcher that wires the hooks into `git commit` calls.
15+
16+
## Version-bearing files (skill-specific rule)
17+
18+
Three files must agree on the next-version string. Nothing in the codebase enforces this
19+
sync — the skill does.
20+
21+
| File | Owns | Bumps with |
22+
| ---------------------- | -------------------------------- | ------------------------------------------ |
23+
| `package.json` (root) | Node/TS workspace + binary entry | the Rust crate (lockstep) |
24+
| `src-tauri/Cargo.toml` | Rust crate version | root `package.json` (lockstep) |
25+
| `tui/package.json` | Terminal UI subpackage | independently; bumps when TUI code changes |
26+
27+
Root + Cargo move together because the desktop binary the user installs is built from
28+
both. `tui/package.json` tracks separately because it's shipped as its own npm package.
29+
30+
## Lockfile regen after a version bump
31+
32+
The lockfiles embed the local workspace's version, so they have to be regenerated after
33+
editing version files — `npm run check` won't fix this on its own. Run:
34+
35+
```bash
36+
npm install --package-lock-only # → package-lock.json
37+
( cd src-tauri && cargo check --offline ) # → src-tauri/Cargo.lock
38+
```
39+
40+
`--package-lock-only` skips the full reinstall (nothing in `node_modules` needs to
41+
change) and `--offline` skips the registry round-trip — only the local crate's version
42+
moved.
43+
44+
## Release pipeline (delegated to CI)
45+
46+
`.github/workflows/release.yml` reads `CHANGELOG.md`, extracts the section matching the
47+
pushed tag's version, attaches it to the auto-built draft release, and a final
48+
`publish` job flips the draft to public. See Phase 7 for the verification flow.
49+
50+
This is the source of truth — if the pipeline changes, edit the workflow and update
51+
Phase 7's narrative, not this file.
52+
53+
## GitHub repo identity
54+
55+
Read once with `git remote get-url origin`; the URL template for commits and releases
56+
follows from there:
57+
58+
- Commit: `<repo-url>/commit/<sha>`
59+
- Release: `<repo-url>/releases/tag/v<X.Y.Z>`
60+
61+
The CHANGELOG template (`changelog-template.md`) hardcodes `delexw/claude-code-trace`
62+
in the link format. If the repo moves, update that file once.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Phase 1 — Inspect repo state and propose scope
2+
3+
Goal: identify the last release, list everything since it, and converge with the user on
4+
**which subset** of those commits ships in this release plus the resulting version
5+
string.
6+
7+
## Step 1.1 — Inspect
8+
9+
```bash
10+
git fetch --tags --quiet
11+
LAST_TAG=$(git describe --tags --abbrev=0)
12+
git log "$LAST_TAG"..HEAD --pretty=format:'%h %s' --no-merges
13+
```
14+
15+
Read each commit's subject (and body when terse) so you can classify by Conventional
16+
Commit prefix. Refer to `${CLAUDE_SKILL_DIR}/references/conventional-commits.md` for the
17+
prefix → bump-level table.
18+
19+
## Step 1.2 — Ask the user about scope
20+
21+
Two modes, mutually exclusive:
22+
23+
- **Linear release** — every commit since `$LAST_TAG` ships. Use when main is
24+
topic-coherent (e.g. only `fix:` commits since last tag, all about the same area).
25+
- **Curated release** — only a chosen subset ships, even though main has other
26+
unreleased commits piled up. Use when main mixes unrelated work (parser fixes + dep
27+
bumps + a feature) and the user wants this release tied to one theme.
28+
29+
Use `AskUserQuestion` to confirm the mode if it's not already clear from context. If the
30+
user named a topic ("watcher fix only", "the parser cleanups"), interpret that as
31+
curated mode and pre-select the matching commits by grepping subjects for keywords
32+
related to the topic — then show your picks for confirmation rather than guessing
33+
silently.
34+
35+
## Step 1.3 — Decide bump and propose version
36+
37+
Classify the **chosen subset** of commits with the table in `conventional-commits.md`.
38+
The release's bump tier is the highest tier among those commits. Compute the next
39+
version:
40+
41+
- **major**: `X.0.0`
42+
- **minor**: `X.Y.0`
43+
- **patch**: `X.Y.Z+1`
44+
45+
State the proposal explicitly to the user:
46+
47+
> "I see 1 `feat:` and 3 `fix:` commits in the chosen subset since `v0.5.0` — that's a
48+
> minor bump → `v0.6.0`. Confirm or override?"
49+
50+
The user may force a specific version (e.g. "release as v0.5.1 even though there's a
51+
feat — I don't want a minor bump"). Honor it.
52+
53+
## Step 1.4 — Record the decision
54+
55+
Hold three values for the rest of the workflow:
56+
57+
- `$LAST_TAG` — e.g. `v0.5.0`
58+
- `$NEXT_VERSION` — e.g. `0.5.1` (no `v` prefix in version files; with `v` in tag names)
59+
- `$SUBSET` — list of commit SHAs that will be cherry-picked, **in their original order
60+
on main** (cherry-pick order matters when one commit depends on another)
61+
62+
Proceed to Phase 2.

0 commit comments

Comments
 (0)