Skip to main content

changelog

Generate a user-facing changelog from recent commits/PRs across watched repos - write it in-repo (Keep a Changelog format) or open a cross-repo changelog PR on a docs/marketing repo.

설치로 이동

소스 정보

저장소
aeonfun/aeon
최근 소스 활동
2026년 9월 14일 14:26
감지된 SKILL.md 언어
영어
스타
738
포크
265

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
changelog
description
Generate a user-facing changelog from recent commits/PRs across watched repos - write it in-repo (Keep a Changelog format) or open a cross-repo changelog PR on a docs/marketing repo.
metadata
{"title":"Changelog","category":"dev","var":"","tags":["dev","content","build"],"mode":"write","commits":true,"permissions":["contents:write","pull-requests:write"],"requires":["GH_GLOBAL?"]}
<!-- autoresearch: variation B — sharper output: Keep a Changelog categories, breaking-change surfacing, plain-English rewrites, noise filtering --> > **${var}** — Selects the mode and target: > - **empty** → in-repo changelog across every repo in `memory/watched-repos.md`. > - **`owner/repo`** (bare slug) → in-repo changelog for that single repo only. > - **`push-to:owner/website-repo`** → cross-repo mode: publish the product's merged PRs as a changelog PR on `owner/website-repo` (product repo comes from `memory/docs-sync.md`). > - **`owner/product->owner/website`** (arrow form) → cross-repo mode with both product and website repos given explicitly. ## 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](https://keepachangelog.com/en/1.1.0/)-style summary: categorized, plain-English, breaking changes surfaced, internal churn filtered out. It runs in two modes: **in-repo** (write the article into this repo) or **push-to** (open a changelog PR on a separate marketing/docs website repo). --- ## 0. Preamble — read memory and parse the selector Read `memory/MEMORY.md` and the last 3 days of `memory/logs/` for context (prior runs, known issues). Before notifying, drop anything already reported in that window. Parse `${var}` to pick the branch: | `${var}` | Branch | Target | |----------|--------|--------| | empty | **A — in-repo** | all repos in `memory/watched-repos.md` | | `owner/repo` (no `push-to:`, no `->`) | **A — in-repo** | only that repo | | `push-to:owner/website-repo` | **B — push-to** | website = `owner/website-repo`; product = config `product_repo` | | `owner/product->owner/website` | **B — push-to** | product = `owner/product`; website = `owner/website` | Disambiguation: a `push-to:` prefix **or** a `->` arrow selects Branch B; anything else (empty or a bare `owner/repo`) selects Branch A. Then jump to the matching branch below. --- # Branch A — in-repo changelog Writes a categorized changelog article into this repo. No cross-repo PR; `GH_GLOBAL` is not needed here. ## A.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. ```markdown # memory/watched-repos.md - owner/repo - another-owner/another-repo ``` If `${var}` is set to a bare `owner/repo`, scan only that repo (skip the file list). ### A.1. Pick the scan set - If `${var}` is a bare `owner/repo`, 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. ### A.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: ```bash 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`): ```bash BRANCH=$(gh repo view owner/repo --json defaultBranchRef --jq '.defaultBranchRef.name') ``` Fetch commits on the default branch since `SINCE`: ```bash 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: ```bash gh pr list --repo owner/repo --state merged --limit 100 \ --search "merged:>=$SINCE" \ --json number,title,body,mergedAt,author,url,labels ``` **Network note:** `gh` uses `GITHUB_TOKEN` internally and works in a GitHub Actions run. If `gh` fails, log `fail` for that repo and continue — do not fall back to WebFetch (public API is rate-limited and adds noise). ### A.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"). ### A.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. ### A.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)`). ### A.6. Assemble the article Save to `output/articles/changelog-${today}.md`: ```markdown # 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.*` ### A.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: output/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. Then log (see the shared **Log** section) with `Mode: in-repo`. --- # Branch B — push-to (cross-repo changelog PR) Takes the product's recently merged PRs and publishes them as a **changelog** on the product's marketing/docs website, via a branch + PR on the website repo. The website is the public face — this keeps "what shipped" visible without anyone hand-writing release notes. **This branch opens a cross-repo PR and requires `GH_GLOBAL`** (a token with cross-repo write to the website repo). `GITHUB_TOKEN` alone only covers the current repo and cannot push to the website. This branch is **config-driven** so the same file works in every instance. It reads which repos to use from `memory/docs-sync.md`; it never hardcodes repo names, handles, or commit identities. ## B.0. Resolve config Read `memory/docs-sync.md`. It defines: - `product_repo` — the repo whose merged PRs become the changelog (e.g. `owner/product`). - `website_repo` — the Next.js marketing site to update (e.g. `owner/product-website`). - `min_prs` (optional, default `1`) — minimum number of *new* unpublished PRs required to publish an entry. - `lookback_days` (optional, default `7`) — only consider PRs merged within this many days. Bounds each entry to one window so a run never sweeps in months of history; matches the weekly schedule. - `draft` (optional, default `true`) — open the website PR as a draft. - `git_user_name` / `git_user_email` (optional) — commit identity for the website PR. Defaults to `aeon` / `aeon@users.noreply.github.com`. Apply the selector on top of config: - `push-to:owner/website-repo` → `website_repo = owner/website-repo`; `product_repo` from config. - `owner/product->owner/website` → `product_repo = owner/product`, `website_repo = owner/website` (overrides config for this run). If neither the selector nor `memory/docs-sync.md` yields **both** a product repo and a website repo, exit with `DOCS_SYNC_NO_CONFIG` (notify + log, no PR). Seed a `memory/docs-sync.md` template (commented placeholder rows) so the operator can fill it in. ## B.1. Gather merged PRs from the product repo Compute the window cutoff first — `lookback_days` ago (default 7), as an ISO timestamp: ```bash SINCE=$(date -u -d "${LOOKBACK_DAYS:-7} days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-"${LOOKBACK_DAYS:-7}"d +%Y-%m-%dT%H:%M:%SZ) ``` Then fetch the last 50 closed PRs and keep only those **merged within the window**, newest merge first: ```bash gh api "repos/${PRODUCT_REPO}/pulls" -X GET -f state=closed -f sort=updated -f direction=desc -f per_page=50 \ --jq "[.[] | select(.merged_at != null) | select(.merged_at > \"$SINCE\") | {number, title, url: .html_url, author: .user.login, merged_at, labels: [.labels[].name], body: (.body // \"\" | .[0:500])}] | sort_by(.merged_at) | reverse" ``` The window is the primary filter; the published-PR dedup in step B.2 is the idempotency guard against overlap and re-runs. Sandbox: if `gh api` fails transiently, retry once. Never use `curl` for the GitHub API — `gh` handles auth. ## B.2. Read what's already published (idempotency) Clone the website repo and read the existing changelog data: ```bash WORK_DIR="/tmp/docs-sync-work" rm -rf "$WORK_DIR" gh repo clone "$WEBSITE_REPO" "$WORK_DIR" -- --depth 20 cd "$WORK_DIR" git config user.name "$GIT_USER_NAME" git config user.email "$GIT_USER_EMAIL" ``` **Pin the commit identity in the clone.** A freshly cloned repo does NOT inherit the workflow's git identity, so without these two lines the commit author falls back to an improvised/unlinked email. Set `GIT_USER_NAME` / `GIT_USER_EMAIL` from `memory/docs-sync.md` (`git_user_name` / `git_user_email`); when the config omits them, default to `aeon` / `aeon@users.noreply.github.com`. Always pin it so every changelog commit + PR is attributed to one stable, intentional identity — never an improvised one. If `app/changelog-data.ts` exists, read it and collect `PUBLISHED_PR_NUMBERS` (every PR number already in `CHANGELOG`). If it doesn't exist yet, this is a **bootstrap** run (see step B.4) and nothing is published. **Compute the new set:** from step B.1's windowed PRs, keep only those whose `number` is NOT in `PUBLISHED_PR_NUMBERS`. PR number is the idempotency key — not dates — so re-running within the same window is always safe and never duplicates. - If the new set is empty → exit `DOCS_SYNC_NOTHING_NEW` (silent: log only, no PR, no notify). - If `0 < count < min_prs` → exit `DOCS_SYNC_BELOW_THRESHOLD` (log only, no PR). Lets PRs accumulate into a meaningful entry.
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기