refactor(cli): tighten tui-worktree, log exclude-write failures, fix event count

This commit is contained in:
Bruno Agatao
2026-08-05 11:56:33 +02:00
parent 90989d451a
commit 4fdaa4c41b
3 changed files with 40 additions and 49 deletions
@@ -5,10 +5,13 @@
// created in.
import path from "path"
import type { Effect } from "effect"
import * as Log from "@opencode-ai/core/util/log"
import { UI } from "@/cli/ui"
import { Filesystem } from "@/util/filesystem"
import { errorMessage } from "@/util/error"
const log = Log.create({ service: "kilocode.tui-worktree" })
// Matches packages/kilo-vscode/src/agent-manager/WorktreeManager.ts's placement
// and its ensureGitExclude(), keeping `.kilo/worktrees/` out of `git status`.
const KILO_WORKTREE_DIR = ".kilo/worktrees"
@@ -19,7 +22,9 @@ export async function ensureGitExclude(root: string) {
const current = (await Filesystem.readText(excludePath).catch(() => "")).replace(/\s+$/, "")
if (current.includes(`${KILO_WORKTREE_DIR}/`)) return
const prefix = current ? `${current}\n\n` : ""
await Filesystem.write(excludePath, `${prefix}# Kilo Code agent worktrees\n${KILO_WORKTREE_DIR}/\n`).catch(() => {})
await Filesystem.write(excludePath, `${prefix}# Kilo Code agent worktrees\n${KILO_WORKTREE_DIR}/\n`).catch((err) =>
log.error("failed to update .git/info/exclude", { excludePath, err }),
)
}
function samePath(a: string, b: string) {
@@ -140,13 +145,10 @@ async function resolveWorktree(name: string, root: string, timeoutMs = 10 * 60_0
// only with `-d` (not `-D`): it refuses unless the branch is fully merged,
// so we never silently discard unmerged work from an interrupted worktree
// or an unrelated branch that happens to share the name.
const branchRef = await run(
Git.Service.use((git) =>
git.run(["show-ref", "--verify", "--quiet", `refs/heads/${slug}`], { cwd: ctx.worktree }),
),
)
const runGit = (args: string[]) => run(Git.Service.use((git) => git.run(args, { cwd: ctx.worktree })))
const branchRef = await runGit(["show-ref", "--verify", "--quiet", `refs/heads/${slug}`])
if (branchRef.exitCode === 0) {
const deleted = await run(Git.Service.use((git) => git.run(["branch", "-d", slug], { cwd: ctx.worktree })))
const deleted = await runGit(["branch", "-d", slug])
if (deleted.exitCode !== 0) {
const message = deleted.stderr.toString("utf8").trim() || deleted.text().trim()
throw new Error(
@@ -9,7 +9,7 @@ describe("public event manifest", () => {
expect(EventManifest.Definitions).toBe(SchemaEventManifest.Definitions)
expect(EventManifest.Latest).toBe(SchemaEventManifest.Latest)
expect(EventManifest.Durable).toBe(SchemaEventManifest.Durable)
expect(EventManifest.Latest.size).toBe(89) // kilocode_change - include global.config.updated
expect(EventManifest.Latest.size).toBe(90) // kilocode_change - include global.config.updated and worktree.setup.ready
expect(EventManifest.Latest.get("session.next.step.ended")).toBe(SessionEvent.Step.Ended)
expect(EventManifest.Latest.get("todo.updated")).toBe(Todo.Event.Updated)
expect(EventManifest.Latest.has("ide.installed")).toBe(false)
@@ -1,7 +1,6 @@
import { describe, expect, test } from "bun:test"
import path from "path"
import { mkdtemp, rm } from "fs/promises"
import { tmpdir as osTmpdir } from "os"
import { tmpdir } from "../../../fixture/fixture"
import { ensureGitExclude, slugify } from "@/kilocode/cli/cmd/tui-worktree"
import { Filesystem } from "@/util/filesystem"
@@ -22,46 +21,36 @@ describe("slugify", () => {
})
describe("ensureGitExclude", () => {
async function withRepo(fn: (root: string) => Promise<void>) {
const root = await mkdtemp(path.join(osTmpdir(), "tui-worktree-exclude-"))
try {
await fn(root)
} finally {
await rm(root, { recursive: true, force: true })
}
}
test("appends the exclude entry when the file exists but is empty", async () => {
await using tmp = await tmpdir({ git: true })
const excludePath = path.join(tmp.path, ".git", "info", "exclude")
await Filesystem.write(excludePath, "")
await ensureGitExclude(tmp.path)
expect(await Filesystem.readText(excludePath)).toContain(".kilo/worktrees/")
})
test("appends the exclude entry when the file exists but is empty", () =>
withRepo(async (root) => {
const excludePath = path.join(root, ".git", "info", "exclude")
await Filesystem.write(excludePath, "")
await ensureGitExclude(root)
const content = await Filesystem.readText(excludePath)
expect(content).toContain(".kilo/worktrees/")
}))
test("preserves existing content and adds a newline before the new entry", async () => {
await using tmp = await tmpdir({ git: true })
const excludePath = path.join(tmp.path, ".git", "info", "exclude")
await Filesystem.write(excludePath, "*.log")
await ensureGitExclude(tmp.path)
const content = await Filesystem.readText(excludePath)
expect(content).toContain("*.log")
expect(content).toContain(".kilo/worktrees/")
})
test("preserves existing content and adds a newline before the new entry", () =>
withRepo(async (root) => {
const excludePath = path.join(root, ".git", "info", "exclude")
await Filesystem.write(excludePath, "*.log")
await ensureGitExclude(root)
const content = await Filesystem.readText(excludePath)
expect(content).toContain("*.log")
expect(content).toContain(".kilo/worktrees/")
}))
test("is idempotent when the entry already exists", async () => {
await using tmp = await tmpdir({ git: true })
const excludePath = path.join(tmp.path, ".git", "info", "exclude")
await Filesystem.write(excludePath, "")
await ensureGitExclude(tmp.path)
await ensureGitExclude(tmp.path)
const content = await Filesystem.readText(excludePath)
expect(content.match(/\.kilo\/worktrees\//g)?.length).toBe(1)
})
test("is idempotent when the entry already exists", () =>
withRepo(async (root) => {
const excludePath = path.join(root, ".git", "info", "exclude")
await Filesystem.write(excludePath, "")
await ensureGitExclude(root)
await ensureGitExclude(root)
const content = await Filesystem.readText(excludePath)
expect(content.match(/\.kilo\/worktrees\//g)?.length).toBe(1)
}))
test("does not throw when .git/info is missing", () =>
withRepo(async (root) => {
await ensureGitExclude(root)
}))
test("does not throw when .git/info is missing", async () => {
await using tmp = await tmpdir()
await ensureGitExclude(tmp.path)
})
})