feat(jetbrains): worktree transfer safety, unified change summaries, setup scripts, and GitHub integration controls - #13566
Conversation
… 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.
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Files Reviewed (2 files)
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)
Previous review (commit 90ad80a)Status: No Issues Found | Recommendation: Merge Files Reviewed (1 files)
Previous review (commit 2ef13de)Status: No Issues Found | Recommendation: Merge Files Reviewed (2 files)
Previous review (commit 1edf5ac)Status: No Issues Found | Recommendation: Merge Files Reviewed (30 files)
Previous review (commit 3402f45)Status: 4 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (7 files)
Fix these issues in Kilo Cloud Previous review (commit 024733e)Status: 4 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (26 files)
Fix these issues in Kilo Cloud Previous review (commit b9d0c73)Status: 3 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (61 files)
Fix these issues in Kilo Cloud Previous review (commit 3f2766f)Status: No Issues Found | Recommendation: Merge Files Reviewed (4 files)
Reviewed by grok-4.6 · Input: 142.7K · Output: 6.1K · Cached: 494.8K Review guidance: REVIEW.md from base branch |
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
…conflict-transfer
…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.
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.
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.
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:
git applycan't consume (No valid patches in input), including withoutMERGE_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.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..kilo/setup-script, so a freshly created worktree needed manual bootstrapping.ghunconditionally. Users withoutgh, 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,ghstate only refreshed on its poll timers, so authorizingghin a terminal or merging a PR in a browser could take up to two minutes to show up.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
mainseparately, 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
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.Unified change summaries
dirty()RPC), fixingstats()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).ActiveList: generic clickable badge slots and model-based action dispatch, so Agent Manager owns PR semantics instead of the shared list/metrics components.backend/diff/GitComparison.kt;KiloWorkspaceRpcApi.branchDiffis now committed-only, with a newlocalDifffor the working-tree side. Both feedKiloWorktreeRpcApiImpl.stats()/dirty()so list totals and the editor header always agree.WorktreeStatsViewandBranchChangesBadgeintoui/ChangesPanel.kt(Compact/Fullmodes), reused by the worktree list, session toolbar, and worktree editor header.KiloDiffEditorKind/KiloDiffEditorContentso every surface (badge click, "Compare to Base", worktree header) produces identical params and reuses the same tab for a given comparison.<base>" captions (the tooltip already carries that context) and fixedOpen/Build-Runvs.Terminalbutton-size inconsistency via an opt-inHoverIcon.matchmode.Worktree setup scripts
.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 withWORKTREE_PATH/REPO_PATHset viaenvVariables(), and fire it automatically (non-blocking, silent no-op if absent) on worktree create/import/move.Kilo.Worktree.RowMenu, not the settings popup.GitHub integration controls
SearchableConfigurablerather than aDraftReadyConfigurablebecause 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.gh.ghStatusandbranchStatustake agithubflag that resolves git state only, so worktree stats, dirty counts, branch name, and the branch dock keep working while noghprocess is created. A git-only probe is deliberately retained:GIT_MISSINGoriginates fromprobeGh'sgit --versionstep and drives both the "Git not found" banner andBranchDock.gitAvailable(), so stopping all probing would silently break both.gh.ghmissing/unauthorized banner gains a Turn off GitHub integration action with a tooltip naming the settings location. It is offered forghproblems only, notGIT_MISSING, where disabling would not help.ghavailability 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'sgh auth statuscache TTL so a burst of events cannot outrun the answer a probe would return anyway.Mode picker no longer cancels sessions
Review follow-ups
Four review findings were addressed in
1edf5acd6f:loadDirty()resolved its directory fromproject.basePath, whichProjectRootdocuments as a synthetic JetBrains Client path in split/remote mode. It was the only one of the three loaders not usingkiloRoot(), sodirty()answered for a directory that does not exist and the empty result published as "no local changes". Fixed to resolve inside the coroutine likeloadStats().githubflag. BecauseKiloWorktreeService.prStatusswallowsCancellationExceptionand 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 realUNAUTH. Replaced with a generation token bumped on launch, on disable, and on stop.HoverIcon.labelledHeight()measured by assigning this button's owntext.setTextfires a property change and callsrevalidate()/repaint(), so a size query invalidated the control and its ancestors on every layout pass. It now measures a detached probe button, rebuilt onupdateUI()so a LAF change re-measures.kilo.jetbrains.frontend.xmlthat they are row-menu only, and corrected the wording.CI failures fixed
Both were genuine bugs rather than flakes:
HoverIcon.matchheight followed the icon size (2ef13de42e).WorktreePrHeaderViewTestfailed on Linux with 18px against the labelled sibling's 17px. The measurement folded the button's own icon in, givinginsets + max(ownIcon, text), while the sibling getsinsets + 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.90ad80a8e7). The test released a gated background lookup and then wrote a different value into the same cache entry. The released coroutine cachesexists = true, so if it resumed in the window between the manual write and the secondupdate()— which straddles a pooled-thread handoff — it clobberedexists = falseand 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 testfrompackages/kilo-jetbrains/— 4397 tests, 0 failures.HoverIcontest countedinvalidate()calls on a parent panel and passed against the unfixed code too, becauseComponent.invalidateParent()only propagates when the parentisValid()and an unrealized container never is. It now listens fortextproperty changes instead.Reviewer test steps
git merge --quitto 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.Changes vs base branch (<branch>)on click. In the session editor header, confirm no text captions, and thatBuild/Run,Open, and the icon-onlyTerminalshare 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..kilo/setup-script.sh; confirm it runs automatically in its own terminal tab withWORKTREE_PATH/REPO_PATHset. Exercise Open/Create and Run from the worktree row menu.ghlogged 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 noghprocess is spawned over several poll intervals. Re-enable from Settings | Tools | Kilo Code | Integrations and confirm badges and banner return within one probe.Not verified
ProjectRoot's contract rather than exercised on a remote host, so theloadDirty()fix is covered by unit tests asserting the resolved root, not by an end-to-end remote run.Checklist
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.