Skip to content

feat(jetbrains): worktree transfer safety, unified change summaries, setup scripts, and GitHub integration controls - #13566

Merged
kirillk merged 23 commits into
mainfrom
fix-worktree-conflict-transfer
Aug 31, 2026
Merged

feat(jetbrains): worktree transfer safety, unified change summaries, setup scripts, and GitHub integration controls#13566
kirillk merged 23 commits into
mainfrom
fix-worktree-conflict-transfer

Conversation

@kirillk

@kirillk kirillk commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Issue

No linked issue: this started as a fix for a locally reported JetBrains "Move to Worktree" failure. It has since accumulated four more lines of work, two of them merged in from other PRs (#13583, #13591).

Context

Five lines of work are on this branch:

  1. Worktree transfer safety (original scope): an unresolved Git index could produce combined diffs that git apply can't consume (No valid patches in input), including without MERGE_HEAD (e.g. after a conflicted stash apply). Ordinary patches and conflict sections coexisting could make a transfer look successful while silently dropping conflicted work.
  2. Unified change summaries (merged from refactor(jetbrains): consolidate worktree change summaries into ChangesPanel #13583, shiny-pebble): Agent Manager's worktree rows, session toolbar, and worktree editor header had three overlapping "changes" implementations (WorktreeStatsView, BranchChangesBadge, ad hoc diff-opening logic) with mismatched comparison semantics.
  3. Worktree setup scripts: no JetBrains equivalent of VS Code's .kilo/setup-script, so a freshly created worktree needed manual bootstrapping.
  4. GitHub integration controls: the plugin polled gh unconditionally. Users without gh, without authorization, or who simply don't want Kilo shelling out to it had no way to turn it off, and the warning banner offered no escape. Separately, gh state only refreshed on its poll timers, so authorizing gh in a terminal or merging a PR in a browser could take up to two minutes to show up.
  5. Mode picker cancelling sessions (merged from fix(jetbrains): stop mode picker from cancelling running sessions #13591): picking a mode in the chat prompt was written to the CLI's global default_agent, which made the CLI dispose every instance it held and cancel every running turn in every worktree.

Two things from the earlier scope are no longer in this diff: the Mermaid rendering work and the worktree run-configuration feature (Run dropdown, Build/Rebuild) both reached main separately, via #13569 and the run-configs PR. The previous title's "run configs" referred to work that is now upstream; only the setup-script half remains here.

Implementation

Worktree transfer safety

  • Resolve the source checkout root and reject unmerged index entries before creating patches, branches, or worktrees. Read NUL-delimited Git listings without terminal-output transformations so filenames remain intact.
  • Capture untracked contents into owned temporary storage rather than copying live source files later; the existing 10 MiB per-file limit now rejects the move instead of silently skipping oversized content.
  • Apply staged patches with git apply --index, then apply unstaged changes separately. Index and untracked-copy failures reach the existing rollback path instead of being ignored; existing destinations are not overwritten.
  • Clean up snapshot storage without turning a completed transfer into a failure. The operation remains a copy: it does not stash, reset, or resolve the source checkout's changes.

Unified change summaries

  • Show uncommitted changes separately from the PR diff on worktree rows (dirty() RPC), fixing stats() reporting nothing once a branch is fully pushed (it now resolves the base branch the same way the branch-diff editor does, instead of diffing against the worktree's own upstream).
  • Decouple PR badges from ActiveList: generic clickable badge slots and model-based action dispatch, so Agent Manager owns PR semantics instead of the shared list/metrics components.
  • Extract shared Git comparison logic into backend/diff/GitComparison.kt; KiloWorkspaceRpcApi.branchDiff is now committed-only, with a new localDiff for the working-tree side. Both feed KiloWorktreeRpcApiImpl.stats()/dirty() so list totals and the editor header always agree.
  • Consolidate WorktreeStatsView and BranchChangesBadge into ui/ChangesPanel.kt (Compact/Full modes), reused by the worktree list, session toolbar, and worktree editor header.
  • Centralize diff-editor opening/naming in KiloDiffEditorKind/KiloDiffEditorContent so every surface (badge click, "Compare to Base", worktree header) produces identical params and reuses the same tab for a given comparison.
  • Header toolbar polish: dropped redundant "Uncommitted"/"vs <base>" captions (the tooltip already carries that context) and fixed Open/Build-Run vs. Terminal button-size inconsistency via an opt-in HoverIcon.match mode.

Worktree setup scripts

  • Port VS Code's .kilo/setup-script[.sh|.ps1|.cmd|.bat] to the JetBrains plugin: resolve it per-platform, create it from VS Code's own templates when opened and missing, run it in a dedicated terminal tab with WORKTREE_PATH/REPO_PATH set via envVariables(), and fire it automatically (non-blocking, silent no-op if absent) on worktree create/import/move.
  • Add Open/Create and Run to the worktree row menu. These are registered only under Kilo.Worktree.RowMenu, not the settings popup.

GitHub integration controls

  • New Integrations settings page under Settings | Tools | Kilo Code, positioned after Context, with a single GitHub toggle (default on). It is a plain SearchableConfigurable rather than a DraftReadyConfigurable because the setting is a local IDE preference that must stay editable when the CLI is unavailable; the toggle writes immediately, so there is nothing to apply or revert.
  • With it off, the plugin never spawns gh. ghStatus and branchStatus take a github flag that resolves git state only, so worktree stats, dirty counts, branch name, and the branch dock keep working while no gh process is created. A git-only probe is deliberately retained: GIT_MISSING originates from probeGh's git --version step and drives both the "Git not found" banner and BranchDock.gitAvailable(), so stopping all probing would silently break both.
  • Turning it off cancels in-flight probes and PR lookups, stops the PR poll, and clears cached PR state, so badges, worktree editor tab titles, and PR context actions drop immediately with no dangling coroutines. The "Import from PR" tab is hidden, since it requires gh.
  • The gh missing/unauthorized banner gains a Turn off GitHub integration action with a tooltip naming the settings location. It is offered for gh problems only, not GIT_MISSING, where disabling would not help.
  • gh availability and PR state are now also refreshed on IDE frame activation and on switching between the Chat and Agents tabs. Submits coalesce rather than queue: dropped while a probe is in flight, while nothing is attached, or within 3s of the last probe, which matches the backend's gh auth status cache TTL so a burst of events cannot outrun the answer a probe would return anyway.

Mode picker no longer cancels sessions

  • The picked mode stays IDE-local and travels with each prompt instead of being written to the CLI's global config. When Kilo does stop a turn on its own, the chat now explains why, offers Retry, and raises a notification instead of reporting a bare "Stopped".

Review follow-ups

Four review findings were addressed in 1edf5acd6f:

  • loadDirty() resolved its directory from project.basePath, which ProjectRoot documents as a synthetic JetBrains Client path in split/remote mode. It was the only one of the three loaders not using kiloRoot(), so dirty() answered for a directory that does not exist and the empty result published as "no local changes". Fixed to resolve inside the coroutine like loadStats().
  • The PR-lookup guard only checked the github flag. Because KiloWorktreeService.prStatus swallows CancellationException and returns an empty OK DTO, a lookup abandoned by turning GitHub off could land after a quick re-enable, wipe fresh badges, and report a false OK over a real UNAUTH. Replaced with a generation token bumped on launch, on disable, and on stop.
  • HoverIcon.labelledHeight() measured by assigning this button's own text. setText fires a property change and calls revalidate()/repaint(), so a size query invalidated the control and its ancestors on every layout pass. It now measures a detached probe button, rebuilt on updateUI() so a LAF change re-measures.
  • The setup-script changeset claimed the actions appear in the settings menu; verified against kilo.jetbrains.frontend.xml that they are row-menu only, and corrected the wording.

CI failures fixed

Both were genuine bugs rather than flakes:

  • HoverIcon.match height followed the icon size (2ef13de42e). WorktreePrHeaderViewTest failed on Linux with 18px against the labelled sibling's 17px. The measurement folded the button's own icon in, giving insets + max(ownIcon, text), while the sibling gets insets + max(itsIcon, text) — and the icons differ (13px terminal icon vs. a smaller product icon). Where the LAF font is shorter than either icon, the icon-only button drifts a pixel taller; macOS fonts are tall enough that text won on both sides, which is why it only failed on Linux. The probe now measures text alone. The added regression test asserts that 8px and 64px matched buttons report the same height, which reproduces the Linux failure on macOS.
  • Race in the setup-script refresh test (90ad80a8e7). The test released a gated background lookup and then wrote a different value into the same cache entry. The released coroutine caches exists = true, so if it resumed in the window between the manual write and the second update() — which straddles a pooled-thread handoff — it clobbered exists = false and the action rendered "Open". The test now flips the fake's flag before releasing, lets the background lookup cache the missing target itself, and waits for that write. The neighbouring config tests use the same shape but write values byte-identical to what the background lookup produces, so they are benign and were left alone.

How to Test

Automated

  • ./gradlew typecheck && ./gradlew test from packages/kilo-jetbrains/ — 4397 tests, 0 failures.
  • Each new regression test was verified by reverting its fix and confirming the test fails. This caught one initially vacuous assertion: a first attempt at the HoverIcon test counted invalidate() calls on a parent panel and passed against the unfixed code too, because Component.invalidateParent() only propagates when the parent isValid() and an unrealized container never is. It now listens for text property changes instead.

Reviewer test steps

  1. Transfer safety: in a disposable repo, create a conflict in a file outside the subdirectory opened as the IDE project, run git merge --quit to drop the active merge marker while retaining unresolved index entries, then attempt "Move to Worktree" — expect a named conflict error, no new branch/worktree, and unchanged source files/index stages. Then resolve/stage a conflict, add staged + unstaged edits to a tracked file plus untracked files at root and nested paths, move to a new worktree, and confirm both index and working-tree contents match at their original repository-relative paths.
  2. Change summaries: open a worktree with a PR, committed changes, and uncommitted local edits. Confirm the row's compact summary shows base file count + diff stats and opens Changes vs base branch (<branch>) on click. In the session editor header, confirm no text captions, and that Build/Run, Open, and the icon-only Terminal share the same height and padding. Click the local and base groups independently and confirm they open separate, correctly titled diff tabs that don't clobber each other on refresh.
  3. Setup scripts: create or import a worktree with a .kilo/setup-script.sh; confirm it runs automatically in its own terminal tab with WORKTREE_PATH/REPO_PATH set. Exercise Open/Create and Run from the worktree row menu.
  4. GitHub integration: with gh logged out, confirm the Agent Manager banner offers Authorize / Learn more / Turn off GitHub integration, and that the last one carries a tooltip naming the settings path. Click it and confirm the banner hides immediately, PR badges disappear, worktree tab titles revert to worktree names, and the New Worktree dialog loses its Pull Request tab, while stats, dirty counts, and the branch dock keep working. Confirm no gh process is spawned over several poll intervals. Re-enable from Settings | Tools | Kilo Code | Integrations and confirm badges and banner return within one probe.
  5. Mode switching: with turns running in two worktrees, change the mode in the chat prompt and confirm neither turn is cancelled and the mode persists to new chats.

Not verified

  • Split-mode/remote development was reasoned about from ProjectRoot's contract rather than exercised on a remote host, so the loadDirty() fix is covered by unit tests asserting the resolved root, not by an end-to-end remote run.

Checklist

  • Issue linked above, or exception explained
  • Tests/verification described
  • Screenshots/video included for visual changes, or marked N/A
  • Changeset considered for user-facing changes
  • I personally reviewed the diff and can explain the changes, including any AI-assisted work.

Implementation and diff review were AI-assisted; human confirmation of the final checklist item remains pending.

Changesets new in this PR: quiet-worktrees-refuse-conflicts, jetbrains-worktree-uncommitted-badge, jetbrains-worktree-setup-script, jetbrains-github-integration-toggle, jetbrains-gh-sync-on-focus, jetbrains-mode-switch-cancels-sessions. The in-PR fixes above need none of their own: each corrects a feature introduced on this same branch, so no shipped behavior regressed.

Get in Touch

Use this PR's comments.

… on worktree rows

Agent Manager worktree rows showed no +/- badge for branches that were
fully pushed, because stats() diffed against the worktree's own
@{upstream} instead of the repo's base branch. Once a branch is pushed,
merge-base(HEAD, upstream) equals HEAD, so the diff reported nothing.

stats() now resolves the base branch the same way the branch-diff editor
does (origin/HEAD, then origin/main/master, then local main/master,
falling back to the main worktree's branch), diffs committed work only
(trailing HEAD on the numstat call excludes the working tree), and
reports which base ref it used.

Uncommitted work moves to a new dirty() RPC and gets its own muted badge
next to the PR diff, since it has a different baseline (HEAD) and
refresh cadence than the PR-parity number.
Keep PR badges beside the diff statistics while exposing generic clickable badge slots and model-based action dispatch. Let Agent Manager own PR semantics instead of the shared list and metrics components.
Reject unresolved indexes before creating a worktree and snapshot untracked content so transfers cannot silently omit or change source work.

Propagate index and copy failures through rollback, and cover conflict states, checkout scope, staging fidelity, and source preservation with real Git tests.
@kilo-code-bot

kilo-code-bot Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (2 files)
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/app/KiloBackendAppServiceTest.kt
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/testing/TestLog.kt
Previous Review Summaries (8 snapshots, latest commit b4366b4)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit b4366b4)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (1 files)
  • .kilo/plans/1788119831861-jetbrains-github-integration-toggle.md

Previous review (commit 90ad80a)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (1 files)
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/actions/KiloRecoveryActionsTest.kt

Previous review (commit 2ef13de)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (2 files)
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/HoverIcon.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/ui/HoverIconTest.kt

Previous review (commit 1edf5ac)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (30 files)
  • .changeset/jetbrains-mode-switch-cancels-sessions.md
  • .changeset/jetbrains-worktree-setup-script.md
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendActivityManager.kt
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendAppService.kt
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendChatManager.kt
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloSessionRpcApiImpl.kt
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/app/KiloBackendActivityManagerTest.kt
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/app/KiloBackendAppServiceTest.kt
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/app/KiloBackendChatManagerTest.kt
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/cli/KiloCliDataParserTest.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/WorktreeStatusService.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/app/KiloSessionService.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/plugin/KiloPluginSettings.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/SessionController.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/HoverIcon.kt
  • packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle.properties
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/agentManager/worktree/NewWorktreeDialogTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/agentManager/worktree/WorktreeStatusServiceTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/ConfigSelectionTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/PromptLifecycleTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/SessionCancellationTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/SessionControllerTestBase.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/TurnLifecycleTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/FakeSessionRpcApi.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/FakeWorktreeRpcApi.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/ui/HoverIconTest.kt
  • packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/log/ChatLogSummary.kt
  • packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/KiloSessionRpcApi.kt
  • packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/ChatDto.kt

Previous review (commit 3402f45)

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 3
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/WorktreeStatusService.kt 145 loadDirty() uses project.basePath instead of kiloRoot(), so uncommitted badges stay empty in split/remote mode
packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/WorktreeStatusService.kt 161 if (!github) does not drop a cancelled prStatus after a quick re-enable, so stale/empty PR data can overwrite a fresh lookup
packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/HoverIcon.kt 62 labelledHeight() mutates text during getPreferredSize(), which revalidates the control on every layout pass

SUGGESTION

File Line Issue
.changeset/jetbrains-worktree-setup-script.md 5 Release note says Open/Create/Run are on the settings menu; they are only on the worktree row menu
Files Reviewed (7 files)
  • .changeset/jetbrains-gh-sync-on-focus.md
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/KiloToolWindowFactory.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/GhStatusCoordinator.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/WorktreeStatusService.kt - 2 issues
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/agentManager/worktree/GhStatusCoordinatorTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/agentManager/worktree/WorktreeStatusServiceTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/TestIdeActivation.kt

Fix these issues in Kilo Cloud

Previous review (commit 024733e)

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 3
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/WorktreeStatusService.kt 135 loadDirty() uses project.basePath instead of kiloRoot(), so uncommitted badges stay empty in split/remote mode
packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/WorktreeStatusService.kt 151 if (!github) does not drop a cancelled prStatus after a quick re-enable, so stale/empty PR data can overwrite a fresh lookup
packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/HoverIcon.kt 62 labelledHeight() mutates text during getPreferredSize(), which revalidates the control on every layout pass

SUGGESTION

File Line Issue
.changeset/jetbrains-worktree-setup-script.md 5 Release note says Open/Create/Run are on the settings menu; they are only on the worktree row menu
Files Reviewed (26 files)
  • .changeset/jetbrains-github-integration-toggle.md
  • .kilo/plans/1788119831861-jetbrains-github-integration-toggle.md
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloWorktreeRpcApiImpl.kt
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/rpc/KiloWorktreeRpcApiImplTest.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/GhBanner.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/GhStatusCoordinator.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/GithubIntegrationListener.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/KiloWorktreeService.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/NewWorktreeDialog.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/WorktreeStatusService.kt - 2 issues
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/plugin/KiloPluginSettings.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/KiloSettingsConfigurable.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/integrations/IntegrationsConfigurable.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/integrations/IntegrationsSettingsUi.kt
  • packages/kilo-jetbrains/frontend/src/main/resources/kilo.jetbrains.frontend.xml
  • packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle.properties
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/agentManager/worktree/GhBannerTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/agentManager/worktree/GhStatusCoordinatorTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/agentManager/worktree/NewWorktreeDialogTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/agentManager/worktree/WorktreeStatusServiceTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionUiLayoutTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/KiloSettingsConfigurableTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/integrations/IntegrationsConfigurableTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/FakeWorktreeRpcApi.kt
  • packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/KiloWorktreeRpcApi.kt

Fix these issues in Kilo Cloud

Previous review (commit b9d0c73)

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/WorktreeStatusService.kt 100 loadDirty() uses project.basePath instead of kiloRoot(), so uncommitted badges stay empty in split/remote mode
packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/HoverIcon.kt 62 labelledHeight() mutates text during getPreferredSize(), which revalidates the control on every layout pass

SUGGESTION

File Line Issue
.changeset/jetbrains-worktree-setup-script.md 5 Release note says Open/Create/Run are on the settings menu; they are only on the worktree row menu
Files Reviewed (61 files)
  • .changeset/jetbrains-worktree-setup-script.md - 1 issue
  • .changeset/jetbrains-worktree-uncommitted-badge.md
  • .changeset/quiet-worktrees-refuse-conflicts.md
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/diff/GitComparison.kt
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloWorkspaceRpcApiImpl.kt
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloWorktreeRpcApiImpl.kt
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/SetupScriptTemplate.kt
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/WorktreeTransfer.kt
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/rpc/BranchDiffTest.kt
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/rpc/BranchLocalDiffTest.kt
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/rpc/KiloWorktreeRpcApiImplTest.kt
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/rpc/SetupScriptResolutionTest.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/actions/OpenConfigActions.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/actions/OpenWorktreeLocalDiffAction.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/actions/RunWorktreeSetupScriptAction.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/AgentManagerPanel.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/KiloWorktreeService.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/WorktreePrHeaderView.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/WorktreeSessionEditorPanel.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/WorktreeSetupScript.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/WorktreeStatsView.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/WorktreeStatusBinding.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/WorktreeStatusService.kt - 1 issue
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/app/KiloWorkspaceService.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/diff/DiffFileStatus.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/diff/KiloDiffEditorContent.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/diff/KiloDiffEditorKind.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/header/BranchChangesBadge.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/header/BranchDock.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/header/PrHeaderView.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/ChangesPanel.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/HoverIcon.kt - 1 issue
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/ToolbarButton.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/list/ActiveListActions.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/list/ActiveListModel.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/list/ActiveListRenderer.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/ui/list/ActiveListView.kt
  • packages/kilo-jetbrains/frontend/src/main/resources/kilo.jetbrains.frontend.xml
  • packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle.properties
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/actions/HistorySessionActionsTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/actions/KiloRecoveryActionsTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/agentManager/AgentManagerPanelTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/agentManager/worktree/WorktreePrHeaderViewTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/agentManager/worktree/WorktreeSessionEditorPanelTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/agentManager/worktree/WorktreeSetupScriptTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/agentManager/worktree/WorktreeStatusServiceTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/app/KiloWorkspaceServiceTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/diff/KiloDiffEditorContentTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/diff/KiloInlineDiffStoreTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionUiLayoutTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/header/BranchDockTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/header/PrHeaderViewTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/base/SettingsListViewTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/FakeWorkspaceRpcApi.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/FakeWorktreeRpcApi.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/ui/ChangesPanelTest.kt
  • packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/KiloWorkspaceRpcApi.kt
  • packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/KiloWorktreeRpcApi.kt
  • packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/SetupScriptTargetDto.kt
  • packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/WorktreeDto.kt

Fix these issues in Kilo Cloud

Previous review (commit 3f2766f)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (4 files)
  • .changeset/quiet-worktrees-refuse-conflicts.md
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloWorktreeRpcApiImpl.kt
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/WorktreeTransfer.kt
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/rpc/KiloWorktreeRpcApiImplTest.kt

Reviewed by grok-4.6 · Input: 142.7K · Output: 6.1K · Cached: 494.8K

Review guidance: REVIEW.md from base branch main

Unify base and local Git comparisons behind shared backend/frontend
boundaries instead of overlapping ad-hoc implementations:

- Extract reusable comparison logic into backend/diff/GitComparison.kt
  and add a committed-only branchDiff plus a new localDiff endpoint.
- Consolidate WorktreeStatsView and BranchChangesBadge into a single
  ChangesPanel with Compact/Full modes, reused across the Agent
  Manager worktree list, session toolbar, and worktree editor header.
- Centralize diff editor opening/naming in KiloDiffEditorKind/Content
  so every surface produces identical params for the same comparison.
- Migrate AgentManagerPanel, ActiveListRenderer, BranchDock,
  WorktreePrHeaderView, and WorktreeSessionEditorPanel onto the new
  component and RPC surface, removing redundant presentation code.

Consolidates the three prior changesets covering PR badge placement,
worktree PR diff counts, and the uncommitted changes badge into one
release note.
# Conflicts:
#	packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/WorktreePrHeaderView.kt
#	packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/agentManager/worktree/WorktreeSessionEditorPanel.kt
#	packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt
#	packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/agentManager/worktree/WorktreePrHeaderViewTest.kt
Remove the "Uncommitted" and "vs <base>" text labels from the worktree
session editor header's full ChangesPanel groups, leaving only the file
count and +/- badges. The distinguishing context is still available via
each group's tooltip, so the header stays compact without redundant text.
Add a vertical separator between the changes summary and the trailing
action buttons (Run, Open, Terminal) in the worktree editor header,
shown only when both sides have visible content.

Fix HoverIcon's labelled variant (used by the Open and Build/Run
buttons) to size itself from its actual icon+text content instead of
falling through to DarculaButtonUI's default preferred size, which
enforces a 72x24 dialog-button minimum. That minimum was inflating
short labels like "Open" into much wider buttons than the icon-only
Terminal button, which already bypassed it.
Revert the labelled-button sizing change: Build/Run and Open go back to
the platform's preferred size, so their insets match what they were.

Instead give the icon-only Terminal button the labelled buttons' margin
and toolbar insets, plus a new HoverIcon.match mode that squares it to
the height the look-and-feel gives a button. Deriving that height from
the LAF (rather than a fixed token) keeps it level with its labelled
siblings under both Darcula and the plain test LAF.

Add a leading separator to the header toolbar so the cluster reads as
separator, standard padding, changes summary, separator, actions. Both
separators stay hidden until an action exists, and the padding is a Stack
gap rather than a filler so it collapses with the hidden separator --
leaving dock hosts with no trailing actions unchanged.
The separator before the changes summary read as clutter next to the PR
title. Replace it with standard left padding on the toolbar row itself,
which keeps the summary spaced off the title without drawing a rule.

Padding now lives in the row's border instead of a Stack gap so it no
longer depends on a separator being visible to apply.
Port VS Code's worktree setup script (.kilo/setup-script[.sh|.ps1|.cmd|
.bat]) to the JetBrains plugin:

- Resolve the script per-platform with disjoint POSIX/Windows candidate
  lists, and create it from VS Code's own templates when opened and
  missing (backend/rpc/SetupScriptTemplate.kt).
- Run it in a terminal tab dedicated to the worktree (distinct from the
  Terminal button's tab), with WORKTREE_PATH/REPO_PATH set via
  envVariables() so the sent command needs no shell-specific env-prefix
  syntax.
- Fire automatically, non-blocking, when a worktree is created (covers
  create, import, and move-to-worktree); silent no-op when no script
  exists.
- Add Open/Create + Run to the worktree row menu (hidden on the main
  row); Open/Create only, not Run, in the tool-window settings popup.

Also shorten several worktree row action labels and add "Open
Uncommitted Changes" (KiloDiffComparison.LOCAL) alongside the existing
base-diff action:

- Open Pull Request in Browser -> Open in Browser
- Open Diff Against Base Branch -> Open Diff Against Base
- Open/Run Worktree Setup Script -> Open/Run Worktree Setup
- Delete Worktree -> Delete
@kirillk kirillk changed the title fix(jetbrains): preserve changes when moving to worktrees feat(jetbrains): worktree transfer safety, unified change summaries, run configs, and Mermaid diagrams Aug 30, 2026
@kirillk kirillk changed the title feat(jetbrains): worktree transfer safety, unified change summaries, run configs, and Mermaid diagrams feat(jetbrains): safer worktree transfers, unified change summaries, and run configs Aug 30, 2026
…trics

Some button UIs size a null/empty-text button purely from its icon,
skipping the font-metrics contribution a labelled sibling gets from its
non-empty text. That made icon-only HoverIcon.match buttons (e.g. the
worktree header's Terminal action) track the icon height instead of
the shared labelled-button height whenever a platform's font metrics
were taller than the icon, producing a 1px mismatch on CI (Linux) that
did not reproduce locally (macOS).

Measure through a temporary single-space placeholder so match mode
goes through the same icon+text layout path a labelled button uses,
keeping both heights level regardless of LAF or font metrics.
Comment thread .changeset/jetbrains-worktree-setup-script.md Outdated
Switching the chat mode picker wrote default_agent to the CLI's global
config. The CLI disposes every instance it holds whenever that file
changes, which cancels every running turn in every open worktree --
three unrelated sessions died mid-turn from a single mode switch, with
no error shown because a server-initiated cancellation and a user Stop
both report the same MessageAbortedError.

The mode pick now stays client-side: it rides on PromptDto.agent per
turn (as it already did) and is remembered in KiloPluginSettings so new
sessions still open in the last-picked mode, matching how VS Code and
the TUI already handle this. No CLI config write happens.

Since the CLI can still legitimately cancel a turn on its own (a
settings/provider change disposing instances), the plugin now tells
those apart from a user Stop: an unrequested abort shows the reason and
offers Retry instead of a silent "Stopped", raises a notification, and
is captured in telemetry. The backend synthesizes a session.interrupted
event naming the cause when disposal happens while a session is busy.
The activity badge for a cancelled turn never appeared. reportDisposal
runs immediately before load(), load() calls activity.start(), and
start() fully stopped first -- clearing errors, statuses, and the
directory resolver. The chat event flow also replays nothing, so the
restarted collector could not re-see the SessionInterrupted it had just
missed. The badge branch was dead on the only path that emits the event.

start() now detaches the collectors in place and keeps what they
recorded; a real teardown still clears everything through stop(). The
badge is recorded by a direct activity.interrupt() call ordered with the
disposal that caused it, rather than racing a flow emission against the
reload that swaps collectors, so the event branch is gone.

Both new tests fail without their respective halves of this fix.
Adds an Integrations page under Settings > Tools > Kilo Code,
positioned after Context, with a single GitHub toggle (default on).

Turning it off stops the plugin from ever spawning gh: ghStatus and
branchStatus take a `github` flag that, when false, resolves git
state only and never runs `gh auth status`, `gh pr view`, or
`gh pr list`. Worktree stats, dirty counts, branch name, and the
branch dock keep working since they only need git.

Disabling cancels in-flight gh probes and PR lookups, stops the PR
poll timer, and clears cached PR state so badges, worktree editor tab
titles, and PR context actions drop immediately with no dangling
coroutines. Re-enabling probes and reloads PRs at once instead of
waiting out the timers.

The gh "not installed"/"not authorized" banner gains a third action,
"Turn off GitHub integration," with a tooltip pointing back to the
Integrations page. The New Worktree dialog drops its PR import tab
while the integration is off, since importing a PR requires gh.
gh availability and PR state were only refreshed by their poll timers
(30s/5s for availability, 120s for PRs), so authorizing gh in a
terminal or merging a PR in a browser could take up to two minutes to
show up in the IDE.

Both are now also submitted on IDE frame activation and on switching
between the Chat and Agents tabs. The submits coalesce rather than
queue, keeping the existing one-probe-at-a-time design intact:

- GhStatusCoordinator.sync() replaces the unused forceProbe(). It
  drops the submit when a probe is already in flight, when nothing is
  attached, or within 3s of the last probe start. That floor matches
  the backend's gh auth status cache TTL, so a burst of events cannot
  outrun the answer a probe would return anyway.
- WorktreeStatusService refreshes PRs unforced on activation, so the
  existing 30s throttle absorbs repeated focus events. Activation of a
  different project's window is ignored.
…tree-conflict-transfer

# Conflicts:
#	packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/plugin/KiloPluginSettings.kt
Four review findings from PR #13566:

- loadDirty() resolved the backend directory from project.basePath,
  which ProjectRoot documents as a synthetic JetBrains Client path in
  split/remote mode. dirty() then answered for a directory that does
  not exist and the empty result published as "no local changes", so
  uncommitted badges never appeared for remote users. It now resolves
  project.kiloRoot() inside the coroutine like loadStats() does.

- The PR lookup guard only checked the github flag. Because
  KiloWorktreeService.prStatus swallows cancellation and answers with
  an empty OK DTO, a lookup abandoned by turning GitHub off could land
  after a quick re-enable, wipe fresh badges, and report a false OK
  over a real UNAUTH. Replaced with a generation token bumped on every
  launch, on disable, and on stop, mirroring GhStatusCoordinator.

- HoverIcon.labelledHeight() measured by assigning this button's text.
  setText fires a property change and calls revalidate()/repaint(), so
  a size query invalidated the control and its ancestors on every
  layout pass. It now measures a detached probe button, rebuilt on
  updateUI so a LAF change re-measures.

- The setup-script changeset claimed the actions appear in the settings
  menu; they are only registered under Kilo.Worktree.RowMenu.
CI failed on Linux with the icon-only terminal action measuring 18px
against its labelled sibling's 17px.

labelledHeight() folded this button's own icon into the measurement, so
the matched height was insets + max(ownIcon, text). A labelled sibling
gets insets + max(itsIcon, text), and the two icons are rarely the same
size: hoverIconButton carries a 13px terminal icon while the
hoverTextButton beside it carries a smaller product icon. On a LAF whose
font is shorter than either icon, the icon-only button drifted a pixel
taller. macOS font metrics are tall enough that text won on both sides,
which is why this only showed up on Linux.

The probe now measures text alone, giving the insets + font-metrics
baseline every labelled sibling shares regardless of icon size on either
side. The default (non-match) square path still tracks the icon.

Adds a platform-independent regression test asserting that two matched
buttons with 8px and 64px icons report the same height, which reproduces
the Linux failure on macOS.
CI failed with "Open Worktree Setup" where the test expected "Create".

The test released the gated background lookup, then immediately wrote a
different value into the same cache entry:

    gate.complete(Unit)
    service().setupScript["/test"] = ...exists = false
    update(action, next)   // expects "Create"

The released coroutine caches exists = true from the fake, so if it
resumed in the window between the manual write and the second update --
which straddles a pooled-thread handoff -- it overwrote exists = false
and the action rendered "Open". Reproduced locally by forcing the
background write into that window.

The fake reads setupScriptExists after the gate, so the test now flips
it to false before releasing and lets the background lookup cache the
missing target itself, then waits for that write before asserting. No
two writers, no window.

The neighbouring local/global config tests use the same shape but write
values byte-identical to what the background lookup produces, so they
are benign and left alone.
packages/kilo-jetbrains/AGENTS.md PR hygiene: .kilo/plans/** must not be
included in JetBrains commits or PRs unless publishing plans was asked
for. Kept on disk, untracked.
@kirillk kirillk changed the title feat(jetbrains): safer worktree transfers, unified change summaries, and run configs feat(jetbrains): worktree transfer safety, unified change summaries, setup scripts, and GitHub integration controls Aug 31, 2026
@kirillk
kirillk enabled auto-merge August 31, 2026 02:27
The test pushed the cancelled turn's idle status right after the disposal
event. Those two land on separate collectors, so on a loaded CI runner the
status map reached idle before the disposal watcher read it, the watcher
concluded nothing was lost, and no badge was recorded.

Wait for the reload to restart the activity collector before reporting idle:
that restart proves the badge was taken from the busy status that preceded the
disposal, and it makes the assertion cover surviving the restart rather than
racing it.
@kirillk
kirillk merged commit ab14325 into main Aug 31, 2026
24 checks passed
@kirillk
kirillk deleted the fix-worktree-conflict-transfer branch August 31, 2026 12:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants