Git cliff
AI skills
npx -y skills add kpatryk/skills --skill git-cliffAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Expert guide for git-cliff, a highly customizable changelog generator from git history. Invoke this skill whenever the user wants to generate, update, or customize a CHANGELOG.md file; auto-bump semantic versions; configure cliff.toml; write Tera templates for changelogs; work with conventional commits for changelog purposes; set up git-cliff in CI/CD pipelines; filter commits by path for monorepos; integrate GitHub/GitLab remote metadata into changelogs; debug changelog output; or run any git-cliff CLI command. Use even when the user just says "generate changelog", "update my CHANGELOG", "prep release notes", or "bump my version" — any changelog or release-notes workflow likely benefits from this skill.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
13.6 KB, as published. Nobody here has run it
git-cliff
git-cliff generates beautiful, customizable changelogs from git history using conventional commits and regex-powered custom parsers. The changelog body is a Tera template (similar to Jinja2/Django templates).
Core Concepts
- Config file:
cliff.tomlin the project root (or~/.config/git-cliff/cliff.tomlglobally) - Template engine: Tera —
{{ variable }},{% for %},{% if %}, filters like| upper_first - Conventional commits:
<type>[scope]: <description>— types includefeat,fix,docs,refactor,perf,test,chore,style,build,ci - Breaking changes:
feat!:/fix!:suffix orBREAKING CHANGE:footer → SemVer major bump feat:→ minor bump ·fix:→ patch bump
Quickstart
git-cliff --init # scaffold cliff.toml in the current directory
git-cliff -o CHANGELOG.md # generate full changelog from all history
CLI Reference
Boolean flags
| Flag | Description |
|---|---|
-u, --unreleased | Only commits not yet associated with a tag |
-l, --latest | Commits from the latest tag to HEAD |
--current | Commits belonging to the currently checked-out tag |
--topo-order | Sort tags topologically instead of chronologically |
--bumped-version | Print the auto-bumped version string only (no changelog) |
-x, --context | Print the template context as JSON (great for debugging) |
--no-exec | Disable external command execution in pre/postprocessors |
--use-branch-tags | Only include tags reachable from the current branch |
--offline | Disable network access (remote integrations) |
Options
| Option | Short | Description |
|---|---|---|
--config <PATH> | -c | Config file path (default: cliff.toml; env: GIT_CLIFF_CONFIG) |
--tag <TAG> | -t | Label unreleased commits as this version |
--output [<PATH>] | -o | Write to file (omit path → writes to CHANGELOG.md) |
--prepend <PATH> | -p | Prepend new entries to an existing changelog file |
--bump | Auto-bump version: auto (default), major, minor, or patch | |
--body <TEMPLATE> | -b | Override the body template inline |
--strip <PART> | -s | Strip header, footer, or all from output |
--sort <SORT> | Commit sort inside sections: oldest (default) or newest | |
--include-path <PATTERN> | Only include commits that touch these paths (glob) | |
--exclude-path <PATTERN> | Exclude commits touching these paths (glob) | |
--tag-pattern <PATTERN> | Regex for matching git tags | |
--skip-tags <PATTERN> | Regex — skip these tags entirely | |
--ignore-tags <PATTERN> | Ignore these tags (their commits roll into the next release) | |
--with-commit <MSG> | Inject a synthetic commit message | |
--skip-commit <SHA> | Skip a specific commit by SHA | |
--workdir <PATH> | -w | Set the working directory |
--repository <PATH> | -r | Set the git repository path |
[RANGE] | Git commit range, e.g. v1.0.0..HEAD |
Remote Integration
git-cliff --github-token $GITHUB_TOKEN --github-repo owner/repo
git-cliff --gitlab-token $GITLAB_TOKEN --gitlab-repo owner/repo
# Also: --gitea-*, --bitbucket-*, --azure-devops-*
cliff.toml Schema
[changelog]
header = "# Changelog\n\nAll notable changes to this project will be documented in this file.\n"
body = """
{% if version %}
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}
## [unreleased]
{% endif %}
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
{% if commit.breaking %}[**breaking**] {% endif %}\
{{ commit.message | upper_first }}
{% endfor %}
{% endfor %}
"""
footer = "<!-- generated by git-cliff -->"
trim = true # strip leading/trailing whitespace from rendered body
# render_always = false # render body even when there are no releases
# output = "CHANGELOG.md" # set default output path in config
postprocessors = [
# Regex replace across the final output string:
# { pattern = "\\(#([0-9]+)\\)", replace = "([#${1}](https://github.com/owner/repo/issues/${1}))" }
]
[git]
conventional_commits = true # parse <type>[scope]: <desc> format
filter_unconventional = true # exclude non-conventional commits
# require_conventional = false # fail if any included commit is non-conventional
# split_commits = false # treat each commit body line as its own commit
commit_preprocessors = [
# Mutate commit messages before parsing:
# { pattern = " +", replace = " " } # collapse multiple spaces
# { pattern = "Merge pull request #([0-9]+) from [^ ]+", replace = "PR #${1}:" }
]
commit_parsers = [
{ message = "^feat", group = "Features" },
{ message = "^fix", group = "Bug Fixes" },
{ message = "^doc", group = "Documentation" },
{ message = "^perf", group = "Performance" },
{ message = "^refactor", group = "Refactor" },
{ message = "^style", group = "Styling" },
{ message = "^test", group = "Testing" },
{ message = "^chore|^ci|^build", group = "Miscellaneous Tasks" },
{ message = "^revert", skip = true },
# { body = ".*security", group = "Security" },
# { footer = "^changelog: ?ignore", skip = true },
# { sha = "abc1234", skip = true },
]
protect_breaking_commits = false # never skip breaking changes, even if a parser would
filter_commits = false # if true, drop commits not matched by any parser
# fail_on_unmatched_commit = false
tag_pattern = "v[0-9].*" # regex for recognizing version tags
# skip_tags = "v0.1.0-beta.1"
# ignore_tags = "-rc[0-9]+$" # roll RC commits into the next full release
topo_order = false # topological tag ordering
sort_commits = "oldest" # oldest | newest
link_parsers = [
# Extract issue links from commit messages:
# { pattern = "#(\\d+)", href = "https://github.com/owner/repo/issues/$1" },
]
# limit_commits = 100
# include_paths = ["src/", "lib/**"]
# exclude_paths = ["vendor/"]
Tera Template Context
The body template receives one release object per tag. header and footer receive the full releases array.
Release fields
| Field | Type | Description |
|---|---|---|
version | string | Tag name, e.g. "v1.2.0" (null for unreleased) |
message | string | Annotated tag message |
timestamp | int | Unix timestamp of the release commit |
previous | object | { version } of the prior release |
commits | array | Commit objects (see below) |
commit_id | string | SHA of the release commit |
statistics | object | commit_count, commits_timespan, conventional_commit_count, links[], days_passed_since_last_release |
Commit fields
| Field | Type | Description |
|---|---|---|
id | string | Full commit SHA |
message | string | Commit description (after type/scope parsing) |
group | string | Set by commit_parsers |
scope | string | Conventional commit scope |
breaking | bool | true if breaking change |
breaking_description | string | Explanation from BREAKING CHANGE: footer |
body | string | Full commit body text |
footers | array | { token, separator, value, breaking } |
conventional | bool | Was parsed as conventional? |
merge_commit | bool | Is a merge commit? |
author | object | { name, email, timestamp } |
committer | object | { name, email, timestamp } |
links | array | { text, href } from link_parsers |
remote | object | { username, pr_title, pr_number, pr_labels, is_first_contributor } |
Useful Tera patterns
{# Group by group label and list scoped commits #}
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits | sort(attribute="message") %}
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}{{ commit.message | upper_first }}
{% endfor %}
{% endfor %}
{# Highlight breaking changes separately #}
{% set breaking = commits | filter(attribute="breaking", value=true) %}
{% if breaking | length > 0 %}
### ⚠️ Breaking Changes
{% for commit in breaking %}
- {{ commit.breaking_description | default(value=commit.message) | upper_first }}
{% endfor %}
{% endif %}
{# Comparison links in footer using previous version #}
{% for release in releases %}
{% if release.previous.version %}
[{{ release.version }}]: https://github.com/owner/repo/compare/{{ release.previous.version }}..{{ release.version }}
{% endif %}
{% endfor %}
{# Trim version prefix for display #}
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
Key Tera built-ins: upper_first, lower, upper, trim, trim_start_matches(pat=...), replace(from=..., to=...), indent(prefix=...), group_by(attribute=...), filter(attribute=..., value=...), sort(attribute=...), unique, length, date(format=...), default(value=...), join(sep=...)
Common Workflows
1. Initialize and generate full changelog
git-cliff --init # creates cliff.toml
git-cliff -o CHANGELOG.md # writes complete changelog
2. Preview unreleased changes
git-cliff --unreleased
git-cliff --unreleased --tag 2.0.0 # label what the next release will be
3. Prepend unreleased to existing changelog (typical release flow)
git-cliff --unreleased --tag v1.3.0 --prepend CHANGELOG.md
# Note: --prepend and -o with the same path are incompatible
4. Auto-bump + prepend (fully automated release)
VERSION=$(git-cliff --bumped-version)
git-cliff --unreleased --tag "$VERSION" --prepend CHANGELOG.md
git add CHANGELOG.md && git commit -m "chore(release): $VERSION"
git tag "$VERSION" && git push --follow-tags
5. Generate for a specific range
git-cliff v1.0.0..HEAD
git-cliff v1.0.0..v2.0.0
git-cliff HEAD~10.. # last 10 commits only
6. Latest release notes only
git-cliff --latest -o RELEASE_NOTES.md
git-cliff --latest --strip all # body only, no header/footer
7. Debug your template
git-cliff --context # print full JSON context
git-cliff --context | jq '.[0].commits[] | {group, scope, message}'
8. Monorepo per-package changelogs
git-cliff --include-path "packages/my-lib/**" \
--tag-pattern "my-lib-v[0-9].*" \
--tag my-lib-v1.0.0 \
-o packages/my-lib/CHANGELOG.md
9. GitHub integration (PR titles, authors, labels)
GITHUB_TOKEN=xxx git-cliff --github-repo owner/repo -o CHANGELOG.md
10. CI/CD (GitHub Actions example)
- name: Generate changelog
run: |
VERSION=$(git-cliff --bumped-version)
git-cliff --unreleased --tag "$VERSION" --prepend CHANGELOG.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Environment Variables
Config field overrides use the pattern GIT_CLIFF__<SECTION>__<FIELD> (double underscore):
export GIT_CLIFF__CHANGELOG__FOOTER="<!-- generated by git-cliff -->"
export GIT_CLIFF__GIT__IGNORE_TAGS="v[0-9]+\.[0-9]+\.[0-9]+-rc[0-9]+"
export GIT_CLIFF_CONFIG=./custom-cliff.toml
export GIT_CLIFF_OUTPUT=CHANGELOG.md
export GITHUB_TOKEN=ghp_xxx # for GitHub remote integration
Tips & Patterns
- Debug first: Run
git-cliff --context | jqto see exactly what data your template receives before writing the template. protect_breaking_commits = true: Ensures breaking changes always appear even if a skip rule would otherwise hide them.filter_commits = true: Drop any commit not matched bycommit_parsers— great for noise-free changelogs.- Opt-out per commit: Add
{ footer = "^changelog: ?ignore", skip = true }to let authors mark individual commits as "don't include". - Monorepo: Use
--include-pathper package with per-package tag patterns. Version tags can be prefixed:my-pkg-v1.0.0. - PR labels as groups: With GitHub remote integration, match
remote.pr_labelsincommit_parsersto group by PR label categories. split_commits = true: Treats each line of a commit body as a separate entry — useful when squash merges pack multiple changes into one commit message.ignore_tagsvsskip_tags:ignore_tagsrolls the commits into the next release;skip_tagsdrops them entirely.- Postprocessors: Run regex replacements on the final rendered changelog string — useful for linkifying issue numbers globally.
Reference
- Full docs: https://git-cliff.org/docs/
- Configuration: https://git-cliff.org/docs/configuration
- Templating: https://git-cliff.org/docs/category/templating
- Template examples: https://git-cliff.org/docs/templating/examples
- CLI args: https://git-cliff.org/docs/usage/args