mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
chore: remove stale desktop package references
This commit is contained in:
@@ -6,9 +6,6 @@ on:
|
||||
- ".github/**"
|
||||
- "github/**"
|
||||
- "sdks/vscode/**"
|
||||
- "packages/app/**"
|
||||
- "packages/desktop/**"
|
||||
- "packages/desktop-electron/**"
|
||||
- "packages/extensions/**"
|
||||
- "packages/opencode/**"
|
||||
- "packages/script/**"
|
||||
|
||||
@@ -43,10 +43,8 @@ All products are clients of the **CLI** (`packages/opencode/`), which contains t
|
||||
|
||||
| Product | Package | Description |
|
||||
|---|---|---|
|
||||
| Kilo CLI | `packages/opencode/` | Core engine. TUI, `kilo run`, `kilo serve`, `kilo web`. Fork of upstream OpenCode. |
|
||||
| Kilo CLI | `packages/opencode/` | Core engine. TUI, `kilo run`, `kilo serve`. Fork of upstream OpenCode. |
|
||||
| Kilo VS Code Extension | `packages/kilo-vscode/` | VS Code extension. Bundles the CLI binary, spawns `kilo serve` as a child process. Includes the **Agent Manager** — a multi-session orchestration panel with git worktree isolation. |
|
||||
| OpenCode Desktop | `packages/desktop/` | Standalone Tauri native app. Bundles CLI as sidecar. Single-session UI. Unrelated to the VS Code extension. Not actively maintained — synced from upstream fork. |
|
||||
| OpenCode Web | `packages/app/` | Shared SolidJS frontend used by both the desktop app and `kilo web` CLI command. Not actively maintained — synced from upstream fork. |
|
||||
|
||||
**Agent Manager** refers to a feature inside `packages/kilo-vscode/` (extension code in `src/agent-manager/`, webview in `webview-ui/agent-manager/`). It is not a standalone product. See the extension's `AGENTS.md` for details.
|
||||
|
||||
@@ -64,9 +62,7 @@ Turborepo + Bun workspaces. The packages you'll work with most:
|
||||
| `packages/kilo-gateway/` | `@kilocode/kilo-gateway` | Kilo auth, provider routing, API integration |
|
||||
| `packages/kilo-telemetry/` | `@kilocode/kilo-telemetry` | PostHog analytics + OpenTelemetry |
|
||||
| `packages/kilo-i18n/` | `@kilocode/kilo-i18n` | Internationalization / translations |
|
||||
| `packages/kilo-ui/` | `@kilocode/kilo-ui` | SolidJS component library shared by the extension webview and `packages/app/` |
|
||||
| `packages/app/` | `@opencode-ai/app` | Shared SolidJS web UI for desktop app and `kilo web` |
|
||||
| `packages/desktop/` | `@opencode-ai/desktop` | Tauri desktop app shell |
|
||||
| `packages/kilo-ui/` | `@kilocode/kilo-ui` | SolidJS component library shared by the extension webview and docs screenshot stories |
|
||||
| `packages/util/` | `@opencode-ai/util` | Shared utilities (error, path, retry, slug, etc.) |
|
||||
| `packages/plugin/` | `@kilocode/plugin` | Plugin/tool interface definitions |
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# TESTING.md
|
||||
|
||||
How to spin up the **local main-branch** Kilo backend and test it with `curl` / `fetch`. Aimed at a running Kilo CLI agent iterating on backend fixes without rebuilding the VS Code extension, TUI, or desktop app.
|
||||
How to spin up the **local main-branch** Kilo backend and test it with `curl` / `fetch`. Aimed at a running Kilo CLI agent iterating on backend fixes without rebuilding the VS Code extension or TUI.
|
||||
|
||||
All examples use plain shell + `curl`. Writing TypeScript files is a last resort (see Section 8).
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ This file provides guidance to agents when working with code in this repository.
|
||||
|
||||
## Product Context
|
||||
|
||||
Kilo Code is an open source AI coding agent platform. It ships as multiple products that all build on the same backend. This package (`packages/kilo-vscode/`) is the **VS Code extension** — one of several clients.
|
||||
Kilo Code is an open source AI coding agent platform. It ships as a CLI and editor clients that all build on the same backend. This package (`packages/kilo-vscode/`) is the **VS Code extension**.
|
||||
|
||||
### Products and How They Relate
|
||||
|
||||
@@ -18,12 +18,12 @@ Every client spawns or connects to a `kilo serve` process and communicates via H
|
||||
│ AI agents, tools, sessions, │
|
||||
│ providers, config, MCP, LSP │
|
||||
│ Hono HTTP server + SSE │
|
||||
└──┬──────────┬──────────┬───────┘
|
||||
│ │ │
|
||||
┌───────┴──┐ ┌────┴────┐ ┌───┴──────────┐
|
||||
│ TUI │ │ VS Code │ │ Desktop / Web│
|
||||
│ (builtin)│ │Extension│ │ │
|
||||
└──────────┘ └─────────┘ └──────────────┘
|
||||
└──┬──────────┬──────────────────┘
|
||||
│ │
|
||||
┌───────┴──┐ ┌────┴────┐
|
||||
│ TUI │ │ VS Code │
|
||||
│ (builtin)│ │Extension│
|
||||
└──────────┘ └─────────┘
|
||||
```
|
||||
|
||||
| Product | Package | What it is | How it uses the CLI |
|
||||
@@ -31,10 +31,6 @@ Every client spawns or connects to a `kilo serve` process and communicates via H
|
||||
| Kilo CLI (TUI) | `packages/opencode/` | Interactive terminal UI (SolidJS + OpenTUI) | In-process — TUI and server run together |
|
||||
| Kilo CLI (`kilo run`) | `packages/opencode/` | Non-interactive headless mode for scripting | In-process — no network socket |
|
||||
| **Kilo VS Code Extension** | **`packages/kilo-vscode/`** | VS Code extension with sidebar chat + Agent Manager | Bundles CLI binary, spawns `kilo serve --port 0` as child process |
|
||||
| OpenCode Desktop | `packages/desktop/` | Standalone native app (Tauri) | Bundles CLI binary as sidecar, spawns `kilo serve` |
|
||||
| OpenCode Web (`kilo web`) | `packages/opencode/` + `packages/app/` | Browser-based UI | CLI starts server + opens browser |
|
||||
|
||||
**OpenCode Desktop** (`packages/desktop/`) and `kilo web` both render the shared `@opencode-ai/app` SolidJS frontend (`packages/app/`). They differ only in the platform layer (Tauri native APIs vs browser APIs). Neither has any relationship to this extension or the Agent Manager — they are independent clients of the same `kilo serve` backend.
|
||||
|
||||
### Kilo-Domain Packages
|
||||
|
||||
@@ -42,7 +38,7 @@ Every client spawns or connects to a `kilo serve` process and communicates via H
|
||||
|---|---|---|
|
||||
| `packages/kilo-vscode/` | `kilo-code` | **This package.** VS Code extension. |
|
||||
| `packages/kilo-gateway/` | `@kilocode/kilo-gateway` | Auth (device flow), AI provider routing (OpenRouter), Kilo API integration (profile, balance, teams) |
|
||||
| `packages/kilo-ui/` | `@kilocode/kilo-ui` | SolidJS component library (40+ components, built on `@kobalte/core`). Shared by this extension's webview and `packages/app/` |
|
||||
| `packages/kilo-ui/` | `@kilocode/kilo-ui` | SolidJS component library (40+ components, built on `@kobalte/core`). Shared by this extension's webview and docs screenshot stories |
|
||||
| `packages/kilo-telemetry/` | `@kilocode/kilo-telemetry` | PostHog analytics + OpenTelemetry tracing for the CLI |
|
||||
| `packages/kilo-i18n/` | `@kilocode/kilo-i18n` | Translation strings (16 languages) |
|
||||
| `packages/kilo-docs/` | `@kilocode/kilo-docs` | Documentation site (Next.js + Markdoc) |
|
||||
@@ -53,8 +49,6 @@ Every client spawns or connects to a `kilo serve` process and communicates via H
|
||||
|---|---|---|
|
||||
| `packages/opencode/` | `@kilocode/cli` | Core CLI — forked from upstream OpenCode. AI agents, tools, sessions, server. |
|
||||
| `packages/sdk/js/` | `@kilocode/sdk` | Auto-generated TypeScript SDK client for the server API. Do not edit `src/gen/` by hand. |
|
||||
| `packages/app/` | `@opencode-ai/app` | Shared SolidJS web UI consumed by desktop app and `kilo web` |
|
||||
| `packages/desktop/` | `@opencode-ai/desktop` | Tauri desktop app shell |
|
||||
| `packages/ui/` | `@opencode-ai/ui` | Shared UI primitives |
|
||||
| `packages/util/` | `@opencode-ai/util` | Shared utilities (error, path, retry, slug) |
|
||||
| `packages/plugin/` | `@kilocode/plugin` | Plugin/tool interface definitions |
|
||||
@@ -181,8 +175,8 @@ New webview features must use **`@kilocode/kilo-ui`** components instead of raw
|
||||
- Global styles imported via `import "@kilocode/kilo-ui/styles"` in [`index.tsx`](webview-ui/src/index.tsx:2)
|
||||
- [`chat.css`](webview-ui/src/styles/chat.css) is being progressively migrated — when replacing a component with kilo-ui, remove the corresponding CSS rules from it
|
||||
- New CSS for components not yet in kilo-ui goes into `chat.css` grouped by comment-delimited sections (`/* Component Name */`). Once a kilo-ui equivalent exists, remove the section.
|
||||
- **Check the desktop app first**: [`packages/app/src/`](../../packages/app/src/) is the reference implementation for how kilo-ui components are composed together. Always check how the app uses a component before implementing it in the webview — don't just look at the component API in isolation.
|
||||
- **`data-component` and `data-slot` attributes carry CSS styling** — kilo-ui uses `[data-component]` and `[data-slot]` attribute selectors, not class names. When the app uses e.g. `data-component="permission-prompt"` and `data-slot="permission-actions"`, these get kilo-ui styling for free.
|
||||
- **Check existing webview usages first**: `webview-ui/src/` and `packages/kilo-ui/src/stories/` show how kilo-ui components are composed. Do not rely only on the component API in isolation.
|
||||
- **`data-component` and `data-slot` attributes carry CSS styling** — kilo-ui uses `[data-component]` and `[data-slot]` attribute selectors, not class names. Reuse existing component slots where available so shared styles apply consistently.
|
||||
- **Prefer kilo-ui styles**: Always reuse existing kilo-ui CSS variables, tokens, and component styles instead of writing custom CSS. If a style doesn't exist in kilo-ui yet, add it there and reuse it rather than inlining or duplicating styles in the webview.
|
||||
- **Icons**: kilo-ui has 75+ custom SVG icons in [`packages/ui/src/components/icon.tsx`](../../packages/ui/src/components/icon.tsx). To list all available icon names: `node -e "const c=require('fs').readFileSync('../../packages/ui/src/components/icon.tsx','utf8');[...c.matchAll(/^\\s{2}[\"']?([\\w-]+)[\"']?:\\s*\x60/gm)].map(m=>m[1]).sort().forEach(n=>console.log(n))"`. Icon names use both hyphenated (`arrow-left`) and bare-word (`brain`, `console`, `providers`) keys.
|
||||
|
||||
|
||||
@@ -26,8 +26,7 @@ const KEY = "enabled"
|
||||
/**
|
||||
* Runtime auto-accept toggle for permissions.
|
||||
*
|
||||
* Mirrors the desktop app pattern (packages/app/src/context/permission.tsx):
|
||||
* instead of writing to the config file, we intercept `permission.asked` SSE
|
||||
* Instead of writing to the config file, we intercept `permission.asked` SSE
|
||||
* events and auto-reply "once" to each. This avoids config-layer issues
|
||||
* (merged vs global, sparse defaults) and works even when the sidebar is closed.
|
||||
*/
|
||||
|
||||
@@ -258,7 +258,7 @@ export async function disconnectProvider(
|
||||
// Config-sourced providers stay "connected" after auth.remove because the
|
||||
// server rebuilds state from config. Add to disabled_providers so the server
|
||||
// excludes them. The config entry is preserved (user may re-enable later).
|
||||
// This matches the desktop app's disableProvider() pattern.
|
||||
// This mirrors the server's disabled provider config behavior.
|
||||
if (configured && !oauth) {
|
||||
const disabled = globalConfig.disabled_providers ?? []
|
||||
if (!disabled.includes(id)) {
|
||||
|
||||
@@ -28,7 +28,7 @@ function isNotFound(err: unknown) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Poll /global/health at the same interval as packages/app/src/context/server.tsx.
|
||||
// Poll /global/health every 10 seconds.
|
||||
// This provides a second detection channel for server death independent of the SSE heartbeat.
|
||||
const HEALTH_POLL_INTERVAL_MS = 10_000
|
||||
|
||||
@@ -514,7 +514,6 @@ export class KiloConnectionService {
|
||||
|
||||
/**
|
||||
* Start polling GET /global/health every 10 seconds.
|
||||
* Ported from packages/app/src/context/server.tsx (HEALTH_POLL_INTERVAL_MS).
|
||||
* Provides a second detection channel for server death independent of the SSE heartbeat.
|
||||
* If the health check fails while we believe we are connected, the SSE client is
|
||||
* disconnected so its reconnect loop kicks in immediately.
|
||||
|
||||
@@ -8,14 +8,14 @@ export type SSEStateHandler = (state: "connecting" | "connected" | "disconnected
|
||||
* SSE adapter that consumes the SDK's `client.global.event()` AsyncGenerator
|
||||
* and distributes events to subscribers via a pub/sub interface.
|
||||
*
|
||||
* Follows the same reconnection pattern as the app (`packages/app/src/context/global-sdk.tsx`):
|
||||
* Follows the original web client reconnection pattern:
|
||||
* - Outer `while (!aborted)` loop for reconnection
|
||||
* - Per-attempt AbortController so heartbeat timeout can cancel a stale connection
|
||||
* - Heartbeat timeout to detect zombie connections
|
||||
*
|
||||
* In this VS Code extension context the connection is localhost (extension ↔
|
||||
* child-process server), so zombie-connection scenarios are less likely than in
|
||||
* the web app (which goes through proxies/CDNs). We keep the heartbeat for
|
||||
* a browser client (which goes through proxies/CDNs). We keep the heartbeat for
|
||||
* consistency with the original strategy but use a generous 90 s timeout to
|
||||
* avoid false-positive reconnections during idle periods.
|
||||
*
|
||||
@@ -33,8 +33,7 @@ export class SdkSSEAdapter {
|
||||
private attemptController: AbortController | null = null
|
||||
private heartbeatTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
// 15s matches packages/app/src/context/global-sdk.tsx — server sends heartbeats
|
||||
// every 10s, so this gives a 5s grace window before forcing a reconnect.
|
||||
// Server sends heartbeats every 10s, so this gives a 5s grace window before forcing a reconnect.
|
||||
// Reduced from 90s: with 90s a dead connection could linger for ~1.5 minutes.
|
||||
private static readonly HEARTBEAT_TIMEOUT_MS = 15_000
|
||||
private static readonly RECONNECT_DELAY_MS = 250
|
||||
|
||||
@@ -167,8 +167,7 @@ export class ServerManager {
|
||||
|
||||
/**
|
||||
* Kill a process and its entire process group.
|
||||
* On Unix, we send the signal to -pid (negative) to reach the whole group,
|
||||
* mirroring the desktop app's ProcessGroup::leader() + start_kill() pattern.
|
||||
* On Unix, we send the signal to -pid (negative) to reach the whole group.
|
||||
* On Windows, process.kill() on the child handle is sufficient.
|
||||
*/
|
||||
private static killProcess(proc: ChildProcess, signal: NodeJS.Signals = "SIGTERM"): void {
|
||||
@@ -197,8 +196,7 @@ export class ServerManager {
|
||||
console.log("[Kilo New] ServerManager: 🔴 Disposing — sending SIGTERM to process group, PID:", proc.pid)
|
||||
ServerManager.killProcess(proc, "SIGTERM")
|
||||
|
||||
// SIGKILL fallback after 5s: mirrors the desktop app going straight to
|
||||
// start_kill(). Ensures the process tree dies even if SIGTERM is ignored
|
||||
// SIGKILL fallback after 5s. Ensures the process tree dies even if SIGTERM is ignored
|
||||
// or Instance.disposeAll() hangs past the serve.ts shutdown timeout.
|
||||
const timer = setTimeout(() => {
|
||||
if (proc.exitCode === null) {
|
||||
|
||||
@@ -52,7 +52,7 @@ async function withRepo(run: (dir: string, base: string) => Promise<void>): Prom
|
||||
describe("generatedLike", () => {
|
||||
it("matches files in ignored folders", () => {
|
||||
expect(generatedLike("node_modules/foo.js")).toBe(true)
|
||||
expect(generatedLike("packages/app/node_modules/foo/index.js")).toBe(true)
|
||||
expect(generatedLike("packages/opencode/node_modules/foo/index.js")).toBe(true)
|
||||
expect(generatedLike("dist/bundle.js")).toBe(true)
|
||||
expect(generatedLike("build/out.js")).toBe(true)
|
||||
expect(generatedLike(".git/HEAD")).toBe(true)
|
||||
|
||||
@@ -70,11 +70,8 @@ function cssVar(name: string, fallback: string): string {
|
||||
* that's the signal VS Code uses to flip `vscode-light` ↔ `vscode-dark`
|
||||
* / `vscode-high-contrast`.
|
||||
*
|
||||
* Matches the intent of opencode desktop's
|
||||
* `packages/app/src/components/terminal.tsx:236-255` approach (memo on
|
||||
* theme mode + `setOptionIfSupported(term, "theme", colors)`), just
|
||||
* driven by a MutationObserver because VS Code is the source of truth
|
||||
* here rather than their own Solid theme signal.
|
||||
* Driven by a MutationObserver because VS Code is the source of truth here
|
||||
* rather than a Solid theme signal.
|
||||
*/
|
||||
function readTheme() {
|
||||
return {
|
||||
|
||||
@@ -1503,7 +1503,6 @@ export const SessionProvider: ParentComponent = (props) => {
|
||||
})
|
||||
}
|
||||
|
||||
// Matches desktop app's event-reducer.ts: message.removed handler.
|
||||
// Splices the message from the store and deletes its parts.
|
||||
function handleMessageRemoved(sessionID: string, messageID: string) {
|
||||
setStore("messages", sessionID, (msgs = []) => msgs.filter((m) => m.id !== messageID))
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
/**
|
||||
* Prompt history navigation hook.
|
||||
* Arrow Up/Down at cursor boundaries cycles through previously sent prompts,
|
||||
* matching the behavior of the CLI TUI and the desktop app.
|
||||
* matching the behavior of the CLI TUI.
|
||||
*
|
||||
* Entries persist via localStorage (same pattern as the desktop app's
|
||||
* Persist.global), surviving webview hide/show cycles.
|
||||
* Entries persist via localStorage, surviving webview hide/show cycles.
|
||||
*/
|
||||
|
||||
import { createSignal } from "solid-js"
|
||||
|
||||
@@ -8,9 +8,6 @@ const SCOPES = [
|
||||
"packages/opencode",
|
||||
"packages/extensions",
|
||||
"packages/ui",
|
||||
"packages/app",
|
||||
"packages/desktop",
|
||||
"packages/desktop-electron",
|
||||
"packages/shared",
|
||||
"packages/script",
|
||||
"packages/storybook",
|
||||
@@ -178,9 +175,6 @@ describe("isExempt", () => {
|
||||
["packages/opencode/src/config/config.ts", false],
|
||||
["packages/opencode/src/indexing/search-service.ts", false],
|
||||
["packages/ui/src/components/icon.tsx", false],
|
||||
["packages/app/src/index.ts", false],
|
||||
["packages/desktop/src/main.ts", false],
|
||||
["packages/desktop-electron/src/main/index.ts", false],
|
||||
["sdks/vscode/src/extension.ts", false],
|
||||
["packages/extensions/zed/extension.toml", false],
|
||||
["github/script/release", false],
|
||||
@@ -199,9 +193,6 @@ describe("isChecked", () => {
|
||||
const cases: Array<[string, boolean]> = [
|
||||
["packages/opencode/src/index.ts", true],
|
||||
["packages/ui/src/components/icon.tsx", true],
|
||||
["packages/app/src/index.ts", true],
|
||||
["packages/desktop/src/main.ts", true],
|
||||
["packages/desktop-electron/src/main/index.ts", true],
|
||||
["sdks/vscode/src/extension.ts", true],
|
||||
["packages/extensions/zed/extension.toml", true],
|
||||
["packages/shared/src/index.ts", true],
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://opencode.ai/desktop-theme.json",
|
||||
"title": "OpenCode Desktop Theme",
|
||||
"description": "A theme definition for the OpenCode desktop application",
|
||||
"title": "OpenCode UI Theme",
|
||||
"description": "A theme definition for OpenCode UI components",
|
||||
"type": "object",
|
||||
"required": ["name", "id", "light", "dark"],
|
||||
"properties": {
|
||||
|
||||
@@ -39,9 +39,6 @@ const SCOPES = [
|
||||
"packages/opencode",
|
||||
"packages/extensions",
|
||||
"packages/ui",
|
||||
"packages/app",
|
||||
"packages/desktop",
|
||||
"packages/desktop-electron",
|
||||
"packages/shared",
|
||||
"packages/script",
|
||||
"packages/storybook",
|
||||
|
||||
@@ -92,8 +92,6 @@ if (Script.release) {
|
||||
// kilocode_change end
|
||||
|
||||
// kilocode_change start
|
||||
// await import(`../packages/desktop/scripts/finalize-latest-json.ts`)
|
||||
// await import(`../packages/desktop-electron/scripts/finalize-latest-yml.ts`)
|
||||
// kilocode_change end
|
||||
|
||||
// kilocode_change start - mark prerelease GitHub releases accordingly
|
||||
|
||||
@@ -31,12 +31,10 @@ const team = [
|
||||
.then((x) => x.filter((x) => x && !x.startsWith("#")))),
|
||||
...bot,
|
||||
]
|
||||
const order = ["Core", "TUI", "Desktop", "SDK", "Extensions"] as const
|
||||
const order = ["Core", "TUI", "SDK", "Extensions"] as const
|
||||
const sections = {
|
||||
core: "Core",
|
||||
tui: "TUI",
|
||||
app: "Desktop",
|
||||
tauri: "Desktop",
|
||||
sdk: "SDK",
|
||||
plugin: "SDK",
|
||||
"extensions/zed": "Extensions",
|
||||
@@ -75,7 +73,7 @@ async function diff(base: string, head: string) {
|
||||
}
|
||||
|
||||
function section(areas: Set<string>) {
|
||||
const priority = ["core", "tui", "app", "tauri", "sdk", "plugin", "extensions/zed", "extensions/vscode", "github"]
|
||||
const priority = ["core", "tui", "sdk", "plugin", "extensions/zed", "extensions/vscode", "github"]
|
||||
for (const area of priority) {
|
||||
if (areas.has(area)) return sections[area as keyof typeof sections]
|
||||
}
|
||||
@@ -116,7 +114,7 @@ async function commits(from: string, to: string) {
|
||||
}
|
||||
|
||||
const log =
|
||||
await $`git log ${base}..${head} --format=%H -- packages/opencode packages/sdk packages/plugin packages/desktop packages/app sdks/vscode packages/extensions github`.text()
|
||||
await $`git log ${base}..${head} --format=%H -- packages/opencode packages/sdk packages/plugin sdks/vscode packages/extensions github`.text()
|
||||
|
||||
const list: Commit[] = []
|
||||
for (const hash of log.split("\n").filter(Boolean)) {
|
||||
@@ -130,8 +128,6 @@ async function commits(from: string, to: string) {
|
||||
for (const file of diff.split("\n").filter(Boolean)) {
|
||||
if (file.startsWith("packages/opencode/src/cli/cmd/")) areas.add("tui")
|
||||
else if (file.startsWith("packages/opencode/")) areas.add("core")
|
||||
else if (file.startsWith("packages/desktop/src-tauri/")) areas.add("tauri")
|
||||
else if (file.startsWith("packages/desktop/") || file.startsWith("packages/app/")) areas.add("app")
|
||||
else if (file.startsWith("packages/sdk/") || file.startsWith("packages/plugin/")) areas.add("sdk")
|
||||
else if (file.startsWith("packages/extensions/")) areas.add("extensions/zed")
|
||||
else if (file.startsWith("sdks/vscode/") || file.startsWith("github/")) areas.add("extensions/vscode")
|
||||
|
||||
@@ -46,7 +46,6 @@ bun run merge.ts --version v1.1.50 --base-branch catrielmuller/kilo-opencode-v1.
|
||||
| `transforms/skip-files.ts` | Skip/remove files that shouldn't exist in Kilo |
|
||||
| `transforms/transform-i18n.ts` | Transform i18n files with Kilo branding |
|
||||
| `transforms/transform-take-theirs.ts` | Take upstream + apply Kilo branding for branding-only files |
|
||||
| `transforms/transform-tauri.ts` | Transform Tauri/Desktop config files |
|
||||
| `transforms/transform-package-json.ts` | Enhanced package.json with Kilo dependency injection |
|
||||
| `transforms/transform-scripts.ts` | Transform script files with GitHub API references |
|
||||
| `transforms/transform-extensions.ts` | Transform extension files (Zed, etc.) |
|
||||
@@ -82,7 +81,6 @@ The merge automation follows this process, applying **all transformations BEFORE
|
||||
- Preserve Kilo's versions
|
||||
- Transform i18n files with Kilo branding
|
||||
- Transform branding-only files (UI components, configs)
|
||||
- Transform Tauri/Desktop config files
|
||||
- Transform package.json files (names, deps, Kilo injections)
|
||||
- Transform script files (GitHub API references)
|
||||
- Transform extension files (Zed, etc.)
|
||||
@@ -132,19 +130,10 @@ Configuration is defined in `utils/config.ts`:
|
||||
|
||||
// Files to take upstream + apply Kilo branding transforms
|
||||
takeTheirsAndTransform: [
|
||||
"packages/app/src/components/**/*.tsx",
|
||||
"packages/app/src/context/**/*.tsx",
|
||||
"packages/ui/src/**/*.tsx",
|
||||
// ...
|
||||
],
|
||||
|
||||
// Tauri/Desktop config files
|
||||
tauriFiles: [
|
||||
"packages/desktop/src-tauri/*.json",
|
||||
"packages/desktop/src-tauri/src/*.rs",
|
||||
// ...
|
||||
],
|
||||
|
||||
// Kilo-specific directories (preserved)
|
||||
kiloDirectories: [
|
||||
"packages/opencode/src/kilocode",
|
||||
@@ -168,11 +157,10 @@ The following transforms are applied to the opencode branch before merging:
|
||||
3. **Versions** - Preserve Kilo's version numbers
|
||||
4. **i18n files** - OpenCode -> Kilo in user-visible strings
|
||||
5. **Branding files** - UI components, configs with branding only
|
||||
6. **Tauri configs** - Desktop app identifiers, names
|
||||
7. **package.json** - Names, dependencies, Kilo injections
|
||||
8. **Scripts** - GitHub API references
|
||||
9. **Extensions** - Zed, etc.
|
||||
10. **Web/docs** - Documentation files
|
||||
6. **package.json** - Names, dependencies, Kilo injections
|
||||
7. **Scripts** - GitHub API references
|
||||
8. **Extensions** - Zed, etc.
|
||||
9. **Web/docs** - Documentation files
|
||||
|
||||
### Post-Merge Strategies
|
||||
|
||||
@@ -181,8 +169,7 @@ After merging, any remaining conflicts are handled based on file type:
|
||||
| File Type | Strategy | Description |
|
||||
|---|---|---|
|
||||
| i18n files | `i18n-transform` | Take upstream, apply Kilo branding |
|
||||
| App components | `take-theirs-transform` | Take upstream, apply branding (no logic changes) |
|
||||
| Tauri configs | `tauri-transform` | Take upstream, transform identifiers/names |
|
||||
| UI components | `take-theirs-transform` | Take upstream, apply branding (no logic changes) |
|
||||
| package.json | `package-transform` | Take upstream, transform names, inject Kilo deps |
|
||||
| Script files | `script-transform` | Take upstream, transform GitHub references |
|
||||
| Extensions | `extension-transform` | Take upstream, apply branding |
|
||||
|
||||
@@ -18,7 +18,6 @@ import { error, header, info, success, warn } from "./utils/logger"
|
||||
import { transformI18nContent } from "./transforms/transform-i18n"
|
||||
import { applyBrandingTransforms } from "./transforms/transform-take-theirs"
|
||||
import { applyScriptTransforms } from "./transforms/transform-scripts"
|
||||
import { applyTauriTransforms } from "./transforms/transform-tauri"
|
||||
import { applyExtensionTransforms } from "./transforms/transform-extensions"
|
||||
import { applyWebTransforms } from "./transforms/transform-web"
|
||||
import { applyPackageNameTransforms } from "./transforms/package-names"
|
||||
@@ -149,8 +148,7 @@ async function translate(file: string, text: string) {
|
||||
const script = applyScriptTransforms(names).result
|
||||
const branded = applyBrandingTransforms(script).result
|
||||
const i18n = transformI18nContent(branded).result
|
||||
const tauri = applyTauriTransforms(i18n, file).result
|
||||
const ext = applyExtensionTransforms(tauri, file).result
|
||||
const ext = applyExtensionTransforms(i18n, file).result
|
||||
const web = applyWebTransforms(ext).result
|
||||
|
||||
return workflow(file, web)
|
||||
|
||||
@@ -35,14 +35,6 @@ export {
|
||||
matchesPattern,
|
||||
} from "./transforms/transform-take-theirs"
|
||||
|
||||
export {
|
||||
transformConflictedTauri,
|
||||
transformTauriFile,
|
||||
transformAllTauri,
|
||||
isTauriFile,
|
||||
applyTauriTransforms,
|
||||
} from "./transforms/transform-tauri"
|
||||
|
||||
export {
|
||||
transformConflictedPackageJson,
|
||||
transformPackageJson,
|
||||
|
||||
@@ -25,31 +25,18 @@ import * as logger from "./utils/logger"
|
||||
import * as version from "./utils/version"
|
||||
import * as report from "./utils/report"
|
||||
import * as worktree from "./utils/worktree"
|
||||
import { defaultConfig, loadConfig, type MergeConfig } from "./utils/config"
|
||||
import { loadConfig } from "./utils/config"
|
||||
import { transformAll as transformPackageNames } from "./transforms/package-names"
|
||||
import { preserveAllVersions } from "./transforms/preserve-versions"
|
||||
import { keepOursFiles, resetToOurs } from "./transforms/keep-ours"
|
||||
import { skipFiles, skipSpecificFiles } from "./transforms/skip-files"
|
||||
import { skipFiles } from "./transforms/skip-files"
|
||||
import { transformConflictedI18n, transformAllI18n } from "./transforms/transform-i18n"
|
||||
// New transforms for auto-resolving more conflict types
|
||||
import {
|
||||
transformConflictedTakeTheirs,
|
||||
shouldTakeTheirs,
|
||||
transformAllTakeTheirs,
|
||||
} from "./transforms/transform-take-theirs"
|
||||
import { transformConflictedTauri, isTauriFile, transformAllTauri } from "./transforms/transform-tauri"
|
||||
import {
|
||||
transformConflictedPackageJson,
|
||||
isPackageJson,
|
||||
transformAllPackageJson,
|
||||
} from "./transforms/transform-package-json"
|
||||
import { transformConflictedScripts, isScriptFile, transformAllScripts } from "./transforms/transform-scripts"
|
||||
import {
|
||||
transformConflictedExtensions,
|
||||
isExtensionFile,
|
||||
transformAllExtensions,
|
||||
} from "./transforms/transform-extensions"
|
||||
import { transformConflictedWeb, isWebFile, transformAllWeb } from "./transforms/transform-web"
|
||||
import { transformConflictedTakeTheirs, transformAllTakeTheirs } from "./transforms/transform-take-theirs"
|
||||
import { transformConflictedPackageJson, transformAllPackageJson } from "./transforms/transform-package-json"
|
||||
import { transformConflictedScripts, transformAllScripts } from "./transforms/transform-scripts"
|
||||
import { transformConflictedExtensions, transformAllExtensions } from "./transforms/transform-extensions"
|
||||
import { transformConflictedWeb, transformAllWeb } from "./transforms/transform-web"
|
||||
import { resolveLockFileConflicts, regenerateLockFiles } from "./transforms/lock-files"
|
||||
|
||||
interface MergeOptions {
|
||||
@@ -470,14 +457,6 @@ async function main() {
|
||||
logger.success(`Transformed ${brandingCount} files with Kilo branding`)
|
||||
}
|
||||
|
||||
// 6e. Transform Tauri/Desktop config files
|
||||
logger.info("Transforming Tauri/Desktop config files...")
|
||||
const tauriPreResults = await transformAllTauri({ dryRun: false, verbose: options.verbose })
|
||||
const tauriPreCount = tauriPreResults.filter((r) => r.action === "transformed" && r.replacements > 0).length
|
||||
if (tauriPreCount > 0) {
|
||||
logger.success(`Transformed ${tauriPreCount} Tauri config files`)
|
||||
}
|
||||
|
||||
// 6f. Transform package.json files (names, deps, Kilo injections)
|
||||
logger.info("Transforming package.json files...")
|
||||
const pkgPreResults = await transformAllPackageJson({ dryRun: false, verbose: options.verbose })
|
||||
@@ -629,26 +608,6 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Transform Tauri files
|
||||
conflictedFiles = await git.getConflictedFiles()
|
||||
if (conflictedFiles.length > 0) {
|
||||
const tauriResults = await transformConflictedTauri(conflictedFiles, {
|
||||
dryRun: false,
|
||||
verbose: options.verbose,
|
||||
})
|
||||
const tauriCount = tauriResults.filter((r) => r.action === "transformed").length
|
||||
if (tauriCount > 0) {
|
||||
logger.success(`Auto-resolved ${tauriCount} Tauri conflicts`)
|
||||
}
|
||||
const tauriFlagged = tauriResults.filter((r) => r.action === "flagged").map((r) => r.file)
|
||||
if (tauriFlagged.length > 0) {
|
||||
logger.warn(
|
||||
`${tauriFlagged.length} Tauri file(s) have kilocode_change markers — flagged for manual resolution`,
|
||||
)
|
||||
flaggedFiles.push(...tauriFlagged)
|
||||
}
|
||||
}
|
||||
|
||||
// Transform package.json files
|
||||
conflictedFiles = await git.getConflictedFiles()
|
||||
if (conflictedFiles.length > 0) {
|
||||
|
||||
@@ -121,28 +121,6 @@ export async function regenerateLockFiles(options: LockFileOptions = {}): Promis
|
||||
}
|
||||
}
|
||||
|
||||
// Check for Cargo.lock in Tauri package
|
||||
const cargoLockPath = "packages/desktop/src-tauri/Cargo.lock"
|
||||
const hasCargoLock = await Bun.file(cargoLockPath).exists()
|
||||
|
||||
if (hasCargoLock) {
|
||||
if (options.dryRun) {
|
||||
info("[DRY-RUN] Would regenerate Cargo.lock via 'cargo generate-lockfile'")
|
||||
results.push({ file: cargoLockPath, action: "regenerated", dryRun: true })
|
||||
} else {
|
||||
info("Regenerating Cargo.lock...")
|
||||
const result = await $`cargo generate-lockfile`.cwd("packages/desktop/src-tauri").quiet().nothrow()
|
||||
if (result.exitCode === 0) {
|
||||
success("Regenerated Cargo.lock")
|
||||
results.push({ file: cargoLockPath, action: "regenerated", dryRun: false })
|
||||
} else {
|
||||
// Cargo might not be installed, just warn
|
||||
warn(`Could not regenerate Cargo.lock (cargo may not be installed): ${result.stderr.toString()}`)
|
||||
results.push({ file: cargoLockPath, action: "skipped", dryRun: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note about nix/hashes.json - regenerated by CI, not locally
|
||||
const nixHashesPath = "nix/hashes.json"
|
||||
const hasNixHashes = await Bun.file(nixHashesPath).exists()
|
||||
|
||||
@@ -7,8 +7,8 @@ test("matches hosted package glob paths", () => {
|
||||
expect(shouldSkip("packages/console/app/package.json", ["packages/console/**"])).toBe(true)
|
||||
})
|
||||
|
||||
test("does not match sibling package glob paths", () => {
|
||||
expect(shouldSkip("packages/app/package.json", ["packages/web/**"])).toBe(false)
|
||||
test("matches removed app package glob paths", () => {
|
||||
expect(shouldSkip("packages/app/package.json", ["packages/app/**"])).toBe(true)
|
||||
})
|
||||
|
||||
test("matches extension glob paths", () => {
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
* String replacement rules:
|
||||
* - opencode.ai -> kilo.ai (domain)
|
||||
* - app.opencode.ai -> app.kilo.ai (app domain)
|
||||
* - OpenCode Desktop -> Kilo Desktop (desktop app name)
|
||||
* - OpenCode -> Kilo (product name in user-visible text)
|
||||
* - opencode upgrade -> kilo upgrade (CLI commands)
|
||||
* - npx opencode -> npx kilo (CLI invocation)
|
||||
@@ -74,13 +73,6 @@ const I18N_REPLACEMENTS: StringReplacement[] = [
|
||||
description: "Main domain (excluding zen)",
|
||||
},
|
||||
|
||||
// Product name (specific phrases first)
|
||||
{
|
||||
pattern: /OpenCode Desktop/g,
|
||||
replacement: "Kilo Desktop",
|
||||
description: "Desktop app name",
|
||||
},
|
||||
|
||||
// CLI commands (be careful with order)
|
||||
{
|
||||
pattern: /npx opencode(?!\w)/g,
|
||||
|
||||
@@ -186,10 +186,6 @@ const KILO_DEPENDENCIES: Record<string, Record<string, string>> = {
|
||||
"@kilocode/kilo-gateway": "workspace:*",
|
||||
"@kilocode/kilo-telemetry": "workspace:*",
|
||||
},
|
||||
// packages/app/package.json needs these
|
||||
"packages/app/package.json": {
|
||||
"@kilocode/kilo-i18n": "workspace:*",
|
||||
},
|
||||
}
|
||||
|
||||
// Kilo-specific bin entries to set on specific packages
|
||||
|
||||
@@ -61,13 +61,6 @@ const BRANDING_REPLACEMENTS: BrandingReplacement[] = [
|
||||
description: "Main domain (excluding zen)",
|
||||
},
|
||||
|
||||
// Product name (specific phrases first)
|
||||
{
|
||||
pattern: /OpenCode Desktop/g,
|
||||
replacement: "Kilo Desktop",
|
||||
description: "Desktop app name",
|
||||
},
|
||||
|
||||
// CLI commands
|
||||
{
|
||||
pattern: /npx opencode(?!\w)/g,
|
||||
@@ -133,16 +126,6 @@ const BRANDING_REPLACEMENTS: BrandingReplacement[] = [
|
||||
},
|
||||
]
|
||||
|
||||
// Patterns that should NOT be replaced (preserved as-is)
|
||||
const PRESERVE_PATTERNS = [
|
||||
/opencode\.json/g, // Config filename
|
||||
/\.opencode\//g, // Directory name
|
||||
/\.opencode`/g, // Directory name in template strings
|
||||
/"\.opencode"/g, // Directory name in quotes
|
||||
/'\.opencode'/g, // Directory name in single quotes
|
||||
/\/\/\s*kilocode_change/g, // Already has marker
|
||||
]
|
||||
|
||||
/**
|
||||
* Check if a file matches any of the patterns
|
||||
*/
|
||||
@@ -169,16 +152,6 @@ export function applyBrandingTransforms(content: string, verbose = false): { res
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if line has preserve patterns
|
||||
let hasPreserve = false
|
||||
for (const pattern of PRESERVE_PATTERNS) {
|
||||
pattern.lastIndex = 0
|
||||
if (pattern.test(line)) {
|
||||
hasPreserve = true
|
||||
pattern.lastIndex = 0
|
||||
}
|
||||
}
|
||||
|
||||
let result = line
|
||||
let count = 0
|
||||
|
||||
|
||||
@@ -1,363 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* Transform Tauri/Desktop config files with Kilo branding
|
||||
*
|
||||
* This script handles Tauri configuration files (JSON, TOML, Rust) by:
|
||||
* 1. Taking upstream's version as the base
|
||||
* 2. Applying predictable Kilo branding transforms
|
||||
*
|
||||
* Handles:
|
||||
* - tauri.conf.json / tauri.prod.conf.json
|
||||
* - Cargo.toml / Cargo.lock
|
||||
* - Rust source files (*.rs)
|
||||
*/
|
||||
|
||||
import { $ } from "bun"
|
||||
import { info, success, warn, debug } from "../utils/logger"
|
||||
import { defaultConfig } from "../utils/config"
|
||||
import { oursHasKilocodeChanges } from "../utils/git"
|
||||
|
||||
export interface TauriTransformResult {
|
||||
file: string
|
||||
action: "transformed" | "skipped" | "failed" | "flagged"
|
||||
replacements: number
|
||||
dryRun: boolean
|
||||
}
|
||||
|
||||
export interface TauriTransformOptions {
|
||||
dryRun?: boolean
|
||||
verbose?: boolean
|
||||
}
|
||||
|
||||
interface TauriReplacement {
|
||||
pattern: RegExp
|
||||
replacement: string
|
||||
description: string
|
||||
fileTypes?: string[] // Only apply to these file extensions
|
||||
}
|
||||
|
||||
// Tauri-specific replacements
|
||||
const TAURI_REPLACEMENTS: TauriReplacement[] = [
|
||||
// JSON config - product names
|
||||
{
|
||||
pattern: /"productName":\s*"OpenCode[^"]*"/g,
|
||||
replacement: '"productName": "Kilo"',
|
||||
description: "Product name in JSON",
|
||||
fileTypes: [".json"],
|
||||
},
|
||||
{
|
||||
pattern: /"title":\s*"OpenCode[^"]*"/g,
|
||||
replacement: '"title": "Kilo"',
|
||||
description: "Title in JSON",
|
||||
fileTypes: [".json"],
|
||||
},
|
||||
|
||||
// JSON config - identifiers
|
||||
{
|
||||
pattern: /ai\.opencode\.desktop\.dev/g,
|
||||
replacement: "ai.kilo.desktop.dev",
|
||||
description: "Dev identifier",
|
||||
},
|
||||
{
|
||||
pattern: /ai\.opencode\.desktop/g,
|
||||
replacement: "ai.kilo.desktop",
|
||||
description: "Prod identifier",
|
||||
},
|
||||
|
||||
// Binary names
|
||||
{
|
||||
pattern: /opencode-cli/g,
|
||||
replacement: "kilo-cli",
|
||||
description: "CLI binary name",
|
||||
},
|
||||
{
|
||||
pattern: /"mainBinaryName":\s*"[Oo]pen[Cc]ode"/g,
|
||||
replacement: '"mainBinaryName": "Kilo"',
|
||||
description: "Main binary name",
|
||||
fileTypes: [".json"],
|
||||
},
|
||||
|
||||
// GitHub references
|
||||
{
|
||||
pattern: /github\.com\/anomalyco\/opencode/g,
|
||||
replacement: "github.com/Kilo-Org/kilocode",
|
||||
description: "GitHub URL",
|
||||
},
|
||||
{
|
||||
pattern: /anomalyco\/opencode/g,
|
||||
replacement: "Kilo-Org/kilocode",
|
||||
description: "GitHub repo",
|
||||
},
|
||||
|
||||
// Cargo.toml specific
|
||||
{
|
||||
pattern: /name\s*=\s*"opencode-desktop"/g,
|
||||
replacement: 'name = "kilo-desktop"',
|
||||
description: "Cargo package name",
|
||||
fileTypes: [".toml"],
|
||||
},
|
||||
{
|
||||
pattern: /authors\s*=\s*\["OpenCode"\]/g,
|
||||
replacement: 'authors = ["Kilo"]',
|
||||
description: "Cargo authors",
|
||||
fileTypes: [".toml"],
|
||||
},
|
||||
{
|
||||
pattern: /name\s*=\s*"opencode_lib"/g,
|
||||
replacement: 'name = "kilo_lib"',
|
||||
description: "Cargo lib name",
|
||||
fileTypes: [".toml"],
|
||||
},
|
||||
|
||||
// Rust source specific
|
||||
{
|
||||
pattern: /opencode\.db/g,
|
||||
replacement: "kilo.db",
|
||||
description: "Database filename",
|
||||
fileTypes: [".rs"],
|
||||
},
|
||||
{
|
||||
pattern: /opencode\.settings\.dat/g,
|
||||
replacement: "kilo.settings.dat",
|
||||
description: "Settings file name",
|
||||
fileTypes: [".rs"],
|
||||
},
|
||||
{
|
||||
pattern: /"\.opencode\/bin"/g,
|
||||
replacement: '".kilo/bin"',
|
||||
description: "CLI install dir",
|
||||
fileTypes: [".rs"],
|
||||
},
|
||||
{
|
||||
pattern: /CLI_BINARY_NAME\s*=\s*"opencode"/g,
|
||||
replacement: 'CLI_BINARY_NAME = "kilo"',
|
||||
description: "CLI binary constant",
|
||||
fileTypes: [".rs"],
|
||||
},
|
||||
{
|
||||
pattern: /opencode_lib::run/g,
|
||||
replacement: "kilo_lib::run",
|
||||
description: "Lib run call",
|
||||
fileTypes: [".rs"],
|
||||
},
|
||||
{
|
||||
pattern: /killall opencode-cli/g,
|
||||
replacement: "killall kilo-cli",
|
||||
description: "Killall command",
|
||||
fileTypes: [".rs"],
|
||||
},
|
||||
|
||||
// Domain
|
||||
{
|
||||
pattern: /opencode\.ai/g,
|
||||
replacement: "kilo.ai",
|
||||
description: "Domain",
|
||||
},
|
||||
|
||||
// Environment variables (exclude OPENCODE_API_KEY)
|
||||
{
|
||||
pattern: /OPENCODE_(?!API_KEY)([A-Z_]+)/g,
|
||||
replacement: "KILO_$1",
|
||||
description: "Env variable",
|
||||
fileTypes: [".rs"],
|
||||
},
|
||||
{
|
||||
pattern: /__OPENCODE__/g,
|
||||
replacement: "__KILO__",
|
||||
description: "Window global",
|
||||
fileTypes: [".rs", ".tsx"],
|
||||
},
|
||||
{
|
||||
pattern: /OPENCODE_PORT/g,
|
||||
replacement: "KILO_PORT",
|
||||
description: "Port env var",
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* Check if a file is a Tauri config file
|
||||
*/
|
||||
export function isTauriFile(file: string): boolean {
|
||||
const patterns = defaultConfig.tauriFiles
|
||||
|
||||
return patterns.some((pattern) => {
|
||||
const regex = new RegExp("^" + pattern.replace(/\./g, "\\.").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*") + "$")
|
||||
return regex.test(file)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file extension
|
||||
*/
|
||||
function getExtension(file: string): string {
|
||||
const match = file.match(/\.[^.]+$/)
|
||||
return match ? match[0] : ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply Tauri-specific transforms to content
|
||||
*/
|
||||
export function applyTauriTransforms(
|
||||
content: string,
|
||||
file: string,
|
||||
verbose = false,
|
||||
): { result: string; replacements: number } {
|
||||
const ext = getExtension(file)
|
||||
let result = content
|
||||
let total = 0
|
||||
|
||||
for (const { pattern, replacement, description, fileTypes } of TAURI_REPLACEMENTS) {
|
||||
// Skip if this replacement is for specific file types and doesn't match
|
||||
if (fileTypes && !fileTypes.includes(ext)) {
|
||||
continue
|
||||
}
|
||||
|
||||
pattern.lastIndex = 0
|
||||
|
||||
if (pattern.test(result)) {
|
||||
pattern.lastIndex = 0
|
||||
const before = result
|
||||
result = result.replace(pattern, replacement)
|
||||
|
||||
if (before !== result) {
|
||||
total++
|
||||
if (verbose) debug(` ${description}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { result, replacements: total }
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a single Tauri file
|
||||
*/
|
||||
export async function transformTauriFile(
|
||||
file: string,
|
||||
options: TauriTransformOptions = {},
|
||||
): Promise<TauriTransformResult> {
|
||||
if (options.dryRun) {
|
||||
info(`[DRY-RUN] Would transform Tauri file: ${file}`)
|
||||
return { file, action: "transformed", replacements: 0, dryRun: true }
|
||||
}
|
||||
|
||||
// If our version has kilocode_change markers, flag for manual resolution
|
||||
if (await oursHasKilocodeChanges(file)) {
|
||||
warn(`${file} has kilocode_change markers — skipping auto-transform, needs manual resolution`)
|
||||
return { file, action: "flagged", replacements: 0, dryRun: false }
|
||||
}
|
||||
|
||||
try {
|
||||
// Take upstream's version first
|
||||
await $`git checkout --theirs ${file}`.quiet().nothrow()
|
||||
await $`git add ${file}`.quiet().nothrow()
|
||||
|
||||
// Read content
|
||||
const content = await Bun.file(file).text()
|
||||
|
||||
// Apply transforms
|
||||
const { result, replacements } = applyTauriTransforms(content, file, options.verbose)
|
||||
|
||||
// Write back if changed
|
||||
if (replacements > 0) {
|
||||
await Bun.write(file, result)
|
||||
await $`git add ${file}`.quiet().nothrow()
|
||||
}
|
||||
|
||||
success(`Transformed Tauri file ${file}: ${replacements} replacements`)
|
||||
return { file, action: "transformed", replacements, dryRun: false }
|
||||
} catch (err) {
|
||||
warn(`Failed to transform Tauri file ${file}: ${err}`)
|
||||
return { file, action: "failed", replacements: 0, dryRun: false }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform conflicted Tauri files
|
||||
*/
|
||||
export async function transformConflictedTauri(
|
||||
files: string[],
|
||||
options: TauriTransformOptions = {},
|
||||
): Promise<TauriTransformResult[]> {
|
||||
const results: TauriTransformResult[] = []
|
||||
|
||||
for (const file of files) {
|
||||
if (!isTauriFile(file)) {
|
||||
debug(`Skipping ${file} - not a Tauri file`)
|
||||
results.push({ file, action: "skipped", replacements: 0, dryRun: options.dryRun ?? false })
|
||||
continue
|
||||
}
|
||||
|
||||
const result = await transformTauriFile(file, options)
|
||||
results.push(result)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform all Tauri files (pre-merge, on opencode branch)
|
||||
*/
|
||||
export async function transformAllTauri(options: TauriTransformOptions = {}): Promise<TauriTransformResult[]> {
|
||||
const { Glob } = await import("bun")
|
||||
const results: TauriTransformResult[] = []
|
||||
const patterns = defaultConfig.tauriFiles
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const glob = new Glob(pattern)
|
||||
|
||||
for await (const path of glob.scan({ absolute: false })) {
|
||||
const file = Bun.file(path)
|
||||
if (!(await file.exists())) continue
|
||||
|
||||
try {
|
||||
const content = await file.text()
|
||||
const { result, replacements } = applyTauriTransforms(content, path, options.verbose)
|
||||
|
||||
if (replacements > 0 && !options.dryRun) {
|
||||
await Bun.write(path, result)
|
||||
success(`Transformed Tauri ${path}: ${replacements} replacements`)
|
||||
} else if (options.dryRun && replacements > 0) {
|
||||
info(`[DRY-RUN] Would transform Tauri ${path}: ${replacements} replacements`)
|
||||
}
|
||||
|
||||
results.push({ file: path, action: "transformed", replacements, dryRun: options.dryRun ?? false })
|
||||
} catch (err) {
|
||||
warn(`Failed to transform Tauri ${path}: ${err}`)
|
||||
results.push({ file: path, action: "failed", replacements: 0, dryRun: options.dryRun ?? false })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// CLI entry point
|
||||
if (import.meta.main) {
|
||||
const args = process.argv.slice(2)
|
||||
const dryRun = args.includes("--dry-run")
|
||||
const verbose = args.includes("--verbose")
|
||||
|
||||
const files = args.filter((a) => !a.startsWith("--"))
|
||||
|
||||
if (files.length === 0) {
|
||||
info("Usage: transform-tauri.ts [--dry-run] [--verbose] <file1> <file2> ...")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (dryRun) {
|
||||
info("Running in dry-run mode")
|
||||
}
|
||||
|
||||
const results = await transformConflictedTauri(files, { dryRun, verbose })
|
||||
|
||||
const transformed = results.filter((r) => r.action === "transformed")
|
||||
const total = results.reduce((sum, r) => sum + r.replacements, 0)
|
||||
|
||||
console.log()
|
||||
success(`Transformed ${transformed.length} Tauri files with ${total} replacements`)
|
||||
|
||||
if (dryRun) {
|
||||
info("Run without --dry-run to apply changes")
|
||||
}
|
||||
}
|
||||
@@ -56,11 +56,6 @@ const WEB_REPLACEMENTS: WebReplacement[] = [
|
||||
},
|
||||
|
||||
// Product names
|
||||
{
|
||||
pattern: /OpenCode Desktop/g,
|
||||
replacement: "Kilo Desktop",
|
||||
description: "Desktop name",
|
||||
},
|
||||
{
|
||||
pattern: /\bOpenCode\b(?!\.json|\/| Zen)/g,
|
||||
replacement: "Kilo",
|
||||
|
||||
@@ -21,9 +21,6 @@ export interface MergeConfig {
|
||||
/** Files that should take upstream version and apply Kilo branding transforms */
|
||||
takeTheirsAndTransform: string[]
|
||||
|
||||
/** Tauri/Desktop config files with predictable branding patterns */
|
||||
tauriFiles: string[]
|
||||
|
||||
/** Script files with GitHub API references */
|
||||
scriptFiles: string[]
|
||||
|
||||
@@ -142,6 +139,9 @@ export const defaultConfig: MergeConfig = {
|
||||
"packages/function/**",
|
||||
"packages/docs/**",
|
||||
"packages/identity/**",
|
||||
"packages/app/**",
|
||||
"packages/desktop/**",
|
||||
"packages/desktop-electron/**",
|
||||
// GitHub Action - Kilo version is fully ported and complete
|
||||
"github/index.ts",
|
||||
"github/package.json",
|
||||
@@ -153,27 +153,9 @@ export const defaultConfig: MergeConfig = {
|
||||
// Files that should take upstream version and apply Kilo branding transforms
|
||||
// These are files with only branding differences, no logic changes
|
||||
takeTheirsAndTransform: [
|
||||
// App components with branding only
|
||||
"packages/app/src/components/**/*.tsx",
|
||||
"packages/app/src/context/**/*.tsx",
|
||||
"packages/app/src/pages/**/*.tsx",
|
||||
// UI components
|
||||
"packages/ui/src/components/**/*.tsx",
|
||||
"packages/ui/src/context/**/*.tsx",
|
||||
// Desktop TypeScript files (not Rust)
|
||||
"packages/desktop/src/**/*.ts",
|
||||
// E2E and test fixtures
|
||||
"packages/app/e2e/**/*.ts",
|
||||
"packages/app/script/**/*.ts",
|
||||
],
|
||||
|
||||
// Tauri/Desktop config files with predictable branding patterns
|
||||
tauriFiles: [
|
||||
"packages/desktop/src-tauri/tauri.conf.json",
|
||||
"packages/desktop/src-tauri/tauri.prod.conf.json",
|
||||
"packages/desktop/src-tauri/Cargo.toml",
|
||||
"packages/desktop/src-tauri/Cargo.lock",
|
||||
"packages/desktop/src-tauri/src/*.rs",
|
||||
],
|
||||
|
||||
// Script files with GitHub API references
|
||||
@@ -228,7 +210,7 @@ export const defaultConfig: MergeConfig = {
|
||||
originRemote: "origin",
|
||||
|
||||
// i18n translation files that need Kilo branding transforms
|
||||
i18nPatterns: ["packages/*/src/i18n/*.ts", "packages/desktop/src/i18n/*.ts"],
|
||||
i18nPatterns: ["packages/*/src/i18n/*.ts"],
|
||||
}
|
||||
|
||||
export function loadConfig(overrides?: Partial<MergeConfig>): MergeConfig {
|
||||
|
||||
@@ -11,7 +11,7 @@ test("recommends skip for hosted package globs", () => {
|
||||
})
|
||||
|
||||
test("does not recommend skip for unrelated packages", () => {
|
||||
expect(getRecommendation("packages/app/package.json", [], ["packages/web/**"]).recommendation).toBe(
|
||||
expect(getRecommendation("packages/ui/package.json", [], ["packages/web/**"]).recommendation).toBe(
|
||||
"package-transform",
|
||||
)
|
||||
})
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface ConflictReport {
|
||||
|
||||
export interface ConflictFile {
|
||||
path: string
|
||||
type: "markdown" | "package" | "code" | "config" | "i18n" | "tauri" | "script" | "extension" | "web" | "other"
|
||||
type: "markdown" | "package" | "code" | "config" | "i18n" | "script" | "extension" | "web" | "other"
|
||||
recommendation:
|
||||
| "keep-ours"
|
||||
| "keep-theirs"
|
||||
@@ -29,7 +29,6 @@ export interface ConflictFile {
|
||||
| "skip"
|
||||
| "i18n-transform"
|
||||
| "take-theirs-transform"
|
||||
| "tauri-transform"
|
||||
| "package-transform"
|
||||
| "script-transform"
|
||||
| "extension-transform"
|
||||
@@ -45,16 +44,6 @@ function isI18nFile(path: string): boolean {
|
||||
return /packages\/[^/]+\/src\/i18n\/[^/]+\.ts$/.test(path) && !path.endsWith("/index.ts")
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a file is a Tauri/Desktop config file
|
||||
*/
|
||||
function isTauriFile(path: string): boolean {
|
||||
return (
|
||||
path.includes("packages/desktop/src-tauri/") &&
|
||||
(path.endsWith(".json") || path.endsWith(".toml") || path.endsWith(".rs") || path.endsWith(".lock"))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a file is a script file
|
||||
*/
|
||||
@@ -80,17 +69,7 @@ function isWebFile(path: string): boolean {
|
||||
* Check if a file should use take-theirs + transform strategy
|
||||
*/
|
||||
function shouldTakeTheirsTransform(path: string): boolean {
|
||||
const patterns = [
|
||||
/^packages\/app\/src\/components\/.*\.tsx$/,
|
||||
/^packages\/app\/src\/context\/.*\.tsx$/,
|
||||
/^packages\/app\/src\/pages\/.*\.tsx$/,
|
||||
/^packages\/ui\/src\/.*\.tsx$/,
|
||||
/^packages\/desktop\/src\/.*\.ts$/,
|
||||
/^packages\/app\/e2e\/.*\.ts$/,
|
||||
/^packages\/app\/script\/.*\.ts$/,
|
||||
/^github\/index\.ts$/,
|
||||
/^packages\/slack\/src\/.*\.ts$/,
|
||||
]
|
||||
const patterns = [/^packages\/ui\/src\/.*\.tsx$/, /^github\/index\.ts$/, /^packages\/slack\/src\/.*\.ts$/]
|
||||
return patterns.some((p) => p.test(path))
|
||||
}
|
||||
|
||||
@@ -99,7 +78,6 @@ function shouldTakeTheirsTransform(path: string): boolean {
|
||||
*/
|
||||
export function classifyFile(path: string): ConflictFile["type"] {
|
||||
if (isI18nFile(path)) return "i18n"
|
||||
if (isTauriFile(path)) return "tauri"
|
||||
if (isScriptFile(path)) return "script"
|
||||
if (isExtensionFile(path)) return "extension"
|
||||
if (isWebFile(path)) return "web"
|
||||
@@ -194,17 +172,6 @@ export function getRecommendation(
|
||||
recommendation: "i18n-transform",
|
||||
reason: "i18n file: take upstream translations and apply Kilo branding",
|
||||
}
|
||||
case "tauri":
|
||||
if (currentContent?.includes("kilocode_change")) {
|
||||
return {
|
||||
recommendation: "manual",
|
||||
reason: "Tauri config has kilocode_change markers — auto-transform skipped, needs manual review",
|
||||
}
|
||||
}
|
||||
return {
|
||||
recommendation: "tauri-transform",
|
||||
reason: "Tauri config: take upstream and apply Kilo branding transforms",
|
||||
}
|
||||
case "script":
|
||||
if (currentContent?.includes("kilocode_change")) {
|
||||
return {
|
||||
@@ -348,7 +315,6 @@ export function generateMarkdownReport(report: ConflictReport): string {
|
||||
"skip",
|
||||
"i18n-transform",
|
||||
"take-theirs-transform",
|
||||
"tauri-transform",
|
||||
"package-transform",
|
||||
"script-transform",
|
||||
"extension-transform",
|
||||
@@ -367,7 +333,6 @@ export function generateMarkdownReport(report: ConflictReport): string {
|
||||
skip: "Skip (Auto-Remove)",
|
||||
"i18n-transform": "i18n Transform (Auto-Apply Kilo Branding)",
|
||||
"take-theirs-transform": "Take Upstream + Kilo Branding (Auto)",
|
||||
"tauri-transform": "Tauri Config Transform (Auto)",
|
||||
"package-transform": "Package.json Transform (Auto)",
|
||||
"script-transform": "Script Transform (Auto)",
|
||||
"extension-transform": "Extension Transform (Auto)",
|
||||
|
||||
Reference in New Issue
Block a user