diff --git a/.changeset/agent-manager-inherit-model.md b/.changeset/agent-manager-inherit-model.md new file mode 100644 index 00000000000..4753b1647e4 --- /dev/null +++ b/.changeset/agent-manager-inherit-model.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": patch +--- + +Inherit the current model and reasoning variant when Agent Manager starts sessions without explicit overrides. diff --git a/.changeset/am-dialog-popover-clipping.md b/.changeset/am-dialog-popover-clipping.md deleted file mode 100644 index 66fc9803756..00000000000 --- a/.changeset/am-dialog-popover-clipping.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Fix the reasoning-variant and mode dropdowns being clipped inside the New Worktree dialog. The dialog's scroll-container overflow escape now covers all inline selector popovers, not just the model picker, so dropdowns render fully above the prompt input. diff --git a/.changeset/clear-model-usage.md b/.changeset/clear-model-usage.md new file mode 100644 index 00000000000..aa29fe3bcdc --- /dev/null +++ b/.changeset/clear-model-usage.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": patch +--- + +Make session model usage easier to scan with collapsible summary rows and aligned steps and cost columns. diff --git a/.changeset/cloud-fork-session-import.md b/.changeset/cloud-fork-session-import.md deleted file mode 100644 index a8fd70504fd..00000000000 --- a/.changeset/cloud-fork-session-import.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": patch ---- - -Fix cloud session fork commands so they import cloud sessions before validating the local session. diff --git a/.changeset/config-file-substitution-trust.md b/.changeset/config-file-substitution-trust.md deleted file mode 100644 index 3bbc2bfd358..00000000000 --- a/.changeset/config-file-substitution-trust.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": patch ---- - -Harden config credential substitution against untrusted project config. Environment references (`{env:VAR}`) now resolve only in trusted config (global config, `KILO_CONFIG`, `KILO_CONFIG_CONTENT`, and org/MDM-managed config); a project-committed `kilo.json` / `opencode.json` can no longer use them. File references (`{file:...}`) still work in project config but are confined to the project root, so absolute paths, `../` traversal, and symlink escapes are rejected. This closes a path where a malicious repository could exfiltrate local secrets to an attacker-controlled `baseURL`. diff --git a/.changeset/console-navbar-kilo-logo.md b/.changeset/console-navbar-kilo-logo.md deleted file mode 100644 index 04f108b4abd..00000000000 --- a/.changeset/console-navbar-kilo-logo.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-console": patch ---- - -Replace the hardcoded "K" letter in the Kilo Console navbar with the real Kilo logo, and drop the redundant "Kilo" wordmark since the logo already carries the name. diff --git a/.changeset/console-profile-external-link-icons.md b/.changeset/console-profile-external-link-icons.md deleted file mode 100644 index 1faeccf4daf..00000000000 --- a/.changeset/console-profile-external-link-icons.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-web-ui": patch ---- - -Show trailing external-link icons for Kilo Console profile links that open in a new tab. \ No newline at end of file diff --git a/.changeset/faster-vscode-settings-save.md b/.changeset/faster-vscode-settings-save.md new file mode 100644 index 00000000000..9724da4aef4 --- /dev/null +++ b/.changeset/faster-vscode-settings-save.md @@ -0,0 +1,6 @@ +--- +"kilo-code": patch +"@kilocode/cli": patch +--- + +Speed up VS Code settings saves by draining pending prompts and disposing worktree instances concurrently. diff --git a/.changeset/fix-bedrock-empty-output.md b/.changeset/fix-bedrock-empty-output.md deleted file mode 100644 index bfc268e6840..00000000000 --- a/.changeset/fix-bedrock-empty-output.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": patch ---- - -Fix Amazon Bedrock models returning no output. A smithy dependency version-skew made the Bedrock event-stream decoder silently fail under the browser build condition, so every Bedrock request completed with an empty response. diff --git a/.changeset/fix-bedrock-sso.md b/.changeset/fix-bedrock-sso.md new file mode 100644 index 00000000000..b3da606f8ce --- /dev/null +++ b/.changeset/fix-bedrock-sso.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": patch +--- + +Resolve AWS Bedrock credentials from SSO profiles in packaged CLI builds. diff --git a/.changeset/fix-jetbrains-prompt-submit.md b/.changeset/fix-jetbrains-prompt-submit.md deleted file mode 100644 index d028d74abcb..00000000000 --- a/.changeset/fix-jetbrains-prompt-submit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Fix prompt submission in JetBrains IDEs when sending messages with file or git-change mentions. diff --git a/.changeset/fix-jetbrains-provider-action-clicks.md b/.changeset/fix-jetbrains-provider-action-clicks.md deleted file mode 100644 index c026cc8bb12..00000000000 --- a/.changeset/fix-jetbrains-provider-action-clicks.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Fix unreliable clicks on inline action buttons (Connect, OAuth, Disconnect, Enable) in the JetBrains provider, agent, and MCP settings lists so the whole button is clickable. diff --git a/.changeset/fix-prompt-bidi-multiline.md b/.changeset/fix-prompt-bidi-multiline.md deleted file mode 100644 index c20e46b5e04..00000000000 --- a/.changeset/fix-prompt-bidi-multiline.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Fix multiline bidirectional prompt input rendering and file mention arrow navigation. diff --git a/.changeset/focused-jetbrains-prompt.md b/.changeset/focused-jetbrains-prompt.md deleted file mode 100644 index 17d01ce6f67..00000000000 --- a/.changeset/focused-jetbrains-prompt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Show a focus outline around the JetBrains prompt input. diff --git a/.changeset/hide-gpt-5-5-pro.md b/.changeset/hide-gpt-5-5-pro.md new file mode 100644 index 00000000000..68aa099db8e --- /dev/null +++ b/.changeset/hide-gpt-5-5-pro.md @@ -0,0 +1,6 @@ +--- +"@kilocode/cli": patch +"kilo-code": patch +--- + +Hide gpt-5.5-pro from the model picker when using ChatGPT OAuth login, since Codex rejects it with HTTP 400. diff --git a/.changeset/inline-read-images.md b/.changeset/inline-read-images.md deleted file mode 100644 index 7d4a511f0d0..00000000000 --- a/.changeset/inline-read-images.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": minor ---- - -Render images inline in the chat view when the agent reads an image file. Images appear below the read tool card and can be clicked to open a full-size preview. diff --git a/.changeset/jetbrains-dialog-focus-prompt.md b/.changeset/jetbrains-dialog-focus-prompt.md new file mode 100644 index 00000000000..18c2d076855 --- /dev/null +++ b/.changeset/jetbrains-dialog-focus-prompt.md @@ -0,0 +1,5 @@ +--- +"@kilocode/kilo-jetbrains": patch +--- + +Return keyboard focus to the JetBrains prompt after clicking inline session dialog actions. diff --git a/.changeset/jetbrains-download-cli.md b/.changeset/jetbrains-download-cli.md deleted file mode 100644 index 82f2b3bb0bd..00000000000 --- a/.changeset/jetbrains-download-cli.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Reduce JetBrains plugin size by downloading the Kilo Core release on first connect. diff --git a/.changeset/jetbrains-inline-subagents.md b/.changeset/jetbrains-inline-subagents.md deleted file mode 100644 index 4928ad409bd..00000000000 --- a/.changeset/jetbrains-inline-subagents.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Show subagent tool activity inline in JetBrains session transcripts. diff --git a/.changeset/jetbrains-orphan-cli-shutdown.md b/.changeset/jetbrains-orphan-cli-shutdown.md new file mode 100644 index 00000000000..2d9ba8da78a --- /dev/null +++ b/.changeset/jetbrains-orphan-cli-shutdown.md @@ -0,0 +1,5 @@ +--- +"@kilocode/kilo-jetbrains": patch +--- + +Stop orphaned Kilo CLI processes when JetBrains IDEs close, including binaries that ignore graceful shutdown. diff --git a/.changeset/jetbrains-picker-popups.md b/.changeset/jetbrains-picker-popups.md deleted file mode 100644 index fb7f2eebea3..00000000000 --- a/.changeset/jetbrains-picker-popups.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Fix JetBrains prompt pickers so reasoning effort opens above the button and expanded model details still allow one-click model selection. diff --git a/.changeset/jetbrains-prompt-floating-toolbar.md b/.changeset/jetbrains-prompt-floating-toolbar.md deleted file mode 100644 index 2480cdf891b..00000000000 --- a/.changeset/jetbrains-prompt-floating-toolbar.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Hide the JetBrains editor floating toolbar from the Kilo prompt input. diff --git a/.changeset/jetbrains-prompt-theme.md b/.changeset/jetbrains-prompt-theme.md new file mode 100644 index 00000000000..242c56960b6 --- /dev/null +++ b/.changeset/jetbrains-prompt-theme.md @@ -0,0 +1,5 @@ +--- +"@kilocode/kilo-jetbrains": patch +--- + +Refresh the JetBrains prompt input chrome when switching IDE themes. diff --git a/.changeset/jetbrains-prompt-toolbar-focus.md b/.changeset/jetbrains-prompt-toolbar-focus.md new file mode 100644 index 00000000000..d41de49e29a --- /dev/null +++ b/.changeset/jetbrains-prompt-toolbar-focus.md @@ -0,0 +1,5 @@ +--- +"@kilocode/kilo-jetbrains": patch +--- + +Polish JetBrains prompt focus and copy toolbar positioning. diff --git a/.changeset/jetbrains-prune-old-cli.md b/.changeset/jetbrains-prune-old-cli.md deleted file mode 100644 index 07600c3f7eb..00000000000 --- a/.changeset/jetbrains-prune-old-cli.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Remove old JetBrains CLI binaries so they no longer accumulate in the IDE cache. Only the active version is kept, the downloaded archive is deleted after extraction, and reinstalling re-downloads a fresh binary. diff --git a/.changeset/jetbrains-revert-loading.md b/.changeset/jetbrains-revert-loading.md new file mode 100644 index 00000000000..b438ea8f21f --- /dev/null +++ b/.changeset/jetbrains-revert-loading.md @@ -0,0 +1,5 @@ +--- +"@kilocode/kilo-jetbrains": patch +--- + +Show rollback/redo progress inline (on the message and redo controls) with a cancel action instead of a full-screen loading overlay. diff --git a/.changeset/jetbrains-revert-redo.md b/.changeset/jetbrains-revert-redo.md new file mode 100644 index 00000000000..dc0e5cb6c3e --- /dev/null +++ b/.changeset/jetbrains-revert-redo.md @@ -0,0 +1,6 @@ +--- +"@kilocode/kilo-jetbrains": patch +"kilo-code": patch +--- + +Support rollback and redo controls in JetBrains sessions and clarify when reverted changes can be redone. diff --git a/.changeset/jetbrains-rollback-snapshot-notice.md b/.changeset/jetbrains-rollback-snapshot-notice.md new file mode 100644 index 00000000000..718edaa047f --- /dev/null +++ b/.changeset/jetbrains-rollback-snapshot-notice.md @@ -0,0 +1,5 @@ +--- +"@kilocode/kilo-jetbrains": patch +--- + +Clarify in JetBrains rollback that only the conversation was reverted when snapshots are disabled. diff --git a/.changeset/jetbrains-shell-tooltip-padding.md b/.changeset/jetbrains-shell-tooltip-padding.md deleted file mode 100644 index 6ab1a926d97..00000000000 --- a/.changeset/jetbrains-shell-tooltip-padding.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Balance JetBrains shell command tooltip padding when a horizontal scrollbar is present. diff --git a/.changeset/jetbrains-todo-padding.md b/.changeset/jetbrains-todo-padding.md deleted file mode 100644 index 1a3672774df..00000000000 --- a/.changeset/jetbrains-todo-padding.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Increase JetBrains todo checklist inner padding. diff --git a/.changeset/jetbrains-toolbar-tooltips.md b/.changeset/jetbrains-toolbar-tooltips.md new file mode 100644 index 00000000000..b5a9789ec40 --- /dev/null +++ b/.changeset/jetbrains-toolbar-tooltips.md @@ -0,0 +1,5 @@ +--- +"@kilocode/kilo-jetbrains": patch +--- + +Polish JetBrains session message toolbar alignment, rollback icon, and copy tooltips. diff --git a/.changeset/jetbrains-transcript-prompt-font.md b/.changeset/jetbrains-transcript-prompt-font.md deleted file mode 100644 index 21212ab9f4b..00000000000 --- a/.changeset/jetbrains-transcript-prompt-font.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Use the standard transcript font for JetBrains prompt text and custom question responses. diff --git a/.changeset/jetbrains-user-prompt-gap.md b/.changeset/jetbrains-user-prompt-gap.md new file mode 100644 index 00000000000..41a2ac21b1b --- /dev/null +++ b/.changeset/jetbrains-user-prompt-gap.md @@ -0,0 +1,5 @@ +--- +"@kilocode/kilo-jetbrains": patch +--- + +Increase spacing before non-initial user prompts in the JetBrains session transcript. diff --git a/.changeset/kilo-console-cloud-fonts.md b/.changeset/kilo-console-cloud-fonts.md deleted file mode 100644 index da077665be5..00000000000 --- a/.changeset/kilo-console-cloud-fonts.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-web-ui": patch ---- - -Use the same font stack as Kilo Cloud (Inter variable for sans, Roboto Mono variable for mono, JetBrains Mono variable as the alt-mono token) in Kilo Console. Fonts are now self-hosted as woff2 in `@kilocode/kilo-web-ui`, so Inter no longer relies on the OS having it installed. \ No newline at end of file diff --git a/.changeset/kilo-memory-cli.md b/.changeset/kilo-memory-cli.md deleted file mode 100644 index 5fb20fccced..00000000000 --- a/.changeset/kilo-memory-cli.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@kilocode/cli": minor -"@kilocode/sdk": minor -"@kilocode/kilo-memory": minor ---- - -Add opt-in project memory commands, tools, automatic capture, and public API support. diff --git a/.changeset/kilo-memory-vscode.md b/.changeset/kilo-memory-vscode.md deleted file mode 100644 index 6dc668d915d..00000000000 --- a/.changeset/kilo-memory-vscode.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": minor ---- - -Integrate project memory into the VS Code extension: memory status and controls in the Context settings tab, task-header and assistant-message affordances, the `/memory` prompt command, and Show/Toggle Project Memory command-palette entries. diff --git a/.changeset/load-initial-diff-previews.md b/.changeset/load-initial-diff-previews.md new file mode 100644 index 00000000000..d6fdf22dd54 --- /dev/null +++ b/.changeset/load-initial-diff-previews.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Load expanded diff previews consistently when opening a review. diff --git a/.changeset/prompt-mention-selection.md b/.changeset/prompt-mention-selection.md deleted file mode 100644 index f1a1c09c089..00000000000 --- a/.changeset/prompt-mention-selection.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Allow Shift+Arrow selections in the prompt input to shrink back across file mentions. diff --git a/.changeset/quiet-permissions-speak.md b/.changeset/quiet-permissions-speak.md new file mode 100644 index 00000000000..2621bf9a0c5 --- /dev/null +++ b/.changeset/quiet-permissions-speak.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Show effective permission defaults and clarify external-directory approvals in VS Code. diff --git a/.changeset/reload-instance.md b/.changeset/reload-instance.md deleted file mode 100644 index 010c58c61e0..00000000000 --- a/.changeset/reload-instance.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@kilocode/cli": minor -"kilo-code": minor ---- - -Add a reload action that reboots the per-directory instance, picking up config, skills, agents, commands, and MCP prompts changed on disk. Sessions and history are preserved. Surfaces: `/reload` in the CLI palette and editor chat, a reload button in the task header and settings panel, the `Kilo Code: Reload Config and Skills` command, and a `POST /instance/reload` HTTP endpoint. The endpoint returns 409 while a session is actively running. diff --git a/.changeset/remote-cli-provider-models.md b/.changeset/remote-cli-provider-models.md deleted file mode 100644 index 3c2c2acdf14..00000000000 --- a/.changeset/remote-cli-provider-models.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": minor ---- - -Support provider-aware model discovery and selection for remote Cloud sessions. diff --git a/.changeset/remote-tui-badge.md b/.changeset/remote-tui-badge.md deleted file mode 100644 index bb23dbb560a..00000000000 --- a/.changeset/remote-tui-badge.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": patch ---- - -Show the Remote badge in the TUI prompt status area when remote session relay is enabled. diff --git a/.changeset/remove-gpt-task-prompt.md b/.changeset/remove-gpt-task-prompt.md new file mode 100644 index 00000000000..0148b26d1d7 --- /dev/null +++ b/.changeset/remove-gpt-task-prompt.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": patch +--- + +Stop explicitly directing GPT and Codex models to delegate tasks to subagents. diff --git a/.changeset/routed-free-model-name.md b/.changeset/routed-free-model-name.md deleted file mode 100644 index 31185ae44e5..00000000000 --- a/.changeset/routed-free-model-name.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Fix the model usage panel showing just "free" for auto-routed sessions. The routed model id (e.g. `tencent/hy3:free`) is now displayed correctly instead of being collapsed to its `:free` suffix. diff --git a/.changeset/sandbox-controls-setting.md b/.changeset/sandbox-controls-setting.md new file mode 100644 index 00000000000..9d35cb50478 --- /dev/null +++ b/.changeset/sandbox-controls-setting.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Show sandbox controls only after sandboxing is enabled in settings. diff --git a/.changeset/sandbox-writable-paths-input-width.md b/.changeset/sandbox-writable-paths-input-width.md deleted file mode 100644 index b48de18dcb9..00000000000 --- a/.changeset/sandbox-writable-paths-input-width.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Widen the Additional Writable Paths input in the Sandboxing settings so longer filesystem paths are easier to read while typing. diff --git a/.changeset/serve-parent-watchdog.md b/.changeset/serve-parent-watchdog.md new file mode 100644 index 00000000000..2b2aaec1e0e --- /dev/null +++ b/.changeset/serve-parent-watchdog.md @@ -0,0 +1,6 @@ +--- +"@kilocode/cli": patch +"kilo-code": patch +--- + +Shut down the headless `kilo serve` process automatically when the editor client that launched it exits without a clean signal, preventing orphaned CLI processes. diff --git a/.changeset/tui-live-spent-cost.md b/.changeset/tui-live-spent-cost.md deleted file mode 100644 index 40d7f92bff7..00000000000 --- a/.changeset/tui-live-spent-cost.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": patch ---- - -Show live session spend in the TUI sidebar while an assistant turn is still running. diff --git a/.changeset/vim-mode-prompt-input.md b/.changeset/vim-mode-prompt-input.md deleted file mode 100644 index b12734093ad..00000000000 --- a/.changeset/vim-mode-prompt-input.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": minor ---- - -Add vim modal editing to the CLI prompt input. Enable it with `"vim": true` in `tui.jsonc`, the `Toggle vim mode` command in the command palette, or the `/vim` slash command. Supports NORMAL-mode motions (h/j/k/l, w/b/e, 0/^/$, gg/G, counts), edits (x, dd, dw, cw, D, C, r, yy/p, u, Ctrl+r), insert transitions (i/a/A/I/o/O), and VISUAL / VISUAL-LINE mode (v/V with selection-extending motions, d/x/c/s/y, o to swap ends), with a mode indicator and matching cursor shape. diff --git a/.changeset/vscode-first-send-agent-scope.md b/.changeset/vscode-first-send-agent-scope.md deleted file mode 100644 index 9cf8af712b6..00000000000 --- a/.changeset/vscode-first-send-agent-scope.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Preserve the selected mode when sending the first message in a new VS Code task so the chosen model is paired with the correct agent instructions. diff --git a/.changeset/warm-speech-capture.md b/.changeset/warm-speech-capture.md deleted file mode 100644 index 64d9be64cc6..00000000000 --- a/.changeset/warm-speech-capture.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Wait for microphone capture to start before showing voice input as recording. diff --git a/.github/actions/setup-bun/action.yml b/.github/actions/setup-bun/action.yml index 228d05c91ef..b8c9ce5479b 100644 --- a/.github/actions/setup-bun/action.yml +++ b/.github/actions/setup-bun/action.yml @@ -33,7 +33,10 @@ runs: shell: bash run: echo "dir=$(bun pm cache)" >> "$GITHUB_OUTPUT" + # Restoring and extracting the ~1 GB cache took 2m23s on Windows, while a # kilocode_change + # fresh install took 1m27s. Keep Windows off this cache until that reverses. # kilocode_change - name: Restore Bun dependencies + if: runner.os != 'Windows' # kilocode_change id: bun-cache uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: @@ -84,16 +87,24 @@ runs: # Workaround for patched peer variants # e.g. ./patches/ for standard-openapi - # https://github.com/oven-sh/bun/issues/28147 + # https://github.com/oven-sh/bun/issues/28147 # kilocode_change if [ "$RUNNER_OS" = "Windows" ]; then - bun install --linker hoisted ${{ inputs.install-flags }} + # kilocode_change start + if ! bun install --frozen-lockfile --linker hoisted ${{ inputs.install-flags }}; then + echo "::warning::Bun install failed on Windows; retrying with conservative extraction" + sleep 5 + BUN_FEATURE_FLAG_DISABLE_STREAMING_INSTALL=1 \ + bun install --frozen-lockfile --linker hoisted --network-concurrency 16 ${{ inputs.install-flags }} + fi + # kilocode_change end else - bun install ${{ inputs.install-flags }} + bun install --frozen-lockfile ${{ inputs.install-flags }} # kilocode_change fi shell: bash + # Do not upload a Windows cache that Windows jobs intentionally never restore. # kilocode_change - name: Save Bun dependencies - if: steps.bun-cache.outputs.cache-hit != 'true' && github.event_name != 'pull_request' && github.event_name != 'pull_request_target' + if: runner.os != 'Windows' && steps.bun-cache.outputs.cache-hit != 'true' && github.event_name != 'pull_request' && github.event_name != 'pull_request_target' # kilocode_change uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ${{ steps.cache.outputs.dir }} diff --git a/.github/actions/setup-linux-sandbox/action.yml b/.github/actions/setup-linux-sandbox/action.yml new file mode 100644 index 00000000000..6e3a7804204 --- /dev/null +++ b/.github/actions/setup-linux-sandbox/action.yml @@ -0,0 +1,21 @@ +# kilocode_change - new file +name: "Setup Linux Sandbox" +description: "Build the Linux bubblewrap helper" +runs: + using: "composite" + steps: + - name: Setup Zig + run: | + curl --fail --location --retry 3 \ + https://ziglang.org/download/0.14.0/zig-linux-x86_64-0.14.0.tar.xz \ + --output "$RUNNER_TEMP/zig.tar.xz" + echo "473ec26806133cf4d1918caf1a410f8403a13d979726a9045b421b685031a982 $RUNNER_TEMP/zig.tar.xz" | sha256sum --check --status + tar -xJf "$RUNNER_TEMP/zig.tar.xz" -C "$RUNNER_TEMP" + echo "$RUNNER_TEMP/zig-linux-x86_64-0.14.0" >> "$GITHUB_PATH" + shell: bash + + - name: Build bubblewrap helper + run: | + bun packages/opencode/script/kilocode/bubblewrap.ts --arch x64 --output "$RUNNER_TEMP/bwrap" + echo "KILO_BWRAP_PATH=$RUNNER_TEMP/bwrap" >> "$GITHUB_ENV" + shell: bash diff --git a/.github/workflows/codeql-kotlin.yml b/.github/workflows/codeql-kotlin.yml index 5f0cec56c87..1390cb1feea 100644 --- a/.github/workflows/codeql-kotlin.yml +++ b/.github/workflows/codeql-kotlin.yml @@ -39,6 +39,12 @@ jobs: distribution: temurin java-version: "21" + - name: Free disk space for CodeQL + shell: bash + run: | + sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/boost + docker system prune --all --force || true + - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 with: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 115e10e59f8..3b5298f99b3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -35,7 +35,9 @@ concurrency: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.version || inpu permissions: id-token: write contents: write + issues: write # kilocode_change - label automated JetBrains CLI pin bump PRs packages: write + pull-requests: write # kilocode_change - create automated JetBrains CLI pin bump PRs jobs: version: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 96c15f41d0b..7684462d27f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,6 +30,7 @@ jobs: pull-requests: read outputs: settings: ${{ steps.matrix.outputs.settings }} + general: ${{ steps.matrix.outputs.general }} steps: - name: Checkout repository if: github.event_name != 'workflow_dispatch' @@ -57,14 +58,16 @@ jobs: env: GENERAL: ${{ github.event_name != 'pull_request' || steps.filter.outputs.general == 'true' }} run: | - if [ "$GENERAL" = "true" ]; then - echo 'settings=[{"name":"linux","host":"blacksmith-4vcpu-ubuntu-2404","run":true},{"name":"macos","host":"macos-15","run":true},{"name":"windows","host":"blacksmith-4vcpu-windows-2025","run":true}]' >> "$GITHUB_OUTPUT" + if [ "$GENERAL" != "true" ]; then + echo 'general=false' >> "$GITHUB_OUTPUT" + echo 'settings=[{"os":"linux","index":1,"total":1,"host":"blacksmith-4vcpu-ubuntu-2404","run":false,"packages":false}]' >> "$GITHUB_OUTPUT" exit 0 fi - echo 'settings=[{"name":"linux","host":"blacksmith-4vcpu-ubuntu-2404","run":false}]' >> "$GITHUB_OUTPUT" + echo 'general=true' >> "$GITHUB_OUTPUT" + echo 'settings=[{"os":"linux","index":1,"total":2,"host":"blacksmith-4vcpu-ubuntu-2404","run":true,"packages":true},{"os":"linux","index":2,"total":2,"host":"blacksmith-4vcpu-ubuntu-2404","run":true,"packages":false},{"os":"macos","index":1,"total":1,"host":"macos-15","run":true,"packages":true},{"os":"windows","index":1,"total":4,"host":"blacksmith-4vcpu-windows-2025","run":true,"packages":true},{"os":"windows","index":2,"total":4,"host":"blacksmith-4vcpu-windows-2025","run":true,"packages":false},{"os":"windows","index":3,"total":4,"host":"blacksmith-4vcpu-windows-2025","run":true,"packages":false},{"os":"windows","index":4,"total":4,"host":"blacksmith-4vcpu-windows-2025","run":true,"packages":false}]' >> "$GITHUB_OUTPUT" unit: - name: unit (${{ matrix.settings.name }}) + name: ${{ !matrix.settings.run && 'unit (unchanged)' || matrix.settings.total > 1 && format('unit ({0}, {1}/{2})', matrix.settings.os, matrix.settings.index, matrix.settings.total) || format('unit ({0})', matrix.settings.os) }} needs: changes strategy: fail-fast: false @@ -107,21 +110,9 @@ jobs: uses: ./.github/actions/setup-bun # kilocode_change start - - name: Setup Zig for Linux sandbox helper + - name: Setup Linux sandbox helper if: matrix.settings.run && runner.os == 'Linux' - run: | - curl --fail --location --retry 3 \ - https://ziglang.org/download/0.14.0/zig-linux-x86_64-0.14.0.tar.xz \ - --output "$RUNNER_TEMP/zig.tar.xz" - echo "473ec26806133cf4d1918caf1a410f8403a13d979726a9045b421b685031a982 $RUNNER_TEMP/zig.tar.xz" | sha256sum --check --status - tar -xJf "$RUNNER_TEMP/zig.tar.xz" -C "$RUNNER_TEMP" - echo "$RUNNER_TEMP/zig-linux-x86_64-0.14.0" >> "$GITHUB_PATH" - - - name: Build Linux sandbox helper - if: matrix.settings.run && runner.os == 'Linux' - run: | - bun packages/opencode/script/kilocode/bubblewrap.ts --arch x64 --output "$RUNNER_TEMP/bwrap" - echo "KILO_BWRAP_PATH=$RUNNER_TEMP/bwrap" >> "$GITHUB_ENV" + uses: ./.github/actions/setup-linux-sandbox # kilocode_change end - name: Configure git identity if: matrix.settings.run @@ -133,32 +124,31 @@ jobs: if: matrix.settings.run uses: actions/cache@v5 # kilocode_change with: - path: node_modules/.cache/turbo - key: turbo-${{ runner.os }}-${{ hashFiles('turbo.json', '**/package.json') }}-${{ github.sha }} + path: .turbo/cache # kilocode_change + key: turbo-${{ runner.os }}-${{ hashFiles('turbo.json', 'bun.lock') }}-${{ matrix.settings.os }}-${{ matrix.settings.index }}-${{ github.sha }} restore-keys: | - turbo-${{ runner.os }}-${{ hashFiles('turbo.json', '**/package.json') }}- + turbo-${{ runner.os }}-${{ hashFiles('turbo.json', 'bun.lock') }}-${{ matrix.settings.os }}-${{ matrix.settings.index }}- + turbo-${{ runner.os }}-${{ hashFiles('turbo.json', 'bun.lock') }}- turbo-${{ runner.os }}- - - name: Run unit tests + - name: Run non-CLI unit tests + if: matrix.settings.run && matrix.settings.packages + run: bun turbo test:ci --filter='!@kilocode/cli' --filter='!@kilocode/kilo-jetbrains' + + - name: Run CLI unit tests if: matrix.settings.run - run: bun turbo test:ci --filter='!@kilocode/kilo-jetbrains' + run: bun turbo test:ci --filter='@kilocode/cli' env: KILO_EXPERIMENTAL_DISABLE_FILEWATCHER: ${{ runner.os == 'Windows' && 'true' || 'false' }} - KILO_TEST_PROFILE: ${{ runner.os == 'macOS' && github.event_name == 'pull_request' && 'darwin' || '' }} # kilocode_change - - # kilocode_change start - - name: Run HttpApi exerciser gates - if: matrix.settings.run && runner.os == 'Linux' - working-directory: packages/opencode - run: bun run test:httpapi - # kilocode_change end + KILO_TEST_PROFILE: ${{ matrix.settings.os == 'macos' && 'darwin' || '' }} + KILO_TEST_SHARD: ${{ format('{0}/{1}', matrix.settings.index, matrix.settings.total) }} - name: Publish unit reports # kilocode_change if: always() && matrix.settings.run uses: mikepenz/action-junit-report@bccf2e31636835cf0874589931c4116687171386 # v6.4.0 with: report_paths: packages/*/.artifacts/unit/junit.xml - check_name: "unit results (${{ matrix.settings.name }})" + annotate_only: true detailed_summary: true include_time_in_summary: true fail_on_failure: false @@ -167,13 +157,52 @@ jobs: if: always() && matrix.settings.run uses: actions/upload-artifact@v7 # kilocode_change with: - name: unit-${{ matrix.settings.name }}-${{ github.run_attempt }} + name: unit-${{ matrix.settings.os }}-${{ matrix.settings.index }}-${{ github.run_attempt }} include-hidden-files: true if-no-files-found: ignore retention-days: 7 path: packages/*/.artifacts/unit/junit.xml # kilocode_change end + # kilocode_change start + httpapi: + name: HttpApi exerciser + needs: changes + if: needs.changes.outputs.general == 'true' + runs-on: blacksmith-4vcpu-ubuntu-2404 + timeout-minutes: 15 + defaults: + run: + shell: bash + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup Bun + uses: ./.github/actions/setup-bun + + - name: Setup Linux sandbox helper + uses: ./.github/actions/setup-linux-sandbox + + - name: Configure git identity + run: | + git config --global user.email "kilo-maintainer[bot]@users.noreply.github.com" + git config --global user.name "kilo-maintainer[bot]" + + - name: Cache Turbo + uses: actions/cache@v5 + with: + path: .turbo/cache + key: turbo-${{ runner.os }}-${{ hashFiles('turbo.json', 'bun.lock') }}-httpapi-${{ github.sha }} + restore-keys: | + turbo-${{ runner.os }}-${{ hashFiles('turbo.json', 'bun.lock') }}-httpapi- + turbo-${{ runner.os }}-${{ hashFiles('turbo.json', 'bun.lock') }}- + turbo-${{ runner.os }}- + + - name: Run HttpApi exerciser gates + run: bun turbo test:httpapi --filter='@kilocode/cli' + # kilocode_change end + # kilocode_change start jetbrains: name: jetbrains @@ -185,18 +214,38 @@ jobs: # kilocode_change end # kilocode_change start + unit-required: + name: unit (linux) + runs-on: blacksmith-4vcpu-ubuntu-2404 + needs: + - unit + if: always() + steps: + - name: Verify unit matrix passed + run: | + echo "unit=${{ needs.unit.result }}" + test "${{ needs.unit.result }}" = "success" + required: name: test (linux) runs-on: blacksmith-4vcpu-ubuntu-2404 needs: + - changes - unit + - httpapi - jetbrains if: always() steps: - name: Verify upstream test jobs passed run: | echo "unit=${{ needs.unit.result }}" + echo "httpapi=${{ needs.httpapi.result }}" echo "jetbrains=${{ needs.jetbrains.result }}" test "${{ needs.unit.result }}" = "success" + if [ "${{ needs.changes.outputs.general }}" = "true" ]; then + test "${{ needs.httpapi.result }}" = "success" + else + test "${{ needs.httpapi.result }}" = "skipped" + fi test "${{ needs.jetbrains.result }}" = "success" # kilocode_change end diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index e247ee39497..81baf52d865 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -5,6 +5,10 @@ on: pull_request: types: [opened, synchronize, reopened] +permissions: + contents: read + pull-requests: read + jobs: check-paths: name: Check changed paths @@ -42,9 +46,10 @@ jobs: - name: Check baseline auto-commit permissions id: autocommit-check env: - BOT_PAT: ${{ secrets.BOT_PAT }} + MAINTAINER_APP_ID: ${{ secrets.KILO_MAINTAINER_APP_ID }} + MAINTAINER_APP_SECRET: ${{ secrets.KILO_MAINTAINER_APP_SECRET }} run: | - if [ "${{ steps.fork-check.outputs.is_fork }}" != "true" ] && [ -n "$BOT_PAT" ]; then + if [ "${{ steps.fork-check.outputs.is_fork }}" != "true" ] && [ -n "$MAINTAINER_APP_ID" ] && [ -n "$MAINTAINER_APP_SECRET" ]; then echo "can_autocommit=true" >> "$GITHUB_OUTPUT" else echo "can_autocommit=false" >> "$GITHUB_OUTPUT" @@ -63,8 +68,8 @@ jobs: uses: actions/checkout@v6 with: lfs: true - # use BOT_PAT only when later baseline pushes are allowed; github.token is read-only on Dependabot PRs. - token: ${{ secrets.BOT_PAT }} + # Use github.token for LFS access. The maintainer app is used only for generated commits. + token: ${{ github.token }} ref: ${{ github.head_ref }} - name: Checkout (read-only) @@ -171,25 +176,33 @@ jobs: exit 1 fi - - name: Commit and push new baselines (if any) + - name: Check for baseline updates if: needs.check-paths.outputs.can_autocommit == 'true' && steps.check-baseline-commit.outputs.is_baseline_update != 'true' - id: commit-baselines - env: - GH_TOKEN: ${{ secrets.BOT_PAT }} + id: baseline-changes run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" git add packages/kilo-docs/public/img/screenshot-tests/kilo-ui/ if git diff --cached --quiet; then - echo "No new baselines — nothing to commit." echo "changed=false" >> "$GITHUB_OUTPUT" else - git commit -m "chore: update visual regression baselines" - git lfs push --all origin - git push --no-verify echo "changed=true" >> "$GITHUB_OUTPUT" fi + - name: Setup Git Committer + if: steps.baseline-changes.outputs.changed == 'true' + uses: ./.github/actions/setup-git-committer + with: + kilo-maintainer-app-id: ${{ secrets.KILO_MAINTAINER_APP_ID }} + kilo-maintainer-app-secret: ${{ secrets.KILO_MAINTAINER_APP_SECRET }} + + - name: Commit and push new baselines (if any) + if: steps.baseline-changes.outputs.changed == 'true' + id: commit-baselines + run: | + git commit -m "chore: update visual regression baselines" + git lfs push --all origin + git push --no-verify origin "HEAD:${GITHUB_HEAD_REF}" + echo "changed=true" >> "$GITHUB_OUTPUT" + - name: Fail if baselines changed if: needs.check-paths.outputs.can_autocommit == 'true' && steps.commit-baselines.outputs.changed == 'true' run: | @@ -219,8 +232,8 @@ jobs: uses: actions/checkout@v6 with: lfs: true - # use BOT_PAT only when later baseline pushes are allowed; github.token is read-only on Dependabot PRs. - token: ${{ secrets.BOT_PAT }} + # Use github.token for LFS access. The maintainer app is used only for generated commits. + token: ${{ github.token }} ref: ${{ github.head_ref }} - name: Checkout (read-only) @@ -357,25 +370,33 @@ jobs: exit 1 fi - - name: Commit and push new baselines (if any) + - name: Check for baseline updates if: needs.check-paths.outputs.can_autocommit == 'true' && steps.check-baseline-commit-vscode.outputs.is_baseline_update != 'true' - id: commit-baselines-vscode - env: - GH_TOKEN: ${{ secrets.BOT_PAT }} + id: baseline-changes-vscode run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" git add packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/ if git diff --cached --quiet; then - echo "No new baselines — nothing to commit." echo "changed=false" >> "$GITHUB_OUTPUT" else - git commit -m "chore: update kilo-vscode visual regression baselines" - git lfs push --all origin - git push --no-verify echo "changed=true" >> "$GITHUB_OUTPUT" fi + - name: Setup Git Committer + if: steps.baseline-changes-vscode.outputs.changed == 'true' + uses: ./.github/actions/setup-git-committer + with: + kilo-maintainer-app-id: ${{ secrets.KILO_MAINTAINER_APP_ID }} + kilo-maintainer-app-secret: ${{ secrets.KILO_MAINTAINER_APP_SECRET }} + + - name: Commit and push new baselines (if any) + if: steps.baseline-changes-vscode.outputs.changed == 'true' + id: commit-baselines-vscode + run: | + git commit -m "chore: update kilo-vscode visual regression baselines" + git lfs push --all origin + git push --no-verify origin "HEAD:${GITHUB_HEAD_REF}" + echo "changed=true" >> "$GITHUB_OUTPUT" + - name: Fail if baselines changed if: needs.check-paths.outputs.can_autocommit == 'true' && steps.commit-baselines-vscode.outputs.changed == 'true' run: | diff --git a/.kilo/plans/1783518847053-prompt-rollback-redo-jetbrains-parity.md b/.kilo/plans/1783518847053-prompt-rollback-redo-jetbrains-parity.md new file mode 100644 index 00000000000..dd71fdffc7f --- /dev/null +++ b/.kilo/plans/1783518847053-prompt-rollback-redo-jetbrains-parity.md @@ -0,0 +1,111 @@ +# Prompt Rollback + Redo — JetBrains Parity + +Bring the VS Code "rollback / redo" (CLI `revert` / `unrevert`) feature to the JetBrains plugin, and fix the misleading VS Code hint string. Implementation-capable agent required — this plan touches source and tests. + +## Context (verified) + +- CLI already exposes everything needed; no CLI release or SDK regen required. Pinned CLI version is `7.4.1` (`packages/kilo-jetbrains/package.json`). + - `POST /session/{id}/revert` body `{ messageID, partID? }`, `POST /session/{id}/unrevert`. + - `Session.revert?: { messageID, partID?, snapshot?, diff? }` — `packages/sdk/js/src/gen/types.gen.ts:554`. Pushed via `session.updated` SSE. +- VS Code entry points: `RevertBanner.tsx`, `VscodeSessionTurn.tsx`, `session.tsx` context. Per-user-message rollback restores reverted prompt text into the input; redo banner shows count + per-file diff stats + Redo / Redo All + hint; reverted messages/parts hidden (`id >= revert.messageID`, partID partial-turn boundary). +- JetBrains split-mode packages differ from AGENTS.md's stated `ai.kilocode.jetbrains`; actual source roots are: + - shared → `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/` + - backend → `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/` + - frontend → `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/` +- All target files confirmed to exist: `SessionDto.kt`, `KiloSessionRpcApi.kt`, `KiloBackendChatManager.kt`, `KiloSessionRpcApiImpl.kt`, `KiloSessionService.kt`, `SessionController.kt`, `SessionModel.kt`, `SessionModelEvent.kt`, `SessionView.kt`, `MessageView.kt`, `MessageToolbar.kt`, `PromptPanel.kt`, `PromptEditorTextField.kt`, plus reuse targets `DiffStatBadge.kt`, `HoverIcon.kt`, `PermissionDiffView.kt`. + +## Decisions + +- **Reuse `DiffStatBadge`** (already the shared per-file +/- renderer used by `PermissionDiffView`) for the banner's per-file diff rows. No new diff-stat component. +- **Reuse the existing user-prompt hover toolbar (`MessageToolbar`)** for the per-message rollback control. +- **Reuse the existing `session.updated → model.setSession(...)` pipeline** for the revert boundary; no new SSE handling. +- **Stale non-English hint strings: reset all to the new English value** and flag for re-translation (applies to VS Code locales; JetBrains base bundle only, others fall back). + +## Part A — VS Code string fix + +File: `packages/kilo-vscode/webview-ui/src/i18n/en.ts:138` + +- Change `revert.banner.hint` from `"Send a new message to make this permanent"` to `"You can redo these changes until you send a new message"`. +- In the 19 non-English locale files under `packages/kilo-vscode/webview-ui/src/i18n/` (ar, br, bs, da, de, es, fr, it, ja, ko, nl, no, pl, ru, th, tr, uk, zh, zht), overwrite `revert.banner.hint` with the identical new English string; leave a note that these need re-translation. +- Do not touch the other revert keys (`revert.banner.count_one`, `revert.banner.count_other`, `revert.banner.redo`, `revert.banner.redo.all`, `revert.disabled.agentBusy`) — only the hint changes meaning. + +## Part B — JetBrains implementation + +### 1. Shared (DTO + RPC contract) + +- `shared/.../rpc/dto/SessionDto.kt`: add `val revert: SessionRevertDto? = null`; add `@Serializable data class SessionRevertDto(val messageID: String, val partID: String? = null, val snapshot: String? = null, val diff: String? = null)`. Field names must match CLI JSON for kotlinx auto-mapping through `session.updated`. +- `shared/.../rpc/KiloSessionRpcApi.kt`: add + - `suspend fun revert(id: String, directory: String, messageID: String, partID: String?)` + - `suspend fun unrevert(id: String, directory: String)` + +### 2. Backend (RPC impl + HTTP) + +- `backend/.../app/KiloBackendChatManager.kt`: add `revert(id, dir, messageID, partID?)` → `POST /session/$id/revert?directory=…` with `{messageID, partID?}` body, and `unrevert(id, dir)` → `POST /session/$id/unrevert?directory=…`. Mirror the existing `compact()` OkHttp pattern; add JSON body builders to `KiloCliDataParser`. +- `backend/.../rpc/KiloSessionRpcApiImpl.kt`: implement both, resolve worktree dir via `sessions.getDirectory(id, directory)`, wrap in `ready { }` like `compact`/`abort`. + +### 3. Frontend service + +- `frontend/.../app/KiloSessionService.kt`: add `suspend revert(...)` / `unrevert(...)` wrappers over the RPC, using `durable {}` per split-mode rules. + +### 4. Frontend controller (`session/controller/SessionController.kt`) + +- Add EDT-entry actions dispatched to the coroutine scope: + - `revert(messageID, partID?)` + - `unrevert()` + - `redo()` — mirror `RevertBanner.handleRedo`: find the next user message after the boundary and revert to it; `unrevert()` if none. + - `redoAll()` → `unrevert()`. +- Revert boundary already arrives via `is ChatEventDto.SessionUpdated -> model.setSession(...)` — no new SSE handling. +- On revert, restore the reverted user prompt text into the input (`session/ui/prompt/PromptPanel.kt` / `PromptEditorTextField`); on full redo (`redoAll` / final `unrevert`), clear it — matching VS Code and TUI. + +### 5. Frontend model (`session/model/SessionModel.kt`, `SessionModelEvent.kt`) + +- Add `SessionModelEvent.RevertChanged(revert: SessionRevertDto?)` with a stable `toString()`, fired from `setSession()` when the revert value changes. Reset in `loadHistory()` and `clear()`. +- Add derived helpers: `revert(): SessionRevertDto?` and `revertedCount()` (count of user messages with `id >= boundary`, mirroring `session.tsx`). +- Transcript hiding: expose whether a message id is hidden by the boundary (`id >= revert.messageID`, with partID partial-turn handling), applied both to newly-arriving messages and on `RevertChanged`. +- Follow the AGENTS.md "Adding a New Event" checklist (event subclass → model field + mutation + resets → controller handling → `-> Unit` stubs in exhaustive `when` blocks). + +### 6. Frontend views + +- New `session/ui/RevertBanner.kt` Swing view, built once and mutated via `update()` per retained-Swing rules. Listens to `RevertChanged` + `DiffUpdated`. Contents: + - arrow-left icon + count label (localized count.one/other), + - per-file diff rows reusing `ai.kilocode.client.ui.DiffStatBadge` fed from `model.diff` (`DiffFileDto.additions/deletions`), + - Redo button → `controller.redo()`, Redo All (only when count > 1) → `controller.redoAll()`, + - hint label (same new wording as Part A). + - Placed in `session/ui/SessionView.kt` at the bottom of the transcript, above the prompt input; shown only when `revert != null`. +- Per-user-message rollback control in `session/views/MessageView.kt`: user prompt bubbles already carry a hover `promptToolbar` (`MessageToolbar` with copy) + `promptHover` state. Add a rollback `HoverIcon` (arrow-left) alongside copy for user messages, wired through `SessionView → controller.revert(msgId)`. Disable/hide while the agent is busy or when there are no assistant messages after it, with the `revert.disabled.agentBusy` tooltip — mirroring `VscodeSessionTurn` `onRevert` + `data-revert-disabled`. Extend `MessageToolbar.kt` to host the extra action (or add a sibling control). +- Apply transcript hiding: `SessionView`/`TurnView` toggle `MessageView` visibility on `RevertChanged` (prefer visibility toggle over rebuild, per Swing lifecycle guidance). + +### 7. i18n (`frontend/src/main/resources/messages/KiloBundle*.properties`) + +- Add to base `KiloBundle.properties`: `revert.banner.count.one`, `revert.banner.count.other`, `revert.banner.redo`, `revert.banner.redo.all`, `revert.banner.hint` (new wording), `revert.disabled.agentBusy`. +- Other locale bundles fall back to base until translated; do not add stale translations. + +### 8. Tests + +- Backend: extend `KiloSessionRpcApiImplTest` / a `KiloBackendChatManager` test against `MockCliServer` asserting the exact HTTP path + JSON body for `revert`/`unrevert`, and that a session carrying `revert` deserializes into `SessionRevertDto`. +- Frontend controller (`SessionControllerTestBase` + `FakeSessionRpcApi`, add revert/unrevert call tracking): `revert()`/`unrevert()`/`redo()`/`redoAll()` hit the right RPC; `RevertChanged` fires; count logic; reverted messages hidden and restored; prompt-text restore on revert / clear on full redo. +- Frontend views (`BasePlatformTestCase`): `RevertBanner` renders count + per-file `DiffStatBadge` + redo buttons (Redo All only when count > 1); `MessageView` shows the rollback control on user messages and disables it while busy; retained-component assertions (`assertSame`, bounded count) per Swing lifecycle rules. + +## Deliverables + +| Area | Files | +|---|---| +| VS Code string | `webview-ui/src/i18n/en.ts` + 19 non-English locale hints reset | +| Shared | `SessionDto.kt` (+`SessionRevertDto`), `KiloSessionRpcApi.kt` | +| Backend | `KiloBackendChatManager.kt`, `KiloCliDataParser`, `KiloSessionRpcApiImpl.kt` | +| Frontend service/controller/model | `KiloSessionService.kt`, `SessionController.kt`, `SessionModel.kt`, `SessionModelEvent.kt` | +| Frontend views | new `RevertBanner.kt` (reusing `DiffStatBadge`), `MessageView.kt`, `MessageToolbar.kt`, `SessionView.kt`, `PromptPanel.kt` | +| i18n | `KiloBundle.properties` | +| Tests | backend RPC/chat-manager, frontend controller + view tests, `FakeSessionRpcApi` | + +## Validation + +- VS Code string change: `bun run typecheck` from repo root (or from `packages/kilo-vscode/`). +- JetBrains: `./gradlew typecheck` and `./gradlew test` from `packages/kilo-jetbrains/` (Java 21). +- Manual: `./gradlew runIde` (or split mode) to exercise rollback → redo → redo-all and reverted-transcript hiding. + +## Risks / Notes + +- Confirm no `kilocode_change` markers are needed for the VS Code i18n edits (Kilo-owned webview path — none required). +- Reset (not delete) is chosen for VS Code locale hints, so the fallback path is not relied upon; flag the 19 files for re-translation. +- Keep new Kotlin identifiers single-word per repo style; annotate EDT/model-touching methods with `@RequiresEdt`; wrap frontend RPC in `durable {}`. diff --git a/.kilo/plans/1783550396285-jetbrains-rollback-hardening.md b/.kilo/plans/1783550396285-jetbrains-rollback-hardening.md new file mode 100644 index 00000000000..fe9efd49150 --- /dev/null +++ b/.kilo/plans/1783550396285-jetbrains-rollback-hardening.md @@ -0,0 +1,199 @@ +# JetBrains Rollback Feature — Hardening Plan + +Harden the new JetBrains rollback/redo feature: (1) communicate when only messages were +reverted because snapshots are disabled, and (2–4) close the code-duplication, testing, +end-to-end, and AGENTS-conformance gaps. + +All work is inside `packages/kilo-jetbrains/`. No shared `packages/opencode/` files change, +so no `kilocode_change` markers are needed. + +## Context an implementer needs + +- Revert flow: user clicks the per-message Rollback button (`MessageToolbar`/`MessageView`) + → `SessionController.revert()` aborts a busy turn then POSTs `/session/:id/revert` + → CLI sets a revert marker → `SessionUpdated` event → `SessionModel.setRevert()` → + `RevertBanner` shows + reverted messages hidden. `redo()`/`redoAll()` drive + `/session/:id/revert` (next user msg) and `/session/:id/unrevert`. +- Snapshot signal: `SessionRevertDto.snapshot` (`shared/.../rpc/dto/SessionDto.kt:22`) is + `null` exactly when the server did NOT restore files (snapshots disabled: `config.snapshot + === false`, non-git worktree, or ACP). It is already parsed and reaches the frontend via + `SessionModel.revert()`. When `snapshot == null` the diff is also empty, so the banner shows + no file rows today. This is the reliable, no-new-API signal for "history only". +- Test harnesses already exist: frontend `SessionControllerTestBase` + `FakeSessionRpcApi` + (already tracks `rpc.reverts` and `rpc.unreverts`); backend `MockCliServer` + + `KiloBackendChatManagerTest`. Banner tests instantiate `RevertBanner(model, redo, redoAll)` + and read components via `components(banner)`. + +## Key files + +- `frontend/.../session/ui/RevertBanner.kt` +- `frontend/.../session/controller/SessionController.kt` (`revert`, `unrevert`, `redo`, `redoAll`, `capture`, `sessionProps`) +- `frontend/.../session/model/SessionModel.kt` (`revert()`, `revertedCount()`, `isRevertedMessage()`) +- `frontend/src/main/resources/messages/KiloBundle.properties` (+ translated `KiloBundle_*.properties`) +- `backend/.../app/KiloBackendChatManager.kt` (`revert`/`unrevert`), `backend/.../cli/KiloCliDataParser.kt` (`buildRevertJson`) +- `backend/.../app/KiloBackendSessionManager.kt` (`revertDto` duplication) +- Tests: `frontend/.../session/controller/TurnLifecycleTest.kt` (or new `RevertFlowTest.kt`), + `frontend/.../session/ui/SessionMessageListPanelTest.kt`, + `frontend/.../session/model/SessionModelTest.kt`, + `backend/.../testing/MockCliServer.kt`, `backend/.../app/KiloBackendChatManagerTest.kt`, + `backend/.../cli/KiloCliDataParserTest.kt` + +## Resolved decisions + +- **Do NOT hide/disable the Rollback button when snapshots are off.** This matches VS Code and + keeps history-only revert usable. We only add messaging. +- **Detect "files not restored" via `model.revert()?.snapshot == null`.** No new config/API, + no shared-code change. +- **Message it in the `RevertBanner`** with a distinct localized line, shown only when + `snapshot == null`. +- Proactively warning on the button before a revert (needs `config.snapshot`/`vcs` exposed to + the frontend) is **out of scope**. + +--- + +## Phase 1 — Communicate history-only revert (snapshots disabled) + +1. Add bundle key in `KiloBundle.properties` (place near the other `revert.banner.*` keys): + - `revert.banner.filesNotRestored=Snapshots are off — only the conversation was reverted; your files were not changed.` + - Add the same key to every translated `KiloBundle_*.properties` file. If translations are + not available, copy the English value (mirror how the other `revert.banner.*` keys were + seeded across bundles) so no key is missing. +2. In `RevertBanner.kt`: + - Add a retained `JBLabel` field `notice` styled like `hint` (`JBFont.small()`), created + once in `init` and added to `body` after `hint`. + - In `update()`, after computing `revert`, set + `notice.isVisible = revert.snapshot == null` and its text to + `KiloBundle.message("revert.banner.filesNotRestored")`. + - Color it via a theme API in `applyStyle` (use `UIUtil.getContextHelpForeground()` to match + the secondary `hint`; do not hardcode a color). Re-evaluate in `applyStyle` alongside + `hint.foreground`. + - Keep the existing empty-diff behavior (no file rows) — the notice replaces the missing + information rather than adding chrome. +3. Remove the redundant `isOpaque = false` on the `body` and `files` `Stack`s (see Phase 4); + do this while editing the file. + +## Phase 2 — Close code-duplication gaps + +4. In `KiloBackendSessionManager.kt`, collapse the two identical `revertDto(...)` overloads + (`SessionRevert` and `GlobalSessionRevert`) into a single private helper that maps the four + fields (`messageID`, `partID`, `snapshot`, `diff`) from a small shared shape. Simplest + approach: one private `fun revertDto(messageID: String?, partID: String?, snapshot: String?, + diff: String?)` returning `SessionRevertDto?` (null when `messageID` is null), and have both + call sites extract fields from their respective model type. Keep behavior identical; this is + a pure refactor covered by existing `KiloBackendSessionManagerTest` parsing assertions. + +## Phase 3 — Fill testing + end-to-end gaps + +### Frontend controller/model logic + +5. `SessionModelTest.kt` — add direct model tests: + - `revertedCount()` returns the number of user messages from the marker to the end + (build entries `u1,a1,u2,a2`; marker at `u1` → 2; marker at `u2` → 1; marker id absent → 0). + - `isRevertedMessage()` true for the marker message and everything after, false before. + - `snapshot`-null marker still yields a non-zero `revertedCount()` (history-only case). +6. `TurnLifecycleTest.kt` (or new `RevertFlowTest.kt`) — add controller tests using `prompted()`: + - `redo()` reverts to the NEXT user message: seed `u1,a1,u2,a2`, apply marker at `u1`, + `edt { m.redo() }`, flush → assert `rpc.reverts` last call targets `u2`. + - `redo()` at the last user message calls unrevert: marker at `u2` → `redo()` → assert + `rpc.unreverts` recorded and no new revert call. + - `redoAll()` calls unrevert: assert `rpc.unreverts` contains `("ses_test","/test")`. + - `unrevert()` clears via RPC: assert `rpc.unreverts` populated. + +### Frontend banner → controller wiring (end-to-end within the UI layer) + +7. `SessionMessageListPanelTest.kt` — add wiring + history-only tests: + - Redo button click invokes the controller: build `RevertBanner(model, redo = { redo++ }, + redoAll = { all++ })`, set a marker, `banner...first { text == redo }.doClick()` → assert + the redo lambda fired; same for Redo All when `revertedCount > 1`. + - History-only notice: marker with `SessionRevertDto("u1", snapshot = null)` → assert the + `revert.banner.filesNotRestored` label is visible and no file rows are present. + - Snapshot-present: marker with `snapshot = "snap1"` → assert the notice label is hidden. + +### Full round-trip end-to-end (frontend) + +8. Add one `RevertFlowTest` case that exercises the whole loop through `SessionControllerTestBase`: + - Seed a multi-turn session, `edt { m.revert(userMsgId) }`, flush → assert busy-abort + ordering + `rpc.reverts`. + - `emit(SessionUpdated(..., revert = SessionRevertDto(userMsgId, snapshot = "s1")))` → + assert `model.isRevertedMessage(userMsgId)` and reverted views hidden. + - Drive Redo All via `m.redoAll()`; `emit(SessionUpdated(..., revert = null))` → assert + messages restored (`isRevertedMessage` false) and marker cleared. + Use existing `emit`/`flush` helpers; no timeouts. + +### Backend HTTP contract + +9. `MockCliServer.kt` — add routing + capture fields (the current generic `/session/ses_*` + regexes are full-match and won't catch the `/revert` suffix, so new cases are safe): + - Fields: `lastRevertPath`, `lastRevertBody`, `lastUnrevertPath`, `lastUnrevertBody` + (`@Volatile var ... : String? = null`). + - Cases (place beside the `summarize`/`prompt_async` cases): + - `bare.matches(Regex("/session/ses_[^/]+/revert")) && method == "POST"` → store path+body, + `respond(output, 200, sessionCreate)`. + - `bare.matches(Regex("/session/ses_[^/]+/unrevert")) && method == "POST"` → store path+body, + `respond(output, 200, sessionCreate)`. +10. `KiloBackendChatManagerTest.kt` — mirror the existing `compact posts summarize request` test: + - `revert posts messageID and partID to revert endpoint`: call + `chat.revert("ses_abc", "/test/project", "msg1", "prt1")`; assert + `requestCount("/session/ses_abc/revert") == 1`, path starts with + `/session/ses_abc/revert?directory=`, body == `{"messageID":"msg1","partID":"prt1"}`. + - `revert omits partID when null`: body == `{"messageID":"msg1"}`. + - `unrevert posts empty body to unrevert endpoint`: body == `{}` and path prefix asserted. +11. `KiloCliDataParserTest.kt` — add `buildRevertJson` unit tests: + - `buildRevertJson("m1", null) == {"messageID":"m1"}`. + - `buildRevertJson("m1", "p1") == {"messageID":"m1","partID":"p1"}`. + - Escaping: an id containing a quote/backslash is JSON-escaped (reuse the parser's `escape`). + +## Phase 4 — AGENTS conformance cleanup + +12. Remove the unused bundle key `revert.disabled.agentBusy` from `KiloBundle.properties` and all + translated bundles (grep confirms it is referenced nowhere in Kotlin). Alternatively, if the + team wants VS Code parity, wire it as a disabled-state tooltip on the Rollback button — but + the recommended, lower-risk action is removal, since JetBrains intentionally aborts the busy + turn instead of disabling the button. +13. Remove redundant `isOpaque = false` on the transparent `Stack`s in `RevertBanner.kt` + (`body`, `files`) — `Stack` is already a no-color panel. Keep `isOpaque = false` on the + `BorderLayoutPanel` root only if it visibly overlays the session surface; otherwise remove it + too and rely on the default. +14. Add usage metrics for the new user-facing actions in `SessionController.kt`, using the + existing `capture(event, props)` + `sessionProps(id)` helpers: + - In `revert()` (on the success path, after `sessions.revert`): `capture("Session Rollback", + sessionProps(id))`. + - In `redo()`/`redoAll()`/`unrevert()`: `capture("Session Redo", sessionProps(id))` / + `capture("Session Redo All", ...)` as appropriate. Keep names consistent with existing + event naming; do not add PII. + +## Out of scope + +- Exposing `config.snapshot`/`vcs` to the frontend to proactively warn before a revert. +- Changing the retained-vs-rebuild strategy of `RevertBanner.files` (low-frequency update; + acceptable as-is). +- Replacing the full-project `ACTION_SYNCHRONIZE` in `synchronizeFromDisk` with a scoped VFS + refresh (only revisit if it shows up as a perf problem). +- Any VS Code webview parity changes beyond the shared i18n strings already present. + +## Validation + +Run from `packages/kilo-jetbrains/`: + +- `./gradlew typecheck` — compiles all Kotlin including generated client. +- `./gradlew test` — runs frontend + backend suites, including the new tests. Requires Java 21; + only check Java if Gradle reports a Java error. +- Targeted while iterating: run the touched test classes (`TurnLifecycleTest`/`RevertFlowTest`, + `SessionMessageListPanelTest`, `SessionModelTest`, `KiloBackendChatManagerTest`, + `KiloCliDataParserTest`). + +Manual smoke (optional, `./gradlew runIde`): trigger a rollback in a non-git directory (or with +`snapshot: false`) and confirm the banner shows the "files not changed" notice with no file rows; +trigger one in a git repo and confirm the notice is hidden and file rows appear. + +## Changeset + +Add `.changeset/.md` (`"@kilocode/kilo-jetbrains": patch`) describing the user-facing +change, e.g. "Clarify in JetBrains rollback that only the conversation was reverted when +snapshots are disabled." Test-only and refactor tasks do not need their own changeset. + +## Suggested order + +Phase 3 backend tests (9–11) and model tests (5) are independent and can be done first to lock +behavior. Then Phase 1 (user-facing messaging) with its banner tests (7). Then Phase 2 refactor, +Phase 4 cleanups, controller tests (6, 8), and the changeset last. diff --git a/.kilo/skills/release-jetbrains/SKILL.md b/.kilo/skills/release-jetbrains/SKILL.md index b34ec79c85e..4eb73e0f074 100644 --- a/.kilo/skills/release-jetbrains/SKILL.md +++ b/.kilo/skills/release-jetbrains/SKILL.md @@ -38,36 +38,67 @@ Show the resolved `version`, `kind`, and default `fromTagDefault` to the user. ## CLI Pin Verification -Before dispatching prepare, verify the JetBrains plugin is pinned to the intended Kilo Core release. The plugin downloads the CLI version from `packages/kilo-jetbrains/package.json`, not from the JetBrains plugin version. +Before dispatching prepare, verify the JetBrains plugin is pinned to the intended Kilo Core release. The plugin downloads the CLI version from `packages/kilo-jetbrains/package.json`, not from the JetBrains plugin version. Prepare tags `origin/main`, so the authoritative pin is the value on `origin/main`, not a local edit. -Read the pinned CLI version: +Run the pin preflight: ```bash -bun -e 'const p=require("./packages/kilo-jetbrains/package.json"); console.log(p.version)' +bun .kilo/skills/release-jetbrains/script/check-pin.ts ``` -Verify the matching GitHub Release exists and includes every runtime asset the backend may download: +The script prints: + +| Field | Meaning | +|---|---| +| `pinMain` | CLI version that `origin/main` will lock into the release tag. | +| `pinLocal` | CLI version in the current worktree, useful for catching stale local checkouts. | +| `latestCli` | Latest stable `v*` Kilo CLI GitHub release. | +| `prevJetbrainsCli` | CLI pin used by the latest `jetbrains/v*` release tag, for reviewing the jump. | +| `pinnedMain` / `pinnedLocal` | Whether `kilo.cli.pinned=true`; `false` means repo CLI dev mode. | +| `assetsOk` / `missingAssets` | Whether the pinned CLI release has every runtime asset. | +| `drift` | `up-to-date`, `behind`, `worktree-behind-main`, `repo-mode-on-main`, `repo-mode-local`, or `assets-missing`. | + +Interpretation: + +| Drift | Action | +|---|---| +| `up-to-date` | Continue after user confirmation. | +| `behind` | Stop and show `pinMain`, `latestCli`, and `prevJetbrainsCli`; ask whether to cancel, bump + test, or proceed anyway. | +| `worktree-behind-main` | Explain that prepare tags `origin/main`; refresh the worktree or rely on `pinMain` in the confirmation. | +| `repo-mode-on-main` | Stop. `kilo.cli.pinned=false` is dev-only and must be reset to `true` on `main` before release. | +| `repo-mode-local` | Stop or reset local `kilo.cli.pinned=true`; release checks should run from a releasable local state. | +| `assets-missing` | Stop. The pinned CLI release is incomplete and would fail runtime download. | + +Show the resolved JetBrains plugin version, release kind, default `fromTagDefault`, `pinMain`, `latestCli`, `prevJetbrainsCli`, and `assetsOk` to the user, then ask for confirmation before continuing. If the user wants a different CLI pin, use the bump workflow below and do not dispatch prepare until the bump is merged to `main`. + +## Bump the CLI Pin + +Use this only when the user wants to test or release with a different CLI than `origin/main` currently pins. The helper refuses versions whose GitHub release or runtime assets are missing. + +Local test edit only: ```bash -cli_version="7.4.1" -gh release view "v${cli_version}" --repo Kilo-Org/kilocode --json assets \ - --jq '.assets[].name' | sort +bun .kilo/skills/release-jetbrains/script/set-pin.ts --latest +# or +bun .kilo/skills/release-jetbrains/script/set-pin.ts --version 7.4.1 ``` -Expected assets: +Then test from `packages/kilo-jetbrains/`: -```text -kilo-darwin-arm64.zip -kilo-darwin-x64.zip -kilo-linux-arm64.tar.gz -kilo-linux-x64.tar.gz -kilo-windows-arm64.zip -kilo-windows-x64.zip +```bash +./gradlew typecheck +./gradlew test ``` -If the pin is stale or the release assets are missing, stop and ask the user to update `packages/kilo-jetbrains/package.json` on `main` before dispatching prepare. The prepare workflow tags `origin/main`, so the pin must already be reviewed and merged before the release tag is created. +If the user confirms the tested pin should be released, open or update a pin bump PR to `main`: -Show the resolved JetBrains plugin version, release kind, default `fromTagDefault`, pinned CLI version, and CLI release asset status to the user, then ask for confirmation before continuing. +```bash +bun .kilo/skills/release-jetbrains/script/set-pin.ts --latest --pr +# or +bun .kilo/skills/release-jetbrains/script/set-pin.ts --version 7.4.1 --pr +``` + +After that PR merges to `main`, re-run `resolve-version.ts`, re-run `check-pin.ts`, confirm `drift=up-to-date`, then dispatch prepare. Do not dispatch prepare from a local-only pin edit; the prepare workflow tags `origin/main`. ## Prepare Workflow @@ -200,6 +231,7 @@ Report the Marketplace channel and GitHub Release URL. RC versions publish to th - If prepare created the tag but failed before creating a PR, rerun prepare for the same version. The existing workflow reuses the tag if it points to the same commit. - If a tag points to an unexpected SHA, stop and inspect manually. Do not move or delete release tags casually. +- If prepare tagged an unintended CLI pin, do not move the tag. Land the intended pin on `main`, resolve the next JetBrains version, and create a new release tag. - If release PR checks fail from an apparent flake, use `gh run rerun --failed`, then `gh run watch --exit-status` before publishing. - If publish fails after merge, rerun the failed workflow only if Marketplace did not already accept the version. - If Marketplace succeeds but GitHub Release upload fails, manually create or edit the GitHub Release for `jetbrains/v` using the reviewed changelog. diff --git a/.kilo/skills/release-jetbrains/script/check-pin.ts b/.kilo/skills/release-jetbrains/script/check-pin.ts new file mode 100644 index 00000000000..f3af8d82d7d --- /dev/null +++ b/.kilo/skills/release-jetbrains/script/check-pin.ts @@ -0,0 +1,89 @@ +#!/usr/bin/env bun + +import { $ } from "bun" +import semver from "semver" +import { parseArgs } from "util" +import { latest, missing } from "./pin-common" + +const repo = process.env.GH_REPO ?? process.env.GITHUB_REPOSITORY ?? "Kilo-Org/kilocode" + +const { values } = parseArgs({ + args: Bun.argv.slice(2), + options: { + help: { type: "boolean", short: "h", default: false }, + }, +}) + +if (values.help) { + console.log(` +Usage: bun .kilo/skills/release-jetbrains/script/check-pin.ts + +Checks the CLI pin that a JetBrains release would lock. Prepare tags origin/main, +so this reads packages/kilo-jetbrains/package.json from origin/main and compares +it with the latest published Kilo CLI release plus the local worktree pin. + +Exit codes: + 0 Pin is release-ready. + 2 Pin drift, repo CLI mode, or missing CLI assets require maintainer review. +`) + process.exit(0) +} + +await $`git fetch origin main --tags`.quiet() + +const pinMain = JSON.parse(await $`git show origin/main:packages/kilo-jetbrains/package.json`.text()).version as string +const pinLocal = (await Bun.file("packages/kilo-jetbrains/package.json").json()).version as string +const propsMain = await $`git show origin/main:packages/kilo-jetbrains/gradle.properties`.text() +const propsLocal = await Bun.file("packages/kilo-jetbrains/gradle.properties").text() +const pinnedMain = pinned(propsMain) +const pinnedLocal = pinned(propsLocal) +const latestCli = await latest(repo) +const prevJetbrainsCli = await previous() +const missingAssets = await missing(repo, pinMain) +const assetsOk = missingAssets.length === 0 +const drift = (() => { + if (!pinnedMain) return "repo-mode-on-main" + if (!pinnedLocal) return "repo-mode-local" + if (pinLocal !== pinMain) return "worktree-behind-main" + if (!assetsOk) return "assets-missing" + if (latestCli && semver.lt(pinMain, latestCli)) return "behind" + return "up-to-date" +})() + +console.log(JSON.stringify({ + pinMain, + pinLocal, + latestCli, + prevJetbrainsCli, + pinnedMain, + pinnedLocal, + assetsOk, + missingAssets, + drift, +}, null, 2)) + +if (drift !== "up-to-date") process.exit(2) + +async function previous() { + const text = await $`git tag --list ${"jetbrains/v*"}`.text() + const tag = text + .split(/\r?\n/) + .map((item) => item.trim()) + .filter(Boolean) + .map((tag) => ({ tag, version: tag.replace(/^jetbrains\/v/, "") })) + .filter((item) => semver.valid(item.version)) + .sort((a, b) => semver.rcompare(a.version, b.version))[0]?.tag + if (!tag) return null + const res = await $`git show ${tag}:packages/kilo-jetbrains/package.json`.nothrow().text() + if (!res.trim()) return null + return JSON.parse(res).version as string +} + +function pinned(text: string) { + const value = text.split(/\r?\n/).flatMap((line) => { + const [key, value] = line.split("=", 2) + if (key.trim() !== "kilo.cli.pinned") return [] + return [value?.trim().toLowerCase()] + })[0] + return value == null || value === "true" +} diff --git a/.kilo/skills/release-jetbrains/script/pin-common.ts b/.kilo/skills/release-jetbrains/script/pin-common.ts new file mode 100644 index 00000000000..d4900fea606 --- /dev/null +++ b/.kilo/skills/release-jetbrains/script/pin-common.ts @@ -0,0 +1,30 @@ +import { $ } from "bun" +import semver from "semver" + +export const assets = [ + "kilo-darwin-arm64.zip", + "kilo-darwin-x64.zip", + "kilo-linux-arm64.tar.gz", + "kilo-linux-x64.tar.gz", + "kilo-windows-arm64.zip", + "kilo-windows-x64.zip", +] + +export async function latest(repo: string) { + const list = (await $`gh release list --repo ${repo} --limit 100 --json tagName,isDraft,isPrerelease`.json()) as { + tagName: string + isDraft: boolean + isPrerelease: boolean + }[] + return list + .filter((item) => /^v\d+\.\d+\.\d+$/.test(item.tagName) && !item.isDraft && !item.isPrerelease) + .map((item) => item.tagName.slice(1)) + .sort(semver.rcompare)[0] ?? null +} + +export async function missing(repo: string, version: string) { + const res = await $`gh release view ${`v${version}`} --repo ${repo} --json assets --jq ${".assets[].name"}`.quiet().nothrow() + if (res.exitCode !== 0) return assets + const names = res.stdout.toString().split(/\r?\n/).map((item) => item.trim()).filter(Boolean) + return assets.filter((item) => !names.includes(item)) +} diff --git a/.kilo/skills/release-jetbrains/script/set-pin.ts b/.kilo/skills/release-jetbrains/script/set-pin.ts new file mode 100644 index 00000000000..473f114395d --- /dev/null +++ b/.kilo/skills/release-jetbrains/script/set-pin.ts @@ -0,0 +1,123 @@ +#!/usr/bin/env bun + +import { $ } from "bun" +import semver from "semver" +import { parseArgs } from "util" +import { latest, missing } from "./pin-common" + +const repo = process.env.GH_REPO ?? process.env.GITHUB_REPOSITORY ?? "Kilo-Org/kilocode" +const file = "packages/kilo-jetbrains/package.json" +const label = "jetbrains-cli-pin-bump" + +const { values } = parseArgs({ + args: Bun.argv.slice(2), + options: { + version: { type: "string" }, + latest: { type: "boolean", default: false }, + pr: { type: "boolean", default: false }, + help: { type: "boolean", short: "h", default: false }, + }, +}) + +if (values.help) { + console.log(` +Usage: bun .kilo/skills/release-jetbrains/script/set-pin.ts (--latest | --version ) [--pr] + +Without --pr, rewrites ${file} in the local worktree so you can test a CLI pin. +With --pr, opens or updates a PR against main using the GitHub API; prepare tags +origin/main, so the pin bump must merge there before a JetBrains release starts. + +Examples: + bun .kilo/skills/release-jetbrains/script/set-pin.ts --latest + bun .kilo/skills/release-jetbrains/script/set-pin.ts --version 7.4.1 + bun .kilo/skills/release-jetbrains/script/set-pin.ts --latest --pr +`) + process.exit(0) +} + +if (values.latest && values.version) throw new Error("Pass either --latest or --version, not both") +const version = values.latest ? await latest(repo) : values.version?.replace(/^v/, "") +if (!version || !semver.valid(version) || semver.prerelease(version)) { + throw new Error("Pass a stable CLI version with --version x.y.z or use --latest") +} + +const miss = await missing(repo, version) +if (miss.length > 0) { + throw new Error(`CLI release v${version} is missing required assets: ${miss.join(", ")}`) +} + +if (values.pr) { + await pr(version) + process.exit(0) +} + +const pkg = await Bun.file(file).json() +const previous = pkg.version as string +pkg.version = version +await Bun.write(file, `${JSON.stringify(pkg, null, 2)}\n`) +if (previous === version) { + console.log(`${file} already pins CLI v${version}`) +} else { + console.log(`Pinned JetBrains CLI ${previous} -> ${version} in ${file}`) +} +console.log("Test locally with: cd packages/kilo-jetbrains && ./gradlew typecheck && ./gradlew test") +console.log("When satisfied, run this script again with --pr so the bump lands on main before prepare tags it.") + +async function pr(version: string) { + await $`git fetch origin main`.quiet() + const branch = `chore/jetbrains-cli-pin-v${version}` + const main = (await $`git rev-parse origin/main`.text()).trim() + const text = await $`git show origin/main:${file}`.text() + const pkg = JSON.parse(text) + const previous = pkg.version as string + if (previous === version) { + console.log(`origin/main already pins CLI v${version}; no PR needed.`) + return + } + pkg.version = version + const body = `${JSON.stringify(pkg, null, 2)}\n` + await ensure(branch, main) + const current = (await $`gh api ${`repos/${repo}/contents/${file}?ref=${branch}`}`.json()) as { sha: string } + await $`gh api --method PUT ${`repos/${repo}/contents/${file}`} -f message=${`chore(jetbrains): bump CLI pin to v${version}`} -f content=${Buffer.from(body).toString("base64")} -f branch=${branch} -f sha=${current.sha}`.quiet() + + const title = `chore(jetbrains): bump CLI pin to v${version}` + const desc = [ + `Bumps the JetBrains CLI pin from v${previous} to v${version}.`, + "", + "Prepare tags origin/main, so this PR must merge before dispatching a JetBrains release that should lock this CLI.", + "", + "After merging, re-run:", + "", + "```bash", + "bun .kilo/skills/release-jetbrains/script/check-pin.ts", + "```", + ].join("\n") + const view = await $`gh pr view ${branch} --repo ${repo} --json url --jq .url`.quiet().nothrow() + if (view.exitCode === 0 && view.stdout.toString().trim()) { + await $`gh pr edit ${branch} --repo ${repo} --title ${title} --body ${desc}` + await tag(branch) + console.log(view.stdout.toString().trim()) + return + } + const url = await $`gh pr create --repo ${repo} --base main --head ${branch} --title ${title} --body ${desc}`.text() + await tag(branch) + console.log(url.trim()) +} + +async function ensure(branch: string, sha: string) { + const ref = `repos/${repo}/git/refs/heads/${branch}` + const exists = await $`gh api ${ref}`.nothrow().quiet() + if (exists.exitCode === 0) { + await $`gh api --method PATCH ${ref} -f sha=${sha} -F force=true`.quiet() + return + } + await $`gh api --method POST ${`repos/${repo}/git/refs`} -f ref=${`refs/heads/${branch}`} -f sha=${sha}`.quiet() +} + +async function tag(branch: string) { + await $`gh label create ${label} --repo ${repo} --color 1D76DB --description ${"JetBrains pinned CLI version bump"}`.quiet().nothrow() + const result = await $`gh pr edit ${branch} --repo ${repo} --add-label ${label}`.quiet().nothrow() + if (result.exitCode !== 0) { + console.warn(`Warning: failed to add ${label} label to ${branch}`) + } +} diff --git a/bun.lock b/bun.lock index e0c5b1329c5..91b50523c9e 100644 --- a/bun.lock +++ b/bun.lock @@ -28,7 +28,7 @@ }, "packages/core": { "name": "@opencode-ai/core", - "version": "7.4.1", + "version": "7.4.5", "bin": { "opencode": "./bin/opencode", }, @@ -93,7 +93,7 @@ }, "packages/effect-drizzle-sqlite": { "name": "@opencode-ai/effect-drizzle-sqlite", - "version": "7.4.1", + "version": "7.4.5", "dependencies": { "drizzle-orm": "catalog:", "effect": "catalog:", @@ -107,7 +107,7 @@ }, "packages/http-recorder": { "name": "@opencode-ai/http-recorder", - "version": "7.4.1", + "version": "7.4.5", "dependencies": { "@effect/platform-node": "catalog:", "effect": "catalog:", @@ -120,7 +120,7 @@ }, "packages/kilo-console": { "name": "@kilocode/kilo-console", - "version": "7.4.1", + "version": "7.4.5", "dependencies": { "@kilocode/kilo-web-ui": "workspace:*", "@kilocode/sdk": "workspace:*", @@ -142,7 +142,7 @@ }, "packages/kilo-docs": { "name": "@kilocode/kilo-docs", - "version": "7.4.1", + "version": "7.4.5", "dependencies": { "@docsearch/css": "^4", "@docsearch/js": "^4", @@ -172,7 +172,7 @@ }, "packages/kilo-gateway": { "name": "@kilocode/kilo-gateway", - "version": "7.4.1", + "version": "7.4.5", "dependencies": { "@ai-sdk/alibaba": "1.0.17", "@ai-sdk/anthropic": "3.0.71", @@ -208,7 +208,7 @@ }, "packages/kilo-i18n": { "name": "@kilocode/kilo-i18n", - "version": "7.4.1", + "version": "7.4.5", "devDependencies": { "@tsconfig/node22": "catalog:", "@types/bun": "catalog:", @@ -218,7 +218,7 @@ }, "packages/kilo-indexing": { "name": "@kilocode/kilo-indexing", - "version": "7.4.1", + "version": "7.4.5", "dependencies": { "@aws-sdk/client-bedrock-runtime": "3.1005.0", "@aws-sdk/credential-provider-ini": "3.972.31", @@ -250,11 +250,11 @@ }, "packages/kilo-jetbrains": { "name": "@kilocode/kilo-jetbrains", - "version": "7.4.1", + "version": "7.4.5", }, "packages/kilo-memory": { "name": "@kilocode/kilo-memory", - "version": "7.4.1", + "version": "7.4.5", "dependencies": { "effect": "catalog:", "zod": "catalog:", @@ -268,7 +268,7 @@ }, "packages/kilo-sandbox": { "name": "@kilocode/sandbox", - "version": "7.4.1", + "version": "7.4.5", "dependencies": { "effect": "catalog:", }, @@ -281,7 +281,7 @@ }, "packages/kilo-telemetry": { "name": "@kilocode/kilo-telemetry", - "version": "7.4.1", + "version": "7.4.5", "dependencies": { "@kilocode/kilo-gateway": "workspace:*", "posthog-node": "4.4.0", @@ -295,7 +295,7 @@ }, "packages/kilo-ui": { "name": "@kilocode/kilo-ui", - "version": "7.4.1", + "version": "7.4.5", "dependencies": { "@kilocode/sdk": "workspace:*", "@kobalte/core": "0.13.11", @@ -332,7 +332,7 @@ }, "packages/kilo-vscode": { "name": "kilo-code", - "version": "7.4.1", + "version": "7.4.5", "dependencies": { "@anthropic-ai/sdk": "^0.39.0", "@kilocode/kilo-gateway": "workspace:*", @@ -401,7 +401,7 @@ }, "packages/kilo-web-ui": { "name": "@kilocode/kilo-web-ui", - "version": "7.4.1", + "version": "7.4.5", "dependencies": { "@kilocode/kilo-ui": "workspace:*", "@kobalte/core": "catalog:", @@ -418,7 +418,7 @@ }, "packages/llm": { "name": "@opencode-ai/llm", - "version": "7.4.1", + "version": "7.4.5", "dependencies": { "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", @@ -436,7 +436,7 @@ }, "packages/opencode": { "name": "@kilocode/cli", - "version": "7.4.1", + "version": "7.4.5", "bin": { "kilo": "./bin/kilo", "kilocode": "./bin/kilo", @@ -500,7 +500,6 @@ "@secretlint/secretlint-rule-preset-recommend": "10.2.2", "@silvia-odwyer/photon-node": "0.3.4", "@solid-primitives/event-bus": "1.1.2", - "@solid-primitives/scheduled": "1.5.2", "@types/ws": "8.18.1", "@zip.js/zip.js": "2.7.62", "ai": "catalog:", @@ -587,7 +586,7 @@ }, "packages/plugin": { "name": "@kilocode/plugin", - "version": "7.4.1", + "version": "7.4.5", "dependencies": { "@kilocode/sdk": "workspace:*", "effect": "catalog:", @@ -615,7 +614,7 @@ }, "packages/plugin-atomic-chat": { "name": "@kilocode/plugin-atomic-chat", - "version": "7.4.1", + "version": "7.4.5", "dependencies": { "@kilocode/plugin": "workspace:*", }, @@ -629,7 +628,7 @@ }, "packages/script": { "name": "@opencode-ai/script", - "version": "7.4.1", + "version": "7.4.5", "dependencies": { "semver": "^7.6.3", }, @@ -640,7 +639,7 @@ }, "packages/sdk/js": { "name": "@kilocode/sdk", - "version": "7.4.1", + "version": "7.4.5", "dependencies": { "cross-spawn": "catalog:", }, @@ -655,7 +654,7 @@ }, "packages/storybook": { "name": "@opencode-ai/storybook", - "version": "7.4.1", + "version": "7.4.5", "devDependencies": { "@opencode-ai/ui": "workspace:*", "@solidjs/meta": "catalog:", @@ -678,7 +677,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "7.4.1", + "version": "7.4.5", "dependencies": { "@kilocode/sdk": "workspace:*", "@kobalte/core": "catalog:", @@ -739,7 +738,6 @@ "@opentui/core": "catalog:", "@opentui/keymap": "catalog:", "@opentui/solid": "catalog:", - "@smithy/util-buffer-from": "4.2.2", "@types/bun": "catalog:", "@types/node": "catalog:", "@xmldom/xmldom": ">=0.8.12", @@ -1899,7 +1897,7 @@ "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.3.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "tslib": "^2.6.2" } }, "sha512-jUH1Eth7Sgn4KPBX5OKYDRpNjzul7AzsIhxKXT1rHXPTSfY00/7Kb9RtNil5SDAlPPsxaUiesR/rql2wjackmw=="], - "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.4.5", "", { "dependencies": { "@smithy/core": "^3.29.0", "tslib": "^2.6.2" } }, "sha512-OTu6rqRA5jcUDVOf2atiMdh+UmZ8GB2gzSdZPElZJAG0hS8lqak0jlvTcrmaR/FNIlP68r5EDUZ+cq1ZLKjq6g=="], + "@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.3.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "tslib": "^2.6.2" } }, "sha512-nfpYCrzSFAgfIXmIHFTjOGNeTV3DVF5E5rfi3ZuNfsOjKSpePBOJF3rjyXlWYND0anvxVoqioIwClWCNdKt4Og=="], @@ -1935,7 +1933,7 @@ "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.3.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "tslib": "^2.6.2" } }, "sha512-T15zTQJ/xKYdS0/3CFckhz1QBbhxmhk/xjL6FKvHKgkJPN4E985If2FI9CcV2kh2v0sfiWMfXVEOKFbqgw4m4w=="], - "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], + "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.3.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "tslib": "^2.6.2" } }, "sha512-sms/ty2CJwHOiGzEaAVWizTVK5KusXpAYqCUeXIa+hWtNKLwjimH4z11mc07d0Fe3DT3lmZJIZWOMcVQ/N4hBQ=="], "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.4.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "tslib": "^2.6.2" } }, "sha512-dRCZKu05AL7KQWrVuRJPotfjCRnvGkCjV56XNP067CRfyTtvgi/Ygu44qrBKb814Hsa52bWwDJ+Vt3pd04BjPA=="], @@ -1975,8 +1973,6 @@ "@solid-primitives/rootless": ["@solid-primitives/rootless@1.5.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-9HULb0QAzL2r47CCad0M+NKFtQ+LrGGNHZfteX/ThdGvKIg2o2GYhBooZubTCd/RTu2l2+Nw4s+dEfiDGvdrrQ=="], - "@solid-primitives/scheduled": ["@solid-primitives/scheduled@1.5.2", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-/j2igE0xyNaHhj6kMfcUQn5rAVSTLbAX+CDEBm25hSNBmNiHLu2lM7Usj2kJJ5j36D67bE8wR1hBNA8hjtvsQA=="], - "@solid-primitives/static-store": ["@solid-primitives/static-store@0.1.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-uxez7SXnr5GiRnzqO2IEDjOJRIXaG+0LZLBizmUA1FwSi+hrpuMzVBwyk70m4prcl8X6FDDXUl9O8hSq8wHbBQ=="], "@solid-primitives/trigger": ["@solid-primitives/trigger@1.2.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-Za2JebEiDyfamjmDwRaESYqBBYOlgYGzB8kHYH0QrkXyLf2qNADlKdGN+z3vWSLCTDcKxChS43Kssjuc0OZhng=="], @@ -4583,8 +4579,6 @@ "@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.23.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ=="], - "@smithy/is-array-buffer/@smithy/core": ["@smithy/core@3.29.0", "", { "dependencies": { "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-sEvpvkBVoMxjoek35XyJFn2ZD3EJ1RpiZrT47WaZodxzAIWS44zkdvbqGE/ZlugtjiQp62cffYZ9ldyRkjAGnA=="], - "@solid-primitives/resize-observer/@solid-primitives/rootless": ["@solid-primitives/rootless@1.5.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-N8cIDAHbWcLahNRLr0knAAQvXyEdEMoAZvIMZKmhNb1mlx9e2UOv9BRD5YNwQUJwbNoYVhhLwFOEOcVXFx0HqA=="], "@storybook/addon-links/storybook": ["storybook@10.4.4", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.2", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "@webcontainer/env": "^1.1.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "oxc-parser": "^0.127.0", "oxc-resolver": "^11.19.1", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "prettier": "^2 || ^3", "vite-plus": "^0.1.15" }, "optionalPeers": ["@types/react", "prettier", "vite-plus"], "bin": "./dist/bin/dispatcher.js" }, "sha512-Nn0qFRxU5fyABa6dGRftfL3lz0Y+HkKOaAkfytF8S4Q2K6Szwwq7TwPAEs3Wsj8hBQbYhsobrKADcPsyXQpJaA=="], @@ -4957,6 +4951,10 @@ "yargs-unparser/decamelize": ["decamelize@4.0.0", "", {}, "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ=="], + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], "@eslint/config-array/minimatch/brace-expansion": ["brace-expansion@1.1.15", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg=="], @@ -5057,8 +5055,6 @@ "@opencode-ai/ui/@solid-primitives/resize-observer/@solid-primitives/rootless": ["@solid-primitives/rootless@1.5.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-N8cIDAHbWcLahNRLr0knAAQvXyEdEMoAZvIMZKmhNb1mlx9e2UOv9BRD5YNwQUJwbNoYVhhLwFOEOcVXFx0HqA=="], - "@smithy/is-array-buffer/@smithy/core/@smithy/types": ["@smithy/types@4.15.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-x3L0XSACF6UYzKpa9biqiRMgvH5+wnFFew9Tm/grFYqgaupPwx/+ojDPpPJM8dZON3S9tjz5U+PQYsCBd1Mw5Q=="], - "@storybook/addon-links/storybook/@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], "@storybook/addon-links/storybook/@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], diff --git a/nix/hashes.json b/nix/hashes.json index 387012efc67..e3524989c2b 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-5/NgXEBhaNEEqIz9GVCXxQaLJwkqWXAAZOPoa6GyHTI=", - "aarch64-linux": "sha256-qt7PKn0SAhDCcYtXRY9+1TBp9x3Far7kK24jhUzoMf8=", - "aarch64-darwin": "sha256-3FN/i3chx01S52lAv0tjxVtRpUlPkIfInphpq+69uWA=", - "x86_64-darwin": "sha256-+rOacJLx+qOCRGW4F+AjvFcc9XUE2biB0BRSy3dTWGI=" + "x86_64-linux": "sha256-5siIK4MUoCSQb24VpTjeNMpM1Y3HXju8F2ftO+afFoA=", + "aarch64-linux": "sha256-AkbE9l9nx9Y+uDaO/3G/ayBa7ccGHSe4QZLYob060XE=", + "aarch64-darwin": "sha256-DvK4m8jVh088uRQ4fPbGqvwRPwXn9pjJ+npsiwkCWBc=", + "x86_64-darwin": "sha256-39bKoOcyRWilHIMMvFy5/hupLiQEcCe+yLhLq60AkEI=" } } diff --git a/package.json b/package.json index d2744d8b237..7e440287802 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "packageManager": "bun@1.3.14", "scripts": { "dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts", + "dev:local": "bun run packages/opencode/script/dev-local.ts", "dev:storybook": "bun --cwd packages/storybook storybook", "lint": "oxlint", "typecheck": "bun turbo typecheck", @@ -140,8 +141,7 @@ "@opentui/core": "catalog:", "@opentui/solid": "catalog:", "solid-js": "catalog:", - "@opentui/keymap": "catalog:", - "@smithy/util-buffer-from": "4.2.2" + "@opentui/keymap": "catalog:" }, "patchedDependencies": { "@npmcli/agent@4.0.0": "patches/@npmcli%2Fagent@4.0.0.patch", @@ -150,6 +150,6 @@ "solid-js@1.9.10": "patches/solid-js@1.9.10.patch", "mammoth@1.12.0": "patches/mammoth@1.12.0.patch" }, - "version": "7.4.1", + "version": "7.4.5", "peerDependencies": {} } diff --git a/packages/core/package.json b/packages/core/package.json index 7808dbc6e9d..a1068025f6b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "7.4.1", + "version": "7.4.5", "name": "@opencode-ai/core", "type": "module", "license": "MIT", diff --git a/packages/effect-drizzle-sqlite/package.json b/packages/effect-drizzle-sqlite/package.json index 7465c919698..fd5b3643166 100644 --- a/packages/effect-drizzle-sqlite/package.json +++ b/packages/effect-drizzle-sqlite/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "7.4.1", + "version": "7.4.5", "name": "@opencode-ai/effect-drizzle-sqlite", "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 0fe80a0cce4..761dc338f13 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "kilo" name = "Kilo" description = "The open source coding agent." -version = "7.4.1" +version = "7.4.5" schema_version = 1 authors = ["Anomaly"] repository = "https://github.com/Kilo-Org/kilocode" @@ -11,26 +11,26 @@ name = "Kilo" icon = "./icons/opencode.svg" [agent_servers.opencode.targets.darwin-aarch64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.1/opencode-darwin-arm64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.5/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.1/opencode-darwin-x64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.5/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.1/opencode-linux-arm64.tar.gz" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.5/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.1/opencode-linux-x64.tar.gz" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.5/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.1/opencode-windows-x64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.5/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/http-recorder/package.json b/packages/http-recorder/package.json index 8737b124db5..855fe7e81e3 100644 --- a/packages/http-recorder/package.json +++ b/packages/http-recorder/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "7.4.1", + "version": "7.4.5", "name": "@opencode-ai/http-recorder", "type": "module", "license": "MIT", diff --git a/packages/kilo-console/package.json b/packages/kilo-console/package.json index 18832504de3..8d451cea651 100755 --- a/packages/kilo-console/package.json +++ b/packages/kilo-console/package.json @@ -1,6 +1,6 @@ { "name": "@kilocode/kilo-console", - "version": "7.4.1", + "version": "7.4.5", "private": true, "type": "module", "scripts": { diff --git a/packages/kilo-docs/AGENTS.md b/packages/kilo-docs/AGENTS.md index 5bab0319bdc..9d338f8e070 100644 --- a/packages/kilo-docs/AGENTS.md +++ b/packages/kilo-docs/AGENTS.md @@ -74,6 +74,10 @@ Use the Markdoc codicon tag format: ## Documentation Guidelines +### Style Guide + +Before writing documentation, review `packages/kilo-docs/STYLE_GUIDE.md` for voice, tone, and formatting conventions. + ### Adding New Pages 1. Create your page in the appropriate directory under `pages/` diff --git a/packages/kilo-docs/STYLE_GUIDE.md b/packages/kilo-docs/STYLE_GUIDE.md new file mode 100644 index 00000000000..205b36932af --- /dev/null +++ b/packages/kilo-docs/STYLE_GUIDE.md @@ -0,0 +1,205 @@ +--- +title: "Documentation Style Guide" +description: "Guidelines for writing Kilo Code documentation" +--- + +# Documentation Style Guide + +This guide covers writing, formatting, and structuring documentation for the Kilo Code docs site. + +## Voice and Tone + +Kilo Code documentation should be: + +- **Clear and direct** - Cut unnecessary words. Prefer active voice. +- **Helpful, not salesy** - Focus on what users can do, not just what's possible. +- **Consistent** - Use the same terminology and phrasing across pages. +- **Friendly but professional** - Write as a knowledgeable teammate explaining concepts. + +### Do + +- Write in the second person ("you") +- Use present tense +- Be specific: "Run `kilo run` to execute a task" not "You can run kilo run" + +### Don't + +- Use marketing fluff or hype language +- Write in passive voice when active is clearer +- Assume prior knowledge not explicitly stated + +## Headings + +- Use sentence case for heading text +- Start with the most important word +- One heading per section +- Use heading levels logically (don't skip from H2 to H4) + +```markdown +## Installing Kilo Code + +### VS Code Extension + +### CLI +``` + +## Procedures + +Use numbered lists for step-by-step instructions. Each step should be a complete action. + +```markdown +1. Open VS Code +2. Go to Extensions (Ctrl+Shift+X / Cmd+Shift+X) +3. Search for "Kilo Code" +4. Click the dropdown arrow next to **Install** and select **Install Pre-Release Version** +``` + +### Procedural tips + +- Include keyboard shortcuts in parentheses +- Use present tense +- Start each step with a verb +- Don't number sub-steps; use nested lists instead + +## Callouts + +Use callouts to highlight important information. Choose the right type: + +| Type | Use for | +|---|---| +| `note` | General information users should know | +| `tip` | Helpful shortcuts or best practices | +| `info` | Context or background information | +| `warning` | Potential problems or important cautions | +| `danger` | Critical warnings that could cause data loss | +| `generic` | Content without a specific visual treatment | + +```markdown +{% callout type="tip" %} +**The easiest way to configure Kilo is to ask the agent.** Just tell the agent what you want. +{% /callout %} +``` + +## Cross-References + +- Use absolute paths starting from `/docs/` for internal links +- Don't include `.md` extensions +- Use descriptive link text, not "click here" + +```markdown +Good: [Quickstart Guide](/docs/getting-started/quickstart) + +Bad: [Click here](/docs/getting-started/quickstart) +``` + +## Code Examples + +- Use fenced code blocks with language specified +- Include comments in code where helpful +- Show realistic, working examples +- Use `kilo run` for CLI examples, not hypothetical commands + +````markdown +```bash +kilo run "create a utils.py file with a function that adds two numbers" +``` +```` + +### Code in prose + +Use backticks for inline code, file references, and commands: + +- `kilo.jsonc` for configuration files +- `Ctrl+Shift+X` for keyboard shortcuts +- `src/utils.ts` for file paths + +## Markdoc Conventions + +### Images + +Use the Markdoc image tag format: + +```markdown +{% image src="/docs/img/kilo-provider/connected-accounts.png" alt="Connect account screen" width="800" caption="Connect account screen" /%} +``` + +**Image path rules:** +- Always include `/docs` prefix +- Use generated screenshots from `packages/kilo-docs/public/img/screenshot-tests/` when available +- Write descriptive alt text for accessibility + +### Tables + +Use compact markdown tables without padding: + +```markdown +| Command | What it runs | +|---|---| +| `kilo serve` | The prod CLI on `$PATH`. | +``` + +### Tabs + +Use tabs for platform-specific content: + +```markdown +{% tabs %} +{% tab label="VS Code" %} + +Content for VS Code + +{% /tab %} +{% tab label="CLI" %} + +Content for CLI + +{% /tab %} +{% /tabs %} +``` + +### Mermaid Diagrams + +Use fenced `mermaid` blocks for architecture diagrams: + +````markdown +```mermaid +flowchart LR + A --> B +``` +```` + +## LLM-Generated Docs + +This documentation site is maintained with AI assistance. When reviewing or editing: + +- Verify technical accuracy manually +- Ensure examples actually work +- Check that terminology is consistent +- Don't accept generated content without review + +## Terminology + +Use consistent terms throughout: + +| Term | Use for | +|---|---| +| Kilo Code | The product name | +| kilo CLI | The command-line interface | +| VS Code extension | The VS Code extension | +| JetBrains plugin | The JetBrains IDE plugin | +| `kilo serve` | The local HTTP server | +| `kilo run` | The headless execution command | +| agent | The AI assistant | + +## Navigation + +- Add new pages to the appropriate nav file in `lib/nav/` +- Update `lib/nav/index.ts` to export the new nav section +- Navigation files are organized by section (e.g., `getting-started.ts`, `code-with-ai.ts`) + +## Documentation Lifecycle + +- Follow the branch naming convention: `docs/description-of-change` +- For documentation-only changes, create branches with the `docs/` prefix +- Update navigation when adding or removing pages +- Add redirects in `previous-docs-redirects.js` when moving or removing pages \ No newline at end of file diff --git a/packages/kilo-docs/lib/nav/automate.ts b/packages/kilo-docs/lib/nav/automate.ts index 1a106a87226..9a7a87ef527 100644 --- a/packages/kilo-docs/lib/nav/automate.ts +++ b/packages/kilo-docs/lib/nav/automate.ts @@ -10,7 +10,6 @@ export const AutomateNav: NavSection[] = [ href: "/automate/code-reviews/overview", children: "Code Reviews", subLinks: [ - { href: "/automate/code-reviews/overview", children: "Overview" }, { href: "/automate/code-reviews/github", children: "GitHub" }, { href: "/automate/code-reviews/gitlab", children: "GitLab" }, ], @@ -19,7 +18,6 @@ export const AutomateNav: NavSection[] = [ href: "/automate/agent-manager", children: "Agent Manager", subLinks: [ - { href: "/automate/agent-manager", children: "Reference" }, { href: "/automate/agent-manager-workflows", children: "Workflows" }, ], }, @@ -42,7 +40,6 @@ export const AutomateNav: NavSection[] = [ href: "/automate/mcp/overview", children: "MCP", subLinks: [ - { href: "/automate/mcp/overview", children: "MCP Overview" }, { href: "/automate/mcp/using-in-kilo-code", children: "Using MCP in Kilo Code", diff --git a/packages/kilo-docs/lib/nav/code-with-ai.ts b/packages/kilo-docs/lib/nav/code-with-ai.ts index 4b998fca3e6..c8a8c0a2238 100644 --- a/packages/kilo-docs/lib/nav/code-with-ai.ts +++ b/packages/kilo-docs/lib/nav/code-with-ai.ts @@ -30,6 +30,11 @@ export const CodeWithAiNav: NavSection[] = [ }, { href: "/code-with-ai/platforms/cloud-agent", children: "Cloud Agent" }, { href: "/code-with-ai/platforms/mobile", children: "Mobile Apps" }, + ], + }, + { + title: "Features", + links: [ { href: "/code-with-ai/app-builder", children: "App Builder" }, { href: "/code-with-ai/gastown", @@ -80,7 +85,6 @@ export const CodeWithAiNav: NavSection[] = [ href: "/code-with-ai/agents/using-agents", children: "Agents", subLinks: [ - { href: "/code-with-ai/agents/using-agents", children: "Using Agents" }, { href: "/code-with-ai/agents/orchestrator-mode", children: "Orchestrator Mode", @@ -89,7 +93,6 @@ export const CodeWithAiNav: NavSection[] = [ }, ], }, - { title: "Productivity Tools", links: [ @@ -113,11 +116,7 @@ export const CodeWithAiNav: NavSection[] = [ href: "/code-with-ai/features/browser-use", children: "Agent Behavior", subLinks: [ - { href: "/code-with-ai/features/browser-use", children: "Browser Use" }, - { - href: "/code-with-ai/features/task-todo-list", - children: "Task Todo List", - }, + { href: "/code-with-ai/features/task-todo-list", children: "Task Todo List" }, { href: "/code-with-ai/features/checkpoints", children: "Checkpoints" }, { href: "/code-with-ai/features/file-encoding", children: "File Encoding" }, ], diff --git a/packages/kilo-docs/lib/nav/collaborate.ts b/packages/kilo-docs/lib/nav/collaborate.ts index a8ae1b34ca5..16514384c98 100644 --- a/packages/kilo-docs/lib/nav/collaborate.ts +++ b/packages/kilo-docs/lib/nav/collaborate.ts @@ -27,27 +27,26 @@ export const CollaborateNav: NavSection[] = [ }, { href: "/collaborate/teams/billing", children: "Billing" }, { href: "/collaborate/teams/analytics", children: "Analytics" }, + ], + }, + { + title: "AI Adoption Dashboard", + links: [ { href: "/collaborate/adoption-dashboard/overview", - children: "AI Adoption Dashboard", - subLinks: [ - { - href: "/collaborate/adoption-dashboard/overview", - children: "Overview", - }, - { - href: "/collaborate/adoption-dashboard/understanding-your-score", - children: "Understanding Your Score", - }, - { - href: "/collaborate/adoption-dashboard/improving-your-score", - children: "Improving Your Score", - }, - { - href: "/collaborate/adoption-dashboard/for-team-leads", - children: "For Team Leads", - }, - ], + children: "Overview", + }, + { + href: "/collaborate/adoption-dashboard/understanding-your-score", + children: "Understanding Your Score", + }, + { + href: "/collaborate/adoption-dashboard/improving-your-score", + children: "Improving Your Score", + }, + { + href: "/collaborate/adoption-dashboard/for-team-leads", + children: "For Team Leads", }, ], }, diff --git a/packages/kilo-docs/lib/nav/getting-started.ts b/packages/kilo-docs/lib/nav/getting-started.ts index 6da808b23a8..96b9f4f6d92 100644 --- a/packages/kilo-docs/lib/nav/getting-started.ts +++ b/packages/kilo-docs/lib/nav/getting-started.ts @@ -2,28 +2,22 @@ import { NavSection } from "../types" export const GettingStartedNav: NavSection[] = [ { - title: "Introduction", + title: "Get Started", links: [ { href: "/getting-started", children: "Overview" }, { href: "/getting-started/installing", children: "Installation" }, - { href: "/getting-started/quickstart", children: "Quickstart" }, + { href: "/getting-started/setup-authentication", children: "Authentication" }, + { href: "/getting-started/quickstart", children: "Your First Task" }, ], }, { title: "Configuration", links: [ - { - href: "/getting-started/setup-authentication", - children: "Setup & Authentication", - }, { href: "/getting-started/using-kilo-for-free", children: "Using Kilo for Free", }, - { - href: "/getting-started/byok", - children: "Bring Your Own Key (BYOK)", - }, + { href: "/getting-started/byok", children: "Bring Your Own Key (BYOK)" }, { href: "/ai-providers", children: "AI Providers" }, { href: "/getting-started/settings", @@ -34,7 +28,10 @@ export const GettingStartedNav: NavSection[] = [ ], }, { href: "/getting-started/adding-credits", children: "Adding Credits" }, - { href: "/getting-started/rate-limits-and-costs", children: "Cost Efficiency & Model Selection" }, + { + href: "/getting-started/rate-limits-and-costs", + children: "Cost Efficiency & Model Selection", + }, { href: "/getting-started/cost-controls-and-usage-safeguards", children: "Cost Controls and Usage Safeguards" }, ], }, diff --git a/packages/kilo-docs/lib/nav/kiloclaw.ts b/packages/kilo-docs/lib/nav/kiloclaw.ts index d7bed669f1f..d880fcf6d94 100644 --- a/packages/kilo-docs/lib/nav/kiloclaw.ts +++ b/packages/kilo-docs/lib/nav/kiloclaw.ts @@ -12,7 +12,6 @@ export const KiloClawNav: NavSection[] = [ href: "/kiloclaw/control-ui/overview", children: "Control UI", subLinks: [ - { href: "/kiloclaw/control-ui/overview", children: "Overview" }, { href: "/kiloclaw/control-ui/changing-models", children: "Changing Models" }, { href: "/kiloclaw/control-ui/exec-approvals", children: "Exec Approvals" }, { href: "/kiloclaw/control-ui/version-pinning", children: "Version Pinning" }, @@ -22,7 +21,6 @@ export const KiloClawNav: NavSection[] = [ href: "/kiloclaw/chat-platforms", children: "Chat Platforms", subLinks: [ - { href: "/kiloclaw/chat-platforms", children: "Overview" }, { href: "/kiloclaw/chat-platforms/telegram", children: "Telegram" }, { href: "/kiloclaw/chat-platforms/discord", children: "Discord" }, { href: "/kiloclaw/chat-platforms/slack", children: "Slack" }, @@ -32,7 +30,6 @@ export const KiloClawNav: NavSection[] = [ href: "/kiloclaw/development-tools", children: "Integrations", subLinks: [ - { href: "/kiloclaw/development-tools", children: "Overview" }, { href: "/kiloclaw/development-tools/github", children: "GitHub" }, { href: "/kiloclaw/development-tools/google", children: "Google Workspace" }, { href: "/kiloclaw/development-tools/linear", children: "Linear" }, @@ -47,7 +44,6 @@ export const KiloClawNav: NavSection[] = [ href: "/kiloclaw/triggers", children: "Triggers", subLinks: [ - { href: "/kiloclaw/triggers", children: "Overview" }, { href: "/kiloclaw/triggers/webhooks", children: "Webhooks" }, { href: "/kiloclaw/triggers/scheduled", children: "Scheduled" }, ], diff --git a/packages/kilo-docs/lychee.toml b/packages/kilo-docs/lychee.toml index b4f1c000e75..9ede3aa94ea 100644 --- a/packages/kilo-docs/lychee.toml +++ b/packages/kilo-docs/lychee.toml @@ -33,6 +33,8 @@ exclude = [ '^https?://api\.voyageai\.com/v1/embeddings/?$', '^https?://inference\.do-ai\.run/v1/?$', '^https?://generativelanguage\.googleapis\.com/v1beta/openai/?$', + # OpenRouter chat-completions is a POST-only endpoint; GET probes return 404 + '^https?://openrouter\.ai/api/v1/chat/completions/?$', '^https?://search\.parallel\.ai/mcp/?$', # xAI API and OAuth endpoints require request parameters or reject plain link checks. '^https?://api\.x\.ai/v1/?$', diff --git a/packages/kilo-docs/markdoc/partials/install-jetbrains.md b/packages/kilo-docs/markdoc/partials/install-jetbrains.md index 4ac16619dc9..b8f163a95e6 100644 --- a/packages/kilo-docs/markdoc/partials/install-jetbrains.md +++ b/packages/kilo-docs/markdoc/partials/install-jetbrains.md @@ -1,28 +1,25 @@ -Kilo Code's current JetBrains plugin uses a native interface and supports JetBrains remote development without requiring Node.js. +Kilo Code v7 for JetBrains is officially available. It uses a native JetBrains interface, works well in [remote development split mode](https://www.jetbrains.com/remote-development/), and does not require Node.js. -### Try the v7 Early Access Program plugin {% #jetbrains-early-access %} +The JetBrains plugin provides the best native JetBrains UX for working with an AI coding agent, and it improves with every release. Enable automatic plugin updates to get the latest fixes and improvements as soon as they are available. -The v7 EAP plugin is available for users who want to try the newest JetBrains experience before it reaches the default Marketplace channel. Follow the [v7 roadmap and release milestone](https://github.com/Kilo-Org/kilocode/milestone/1) for planned work and release progress. +### Install the JetBrains plugin + +1. Open IntelliJ IDEA or another [JetBrains IDE](https://www.jetbrains.com/ides/) +2. Go to **Settings → Plugins** +3. Search for **Kilo Code** in the **Marketplace** tab +4. Click **Install** or **Update** and restart your IDE if prompted +5. Open **Settings → Appearance & Behavior → System Settings → Updates**, then enable **Update plugins automatically** (recommended) + +{% image src="/docs/img/jetbrains/plugin-marketplace.png" alt="JetBrains Plugins Marketplace showing the Kilo Code plugin search result" width="900" caption="Search for Kilo Code in the JetBrains Plugins Marketplace." /%} + +{% image src="/docs/img/jetbrains/plugin-auto-updates.png" alt="JetBrains Updates settings with Update plugins automatically enabled" width="900" caption="Enable automatic plugin updates to receive Kilo Code fixes and improvements." /%} + +### If you used the v7 EAP {% #jetbrains-early-access %} {% callout type="info" %} -The v7 EAP plugin is compatible with JetBrains IDE builds 261 and later. EAP builds update frequently, so we recommend enabling automatic plugin updates in your JetBrains IDE from **Settings/Preferences → System Settings → Updates → Update plugins automatically**. Share feedback in the JetBrains channel on the [Kilo Discord](https://kilo.ai/discord). +Remove the EAP repository URL from **Settings → Plugins → Manage Plugin Repositories**. The official v7 plugin is now available from the default JetBrains Marketplace channel, and leaving the custom repository configured can keep your IDE on EAP updates. {% /callout %} -To install the EAP build and receive updates: - -1. Open IntelliJ IDEA or another JetBrains IDE -2. Go to **Settings/Preferences → Plugins** -3. Click the gear icon and choose **Manage Plugin Repositories** -4. Add this repository URL: - -{% copyLine text="https://plugins.jetbrains.com/plugins/list?channel=eap&pluginId=28350" /%} - -5. Return to the **Marketplace** tab -6. Search for **Kilo Code** -7. Click **Install** or **Update** and restart your IDE if prompted - -After the custom repository is added, JetBrains will offer EAP updates through the normal plugin update flow. - ### Supported IDEs - IntelliJ IDEA @@ -34,7 +31,3 @@ After the custom repository is added, JetBrains will offer EAP updates through t - CLion - RubyMine - DataGrip - -{% callout type="info" %} -Both Community and Ultimate editions are supported. Some AI features may vary based on your JetBrains license. -{% /callout %} diff --git a/packages/kilo-docs/package.json b/packages/kilo-docs/package.json index 543a3b17bfa..233a8baa937 100644 --- a/packages/kilo-docs/package.json +++ b/packages/kilo-docs/package.json @@ -1,6 +1,6 @@ { "name": "@kilocode/kilo-docs", - "version": "7.4.1", + "version": "7.4.5", "private": true, "scripts": { "dev": "next dev --webpack --port 3002", diff --git a/packages/kilo-docs/pages/automate/agent-manager.md b/packages/kilo-docs/pages/automate/agent-manager.md index f190c92763e..cc3cdb6be4a 100644 --- a/packages/kilo-docs/pages/automate/agent-manager.md +++ b/packages/kilo-docs/pages/automate/agent-manager.md @@ -39,7 +39,7 @@ The panel opens as an editor tab and stays active across focus changes. Agent Manager uses the same sign-in, provider settings, models, BYOK keys, custom providers, MCP servers, and permission rules as the extension sidebar. Configure them from extension Settings and they apply to Agent Manager as well. -See [Setup & Authentication](/docs/getting-started/setup-authentication), [AI Providers](/docs/ai-providers), and [Bring Your Own Key](/docs/getting-started/byok) for setup details. +See [Authentication](/docs/getting-started/setup-authentication), [AI Providers](/docs/ai-providers), and [Bring Your Own Key](/docs/getting-started/byok) for setup details. ## Working with Worktrees @@ -153,7 +153,7 @@ The tool supports two modes: | `worktree` | Creates one Agent Manager git worktree and session per task | | `local` | Creates Agent Manager sessions in the current workspace without git worktree isolation | -Each request can include 1-20 tasks. Each task must include at least one of `prompt`, `name`, or `branchName`. A task with an initial prompt can also specify a `model` (by name, e.g. `Claude Opus 4.1`) and one of that model's reasoning `variant` values. Agent Manager resolves the provider for the chosen model, preferring the provider used by the current default model and falling back to the Kilo Gateway; a qualified `provider/model` ID is also accepted to force a specific provider. Tasks without those fields use the normal model defaults. Use `versions: true` only when the tasks are alternate versions of the same work to compare; otherwise, multiple tasks start as independent sessions. +Each request can include 1-20 tasks. Each task must include at least one of `prompt`, `name`, or `branchName`. Prompted tasks inherit the model and reasoning variant used by the chat turn that starts them. A task can override that selection with a `model` (by name, e.g. `Claude Opus 4.1`) when you explicitly request a different model, or with one of the current model's reasoning `variant` values when you request a different variant. Agent Manager resolves the provider for a model override, preferring the provider used by the current turn and falling back to the Kilo Gateway; a qualified `provider/model` ID is also accepted to force a specific provider. Prepared sessions without an initial prompt use the normal model defaults. Use `versions: true` only when the tasks are alternate versions of the same work to compare; otherwise, multiple tasks start as independent sessions. The companion `agent_manager_models` tool searches models and their supported reasoning variants on demand. Results are grouped by model name (with the offering providers listed for reference) and limited to 20 per call, so the full catalog is never added to the conversation context. diff --git a/packages/kilo-docs/pages/code-with-ai/platforms/cli.md b/packages/kilo-docs/pages/code-with-ai/platforms/cli.md index d785539288e..0f2993f560f 100644 --- a/packages/kilo-docs/pages/code-with-ai/platforms/cli.md +++ b/packages/kilo-docs/pages/code-with-ai/platforms/cli.md @@ -339,6 +339,10 @@ The Kilo CLI is a fork of [OpenCode](https://opencode.ai) and supports the same Project-level configuration takes precedence over global settings. +{% callout type="warning" %} +**Migrating from opencode?** Kilo no longer falls back to opencode configuration stored in `.opencode` directories (such as `~/.config/opencode` or a project `./.opencode/`). To keep using it, move your global config into `~/.config/kilo/` and any project config into `./.kilo/`. +{% /callout %} + ### Key Configuration Options ```json diff --git a/packages/kilo-docs/pages/getting-started/index.md b/packages/kilo-docs/pages/getting-started/index.md index e3a0e8e89db..1fc06f14b63 100644 --- a/packages/kilo-docs/pages/getting-started/index.md +++ b/packages/kilo-docs/pages/getting-started/index.md @@ -28,7 +28,7 @@ Your sessions sync across all of these, so you can start a task on your phone an ## Quick Start 1. [Install Kilo Code](/docs/getting-started/installing) in your preferred environment -2. [Connect an AI provider](/docs/ai-providers) or use Kilo's built-in provider & credits +2. [Set up authentication](/docs/getting-started/setup-authentication) or use Kilo's built-in provider 3. [Run your first task](/docs/getting-started/quickstart) {% callout type="tip" %} diff --git a/packages/kilo-docs/pages/getting-started/installing.md b/packages/kilo-docs/pages/getting-started/installing.md index de0045e42a4..eafdecdb43a 100644 --- a/packages/kilo-docs/pages/getting-started/installing.md +++ b/packages/kilo-docs/pages/getting-started/installing.md @@ -124,11 +124,10 @@ If you plan to remain on that version for a while, you may also want to temporar ## Next Steps -After installation, check out these resources to get started: +After installation: -- [Quickstart Guide](/docs/getting-started/quickstart) - Get up and running in minutes -- [Setting Up Authentication](/docs/getting-started/setup-authentication) - Configure your AI provider -- [Your First Task](/docs/code-with-ai/agents/chat-interface) - Learn the basics of working with Kilo Code +1. **[Set up authentication](/docs/getting-started/setup-authentication)** to configure your AI provider +2. **[Run your first task](/docs/getting-started/quickstart)** — Learn how to chat with Kilo to complete tasks ## Getting Support diff --git a/packages/kilo-docs/pages/getting-started/quickstart.md b/packages/kilo-docs/pages/getting-started/quickstart.md index 77fbd4ef9d7..98354ee1520 100644 --- a/packages/kilo-docs/pages/getting-started/quickstart.md +++ b/packages/kilo-docs/pages/getting-started/quickstart.md @@ -1,14 +1,14 @@ --- -title: "Quickstart" +title: "Your First Task" description: "Get up and running with Kilo Code in minutes" --- -# Quickstart +# Your First Task After you [set up Kilo Code](/docs/getting-started/setup-authentication), follow the guide for your platform below. {% tabs %} -{% tab label="VSCode" %} +{% tab label="VS Code" %} ## Step by Step Guide @@ -36,16 +36,9 @@ Kilo Code analyzes your request and proposes actions. By default, most tools are To change which actions require approval, open **Settings** (gear icon) and go to the **Auto-Approve** tab. You can set each tool to Allow, Ask, or Deny. See [Auto-Approving Actions](/docs/getting-started/settings/auto-approving-actions) for details. -### Step 5: Iterate +### Step 5: Iterate and Review -Kilo Code works iteratively. Continue giving feedback or follow-up instructions until your task is complete. - -### Key Differences from Legacy - -- **Settings** are managed via `kilo.jsonc` config files (the Settings webview reads and writes the same files) -- **Permissions** use a granular per-tool system instead of broad approval categories -- **Modes** are called "agents" and configured as `.md` files or via the `agent` config key -- **Autocomplete** uses FIM (Fill-in-the-Middle) with Codestral +Kilo Code works iteratively. Continue giving feedback or follow-up instructions until your task is complete. The assistant will propose file edits, run commands, and complete your request step by step. {% /tab %} {% tab label="CLI" %} @@ -68,7 +61,7 @@ Run the `kilo` command to start the interactive TUI (terminal user interface): kilo ``` -If this is your first time, run `kilo auth login` first to authenticate (see [Setup & Authentication](/docs/getting-started/setup-authentication)). +If this is your first time, run `kilo auth login` first to authenticate (see [Authentication](/docs/getting-started/setup-authentication)). ### Step 3: Type Your Task @@ -86,9 +79,9 @@ Kilo analyzes your request and proposes actions. By default, most tools are auto To change permission defaults, configure the `permission` key in your `kilo.jsonc` config file. See [Auto-Approving Actions](/docs/getting-started/settings/auto-approving-actions) for details. -### Step 5: Iterate +### Step 5: Iterate and Review -Kilo works iteratively. Continue giving feedback or follow-up instructions until your task is complete. +Kilo works iteratively. Continue giving feedback or follow-up instructions until your task is complete. The assistant will propose file edits, run commands, and complete your request step by step. ### One-Shot Mode @@ -107,19 +100,13 @@ kilo run --auto "fix the failing tests in test/auth.test.ts" {% /tab %} {% /tabs %} -## Conclusion +## What You Can Do Next -You've completed your first task. Along the way you learned: +Now that you've completed your first task, try these capabilities: -- How to interact with Kilo Code using natural language -- Why approval keeps you in control -- How iteration lets the AI refine its work - -Ready for more? Here are some next steps: - -- **[Autocomplete](/docs/code-with-ai/features/autocomplete)** — Get inline code suggestions as you type -- **[Agents](/docs/code-with-ai/agents/using-agents)** — Explore different agents for different tasks -- **[Git commit generation](/docs/code-with-ai/features/git-commit-generation)** — Automatically generate commit messages +- **[Autocomplete](/docs/code-with-ai/features/autocomplete)** — Get inline code suggestions as you type in your editor +- **[Agents](/docs/code-with-ai/agents/using-agents)** — Switch between specialized agents for coding, architecture, debugging, and more +- **[Git](/docs/code-with-ai/features/git-commit-generation)** — Auto-generate commit messages from your changes {% callout type="tip" %} **Accelerate development:** Check out multiple copies of your repository and run Kilo Code on all of them in parallel (using git to resolve any conflicts, same as with human devs). This can dramatically speed up development on large projects. diff --git a/packages/kilo-docs/pages/getting-started/settings/index.md b/packages/kilo-docs/pages/getting-started/settings/index.md index 24e83c15c7e..7a32691755b 100644 --- a/packages/kilo-docs/pages/getting-started/settings/index.md +++ b/packages/kilo-docs/pages/getting-started/settings/index.md @@ -27,6 +27,12 @@ This is especially useful for complex configuration like custom model definition ## Managing Settings +Kilo reads JSONC config from a **global** location (`~/.config/kilo/kilo.jsonc`) and from your **project** (`kilo.jsonc`, or `.kilo/kilo.jsonc`). All clients — CLI, VS Code, and JetBrains — read the same files. + +{% callout type="warning" %} +**Migrating from opencode?** Kilo no longer falls back to opencode configuration stored in `.opencode` directories (such as `~/.config/opencode` or a project `./.opencode/`). To keep using it, move your global config into `~/.config/kilo/` and any project config into `./.kilo/`. +{% /callout %} + {% tabs %} {% tab label="VSCode" %} @@ -155,6 +161,12 @@ For **session** export and import, use the CLI commands: {% /tab %} {% /tabs %} +## Sandbox + +On macOS and Linux, the VS Code extension includes a dedicated **Sandboxing** settings tab. The sandbox is disabled by default. When enabled, it limits agent filesystem writes and can block outbound network access from model-originated tools. Windows users do not see these settings because Windows sandboxing is not supported. + +See [Sandboxing](/docs/getting-started/settings/sandboxing) for setup instructions, the exact filesystem and network boundaries, and platform limitations. + ## Experimental Features {% tabs %} @@ -169,7 +181,6 @@ Available experimental settings include: - **Paste summary** - summarize large clipboard pastes before including them - **Batch tool** - allow the agent to batch multiple tool calls in one step - **OpenTelemetry** - enable Kilo telemetry and optional OTLP export when configured -- **Sandbox** - confine agent shell commands and file writes to the project and Kilo state directories, with optional outbound network blocking. See [Sandboxing](/docs/getting-started/settings/sandboxing). Advanced options not exposed in the UI can be configured via the `experimental` key in `kilo.jsonc`: diff --git a/packages/kilo-docs/pages/getting-started/settings/sandboxing.md b/packages/kilo-docs/pages/getting-started/settings/sandboxing.md index ed9e2817fb3..c057d609593 100644 --- a/packages/kilo-docs/pages/getting-started/settings/sandboxing.md +++ b/packages/kilo-docs/pages/getting-started/settings/sandboxing.md @@ -1,76 +1,163 @@ --- title: "Sandboxing" -description: "Confine agent shell commands and file writes with the experimental OS-level sandbox" +description: "Understand and configure filesystem write and network restrictions for agent tools" --- # Sandboxing -The experimental sandbox runs agent shell commands and file-tool writes inside an OS-level sandbox that restricts filesystem writes to your project and Kilo state directories, and can block outbound network access from model-originated commands. It is an extra guardrail on top of the permission system: even if the agent is allowed to run a command, the operating system will deny writes outside the allowed roots. +The sandbox adds an operating-system boundary around agent tools. It limits where tools can write and, by default, blocks outbound network access from model-originated commands. This boundary applies even when a tool passes Kilo's permission checks. + +The sandbox is **disabled by default**. It does not restrict filesystem reads. An agent can still read any file that your user account can read, but it can write only to explicitly allowed locations. {% callout type="warning" %} -Sandboxing is experimental. Behavior may change between releases, and it is not available on Windows. +Sandboxing is not available on Windows. If the macOS or Linux sandbox backend is unavailable, Kilo reports the reason and runs tools without sandbox confinement. The sandbox does not fail closed. {% /callout %} -## How it works - -When enabled, the agent's shell commands and file-write tools run confined to a small set of writable directories: - -- Your **project directory** (and its worktree, when running in a linked git worktree) -- Kilo **state directories**: data, cache, config, state, tmp, bin, log, and repos - -Everything else is denied at the OS level. File **reads are not confined** — the agent can still read anywhere it has permission to. The `.git` directory is always denied for writes, regardless of location. - -When network restriction is on (the default), outbound network access is blocked for: - -- Shell commands originated by the model -- First-party HTTP tools (for example web fetch and browser tools) - -The following are **not** affected by the network restriction: - -- **Provider and model inference traffic** — your LLM API calls keep working -- **Local MCP servers and plugin hooks** — these run outside the restriction - ## Enable the sandbox -The sandbox is off by default. Enable it under the `experimental` key in `kilo.jsonc`: +In the VS Code extension: + +1. Open Kilo Code Settings using the gear icon ({% codicon name="gear" /%}). +2. Select **Sandboxing**. +3. Turn on **Sandbox**. +4. Keep **Restrict Network Access** on unless the agent's commands need outbound network access. +5. Save the settings. + +The **Sandboxing** tab is visible to all macOS and Linux users, including when the sandbox is off. Windows users do not see the tab because no Windows backend is available. + +You can also configure the default in the global `kilo.jsonc` file: ```json { - "experimental": { - "sandbox": true, - "sandbox_restrict_network": true + "sandbox": { + "enabled": true, + "network": "deny", + "writable_paths": ["~/shared-output"] } } ``` | Key | Default | Effect | |---|---|---| -| `experimental.sandbox` | `false` | Turn the sandbox on. When `false`, no confinement applies. | -| `experimental.sandbox_restrict_network` | `true` | Block outbound network from model-originated commands and HTTP tools. Set to `false` to allow network (filesystem confinement still applies). | +| `sandbox.enabled` | `false` | Use sandbox confinement by default for new sessions. | +| `sandbox.network` | `"deny"` | Control outbound network access while filesystem confinement is active. Set this to `"allow"` to permit network access without removing filesystem write restrictions. | +| `sandbox.writable_paths` | `[]` | Add writable files or directories outside the built-in writable locations. Only global config may set these paths. | -You can also enable it from the VS Code Settings webview: gear icon ({% codicon name="gear" /%}) → **Experimental** → **Sandbox**. Once the sandbox is on, a dedicated **Sandboxing** tab appears with the **Restrict Network Access** switch for `sandbox_restrict_network`. +Project config may tighten sandbox policy by setting `enabled` to `true` or `network` to `"deny"`. It cannot disable a globally enabled sandbox, allow network denied by global config, or add writable paths. This prevents repository-controlled configuration from weakening the user's security boundary. -## Toggle per session +## When to use sandboxing -Enabling `experimental.sandbox` sets the default for new sessions, but the setting is ephemeral per session and can be flipped without editing config: +Use the sandbox when the agent may run unfamiliar commands, install dependencies, execute code from an untrusted repository, or process content that could contain prompt injection. It provides a second boundary if the model makes a mistake or follows malicious instructions embedded in source files, issue text, web pages, or tool output. -- **VS Code**: a sandbox toggle appears in the prompt input when `experimental.sandbox` is on (not available for cloud sessions). The tooltip shows whether filesystem writes and network are restricted. -- **CLI / TUI**: run the `/sandbox` slash command or the **Toggle sandbox** palette command. A `◆ Sandbox on` indicator appears next to the prompt when active. +The sandbox can reduce the impact of an unsafe tool call by: -Toggling is in-memory and scoped to the current session, so it does not persist across restarts. If the OS sandbox backend is unavailable on your platform, the toggle reports the reason and confinement stays off. +- Preventing writes outside the workspace and other explicitly writable locations +- Keeping sandboxed commands from changing `.git` metadata +- Blocking direct outbound connections from sandboxed commands and policy-aware tools when network restriction is on +- Applying the same restrictions to child processes, such as package installation and build scripts launched by a shell command + +This can reduce the risk of auto-approving selected routine commands, such as builds and tests, by placing operating-system limits around many of their effects. It does **not** make **Allow Everything** safe. An allowed command can still modify or delete workspace files, alter other writable Kilo directories, consume data it can read, or write unsafe code that runs later outside the sandbox. + +The sandbox does not protect against every result of prompt injection. In particular, it does not prevent the agent from reading accessible files or including their contents in model context. It also cannot confine local MCP servers, plugin hooks, or any integration that runs outside the sandbox boundary. + +{% callout type="warning" %} +The network sandbox is not a provider privacy control. Provider and model inference traffic remains available. If Kilo reads a secret and includes it in a prompt, tool result, or conversation context, that content may be sent to the configured model provider even while network restriction is on. Choose providers with data-handling policies appropriate for your work, consider a local model for sensitive projects, and use read permissions to block or prompt for sensitive files. See [Prompt-Training Model Visibility](/docs/getting-started/settings#prompt-training-model-visibility). +{% /callout %} + +## Sandboxing and permissions + +Permissions and sandboxing solve different parts of the security problem and work best together. + +| Control | What it decides | Best used for | +|---|---|---| +| Permissions | Whether Kilo allows, asks about, or denies a matching tool invocation | Prompting for sensitive file reads, blocking specific commands or tools, reviewing consequential actions, and limiting MCP tool or subagent invocation | +| Sandbox | What an allowed tool call can change or connect to while it runs | Limiting the impact of model mistakes, prompt injection, malicious dependencies, and unexpected child-process behavior | + +Permissions can ask or deny Kilo tool invocations that read or change data. For example, set `read` or `external_directory` rules to `ask` or `deny` for credentials, personal files, or directories the agent does not need. Kilo's `read` tool also prompts for `.env` and `.env.*` unless you explicitly create a matching sensitive-file rule. See [Agent Permissions](/docs/customize/agent-permissions) for path and command rules. + +Permission rules are tool-specific and do not create a complete file-confidentiality boundary. A `read` denial controls Kilo's file-reading tool, but an allowed `grep` call, shell command, build script, or other process may read the same file through a different path. A child process can also print sensitive content into tool output, which may then become model context. Configure `grep`, `bash`, and other data-accessing tools separately, and avoid running untrusted code when sensitive files remain readable by your operating-system account. + +For a given tool invocation, approving a shell command does not grant writes outside the sandbox, and a path being writable inside the sandbox does not bypass a matching permission rule. Some integration code runs outside this boundary: plugin hooks can run before a tool's internal permission check, and a local MCP server starts as a separate trusted process. MCP permissions control exposed tool invocations, not everything the server process can do during startup or in the background. Enable only local MCP servers and plugins you trust. + +A practical setup for work on unfamiliar or partially trusted code is: + +- Keep `read`, `grep`, and unnecessary external-directory access set to `ask` or `deny` when they may expose sensitive content. +- Allow only routine tools and command patterns that you want to run without interruption. +- Keep shell approval prompts for commands with important in-workspace effects or commands that can read sensitive data, because the sandbox still allows workspace writes and filesystem reads. +- Enable the sandbox and keep network restriction on to reduce write and direct network-exfiltration impact if an approved action behaves unexpectedly. +- Add extra writable paths only when a known workflow requires them. + +For a stronger confidentiality boundary, remove sensitive files from the environment or run Kilo under a separate operating-system account, container, or virtual machine that cannot read them. If file contents must not leave your machine, use local inference and disable other integrations that can send data over the network. If remote processing is acceptable, choose a provider with data-handling terms suitable for the data involved. + +Configure these rules in **Settings > Auto Approve** or `kilo.jsonc`. See [Auto-Approving Actions](/docs/getting-started/settings/auto-approving-actions) for the settings UI and default permission behavior. + +## Filesystem restrictions + +When the sandbox is active, agent tools can read files normally. The sandbox restricts writes, including creating, changing, renaming, and deleting files. + +Writes are allowed in: + +- The active workspace or worktree +- Kilo's runtime directories listed below +- Paths listed in `sandbox.writable_paths` + +| Writable Kilo path | Purpose | +|---|---| +| `$XDG_DATA_HOME/kilo` (normally `~/.local/share/kilo`) | Session data, logs, and Kilo's managed repository cache under `repos/` | +| `$XDG_CACHE_HOME/kilo` (normally `~/.cache/kilo`) | Cached data and downloaded binaries | +| `$XDG_CONFIG_HOME/kilo` (normally `~/.config/kilo`) | Configuration and installed plugins | +| `$XDG_STATE_HOME/kilo` (normally `~/.local/state/kilo`) | Runtime state | +| `$TMPDIR/kilo` | Temporary files; on macOS this is commonly under `/var/folders/.../T/kilo` | + +Writes are denied everywhere else. The following rules still apply inside writable locations: + +- `.git` directories are always read-only to sandboxed tools. +- Kilo's stored sandbox policy and preference files are read-only. +- A permission approval for a path outside the sandbox does not make that path writable. Add the path to **Additional Writable Paths** if the tool must modify it. +- Linked worktree sessions can write to their active worktree, not the primary checkout or sibling worktrees. + +Shell commands and their child processes inherit the same restrictions. Kilo's file tools perform mutations through a sandboxed worker. Writable file handles are unavailable, so a tool that requires an open read-write handle may fail even for an allowed path. + +Because Kilo's config directory is writable, a shell command can change configuration, permissions, plugins, or additional writable paths that affect future tool calls. Direct filesystem access inside trusted integrations is confined only when the integration uses Kilo's sandbox-aware filesystem service. Starting or restarting a process with the background-process tool is unavailable while sandboxing is active. + +{% callout type="info" %} +The sandbox is a write boundary, not a privacy boundary. It does not prevent an agent from reading files outside your workspace if your operating-system account can read them. +{% /callout %} + +## Network restrictions + +**Restrict Network Access** controls outbound network access independently of filesystem writes. Turning it off leaves the filesystem write restrictions active. + +When network restriction is on, Kilo blocks: + +- Outbound network access from model-originated shell commands and their child processes +- Requests from built-in HTTP tools such as web fetch and web search +- Remote MCP tool calls and custom or plugin tools that Kilo cannot prove will remain offline +- Built-in tools such as codebase search, semantic search, and LSP that may use opaque or indirect network access + +Network restriction does not block: + +- Provider and model inference traffic, so conversations with the selected model continue to work +- Local MCP server processes +- Plugin hooks that run outside the sandboxed tool execution +- Filesystem reads + +This is not a system-wide firewall. It applies to the sandboxed tool execution boundary, not every Kilo, extension, or local process. Proxy environment variables are removed from sandboxed commands while network access is restricted. + +## Session behavior + +The config setting supplies the initial default for new sessions that do not have a saved preference. Use the lock button in the VS Code prompt or `/sandbox` in the CLI to change the current session. Your latest choice is saved as the default for future sessions in that project, takes precedence over the config default, and persists across restarts. + +Each initialized session keeps its sandbox enabled state and network mode. Changing those settings affects new sessions; use the prompt control or `/sandbox` to change an existing session's enabled state. Changes to **Additional Writable Paths** are read when tools run and therefore also apply to existing sandboxed sessions. + +Forked sessions retain the source session's confinement. Subagents inherit the stricter combination of the parent and child settings: sandboxing remains enabled if either requires it, and network remains blocked if either requires blocking. + +Cloud sessions do not expose the local sandbox control because their tools do not run in your local sandbox. ## Platform support | Platform | Backend | Notes | |---|---|---| -| macOS | `sandbox-exec` (seatbelt) | Uses the system `/usr/bin/sandbox-exec`. | -| Linux | Bubblewrap (`bwrap`) | Uses system `/usr/bin/bwrap`, or a bundled, SHA-256-verified binary. Override the path with the `KILO_BWRAP_PATH` environment variable. The executable is probed at startup to confirm it can create the sandbox. | -| Windows | none | The sandbox backend is unavailable on Windows. Enabling the config has no effect. | - -## Limitations - -- **Windows is not supported.** -- Local MCP servers and plugin hooks are **not** covered by the network restriction. -- File **reads** are not confined — only writes and shell command effects are. -- Writable file handles are unavailable while the sandbox is active; writes are performed through a sandboxed worker, so some tools that open files for writing may behave differently. -- The sandbox is additive to the permission system, not a replacement. Permission rules still apply first. +| macOS | `sandbox-exec` (Seatbelt) | Uses a Seatbelt profile through `/usr/bin/sandbox-exec`. | +| Linux | Bubblewrap (`bwrap`) | Uses system `/usr/bin/bwrap` or a bundled, SHA-256-verified binary. `KILO_BWRAP_PATH` can select another binary. Kilo probes filesystem and network namespace support before enabling confinement. Additional writable paths must already exist before Bubblewrap starts. | +| Windows | None | Unsupported. The VS Code settings and prompt controls are hidden, and enabling the config has no effect. | diff --git a/packages/kilo-docs/pages/getting-started/setup-authentication.md b/packages/kilo-docs/pages/getting-started/setup-authentication.md index 87521fd1a18..55946f5fedc 100644 --- a/packages/kilo-docs/pages/getting-started/setup-authentication.md +++ b/packages/kilo-docs/pages/getting-started/setup-authentication.md @@ -1,16 +1,16 @@ --- -title: "Setup & Authentication" +title: "Authentication" description: "Configure Kilo Code and connect to your AI providers" --- -# Setup & Authentication +# Authentication When you install Kilo Code, you'll be prompted to sign in or create a free account. This automatically configures everything you need to get started. ## Quick Start with Kilo Account {% tabs %} -{% tab label="VSCode" %} +{% tab label="VS Code" %} The extension prompts you to sign in when you first open Kilo Code in VS Code. Click **Sign In** and complete the browser-based flow. Sign-in applies across extension surfaces, including the sidebar and Agent Manager. @@ -93,7 +93,7 @@ Already have a ChatGPT subscription? You can use it with Kilo Code through the [ ### Configuring Your Provider {% tabs %} -{% tab label="VSCode" %} +{% tab label="VS Code" %} 1. Open Kilo Code in VS Code 2. Click the gear icon ({% codicon name="gear" /%}) in the extension UI to open **Settings** diff --git a/packages/kilo-docs/public/img/jetbrains/plugin-auto-updates.png b/packages/kilo-docs/public/img/jetbrains/plugin-auto-updates.png new file mode 100644 index 00000000000..5cacbf807f6 Binary files /dev/null and b/packages/kilo-docs/public/img/jetbrains/plugin-auto-updates.png differ diff --git a/packages/kilo-docs/public/img/jetbrains/plugin-marketplace.png b/packages/kilo-docs/public/img/jetbrains/plugin-marketplace.png new file mode 100644 index 00000000000..8578772da90 Binary files /dev/null and b/packages/kilo-docs/public/img/jetbrains/plugin-marketplace.png differ diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/bash-with-permission-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/bash-with-permission-chromium-linux.png index ba354fdf8a3..fd4d53565ef 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/bash-with-permission-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/bash-with-permission-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:234454319a6d8ce9e513411c7eb94865053bd24b8ac6624b08151af6658ac7ac -size 14909 +oid sha256:12f523463f86c86dffc350a32c0f76e39391f602c32f0b778d410c87cc83fb1a +size 15708 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/glob-with-permission-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/glob-with-permission-chromium-linux.png index ffc6b05851c..6be0770b52a 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/glob-with-permission-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/glob-with-permission-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a80f1cfd15e41bff690dda11e083b45dd0dba6936833bbb1292fd20e4e8e3a4 -size 15091 +oid sha256:58b145cc30b2d7580ea859dcd27b88535d33983d4125004586ba2568e3024252 +size 15889 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-bash-many-rules-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-bash-many-rules-chromium-linux.png index c19ee28042d..b9cfee32b98 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-bash-many-rules-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-bash-many-rules-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6f7e685eee7798085c2f68e3909b97c5cdef4610a5172ac29ec990e02d164e43 -size 17881 +oid sha256:d5a5fdd318bd14100e0c71f7f9055d830db1e59b6af57007608881f433a177ca +size 18703 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-external-dir-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-external-dir-chromium-linux.png index 72c72e77131..9e304fcc270 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-external-dir-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-external-dir-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6f05d9512bbe3309c6d073fd8aca8e0800c7e2827bdefe6115e636e8a6e94070 -size 16239 +oid sha256:d544d3964f4a442b12d1eb47aa992c756ac537c2c810adb9abb072f99a815135 +size 17177 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-subagent-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-subagent-chromium-linux.png index be6a5d5bff0..ee392e026bb 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-subagent-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-subagent-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:836d6b819a7108510a2abd5db6c9307945a7489c5a2b240035bfc1acb3405385 -size 13009 +oid sha256:10956634c051d1d1eff54229083434ddb1882adc49161d524a14ab3fc3859554 +size 13822 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-todo-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-todo-chromium-linux.png index b4ad5e3c158..c4a677a6b78 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-todo-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-todo-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19395088343e6546ae2f8d3232b8e1e17e5db482841dcf516a80c40fcbc8604a -size 14912 +oid sha256:0249acab24874167ad0f6e88d12897be50dc12861e763a89a4d90545309de3b3 +size 15709 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-websearch-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-websearch-chromium-linux.png index e01e50db549..36c34d09aab 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-websearch-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/permission-dock-websearch-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:45f622206510533051173fbab26f30c1359d25b9baff7d515c05b664b16d7aa4 -size 14734 +oid sha256:dc61e88dc65c15583e0c972f2d370e1c75c7a87949c557492ffbc146b2f75ac5 +size 15535 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/question-dismissed-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/question-dismissed-chromium-linux.png index 52aaff1f7d3..b403a285409 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/question-dismissed-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/composite-webview/question-dismissed-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cba6f84ec6138fc59be088cdf84d2ebe57a55f8514df58eed693a84969ff7a90 -size 4794 +oid sha256:eecccfc0bbd53bd52261ff8cfc02c444c6d97f6438e6693d8cd998c36909708a +size 5178 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/labs-tool-call-lab/search-previews-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/labs-tool-call-lab/search-previews-chromium-linux.png index 3ef81e61c15..cd9e5cc0ec2 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/labs-tool-call-lab/search-previews-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/labs-tool-call-lab/search-previews-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c94c54757e88700b5c48a10a60d8626d8d5ed76a29879f06059e5f6457382a5f -size 614004 +oid sha256:42c43e9ba12bc73da98c915dafca274beee1d0c893c168d8af4e3e3f41edd367 +size 630896 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/auto-approve-bash-only-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/auto-approve-bash-only-chromium-linux.png new file mode 100644 index 00000000000..7e95d738d59 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/auto-approve-bash-only-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de86ff4ba9cc043a58527d8ffb77dccc9e6ae5c621d15b30fc8cfc3f2de366ab +size 48316 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/sandboxing-panel-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/sandboxing-panel-chromium-linux.png index 6d462eb5f3c..3bde409405d 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/sandboxing-panel-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/sandboxing-panel-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ea36a9604fbc773d2312289f46205221e607a927b0d302b1d9a5e6bdc1d80308 -size 28888 +oid sha256:3c0693f1ad6eed615e5a49733ef3a3ec58026fac922cb80e2f3720c94755591f +size 47933 diff --git a/packages/kilo-docs/source-links.md b/packages/kilo-docs/source-links.md index b64ddd110bb..e2dee6036e4 100644 --- a/packages/kilo-docs/source-links.md +++ b/packages/kilo-docs/source-links.md @@ -32,6 +32,8 @@ - +- + - - @@ -137,6 +139,8 @@ - +- + - - @@ -156,6 +160,8 @@ - +- + - - diff --git a/packages/kilo-gateway/package.json b/packages/kilo-gateway/package.json index c0959ee2eff..434bce6af3a 100644 --- a/packages/kilo-gateway/package.json +++ b/packages/kilo-gateway/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/kilo-gateway", - "version": "7.4.1", + "version": "7.4.5", "type": "module", "license": "MIT", "description": "Unified Kilo Gateway package for OpenCode - authentication, provider, and API integration", diff --git a/packages/kilo-gateway/src/api/models.ts b/packages/kilo-gateway/src/api/models.ts index 5ee3a633528..5c1d4b24f9a 100644 --- a/packages/kilo-gateway/src/api/models.ts +++ b/packages/kilo-gateway/src/api/models.ts @@ -90,6 +90,75 @@ export async function fetchKiloModels(options?: { kilocodeOrganizationId?: string baseURL?: string }): Promise { + const raw = await fetchRawKiloModels(options) + if (raw.error) return { models: {}, error: raw.error } + + // Transform models to ModelsDev.Model format + const models: Record = {} + + for (const model of raw.data) { + // Skip image generation models + if (model.architecture?.output_modalities?.includes("image")) { + continue + } + + // Skip models that don't support tools — Kilo requires tool calling + if (!model.supported_parameters?.includes("tools")) { + continue + } + + const transformedModel = transformToModelDevFormat(model) + models[model.id] = transformedModel + } + + return { models } +} + +export type KiloImageModel = { + id: string + name: string + description?: string +} + +export type KiloImageModelsResult = { + models: KiloImageModel[] + error?: { kind: "unauthorized" | "network" | "schema" | "http"; status?: number } +} + +/** + * Fetch image-capable models from Kilo API (OpenRouter-compatible endpoint). + * Uses the same raw fetch as {@link fetchKiloModels} but keeps only models + * whose `output_modalities` include `"image"`. + */ +export async function fetchKiloImageModels(options?: { + kilocodeToken?: string + kilocodeOrganizationId?: string + baseURL?: string +}): Promise { + const raw = await fetchRawKiloModels(options) + if (raw.error) return { models: [], error: raw.error } + + const models: KiloImageModel[] = [] + + for (const model of raw.data) { + if (model.architecture?.output_modalities?.includes("image")) { + models.push({ id: model.id, name: model.name, description: model.description }) + } + } + + return { models } +} + +/** + * Shared raw fetch + validate used by both {@link fetchKiloModels} and {@link fetchKiloImageModels}. + */ +async function fetchRawKiloModels(options?: { + kilocodeToken?: string + kilocodeOrganizationId?: string + baseURL?: string +}): Promise< + { data: OpenRouterModel[]; error?: undefined } | { data?: undefined; error: NonNullable } +> { const token = options?.kilocodeToken const organizationId = options?.kilocodeOrganizationId @@ -114,50 +183,32 @@ export async function fetchKiloModels(options?: { }).catch((err: unknown) => err as Error) if (response instanceof Error) { - return { models: {}, error: { kind: "network" } } + return { error: { kind: "network" } } } if (!response.ok) { // 401 with auth credentials: fall back to unauthenticated public endpoint if (response.status === 401 && (token || organizationId)) { - return fetchKiloModels({}) + return fetchRawKiloModels({}) } const kind = response.status === 401 || response.status === 403 ? "unauthorized" : "http" - return { models: {}, error: { kind, status: response.status } } + return { error: { kind, status: response.status } } } const json = await response.json().catch(() => null) if (json === null) { - return { models: {}, error: { kind: "schema" } } + return { error: { kind: "schema" } } } // Validate response schema const result = openRouterModelsResponseSchema.safeParse(json) if (!result.success) { - return { models: {}, error: { kind: "schema" } } + return { error: { kind: "schema" } } } - // Transform models to ModelsDev.Model format - const models: Record = {} - - for (const model of result.data.data) { - // Skip image generation models - if (model.architecture?.output_modalities?.includes("image")) { - continue - } - - // Skip models that don't support tools — Kilo requires tool calling - if (!model.supported_parameters?.includes("tools")) { - continue - } - - const transformedModel = transformToModelDevFormat(model) - models[model.id] = transformedModel - } - - return { models } + return { data: result.data.data } } /** diff --git a/packages/kilo-gateway/src/api/profile.ts b/packages/kilo-gateway/src/api/profile.ts index aa717a64c43..0540af4eeff 100644 --- a/packages/kilo-gateway/src/api/profile.ts +++ b/packages/kilo-gateway/src/api/profile.ts @@ -25,6 +25,8 @@ export async function fetchProfile(token: string): Promise { email?: string name?: string organizations?: Organization[] + selectedOrganizationId?: string | null + hasPersonalAccount?: boolean | null } // Backend returns { user: { email, name, ... }, organizations } // Transform to flat KilocodeProfile structure @@ -32,6 +34,8 @@ export async function fetchProfile(token: string): Promise { email: data.user?.email ?? data.email ?? "", name: data.user?.name ?? data.name, organizations: data.organizations, + selectedOrganizationId: data.selectedOrganizationId ?? undefined, + hasPersonalAccount: data.hasPersonalAccount ?? undefined, } } @@ -40,6 +44,21 @@ export async function fetchProfile(token: string): Promise { */ export const getKiloProfile = fetchProfile +/** + * Resolve the organization a fresh login should default to. + * + * Applied once at login time (not on every profile fetch): prefer the cloud's + * validated `selectedOrganizationId`, and fall back to the first organization + * when the account has no personal account to sit on. Returns `undefined` to + * mean "personal account". + */ +export function defaultOrganizationId(profile: KilocodeProfile): string | undefined { + const orgs = profile.organizations ?? [] + const selected = profile.selectedOrganizationId + const valid = selected && orgs.some((org) => org.id === selected) ? selected : undefined + return valid ?? (profile.hasPersonalAccount === false ? orgs[0]?.id : undefined) +} + /** * Fetch user balance from Kilo API * @param token - Authentication token diff --git a/packages/kilo-gateway/src/auth/device-auth-tui.ts b/packages/kilo-gateway/src/auth/device-auth-tui.ts index 466aca7baaf..3e150b4f787 100644 --- a/packages/kilo-gateway/src/auth/device-auth-tui.ts +++ b/packages/kilo-gateway/src/auth/device-auth-tui.ts @@ -1,7 +1,7 @@ import { execFile } from "child_process" import type { DeviceAuthInitiateResponse, DeviceAuthPollResponse } from "../types.js" import { poll } from "./polling.js" -import { getKiloProfile, getKiloDefaultModel } from "../api/profile.js" +import { getKiloProfile, getKiloDefaultModel, defaultOrganizationId } from "../api/profile.js" import { KILO_API_BASE, POLL_INTERVAL_MS } from "../api/constants.js" import type { AuthOuathResult } from "@kilocode/plugin" @@ -127,13 +127,14 @@ export async function authenticateWithDeviceAuthTUI(inputs?: Record undefined) + const organizationId = profile ? defaultOrganizationId(profile) : undefined // Fetch default model - const model = await getKiloDefaultModel(token, organizationId) + await getKiloDefaultModel(token, organizationId) // Return success with OAuth credentials return { @@ -142,6 +143,7 @@ export async function authenticateWithDeviceAuthTUI(inputs?: Record { + try { + const proxy = await getProxyAuth() + if (!proxy.auth || !proxy.token) throw new UnauthorizedError() + + const result = await fetchKiloImageModels({ + kilocodeToken: proxy.token, + kilocodeOrganizationId: proxy.organizationId, + }) + if (result.error) { + if (result.error.kind === "unauthorized") throw new UnauthorizedError() + throw new Error(`Failed to fetch image models: ${result.error.kind}`) + } + return c.json(result.models) + } catch (err) { + if (!(err instanceof UnauthorizedError)) throw err + return c.json({ error: "Not authenticated with Kilo Gateway" }, 401) + } + }, + ) + .post( + "/image/generations", + describeRoute({ + summary: "Image generation", + description: + "Proxy an image generation request (chat-completions with modalities) to the Kilo Gateway OpenRouter passthrough", + operationId: "kilo.image.generations", + responses: { + 200: { + description: "Image generation response", + content: { + "application/json": { + schema: resolver(z.unknown()), + }, + }, + }, + ...errors(400, 401), + }, + }), + validator("json", z.object({ body: z.unknown() }).passthrough()), + async (c: any) => { + const proxy = await getProxyAuth() + if (!proxy.auth) return c.json({ error: "Not authenticated with Kilo Gateway" }, 401) + if (!proxy.token) return c.json({ error: "No valid token found" }, 401) + + const payload = c.req.valid("json") + const headers = { + "Content-Type": "application/json", + Authorization: `Bearer ${proxy.token}`, + ...buildKiloHeaders(undefined, { kilocodeOrganizationId: proxy.organizationId }), + [HEADER_FEATURE]: "vscode-extension", + } + + const response = await fetch(`${KILO_API_BASE}/api/openrouter/chat/completions`, { + method: "POST", + headers, + signal: c.req.raw.signal, + body: JSON.stringify(payload.body ?? payload), + }) + + const text = await response.text() + return new Response(text, { + status: response.status, + headers: { + "Content-Type": response.headers.get("Content-Type") ?? "application/json", + }, + }) + }, + ) .get( "/notifications", describeRoute({ diff --git a/packages/kilo-gateway/src/tui/helpers.ts b/packages/kilo-gateway/src/tui/helpers.ts index 6884312e49e..f57f2e944c8 100644 --- a/packages/kilo-gateway/src/tui/helpers.ts +++ b/packages/kilo-gateway/src/tui/helpers.ts @@ -56,19 +56,25 @@ export function formatProfileInfo( export function getOrganizationOptions( organizations: Organization[], currentOrgId?: string, + hasPersonalAccount = true, ): Array<{ title: string value: string | null description?: string category: string }> { + const personal = hasPersonalAccount || organizations.length === 0 return [ - { - title: "Personal Account", - value: null, - description: !currentOrgId ? "→ (current)" : undefined, - category: "Accounts", - }, + ...(personal + ? [ + { + title: "Personal Account", + value: null, + description: !currentOrgId ? "→ (current)" : undefined, + category: "Accounts", + }, + ] + : []), ...organizations.map((org) => ({ title: org.name, value: org.id, diff --git a/packages/kilo-gateway/src/types.ts b/packages/kilo-gateway/src/types.ts index 801464966b7..3100b1e6304 100644 --- a/packages/kilo-gateway/src/types.ts +++ b/packages/kilo-gateway/src/types.ts @@ -27,6 +27,8 @@ export interface KilocodeProfile { email: string name?: string organizations?: Organization[] + selectedOrganizationId?: string + hasPersonalAccount?: boolean } export interface KilocodeBalance { diff --git a/packages/kilo-gateway/test/api/profile.test.ts b/packages/kilo-gateway/test/api/profile.test.ts new file mode 100644 index 00000000000..5ae6b2aaf5c --- /dev/null +++ b/packages/kilo-gateway/test/api/profile.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, test } from "bun:test" +import { defaultOrganizationId } from "../../src/api/profile.js" +import type { KilocodeProfile } from "../../src/types.js" + +const profile = (input: Partial = {}): KilocodeProfile => ({ + email: "user@example.com", + organizations: [{ id: "org_1", name: "Acme", role: "MEMBER" }], + ...input, +}) + +describe("defaultOrganizationId", () => { + test("defaults to the cloud selected organization", () => { + expect(defaultOrganizationId(profile({ selectedOrganizationId: "org_1" }))).toBe("org_1") + }) + + test("defaults to personal when there is no cloud selection", () => { + expect(defaultOrganizationId(profile())).toBeUndefined() + }) + + test("ignores a cloud selection that is not one of the user's organizations", () => { + expect(defaultOrganizationId(profile({ selectedOrganizationId: "missing" }))).toBeUndefined() + }) + + test("falls back to the first organization when there is no personal account", () => { + expect( + defaultOrganizationId( + profile({ + hasPersonalAccount: false, + organizations: [ + { id: "org_1", name: "Acme", role: "MEMBER" }, + { id: "org_2", name: "Beta", role: "MEMBER" }, + ], + }), + ), + ).toBe("org_1") + }) + + test("prefers a valid cloud selection over the first-organization fallback", () => { + expect( + defaultOrganizationId( + profile({ + selectedOrganizationId: "org_2", + hasPersonalAccount: false, + organizations: [ + { id: "org_1", name: "Acme", role: "MEMBER" }, + { id: "org_2", name: "Beta", role: "MEMBER" }, + ], + }), + ), + ).toBe("org_2") + }) +}) diff --git a/packages/kilo-i18n/package.json b/packages/kilo-i18n/package.json index 3a3b5ef3b0e..8bef38f49e7 100644 --- a/packages/kilo-i18n/package.json +++ b/packages/kilo-i18n/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/kilo-i18n", - "version": "7.4.1", + "version": "7.4.5", "type": "module", "license": "MIT", "description": "Kilo-specific i18n translations and overrides", diff --git a/packages/kilo-indexing/package.json b/packages/kilo-indexing/package.json index 528b4dd8d28..d1c4cb4a80d 100644 --- a/packages/kilo-indexing/package.json +++ b/packages/kilo-indexing/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/kilo-indexing", - "version": "7.4.1", + "version": "7.4.5", "type": "module", "license": "MIT", "description": "Standalone indexing engine and host helpers for Kilo Code", diff --git a/packages/kilo-indexing/src/indexing/processors/file-watcher.ts b/packages/kilo-indexing/src/indexing/processors/file-watcher.ts index eda57909ab6..4a0d925e47f 100644 --- a/packages/kilo-indexing/src/indexing/processors/file-watcher.ts +++ b/packages/kilo-indexing/src/indexing/processors/file-watcher.ts @@ -3,7 +3,6 @@ import { stat, readFile } from "fs/promises" import { createHash } from "crypto" import path from "path" import { v5 as uuidv5 } from "uuid" -import type { Ignore } from "ignore" import { Emitter, type Disposable } from "../runtime" import { QDRANT_CODE_BLOCK_NAMESPACE, @@ -33,6 +32,7 @@ import { FileIgnore } from "../../file/ignore" import { Log } from "../../util/log" import type { WorktreeOverlay } from "../worktree-overlay" import { sanitizeErrorMessage } from "../shared/validation-helpers" +import type { IgnoreMatcher } from "../shared/load-ignore" const log = Log.create({ service: "file-watcher" }) @@ -43,7 +43,7 @@ const log = Log.create({ service: "file-watcher" }) * so the watcher works outside VS Code (CLI, tests, headless). */ export class FileWatcher implements IFileWatcher { - private ignoreInstance?: Ignore + private ignoreInstance?: IgnoreMatcher private watcher?: ChokidarFSWatcher private accumulatedEvents: Map = new Map() private batchProcessDebounceTimer?: NodeJS.Timeout @@ -70,7 +70,7 @@ export class FileWatcher implements IFileWatcher { private readonly cacheManager: CacheManager, private embedder?: IEmbedder, private vectorStore?: IVectorStore, - ignoreInstance?: Ignore, + ignoreInstance?: IgnoreMatcher, batchSegmentThreshold?: number, maxBatchRetries?: number, private readonly onTelemetry?: IndexingTelemetryReporter, diff --git a/packages/kilo-indexing/src/indexing/processors/scanner.ts b/packages/kilo-indexing/src/indexing/processors/scanner.ts index 564c0d47c60..a746b5d456d 100644 --- a/packages/kilo-indexing/src/indexing/processors/scanner.ts +++ b/packages/kilo-indexing/src/indexing/processors/scanner.ts @@ -1,4 +1,3 @@ -import type { Ignore } from "ignore" import { stat, readFile } from "fs/promises" import path from "path" import { glob } from "glob" @@ -28,6 +27,7 @@ import { FileIgnore } from "../../file/ignore" import { Log } from "../../util/log" import { sanitizeErrorMessage } from "../shared/validation-helpers" import type { IndexingTelemetryMeta, IndexingTelemetryMode, IndexingTelemetryReporter } from "../interfaces/telemetry" +import type { IgnoreMatcher } from "../shared/load-ignore" const log = Log.create({ service: "indexing-scanner" }) @@ -41,7 +41,7 @@ export class DirectoryScanner implements IDirectoryScanner { private readonly vectorStore: IVectorStore, private readonly codeParser: ICodeParser, private readonly cacheManager: CacheManager, - private readonly ignoreInstance: Ignore, + private readonly ignoreInstance: IgnoreMatcher, batchSegmentThreshold?: number, maxBatchRetries?: number, private readonly onTelemetry?: IndexingTelemetryReporter, diff --git a/packages/kilo-indexing/src/indexing/service-factory.ts b/packages/kilo-indexing/src/indexing/service-factory.ts index af4ac4cbf3b..09e4706aac2 100644 --- a/packages/kilo-indexing/src/indexing/service-factory.ts +++ b/packages/kilo-indexing/src/indexing/service-factory.ts @@ -1,4 +1,3 @@ -import type { Ignore } from "ignore" import path from "path" import { getDefaultModelId } from "./model-registry" @@ -28,6 +27,7 @@ import { REMOTE_EMBEDDER_VALIDATION_TIMEOUT_MS, } from "./constants" import { Log } from "../util/log" +import type { IgnoreMatcher } from "./shared/load-ignore" const log = Log.create({ service: "indexing-factory" }) @@ -208,7 +208,7 @@ export class CodeIndexServiceFactory { embedder: IEmbedder, vectorStore: IVectorStore, parser: ICodeParser, - ignoreInstance: Ignore, + ignoreInstance: IgnoreMatcher, ): DirectoryScanner { const config = this.configManager.getConfig() const meta = this.getTelemetryMeta() @@ -229,7 +229,7 @@ export class CodeIndexServiceFactory { embedder: IEmbedder, vectorStore: IVectorStore, cacheManager: CacheManager, - ignoreInstance: Ignore, + ignoreInstance: IgnoreMatcher, ): IFileWatcher { const config = this.configManager.getConfig() const meta = this.getTelemetryMeta() @@ -248,7 +248,7 @@ export class CodeIndexServiceFactory { public createServices( cacheManager: CacheManager, - ignoreInstance: Ignore, + ignoreInstance: IgnoreMatcher, ): { embedder: IEmbedder vectorStore: IVectorStore diff --git a/packages/kilo-indexing/src/indexing/shared/load-ignore.ts b/packages/kilo-indexing/src/indexing/shared/load-ignore.ts index 77d123645a2..72e1c31b2e0 100644 --- a/packages/kilo-indexing/src/indexing/shared/load-ignore.ts +++ b/packages/kilo-indexing/src/indexing/shared/load-ignore.ts @@ -1,8 +1,21 @@ import fs from "fs/promises" +import { glob } from "glob" import ignore, { type Ignore } from "ignore" import path from "path" +import { FileIgnore } from "../../file/ignore" const files = [".gitignore", ".kilocodeignore"] as const +const order = new Map(files.map((name, index) => [name, index])) + +type Entry = { + dir: string + name: string + txt: string | undefined +} + +export interface IgnoreMatcher { + ignores(filePath: string): boolean +} function notFound(err: unknown): boolean { if (!err || typeof err !== "object") { @@ -11,8 +24,30 @@ function notFound(err: unknown): boolean { return "code" in err && err.code === "ENOENT" } -async function read(root: string, name: string): Promise { - return fs.readFile(path.join(root, name), "utf8").catch((err) => { +function toPosix(value: string): string { + return value.replaceAll("\\", "/") +} + +function depth(dir: string): number { + if (!dir) { + return 0 + } + return dir.split("/").length +} + +function relative(root: string, filePath: string): string | undefined { + const rel = toPosix(path.relative(root, filePath)) + if (!rel || rel === ".") { + return + } + if (rel === ".." || rel.startsWith("../") || path.isAbsolute(rel)) { + return + } + return rel +} + +async function read(filePath: string): Promise { + return fs.readFile(filePath, "utf8").catch((err) => { if (notFound(err)) { return undefined } @@ -20,18 +55,127 @@ async function read(root: string, name: string): Promise { }) } -export async function loadIgnore(root: string): Promise { - const ig = ignore() +function escape(dir: string): string { + return dir + .split("/") + .map((part) => part.replace(/[\\[\]*?!#]/g, "\\$&")) + .join("/") +} - for (const name of files) { - const txt = await read(root, name) - if (!txt?.trim()) { +function discovery(): string[] { + const result = new Set(FileIgnore.PATTERNS) + for (const pattern of FileIgnore.PATTERNS) { + if (pattern.includes("/") || [...pattern].some((char) => "*!?[]{}()".includes(char))) { + continue + } + result.add(`${pattern}/**`) + result.add(`**/${pattern}/**`) + } + return [...result] +} + +function rules(dir: string, txt: string): string[] { + const result = [] + for (const line of txt.split(/\r?\n/)) { + if (!line.trim() || line.startsWith("#")) { continue } - ig.add(txt) - ig.add(name) - } + const negated = line.startsWith("!") + const raw = negated ? line.slice(1) : line + const anchored = raw.startsWith("/") + const body = anchored ? raw.slice(1) : raw + if (!body) { + continue + } - return ig + const root = escape(dir) + const match = body.endsWith("/") ? body.slice(0, -1) : body + const scoped = anchored || match.includes("/") ? `${root}/${body}` : `${root}/**/${body}` + result.push(negated ? `!${scoped}` : scoped) + } + return result +} + +class WorkspaceIgnore implements IgnoreMatcher { + constructor(private readonly matcher: Ignore) {} + + ignores(filePath: string): boolean { + const rel = toPosix(path.normalize(filePath)) + if (!rel || rel === "." || rel === ".." || rel.startsWith("../") || path.isAbsolute(rel)) { + return false + } + + return this.matcher.ignores(rel) + } +} + +export async function loadIgnore(root: string): Promise { + const paths = await glob("**/{.gitignore,.kilocodeignore}", { + cwd: root, + absolute: true, + nodir: true, + dot: true, + ignore: discovery(), + maxDepth: Infinity, + }) + + const entries = await Promise.all( + paths.map(async (filePath) => { + const rel = relative(root, filePath) + if (!rel) { + return + } + if (FileIgnore.match(rel)) { + return + } + + const dir = toPosix(path.dirname(rel)) + const name = path.basename(rel) + if (!order.has(name as (typeof files)[number])) { + return + } + + const txt = await read(filePath) + + return { + dir: dir === "." ? "" : dir, + name, + txt, + } + }), + ) + + const sorted = entries + .filter((entry): entry is Entry => Boolean(entry)) + .sort((left, right) => { + const level = depth(left.dir) - depth(right.dir) + if (level !== 0) { + return level + } + const dir = left.dir.localeCompare(right.dir) + if (dir !== 0) { + return dir + } + return order.get(left.name as (typeof files)[number])! - order.get(right.name as (typeof files)[number])! + }) + + const matcher = ignore() + for (const entry of sorted) { + if (!entry.dir) { + if (entry.txt?.trim()) { + matcher.add(entry.txt) + } + matcher.add(entry.name) + continue + } + + if (entry.txt?.trim()) { + matcher.add(rules(entry.dir, entry.txt)) + } + matcher.add(`${entry.dir}/${entry.name}`) + } + matcher.add([".gitignore", ".kilocodeignore", "**/.gitignore", "**/.kilocodeignore"]) + + return new WorkspaceIgnore(matcher) } diff --git a/packages/kilo-indexing/test/kilocode/indexing/processors/file-watcher.test.ts b/packages/kilo-indexing/test/kilocode/indexing/processors/file-watcher.test.ts index 688fbb37a2d..80c8ace10f1 100644 --- a/packages/kilo-indexing/test/kilocode/indexing/processors/file-watcher.test.ts +++ b/packages/kilo-indexing/test/kilocode/indexing/processors/file-watcher.test.ts @@ -1,5 +1,5 @@ import { describe, test, expect } from "bun:test" -import { mkdtemp, mkdir, writeFile } from "fs/promises" +import { mkdtemp, mkdir, rm, writeFile } from "fs/promises" import { tmpdir } from "os" import path from "path" import { createHash } from "crypto" @@ -317,4 +317,29 @@ describe("FileWatcher", () => { expect(result.status).toBe("skipped") expect(result.reason).toBe("File is ignored by .gitignore or .kilocodeignore") }) + + test("processFile skips files matched by nested .gitignore during incremental updates", async () => { + const root = await mkdtemp(path.join(tmpdir(), "file-watcher-test-")) + try { + const cacheDir = path.join(root, ".cache") + const dir = path.join(root, "pkg") + const file = path.join(dir, "secret.ts") + + await mkdir(cacheDir, { recursive: true }) + await mkdir(dir, { recursive: true }) + await writeFile(path.join(dir, ".gitignore"), "secret.ts\n") + await writeFile(file, "export const secret = 1\n") + + const cache = new CacheManager(cacheDir, root) + await cache.initialize() + + const watcher = new FileWatcher(root, cache, createEmbedder(), undefined, await loadIgnore(root)) + const result = await watcher.processFile(file) + + expect(result.status).toBe("skipped") + expect(result.reason).toBe("File is ignored by .gitignore or .kilocodeignore") + } finally { + await rm(root, { recursive: true, force: true }) + } + }) }) diff --git a/packages/kilo-indexing/test/kilocode/indexing/processors/scanner.test.ts b/packages/kilo-indexing/test/kilocode/indexing/processors/scanner.test.ts index 65293df016b..6ea5bfff022 100644 --- a/packages/kilo-indexing/test/kilocode/indexing/processors/scanner.test.ts +++ b/packages/kilo-indexing/test/kilocode/indexing/processors/scanner.test.ts @@ -1,5 +1,5 @@ import { createHash } from "crypto" -import { mkdtemp } from "fs/promises" +import { mkdir, mkdtemp, rm } from "fs/promises" import ignore from "ignore" import { tmpdir } from "os" import { join } from "path" @@ -309,6 +309,34 @@ describe("DirectoryScanner", () => { expect(cache.getHash(open)).toBeDefined() }) + test("skips files matched by nested .kilocodeignore during full scans", async () => { + const root = await mkdtemp(join(tmpdir(), "scanner-test-")) + const cacheDir = await mkdtemp(join(tmpdir(), "scanner-cache-")) + try { + const dir = join(root, "pkg") + const blocked = join(dir, "blocked.ts") + const open = join(dir, "open.ts") + + await mkdir(dir, { recursive: true }) + await Bun.write(join(dir, ".kilocodeignore"), "blocked.ts\n") + await Bun.write(blocked, "export const blocked = 1\n") + await Bun.write(open, "export const open = 1\n") + + const cache = new CacheManager(cacheDir, root) + await cache.initialize() + + const scan = new DirectoryScanner(new Emb(), new Store(), new Parser(), cache, await loadIgnore(root), 1, 1) + const result = await scan.scanDirectory(root) + + expect(result.stats.processed).toBe(1) + expect(cache.getHash(blocked)).toBeUndefined() + expect(cache.getHash(open)).toBeDefined() + } finally { + await rm(root, { recursive: true, force: true }) + await rm(cacheDir, { recursive: true, force: true }) + } + }) + test("emits retry telemetry for transient batch failures", async () => { const root = await mkdtemp(join(tmpdir(), "scanner-test-")) const cacheDir = await mkdtemp(join(tmpdir(), "scanner-cache-")) diff --git a/packages/kilo-indexing/test/kilocode/indexing/shared/load-ignore.test.ts b/packages/kilo-indexing/test/kilocode/indexing/shared/load-ignore.test.ts index 0ed75e61d86..21f3b7b9f5e 100644 --- a/packages/kilo-indexing/test/kilocode/indexing/shared/load-ignore.test.ts +++ b/packages/kilo-indexing/test/kilocode/indexing/shared/load-ignore.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test" -import { mkdtemp, rm, writeFile } from "fs/promises" +import { mkdir, mkdtemp, rm, writeFile } from "fs/promises" import { tmpdir } from "os" import path from "path" import { loadIgnore } from "../../../../src/indexing/shared/load-ignore" @@ -38,6 +38,121 @@ describe("loadIgnore", () => { expect(ig.ignores("src/app.ts")).toBe(false) }) + test("loads nested .kilocodeignore relative to its directory", async () => { + await mkdir(path.join(root, "pkg", "sub"), { recursive: true }) + await writeFile(path.join(root, "pkg", ".kilocodeignore"), "secret.ts\n") + + const ig = await loadIgnore(root) + + expect(ig.ignores("pkg/secret.ts")).toBe(true) + expect(ig.ignores("pkg/sub/secret.ts")).toBe(true) + expect(ig.ignores("secret.ts")).toBe(false) + expect(ig.ignores("pkg/open.ts")).toBe(false) + }) + + test("anchors nested patterns that start with slash to the ignore file directory", async () => { + await mkdir(path.join(root, "pkg", "sub"), { recursive: true }) + await writeFile(path.join(root, "pkg", ".gitignore"), "/secret.ts\n") + + const ig = await loadIgnore(root) + + expect(ig.ignores("pkg/secret.ts")).toBe(true) + expect(ig.ignores("pkg/sub/secret.ts")).toBe(false) + }) + + test("matches nested bare directory patterns at any depth", async () => { + await mkdir(path.join(root, "pkg", "sub"), { recursive: true }) + await writeFile(path.join(root, "pkg", ".gitignore"), "dist/\n") + + const ig = await loadIgnore(root) + + expect(ig.ignores("pkg/dist/file.ts")).toBe(true) + expect(ig.ignores("pkg/sub/dist/file.ts")).toBe(true) + }) + + test("lets child ignore files override parent rules with negation", async () => { + await mkdir(path.join(root, "pkg"), { recursive: true }) + await writeFile(path.join(root, ".gitignore"), "*.ts\n") + await writeFile(path.join(root, "pkg", ".gitignore"), "!keep.ts\n") + + const ig = await loadIgnore(root) + + expect(ig.ignores("root.ts")).toBe(true) + expect(ig.ignores("pkg/drop.ts")).toBe(true) + expect(ig.ignores("pkg/keep.ts")).toBe(false) + }) + + test("keeps files ignored when a parent directory is ignored", async () => { + await mkdir(path.join(root, "pkg"), { recursive: true }) + await writeFile(path.join(root, ".gitignore"), "pkg/\n") + await writeFile(path.join(root, "pkg", ".gitignore"), "!keep.ts\n") + + const ig = await loadIgnore(root) + + expect(ig.ignores("pkg/keep.ts")).toBe(true) + }) + + test("allows descendants when a parent directory is re-included", async () => { + await mkdir(path.join(root, "pkg"), { recursive: true }) + await writeFile(path.join(root, ".gitignore"), "pkg/\n") + await writeFile(path.join(root, ".kilocodeignore"), "!pkg/\n") + + const ig = await loadIgnore(root) + + expect(ig.ignores("pkg/file.ts")).toBe(false) + }) + + test("keeps explicit file ignores when a parent directory is re-included", async () => { + await mkdir(path.join(root, "pkg"), { recursive: true }) + await writeFile(path.join(root, ".gitignore"), "*.ts\n") + await writeFile(path.join(root, ".kilocodeignore"), "!pkg/\n") + + const ig = await loadIgnore(root) + + expect(ig.ignores("pkg/file.ts")).toBe(true) + }) + + test("keeps explicit file ignores when a re-included parent also had explicit file rules", async () => { + await mkdir(path.join(root, "pkg"), { recursive: true }) + await writeFile(path.join(root, ".gitignore"), "pkg/\npkg/*.ts\n") + await writeFile(path.join(root, ".kilocodeignore"), "!pkg/\n") + + const ig = await loadIgnore(root) + + expect(ig.ignores("pkg/file.ts")).toBe(true) + }) + + test("keeps descendants ignored when only a child directory is re-included", async () => { + await mkdir(path.join(root, "pkg", "sub"), { recursive: true }) + await writeFile(path.join(root, ".gitignore"), "pkg/\n") + await writeFile(path.join(root, ".kilocodeignore"), "!pkg/sub/\n") + + const ig = await loadIgnore(root) + + expect(ig.ignores("pkg/sub/file.ts")).toBe(true) + }) + + test("allows child negation after a parent directory is re-included", async () => { + await mkdir(path.join(root, "pkg"), { recursive: true }) + await writeFile(path.join(root, ".gitignore"), "pkg/\n") + await writeFile(path.join(root, ".kilocodeignore"), "!pkg/\n") + await writeFile(path.join(root, "pkg", ".gitignore"), "!keep.ts\n") + + const ig = await loadIgnore(root) + + expect(ig.ignores("pkg/keep.ts")).toBe(false) + }) + + test("applies .kilocodeignore after .gitignore in the same directory", async () => { + await writeFile(path.join(root, ".gitignore"), "*.ts\n") + await writeFile(path.join(root, ".kilocodeignore"), "!keep.ts\n") + + const ig = await loadIgnore(root) + + expect(ig.ignores("drop.ts")).toBe(true) + expect(ig.ignores("keep.ts")).toBe(false) + }) + test("ignores the ignore files themselves", async () => { await writeFile(path.join(root, ".gitignore"), "dist/\n") await writeFile(path.join(root, ".kilocodeignore"), "secret/\n") @@ -47,4 +162,38 @@ describe("loadIgnore", () => { expect(ig.ignores(".gitignore")).toBe(true) expect(ig.ignores(".kilocodeignore")).toBe(true) }) + + test("keeps ignore files ignored after negation rules", async () => { + await writeFile(path.join(root, ".kilocodeignore"), "!.gitignore\n") + + const ig = await loadIgnore(root) + + expect(ig.ignores(".gitignore")).toBe(true) + }) + + test("ignores ignore file names even when absent during loading", async () => { + const ig = await loadIgnore(root) + + expect(ig.ignores(".gitignore")).toBe(true) + expect(ig.ignores("pkg/.kilocodeignore")).toBe(true) + }) + + test("does not load ignore files from hardcoded ignored folders", async () => { + await mkdir(path.join(root, "dist"), { recursive: true }) + await writeFile(path.join(root, "dist", ".gitignore"), "*.ts\n") + + const ig = await loadIgnore(root) + + expect(ig.ignores("dist/file.ts")).toBe(false) + }) + + test("escapes nested ignore file directory names in generated patterns", async () => { + await mkdir(path.join(root, "pkg[1]"), { recursive: true }) + await writeFile(path.join(root, "pkg[1]", ".gitignore"), "secret.ts\n") + + const ig = await loadIgnore(root) + + expect(ig.ignores("pkg[1]/secret.ts")).toBe(true) + expect(ig.ignores("pkg1/secret.ts")).toBe(false) + }) }) diff --git a/packages/kilo-jetbrains/.run/Run IDE (Backend).run.xml b/packages/kilo-jetbrains/.run/Run IDE (Backend).run.xml index cfa3d9997d4..af1ad2c5507 100644 --- a/packages/kilo-jetbrains/.run/Run IDE (Backend).run.xml +++ b/packages/kilo-jetbrains/.run/Run IDE (Backend).run.xml @@ -1,6 +1,6 @@ - +