# Qv Sdk Changelog

> Generate changelogs for SDK pod packages using tag-based GitFlow. Use when preparing a release, generating changelog, or creating CHANGELOG_LLM.md.

- Skill: `tetherto/qv-sdk-changelog` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tetherto/qv-sdk-changelog`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tetherto/qv-sdk-changelog/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tetherto (https://skillmd.com/u/tetherto)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tetherto/qv-sdk-changelog

---


# SDK Changelog Generation

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:

1. `git rev-parse --is-shallow-repository` must be `false` (else
   `git fetch --unshallow` / re-clone without `--depth`, then stop).
2. 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.json` version:
  - **Minor/major release** (version ends in `.0`, e.g. `0.9.0`): uses the latest `.0` tag 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`)
- If no tags: ask the user for `--base-commit` and `--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:

```bash
node scripts/sdk/generate-changelog-sdk-pod.cjs --package=<name>
```

With migration flags:

```bash
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 `Backmerge` or `Merge 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):

1. **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.
2. **Entry-count suffixes are stripped.** `(N entries)` /
   `(N entries — short note)` decorations are removed from the displayed
   text — readers can follow the `models.md` link 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](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:

```bash
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.

```bash
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 release` header.
- NPM, GitHub release, and full-changelog tree links.
- `:warning: Breaking Changes` section with link to `breaking.md` — emitted
  only when `breaking.md` exists 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:

```bash
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:

```bash
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/website` dependencies installed (`cd docs/website && npm install`).
- `SDK_PATH` set in `docs/website/.env` pointing at the SDK package root
  (`packages/sdk`, the directory containing `index.ts` and `tsconfig.json`).
  Copy `docs/website/.env.example` to `.env` if it doesn't exist yet.
  `CHANGELOG_REPO_ROOT` defaults 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):**

```bash
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):**

```bash
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 by `release-version.ts`)
- `docs/website/.next/`, `.source/`, `out/`, `dist/` (from `npm run build`)
- `docs/website/next-env.d.ts`
- `packages/sdk/dist/` (from the `prebuild:examples` build 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 changelog
- `breaking.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), preferring `CHANGELOG_LLM.md` (human-readable) from each version folder when available, falling back to `CHANGELOG.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 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 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`, not `release-*`
- [ ] Clone is not shallow (`git rev-parse --is-shallow-repository` → `false`)
- [ ] Base reference resolved (tag or `--base-commit`) and is an ancestor of `HEAD`
- [ ] 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 --check` on the changelog output passes)
- [ ] announcement-post.txt generated (mandatory, gitignored)
- [ ] NOTICE file updated for the target package
- [ ] When `--package=sdk`: `qv-sdk-lockstep-sync` run (sdk-python), python `generate.py --check` passing
- [ ] When `--package=sdk`: site docs generated via `release-version.ts`, `npm run build` passed, and `git status` shows only `reference/api/**`, `reference/release-notes/**`, `src/lib/versions.ts` (and `public/_redirects` on **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](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`

