diff --git a/.claude/commands/create-pr.md b/.claude/commands/create-pr.md
index d58c3414..708f8dd9 100644
--- a/.claude/commands/create-pr.md
+++ b/.claude/commands/create-pr.md
@@ -29,7 +29,13 @@ Create a GitHub pull request with auto-generated summary from branch commits:
- Group TODOs by category or file if helpful
- If no TODOs found, omit the TODOs section entirely
-5. **Generate PR Content**
+5. **Check the CHANGELOG**
+ - If `CHANGELOG.md` is not in `git diff ...HEAD --name-only`, decide whether the change needs an entry, using the rule in AGENTS.md (Workflow, step 3): anything that changes behavior, CLI flags, outputs, imports, or call signatures does
+ - If it does: draft the entry under `## [Unreleased]` in the matching section, show it to the user, and commit it before creating the PR
+ - If it doesn't: say why, and add `--label no-changelog` to `gh pr create`
+ - CI (`.github/workflows/changelog.yml`) fails a PR with neither
+
+6. **Generate PR Content**
- **Title**:
- If single commit: Use that commit message
- If multiple commits of same type: `: `
@@ -49,10 +55,10 @@ Create a GitHub pull request with auto-generated summary from branch commits:
- [ ]
```
-6. **Present for Review**
+7. **Present for Review**
- Show generated PR title and body
- Ask user: "Create PR with this content? (yes/edit/cancel)"
- - If "yes": Proceed to step 7
+ - If "yes": Proceed to step 8
- If "edit":
- Ask for new title (or keep current)
- Ask for new description (or keep current)
@@ -60,7 +66,7 @@ Create a GitHub pull request with auto-generated summary from branch commits:
- Ask again: "Create PR with this content? (yes/cancel)"
- If "cancel": Exit without creating PR
-7. **Create Pull Request**
+8. **Create Pull Request**
- Use GitHub CLI: `gh pr create --title "..." --body "..."`
- Use heredoc for multiline body:
```bash
@@ -73,7 +79,7 @@ Create a GitHub pull request with auto-generated summary from branch commits:
- Capture PR URL from output
- Display success message with PR URL
-8. **Error Handling**
+9. **Error Handling**
- Check if `gh` CLI is installed: `gh --version`
- If not installed: "GitHub CLI (gh) is not installed. Install from: https://cli.github.com/"
- Check if authenticated: `gh auth status`
diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml
new file mode 100644
index 00000000..56b042c7
--- /dev/null
+++ b/.github/workflows/changelog.yml
@@ -0,0 +1,31 @@
+name: Changelog
+
+# Every PR records its change in CHANGELOG.md (AGENTS.md, Workflow), unless it
+# carries the `no-changelog` label: for changes nobody would notice, such as a
+# typo fix or a refactor with no import or behavior change. Runs on PRs into any
+# branch, so stacked PRs are checked too.
+on:
+ pull_request:
+ types: [opened, synchronize, reopened, labeled, unlabeled]
+
+permissions:
+ pull-requests: read
+
+jobs:
+ changelog:
+ name: CHANGELOG entry
+ if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-changelog') }}
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Require a change to CHANGELOG.md
+ env:
+ GH_TOKEN: ${{ github.token }}
+ PR: ${{ github.event.pull_request.number }}
+ run: |
+ if gh pr diff "$PR" --repo "$GITHUB_REPOSITORY" --name-only | grep -qx CHANGELOG.md; then
+ echo "CHANGELOG.md is updated."
+ else
+ echo "::error file=CHANGELOG.md::Add an entry under [Unreleased] (see AGENTS.md, Workflow), or add the no-changelog label if nobody would notice this change."
+ exit 1
+ fi
diff --git a/AGENTS.md b/AGENTS.md
index 668816b7..83188966 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -178,7 +178,7 @@ Format: `/` (kebab-case), e.g. `feat/add-gpt4-support`,
1. Branch from `main`
2. Make changes; run `uv run pytest -m "not live"` for code changes
-3. Record the change in [CHANGELOG.md](./CHANGELOG.md) under `## [Unreleased]`, in the matching section (e.g. "Breaking / migration", "Runtime, CLI, and pipeline", "Testing and CI"). Follow the existing entry style: `- **Short title**—one or two sentences`. Every user-visible or contributor-visible change needs an entry: behavior, CLI flags, outputs, renamed or moved modules, test/CI policy. Skip it only for changes nobody would notice, such as typo fixes or internal refactors with no import or behavior change
+3. Record the change in [CHANGELOG.md](./CHANGELOG.md) under `## [Unreleased]`, in the matching section (e.g. "Breaking / migration", "Runtime, CLI, and pipeline", "Testing and CI"). Follow the existing entry style: `- **Short title**—one or two sentences`. Every user-visible or contributor-visible change needs an entry: behavior, CLI flags, outputs, renamed or moved modules, test/CI policy. Skip it only for changes nobody would notice, such as typo fixes or internal refactors with no import or behavior change, and mark such a PR with the `no-changelog` label: CI fails a PR that has neither an entry nor the label
- **Suggest a version bump; don't make it.** Releases are git tags (`v2.0.0`) that match a CHANGELOG heading. When `[Unreleased]` holds a "Breaking / migration" entry, or enough changes to be worth a release, say so to the human and propose the next version under [Semantic Versioning](https://semver.org/): major for breaking changes, minor for new features, patch for fixes. The human decides; don't rename the `[Unreleased]` heading or create tags yourself
4. Atomic commits; pre-commit hooks run on commit
5. Push and open a PR
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 96ae8411..5a192976 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
### Testing and CI
+- **PRs need a CHANGELOG entry**—A new check fails a pull request that doesn't change `CHANGELOG.md`. Changes nobody would notice, such as a typo fix or a refactor with no import or behavior change, take the `no-changelog` label instead. `/create-pr` now drafts the entry, or adds the label, before opening the PR.
- **Guards against changed scores**—Unit tests pin results with fixed inputs, so a code change can't move scores unnoticed. One per stage: golden persona prompts and a replay of recorded user and chatbot replies (generate), scripted answers walking the SI rubric (judge), and golden `scores.json` files (score); plus a replay of the whole recorded pipeline. None calls a model. See [docs/safe-changes.md](docs/safe-changes.md).
### Documentation and development workflow