changelog
GitHub用于生成或更新当前分支的变更日志条目。支持独立运行或在发布流程中调用,自动从Git和差异中提取元数据,生成符合规范的前置信息和分类正文,执行确定性增强脚本并验证结果,确保幂等性和一致性。
触发场景
安装
npx skills add NeverSight/learn-skills.dev --skill changelog -g -y
SKILL.md
Frontmatter
{
"name": "changelog",
"license": "MIT",
"metadata": {
"author": "Rob Easthope",
"version": "0.9.3"
},
"description": "Author, refresh, or repair the changelog entry for the current branch — derive metadata, write the frontmatter and grouped body, run the deterministic enrichment scripts, and validate against the changelog contract. Use when asked to write or update a changelog entry, refresh an entry after new commits, or as the changelog step inside a ship\/PR flow. Detects an existing entry for the branch (idempotent update-vs-create), keeps `created_at` sacred, leaves post-merge fields to the release step, and validates with a zero-dependency Node script.",
"allowed-tools": "Write, Read, Edit, Glob, Grep, Bash(git:*), Bash(node:*), Bash(pnpm:*)",
"compatibility": "Requires Node.js ≥22 for the bundled scripts (no npm dependencies — Node built-ins only) and the `git` CLI for branch\/diff analysis. The optional `preflight-changelog-ci.mjs` step assumes the consumer repo uses pnpm with a committed lockfile; skip it if yours does not."
}
changelog
Generate or update the changelog entry for the current branch under
changelog/YYYYMMDD-HHMMSS-<slug>.md: derive its metadata from git and the diff,
write the frontmatter and a grouped, categorised body, run the deterministic
enrichment scripts, then validate the result.
This skill is the single source of truth for what a valid changelog entry is — the frontmatter schema, the field-ownership boundaries, idempotent update-vs-create, and the validation gate. The same contract is enforced downstream by a consumer repo's CI and relied on by a release-orchestrator that finalises the post-merge fields, so the authoring rules live here once.
It is invoked two ways:
- Standalone (
/changelog) — author, refresh, or repair this branch's entry and leave it uncommitted in the working tree for review. No commit, push, or PR. - Inside a ship flow (e.g. a
/send-it) — the changelog step that runs before push; the ship flow commits the entry, pushes, and opens the PR.
Configuration
Config lives in config.json beside this file; the bundled
scripts read it automatically. Edit your copied config.json to match the
consuming repo (a neutral config.example.json ships as a
template). issueKeys and linearWorkspaceSlug are required — they have no
default, so a missing config.json or either key absent makes the scripts fail
loudly rather than silently inherit ACME's identity. The rest are structural and
keep generic, overridable defaults:
| Key | Meaning | Default |
|---|---|---|
issueKeys |
Team-key prefixes used to recognise issue IDs in the branch and body. The issue-ID regex is built from these. | required |
linearWorkspaceSlug |
Linear workspace slug used to build issue links (https://linear.app/<slug>/issue/<id>). |
required |
baseBranch |
The trunk the branch diff is taken against (origin/<baseBranch>). Overridable per-run via the BASE_REF env var. |
"main" |
changelogDir |
Directory the dated entries live in (scanned by the enrichment + validation scripts). | "changelog" |
packageRoots |
Monorepo dir prefixes mapping <root>/<x>/… → package <x> when deriving affected_packages. |
["apps", "packages", "services"] |
fallbackPackage |
Package name for changed paths matching no packageRoots prefix. |
"infrastructure" |
affectedPackages |
Whether to emit the affected_packages field at all. Leave false for single-package repos (the field is write-only and redundant there — entries stay clean); set true in genuine monorepos. initialise-skills flips it on when it detects a workspace config. |
false |
All bundled scripts use only Node built-ins — no npm install, no build step.
They operate on the consumer repo's root changelog/ directory (run them
from the repo root).
Running it
Step 1 — Detect an existing entry (idempotency)
Grep changelog/ for a file whose frontmatter contains branch: "<current-branch>".
If exactly one matches, you are in update mode: preserve its created_at and
filename, rewrite the rest. Otherwise you are in create mode.
Step 2 — Analyse the branch
git log origin/<base>..HEAD --pretty=full— full commit list including bodies and trailers.git diff origin/<base>...HEAD --name-only— changed files, for grouping the body by package.
<base> is config.json's baseBranch (default main). Fetch it first
(git fetch origin <base>) so the diff is accurate — skip the fetch if the
caller already did it (e.g. a ship flow fetches in its preflight step).
Step 3 — Derive metadata
| Field | How to derive |
|---|---|
issues |
Match the issue-ID regex (built from issueKeys) against the branch name (upper-cased) and against commit subjects/bodies. Deduplicate. |
author |
git config user.email. |
co_authors |
Parse Co-authored-by: Name <email> trailers across all branch commits. Store the email or Name <email> form. Empty array if none. |
category |
Infer from commit subjects and diff: feature, fix, chore, docs, refactor, perf. If ambiguous, ask the user to confirm. |
breaking |
Infer from BREAKING CHANGE: trailers, ! in conventional-commit subjects, or removal of public surfaces. If unclear, ask the user. Default false. |
release_note |
One-sentence user-facing summary distinct from title. Optional — leave blank if the change has no public-facing impact (chore, internal refactor). |
Field ownership — what this skill authors vs. what it must leave alone is the
crux of the contract; see references/changelog-contract.md
for the full rules. In short:
- Authored here:
title,release_note,category,breaking,issues,co_authors,author, and — only whenaffectedPackagesis on —affected_packages(written by the enrichment script in Step 5, not hand-edited). Single-package repos leaveaffectedPackages: false(the default) and omit the field entirely. created_atis sacred — set once on create (UTC time of first run); on update, preserve it verbatim.- Never authored here:
stats(files_changed,loc_added,loc_removed,commits) and the post-merge fieldsmerged_at/commit/pr/merge_strategy. A release step finalises them from canonical GitHub PR data after merge —princluded, resolved from the merged PR by itsbranch:(never written by the ship flow). Emit them as blank placeholders on create; leave existing values untouched on update.
The skill emits the derived issues array as a handoff — a ship flow reuses
it for the PR body and any Linear writeback (e.g. via a linear-sync skill).
Step 4 — Generate the body
Group bullets by package, categorised under ## Added / ## Changed / ## Fixed.
Only include headings that have entries. For multi-package changes use
**<pkg-name>:** subheaders.
If breaking: true, the body MUST start with a ## Breaking section describing
the change and the migration path.
Write the title, release_note, and body prose in the consuming repo's documented
prose language. Across this estate that is British English (colour, behaviour,
-ise/-yse) — prose only, never identifiers, dependency names, or upstream API
field names.
Step 5 — Write or update the file
Filename: changelog/YYYYMMDD-HHMMSS-<slug>.md, where the timestamp is
created_at (UTC time of first run) and the slug derives from title (lowercase,
non-alphanumerics → -, collapse repeats, ~60-char cap on a word boundary).
Always quote timestamp strings in YAML (created_at: "2026-04-26T13:24:00Z").
Unquoted ISO timestamps parse as Date objects and gain .000Z millis on the
enrichment round-trip; quoting keeps them lossless.
On update: preserve created_at and the filename; rewrite title,
release_note, category, breaking, co_authors, issues, and the body;
leave merged_at / commit / pr / merge_strategy / stats alone (the
release/enrich step fills them post-merge, pr branch-resolved).
Use the frontmatter field order shown in
references/changelog-contract.md. Only when
affectedPackages is on, emit affected_packages: [] as a placeholder — the
script fills it in place. When it is off (the single-package default), omit the
field; set-affected-packages.mjs is a no-op.
Then run the two deterministic enrichment scripts from the consumer repo root
(both idempotent; they match the entry by its branch: frontmatter and leave the
post-merge fields blank):
node skills/changelog/scripts/set-affected-packages.mjs # writes affected_packages from the branch diff
node skills/changelog/scripts/add-links.mjs # rewrites bare issue IDs in the current branch's entry to Linear URLs
Adjust the path prefix if you installed the skill to a different location.
Both enrichment scripts also accept --check (alias --dry-run) — a read-only
preview that reports what would change and writes nothing, exiting 0 when the
entry is already up to date and 1 when a rewrite is needed (prettier---check
style, so CI can gate on it):
node skills/changelog/scripts/set-affected-packages.mjs --check # current branch's entry only
node skills/changelog/scripts/add-links.mjs --check # ALL entries in the changelog dir
Both enrichers are branch-scoped by default (A-603): add-links.mjs with no
arguments rewrites only the entry/entries whose branch: frontmatter matches the
current git branch, so authoring a new entry never churns unrelated, already-merged
ones. Two modes still scan the whole directory: --all (a deliberate
full-directory rewrite) and --check/--dry-run (the completeness gate, which can
exit 1 on a historical entry). Use --check to confirm the directory is fully
enriched; use the default for the per-PR pass on one branch's entry. (When git is
unavailable the default falls back to the full sweep.)
Step 6 — Validate against the contract
This is the gate:
node scripts/preflight-changelog-ci.mjs # optional: checks Node vs engines/.nvmrc, then pnpm install --frozen-lockfile
node scripts/validate-changelog.mjs # validates frontmatter schema, filename format, field types, ISO timestamps, Breaking section, issue IDs
preflight-changelog-ci.mjs is optional and pnpm-specific — skip it if the
consumer repo doesn't use pnpm. On failure, stop and fix the entry before
continuing — do not hand a malformed entry to the ship flow.
Standalone vs inside a ship flow
- Standalone (
/changelog) runs Steps 1–6 and then reports, leaving the entry uncommitted in the working tree for the user to review and commit. It never pushes or opens a PR. - Inside a ship flow the same steps run before push; the ship flow then
commits the entry (
docs(changelog): <title>), pushes, and opens or updates the PR. It leavesprblank — the release/enrich step fills it post-merge, branch-resolved from the merged PR.
Implementation
All the scripts the changelog lifecycle needs live under scripts/
in this bundle and run on plain Node (no npm dependencies, no build step). They
cover the whole lifecycle the bundle owns — authoring (run by this skill) and
finalisation (wired into the consumer's package.json / CI / release
orchestrator). Each takes --help (usage, exit 0) and --self-test (an offline
smoke test of its pure logic); the file-writing scripts also take --check /
--dry-run (report, write nothing).
Authoring — run by this skill (the /changelog flow):
scripts/set-affected-packages.mjs— writesaffected_packagesfrom the branch diff (monorepo consumers only; a no-op whenaffectedPackagesis off).scripts/add-links.mjs— rewrites bare issue IDs in the body to Linear URLs.scripts/preflight-changelog-ci.mjs— optional Node/lockfile CI-parity check (pnpm).scripts/validate-changelog.mjs— validates the entry against the contract.
Finalisation and the CI gate — run by the consumer, not by this skill. These
ship in the bundle too, and an adopter wiring up the orchestrator/CI gate needs
them. They are referenced from the consumer's package.json scripts and
workflows rather than invoked during authoring:
scripts/finalise-changelog.mjs— release-time enrichment + version-stamping for npm targets, run by the release orchestrator right afterrelease-please release-pr(the consumer exposes it as thechangelog:finalisescript). For each un-finalised entry it resolves the merged PR viagh/git, fills the post-merge fields (merged_at/commit/pr/merge_strategy/stats, the last including the merge-excludedcommitscount from the PR commits API), stampsversionwith the just-bumpedpackage.jsonversion, and links bare Linear IDs. It composeslib/enrich.mjs(the PR-metadata fill),lib/commit-count.mjs(the merge-excluded commit count) andlib/stamp.mjs(the version stamp).scripts/enrich-changelog.mjs— post-merge enrichment for deploy targets (octavo, shared-workflows), which are never checked out during the release flow and so can't finalise inline. Run by the release orchestrator's dailyenrich-changelogs.ymlcron on the checked-out target (the consumer exposes it as thechangelog:enrichscript). It reads one merged PR's data from an env-var interface (BRANCH_NAME/MERGED_AT/MERGE_SHA/MERGE_STRATEGY/PR_NUMBER/ADDITIONS/DELETIONS/CHANGED_FILES), finds the entry by itsbranch:, and fills the same post-merge field group as finalise (minusversion, which a deploy target's own tag flow owns, and minuscommits, which the cron doesn't resolve). A thin wrapper overlib/enrich.mjs; fill-once and idempotent, so the cron can re-run safely.--checkexits 1 when an entry still needs enriching;--dry-runpreviews.scripts/check-changelog-completeness.mjs— the CI completeness gate, run by the consumer's validation workflow: a release-triggering (feat/fix/breaking) PR title must carry a datedchangelog/entry, or the build fails.scripts/backfill-commits.mjs— a one-off backfill ofstats.commitsacross the existingchangelog/backlog (for adopting the count after the fact). Resolves each entry's merged PR viagh, splices in only thecommitsline (no re-serialise), and is idempotent;--dry-runpreviews. Not part of authoring or the release flow.
They share helpers under scripts/lib/ (changelog.mjs, derive-packages.mjs,
frontmatter.mjs, config.mjs, enrich.mjs, commit-count.mjs, stamp.mjs). So while this skill
itself stops at authoring + validation and leaves the post-merge fields blank,
the finalisation and completeness scripts that fill them are part of this
bundle — the consumer's package.json / CI / release orchestrator run them, not
the /changelog flow.
Note for adopters: unit tests for these scripts are maintained in the
agent-skillsrepo (not bundled into the skill). See the skill's README.
版本历史
- c3c0a1e 当前 2026-07-23 10:21


