Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cli-ctrl-c-clear-input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kilocode/cli": patch
---

Clear input field when Ctrl+C is pressed
5 changes: 5 additions & 0 deletions .changeset/cli-diagnostic-delay-skip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---

Skip VSCode-specific diagnostic operations in CLI mode for improved performance
68 changes: 68 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,74 @@ For mode-specific guidance, see the following files:

- **Translate mode**: `.roo/rules-translate/AGENTS.md` - Translation and localization guidelines

## Changesets

Each PR requires a changeset unless it's documentation-only or internal tooling. Create one with:

```bash
pnpm changeset
```

Format (in `.changeset/<random-name>.md`):

```md
---
"kilo-code": patch
---

Brief description of the change
```

- Use `patch` for fixes, `minor` for features, `major` for breaking changes
- For CLI changes, use `"@kilocode/cli": patch` instead

Keep changesets concise but well-written as they become part of release notes.

## Fork Merge Process

Kilo Code is a fork of [Roo Code](https://github.com/RooVetGit/Roo-Code). We periodically merge upstream changes using scripts in `scripts/kilocode/`.

## kilocode_change Markers

To minimize merge conflicts when syncing with upstream, mark Kilo Code-specific changes in shared code with `kilocode_change` comments.

**Single line:**
```typescript
const value = 42 // kilocode_change
```

**Multi-line:**
```typescript
// kilocode_change start
const foo = 1
const bar = 2
// kilocode_change end
```

**New files:**
```typescript
// kilocode_change - new file
```

### When markers are NOT needed

Code in these directories is Kilo Code-specific and doesn't need markers:

- `cli/` - CLI package
- `jetbrains/` - JetBrains plugin
- Any path containing `kilocode` in filename or directory name
- `src/services/ghost/` - Ghost service

### When markers ARE needed

All modifications to core extension code (files that exist in upstream Roo Code) require markers:

- `src/` (except Kilo-specific subdirectories listed above)
- `webview-ui/`
- `packages/` (shared packages)

Keep changes to core extension code minimal to reduce merge conflicts during upstream syncs.

## Code Quality Rules

1. Test Coverage:
Expand Down
36 changes: 36 additions & 0 deletions cli/src/state/atoms/__tests__/keyboard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1114,5 +1114,41 @@ describe("keypress atoms", () => {
expect(store.get(exitPromptVisibleAtom)).toBe(false)
expect(store.get(exitRequestCounterAtom)).toBe(1)
})

it("should clear text buffer when Ctrl+C is pressed", async () => {
// Type some text first
const chars = ["t", "e", "s", "t"]
for (const char of chars) {
const key: Key = {
name: char,
sequence: char,
ctrl: false,
meta: false,
shift: false,
paste: false,
}
store.set(keyboardHandlerAtom, key)
}

// Verify we have text in the buffer
expect(store.get(textBufferStringAtom)).toBe("test")

// Press Ctrl+C
const ctrlCKey: Key = {
name: "c",
sequence: "\u0003",
ctrl: true,
meta: false,
shift: false,
paste: false,
}
await store.set(keyboardHandlerAtom, ctrlCKey)

// Text buffer should be cleared
expect(store.get(textBufferStringAtom)).toBe("")

// Exit prompt should be visible
expect(store.get(exitPromptVisibleAtom)).toBe(true)
})
})
})
1 change: 1 addition & 0 deletions cli/src/state/atoms/keyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,7 @@ function handleGlobalHotkeys(get: Getter, set: Setter, key: Key): boolean {
case "c":
if (key.ctrl) {
set(triggerExitConfirmationAtom)
set(clearTextBufferAtom)
return true
}
break
Expand Down
42 changes: 26 additions & 16 deletions src/integrations/editor/DiffViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,10 @@ export class DiffViewProvider {
}> {
const absolutePath = path.resolve(this.cwd, relPath)

// kilocode_change start: In CLI mode, skip VSCode-specific operations (diagnostics are mocked)
const skipVscodeOps = process.env.KILO_CLI_MODE === "true"
// kilocode_change end

// Get diagnostics before editing the file
this.preDiagnostics = vscode.languages.getDiagnostics()

Expand All @@ -696,28 +700,34 @@ export class DiffViewProvider {

// Open the document to ensure diagnostics are loaded
// When openFile is false (PREVENT_FOCUS_DISRUPTION enabled), we only open in memory
if (openFile) {
// Show the document in the editor
await vscode.window.showTextDocument(vscode.Uri.file(absolutePath), {
preview: false,
preserveFocus: true,
})
} else {
// Just open the document in memory to trigger diagnostics without showing it
const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(absolutePath))
// kilocode_change start: Skip document opening in CLI mode
if (!skipVscodeOps) {
// kilocode_change end
if (openFile) {
// Show the document in the editor
await vscode.window.showTextDocument(vscode.Uri.file(absolutePath), {
preview: false,
preserveFocus: true,
})
} else {
// Just open the document in memory to trigger diagnostics without showing it
const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(absolutePath))

// Save the document to ensure VSCode recognizes it as saved and triggers diagnostics
if (doc.isDirty) {
await doc.save()
}
// Save the document to ensure VSCode recognizes it as saved and triggers diagnostics
if (doc.isDirty) {
await doc.save()
}

// Force a small delay to ensure diagnostics are triggered
await new Promise((resolve) => setTimeout(resolve, 100))
// Force a small delay to ensure diagnostics are triggered
await new Promise((resolve) => setTimeout(resolve, 100))
}
}

let newProblemsMessage = ""

if (diagnosticsEnabled) {
// kilocode_change start: Skip diagnostic delay in CLI mode
if (diagnosticsEnabled && !skipVscodeOps) {
// kilocode_change end
// Add configurable delay to allow linters time to process
const safeDelayMs = Math.max(0, writeDelayMs)

Expand Down
68 changes: 68 additions & 0 deletions src/integrations/editor/__tests__/DiffViewProvider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,12 +358,25 @@ describe("DiffViewProvider", () => {
})

describe("saveDirectly method", () => {
const originalCliMode = process.env.KILO_CLI_MODE

beforeEach(() => {
// Ensure tests run in non-CLI mode by default
delete process.env.KILO_CLI_MODE
// Mock vscode functions
vi.mocked(vscode.window.showTextDocument).mockResolvedValue({} as any)
vi.mocked(vscode.languages.getDiagnostics).mockReturnValue([])
})

afterEach(() => {
// Restore original environment
if (originalCliMode === undefined) {
delete process.env.KILO_CLI_MODE
} else {
process.env.KILO_CLI_MODE = originalCliMode
}
})

it("should write content directly to file without opening diff view", async () => {
const mockDelay = vi.mocked(delay)
mockDelay.mockClear()
Expand Down Expand Up @@ -517,4 +530,59 @@ describe("DiffViewProvider", () => {
expect(vscode.languages.getDiagnostics).toHaveBeenCalled()
})
})

describe("CLI mode optimization", () => {
const originalEnv = process.env.KILO_CLI_MODE

afterEach(() => {
// Restore original environment
if (originalEnv === undefined) {
delete process.env.KILO_CLI_MODE
} else {
process.env.KILO_CLI_MODE = originalEnv
}
})

describe("saveDirectly in CLI mode", () => {
beforeEach(() => {
vi.mocked(vscode.window.showTextDocument).mockResolvedValue({} as any)
vi.mocked(vscode.languages.getDiagnostics).mockReturnValue([])
})

it("should skip diagnostic delay when KILO_CLI_MODE is true", async () => {
process.env.KILO_CLI_MODE = "true"
const mockDelay = vi.mocked(delay)
mockDelay.mockClear()
vi.mocked(vscode.languages.getDiagnostics).mockClear()

await diffViewProvider.saveDirectly("test.ts", "new content", true, true, 2000)

// In CLI mode, delay should NOT be called even when diagnosticsEnabled is true
expect(mockDelay).not.toHaveBeenCalled()
// getDiagnostics should only be called once for pre-diagnostics, not for post-diagnostics
expect(vscode.languages.getDiagnostics).toHaveBeenCalledTimes(1)
})

it("should apply diagnostic delay when KILO_CLI_MODE is not set", async () => {
delete process.env.KILO_CLI_MODE
const mockDelay = vi.mocked(delay)
mockDelay.mockClear()

await diffViewProvider.saveDirectly("test.ts", "new content", true, true, 2000)

// Without CLI mode, delay should be called
expect(mockDelay).toHaveBeenCalledWith(2000)
})

it("should skip document opening in CLI mode when openFile is false", async () => {
process.env.KILO_CLI_MODE = "true"
vi.mocked(vscode.workspace.openTextDocument).mockClear()

await diffViewProvider.saveDirectly("test.ts", "new content", false, true, 1000)

// In CLI mode with openFile=false, should not open document at all
expect(vscode.workspace.openTextDocument).not.toHaveBeenCalled()
})
})
})
})