From 419ddea52a3a5d2d27578df0528d46d6c55fb039 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 14:06:37 +0000 Subject: [PATCH 01/20] chore(upstream): implement compatibility base tracking for merges Update the upstream merge script to identify and link to the previous compatibility commit. This ensures that pre-merge transformations maintain a proper commit history by using multiple parents when a prior compatibility base is found, rather than creating disconnected commits. - Add `findLatestCompatCommit` to locate previous compatibility commits - Implement `commitTree` and `updateBranch` for low-level git manipulation - Update `merge.ts` to integrate compatibility base detection and multi-parent commits --- script/upstream/merge.ts | 19 ++++++++++++- script/upstream/utils/git.ts | 55 ++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/script/upstream/merge.ts b/script/upstream/merge.ts index 01affc03caf..967ea1d9c78 100644 --- a/script/upstream/merge.ts +++ b/script/upstream/merge.ts @@ -429,6 +429,15 @@ async function main() { await git.createBranch(opencodeBranch) logger.info(`Created opencode branch: ${opencodeBranch}`) + const prior = await git.findLatestCompatCommit(config.baseBranch, targetVersion.commit) + if (prior) { + logger.info( + `Found previous compatibility base: ${prior.message} (${prior.commit.slice(0, 8)}) from upstream ${prior.upstream.slice(0, 8)}`, + ) + } else { + logger.warn("No previous compatibility base found; merge base will remain pristine upstream") + } + // Step 6: Apply ALL transformations to opencode branch (pre-merge) // This reduces conflicts by transforming upstream code to Kilo conventions BEFORE merging logger.step(6, 8, "Applying transformations to opencode branch (pre-merge)...") @@ -524,7 +533,15 @@ async function main() { // Commit all transformations await git.stageAll() - await git.commit(`refactor: kilo compat for ${targetVersion.tag}`) + const compatMessage = `refactor: kilo compat for ${targetVersion.tag}` + if (prior) { + const tree = await git.writeTree() + const commit = await git.commitTree(tree, compatMessage, [targetVersion.commit, prior.commit]) + await git.updateBranch(opencodeBranch, commit) + await git.checkout(opencodeBranch) + } else { + await git.commit(compatMessage) + } logger.success("Committed pre-merge transformations") // Step 7: Merge into Kilo branch diff --git a/script/upstream/utils/git.ts b/script/upstream/utils/git.ts index a781276e983..d30cb2b99ea 100644 --- a/script/upstream/utils/git.ts +++ b/script/upstream/utils/git.ts @@ -15,6 +15,12 @@ export interface RemoteInfo { url: string } +export interface CompatBase { + commit: string + upstream: string + message: string +} + export async function getCurrentBranch(): Promise { const result = await $`git rev-parse --abbrev-ref HEAD`.text() return result.trim() @@ -177,6 +183,55 @@ export async function getCommitHash(ref: string): Promise { return result.trim() } +export async function getCommitParents(ref: string): Promise { + const result = await $`git show --no-patch --format=%P ${ref}`.text() + return result + .trim() + .split(/\s+/) + .filter((parent) => parent.length > 0) +} + +export async function writeTree(): Promise { + const result = await $`git write-tree`.text() + return result.trim() +} + +export async function commitTree(tree: string, message: string, parents: string[]): Promise { + const args = parents.flatMap((parent) => ["-p", parent]) + const result = await $`git commit-tree ${tree} ${args} -m ${message}`.text() + return result.trim() +} + +export async function updateBranch(name: string, commit: string): Promise { + await $`git update-ref refs/heads/${name} ${commit}` +} + +export async function findLatestCompatCommit(base: string, target: string): Promise { + const grep = "^refactor: kilo compat for " + const result = await $`git log --format=%H%x00%s --grep=${grep} ${base}`.quiet().nothrow() + if (result.exitCode !== 0) { + throw new Error(`Failed to search compatibility commits: ${result.stderr.toString()}`) + } + + const lines = result.stdout + .toString() + .trim() + .split("\n") + .filter((line) => line.length > 0) + + for (const line of lines) { + const [commit, message = ""] = line.split("\0") + if (!commit) continue + + const parents = await getCommitParents(commit) + for (const parent of parents) { + if (await isAncestor(parent, target)) return { commit, upstream: parent, message } + } + } + + return null +} + /** * Check if `commit` is an ancestor of `ref` (i.e. reachable from `ref`). * Uses `git merge-base --is-ancestor`, which exits 0 for yes, 1 for no. From 4b1c1930c2fd07a4dec556f138217e4953685594 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 15:54:19 +0000 Subject: [PATCH 02/20] fix: preserve upstream compat merge bases --- script/upstream/merge.ts | 2 +- script/upstream/utils/git.test.ts | 55 +++++++++++++++++++++++++++++++ script/upstream/utils/git.ts | 5 ++- 3 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 script/upstream/utils/git.test.ts diff --git a/script/upstream/merge.ts b/script/upstream/merge.ts index 967ea1d9c78..db3245c1c1d 100644 --- a/script/upstream/merge.ts +++ b/script/upstream/merge.ts @@ -536,7 +536,7 @@ async function main() { const compatMessage = `refactor: kilo compat for ${targetVersion.tag}` if (prior) { const tree = await git.writeTree() - const commit = await git.commitTree(tree, compatMessage, [targetVersion.commit, prior.commit]) + const commit = await git.createMergeCommit(tree, compatMessage, targetVersion.commit, prior.commit) await git.updateBranch(opencodeBranch, commit) await git.checkout(opencodeBranch) } else { diff --git a/script/upstream/utils/git.test.ts b/script/upstream/utils/git.test.ts new file mode 100644 index 00000000000..5636241a2f7 --- /dev/null +++ b/script/upstream/utils/git.test.ts @@ -0,0 +1,55 @@ +import { afterEach, beforeEach, expect, test } from "bun:test" +import { mkdtemp, rm } from "node:fs/promises" +import { join } from "node:path" +import { tmpdir } from "node:os" +import { $ } from "bun" +import { createMergeCommit, findLatestCompatCommit, getCommitHash, updateBranch, writeTree } from "./git" + +const cwd = process.cwd() +let dir = "" + +async function commit(message: string) { + await $`git add -A`.quiet() + await $`git -c user.name=Test -c user.email=test@example.com commit -m ${message}`.quiet() + return getCommitHash("HEAD") +} + +beforeEach(async () => { + dir = await mkdtemp(join(tmpdir(), "kilo-upstream-git-")) + process.chdir(dir) + await $`git init -b upstream`.quiet() + await $`git config user.name Test`.quiet() + await $`git config user.email test@example.com`.quiet() +}) + +afterEach(async () => { + process.chdir(cwd) + await rm(dir, { recursive: true, force: true }) +}) + +test("finds previous compatibility commit for transformed merge base", async () => { + await Bun.write("brand.txt", "opencode A\n") + const old = await commit("release: v1.0.0") + + await Bun.write("brand.txt", "opencode B\n") + const target = await commit("release: v1.0.1") + + await $`git checkout -b main ${old}`.quiet() + await Bun.write("brand.txt", "kilo A\n") + const prior = await commit("refactor: kilo compat for v1.0.0") + + const found = await findLatestCompatCommit("main", target) + expect(found?.commit).toBe(prior) + expect(found?.upstream).toBe(old) + + await $`git checkout ${target}`.quiet() + await $`git checkout -b opencode-v1.0.1`.quiet() + await Bun.write("brand.txt", "kilo B\n") + await $`git add -A`.quiet() + const tree = await writeTree() + const next = await createMergeCommit(tree, "refactor: kilo compat for v1.0.1", target, prior) + await updateBranch("opencode-v1.0.1", next) + + const base = (await $`git merge-base main opencode-v1.0.1`.text()).trim() + expect(base).toBe(prior) +}) diff --git a/script/upstream/utils/git.ts b/script/upstream/utils/git.ts index d30cb2b99ea..4e9ccea8197 100644 --- a/script/upstream/utils/git.ts +++ b/script/upstream/utils/git.ts @@ -196,9 +196,8 @@ export async function writeTree(): Promise { return result.trim() } -export async function commitTree(tree: string, message: string, parents: string[]): Promise { - const args = parents.flatMap((parent) => ["-p", parent]) - const result = await $`git commit-tree ${tree} ${args} -m ${message}`.text() +export async function createMergeCommit(tree: string, message: string, first: string, second: string): Promise { + const result = await $`git commit-tree ${tree} -p ${first} -p ${second} -m ${message}`.text() return result.trim() } From 4b22bfbc57c2ac1a3911a910ec59e1d045e6e540 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 07:57:01 +0000 Subject: [PATCH 03/20] fix: keep compat branches linear --- script/upstream/merge.ts | 2 +- script/upstream/utils/git.test.ts | 7 ++++--- script/upstream/utils/git.ts | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/script/upstream/merge.ts b/script/upstream/merge.ts index db3245c1c1d..e4ac18f7441 100644 --- a/script/upstream/merge.ts +++ b/script/upstream/merge.ts @@ -536,7 +536,7 @@ async function main() { const compatMessage = `refactor: kilo compat for ${targetVersion.tag}` if (prior) { const tree = await git.writeTree() - const commit = await git.createMergeCommit(tree, compatMessage, targetVersion.commit, prior.commit) + const commit = await git.createCommit(tree, compatMessage, prior.commit) await git.updateBranch(opencodeBranch, commit) await git.checkout(opencodeBranch) } else { diff --git a/script/upstream/utils/git.test.ts b/script/upstream/utils/git.test.ts index 5636241a2f7..999510bb42b 100644 --- a/script/upstream/utils/git.test.ts +++ b/script/upstream/utils/git.test.ts @@ -3,7 +3,7 @@ import { mkdtemp, rm } from "node:fs/promises" import { join } from "node:path" import { tmpdir } from "node:os" import { $ } from "bun" -import { createMergeCommit, findLatestCompatCommit, getCommitHash, updateBranch, writeTree } from "./git" +import { createCommit, findLatestCompatCommit, getCommitHash, getCommitParents, updateBranch, writeTree } from "./git" const cwd = process.cwd() let dir = "" @@ -27,7 +27,7 @@ afterEach(async () => { await rm(dir, { recursive: true, force: true }) }) -test("finds previous compatibility commit for transformed merge base", async () => { +test("finds previous compatibility commit for transformed base", async () => { await Bun.write("brand.txt", "opencode A\n") const old = await commit("release: v1.0.0") @@ -47,9 +47,10 @@ test("finds previous compatibility commit for transformed merge base", async () await Bun.write("brand.txt", "kilo B\n") await $`git add -A`.quiet() const tree = await writeTree() - const next = await createMergeCommit(tree, "refactor: kilo compat for v1.0.1", target, prior) + const next = await createCommit(tree, "refactor: kilo compat for v1.0.1", prior) await updateBranch("opencode-v1.0.1", next) const base = (await $`git merge-base main opencode-v1.0.1`.text()).trim() expect(base).toBe(prior) + expect(await getCommitParents(next)).toEqual([prior]) }) diff --git a/script/upstream/utils/git.ts b/script/upstream/utils/git.ts index 4e9ccea8197..b25212de02a 100644 --- a/script/upstream/utils/git.ts +++ b/script/upstream/utils/git.ts @@ -196,8 +196,8 @@ export async function writeTree(): Promise { return result.trim() } -export async function createMergeCommit(tree: string, message: string, first: string, second: string): Promise { - const result = await $`git commit-tree ${tree} -p ${first} -p ${second} -m ${message}`.text() +export async function createCommit(tree: string, message: string, parent: string): Promise { + const result = await $`git commit-tree ${tree} -p ${parent} -m ${message}`.text() return result.trim() } From fe3724605fd10da25f2a0c624049eea373b4b284 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 08:00:21 +0000 Subject: [PATCH 04/20] fix: resolve compat bases from version tags --- script/upstream/utils/git.test.ts | 2 ++ script/upstream/utils/git.ts | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/script/upstream/utils/git.test.ts b/script/upstream/utils/git.test.ts index 999510bb42b..f841d74bf59 100644 --- a/script/upstream/utils/git.test.ts +++ b/script/upstream/utils/git.test.ts @@ -33,8 +33,10 @@ test("finds previous compatibility commit for transformed base", async () => { await Bun.write("brand.txt", "opencode B\n") const target = await commit("release: v1.0.1") + await $`git tag v1.0.1 ${target}`.quiet() await $`git checkout -b main ${old}`.quiet() + await $`git tag v1.0.0 ${old}`.quiet() await Bun.write("brand.txt", "kilo A\n") const prior = await commit("refactor: kilo compat for v1.0.0") diff --git a/script/upstream/utils/git.ts b/script/upstream/utils/git.ts index b25212de02a..d8730d2d378 100644 --- a/script/upstream/utils/git.ts +++ b/script/upstream/utils/git.ts @@ -205,6 +205,19 @@ export async function updateBranch(name: string, commit: string): Promise await $`git update-ref refs/heads/${name} ${commit}` } +async function compatUpstream(message: string): Promise { + const prefix = "refactor: kilo compat for " + if (!message.startsWith(prefix)) return null + + const tag = message.slice(prefix.length).trim().split(/\s+/)[0] + if (!tag) return null + + const ref = `${tag}^{commit}` + const result = await $`git rev-parse ${ref}`.quiet().nothrow() + if (result.exitCode !== 0) return null + return result.stdout.toString().trim() +} + export async function findLatestCompatCommit(base: string, target: string): Promise { const grep = "^refactor: kilo compat for " const result = await $`git log --format=%H%x00%s --grep=${grep} ${base}`.quiet().nothrow() @@ -222,6 +235,9 @@ export async function findLatestCompatCommit(base: string, target: string): Prom const [commit, message = ""] = line.split("\0") if (!commit) continue + const upstream = await compatUpstream(message) + if (upstream && (await isAncestor(upstream, target))) return { commit, upstream, message } + const parents = await getCommitParents(commit) for (const parent of parents) { if (await isAncestor(parent, target)) return { commit, upstream: parent, message } From 5c14ec2bed7048a24071de856976cbda4362ddc9 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 08:10:36 +0000 Subject: [PATCH 05/20] fix: preserve upstream ancestry on kilo merge --- script/upstream/merge.ts | 4 ++++ script/upstream/utils/git.test.ts | 26 ++++++++++++++++++++++++-- script/upstream/utils/git.ts | 10 ++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/script/upstream/merge.ts b/script/upstream/merge.ts index e4ac18f7441..77f3faa4713 100644 --- a/script/upstream/merge.ts +++ b/script/upstream/merge.ts @@ -548,6 +548,10 @@ async function main() { logger.step(7, 8, "Merging into Kilo branch...") await git.checkout(kiloBranch) + if (prior) { + const linked = await git.recordAncestor(targetVersion.commit, `merge: record upstream ${targetVersion.tag}`) + if (linked) logger.info(`Recorded upstream ${targetVersion.tag} as Kilo branch ancestry`) + } const mergeResult = await git.merge(opencodeBranch) if (!mergeResult.success) { diff --git a/script/upstream/utils/git.test.ts b/script/upstream/utils/git.test.ts index f841d74bf59..7d1f5e3c904 100644 --- a/script/upstream/utils/git.test.ts +++ b/script/upstream/utils/git.test.ts @@ -3,7 +3,16 @@ import { mkdtemp, rm } from "node:fs/promises" import { join } from "node:path" import { tmpdir } from "node:os" import { $ } from "bun" -import { createCommit, findLatestCompatCommit, getCommitHash, getCommitParents, updateBranch, writeTree } from "./git" +import { + createCommit, + findLatestCompatCommit, + getCommitHash, + getCommitParents, + isAncestor, + recordAncestor, + updateBranch, + writeTree, +} from "./git" const cwd = process.cwd() let dir = "" @@ -51,8 +60,21 @@ test("finds previous compatibility commit for transformed base", async () => { const tree = await writeTree() const next = await createCommit(tree, "refactor: kilo compat for v1.0.1", prior) await updateBranch("opencode-v1.0.1", next) - const base = (await $`git merge-base main opencode-v1.0.1`.text()).trim() expect(base).toBe(prior) expect(await getCommitParents(next)).toEqual([prior]) + + await $`git checkout main`.quiet() + expect(await recordAncestor(target, "merge: record upstream v1.0.1")).toBe(true) + const link = await getCommitHash("HEAD") + expect(await getCommitParents(link)).toEqual([prior, target]) + expect(await isAncestor(target, link)).toBe(true) + + const linked = (await $`git merge-base main opencode-v1.0.1`.text()).trim() + expect(linked).toBe(prior) + + await $`git merge opencode-v1.0.1`.quiet() + const head = await getCommitHash("HEAD") + expect(await getCommitParents(head)).toEqual([link, next]) + expect(await isAncestor(target, head)).toBe(true) }) diff --git a/script/upstream/utils/git.ts b/script/upstream/utils/git.ts index d8730d2d378..35588124e00 100644 --- a/script/upstream/utils/git.ts +++ b/script/upstream/utils/git.ts @@ -205,6 +205,16 @@ export async function updateBranch(name: string, commit: string): Promise await $`git update-ref refs/heads/${name} ${commit}` } +export async function recordAncestor(ref: string, message: string): Promise { + if (await isAncestor(ref, "HEAD")) return false + + const result = await $`git merge -s ours --no-ff ${ref} -m ${message}`.nothrow() + if (result.exitCode !== 0) { + throw new Error(`Failed to record ancestor ${ref}: ${result.stderr.toString()}`) + } + return true +} + async function compatUpstream(message: string): Promise { const prefix = "refactor: kilo compat for " if (!message.startsWith(prefix)) return null From 70fde2221e3cca5624c0a728b956483b3f2e345a Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 14:34:13 +0000 Subject: [PATCH 06/20] refactor(upstream): improve compatibility commit lookup using semver Update `findLatestCompatCommit` to use semantic versioning when identifying the latest compatibility commit. This ensures the correct commit is selected even when upstream tags diverge or are not strictly linear. - Add semver parsing and comparison utilities - Implement `targetSemver` to resolve versions from tags or commit messages - Filter and sort compatibility candidates by version relative to the target version - Add test case for diverging upstream tags --- script/upstream/utils/git.test.ts | 30 +++++++++++++ script/upstream/utils/git.ts | 70 +++++++++++++++++++++++++++++-- 2 files changed, 96 insertions(+), 4 deletions(-) diff --git a/script/upstream/utils/git.test.ts b/script/upstream/utils/git.test.ts index 7d1f5e3c904..b51dd35ea4a 100644 --- a/script/upstream/utils/git.test.ts +++ b/script/upstream/utils/git.test.ts @@ -78,3 +78,33 @@ test("finds previous compatibility commit for transformed base", async () => { expect(await getCommitParents(head)).toEqual([link, next]) expect(await isAncestor(target, head)).toBe(true) }) + +test("finds previous compatibility commit when upstream tags diverge", async () => { + await Bun.write("brand.txt", "opencode 1.4.9\n") + const old = await commit("release: v1.4.9") + await $`git tag v1.4.9 ${old}`.quiet() + + await $`git checkout -b release-30 ${old}`.quiet() + await Bun.write("brand.txt", "opencode 1.14.30\n") + const side = await commit("release: v1.14.30") + await $`git tag v1.14.30 ${side}`.quiet() + + await $`git checkout -b release-31 ${old}`.quiet() + await Bun.write("brand.txt", "opencode 1.14.31\n") + const target = await commit("release: v1.14.31") + await $`git tag v1.14.31 ${target}`.quiet() + + await $`git checkout -b main ${old}`.quiet() + await Bun.write("brand.txt", "kilo 1.4.9\n") + const ancient = await commit("refactor: kilo compat for v1.4.9") + await Bun.write("brand.txt", "kilo 1.14.30\n") + const prior = await commit("refactor: kilo compat for v1.14.30") + + expect(await isAncestor(side, target)).toBe(false) + expect(await isAncestor(old, target)).toBe(true) + + const found = await findLatestCompatCommit("main", target) + expect(found?.commit).toBe(prior) + expect(found?.upstream).toBe(side) + expect(found?.commit).not.toBe(ancient) +}) diff --git a/script/upstream/utils/git.ts b/script/upstream/utils/git.ts index 69d965cd553..e23823ed9a4 100644 --- a/script/upstream/utils/git.ts +++ b/script/upstream/utils/git.ts @@ -21,6 +21,12 @@ export interface CompatBase { message: string } +type Semver = readonly [number, number, number] + +interface Candidate extends CompatBase { + version: Semver +} + export async function getCurrentBranch(): Promise { const result = await $`git rev-parse --abbrev-ref HEAD`.text() return result.trim() @@ -216,10 +222,7 @@ export async function recordAncestor(ref: string, message: string): Promise { - const prefix = "refactor: kilo compat for " - if (!message.startsWith(prefix)) return null - - const tag = message.slice(prefix.length).trim().split(/\s+/)[0] + const tag = compatTag(message) if (!tag) return null const ref = `${tag}^{commit}` @@ -228,6 +231,55 @@ async function compatUpstream(message: string): Promise { return result.stdout.toString().trim() } +function compatTag(message: string): string | null { + const prefix = "refactor: kilo compat for " + if (!message.startsWith(prefix)) return null + return message.slice(prefix.length).trim().split(/\s+/)[0] ?? null +} + +function parseSemver(value: string | undefined): Semver | null { + const match = value?.match(/^v?(\d+)\.(\d+)\.(\d+)$/) + if (!match) return null + const major = Number.parseInt(match[1] ?? "0", 10) + const minor = Number.parseInt(match[2] ?? "0", 10) + const patch = Number.parseInt(match[3] ?? "0", 10) + return [major, minor, patch] +} + +function compareSemver(a: Semver, b: Semver): number { + for (const idx of [0, 1, 2] as const) { + if (a[idx] < b[idx]) return -1 + if (a[idx] > b[idx]) return 1 + } + return 0 +} + +function exists(value: T | null): value is T { + return value !== null +} + +async function targetSemver(ref: string): Promise { + const tags = await getTagsForCommit(ref) + const tag = tags.find((item) => parseSemver(item) !== null) + const parsed = parseSemver(tag) + if (parsed) return parsed + + const message = await getCommitMessage(ref) + const match = message.match(/\bv?\d+\.\d+\.\d+\b/) + return parseSemver(match?.[0]) +} + +async function candidate(line: string): Promise { + const [commit, message = ""] = line.split("\0") + if (!commit) return null + + const version = parseSemver(compatTag(message) ?? undefined) + if (!version) return null + + const upstream = (await compatUpstream(message)) ?? (await getCommitParents(commit))[0] ?? commit + return { commit, upstream, message, version } +} + export async function findLatestCompatCommit(base: string, target: string): Promise { const grep = "^refactor: kilo compat for " const result = await $`git log --format=%H%x00%s --grep=${grep} ${base}`.quiet().nothrow() @@ -241,6 +293,16 @@ export async function findLatestCompatCommit(base: string, target: string): Prom .split("\n") .filter((line) => line.length > 0) + const targetVersion = await targetSemver(target) + if (targetVersion) { + const candidates = (await Promise.all(lines.map(candidate))) + .filter(exists) + .filter((item) => compareSemver(item.version, targetVersion) < 0) + .sort((a, b) => compareSemver(b.version, a.version)) + const latest = candidates[0] + if (latest) return { commit: latest.commit, upstream: latest.upstream, message: latest.message } + } + for (const line of lines) { const [commit, message = ""] = line.split("\0") if (!commit) continue From fc6ce735c9dde64a544e0952d789397fa9fb7815 Mon Sep 17 00:00:00 2001 From: Marius Date: Mon, 11 May 2026 09:21:37 +0200 Subject: [PATCH 07/20] fix(vscode): hide empty task header graph (#10125) --- .changeset/hide-empty-task-header-graph.md | 5 +++++ .../webview-ui/src/components/chat/TaskHeader.tsx | 10 +++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 .changeset/hide-empty-task-header-graph.md diff --git a/.changeset/hide-empty-task-header-graph.md b/.changeset/hide-empty-task-header-graph.md new file mode 100644 index 00000000000..06609ec09aa --- /dev/null +++ b/.changeset/hide-empty-task-header-graph.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Hide the task header graph area until timeline data is available. diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/TaskHeader.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/TaskHeader.tsx index 409bc62bbcb..e04f51dc7d3 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/TaskHeader.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/TaskHeader.tsx @@ -78,6 +78,14 @@ export const TaskHeader: Component = (props) => { return undefined }) + const hasTimeline = createMemo(() => { + for (const m of session.messages()) { + if (m.role !== "assistant") continue + if (session.getParts(m.id).some((p) => p.type !== "step-start")) return true + } + return false + }) + const fmtNum = (n: number): string => { if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M` if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K` @@ -166,7 +174,7 @@ export const TaskHeader: Component = (props) => { {/* Expanded graph section: timeline + context bar + token breakdown */} - m.role === "assistant")}> +
From 3259008074cafe232a30d380e9ecc73c63ca0a35 Mon Sep 17 00:00:00 2001 From: Marius Date: Mon, 11 May 2026 09:27:41 +0200 Subject: [PATCH 08/20] fix(vscode): stabilize shell command streaming (#10127) --- .changeset/stable-shell-streaming.md | 5 ++++ .../src/components/chat/AssistantMessage.tsx | 29 ++++++++++++------- .../webview-ui/src/context/session.tsx | 9 ++++-- 3 files changed, 30 insertions(+), 13 deletions(-) create mode 100644 .changeset/stable-shell-streaming.md diff --git a/.changeset/stable-shell-streaming.md b/.changeset/stable-shell-streaming.md new file mode 100644 index 00000000000..bdae1f250a5 --- /dev/null +++ b/.changeset/stable-shell-streaming.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Keep shell command blocks stable while output streams so pending animations and expanded state do not reset. diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx index 998d610d579..9acf713193d 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx @@ -67,19 +67,26 @@ interface AssistantMessageProps { feedback?: MessageFeedbackControls } +type ToolStateProps = { + input?: Record + metadata?: Record + output?: string + status?: string +} + function TodoToolCard(props: { part: ToolPart }) { const render = ToolRegistry.render(props.part.tool) - const state = props.part.state as any + const state = () => props.part.state as ToolStateProps return ( {(renderFn) => ( @@ -90,23 +97,23 @@ function TodoToolCard(props: { part: ToolPart }) { function BashToolCard(props: { part: ToolPart; defaultOpen: boolean }) { const render = ToolRegistry.render(props.part.tool) - const state = props.part.state as any + const state = () => props.part.state as ToolStateProps return ( {(card) => ( >} - input={state?.input ?? {}} - metadata={state?.metadata ?? {}} + input={state()?.input ?? {}} + metadata={state()?.metadata ?? {}} partMetadata={props.part.metadata ?? {}} tool={props.part.tool} partID={props.part.id} callID={props.part.callID} - output={state?.output} - status={state?.status} + output={state()?.output} + status={state()?.status} defaultOpen={props.defaultOpen} animate - reveal={state?.status === "pending" || state?.status === "running"} + reveal={state()?.status === "pending" || state()?.status === "running"} /> )} diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index 8ebe0d41edf..2da1f032c68 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -1192,8 +1192,13 @@ export const SessionProvider: ParentComponent = (props) => { // Append text delta to text or reasoning parts ;(existing as { text: string }).text += delta.textDelta } else { - // Replace entire part - parts[effectiveMessageID][existingIndex] = part + // Preserve the proxy identity so Solid does not remount tool UI + // during streaming updates and restart pending animations. + const target = existing as unknown as Record + for (const key of Object.keys(target)) { + if (!(key in part)) delete target[key] + } + Object.assign(existing, part) } } else { // Add new part From 27da09614efa15375cd2d32ce2e8cfdbc12e60a2 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Thu, 7 May 2026 12:26:10 +0200 Subject: [PATCH 09/20] fix: restore root package.json entries dropped by upstream-compat The automated kilo compat transform keeps upstream's root scripts/catalog wholesale and only re-applies a handful of Kilo-specific scripts, which silently dropped: - postinstall's `&& bun run script/setup-git.ts` tail (needed to set merge.conflictStyle=zdiff3 locally, which upstream merges rely on) - the `dev-setup` script shorthand - kept dead `dev:desktop` / `dev:web` / `dev:console` scripts whose target packages aren't tracked in Kilo - kept `@sentry/solid` / `@sentry/vite-plugin` catalog entries that have zero consumers in our tree --- package.json | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index d5cbbaf4375..e4f650194da 100644 --- a/package.json +++ b/package.json @@ -7,13 +7,11 @@ "packageManager": "bun@1.3.13", "scripts": { "dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts", - "dev:desktop": "bun --cwd packages/desktop-electron dev", - "dev:web": "bun --cwd packages/app dev", - "dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev", + "dev-setup": "bun run --cwd packages/opencode --conditions=browser src/index.ts dev-setup", "dev:storybook": "bun --cwd packages/storybook storybook", "lint": "oxlint", "typecheck": "bun turbo typecheck", - "postinstall": "bun run --cwd packages/opencode fix-node-pty", + "postinstall": "bun run --cwd packages/opencode fix-node-pty && bun run script/setup-git.ts", "prepare": "husky", "random": "echo 'Random script'", "hello": "echo 'Hello World!'", @@ -76,8 +74,6 @@ "@solidjs/meta": "0.29.4", "@solidjs/router": "0.15.4", "@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020", - "@sentry/solid": "10.36.0", - "@sentry/vite-plugin": "4.6.0", "solid-js": "1.9.12", "vite-plugin-solid": "2.11.10", "@lydell/node-pty": "1.2.0-beta.10" From a156e7a98d01a7b343a2669dac85128c0f463346 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Thu, 7 May 2026 13:41:00 +0200 Subject: [PATCH 10/20] chore(upstream): preserve & prune root package.json entries on merge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The transform that materialises the 'kilo compat for vX.Y.Z' commit during an upstream merge takes upstream's package.json wholesale and re-applies a hand-picked list of Kilo scripts. That list was incomplete: it only covered `extension`, `changeset`, `changeset:version`, `test`, and `test:ci`, so every merge silently dropped Kilo's `postinstall` tail (`&& bun run script/setup-git.ts`) and the `dev-setup` root shortcut, and it never pruned upstream-only scripts (`dev:desktop` / `dev:web` / `dev:console`) or upstream-only catalog entries (`@sentry/solid`, `@sentry/vite-plugin`) whose target packages Kilo doesn't ship. - replace the per-script if-blocks with data-driven PRESERVE_SCRIPTS - add DELETE_UPSTREAM_SCRIPTS for scripts that reference packages Kilo doesn't ship - add DELETE_UPSTREAM_CATALOG for catalog entries with zero Kilo consumers - apply the same policy in both transformPackageJson (conflict path) and transformAllPackageJson (pre-merge sweep path) — previously the pre-merge sweep was missing `changeset` / `changeset:version` preservation - new tests covering preservation, deletion, opencode test scripts, and the catalog pruning --- .../transforms/transform-package-json.test.ts | 86 ++++++++++++ .../transforms/transform-package-json.ts | 131 +++++++++--------- 2 files changed, 155 insertions(+), 62 deletions(-) create mode 100644 script/upstream/transforms/transform-package-json.test.ts diff --git a/script/upstream/transforms/transform-package-json.test.ts b/script/upstream/transforms/transform-package-json.test.ts new file mode 100644 index 00000000000..11016f0e135 --- /dev/null +++ b/script/upstream/transforms/transform-package-json.test.ts @@ -0,0 +1,86 @@ +import { expect, test } from "bun:test" +import { fixCatalog, fixScripts } from "./transform-package-json" + +test("fixScripts preserves Kilo-only root scripts from base", () => { + const ours = { + scripts: { + "dev-setup": "kilo dev-setup", + "postinstall": "bun run --cwd packages/opencode fix-node-pty && bun run script/setup-git.ts", + "extension": "bun --cwd packages/kilo-vscode script/launch.ts", + }, + } + const pkg: Record = { + scripts: { postinstall: "bun run --cwd packages/opencode fix-node-pty" }, + } + const changes: string[] = [] + fixScripts(pkg, "package.json", ours, changes) + const scripts = pkg.scripts as Record + expect(scripts.postinstall).toBe(ours.scripts.postinstall) + expect(scripts["dev-setup"]).toBe(ours.scripts["dev-setup"]) + expect(scripts.extension).toBe(ours.scripts.extension) + expect(changes.some((c) => c.includes("postinstall"))).toBe(true) + expect(changes.some((c) => c.includes("dev-setup"))).toBe(true) +}) + +test("fixScripts removes upstream-only dead scripts from root", () => { + const pkg: Record = { + scripts: { + "dev": "bun run --cwd packages/opencode src/index.ts", + "dev:desktop": "bun --cwd packages/desktop-electron dev", + "dev:web": "bun --cwd packages/app dev", + "dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev", + }, + } + const changes: string[] = [] + fixScripts(pkg, "package.json", null, changes) + const scripts = pkg.scripts as Record + expect(scripts.dev).toBeDefined() + expect(scripts["dev:desktop"]).toBeUndefined() + expect(scripts["dev:web"]).toBeUndefined() + expect(scripts["dev:console"]).toBeUndefined() + expect(changes.length).toBe(3) +}) + +test("fixScripts preserves opencode test scripts", () => { + const ours = { scripts: { test: "bun test", "test:ci": "bun test --ci" } } + const pkg: Record = { scripts: { test: "vitest" } } + const changes: string[] = [] + fixScripts(pkg, "packages/opencode/package.json", ours, changes) + const scripts = pkg.scripts as Record + expect(scripts.test).toBe("bun test") + expect(scripts["test:ci"]).toBe("bun test --ci") +}) + +test("fixScripts leaves unknown packages untouched", () => { + const pkg: Record = { scripts: { build: "tsc" } } + const changes: string[] = [] + fixScripts(pkg, "packages/some-unknown/package.json", null, changes) + expect((pkg.scripts as Record).build).toBe("tsc") + expect(changes.length).toBe(0) +}) + +test("fixCatalog removes upstream-only desktop sentry entries", () => { + const pkg: Record = { + workspaces: { + catalog: { + "@sentry/solid": "10.36.0", + "@sentry/vite-plugin": "4.6.0", + "solid-js": "1.9.12", + }, + }, + } + const changes: string[] = [] + fixCatalog(pkg, "package.json", changes) + const cat = (pkg.workspaces as { catalog: Record }).catalog + expect(cat["@sentry/solid"]).toBeUndefined() + expect(cat["@sentry/vite-plugin"]).toBeUndefined() + expect(cat["solid-js"]).toBe("1.9.12") + expect(changes.length).toBe(2) +}) + +test("fixCatalog is a no-op when catalog is absent", () => { + const pkg: Record = {} + const changes: string[] = [] + fixCatalog(pkg, "package.json", changes) + expect(changes.length).toBe(0) +}) diff --git a/script/upstream/transforms/transform-package-json.ts b/script/upstream/transforms/transform-package-json.ts index bbc812ea958..327ee30facc 100644 --- a/script/upstream/transforms/transform-package-json.ts +++ b/script/upstream/transforms/transform-package-json.ts @@ -204,6 +204,69 @@ const TRANSFORM_PACKAGE_NAMES: Record = { "packages/sdk/js/package.json": "@kilocode/sdk", } +// Kilo-specific scripts to preserve from the base branch per package.json. +// Upstream's version wholesale-replaces the scripts block, so anything listed +// here gets re-applied from ours after taking theirs. +const PRESERVE_SCRIPTS: Record = { + "package.json": ["extension", "changeset", "changeset:version", "dev-setup", "postinstall"], + "packages/opencode/package.json": ["test", "test:ci"], +} + +// Upstream-only scripts to delete per package.json. These reference packages +// Kilo doesn't ship (desktop-electron, console/app, app) and would otherwise +// reappear on every merge. +const DELETE_UPSTREAM_SCRIPTS: Record = { + "package.json": ["dev:desktop", "dev:web", "dev:console"], +} + +// Upstream-only catalog entries to delete per package.json. These are pulled +// in by upstream features (e.g. desktop Sentry integration) that Kilo doesn't +// ship, so they add install weight with zero consumers in our tree. +const DELETE_UPSTREAM_CATALOG: Record = { + "package.json": ["@sentry/solid", "@sentry/vite-plugin"], +} + +/** + * Re-apply Kilo-specific scripts on top of the upstream-shaped scripts block, + * and prune upstream-only scripts that target packages Kilo doesn't ship. + */ +export function fixScripts(pkg: Record, path: string, ours: Record | null, changes: string[]): void { + const theirs = (pkg.scripts as Record | undefined) || {} + const oursScripts = (ours?.scripts as Record | undefined) || {} + + for (const name of PRESERVE_SCRIPTS[path] || []) { + const val = oursScripts[name] + if (val && theirs[name] !== val) { + theirs[name] = val + changes.push(`scripts.${name}: preserved from base`) + } + } + + for (const name of DELETE_UPSTREAM_SCRIPTS[path] || []) { + if (theirs[name]) { + delete theirs[name] + changes.push(`scripts.${name}: removed (upstream-only, no Kilo target)`) + } + } + + if (Object.keys(theirs).length > 0) pkg.scripts = theirs +} + +/** + * Prune upstream-only catalog entries that have no consumers in Kilo. + */ +export function fixCatalog(pkg: Record, path: string, changes: string[]): void { + const ws = pkg.workspaces as { catalog?: Record } | undefined + const cat = ws?.catalog + if (!cat) return + for (const name of DELETE_UPSTREAM_CATALOG[path] || []) { + if (cat[name]) { + delete cat[name] + changes.push(`workspaces.catalog.${name}: removed (upstream-only, no Kilo consumer)`) + } + } +} + /** * Check if file is a package.json */ @@ -358,46 +421,7 @@ export async function transformPackageJson(file: string, options: PackageJsonOpt changes.push(`workspaces.packages: preserved Kilo's workspace configuration`) } - const ourScripts = ourPkg.scripts as Record | undefined - if (relativePath === "package.json" && ourScripts?.extension && pkg.scripts?.extension !== ourScripts.extension) { - pkg.scripts = pkg.scripts || {} - pkg.scripts.extension = ourScripts.extension - changes.push(`scripts.extension: preserved Kilo's extension script`) - } - if (relativePath === "package.json" && ourScripts?.changeset && pkg.scripts?.changeset !== ourScripts.changeset) { - pkg.scripts = pkg.scripts || {} - pkg.scripts.changeset = ourScripts.changeset - changes.push(`scripts.changeset: preserved Kilo's changeset script`) - } - if ( - relativePath === "package.json" && - ourScripts?.["changeset:version"] && - pkg.scripts?.["changeset:version"] !== ourScripts["changeset:version"] - ) { - pkg.scripts = pkg.scripts || {} - pkg.scripts["changeset:version"] = ourScripts["changeset:version"] - changes.push(`scripts.changeset:version: preserved Kilo's changeset:version script`) - } - - // Preserve Kilo's test runner scripts for packages/opencode - if ( - relativePath === "packages/opencode/package.json" && - ourScripts?.test && - pkg.scripts?.test !== ourScripts.test - ) { - pkg.scripts = pkg.scripts || {} - pkg.scripts.test = ourScripts.test - changes.push(`scripts.test: preserved Kilo's test runner script`) - } - if ( - relativePath === "packages/opencode/package.json" && - ourScripts?.["test:ci"] && - pkg.scripts?.["test:ci"] !== ourScripts["test:ci"] - ) { - pkg.scripts = pkg.scripts || {} - pkg.scripts["test:ci"] = ourScripts["test:ci"] - changes.push(`scripts.test:ci: preserved Kilo's CI test runner script`) - } + fixScripts(pkg, relativePath, ourPkg, changes) // Merge catalog with "newest wins" strategy if (ourWorkspaces?.catalog || theirWorkspaces?.catalog) { @@ -409,6 +433,8 @@ export async function transformPackageJson(file: string, options: PackageJsonOpt "workspaces.catalog", ) } + + fixCatalog(pkg, relativePath, changes) } // 7. Transform dependency names (opencode -> kilo) @@ -617,28 +643,7 @@ export async function transformAllPackageJson(options: PackageJsonOptions = {}): changes.push(`workspaces.packages: preserved Kilo's workspace configuration`) } - const kiloScripts = kiloPkg.scripts as Record | undefined - if (path === "package.json" && kiloScripts?.extension && pkg.scripts?.extension !== kiloScripts.extension) { - pkg.scripts = pkg.scripts || {} - pkg.scripts.extension = kiloScripts.extension - changes.push(`scripts.extension: preserved Kilo's extension script`) - } - - // Preserve Kilo's test runner scripts for packages/opencode - if (path === "packages/opencode/package.json" && kiloScripts?.test && pkg.scripts?.test !== kiloScripts.test) { - pkg.scripts = pkg.scripts || {} - pkg.scripts.test = kiloScripts.test - changes.push(`scripts.test: preserved Kilo's test runner script`) - } - if ( - path === "packages/opencode/package.json" && - kiloScripts?.["test:ci"] && - pkg.scripts?.["test:ci"] !== kiloScripts["test:ci"] - ) { - pkg.scripts = pkg.scripts || {} - pkg.scripts["test:ci"] = kiloScripts["test:ci"] - changes.push(`scripts.test:ci: preserved Kilo's CI test runner script`) - } + fixScripts(pkg, path, kiloPkg, changes) // Merge catalog with "newest wins" strategy if (kiloWorkspaces?.catalog || upstreamWorkspaces?.catalog) { @@ -650,6 +655,8 @@ export async function transformAllPackageJson(options: PackageJsonOptions = {}): "workspaces.catalog", ) } + + fixCatalog(pkg, path, changes) } // 7. Transform dependency names (opencode -> kilo) From d79664f0841f93dace85c35239c12a8d64bec35f Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Thu, 7 May 2026 16:44:50 +0200 Subject: [PATCH 11/20] fix(upstream-merge): preserve ours' key order in package.json dep merge Seeding the result with theirs' keys and appending ours-only keys at the end caused kilo-only deps (e.g. rotating-file-stream in packages/core) to relocate from the middle of the deps block to the end during the pre-merge transform. Git's textual 3-way merge then saw ours keeping the line in place and theirs adding the same key elsewhere, producing a duplicate JSON key in the merged file. Iterate ours first so kilo-only deps stay in their original position, then append any theirs-only keys at the end. --- .../transforms/transform-package-json.test.ts | 32 ++++++++- .../transforms/transform-package-json.ts | 66 ++++++++++--------- 2 files changed, 67 insertions(+), 31 deletions(-) diff --git a/script/upstream/transforms/transform-package-json.test.ts b/script/upstream/transforms/transform-package-json.test.ts index 11016f0e135..a239bcc5edf 100644 --- a/script/upstream/transforms/transform-package-json.test.ts +++ b/script/upstream/transforms/transform-package-json.test.ts @@ -1,5 +1,5 @@ import { expect, test } from "bun:test" -import { fixCatalog, fixScripts } from "./transform-package-json" +import { fixCatalog, fixScripts, mergeWithNewestVersions } from "./transform-package-json" test("fixScripts preserves Kilo-only root scripts from base", () => { const ours = { @@ -84,3 +84,33 @@ test("fixCatalog is a no-op when catalog is absent", () => { fixCatalog(pkg, "package.json", changes) expect(changes.length).toBe(0) }) + +test("mergeWithNewestVersions preserves ours' key order so kilo-only deps don't relocate", () => { + // Regression: when ours has a kilo-only dep in the middle (e.g. rotating-file-stream + // alphabetically between npm-package-arg and semver) and theirs lacks it, the merge + // result must keep that key in its original position. Previously this function + // started from theirs' keys and appended ours-only keys at the end, causing git's + // textual 3-way merge to produce a duplicate JSON key. + const ours = { + "npm-package-arg": "13.0.2", + "rotating-file-stream": "3.2.9", + semver: "^7.6.3", + zod: "catalog:", + } + const theirs = { + "npm-package-arg": "13.0.2", + semver: "^7.6.3", + zod: "catalog:", + } + const changes: string[] = [] + const result = mergeWithNewestVersions(ours, theirs, changes, "dependencies") + expect(Object.keys(result)).toEqual(["npm-package-arg", "rotating-file-stream", "semver", "zod"]) +}) + +test("mergeWithNewestVersions appends theirs-only keys at the end", () => { + const ours = { a: "1.0.0", b: "1.0.0" } + const theirs = { a: "1.0.0", c: "1.0.0" } + const changes: string[] = [] + const result = mergeWithNewestVersions(ours, theirs, changes, "dependencies") + expect(Object.keys(result)).toEqual(["a", "b", "c"]) +}) diff --git a/script/upstream/transforms/transform-package-json.ts b/script/upstream/transforms/transform-package-json.ts index 327ee30facc..45f396a18ff 100644 --- a/script/upstream/transforms/transform-package-json.ts +++ b/script/upstream/transforms/transform-package-json.ts @@ -108,8 +108,14 @@ function compareVersions(a: string, b: string): number | null { /** * Merge two dependency objects using "newest wins" strategy * For non-comparable versions (URLs, catalog:, workspace:*), upstream (theirs) wins + * + * Key order preserves ours' order first (so kilo-only deps stay in their + * original position), then appends theirs-only keys at the end. This avoids + * relocating existing keys, which would otherwise let git's textual merge + * produce duplicate JSON keys (ours keeps the line in place, theirs appears + * to "add" the same key elsewhere → both survive the merge). */ -function mergeWithNewestVersions( +export function mergeWithNewestVersions( ours: Record | undefined, theirs: Record | undefined, changes: string[], @@ -117,38 +123,38 @@ function mergeWithNewestVersions( ): Record { const result: Record = {} - // Start with all of theirs - if (theirs) { - for (const [name, version] of Object.entries(theirs)) { - result[name] = version + // Seed with ours' keys in ours' order, applying newest-wins per key. + if (ours) { + for (const [name, ourVersion] of Object.entries(ours)) { + const theirVersion = theirs?.[name] + if (theirVersion === undefined) { + result[name] = ourVersion + changes.push(`${section}: preserved ${name}@${ourVersion} (kilo-only)`) + continue + } + if (ourVersion === theirVersion) { + result[name] = theirVersion + continue + } + const cmp = compareVersions(ourVersion, theirVersion) + if (cmp === null) { + result[name] = theirVersion + changes.push(`${section}: ${name} kept upstream ${theirVersion} (special format)`) + } else if (cmp > 0) { + result[name] = ourVersion + changes.push(`${section}: ${name} ${theirVersion} -> ${ourVersion} (kilo newer)`) + } else { + result[name] = theirVersion + if (cmp < 0) changes.push(`${section}: ${name} kept upstream ${theirVersion} (upstream newer)`) + } } } - // Merge in ours, keeping newer versions - if (ours) { - for (const [name, ourVersion] of Object.entries(ours)) { - const theirVersion = result[name] - - if (!theirVersion) { - // Dependency only exists in ours - keep it - result[name] = ourVersion - changes.push(`${section}: preserved ${name}@${ourVersion} (kilo-only)`) - } else if (ourVersion !== theirVersion) { - // Both have it with different versions - compare - const comparison = compareVersions(ourVersion, theirVersion) - - if (comparison === null) { - // Can't compare (special format) - upstream wins per user preference - changes.push(`${section}: ${name} kept upstream ${theirVersion} (special format)`) - } else if (comparison > 0) { - // Ours is newer - result[name] = ourVersion - changes.push(`${section}: ${name} ${theirVersion} -> ${ourVersion} (kilo newer)`) - } else if (comparison < 0) { - // Theirs is newer - already in result - changes.push(`${section}: ${name} kept upstream ${theirVersion} (upstream newer)`) - } - // If equal, keep theirs (already in result) + // Append any theirs-only keys at the end, preserving theirs' relative order. + if (theirs) { + for (const [name, version] of Object.entries(theirs)) { + if (result[name] === undefined) { + result[name] = version } } } From ee3ee5b06b9b8c06d2ef2a5ce0799c92fc5bdb91 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Thu, 7 May 2026 17:41:58 +0200 Subject: [PATCH 12/20] fix(upstream-merge): always reconcile package.json post-merge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git rerere learns conflict resolutions from past upstream merges and replays them on the next merge. When past resolutions used the buggy mergeWithNewestVersions ordering, rerere auto-resolves package.json files with stale content before transformConflictedPackageJson ever gets a chance to run — so the fixed merge logic never reaches the file. Add reconcileAllPackageJson, which runs after every merge (clean, auto-resolved, or partially conflicted) and rewrites every package.json that the merge touched from the kilo branch's pre-merge HEAD and the opencode compat branch using the same merge logic. Files that are still conflicted are skipped so manual resolution isn't silently overwritten. This makes our merge logic the source of truth for package.json content, regardless of what rerere or git's textual merge produced. --- script/upstream/merge.ts | 35 ++- .../transforms/transform-package-json.ts | 240 ++++++++++++++++++ 2 files changed, 274 insertions(+), 1 deletion(-) diff --git a/script/upstream/merge.ts b/script/upstream/merge.ts index 02933816c1f..332e7795450 100644 --- a/script/upstream/merge.ts +++ b/script/upstream/merge.ts @@ -33,7 +33,11 @@ import { skipFiles } from "./transforms/skip-files" import { transformConflictedI18n, transformAllI18n } from "./transforms/transform-i18n" // New transforms for auto-resolving more conflict types import { transformConflictedTakeTheirs, transformAllTakeTheirs } from "./transforms/transform-take-theirs" -import { transformConflictedPackageJson, transformAllPackageJson } from "./transforms/transform-package-json" +import { + transformConflictedPackageJson, + transformAllPackageJson, + reconcileAllPackageJson, +} from "./transforms/transform-package-json" import { transformConflictedScripts, transformAllScripts } from "./transforms/transform-scripts" import { transformConflictedExtensions, transformAllExtensions } from "./transforms/transform-extensions" import { transformConflictedWeb, transformAllWeb } from "./transforms/transform-web" @@ -707,6 +711,24 @@ async function main() { } } + // Reconcile every package.json that the merge touched, regardless of + // whether it was conflicted, auto-resolved by rerere, or merged textually. + // rerere can replay stale resolutions that bypass our package.json + // transform entirely, so always run our merge logic as the final word for + // package.json content. Skip files that are still conflicted so the user + // can resolve them manually instead of silently overwriting markers. + const stillConflicted = new Set(await git.getConflictedFiles()) + const reconcileResults = await reconcileAllPackageJson({ + oursRef: baseSha, + theirsRef: opencodeBranch, + verbose: options.verbose, + skip: stillConflicted, + }) + const reconcileCount = reconcileResults.filter((r) => r.action === "transformed" && r.changes.length > 0).length + if (reconcileCount > 0) { + logger.success(`Reconciled ${reconcileCount} package.json file(s) post-merge`) + } + // Check remaining conflicts const remaining = await git.getConflictedFiles() // Combine git-reported conflicts with files flagged due to kilocode_change markers @@ -764,6 +786,17 @@ async function main() { } } else { logger.success("Merge completed without conflicts!") + // Same reconcile pass as the conflict path: ensure rerere or git's textual + // merge can't slip stale package.json resolutions through. + const reconcileResults = await reconcileAllPackageJson({ + oursRef: baseSha, + theirsRef: opencodeBranch, + verbose: options.verbose, + }) + const reconcileCount = reconcileResults.filter((r) => r.action === "transformed" && r.changes.length > 0).length + if (reconcileCount > 0) { + logger.success(`Reconciled ${reconcileCount} package.json file(s) post-merge`) + } await git.stageAll() const hasChanges = await git.hasUncommittedChanges() if (hasChanges) { diff --git a/script/upstream/transforms/transform-package-json.ts b/script/upstream/transforms/transform-package-json.ts index 45f396a18ff..501786e1453 100644 --- a/script/upstream/transforms/transform-package-json.ts +++ b/script/upstream/transforms/transform-package-json.ts @@ -175,6 +175,17 @@ export interface PackageJsonOptions { preserveVersion?: boolean } +export interface ReconcileOptions extends PackageJsonOptions { + oursRef: string + theirsRef: string + /** + * Files to skip (e.g. still-conflicted files where the user is going to + * resolve manually). The reconciler would otherwise overwrite the conflict + * markers and silently auto-resolve. + */ + skip?: Set +} + // Package name mappings const PACKAGE_NAME_MAP: Record = { "opencode-ai": "@kilocode/cli", @@ -729,6 +740,235 @@ export async function transformAllPackageJson(options: PackageJsonOptions = {}): return results } +/** + * Reconcile a single package.json after a merge has finished, regardless of + * whether it was conflicted or auto-resolved (by rerere or git's textual + * merge). Reads ours from `oursRef` and theirs from `theirsRef`, then applies + * the same merge logic used for conflict resolution and writes the result to + * the working tree. Stages the file. + * + * This is needed because rerere can replay stale resolutions for files like + * `package.json` that include cosmetic reordering — those resolutions bypass + * `transformConflictedPackageJson` entirely. Running this reconciler after + * the merge guarantees our merge logic always wins. + * + * Returns "skipped" if neither side touched the file (or both sides match) so + * callers can avoid unnecessary churn. Returns "flagged" if ours has + * kilocode_change markers (manual review needed). + */ +export async function reconcilePackageJsonFromRefs( + file: string, + options: ReconcileOptions, +): Promise { + const changes: string[] = [] + const dryRun = options.dryRun ?? false + + if (await oursHasKilocodeChanges(file)) { + warn(`${file} has kilocode_change markers — skipping reconcile, needs manual resolution`) + return { file, action: "flagged", changes: [], dryRun } + } + + let ourPkg: Record | null = null + try { + const ourContent = await $`git show ${options.oursRef}:${file}`.text() + ourPkg = JSON.parse(ourContent) + } catch { + // file didn't exist in ours - that's fine + } + + let pkg: Record | null = null + try { + const theirContent = await $`git show ${options.theirsRef}:${file}`.text() + pkg = JSON.parse(theirContent) + } catch { + // file didn't exist in theirs either - nothing to reconcile + } + + if (!pkg) { + if (!ourPkg) return { file, action: "skipped", changes: [], dryRun } + pkg = JSON.parse(JSON.stringify(ourPkg)) + } + + const relativePath = file.replace(process.cwd() + "/", "") + const newName = TRANSFORM_PACKAGE_NAMES[relativePath] + if (newName && pkg.name !== newName) { + changes.push(`name: ${pkg.name} -> ${newName}`) + pkg.name = newName + } + + if (options.preserveVersion !== false) { + const kiloVersion = await getCurrentVersion() + if (pkg.version !== kiloVersion) { + changes.push(`version: ${pkg.version} -> ${kiloVersion}`) + pkg.version = kiloVersion + } + } + + if (ourPkg) { + pkg.dependencies = mergeWithNewestVersions( + ourPkg.dependencies as Record | undefined, + pkg.dependencies as Record | undefined, + changes, + "dependencies", + ) + pkg.devDependencies = mergeWithNewestVersions( + ourPkg.devDependencies as Record | undefined, + pkg.devDependencies as Record | undefined, + changes, + "devDependencies", + ) + pkg.peerDependencies = mergeWithNewestVersions( + ourPkg.peerDependencies as Record | undefined, + pkg.peerDependencies as Record | undefined, + changes, + "peerDependencies", + ) + + const ourOverrides = ourPkg.overrides as Record | undefined + if (ourOverrides || pkg.overrides) { + pkg.overrides = mergeWithNewestVersions( + ourOverrides, + pkg.overrides as Record | undefined, + changes, + "overrides", + ) + } + + const ourPatched = ourPkg.patchedDependencies as Record | undefined + if (ourPatched) { + pkg.patchedDependencies = (pkg.patchedDependencies as Record) || {} + const patched = pkg.patchedDependencies as Record + for (const [name, patch] of Object.entries(ourPatched)) { + if (!patched[name]) { + patched[name] = patch + changes.push(`patchedDependencies: preserved ${name}`) + } + } + } + + const ourRepo = ourPkg.repository + if (ourRepo && JSON.stringify(pkg.repository) !== JSON.stringify(ourRepo)) { + pkg.repository = ourRepo + changes.push(`repository: preserved Kilo's repository configuration`) + } + + const ourWs = ourPkg.workspaces as { packages?: string[]; catalog?: Record } | undefined + const theirWs = pkg.workspaces as { packages?: string[]; catalog?: Record } | undefined + + if (relativePath === "package.json" && ourWs?.packages) { + pkg.workspaces = (pkg.workspaces as Record) || {} + ;(pkg.workspaces as { packages: string[] }).packages = ourWs.packages + changes.push(`workspaces.packages: preserved Kilo's workspace configuration`) + } + + fixScripts(pkg, relativePath, ourPkg, changes) + + if (ourWs?.catalog || theirWs?.catalog) { + pkg.workspaces = (pkg.workspaces as Record) || {} + ;(pkg.workspaces as { catalog: Record }).catalog = mergeWithNewestVersions( + ourWs?.catalog, + theirWs?.catalog, + changes, + "workspaces.catalog", + ) + } + + fixCatalog(pkg, relativePath, changes) + } + + if (pkg.dependencies) { + const { result, changes: depChanges } = transformDependencies(pkg.dependencies as Record) + pkg.dependencies = result + changes.push(...depChanges.map((c) => `dependencies: ${c}`)) + } + if (pkg.devDependencies) { + const { result, changes: devChanges } = transformDependencies(pkg.devDependencies as Record) + if (devChanges.length > 0) { + pkg.devDependencies = result + changes.push(...devChanges.map((c) => `devDependencies: ${c}`)) + } + } + if (pkg.peerDependencies) { + const { result, changes: peerChanges } = transformDependencies(pkg.peerDependencies as Record) + if (peerChanges.length > 0) { + pkg.peerDependencies = result + changes.push(...peerChanges.map((c) => `peerDependencies: ${c}`)) + } + } + + const kiloDeps = KILO_DEPENDENCIES[relativePath] + if (kiloDeps) { + pkg.dependencies = (pkg.dependencies as Record) || {} + const deps = pkg.dependencies as Record + for (const [name, version] of Object.entries(kiloDeps)) { + if (!deps[name]) { + deps[name] = version + changes.push(`injected: ${name}`) + } + } + } + + const kiloBin = KILO_BIN[relativePath] + if (kiloBin) { + pkg.bin = kiloBin + changes.push(`bin: set Kilo bin entries`) + } + + if (dryRun) { + info(`[DRY-RUN] Would reconcile ${file}: ${changes.length} changes`) + return { file, action: "transformed", changes, dryRun: true } + } + + const newContent = JSON.stringify(pkg, null, 2) + "\n" + await Bun.write(file, newContent) + await $`git add ${file}`.quiet().nothrow() + + if (changes.length > 0) { + success(`Reconciled ${file}: ${changes.length} changes`) + if (options.verbose) { + for (const change of changes) debug(` - ${change}`) + } + } + + return { file, action: "transformed", changes, dryRun: false } +} + +/** + * Reconcile every package.json that differs between `oursRef` and `theirsRef` + * after a merge. This is meant to run after `git merge` (whether the merge + * was clean, conflict-resolved, or rerere-replayed) to ensure our merge logic + * is the source of truth for package.json content. + */ +export async function reconcileAllPackageJson(options: ReconcileOptions): Promise { + // Collect every package.json that differs in either direction so we cover + // upstream-only and kilo-only files alike. + const diffOurs = await $`git diff --name-only ${options.oursRef} -- '*package.json'`.text() + const diffTheirs = await $`git diff --name-only ${options.theirsRef} -- '*package.json'`.text() + const candidates = new Set() + for (const line of [...diffOurs.split("\n"), ...diffTheirs.split("\n")]) { + const path = line.trim() + if (!path) continue + if (path.includes("node_modules")) continue + if (!path.endsWith("package.json")) continue + candidates.add(path) + } + + const results: PackageJsonResult[] = [] + for (const file of candidates) { + if (options.skip?.has(file)) { + results.push({ file, action: "skipped", changes: [], dryRun: options.dryRun ?? false }) + continue + } + const f = Bun.file(file) + if (!(await f.exists())) { + // file was removed by the merge - nothing to reconcile + continue + } + results.push(await reconcilePackageJsonFromRefs(file, options)) + } + return results +} + // CLI entry point if (import.meta.main) { const args = process.argv.slice(2) From 8710e9d9de7fbc02cb073919fad3f9925c8bd621 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 08:11:40 +0000 Subject: [PATCH 13/20] feat(upstream): record last merged tag in .opencode-version Avoids the ls-remote + merge-base --is-ancestor walk in last() for the common case by reading the recorded tag from .opencode-version. merge.ts writes the file as part of the pre-merge compat commit so future runs of fix-kilocode-markers, reset-to-upstream, and find-reset-candidates can resolve the base tag instantly. Seeded with v1.14.33 (PR #9978). --- .opencode-version | 1 + script/upstream/README.md | 4 +- script/upstream/merge.ts | 6 +++ script/upstream/utils/upstream.ts | 62 +++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 .opencode-version diff --git a/.opencode-version b/.opencode-version new file mode 100644 index 00000000000..9866d345648 --- /dev/null +++ b/.opencode-version @@ -0,0 +1 @@ +v1.14.33 diff --git a/script/upstream/README.md b/script/upstream/README.md index ed37cd23422..2024a11ad6b 100644 --- a/script/upstream/README.md +++ b/script/upstream/README.md @@ -243,7 +243,9 @@ Options: --dry-run Show what would change without writing the file ``` -The command finds the newest upstream tag already merged into `HEAD`, reads that upstream version of the file, applies the same branding transforms used by upstream merge automation, strips existing `kilocode_change` markers from the current file, and adds fresh markers around the remaining lines that differ from upstream. +The command finds the newest upstream tag already merged into `HEAD` (read from `.opencode-version` at the repo root, falling back to an `ls-remote` + `merge-base --is-ancestor` walk), reads that upstream version of the file, applies the same branding transforms used by upstream merge automation, strips existing `kilocode_change` markers from the current file, and adds fresh markers around the remaining lines that differ from upstream. + +The `.opencode-version` file is a single-line tag (e.g. `v1.14.33`) recorded by `merge.ts` after every successful upstream merge. Editing it by hand pins the "last merged" tag for the per-file commands above; delete it to fall back to the slower automatic discovery. ### reset-to-upstream.ts diff --git a/script/upstream/merge.ts b/script/upstream/merge.ts index 24587c9ecec..bca7a4b497a 100644 --- a/script/upstream/merge.ts +++ b/script/upstream/merge.ts @@ -38,6 +38,7 @@ import { transformConflictedScripts, transformAllScripts } from "./transforms/tr import { transformConflictedExtensions, transformAllExtensions } from "./transforms/transform-extensions" import { transformConflictedWeb, transformAllWeb } from "./transforms/transform-web" import { resolveLockFileConflicts, regenerateLockFiles } from "./transforms/lock-files" +import { writeVersion } from "./utils/upstream" interface MergeOptions { version?: string @@ -508,6 +509,11 @@ async function main() { const keepOursResults = await resetToOurs(config.keepOurs, { dryRun: false, verbose: options.verbose }) logger.success(`Reset ${keepOursResults.length} files to Kilo's version`) + // 6k. Record the last merged upstream tag so future automation can find it + // without walking ls-remote + isAncestor for every tag. + const versionFile = await writeVersion(targetVersion.tag) + logger.success(`Recorded ${targetVersion.tag} in ${versionFile.split("/").pop()}`) + // Clean untracked build artifacts from Kilo-specific directories. // These packages don't exist in upstream, so their .gitignore files are absent // on the opencode branch. Artifacts like bin/, out/, .next/ etc. would otherwise diff --git a/script/upstream/utils/upstream.ts b/script/upstream/utils/upstream.ts index 2ab0c1b3537..c63425eed48 100644 --- a/script/upstream/utils/upstream.ts +++ b/script/upstream/utils/upstream.ts @@ -15,6 +15,12 @@ import { isAncestor } from "./git" const url = "https://github.com/anomalyco/opencode.git" const workflows = [".github/workflows/publish.yml", ".github/workflows/beta.yml"] +/** + * Repo-relative path of the file that records the last merged upstream tag. + * Single line containing the upstream tag (e.g. `v1.14.33`). + */ +export const versionFile = ".opencode-version" + export async function root() { return (await $`git rev-parse --show-toplevel`.text()).trim() } @@ -39,6 +45,9 @@ export async function remote() { } export async function last(): Promise { + const recorded = await readVersionFile() + if (recorded) return recorded + const source = await remote() info(`Fetching upstream tags from ${source}...`) @@ -53,6 +62,59 @@ export async function last(): Promise { throw new Error("Could not find a merged upstream tag in HEAD") } +/** + * Read the recorded last-merged upstream tag from `.opencode-version`. Returns + * null if the file is missing/empty, or if the recorded tag cannot be resolved + * to a commit (e.g. tags have not been fetched yet). Falls back to the + * isAncestor-based discovery in `last()`. + */ +async function readVersionFile(): Promise { + const repo = await root() + const file = Bun.file(`${repo}/${versionFile}`) + if (!(await file.exists())) return null + + const tag = (await file.text()).trim() + if (!tag) return null + + const version = parseVersion(tag) + if (!version) { + warn(`${versionFile} contains '${tag}' which is not a valid version tag; ignoring`) + return null + } + + const commit = await resolveTag(tag) + if (!commit) return null + + return { version, tag, commit } +} + +async function resolveTag(tag: string): Promise { + const local = await $`git rev-parse --verify --quiet ${tag}^{commit}`.quiet().nothrow() + if (local.exitCode === 0) return local.stdout.toString().trim() + + const source = await remote() + info(`Tag ${tag} not present locally; fetching from ${source}...`) + const fetch = await $`git fetch ${source} tag ${tag} --no-tags`.quiet().nothrow() + if (fetch.exitCode !== 0) { + warn(`Failed to fetch tag ${tag}: ${fetch.stderr.toString()}`) + return null + } + + const after = await $`git rev-parse --verify --quiet ${tag}^{commit}`.quiet().nothrow() + return after.exitCode === 0 ? after.stdout.toString().trim() : null +} + +/** + * Record the merged upstream tag in `.opencode-version` so subsequent runs of + * `last()` resolve instantly without an `ls-remote` walk. + */ +export async function writeVersion(tag: string): Promise { + const repo = await root() + const path = `${repo}/${versionFile}` + await Bun.write(path, `${tag}\n`) + return path +} + export async function versions(source: string): Promise { const result = await $`git ls-remote --tags ${source}`.quiet().nothrow() if (result.exitCode !== 0) throw new Error(`Failed to list upstream tags: ${result.stderr.toString()}`) From 5482cc0d09f7c22e15063c75dac223f13b1fa4f9 Mon Sep 17 00:00:00 2001 From: Marius Date: Mon, 11 May 2026 10:13:36 +0200 Subject: [PATCH 14/20] fix(vscode): improve review comment icon contrast (#10130) --- .changeset/review-plus-contrast.md | 6 ++++++ packages/kilo-ui/src/components/diff.tsx | 7 ++----- .../kilo-vscode/webview-ui/agent-manager/agent-manager.css | 4 ++-- 3 files changed, 10 insertions(+), 7 deletions(-) create mode 100644 .changeset/review-plus-contrast.md diff --git a/.changeset/review-plus-contrast.md b/.changeset/review-plus-contrast.md new file mode 100644 index 00000000000..49414a2be15 --- /dev/null +++ b/.changeset/review-plus-contrast.md @@ -0,0 +1,6 @@ +--- +"kilo-code": patch +"@kilocode/kilo-ui": patch +--- + +Improve review comment plus icon contrast in diff viewers. diff --git a/packages/kilo-ui/src/components/diff.tsx b/packages/kilo-ui/src/components/diff.tsx index 43abaaf2459..febb70ca813 100644 --- a/packages/kilo-ui/src/components/diff.tsx +++ b/packages/kilo-ui/src/components/diff.tsx @@ -256,10 +256,7 @@ export function Diff(props: DiffProps) { host.removeAttribute("data-color-scheme") } - // Patch a bug in @pierre/diffs where `grid-template-columns: 100% auto` is set - // for `line-info-basic` separators under `@media (pointer: fine)`, causing the - // expand button to consume 100% of the gutter width and overlap the separator - // content text. We inject into `@layer unsafe` which overrides `@layer base`. + // Patch Pierre shadow styles for Kilo-specific layout and contrast tweaks. let separatorPatchSheet: CSSStyleSheet | null = null const patchSeparatorLayout = () => { const root = getRoot() @@ -267,7 +264,7 @@ export function Diff(props: DiffProps) { if (!separatorPatchSheet) { separatorPatchSheet = new CSSStyleSheet() separatorPatchSheet.replaceSync( - `@layer unsafe { @media (pointer: fine) { [data-separator='line-info-basic'][data-expand-index] [data-separator-wrapper] { grid-template-columns: 34px auto; } } }`, + `@layer unsafe { @media (pointer: fine) { [data-separator='line-info-basic'][data-expand-index] [data-separator-wrapper] { grid-template-columns: 34px auto; } } [data-utility-button] { background-color: var(--vscode-button-background, var(--button-primary-base, var(--icon-interactive-base))); color: var(--vscode-button-foreground, var(--icon-invert-base, #fff)); } }`, ) } if (!root.adoptedStyleSheets.includes(separatorPatchSheet)) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css index 909688ed72d..5181f537af1 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css @@ -1991,8 +1991,8 @@ body.am-wt-dragging-active * { .am-markdown-comment-button:hover, .am-markdown-comment-button:focus-visible { - color: var(--text-base); - background: var(--surface-interactive-base); + color: var(--vscode-button-foreground, var(--icon-invert-base, #fff)); + background: var(--vscode-button-background, var(--button-primary-base, var(--icon-interactive-base))); } .am-markdown-line-number { From 1988db53e32f2a91a8ea1e21f3a87cef975c9b64 Mon Sep 17 00:00:00 2001 From: Marius Date: Mon, 11 May 2026 10:29:20 +0200 Subject: [PATCH 15/20] chore: ignore generated Kilo config artifacts (#10128) * chore: ignore generated Kilo config artifacts * chore: ignore Kilo config node modules --- .../check-kilo-generated-artifacts.yml | 27 ++ .gitignore | 16 +- .kilo/package-lock.json | 376 ------------------ .kilocode/package-lock.json | 376 ------------------ script/check-kilo-generated-artifacts.ts | 51 +++ script/check-workflows.ts | 1 + 6 files changed, 94 insertions(+), 753 deletions(-) create mode 100644 .github/workflows/check-kilo-generated-artifacts.yml delete mode 100644 .kilo/package-lock.json delete mode 100644 .kilocode/package-lock.json create mode 100644 script/check-kilo-generated-artifacts.ts diff --git a/.github/workflows/check-kilo-generated-artifacts.yml b/.github/workflows/check-kilo-generated-artifacts.yml new file mode 100644 index 00000000000..1fa1c083f28 --- /dev/null +++ b/.github/workflows/check-kilo-generated-artifacts.yml @@ -0,0 +1,27 @@ +# kilocode_change - new file +name: Check Kilo generated artifacts + +on: + pull_request: + paths: + - ".gitignore" + - ".github/workflows/check-kilo-generated-artifacts.yml" + - ".kilo/**" + - ".kilocode/**" + - "script/check-kilo-generated-artifacts.ts" + workflow_dispatch: + +jobs: + check: + name: Check Kilo generated artifacts + if: github.repository == 'Kilo-Org/kilocode' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 # kilocode_change + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - uses: oven-sh/setup-bun@v2 + + - name: Run check + run: bun run script/check-kilo-generated-artifacts.ts diff --git a/.gitignore b/.gitignore index 95a44149d17..91bf7230c02 100644 --- a/.gitignore +++ b/.gitignore @@ -45,8 +45,22 @@ telemetry-id tsconfig.tsbuildinfo # Kilo +.kilo/.gitignore +.kilo/package.json +.kilo/package-lock.json +.kilo/pnpm-lock.yaml +.kilo/bun.lock +.kilo/yarn.lock +.kilo/node_modules .kilo/plans/*upstream-merge-report-*.md +.kilocode/.gitignore +.kilocode/package.json +.kilocode/package-lock.json +.kilocode/pnpm-lock.yaml +.kilocode/bun.lock +.kilocode/yarn.lock +.kilocode/node_modules # Test Artifacts packages/app/.artifacts/ -packages/opencode/.artifacts/ \ No newline at end of file +packages/opencode/.artifacts/ diff --git a/.kilo/package-lock.json b/.kilo/package-lock.json deleted file mode 100644 index ce556edce27..00000000000 --- a/.kilo/package-lock.json +++ /dev/null @@ -1,376 +0,0 @@ -{ - "name": ".kilo", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@kilocode/plugin": "7.2.31" - } - }, - "node_modules/@kilocode/plugin": { - "version": "7.2.31", - "resolved": "https://registry.npmjs.org/@kilocode/plugin/-/plugin-7.2.31.tgz", - "integrity": "sha512-KmKTTIly7hRlJdXKhqZ/j/brvTPh0z0UTjWSjJWq5fqf4pATgYGn7G0g3ZjILnN7MUkkZXuljgqExTEeQJHGkQ==", - "license": "MIT", - "dependencies": { - "@kilocode/sdk": "7.2.31", - "effect": "4.0.0-beta.48", - "zod": "4.1.8" - }, - "peerDependencies": { - "@opentui/core": ">=0.1.100", - "@opentui/solid": ">=0.1.100" - }, - "peerDependenciesMeta": { - "@opentui/core": { - "optional": true - }, - "@opentui/solid": { - "optional": true - } - } - }, - "node_modules/@kilocode/sdk": { - "version": "7.2.31", - "resolved": "https://registry.npmjs.org/@kilocode/sdk/-/sdk-7.2.31.tgz", - "integrity": "sha512-Sx05yv+3TIlc6M4Ze+YGgKCLoIg8B0WRE15JXDriVncT+wz7M6+e+4mjNWkwfsuywdeOTYPfeFViqX8iO7ckKA==", - "license": "MIT", - "dependencies": { - "cross-spawn": "7.0.6" - } - }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", - "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", - "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", - "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", - "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", - "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", - "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/effect": { - "version": "4.0.0-beta.48", - "resolved": "https://registry.npmjs.org/effect/-/effect-4.0.0-beta.48.tgz", - "integrity": "sha512-MMAM/ZabuNdNmgXiin+BAanQXK7qM8mlt7nfXDoJ/Gn9V8i89JlCq+2N0AiWmqFLXjGLA0u3FjiOjSOYQk5uMw==", - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "fast-check": "^4.6.0", - "find-my-way-ts": "^0.1.6", - "ini": "^6.0.0", - "kubernetes-types": "^1.30.0", - "msgpackr": "^1.11.9", - "multipasta": "^0.2.7", - "toml": "^4.1.1", - "uuid": "^13.0.0", - "yaml": "^2.8.3" - } - }, - "node_modules/fast-check": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.7.0.tgz", - "integrity": "sha512-NsZRtqvSSoCP0HbNjUD+r1JH8zqZalyp6gLY9e7OYs7NK9b6AHOs2baBFeBG7bVNsuoukh89x2Yg3rPsul8ziQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT", - "dependencies": { - "pure-rand": "^8.0.0" - }, - "engines": { - "node": ">=12.17.0" - } - }, - "node_modules/find-my-way-ts": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/find-my-way-ts/-/find-my-way-ts-0.1.6.tgz", - "integrity": "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==", - "license": "MIT" - }, - "node_modules/ini": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", - "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/kubernetes-types": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/kubernetes-types/-/kubernetes-types-1.30.0.tgz", - "integrity": "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==", - "license": "Apache-2.0" - }, - "node_modules/msgpackr": { - "version": "1.11.10", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.10.tgz", - "integrity": "sha512-iCZNq+HszvF+fC3anCm4nBmWEnbeIAfpDs6IStAEKhQ2YSgkjzVG2FF9XJqwwQh5bH3N9OUTUt4QwVN6MLMLtA==", - "license": "MIT", - "optionalDependencies": { - "msgpackr-extract": "^3.0.2" - } - }, - "node_modules/msgpackr-extract": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", - "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-gyp-build-optional-packages": "5.2.2" - }, - "bin": { - "download-msgpackr-prebuilds": "bin/download-prebuilds.js" - }, - "optionalDependencies": { - "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" - } - }, - "node_modules/multipasta": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/multipasta/-/multipasta-0.2.7.tgz", - "integrity": "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==", - "license": "MIT" - }, - "node_modules/node-gyp-build-optional-packages": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", - "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", - "license": "MIT", - "optional": true, - "dependencies": { - "detect-libc": "^2.0.1" - }, - "bin": { - "node-gyp-build-optional-packages": "bin.js", - "node-gyp-build-optional-packages-optional": "optional.js", - "node-gyp-build-optional-packages-test": "build-test.js" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pure-rand": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.0.tgz", - "integrity": "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/toml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/toml/-/toml-4.1.1.tgz", - "integrity": "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw==", - "license": "MIT", - "engines": { - "node": ">=20" - } - }, - "node_modules/uuid": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", - "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist-node/bin/uuid" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/zod": { - "version": "4.1.8", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/.kilocode/package-lock.json b/.kilocode/package-lock.json deleted file mode 100644 index 32676c56243..00000000000 --- a/.kilocode/package-lock.json +++ /dev/null @@ -1,376 +0,0 @@ -{ - "name": ".kilocode", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@kilocode/plugin": "7.2.31" - } - }, - "node_modules/@kilocode/plugin": { - "version": "7.2.31", - "resolved": "https://registry.npmjs.org/@kilocode/plugin/-/plugin-7.2.31.tgz", - "integrity": "sha512-KmKTTIly7hRlJdXKhqZ/j/brvTPh0z0UTjWSjJWq5fqf4pATgYGn7G0g3ZjILnN7MUkkZXuljgqExTEeQJHGkQ==", - "license": "MIT", - "dependencies": { - "@kilocode/sdk": "7.2.31", - "effect": "4.0.0-beta.48", - "zod": "4.1.8" - }, - "peerDependencies": { - "@opentui/core": ">=0.1.100", - "@opentui/solid": ">=0.1.100" - }, - "peerDependenciesMeta": { - "@opentui/core": { - "optional": true - }, - "@opentui/solid": { - "optional": true - } - } - }, - "node_modules/@kilocode/sdk": { - "version": "7.2.31", - "resolved": "https://registry.npmjs.org/@kilocode/sdk/-/sdk-7.2.31.tgz", - "integrity": "sha512-Sx05yv+3TIlc6M4Ze+YGgKCLoIg8B0WRE15JXDriVncT+wz7M6+e+4mjNWkwfsuywdeOTYPfeFViqX8iO7ckKA==", - "license": "MIT", - "dependencies": { - "cross-spawn": "7.0.6" - } - }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", - "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", - "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", - "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", - "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", - "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", - "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/effect": { - "version": "4.0.0-beta.48", - "resolved": "https://registry.npmjs.org/effect/-/effect-4.0.0-beta.48.tgz", - "integrity": "sha512-MMAM/ZabuNdNmgXiin+BAanQXK7qM8mlt7nfXDoJ/Gn9V8i89JlCq+2N0AiWmqFLXjGLA0u3FjiOjSOYQk5uMw==", - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "fast-check": "^4.6.0", - "find-my-way-ts": "^0.1.6", - "ini": "^6.0.0", - "kubernetes-types": "^1.30.0", - "msgpackr": "^1.11.9", - "multipasta": "^0.2.7", - "toml": "^4.1.1", - "uuid": "^13.0.0", - "yaml": "^2.8.3" - } - }, - "node_modules/fast-check": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.7.0.tgz", - "integrity": "sha512-NsZRtqvSSoCP0HbNjUD+r1JH8zqZalyp6gLY9e7OYs7NK9b6AHOs2baBFeBG7bVNsuoukh89x2Yg3rPsul8ziQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT", - "dependencies": { - "pure-rand": "^8.0.0" - }, - "engines": { - "node": ">=12.17.0" - } - }, - "node_modules/find-my-way-ts": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/find-my-way-ts/-/find-my-way-ts-0.1.6.tgz", - "integrity": "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==", - "license": "MIT" - }, - "node_modules/ini": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", - "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/kubernetes-types": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/kubernetes-types/-/kubernetes-types-1.30.0.tgz", - "integrity": "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==", - "license": "Apache-2.0" - }, - "node_modules/msgpackr": { - "version": "1.11.10", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.10.tgz", - "integrity": "sha512-iCZNq+HszvF+fC3anCm4nBmWEnbeIAfpDs6IStAEKhQ2YSgkjzVG2FF9XJqwwQh5bH3N9OUTUt4QwVN6MLMLtA==", - "license": "MIT", - "optionalDependencies": { - "msgpackr-extract": "^3.0.2" - } - }, - "node_modules/msgpackr-extract": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", - "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-gyp-build-optional-packages": "5.2.2" - }, - "bin": { - "download-msgpackr-prebuilds": "bin/download-prebuilds.js" - }, - "optionalDependencies": { - "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" - } - }, - "node_modules/multipasta": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/multipasta/-/multipasta-0.2.7.tgz", - "integrity": "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==", - "license": "MIT" - }, - "node_modules/node-gyp-build-optional-packages": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", - "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", - "license": "MIT", - "optional": true, - "dependencies": { - "detect-libc": "^2.0.1" - }, - "bin": { - "node-gyp-build-optional-packages": "bin.js", - "node-gyp-build-optional-packages-optional": "optional.js", - "node-gyp-build-optional-packages-test": "build-test.js" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pure-rand": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.0.tgz", - "integrity": "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/toml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/toml/-/toml-4.1.1.tgz", - "integrity": "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw==", - "license": "MIT", - "engines": { - "node": ">=20" - } - }, - "node_modules/uuid": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.2.tgz", - "integrity": "sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist-node/bin/uuid" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/zod": { - "version": "4.1.8", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/script/check-kilo-generated-artifacts.ts b/script/check-kilo-generated-artifacts.ts new file mode 100644 index 00000000000..e7d377b0eef --- /dev/null +++ b/script/check-kilo-generated-artifacts.ts @@ -0,0 +1,51 @@ +#!/usr/bin/env bun +// kilocode_change - new file + +/** + * Guards generated Kilo config dependency artifacts. + * + * Kilo loads project config from .kilo/ and .kilocode/ and installs + * @kilocode/plugin there at runtime. npm writes package.json, lockfiles, + * .gitignore, and node_modules as generated local state. These paths must stay + * untracked so background installs do not create recurring branch diffs. + */ + +import { spawnSync } from "node:child_process" + +const paths = [ + ".kilo/.gitignore", + ".kilo/package.json", + ".kilo/package-lock.json", + ".kilo/pnpm-lock.yaml", + ".kilo/bun.lock", + ".kilo/yarn.lock", + ".kilo/node_modules", + ".kilocode/.gitignore", + ".kilocode/package.json", + ".kilocode/package-lock.json", + ".kilocode/pnpm-lock.yaml", + ".kilocode/bun.lock", + ".kilocode/yarn.lock", + ".kilocode/node_modules", +] + +const git = spawnSync("git", ["ls-files", "-z", "--", ...paths], { encoding: "utf8" }) + +if (git.status !== 0) { + console.error(git.stderr.trim() || "git ls-files failed") + process.exit(1) +} + +const bad = git.stdout.split("\0").filter(Boolean).sort() + +if (bad.length === 0) { + console.log("check-kilo-generated-artifacts: ok") + process.exit(0) +} + +console.error("Generated Kilo config dependency artifacts are tracked:") +for (const file of bad) console.error(` ${file}`) +console.error("") +console.error("These files are created by runtime dependency installs in .kilo/ and .kilocode/.") +console.error("Remove them from git and keep them ignored.") +process.exit(1) diff --git a/script/check-workflows.ts b/script/check-workflows.ts index a7536cd0799..487b03f20a5 100644 --- a/script/check-workflows.ts +++ b/script/check-workflows.ts @@ -29,6 +29,7 @@ const DIR = path.join(ROOT, ".github", "workflows") const active = new Set([ "auto-docs.yml", "beta.yml", + "check-kilo-generated-artifacts.yml", "check-md-table-padding.yml", "check-opencode-annotations.yml", "check-org-member.yml", From d8582521c3d0b1daef7ca6da64cc2a8340786ca9 Mon Sep 17 00:00:00 2001 From: Marius Date: Mon, 11 May 2026 10:30:01 +0200 Subject: [PATCH 16/20] fix(vscode): keep revert checkpoints accessible (#10129) * fix(vscode): keep revert checkpoints accessible * fix(vscode): format revert checkpoint guard --- .changeset/revert-checkpoints-stay.md | 5 +++++ .../tests/unit/revert-checkpoints.test.ts | 20 +++++++++++++++++++ .../src/components/chat/VscodeSessionTurn.tsx | 8 +++----- 3 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 .changeset/revert-checkpoints-stay.md create mode 100644 packages/kilo-vscode/tests/unit/revert-checkpoints.test.ts diff --git a/.changeset/revert-checkpoints-stay.md b/.changeset/revert-checkpoints-stay.md new file mode 100644 index 00000000000..bb505f02d57 --- /dev/null +++ b/.changeset/revert-checkpoints-stay.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Keep older revert checkpoints available after reverting a message in the VS Code extension. diff --git a/packages/kilo-vscode/tests/unit/revert-checkpoints.test.ts b/packages/kilo-vscode/tests/unit/revert-checkpoints.test.ts new file mode 100644 index 00000000000..83f03b0fa76 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/revert-checkpoints.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from "bun:test" +import fs from "node:fs" +import path from "node:path" + +const ROOT = path.resolve(import.meta.dir, "../..") +const TURN_FILE = path.join(ROOT, "webview-ui/src/components/chat/VscodeSessionTurn.tsx") + +const src = fs.readFileSync(TURN_FILE, "utf-8") + +describe("message revert checkpoints", () => { + it("keeps revert actions available after a session is already reverted", () => { + expect(src).toMatch(/onRevert=\{\s*assistantMessages\(\)\.length > 0\s*\? \(\) =>/) + expect(src).not.toMatch(/onRevert=\{[\s\S]*?&& !session\.revert\(\)[\s\S]*?\? \(\) =>/) + }) + + it("only marks revert disabled while the agent is busy", () => { + expect(src).toMatch(/data-revert-disabled=\{\s*assistantMessages\(\)\.length > 0 && session\.status\(\) !== "idle"/) + expect(src).not.toMatch(/data-revert-disabled=\{[\s\S]*?!session\.revert\(\)/) + }) +}) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/VscodeSessionTurn.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/VscodeSessionTurn.tsx index c4de848fa2a..c667615beae 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/VscodeSessionTurn.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/VscodeSessionTurn.tsx @@ -122,11 +122,9 @@ export const VscodeSessionTurn: Component = (props) => {
0 && !session.revert() && session.status() !== "idle" ? "" : undefined - } + data-revert-disabled={assistantMessages().length > 0 && session.status() !== "idle" ? "" : undefined} title={ - assistantMessages().length > 0 && !session.revert() && session.status() !== "idle" + assistantMessages().length > 0 && session.status() !== "idle" ? language.t("revert.disabled.agentBusy") : undefined } @@ -138,7 +136,7 @@ export const VscodeSessionTurn: Component = (props) => { queued={props.queued} onFork={props.onForkMessage ? () => props.onForkMessage?.(msg().sessionID, msg().id) : undefined} onRevert={ - assistantMessages().length > 0 && !session.revert() + assistantMessages().length > 0 ? () => { if (session.status() !== "idle") return session.revertSession(msg().id) From 5b8121ea5c70e161a2596a3350b8d489ef903c89 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 08:51:59 +0000 Subject: [PATCH 17/20] fix(upstream): rename shadowing path local in writeVersion --- script/upstream/utils/upstream.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/script/upstream/utils/upstream.ts b/script/upstream/utils/upstream.ts index c63425eed48..ab4107c40db 100644 --- a/script/upstream/utils/upstream.ts +++ b/script/upstream/utils/upstream.ts @@ -110,9 +110,9 @@ async function resolveTag(tag: string): Promise { */ export async function writeVersion(tag: string): Promise { const repo = await root() - const path = `${repo}/${versionFile}` - await Bun.write(path, `${tag}\n`) - return path + const dest = `${repo}/${versionFile}` + await Bun.write(dest, `${tag}\n`) + return dest } export async function versions(source: string): Promise { From c11f63391d1d85919ff092a67f1c49a5c9d69608 Mon Sep 17 00:00:00 2001 From: Imanol Maiztegui Date: Mon, 11 May 2026 10:59:43 +0200 Subject: [PATCH 18/20] Fix cursor positioning when text ends with newline (#10133) * fix(vscode): fix cursor positioning when text ends with newline A trailing newline is collapsed in a
with white-space: pre-wrap but renders as a real empty line in the