From 36210fa6d15e5b6b9ca8f9fd97872f90118c65bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Catriel=20M=C3=BCller?= Date: Mon, 16 Mar 2026 20:33:36 -0300 Subject: [PATCH] resolve merge conflicts --- packages/app/e2e/actions.ts | 6 +- packages/extensions/zed/extension.toml | 12 +-- packages/opencode/package.json | 2 + packages/opencode/src/cli/cmd/session.ts | 5 +- .../src/cli/cmd/tui/util/clipboard.ts | 9 +- packages/opencode/src/file/index.ts | 34 ++++--- packages/opencode/src/file/ripgrep.ts | 3 +- packages/opencode/src/format/formatter.ts | 43 ++++----- packages/opencode/src/lsp/server.ts | 89 ++++++++++--------- packages/opencode/src/project/project.ts | 3 +- packages/opencode/src/provider/provider.ts | 5 +- packages/opencode/src/shell/shell.ts | 5 +- packages/opencode/src/util/which.ts | 10 +++ packages/opencode/src/worktree/index.ts | 9 +- packages/opencode/test/file/fsmonitor.test.ts | 62 +++++++++++++ .../opencode/test/fixture/fixture.test.ts | 26 ++++++ packages/opencode/test/fixture/fixture.ts | 32 ++++++- .../test/project/worktree-remove.test.ts | 31 +++++++ packages/opencode/test/util/which.test.ts | 82 +++++++++++++++++ packages/plugin/package.json | 12 +-- packages/sdk/js/package.json | 12 +-- sdks/vscode/package.json | 4 +- 22 files changed, 380 insertions(+), 116 deletions(-) create mode 100644 packages/opencode/src/util/which.ts create mode 100644 packages/opencode/test/file/fsmonitor.test.ts create mode 100644 packages/opencode/test/fixture/fixture.test.ts create mode 100644 packages/opencode/test/util/which.test.ts diff --git a/packages/app/e2e/actions.ts b/packages/app/e2e/actions.ts index f13ff63cd2..bf95feabcb 100644 --- a/packages/app/e2e/actions.ts +++ b/packages/app/e2e/actions.ts @@ -197,6 +197,7 @@ export async function createTestProject() { await fs.writeFile(path.join(root, "README.md"), "# e2e\n") execSync("git init", { cwd: root, stdio: "ignore" }) + execSync("git config core.fsmonitor false", { cwd: root, stdio: "ignore" }) execSync("git add -A", { cwd: root, stdio: "ignore" }) execSync('git -c user.name="e2e" -c user.email="e2e@example.com" commit -m "init" --allow-empty', { cwd: root, @@ -207,7 +208,10 @@ export async function createTestProject() { } export async function cleanupTestProject(directory: string) { - await fs.rm(directory, { recursive: true, force: true }).catch(() => undefined) + try { + execSync("git fsmonitor--daemon stop", { cwd: directory, stdio: "ignore" }) + } catch {} + await fs.rm(directory, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }).catch(() => undefined) } export function sessionIDFromUrl(url: string) { diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 4e2b2273cd..036d35d170 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "kilo" name = "Kilo" description = "The open source coding agent." -version = "1.2.19" +version = "1.2.20" schema_version = 1 authors = ["Anomaly"] repository = "https://github.com/Kilo-Org/kilocode" @@ -11,26 +11,26 @@ name = "Kilo" icon = "./icons/opencode.svg" [agent_servers.opencode.targets.darwin-aarch64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.2.19/opencode-darwin-arm64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.2.20/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.2.19/opencode-darwin-x64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.2.20/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.2.19/opencode-linux-arm64.tar.gz" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.2.20/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.2.19/opencode-linux-x64.tar.gz" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.2.20/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.2.19/opencode-windows-x64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.2.20/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 527d290028..5a84fb701e 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -43,6 +43,7 @@ "@types/mime-types": "3.0.1", "@types/turndown": "5.0.5", "@types/yargs": "17.0.33", + "@types/which": "3.0.4", "@typescript/native-preview": "catalog:", "drizzle-kit": "1.0.0-beta.12-a5629fb", "drizzle-orm": "1.0.0-beta.12-a5629fb", @@ -127,6 +128,7 @@ "ulid": "catalog:", "vscode-jsonrpc": "8.2.1", "web-tree-sitter": "0.25.10", + "which": "6.0.1", "xdg-basedir": "5.1.0", "yargs": "18.0.0", "zod": "catalog:", diff --git a/packages/opencode/src/cli/cmd/session.ts b/packages/opencode/src/cli/cmd/session.ts index cdd28e586a..8c770fdb65 100644 --- a/packages/opencode/src/cli/cmd/session.ts +++ b/packages/opencode/src/cli/cmd/session.ts @@ -9,6 +9,7 @@ import { Filesystem } from "../../util/filesystem" import { Process } from "../../util/process" import { EOL } from "os" import path from "path" +import { which } from "../../util/which" function pagerCmd(): string[] { const lessOptions = ["-R", "-S"] @@ -17,7 +18,7 @@ function pagerCmd(): string[] { } // user could have less installed via other options - const lessOnPath = Bun.which("less") + const lessOnPath = which("less") if (lessOnPath) { if (Filesystem.stat(lessOnPath)?.size) return [lessOnPath, ...lessOptions] } @@ -27,7 +28,7 @@ function pagerCmd(): string[] { if (Filesystem.stat(less)?.size) return [less, ...lessOptions] } - const git = Bun.which("git") + const git = which("git") if (git) { const less = path.join(git, "..", "..", "usr", "bin", "less.exe") if (Filesystem.stat(less)?.size) return [less, ...lessOptions] diff --git a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts index 1a8197bf4e..412ec654ff 100644 --- a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts +++ b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts @@ -6,6 +6,7 @@ import { tmpdir } from "os" import path from "path" import { Filesystem } from "../../../../util/filesystem" import { Process } from "../../../../util/process" +import { which } from "../../../../util/which" /** * Writes text to clipboard via OSC 52 escape sequence. @@ -76,7 +77,7 @@ export namespace Clipboard { const getCopyMethod = lazy(() => { const os = platform() - if (os === "darwin" && Bun.which("osascript")) { + if (os === "darwin" && which("osascript")) { console.log("clipboard: using osascript") return async (text: string) => { const escaped = text.replace(/\\/g, "\\\\").replace(/"/g, '\\"') @@ -85,7 +86,7 @@ export namespace Clipboard { } if (os === "linux") { - if (process.env["WAYLAND_DISPLAY"] && Bun.which("wl-copy")) { + if (process.env["WAYLAND_DISPLAY"] && which("wl-copy")) { console.log("clipboard: using wl-copy") return async (text: string) => { const proc = Process.spawn(["wl-copy"], { stdin: "pipe", stdout: "ignore", stderr: "ignore" }) @@ -95,7 +96,7 @@ export namespace Clipboard { await proc.exited.catch(() => {}) } } - if (Bun.which("xclip")) { + if (which("xclip")) { console.log("clipboard: using xclip") return async (text: string) => { const proc = Process.spawn(["xclip", "-selection", "clipboard"], { @@ -109,7 +110,7 @@ export namespace Clipboard { await proc.exited.catch(() => {}) } } - if (Bun.which("xsel")) { + if (which("xsel")) { console.log("clipboard: using xsel") return async (text: string) => { const proc = Process.spawn(["xsel", "--clipboard", "--input"], { diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts index b7daddc5fb..01f07c9afa 100644 --- a/packages/opencode/src/file/index.ts +++ b/packages/opencode/src/file/index.ts @@ -418,7 +418,7 @@ export namespace File { const project = Instance.project if (project.vcs !== "git") return [] - const diffOutput = await $`git -c core.quotepath=false diff --numstat HEAD` + const diffOutput = await $`git -c core.fsmonitor=false -c core.quotepath=false diff --numstat HEAD` .cwd(Instance.directory) .quiet() .nothrow() @@ -439,11 +439,12 @@ export namespace File { } } - const untrackedOutput = await $`git -c core.quotepath=false ls-files --others --exclude-standard` - .cwd(Instance.directory) - .quiet() - .nothrow() - .text() + const untrackedOutput = + await $`git -c core.fsmonitor=false -c core.quotepath=false ls-files --others --exclude-standard` + .cwd(Instance.directory) + .quiet() + .nothrow() + .text() if (untrackedOutput.trim()) { const untrackedFiles = untrackedOutput.trim().split("\n") @@ -464,11 +465,12 @@ export namespace File { } // Get deleted files - const deletedOutput = await $`git -c core.quotepath=false diff --name-only --diff-filter=D HEAD` - .cwd(Instance.directory) - .quiet() - .nothrow() - .text() + const deletedOutput = + await $`git -c core.fsmonitor=false -c core.quotepath=false diff --name-only --diff-filter=D HEAD` + .cwd(Instance.directory) + .quiet() + .nothrow() + .text() if (deletedOutput.trim()) { const deletedFiles = deletedOutput.trim().split("\n") @@ -539,8 +541,14 @@ export namespace File { const content = (await Filesystem.readText(full).catch(() => "")).trim() if (project.vcs === "git") { - let diff = await $`git diff ${file}`.cwd(Instance.directory).quiet().nothrow().text() - if (!diff.trim()) diff = await $`git diff --staged ${file}`.cwd(Instance.directory).quiet().nothrow().text() + let diff = await $`git -c core.fsmonitor=false diff ${file}`.cwd(Instance.directory).quiet().nothrow().text() + if (!diff.trim()) { + diff = await $`git -c core.fsmonitor=false diff --staged ${file}` + .cwd(Instance.directory) + .quiet() + .nothrow() + .text() + } if (diff.trim()) { const original = await $`git show HEAD:${file}`.cwd(Instance.directory).quiet().nothrow().text() const patch = structuredPatch(file, file, original, content, "old", "new", { diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts index f47e43c40d..4467597ba8 100644 --- a/packages/opencode/src/file/ripgrep.ts +++ b/packages/opencode/src/file/ripgrep.ts @@ -9,6 +9,7 @@ import { lazy } from "../util/lazy" import { $ } from "bun" import { Filesystem } from "../util/filesystem" import { Process } from "../util/process" +import { which } from "../util/which" import { text } from "node:stream/consumers" import { ZipReader, BlobReader, BlobWriter } from "@zip.js/zip.js" @@ -127,7 +128,7 @@ export namespace Ripgrep { ) const state = lazy(async () => { - const system = Bun.which("rg") + const system = which("rg") if (system) { const stat = await fs.stat(system).catch(() => undefined) if (stat?.isFile()) return { filepath: system } diff --git a/packages/opencode/src/format/formatter.ts b/packages/opencode/src/format/formatter.ts index 472466414c..c519411c23 100644 --- a/packages/opencode/src/format/formatter.ts +++ b/packages/opencode/src/format/formatter.ts @@ -3,6 +3,7 @@ import { BunProc } from "../bun" import { Instance } from "../project/instance" import { Filesystem } from "../util/filesystem" import { Process } from "../util/process" +import { which } from "../util/which" import { Flag } from "@/flag/flag" export interface Info { @@ -18,7 +19,7 @@ export const gofmt: Info = { command: ["gofmt", "-w", "$FILE"], extensions: [".go"], async enabled() { - return Bun.which("gofmt") !== null + return which("gofmt") !== null }, } @@ -27,7 +28,7 @@ export const mix: Info = { command: ["mix", "format", "$FILE"], extensions: [".ex", ".exs", ".eex", ".heex", ".leex", ".neex", ".sface"], async enabled() { - return Bun.which("mix") !== null + return which("mix") !== null }, } @@ -152,7 +153,7 @@ export const zig: Info = { command: ["zig", "fmt", "$FILE"], extensions: [".zig", ".zon"], async enabled() { - return Bun.which("zig") !== null + return which("zig") !== null }, } @@ -171,7 +172,7 @@ export const ktlint: Info = { command: ["ktlint", "-F", "$FILE"], extensions: [".kt", ".kts"], async enabled() { - return Bun.which("ktlint") !== null + return which("ktlint") !== null }, } @@ -180,7 +181,7 @@ export const ruff: Info = { command: ["ruff", "format", "$FILE"], extensions: [".py", ".pyi"], async enabled() { - if (!Bun.which("ruff")) return false + if (!which("ruff")) return false const configs = ["pyproject.toml", "ruff.toml", ".ruff.toml"] for (const config of configs) { const found = await Filesystem.findUp(config, Instance.directory, Instance.worktree) @@ -210,7 +211,7 @@ export const rlang: Info = { command: ["air", "format", "$FILE"], extensions: [".R"], async enabled() { - const airPath = Bun.which("air") + const airPath = which("air") if (airPath == null) return false try { @@ -239,7 +240,7 @@ export const uvformat: Info = { extensions: [".py", ".pyi"], async enabled() { if (await ruff.enabled()) return false - if (Bun.which("uv") !== null) { + if (which("uv") !== null) { const proc = Process.spawn(["uv", "format", "--help"], { stderr: "pipe", stdout: "pipe" }) const code = await proc.exited return code === 0 @@ -253,7 +254,7 @@ export const rubocop: Info = { command: ["rubocop", "--autocorrect", "$FILE"], extensions: [".rb", ".rake", ".gemspec", ".ru"], async enabled() { - return Bun.which("rubocop") !== null + return which("rubocop") !== null }, } @@ -262,7 +263,7 @@ export const standardrb: Info = { command: ["standardrb", "--fix", "$FILE"], extensions: [".rb", ".rake", ".gemspec", ".ru"], async enabled() { - return Bun.which("standardrb") !== null + return which("standardrb") !== null }, } @@ -271,7 +272,7 @@ export const htmlbeautifier: Info = { command: ["htmlbeautifier", "$FILE"], extensions: [".erb", ".html.erb"], async enabled() { - return Bun.which("htmlbeautifier") !== null + return which("htmlbeautifier") !== null }, } @@ -280,7 +281,7 @@ export const dart: Info = { command: ["dart", "format", "$FILE"], extensions: [".dart"], async enabled() { - return Bun.which("dart") !== null + return which("dart") !== null }, } @@ -289,7 +290,7 @@ export const ocamlformat: Info = { command: ["ocamlformat", "-i", "$FILE"], extensions: [".ml", ".mli"], async enabled() { - if (!Bun.which("ocamlformat")) return false + if (!which("ocamlformat")) return false const items = await Filesystem.findUp(".ocamlformat", Instance.directory, Instance.worktree) return items.length > 0 }, @@ -300,7 +301,7 @@ export const terraform: Info = { command: ["terraform", "fmt", "$FILE"], extensions: [".tf", ".tfvars"], async enabled() { - return Bun.which("terraform") !== null + return which("terraform") !== null }, } @@ -309,7 +310,7 @@ export const latexindent: Info = { command: ["latexindent", "-w", "-s", "$FILE"], extensions: [".tex"], async enabled() { - return Bun.which("latexindent") !== null + return which("latexindent") !== null }, } @@ -318,7 +319,7 @@ export const gleam: Info = { command: ["gleam", "format", "$FILE"], extensions: [".gleam"], async enabled() { - return Bun.which("gleam") !== null + return which("gleam") !== null }, } @@ -327,7 +328,7 @@ export const shfmt: Info = { command: ["shfmt", "-w", "$FILE"], extensions: [".sh", ".bash"], async enabled() { - return Bun.which("shfmt") !== null + return which("shfmt") !== null }, } @@ -336,7 +337,7 @@ export const nixfmt: Info = { command: ["nixfmt", "$FILE"], extensions: [".nix"], async enabled() { - return Bun.which("nixfmt") !== null + return which("nixfmt") !== null }, } @@ -345,7 +346,7 @@ export const rustfmt: Info = { command: ["rustfmt", "$FILE"], extensions: [".rs"], async enabled() { - return Bun.which("rustfmt") !== null + return which("rustfmt") !== null }, } @@ -372,7 +373,7 @@ export const ormolu: Info = { command: ["ormolu", "-i", "$FILE"], extensions: [".hs"], async enabled() { - return Bun.which("ormolu") !== null + return which("ormolu") !== null }, } @@ -381,7 +382,7 @@ export const cljfmt: Info = { command: ["cljfmt", "fix", "--quiet", "$FILE"], extensions: [".clj", ".cljs", ".cljc", ".edn"], async enabled() { - return Bun.which("cljfmt") !== null + return which("cljfmt") !== null }, } @@ -390,6 +391,6 @@ export const dfmt: Info = { command: ["dfmt", "-i", "$FILE"], extensions: [".d"], async enabled() { - return Bun.which("dfmt") !== null + return which("dfmt") !== null }, } diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index dbd4c59acf..e63dabc656 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -12,6 +12,7 @@ import { Instance } from "../project/instance" import { Flag } from "../flag/flag" import { Archive } from "../util/archive" import { Process } from "../util/process" +import { which } from "../util/which" // kilocode_change start - prevent CMD window flash on Windows for all LSP server spawns function spawn(cmd: string, opts?: SpawnOptions): ChildProcessWithoutNullStreams @@ -85,7 +86,7 @@ export namespace LSPServer { }, extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs"], async spawn(root) { - const deno = Bun.which("deno") + const deno = which("deno") if (!deno) { log.info("deno not found, please install deno first") return @@ -132,7 +133,7 @@ export namespace LSPServer { extensions: [".vue"], root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]), async spawn(root) { - let binary = Bun.which("vue-language-server") + let binary = which("vue-language-server") const args: string[] = [] if (!binary) { const js = path.join( @@ -270,7 +271,7 @@ export namespace LSPServer { let lintBin = await resolveBin(lintTarget) if (!lintBin) { - const found = Bun.which("oxlint") + const found = which("oxlint") if (found) lintBin = found } @@ -291,7 +292,7 @@ export namespace LSPServer { let serverBin = await resolveBin(serverTarget) if (!serverBin) { - const found = Bun.which("oxc_language_server") + const found = which("oxc_language_server") if (found) serverBin = found } if (serverBin) { @@ -342,7 +343,7 @@ export namespace LSPServer { let bin: string | undefined if (await Filesystem.exists(localBin)) bin = localBin if (!bin) { - const found = Bun.which("biome") + const found = which("biome") if (found) bin = found } @@ -378,11 +379,11 @@ export namespace LSPServer { }, extensions: [".go"], async spawn(root) { - let bin = Bun.which("gopls", { + let bin = which("gopls", { PATH: process.env["PATH"] + path.delimiter + Global.Path.bin, }) if (!bin) { - if (!Bun.which("go")) return + if (!which("go")) return if (Flag.KILO_DISABLE_LSP_DOWNLOAD) return log.info("installing gopls") @@ -415,12 +416,12 @@ export namespace LSPServer { root: NearestRoot(["Gemfile"]), extensions: [".rb", ".rake", ".gemspec", ".ru"], async spawn(root) { - let bin = Bun.which("rubocop", { + let bin = which("rubocop", { PATH: process.env["PATH"] + path.delimiter + Global.Path.bin, }) if (!bin) { - const ruby = Bun.which("ruby") - const gem = Bun.which("gem") + const ruby = which("ruby") + const gem = which("gem") if (!ruby || !gem) { log.info("Ruby not found, please install Ruby first") return @@ -467,7 +468,7 @@ export namespace LSPServer { return undefined } - let binary = Bun.which("ty") + let binary = which("ty") const initialization: Record = {} @@ -519,7 +520,7 @@ export namespace LSPServer { extensions: [".py", ".pyi"], root: NearestRoot(["pyproject.toml", "setup.py", "setup.cfg", "requirements.txt", "Pipfile", "pyrightconfig.json"]), async spawn(root) { - let binary = Bun.which("pyright-langserver") + let binary = which("pyright-langserver") const args = [] if (!binary) { const js = path.join(Global.Path.bin, "node_modules", "pyright", "dist", "pyright-langserver.js") @@ -573,7 +574,7 @@ export namespace LSPServer { extensions: [".ex", ".exs"], root: NearestRoot(["mix.exs", "mix.lock"]), async spawn(root) { - let binary = Bun.which("elixir-ls") + let binary = which("elixir-ls") if (!binary) { const elixirLsPath = path.join(Global.Path.bin, "elixir-ls") binary = path.join( @@ -584,7 +585,7 @@ export namespace LSPServer { ) if (!(await Filesystem.exists(binary))) { - const elixir = Bun.which("elixir") + const elixir = which("elixir") if (!elixir) { log.error("elixir is required to run elixir-ls") return @@ -635,12 +636,12 @@ export namespace LSPServer { extensions: [".zig", ".zon"], root: NearestRoot(["build.zig"]), async spawn(root) { - let bin = Bun.which("zls", { + let bin = which("zls", { PATH: process.env["PATH"] + path.delimiter + Global.Path.bin, }) if (!bin) { - const zig = Bun.which("zig") + const zig = which("zig") if (!zig) { log.error("Zig is required to use zls. Please install Zig first.") return @@ -747,11 +748,11 @@ export namespace LSPServer { root: NearestRoot([".slnx", ".sln", ".csproj", "global.json"]), extensions: [".cs"], async spawn(root) { - let bin = Bun.which("csharp-ls", { + let bin = which("csharp-ls", { PATH: process.env["PATH"] + path.delimiter + Global.Path.bin, }) if (!bin) { - if (!Bun.which("dotnet")) { + if (!which("dotnet")) { log.error(".NET SDK is required to install csharp-ls") return } @@ -786,11 +787,11 @@ export namespace LSPServer { root: NearestRoot([".slnx", ".sln", ".fsproj", "global.json"]), extensions: [".fs", ".fsi", ".fsx", ".fsscript"], async spawn(root) { - let bin = Bun.which("fsautocomplete", { + let bin = which("fsautocomplete", { PATH: process.env["PATH"] + path.delimiter + Global.Path.bin, }) if (!bin) { - if (!Bun.which("dotnet")) { + if (!which("dotnet")) { log.error(".NET SDK is required to install fsautocomplete") return } @@ -827,7 +828,7 @@ export namespace LSPServer { async spawn(root) { // Check if sourcekit-lsp is available in the PATH // This is installed with the Swift toolchain - const sourcekit = Bun.which("sourcekit-lsp") + const sourcekit = which("sourcekit-lsp") if (sourcekit) { return { process: spawn(sourcekit, { @@ -838,7 +839,7 @@ export namespace LSPServer { // If sourcekit-lsp not found, check if xcrun is available // This is specific to macOS where sourcekit-lsp is typically installed with Xcode - if (!Bun.which("xcrun")) return + if (!which("xcrun")) return const lspLoc = await $`xcrun --find sourcekit-lsp`.quiet().nothrow() @@ -887,7 +888,7 @@ export namespace LSPServer { }, extensions: [".rs"], async spawn(root) { - const bin = Bun.which("rust-analyzer") + const bin = which("rust-analyzer") if (!bin) { log.info("rust-analyzer not found in path, please install it") return @@ -906,7 +907,7 @@ export namespace LSPServer { extensions: [".c", ".cpp", ".cc", ".cxx", ".c++", ".h", ".hpp", ".hh", ".hxx", ".h++"], async spawn(root) { const args = ["--background-index", "--clang-tidy"] - const fromPath = Bun.which("clangd") + const fromPath = which("clangd") if (fromPath) { return { process: spawn(fromPath, args, { @@ -1051,7 +1052,7 @@ export namespace LSPServer { extensions: [".svelte"], root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]), async spawn(root) { - let binary = Bun.which("svelteserver") + let binary = which("svelteserver") const args: string[] = [] if (!binary) { const js = path.join(Global.Path.bin, "node_modules", "svelte-language-server", "bin", "server.js") @@ -1098,7 +1099,7 @@ export namespace LSPServer { } const tsdk = path.dirname(tsserver) - let binary = Bun.which("astro-ls") + let binary = which("astro-ls") const args: string[] = [] if (!binary) { const js = path.join(Global.Path.bin, "node_modules", "@astrojs", "language-server", "bin", "nodeServer.js") @@ -1142,7 +1143,7 @@ export namespace LSPServer { root: NearestRoot(["pom.xml", "build.gradle", "build.gradle.kts", ".project", ".classpath"]), extensions: [".java"], async spawn(root) { - const java = Bun.which("java") + const java = which("java") if (!java) { log.error("Java 21 or newer is required to run the JDTLS. Please install it first.") return @@ -1334,7 +1335,7 @@ export namespace LSPServer { extensions: [".yaml", ".yml"], root: NearestRoot(["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"]), async spawn(root) { - let binary = Bun.which("yaml-language-server") + let binary = which("yaml-language-server") const args: string[] = [] if (!binary) { const js = path.join( @@ -1390,7 +1391,7 @@ export namespace LSPServer { ]), extensions: [".lua"], async spawn(root) { - let bin = Bun.which("lua-language-server", { + let bin = which("lua-language-server", { PATH: process.env["PATH"] + path.delimiter + Global.Path.bin, }) @@ -1522,7 +1523,7 @@ export namespace LSPServer { extensions: [".php"], root: NearestRoot(["composer.json", "composer.lock", ".php-version"]), async spawn(root) { - let binary = Bun.which("intelephense") + let binary = which("intelephense") const args: string[] = [] if (!binary) { const js = path.join(Global.Path.bin, "node_modules", "intelephense", "lib", "intelephense.js") @@ -1566,7 +1567,7 @@ export namespace LSPServer { extensions: [".prisma"], root: NearestRoot(["schema.prisma", "prisma/schema.prisma", "prisma"], ["package.json"]), async spawn(root) { - const prisma = Bun.which("prisma") + const prisma = which("prisma") if (!prisma) { log.info("prisma not found, please install prisma") return @@ -1584,7 +1585,7 @@ export namespace LSPServer { extensions: [".dart"], root: NearestRoot(["pubspec.yaml", "analysis_options.yaml"]), async spawn(root) { - const dart = Bun.which("dart") + const dart = which("dart") if (!dart) { log.info("dart not found, please install dart first") return @@ -1602,7 +1603,7 @@ export namespace LSPServer { extensions: [".ml", ".mli"], root: NearestRoot(["dune-project", "dune-workspace", ".merlin", "opam"]), async spawn(root) { - const bin = Bun.which("ocamllsp") + const bin = which("ocamllsp") if (!bin) { log.info("ocamllsp not found, please install ocaml-lsp-server") return @@ -1619,7 +1620,7 @@ export namespace LSPServer { extensions: [".sh", ".bash", ".zsh", ".ksh"], root: async () => Instance.directory, async spawn(root) { - let binary = Bun.which("bash-language-server") + let binary = which("bash-language-server") const args: string[] = [] if (!binary) { const js = path.join(Global.Path.bin, "node_modules", "bash-language-server", "out", "cli.js") @@ -1658,7 +1659,7 @@ export namespace LSPServer { extensions: [".tf", ".tfvars"], root: NearestRoot([".terraform.lock.hcl", "terraform.tfstate", "*.tf"]), async spawn(root) { - let bin = Bun.which("terraform-ls", { + let bin = which("terraform-ls", { PATH: process.env["PATH"] + path.delimiter + Global.Path.bin, }) @@ -1741,7 +1742,7 @@ export namespace LSPServer { extensions: [".tex", ".bib"], root: NearestRoot([".latexmkrc", "latexmkrc", ".texlabroot", "texlabroot"]), async spawn(root) { - let bin = Bun.which("texlab", { + let bin = which("texlab", { PATH: process.env["PATH"] + path.delimiter + Global.Path.bin, }) @@ -1831,7 +1832,7 @@ export namespace LSPServer { extensions: [".dockerfile", "Dockerfile"], root: async () => Instance.directory, async spawn(root) { - let binary = Bun.which("docker-langserver") + let binary = which("docker-langserver") const args: string[] = [] if (!binary) { const js = path.join(Global.Path.bin, "node_modules", "dockerfile-language-server-nodejs", "lib", "server.js") @@ -1870,7 +1871,7 @@ export namespace LSPServer { extensions: [".gleam"], root: NearestRoot(["gleam.toml"]), async spawn(root) { - const gleam = Bun.which("gleam") + const gleam = which("gleam") if (!gleam) { log.info("gleam not found, please install gleam first") return @@ -1888,9 +1889,9 @@ export namespace LSPServer { extensions: [".clj", ".cljs", ".cljc", ".edn"], root: NearestRoot(["deps.edn", "project.clj", "shadow-cljs.edn", "bb.edn", "build.boot"]), async spawn(root) { - let bin = Bun.which("clojure-lsp") + let bin = which("clojure-lsp") if (!bin && process.platform === "win32") { - bin = Bun.which("clojure-lsp.exe") + bin = which("clojure-lsp.exe") } if (!bin) { log.info("clojure-lsp not found, please install clojure-lsp first") @@ -1919,7 +1920,7 @@ export namespace LSPServer { return Instance.directory }, async spawn(root) { - const nixd = Bun.which("nixd") + const nixd = which("nixd") if (!nixd) { log.info("nixd not found, please install nixd first") return @@ -1940,7 +1941,7 @@ export namespace LSPServer { extensions: [".typ", ".typc"], root: NearestRoot(["typst.toml"]), async spawn(root) { - let bin = Bun.which("tinymist", { + let bin = which("tinymist", { PATH: process.env["PATH"] + path.delimiter + Global.Path.bin, }) @@ -2034,7 +2035,7 @@ export namespace LSPServer { extensions: [".hs", ".lhs"], root: NearestRoot(["stack.yaml", "cabal.project", "hie.yaml", "*.cabal"]), async spawn(root) { - const bin = Bun.which("haskell-language-server-wrapper") + const bin = which("haskell-language-server-wrapper") if (!bin) { log.info("haskell-language-server-wrapper not found, please install haskell-language-server") return @@ -2052,7 +2053,7 @@ export namespace LSPServer { extensions: [".jl"], root: NearestRoot(["Project.toml", "Manifest.toml", "*.jl"]), async spawn(root) { - const julia = Bun.which("julia") + const julia = which("julia") if (!julia) { log.info("julia not found, please install julia first (https://julialang.org/downloads/)") return diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index ec48c56626..dea1e41a87 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -14,6 +14,7 @@ import { GlobalBus } from "@/bus/global" import { existsSync } from "fs" import { git } from "../util/git" import { Glob } from "../util/glob" +import { which } from "../util/which" export namespace Project { const log = Log.create({ service: "project" }) @@ -97,7 +98,7 @@ export namespace Project { if (dotgit) { let sandbox = path.dirname(dotgit) - const gitBinary = Bun.which("git") + const gitBinary = which("git") // cached id calculation let id = await Filesystem.readText(path.join(dotgit, "opencode")) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 836c49996d..eef41c93ce 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -8,11 +8,8 @@ import { Log } from "../util/log" import { BunProc } from "../bun" import { Hash } from "../util/hash" import { Plugin } from "../plugin" -import { - ModelsDev, - Prompt, // kilocode_change -} from "./models" import { NamedError } from "@opencode-ai/util/error" +import { ModelsDev, Prompt } from "./models" // kilocode_change import { Auth } from "../auth" import { Env } from "../env" import { Instance } from "../project/instance" diff --git a/packages/opencode/src/shell/shell.ts b/packages/opencode/src/shell/shell.ts index 21879dd0bf..0eaff9d701 100644 --- a/packages/opencode/src/shell/shell.ts +++ b/packages/opencode/src/shell/shell.ts @@ -1,6 +1,7 @@ import { Flag } from "@/flag/flag" import { lazy } from "@/util/lazy" import { Filesystem } from "@/util/filesystem" +import { which } from "@/util/which" import path from "path" import { spawn, type ChildProcess } from "child_process" import { setTimeout as sleep } from "node:timers/promises" @@ -40,7 +41,7 @@ export namespace Shell { function fallback() { if (process.platform === "win32") { if (Flag.KILO_GIT_BASH_PATH) return Flag.KILO_GIT_BASH_PATH - const git = Bun.which("git") + const git = which("git") if (git) { // git.exe is typically at: C:\Program Files\Git\cmd\git.exe // bash.exe is at: C:\Program Files\Git\bin\bash.exe @@ -50,7 +51,7 @@ export namespace Shell { return process.env.COMSPEC || "cmd.exe" } if (process.platform === "darwin") return "/bin/zsh" - const bash = Bun.which("bash") + const bash = which("bash") if (bash) return bash return "/bin/sh" } diff --git a/packages/opencode/src/util/which.ts b/packages/opencode/src/util/which.ts new file mode 100644 index 0000000000..78e651e8e8 --- /dev/null +++ b/packages/opencode/src/util/which.ts @@ -0,0 +1,10 @@ +import whichPkg from "which" + +export function which(cmd: string, env?: NodeJS.ProcessEnv) { + const result = whichPkg.sync(cmd, { + nothrow: true, + path: env?.PATH, + pathExt: env?.PATHEXT, + }) + return typeof result === "string" ? result : null +} diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts index 2267322494..15efcea386 100644 --- a/packages/opencode/src/worktree/index.ts +++ b/packages/opencode/src/worktree/index.ts @@ -474,6 +474,11 @@ export namespace Worktree { throw new RemoveFailedError({ message: message || "Failed to remove git worktree directory" }) }) + const stop = async (target: string) => { + if (!(await exists(target))) return + await $`git fsmonitor--daemon stop`.quiet().nothrow().cwd(target) + } + const list = await $`git worktree list --porcelain`.quiet().nothrow().cwd(Instance.worktree) if (list.exitCode !== 0) { throw new RemoveFailedError({ message: errorText(list) || "Failed to read git worktrees" }) @@ -484,11 +489,13 @@ export namespace Worktree { if (!entry?.path) { const directoryExists = await exists(directory) if (directoryExists) { + await stop(directory) await clean(directory) } return true } + await stop(entry.path) const removed = await $`git worktree remove --force ${entry.path}`.quiet().nothrow().cwd(Instance.worktree) if (removed.exitCode !== 0) { const next = await $`git worktree list --porcelain`.quiet().nothrow().cwd(Instance.worktree) @@ -637,7 +644,7 @@ export namespace Worktree { throw new ResetFailedError({ message: errorText(subClean) || "Failed to clean submodules" }) } - const status = await $`git status --porcelain=v1`.quiet().nothrow().cwd(worktreePath) + const status = await $`git -c core.fsmonitor=false status --porcelain=v1`.quiet().nothrow().cwd(worktreePath) if (status.exitCode !== 0) { throw new ResetFailedError({ message: errorText(status) || "Failed to read git status" }) } diff --git a/packages/opencode/test/file/fsmonitor.test.ts b/packages/opencode/test/file/fsmonitor.test.ts new file mode 100644 index 0000000000..8cdde014db --- /dev/null +++ b/packages/opencode/test/file/fsmonitor.test.ts @@ -0,0 +1,62 @@ +import { $ } from "bun" +import { describe, expect, test } from "bun:test" +import fs from "fs/promises" +import path from "path" +import { File } from "../../src/file" +import { Instance } from "../../src/project/instance" +import { tmpdir } from "../fixture/fixture" + +const wintest = process.platform === "win32" ? test : test.skip + +describe("file fsmonitor", () => { + wintest("status does not start fsmonitor for readonly git checks", async () => { + await using tmp = await tmpdir({ git: true }) + const target = path.join(tmp.path, "tracked.txt") + + await fs.writeFile(target, "base\n") + await $`git add tracked.txt`.cwd(tmp.path).quiet() + await $`git commit -m init`.cwd(tmp.path).quiet() + await $`git config core.fsmonitor true`.cwd(tmp.path).quiet() + await $`git fsmonitor--daemon stop`.cwd(tmp.path).quiet().nothrow() + await fs.writeFile(target, "next\n") + await fs.writeFile(path.join(tmp.path, "new.txt"), "new\n") + + const before = await $`git fsmonitor--daemon status`.cwd(tmp.path).quiet().nothrow() + expect(before.exitCode).not.toBe(0) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await File.status() + }, + }) + + const after = await $`git fsmonitor--daemon status`.cwd(tmp.path).quiet().nothrow() + expect(after.exitCode).not.toBe(0) + }) + + wintest("read does not start fsmonitor for git diffs", async () => { + await using tmp = await tmpdir({ git: true }) + const target = path.join(tmp.path, "tracked.txt") + + await fs.writeFile(target, "base\n") + await $`git add tracked.txt`.cwd(tmp.path).quiet() + await $`git commit -m init`.cwd(tmp.path).quiet() + await $`git config core.fsmonitor true`.cwd(tmp.path).quiet() + await $`git fsmonitor--daemon stop`.cwd(tmp.path).quiet().nothrow() + await fs.writeFile(target, "next\n") + + const before = await $`git fsmonitor--daemon status`.cwd(tmp.path).quiet().nothrow() + expect(before.exitCode).not.toBe(0) + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await File.read("tracked.txt") + }, + }) + + const after = await $`git fsmonitor--daemon status`.cwd(tmp.path).quiet().nothrow() + expect(after.exitCode).not.toBe(0) + }) +}) diff --git a/packages/opencode/test/fixture/fixture.test.ts b/packages/opencode/test/fixture/fixture.test.ts new file mode 100644 index 0000000000..153276a283 --- /dev/null +++ b/packages/opencode/test/fixture/fixture.test.ts @@ -0,0 +1,26 @@ +import { $ } from "bun" +import { describe, expect, test } from "bun:test" +import fs from "fs/promises" +import { tmpdir } from "./fixture" + +describe("tmpdir", () => { + test("disables fsmonitor for git fixtures", async () => { + await using tmp = await tmpdir({ git: true }) + + const value = (await $`git config core.fsmonitor`.cwd(tmp.path).quiet().text()).trim() + expect(value).toBe("false") + }) + + test("removes directories on dispose", async () => { + const tmp = await tmpdir({ git: true }) + const dir = tmp.path + + await tmp[Symbol.asyncDispose]() + + const exists = await fs + .stat(dir) + .then(() => true) + .catch(() => false) + expect(exists).toBe(false) + }) +}) diff --git a/packages/opencode/test/fixture/fixture.ts b/packages/opencode/test/fixture/fixture.ts index b1cb30146e..a583ce7143 100644 --- a/packages/opencode/test/fixture/fixture.ts +++ b/packages/opencode/test/fixture/fixture.ts @@ -9,6 +9,27 @@ function sanitizePath(p: string): string { return p.replace(/\0/g, "") } +function exists(dir: string) { + return fs + .stat(dir) + .then(() => true) + .catch(() => false) +} + +function clean(dir: string) { + return fs.rm(dir, { + recursive: true, + force: true, + maxRetries: 5, + retryDelay: 100, + }) +} + +async function stop(dir: string) { + if (!(await exists(dir))) return + await $`git fsmonitor--daemon stop`.cwd(dir).quiet().nothrow() +} + type TmpDirOptions = { git?: boolean config?: Partial @@ -20,6 +41,7 @@ export async function tmpdir(options?: TmpDirOptions) { await fs.mkdir(dirpath, { recursive: true }) if (options?.git) { await $`git init`.cwd(dirpath).quiet() + await $`git config core.fsmonitor false`.cwd(dirpath).quiet() await $`git commit --allow-empty -m "root commit ${dirpath}"`.cwd(dirpath).quiet() } if (options?.config) { @@ -31,12 +53,16 @@ export async function tmpdir(options?: TmpDirOptions) { }), ) } - const extra = await options?.init?.(dirpath) const realpath = sanitizePath(await fs.realpath(dirpath)) + const extra = await options?.init?.(realpath) const result = { [Symbol.asyncDispose]: async () => { - await options?.dispose?.(dirpath) - // await fs.rm(dirpath, { recursive: true, force: true }) + try { + await options?.dispose?.(realpath) + } finally { + if (options?.git) await stop(realpath).catch(() => undefined) + await clean(realpath).catch(() => undefined) + } }, path: realpath, extra: extra as T, diff --git a/packages/opencode/test/project/worktree-remove.test.ts b/packages/opencode/test/project/worktree-remove.test.ts index e17a5392bc..a6b5bb7c34 100644 --- a/packages/opencode/test/project/worktree-remove.test.ts +++ b/packages/opencode/test/project/worktree-remove.test.ts @@ -7,6 +7,8 @@ import { Worktree } from "../../src/worktree" import { Filesystem } from "../../src/util/filesystem" import { tmpdir } from "../fixture/fixture" +const wintest = process.platform === "win32" ? test : test.skip + describe("Worktree.remove", () => { test("continues when git remove exits non-zero after detaching", async () => { await using tmp = await tmpdir({ git: true }) @@ -62,4 +64,33 @@ describe("Worktree.remove", () => { const ref = await $`git show-ref --verify --quiet refs/heads/${branch}`.cwd(root).quiet().nothrow() expect(ref.exitCode).not.toBe(0) }) + + wintest("stops fsmonitor before removing a worktree", async () => { + await using tmp = await tmpdir({ git: true }) + const root = tmp.path + const name = `remove-fsmonitor-${Date.now().toString(36)}` + const branch = `opencode/${name}` + const dir = path.join(root, "..", name) + + await $`git worktree add --no-checkout -b ${branch} ${dir}`.cwd(root).quiet() + await $`git reset --hard`.cwd(dir).quiet() + await $`git config core.fsmonitor true`.cwd(dir).quiet() + await $`git fsmonitor--daemon stop`.cwd(dir).quiet().nothrow() + await Bun.write(path.join(dir, "tracked.txt"), "next\n") + await $`git diff`.cwd(dir).quiet() + + const before = await $`git fsmonitor--daemon status`.cwd(dir).quiet().nothrow() + expect(before.exitCode).toBe(0) + + const ok = await Instance.provide({ + directory: root, + fn: () => Worktree.remove({ directory: dir }), + }) + + expect(ok).toBe(true) + expect(await Filesystem.exists(dir)).toBe(false) + + const ref = await $`git show-ref --verify --quiet refs/heads/${branch}`.cwd(root).quiet().nothrow() + expect(ref.exitCode).not.toBe(0) + }) }) diff --git a/packages/opencode/test/util/which.test.ts b/packages/opencode/test/util/which.test.ts new file mode 100644 index 0000000000..323173b181 --- /dev/null +++ b/packages/opencode/test/util/which.test.ts @@ -0,0 +1,82 @@ +import { describe, expect, test } from "bun:test" +import fs from "fs/promises" +import path from "path" +import { which } from "../../src/util/which" +import { tmpdir } from "../fixture/fixture" + +async function cmd(dir: string, name: string, exec = true) { + const ext = process.platform === "win32" ? ".cmd" : "" + const file = path.join(dir, name + ext) + const body = process.platform === "win32" ? "@echo off\r\n" : "#!/bin/sh\n" + await fs.writeFile(file, body) + if (process.platform !== "win32") { + await fs.chmod(file, exec ? 0o755 : 0o644) + } + return file +} + +function env(PATH: string): NodeJS.ProcessEnv { + return { + PATH, + PATHEXT: process.env["PATHEXT"], + } +} + +function same(a: string | null, b: string) { + if (process.platform === "win32") { + expect(a?.toLowerCase()).toBe(b.toLowerCase()) + return + } + + expect(a).toBe(b) +} + +describe("util.which", () => { + test("returns null when command is missing", () => { + expect(which("opencode-missing-command-for-test")).toBeNull() + }) + + test("finds a command from PATH override", async () => { + await using tmp = await tmpdir() + const bin = path.join(tmp.path, "bin") + await fs.mkdir(bin) + const file = await cmd(bin, "tool") + + same(which("tool", env(bin)), file) + }) + + test("uses first PATH match", async () => { + await using tmp = await tmpdir() + const a = path.join(tmp.path, "a") + const b = path.join(tmp.path, "b") + await fs.mkdir(a) + await fs.mkdir(b) + const first = await cmd(a, "dupe") + await cmd(b, "dupe") + + same(which("dupe", env([a, b].join(path.delimiter))), first) + }) + + test("returns null for non-executable file on unix", async () => { + if (process.platform === "win32") return + + await using tmp = await tmpdir() + const bin = path.join(tmp.path, "bin") + await fs.mkdir(bin) + await cmd(bin, "noexec", false) + + expect(which("noexec", env(bin))).toBeNull() + }) + + test("uses PATHEXT on windows", async () => { + if (process.platform !== "win32") return + + await using tmp = await tmpdir() + const bin = path.join(tmp.path, "bin") + await fs.mkdir(bin) + const file = path.join(bin, "pathext.CMD") + await fs.writeFile(file, "@echo off\r\n") + + expect(which("pathext", { PATH: bin, PATHEXT: ".CMD" })).toBe(file) + }) +}) diff --git a/packages/plugin/package.json b/packages/plugin/package.json index bf2b00b5da..eacca42005 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -4,11 +4,6 @@ "version": "7.0.47", "type": "module", "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/Kilo-Org/kilocode", - "directory": "packages/plugin" - }, "scripts": { "typecheck": "tsgo --noEmit", "build": "tsc" @@ -30,5 +25,10 @@ "typescript": "catalog:", "@typescript/native-preview": "catalog:" }, - "peerDependencies": {} + "peerDependencies": {}, + "repository": { + "type": "git", + "url": "https://github.com/Kilo-Org/kilocode", + "directory": "packages/plugin" + } } diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 710421f73f..83062a71d9 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -4,11 +4,6 @@ "version": "7.0.47", "type": "module", "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/Kilo-Org/kilocode", - "directory": "packages/sdk/js" - }, "scripts": { "typecheck": "tsgo --noEmit", "build": "bun ./script/build.ts" @@ -33,5 +28,10 @@ "@typescript/native-preview": "catalog:" }, "dependencies": {}, - "peerDependencies": {} + "peerDependencies": {}, + "repository": { + "type": "git", + "url": "https://github.com/Kilo-Org/kilocode", + "directory": "packages/sdk/js" + } } diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index 3d2263a974..dd3a7c2777 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -104,5 +104,7 @@ "typescript": "^5.8.3", "@vscode/test-cli": "^0.0.11", "@vscode/test-electron": "^2.5.2" - } + }, + "dependencies": {}, + "peerDependencies": {} }