Atrium-Hermes

Changelog

Generate a user-facing weekly changelog (Keep a Changelog format) from recent commits across watched repos

Atrium-Hermes 0 Updated 4mo ago
GitHub

Install

npx skillscat add atrium-hermes/atrium-lighthouse/changelog

Install via the SkillsCat registry.

SKILL.md

${var} — Repo (owner/repo) to scan. If empty, scans all watched repos.

Why this skill exists

A changelog is not a commit log. Raw commit dumps grouped by conventional prefix are the noise anti-pattern — users can't tell what matters. This skill produces a Keep a Changelog-style weekly summary: categorized, plain-English, breaking changes surfaced, internal churn filtered out.

Config

Reads repos from memory/watched-repos.md. If the file doesn't exist, abort and notify: "changelog: memory/watched-repos.md missing — nothing to scan." Do not create it silently.

# memory/watched-repos.md
- owner/repo
- another-owner/another-repo

If ${var} is set to owner/repo, scan only that repo (skip the file list).


Read memory/MEMORY.md and the last 3 days of memory/logs/ for context (prior runs, known issues).

Steps

1. Pick the scan set

  • If ${var} is non-empty, scan only ${var}.
  • Otherwise, read memory/watched-repos.md and parse - owner/repo lines.
  • If the list is empty, notify "changelog: no repos configured" and exit cleanly.

2. Fetch commits and merged PRs per repo

For each repo, isolate failures — one broken repo must not kill the run. Track status in a sources dict (repo → ok|empty|fail).

Compute SINCE as UTC 7 days ago:

SINCE=$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-7d +%Y-%m-%dT%H:%M:%SZ)

Detect the default branch (don't assume main):

BRANCH=$(gh repo view owner/repo --json defaultBranchRef --jq '.defaultBranchRef.name')

Fetch commits on the default branch since SINCE:

gh api -X GET "repos/owner/repo/commits" -f sha="$BRANCH" -f since="$SINCE" --paginate \
  --jq '.[] | {sha: .sha, short: .sha[0:7], message: .commit.message, author: (.author.login // .commit.author.name), date: .commit.author.date, url: .html_url}'

Also fetch merged PRs in the window — PR titles/bodies are usually cleaner than raw commit messages:

gh pr list --repo owner/repo --state merged --limit 100 \
  --search "merged:>=$SINCE" \
  --json number,title,body,mergedAt,author,url,labels

Sandbox note: gh uses GITHUB_TOKEN internally and works in the sandbox. If gh fails, log fail for that repo and continue — do not fall back to WebFetch (public API is rate-limited and adds noise).

3. Filter noise

Exclude before classifying:

  • Bot authors: dependabot[bot], renovate[bot], claude[bot], github-actions[bot].
  • Merge commits where the underlying PR commits are already included (dedupe by PR number).
  • Revert commits paired with the reverted commit in the same window (collapse both into a single "Reverted: X" Fixed entry, or drop if trivial).
  • Pure auto-generated commits: "Update submodule", "Bump version to X", release-bot tags.

Keep a per-repo count of filtered commits for the footer ("N internal/bot commits hidden").

4. Classify into Keep a Changelog categories

Do not use Features/Fixes/Docs/Chores — those are for developers. Use:

Category Use for
⚠️ Breaking feat!: / fix!: / any commit whose body contains BREAKING CHANGE:. Also any removed public API.
Added New user-visible features (typically feat: without !).
Changed Modifications to existing functionality users will notice (behaviour, UX, defaults).
Fixed Bug fixes users care about (fix: only if the bug was observable).
Security security: prefix, CVE-, dependency bumps flagged as security, or commits touching auth/crypto with obvious security framing.
Internal Everything else (chore, ci, build, test, refactor, style, docs unless docs are user-facing). Show only a one-line count, not full entries.

Deprecated and Removed categories: include only if genuinely present — don't pad with empty sections.

5. Rewrite each entry in user language

Commit message → changelog line rules:

  • Strip the type(scope): prefix. Keep scope only if it clarifies (dashboard: add dark mode is fine; core: fix bug is not).
  • Rewrite imperative dev-speak into a past-tense user statement: feat(auth): add oauth2 pkce flow → OAuth 2 PKCE login is now supported.
  • Collapse related commits into one entry when they share a PR or scope (e.g. 4 commits for one feature → one line, list the shas in parentheses).
  • Length: one sentence, ≤20 words per entry. Cut internal implementation details.
  • Include one linked reference per entry: prefer PR ([#123](url)) over sha; fall back to short sha ([a1b2c3d](url)).

6. Assemble the article

Save to articles/changelog-${today}.md:

# Changelog — Week of ${today}

*Window: ${SINCE_date} → ${today} · Sources: repo1=ok, repo2=empty, repo3=fail*

## owner/repo

> **Highlights:** ≤2 sentences naming the most important user-facing change(s). If nothing user-facing, write "No user-facing changes this week; N internal commits."

### ⚠️ Breaking
- Plain-English breaking change description. Migration hint if obvious. ([#123](url))

### Added
- User-facing feature description. ([#124](url))

### Changed
- Behaviour/UX change. ([a1b2c3d](url))

### Fixed
- Bug that users would have hit. ([#125](url))

### Security
- Patch description, CVE if known. ([a1b2c3d](url))

*Internal: N commits hidden (chore/ci/build/refactor). Bots filtered: M.*

---

## owner/repo2
…

Rules:

  • Omit categories that are empty (don't print "### Added\n- None").
  • Omit entire repo section if sources[repo] == empty and no Highlights line is meaningful — but still list the repo in the sources line.
  • If sources[repo] == fail, include a stub: ## owner/repo\n\n*Could not fetch — see logs.*

7. Notify

Send one concise paragraph via ./notify:

*Changelog — Week of ${today}*
${total_repos} repos: ${total_user_facing} user-facing changes (${breaking_count} breaking, ${added_count} added, ${fixed_count} fixed, ${security_count} security). Top: ${one_line_most_important_change}. Full: articles/changelog-${today}.md

If zero user-facing changes across all repos: send CHANGELOG_QUIET — no user-facing changes across ${N} repos this week.

If all repos failed: send CHANGELOG_ERROR — all ${N} repos failed to fetch. See logs. and exit non-zero.

8. Log to memory

Append to memory/logs/${today}.md:

### changelog
- Window: ${SINCE_date} → ${today}
- Repos: ${ok_count} ok, ${empty_count} empty, ${fail_count} fail
- User-facing: ${breaking} breaking, ${added} added, ${changed} changed, ${fixed} fixed, ${security} security
- Internal filtered: ${internal_count} commits, ${bot_count} bot commits
- Article: articles/changelog-${today}.md
- Notes: [anything surprising — e.g. big breaking change, repo with no activity, first run for a new repo]

Constraints

  • Never paste raw commit messages as changelog entries — always rewrite.
  • Never emit empty categories or empty-highlight repos.
  • Never include bot commits in user-facing output.
  • Breaking changes always lead. Never bury a !: commit under Added/Changed.
  • Keep notifications to one paragraph per CLAUDE.md rules.

Sandbox note

gh CLI handles auth and works in the sandbox. If gh api fails for a repo, mark it fail in the sources dict and continue with other repos — don't abort the whole run, and don't fall back to unauthenticated WebFetch (rate limits will cascade failures).