# Changelog

> Generate or update a CHANGELOG.md from git history in Keep-a-Changelog format. Triggers on "write changelog", "update changelog", "generate changelog for vX.Y.Z", "what changed since <tag>", or when the user is preparing a release and mentions CHANGELOG.

- Skill: `duckcode-js/changelog` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add duckcode-js/changelog`
- Raw SKILL.md: https://api.skillmd.com/api/skills/duckcode-js/changelog/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: DuckCode-js (https://skillmd.com/u/duckcode-js)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/duckcode-js/changelog

---


# changelog

Turn raw git history into a clean CHANGELOG.md entry in the
[Keep a Changelog](https://keepachangelog.com) format. Handles both first-time
generation (whole history) and incremental updates (new release since last tag).

## When to use this

Any of the following:
- User says "write a changelog for vX.Y.Z" or "update the changelog"
- User asks "what changed since v1.2.0" in a context where they intend to release
- User is preparing a release (running `npm version`, `git tag`, etc.) and needs
  release notes
- User asks you to add a section to an existing CHANGELOG.md

## Procedure

### 1. Figure out the range

Ask git for the commits to summarize. Priority order:

1. If the user named a version range (`v1.2.0..HEAD`, `main..release/1.3`), use it.
2. Otherwise, use `git describe --tags --abbrev=0` to find the most recent tag,
   and summarize `<that-tag>..HEAD`.
3. If there are no tags yet, summarize the whole history (`git log`).

For all cases, use:

```sh
git log --no-merges --pretty=format:"%h %s%n%b%n---" <range>
```

`%b` gives the body; the `---` separator makes commit boundaries obvious after
multi-line bodies. Skip merge commits.

### 2. Categorize each commit

Sort commits into the standard Keep-a-Changelog buckets:

- **Added** — new features
- **Changed** — changes in existing behavior
- **Deprecated** — features marked for removal (usually flagged in the commit message)
- **Removed** — features taken out
- **Fixed** — bug fixes
- **Security** — vulnerability fixes

Heuristics for uncategorized commits:

- Commit subjects starting `add`, `introduce`, `new`, `feat:` → **Added**
- `fix`, `bugfix`, `patch`, `fix:` → **Fixed**
- `remove`, `delete`, `drop` → **Removed**
- `refactor`, `rework`, `rename`, `update` → **Changed**
- `security`, `CVE-`, `sanitize`, `escape` → **Security**
- Anything else → make a best guess based on the body; when in doubt, **Changed**

Skip trivial commits: whitespace-only, "wip", "typo", "fix typo in comment",
CI-only tweaks that don't affect users. If a commit's user-visible impact is
zero, drop it — the changelog is for users, not the repo's history.

### 3. Rewrite for humans

Each commit's subject line was written for other developers. Rewrite each entry
for the user of the software:

- Drop the conventional-commit prefix (`feat:`, `fix:`, etc.)
- Rewrite in imperative present tense from the reader's perspective
- Collapse related commits into one bullet (five commits fixing the same bug
  become one line)
- If a commit references an issue or PR (`#123`, `GH-456`), keep the reference
  at the end in parentheses

Bad: `feat: implement CachedResolver with hit-rate tracking (#456)`
Good: `- Added result caching to resolver — repeat lookups are now instant. (#456)`

### 4. Write the output

If `CHANGELOG.md` exists at the repo root, insert the new release above the
previous most-recent release, keeping formatting consistent. If not, generate a
full file with a heading, an intro (link to Keep a Changelog), and the new
release as the first entry.

Standard entry format:

```markdown
## [1.3.0] — 2026-07-23

### Added
- Result caching in the resolver — repeat lookups are instant. (#456)
- `--json` flag on the CLI for machine-readable output.

### Fixed
- Crash when the config file was missing a trailing newline. (#472)

### Security
- Upgraded `some-lib` from 1.4.2 to 1.4.5, closing CVE-2026-1234.
```

Use today's date. If the release version wasn't given, either take it from
`package.json`/`pyproject.toml`/git tag, or ask.

### 5. Sanity check

Before showing the user, verify:

- Every commit in the range was either categorized or intentionally dropped
- No commit hashes leak into the output (they belong in git, not the changelog)
- No internal jargon or codenames the reader wouldn't know
- Version and date are correct

Show the result. Offer to write it to `CHANGELOG.md` directly.

## Anti-patterns to avoid

- Do not just dump `git log --oneline` into the file. That's raw history, not a
  changelog. The reader is a user of the software, not a maintainer of the repo.
- Do not include every commit. Trivial ones (chore, ci, test-only) get dropped.
- Do not fabricate context. If a commit's purpose isn't clear from message +
  body + diff, ask rather than guess.
- Do not mix release entries into one bucket. Separate `Added`/`Fixed`/etc.
  even if there's only one entry per section.

## Examples

See `examples/` for input git logs and the corresponding changelog output this
skill produces.

