diff --git a/ccw/src/core/routes/codexlens-routes.ts b/ccw/src/core/routes/codexlens-routes.ts index 1daa392b..d69afa31 100644 --- a/ccw/src/core/routes/codexlens-routes.ts +++ b/ccw/src/core/routes/codexlens-routes.ts @@ -407,6 +407,41 @@ export async function handleCodexLensRoutes(ctx: RouteContext): Promise if (pathname === '/api/codexlens/uninstall' && req.method === 'POST') { handlePostRequest(req, res, async () => { try { + // Stop watcher if running (to release file handles) + if (watcherStats.running && watcherProcess) { + console.log('[CodexLens] Stopping watcher before uninstall...'); + try { + watcherProcess.kill('SIGTERM'); + await new Promise(resolve => setTimeout(resolve, 500)); + if (watcherProcess && !watcherProcess.killed) { + watcherProcess.kill('SIGKILL'); + } + } catch { + // Ignore errors stopping watcher + } + watcherStats.running = false; + watcherProcess = null; + } + + // Cancel any running indexing process + if (currentIndexingProcess) { + console.log('[CodexLens] Cancelling indexing before uninstall...'); + try { + if (process.platform === 'win32') { + const { execSync } = require('child_process'); + execSync(`taskkill /pid ${currentIndexingProcess.pid} /T /F`, { stdio: 'ignore' }); + } else { + currentIndexingProcess.kill('SIGKILL'); + } + } catch { + // Ignore errors + } + currentIndexingProcess = null; + } + + // Wait a moment for processes to fully exit and release handles + await new Promise(resolve => setTimeout(resolve, 1000)); + const result = await uninstallCodexLens(); if (result.success) { // Broadcast uninstallation event diff --git a/ccw/src/tools/codex-lens.ts b/ccw/src/tools/codex-lens.ts index 0caf28d9..f8771c9e 100644 --- a/ccw/src/tools/codex-lens.ts +++ b/ccw/src/tools/codex-lens.ts @@ -1360,11 +1360,57 @@ async function uninstallCodexLens(): Promise { } console.log('[CodexLens] Uninstalling CodexLens...'); + + // On Windows, kill any Python processes that might be holding locks on .db files + if (process.platform === 'win32') { + console.log('[CodexLens] Killing any CodexLens Python processes...'); + const { execSync } = await import('child_process'); + try { + // Kill any python processes from our venv that might be holding file locks + execSync(`taskkill /F /IM python.exe /FI "MODULES eq sqlite3" 2>nul`, { stdio: 'ignore' }); + } catch { + // Ignore errors - no processes to kill + } + // Small delay to allow file handles to be released + await new Promise(resolve => setTimeout(resolve, 500)); + } + console.log(`[CodexLens] Removing directory: ${CODEXLENS_DATA_DIR}`); - // Remove the entire .codexlens directory + // Remove the entire .codexlens directory with retry logic for locked files const fs = await import('fs'); - fs.rmSync(CODEXLENS_DATA_DIR, { recursive: true, force: true }); + const path = await import('path'); + + // Helper function to remove directory with retries (Windows EBUSY workaround) + const removeWithRetry = async (dirPath: string, maxRetries = 3, delay = 1000): Promise => { + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + fs.rmSync(dirPath, { recursive: true, force: true, maxRetries: 3, retryDelay: 500 }); + return; + } catch (err: any) { + if (err.code === 'EBUSY' || err.code === 'EPERM' || err.code === 'ENOTEMPTY') { + console.log(`[CodexLens] Retry ${attempt}/${maxRetries} - file locked, waiting...`); + if (attempt < maxRetries) { + // On Windows, try to forcefully release file handles + if (process.platform === 'win32' && err.path) { + try { + const { execSync } = await import('child_process'); + // Try to close handles on the specific file + execSync(`handle -c ${err.path} -y 2>nul`, { stdio: 'ignore' }); + } catch { + // handle.exe may not be installed, ignore + } + } + await new Promise(resolve => setTimeout(resolve, delay)); + continue; + } + } + throw err; + } + } + }; + + await removeWithRetry(CODEXLENS_DATA_DIR); // Reset bootstrap cache bootstrapChecked = false; @@ -1374,7 +1420,15 @@ async function uninstallCodexLens(): Promise { console.log('[CodexLens] CodexLens uninstalled successfully'); return { success: true, message: 'CodexLens uninstalled successfully' }; } catch (err) { - return { success: false, error: `Failed to uninstall CodexLens: ${(err as Error).message}` }; + const errorMsg = (err as Error).message; + // Provide helpful message for Windows users with locked files + if (errorMsg.includes('EBUSY') || errorMsg.includes('resource busy')) { + return { + success: false, + error: `Failed to uninstall CodexLens: Files are locked. Please close any applications using CodexLens indexes (e.g., Claude Code, VS Code) and try again. Details: ${errorMsg}` + }; + } + return { success: false, error: `Failed to uninstall CodexLens: ${errorMsg}` }; } } diff --git a/codex-lens/src/codex_lens.egg-info/SOURCES.txt b/codex-lens/src/codex_lens.egg-info/SOURCES.txt index 8e9dbc08..37584435 100644 --- a/codex-lens/src/codex_lens.egg-info/SOURCES.txt +++ b/codex-lens/src/codex_lens.egg-info/SOURCES.txt @@ -24,6 +24,7 @@ src/codexlens/parsers/factory.py src/codexlens/parsers/tokenizer.py src/codexlens/parsers/treesitter_parser.py src/codexlens/search/__init__.py +src/codexlens/search/binary_searcher.py src/codexlens/search/chain_search.py src/codexlens/search/enrichment.py src/codexlens/search/graph_expander.py @@ -46,6 +47,7 @@ src/codexlens/semantic/reranker/__init__.py src/codexlens/semantic/reranker/api_reranker.py src/codexlens/semantic/reranker/base.py src/codexlens/semantic/reranker/factory.py +src/codexlens/semantic/reranker/fastembed_reranker.py src/codexlens/semantic/reranker/legacy.py src/codexlens/semantic/reranker/litellm_reranker.py src/codexlens/semantic/reranker/onnx_reranker.py