# Story 10.12: Automated Changelog Generation

**Status:** Complete
**Priority:** Medium
**Depends on:** Story 10.10 (Changelog Release Traceability)

---

## Context / Background

Story 10.10 addresses the immediate issue of missing changelog entries for v0.3.4 and v0.3.5 by manually reconstructing them from git history. This story automates that process to prevent future occurrences.

**Current state:**

- Changelog is manually maintained in `CHANGELOG.md`
- Release workflow in `.github/workflows/release.yml` extracts changelog sections but doesn't generate them
- No enforcement that commits follow conventional format
- Easy to forget changelog updates before tagging

**Goal:**

Implement tooling that generates changelog entries from git history, reducing manual effort and preventing missing entries.

---

## Scope (In / Out)

### In Scope

- Add conventional commit linting to enforce commit message format
- Integrate changelog generation tool (e.g., `git-cliff`, `conventional-changelog`)
- Add pre-release check that changelog has entry for upcoming version
- Document the new workflow

### Out of Scope

- Retroactive reformatting of historical commits
- Fully automated releases (human review of changelog still required)
- Changing Keep-a-Changelog format (tool must output compatible format)

---

## Acceptance Criteria

### AC1: Conventional Commit Linting Enabled

**Description:** Commits that don't follow conventional format are rejected by pre-commit/CI.

**Validation:**

```bash
# This should fail
echo "fixed a bug" | commitlint

# This should pass
echo "fix(core): resolve queue overflow" | commitlint
```

### AC2: Changelog Generation Tool Installed

**Description:** A tool can generate changelog entries from commits between tags.

**Validation:**

```bash
# Generate changelog for unreleased changes
git-cliff --unreleased

# Generate full changelog
git-cliff -o CHANGELOG.md
```

### AC3: Pre-Release Validation

**Description:** Release workflow fails if changelog section for tagged version is missing or empty.

**Validation:**

```yaml
# In .github/workflows/release.yml
- name: Validate changelog
  run: |
    VERSION=${GITHUB_REF#refs/tags/v}
    grep -E "^\[${VERSION}\]" CHANGELOG.md || exit 1
```

### AC4: Generated Changelog Matches Format

**Description:** Generated entries follow Keep-a-Changelog format with proper categories.

**Validation:**

```markdown
## [0.4.0] - 2026-01-20

### Added
- feat(core): new feature description

### Fixed
- fix(sinks): bug fix description

### Changed
- refactor(plugins): change description
```

---

## Implementation Notes

### File Changes

```text
.pre-commit-config.yaml (MODIFIED - add commitlint)
.commitlintrc.yaml (NEW - commitlint config)
cliff.toml (NEW - git-cliff config)
.github/workflows/release.yml (MODIFIED - add validation)
pyproject.toml (MODIFIED - add dev dependency)
docs/contributing/changelog.md (NEW - document workflow)
```

### Tool Selection

**Recommended: `git-cliff`**

- Rust-based, fast, single binary
- Highly configurable output format
- Can match Keep-a-Changelog format
- Supports conventional commits mapping

**Alternative: `conventional-changelog`**

- Node.js based
- More ecosystem support
- Requires Node in CI

### Commit Type Mapping

| Commit Type | Changelog Category |
|-------------|-------------------|
| feat | Added |
| fix | Fixed |
| docs | Documentation |
| refactor | Changed |
| perf | Changed |
| test | (excluded) |
| chore | (excluded) |
| ci | (excluded) |

### git-cliff Configuration

```toml
# cliff.toml
[changelog]
header = """
# Changelog

All notable changes to this project will be documented in this file.
"""
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 %}
        - {{ commit.message | upper_first }}\
    {% endfor %}
{% endfor %}
"""
trim = true

[git]
conventional_commits = true
filter_unconventional = true
commit_parsers = [
    { message = "^feat", group = "Added" },
    { message = "^fix", group = "Fixed" },
    { message = "^doc", group = "Documentation" },
    { message = "^perf", group = "Changed" },
    { message = "^refactor", group = "Changed" },
    { message = "^style", group = "Changed" },
    { message = "^test", skip = true },
    { message = "^chore", skip = true },
    { message = "^ci", skip = true },
]
```

