---
name: keepachangelog-conventions
source: https://app.decimal.ai/s/keepachangelog-conventions@2/SKILL.md
source_sha256: dede7c70b332
---

# Keep a Changelog conventions

## Contract
When asked to turn commits into a changelog or release notes, render them in the **Keep a Changelog** format with a **Conventional-Commit-derived semver bump** — not GitHub "What's Changed" style, not free-form "Features / Bug Fixes / Improvements" headings, not emoji. Apply this whenever the task is "draft a changelog", "write release notes", or "summarize these commits for a release".

## Rules

### R1 — Section names: exactly these six, in this order
Use ONLY these section headings, and always in this order:

1. `### Added`
2. `### Changed`
3. `### Deprecated`
4. `### Removed`
5. `### Fixed`
6. `### Security`

- Render each as a level-3 heading: `### Added`.
- **OMIT** any section that has zero entries — never print an empty `### Fixed`.
- **Never invent** other section names. No "Features", "Bug Fixes", "Enhancements", "Improvements", "Performance", "What's Changed", "New", "Misc", "Other".
- The relative order is fixed even when some are omitted: Added always precedes Changed precedes Fixed, etc.

### R2 — Conventional Commit type → section mapping
Map each commit to a section by its type:

| Commit | Section |
|---|---|
| `feat:` | **Added** |
| `fix:` (non-security) | **Fixed** |
| `perf:` | **Changed** |
| `refactor:` | **Changed** |
| a deprecation (commit that deprecates something) | **Deprecated** |
| a removal (commit that removes a feature/flag/API) | **Removed** |
| `fix(security)` or any security-related fix | **Security** |
| `docs:`, `style:`, `test:`, `build:`, `ci:`, `chore:`, `revert:` | **OMIT** (internal-only, never in the changelog body) |

- Security routing wins over the plain `fix:`→Fixed rule: a security fix goes to **Security**, not Fixed.
- A commit that *removes* something goes to **Removed** even if it is typed `feat!:` — the user-facing effect (removal) picks the section.
- A commit that *deprecates* something goes to **Deprecated** even if typed `refactor:` or `feat:`.

### R3 — Semver bump (computed from the previous released version)
Pick exactly one bump level, highest-precedence first:

1. **MAJOR** — if any commit has a `!` after its type (`feat!:`, `fix!:`, …) OR a `BREAKING CHANGE:` footer. New version = `(X+1).0.0`.
2. **MINOR** — else if any `feat:` is present. New version = `X.(Y+1).0`.
3. **PATCH** — else (only `fix`/`perf`/`refactor`/etc.). New version = `X.Y.(Z+1)`.

- A breaking change forces MAJOR even on a `0.x` version: `0.9.4` + breaking → `1.0.0`.
- Reset the lower fields to 0 on the bump: MINOR zeroes the patch (`3.1.4`→`3.2.0`), MAJOR zeroes minor and patch (`4.2.0`→`5.0.0`).
- Commits that are OMITTED from the body (chore/docs/etc.) **still count for the bump**: a release of only `chore:`+`build:` is a PATCH bump.

### R4 — Version heading
`## [X.Y.Z] - YYYY-MM-DD`

- Version in **square brackets**, then **space-hyphen-space** (` - `), then an **ISO date** `YYYY-MM-DD`.
- Keep a `## [Unreleased]` section at the very top for merged-but-unreleased work.
- List versions **newest-first** (reverse chronological): the newer version's block appears ABOVE the older one's.

### R5 — Entries
- One bullet (`- `) per change.
- **Strip the Conventional-Commit prefix** from the text: `feat(auth): add SSO login` renders as `add SSO login`, not `feat(auth): add SSO login`.
- If the commit has a **scope**, prefix the bullet with the bold scope: `- **auth:** add SSO login`.
- Document each **breaking change explicitly** with a `**BREAKING:**` lead inside its section: `- **BREAKING:** rename userId to user_id`.

## Worked examples (BEFORE = base default, AFTER = conforming)

### E1 — Section names (R1)
Commits: `feat(auth): add SSO login`, `fix(api): handle null user id`
- BEFORE: `## Features\n- add SSO login\n## Bug Fixes\n- handle null user id`
- AFTER:
  ```
  ### Added
  - **auth:** add SSO login

  ### Fixed
  - **api:** handle null user id
  ```

### E2 — Type mapping for perf/refactor (R2)
Commits: `perf(db): cache hot queries`, `refactor(core): split scheduler module`
- BEFORE: `## Performance\n- cache hot queries\n## Refactoring\n- split scheduler module`
- AFTER:
  ```
  ### Changed
  - **db:** cache hot queries
  - **core:** split scheduler module
  ```

### E3 — Security routing (R2)
Commit: `fix(security): patch XSS in comment rendering` alongside `fix(api): correct pagination cursor`
- BEFORE: both under `### Fixed` (security collapsed into the generic fix bucket).
- AFTER:
  ```
  ### Fixed
  - **api:** correct pagination cursor

  ### Security
  - patch XSS in comment rendering
  ```

### E4 — Omitting internal commits (R2)
Commits: `feat(api): add /v2/search`, `docs(readme): fix typos`, `test(api): add coverage`, `chore(deps): update lockfile`
- BEFORE: a "Chores" / "Documentation" / "Tests" section listing the chore/docs/test commits.
- AFTER: only `### Added` with `- **api:** add /v2/search`. The docs/test/chore lines do not appear anywhere in the body.

