---
name: kodadocs-changelog-gen
description: "Analyze git history and PRs to generate user-facing release notes and changelog pages for your doc site."
---

# Changelog Generator

## Overview

This skill generates user-facing release notes by analyzing git commit history, pull request titles, and conventional commit messages. It groups changes into categories (features, fixes, improvements, breaking changes), produces clean markdown output, and optionally integrates the changelog as a page in your KodaDocs site.

Use this skill when:
- You are preparing a new release and need user-facing release notes
- You want to add a "What's New" or "Changelog" page to your documentation site
- You need to summarize what changed between two versions or over a time period
- You want to convert developer-facing commit messages into user-friendly descriptions

## Prerequisites

Before running this skill, ensure the following:

1. **Git repository:** The project must be a git repository with commit history.
2. **GitHub CLI (optional):** If you want to include PR titles, `gh` must be authenticated (`gh auth status`).
3. **KodaDocs manifest (optional):** If integrating with an existing doc site, a `.kodadocs/run_manifest.json` should exist from a previous pipeline run.
4. **Version tag or date range:** The user should specify either:
   - A version tag to generate notes for (e.g., `v2.1.0`)
   - A date range (e.g., "last 2 weeks", "since 2026-02-15")
   - "Since last tag" to auto-detect the range

## Workflow

### Step 1: Determine the Commit Range

Identify the range of commits to include in the changelog.

**If the user provides a version tag:**
```bash
# Find the previous tag
git describe --tags --abbrev=0 <tag>^
# Use that as the starting point
git log <previous-tag>..<tag> --oneline --no-merges
```

**If the user provides a date:**
```bash
git log --since="<date>" --oneline --no-merges
```

**If the user says "since last tag":**
```bash
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null)
if [ -n "$LAST_TAG" ]; then
  git log ${LAST_TAG}..HEAD --oneline --no-merges
else
  git log --since="30 days ago" --oneline --no-merges
fi
```

**If no tags exist at all:**
Fall back to the last 30 days of commits:
```bash
git log --since="30 days ago" --oneline --no-merges
```

Store the full commit list with hashes, messages, authors, and dates:
```bash
git log <range> --format="%H|%s|%an|%ai" --no-merges
```

### Step 2: Parse Conventional Commits

Scan each commit message and classify it according to conventional commit prefixes.

**Category mapping:**

| Prefix | Category | User-Facing Label |
|--------|----------|-------------------|
| `feat:` or `feat(...):`  | Features | New Features |
| `fix:` or `fix(...):`    | Fixes | Bug Fixes |
| `perf:` or `perf(...):`  | Performance | Performance Improvements |
| `docs:` or `docs(...):`  | Documentation | Documentation |
| `refactor:` or `refactor(...):` | Improvements | Improvements |
| `chore:` or `chore(...):`      | Chores | Maintenance |
| `test:` or `test(...):`        | Tests | (omit from user-facing notes) |
| `ci:` or `ci(...):`            | CI | (omit from user-facing notes) |
| `build:` or `build(...):`      | Build | (omit from user-facing notes) |
| `style:` or `style(...):`      | Style | (omit from user-facing notes) |

**Breaking change detection:**
- Check for `BREAKING CHANGE:` or `BREAKING-CHANGE:` in the commit body
- Check for `!` after the prefix (e.g., `feat!:`)
- Flag these commits as "Breaking Changes" regardless of their prefix

**For non-conventional commits** (no recognized prefix):
- Classify as "Other Changes"
- Attempt to infer category from keywords: "add" -> Features, "fix" -> Fixes, "update" -> Improvements, "remove" -> Improvements

### Step 3: Enrich with Pull Request Data

If `gh` is available and authenticated, fetch merged PRs for the same time range:

```bash
gh pr list --state merged --limit 100 --json number,title,labels,mergedAt,author
```

**Matching logic:**
1. For each commit, check if its message contains a PR reference (e.g., `(#123)`)
2. If found, use the PR title instead of the commit message (PR titles are typically more user-friendly)
3. If a PR has labels, use those for categorization:
   - `bug` label -> Fixes
   - `enhancement` or `feature` label -> Features
   - `breaking-change` label -> Breaking Changes
   - `documentation` label -> Documentation

**Deduplication:**
- If multiple commits reference the same PR, include the PR only once
- Prefer PR title over individual commit messages when available
- If a squash merge commit matches a PR, use the PR title

### Step 4: Rewrite Commit Messages for Users

Transform developer-facing commit messages into user-friendly descriptions.

**Transformation rules:**
1. Remove the conventional commit prefix (`feat:`, `fix:`, etc.)
2. Remove scope annotations (`feat(auth):` -> just the description)
3. Capitalize the first letter
4. Remove PR references like `(#123)` from the end
5. Convert technical jargon into plain language where possible:
   - "refactor auth middleware" -> "Improved authentication handling"
   - "fix race condition in queue" -> "Fixed an issue where tasks could run out of order"
6. Keep descriptions to one sentence, under 100 characters
7. If the original message is already user-friendly, keep it as-is

**Do NOT rewrite if:**
- The commit message is already clear and non-technical
- The PR title was used (PR titles are usually already user-friendly)

### Step 5: Group and Sort Changes

Organize the processed entries into the final structure.

**Ordering of categories (top to bottom):**
1. Breaking Changes (if any)
2. New Features
3. Bug Fixes
4. Performance Improvements
5. Improvements
6. Documentation
7. Maintenance
8. Other Changes

**Within each category:**
- Sort by date (newest first)
- If entries relate to the same area (e.g., "auth", "dashboard"), group them together

**Omit empty categories.** Do not include a section header if there are no entries.

