$npx -y skills add dcb/homeassistant-claude-kit --skill releaseCut a new homeassistant-claude-kit version. Curates commits since the last tag into kit-changelog.yaml entries, derives the semver bump, renders CHANGELOG.md, bumps .kit-version + dashboard/package.json, then creates a signed tag and (confirmed) GitHub release. Producer-only — ru
| 1 | # Release the Kit |
| 2 | |
| 3 | This skill is the **producer** half of kit versioning. It reads ONLY the kit's own git |
| 4 | history and writes the version artifacts — the git tag, `.kit-version`, `kit-changelog.yaml`, |
| 5 | `CHANGELOG.md`, and `dashboard/package.json` — in **one commit** so version and content always |
| 6 | travel together. It is **idempotent per version**: re-running it on a version that is already |
| 7 | released, tagged, and pushed is a no-op at every step (append-only changelog, pre-existing-tag |
| 8 | guard, idempotent GitHub release). The changelog's `detect`/`apply` prose is authored |
| 9 | generically and is **never executed**. |
| 10 | |
| 11 | See `references/changelog-schema.md` for the full record schema, the intent contract, the |
| 12 | deterministic commit→type mapping, and the rendering rules. |
| 13 | |
| 14 | ## Step 0: Prerequisites |
| 15 | |
| 16 | Run each check; branch on the sentinel it prints. |
| 17 | |
| 18 | ```bash |
| 19 | # Clean working tree (untracked files are allowed; tracked modifications are not) |
| 20 | git diff --quiet && git diff --cached --quiet && echo "TREE_OK" || echo "TREE_DIRTY" |
| 21 | |
| 22 | # On the default branch |
| 23 | def=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@') |
| 24 | [ -z "$def" ] && def=main |
| 25 | [ "$(git branch --show-current)" = "$def" ] && echo "BRANCH_OK" || echo "BRANCH_WRONG" |
| 26 | |
| 27 | # A baseline tag exists and is >= v0.1.0 |
| 28 | git fetch --tags --quiet 2>/dev/null |
| 29 | last=$(git tag --list 'v*' --sort=-v:refname | head -1) |
| 30 | [ -n "$last" ] && echo "LAST_TAG=$last" || echo "TAGS_MISSING" |
| 31 | |
| 32 | # Signing capability (decides -s vs -a in Step 7) |
| 33 | if [ -n "$(git config --get user.signingkey)" ] && git tag -s __sigprobe__ -m x >/dev/null 2>&1; then |
| 34 | git tag -d __sigprobe__ >/dev/null 2>&1; echo "SIGN_OK" |
| 35 | else |
| 36 | git tag -d __sigprobe__ >/dev/null 2>&1; echo "SIGN_NONE" |
| 37 | fi |
| 38 | |
| 39 | # GitHub CLI auth (for the release in Step 8) |
| 40 | gh auth status >/dev/null 2>&1 && echo "GH_OK" || echo "GH_NONE" |
| 41 | ``` |
| 42 | |
| 43 | - **TREE_DIRTY** → stop. Tell the user to commit or stash; a release commits a curated set, never accidental WIP. |
| 44 | - **BRANCH_WRONG** → stop. Releases are cut from the default branch only. |
| 45 | - **TAGS_MISSING** → stop. The baseline `v0.1.0` tag must exist first (it is created once, during the versioning foundation). Do not invent one. |
| 46 | - **SIGN_NONE** → continue, but **downgrade** the Step-7 tag from `git tag -s` to `git tag -a` and log: *"no usable signing key — creating an annotated (unsigned) tag."* Never abort for this. |
| 47 | - **GH_NONE** → continue; in Step 8 skip `gh release create` and print the exact command for the user to run later. |
| 48 | |
| 49 | ### 0a. Intent-contract gate |
| 50 | |
| 51 | The changelog's `detect`/`apply` can only be synthesized well if commit messages carry intent. |
| 52 | |
| 53 | ```bash |
| 54 | git log "$last"..HEAD --no-merges --pretty='%h%x09%s' |
| 55 | ``` |
| 56 | |
| 57 | For each **releasable** commit (one NOT in the skip-list of Step 3), require a Conventional-Commit |
| 58 | subject (`^(feat|fix|change|removed|security|perf|refactor|docs|chore|style|test|ci|build)(\(.+\))?!?: `) |
| 59 | and a body that explains *why* / *how you'd know you're affected*. |
| 60 | |
| 61 | - Bot / `*(deps)` commits that are non-Conventional → **warn and skip** (do not block the release). |
| 62 | - Any other releasable commit that is non-Conventional or body-less → **STOP**, list the offenders, and ask the maintainer to reword (interactive rebase) before releasing. Thin commits produce hollow `detect`/`apply`. |
| 63 | |
| 64 | ## Step 1: Collect commits |
| 65 | |
| 66 | ```bash |
| 67 | git rev-list --count "$last"..HEAD # 0 → nothing new |
| 68 | git log "$last"..HEAD --no-merges --reverse --pretty='%h%x09%s%n%b' |
| 69 | ``` |
| 70 | |
| 71 | - **Empty range** (count 0) → print *"Nothing to release since $last"* and **exit 0** (no mutation). This is the idempotent re-run case. |
| 72 | - Merge commits are excluded (`--no-merges`). |
| 73 | - For each commit, also capture its touched paths (`git show --stat --name-only <sha>`) — needed for the file-aware skip decision (Step 3) and for `conditions`/`detect_hint`. |
| 74 | |
| 75 | ## Step 2: Curate into logical changes |
| 76 | |
| 77 | Group related commits into **one logical change each** (a feature's many commits → one entry with a |
| 78 | commit range; a follow-up fix to an unreleased feature folds into that feature's entry). This grouping |
| 79 | is the **only** step that uses judgment — everything downstream is deterministic. |
| 80 | |
| 81 | **One entry = exactly one `type`.** Never merge a `fix` and a `feat` into one entry, even if they touch |
| 82 | the same files — emit two entries, each with its own `commits` subset, so each renders in its correct |
| 83 | section. Present the proposed grouping to the user for confirmation before synthesizing entries. |
| 84 | |
| 85 | ## Step 3: Classify each group (det |