ci: replace Release Drafter with git-cliff for commit-based release notes
Release Drafter only catches PRs; this repo's workflow is mostly direct-to-master commits per the auto-mode pattern, so 80%+ of substantive changes were invisible to it. git-cliff is commit-based: parses every commit since the last tag, categorizes by conventional-commit prefix (`docs:`, `scripts:`, `composes:`, `models:`, `fix:`, `chore:`) with keyword-based fallback for un-prefixed commits (`Document Cliff 1`, `Add Gemma 4 compose`, `power-cap-sweep:`, `verify-full.sh:`). Squash-merged PRs flow through the same parsers since their squashed title becomes the commit message. Workflow triggers on `v[0-9]+.[0-9]+.[0-9]+` tag push, runs git-cliff with `--latest`, creates a GitHub Release with the categorized body. Tested locally on 274 commits since repo init: 49 land in catch-all "Other" (genuinely unconventional one-off commits); rest distribute across 9 categories. Existing v2026.05.09 release stays as-is (hand-written); next CalVer tag onwards uses this pipeline. Future cadence: `git tag v$(date +%Y.%m.%d) && git push origin v...` — workflow does the rest. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
187
cliff.toml
Normal file
187
cliff.toml
Normal file
@@ -0,0 +1,187 @@
|
||||
# git-cliff config for club-3090 — generates GitHub Release notes from
|
||||
# commits since the last tag. Categorizes by conventional-commit prefix
|
||||
# OR by content keywords (cliff/regression/pin-bump). Both direct-to-master
|
||||
# commits and squash-merged PRs flow through the same parsers.
|
||||
#
|
||||
# Workflow: tag with CalVer (`git tag v2026.MM.DD && git push origin v...`)
|
||||
# → .github/workflows/release.yml runs → git-cliff generates the body
|
||||
# → release published.
|
||||
|
||||
[changelog]
|
||||
# Header rendered once at top of every release body.
|
||||
header = """
|
||||
"""
|
||||
|
||||
# Body template — rendered per release (we use --latest so only one).
|
||||
# Tera templating syntax. Each commit shows as a bullet with PR link if present.
|
||||
body = """
|
||||
{% if version %}\
|
||||
## What's in {{ version }}
|
||||
|
||||
{% else %}\
|
||||
## Unreleased
|
||||
|
||||
{% endif %}\
|
||||
{% for group, commits in commits | group_by(attribute="group") %}
|
||||
### {{ group }}
|
||||
|
||||
{% for commit in commits %}\
|
||||
- {{ commit.message | split(pat="\\n") | first | trim }}{% if commit.github.pr_number %} ([#{{ commit.github.pr_number }}](https://github.com/noonghunna/club-3090/pull/{{ commit.github.pr_number }}) by @{{ commit.github.username }}){% else %} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/noonghunna/club-3090/commit/{{ commit.id }})){% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
---
|
||||
|
||||
## Pinning to this release
|
||||
|
||||
This is a snapshot of the rolling stack — not a versioned API. To pin to this exact state:
|
||||
|
||||
```bash
|
||||
git checkout {{ version }}
|
||||
```
|
||||
|
||||
When posting cross-rig benchmark numbers ([disc #86](https://github.com/noonghunna/club-3090/discussions/86)), please include this version tag (or commit SHA) so others can reproduce against the same script revision.
|
||||
|
||||
{% if previous.version %}\
|
||||
**Full diff:** [{{ previous.version }}...{{ version }}](https://github.com/noonghunna/club-3090/compare/{{ previous.version }}...{{ version }})
|
||||
{% endif %}\
|
||||
"""
|
||||
|
||||
footer = ""
|
||||
|
||||
# Strip leading/trailing whitespace from rendered body.
|
||||
trim = true
|
||||
|
||||
# Don't include a postprocessor — keep the markdown raw.
|
||||
postprocessors = []
|
||||
|
||||
[git]
|
||||
# Treat the message as conventional-commit format where possible, but don't
|
||||
# discard non-conforming messages (filter_unconventional = false).
|
||||
conventional_commits = false
|
||||
filter_unconventional = false
|
||||
|
||||
# Don't split commits on newline — keep multi-line bodies intact.
|
||||
split_commits = false
|
||||
|
||||
# Parse commits in order they appear in `git log`.
|
||||
topo_order = false
|
||||
|
||||
# Sort commits within each group by date descending.
|
||||
sort_commits = "newest"
|
||||
|
||||
# Skip merge commits — squash-merged PRs show as regular commits, --no-ff
|
||||
# merges create noise. (PR titles still come through via the squashed commit.)
|
||||
filter_commits = false
|
||||
|
||||
# Patterns for matching commit messages → groups. First match wins, so order
|
||||
# matters: PREFIX-BASED parsers run first (deterministic, low false-positive),
|
||||
# keyword-based parsers act as fallback for unprefixed messages (catches
|
||||
# `Document Cliff 1 closure` or `Add Gemma 4` style un-prefixed commits).
|
||||
#
|
||||
# Patterns match against the full commit message (subject + body), so we must
|
||||
# anchor with `^` to scope to subject when we want a prefix match.
|
||||
commit_parsers = [
|
||||
# --- Skip merge-commit headers ---
|
||||
# Squashed-merge PR commits already carry the proper title; --no-ff merges
|
||||
# add noise.
|
||||
{ message = "^Merge pull request", skip = true },
|
||||
{ message = "^Merge branch", skip = true },
|
||||
{ message = "^Merge remote-tracking", skip = true },
|
||||
|
||||
# --- Prefix-based (deterministic, anchored to subject line) ---
|
||||
# New models / new compose paths
|
||||
{ message = "^models[(:]", group = "🎯 New models + serving paths" },
|
||||
{ message = "^composes?[(:]", group = "🎯 New models + serving paths" },
|
||||
|
||||
# Benchmarks
|
||||
{ message = "^bench[(:]", group = "📊 Benchmarks + cross-rig data" },
|
||||
|
||||
# Scripts / tooling / CI
|
||||
{ message = "^scripts?[(:]", group = "🛠️ Scripts + tooling" },
|
||||
{ message = "^tools?[(:]", group = "🛠️ Scripts + tooling" },
|
||||
{ message = "^ci[(:]", group = "🛠️ Scripts + tooling" },
|
||||
{ message = "^build[(:]", group = "🛠️ Scripts + tooling" },
|
||||
|
||||
# Bug fixes
|
||||
{ message = "^fix[(:]", group = "🐛 Bug fixes" },
|
||||
{ message = "^bugfix[(:]", group = "🐛 Bug fixes" },
|
||||
{ message = "^hotfix[(:]", group = "🐛 Bug fixes" },
|
||||
|
||||
# Features
|
||||
{ message = "^feat[(:]", group = "✨ Features" },
|
||||
{ message = "^feature[(:]", group = "✨ Features" },
|
||||
|
||||
# Documentation
|
||||
{ message = "^docs?[(:]", group = "📝 Documentation" },
|
||||
{ message = "^README", group = "📝 Documentation" },
|
||||
|
||||
# Maintenance
|
||||
{ message = "^chore[(:]", group = "🧹 Maintenance" },
|
||||
{ message = "^refactor[(:]", group = "🧹 Maintenance" },
|
||||
{ message = "^style[(:]", group = "🧹 Maintenance" },
|
||||
{ message = "^test[(:]", group = "🧹 Maintenance" },
|
||||
|
||||
# --- Keyword-based fallback (only fires if no prefix matched) ---
|
||||
# Pin bumps — keyword-based since they sometimes use no prefix.
|
||||
{ message = "(?i)^.*\\b(pin bump|bump (genesis|vllm|sglang|llama|llamacpp)|genesis v[0-9])", group = "🔧 Pin bumps + upstream" },
|
||||
|
||||
# Un-prefixed model/compose additions (e.g. `Add Gemma 4 + DFlash compose`)
|
||||
{ message = "(?i)^(add|introduce)\\s+(gemma|qwen|llama|mixtral|deepseek|kimi|qwopus|new model|.*compose)", group = "🎯 New models + serving paths" },
|
||||
{ message = "(?i)^add[/]?vllm-", group = "🎯 New models + serving paths" },
|
||||
|
||||
# Documentation written without `docs:` prefix (e.g. `AGENTS.md: codify...`,
|
||||
# `Add docs/CLIFFS.md`, `Document Cliff 1 closure`).
|
||||
{ message = "^[A-Z][A-Z_]+\\.md[:\\s]", group = "📝 Documentation" },
|
||||
{ message = "^Add docs[/]", group = "📝 Documentation" },
|
||||
{ message = "^Document\\s+", group = "📝 Documentation" },
|
||||
{ message = "^README", group = "📝 Documentation" },
|
||||
|
||||
# Cliffs / regressions in subject line (anchored with ^.* to require it
|
||||
# appear in the subject, not just the body)
|
||||
{ message = "(?i)^.*\\b(cliff|regression|gotcha)\\b", group = "⚠️ Cliffs, gotchas, regressions" },
|
||||
|
||||
# Tool/script names used as prefix (e.g. `power-cap-sweep:`, `setup.sh:`,
|
||||
# `verify-full.sh:`) — these are de-facto scripts/tooling commits.
|
||||
{ message = "^[a-z][a-z0-9_-]*\\.(sh|py|toml|yml)[:\\s]", group = "🛠️ Scripts + tooling" },
|
||||
{ message = "^power-cap-sweep[:\\s]", group = "🛠️ Scripts + tooling" },
|
||||
{ message = "^verify-[a-z]+[:\\s]", group = "🛠️ Scripts + tooling" },
|
||||
{ message = "^launch\\.sh", group = "🛠️ Scripts + tooling" },
|
||||
{ message = "^preflight[:\\s]", group = "🛠️ Scripts + tooling" },
|
||||
{ message = "^Add scripts[/]", group = "🛠️ Scripts + tooling" },
|
||||
{ message = "^Split verify-", group = "🛠️ Scripts + tooling" },
|
||||
|
||||
# Charts / changelog / results — documentation-adjacent
|
||||
{ message = "^charts?[:\\s]", group = "📝 Documentation" },
|
||||
{ message = "^changelog[:\\s]", group = "📝 Documentation" },
|
||||
{ message = "^results?[:\\s]", group = "📊 Benchmarks + cross-rig data" },
|
||||
{ message = "^docs\\s*[+]", group = "📝 Documentation" },
|
||||
|
||||
# Substantive verbs without prefix — common in this repo's history
|
||||
{ message = "(?i)^restructure", group = "🧹 Maintenance" },
|
||||
{ message = "(?i)^audit\\b", group = "🧹 Maintenance" },
|
||||
{ message = "(?i)^remove\\b", group = "🧹 Maintenance" },
|
||||
{ message = "(?i)^drop\\b", group = "🧹 Maintenance" },
|
||||
{ message = "(?i)^migrate", group = "🧹 Maintenance" },
|
||||
{ message = "(?i)^reconcile", group = "🧹 Maintenance" },
|
||||
{ message = "(?i)^(ship|push|land)\\s+", group = "✨ Features" },
|
||||
{ message = "(?i)^verify\\s+", group = "✨ Features" },
|
||||
{ message = "(?i)\\bclosure\\b", group = "✨ Features" },
|
||||
{ message = "(?i)\\bship verified\\b", group = "✨ Features" },
|
||||
|
||||
# --- Catch-all ---
|
||||
# Everything that didn't match — keeps unconventional messages visible
|
||||
# rather than silently dropped.
|
||||
{ message = ".*", group = "🧹 Other" },
|
||||
]
|
||||
|
||||
# Tag pattern for CalVer (year.month.day)
|
||||
tag_pattern = "v[0-9]+\\.[0-9]+\\.[0-9]+"
|
||||
|
||||
# When generating, ignore tags that don't match the pattern.
|
||||
ignore_tags = ""
|
||||
|
||||
# GitHub remote — used by --github-repo or for PR/contributor enrichment.
|
||||
[remote.github]
|
||||
owner = "noonghunna"
|
||||
repo = "club-3090"
|
||||
Reference in New Issue
Block a user