changelog-from-commits
Use when cutting a release, writing a CHANGELOG entry, or summarizing what changed since a tag — when the repo's commits follow (or mostly follow) Conventional Commits and a mechanically-accurate section breakdown beats a model's reading of the diff.
Install
npx skills add https://github.com/Rtur2003/Claude-Code-Promts-Skills/tree/main/.claude/skills/changelog-from-commits
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install rtur2003-claude-code-promts-skills@llmmart
git clone https://github.com/Rtur2003/Claude-Code-Promts-Skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole rtur2003/claude-code-promts-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Changelog From Commits
Overview
Parses git log against the Conventional Commits spec and prints a categorized changelog: Features, Fixes, Performance, Refactoring, Docs, Tests, Build, CI, Chore, Other, plus a BREAKING CHANGES section pulled from ! markers and BREAKING CHANGE: footers. Deterministic — the categorization is a fact about the commit message, not a judgment call, so a script produces it more reliably than a model re-reading commit subjects.
Core principle: A changelog section is either correct (matches the commit's own declared type) or it silently drops/miscategorizes a change. Parsing is not the place for approximation.
Usage
bash ${CLAUDE_SKILL_DIR}/scripts/generate.sh [from-ref] [to-ref]
from-refdefaults to the most recent tag, or the repo's root commit if there are no tags.to-refdefaults toHEAD.- Requires a git repository; exits 2 if run outside one.
- Prints Markdown to stdout — pipe into a file, or paste directly into
CHANGELOG.md.
bash ${CLAUDE_SKILL_DIR}/scripts/generate.sh v1.2.0 HEAD > /tmp/new-entries.md
bash ${CLAUDE_SKILL_DIR}/scripts/generate.sh # since the last tag, to HEAD
What it gets right and what it doesn't
Reliable: type(scope)!: subject parsing, breaking-change detection (! or a BREAKING CHANGE: footer), section grouping, short-hash linking, merge commits excluded automatically.
Falls back to "Other": any commit whose subject doesn't start with a known Conventional Commits type (feat/fix/perf/refactor/docs/test/build/ci/chore) — including free-text subjects like "Initial plan" or "Add X". A repo that doesn't consistently use Conventional Commits will produce a large "Other" section; that's an honest signal about the commit history, not a script bug. See it happen: run this against this library's own pre-2.0 history and most entries land in Other because they predate the Conventional Commits convention adopted later.
Not attempted: rewriting or improving commit subjects, deduplicating near-identical entries, or inferring a type the author didn't declare.
After generating
The script produces raw categorized entries — it does not write prose framing, a version header, or a date. Add those yourself (or ask Claude to), and edit any "Other" entries that deserve reclassification before they ship in a release. Don't paste the raw output into CHANGELOG.md as the final section without a pass over "Other" and "BREAKING CHANGES" — those two sections are exactly where a human or model read adds real value on top of the deterministic parse.
Remember
Categorization is deterministic; the narrative framing around it (a version header, a "why" sentence, what to call out) is not. Do the first with the script, the second with judgment.
Files (claude-code-promts-skills)
-
scripts
-
generate.sh 2.7 KB
#!/usr/bin/env bash # Deterministic changelog generation from Conventional Commits. Requires git. # Usage: generate.sh [from-ref] [to-ref] # from-ref defaults to the most recent tag (or the repo root commit if no tags exist) # to-ref defaults to HEAD set -euo pipefail if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then echo "error: not a git repository" >&2 exit 2 fi FROM="${1:-}" TO="${2:-HEAD}" if [ -z "$FROM" ]; then FROM="$(git describe --tags --abbrev=0 2>/dev/null || git rev-list --max-parents=0 HEAD | tail -1)" fi RANGE="${FROM}..${TO}" # Tab-separated: hash, subject. Body is fetched separately per-commit for breaking-change detection. COMMITS="$(git log "$RANGE" --no-merges --pretty=format:'%H%x09%s' 2>/dev/null || true)" if [ -z "$COMMITS" ]; then echo "No commits in range $RANGE" >&2 exit 0 fi declare -A SECTIONS SECTIONS[feat]="### Features" SECTIONS[fix]="### Fixes" SECTIONS[perf]="### Performance" SECTIONS[refactor]="### Refactoring" SECTIONS[docs]="### Documentation" SECTIONS[test]="### Tests" SECTIONS[build]="### Build" SECTIONS[ci]="### CI" SECTIONS[chore]="### Chore" SECTIONS[other]="### Other" TMPDIR="$(mktemp -d)" trap 'rm -rf "$TMPDIR"' EXIT BREAKING_FILE="$TMPDIR/breaking.txt" : > "$BREAKING_FILE" while IFS=$'\t' read -r hash subject; do [ -z "$hash" ] && continue body="$(git log -1 --pretty=format:'%b' "$hash" 2>/dev/null || true)" # Conventional Commits: type(scope)!: subject — the ! marks a breaking change, # as does a "BREAKING CHANGE:" footer in the body. if printf '%s' "$subject" | grep -Eq '^[a-z]+(\([^)]+\))?!:' || printf '%s' "$body" | grep -q '^BREAKING CHANGE:'; then printf '- %s (%s)\n' "$subject" "${hash:0:7}" >> "$BREAKING_FILE" fi type="$(printf '%s' "$subject" | sed -nE 's/^([a-z]+)(\([^)]+\))?!?:.*/\1/p')" scope="$(printf '%s' "$subject" | sed -nE 's/^[a-z]+\(([^)]+)\)!?:.*/\1/p')" desc="$(printf '%s' "$subject" | sed -E 's/^[a-z]+(\([^)]+\))?!?:[[:space:]]*//')" if [ -z "$type" ] || [ -z "${SECTIONS[$type]:-}" ]; then type="other" desc="$subject" fi if [ -n "$scope" ]; then printf '%s\t- **%s:** %s (%s)\n' "$type" "$scope" "$desc" "${hash:0:7}" >> "$TMPDIR/entries.tsv" else printf '%s\t- %s (%s)\n' "$type" "$desc" "${hash:0:7}" >> "$TMPDIR/entries.tsv" fi done <<< "$COMMITS" if [ -s "$BREAKING_FILE" ]; then echo "## BREAKING CHANGES" echo cat "$BREAKING_FILE" echo fi for type in feat fix perf refactor docs test build ci chore other; do if [ -f "$TMPDIR/entries.tsv" ] && grep -q "^${type} " "$TMPDIR/entries.tsv"; then echo "${SECTIONS[$type]}" echo grep "^${type} " "$TMPDIR/entries.tsv" | cut -f2- echo fi done
-
-
SKILL.md 3.1 KB
--- name: changelog-from-commits description: Use when cutting a release, writing a CHANGELOG entry, or summarizing what changed since a tag — when the repo's commits follow (or mostly follow) Conventional Commits and a mechanically-accurate section breakdown beats a model's reading of the diff. --- # Changelog From Commits ## Overview Parses `git log` against the [Conventional Commits](https://www.conventionalcommits.org/) spec and prints a categorized changelog: Features, Fixes, Performance, Refactoring, Docs, Tests, Build, CI, Chore, Other, plus a BREAKING CHANGES section pulled from `!` markers and `BREAKING CHANGE:` footers. Deterministic — the categorization is a fact about the commit message, not a judgment call, so a script produces it more reliably than a model re-reading commit subjects. **Core principle:** A changelog section is either correct (matches the commit's own declared type) or it silently drops/miscategorizes a change. Parsing is not the place for approximation. ## Usage ```bash bash ${CLAUDE_SKILL_DIR}/scripts/generate.sh [from-ref] [to-ref] ``` - `from-ref` defaults to the most recent tag, or the repo's root commit if there are no tags. - `to-ref` defaults to `HEAD`. - Requires a git repository; exits 2 if run outside one. - Prints Markdown to stdout — pipe into a file, or paste directly into `CHANGELOG.md`. ```bash bash ${CLAUDE_SKILL_DIR}/scripts/generate.sh v1.2.0 HEAD > /tmp/new-entries.md bash ${CLAUDE_SKILL_DIR}/scripts/generate.sh # since the last tag, to HEAD ``` ## What it gets right and what it doesn't **Reliable:** `type(scope)!: subject` parsing, breaking-change detection (`!` or a `BREAKING CHANGE:` footer), section grouping, short-hash linking, merge commits excluded automatically. **Falls back to "Other":** any commit whose subject doesn't start with a known Conventional Commits type (`feat`/`fix`/`perf`/`refactor`/`docs`/`test`/`build`/`ci`/`chore`) — including free-text subjects like "Initial plan" or "Add X". A repo that doesn't consistently use Conventional Commits will produce a large "Other" section; that's an honest signal about the commit history, not a script bug. See it happen: run this against this library's own pre-2.0 history and most entries land in Other because they predate the Conventional Commits convention adopted later. **Not attempted:** rewriting or improving commit subjects, deduplicating near-identical entries, or inferring a type the author didn't declare. ## After generating The script produces raw categorized entries — it does not write prose framing, a version header, or a date. Add those yourself (or ask Claude to), and edit any "Other" entries that deserve reclassification before they ship in a release. Don't paste the raw output into `CHANGELOG.md` as the final section without a pass over "Other" and "BREAKING CHANGES" — those two sections are exactly where a human or model read adds real value on top of the deterministic parse. ## Remember > Categorization is deterministic; the narrative framing around it (a version header, a "why" sentence, what to call out) is not. Do the first with the script, the second with judgment.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.