From 25df75d1770361f5d3c28629e3f499fc32ab0398 Mon Sep 17 00:00:00 2001 From: kirillk Date: Mon, 11 May 2026 16:58:39 -0400 Subject: [PATCH 1/8] fix(vscode): render plan exit as file link --- .../kilo-vscode/tests/unit/plan-exit.test.ts | 101 ++++++++++ .../src/components/chat/AssistantMessage.tsx | 184 ++++++++++++++++-- .../kilo-vscode/webview-ui/src/i18n/en.ts | 5 + .../webview-ui/src/styles/chat.css | 1 + .../webview-ui/src/styles/plan-exit.css | 39 ++++ .../webview-ui/src/utils/plan-path.ts | 43 ++++ 6 files changed, 357 insertions(+), 16 deletions(-) create mode 100644 packages/kilo-vscode/tests/unit/plan-exit.test.ts create mode 100644 packages/kilo-vscode/webview-ui/src/styles/plan-exit.css create mode 100644 packages/kilo-vscode/webview-ui/src/utils/plan-path.ts diff --git a/packages/kilo-vscode/tests/unit/plan-exit.test.ts b/packages/kilo-vscode/tests/unit/plan-exit.test.ts new file mode 100644 index 00000000000..b67337cb7fc --- /dev/null +++ b/packages/kilo-vscode/tests/unit/plan-exit.test.ts @@ -0,0 +1,101 @@ +/** + * Tests for plan_exit webview helpers: + * - planDisplayPath: relative/absolute path display logic + * - plan_exit renderer uses openFile, not openDiff + */ + +import { describe, expect, it } from "bun:test" +import { planDisplayPath } from "../../webview-ui/src/utils/plan-path" +import fs from "node:fs" +import path from "node:path" + +describe("planDisplayPath", () => { + it("returns a relative path unchanged", () => { + expect(planDisplayPath(".kilo/plans/my-plan.md", "/repo")).toBe(".kilo/plans/my-plan.md") + }) + + it("returns absolute path inside repo as repo-relative", () => { + expect(planDisplayPath("/repo/.kilo/plans/my-plan.md", "/repo")).toBe(".kilo/plans/my-plan.md") + }) + + it("returns absolute path inside repo with trailing slash on root as repo-relative", () => { + expect(planDisplayPath("/repo/.kilo/plans/my-plan.md", "/repo/")).toBe(".kilo/plans/my-plan.md") + }) + + it("returns absolute path outside repo unchanged", () => { + expect(planDisplayPath("/other/path/plan.md", "/repo")).toBe("/other/path/plan.md") + }) + + it("handles Windows absolute paths inside root", () => { + expect(planDisplayPath("C:\\repo\\.kilo\\plans\\plan.md", "C:\\repo")).toBe(".kilo\\plans\\plan.md") + }) + + it("handles Windows absolute paths outside root", () => { + expect(planDisplayPath("D:\\other\\plan.md", "C:\\repo")).toBe("D:\\other\\plan.md") + }) + + it("returns empty string unchanged", () => { + expect(planDisplayPath("", "/repo")).toBe("") + }) + + it("path equal to root returns original", () => { + // Edge: plan path IS the root directory itself — fall back to original + expect(planDisplayPath("/repo", "/repo")).toBe("/repo") + }) +}) + +describe("plan_exit renderer uses openFile not openDiff (source)", () => { + const ROOT = path.resolve(import.meta.dir, "../..") + const FILE = path.join(ROOT, "webview-ui/src/components/chat/AssistantMessage.tsx") + const src = fs.readFileSync(FILE, "utf-8") + + it("PlanExitCard calls data.openFile", () => { + expect(src).toContain("data.openFile") + }) + + it("infers status from write/edit tools in the same assistant turn", () => { + expect(src).toContain("function inferPlanStatus") + expect(src).toContain('part.tool === "edit"') + expect(src).toContain('part.tool === "read"') + expect(src).toContain('part.tool === "write"') + expect(src).toContain('part.tool === "apply_patch"') + expect(src).toContain("toolDeletions(part) > 0") + expect(src).toContain("patchUpdatedPlan(plan, part)") + expect(src).toContain("if (read && write) return \"updated\"") + }) + + it("matches apply_patch metadata files against the plan path", () => { + expect(src).toContain("function patchUpdatedPlan") + expect(src).toContain("meta.files") + expect(src).toContain('file.type === "update"') + expect(src).toContain("file.deletions > 0") + }) + + it("infers status from all loaded message parts", () => { + expect(src).toContain("Object.values(data.store.part ?? {}).flat()") + expect(src).toContain("[...props.parts, ...all()]") + }) + + it("does not depend on opencode-provided plan status metadata", () => { + expect(src).toContain('meta.status === "updated" || meta.status === "new"') + expect(src).toContain("inferPlanStatus(plan, parts, tp)") + }) + + it("PlanExitCard does not call openDiffVirtual", () => { + // Extract just the PlanExitCard function body to scope the assertion + const start = src.indexOf("function PlanExitCard") + const end = src.indexOf("\nfunction ", start + 1) + const block = end === -1 ? src.slice(start) : src.slice(start, end) + expect(block).not.toContain("openDiffVirtual") + expect(block).not.toContain("openDiff") + }) + + it("plan_exit tool is handled before generic Part renderer", () => { + const planExitIdx = src.indexOf("planExit()") + // }).metadata ?? {} + const plan = typeof meta.plan === "string" ? meta.plan : undefined + if (!plan) return undefined + const status = meta.status === "updated" || meta.status === "new" ? meta.status : inferPlanStatus(plan, parts, tp) + return { plan, status } +} + +function inferPlanStatus(plan: string, parts: SDKPart[], exit: ToolPart): PlanExitStatus { + const idx = parts.findIndex((part) => part.type === "tool" && (part as unknown as ToolPart).id === exit.id) + const prior = idx === -1 ? parts : parts.slice(0, idx) + const matches = prior + .filter((part): part is SDKPart & { type: "tool" } => part.type === "tool") + .map((part) => part as unknown as ToolPart) + .filter((part) => part.state?.status === "completed") + .filter((part) => toolTouchesPlan(plan, part)) + + const read = matches.some((part) => part.tool === "read") + const write = matches.some((part) => part.tool === "write") + + if (matches.some((part) => part.tool === "edit")) return "updated" + if (matches.some((part) => part.tool === "apply_patch" && patchUpdatedPlan(plan, part))) return "updated" + if (matches.some((part) => part.tool === "write" && toolDeletions(part) > 0)) return "updated" + if (read && write) return "updated" + if (write) return "new" + return "ready" +} + +function toolFilePath(part: ToolPart): string | undefined { + const state = part.state as { input?: Record; metadata?: Record } + const input = state.input ?? {} + const meta = state.metadata ?? {} + if (typeof input.filePath === "string") return input.filePath + if (typeof input.path === "string") return input.path + const diff = meta.filediff as { file?: unknown } | undefined + if (typeof diff?.file === "string") return diff.file + return undefined +} + +function toolTouchesPlan(plan: string, part: ToolPart): boolean { + if (samePlanPath(plan, toolFilePath(part))) return true + const meta = (part.state as { metadata?: Record }).metadata ?? {} + const files = meta.files + if (!Array.isArray(files)) return false + return files.some((item) => samePlanPath(plan, planPath(item))) +} + +function planPath(item: unknown): string | undefined { + if (!item || typeof item !== "object") return undefined + const obj = item as { filePath?: unknown; relativePath?: unknown } + if (typeof obj.relativePath === "string") return obj.relativePath + if (typeof obj.filePath === "string") return obj.filePath + return undefined +} + +function patchUpdatedPlan(plan: string, part: ToolPart): boolean { + const meta = (part.state as { metadata?: Record }).metadata ?? {} + const files = meta.files + if (!Array.isArray(files)) return false + return files.some((item) => { + if (!samePlanPath(plan, planPath(item))) return false + const file = item as { type?: unknown; deletions?: unknown } + if (file.type === "update" || file.type === "delete" || file.type === "move") return true + return typeof file.deletions === "number" && file.deletions > 0 + }) +} + +function toolDeletions(part: ToolPart): number { + const meta = (part.state as { metadata?: Record }).metadata ?? {} + const diff = meta.filediff as { deletions?: unknown } | undefined + return typeof diff?.deletions === "number" ? diff.deletions : 0 +} + +function samePlanPath(plan: string, file: string | undefined): boolean { + if (!file) return false + const a = normalizePlanPath(plan) + const b = normalizePlanPath(file) + if (a === b) return true + return a.endsWith("/" + b) || b.endsWith("/" + a) +} + +function normalizePlanPath(value: string): string { + return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "") +} + +function PlanExitCard(props: { part: ToolPart; parts: SDKPart[] }) { + const language = useLanguage() + const server = useServer() + const data = useData() + const all = createMemo(() => Object.values(data.store.part ?? {}).flat() as SDKPart[]) + const info = createMemo(() => + planExitInfo(props.part as unknown as SDKPart, [...props.parts, ...all()]), + ) + const display = createMemo(() => { + const i = info() + if (!i) return "" + return planDisplayPath(i.plan, server.workspaceDirectory()) + }) + const label = createMemo(() => { + const i = info() + if (!i) return "" + if (i.status === "updated") return language.t("plan.exit.readyUpdated") + if (i.status === "new") return language.t("plan.exit.readyNew") + return language.t("plan.exit.ready") + }) + const open = (e: MouseEvent) => { + e.preventDefault() + const i = info() + if (!i || !data.openFile) return + data.openFile(i.plan) + } + return ( + +
+ {label()} + {" "} + + {display()} + +
+
+ ) +} + function isRenderable(part: SDKPart): boolean { if (part.type === "tool") { const tool = (part as SDKPart & { tool: string }).tool @@ -154,10 +292,17 @@ export const AssistantMessage: Component = (props) => { if (tool.state?.status === "error") return return part }) + const planExit = createMemo(() => { + if (part.type !== "tool") return + const tp = part as unknown as ToolPart + if (tp.tool !== "plan_exit") return + if (tp.state?.status !== "completed") return + return tp + }) return (
= (props) => { when={activeSuggestion()} fallback={ } - /> + > + + } > - + {(tool) => } } > - {(tool) => } + {(tp) => } } > diff --git a/packages/kilo-vscode/webview-ui/src/i18n/en.ts b/packages/kilo-vscode/webview-ui/src/i18n/en.ts index 412d8a6ed65..39ef0754cb3 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/en.ts @@ -1584,4 +1584,9 @@ export const dict = { "diffViewer.baseBranch.empty": "No matching branches", "diffViewer.baseBranch.loading": "Loading branches…", "diffViewer.baseBranch.none": "—", + + // plan_exit tool part display + "plan.exit.ready": "Plan is ready:", + "plan.exit.readyNew": "New plan is ready:", + "plan.exit.readyUpdated": "Existing plan was updated:", } diff --git a/packages/kilo-vscode/webview-ui/src/styles/chat.css b/packages/kilo-vscode/webview-ui/src/styles/chat.css index a40375411cc..9eda4835ed9 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/chat.css +++ b/packages/kilo-vscode/webview-ui/src/styles/chat.css @@ -19,6 +19,7 @@ @import "./notifications.css"; @import "./tool-overrides.css"; @import "./question-dock.css"; +@import "./plan-exit.css"; @import "./suggest-bar.css"; @import "./settings.css"; @import "./high-contrast.css"; diff --git a/packages/kilo-vscode/webview-ui/src/styles/plan-exit.css b/packages/kilo-vscode/webview-ui/src/styles/plan-exit.css new file mode 100644 index 00000000000..c9378481d5f --- /dev/null +++ b/packages/kilo-vscode/webview-ui/src/styles/plan-exit.css @@ -0,0 +1,39 @@ +/* ============================================ + Plan Exit Card + ============================================ */ + +[data-component="tool-part-wrapper"]:has([data-component="plan-exit-card"]) { + border: none !important; + border-radius: 0; + overflow: visible; +} + +[data-component="plan-exit-card"] { + display: flex; + align-items: baseline; + flex-wrap: wrap; + gap: 4px; + padding: 6px 0; + font-family: var(--font-family-sans); + font-size: var(--kilo-font-size-13); + line-height: var(--line-height-large); + + [data-slot="plan-exit-label"] { + color: var(--text-base); + font-weight: var(--font-weight-medium); + } + + [data-slot="plan-exit-link"] { + cursor: pointer; + color: var(--text-interactive-base); + text-decoration: none; + text-underline-offset: 2px; + font-family: var(--font-family-mono); + font-size: var(--kilo-font-size-12); + word-break: break-all; + + &:hover { + text-decoration: underline; + } + } +} diff --git a/packages/kilo-vscode/webview-ui/src/utils/plan-path.ts b/packages/kilo-vscode/webview-ui/src/utils/plan-path.ts new file mode 100644 index 00000000000..99569f52512 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/src/utils/plan-path.ts @@ -0,0 +1,43 @@ +/** + * Compute the display path for a plan file link. + * + * - Relative paths are returned as-is. + * - Absolute paths inside `root` are returned as a repo-relative path. + * - Absolute paths outside `root` are returned as-is (absolute). + * + * `root` is the workspace/worktree directory string from the server context. + * Both Unix and Windows separators are normalised before comparison. + */ +export function planDisplayPath(plan: string, root: string): string { + if (!plan) return plan + + // Already relative — nothing to do. + if (!isAbsolutePlan(plan)) return plan + + const normalRoot = normaliseDir(root) + const normalPlan = normalisePath(plan) + + if (!normalRoot || !normalPlan.startsWith(normalRoot)) return plan + + // Strip the root prefix (and a trailing separator if present). + const rel = plan.slice(normalRoot.length).replace(/^[\\/]/, "") + return rel || plan +} + +function normalisePath(p: string): string { + return p.replace(/\\/g, "/") +} + +function normaliseDir(dir: string): string { + if (!dir) return "" + const n = dir.replace(/\\/g, "/") + return n.endsWith("/") ? n : n + "/" +} + +function isAbsolutePlan(p: string): boolean { + // Unix absolute + if (p.startsWith("/")) return true + // Windows absolute: C:\ or C:/ + if (/^[A-Za-z]:[\\/]/.test(p)) return true + return false +} From e5f7f6d8e41140f4f4c521c20c2b9fd0b9b3824d Mon Sep 17 00:00:00 2001 From: kirillk Date: Wed, 13 May 2026 15:31:27 -0400 Subject: [PATCH 2/8] fix(vscode): simplify plan ready label --- .../kilo-vscode/tests/unit/plan-exit.test.ts | 41 ++++---- .../src/components/chat/AssistantMessage.tsx | 98 ++----------------- 2 files changed, 27 insertions(+), 112 deletions(-) diff --git a/packages/kilo-vscode/tests/unit/plan-exit.test.ts b/packages/kilo-vscode/tests/unit/plan-exit.test.ts index b67337cb7fc..1c202b447c7 100644 --- a/packages/kilo-vscode/tests/unit/plan-exit.test.ts +++ b/packages/kilo-vscode/tests/unit/plan-exit.test.ts @@ -47,38 +47,37 @@ describe("planDisplayPath", () => { describe("plan_exit renderer uses openFile not openDiff (source)", () => { const ROOT = path.resolve(import.meta.dir, "../..") const FILE = path.join(ROOT, "webview-ui/src/components/chat/AssistantMessage.tsx") + const TURN_FILE = path.join(ROOT, "webview-ui/src/components/chat/VscodeSessionTurn.tsx") const src = fs.readFileSync(FILE, "utf-8") + const turnSrc = fs.readFileSync(TURN_FILE, "utf-8") it("PlanExitCard calls data.openFile", () => { expect(src).toContain("data.openFile") }) - it("infers status from write/edit tools in the same assistant turn", () => { - expect(src).toContain("function inferPlanStatus") - expect(src).toContain('part.tool === "edit"') - expect(src).toContain('part.tool === "read"') - expect(src).toContain('part.tool === "write"') - expect(src).toContain('part.tool === "apply_patch"') - expect(src).toContain("toolDeletions(part) > 0") - expect(src).toContain("patchUpdatedPlan(plan, part)") - expect(src).toContain("if (read && write) return \"updated\"") + it("always uses the generic ready label", () => { + expect(src).toContain('language.t("plan.exit.ready")') + expect(src).not.toContain('language.t("plan.exit.readyUpdated")') + expect(src).not.toContain('language.t("plan.exit.readyNew")') }) - it("matches apply_patch metadata files against the plan path", () => { - expect(src).toContain("function patchUpdatedPlan") - expect(src).toContain("meta.files") - expect(src).toContain('file.type === "update"') - expect(src).toContain("file.deletions > 0") - }) - - it("infers status from all loaded message parts", () => { - expect(src).toContain("Object.values(data.store.part ?? {}).flat()") - expect(src).toContain("[...props.parts, ...all()]") + it("does not infer status from tool history", () => { + expect(src).not.toContain("function inferPlanStatus") + expect(src).not.toContain("function patchUpdatedPlan") + expect(src).not.toContain("function toolTouchesPlan") + expect(src).not.toContain("toolDeletions") + expect(src).not.toContain("readyUpdated") + expect(src).not.toContain("readyNew") + expect(src).not.toContain("Object.values(data.store.part ?? {}).flat()") + expect(src).not.toContain("[...props.parts, ...all()]") + expect(src).not.toContain("turnParts") + expect(turnSrc).not.toContain("assistantMessages().flatMap") + expect(turnSrc).not.toContain("turnParts={assistantParts()}") }) it("does not depend on opencode-provided plan status metadata", () => { - expect(src).toContain('meta.status === "updated" || meta.status === "new"') - expect(src).toContain("inferPlanStatus(plan, parts, tp)") + expect(src).not.toContain('meta.status === "updated" || meta.status === "new"') + expect(src).not.toContain("meta.status") }) it("PlanExitCard does not call openDiffVirtual", () => { diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx index 6c75bc1a0bb..6cd78cecdc6 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx @@ -33,10 +33,8 @@ import { SuggestBar } from "./SuggestBar" // so the user can see what the AI set up. export const UPSTREAM_SUPPRESSED_TOOLS = new Set(["todowrite", "todoread"]) -type PlanExitStatus = "new" | "updated" | "ready" - -/** Extract plan path and status from a completed plan_exit tool part. */ -function planExitInfo(part: SDKPart, parts: SDKPart[]): { plan: string; status: PlanExitStatus } | undefined { +/** Extract plan path from a completed plan_exit tool part. */ +function planExitInfo(part: SDKPart): { plan: string } | undefined { if (part.type !== "tool") return undefined const tp = part as unknown as ToolPart if (tp.tool !== "plan_exit") return undefined @@ -44,94 +42,15 @@ function planExitInfo(part: SDKPart, parts: SDKPart[]): { plan: string; status: const meta = (tp.state as { metadata?: Record }).metadata ?? {} const plan = typeof meta.plan === "string" ? meta.plan : undefined if (!plan) return undefined - const status = meta.status === "updated" || meta.status === "new" ? meta.status : inferPlanStatus(plan, parts, tp) - return { plan, status } + return { plan } } -function inferPlanStatus(plan: string, parts: SDKPart[], exit: ToolPart): PlanExitStatus { - const idx = parts.findIndex((part) => part.type === "tool" && (part as unknown as ToolPart).id === exit.id) - const prior = idx === -1 ? parts : parts.slice(0, idx) - const matches = prior - .filter((part): part is SDKPart & { type: "tool" } => part.type === "tool") - .map((part) => part as unknown as ToolPart) - .filter((part) => part.state?.status === "completed") - .filter((part) => toolTouchesPlan(plan, part)) - - const read = matches.some((part) => part.tool === "read") - const write = matches.some((part) => part.tool === "write") - - if (matches.some((part) => part.tool === "edit")) return "updated" - if (matches.some((part) => part.tool === "apply_patch" && patchUpdatedPlan(plan, part))) return "updated" - if (matches.some((part) => part.tool === "write" && toolDeletions(part) > 0)) return "updated" - if (read && write) return "updated" - if (write) return "new" - return "ready" -} - -function toolFilePath(part: ToolPart): string | undefined { - const state = part.state as { input?: Record; metadata?: Record } - const input = state.input ?? {} - const meta = state.metadata ?? {} - if (typeof input.filePath === "string") return input.filePath - if (typeof input.path === "string") return input.path - const diff = meta.filediff as { file?: unknown } | undefined - if (typeof diff?.file === "string") return diff.file - return undefined -} - -function toolTouchesPlan(plan: string, part: ToolPart): boolean { - if (samePlanPath(plan, toolFilePath(part))) return true - const meta = (part.state as { metadata?: Record }).metadata ?? {} - const files = meta.files - if (!Array.isArray(files)) return false - return files.some((item) => samePlanPath(plan, planPath(item))) -} - -function planPath(item: unknown): string | undefined { - if (!item || typeof item !== "object") return undefined - const obj = item as { filePath?: unknown; relativePath?: unknown } - if (typeof obj.relativePath === "string") return obj.relativePath - if (typeof obj.filePath === "string") return obj.filePath - return undefined -} - -function patchUpdatedPlan(plan: string, part: ToolPart): boolean { - const meta = (part.state as { metadata?: Record }).metadata ?? {} - const files = meta.files - if (!Array.isArray(files)) return false - return files.some((item) => { - if (!samePlanPath(plan, planPath(item))) return false - const file = item as { type?: unknown; deletions?: unknown } - if (file.type === "update" || file.type === "delete" || file.type === "move") return true - return typeof file.deletions === "number" && file.deletions > 0 - }) -} - -function toolDeletions(part: ToolPart): number { - const meta = (part.state as { metadata?: Record }).metadata ?? {} - const diff = meta.filediff as { deletions?: unknown } | undefined - return typeof diff?.deletions === "number" ? diff.deletions : 0 -} - -function samePlanPath(plan: string, file: string | undefined): boolean { - if (!file) return false - const a = normalizePlanPath(plan) - const b = normalizePlanPath(file) - if (a === b) return true - return a.endsWith("/" + b) || b.endsWith("/" + a) -} - -function normalizePlanPath(value: string): string { - return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "") -} - -function PlanExitCard(props: { part: ToolPart; parts: SDKPart[] }) { +function PlanExitCard(props: { part: ToolPart }) { const language = useLanguage() const server = useServer() const data = useData() - const all = createMemo(() => Object.values(data.store.part ?? {}).flat() as SDKPart[]) const info = createMemo(() => - planExitInfo(props.part as unknown as SDKPart, [...props.parts, ...all()]), + planExitInfo(props.part as unknown as SDKPart), ) const display = createMemo(() => { const i = info() @@ -139,10 +58,7 @@ function PlanExitCard(props: { part: ToolPart; parts: SDKPart[] }) { return planDisplayPath(i.plan, server.workspaceDirectory()) }) const label = createMemo(() => { - const i = info() - if (!i) return "" - if (i.status === "updated") return language.t("plan.exit.readyUpdated") - if (i.status === "new") return language.t("plan.exit.readyNew") + if (!info()) return "" return language.t("plan.exit.ready") }) const open = (e: MouseEvent) => { @@ -342,7 +258,7 @@ export const AssistantMessage: Component = (props) => { } > - {(tp) => } + {(tp) => } } > From efc411eeb820013eb75da1a5904eee0dab00bec2 Mon Sep 17 00:00:00 2001 From: kirillk Date: Wed, 13 May 2026 15:53:19 -0400 Subject: [PATCH 3/8] fix(i18n): add plan ready translations --- packages/kilo-vscode/webview-ui/src/i18n/ar.ts | 2 ++ packages/kilo-vscode/webview-ui/src/i18n/br.ts | 2 ++ packages/kilo-vscode/webview-ui/src/i18n/bs.ts | 2 ++ packages/kilo-vscode/webview-ui/src/i18n/da.ts | 2 ++ packages/kilo-vscode/webview-ui/src/i18n/de.ts | 2 ++ packages/kilo-vscode/webview-ui/src/i18n/en.ts | 2 -- packages/kilo-vscode/webview-ui/src/i18n/es.ts | 2 ++ packages/kilo-vscode/webview-ui/src/i18n/fr.ts | 2 ++ packages/kilo-vscode/webview-ui/src/i18n/ja.ts | 2 ++ packages/kilo-vscode/webview-ui/src/i18n/ko.ts | 2 ++ packages/kilo-vscode/webview-ui/src/i18n/nl.ts | 2 ++ packages/kilo-vscode/webview-ui/src/i18n/no.ts | 2 ++ packages/kilo-vscode/webview-ui/src/i18n/pl.ts | 2 ++ packages/kilo-vscode/webview-ui/src/i18n/ru.ts | 2 ++ packages/kilo-vscode/webview-ui/src/i18n/th.ts | 2 ++ packages/kilo-vscode/webview-ui/src/i18n/tr.ts | 2 ++ packages/kilo-vscode/webview-ui/src/i18n/uk.ts | 2 ++ packages/kilo-vscode/webview-ui/src/i18n/zh.ts | 2 ++ packages/kilo-vscode/webview-ui/src/i18n/zht.ts | 2 ++ 19 files changed, 36 insertions(+), 2 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts index cb292f16288..c3d6ca84c7e 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts @@ -1560,4 +1560,6 @@ export const dict = { "diffViewer.baseBranch.empty": "لا توجد فروع مطابقة", "diffViewer.baseBranch.loading": "جارٍ تحميل الفروع…", "diffViewer.baseBranch.none": "—", + // plan_exit tool part display + "plan.exit.ready": "الخطة جاهزة:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/br.ts b/packages/kilo-vscode/webview-ui/src/i18n/br.ts index c7dd24dc01c..28e26bc40b4 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/br.ts @@ -1602,4 +1602,6 @@ export const dict = { "diffViewer.baseBranch.empty": "Nenhum branch correspondente", "diffViewer.baseBranch.loading": "Carregando branches…", "diffViewer.baseBranch.none": "—", + // plan_exit tool part display + "plan.exit.ready": "Plano pronto:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts index 461584d1240..e918daeb665 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts @@ -1598,4 +1598,6 @@ export const dict = { "diffViewer.baseBranch.empty": "No matching branches", "diffViewer.baseBranch.loading": "Loading branches…", "diffViewer.baseBranch.none": "—", + // plan_exit tool part display + "plan.exit.ready": "Plan je spreman:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/da.ts b/packages/kilo-vscode/webview-ui/src/i18n/da.ts index 77996e22001..318e353d109 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/da.ts @@ -1588,4 +1588,6 @@ export const dict = { "diffViewer.baseBranch.empty": "No matching branches", "diffViewer.baseBranch.loading": "Loading branches…", "diffViewer.baseBranch.none": "—", + // plan_exit tool part display + "plan.exit.ready": "Planen er klar:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/de.ts b/packages/kilo-vscode/webview-ui/src/i18n/de.ts index 231bab9d25a..f7b45732b50 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/de.ts @@ -1618,4 +1618,6 @@ export const dict = { "diffViewer.baseBranch.empty": "Keine passenden Branches", "diffViewer.baseBranch.loading": "Branches werden geladen…", "diffViewer.baseBranch.none": "—", + // plan_exit tool part display + "plan.exit.ready": "Plan ist bereit:", } satisfies Partial> diff --git a/packages/kilo-vscode/webview-ui/src/i18n/en.ts b/packages/kilo-vscode/webview-ui/src/i18n/en.ts index 9c0754c551c..b2791df00e6 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/en.ts @@ -1592,6 +1592,4 @@ export const dict = { // plan_exit tool part display "plan.exit.ready": "Plan is ready:", - "plan.exit.readyNew": "New plan is ready:", - "plan.exit.readyUpdated": "Existing plan was updated:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/es.ts b/packages/kilo-vscode/webview-ui/src/i18n/es.ts index 626627dadfa..114b6a01f00 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/es.ts @@ -1608,4 +1608,6 @@ export const dict = { "diffViewer.baseBranch.empty": "Sin ramas que coincidan", "diffViewer.baseBranch.loading": "Cargando ramas…", "diffViewer.baseBranch.none": "—", + // plan_exit tool part display + "plan.exit.ready": "El plan está listo:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts index 159e32340c2..4d62e8f174a 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts @@ -1626,4 +1626,6 @@ export const dict = { "diffViewer.baseBranch.empty": "Aucune branche correspondante", "diffViewer.baseBranch.loading": "Chargement des branches…", "diffViewer.baseBranch.none": "—", + // plan_exit tool part display + "plan.exit.ready": "Le plan est prêt :", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts index a6a13784a65..339fdd30dde 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts @@ -1582,4 +1582,6 @@ export const dict = { "diffViewer.baseBranch.empty": "一致するブランチがありません", "diffViewer.baseBranch.loading": "ブランチを読み込み中…", "diffViewer.baseBranch.none": "—", + // plan_exit tool part display + "plan.exit.ready": "プランの準備ができました:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts index 2367e50cc87..4bed0abd4a5 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts @@ -1567,4 +1567,6 @@ export const dict = { "diffViewer.baseBranch.empty": "일치하는 브랜치 없음", "diffViewer.baseBranch.loading": "브랜치 로딩 중…", "diffViewer.baseBranch.none": "—", + // plan_exit tool part display + "plan.exit.ready": "계획이 준비되었습니다:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts index 6c93dc790a1..ab395da73de 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts @@ -1620,4 +1620,6 @@ export const dict = { "diffViewer.baseBranch.empty": "No matching branches", "diffViewer.baseBranch.loading": "Loading branches…", "diffViewer.baseBranch.none": "—", + // plan_exit tool part display + "plan.exit.ready": "Plan is klaar:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/no.ts b/packages/kilo-vscode/webview-ui/src/i18n/no.ts index a7b08921e48..6e96ba5fb10 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/no.ts @@ -1582,4 +1582,6 @@ export const dict = { "diffViewer.baseBranch.empty": "No matching branches", "diffViewer.baseBranch.loading": "Loading branches…", "diffViewer.baseBranch.none": "—", + // plan_exit tool part display + "plan.exit.ready": "Planen er klar:", } satisfies Partial> diff --git a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts index 84550227de4..76bf5da3ad4 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts @@ -1592,4 +1592,6 @@ export const dict = { "diffViewer.baseBranch.empty": "No matching branches", "diffViewer.baseBranch.loading": "Loading branches…", "diffViewer.baseBranch.none": "—", + // plan_exit tool part display + "plan.exit.ready": "Plan jest gotowy:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts index baf9174e0d4..ec33d2fcd1c 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts @@ -1592,4 +1592,6 @@ export const dict = { "diffViewer.baseBranch.empty": "Нет подходящих веток", "diffViewer.baseBranch.loading": "Загрузка веток…", "diffViewer.baseBranch.none": "—", + // plan_exit tool part display + "plan.exit.ready": "План готов:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/th.ts b/packages/kilo-vscode/webview-ui/src/i18n/th.ts index 5438042d7ee..c298e56030f 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/th.ts @@ -1565,4 +1565,6 @@ export const dict = { "diffViewer.baseBranch.empty": "No matching branches", "diffViewer.baseBranch.loading": "Loading branches…", "diffViewer.baseBranch.none": "—", + // plan_exit tool part display + "plan.exit.ready": "แผนพร้อมแล้ว:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts index 8a7d0c756ae..d079bf58868 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts @@ -1609,4 +1609,6 @@ export const dict = { "diffViewer.baseBranch.empty": "No matching branches", "diffViewer.baseBranch.loading": "Loading branches…", "diffViewer.baseBranch.none": "—", + // plan_exit tool part display + "plan.exit.ready": "Plan hazır:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts index e2f36ebc4a3..26e1ef502c5 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts @@ -1608,4 +1608,6 @@ export const dict = { "diffViewer.baseBranch.empty": "No matching branches", "diffViewer.baseBranch.loading": "Loading branches…", "diffViewer.baseBranch.none": "—", + // plan_exit tool part display + "plan.exit.ready": "План готовий:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts index 9d55d18cfa9..51b45b71e6c 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts @@ -1526,4 +1526,6 @@ export const dict = { "diffViewer.baseBranch.empty": "没有匹配的分支", "diffViewer.baseBranch.loading": "正在加载分支…", "diffViewer.baseBranch.none": "—", + // plan_exit tool part display + "plan.exit.ready": "计划已准备就绪:", } satisfies Partial> diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts index 4cddda95852..aa5d88539a3 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts @@ -1533,4 +1533,6 @@ export const dict = { "diffViewer.baseBranch.empty": "沒有相符的分支", "diffViewer.baseBranch.loading": "正在載入分支…", "diffViewer.baseBranch.none": "—", + // plan_exit tool part display + "plan.exit.ready": "計畫已準備就緒:", } satisfies Partial> From 0db81958036a11c8de4f66b512cc94a8aa510ae9 Mon Sep 17 00:00:00 2001 From: kirillk Date: Wed, 13 May 2026 15:57:46 -0400 Subject: [PATCH 4/8] chore: add plan files --- .kilo/plans/1778523694544-stellar-panda.md | 133 +++++++++++ .kilo/plans/1778697937544-misty-wolf.md | 242 +++++++++++++++++++++ .kilo/plans/1778699914978-brave-moon.md | 177 +++++++++++++++ 3 files changed, 552 insertions(+) create mode 100644 .kilo/plans/1778523694544-stellar-panda.md create mode 100644 .kilo/plans/1778697937544-misty-wolf.md create mode 100644 .kilo/plans/1778699914978-brave-moon.md diff --git a/.kilo/plans/1778523694544-stellar-panda.md b/.kilo/plans/1778523694544-stellar-panda.md new file mode 100644 index 00000000000..650ce30897b --- /dev/null +++ b/.kilo/plans/1778523694544-stellar-panda.md @@ -0,0 +1,133 @@ +# Plan: Fix Plan Updated Label Without Performance Regression + +## Goal +Fix the VS Code `plan_exit` label so updated plans reliably show `Existing plan was updated:` while avoiding broad scans or reactive work that could slow down chat rendering. + +## Current Behavior +The plan-ready card sometimes shows `Plan is ready:` even when the preceding tool clearly updated the plan, e.g.: + +- `Write ... .kilo/plans/ +218 -44` +- `Patch ... .kilo/plans/ +17 -3` + +Those should show `Existing plan was updated:`. + +## Why The Current Fix Fails +The current implementation in `packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx` tries to infer status inside `PlanExitCard`: + +```ts +const all = createMemo(() => Object.values(data.store.part ?? {}).flat() as SDKPart[]) +planExitInfo(props.part as unknown as SDKPart, [...props.parts, ...all()]) +``` + +This has two problems: + +1. Ordering bug + - `props.parts` is the current assistant message’s parts. + - When the current assistant message contains only `plan_exit`, the combined array starts with `plan_exit`. + - `inferPlanStatus()` finds the first `plan_exit` index and only checks parts before it: + ```ts + const idx = parts.findIndex(...exit.id...) + const prior = idx === -1 ? parts : parts.slice(0, idx) + ``` + - Because `plan_exit` is first, `prior` is empty, so it never sees the earlier `write`/`apply_patch` tool. + +2. Performance risk + - `Object.values(data.store.part ?? {}).flat()` runs inside every `PlanExitCard` render and can scan all loaded part arrays in the session store. + - This is unnecessary work for almost every assistant message and can grow with long sessions. + +## Correct Approach +Use turn-local context instead of scanning all loaded session parts. + +`VscodeSessionTurn.tsx` already has the full current turn: + +- `props.turn.user` +- `props.turn.assistant` + +It also already hydrates parts for that exact turn: + +```ts +const ids = turn.partial ? turn.assistant.map((m) => m.id) : [turn.user.id, ...turn.assistant.map((m) => m.id)] +session.hydrateParts(ids) +``` + +The status inference only needs tools in this planning turn, not the whole session. The update/write/patch that produced the plan and the `plan_exit` part are part of the same visible turn. So we should compute ordered turn parts once in `VscodeSessionTurn` and pass them into every `AssistantMessage` for that turn. + +## Implementation Plan + +1. Add a turn-local ordered assistant parts memo in `VscodeSessionTurn.tsx`. + - Compute it from `assistantMessages()` in order. + - For each assistant message id, read `data.store.part?.[msg.id] ?? emptyParts`. + - Flatten only those arrays. + - Example shape: + ```ts + const assistantParts = createMemo(() => + assistantMessages().flatMap((msg) => (data.store.part?.[msg.id] ?? emptyParts) as SDKPart[]), + ) + ``` + - This is bounded by the current turn, not all loaded session history. + +2. Pass the turn-local parts into `AssistantMessage`. + - Extend `AssistantMessageProps` with `parts?: SDKPart[]` or `turnParts?: SDKPart[]`. + - In `VscodeSessionTurn.tsx`, pass `turnParts={assistantParts()}`. + - Keep naming clear; `turnParts` is probably best. + +3. Remove the broad store scan from `PlanExitCard`. + - Remove: + ```ts + const all = createMemo(() => Object.values(data.store.part ?? {}).flat() as SDKPart[]) + ``` + - Call: + ```ts + planExitInfo(props.part as unknown as SDKPart, props.turnParts) + ``` + - Do not concatenate `props.parts` in front of the ordered list. + +4. Make `inferPlanStatus()` robust to list ordering. + - Keep current behavior of considering only parts before the current `plan_exit` if the exit id is found. + - If the exit id is not found in `turnParts` for any reason, use all `turnParts` except any `plan_exit` parts. + - This avoids false positives from tools after `plan_exit` while still avoiding the empty-prior bug. + - Example: + ```ts + const idx = parts.findIndex(...) + const prior = idx >= 0 ? parts.slice(0, idx) : parts.filter((p) => !isPlanExit(p)) + ``` + +5. Keep the existing status heuristics but apply them to turn-local ordered parts. + - `edit` touching plan -> updated + - `apply_patch` touching plan with `type: "update" | "delete" | "move"` or deletions > 0 -> updated + - `write` touching plan with `filediff.deletions > 0` -> updated + - `read` + `write` touching plan -> updated + - `write` touching plan only -> new + - no match -> ready + +6. Add/adjust tests to lock the performance-sensitive shape. + - Update `packages/kilo-vscode/tests/unit/plan-exit.test.ts` static assertions: + - Assert it no longer contains `Object.values(data.store.part ?? {}).flat()`. + - Assert `VscodeSessionTurn.tsx` contains a turn-local assistant parts memo using `assistantMessages().flatMap`. + - Assert `AssistantMessage` receives/passes `turnParts`. + - Keep assertions for: + - `data.openFile` and no `openDiff` usage. + - `write`, `edit`, `apply_patch`, `read + write`, and deletion heuristics. + +7. Run targeted checks. + - `bun test tests/unit/plan-exit.test.ts` from `packages/kilo-vscode/`. + - If making TypeScript prop changes, run a targeted typecheck if feasible: + - `bun run typecheck` from `packages/kilo-vscode/` or at least `bun turbo typecheck --filter=kilo-code` if that is the established path after the merge. + - No opencode annotation check should be necessary because this fix should stay entirely in `packages/kilo-vscode/`. + +## Performance Notes + +- Current approach is O(total loaded parts) per `PlanExitCard` recomputation. +- Proposed approach is O(parts in current turn), computed once per visible turn and reused by its assistant messages. +- Typical turns have a small bounded number of parts compared to the full loaded session history. +- This also avoids allocating a flattened array for every plan-ready card from the full store. +- No backend or opencode changes are needed. + +## Expected Outcome + +After the fix: + +- The screenshot case `Write ... +218 -44` should render `Existing plan was updated: .kilo/plans/...`. +- The `Patch ... +17 -3` case should render `Existing plan was updated: .kilo/plans/...`. +- A true first-time `write` with no deletions and no prior read should render `New plan is ready: .kilo/plans/...`. +- The renderer should remain fast because it uses only current-turn parts, not all loaded parts. diff --git a/.kilo/plans/1778697937544-misty-wolf.md b/.kilo/plans/1778697937544-misty-wolf.md new file mode 100644 index 00000000000..8e76ad8aff8 --- /dev/null +++ b/.kilo/plans/1778697937544-misty-wolf.md @@ -0,0 +1,242 @@ +# Plan: Colored Git Prompt Showing Branch + +Goal: create a custom shell prompt that displays the current Git branch in color when the shell is inside a Git repository, hides the Git segment elsewhere, and remains easy to remove or extend later. + +## Scope + +- Target shell: `zsh`, because the environment default shell is `/bin/zsh`. +- Target config file: usually `~/.zshrc`, unless the existing prompt is sourced from another file. +- Prompt behavior: + - Show the current directory. + - Show the current Git branch in a colored segment when inside a Git repo. + - Show a short commit hash when in detached HEAD state. + - Show no Git text and no Git errors outside a repo. +- Implementation style: + - Prefer native `zsh` prompt escapes and shell functions. + - Avoid prompt frameworks unless one is already installed and clearly managing the prompt. + - Keep the change small, readable, and reversible. + +## Design Choices + +1. Use a helper function instead of embedding Git commands directly in `PROMPT`. + - This keeps the prompt definition readable. + - It makes later features like dirty-state or ahead/behind indicators easier to add. + - It isolates error suppression for non-Git directories. + +2. Use `git symbolic-ref --short HEAD` for normal branches. + - This is simple and directly returns branch names such as `main` or `feature/login`. + - It fails in detached HEAD state, which can be handled by a fallback. + +3. Use `git rev-parse --short HEAD` as the detached HEAD fallback. + - This keeps the prompt useful when checking out a commit, tag, rebase state, or bisect state. + - The prompt can display a short hash such as `(a1b2c3d)`. + +4. Use `setopt PROMPT_SUBST`. + - This allows `$(git_branch)` to be evaluated every time the prompt is redrawn. + - Without this option, the Git branch would either not appear or would be evaluated only once. + +5. Use zsh prompt color escapes instead of raw ANSI codes. + - `%F{green}` starts green foreground text. + - `%F{blue}` starts blue foreground text. + - `%f` resets the foreground color. + - These escapes are prompt-aware and avoid cursor-position issues caused by unwrapped ANSI sequences. + +## Pre-Implementation Inspection + +Before editing any shell config, inspect the current prompt setup: + +1. Check `~/.zshrc`. + - Look for existing `PROMPT=`, `PS1=`, `RPROMPT=`, or `RPS1=` assignments. + - Look for `source` lines that load prompt-related files. + - Look for `setopt PROMPT_SUBST` or `unsetopt PROMPT_SUBST`. + +2. Check whether a prompt framework is already active. + - Oh My Zsh indicators: + - `export ZSH=...` + - `ZSH_THEME=...` + - `source $ZSH/oh-my-zsh.sh` + - Powerlevel10k indicators: + - `powerlevel10k/powerlevel10k` + - `source ~/.p10k.zsh` + - Starship indicators: + - `eval "$(starship init zsh)"` + - Spaceship or other theme indicators: + - `SPACESHIP_...` + - theme plugin source lines. + +3. Decide how to apply the prompt change based on what exists. + - If no framework manages the prompt, add the custom helper and `PROMPT` directly. + - If a framework manages the prompt, do not blindly override it. + - If the user already has a custom `PROMPT`, modify it carefully or add the Git segment in the least disruptive place. + - If Starship or Powerlevel10k is active, prefer configuring that framework instead of replacing it. + +## Backup Strategy + +Before changing shell config: + +1. Create a timestamped backup of the file to be edited. + - Example: `~/.zshrc.backup.20260513-144700` + +2. Keep the change grouped and labeled. + - Add a short marker comment before the custom prompt block. + - This makes rollback simple without restoring the whole backup. + +3. Do not modify unrelated shell settings. + - Avoid reorganizing aliases, exports, plugins, or path setup. + - Keep the prompt block near existing prompt configuration if possible. + +## Implementation Plan + +1. Add prompt substitution if needed. + - Add `setopt PROMPT_SUBST` near the prompt block. + - If it already exists, do not duplicate it. + +2. Add the Git branch function. + - Use a compact helper named `git_branch`. + - Suppress Git errors with `2>/dev/null`. + - Return no output outside Git repositories. + - Print a leading space only when a Git value exists, so the prompt spacing stays clean. + +3. Define the colored prompt. + - Keep the current directory visible with `%~`. + - Color the directory blue. + - Color the branch segment green. + - Reset colors after each colored segment. + - End with `%#`, which shows `%` for normal users and `#` for root. + +4. Keep the prompt single-line by default. + - A simple default is easier to validate and less disruptive. + - Multi-line prompt variants can be added later if desired. + +5. Avoid adding expensive Git status checks in the first version. + - Branch lookup is fast enough for normal use. + - Dirty-state checks can be added later, but they may slow prompts in large repos. + +## Proposed zsh Snippet + +```zsh +# Custom colored Git prompt +setopt PROMPT_SUBST + +git_branch() { + local branch + branch=$(git symbolic-ref --short HEAD 2>/dev/null) || branch=$(git rev-parse --short HEAD 2>/dev/null) || return + print -r -- " %F{green}(${branch})%f" +} + +PROMPT='%F{blue}%~%f$(git_branch) %# ' +``` + +Expected examples: + +```text +~/projects/app (main) % +~/projects/app (feature/git-prompt) % +~/projects/app (a1b2c3d) % +~/Downloads % +``` + +## Optional Enhanced Variant + +If the user wants a slightly richer prompt, add a dirty working tree marker later: + +```zsh +git_branch() { + local branch dirty + branch=$(git symbolic-ref --short HEAD 2>/dev/null) || branch=$(git rev-parse --short HEAD 2>/dev/null) || return + git diff --quiet --ignore-submodules HEAD 2>/dev/null || dirty='*' + print -r -- " %F{green}(${branch}${dirty})%f" +} +``` + +Behavior: + +- `(main)` means clean working tree. +- `(main*)` means the repo has uncommitted changes. + +Caveat: + +- Dirty-state checks can be slower in very large repositories, so this should be optional rather than the default first implementation. + +## Validation Plan + +Validate in a new shell process so a broken prompt does not disrupt the current session. + +1. Syntax-load the config. + - Run a new `zsh` process that sources the modified config. + - Confirm there are no syntax errors. + +2. Test outside a Git repo. + - Move to a non-repository directory. + - Confirm the prompt shows only the path and prompt symbol. + - Confirm no Git error messages appear. + +3. Test inside a Git repo on a branch. + - Move to any repository with a checked-out branch. + - Confirm the branch appears in green. + - Confirm the directory appears in blue. + +4. Test after changing branches. + - Switch to another branch in a disposable repo if available. + - Confirm the prompt updates without restarting the shell. + +5. Test detached HEAD if practical. + - In a safe test repo, check out a commit hash temporarily. + - Confirm the prompt shows a short hash. + - Return to the original branch afterward. + +6. Test command readability. + - Type a long command and use backspace/navigation. + - Confirm cursor movement is correct, which validates that color escapes are prompt-safe. + +## Rollback Plan + +If the prompt behaves incorrectly: + +1. Remove the custom prompt block. + - Delete the `git_branch` function and the related `PROMPT` assignment. + - Remove `setopt PROMPT_SUBST` only if it was added solely for this prompt. + +2. Restore the backup if needed. + - Copy the timestamped backup over the edited config. + +3. Start a new shell. + - Confirm the old prompt is restored. + +## Risks and Mitigations + +- Risk: overriding an existing framework prompt. + - Mitigation: inspect for Oh My Zsh, Powerlevel10k, Starship, or theme setup before editing. + +- Risk: raw ANSI colors break cursor movement. + - Mitigation: use zsh prompt escapes like `%F{green}` and `%f`. + +- Risk: Git command errors appear outside repositories. + - Mitigation: redirect Git stderr to `/dev/null` and return no output when Git commands fail. + +- Risk: prompt becomes slow in large repos. + - Mitigation: only resolve branch by default; make dirty-state checks optional. + +- Risk: function name conflicts with an existing shell function. + - Mitigation: check for an existing `git_branch` function first, or rename to something more specific such as `prompt_git_branch`. + +## Completion Criteria + +The task is complete when: + +- The prompt config is backed up before modification. +- The prompt shows a colored Git branch inside repositories. +- The prompt hides the Git segment outside repositories. +- Detached HEAD state displays a short commit hash. +- Existing framework-managed prompts are not accidentally overwritten. +- A new shell can load the config without syntax errors. +- Rollback instructions are clear and tested enough to be reliable. + +## Future Enhancements + +- Add dirty-state marker such as `*` for uncommitted changes. +- Add staged/unstaged indicators with different symbols. +- Add upstream ahead/behind counts. +- Add right-side prompt support with `RPROMPT`. +- Add a multi-line prompt layout. +- Switch to Starship if the user wants a cross-shell prompt with TOML configuration. \ No newline at end of file diff --git a/.kilo/plans/1778699914978-brave-moon.md b/.kilo/plans/1778699914978-brave-moon.md new file mode 100644 index 00000000000..f4ee4b4a2a6 --- /dev/null +++ b/.kilo/plans/1778699914978-brave-moon.md @@ -0,0 +1,177 @@ +# Plan: Deliberate Custom Colored Git Prompt + +## Objective +Create a polished, intentional shell prompt that clearly displays the current Git branch in color when inside a repository, stays clean outside Git repositories, and avoids slow Git operations on every prompt render. + +## Design Principles +- Keep the prompt readable at a glance: path first, Git context second, prompt symbol last. +- Use color to communicate meaning, not decoration only. +- Avoid noisy output in non-Git directories. +- Support normal branches and detached HEAD states. +- Start with branch display only; add dirty-state indicators only if the user wants that extra signal. +- Keep the implementation self-contained in the shell config and easy to remove. + +## Target Shell +Use `zsh` by default because the current environment uses zsh. + +Primary config file: +```sh +~/.zshrc +``` + +If the target shell is different, adapt the same structure: +- `bash`: use `~/.bashrc` or `~/.bash_profile` +- `fish`: use `~/.config/fish/config.fish` with fish-specific syntax + +## Prompt Layout +Use this deliberate layout: +```sh + +``` + +Examples: +```sh +~/projects % +~/projects/app (main) % +~/projects/app (a1b2c3d) % +``` + +Color choices: +- Path: cyan, because it is stable location context. +- Git branch: green, because it is contextual state and should stand out. +- Detached HEAD: yellow, because it is notable but not necessarily an error. +- Prompt symbol: default color, to avoid visual clutter. + +## Git Branch Behavior +The branch helper should return: +- nothing outside a Git worktree +- the branch name on a normal branch +- a short commit hash in detached HEAD state + +Use this order: +1. `git symbolic-ref --quiet --short HEAD` for normal branches. +2. `git rev-parse --short HEAD` for detached HEAD. +3. return nothing if both fail. + +Redirect errors to `/dev/null` so the prompt never prints Git errors. + +## Implementation Plan +1. Back up the existing prompt configuration mentally before editing. + - Inspect `~/.zshrc` for an existing `PROMPT`, `PS1`, `precmd`, or prompt framework setup. + - If a framework is active, such as Oh My Zsh, Starship, Powerlevel10k, or Prezto, do not blindly override it; either disable that framework's prompt or integrate the Git segment into the existing prompt style. + +2. Add prompt substitution for zsh. + ```sh + setopt PROMPT_SUBST + ``` + This allows `$(git_prompt)` to be evaluated every time the prompt is drawn. + +3. Add a small helper for the current Git ref. + - Keep the function focused on data retrieval only. + - Do not format colors in this helper. + +4. Add a formatter for the Git prompt segment. + - Return an empty string outside Git repos. + - Wrap normal branches in green. + - Wrap detached HEAD hashes in yellow. + - Keep parentheses around the value for visual separation. + +5. Set the prompt. + - Use `%~` for compact path display. + - Use `%#` for the prompt symbol. + - Keep one space between path, Git segment, and symbol. + +6. Reload the shell config. + ```sh + source ~/.zshrc + ``` + +7. Verify behavior in three directories. + - Home or another non-Git directory: no Git segment appears. + - A Git repo on a branch: branch appears in green. + - Detached HEAD state: short commit hash appears in yellow. + +## Recommended zsh Snippet +```sh +setopt PROMPT_SUBST + +_git_ref() { + git symbolic-ref --quiet --short HEAD 2>/dev/null || git rev-parse --short HEAD 2>/dev/null +} + +_git_prompt() { + local ref="$(_git_ref)" + + if [[ -z "$ref" ]]; then + return + fi + + if git symbolic-ref --quiet HEAD >/dev/null 2>&1; then + printf '%%F{green}(%s)%%f' "$ref" + return + fi + + printf '%%F{yellow}(%s)%%f' "$ref" +} + +PROMPT='%F{cyan}%~%f $(_git_prompt) %# ' +``` + +## More Polished Variant +If the user wants a more styled prompt, use separators while keeping it simple: +```sh +PROMPT='%F{cyan}%~%f %F{242}|%f $(_git_prompt) %# ' +``` + +Example: +```sh +~/projects/app | (main) % +``` + +This is visually cleaner in long paths, but the plain version is better if minimalism is preferred. + +## Optional Dirty-State Indicator +Add this only after the basic prompt works. + +Desired behavior: +- Clean repo: `(main)` in green +- Dirty repo: `(main*)` in yellow +- Detached HEAD: `(a1b2c3d)` in yellow + +Implementation idea: +```sh +_git_dirty() { + git diff --quiet --ignore-submodules -- 2>/dev/null && git diff --cached --quiet --ignore-submodules -- 2>/dev/null +} +``` + +Tradeoff: +- Useful signal, but adds Git work on every prompt render. +- Usually fine in small repos, but can feel slow in very large repos. +- Do not include remote ahead/behind checks by default because they are more expensive. + +## Framework Compatibility Notes +If a prompt framework is already installed: +- Starship: configure Git branch in `~/.config/starship.toml` instead of writing custom zsh functions. +- Oh My Zsh: either select a theme with Git support or add this custom segment after theme initialization. +- Powerlevel10k: use `p10k configure` or edit `~/.p10k.zsh`; do not overwrite `PROMPT` directly. + +Default recommendation: only use the custom snippet when the shell config does not already delegate prompt rendering to a framework. + +## Verification Checklist +- `source ~/.zshrc` exits with no errors. +- Prompt has no empty `()` segment outside Git repos. +- Branch appears in green inside a normal Git branch. +- Detached HEAD appears as a short hash in yellow. +- Prompt remains responsive after several `cd` operations. +- Existing shell aliases, completions, and prompt framework behavior are not accidentally broken. + +## Rollback Plan +If the prompt behaves incorrectly: +1. Reopen `~/.zshrc`. +2. Remove or comment out the custom functions and `PROMPT=...` line. +3. Restore the previous `PROMPT`, `PS1`, or framework initialization. +4. Run `source ~/.zshrc` again. + +## Final Recommendation +Implement the basic zsh snippet first. It gives a clean, colored branch display with detached HEAD support and minimal performance cost. Add dirty-state indicators only after confirming the base prompt feels good in daily use. From fb420a1af369247315d8207e28a855decc2e5b45 Mon Sep 17 00:00:00 2001 From: kirillk Date: Wed, 13 May 2026 16:02:58 -0400 Subject: [PATCH 5/8] fix(vscode): use inert plan link href --- packages/kilo-vscode/tests/unit/plan-exit.test.ts | 5 +++++ .../webview-ui/src/components/chat/AssistantMessage.tsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/kilo-vscode/tests/unit/plan-exit.test.ts b/packages/kilo-vscode/tests/unit/plan-exit.test.ts index 1c202b447c7..11042c38fa6 100644 --- a/packages/kilo-vscode/tests/unit/plan-exit.test.ts +++ b/packages/kilo-vscode/tests/unit/plan-exit.test.ts @@ -55,6 +55,11 @@ describe("plan_exit renderer uses openFile not openDiff (source)", () => { expect(src).toContain("data.openFile") }) + it("uses a safe inert anchor href", () => { + expect(src).toContain('href="#"') + expect(src).not.toContain("href={display()}") + }) + it("always uses the generic ready label", () => { expect(src).toContain('language.t("plan.exit.ready")') expect(src).not.toContain('language.t("plan.exit.readyUpdated")') diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx index 6cd78cecdc6..9e160ef9f1b 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx @@ -74,7 +74,7 @@ function PlanExitCard(props: { part: ToolPart }) { {" "} {display()} From 62e98e8b3dd8a110e23c42bcf912d487f116f1d9 Mon Sep 17 00:00:00 2001 From: kirillk Date: Wed, 13 May 2026 16:10:18 -0400 Subject: [PATCH 6/8] style(vscode): format assistant message --- .../src/components/chat/AssistantMessage.tsx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx index 9e160ef9f1b..18662f53a64 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx @@ -49,9 +49,7 @@ function PlanExitCard(props: { part: ToolPart }) { const language = useLanguage() const server = useServer() const data = useData() - const info = createMemo(() => - planExitInfo(props.part as unknown as SDKPart), - ) + const info = createMemo(() => planExitInfo(props.part as unknown as SDKPart)) const display = createMemo(() => { const i = info() if (!i) return "" @@ -70,13 +68,8 @@ function PlanExitCard(props: { part: ToolPart }) { return ( @@ -218,7 +211,14 @@ export const AssistantMessage: Component = (props) => { return (
Date: Wed, 13 May 2026 16:31:21 -0400 Subject: [PATCH 7/8] chore: remove plan files --- .kilo/plans/1778523694544-stellar-panda.md | 133 ----------- .kilo/plans/1778697937544-misty-wolf.md | 242 --------------------- .kilo/plans/1778699914978-brave-moon.md | 177 --------------- 3 files changed, 552 deletions(-) delete mode 100644 .kilo/plans/1778523694544-stellar-panda.md delete mode 100644 .kilo/plans/1778697937544-misty-wolf.md delete mode 100644 .kilo/plans/1778699914978-brave-moon.md diff --git a/.kilo/plans/1778523694544-stellar-panda.md b/.kilo/plans/1778523694544-stellar-panda.md deleted file mode 100644 index 650ce30897b..00000000000 --- a/.kilo/plans/1778523694544-stellar-panda.md +++ /dev/null @@ -1,133 +0,0 @@ -# Plan: Fix Plan Updated Label Without Performance Regression - -## Goal -Fix the VS Code `plan_exit` label so updated plans reliably show `Existing plan was updated:` while avoiding broad scans or reactive work that could slow down chat rendering. - -## Current Behavior -The plan-ready card sometimes shows `Plan is ready:` even when the preceding tool clearly updated the plan, e.g.: - -- `Write ... .kilo/plans/ +218 -44` -- `Patch ... .kilo/plans/ +17 -3` - -Those should show `Existing plan was updated:`. - -## Why The Current Fix Fails -The current implementation in `packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx` tries to infer status inside `PlanExitCard`: - -```ts -const all = createMemo(() => Object.values(data.store.part ?? {}).flat() as SDKPart[]) -planExitInfo(props.part as unknown as SDKPart, [...props.parts, ...all()]) -``` - -This has two problems: - -1. Ordering bug - - `props.parts` is the current assistant message’s parts. - - When the current assistant message contains only `plan_exit`, the combined array starts with `plan_exit`. - - `inferPlanStatus()` finds the first `plan_exit` index and only checks parts before it: - ```ts - const idx = parts.findIndex(...exit.id...) - const prior = idx === -1 ? parts : parts.slice(0, idx) - ``` - - Because `plan_exit` is first, `prior` is empty, so it never sees the earlier `write`/`apply_patch` tool. - -2. Performance risk - - `Object.values(data.store.part ?? {}).flat()` runs inside every `PlanExitCard` render and can scan all loaded part arrays in the session store. - - This is unnecessary work for almost every assistant message and can grow with long sessions. - -## Correct Approach -Use turn-local context instead of scanning all loaded session parts. - -`VscodeSessionTurn.tsx` already has the full current turn: - -- `props.turn.user` -- `props.turn.assistant` - -It also already hydrates parts for that exact turn: - -```ts -const ids = turn.partial ? turn.assistant.map((m) => m.id) : [turn.user.id, ...turn.assistant.map((m) => m.id)] -session.hydrateParts(ids) -``` - -The status inference only needs tools in this planning turn, not the whole session. The update/write/patch that produced the plan and the `plan_exit` part are part of the same visible turn. So we should compute ordered turn parts once in `VscodeSessionTurn` and pass them into every `AssistantMessage` for that turn. - -## Implementation Plan - -1. Add a turn-local ordered assistant parts memo in `VscodeSessionTurn.tsx`. - - Compute it from `assistantMessages()` in order. - - For each assistant message id, read `data.store.part?.[msg.id] ?? emptyParts`. - - Flatten only those arrays. - - Example shape: - ```ts - const assistantParts = createMemo(() => - assistantMessages().flatMap((msg) => (data.store.part?.[msg.id] ?? emptyParts) as SDKPart[]), - ) - ``` - - This is bounded by the current turn, not all loaded session history. - -2. Pass the turn-local parts into `AssistantMessage`. - - Extend `AssistantMessageProps` with `parts?: SDKPart[]` or `turnParts?: SDKPart[]`. - - In `VscodeSessionTurn.tsx`, pass `turnParts={assistantParts()}`. - - Keep naming clear; `turnParts` is probably best. - -3. Remove the broad store scan from `PlanExitCard`. - - Remove: - ```ts - const all = createMemo(() => Object.values(data.store.part ?? {}).flat() as SDKPart[]) - ``` - - Call: - ```ts - planExitInfo(props.part as unknown as SDKPart, props.turnParts) - ``` - - Do not concatenate `props.parts` in front of the ordered list. - -4. Make `inferPlanStatus()` robust to list ordering. - - Keep current behavior of considering only parts before the current `plan_exit` if the exit id is found. - - If the exit id is not found in `turnParts` for any reason, use all `turnParts` except any `plan_exit` parts. - - This avoids false positives from tools after `plan_exit` while still avoiding the empty-prior bug. - - Example: - ```ts - const idx = parts.findIndex(...) - const prior = idx >= 0 ? parts.slice(0, idx) : parts.filter((p) => !isPlanExit(p)) - ``` - -5. Keep the existing status heuristics but apply them to turn-local ordered parts. - - `edit` touching plan -> updated - - `apply_patch` touching plan with `type: "update" | "delete" | "move"` or deletions > 0 -> updated - - `write` touching plan with `filediff.deletions > 0` -> updated - - `read` + `write` touching plan -> updated - - `write` touching plan only -> new - - no match -> ready - -6. Add/adjust tests to lock the performance-sensitive shape. - - Update `packages/kilo-vscode/tests/unit/plan-exit.test.ts` static assertions: - - Assert it no longer contains `Object.values(data.store.part ?? {}).flat()`. - - Assert `VscodeSessionTurn.tsx` contains a turn-local assistant parts memo using `assistantMessages().flatMap`. - - Assert `AssistantMessage` receives/passes `turnParts`. - - Keep assertions for: - - `data.openFile` and no `openDiff` usage. - - `write`, `edit`, `apply_patch`, `read + write`, and deletion heuristics. - -7. Run targeted checks. - - `bun test tests/unit/plan-exit.test.ts` from `packages/kilo-vscode/`. - - If making TypeScript prop changes, run a targeted typecheck if feasible: - - `bun run typecheck` from `packages/kilo-vscode/` or at least `bun turbo typecheck --filter=kilo-code` if that is the established path after the merge. - - No opencode annotation check should be necessary because this fix should stay entirely in `packages/kilo-vscode/`. - -## Performance Notes - -- Current approach is O(total loaded parts) per `PlanExitCard` recomputation. -- Proposed approach is O(parts in current turn), computed once per visible turn and reused by its assistant messages. -- Typical turns have a small bounded number of parts compared to the full loaded session history. -- This also avoids allocating a flattened array for every plan-ready card from the full store. -- No backend or opencode changes are needed. - -## Expected Outcome - -After the fix: - -- The screenshot case `Write ... +218 -44` should render `Existing plan was updated: .kilo/plans/...`. -- The `Patch ... +17 -3` case should render `Existing plan was updated: .kilo/plans/...`. -- A true first-time `write` with no deletions and no prior read should render `New plan is ready: .kilo/plans/...`. -- The renderer should remain fast because it uses only current-turn parts, not all loaded parts. diff --git a/.kilo/plans/1778697937544-misty-wolf.md b/.kilo/plans/1778697937544-misty-wolf.md deleted file mode 100644 index 8e76ad8aff8..00000000000 --- a/.kilo/plans/1778697937544-misty-wolf.md +++ /dev/null @@ -1,242 +0,0 @@ -# Plan: Colored Git Prompt Showing Branch - -Goal: create a custom shell prompt that displays the current Git branch in color when the shell is inside a Git repository, hides the Git segment elsewhere, and remains easy to remove or extend later. - -## Scope - -- Target shell: `zsh`, because the environment default shell is `/bin/zsh`. -- Target config file: usually `~/.zshrc`, unless the existing prompt is sourced from another file. -- Prompt behavior: - - Show the current directory. - - Show the current Git branch in a colored segment when inside a Git repo. - - Show a short commit hash when in detached HEAD state. - - Show no Git text and no Git errors outside a repo. -- Implementation style: - - Prefer native `zsh` prompt escapes and shell functions. - - Avoid prompt frameworks unless one is already installed and clearly managing the prompt. - - Keep the change small, readable, and reversible. - -## Design Choices - -1. Use a helper function instead of embedding Git commands directly in `PROMPT`. - - This keeps the prompt definition readable. - - It makes later features like dirty-state or ahead/behind indicators easier to add. - - It isolates error suppression for non-Git directories. - -2. Use `git symbolic-ref --short HEAD` for normal branches. - - This is simple and directly returns branch names such as `main` or `feature/login`. - - It fails in detached HEAD state, which can be handled by a fallback. - -3. Use `git rev-parse --short HEAD` as the detached HEAD fallback. - - This keeps the prompt useful when checking out a commit, tag, rebase state, or bisect state. - - The prompt can display a short hash such as `(a1b2c3d)`. - -4. Use `setopt PROMPT_SUBST`. - - This allows `$(git_branch)` to be evaluated every time the prompt is redrawn. - - Without this option, the Git branch would either not appear or would be evaluated only once. - -5. Use zsh prompt color escapes instead of raw ANSI codes. - - `%F{green}` starts green foreground text. - - `%F{blue}` starts blue foreground text. - - `%f` resets the foreground color. - - These escapes are prompt-aware and avoid cursor-position issues caused by unwrapped ANSI sequences. - -## Pre-Implementation Inspection - -Before editing any shell config, inspect the current prompt setup: - -1. Check `~/.zshrc`. - - Look for existing `PROMPT=`, `PS1=`, `RPROMPT=`, or `RPS1=` assignments. - - Look for `source` lines that load prompt-related files. - - Look for `setopt PROMPT_SUBST` or `unsetopt PROMPT_SUBST`. - -2. Check whether a prompt framework is already active. - - Oh My Zsh indicators: - - `export ZSH=...` - - `ZSH_THEME=...` - - `source $ZSH/oh-my-zsh.sh` - - Powerlevel10k indicators: - - `powerlevel10k/powerlevel10k` - - `source ~/.p10k.zsh` - - Starship indicators: - - `eval "$(starship init zsh)"` - - Spaceship or other theme indicators: - - `SPACESHIP_...` - - theme plugin source lines. - -3. Decide how to apply the prompt change based on what exists. - - If no framework manages the prompt, add the custom helper and `PROMPT` directly. - - If a framework manages the prompt, do not blindly override it. - - If the user already has a custom `PROMPT`, modify it carefully or add the Git segment in the least disruptive place. - - If Starship or Powerlevel10k is active, prefer configuring that framework instead of replacing it. - -## Backup Strategy - -Before changing shell config: - -1. Create a timestamped backup of the file to be edited. - - Example: `~/.zshrc.backup.20260513-144700` - -2. Keep the change grouped and labeled. - - Add a short marker comment before the custom prompt block. - - This makes rollback simple without restoring the whole backup. - -3. Do not modify unrelated shell settings. - - Avoid reorganizing aliases, exports, plugins, or path setup. - - Keep the prompt block near existing prompt configuration if possible. - -## Implementation Plan - -1. Add prompt substitution if needed. - - Add `setopt PROMPT_SUBST` near the prompt block. - - If it already exists, do not duplicate it. - -2. Add the Git branch function. - - Use a compact helper named `git_branch`. - - Suppress Git errors with `2>/dev/null`. - - Return no output outside Git repositories. - - Print a leading space only when a Git value exists, so the prompt spacing stays clean. - -3. Define the colored prompt. - - Keep the current directory visible with `%~`. - - Color the directory blue. - - Color the branch segment green. - - Reset colors after each colored segment. - - End with `%#`, which shows `%` for normal users and `#` for root. - -4. Keep the prompt single-line by default. - - A simple default is easier to validate and less disruptive. - - Multi-line prompt variants can be added later if desired. - -5. Avoid adding expensive Git status checks in the first version. - - Branch lookup is fast enough for normal use. - - Dirty-state checks can be added later, but they may slow prompts in large repos. - -## Proposed zsh Snippet - -```zsh -# Custom colored Git prompt -setopt PROMPT_SUBST - -git_branch() { - local branch - branch=$(git symbolic-ref --short HEAD 2>/dev/null) || branch=$(git rev-parse --short HEAD 2>/dev/null) || return - print -r -- " %F{green}(${branch})%f" -} - -PROMPT='%F{blue}%~%f$(git_branch) %# ' -``` - -Expected examples: - -```text -~/projects/app (main) % -~/projects/app (feature/git-prompt) % -~/projects/app (a1b2c3d) % -~/Downloads % -``` - -## Optional Enhanced Variant - -If the user wants a slightly richer prompt, add a dirty working tree marker later: - -```zsh -git_branch() { - local branch dirty - branch=$(git symbolic-ref --short HEAD 2>/dev/null) || branch=$(git rev-parse --short HEAD 2>/dev/null) || return - git diff --quiet --ignore-submodules HEAD 2>/dev/null || dirty='*' - print -r -- " %F{green}(${branch}${dirty})%f" -} -``` - -Behavior: - -- `(main)` means clean working tree. -- `(main*)` means the repo has uncommitted changes. - -Caveat: - -- Dirty-state checks can be slower in very large repositories, so this should be optional rather than the default first implementation. - -## Validation Plan - -Validate in a new shell process so a broken prompt does not disrupt the current session. - -1. Syntax-load the config. - - Run a new `zsh` process that sources the modified config. - - Confirm there are no syntax errors. - -2. Test outside a Git repo. - - Move to a non-repository directory. - - Confirm the prompt shows only the path and prompt symbol. - - Confirm no Git error messages appear. - -3. Test inside a Git repo on a branch. - - Move to any repository with a checked-out branch. - - Confirm the branch appears in green. - - Confirm the directory appears in blue. - -4. Test after changing branches. - - Switch to another branch in a disposable repo if available. - - Confirm the prompt updates without restarting the shell. - -5. Test detached HEAD if practical. - - In a safe test repo, check out a commit hash temporarily. - - Confirm the prompt shows a short hash. - - Return to the original branch afterward. - -6. Test command readability. - - Type a long command and use backspace/navigation. - - Confirm cursor movement is correct, which validates that color escapes are prompt-safe. - -## Rollback Plan - -If the prompt behaves incorrectly: - -1. Remove the custom prompt block. - - Delete the `git_branch` function and the related `PROMPT` assignment. - - Remove `setopt PROMPT_SUBST` only if it was added solely for this prompt. - -2. Restore the backup if needed. - - Copy the timestamped backup over the edited config. - -3. Start a new shell. - - Confirm the old prompt is restored. - -## Risks and Mitigations - -- Risk: overriding an existing framework prompt. - - Mitigation: inspect for Oh My Zsh, Powerlevel10k, Starship, or theme setup before editing. - -- Risk: raw ANSI colors break cursor movement. - - Mitigation: use zsh prompt escapes like `%F{green}` and `%f`. - -- Risk: Git command errors appear outside repositories. - - Mitigation: redirect Git stderr to `/dev/null` and return no output when Git commands fail. - -- Risk: prompt becomes slow in large repos. - - Mitigation: only resolve branch by default; make dirty-state checks optional. - -- Risk: function name conflicts with an existing shell function. - - Mitigation: check for an existing `git_branch` function first, or rename to something more specific such as `prompt_git_branch`. - -## Completion Criteria - -The task is complete when: - -- The prompt config is backed up before modification. -- The prompt shows a colored Git branch inside repositories. -- The prompt hides the Git segment outside repositories. -- Detached HEAD state displays a short commit hash. -- Existing framework-managed prompts are not accidentally overwritten. -- A new shell can load the config without syntax errors. -- Rollback instructions are clear and tested enough to be reliable. - -## Future Enhancements - -- Add dirty-state marker such as `*` for uncommitted changes. -- Add staged/unstaged indicators with different symbols. -- Add upstream ahead/behind counts. -- Add right-side prompt support with `RPROMPT`. -- Add a multi-line prompt layout. -- Switch to Starship if the user wants a cross-shell prompt with TOML configuration. \ No newline at end of file diff --git a/.kilo/plans/1778699914978-brave-moon.md b/.kilo/plans/1778699914978-brave-moon.md deleted file mode 100644 index f4ee4b4a2a6..00000000000 --- a/.kilo/plans/1778699914978-brave-moon.md +++ /dev/null @@ -1,177 +0,0 @@ -# Plan: Deliberate Custom Colored Git Prompt - -## Objective -Create a polished, intentional shell prompt that clearly displays the current Git branch in color when inside a repository, stays clean outside Git repositories, and avoids slow Git operations on every prompt render. - -## Design Principles -- Keep the prompt readable at a glance: path first, Git context second, prompt symbol last. -- Use color to communicate meaning, not decoration only. -- Avoid noisy output in non-Git directories. -- Support normal branches and detached HEAD states. -- Start with branch display only; add dirty-state indicators only if the user wants that extra signal. -- Keep the implementation self-contained in the shell config and easy to remove. - -## Target Shell -Use `zsh` by default because the current environment uses zsh. - -Primary config file: -```sh -~/.zshrc -``` - -If the target shell is different, adapt the same structure: -- `bash`: use `~/.bashrc` or `~/.bash_profile` -- `fish`: use `~/.config/fish/config.fish` with fish-specific syntax - -## Prompt Layout -Use this deliberate layout: -```sh - -``` - -Examples: -```sh -~/projects % -~/projects/app (main) % -~/projects/app (a1b2c3d) % -``` - -Color choices: -- Path: cyan, because it is stable location context. -- Git branch: green, because it is contextual state and should stand out. -- Detached HEAD: yellow, because it is notable but not necessarily an error. -- Prompt symbol: default color, to avoid visual clutter. - -## Git Branch Behavior -The branch helper should return: -- nothing outside a Git worktree -- the branch name on a normal branch -- a short commit hash in detached HEAD state - -Use this order: -1. `git symbolic-ref --quiet --short HEAD` for normal branches. -2. `git rev-parse --short HEAD` for detached HEAD. -3. return nothing if both fail. - -Redirect errors to `/dev/null` so the prompt never prints Git errors. - -## Implementation Plan -1. Back up the existing prompt configuration mentally before editing. - - Inspect `~/.zshrc` for an existing `PROMPT`, `PS1`, `precmd`, or prompt framework setup. - - If a framework is active, such as Oh My Zsh, Starship, Powerlevel10k, or Prezto, do not blindly override it; either disable that framework's prompt or integrate the Git segment into the existing prompt style. - -2. Add prompt substitution for zsh. - ```sh - setopt PROMPT_SUBST - ``` - This allows `$(git_prompt)` to be evaluated every time the prompt is drawn. - -3. Add a small helper for the current Git ref. - - Keep the function focused on data retrieval only. - - Do not format colors in this helper. - -4. Add a formatter for the Git prompt segment. - - Return an empty string outside Git repos. - - Wrap normal branches in green. - - Wrap detached HEAD hashes in yellow. - - Keep parentheses around the value for visual separation. - -5. Set the prompt. - - Use `%~` for compact path display. - - Use `%#` for the prompt symbol. - - Keep one space between path, Git segment, and symbol. - -6. Reload the shell config. - ```sh - source ~/.zshrc - ``` - -7. Verify behavior in three directories. - - Home or another non-Git directory: no Git segment appears. - - A Git repo on a branch: branch appears in green. - - Detached HEAD state: short commit hash appears in yellow. - -## Recommended zsh Snippet -```sh -setopt PROMPT_SUBST - -_git_ref() { - git symbolic-ref --quiet --short HEAD 2>/dev/null || git rev-parse --short HEAD 2>/dev/null -} - -_git_prompt() { - local ref="$(_git_ref)" - - if [[ -z "$ref" ]]; then - return - fi - - if git symbolic-ref --quiet HEAD >/dev/null 2>&1; then - printf '%%F{green}(%s)%%f' "$ref" - return - fi - - printf '%%F{yellow}(%s)%%f' "$ref" -} - -PROMPT='%F{cyan}%~%f $(_git_prompt) %# ' -``` - -## More Polished Variant -If the user wants a more styled prompt, use separators while keeping it simple: -```sh -PROMPT='%F{cyan}%~%f %F{242}|%f $(_git_prompt) %# ' -``` - -Example: -```sh -~/projects/app | (main) % -``` - -This is visually cleaner in long paths, but the plain version is better if minimalism is preferred. - -## Optional Dirty-State Indicator -Add this only after the basic prompt works. - -Desired behavior: -- Clean repo: `(main)` in green -- Dirty repo: `(main*)` in yellow -- Detached HEAD: `(a1b2c3d)` in yellow - -Implementation idea: -```sh -_git_dirty() { - git diff --quiet --ignore-submodules -- 2>/dev/null && git diff --cached --quiet --ignore-submodules -- 2>/dev/null -} -``` - -Tradeoff: -- Useful signal, but adds Git work on every prompt render. -- Usually fine in small repos, but can feel slow in very large repos. -- Do not include remote ahead/behind checks by default because they are more expensive. - -## Framework Compatibility Notes -If a prompt framework is already installed: -- Starship: configure Git branch in `~/.config/starship.toml` instead of writing custom zsh functions. -- Oh My Zsh: either select a theme with Git support or add this custom segment after theme initialization. -- Powerlevel10k: use `p10k configure` or edit `~/.p10k.zsh`; do not overwrite `PROMPT` directly. - -Default recommendation: only use the custom snippet when the shell config does not already delegate prompt rendering to a framework. - -## Verification Checklist -- `source ~/.zshrc` exits with no errors. -- Prompt has no empty `()` segment outside Git repos. -- Branch appears in green inside a normal Git branch. -- Detached HEAD appears as a short hash in yellow. -- Prompt remains responsive after several `cd` operations. -- Existing shell aliases, completions, and prompt framework behavior are not accidentally broken. - -## Rollback Plan -If the prompt behaves incorrectly: -1. Reopen `~/.zshrc`. -2. Remove or comment out the custom functions and `PROMPT=...` line. -3. Restore the previous `PROMPT`, `PS1`, or framework initialization. -4. Run `source ~/.zshrc` again. - -## Final Recommendation -Implement the basic zsh snippet first. It gives a clean, colored branch display with detached HEAD support and minimal performance cost. Add dirty-state indicators only after confirming the base prompt feels good in daily use. From fff4277c4ed172cef433012bc2c78461eb837e21 Mon Sep 17 00:00:00 2001 From: kirillk Date: Wed, 13 May 2026 16:34:49 -0400 Subject: [PATCH 8/8] chore(i18n): remove plan ready comments --- packages/kilo-vscode/webview-ui/src/i18n/ar.ts | 1 - packages/kilo-vscode/webview-ui/src/i18n/br.ts | 1 - packages/kilo-vscode/webview-ui/src/i18n/bs.ts | 1 - packages/kilo-vscode/webview-ui/src/i18n/da.ts | 1 - packages/kilo-vscode/webview-ui/src/i18n/de.ts | 1 - packages/kilo-vscode/webview-ui/src/i18n/en.ts | 1 - packages/kilo-vscode/webview-ui/src/i18n/es.ts | 1 - packages/kilo-vscode/webview-ui/src/i18n/fr.ts | 1 - packages/kilo-vscode/webview-ui/src/i18n/ja.ts | 1 - packages/kilo-vscode/webview-ui/src/i18n/ko.ts | 1 - packages/kilo-vscode/webview-ui/src/i18n/nl.ts | 1 - packages/kilo-vscode/webview-ui/src/i18n/no.ts | 1 - packages/kilo-vscode/webview-ui/src/i18n/pl.ts | 1 - packages/kilo-vscode/webview-ui/src/i18n/ru.ts | 1 - packages/kilo-vscode/webview-ui/src/i18n/th.ts | 1 - packages/kilo-vscode/webview-ui/src/i18n/tr.ts | 1 - packages/kilo-vscode/webview-ui/src/i18n/uk.ts | 1 - packages/kilo-vscode/webview-ui/src/i18n/zh.ts | 1 - packages/kilo-vscode/webview-ui/src/i18n/zht.ts | 1 - 19 files changed, 19 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts index 2ddc1c7703d..5423280f4bb 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts @@ -1563,6 +1563,5 @@ export const dict = { "diffViewer.baseBranch.empty": "لا توجد فروع مطابقة", "diffViewer.baseBranch.loading": "جارٍ تحميل الفروع…", "diffViewer.baseBranch.none": "—", - // plan_exit tool part display "plan.exit.ready": "الخطة جاهزة:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/br.ts b/packages/kilo-vscode/webview-ui/src/i18n/br.ts index 70aaac9a47b..82051426117 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/br.ts @@ -1605,6 +1605,5 @@ export const dict = { "diffViewer.baseBranch.empty": "Nenhum branch correspondente", "diffViewer.baseBranch.loading": "Carregando branches…", "diffViewer.baseBranch.none": "—", - // plan_exit tool part display "plan.exit.ready": "Plano pronto:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts index 02c7551c8df..d19f4c44912 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts @@ -1601,6 +1601,5 @@ export const dict = { "diffViewer.baseBranch.empty": "No matching branches", "diffViewer.baseBranch.loading": "Loading branches…", "diffViewer.baseBranch.none": "—", - // plan_exit tool part display "plan.exit.ready": "Plan je spreman:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/da.ts b/packages/kilo-vscode/webview-ui/src/i18n/da.ts index fdd5c33c2c0..0b7d73b3169 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/da.ts @@ -1591,6 +1591,5 @@ export const dict = { "diffViewer.baseBranch.empty": "No matching branches", "diffViewer.baseBranch.loading": "Loading branches…", "diffViewer.baseBranch.none": "—", - // plan_exit tool part display "plan.exit.ready": "Planen er klar:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/de.ts b/packages/kilo-vscode/webview-ui/src/i18n/de.ts index 5a86d270c1d..844bbc64941 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/de.ts @@ -1621,6 +1621,5 @@ export const dict = { "diffViewer.baseBranch.empty": "Keine passenden Branches", "diffViewer.baseBranch.loading": "Branches werden geladen…", "diffViewer.baseBranch.none": "—", - // plan_exit tool part display "plan.exit.ready": "Plan ist bereit:", } satisfies Partial> diff --git a/packages/kilo-vscode/webview-ui/src/i18n/en.ts b/packages/kilo-vscode/webview-ui/src/i18n/en.ts index 2274127a0ee..1af4206e182 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/en.ts @@ -1593,6 +1593,5 @@ export const dict = { "diffViewer.baseBranch.loading": "Loading branches…", "diffViewer.baseBranch.none": "—", - // plan_exit tool part display "plan.exit.ready": "Plan is ready:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/es.ts b/packages/kilo-vscode/webview-ui/src/i18n/es.ts index b62d6c7fe02..605328efa99 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/es.ts @@ -1611,6 +1611,5 @@ export const dict = { "diffViewer.baseBranch.empty": "Sin ramas que coincidan", "diffViewer.baseBranch.loading": "Cargando ramas…", "diffViewer.baseBranch.none": "—", - // plan_exit tool part display "plan.exit.ready": "El plan está listo:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts index 631e8b4d032..98a7a90149b 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts @@ -1630,6 +1630,5 @@ export const dict = { "diffViewer.baseBranch.empty": "Aucune branche correspondante", "diffViewer.baseBranch.loading": "Chargement des branches…", "diffViewer.baseBranch.none": "—", - // plan_exit tool part display "plan.exit.ready": "Le plan est prêt :", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts index a0922f60bda..ba4f4e14f73 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts @@ -1585,6 +1585,5 @@ export const dict = { "diffViewer.baseBranch.empty": "一致するブランチがありません", "diffViewer.baseBranch.loading": "ブランチを読み込み中…", "diffViewer.baseBranch.none": "—", - // plan_exit tool part display "plan.exit.ready": "プランの準備ができました:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts index 35ceb502a44..fb5951c77bb 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts @@ -1570,6 +1570,5 @@ export const dict = { "diffViewer.baseBranch.empty": "일치하는 브랜치 없음", "diffViewer.baseBranch.loading": "브랜치 로딩 중…", "diffViewer.baseBranch.none": "—", - // plan_exit tool part display "plan.exit.ready": "계획이 준비되었습니다:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts index 99beaf4a7f8..88275330baa 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts @@ -1623,6 +1623,5 @@ export const dict = { "diffViewer.baseBranch.empty": "No matching branches", "diffViewer.baseBranch.loading": "Loading branches…", "diffViewer.baseBranch.none": "—", - // plan_exit tool part display "plan.exit.ready": "Plan is klaar:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/no.ts b/packages/kilo-vscode/webview-ui/src/i18n/no.ts index 34ece930889..83c8cca9ea7 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/no.ts @@ -1585,6 +1585,5 @@ export const dict = { "diffViewer.baseBranch.empty": "No matching branches", "diffViewer.baseBranch.loading": "Loading branches…", "diffViewer.baseBranch.none": "—", - // plan_exit tool part display "plan.exit.ready": "Planen er klar:", } satisfies Partial> diff --git a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts index b7771c7f2a7..b785479908b 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts @@ -1595,6 +1595,5 @@ export const dict = { "diffViewer.baseBranch.empty": "No matching branches", "diffViewer.baseBranch.loading": "Loading branches…", "diffViewer.baseBranch.none": "—", - // plan_exit tool part display "plan.exit.ready": "Plan jest gotowy:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts index 06092e9480c..5b1d9ba1823 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts @@ -1595,6 +1595,5 @@ export const dict = { "diffViewer.baseBranch.empty": "Нет подходящих веток", "diffViewer.baseBranch.loading": "Загрузка веток…", "diffViewer.baseBranch.none": "—", - // plan_exit tool part display "plan.exit.ready": "План готов:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/th.ts b/packages/kilo-vscode/webview-ui/src/i18n/th.ts index 6c21148fb97..953155c1fbc 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/th.ts @@ -1568,6 +1568,5 @@ export const dict = { "diffViewer.baseBranch.empty": "No matching branches", "diffViewer.baseBranch.loading": "Loading branches…", "diffViewer.baseBranch.none": "—", - // plan_exit tool part display "plan.exit.ready": "แผนพร้อมแล้ว:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts index 96d2c6ccd1b..4ba9fe8e7c9 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts @@ -1612,6 +1612,5 @@ export const dict = { "diffViewer.baseBranch.empty": "No matching branches", "diffViewer.baseBranch.loading": "Loading branches…", "diffViewer.baseBranch.none": "—", - // plan_exit tool part display "plan.exit.ready": "Plan hazır:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts index 8353b094830..8deb7a453d2 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts @@ -1611,6 +1611,5 @@ export const dict = { "diffViewer.baseBranch.empty": "No matching branches", "diffViewer.baseBranch.loading": "Loading branches…", "diffViewer.baseBranch.none": "—", - // plan_exit tool part display "plan.exit.ready": "План готовий:", } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts index 978d87fb65f..132b0b676a5 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts @@ -1528,6 +1528,5 @@ export const dict = { "diffViewer.baseBranch.empty": "没有匹配的分支", "diffViewer.baseBranch.loading": "正在加载分支…", "diffViewer.baseBranch.none": "—", - // plan_exit tool part display "plan.exit.ready": "计划已准备就绪:", } satisfies Partial> diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts index 765d7a41770..d45a41f29fa 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts @@ -1535,6 +1535,5 @@ export const dict = { "diffViewer.baseBranch.empty": "沒有相符的分支", "diffViewer.baseBranch.loading": "正在載入分支…", "diffViewer.baseBranch.none": "—", - // plan_exit tool part display "plan.exit.ready": "計畫已準備就緒:", } satisfies Partial>