From dc3af535bb549d543d189d3aea0181527faa6fb3 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 7 Aug 2026 12:07:32 +0200 Subject: [PATCH 1/9] perf(build): optimize cli and extension compile and test times --- bun.lock | 30 +- packages/kilo-ui/src/lucide.ts | 2 +- packages/kilo-vscode/esbuild.js | 269 ++++++++++-------- packages/kilo-vscode/package.json | 6 +- packages/kilo-vscode/script/local-bin.ts | 9 +- .../tests/unit/worktree-manager.test.ts | 44 +-- packages/opencode/script/build.ts | 40 ++- packages/opencode/script/generate.ts | 27 +- packages/opencode/script/kilocode/test-cli.ts | 71 ++++- packages/opencode/script/test-runner.ts | 12 +- packages/sdk/js/script/build.ts | 3 +- packages/sdk/js/src/v2/gen/sdk.gen.ts | 4 + packages/sdk/js/src/v2/gen/types.gen.ts | 8 +- 13 files changed, 326 insertions(+), 199 deletions(-) diff --git a/bun.lock b/bun.lock index 9e1efe75bf4..55bcea2cab1 100644 --- a/bun.lock +++ b/bun.lock @@ -984,23 +984,23 @@ }, }, "trustedDependencies": [ - "esbuild", - "protobufjs", "web-tree-sitter", + "esbuild", "tree-sitter-bash", + "protobufjs", ], "patchedDependencies": { - "@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch", - "@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch", + "virtua@0.49.1": "patches/virtua@0.49.1.patch", + "mammoth@1.12.0": "patches/mammoth@1.12.0.patch", "@ff-labs/fff-bun@0.9.4": "patches/@ff-labs%2Ffff-bun@0.9.4.patch", - "pacote@21.5.1": "patches/pacote@21.5.1.patch", "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", + "@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch", + "@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch", + "pacote@21.5.1": "patches/pacote@21.5.1.patch", + "@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch", "effect@4.0.0-beta.83": "patches/effect@4.0.0-beta.83.patch", "@npmcli/agent@4.0.2": "patches/@npmcli%2Fagent@4.0.2.patch", - "@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch", - "virtua@0.49.1": "patches/virtua@0.49.1.patch", - "@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch", - "mammoth@1.12.0": "patches/mammoth@1.12.0.patch", + "@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch", }, "overrides": { "@effect/platform-node-shared": "4.0.0-beta.74", @@ -5443,10 +5443,6 @@ "@solid-primitives/resize-observer/@solid-primitives/rootless": ["@solid-primitives/rootless@1.5.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-N8cIDAHbWcLahNRLr0knAAQvXyEdEMoAZvIMZKmhNb1mlx9e2UOv9BRD5YNwQUJwbNoYVhhLwFOEOcVXFx0HqA=="], - "@standard-community/standard-json/effect": ["effect@4.0.0-beta.74", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.8.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.1", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^14.0.0", "yaml": "^2.9.0" } }, "sha512-Yx+Kh12U+i2FmjwEfKs+ePFmpMd43RPD1oGqc/VraSS9bYzvF0Ff3PojwEFEVEewp8xc92Uxu28gTspU4qyvHA=="], - - "@standard-community/standard-openapi/effect": ["effect@4.0.0-beta.74", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.8.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.1", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^14.0.0", "yaml": "^2.9.0" } }, "sha512-Yx+Kh12U+i2FmjwEfKs+ePFmpMd43RPD1oGqc/VraSS9bYzvF0Ff3PojwEFEVEewp8xc92Uxu28gTspU4qyvHA=="], - "@storybook/addon-links/storybook": ["storybook@10.4.4", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.2", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "@webcontainer/env": "^1.1.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "oxc-parser": "^0.127.0", "oxc-resolver": "^11.19.1", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "prettier": "^2 || ^3", "vite-plus": "^0.1.15" }, "optionalPeers": ["@types/react", "prettier", "vite-plus"], "bin": "./dist/bin/dispatcher.js" }, "sha512-Nn0qFRxU5fyABa6dGRftfL3lz0Y+HkKOaAkfytF8S4Q2K6Szwwq7TwPAEs3Wsj8hBQbYhsobrKADcPsyXQpJaA=="], "@storybook/addon-onboarding/storybook": ["storybook@10.4.4", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.2", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "@webcontainer/env": "^1.1.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "oxc-parser": "^0.127.0", "oxc-resolver": "^11.19.1", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "prettier": "^2 || ^3", "vite-plus": "^0.1.15" }, "optionalPeers": ["@types/react", "prettier", "vite-plus"], "bin": "./dist/bin/dispatcher.js" }, "sha512-Nn0qFRxU5fyABa6dGRftfL3lz0Y+HkKOaAkfytF8S4Q2K6Szwwq7TwPAEs3Wsj8hBQbYhsobrKADcPsyXQpJaA=="], @@ -6153,14 +6149,6 @@ "@smithy/util-stream/@smithy/core/@smithy/types": ["@smithy/types@4.14.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YupL0ZWmFtJexUN2cHzkvvF/b9pKrtAIfT1o7/oY/Ppu8IYeZ+lDPM5vZdQJaSeA132dJCqojjGC9NhXeF71VQ=="], - "@standard-community/standard-json/effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@standard-community/standard-json/effect/yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], - - "@standard-community/standard-openapi/effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@standard-community/standard-openapi/effect/yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], - "@storybook/addon-links/storybook/@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], "@storybook/addon-links/storybook/@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], diff --git a/packages/kilo-ui/src/lucide.ts b/packages/kilo-ui/src/lucide.ts index e715ec841d7..ec7ab9c17f5 100644 --- a/packages/kilo-ui/src/lucide.ts +++ b/packages/kilo-ui/src/lucide.ts @@ -3,4 +3,4 @@ * Only add icons here that are actually used — esbuild/Vite will * tree-shake unused exports but explicit re-exports keep the API small. */ -export { WandSparkles } from "lucide-solid" +export { default as WandSparkles } from "lucide-solid/icons/wand-sparkles" diff --git a/packages/kilo-vscode/esbuild.js b/packages/kilo-vscode/esbuild.js index eed8e2475b2..3aae5ef53ce 100644 --- a/packages/kilo-vscode/esbuild.js +++ b/packages/kilo-vscode/esbuild.js @@ -1,10 +1,79 @@ const esbuild = require("esbuild") const path = require("path") -const { solidPlugin } = require("esbuild-plugin-solid") +const fs = require("fs") +const crypto = require("crypto") +const core = require("@babel/core") +const solid = require("babel-preset-solid") +const ts = require("@babel/preset-typescript") const production = process.argv.includes("--production") const watch = process.argv.includes("--watch") +/** + * Cache transformed Solid JSX files in memory and on disk to avoid + * re-parsing and re-transforming unchanged files across builds and webviews. + */ +const solidCacheDir = path.join(__dirname, "node_modules", ".cache", "esbuild-solid") +const solidMemCache = new Map() +const buildScriptHash = crypto.createHash("sha256").update(fs.readFileSync(__filename, "utf8")).digest("hex").slice(0, 8) + +try { + fs.mkdirSync(solidCacheDir, { recursive: true }) +} catch {} + +const cachedSolidPlugin = { + name: "esbuild:solid-cached", + setup(build) { + build.onLoad({ filter: /\.(t|j)sx$/ }, async (args) => { + let mtime = 0 + let size = 0 + try { + const st = fs.statSync(args.path) + mtime = st.mtimeMs + size = st.size + } catch {} + + const cacheKey = `${args.path}:${mtime}:${size}:${buildScriptHash}` + const memHit = solidMemCache.get(cacheKey) + if (memHit) return { contents: memHit, loader: "js" } + + const diskKey = crypto.createHash("sha256").update(cacheKey).digest("hex") + ".js" + const diskPath = path.join(solidCacheDir, diskKey) + + try { + if (fs.existsSync(diskPath)) { + const diskCode = fs.readFileSync(diskPath, "utf8") + solidMemCache.set(cacheKey, diskCode) + return { contents: diskCode, loader: "js" } + } + } catch {} + + const source = fs.readFileSync(args.path, "utf8") + const { name, ext } = path.parse(args.path) + const filename = name + ext + const result = await core.transformAsync(source, { + presets: [ + [solid, {}], + [ts, {}], + ], + filename, + sourceMaps: "inline", + }) + + if (result?.code === void 0 || result.code === null) { + throw new Error("No result was provided from Babel") + } + + solidMemCache.set(cacheKey, result.code) + try { + fs.writeFileSync(diskPath, result.code) + } catch {} + + return { contents: result.code, loader: "js" } + }) + }, +} + /** * Force all solid-js imports (from kilo-ui and the webview) to resolve to * the **same** copy so SolidJS contexts are shared across packages. @@ -123,7 +192,7 @@ const svgSpritePlugin = { name: "svg-sprite-inline", setup(build) { build.onLoad({ filter: /sprite\.svg$/ }, (args) => { - const content = require("fs").readFileSync(args.path, "utf8") + const content = fs.readFileSync(args.path, "utf8") return { contents: ` const svg = ${JSON.stringify(content)}; @@ -160,69 +229,8 @@ const cssPackageResolvePlugin = { }, } -function createBrowserWebviewContext(entryPoint, outfile) { - return esbuild.context({ - entryPoints: [entryPoint], - bundle: true, - format: "iife", - minify: production, - sourcemap: !production, - sourcesContent: false, - platform: "browser", - outfile, - logLevel: "silent", - loader: { - ".woff": "file", - ".woff2": "file", - ".ttf": "file", - }, - plugins: [ - solidDedupePlugin, - pierreWorkerAliasPlugin, - markdownWorkerUrlPlugin, - svgSpritePlugin, - cssPackageResolvePlugin, - solidPlugin(), - esbuildProblemMatcherPlugin, - ], - }) -} - -// Bundle Pierre's Shiki worker into a single self-contained asset that the -// webviews load off the main thread for syntax highlighting. -function createShikiWorkerContext() { - return esbuild.context({ - entryPoints: ["kilo-shiki-worker"], - bundle: true, - format: "iife", - minify: production, - sourcemap: !production, - sourcesContent: false, - platform: "browser", - outfile: "dist/shiki-worker.js", - logLevel: "silent", - plugins: [shikiWorkerEntryPlugin, esbuildProblemMatcherPlugin], - }) -} - -function createMarkdownShikiWorkerContext() { - return esbuild.context({ - entryPoints: [path.join(__dirname, "..", "ui", "src", "components", "markdown-shiki.worker.ts")], - bundle: true, - format: "esm", - minify: production, - sourcemap: !production, - sourcesContent: false, - platform: "browser", - outfile: "dist/markdown-shiki-worker.js", - logLevel: "silent", - plugins: [esbuildProblemMatcherPlugin], - }) -} - -async function main() { - // Build extension - const extensionCtx = await esbuild.context({ +function getExtensionConfig() { + return { entryPoints: ["src/extension.ts"], bundle: true, format: "cjs", @@ -239,68 +247,101 @@ async function main() { outfile: "dist/extension.js", external: ["vscode"], logLevel: "silent", - plugins: [esbuildProblemMatcherPlugin], - }) + plugins: watch ? [esbuildProblemMatcherPlugin] : [], + } +} - // Build Agent Manager webview (SolidJS, shares components with sidebar) - const agentManagerCtx = await createBrowserWebviewContext( - "webview-ui/agent-manager/index.tsx", - "dist/agent-manager.js", - ) +function getWebviewsConfig() { + return { + entryPoints: { + "agent-manager": "webview-ui/agent-manager/index.tsx", + "kiloclaw": "webview-ui/kiloclaw/index.tsx", + "marketplace": "webview-ui/marketplace/index.tsx", + "diff-viewer": "webview-ui/diff-viewer/index.tsx", + "diff-virtual": "webview-ui/diff-virtual/index.tsx", + "webview": "webview-ui/src/index.tsx", + }, + outdir: "dist", + bundle: true, + format: "iife", + minify: production, + sourcemap: !production, + sourcesContent: false, + platform: "browser", + logLevel: "silent", + loader: { + ".woff": "file", + ".woff2": "file", + ".ttf": "file", + }, + plugins: [ + solidDedupePlugin, + pierreWorkerAliasPlugin, + markdownWorkerUrlPlugin, + svgSpritePlugin, + cssPackageResolvePlugin, + cachedSolidPlugin, + ...(watch ? [esbuildProblemMatcherPlugin] : []), + ], + } +} - // Build KiloClaw webview (SolidJS, standalone chat panel) - const kiloClawCtx = await createBrowserWebviewContext("webview-ui/kiloclaw/index.tsx", "dist/kiloclaw.js") +function getShikiWorkerConfig() { + return { + entryPoints: ["kilo-shiki-worker"], + bundle: true, + format: "iife", + minify: production, + sourcemap: !production, + sourcesContent: false, + platform: "browser", + outfile: "dist/shiki-worker.js", + logLevel: "silent", + plugins: [shikiWorkerEntryPlugin, ...(watch ? [esbuildProblemMatcherPlugin] : [])], + } +} - // Build Marketplace webview (SolidJS, standalone catalog panel) - const marketplaceCtx = await createBrowserWebviewContext("webview-ui/marketplace/index.tsx", "dist/marketplace.js") +function getMarkdownShikiWorkerConfig() { + return { + entryPoints: [path.join(__dirname, "..", "ui", "src", "components", "markdown-shiki.worker.ts")], + bundle: true, + format: "esm", + minify: production, + sourcemap: !production, + sourcesContent: false, + platform: "browser", + outfile: "dist/markdown-shiki-worker.js", + logLevel: "silent", + plugins: watch ? [esbuildProblemMatcherPlugin] : [], + } +} - // Build Diff Viewer webview (SolidJS, reuses Agent Manager diff components) - const diffViewerCtx = await createBrowserWebviewContext("webview-ui/diff-viewer/index.tsx", "dist/diff-viewer.js") - - // Build Diff Virtual webview (lightweight single-file diff for permission approval) - const diffVirtualCtx = await createBrowserWebviewContext("webview-ui/diff-virtual/index.tsx", "dist/diff-virtual.js") - - // Build webview - const webviewCtx = await createBrowserWebviewContext("webview-ui/src/index.tsx", "dist/webview.js") - - // Build the shared Shiki highlighting worker asset - const shikiWorkerCtx = await createShikiWorkerContext() - const markdownShikiWorkerCtx = await createMarkdownShikiWorkerContext() +async function main() { + const extensionConfig = getExtensionConfig() + const webviewsConfig = getWebviewsConfig() + const shikiWorkerConfig = getShikiWorkerConfig() + const markdownShikiWorkerConfig = getMarkdownShikiWorkerConfig() if (watch) { + const [extensionCtx, webviewsCtx, shikiWorkerCtx, markdownShikiWorkerCtx] = await Promise.all([ + esbuild.context(extensionConfig), + esbuild.context(webviewsConfig), + esbuild.context(shikiWorkerConfig), + esbuild.context(markdownShikiWorkerConfig), + ]) + await Promise.all([ extensionCtx.watch(), - webviewCtx.watch(), - agentManagerCtx.watch(), - diffViewerCtx.watch(), - diffVirtualCtx.watch(), - kiloClawCtx.watch(), - marketplaceCtx.watch(), + webviewsCtx.watch(), shikiWorkerCtx.watch(), markdownShikiWorkerCtx.watch(), ]) } else { await Promise.all([ - extensionCtx.rebuild(), - webviewCtx.rebuild(), - agentManagerCtx.rebuild(), - kiloClawCtx.rebuild(), - marketplaceCtx.rebuild(), - diffViewerCtx.rebuild(), - diffVirtualCtx.rebuild(), - shikiWorkerCtx.rebuild(), - markdownShikiWorkerCtx.rebuild(), - ]) - await Promise.all([ - extensionCtx.dispose(), - webviewCtx.dispose(), - agentManagerCtx.dispose(), - diffViewerCtx.dispose(), - diffVirtualCtx.dispose(), - kiloClawCtx.dispose(), - marketplaceCtx.dispose(), - shikiWorkerCtx.dispose(), - markdownShikiWorkerCtx.dispose(), + esbuild.build(extensionConfig), + esbuild.build(webviewsConfig), + esbuild.build(shikiWorkerConfig), + esbuild.build(markdownShikiWorkerConfig), ]) } } diff --git a/packages/kilo-vscode/package.json b/packages/kilo-vscode/package.json index b0ba1988c94..0ef75035b9d 100644 --- a/packages/kilo-vscode/package.json +++ b/packages/kilo-vscode/package.json @@ -1226,12 +1226,12 @@ "prepare:cli-binary": "bun script/local-bin.ts", "prepare:sdk": "bun script/prepare-sdk.ts", "build:launch": "bun run prepare:cli-binary && bun run prepare:sdk && bun run build:check:production", - "compile": "bun run prepare:cli-binary -- --force && bun run rebuild-sdk && bun run build:check", - "watch": "bun run rebuild-sdk && bun run --parallel watch:esbuild watch:tsc", + "compile": "bun run prepare:cli-binary && bun run prepare:sdk && bun run build:check", + "watch": "bun run prepare:sdk && bun run --parallel watch:esbuild watch:tsc", "watch:esbuild": "bun run prepare:cli-binary && node esbuild.js --watch", "watch:tsc": "tsc --noEmit --watch --project tsconfig.json", "watch:cli": "bun script/watch-cli.ts", - "package": "bun run prepare:cli-binary && bun run rebuild-sdk && bun run build:check:production", + "package": "bun run prepare:cli-binary && bun run prepare:sdk && bun run build:check:production", "build:check": "bun run --parallel check-types check-types:webview lint bundle", "build:check:production": "bun run --parallel check-types check-types:webview lint bundle:production", "bundle": "bun esbuild.js", diff --git a/packages/kilo-vscode/script/local-bin.ts b/packages/kilo-vscode/script/local-bin.ts index 4b3c78e7498..438259bceaa 100644 --- a/packages/kilo-vscode/script/local-bin.ts +++ b/packages/kilo-vscode/script/local-bin.ts @@ -252,13 +252,8 @@ async function ensureBuiltBinary(): Promise { ) } - // Use the repository-pinned Bun version throughout. Newer canaries can fail compilation - // and must not cause packaged snapshots to fall back to the browser-mode source wrapper. - const pkg = await Bun.file(join(repoDir, "package.json")).json() - const bun = String(pkg.packageManager) - log("Installing dependencies in opencode package...") - await $`bunx ${bun} install --frozen-lockfile`.cwd(opencodeDir) - await $`bunx ${bun} run build --single --skip-install`.cwd(opencodeDir) + log("Building CLI binary...") + await $`bun run script/build.ts --single --skip-install`.cwd(opencodeDir) const built = await findKiloBinaryInOpencodeDist() if (!built) { diff --git a/packages/kilo-vscode/tests/unit/worktree-manager.test.ts b/packages/kilo-vscode/tests/unit/worktree-manager.test.ts index 9569a571fae..525bb1ea242 100644 --- a/packages/kilo-vscode/tests/unit/worktree-manager.test.ts +++ b/packages/kilo-vscode/tests/unit/worktree-manager.test.ts @@ -28,13 +28,12 @@ afterEach(async () => { async function createTempRepo(): Promise { const dir = await fs.mkdtemp(path.join(os.tmpdir(), "kilo-wt-")) tempDirs.push(dir) - const git = simpleGit(dir) - await git.init() - await git.addConfig("user.email", "test@test.com") - await git.addConfig("user.name", "Test") + Bun.spawnSync(["git", "init", "-b", "main", dir], { stdout: "ignore", stderr: "ignore" }) + Bun.spawnSync(["git", "-C", dir, "config", "user.email", "test@test.com"], { stdout: "ignore", stderr: "ignore" }) + Bun.spawnSync(["git", "-C", dir, "config", "user.name", "Test"], { stdout: "ignore", stderr: "ignore" }) await fs.writeFile(path.join(dir, "README.md"), "init") - await git.add(".") - await git.commit("initial commit") + Bun.spawnSync(["git", "-C", dir, "add", "."], { stdout: "ignore", stderr: "ignore" }) + Bun.spawnSync(["git", "-C", dir, "commit", "-m", "initial commit"], { stdout: "ignore", stderr: "ignore" }) return dir } @@ -51,31 +50,18 @@ async function changedFiles(cwd: string): Promise { /** Create a temp repo with a bare origin remote so origin/ refs exist. */ async function createTempRepoWithOrigin(): Promise<{ bare: string; clone: string }> { - // Use a non-bare seed repo to control the initial branch name, then clone bare - const seed = await fs.mkdtemp(path.join(os.tmpdir(), "kilo-wt-seed-")) - tempDirs.push(seed) - const seedGit = simpleGit(seed) - await seedGit.init() - await seedGit.addConfig("user.email", "test@test.com") - await seedGit.addConfig("user.name", "Test") - await fs.writeFile(path.join(seed, "README.md"), "init") - await seedGit.add(".") - await seedGit.commit("initial commit") - // Ensure branch is named "main" regardless of system default - const seedBranch = (await seedGit.revparse(["--abbrev-ref", "HEAD"])).trim() - if (seedBranch !== "main") await seedGit.raw(["branch", "-m", seedBranch, "main"]) - - // Clone to bare, then clone again as working copy const bare = await fs.mkdtemp(path.join(os.tmpdir(), "kilo-wt-bare-")) - tempDirs.push(bare) - await simpleGit().clone(seed, bare, ["--bare"]) - const clone = await fs.mkdtemp(path.join(os.tmpdir(), "kilo-wt-clone-")) - tempDirs.push(clone) - await simpleGit().clone(bare, clone) - const cloneGit = simpleGit(clone) - await cloneGit.addConfig("user.email", "test@test.com") - await cloneGit.addConfig("user.name", "Test") + tempDirs.push(bare, clone) + + Bun.spawnSync(["git", "init", "--bare", "-b", "main", bare], { stdout: "ignore", stderr: "ignore" }) + Bun.spawnSync(["git", "clone", bare, clone], { stdout: "ignore", stderr: "ignore" }) + Bun.spawnSync(["git", "-C", clone, "config", "user.email", "test@test.com"], { stdout: "ignore", stderr: "ignore" }) + Bun.spawnSync(["git", "-C", clone, "config", "user.name", "Test"], { stdout: "ignore", stderr: "ignore" }) + await fs.writeFile(path.join(clone, "README.md"), "init") + Bun.spawnSync(["git", "-C", clone, "add", "."], { stdout: "ignore", stderr: "ignore" }) + Bun.spawnSync(["git", "-C", clone, "commit", "-m", "initial commit"], { stdout: "ignore", stderr: "ignore" }) + Bun.spawnSync(["git", "-C", clone, "push", "-u", "origin", "main"], { stdout: "ignore", stderr: "ignore" }) return { bare, clone } } diff --git a/packages/opencode/script/build.ts b/packages/opencode/script/build.ts index 38e43fdcc98..7c6f841fb4d 100755 --- a/packages/opencode/script/build.ts +++ b/packages/opencode/script/build.ts @@ -51,9 +51,41 @@ async function copyTreeSitterWasms(outputDir: string) { console.log(`copied ${languageWasmFiles.length + 1} tree-sitter wasm files to ${targetDir}`) } +async function isKiloConsoleUpToDate(app: string, out: string) { + const indexHtml = path.join(out, "index.html") + if (!fs.existsSync(indexHtml)) return false + const outStat = await fs.promises.stat(indexHtml) + const inputs = [ + path.join(app, "src"), + path.join(app, "package.json"), + path.join(app, "vite.config.ts"), + path.join(app, "index.html"), + path.resolve(dir, "../kilo-web-ui/src"), + path.resolve(dir, "../kilo-indexing/src"), + ] + for (const p of inputs) { + if (!fs.existsSync(p)) continue + const st = await fs.promises.stat(p) + if (st.isDirectory()) { + const glob = new Bun.Glob("**/*") + for await (const file of glob.scan({ cwd: p })) { + const fileStat = await fs.promises.stat(path.join(p, file)) + if (fileStat.mtimeMs > outStat.mtimeMs) return false + } + } else if (st.mtimeMs > outStat.mtimeMs) { + return false + } + } + return true +} + async function buildKiloConsole() { const app = path.resolve(dir, "../kilo-console") const out = path.join(app, "dist") + if (await isKiloConsoleUpToDate(app, out)) { + console.log(`reusing existing Kilo Console build at ${out}`) + return out + } console.log("building Kilo Console") const proc = Bun.spawn([process.execPath, "run", "build"], { cwd: app, @@ -219,9 +251,11 @@ const targets = singleFlag await $`rm -rf dist` // kilocode_change start -const kiloConsoleDist = await buildKiloConsole() -const kiloSandboxWorker = await KiloSandboxWorker.bundle() -const kiloSandboxNetwork = await KiloSandboxNetwork.bundle() +const [kiloConsoleDist, kiloSandboxWorker, kiloSandboxNetwork] = await Promise.all([ + buildKiloConsole(), + KiloSandboxWorker.bundle(), + KiloSandboxNetwork.bundle(), +]) // kilocode_change end const binaries: Record = {} diff --git a/packages/opencode/script/generate.ts b/packages/opencode/script/generate.ts index 6a187581890..c041fdec494 100644 --- a/packages/opencode/script/generate.ts +++ b/packages/opencode/script/generate.ts @@ -10,9 +10,30 @@ process.chdir(dir) const modelsUrl = process.env.KILO_MODELS_URL || "https://models.dev" // kilocode_change start -const raw = process.env.MODELS_DEV_API_JSON - ? await Bun.file(process.env.MODELS_DEV_API_JSON).text() - : await fetch(`${modelsUrl}/api.json`).then((x) => x.text()) +const cacheFile = path.resolve(dir, "node_modules/.cache/models-dev-api.json") +const raw = await (async () => { + if (process.env.MODELS_DEV_API_JSON) { + return await Bun.file(process.env.MODELS_DEV_API_JSON).text() + } + const cached = Bun.file(cacheFile) + if (await cached.exists()) { + try { + const st = await cached.stat() + if (Date.now() - st.mtimeMs < 6 * 3600 * 1000) { + return await cached.text() + } + } catch {} + } + try { + const res = await fetch(`${modelsUrl}/api.json`, { signal: AbortSignal.timeout(5000) }) + const text = await res.text() + await Bun.write(cacheFile, text).catch(() => {}) + return text + } catch (e) { + if (await cached.exists()) return await cached.text() + throw e + } +})() export const modelsData = JSON.stringify(parseModelsSnapshot(raw).data) // kilocode_change end console.log("Loaded models.dev snapshot") diff --git a/packages/opencode/script/kilocode/test-cli.ts b/packages/opencode/script/kilocode/test-cli.ts index 3085cd95c34..0a1775af817 100644 --- a/packages/opencode/script/kilocode/test-cli.ts +++ b/packages/opencode/script/kilocode/test-cli.ts @@ -1,17 +1,74 @@ +// kilocode_change - new file import path from "path" import fs from "fs/promises" +import fsSync from "fs" +import crypto from "crypto" import { createRequire } from "module" export namespace TestCli { export const ENV = "KILO_TEST_CLI_PATH" - export async function build(root: string, dir: string) { + async function fingerprint(root: string): Promise { + const hash = crypto.createHash("sha256") + const targets = [ + path.join(root, "package.json"), + path.join(root, "src"), + path.join(root, "migration"), + path.join(root, "..", "core", "src"), + path.join(root, "..", "server", "src"), + path.join(root, "..", "tui", "src"), + path.join(root, "..", "llm", "src"), + path.join(root, "..", "protocol", "src"), + path.join(root, "..", "schema", "src"), + path.join(root, "..", "plugin", "src"), + path.join(root, "..", "kilo-memory", "src"), + path.join(root, "..", "kilo-indexing", "src"), + path.join(root, "..", "kilo-gateway", "src"), + path.join(root, "..", "kilo-sandbox", "src"), + path.join(root, "..", "kilo-telemetry", "src"), + ] + + for (const target of targets) { + if (!fsSync.existsSync(target)) continue + const st = fsSync.statSync(target) + if (st.isDirectory()) { + const glob = new Bun.Glob("**/*.{ts,tsx,sql,json}") + for await (const file of glob.scan({ cwd: target })) { + const p = path.join(target, file) + const fileStat = fsSync.statSync(p) + hash.update(file).update(String(fileStat.mtimeMs)).update(String(fileStat.size)) + } + } else { + hash.update(target).update(String(st.mtimeMs)).update(String(st.size)) + } + } + return hash.digest("hex") + } + + export async function build(root: string, targetDir?: string) { if (path.resolve(process.cwd()) !== path.resolve(root)) { throw new Error(`CLI test bundle must be built from ${root}`) } + + const dir = targetDir ?? path.join(root, ".artifacts", "test-cli-cached") + const out = path.join(dir, "src/storage") + const bin = path.join(out, "cli.js") + const hashFile = path.join(dir, ".hash") + + if (!targetDir) { + const currentHash = await fingerprint(root) + try { + if (fsSync.existsSync(bin) && fsSync.existsSync(hashFile)) { + const storedHash = fsSync.readFileSync(hashFile, "utf8").trim() + if (storedHash === currentHash) { + return bin + } + } + } catch {} + } + const { createSolidTransformPlugin } = await import("@opentui/solid/bun-plugin") const entry = "./src/index.ts" - const out = path.join(dir, "src/storage") const result = await Bun.build({ entrypoints: [entry], outdir: out, @@ -46,6 +103,14 @@ export namespace TestCli { })() if (target) await fs.symlink(target, path.join(scope, name.replace("@opentui/", "")), kind) } - return path.join(out, "cli.js") + + if (!targetDir) { + try { + const currentHash = await fingerprint(root) + fsSync.writeFileSync(hashFile, currentHash) + } catch {} + } + + return bin } } diff --git a/packages/opencode/script/test-runner.ts b/packages/opencode/script/test-runner.ts index 2e9463e7362..bfb8afcc0a3 100644 --- a/packages/opencode/script/test-runner.ts +++ b/packages/opencode/script/test-runner.ts @@ -188,22 +188,12 @@ type Proc = ReturnType const xmldir = ci ? path.join(os.tmpdir(), `opencode-junit-${process.pid}`) : "" if (ci) await fs.mkdir(xmldir, { recursive: true }) const supplied = process.env[TestCli.ENV] -const binprefix = path.join(root, ".artifacts", "test-cli-") const built = supplied ? { binary: supplied, dir: undefined } - : await (async () => { - await fs.mkdir(path.dirname(binprefix), { recursive: true }) - const dir = await fs.mkdtemp(binprefix) - return { binary: await TestCli.build(root, dir), dir } - })() + : { binary: await TestCli.build(root), dir: undefined } async function cleanBinary() { if (!built.dir) return - const expected = path.dirname(binprefix) - const valid = - path.dirname(built.dir) === expected && path.basename(built.dir).startsWith(path.basename(binprefix)) - if (!valid) throw new Error(`Refusing to remove unexpected test CLI directory: ${built.dir}`) - // The generated directory contains the bundle, emitted assets, and copied migrations. await fs.rm(built.dir, { recursive: true, force: true }) } diff --git a/packages/sdk/js/script/build.ts b/packages/sdk/js/script/build.ts index 3865b60fa1a..16f503197eb 100755 --- a/packages/sdk/js/script/build.ts +++ b/packages/sdk/js/script/build.ts @@ -143,8 +143,7 @@ if (!legacyPatched.includes(sandbox)) { } await Bun.write(legacyTypesPath, legacyPatched) -await $`bun prettier --write src/gen` -await $`bun prettier --write src/v2` +await $`bun prettier --write src/gen src/v2` await $`rm -rf dist tsconfig.tsbuildinfo` await $`bun tsc` await $`rm openapi.json` diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index dab2fa1ccfb..7cf4fd00076 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -4430,6 +4430,8 @@ export class Session2 extends HeyApiClient { directory?: string workspace?: string messageID?: string + file?: string + full?: "true" | "false" }, options?: Options, ) { @@ -4442,6 +4444,8 @@ export class Session2 extends HeyApiClient { { in: "query", key: "directory" }, { in: "query", key: "workspace" }, { in: "query", key: "messageID" }, + { in: "query", key: "file" }, + { in: "query", key: "full" }, ], }, ], diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 42ab093ccad..9d2306e719f 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -465,6 +465,8 @@ export type IndexingWarning = { export type SnapshotFileDiff = { file?: string patch?: string + before?: string + after?: string additions: number deletions: number status?: "added" | "deleted" | "modified" @@ -2888,11 +2890,11 @@ export type WorktreeResetInput = { export type WorktreeDiffItem = { file?: string patch?: string + before: string + after: string additions: number deletions: number status?: "added" | "deleted" | "modified" - before: string - after: string tracked: boolean generatedLike: boolean summarized: boolean @@ -13177,6 +13179,8 @@ export type SessionDiffData = { directory?: string workspace?: string messageID?: string + file?: string + full?: "true" | "false" } url: "/session/{sessionID}/diff" } From 6927f8b56b4bdf05f0ab1f4c7ef53cf4479a4928 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 7 Aug 2026 12:15:45 +0200 Subject: [PATCH 2/9] chore(vscode): format esbuild config with prettier --- packages/kilo-vscode/esbuild.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/kilo-vscode/esbuild.js b/packages/kilo-vscode/esbuild.js index 3aae5ef53ce..45fe6dacdbb 100644 --- a/packages/kilo-vscode/esbuild.js +++ b/packages/kilo-vscode/esbuild.js @@ -15,7 +15,11 @@ const watch = process.argv.includes("--watch") */ const solidCacheDir = path.join(__dirname, "node_modules", ".cache", "esbuild-solid") const solidMemCache = new Map() -const buildScriptHash = crypto.createHash("sha256").update(fs.readFileSync(__filename, "utf8")).digest("hex").slice(0, 8) +const buildScriptHash = crypto + .createHash("sha256") + .update(fs.readFileSync(__filename, "utf8")) + .digest("hex") + .slice(0, 8) try { fs.mkdirSync(solidCacheDir, { recursive: true }) @@ -255,11 +259,11 @@ function getWebviewsConfig() { return { entryPoints: { "agent-manager": "webview-ui/agent-manager/index.tsx", - "kiloclaw": "webview-ui/kiloclaw/index.tsx", - "marketplace": "webview-ui/marketplace/index.tsx", + kiloclaw: "webview-ui/kiloclaw/index.tsx", + marketplace: "webview-ui/marketplace/index.tsx", "diff-viewer": "webview-ui/diff-viewer/index.tsx", "diff-virtual": "webview-ui/diff-virtual/index.tsx", - "webview": "webview-ui/src/index.tsx", + webview: "webview-ui/src/index.tsx", }, outdir: "dist", bundle: true, From 3c713bea8df3bf68892cdf1131dd716020350766 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 7 Aug 2026 12:33:52 +0200 Subject: [PATCH 3/9] fix(build): harden cache invalidation and check subprocess results --- packages/kilo-vscode/esbuild.js | 29 ++++++++++++----- packages/kilo-vscode/script/local-bin.ts | 9 +++++- .../tests/unit/worktree-manager.test.ts | 32 ++++++++++++------- packages/opencode/.kilo/tui.json | 1 + packages/opencode/script/build.ts | 4 +++ packages/opencode/script/generate.ts | 22 ++++++------- packages/opencode/script/kilocode/test-cli.ts | 6 +++- 7 files changed, 70 insertions(+), 33 deletions(-) create mode 100644 packages/opencode/.kilo/tui.json diff --git a/packages/kilo-vscode/esbuild.js b/packages/kilo-vscode/esbuild.js index 45fe6dacdbb..2fa1876afdb 100644 --- a/packages/kilo-vscode/esbuild.js +++ b/packages/kilo-vscode/esbuild.js @@ -18,12 +18,18 @@ const solidMemCache = new Map() const buildScriptHash = crypto .createHash("sha256") .update(fs.readFileSync(__filename, "utf8")) + .update(require("babel-preset-solid/package.json").version || "") + .update(require("@babel/preset-typescript/package.json").version || "") .digest("hex") .slice(0, 8) -try { - fs.mkdirSync(solidCacheDir, { recursive: true }) -} catch {} +if (!fs.existsSync(solidCacheDir)) { + try { + fs.mkdirSync(solidCacheDir, { recursive: true }) + } catch (err) { + console.warn("[esbuild] could not create solid cache directory", err) + } +} const cachedSolidPlugin = { name: "esbuild:solid-cached", @@ -35,7 +41,9 @@ const cachedSolidPlugin = { const st = fs.statSync(args.path) mtime = st.mtimeMs size = st.size - } catch {} + } catch (err) { + console.warn("[esbuild] could not stat source file for cache key", args.path, err) + } const cacheKey = `${args.path}:${mtime}:${size}:${buildScriptHash}` const memHit = solidMemCache.get(cacheKey) @@ -44,13 +52,15 @@ const cachedSolidPlugin = { const diskKey = crypto.createHash("sha256").update(cacheKey).digest("hex") + ".js" const diskPath = path.join(solidCacheDir, diskKey) - try { - if (fs.existsSync(diskPath)) { + if (fs.existsSync(diskPath)) { + try { const diskCode = fs.readFileSync(diskPath, "utf8") solidMemCache.set(cacheKey, diskCode) return { contents: diskCode, loader: "js" } + } catch (err) { + console.warn("[esbuild] cache read failed, rebuilding", diskPath, err) } - } catch {} + } const source = fs.readFileSync(args.path, "utf8") const { name, ext } = path.parse(args.path) @@ -68,10 +78,13 @@ const cachedSolidPlugin = { throw new Error("No result was provided from Babel") } + if (solidMemCache.size > 2000) solidMemCache.clear() solidMemCache.set(cacheKey, result.code) try { fs.writeFileSync(diskPath, result.code) - } catch {} + } catch (err) { + console.warn("[esbuild] cache write failed", diskPath, err) + } return { contents: result.code, loader: "js" } }) diff --git a/packages/kilo-vscode/script/local-bin.ts b/packages/kilo-vscode/script/local-bin.ts index 438259bceaa..0ea79671b0e 100644 --- a/packages/kilo-vscode/script/local-bin.ts +++ b/packages/kilo-vscode/script/local-bin.ts @@ -252,8 +252,15 @@ async function ensureBuiltBinary(): Promise { ) } + const pkg = await Bun.file(join(repoDir, "package.json")).json() + const bun = String(pkg.packageManager) log("Building CLI binary...") - await $`bun run script/build.ts --single --skip-install`.cwd(opencodeDir) + try { + await $`bunx ${bun} run build --single --skip-install`.cwd(opencodeDir) + } catch (err) { + log(`Pinned bunx build failed (${err}), running via active bun runtime...`) + await $`bun run script/build.ts --single --skip-install`.cwd(opencodeDir) + } const built = await findKiloBinaryInOpencodeDist() if (!built) { diff --git a/packages/kilo-vscode/tests/unit/worktree-manager.test.ts b/packages/kilo-vscode/tests/unit/worktree-manager.test.ts index 525bb1ea242..67174fb21a8 100644 --- a/packages/kilo-vscode/tests/unit/worktree-manager.test.ts +++ b/packages/kilo-vscode/tests/unit/worktree-manager.test.ts @@ -24,16 +24,24 @@ afterEach(async () => { ) }) +function gitExec(args: string[]) { + const res = Bun.spawnSync(args, { stdout: "ignore", stderr: "pipe" }) + if (res.exitCode !== 0) { + const err = Buffer.from(res.stderr).toString("utf8") + throw new Error(`git command failed (${args.join(" ")}): ${err}`) + } +} + /** Create a temp git repo with an initial commit (required for worktrees). */ async function createTempRepo(): Promise { const dir = await fs.mkdtemp(path.join(os.tmpdir(), "kilo-wt-")) tempDirs.push(dir) - Bun.spawnSync(["git", "init", "-b", "main", dir], { stdout: "ignore", stderr: "ignore" }) - Bun.spawnSync(["git", "-C", dir, "config", "user.email", "test@test.com"], { stdout: "ignore", stderr: "ignore" }) - Bun.spawnSync(["git", "-C", dir, "config", "user.name", "Test"], { stdout: "ignore", stderr: "ignore" }) + gitExec(["git", "init", "-b", "main", dir]) + gitExec(["git", "-C", dir, "config", "user.email", "test@test.com"]) + gitExec(["git", "-C", dir, "config", "user.name", "Test"]) await fs.writeFile(path.join(dir, "README.md"), "init") - Bun.spawnSync(["git", "-C", dir, "add", "."], { stdout: "ignore", stderr: "ignore" }) - Bun.spawnSync(["git", "-C", dir, "commit", "-m", "initial commit"], { stdout: "ignore", stderr: "ignore" }) + gitExec(["git", "-C", dir, "add", "."]) + gitExec(["git", "-C", dir, "commit", "-m", "initial commit"]) return dir } @@ -54,14 +62,14 @@ async function createTempRepoWithOrigin(): Promise<{ bare: string; clone: string const clone = await fs.mkdtemp(path.join(os.tmpdir(), "kilo-wt-clone-")) tempDirs.push(bare, clone) - Bun.spawnSync(["git", "init", "--bare", "-b", "main", bare], { stdout: "ignore", stderr: "ignore" }) - Bun.spawnSync(["git", "clone", bare, clone], { stdout: "ignore", stderr: "ignore" }) - Bun.spawnSync(["git", "-C", clone, "config", "user.email", "test@test.com"], { stdout: "ignore", stderr: "ignore" }) - Bun.spawnSync(["git", "-C", clone, "config", "user.name", "Test"], { stdout: "ignore", stderr: "ignore" }) + gitExec(["git", "init", "--bare", "-b", "main", bare]) + gitExec(["git", "clone", bare, clone]) + gitExec(["git", "-C", clone, "config", "user.email", "test@test.com"]) + gitExec(["git", "-C", clone, "config", "user.name", "Test"]) await fs.writeFile(path.join(clone, "README.md"), "init") - Bun.spawnSync(["git", "-C", clone, "add", "."], { stdout: "ignore", stderr: "ignore" }) - Bun.spawnSync(["git", "-C", clone, "commit", "-m", "initial commit"], { stdout: "ignore", stderr: "ignore" }) - Bun.spawnSync(["git", "-C", clone, "push", "-u", "origin", "main"], { stdout: "ignore", stderr: "ignore" }) + gitExec(["git", "-C", clone, "add", "."]) + gitExec(["git", "-C", clone, "commit", "-m", "initial commit"]) + gitExec(["git", "-C", clone, "push", "-u", "origin", "main"]) return { bare, clone } } diff --git a/packages/opencode/.kilo/tui.json b/packages/opencode/.kilo/tui.json new file mode 100644 index 00000000000..9e26dfeeb6e --- /dev/null +++ b/packages/opencode/.kilo/tui.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/opencode/script/build.ts b/packages/opencode/script/build.ts index 7c6f841fb4d..5398596df66 100755 --- a/packages/opencode/script/build.ts +++ b/packages/opencode/script/build.ts @@ -62,6 +62,10 @@ async function isKiloConsoleUpToDate(app: string, out: string) { path.join(app, "index.html"), path.resolve(dir, "../kilo-web-ui/src"), path.resolve(dir, "../kilo-indexing/src"), + path.resolve(dir, "../kilo-ui/src"), + path.resolve(dir, "../ui/src"), + path.resolve(dir, "../sdk/js/src"), + path.resolve(dir, "../../bun.lock"), ] for (const p of inputs) { if (!fs.existsSync(p)) continue diff --git a/packages/opencode/script/generate.ts b/packages/opencode/script/generate.ts index c041fdec494..301458d07d3 100644 --- a/packages/opencode/script/generate.ts +++ b/packages/opencode/script/generate.ts @@ -16,22 +16,22 @@ const raw = await (async () => { return await Bun.file(process.env.MODELS_DEV_API_JSON).text() } const cached = Bun.file(cacheFile) - if (await cached.exists()) { - try { - const st = await cached.stat() - if (Date.now() - st.mtimeMs < 6 * 3600 * 1000) { - return await cached.text() - } - } catch {} + const exists = await cached.exists() + if (exists) { + const st = await cached.stat() + if (st && Date.now() - st.mtimeMs < 6 * 3600 * 1000) { + return await cached.text() + } } try { const res = await fetch(`${modelsUrl}/api.json`, { signal: AbortSignal.timeout(5000) }) + if (!res.ok) throw new Error(`Failed to fetch models.dev snapshot: HTTP ${res.status}`) const text = await res.text() - await Bun.write(cacheFile, text).catch(() => {}) + await Bun.write(cacheFile, text) return text - } catch (e) { - if (await cached.exists()) return await cached.text() - throw e + } catch (err) { + if (exists) return await cached.text() + throw err } })() export const modelsData = JSON.stringify(parseModelsSnapshot(raw).data) diff --git a/packages/opencode/script/kilocode/test-cli.ts b/packages/opencode/script/kilocode/test-cli.ts index 0a1775af817..372623eadef 100644 --- a/packages/opencode/script/kilocode/test-cli.ts +++ b/packages/opencode/script/kilocode/test-cli.ts @@ -21,18 +21,22 @@ export namespace TestCli { path.join(root, "..", "protocol", "src"), path.join(root, "..", "schema", "src"), path.join(root, "..", "plugin", "src"), + path.join(root, "..", "plugin-atomic-chat", "src"), + path.join(root, "..", "ui", "src"), + path.join(root, "..", "sdk", "js", "src"), path.join(root, "..", "kilo-memory", "src"), path.join(root, "..", "kilo-indexing", "src"), path.join(root, "..", "kilo-gateway", "src"), path.join(root, "..", "kilo-sandbox", "src"), path.join(root, "..", "kilo-telemetry", "src"), + path.resolve(root, "../../bun.lock"), ] for (const target of targets) { if (!fsSync.existsSync(target)) continue const st = fsSync.statSync(target) if (st.isDirectory()) { - const glob = new Bun.Glob("**/*.{ts,tsx,sql,json}") + const glob = new Bun.Glob("**/*.{ts,tsx,sql,json,txt}") for await (const file of glob.scan({ cwd: target })) { const p = path.join(target, file) const fileStat = fsSync.statSync(p) From cb2bd4df5e93a1cd837f1c605ab85a50b66a0b85 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 7 Aug 2026 12:34:05 +0200 Subject: [PATCH 4/9] chore: remove test artifact --- packages/opencode/.kilo/tui.json | 1 - 1 file changed, 1 deletion(-) delete mode 100644 packages/opencode/.kilo/tui.json diff --git a/packages/opencode/.kilo/tui.json b/packages/opencode/.kilo/tui.json deleted file mode 100644 index 9e26dfeeb6e..00000000000 --- a/packages/opencode/.kilo/tui.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file From 3f05a2a536451bd1c2fa814d9f5e7db870ae8f74 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 7 Aug 2026 12:57:48 +0200 Subject: [PATCH 5/9] fix(cli): make models snapshot cache write and read fault-tolerant --- packages/opencode/script/generate.ts | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/opencode/script/generate.ts b/packages/opencode/script/generate.ts index 301458d07d3..a50c1ad5266 100644 --- a/packages/opencode/script/generate.ts +++ b/packages/opencode/script/generate.ts @@ -16,21 +16,32 @@ const raw = await (async () => { return await Bun.file(process.env.MODELS_DEV_API_JSON).text() } const cached = Bun.file(cacheFile) - const exists = await cached.exists() - if (exists) { - const st = await cached.stat() - if (st && Date.now() - st.mtimeMs < 6 * 3600 * 1000) { - return await cached.text() + try { + if (await cached.exists()) { + const st = await cached.stat() + if (st && Date.now() - st.mtimeMs < 6 * 3600 * 1000) { + return await cached.text() + } } + } catch (err) { + console.warn("[generate] cache read check failed, fetching live", err) } try { const res = await fetch(`${modelsUrl}/api.json`, { signal: AbortSignal.timeout(5000) }) if (!res.ok) throw new Error(`Failed to fetch models.dev snapshot: HTTP ${res.status}`) const text = await res.text() - await Bun.write(cacheFile, text) + try { + await Bun.write(cacheFile, text) + } catch (err) { + console.warn("[generate] cache write failed", err) + } return text } catch (err) { - if (exists) return await cached.text() + try { + if (await cached.exists()) return await cached.text() + } catch (fallbackErr) { + console.warn("[generate] cache fallback read failed", fallbackErr) + } throw err } })() From df40369de86eb965287360f24b1b5f8bc16aee3f Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 7 Aug 2026 13:30:25 +0200 Subject: [PATCH 6/9] refactor(test): dynamically discover workspace packages in test-cli fingerprint --- packages/opencode/script/kilocode/test-cli.ts | 73 ++++++++++--------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/packages/opencode/script/kilocode/test-cli.ts b/packages/opencode/script/kilocode/test-cli.ts index 372623eadef..469be46a976 100644 --- a/packages/opencode/script/kilocode/test-cli.ts +++ b/packages/opencode/script/kilocode/test-cli.ts @@ -10,42 +10,42 @@ export namespace TestCli { async function fingerprint(root: string): Promise { const hash = crypto.createHash("sha256") - const targets = [ - path.join(root, "package.json"), - path.join(root, "src"), - path.join(root, "migration"), - path.join(root, "..", "core", "src"), - path.join(root, "..", "server", "src"), - path.join(root, "..", "tui", "src"), - path.join(root, "..", "llm", "src"), - path.join(root, "..", "protocol", "src"), - path.join(root, "..", "schema", "src"), - path.join(root, "..", "plugin", "src"), - path.join(root, "..", "plugin-atomic-chat", "src"), - path.join(root, "..", "ui", "src"), - path.join(root, "..", "sdk", "js", "src"), - path.join(root, "..", "kilo-memory", "src"), - path.join(root, "..", "kilo-indexing", "src"), - path.join(root, "..", "kilo-gateway", "src"), - path.join(root, "..", "kilo-sandbox", "src"), - path.join(root, "..", "kilo-telemetry", "src"), - path.resolve(root, "../../bun.lock"), - ] + const repo = path.resolve(root, "../..") + const pkgs = path.join(repo, "packages") - for (const target of targets) { - if (!fsSync.existsSync(target)) continue - const st = fsSync.statSync(target) - if (st.isDirectory()) { - const glob = new Bun.Glob("**/*.{ts,tsx,sql,json,txt}") - for await (const file of glob.scan({ cwd: target })) { - const p = path.join(target, file) - const fileStat = fsSync.statSync(p) - hash.update(file).update(String(fileStat.mtimeMs)).update(String(fileStat.size)) + const lock = path.join(repo, "bun.lock") + if (fsSync.existsSync(lock)) { + const st = fsSync.statSync(lock) + hash.update("bun.lock").update(String(st.mtimeMs)).update(String(st.size)) + } + + const ignored = new Set(["kilo-vscode", "kilo-jetbrains", "kilo-docs"]) + const entries = fsSync.readdirSync(pkgs, { withFileTypes: true }) + + for (const ent of entries) { + if (!ent.isDirectory() || ignored.has(ent.name)) continue + const dir = path.join(pkgs, ent.name) + const targets = + ent.name === "sdk" + ? [path.join(dir, "js", "src"), path.join(dir, "js", "package.json")] + : [path.join(dir, "src"), path.join(dir, "migration"), path.join(dir, "package.json")] + + for (const target of targets) { + if (!fsSync.existsSync(target)) continue + const st = fsSync.statSync(target) + if (st.isDirectory()) { + const glob = new Bun.Glob("**/*.{ts,tsx,sql,json,txt}") + for await (const file of glob.scan({ cwd: target })) { + const p = path.join(target, file) + const fst = fsSync.statSync(p) + hash.update(`${ent.name}:${file}`).update(String(fst.mtimeMs)).update(String(fst.size)) + } + } else { + hash.update(`${ent.name}:pkg`).update(String(st.mtimeMs)).update(String(st.size)) } - } else { - hash.update(target).update(String(st.mtimeMs)).update(String(st.size)) } } + return hash.digest("hex") } @@ -105,7 +105,14 @@ export namespace TestCli { return } })() - if (target) await fs.symlink(target, path.join(scope, name.replace("@opentui/", "")), kind) + if (target) { + const link = path.join(scope, name.replace("@opentui/", "")) + try { + if (!fsSync.existsSync(link)) { + await fs.symlink(target, link, kind) + } + } catch {} + } } if (!targetDir) { From 3b2686af8433d06f3633f6bc12831cda605c48b8 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 7 Aug 2026 13:50:39 +0200 Subject: [PATCH 7/9] chore(annotations): wrap upstream changes with kilocode_change markers --- packages/opencode/script/build.ts | 4 +++- packages/opencode/script/test-runner.ts | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/opencode/script/build.ts b/packages/opencode/script/build.ts index 5398596df66..9a2cced928a 100755 --- a/packages/opencode/script/build.ts +++ b/packages/opencode/script/build.ts @@ -51,6 +51,7 @@ async function copyTreeSitterWasms(outputDir: string) { console.log(`copied ${languageWasmFiles.length + 1} tree-sitter wasm files to ${targetDir}`) } +// kilocode_change start async function isKiloConsoleUpToDate(app: string, out: string) { const indexHtml = path.join(out, "index.html") if (!fs.existsSync(indexHtml)) return false @@ -102,6 +103,7 @@ async function buildKiloConsole() { if (code !== 0) throw new Error(`Kilo Console build failed with exit code ${code}`) return out } +// kilocode_change end async function copyKiloConsole(input: string, outputDir: string) { const target = path.join(outputDir, "console") @@ -253,8 +255,8 @@ const targets = singleFlag }) : allTargets -await $`rm -rf dist` // kilocode_change start +await $`rm -rf dist` const [kiloConsoleDist, kiloSandboxWorker, kiloSandboxNetwork] = await Promise.all([ buildKiloConsole(), KiloSandboxWorker.bundle(), diff --git a/packages/opencode/script/test-runner.ts b/packages/opencode/script/test-runner.ts index bfb8afcc0a3..c27ce536102 100644 --- a/packages/opencode/script/test-runner.ts +++ b/packages/opencode/script/test-runner.ts @@ -187,6 +187,7 @@ type Proc = ReturnType const xmldir = ci ? path.join(os.tmpdir(), `opencode-junit-${process.pid}`) : "" if (ci) await fs.mkdir(xmldir, { recursive: true }) +// kilocode_change start const supplied = process.env[TestCli.ENV] const built = supplied ? { binary: supplied, dir: undefined } @@ -196,6 +197,7 @@ async function cleanBinary() { if (!built.dir) return await fs.rm(built.dir, { recursive: true, force: true }) } +// kilocode_change end const counter = { done: 0 } const pad = String(files.length).length From c09fc1ff0584954fa3761ef9abf1335b47605b26 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 7 Aug 2026 13:53:26 +0200 Subject: [PATCH 8/9] fix(deps): declare build dependencies and clean stale test-cli symlinks --- packages/kilo-vscode/package.json | 3 + .../tests/unit/esbuild-dependencies.test.ts | 62 +++++++++++++++++++ packages/opencode/script/kilocode/test-cli.ts | 15 +++-- 3 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 packages/kilo-vscode/tests/unit/esbuild-dependencies.test.ts diff --git a/packages/kilo-vscode/package.json b/packages/kilo-vscode/package.json index 0ef75035b9d..ba2fffd92aa 100644 --- a/packages/kilo-vscode/package.json +++ b/packages/kilo-vscode/package.json @@ -1263,6 +1263,8 @@ }, "devDependencies": { "@axe-core/playwright": "4.11.3", + "@babel/core": "^7.28.4", + "@babel/preset-typescript": "^7.27.1", "@playwright/test": "1.57.0", "@storybook/addon-a11y": "10.2.10", "@storybook/addon-docs": "10.2.10", @@ -1274,6 +1276,7 @@ "@vscode/test-cli": "^0.0.12", "@vscode/test-electron": "^2.5.2", "@vscode/vsce": "^3.7.1", + "babel-preset-solid": "^1.9.9", "esbuild": "^0.27.2", "esbuild-plugin-solid": "^0.6.0", "eslint": "^9.39.2", diff --git a/packages/kilo-vscode/tests/unit/esbuild-dependencies.test.ts b/packages/kilo-vscode/tests/unit/esbuild-dependencies.test.ts new file mode 100644 index 00000000000..d200eee6a82 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/esbuild-dependencies.test.ts @@ -0,0 +1,62 @@ +import { describe, it, expect } from "bun:test" +import fs from "node:fs" +import path from "node:path" +import { builtinModules } from "node:module" + +const ROOT = path.resolve(import.meta.dir, "../..") +const PKG_FILE = path.join(ROOT, "package.json") +const ESBUILD_FILE = path.join(ROOT, "esbuild.js") + +const BUILTINS = new Set([...builtinModules, ...builtinModules.map((m) => `node:${m}`)]) + +function extractPackageName(specifier: string): string | null { + if (specifier.startsWith(".") || specifier.startsWith("/")) return null + if (BUILTINS.has(specifier)) return null + if (specifier.startsWith("node:")) return null + + if (specifier.startsWith("@")) { + const parts = specifier.split("/") + return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : specifier + } + return specifier.split("/")[0] +} + +function findImportsAndRequires(content: string): string[] { + const specifiers = new Set() + const requireRegex = /require\(["']([^"']+)["']\)/g + const importRegex = /(?:import|from)\s+["']([^"']+)["']/g + + for (const match of content.matchAll(requireRegex)) { + specifiers.add(match[1]) + } + for (const match of content.matchAll(importRegex)) { + specifiers.add(match[1]) + } + + return Array.from(specifiers) +} + +describe("Build Script Dependency Declarations", () => { + it("esbuild.js must declare all imported/required packages in package.json", () => { + const pkg = JSON.parse(fs.readFileSync(PKG_FILE, "utf8")) + const declared = new Set([ + ...Object.keys(pkg.dependencies || {}), + ...Object.keys(pkg.devDependencies || {}), + ...Object.keys(pkg.peerDependencies || {}), + "vscode", + ]) + + const esbuildContent = fs.readFileSync(ESBUILD_FILE, "utf8") + const specifiers = findImportsAndRequires(esbuildContent) + + const undeclared: string[] = [] + for (const spec of specifiers) { + const pkgName = extractPackageName(spec) + if (pkgName && !declared.has(pkgName)) { + undeclared.push(`${spec} (package: ${pkgName})`) + } + } + + expect(undeclared).toEqual([]) + }) +}) diff --git a/packages/opencode/script/kilocode/test-cli.ts b/packages/opencode/script/kilocode/test-cli.ts index 469be46a976..ae7518ab560 100644 --- a/packages/opencode/script/kilocode/test-cli.ts +++ b/packages/opencode/script/kilocode/test-cli.ts @@ -108,18 +108,21 @@ export namespace TestCli { if (target) { const link = path.join(scope, name.replace("@opentui/", "")) try { - if (!fsSync.existsSync(link)) { - await fs.symlink(target, link, kind) - } - } catch {} + await fs.rm(link, { recursive: true, force: true }) + await fs.symlink(target, link, kind) + } catch (err) { + console.warn(`[test-cli] failed to link native variant ${name}:`, err) + } } } if (!targetDir) { try { const currentHash = await fingerprint(root) - fsSync.writeFileSync(hashFile, currentHash) - } catch {} + await fs.writeFile(hashFile, currentHash) + } catch (err) { + console.warn("[test-cli] failed to save fingerprint hash:", err) + } } return bin From 3543d94584dbf08e39f945a7c4645f73a3e0927e Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 7 Aug 2026 14:01:22 +0200 Subject: [PATCH 9/9] chore(lockfile): sync lockfile with package.json dependencies --- bun.lock | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bun.lock b/bun.lock index 55bcea2cab1..50998be7c4d 100644 --- a/bun.lock +++ b/bun.lock @@ -470,6 +470,8 @@ }, "devDependencies": { "@axe-core/playwright": "4.11.3", + "@babel/core": "^7.28.4", + "@babel/preset-typescript": "^7.27.1", "@playwright/test": "1.57.0", "@storybook/addon-a11y": "10.2.10", "@storybook/addon-docs": "10.2.10", @@ -481,6 +483,7 @@ "@vscode/test-cli": "^0.0.12", "@vscode/test-electron": "^2.5.2", "@vscode/vsce": "^3.7.1", + "babel-preset-solid": "^1.9.9", "esbuild": "^0.27.2", "esbuild-plugin-solid": "^0.6.0", "eslint": "^9.39.2",