### E5 — MINOR bump (R3)
Previous `1.4.2`; commits include a `feat:` and a `fix:`, no breaking change.
- BEFORE: `## v1.4.3` (treats every release as a patch) or `## 2.0.0` (over-bumps).
- AFTER: `## [1.5.0] - 2026-06-26` (feat present → minor; patch resets to 0).

### E6 — MAJOR bump on a breaking change (R3)
Previous `0.9.4`; commit `feat(export)!: change CSV column order` with `BREAKING CHANGE:` footer.
- BEFORE: `## 0.10.0` (treats `!` as just another feat).
- AFTER: `## [1.0.0] - 2026-06-26` (the `!`/BREAKING footer forces a major bump, even from 0.x).

### E7 — Version heading format (R4)
- BEFORE: `## v1.0.0 (2026-06-26)` or `# Release 1.0.0`.
- AFTER: `## [1.0.0] - 2026-06-26` — square brackets, ` - `, ISO date.

### E8 — Unreleased + reverse-chronological order (R4)
A new `feat` on top of an already-shipped version.
- BEFORE: oldest version first, no Unreleased placeholder.
- AFTER:
  ```
  ## [Unreleased]

  ## [3.1.0] - 2026-06-26
  ### Added
  - **core:** add plugin API

  ## [3.0.0] - 2026-06-10
  ### Added
  - **core:** initial rewrite
  ```

### E9 — Entry text + scope prefix (R5)
Commit: `feat(billing): add proration support`
- BEFORE: `- feat(billing): add proration support` (prefix left in).
- AFTER: `- **billing:** add proration support` (type stripped, scope bolded).

### E10 — Explicit breaking-change flag (R5)
Commit: `feat(api)!: rename 'userId' to 'user_id'` with `BREAKING CHANGE:` footer.
- BEFORE: `- rename userId to user_id` (no breaking indicator).
- AFTER: `- **BREAKING:** **api:** rename userId to user_id` under the relevant section, and a MAJOR version bump.

## Edge cases & exceptions
- **Removal vs. feat!:** `feat(cli)!: remove the --legacy flag` is a removal — put it under **Removed** (and bump MAJOR for the `!`). The effect, not the type token, picks the section.
- **Deprecation typed as refactor/feat:** `refactor(auth): deprecate password-only login` goes under **Deprecated**, not Changed.
- **Release with only omitted types:** `chore:`+`build:`+`style:` only → no body sections at all, but still a **PATCH** version bump with a version heading.
- **`revert:` commits** are omitted from the body (internal), even though they undo a prior feature.
- **Multiple security fixes** all collapse into a single `### Security` section, one bullet each.
- **Both feat and breaking present:** the breaking change still forces MAJOR; the non-breaking feats just list under Added.
- **Pre-1.0 (0.x) versions** follow the same rules — a breaking change still jumps to `1.0.0`.

## Do / Don't
- DO use `### Added / Changed / Deprecated / Removed / Fixed / Security`. DON'T use `Features`, `Bug Fixes`, `Improvements`, `Performance`, or `What's Changed`.
- DO route security fixes to **Security**. DON'T leave them under Fixed.
- DO omit `docs/test/chore/build/ci/style/revert` from the body. DON'T add a "Chores" or "Docs" section.
- DO compute the bump highest-precedence-first (breaking > feat > fix). DON'T default every release to a patch.
- DO write `## [X.Y.Z] - YYYY-MM-DD`. DON'T write `## vX.Y.Z` or `## X.Y.Z (date)`.
- DO list newest version first. DON'T print oldest-first.
- DO strip the `feat:`/`fix:` prefix and bold the scope. DON'T echo the raw commit line.

## Common mistakes (base's wrong defaults)
- Emitting GitHub "What's Changed" / "Features" / "Bug Fixes" headings instead of the six Keep a Changelog sections.
- Filing a `perf:` or `refactor:` under its own "Performance"/"Refactoring" heading instead of **Changed**.
- Listing `chore:`/`docs:`/`test:` commits in the body instead of omitting them.
- Treating `!`/`BREAKING CHANGE:` as a normal feat → under-bumping to minor instead of major.
- Always patch-bumping (or always minor-bumping) regardless of the commit types present.
- Writing the heading as `## v1.5.0` or `## 1.5.0 - June 26` instead of `## [1.5.0] - 2026-06-26`.
- Forgetting the `## [Unreleased]` placeholder and/or ordering versions oldest-first.
- Leaving the `feat(scope):` prefix in the bullet text instead of stripping it and bolding the scope.

## Quick checklist
- [ ] Sections limited to the six names, in order, empties omitted.
- [ ] feat→Added, fix→Fixed, perf/refactor→Changed, security→Security, deprecate→Deprecated, remove→Removed.
- [ ] docs/test/chore/build/ci/style/revert omitted from the body.
- [ ] Bump = breaking? major : feat? minor : patch — lower fields reset to 0.
- [ ] Heading `## [X.Y.Z] - YYYY-MM-DD`, `## [Unreleased]` on top, newest-first.
- [ ] Bullets strip the type prefix, bold the scope, flag breaking with `**BREAKING:**`.