| name | daily-changelog |
| description | Generate a daily changelog / release-notes document from the PRs that merged on a given date and write it to `config.paths.changelogDir` as `YYYY-MM-DD.md`. Use when the user asks for "the daily changelog", "daily release notes", "release notes", "what shipped today/yesterday", "what landed on <date>", "write up today's changes", "summarize the day's PRs", or any variant of "what merged on <date>". Defaults to yesterday when no date is given. Always writes one markdown file per day. |
Daily changelog
This skill turns a day's worth of merged PRs into a short, readable changelog at
config.paths.changelogDir/YYYY-MM-DD.md. It exists because the repo ships through PRs (no direct
pushes to config.defaultBranch), so "what shipped on day X" is exactly "which PRs merged on day X."
The output is for humans skimming the project's history later — not for marketing, and not for end
users. It is an internal, day-by-day record that captures the flow between version bumps.
Optimize for what changed and why, with enough detail that a reader can decide whether to dig into
the PR.
If the repo also has a user-facing release surface (e.g. a per-version release-notes file or a
rendered docs-site changelog), this daily changelog is distinct from it: that surface is updated
only when a new version ships (handled by the repo's release process), while this file is the
internal activity log. Don't touch the user-facing version surface from this skill.
Load the repo config
Before anything else, read .claude/maintainerd.json from the repo root (see
../../../core/reference/config-schema.md for the full contract). If it
does not exist, stop and tell the user:
This repo has no .claude/maintainerd.json. Run /bootstrap to generate it, then re-run me.
Don't guess values or hardcode another repo's settings. The keys this skill needs:
config.repo — GitHub owner/name, passed to every gh ... --repo.
config.defaultBranch — the branch PRs merge into (main, master, …).
config.paths.changelogDir — where this skill writes YYYY-MM-DD.md. If absent, fall back to
planning/changelog/ and note the fallback in your report.
Workflow
1. Pick the date
If the user named a date, use it. Otherwise default to yesterday in the user's local timezone —
today's work is usually still in flight, and a daily changelog is a retrospective format.
Resolve the date to ISO YYYY-MM-DD. Today's date is in the system context; subtract one day for the
default.
If the user says something ambiguous like "this week" or "the last few days", ask which single day
they want — this skill writes one file per day. Offer to run it once per day in the range if they
want a batch.
2. Find the PRs that merged that day
GitHub's merged:YYYY-MM-DD shorthand interprets the date in UTC, which silently misclassifies PRs
merged late local-evening for anyone west of UTC (e.g. a Pacific user's 11pm merge lands in the
next UTC day). Always use a local-timezone-anchored ISO range so the day boundary matches what the
user means by "today" / "yesterday":
TZ_OFFSET=$(date +%z)
TZ_FORMATTED="${TZ_OFFSET:0:3}:${TZ_OFFSET:3}"
gh pr list --repo <config.repo> \
--state merged \
--search "merged:YYYY-MM-DDT00:00:00${TZ_FORMATTED}..YYYY-MM-DDT23:59:59${TZ_FORMATTED}" \
--limit 200 \
--json number,title,url,author,mergedAt,body,headRefName,additions,deletions
--limit 200 is well above any realistic per-day PR count. After fetching, check whether the
returned count equals the limit — if len(results) == 200, you've hit the local fetch cap (not
GitHub's), so bump --limit and re-fetch. Separately, the GitHub Search API hard-caps results at
1000 even with paging; if you ever approach that on a single day (extremely unusual), narrow the
window — split the day into morning/afternoon halves and run two queries.
If the result is empty, write the file anyway with a one-line "no PRs merged" entry. An empty day is
itself signal (was the team out? freeze?) and the file's existence keeps the daily cadence visible.
3. Pull the body for each PR
The list query above already includes body. If you need more (review comments, the actual diff
stat by file, linked issues), use:
gh pr view <N> --repo <config.repo> --json title,body,commits,files,reviews
You usually don't need this — the PR title plus body is enough for the summary. Only dig in if a PR's
body is empty or unhelpful.
Ignore the CodeRabbit auto-generated block at the end of every body. It starts with
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> followed by
## Summary by CodeRabbit, and it's a generic restatement of what the human Summary already covered.
Paraphrasing both produces redundant, marketing-flavored notes. Read only the human-authored sections
(typically ## Summary, ## Changes, ## Test plan per the repo's PR template).
4. Credit external contributors
Many repos accept external contributions. When a PR's author.login is not the maintainer's own
account:
- Mention the contributor in the entry:
Contributed by @<login>.
- Don't editorialize about the maintainer's takeover/cleanup — that's PR-thread context, not
changelog content.
5. Write the Markdown
File path: config.paths.changelogDir/YYYY-MM-DD.md. Create the directory if it doesn't exist.
If the file already exists, read it first. The user may have hand-edited prior notes; don't blow
that away. Default behavior: regenerate fresh and overwrite, but call out in your reply that you
replaced the file. If the user wants merge-don't-replace behavior, they'll say so.
Use this template:
# Daily changelog — <Month DD, YYYY>
**Date:** YYYY-MM-DD
**PRs merged:** <N>
---
## Summary
<One short paragraph (2–4 sentences) framing the day. Lead with the most consequential change. If the day was thin, say "Quiet day — <one-liner>." Don't pad.>
## What shipped
### [#<num> <PR title>](<url>)
<1–3 sentences describing what the PR does and _why_, drawn from the PR body. If the PR body is empty or just "see commit", fall back to the title and `gh pr view`'s commit messages.>
<Optional second paragraph if the PR has a non-obvious follow-up, caveat, or migration step worth flagging.>
<Optional `Contributed by @<login>.` line for external contributors.>
### [#<num> <PR title>](<url>)
...
Order PRs by mergedAt ascending (chronological). Don't try to invent thematic groupings unless the
day genuinely clusters into 2–3 themes — chronological is honest and easy to scan.
6. Tell the user what you wrote
After writing, reply with:
- The file path
- The PR count
- One line on the day's shape (e.g., "mostly bugfixes" / "feature-heavy day" / "scheduler infra
cleanup")
Don't paste the whole file back. The user can open it.
Voice and style
Match the existing changelog / PR-template voice — prose-forward, explains the why, doesn't shout
in all-caps. A few specifics:
- No emojis in the file. Existing docs don't use them and neither should this.
- No marketing language ("excited to announce", "powerful new"). State what changed.
- Link to the PR, always. The PR is the source of truth; the changelog entry is a pointer.
- Don't list every commit. A PR can have 12 commits; the reader cares about the PR's net effect,
not the commit-by-commit history. Use commits only when the PR body is unhelpful and you need to
reconstruct what happened.
- Quote the PR body sparingly. Paraphrase to one tight paragraph. If the PR body is already a
clean two-sentence Summary, you can lift it nearly verbatim — but trim ceremony.
- Be honest about scope. A PR that's "Address CR feedback" is a fixup, not a feature. Say so.
Trivial PRs can collapse to a single line.
What not to do
- Don't write a changelog for an arbitrary date range in one file. One date → one file. If asked
for a week, generate seven files (or ask the user to pick a single day).
- Don't include direct commits to
config.defaultBranch. The repo's convention is PR-only. If a
stray non-merge commit landed on the default branch on that date, mention it in the Summary as an
exception, but don't fabricate a "PR" entry for it.
- Don't fetch the full diff unless a PR body is actively unhelpful. The diff is large and you
rarely need it; the PR body and commit messages are usually sufficient.
- Don't commit the file as part of this skill unless the user asks. Just write it. When invoked
by
daily-update, the wrapper handles the commit + PR flow. When invoked manually, the user
reviews and decides whether to stage it at all.
- Don't touch the user-facing version-release surface (a per-version release-notes file, a
rendered docs-site changelog, etc.). Those are handled by the repo's release process on
version-bump days, not by this daily activity log.
- Don't generate "Future work" or "Next steps" sections. This is a retrospective of what
shipped, not a plan. Forward-looking content lives in PR descriptions and issues.
Edge cases
- Squash-merged PRs with no merge commit. Doesn't matter —
gh pr list --search "merged:DATE"
finds them by merge timestamp regardless of merge strategy.
- PRs merged across midnight UTC. Already handled by the local-tz query in step 2 — every PR
sorts onto the user-local day it actually shipped on. Nothing extra to do here.
- Reverts. Treat the revert PR as its own entry. Don't try to silently retract the original PR's
entry from a prior day's file.
- Dependabot / automated PRs. Include them. Group multiple bot PRs under one entry if there are
many on the same day ("Dependency bumps: #X, #Y, #Z — minor/patch updates to A, B, C"). A
dependencies label, if the repo applies one, makes them easy to spot.
- Version-bump PRs. When a PR is the version-bump that triggers a release (touches version
manifests like
package.json / manifest.json / pyproject.toml / a release-notes file), call
out the new version in the entry. The reader benefits from the version landmark.
Related skills
bootstrap — generates the .claude/maintainerd.json this skill reads.
daily-update — the meta-skill that runs this plus the other per-day housekeeping skills and
bundles their output into one PR.
create-pr — used by daily-update (or manually) to open the PR once the file is written.