**Omit internal-only categories** (Tests, CI, Build, Style) from the user-facing output unless the user explicitly requests them.

### Step 6: Generate Markdown Output

Produce the changelog in clean markdown format.

**Template:**

```markdown
# Changelog

## [<version>] - <YYYY-MM-DD>

### Breaking Changes

- **<area>:** <description> ([#<pr>](<pr-url>))

### New Features

- **<area>:** <description> ([#<pr>](<pr-url>))
- <description>

### Bug Fixes

- <description> ([#<pr>](<pr-url>))
- <description>

### Improvements

- <description>

### Documentation

- <description>
```

**Formatting rules:**
- Use `**bold**` for area/scope labels when present
- Link PR numbers to GitHub when the repo URL is known
- If no version tag is provided, use "Unreleased" as the version header
- Include the date in ISO format (YYYY-MM-DD)
- Each entry is a single bullet point, one line

**If generating for multiple versions:**
Stack them in reverse chronological order (newest version at the top).

### Step 7: Save the Changelog

Save the generated output to the project:

```bash
# Save as standalone changelog
# File: CHANGELOG.md (project root) or docs/CHANGELOG.md
```

Write the file to the location the user specifies. If no location is specified, default to `CHANGELOG.md` in the project root.

If an existing `CHANGELOG.md` exists, prepend the new version at the top (below the `# Changelog` heading) rather than overwriting.

### Step 8: Integrate with Doc Site (Optional)

If the user wants to add the changelog to their KodaDocs documentation site:

1. **Load the existing manifest:**
   Use `mcp__kodadocs__load_manifest` to retrieve the current pipeline state, including the output directory, project name, and existing articles.

2. **Create a changelog article:**
   Structure the changelog as an article object compatible with `mcp__kodadocs__assemble_vitepress`:
   ```json
   {
     "title": "Changelog",
     "slug": "changelog",
     "content": "<the generated markdown content>",
     "order": 999
   }
   ```

3. **Reassemble the site:**
   Call `mcp__kodadocs__assemble_vitepress` with the existing articles plus the new changelog article. Use the same configuration (brand_color, project_name, etc.) from the manifest.

4. **Optionally redeploy:**
   If the user requests it, call `mcp__kodadocs__deploy_site` to push the updated site live.

5. **Save updated manifest:**
   Use `mcp__kodadocs__save_manifest` to record the changelog generation in the pipeline state.

## Decision Trees

### Tag-Based vs. Date-Based Changelog

```
Does the user specify a version tag?
  YES -> Use tag-based range (previous tag to specified tag)
  NO  -> Does the user specify a date?
           YES -> Use --since date filtering
           NO  -> Does the repo have tags?
                    YES -> Use "since last tag"
                    NO  -> Use "last 30 days"
```

### PR Enrichment Availability

```
Is `gh` CLI authenticated? (gh auth status)
  YES -> Fetch merged PRs and use PR titles for enrichment
  NO  -> Rely solely on git commit messages
         Inform the user: "PR titles not available. Run `gh auth login` for richer changelogs."
```

### Existing Changelog Handling

```
Does CHANGELOG.md already exist?
  YES -> Parse the existing file to find the insertion point
         Prepend new version block after the `# Changelog` heading
         Preserve all existing content below
  NO  -> Create a new CHANGELOG.md with the standard header
```

### Doc Site Integration

```
Does .kodadocs/run_manifest.json exist?
  YES -> Offer to add changelog as a page to the existing doc site
  NO  -> Skip doc site integration; only generate the standalone file
```

## Output Format

The skill produces one or more of the following:

1. **CHANGELOG.md** - A standalone markdown file with the generated release notes
2. **Doc site changelog page** (optional) - A "What's New" page integrated into the KodaDocs VitePress site
3. **Console summary** - A brief summary printed to the terminal:

```
Changelog generated for v2.1.0 (2026-03-01)
  - 4 new features
  - 7 bug fixes
  - 2 improvements
  - 1 breaking change
  Commits analyzed: 34
  PRs referenced: 12
  Saved to: CHANGELOG.md
```

## Error Handling

### No commits found in range
If `git log` returns no commits for the specified range:
- Inform the user: "No commits found in the specified range."
- Suggest widening the date range or checking the tag name
- Do not generate an empty changelog

### Git repository not found
If the working directory is not a git repository:
- Stop immediately
- Inform the user: "This directory is not a git repository. Navigate to a project with git history."

### GitHub CLI not authenticated
If `gh auth status` fails:
- Continue without PR enrichment
- Log a warning: "GitHub CLI not authenticated. Changelog will use commit messages only."

### Ambiguous version tags
If multiple tags match the user's input:
- List the matching tags and ask the user to specify which one
- Do not guess

### Merge conflicts with existing CHANGELOG.md
If the existing file has an unexpected format:
- Append the new version at the very top of the file (after any title line)
- Warn the user: "Existing changelog format was unexpected. New entries were prepended."

### No conventional commits detected
If none of the commits follow conventional commit format:
- Fall back to keyword-based classification
- If that also fails, place everything under "Changes" as a single category
- Suggest to the user: "Consider adopting conventional commits for better changelog generation."

## Success Criteria

The run is successful when:
- [ ] Commit range was correctly determined from user input
- [ ] All commits in the range were parsed and classified
- [ ] PR enrichment was attempted if `gh` is available
- [ ] Entries were rewritten into user-friendly language
- [ ] Changes are grouped by category in the correct order
- [ ] Markdown output follows the template format
- [ ] File was saved to the correct location
- [ ] If existing CHANGELOG.md: new content was prepended, old content preserved
- [ ] If doc site integration: changelog page was added and site reassembled
- [ ] Console summary was printed with statistics
