From 82e6879b841596aa2aadf682270b5bb9bab8805d Mon Sep 17 00:00:00 2001 From: opaninakuffo Date: Thu, 17 Sep 2026 21:58:27 +0000 Subject: [PATCH 1/6] chore[notask]: document changelog lockstep base and holepunch prettier --- .agents/skills/qv-sdk-changelog/SKILL.md | 88 ++++++++++++++++++++---- 1 file changed, 73 insertions(+), 15 deletions(-) diff --git a/.agents/skills/qv-sdk-changelog/SKILL.md b/.agents/skills/qv-sdk-changelog/SKILL.md index 4cb7d44484..5746624e0e 100644 --- a/.agents/skills/qv-sdk-changelog/SKILL.md +++ b/.agents/skills/qv-sdk-changelog/SKILL.md @@ -30,13 +30,19 @@ If the user doesn't specify, ask which SDK pod package they want to generate a c Package slugs match git tags (`sdk`, `inference`, `cli`, `ai-sdk-provider`, `opencode-plugin`, `openclaw-plugin`, …). Directory resolution (including `plugins/*`) is in `scripts/sdk/package-paths.cjs`. -**`sdk` and `inference` are separate releases that share a major and minor.** Moving to a -new major.minor is two changelogs and two releases, engine first: `--package=inference` -for `release-inference-`, then `--package=sdk` for `release-sdk-`. Engine -commits are scanned into the SDK changelog too (`CHANGELOG_EXTRA_SCAN_DIRS` in -`scripts/sdk/package-paths.cjs`), so the engine's user-facing changes appear in the SDK -release notes as well — the SDK is where consumers read them. A patch on either side is -one release of its own. +**`sdk` and `inference` are lockstep on major.minor.** Two changelogs, two +releases, engine first: `--package=inference` for `release-inference-`, +then `--package=sdk` for `release-sdk-`. **Both changelogs for the same +`x.y.z` start at the same base** — the last shipped lockstep version, including +its latest patch (e.g. both 0.20.0 notes start at 0.19.1). Do not take inference +from the previous `.0` while SDK used the last patch; that duplicates already- +shipped notes on the engine and makes the two files disagree. + +The SDK is the consumer-facing full notes. `--package=sdk` also scans +`packages/inference` (`CHANGELOG_EXTRA_SCAN_DIRS` in +`scripts/sdk/package-paths.cjs`), so engine work that landed after that shared +base must appear in the SDK changelog. The inference changelog is the engine-only +slice from that same base. A patch on either side is one release of its own. **Working branch (when cutting from a release line):** use `chore/--changelog` (e.g. `chore/sdk-0.17.0-changelog`). Do **not** @@ -70,8 +76,27 @@ Run `git tag --list "-v*" --sort=-v:refname` to check for existing vers **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. +between the previous minor release and the last backmerge. The correct base for a **standalone** +minor (cli, plugins, a package not lockstep with another) is the previous minor's `.0` tag. + +**Lockstep exception (`sdk` + `inference` at the same major.minor):** ignore the +`.0` default. Both packages use the **same** `--base-commit` / `--base-version` as +the last lockstep ship (often the last patch backmerge on main). Find it from the +SHA the SDK changelog for that version used, or: + +```bash +git log --oneline -- packages/inference/package.json +# skiplog backmerge that landed the last lockstep version on main +``` + +**`inference-v*` / historical `sdk-v*` tags are often not ancestors of `main`.** +Inference tags used to live on `release-sdk-*`; after split-publish they live on +`release-inference-*`. Auto-detected tags then fail `merge-base --is-ancestor`. +Pass `--base-commit= --base-version=` instead of relying on the tag. + +**Nested worktrees:** unset stale `GIT_DIR` / `GIT_WORK_TREE` from a parent Cursor +session before any git or changelog command (`unset GIT_DIR GIT_WORK_TREE`). +Otherwise the generator runs against the parent repo. ### Step 3: Generate Raw Changelog @@ -148,17 +173,27 @@ prefers it over `CHANGELOG.md`). Easiest way: re-run the script from Step 3 — **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: +(`format` = `prettier --check .`, `format:fix` = `prettier --write .`) with +`.prettierrc` set to `"prettier-config-holepunch"`. **Never `--no-config`.** CI +(`[inference] format`, `[sdk] format`, …) loads holepunch; `--no-config` or a +different parser (quote style, trailing commas) is the usual red we hit. + +`bunx prettier` from a worktree **without** `node_modules` fails with +`Cannot find package 'prettier-config-holepunch'` and then either skips the +check or formats with a fallback that CI will reject. Install first, then check +the same way CI does: ```bash cd packages/ +# if this worktree has no node_modules: +bun install bunx prettier --check "changelog/**/*.md" "CHANGELOG.md" +# or, matching CI: bun run format ``` -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. +If it reports problems, fix them — `bunx prettier --write` on the same paths, or +`bun run format:fix` — 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/` subsection of the @@ -363,6 +398,26 @@ Examples: - `sdk-v0.8.1` (patch — used as base for next patch release) - `rag-v2.0.0` +## Repeated footguns + +These have gone red on more than one SDK-pod changelog PR. Fix them before +push, and keep this list to things that are cheap to prevent: + +- **Prettier is holepunch, not stock.** `.prettierrc` is `"prettier-config-holepunch"`. + Never `--no-config`. Never `bunx prettier` until `packages//node_modules` + (or a linked install) can resolve that package. Quote style and trailing commas + on `CHANGELOG_LLM.md` are the usual fail. +- **Lockstep base.** `sdk` and `inference` at the same `x.y.z` share one + `--base-commit` / `--base-version` (last lockstep ship, including last patch). + SDK notes are the full consumer set; inference is the engine slice of that set. +- **`inference-v*` is often not on `main`.** Use the backmerge SHA, not the tag. +- **Nested worktrees inherit `GIT_DIR`.** `unset GIT_DIR GIT_WORK_TREE` before + generate/commit/cherry-pick, or you operate on the parent repo. +- **Push the org remote** (`upstream` when that is `tetherto/qvac`), not the + contributor fork. `git push` with no remote follows `origin`. +- **Do not skip SDK Pod Checks.** Workspace red vs published red is a real + signal; `[skip-sdk-pod-checks]` is not the changelog fix. + ## Quality Checklist Before completing: @@ -371,10 +426,12 @@ Before completing: - [ ] Working head (if branched for the release PR) is `chore/--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` +- [ ] For lockstep `sdk` + `inference` at the same major.minor: both used the **same** `--base-commit` / `--base-version` (last lockstep ship, including last patch); SDK changelog includes the engine slice +- [ ] `GIT_DIR` / `GIT_WORK_TREE` unset (or pointed at this worktree) so generate/git did not run in a parent repo - [ ] 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) +- [ ] Generated markdown is prettier-clean with **prettier-config-holepunch** resolved (`bun install` in `packages/` if needed; never `--no-config`) - [ ] announcement-post.txt generated (mandatory, gitignored) - [ ] NOTICE file updated for the target package - [ ] When `--package=sdk`: `qv-sdk-inference-version` run (engine version published, sdk version and `@qvac/inference` range sharing a major.minor, sdk-python regenerated), python `generate.py --check` passing @@ -383,6 +440,7 @@ Before completing: - [ ] Versions sorted in descending semver order - [ ] No duplicated versions - [ ] Root file is deterministic (fully regenerated) +- [ ] Org remote (`upstream` when that is tetherto/qvac) is the push target, not the fork ## References From 3cd8f068860b48c36b2c8fb523eff38fab0daa49 Mon Sep 17 00:00:00 2001 From: opaninakuffo Date: Fri, 18 Sep 2026 09:24:54 +0000 Subject: [PATCH 2/6] chore[notask]: require a patch-gap audit on lockstep changelog minors --- .agents/skills/qv-sdk-changelog/SKILL.md | 73 +++++++++++++++++------- 1 file changed, 52 insertions(+), 21 deletions(-) diff --git a/.agents/skills/qv-sdk-changelog/SKILL.md b/.agents/skills/qv-sdk-changelog/SKILL.md index 5746624e0e..7fcffd8032 100644 --- a/.agents/skills/qv-sdk-changelog/SKILL.md +++ b/.agents/skills/qv-sdk-changelog/SKILL.md @@ -32,17 +32,20 @@ Package slugs match git tags (`sdk`, `inference`, `cli`, `ai-sdk-provider`, `ope **`sdk` and `inference` are lockstep on major.minor.** Two changelogs, two releases, engine first: `--package=inference` for `release-inference-`, -then `--package=sdk` for `release-sdk-`. **Both changelogs for the same -`x.y.z` start at the same base** — the last shipped lockstep version, including -its latest patch (e.g. both 0.20.0 notes start at 0.19.1). Do not take inference -from the previous `.0` while SDK used the last patch; that duplicates already- -shipped notes on the engine and makes the two files disagree. +then `--package=sdk` for `release-sdk-`. Both notes for the same `x.y.z` +share one **display floor** so they do not re-list an intervening patch (e.g. +0.20.0 should not repeat 0.19.1). That floor is `--base-commit` = the last +lockstep patch backmerge on main, **plus** the patch-gap audit in Step 2. +Do not take inference from the previous `.0` while SDK used the last patch: +that duplicates already-shipped patch notes and makes the two files disagree. +Do not treat that shared `--base-commit` as the whole story either: `git log +..HEAD` cannot see main-only PRs that landed while the patch +branch was open. The SDK is the consumer-facing full notes. `--package=sdk` also scans `packages/inference` (`CHANGELOG_EXTRA_SCAN_DIRS` in -`scripts/sdk/package-paths.cjs`), so engine work that landed after that shared -base must appear in the SDK changelog. The inference changelog is the engine-only -slice from that same base. A patch on either side is one release of its own. +`scripts/sdk/package-paths.cjs`). The inference changelog is the engine-only +slice of that same set. A patch on either side is one release of its own. **Working branch (when cutting from a release line):** use `chore/--changelog` (e.g. `chore/sdk-0.17.0-changelog`). Do **not** @@ -75,18 +78,43 @@ Run `git tag --list "-v*" --sort=-v:refname` to check for existing vers - 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 **standalone** -minor (cli, plugins, a package not lockstep with another) is the previous minor's `.0` tag. +Using the latest patch tag as `--base-commit` for a minor would miss every PR that +landed on **main** between the previous `.0` and the patch backmerge and was never +cherry-picked onto the patch branch. The generator range is `git log ..HEAD`, +so those commits are already ancestors of the backmerge and silently disappear. +Exhibit: [#4384](https://github.com/tetherto/qvac/pull/4384) merged to main during +SDK 0.19.1, never sat on `release-sdk-0.19.1`, and dropped out of both 0.19.1 and +0.20.0 notes until it was added by hand. + +The correct `--base-commit` for a **standalone** minor (cli, plugins, a package +not lockstep with another) is still the previous minor's `.0` tag. + +**Lockstep `sdk` + `inference` at the same major.minor:** both packages use the +**same** `--base-commit` / `--base-version` as the last lockstep *display floor* +(the last patch backmerge, so 0.19.1 is not duplicated). Then, when that floor is +a patch (version not `.0`), the gap audit below is mandatory — not optional, not +"if you remember 4384". Find the backmerge SHA from the SDK changelog for that +version, or: -**Lockstep exception (`sdk` + `inference` at the same major.minor):** ignore the -`.0` default. Both packages use the **same** `--base-commit` / `--base-version` as -the last lockstep ship (often the last patch backmerge on main). Find it from the -SHA the SDK changelog for that version used, or: +```bash +git log --oneline --grep='backmerge release-sdk-' -20 +``` + +**Patch-gap audit (mandatory on lockstep minors whose previous ship was a patch).** +After the raw generate, list first-parent merges on main from the previous lockstep +`.0` backmerge to the patch backmerge that touch `packages/inference` or +`packages/sdk`. Drop `[skiplog]`. Drop PR numbers already in +`packages/sdk/changelog//CHANGELOG.md`. Anything left is in this release's +*code* but in no notes — hand-add it to **both** lockstep changelogs (`CHANGELOG.md`, +`CHANGELOG_LLM.md`, and `breaking.md` / `models.md` / `api.md` when the PR tag +requires them). Do not re-run the generator over the hand edits; use +`--update-root-changelog` only. ```bash -git log --oneline -- packages/inference/package.json -# skiplog backmerge that landed the last lockstep version on main +PREV_MINOR_BACKMERGE= # e.g. backmerge release-sdk-0.19.0 +PATCH_BACKMERGE= # e.g. backmerge release-sdk-0.19.1 +git log --first-parent --format='%s' ${PREV_MINOR_BACKMERGE}..${PATCH_BACKMERGE} +# then keep subjects whose merge diff touches packages/inference or packages/sdk ``` **`inference-v*` / historical `sdk-v*` tags are often not ancestors of `main`.** @@ -407,9 +435,11 @@ push, and keep this list to things that are cheap to prevent: Never `--no-config`. Never `bunx prettier` until `packages//node_modules` (or a linked install) can resolve that package. Quote style and trailing commas on `CHANGELOG_LLM.md` are the usual fail. -- **Lockstep base.** `sdk` and `inference` at the same `x.y.z` share one - `--base-commit` / `--base-version` (last lockstep ship, including last patch). - SDK notes are the full consumer set; inference is the engine slice of that set. +- **Lockstep display floor is not the whole range.** `sdk` and `inference` at + the same `x.y.z` share one `--base-commit` (last patch backmerge) so patch + notes are not duplicated. That range misses main-only PRs during the patch + cut. Run the Step 2 patch-gap audit; do not "ignore the `.0` default" without + it. SDK notes are the full consumer set; inference is the engine slice. - **`inference-v*` is often not on `main`.** Use the backmerge SHA, not the tag. - **Nested worktrees inherit `GIT_DIR`.** `unset GIT_DIR GIT_WORK_TREE` before generate/commit/cherry-pick, or you operate on the parent repo. @@ -426,7 +456,8 @@ Before completing: - [ ] Working head (if branched for the release PR) is `chore/--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` -- [ ] For lockstep `sdk` + `inference` at the same major.minor: both used the **same** `--base-commit` / `--base-version` (last lockstep ship, including last patch); SDK changelog includes the engine slice +- [ ] For lockstep `sdk` + `inference` at the same major.minor: both used the **same** `--base-commit` / `--base-version` (last lockstep display floor); SDK changelog includes the engine slice +- [ ] When that floor is a patch: patch-gap audit ran (previous `.0` backmerge → patch backmerge, minus skiplog, minus PRs already in the patch notes) and leftovers were hand-added to both changelogs - [ ] `GIT_DIR` / `GIT_WORK_TREE` unset (or pointed at this worktree) so generate/git did not run in a parent repo - [ ] PRs scoped to package path only - [ ] Changelog files written to correct version directory From f496669f590ebd4e5cb4fe7f4f886df060fd24af Mon Sep 17 00:00:00 2001 From: opaninakuffo Date: Fri, 18 Sep 2026 09:55:35 +0000 Subject: [PATCH 3/6] chore[notask]: generalize lockstep changelog floor and main-only union --- .agents/skills/qv-sdk-changelog/SKILL.md | 87 +++++++++++------------- 1 file changed, 39 insertions(+), 48 deletions(-) diff --git a/.agents/skills/qv-sdk-changelog/SKILL.md b/.agents/skills/qv-sdk-changelog/SKILL.md index 7fcffd8032..1210933bc0 100644 --- a/.agents/skills/qv-sdk-changelog/SKILL.md +++ b/.agents/skills/qv-sdk-changelog/SKILL.md @@ -33,14 +33,14 @@ Package slugs match git tags (`sdk`, `inference`, `cli`, `ai-sdk-provider`, `ope **`sdk` and `inference` are lockstep on major.minor.** Two changelogs, two releases, engine first: `--package=inference` for `release-inference-`, then `--package=sdk` for `release-sdk-`. Both notes for the same `x.y.z` -share one **display floor** so they do not re-list an intervening patch (e.g. -0.20.0 should not repeat 0.19.1). That floor is `--base-commit` = the last -lockstep patch backmerge on main, **plus** the patch-gap audit in Step 2. -Do not take inference from the previous `.0` while SDK used the last patch: -that duplicates already-shipped patch notes and makes the two files disagree. -Do not treat that shared `--base-commit` as the whole story either: `git log -..HEAD` cannot see main-only PRs that landed while the patch -branch was open. +share one **display floor**: the last lockstep version already shipped (patch or +`.0`). `--base-commit` is that version's backmerge on main so the new notes do +not repeat it. Use the same floor on both packages; splitting them (inference +from the previous `.0`, SDK from a later patch) duplicates patch notes and +makes the files disagree. + +`--base-commit` is only the generate range, not the full set of notes. See +Step 2 when that floor is a patch. The SDK is the consumer-facing full notes. `--package=sdk` also scans `packages/inference` (`CHANGELOG_EXTRA_SCAN_DIRS` in @@ -77,44 +77,35 @@ Run `git tag --list "-v*" --sort=-v:refname` to check for existing vers - **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-commit` for a minor would miss every PR that -landed on **main** between the previous `.0` and the patch backmerge and was never -cherry-picked onto the patch branch. The generator range is `git log ..HEAD`, -so those commits are already ancestors of the backmerge and silently disappear. -Exhibit: [#4384](https://github.com/tetherto/qvac/pull/4384) merged to main during -SDK 0.19.1, never sat on `release-sdk-0.19.1`, and dropped out of both 0.19.1 and -0.20.0 notes until it was added by hand. - -The correct `--base-commit` for a **standalone** minor (cli, plugins, a package -not lockstep with another) is still the previous minor's `.0` tag. - -**Lockstep `sdk` + `inference` at the same major.minor:** both packages use the -**same** `--base-commit` / `--base-version` as the last lockstep *display floor* -(the last patch backmerge, so 0.19.1 is not duplicated). Then, when that floor is -a patch (version not `.0`), the gap audit below is mandatory — not optional, not -"if you remember 4384". Find the backmerge SHA from the SDK changelog for that -version, or: +**Why this matters:** patches ship on a `release--*` branch and backmerge +into main. The generator's range is `git log ..HEAD`. A commit that landed +on **main** while that patch branch was open, and was never on the patch branch, +is already an ancestor of the backmerge, so it is invisible to that range. It +also never appeared in the patch notes. The next minor then ships the code +with no changelog line unless those PRs are unioned in. -```bash -git log --oneline --grep='backmerge release-sdk-' -20 -``` +**Standalone minor** (cli, plugins, anything not lockstep with another package): +`--base-commit` is the previous `.0`. That range still includes main-only work +in the patch window, and it will also re-list the patch. Accept the overlap or +drop PR numbers already in `changelog//`. + +**Lockstep `sdk` + `inference`:** both packages use the **same** `--base-commit` +/ `--base-version` — the last lockstep display floor. When that floor is a +`.0`, generate is enough. When it is a **patch**, the notes are: + +1. Generator output from the patch backmerge (work after the floor). +2. Plus main-only work between the previous lockstep `.0` backmerge and that + patch backmerge: first-parent merges touching `packages/inference` or + `packages/sdk`, minus `[skiplog]`, minus PR numbers already in + `packages/sdk/changelog//CHANGELOG.md`. -**Patch-gap audit (mandatory on lockstep minors whose previous ship was a patch).** -After the raw generate, list first-parent merges on main from the previous lockstep -`.0` backmerge to the patch backmerge that touch `packages/inference` or -`packages/sdk`. Drop `[skiplog]`. Drop PR numbers already in -`packages/sdk/changelog//CHANGELOG.md`. Anything left is in this release's -*code* but in no notes — hand-add it to **both** lockstep changelogs (`CHANGELOG.md`, -`CHANGELOG_LLM.md`, and `breaking.md` / `models.md` / `api.md` when the PR tag -requires them). Do not re-run the generator over the hand edits; use -`--update-root-changelog` only. +Hand-add (2) to **both** lockstep changelogs (`CHANGELOG.md`, `CHANGELOG_LLM.md`, +and `breaking.md` / `models.md` / `api.md` when the PR tag requires them). Do +not re-run the generator over the hand edits; `--update-root-changelog` only. ```bash -PREV_MINOR_BACKMERGE= # e.g. backmerge release-sdk-0.19.0 -PATCH_BACKMERGE= # e.g. backmerge release-sdk-0.19.1 -git log --first-parent --format='%s' ${PREV_MINOR_BACKMERGE}..${PATCH_BACKMERGE} -# then keep subjects whose merge diff touches packages/inference or packages/sdk +git log --first-parent --format='%s' .. +# keep subjects whose merge diff touches packages/inference or packages/sdk ``` **`inference-v*` / historical `sdk-v*` tags are often not ancestors of `main`.** @@ -435,11 +426,11 @@ push, and keep this list to things that are cheap to prevent: Never `--no-config`. Never `bunx prettier` until `packages//node_modules` (or a linked install) can resolve that package. Quote style and trailing commas on `CHANGELOG_LLM.md` are the usual fail. -- **Lockstep display floor is not the whole range.** `sdk` and `inference` at - the same `x.y.z` share one `--base-commit` (last patch backmerge) so patch - notes are not duplicated. That range misses main-only PRs during the patch - cut. Run the Step 2 patch-gap audit; do not "ignore the `.0` default" without - it. SDK notes are the full consumer set; inference is the engine slice. +- **Lockstep display floor is not the whole range.** Same `--base-commit` on + `sdk` and `inference` so already-shipped notes are not repeated. When that + floor is a patch, union in the Step 2 main-only window (previous lockstep + `.0` backmerge → patch backmerge, minus skiplog, minus the patch notes). + SDK is the consumer set; inference is the engine slice. - **`inference-v*` is often not on `main`.** Use the backmerge SHA, not the tag. - **Nested worktrees inherit `GIT_DIR`.** `unset GIT_DIR GIT_WORK_TREE` before generate/commit/cherry-pick, or you operate on the parent repo. @@ -457,7 +448,7 @@ Before completing: - [ ] Clone is not shallow (`git rev-parse --is-shallow-repository` → `false`) - [ ] Base reference resolved (tag or `--base-commit`) and is an ancestor of `HEAD` - [ ] For lockstep `sdk` + `inference` at the same major.minor: both used the **same** `--base-commit` / `--base-version` (last lockstep display floor); SDK changelog includes the engine slice -- [ ] When that floor is a patch: patch-gap audit ran (previous `.0` backmerge → patch backmerge, minus skiplog, minus PRs already in the patch notes) and leftovers were hand-added to both changelogs +- [ ] When that floor is a patch: main-only window unioned in (previous lockstep `.0` backmerge → patch backmerge, minus skiplog, minus the patch notes) on both changelogs - [ ] `GIT_DIR` / `GIT_WORK_TREE` unset (or pointed at this worktree) so generate/git did not run in a parent repo - [ ] PRs scoped to package path only - [ ] Changelog files written to correct version directory From 89d35ef8b0ceddefefd19cdcaee1919bccd9ae27 Mon Sep 17 00:00:00 2001 From: opaninakuffo Date: Fri, 18 Sep 2026 14:01:23 +0000 Subject: [PATCH 4/6] chore[notask]: audit changelog notes against the last published tag git log is the generate range, not the consumer delta. Catch missed exports, routes, catalog removals, and NOTICE JS wipes on every package. --- .agents/skills/qv-notice-generate/SKILL.md | 5 +- .agents/skills/qv-sdk-changelog/SKILL.md | 74 ++++++++++++++++++---- .agents/skills/qv-sdk-pr-create/SKILL.md | 9 +++ 3 files changed, 74 insertions(+), 14 deletions(-) diff --git a/.agents/skills/qv-notice-generate/SKILL.md b/.agents/skills/qv-notice-generate/SKILL.md index 655d4b7b55..9670c4db6c 100644 --- a/.agents/skills/qv-notice-generate/SKILL.md +++ b/.agents/skills/qv-notice-generate/SKILL.md @@ -40,7 +40,10 @@ System requirements for Python scanning: 1. Ask which package to generate NOTICE for (or `--all` for all packages) 2. Source `.env` in the shell 3. Run the generator script — this writes NOTICE files directly -4. Only use `--dry-run` if the user explicitly asks for it +4. If the JS scan logs `npm install failed` (or the JS section went to zero + deps while `HEAD` had a JS section), restore that JS block from `HEAD`. + Keep successful model-scan additions. Do not commit a wiped JS section. +5. Only use `--dry-run` if the user explicitly asks for it **Do NOT commit changes.** The user will review and commit manually. diff --git a/.agents/skills/qv-sdk-changelog/SKILL.md b/.agents/skills/qv-sdk-changelog/SKILL.md index 1210933bc0..d602db930e 100644 --- a/.agents/skills/qv-sdk-changelog/SKILL.md +++ b/.agents/skills/qv-sdk-changelog/SKILL.md @@ -87,7 +87,9 @@ with no changelog line unless those PRs are unioned in. **Standalone minor** (cli, plugins, anything not lockstep with another package): `--base-commit` is the previous `.0`. That range still includes main-only work in the patch window, and it will also re-list the patch. Accept the overlap or -drop PR numbers already in `changelog//`. +drop PR numbers already in `changelog//`. `--base-commit` can still sit +*after* commits that never reached the last **published** tag. The published-tag +audit after generate is what catches that, for every package. **Lockstep `sdk` + `inference`:** both packages use the **same** `--base-commit` / `--base-version` — the last lockstep display floor. When that floor is a @@ -143,7 +145,15 @@ The script automatically excludes: 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`. +giant row). It also writes `models.md`. + +`CHANGELOG.md` inline lines may stay at `MAX_INLINE_MODELS` (5) plus +`(and N more)`. **`models.md` is the full added/removed set** — never truncated. +PR bodies are often incomplete; if the published-tag audit disagrees, replace +`models.md` from the export/constant diff, not from the PR body. + +If this package's public API *is* those exported constants, removed names are +breaking: they go in `breaking.md` even when the PR was only `[mod]`. The extractor applies two policies (in this order): @@ -169,12 +179,23 @@ After both filters, each section is trimmed to `MAX_INLINE_MODELS` (currently 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. +### After generate: published-tag audit (mandatory) + +`git log ..HEAD` is the generate range. It is not the consumer delta. + +For **every** SDK pod package, after the raw files exist: + +1. Resolve the last published tag on upstream: `-v*` (highest already + shipped). That tag is the audit base, not `--base-commit`. +2. Diff that tag vs `HEAD` for this package's public surface: package + `exports`, serve/HTTP routes, exported constants/catalog. +3. Every user-facing add, remove, or rename in that diff must appear in + `api.md`, `breaking.md`, and/or `models.md`. Hand-add. Do not re-run the + generator over those edits; `--update-root-changelog` only. +4. New public exports under an older umbrella PR still need their own `api.md` + example. + +Fail-stop until the notes match the tree. ### Step 4: Generate CHANGELOG_LLM.md (mandatory) @@ -185,9 +206,21 @@ After raw changelog files exist, generate the human-readable version at 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//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. +Apply the same "no informational value" rule: skip backmerges, automated bumps, +and any entry whose subject would just repeat what a previous release already +said. Models body stays concise; the full constant lists live in `models.md` +and in the LLM `### Added` / `### Removed` blocks. + +After writing the file, rebuild the root aggregate so +`packages//CHANGELOG.md` picks up `CHANGELOG_LLM.md` (the aggregator +prefers it over `CHANGELOG.md`): + +```bash +node scripts/sdk/generate-changelog-sdk-pod.cjs --package= --update-root-changelog +``` + +Do **not** re-run a full generate after the published-tag hand edits — that +overwrites `api.md` / `breaking.md` / `models.md`. **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 @@ -206,10 +239,13 @@ the same way CI does: cd packages/ # if this worktree has no node_modules: bun install -bunx prettier --check "changelog/**/*.md" "CHANGELOG.md" +bunx prettier --check "changelog//**/*.md" "CHANGELOG.md" # or, matching CI: bun run format ``` +Scope those globs to **this package**. Do not run `changelog/**/*.md` from the +repo root or another package cwd — that walks every historical version folder. + If it reports problems, fix them — `bunx prettier --write` on the same paths, or `bun run format:fix` — and re-run the check until it passes clean. Do this before moving on so the release commit carries only prettier-clean markdown. @@ -264,6 +300,11 @@ source .env node .agents/skills/qv-notice-generate/scripts/generate-notice.js ``` +If JS `npm install` fails (unpublished lockstep dep, registry miss), **do not +commit** a NOTICE whose JS section is empty. Restore the JS block from `HEAD` +and keep any successful model-scan additions. Models-only packages still update +model attributions against the last published NOTICE. + Do NOT commit the announcement post (gitignored) and let the user review the rest before committing. @@ -438,6 +479,11 @@ push, and keep this list to things that are cheap to prevent: contributor fork. `git push` with no remote follows `origin`. - **Do not skip SDK Pod Checks.** Workspace red vs published red is a real signal; `[skip-sdk-pod-checks]` is not the changelog fix. +- **Notes vs last published tag, not only `git log`.** After generate, audit + exports / routes / constants against `-v*` (highest shipped). `git log` + `..HEAD` misses work that is already an ancestor of `--base-commit`. +- **NOTICE JS wipe.** A failed `npm install` must not replace the JS section + with zero deps. Restore JS from `HEAD`; keep successful model-scan adds. ## Quality Checklist @@ -455,7 +501,9 @@ Before completing: - [ ] CHANGELOG_LLM.md generated (mandatory) and follows format guide - [ ] Generated markdown is prettier-clean with **prettier-config-holepunch** resolved (`bun install` in `packages/` if needed; never `--no-config`) - [ ] announcement-post.txt generated (mandatory, gitignored) -- [ ] NOTICE file updated for the target package +- [ ] Published-tag audit done: last `-v*` vs HEAD public surface matches `api.md` / `breaking.md` / `models.md` +- [ ] `models.md` is the full added/removed set (inline `CHANGELOG.md` may still use `(and N more)`); catalog-as-API removals are in `breaking.md` +- [ ] NOTICE updated; JS section not emptied by a failed install - [ ] When `--package=sdk`: `qv-sdk-inference-version` run (engine version published, sdk version and `@qvac/inference` range sharing a major.minor, sdk-python regenerated), 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) diff --git a/.agents/skills/qv-sdk-pr-create/SKILL.md b/.agents/skills/qv-sdk-pr-create/SKILL.md index 90a8a03a8f..d2b68ba7fe 100644 --- a/.agents/skills/qv-sdk-pr-create/SKILL.md +++ b/.agents/skills/qv-sdk-pr-create/SKILL.md @@ -54,6 +54,14 @@ Consequences for release changelog / metadata PRs: `backmerge/release--` heads are fine — they do not match the `release-*` push trigger. +**Release changelog PRs** (metadata + notes onto `release--x.y.z`): + +- Title is `chore:` (optional `[mod]` when the cut is catalog-only). Do not put + `[bc]` on the release title; breaking lives in `breaking.md`. +- Body API / Models / Breaking sections are copied from + `changelog//`, not from an earlier hop or the generator + summary. Delete a section only when that file is absent. + ## Workflow 1. Identify base and current branch — note whether the base is `main` or a `release--` branch. For release PRs, apply **Release PR branch naming** above (base three-part; head not `release-*`) @@ -280,6 +288,7 @@ Before outputting the PR description, verify: - [ ] For sdk releases with generated docs, `git status` shows only `reference/api/**`, `reference/release-notes/**`, and `src/lib/versions.ts` as committable docs changes — disposable byproducts (`api-data.json`, `out/`, `.next/`, `dist/`, etc.) are gitignored - [ ] If base is `release--`, the dual-PR flow ran (or `--no-backmerge` was set), and both PR URLs are reported - [ ] Release PRs: base is three-part `release--x.y.z`; org head is `chore/--changelog` (or other non-`release-*` name) +- [ ] Release changelog PRs: title is `chore:` (no `[bc]`); body API / Models / Breaking match `changelog//` - [ ] Head was pushed to the org remote when write access allows; fork path only used as fallback (with `fork-ci` re-approval called out) - [ ] PR is Ready for review when baseline CI is expected (not left as Draft unintentionally) From 8c9b223f6bdd052b0d8843eeb98436319d0a7f79 Mon Sep 17 00:00:00 2001 From: opaninakuffo Date: Fri, 18 Sep 2026 14:16:23 +0000 Subject: [PATCH 5/6] chore[notask]: stop changelog generate from fighting the release procedure Lockstep always passes --base-commit; cascade follows the skill instead of the generator shortcut; release heads stay chore/*-changelog. --- .agents/skills/qv-agent-stack-sync/SKILL.md | 17 ++- .../references/prepare-cascade.md | 31 ++-- .agents/skills/qv-sdk-changelog/SKILL.md | 136 ++++++++---------- .../references/changelog-llm-format.md | 4 +- .agents/skills/qv-sdk-pr-create/SKILL.md | 3 +- 5 files changed, 93 insertions(+), 98 deletions(-) diff --git a/.agents/skills/qv-agent-stack-sync/SKILL.md b/.agents/skills/qv-agent-stack-sync/SKILL.md index 6e9702face..a63a3a69b3 100644 --- a/.agents/skills/qv-agent-stack-sync/SKILL.md +++ b/.agents/skills/qv-agent-stack-sync/SKILL.md @@ -90,7 +90,8 @@ Requires explicit user confirmation of the plan (versions + which packages). Then for each `needs_release` package in dependency order, follow [references/prepare-cascade.md](references/prepare-cascade.md): 1. Create org `release--` from `main` if missing. -2. Prep version bump + dep ranges + changelog (`qv-sdk-changelog --package=`) + NOTICE. +2. Prep version bump + dep ranges, then `qv-sdk-changelog --package=` + (follow that skill; do not call the generator as a shortcut) + NOTICE. 3. Open **draft** release PR → `release--`. 4. Open **draft** backmerge PR → `main` (`[skiplog]`, cherry-pick `-x`) in the same session. 5. Skip packages marked blocked; report them clearly. @@ -104,8 +105,16 @@ releases chain `qv-sdk-inference-version` + docs Step 8 from `qv-sdk-changelog`. 1. Confirm lower dependencies are on npm (`npm view`). 2. Fresh-install verification (below). -3. Mark the draft release PR ready for review (and keep backmerge draft until release merges, or ready it alongside — prefer ready both when release is ready to merge). -4. Remind: human merges; human triggers publish; then promote the next upper package. +3. Re-run NOTICE for this package so JS can resolve. If install still fails, + restore the JS block from `HEAD`. +4. Mark the draft release PR ready for review (and keep backmerge draft until + release merges, or ready it alongside — prefer ready both when release is + ready to merge). +5. Remind: human merges; human triggers publish; then promote the next upper + package. + +The `release--*` line is the cut. Do not cherry-pick later `main` onto it. +Backmerge keeps `main` in sync with the cut. ## File updates (when preparing releases) @@ -179,6 +188,8 @@ Always end with: - [ ] OpenClaw included in the cascade - [ ] `@qvac/inference` released ahead of an SDK release that moves major.minor - [ ] Draft release **and** draft backmerge opened together per package +- [ ] Release head is `chore/--changelog`, not `release-*` +- [ ] `qv-sdk-changelog` completed for each hop (published-tag audit included) - [ ] Blocked plugins (e.g. AI SDK mismatch) not force-released - [ ] No publish / workflow_dispatch for npm - [ ] Org-branch heads preferred (`tetherto/qvac`) diff --git a/.agents/skills/qv-agent-stack-sync/references/prepare-cascade.md b/.agents/skills/qv-agent-stack-sync/references/prepare-cascade.md index ceb5e91828..c2dc8db2b9 100644 --- a/.agents/skills/qv-agent-stack-sync/references/prepare-cascade.md +++ b/.agents/skills/qv-agent-stack-sync/references/prepare-cascade.md @@ -17,36 +17,37 @@ Reuse if it exists. Do not force-push. ### 2. Head + metadata ```bash -git checkout -b release/- ORG_REMOTE/release-- +git checkout -b chore/--changelog ORG_REMOTE/release-- ``` +Head must not start with `release-` (Merge Guard runs on the pushed ref). Full +naming: `qv-sdk-pr-create` → "Release PR branch naming". + 1. Bump `package.json` (and `openclaw.plugin.json` when present). 2. Apply planned dep / peer ranges. -3. Changelog + NOTICE: - -```bash -node scripts/sdk/generate-changelog-sdk-pod.cjs --package= -``` - -Author `CHANGELOG_LLM.md`, prettier-check, rebuild aggregate, announcement-post (gitignored), NOTICE. Path map: `scripts/sdk/package-paths.cjs`. - -4. `--package=sdk` only: `qv-sdk-inference-version` + docs Step 8 from `qv-sdk-changelog`. It - writes the range from a published engine version, so the `inference` release merges and - publishes before the SDK draft is promoted. +3. Follow `qv-sdk-changelog` for this `--package` (lockstep `--base-commit`, + published-tag audit, LLM, prettier, NOTICE). Do not call the generator as a + shortcut. +4. `--package=sdk` only: `qv-sdk-inference-version` + docs Step 8 from + `qv-sdk-changelog`. It writes the range from a published engine version, so + the `inference` release merges and publishes before the SDK draft is promoted. -Commit `chore[notask]: release @qvac/ ` (use `chore[bc|notask]:` when breaking). Push to `ORG_REMOTE`. +Commit `chore[notask]: release @qvac/ ` — no `[bc]` on the title. +Push to `ORG_REMOTE`. ### 3. Draft release PR ```bash gh pr create --repo tetherto/qvac --draft \ --base release-- \ - --head release/- \ + --head chore/--changelog \ --title "chore[notask]: release @qvac/ " \ --body "..." ``` -SDK pod template. Note future dep versions if lower npm is not live. Publish is human-gated. +SDK pod template. Body API / Models / Breaking copied from +`changelog//`. Note future dep versions if lower npm is not live. +Publish is human-gated. ### 4. Draft backmerge PR diff --git a/.agents/skills/qv-sdk-changelog/SKILL.md b/.agents/skills/qv-sdk-changelog/SKILL.md index d602db930e..51d5641b84 100644 --- a/.agents/skills/qv-sdk-changelog/SKILL.md +++ b/.agents/skills/qv-sdk-changelog/SKILL.md @@ -70,67 +70,50 @@ The script fetches from `upstream` first, falling back to `origin`. The generator enforces both checks and exits non-zero on failure. -Run `git tag --list "-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 a `release--*` branch and backmerge -into main. The generator's range is `git log ..HEAD`. A commit that landed -on **main** while that patch branch was open, and was never on the patch branch, -is already an ancestor of the backmerge, so it is invisible to that range. It -also never appeared in the patch notes. The next minor then ships the code -with no changelog line unless those PRs are unioned in. - -**Standalone minor** (cli, plugins, anything not lockstep with another package): -`--base-commit` is the previous `.0`. That range still includes main-only work -in the patch window, and it will also re-list the patch. Accept the overlap or -drop PR numbers already in `changelog//`. `--base-commit` can still sit -*after* commits that never reached the last **published** tag. The published-tag -audit after generate is what catches that, for every package. - -**Lockstep `sdk` + `inference`:** both packages use the **same** `--base-commit` -/ `--base-version` — the last lockstep display floor. When that floor is a -`.0`, generate is enough. When it is a **patch**, the notes are: - -1. Generator output from the patch backmerge (work after the floor). -2. Plus main-only work between the previous lockstep `.0` backmerge and that - patch backmerge: first-parent merges touching `packages/inference` or - `packages/sdk`, minus `[skiplog]`, minus PR numbers already in - `packages/sdk/changelog//CHANGELOG.md`. - -Hand-add (2) to **both** lockstep changelogs (`CHANGELOG.md`, `CHANGELOG_LLM.md`, -and `breaking.md` / `models.md` / `api.md` when the PR tag requires them). Do -not re-run the generator over the hand edits; `--update-root-changelog` only. +**Nested worktrees:** `unset GIT_DIR GIT_WORK_TREE` before any git or changelog +command, or the generator runs against the parent repo. + +**Pick `--base-commit`, then pass it.** Do not run the generator unflagged and +hope auto-detect is right. + +- **Lockstep `sdk` + `inference`:** always pass `--base-commit` / `--base-version` + — the last lockstep ship's backmerge on main, same floor on both packages. + Never tag auto-detect: `inference-v*` is often not an ancestor of `main`, and + a minor auto-detects the previous `.0` (repeats already-shipped patch notes). + When that floor is a `.0`, generate is enough. When it is a **patch**, generate + from the patch backmerge, then union main-only work from the previous lockstep + `.0` backmerge → that patch backmerge: first-parent merges touching + `packages/inference` or `packages/sdk`, minus `[skiplog]`, minus PR numbers + already in `packages/sdk/changelog//CHANGELOG.md`. Hand-add to both + changelogs (`CHANGELOG.md` and `breaking.md` / `models.md` / `api.md` when the + PR tag requires them). `--update-root-changelog` only after that. ```bash git log --first-parent --format='%s' .. # keep subjects whose merge diff touches packages/inference or packages/sdk ``` -**`inference-v*` / historical `sdk-v*` tags are often not ancestors of `main`.** -Inference tags used to live on `release-sdk-*`; after split-publish they live on -`release-inference-*`. Auto-detected tags then fail `merge-base --is-ancestor`. -Pass `--base-commit= --base-version=` instead of relying on the tag. +- **Standalone** (cli, plugins, anything not lockstep): omit the flags. The + generator auto-detects (minor → previous `.0`, patch → previous tag). A + previous `.0` still includes main-only work in the patch window; drop PR + numbers already in `changelog//` if they re-list. If no tags exist, + ask for `--base-commit` and `--base-version`. -**Nested worktrees:** unset stale `GIT_DIR` / `GIT_WORK_TREE` from a parent Cursor -session before any git or changelog command (`unset GIT_DIR GIT_WORK_TREE`). -Otherwise the generator runs against the parent repo. +`--base-commit` is only the generate range. The published-tag audit after +generate is the consumer delta, for every package. ### Step 3: Generate Raw Changelog -All SDK pod packages use the same command: +Lockstep (`sdk` / `inference`) always passes the floor from Step 2: ```bash -node scripts/sdk/generate-changelog-sdk-pod.cjs --package= +node scripts/sdk/generate-changelog-sdk-pod.cjs --package= --base-commit= --base-version= ``` -With migration flags: +Standalone packages can omit the flags (auto-detect): ```bash -node scripts/sdk/generate-changelog-sdk-pod.cjs --package= --base-commit= --base-version= +node scripts/sdk/generate-changelog-sdk-pod.cjs --package= ``` The script automatically excludes: @@ -185,30 +168,31 @@ the bullet emits with no continuation lines. For **every** SDK pod package, after the raw files exist: -1. Resolve the last published tag on upstream: `-v*` (highest already - shipped). That tag is the audit base, not `--base-commit`. -2. Diff that tag vs `HEAD` for this package's public surface: package - `exports`, serve/HTTP routes, exported constants/catalog. -3. Every user-facing add, remove, or rename in that diff must appear in - `api.md`, `breaking.md`, and/or `models.md`. Hand-add. Do not re-run the - generator over those edits; `--update-root-changelog` only. -4. New public exports under an older umbrella PR still need their own `api.md` - example. +```bash +LAST=$(git tag --list "-v*" --sort=-v:refname | head -1) +git diff "$LAST" HEAD -- packages//package.json +``` + +Diff this package's public surface the same way: `exports`, serve/HTTP routes, +exported constants/catalog. Every user-facing add, remove, or rename in that +diff must appear in `api.md`, `breaking.md`, and/or `models.md`. Hand-add. +`--update-root-changelog` only — do not re-run a full generate. New public +exports under an older umbrella PR still get their own `api.md` example. -Fail-stop until the notes match the tree. +Fail-stop until the notes match the tree. Then write `CHANGELOG_LLM.md`. ### 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//changelog//CHANGELOG_LLM.md`. +Author `CHANGELOG_LLM.md` from `changelog//` **after** the published-tag +audit, not from `git log`. Title and NPM line are this package (`@qvac/`), +not always sdk. -See [references/changelog-llm-format.md](references/changelog-llm-format.md) for the format guide. +See [references/changelog-llm-format.md](references/changelog-llm-format.md). -Apply the same "no informational value" rule: skip backmerges, automated bumps, -and any entry whose subject would just repeat what a previous release already -said. Models body stays concise; the full constant lists live in `models.md` +Skip backmerges, automated bumps, and entries that only repeat a previous +release. Models body stays concise; the full constant lists live in `models.md` and in the LLM `### Added` / `### Removed` blocks. After writing the file, rebuild the root aggregate so @@ -282,9 +266,8 @@ Layout: - `:qvac: SDK :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. + when `breaking.md` exists (including hand-added catalog-as-API removals). + File presence, not `[bc]` tags and not 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), @@ -413,8 +396,8 @@ See `docs/website/docs-workflow.md` for the full pipeline reference. | 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) | +| `--base-commit` | Lockstep | Generate-range start. Required for `sdk`/`inference`; overrides tag auto-detect | +| `--base-version` | Lockstep | Display label for that floor | | `--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//CHANGELOG.md` | @@ -426,9 +409,9 @@ See `docs/website/docs-workflow.md` for the full pipeline reference. Generates changelog files in `packages//changelog//`: - `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) +- `breaking.md` - Breaking changes (`[bc]` PRs and catalog-as-API removals) +- `api.md` - API changes (`[api]` PRs and published-tag export/route diffs) +- `models.md` - Model changes (full set; `[mod]` PRs and constant diffs) - `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) @@ -467,11 +450,10 @@ push, and keep this list to things that are cheap to prevent: Never `--no-config`. Never `bunx prettier` until `packages//node_modules` (or a linked install) can resolve that package. Quote style and trailing commas on `CHANGELOG_LLM.md` are the usual fail. -- **Lockstep display floor is not the whole range.** Same `--base-commit` on - `sdk` and `inference` so already-shipped notes are not repeated. When that - floor is a patch, union in the Step 2 main-only window (previous lockstep - `.0` backmerge → patch backmerge, minus skiplog, minus the patch notes). - SDK is the consumer set; inference is the engine slice. +- **Lockstep: pass the floor.** Never run the generator unflagged for `sdk` / + `inference`. Same `--base-commit` on both. When that floor is a patch, union + the Step 2 main-only window. SDK is the consumer set; inference is the engine + slice. - **`inference-v*` is often not on `main`.** Use the backmerge SHA, not the tag. - **Nested worktrees inherit `GIT_DIR`.** `unset GIT_DIR GIT_WORK_TREE` before generate/commit/cherry-pick, or you operate on the parent repo. @@ -493,12 +475,12 @@ Before completing: - [ ] Working head (if branched for the release PR) is `chore/--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` -- [ ] For lockstep `sdk` + `inference` at the same major.minor: both used the **same** `--base-commit` / `--base-version` (last lockstep display floor); SDK changelog includes the engine slice -- [ ] When that floor is a patch: main-only window unioned in (previous lockstep `.0` backmerge → patch backmerge, minus skiplog, minus the patch notes) on both changelogs +- [ ] For lockstep `sdk` + `inference`: both generated with the **same** `--base-commit` / `--base-version` (last lockstep display floor); never unflagged auto-detect; SDK changelog includes the engine slice +- [ ] When that floor is a patch: main-only window unioned in on both changelogs - [ ] `GIT_DIR` / `GIT_WORK_TREE` unset (or pointed at this worktree) so generate/git did not run in a parent repo - [ ] PRs scoped to package path only - [ ] Changelog files written to correct version directory -- [ ] CHANGELOG_LLM.md generated (mandatory) and follows format guide +- [ ] CHANGELOG_LLM.md authored from `changelog//` after the published-tag audit (this package's name on the title/NPM line) - [ ] Generated markdown is prettier-clean with **prettier-config-holepunch** resolved (`bun install` in `packages/` if needed; never `--no-config`) - [ ] announcement-post.txt generated (mandatory, gitignored) - [ ] Published-tag audit done: last `-v*` vs HEAD public surface matches `api.md` / `breaking.md` / `models.md` diff --git a/.agents/skills/qv-sdk-changelog/references/changelog-llm-format.md b/.agents/skills/qv-sdk-changelog/references/changelog-llm-format.md index 0484520611..c389b82362 100644 --- a/.agents/skills/qv-sdk-changelog/references/changelog-llm-format.md +++ b/.agents/skills/qv-sdk-changelog/references/changelog-llm-format.md @@ -29,9 +29,9 @@ Create `{PACKAGE_ROOT}/changelog/{VERSION}/CHANGELOG_LLM.md` with these guidelin ### Format Requirements -1. **Title**: `# QVAC SDK v{VERSION} Release Notes` +1. **Title**: `# QVAC v{VERSION} Release Notes` — this package, not always SDK. -2. **NPM Link**: Add `📦 **NPM:** https://www.npmjs.com/package/@qvac/sdk/v/{VERSION}` right after the title. This is the **only** place an emoji is allowed in the document (see the no-emoji rule below). +2. **NPM Link**: `📦 **NPM:** https://www.npmjs.com/package/@qvac//v/{VERSION}` right after the title. This is the **only** place an emoji is allowed in the document (see the no-emoji rule below). 3. **Introduction**: Write a brief 2-3 sentence summary of what this release brings diff --git a/.agents/skills/qv-sdk-pr-create/SKILL.md b/.agents/skills/qv-sdk-pr-create/SKILL.md index d2b68ba7fe..17cd6acbc2 100644 --- a/.agents/skills/qv-sdk-pr-create/SKILL.md +++ b/.agents/skills/qv-sdk-pr-create/SKILL.md @@ -101,6 +101,7 @@ Infer first, ask only if uncertain: - `[bc]`: removed/changed existing public API signatures - `[mod]`: changes to model constant definitions - ASK only if change scope is ambiguous +- **Release changelog PRs** (base `release-*`, diff is notes / NOTICE / version / generated docs): title is `chore:` (optional `[mod]`). Do not copy `[bc]` / `[api]` from the notes into the title. Body API / Models / Breaking still copy `changelog//`. **Testing section:** - If test files modified → "Unit tests added/updated for X" @@ -278,7 +279,7 @@ Before outputting the PR description, verify: - [ ] "What problem" describes user impact, not implementation - [ ] "How it solves" is high-level approach, not line-by-line - [ ] Unused sections are deleted -- [ ] `[bc]` tag has BEFORE/AFTER code examples +- [ ] `[bc]` tag has BEFORE/AFTER code examples (feature PRs). Release changelog PRs: no `[bc]` on the title; Breaking section copies `breaking.md` when that file exists - [ ] `[api]` tag has usage example - [ ] `[mod]` tag has Added/Removed models list - [ ] Description is concise - bullet points, no fluff From 8d67d058ae45dad15a70fa98841d5a68b6ed8f68 Mon Sep 17 00:00:00 2001 From: opaninakuffo Date: Fri, 18 Sep 2026 15:00:04 +0000 Subject: [PATCH 6/6] chore[notask]: resolve changelog audit and prettier via package-paths Plugins are not under packages/. bun install is not safe while a lockstep dep is unpublished. --- .agents/skills/qv-sdk-changelog/SKILL.md | 29 ++++++++++++------------ 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/.agents/skills/qv-sdk-changelog/SKILL.md b/.agents/skills/qv-sdk-changelog/SKILL.md index 51d5641b84..ec9d3343a1 100644 --- a/.agents/skills/qv-sdk-changelog/SKILL.md +++ b/.agents/skills/qv-sdk-changelog/SKILL.md @@ -169,8 +169,9 @@ the bullet emits with no continuation lines. For **every** SDK pod package, after the raw files exist: ```bash +DIR=$(node -e "console.log(require('./scripts/sdk/package-paths.cjs').getPackageDir(''))") LAST=$(git tag --list "-v*" --sort=-v:refname | head -1) -git diff "$LAST" HEAD -- packages//package.json +git diff "$LAST" HEAD -- "$DIR/package.json" ``` Diff this package's public surface the same way: `exports`, serve/HTTP routes, @@ -214,17 +215,15 @@ committed-file format check would later reject. Every SDK pod package uses prett (`[inference] format`, `[sdk] format`, …) loads holepunch; `--no-config` or a different parser (quote style, trailing commas) is the usual red we hit. -`bunx prettier` from a worktree **without** `node_modules` fails with -`Cannot find package 'prettier-config-holepunch'` and then either skips the -check or formats with a fallback that CI will reject. Install first, then check -the same way CI does: +`bunx prettier` fails with `Cannot find package 'prettier-config-holepunch'` +when that package is not resolvable, then either skips or formats with a +fallback CI rejects. **Never `--no-config`.** Do not `bun install` in a package +whose range names an unpublished lockstep dep (e.g. sdk waiting on inference) — +run `bunx` from a sibling that already has `node_modules`. ```bash -cd packages/ -# if this worktree has no node_modules: -bun install -bunx prettier --check "changelog//**/*.md" "CHANGELOG.md" -# or, matching CI: bun run format +DIR=$(node -e "console.log(require('./scripts/sdk/package-paths.cjs').getPackageDir(''))") +bunx prettier --check "$DIR/changelog//**/*.md" "$DIR/CHANGELOG.md" ``` Scope those globs to **this package**. Do not run `changelog/**/*.md` from the @@ -447,9 +446,9 @@ These have gone red on more than one SDK-pod changelog PR. Fix them before push, and keep this list to things that are cheap to prevent: - **Prettier is holepunch, not stock.** `.prettierrc` is `"prettier-config-holepunch"`. - Never `--no-config`. Never `bunx prettier` until `packages//node_modules` - (or a linked install) can resolve that package. Quote style and trailing commas - on `CHANGELOG_LLM.md` are the usual fail. + Never `--no-config`. Resolve holepunch from a package that can install; do not + `bun install` against an unpublished lockstep dep. Quote style and trailing + commas on `CHANGELOG_LLM.md` are the usual fail. - **Lockstep: pass the floor.** Never run the generator unflagged for `sdk` / `inference`. Same `--base-commit` on both. When that floor is a patch, union the Step 2 main-only window. SDK is the consumer set; inference is the engine @@ -481,9 +480,9 @@ Before completing: - [ ] PRs scoped to package path only - [ ] Changelog files written to correct version directory - [ ] CHANGELOG_LLM.md authored from `changelog//` after the published-tag audit (this package's name on the title/NPM line) -- [ ] Generated markdown is prettier-clean with **prettier-config-holepunch** resolved (`bun install` in `packages/` if needed; never `--no-config`) +- [ ] Generated markdown is prettier-clean with **prettier-config-holepunch** resolved (never `--no-config`; do not `bun install` against unpublished lockstep deps) - [ ] announcement-post.txt generated (mandatory, gitignored) -- [ ] Published-tag audit done: last `-v*` vs HEAD public surface matches `api.md` / `breaking.md` / `models.md` +- [ ] Published-tag audit done: last `-v*` vs HEAD public surface under `getPackageDir()` matches `api.md` / `breaking.md` / `models.md` - [ ] `models.md` is the full added/removed set (inline `CHANGELOG.md` may still use `(and N more)`); catalog-as-API removals are in `breaking.md` - [ ] NOTICE updated; JS section not emptied by a failed install - [ ] When `--package=sdk`: `qv-sdk-inference-version` run (engine version published, sdk version and `@qvac/inference` range sharing a major.minor, sdk-python regenerated), python `generate.py --check` passing