From 36d6d7dab6c7305ff1a25b2cb1d6c6bd22b8f3df Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Tue, 8 Sep 2026 14:56:21 +0200 Subject: [PATCH] fix(release): include CLI notes in GitHub releases --- .changeset/complete-release-notes.md | 5 ++++ script/kilocode/release-notes.test.ts | 38 ++++++++++++++++++++++++++- script/kilocode/release-notes.ts | 29 ++++++++++++++++++-- 3 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 .changeset/complete-release-notes.md diff --git a/.changeset/complete-release-notes.md b/.changeset/complete-release-notes.md new file mode 100644 index 000000000000..d04b16b13e2e --- /dev/null +++ b/.changeset/complete-release-notes.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": patch +--- + +Include CLI changes alongside VS Code changes in GitHub release notes. diff --git a/script/kilocode/release-notes.test.ts b/script/kilocode/release-notes.test.ts index bd4a0aab02a6..fee8eaab465c 100644 --- a/script/kilocode/release-notes.test.ts +++ b/script/kilocode/release-notes.test.ts @@ -1,9 +1,45 @@ import { expect, test } from "bun:test" -import { buildNotes } from "./release-notes" +import { buildNotes, buildReleaseNotes } from "./release-notes" const section = (version: string, body: string) => `## ${version}\n\n${body}\n` const release = (tagName: string, isPrerelease = false, isDraft = false) => ({ tagName, isPrerelease, isDraft }) +test("release notes include VS Code and CLI changes with nested headings", () => { + expect( + buildReleaseNotes({ + version: "7.5.16", + prerelease: false, + releases: [], + vscode: section("7.5.16", "### Minor Changes\n\n- Improve Agent Manager"), + cli: section("7.5.16", "### Patch Changes\n\n- #13896 Fix session list --all"), + }), + ).toBe( + "## VS Code\n\n### 7.5.16\n\n#### Minor Changes\n\n- Improve Agent Manager\n\n" + + "## CLI\n\n### 7.5.16\n\n#### Patch Changes\n\n- #13896 Fix session list --all", + ) +}) + +test("combined notes retain channel filtering for both packages", () => { + const input = { + version: "1.2.3", + releases: [release("v1.2.2", true), release("v1.2.1")], + vscode: section("1.2.3", "editor") + section("1.2.2", "editor preview") + section("1.2.1", "old editor"), + cli: section("1.2.3", "cli") + section("1.2.2", "cli preview") + section("1.2.1", "old cli"), + } + expect(buildReleaseNotes({ ...input, prerelease: true })).toBe("## VS Code\n\neditor\n\n## CLI\n\ncli") + expect(buildReleaseNotes({ ...input, prerelease: false })).toBe( + "## VS Code\n\n### 1.2.3\n\neditor\n\n### 1.2.2\n\neditor preview\n\n" + + "## CLI\n\n### 1.2.3\n\ncli\n\n### 1.2.2\n\ncli preview", + ) +}) + +test("combined notes omit empty packages and use one fallback when both are empty", () => { + const input = { version: "1.2.3", prerelease: true, releases: [] } + expect(buildReleaseNotes({ ...input, vscode: "", cli: section("1.2.3", "fix") })).toBe("## CLI\n\nfix") + expect(buildReleaseNotes({ ...input, vscode: section("1.2.3", "fix"), cli: "" })).toBe("## VS Code\n\nfix") + expect(buildReleaseNotes({ ...input, vscode: "", cli: section("1.2.3", "") })).toBe("No notable changes") +}) + test("prerelease notes contain only their own section", () => { const changelog = section("1.2.3", "current prerelease") + section("1.2.2", "previous prerelease") const notes = buildNotes({ diff --git a/script/kilocode/release-notes.ts b/script/kilocode/release-notes.ts index a9b0262422c8..c9dbc88b1da8 100644 --- a/script/kilocode/release-notes.ts +++ b/script/kilocode/release-notes.ts @@ -40,11 +40,36 @@ export function buildNotes(input: { version: string; prerelease: boolean; releas return notes.join("\n\n") || fallback } +export function buildReleaseNotes(input: { + version: string + prerelease: boolean + releases: Release[] + vscode: string + cli: string +}) { + const notes = ( + [ + ["VS Code", input.vscode], + ["CLI", input.cli], + ] as const + ).flatMap(([name, changelog]) => { + const body = buildNotes({ ...input, changelog }) + if (body === fallback) return [] + return [`## ${name}\n\n${input.prerelease ? body : body.replace(/^(#+) /gm, "#$1 ")}`] + }) + return notes.join("\n\n") || fallback +} + export async function publishNotes(input: { version: string; prerelease: boolean; repo?: string; temp?: string }) { const repo = input.repo ? ["--repo", input.repo] : [] const releases: Release[] = await $`gh release list --limit 1000 --json tagName,isDraft,isPrerelease ${repo}`.json() - const changelog = await Bun.file(new URL("../../packages/kilo-vscode/CHANGELOG.md", import.meta.url)).text() - const body = buildNotes({ ...input, releases, changelog }) + const [vscode, cli] = await Promise.all([ + Bun.file(new URL("../../packages/kilo-vscode/CHANGELOG.md", import.meta.url)).text(), + // The inherited "opencode" folder contains Kilo's CLI (@kilocode/cli), not upstream release notes. + // Read Kilo's changelog, including compact upstream summaries added when those changes are merged into Kilo. + Bun.file(new URL("../../packages/opencode/CHANGELOG.md", import.meta.url)).text(), + ]) + const body = buildReleaseNotes({ ...input, releases, vscode, cli }) const notes = `${input.temp ?? "/tmp"}/release-notes.txt` const target = input.version.startsWith("v") ? input.version : `v${input.version}` const flags = input.prerelease ? ["--draft=false", "--prerelease"] : ["--draft=false"]