SDK Changelog Generation
SkillDev toolsGenerate changelogs for SDK pod packages using tag-based GitFlow. Use when preparing a release, generating changelog, or creating CHANGELOG_LLM.md.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the SDK Changelog Generation skill
What this skill tells your AI
The instructions your AI receives, as published by tetherto/qvac in .agents/skills/qv-sdk-changelog/SKILL.md and read by ahel’s review.
Generate changelogs for SDK pod packages following the monorepo GitFlow.
When to use this skill
Applies to SDK pod packages whose paths are owned by .github/teams/sdk.json.
Use when:
- Preparing a release for any SDK pod package
- User asks to generate changelog
- User asks to create human-readable/presentable changelog
- User asks to generate CHANGELOG_LLM.md
- User invokes
/qv-sdk-changelog
Workflow
Every step is mandatory. Do not ask the user whether to do CHANGELOG_LLM.md or
NOTICE — they are part of this skill and always run.
Step 1: Identify Target Package
If the user doesn't specify, ask which SDK pod package they want to generate a changelog for.
Package slugs match git tags (sdk, cli, ai-sdk-provider, opencode-plugin, openclaw-plugin, …). Directory resolution (including plugins/*) is in scripts/sdk/package-paths.cjs.
Working branch (when cutting from a release line): use
chore/<pkg>-<x.y.z>-changelog (e.g. chore/sdk-0.17.0-changelog). Do not
name the head release-* — org pushes to release-* run Release Merge Guard
against the pushed ref (not the PR base). The release cut itself must be
three-part release-<pkg>-x.y.z. Full rules live in
qv-sdk-pr-create → "Release PR branch naming".
Step 2: Fetch Tags and Resolve Base
Tags live on the upstream remote (tetherto/qvac), not the contributor's fork.
The script fetches from upstream first, falling back to origin.
Full-history requirement (fail-stop): discovery is
git log <base>..HEAD -- <packagePath>. Before generating:
git rev-parse --is-shallow-repositorymust befalse(elsegit fetch --unshallow/ re-clone without--depth, then stop).- Base must be an ancestor of
HEAD(git merge-base --is-ancestor <base> HEAD); otherwise check out the release tip / package tag first.
The generator enforces both checks and exits non-zero on failure.
Run git tag --list "<package>-v*" --sort=-v:refname to check for existing version tags.
- If tags exist: the script auto-detects the release type from
package.jsonversion:- Minor/major release (version ends in
.0, e.g.0.9.0): uses the latest.0tag as base (e.g.sdk-v0.8.0), skipping patch tags - Patch release (version ends in non-zero patch, e.g.
0.8.4): uses the absolute latest tag as base (e.g.sdk-v0.8.3)
- Minor/major release (version ends in
- If no tags: ask the user for
--base-commitand--base-version(migration scenario)
Why this matters: patches ship on separate release branches and get backmerged into main.
Using the latest patch tag as base for a minor release would miss all PRs that landed on main
between the previous minor release and the last backmerge. The correct base for a minor release
is the previous minor's .0 tag.
Step 3: Generate Raw Changelog
All SDK pod packages use the same command:
node scripts/sdk/generate-changelog-sdk-pod.cjs --package=<name>
With migration flags:
node scripts/sdk/generate-changelog-sdk-pod.cjs --package=<name> --base-commit=<sha> --base-version=<version>
The script automatically excludes:
- PRs tagged
[skiplog]. - Backmerge PRs (subjects starting with
BackmergeorMerge release …). Backmerges merge a release branch back into main; their content is already documented in the release branch's own changelog, so listing them here is noise. - PRs whose title fails the SDK PR-format validator (these are warned, not silently dropped — fix the title and re-run, or surface to the PR author).
For [mod] PRs, the script extracts the Added/Updated/Removed model lists
from the PR body and renders them as indented continuation lines beneath the
bullet in CHANGELOG.md (each section on its own line — never inline as one
giant row). The same filtered lists are written to models.md.
The extractor applies two policies (in this order):
- Companion entries are dropped. Companions are auxiliary files that ship
alongside a primary model but aren't independently usable — vocab files,
lexicons, raw data shards, metadata blobs. The filter recognises constant
suffixes (
*_LEX,*_VOCAB,*_DATA,*_METADATA) and any free-form description containing the word "companion". Only first-class models reach the changelog. - Entry-count suffixes are stripped.
(N entries)/(N entries — short note)decorations are removed from the displayed text — readers can follow themodels.mdlink for exact counts.
After both filters, each section is trimmed to MAX_INLINE_MODELS (currently
5) entries, with (and N more) for the remainder. Example:
- Regenerate model registry. (see PR [#123](...)) - See [model changes](./models.md)
Added: NMT_Q0F16, NMT_Q4_0 (and 12 more)
Removed: MARIAN_OPUS_*
If after filtering a section is empty, it's omitted. If all sections are empty the bullet emits with no continuation lines.
When writing the human-readable CHANGELOG_LLM.md (Step 4), apply the same
"no informational value" rule manually: skip backmerges, automated bumps, and any
entry whose subject would just repeat what a previous release already said. For
the Models section, mirror the script's policy — keep it concise in the body
(highlight the most notable adds/removes) and defer the full constant list to
the ### Added / ### Removed blocks at the bottom.
Step 4: Generate CHANGELOG_LLM.md (mandatory)
Always run this step. Do not ask the user — it's part of the skill.
After raw changelog files exist, generate the human-readable version at
packages/<package>/changelog/<version>/CHANGELOG_LLM.md.
See references/changelog-llm-format.md for the format guide.
After writing the file, re-run the raw generator (or rebuild the root aggregate) so
packages/<package>/CHANGELOG.md picks up the new CHANGELOG_LLM.md (the aggregator
prefers it over CHANGELOG.md). Easiest way: re-run the script from Step 3 — it's idempotent.
Format the generated markdown (mandatory). CHANGELOG_LLM.md is authored by
hand here, so it is the file most likely to carry markdown formatting issues that a
committed-file format check would later reject. Every SDK pod package uses prettier
(format = prettier --check ., format:fix = prettier --write .). Run the check
scoped to the changelog output so any issue surfaces now:
cd packages/<package>
bunx prettier --check "changelog/**/*.md" "CHANGELOG.md"
If it reports problems, fix them — bunx prettier --write on the same paths, or hand-edit —
and re-run the check until it passes clean. Do this before moving on so the release commit
carries only prettier-clean markdown.
Downstream rendering note: the docs site reads CHANGELOG_LLM.md
verbatim and inlines it under a ### @qvac/<pkg> subsection of the
minor series page (one permanent v<X.Y>.x.mdx per minor line — see
docs/website/docs-workflow.md). Each headline you write becomes a
section header on the public docs site (with two levels of demotion to
fit the nesting), so phrase them as standalone reader-facing prose, not
internal categories. Keep headings emoji-free (e.g. ## Breaking Changes, not ## 💥 Breaking Changes) — emoji prefixes leak verbatim
into the public headers; the only allowed emoji is the 📦 **NPM:**
line. See the format guide for the full rule.
Step 5: Generate announcement-post.txt (mandatory)
Always run this step after Step 4. It produces a Slack-ready copy-paste post at
packages/<package>/changelog/<version>/announcement-post.txt.
The file is gitignored (packages/*/changelog/*/announcement-post.txt) — it's a
local working artifact, not a committed deliverable. Never git add it.
node scripts/sdk/generate-changelog-sdk-pod.cjs --package=<name> --generate-announcement-post
The script emits the short Slack template — header + three links + optional breaking-changes block + footer. Per-section bullet lists are intentionally omitted; readers follow the full-changelog link for the detail.
Layout:
:qvac: SDK <version> :rocket: NPM Public releaseheader.- NPM, GitHub release, and full-changelog tree links.
:warning: Breaking Changessection with link tobreaking.md— emitted only whenbreaking.mdexists in the version folder (i.e. at least one PR carries the[bc]tag). Detected by file presence, not by parsing CHANGELOG.md.- Footer:
Thanks to everyone on QVAC team :green_heart: :qvac: :green_heart:.
If the post needs hand-tuning (e.g. a custom note for a specific release), edit the file directly. It's gitignored, so changes won't pollute the diff.
Step 6: Update NOTICE file for the target package
After Step 5 completes, run notice-generate for the same --package to ensure
its NOTICE file reflects any dependency changes in the release:
source .env
node .agents/skills/qv-notice-generate/scripts/generate-notice.js <package-name>
Do NOT commit the announcement post (gitignored) and let the user review the rest before committing.
See .agents/skills/qv-notice-generate/SKILL.md for full details.
Step 7: Sync lockstep clients (only when --package=sdk)
@qvac/sdk and tetherto-qvac-sdk release in lockstep at the
@qvac/inference version anchor. Every sdk release must stamp that anchor into
sdk and regenerate the Python
client (SDK_VERSION and other _generated/ outputs). Skip this step for any
other --package value.
Read and follow .agents/skills/qv-sdk-lockstep-sync/SKILL.md (Steps 1–3).
Short form:
node .agents/skills/qv-sdk-lockstep-sync/scripts/sync-sdk-pod.mjs
cd packages/sdk-python
.venv/bin/python3 scripts/generate.py
.venv/bin/python3 scripts/generate.py --check
Include sdk-python generated updates in the release commit. The Python
client does not get its own changelog — history lives in packages/sdk/CHANGELOG.md.
Step 8: Generate site docs (only when --package=sdk)
Generate the documentation-site API reference and release notes for the new
version in the same working tree, so the changelog PR also carries the docs
update. This replaces the old standalone docs-release.yml workflow (which
opened a second, separate docs PR). Skip this step entirely for any other
--package value — only the SDK release drives the versioned docs site.
Generation is deterministic: it runs the existing docs/website scripts
(TypeDoc + Nunjucks render + verbatim CHANGELOG_LLM.md inlining). No LLM is
involved in producing the API reference or release notes here — Step 4 already
authored CHANGELOG_LLM.md, and this step only renders it into the site.
Prerequisites:
docs/websitedependencies installed (cd docs/website && npm install).SDK_PATHset indocs/website/.envpointing at the SDK package root (packages/sdk, the directory containingindex.tsandtsconfig.json). Copydocs/website/.env.exampleto.envif it doesn't exist yet.CHANGELOG_REPO_ROOTdefaults to the repo root, so no override is needed when running inside the monorepo.
1. Generate the API reference + release notes (auto-detects minor vs patch):
cd docs/website
bun run scripts/release-version.ts <version> --force-extract
This is the exact command the old workflow ran. The dispatcher reads the
version and forwards to the minor (X.Y.0: generate the new series' MDX at
reference/{api,release-notes}/v<X.Y>.x.mdx, rewrite both index.mdx shims
to <include> the new series file, and rotate the managed alias block in
public/_redirects so the new v<X.Y>.x URL 301s to the shim canonical)
or patch (X.Y.Z, Z >= 1: insert the ## vX.Y.Z section into the target
series' v<X.Y>.x.mdx; for patch-latest, also mirror the refreshed
description onto the release-notes shim) orchestrator. It writes only:
docs/website/content/docs/reference/api/**(API summary MDX)docs/website/content/docs/reference/release-notes/**(release notes MDX)docs/website/src/lib/versions.ts(version-switcher manifest)docs/website/public/_redirects(minor only — the managed# ==== BEGIN latest-series alias (managed) ====block; patches never touch this file)
2. Verify the site still builds (mandatory):
cd docs/website
npm run build
A clean build confirms nothing on the website broke. Treat a build failure as fail-stop: surface the error and do NOT proceed to commit until it's fixed.
Staging follows the same convention as the other steps. Like every other
step, this one only generates files — it never runs git add or git commit.
The three surfaces above are part of the release commit (same as Step 7's
lockstep-client files: "Include … in the release commit"), and every
generation/build byproduct is gitignored — exactly like Step 5's
announcement-post.txt — so a normal git status review shows only the
committable files. Let the user review before committing. Generated + gitignored
byproducts (do not git add them):
docs/website/scripts/api-docs/api-data.json(written byrelease-version.ts)docs/website/.next/,.source/,out/,dist/(fromnpm run build)docs/website/next-env.d.tspackages/sdk/dist/(from theprebuild:examplesbuild step)
See docs/website/docs-workflow.md for the full pipeline reference.
CLI Parameters
| Flag | Required | Description |
|---|---|---|
--package | Yes | Package name (e.g., sdk) |
--base-commit | No | Initial commit SHA for migration (overrides tag lookup) |
--base-version | No | Version label for base commit (display only) |
--release-type | No | minor or patch (auto-detected from package.json version) |
--dry-run | No | Preview output without writing files |
--update-root-changelog | No | Rebuild only the root aggregate packages/<pkg>/CHANGELOG.md |
--generate-announcement-post | No | Generate announcement-post.txt for the package's current version |
--version | No | Override version when used with --generate-announcement-post |
Output
Generates changelog files in packages/<package>/changelog/<version>/:
CHANGELOG.md- Main changelogbreaking.md- Breaking changes detail (if[bc]PRs)api.md- API changes detail (if[api]PRs)models.md- Model changes (if[mod]PRs)CHANGELOG_LLM.md- Human-readable version (always generated, see Step 4)announcement-post.txt- Slack copy-paste post (always generated, see Step 5, gitignored — never commit)
Additionally:
packages/<package>/CHANGELOG.md– Aggregated changelog containing all versions (newest → oldest), preferringCHANGELOG_LLM.md(human-readable) from each version folder when available, falling back toCHANGELOG.md
When --package=sdk, Step 8 also generates the documentation-site surfaces
(commit these alongside the changelog):
docs/website/content/docs/reference/api/**– API reference MDXdocs/website/content/docs/reference/release-notes/**– Release notes MDXdocs/website/src/lib/versions.ts– Version-switcher manifestdocs/website/public/_redirects– minor releases only — the managed latest-series alias block (delimited by# ==== BEGIN latest-series alias (managed) ====markers). Patch releases never touch this file.
Tag Format
Tags follow the pattern: <package>-v<x.y.z> and are created on upstream (not the fork).
Examples:
sdk-v0.8.0(minor — used as base for next minor release)sdk-v0.8.1(patch — used as base for next patch release)rag-v2.0.0
Quality Checklist
Before completing:
- Correct package identified
- Working head (if branched for the release PR) is
chore/<pkg>-<x.y.z>-changelog, notrelease-* - Clone is not shallow (
git rev-parse --is-shallow-repository→false) - Base reference resolved (tag or
--base-commit) and is an ancestor ofHEAD - PRs scoped to package path only
- Changelog files written to correct version directory
- CHANGELOG_LLM.md generated (mandatory) and follows format guide
- Generated markdown is prettier-clean (
prettier --checkon the changelog output passes) - announcement-post.txt generated (mandatory, gitignored)
- NOTICE file updated for the target package
- When
--package=sdk:qv-sdk-lockstep-syncrun (sdk-python), pythongenerate.py --checkpassing - When
--package=sdk: site docs generated viarelease-version.ts,npm run buildpassed, andgit statusshows onlyreference/api/**,reference/release-notes/**,src/lib/versions.ts(andpublic/_redirectson minor releases — the managed latest-series alias block) as committable docs changes (byproducts gitignored) - Root CHANGELOG.md rebuilt from all version folders (and picks up CHANGELOG_LLM.md)
- Versions sorted in descending semver order
- No duplicated versions
- Root file is deterministic (fully regenerated)
References
- SDK pod ownership:
.github/teams/sdk.json - GitFlow and PR format:
docs/gitflow.md - LLM changelog format: references/changelog-llm-format.md
- NOTICE generation:
.agents/skills/qv-notice-generate/SKILL.md - sdk lockstep clients:
.agents/skills/qv-sdk-lockstep-sync/SKILL.md - Docs site pipeline (Step 8):
docs/website/docs-workflow.md - Release PR branch naming (org
release-*push / Merge Guard):.agents/skills/qv-sdk-pr-create/SKILL.md
Signals
- GitHub stars
- 601
- Forks
- 111
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
qv-sdk-changelog- Source
- github.com/tetherto/qvac