---

## Tasks

### Phase 1: Commit Linting

- [ ] Add `commitlint` to dev dependencies
- [ ] Create `.commitlintrc.yaml` with conventional config
- [ ] Add commit-msg hook to `.pre-commit-config.yaml`
- [ ] Test locally with valid/invalid commits

### Phase 2: Changelog Generation

- [ ] Choose tool (git-cliff recommended)
- [ ] Create `cliff.toml` configuration
- [ ] Test generation against recent commits
- [ ] Verify output matches Keep-a-Changelog format

### Phase 3: CI Integration

- [ ] Add changelog validation to release workflow
- [ ] Add optional changelog preview to PR workflow
- [ ] Test with a dry-run release

### Phase 4: Documentation

- [ ] Document commit message format in CONTRIBUTING.md
- [ ] Create `docs/contributing/changelog.md` guide
- [ ] Update CHANGELOG with this change

---

## Tests

### Manual Verification

```bash
# Test commit linting
echo "bad commit" | npx commitlint
echo "feat(core): good commit" | npx commitlint

# Test changelog generation
git-cliff --unreleased --output /dev/stdout

# Test full generation
git-cliff -o CHANGELOG.new.md
diff CHANGELOG.md CHANGELOG.new.md
```

### CI Verification

- PR with non-conventional commit should fail
- Release without changelog entry should fail
- Generated changelog should pass markdownlint

---

## Definition of Done

### Code Complete

- [x] Commit linting configured and working
- [x] Changelog generation configured and working
- [x] Release validation in place

### Quality Assurance

- [x] Local hooks work correctly
- [x] CI integration tested
- [x] Generated output passes markdownlint

### Documentation

- [x] Commit format documented
- [x] Changelog workflow documented
- [x] CHANGELOG updated

---

## Risks / Rollback

### Risks

1. **Risk:** Existing contributors reject conventional commits
   - **Mitigation:** Document clearly; provide examples; squash-merge PRs

2. **Risk:** Generated changelog needs manual editing
   - **Mitigation:** This is expected; generation is a starting point, not final

3. **Risk:** Tool maintenance burden
   - **Mitigation:** git-cliff is stable; commitlint is widely used

### Rollback Plan

If issues occur:

1. Remove commit-msg hook (keep other hooks)
2. Revert to manual changelog maintenance
3. Keep validation step as safety net

---

## Related Stories

- **Depends on:** Story 10.10 - Changelog Release Traceability (fixes immediate gap)
- **Related:** CONTRIBUTING.md updates

---

## Code Review

**Date:** 2026-01-18
**Reviewer:** Claude
**Verdict:** OK to PR

### Summary

Added automated changelog generation tooling: conventional commit linting via pre-commit hook, git-cliff configuration, and release workflow validation.

### AC Verification

| Criterion | Evidence |
|-----------|----------|
| AC1: Conventional Commit Linting | `.pre-commit-config.yaml:41-54` - conventional-pre-commit hook with commit-msg stage |
| AC2: Changelog Generation Tool | `cliff.toml:1-102` - complete git-cliff config with Keep-a-Changelog template |
| AC3: Pre-Release Validation | `.github/workflows/release.yml:21-42` - validates `## [VERSION]` exists |
| AC4: Generated Format Matches | `cliff.toml:20-35` - template generates proper headings and categories |

### Quality Gates

- [x] ruff check passed
- [x] ruff format passed
- [x] Pre-commit hooks passed
- [x] Config files validated (YAML, TOML)
- [x] Commit linting verified during commit

---

## Change Log

| Date       | Change                          | Author |
|------------|---------------------------------|--------|
| 2026-01-18 | Code review passed              | Claude |
| 2026-01-16 | Initial draft                   | Claude |
