diff --git a/packages/types/src/codebase-index.ts b/packages/types/src/codebase-index.ts index be7778f5387..3bbe83dc29b 100644 --- a/packages/types/src/codebase-index.ts +++ b/packages/types/src/codebase-index.ts @@ -20,6 +20,7 @@ export const CODEBASE_INDEX_DEFAULTS = { export const codebaseIndexConfigSchema = z.object({ codebaseIndexEnabled: z.boolean().optional(), + codebaseIndexBranchIsolation: z.boolean().optional(), codebaseIndexQdrantUrl: z.string().optional(), codebaseIndexEmbedderProvider: z .enum(["openai", "ollama", "openai-compatible", "gemini", "mistral", "vercel-ai-gateway"]) diff --git a/src/services/code-index/__tests__/config-manager.spec.ts b/src/services/code-index/__tests__/config-manager.spec.ts index 9fc096ba742..92bea0ee167 100644 --- a/src/services/code-index/__tests__/config-manager.spec.ts +++ b/src/services/code-index/__tests__/config-manager.spec.ts @@ -1120,6 +1120,7 @@ describe("CodeIndexConfigManager", () => { ollamaBaseUrl: undefined, qdrantUrl: "http://qdrant.local", qdrantApiKey: undefined, + branchIsolation: false, } const requiresRestart = configManager.doesConfigChangeRequireRestart(mockPrevConfig) @@ -1437,6 +1438,7 @@ describe("CodeIndexConfigManager", () => { embedderProvider: "openai", openAiKey: "test-key", qdrantUrl: "http://localhost:6333", + branchIsolation: false, } // Update to disabled @@ -1490,6 +1492,7 @@ describe("CodeIndexConfigManager", () => { enabled: false, configured: false, embedderProvider: "openai", + branchIsolation: false, } // Same config, still disabled @@ -1514,6 +1517,7 @@ describe("CodeIndexConfigManager", () => { embedderProvider: "openai", openAiKey: "test-key", qdrantUrl: "http://localhost:6333", + branchIsolation: false, } const result = configManager.doesConfigChangeRequireRestart(previousSnapshot) @@ -1533,6 +1537,7 @@ describe("CodeIndexConfigManager", () => { enabled: false, configured: false, embedderProvider: "openai", + branchIsolation: false, } // Provider changed but feature is disabled diff --git a/src/services/code-index/config-manager.ts b/src/services/code-index/config-manager.ts index 2c0e8bb5c9e..4f076ee7a30 100644 --- a/src/services/code-index/config-manager.ts +++ b/src/services/code-index/config-manager.ts @@ -11,6 +11,7 @@ import { getDefaultModelId, getModelDimension, getModelScoreThreshold } from ".. */ export class CodeIndexConfigManager { private codebaseIndexEnabled: boolean = true + private codebaseIndexBranchIsolation: boolean = false private embedderProvider: EmbedderProvider = "openai" private modelId?: string private modelDimension?: number @@ -45,6 +46,7 @@ export class CodeIndexConfigManager { // Load configuration from storage const codebaseIndexConfig = this.contextProxy?.getGlobalState("codebaseIndexConfig") ?? { codebaseIndexEnabled: true, + codebaseIndexBranchIsolation: false, codebaseIndexQdrantUrl: "http://localhost:6333", codebaseIndexEmbedderProvider: "openai", codebaseIndexEmbedderBaseUrl: "", @@ -55,6 +57,7 @@ export class CodeIndexConfigManager { const { codebaseIndexEnabled, + codebaseIndexBranchIsolation, codebaseIndexQdrantUrl, codebaseIndexEmbedderProvider, codebaseIndexEmbedderBaseUrl, @@ -74,6 +77,7 @@ export class CodeIndexConfigManager { // Update instance variables with configuration this.codebaseIndexEnabled = codebaseIndexEnabled ?? true + this.codebaseIndexBranchIsolation = codebaseIndexBranchIsolation ?? false this.qdrantUrl = codebaseIndexQdrantUrl this.qdrantApiKey = qdrantApiKey ?? "" this.searchMinScore = codebaseIndexSearchMinScore @@ -156,6 +160,7 @@ export class CodeIndexConfigManager { // Capture the ACTUAL previous state before loading new configuration const previousConfigSnapshot: PreviousConfigSnapshot = { enabled: this.codebaseIndexEnabled, + branchIsolation: this.codebaseIndexBranchIsolation, configured: this.isConfigured(), embedderProvider: this.embedderProvider, modelId: this.modelId, @@ -259,6 +264,7 @@ export class CodeIndexConfigManager { // Handle null/undefined values safely const prevEnabled = prev?.enabled ?? false + const prevBranchIsolation = prev?.branchIsolation ?? false const prevConfigured = prev?.configured ?? false const prevProvider = prev?.embedderProvider ?? "openai" const prevOpenAiKey = prev?.openAiKey ?? "" @@ -282,6 +288,11 @@ export class CodeIndexConfigManager { return true } + // 2.5. Branch isolation setting changed + if (prevBranchIsolation !== this.codebaseIndexBranchIsolation) { + return true + } + // 3. If wasn't ready before and isn't ready now, no restart needed if ((!prevEnabled || !prevConfigured) && (!this.codebaseIndexEnabled || !nowConfigured)) { return false @@ -480,4 +491,12 @@ export class CodeIndexConfigManager { public get currentSearchMaxResults(): number { return this.searchMaxResults ?? DEFAULT_MAX_SEARCH_RESULTS } + + /** + * Gets whether branch isolation is enabled for codebase indexing. + * When enabled, each Git branch will have its own separate index. + */ + public get isBranchIsolationEnabled(): boolean { + return this.codebaseIndexBranchIsolation + } } diff --git a/src/services/code-index/interfaces/config.ts b/src/services/code-index/interfaces/config.ts index f168e268691..e415ede3b4f 100644 --- a/src/services/code-index/interfaces/config.ts +++ b/src/services/code-index/interfaces/config.ts @@ -26,6 +26,7 @@ export interface CodeIndexConfig { */ export type PreviousConfigSnapshot = { enabled: boolean + branchIsolation: boolean configured: boolean embedderProvider: EmbedderProvider modelId?: string diff --git a/src/services/code-index/service-factory.ts b/src/services/code-index/service-factory.ts index 6d69e1f0b6c..fbf7970d662 100644 --- a/src/services/code-index/service-factory.ts +++ b/src/services/code-index/service-factory.ts @@ -145,8 +145,15 @@ export class CodeIndexServiceFactory { throw new Error(t("embeddings:serviceFactory.qdrantUrlMissing")) } - // Assuming constructor is updated: new QdrantVectorStore(workspacePath, url, vectorSize, apiKey?) - return new QdrantVectorStore(this.workspacePath, config.qdrantUrl, vectorSize, config.qdrantApiKey) + // Pass branch isolation setting to QdrantVectorStore + const branchIsolationEnabled = this.configManager.isBranchIsolationEnabled + return new QdrantVectorStore( + this.workspacePath, + config.qdrantUrl, + vectorSize, + config.qdrantApiKey, + branchIsolationEnabled, + ) } /** diff --git a/src/services/code-index/vector-store/__tests__/qdrant-client-branch-isolation.spec.ts b/src/services/code-index/vector-store/__tests__/qdrant-client-branch-isolation.spec.ts new file mode 100644 index 00000000000..a8adcb932df --- /dev/null +++ b/src/services/code-index/vector-store/__tests__/qdrant-client-branch-isolation.spec.ts @@ -0,0 +1,312 @@ +import { describe, it, expect, beforeEach, vi } from "vitest" +import { QdrantClient } from "@qdrant/js-client-rest" +import { QdrantVectorStore } from "../qdrant-client" +import * as gitUtils from "../../../../utils/git" + +// Mock dependencies +vi.mock("@qdrant/js-client-rest") +vi.mock("crypto", () => ({ + createHash: vi.fn(() => ({ + update: vi.fn(() => ({ + digest: vi.fn(() => "mockedhash1234567890abcdef"), + })), + })), +})) + +vi.mock("../../../../utils/git", () => ({ + getCurrentBranch: vi.fn(), + sanitizeBranchName: vi.fn(), +})) + +describe("QdrantVectorStore - Branch Isolation", () => { + let mockQdrantClientInstance: any + const mockWorkspacePath = "/test/workspace" + const mockQdrantUrl = "http://localhost:6333" + const mockVectorSize = 1536 + const mockApiKey = "test-api-key" + + beforeEach(() => { + vi.clearAllMocks() + mockQdrantClientInstance = { + getCollection: vi.fn(), + createCollection: vi.fn(), + deleteCollection: vi.fn(), + createPayloadIndex: vi.fn(), + upsert: vi.fn(), + query: vi.fn(), + delete: vi.fn(), + } + ;(QdrantClient as any).mockImplementation(() => mockQdrantClientInstance) + }) + + describe("Branch Isolation Disabled", () => { + it("should use workspace-only collection name when branch isolation is disabled", async () => { + const vectorStore = new QdrantVectorStore( + mockWorkspacePath, + mockQdrantUrl, + mockVectorSize, + mockApiKey, + false, // branchIsolationEnabled = false + ) + + // Mock collection doesn't exist + mockQdrantClientInstance.getCollection.mockRejectedValue({ + status: 404, + message: "Collection not found", + }) + + await vectorStore.initialize() + + // Should create collection with workspace-only name + expect(mockQdrantClientInstance.createCollection).toHaveBeenCalledWith( + "ws-mockedhash123456", + expect.any(Object), + ) + expect(gitUtils.getCurrentBranch).not.toHaveBeenCalled() + }) + + it("should return null for getCurrentBranch when branch isolation is disabled", () => { + const vectorStore = new QdrantVectorStore( + mockWorkspacePath, + mockQdrantUrl, + mockVectorSize, + mockApiKey, + false, + ) + + expect(vectorStore.getCurrentBranch()).toBeNull() + }) + }) + + describe("Branch Isolation Enabled", () => { + it("should use branch-specific collection name when on a Git branch", async () => { + ;(gitUtils.getCurrentBranch as any).mockResolvedValue("feature/test-branch") + ;(gitUtils.sanitizeBranchName as any).mockReturnValue("feature-test-branch") + + const vectorStore = new QdrantVectorStore( + mockWorkspacePath, + mockQdrantUrl, + mockVectorSize, + mockApiKey, + true, // branchIsolationEnabled = true + ) + + // Mock collection doesn't exist + mockQdrantClientInstance.getCollection.mockRejectedValue({ + status: 404, + message: "Collection not found", + }) + + await vectorStore.initialize() + + // Should detect current branch + expect(gitUtils.getCurrentBranch).toHaveBeenCalledWith(mockWorkspacePath) + expect(gitUtils.sanitizeBranchName).toHaveBeenCalledWith("feature/test-branch") + + // Should create collection with branch-specific name + expect(mockQdrantClientInstance.createCollection).toHaveBeenCalledWith( + "ws-mockedhash123456-br-feature-test-branch", + expect.any(Object), + ) + }) + + it("should use workspace-only collection name when in detached HEAD state", async () => { + ;(gitUtils.getCurrentBranch as any).mockResolvedValue(null) // Detached HEAD + + const vectorStore = new QdrantVectorStore( + mockWorkspacePath, + mockQdrantUrl, + mockVectorSize, + mockApiKey, + true, + ) + + // Mock collection doesn't exist + mockQdrantClientInstance.getCollection.mockRejectedValue({ + status: 404, + message: "Collection not found", + }) + + await vectorStore.initialize() + + // Should detect detached HEAD + expect(gitUtils.getCurrentBranch).toHaveBeenCalledWith(mockWorkspacePath) + + // Should create collection with workspace-only name (no branch suffix) + expect(mockQdrantClientInstance.createCollection).toHaveBeenCalledWith( + "ws-mockedhash123456", + expect.any(Object), + ) + }) + + it("should use workspace-only collection name when not in a Git repository", async () => { + ;(gitUtils.getCurrentBranch as any).mockResolvedValue(null) // Not a Git repo + + const vectorStore = new QdrantVectorStore( + mockWorkspacePath, + mockQdrantUrl, + mockVectorSize, + mockApiKey, + true, + ) + + // Mock collection doesn't exist + mockQdrantClientInstance.getCollection.mockRejectedValue({ + status: 404, + message: "Collection not found", + }) + + await vectorStore.initialize() + + // Should detect not a Git repo + expect(gitUtils.getCurrentBranch).toHaveBeenCalledWith(mockWorkspacePath) + + // Should create collection with workspace-only name (no branch suffix) + expect(mockQdrantClientInstance.createCollection).toHaveBeenCalledWith( + "ws-mockedhash123456", + expect.any(Object), + ) + }) + + it("should handle branch names with special characters", async () => { + ;(gitUtils.getCurrentBranch as any).mockResolvedValue("feature/user-auth-2.0") + ;(gitUtils.sanitizeBranchName as any).mockReturnValue("feature-user-auth-2-0") + + const vectorStore = new QdrantVectorStore( + mockWorkspacePath, + mockQdrantUrl, + mockVectorSize, + mockApiKey, + true, + ) + + // Mock collection doesn't exist + mockQdrantClientInstance.getCollection.mockRejectedValue({ + status: 404, + message: "Collection not found", + }) + + await vectorStore.initialize() + + // Should sanitize branch name + expect(gitUtils.sanitizeBranchName).toHaveBeenCalledWith("feature/user-auth-2.0") + + // Should create collection with sanitized branch name + expect(mockQdrantClientInstance.createCollection).toHaveBeenCalledWith( + "ws-mockedhash123456-br-feature-user-auth-2-0", + expect.any(Object), + ) + }) + + it("should return the current branch name when branch isolation is enabled", async () => { + ;(gitUtils.getCurrentBranch as any).mockResolvedValue("main") + ;(gitUtils.sanitizeBranchName as any).mockReturnValue("main") + + const vectorStore = new QdrantVectorStore( + mockWorkspacePath, + mockQdrantUrl, + mockVectorSize, + mockApiKey, + true, + ) + + // Mock collection doesn't exist + mockQdrantClientInstance.getCollection.mockRejectedValue({ + status: 404, + message: "Collection not found", + }) + + await vectorStore.initialize() + + expect(vectorStore.getCurrentBranch()).toBe("main") + }) + + it("should handle very long branch names", async () => { + const longBranchName = "feature/" + "a".repeat(100) + const sanitizedLongName = "feature-" + "a".repeat(42) // Truncated to 50 chars + + ;(gitUtils.getCurrentBranch as any).mockResolvedValue(longBranchName) + ;(gitUtils.sanitizeBranchName as any).mockReturnValue(sanitizedLongName) + + const vectorStore = new QdrantVectorStore( + mockWorkspacePath, + mockQdrantUrl, + mockVectorSize, + mockApiKey, + true, + ) + + // Mock collection doesn't exist + mockQdrantClientInstance.getCollection.mockRejectedValue({ + status: 404, + message: "Collection not found", + }) + + await vectorStore.initialize() + + // Should handle long branch name + expect(gitUtils.sanitizeBranchName).toHaveBeenCalledWith(longBranchName) + + // Should create collection with truncated branch name + expect(mockQdrantClientInstance.createCollection).toHaveBeenCalledWith( + `ws-mockedhash123456-br-${sanitizedLongName}`, + expect.any(Object), + ) + }) + }) + + describe("Branch Switching", () => { + it("should use different collection names for different branches", async () => { + // First initialization on main branch + ;(gitUtils.getCurrentBranch as any).mockResolvedValue("main") + ;(gitUtils.sanitizeBranchName as any).mockReturnValue("main") + + const vectorStore1 = new QdrantVectorStore( + mockWorkspacePath, + mockQdrantUrl, + mockVectorSize, + mockApiKey, + true, + ) + + mockQdrantClientInstance.getCollection.mockRejectedValue({ + status: 404, + message: "Collection not found", + }) + + await vectorStore1.initialize() + + expect(mockQdrantClientInstance.createCollection).toHaveBeenCalledWith( + "ws-mockedhash123456-br-main", + expect.any(Object), + ) + + // Clear mocks for second initialization + vi.clearAllMocks() + + // Second initialization on feature branch + ;(gitUtils.getCurrentBranch as any).mockResolvedValue("feature/new-feature") + ;(gitUtils.sanitizeBranchName as any).mockReturnValue("feature-new-feature") + + const vectorStore2 = new QdrantVectorStore( + mockWorkspacePath, + mockQdrantUrl, + mockVectorSize, + mockApiKey, + true, + ) + + mockQdrantClientInstance.getCollection.mockRejectedValue({ + status: 404, + message: "Collection not found", + }) + + await vectorStore2.initialize() + + expect(mockQdrantClientInstance.createCollection).toHaveBeenCalledWith( + "ws-mockedhash123456-br-feature-new-feature", + expect.any(Object), + ) + }) + }) +}) diff --git a/src/services/code-index/vector-store/qdrant-client.ts b/src/services/code-index/vector-store/qdrant-client.ts index ce152824a73..5aa68cc76f6 100644 --- a/src/services/code-index/vector-store/qdrant-client.ts +++ b/src/services/code-index/vector-store/qdrant-client.ts @@ -6,6 +6,7 @@ import { IVectorStore } from "../interfaces/vector-store" import { Payload, VectorStoreSearchResult } from "../interfaces" import { DEFAULT_MAX_SEARCH_RESULTS, DEFAULT_SEARCH_MIN_SCORE } from "../constants" import { t } from "../../../i18n" +import { getCurrentBranch, sanitizeBranchName } from "../../../utils/git" /** * Qdrant implementation of the vector store interface @@ -18,19 +19,31 @@ export class QdrantVectorStore implements IVectorStore { private readonly collectionName: string private readonly qdrantUrl: string = "http://localhost:6333" private readonly workspacePath: string + private readonly branchIsolationEnabled: boolean + private currentBranch: string | null = null /** * Creates a new Qdrant vector store * @param workspacePath Path to the workspace * @param url Optional URL to the Qdrant server + * @param vectorSize Size of the vector embeddings + * @param apiKey Optional API key for Qdrant + * @param branchIsolationEnabled Whether to use branch-specific collections */ - constructor(workspacePath: string, url: string, vectorSize: number, apiKey?: string) { + constructor( + workspacePath: string, + url: string, + vectorSize: number, + apiKey?: string, + branchIsolationEnabled: boolean = false, + ) { // Parse the URL to determine the appropriate QdrantClient configuration const parsedUrl = this.parseQdrantUrl(url) // Store the resolved URL for our property this.qdrantUrl = parsedUrl this.workspacePath = workspacePath + this.branchIsolationEnabled = branchIsolationEnabled try { const urlObj = new URL(parsedUrl) @@ -80,7 +93,18 @@ export class QdrantVectorStore implements IVectorStore { // Generate collection name from workspace path const hash = createHash("sha256").update(workspacePath).digest("hex") this.vectorSize = vectorSize - this.collectionName = `ws-${hash.substring(0, 16)}` + + // Base collection name + let collectionName = `ws-${hash.substring(0, 16)}` + + // Add branch suffix if branch isolation is enabled + if (this.branchIsolationEnabled) { + // We'll set the actual collection name in initialize() after detecting the branch + // For now, just store the base name + collectionName = `ws-${hash.substring(0, 16)}` + } + + this.collectionName = collectionName } /** @@ -147,6 +171,11 @@ export class QdrantVectorStore implements IVectorStore { * @returns Promise resolving to boolean indicating if a new collection was created */ async initialize(): Promise { + // Update collection name based on current branch if branch isolation is enabled + if (this.branchIsolationEnabled) { + await this.updateCollectionNameForBranch() + } + let created = false try { const collectionInfo = await this.getCollectionInfo() @@ -548,4 +577,37 @@ export class QdrantVectorStore implements IVectorStore { const collectionInfo = await this.getCollectionInfo() return collectionInfo !== null } + + /** + * Updates the collection name based on the current Git branch + * Only called when branch isolation is enabled + */ + private async updateCollectionNameForBranch(): Promise { + const branch = await getCurrentBranch(this.workspacePath) + + // Generate base collection name + const hash = createHash("sha256").update(this.workspacePath).digest("hex") + let collectionName = `ws-${hash.substring(0, 16)}` + + if (branch) { + // Sanitize branch name for use in collection name + const sanitizedBranch = sanitizeBranchName(branch) + collectionName = `${collectionName}-br-${sanitizedBranch}` + this.currentBranch = branch + } else { + // Detached HEAD or not a git repo - use workspace-only collection + this.currentBranch = null + } + + // Update the collection name + ;(this as any).collectionName = collectionName + } + + /** + * Gets the current branch being used for the collection + * @returns The current branch name or null if not using branch isolation + */ + public getCurrentBranch(): string | null { + return this.branchIsolationEnabled ? this.currentBranch : null + } } diff --git a/src/utils/git.ts b/src/utils/git.ts index 3bb562bf43f..8114d87d1f6 100644 --- a/src/utils/git.ts +++ b/src/utils/git.ts @@ -355,3 +355,67 @@ export async function getWorkingState(cwd: string): Promise { return `Failed to get working state: ${error instanceof Error ? error.message : String(error)}` } } + +/** + * Gets the current Git branch name for a given directory + * @param cwd The directory to check (defaults to current working directory) + * @returns The current branch name, or null if not in a git repo or in detached HEAD state + */ +export async function getCurrentBranch(cwd: string): Promise { + try { + const isInstalled = await checkGitInstalled() + if (!isInstalled) { + return null + } + + const isRepo = await checkGitRepo(cwd) + if (!isRepo) { + return null + } + + // Try to get the current branch name + const { stdout } = await execAsync("git rev-parse --abbrev-ref HEAD", { cwd }) + const branch = stdout.trim() + + // Check if we're in detached HEAD state + if (branch === "HEAD") { + return null + } + + return branch + } catch (error) { + console.error("Error getting current branch:", error) + return null + } +} + +/** + * Sanitizes a branch name for use in collection names + * @param branchName The original branch name + * @returns Sanitized branch name suitable for use in identifiers + */ +export function sanitizeBranchName(branchName: string): string { + // Convert to lowercase + let sanitized = branchName.toLowerCase() + + // Replace non-alphanumeric characters with hyphens + sanitized = sanitized.replace(/[^a-z0-9]+/g, "-") + + // Remove leading/trailing hyphens + sanitized = sanitized.replace(/^-+|-+$/g, "") + + // Collapse multiple consecutive hyphens + sanitized = sanitized.replace(/-+/g, "-") + + // Limit length to 50 characters (reasonable limit for branch segment) + if (sanitized.length > 50) { + sanitized = sanitized.substring(0, 50).replace(/-+$/, "") + } + + // If empty after sanitization, use a default + if (!sanitized) { + sanitized = "default" + } + + return sanitized +} diff --git a/webview-ui/src/components/chat/CodeIndexPopover.tsx b/webview-ui/src/components/chat/CodeIndexPopover.tsx index 45bf4224a12..51543728bf4 100644 --- a/webview-ui/src/components/chat/CodeIndexPopover.tsx +++ b/webview-ui/src/components/chat/CodeIndexPopover.tsx @@ -57,6 +57,7 @@ interface CodeIndexPopoverProps { interface LocalCodeIndexSettings { // Global state settings codebaseIndexEnabled: boolean + codebaseIndexBranchIsolation: boolean codebaseIndexQdrantUrl: string codebaseIndexEmbedderProvider: EmbedderProvider codebaseIndexEmbedderBaseUrl?: string @@ -180,6 +181,7 @@ export const CodeIndexPopover: React.FC = ({ // Default settings template const getDefaultSettings = (): LocalCodeIndexSettings => ({ codebaseIndexEnabled: true, + codebaseIndexBranchIsolation: false, codebaseIndexQdrantUrl: "", codebaseIndexEmbedderProvider: "openai", codebaseIndexEmbedderBaseUrl: "", @@ -212,6 +214,7 @@ export const CodeIndexPopover: React.FC = ({ if (codebaseIndexConfig) { const settings = { codebaseIndexEnabled: codebaseIndexConfig.codebaseIndexEnabled ?? true, + codebaseIndexBranchIsolation: codebaseIndexConfig.codebaseIndexBranchIsolation ?? false, codebaseIndexQdrantUrl: codebaseIndexConfig.codebaseIndexQdrantUrl || "", codebaseIndexEmbedderProvider: codebaseIndexConfig.codebaseIndexEmbedderProvider || "openai", codebaseIndexEmbedderBaseUrl: codebaseIndexConfig.codebaseIndexEmbedderBaseUrl || "", @@ -508,8 +511,9 @@ export const CodeIndexPopover: React.FC = ({ settingsToSave[key] = value } - // Always include codebaseIndexEnabled to ensure it's persisted + // Always include codebaseIndexEnabled and codebaseIndexBranchIsolation to ensure they're persisted settingsToSave.codebaseIndexEnabled = currentSettings.codebaseIndexEnabled + settingsToSave.codebaseIndexBranchIsolation = currentSettings.codebaseIndexBranchIsolation // Save settings to backend vscode.postMessage({ diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index dfccc49cc4c..ce309d92efa 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -91,6 +91,12 @@ "qdrantApiKeyPlaceholder": "Enter your Qdrant API key (optional)", "setupConfigLabel": "Setup", "advancedConfigLabel": "Advanced Configuration", + "advancedConfiguration": "Advanced Configuration", + "branchIsolation": { + "enableLabel": "Enable Branch Isolation", + "enableDescription": "When enabled, each Git branch will have its own separate index. This ensures search results always reflect the current branch's code.", + "storageWarning": "Warning: Enabling branch isolation will increase storage usage as each branch maintains its own index." + }, "searchMinScoreLabel": "Search Score Threshold", "searchMinScoreDescription": "Minimum similarity score (0.0-1.0) required for search results. Lower values return more results but may be less relevant. Higher values return fewer but more relevant results.", "searchMinScoreResetTooltip": "Reset to default value (0.4)",