Skip to content

feat(jetbrains): exclude Kilo worktrees from IntelliJ indexing - #13800

Merged
kirillk merged 2 commits into
mainfrom
eager-yak
Sep 5, 2026
Merged

kirillk merged 2 commits into
mainfrom
eager-yak

Conversation

@kirillk

@kirillk kirillk commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Issue

No linked issue. This came out of an ad hoc investigation into why indexing felt slow with several Agent Manager worktrees open in one IntelliJ frame (findings recorded in a local intellij.md, not filed as a GitHub issue).

Context

Kilo-managed agent worktrees live under <project>/.kilo/worktrees, inside the project directory. IntelliJ indexing follows the workspace model, not git, so .git/info/exclude (where Agent Manager already hides worktrees from git) is never consulted by the indexer. Every worktree checkout was indexed like any other project file, which can double scan time on a project with active worktrees and produces duplicate hits in Search Everywhere / Go to File.

Implementation

  • Registered a DirectoryIndexExcludePolicy (KiloWorktreeExcludePolicy, backend module) that excludes <project.basePath>/.kilo/worktrees from the containing project's index. This is the legacy-but-still-supported exclusion API; the modern WorkspaceFileIndexContributor replacement has no usable project-scoped entity (ProjectRootEntity is @ApiStatus.Internal, and the content-root-scoped variant would register per content root).
  • The exclusion is gated by a new KiloWorktreeIndexSettings (PropertiesComponent-backed, default off) so it can be turned back on. Added a "Index agent worktrees" toggle to Kilo Settings → Advanced; flipping it round-trips over a new indexWorktrees()/setIndexWorktrees() pair on KiloAppRpcApi and fires ProjectRootManagerEx.makeRootsChange(RESCAN_DEPENDENCIES_IF_NEEDED) for every open project so the change takes effect without a restart.
  • Excluding a directory from indexing also means files opened from it in the main frame lose resolve/inspections (the daemon skips excluded files). Since exclusions are per-project, opening a worktree as its own project always indexes it fully — so KiloWorkspaceRpcApiImpl.project() now prefers the open project whose base path is the longest match instead of just the first one. A worktree file opened via a Kilo session file link, diff "Open File", or @-mention now routes to the worktree's own frame (full IDE features) when it's open, instead of always landing in the main checkout.
  • Extracted the .kilo/worktrees literal into a shared WORKTREE_STORAGE constant reused by the existing isManagedWorktreeStorage scoping check.

Screenshots / Video

N/A — settings toggle only; no new visual surface beyond one row on an existing settings page.

How to Test

Manual/local verification

  • ./gradlew typecheck and ./gradlew test from packages/kilo-jetbrains/ (all three modules) — see below.
  • Manually opened Kilo Settings → Advanced and confirmed the new "Index agent worktrees" row renders under an "Indexing" section, defaults off, and toggling it flips isModified().

Reviewer test steps

  1. Open a repo that has Agent Manager worktrees under .kilo/worktrees/<slug>.
  2. In the Project view, confirm .kilo/worktrees renders in the excluded-folder color while .kilo/kilo.jsonc, .kilo/commands, etc. render normally.
  3. Use Search Everywhere / Go to File for a class that exists in a worktree; confirm no duplicate .kilo/worktrees/... hit shows up.
  4. Open Kilo Settings → Advanced, toggle "Index agent worktrees" on and Apply; confirm indexing restarts and the worktree files reappear in Search Everywhere. Toggle back off and Apply; confirm they disappear again.
  5. With a worktree also open as its own project, click a file link inside that worktree's session (main frame); confirm the file opens in the worktree's own frame with full highlighting rather than in the main checkout.

Blocked checks and substitute verification

  • Did not run a full manual IDE sandbox pass (./gradlew runIde) for steps 2–5 above in this session; substitute verification was the automated test suite below plus manual code/API review against the local intellij-community checkout referenced in the implementation notes (DirectoryIndexExcludePolicy, RootsChangeRescanningInfo, ProjectRootManagerEx.makeRootsChange signatures).

Executed by the agent:

  • ./gradlew typecheck from packages/kilo-jetbrains/BUILD SUCCESSFUL.
  • ./gradlew test from packages/kilo-jetbrains/BUILD SUCCESSFUL, 0 failures across all three modules (895 backend tests, 3747 frontend tests, 9 shared tests), including new coverage:
    • KiloWorktreeExcludePolicyTest (exclusion URL derives from project.basePath; empty when the setting is on; empty for the default project with no base path)
    • KiloAppRpcApiImplIndexWorktreesTest (setting persists and round-trips through the RPC methods)
    • WorkspacePathScopingTest additions for the new deepest() project-matching helper
    • AdvancedConfigurableTest additions for the async fetch, dirty-tracking, and apply-only-when-changed behavior of the new toggle

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.

Get in Touch

Kilo-managed agent worktrees live under <project>/.kilo/worktrees,
inside the project directory. IntelliJ indexing follows the workspace
model rather than git, so it indexed every worktree checkout as part
of the containing project, doubling scan time and surfacing duplicate
results in Search Everywhere / Go to File.

