Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions .claude/skills/cli-release/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
---
name: cli-release
description: Use before creating any commit intended for release — determines correct version bump, ensures commit prefix is right, and walks through the release flow. Triggers on "release", "bump version", "merge to staging", "merge to main", "create a release", "what version will this be", or any PR from a feature branch to staging or main.
---

# CLI Release Skill

Use this skill **before writing the commit message** for any change headed to production.
Getting the prefix wrong means the wrong version ships — and fixing it requires an amended force-push.

---

## Step 1: Determine the correct version bump

1. Check the current published tag:
```bash
git fetch origin --tags
git describe --tags --abbrev=0
```

2. Examine commits since that tag on your branch:
```bash
git log <last-tag>..HEAD --oneline
```

3. Apply the bump rules (highest wins):
| Commit prefix | Bump |
|---|---|
| `feat:` | **minor** — X.(Y+1).0 |
| `fix:` | **patch** — X.Y.(Z+1) |
| `chore:`, `docs:`, other | no bump (don't use for deliverable changes) |

4. **State the expected version out loud** before any commit:
> "This will produce **1.20.1** because all commits use `fix:` (no `feat:`)."

---

## Step 2: Craft the commit message

- Use `fix:` for bug fixes, `feat:` for new features.
- If the prefix and bump disagree, add an explicit keyword override:
- `#patch` — force patch regardless of prefix
- `#minor` — force minor regardless of prefix
- `#major` — force major (rare)
- **Only use `feat:`/`fix:` for commits that touch deliverable paths**: `cortexapps_cli/`, `pyproject.toml`, `poetry.lock`, `tests/`, `docker/`. Use `chore:` for anything else.

Example:
```
fix: two-pass re-import for catalog entities with x-cortex-relationships #patch
```

---

## Step 3: Choose the release path

**Two valid paths — pick based on whether you're batching fixes:**

### Option A: Feature → main directly (single fix, ship now)
Use this for most fixes. CI runs, tests pass, PR auto-merges, publish fires.

```bash
gh pr create --base main --head <feature-branch> --title "fix: <description>"
```

- `test-pr.yml` runs tests and auto-merges when they pass.
- `publish.yml` triggers on the resulting push to `main`.
- The workflow auto-syncs HISTORY.md back to `staging` after publish.

### Option B: Feature → staging → main (batching multiple fixes)
Use this when you want to collect several fixes before cutting a release.

```bash
# Step 1: land feature on staging (tests run + automerge)
gh pr create --base staging --head <feature-branch> --title "fix: <description>"
# Step 2: when ready to release, merge staging → main (triggers publish)
gh pr create --base main --head staging --title "Release X.Y.Z: <description>"
```

---

## Human review (when needed)

By default, PRs auto-merge when tests pass. When you need a human to review first:

1. Open the PR as a **draft** — tests run for fast feedback, but automerge is skipped.
2. Share for review. Reviewer approves while still in draft.
3. Convert to ready (`gh pr ready <PR_NUMBER>`) — tests re-run, automerge fires.

The reviewer approves *before* you convert, so you control the merge timing.

```bash
# Open as draft
gh pr create --draft --base main --head <feature-branch> --title "fix: <description>"

# After reviewer approves, convert to ready
gh pr ready <PR_NUMBER>
```

---

## Step 4: Monitor PR CI and auto-merge (no human in the loop)

CI takes ~5 minutes. Use this polling loop — it waits 5 minutes before the first check, then polls every 60 seconds. This keeps API calls to a minimum.

```bash
echo "Waiting 5 minutes for CI..."; sleep 300
while true; do
STATUS=$(gh pr checks <PR_NUMBER> 2>/dev/null | awk '{print $2}' | sort -u)
echo "$(date '+%H:%M:%S') checks: $STATUS"
if echo "$STATUS" | grep -q "fail"; then
echo "CI failed — inspect with: gh pr checks <PR_NUMBER>"; break
fi
if ! echo "$STATUS" | grep -qE "pending|in_progress|queued"; then
echo "All checks passed — merging"
gh pr merge <PR_NUMBER> --merge
break
fi
sleep 60
done
```

**Critically: do NOT add any commits during or after this loop.** The publish workflow auto-commits `chore: update HISTORY.md for main` after merging. That commit is excluded from the `paths:` trigger filter (`HISTORY.md` is not in `cortexapps_cli/**`, `docker/**`, `pyproject.toml`, or `poetry.lock`), so it will **not** trigger a second publish run. Any commit you push that *does* touch those paths will trigger a new, unwanted build.

---

## Step 5: Monitor the publish workflow after merge

The publish workflow runs 5 parallel jobs under the same `GH_TOKEN` PAT:
`pypi` → `pypi-deploy-event`, `docker`, `docker-deploy-event`, `homebrew`

Wait 2 minutes for the workflow to start, then poll:

```bash
echo "Waiting 2 minutes for publish workflow to start..."; sleep 120
while true; do
gh run list --limit 5 --branch main --json status,conclusion,name,databaseId \
--jq '.[] | "\(.name) \(.status) \(.conclusion // "running") \(.databaseId)"'
echo "---"
DONE=$(gh run list --limit 1 --branch main --json status --jq '.[0].status')
[ "$DONE" = "completed" ] && break
sleep 60
done
# Check final result
gh run list --limit 1 --branch main --json conclusion --jq '.[0].conclusion'
```

**If a job fails with a rate limit error — do NOT push a new commit.** Re-run only the failed jobs:
```bash
gh run rerun --failed <run-id>
```

**Confirm the tag was cut after a successful run:**
```bash
git fetch origin --tags && git tag --sort=-version:refname | head -3
```

---

## Step 5: Homebrew dependency caveat

`mislav/bump-homebrew-formula-action` **cannot** update `resource` blocks for Python dependencies.
If `pyproject.toml` or `poetry.lock` changed dependency versions, manually update `cortexapps/homebrew-tap/Formula/cortexapps-cli.rb` resource blocks after release.

---

## Quick-reference: what lives where

| Thing | Location |
|---|---|
| Versioning rules (full reference) | `CLAUDE.md` lines 165–227 |
| Changelog prefixes (`add:`, `change:`, `remove:`) | `CLAUDE.md` lines 219–224 |
| GitHub Actions release workflow | `.github/workflows/publish.yml` |
| Homebrew formula (local copy) | `homebrew/cortexapps-cli.rb` |
| Re-run failed jobs | `gh run rerun --failed <run-id>` |
16 changes: 16 additions & 0 deletions .github/workflows/test-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ on:
pull_request:
branches:
- staging
- main
paths:
- 'cortexapps_cli/**'
- 'tests/**'
Expand Down Expand Up @@ -68,3 +69,18 @@ jobs:
- name: Test with pytest
run: |
just test-all

automerge:
needs: test
# Only auto-merge on non-draft PRs. For PRs requiring human review, keep as
# draft until approved, then convert to ready — that re-triggers this workflow.
if: github.event_name == 'pull_request' && github.event.pull_request.draft == false
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: write
steps:
- name: Auto-merge PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh pr merge ${{ github.event.pull_request.number }} --merge --repo ${{ github.repository }}
38 changes: 25 additions & 13 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,36 +143,48 @@ Use the GitHub-recommended format: `<issue-number>-<short-description>`
Documentation-only changes (like updates to CLAUDE.md, README.md, STYLE.md) can be committed directly to `main` without going through the staging workflow.

### Release Workflow

**Default (single-feature release):** Feature branch → PR directly to `main`
1. Create feature branch for changes
2. Create PR to merge feature branch to `staging` for testing
2. Create PR to merge feature branch to `main`:
```bash
gh pr create --base main --head <feature-branch> --title "feat: description"
```
3. Merge the PR to trigger release

**Multi-feature release:** Use `staging` to bundle multiple features
1. Create feature branches for each change
2. Create PRs to merge each feature branch to `staging`
3. Create PR to merge `staging` to `main` to trigger release:
```bash
gh pr create --base main --head staging --title "Release X.Y.Z: Description"
```
- Include the expected version number and brief description in title
- List all changes in the PR body
4. Version bumping is automatic based on **conventional commit prefixes** in the commit history since the last tag:
- `feat:` prefix → **minor** version bump (new features)
- `fix:` prefix → **patch** version bump (bug fixes)
- If multiple types present, the highest wins (feat > fix)
- Default (no recognized prefix): patch bump
5. Release publishes to:
- PyPI
- Docker Hub (`cortexapp/cli:VERSION` and `cortexapp/cli:latest`)
- Homebrew tap (`cortexapps/homebrew-tap`)

**Version bumping** is automatic based on **conventional commit prefixes** in the commit history since the last tag:
- `feat:` prefix → **minor** version bump (new features)
- `fix:` prefix → **patch** version bump (bug fixes)
- If multiple types present, the highest wins (feat > fix)
- Default (no recognized prefix): patch bump

**Release publishes to:**
- PyPI
- Docker Hub (`cortexapp/cli:VERSION` and `cortexapp/cli:latest`)
- Homebrew tap (`cortexapps/homebrew-tap`)

### Determining the Next Version (Claude Instructions)
Before creating a staging-to-main release PR, Claude must:
Before merging to `main`, Claude must:

1. **Check the current version tag**:
```bash
git fetch origin --tags
git describe --tags --abbrev=0
```

2. **Analyze commits since the last tag**:
2. **Analyze commits since the last tag** (use the source branch):
```bash
git log <last-tag>..origin/staging --oneline
git log <last-tag>..<feature-branch-or-staging> --oneline
```

3. **Determine the version bump** by examining commit prefixes:
Expand Down
13 changes: 13 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

<!-- insertion marker -->
## [1.20.0](https://github.com/cortexapps/cli/releases/tag/1.20.0) - 2026-06-11

<small>[Compare with 1.19.2](https://github.com/cortexapps/cli/compare/1.19.2...1.20.0)</small>

### Features

- add integration tests for users roles list ([ef639c6](https://github.com/cortexapps/cli/commit/ef639c6c49a6de44e38c9c72e4d84754af4c5e8a) by Jeff Schnitter).
- add users roles list command ([52013c9](https://github.com/cortexapps/cli/commit/52013c981a38236e8a381b7c069b5f067cbdadba) by Jeff Schnitter).

### Bug Fixes

- add explicit pytest import to test_users.py ([517b60a](https://github.com/cortexapps/cli/commit/517b60a48ee1977c575c840a81e90faf92c1f793) by Jeff Schnitter).

## [1.19.2](https://github.com/cortexapps/cli/releases/tag/1.19.2) - 2026-06-10

<small>[Compare with 1.19.1](https://github.com/cortexapps/cli/compare/1.19.1...1.19.2)</small>
Expand Down
Loading
Loading