Register a DirectoryIndexExcludePolicy that excludes
<project.basePath>/.kilo/worktrees from the containing project's
index. A new "Index agent worktrees" toggle in Kilo Settings ->
Advanced (off by default) lets users opt back in; flipping it
reindexes every open project via ProjectRootManagerEx.makeRootsChange.

Opening a worktree as its own project always indexes it fully, since
exclusions are per-project. To make that the common case when a file
link is opened from a worktree session, KiloWorkspaceRpcApiImpl.project()
now prefers the open project with the longest matching base path
instead of the first one, so a managed worktree file routes to the
worktree's own frame (full IDE features) when it is open, instead of
always landing in the main checkout with degraded highlighting.
@kilo-code-bot

kilo-code-bot Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (5 files)
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/app/KiloAppService.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/AdvancedConfigurable.kt
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/AdvancedSettingsUi.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/AdvancedConfigurableTest.kt
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/FakeAppRpcApi.kt
Previous Review Summary (commit de5bdbe)

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

Previous review (commit de5bdbe)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

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

WARNING

File Line Issue
packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/AdvancedConfigurable.kt 59 Apply launches setIndexWorktrees on the settings UI scope, which is cancelled when the user clicks OK

SUGGESTION

File Line Issue
packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/AdvancedSettingsUi.kt 108 Async refresh overwrites the toggle after the user has already changed it
Files Reviewed (17 files)
  • .changeset/jetbrains-exclude-worktrees-from-index.md - 0 issues
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloAppRpcApiImpl.kt - 0 issues
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloWorkspaceRpcApiImpl.kt - 0 issues
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/workspace/KiloWorktreeExcludePolicy.kt - 0 issues
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/workspace/KiloWorktreeIndexSettings.kt - 0 issues
  • packages/kilo-jetbrains/backend/src/main/resources/kilo.jetbrains.backend.xml - 0 issues
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/rpc/KiloAppRpcApiImplIndexWorktreesTest.kt - 0 issues
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/rpc/WorkspacePathScopingTest.kt - 0 issues
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/workspace/KiloWorktreeExcludePolicyTest.kt - 0 issues
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/app/KiloAppService.kt - 0 issues
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/AdvancedConfigurable.kt - 1 issue
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/AdvancedSettingsUi.kt - 1 issue
  • packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle.properties - 0 issues
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/AdvancedConfigurableTest.kt - 0 issues
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/FakeAppRpcApi.kt - 0 issues
  • packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/KiloAppRpcApi.kt - 0 issues
  • packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/WorkspacePath.kt - 0 issues

Fix these issues in Kilo Cloud


Reviewed by grok-4.6 · Input: 98.3K · Output: 10.9K · Cached: 559.2K

Review guidance: REVIEW.md from base branch main

Two bugs found in review of the settings toggle:

The save was launched on the configurable's own scope, which
disposeUIResources() cancels. Since OK runs apply() then
disposeUIResources(), the RPC and the reindex it triggers could be
cancelled before they ran, so Apply worked but OK often silently did
nothing. Move the write behind KiloAppService.setIndexWorktreesAsync,
which launches on the app-lifetime scope, mirroring applyLogConfigAsync.

The initial value is fetched asynchronously, and refreshIndexWorktrees
overwrote the control unconditionally. A slow split-mode fetch landing
after the user flipped the switch discarded their choice and cleared
isModified. Track whether the user touched the toggle and skip the
refresh in that case; resetForm() clears the flag so a later fetch can
still populate a pristine form.

Both paths now have regression tests, each verified to fail against the
previous behavior. The dispose test gates the save inside the fake RPC
so the dispose provably happens while the call is in flight rather than
racing the coroutine's first dispatch.
@kirillk

kirillk commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Addressed both review findings in f608dd9.

1. OK could cancel the save (WARNING). apply() was launching the write on the configurable's own scope, which disposeUIResources() cancels — so the Apply button worked but OK raced the coroutine's first dispatch. Added KiloAppService.setIndexWorktreesAsync, which launches on the app-lifetime scope (the same pattern as applyLogConfigAsync), and apply() now calls that.

2. A late fetch could discard the user's toggle (SUGGESTION). refreshIndexWorktrees overwrote the control unconditionally. It now returns early once the user has flipped the toggle, tracked with a flag fed by the SettingsToggle callback. I used a touched flag rather than checking modified() because modified() is also true when only the log rows changed (which shouldn't block the fetch), and because a value comparison can't distinguish "user turned it off" from "still at the default off". resetForm() clears the flag so a late fetch can still populate a pristine form.

Verification — both regression tests were confirmed to fail against the previous behavior before the fixes were restored:

  • test index worktrees save survives dialog dispose on ok
  • test a late index worktrees fetch keeps a user toggle

The dispose test gates the save inside FakeAppRpcApi so the dispose provably happens while the RPC is in flight. This matters: the naive version of that test passed against the buggy code whenever the coroutine won the dispatch race, which would have made it a false-confidence test.

./gradlew typecheck and ./gradlew test from packages/kilo-jetbrains/ both pass — 0 failures across all modules (895 backend, 3749 frontend, 9 shared).

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