From 2f0cce008938cc1d4a8fcb2fdab64641eb3b12d5 Mon Sep 17 00:00:00 2001 From: catlog22 Date: Fri, 19 Dec 2025 15:10:37 +0800 Subject: [PATCH] feat: Enhance CodexLens indexing and search capabilities with new CLI options and improved error handling --- .claude/CLAUDE.md | 4 +- .claude/workflows/context-tools.md | 13 +- .claude/workflows/windows-platform.md | 16 ++ .mcp.json | 10 ++ ccw/src/commands/install.ts | 25 ++- ccw/src/core/routes/claude-routes.ts | 136 +++++++++++++++-- ccw/src/core/routes/codexlens-routes.ts | 12 +- .../dashboard-js/components/index-manager.js | 19 ++- ccw/src/templates/dashboard-js/i18n.js | 20 +++ .../dashboard-js/views/cli-manager.js | 69 ++++++++- .../dashboard-js/views/codexlens-manager.js | 20 ++- ccw/src/tools/smart-search.ts | 28 ++-- .../src/codexlens/search/chain_search.py | 8 +- .../src/codexlens/semantic/graph_analyzer.py | 17 ++- codex-lens/src/codexlens/storage/dir_index.py | 143 ++++++++++++++---- .../src/codexlens/storage/index_tree.py | 35 ++--- .../src/codexlens/storage/sqlite_store.py | 4 +- codex-lens/tests/test_graph_analyzer.py | 29 ++-- 18 files changed, 480 insertions(+), 128 deletions(-) create mode 100644 .claude/workflows/windows-platform.md diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 6051dfda..b40d45b4 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -2,5 +2,5 @@ - **CLI Tools Usage**: @~/.claude/workflows/cli-tools-usage.md - **Coding Philosophy**: @~/.claude/workflows/coding-philosophy.md -- **Context Requirements**: @~/.claude/workflows/context-tools.md -- **File Modification**: @~/.claude/workflows/file-modification.md \ No newline at end of file +- **Context Requirements**: @~/.claude/workflows/context-tools.md +- **File Modification**: @~/.claude/workflows/file-modification.md \ No newline at end of file diff --git a/.claude/workflows/context-tools.md b/.claude/workflows/context-tools.md index ddcd032a..54a36296 100644 --- a/.claude/workflows/context-tools.md +++ b/.claude/workflows/context-tools.md @@ -4,13 +4,18 @@ **When**: Find code, understand codebase structure, locate implementations -**How**: +**Workflow** (search first, init if needed): ```javascript -smart_search(query="authentication logic") // Auto mode (recommended) -smart_search(action="init", path=".") // First-time setup +// Step 1: Try search directly (works if index exists or uses ripgrep fallback) +smart_search(query="authentication logic") + +// Step 2: Only if search warns "No CodexLens index found", then init +smart_search(action="init", path=".") // Creates FTS index only + +// Note: For semantic/vector search, use "ccw view" dashboard to create vector index ``` -**Modes**: `auto` (intelligent routing), `hybrid` (best quality), `exact` (FTS) +**Modes**: `auto` (intelligent routing), `hybrid` (semantic, needs vector index), `exact` (FTS), `ripgrep` (no index) --- diff --git a/.claude/workflows/windows-platform.md b/.claude/workflows/windows-platform.md new file mode 100644 index 00000000..06aa3681 --- /dev/null +++ b/.claude/workflows/windows-platform.md @@ -0,0 +1,16 @@ +# Windows Platform Guidelines + +## Path Format Guidelines + +### MCP Tools +- Use double backslash: `D:\\path\\file.txt` +- Example: `read_file(paths="D:\\Claude_dms3\\src\\index.ts")` + +### Bash Commands +- Use forward slash: `D:/path/file.txt` or `/d/path/file.txt` +- Example: `cd D:/Claude_dms3/src` + +### Relative Paths +- Universal format works in both MCP and Bash contexts +- Example: `./src/index.ts` or `../shared/utils.ts` + diff --git a/.mcp.json b/.mcp.json index f2cb6860..c8ef67d5 100644 --- a/.mcp.json +++ b/.mcp.json @@ -7,6 +7,16 @@ "chrome-devtools-mcp@latest" ], "env": {} + }, + "ccw-tools": { + "command": "npx", + "args": [ + "-y", + "ccw-mcp" + ], + "env": { + "CCW_ENABLED_TOOLS": "write_file,edit_file,smart_search" + } } } } \ No newline at end of file diff --git a/ccw/src/commands/install.ts b/ccw/src/commands/install.ts index a7027f37..ff411f98 100644 --- a/ccw/src/commands/install.ts +++ b/ccw/src/commands/install.ts @@ -18,6 +18,9 @@ const SOURCE_DIRS = ['.claude', '.codex', '.gemini', '.qwen']; // Subdirectories that should always be installed to global (~/.claude/) const GLOBAL_SUBDIRS = ['workflows', 'scripts', 'templates']; +// Files that should be excluded from installation (user-specific settings) +const EXCLUDED_FILES = ['settings.json', 'settings.local.json']; + interface InstallOptions { mode?: string; path?: string; @@ -359,20 +362,28 @@ function getNewInstallationFiles(sourceDir: string, installPath: string, mode: s * @param destDir - Destination directory * @param files - Set to add file paths to * @param excludeDirs - Directories to exclude + * @param excludeFiles - Files to exclude */ -function collectFilesRecursive(srcDir: string, destDir: string, files: Set, excludeDirs: string[] = []): void { +function collectFilesRecursive( + srcDir: string, + destDir: string, + files: Set, + excludeDirs: string[] = [], + excludeFiles: string[] = EXCLUDED_FILES +): void { if (!existsSync(srcDir)) return; const entries = readdirSync(srcDir); for (const entry of entries) { if (excludeDirs.includes(entry)) continue; + if (excludeFiles.includes(entry)) continue; const srcPath = join(srcDir, entry); const destPath = join(destDir, entry); const stat = statSync(srcPath); if (stat.isDirectory()) { - collectFilesRecursive(srcPath, destPath, files); + collectFilesRecursive(srcPath, destPath, files, [], excludeFiles); } else { files.add(destPath.toLowerCase().replace(/\\/g, '/')); } @@ -491,7 +502,8 @@ async function copyDirectory( src: string, dest: string, manifest: any = null, - excludeDirs: string[] = [] + excludeDirs: string[] = [], + excludeFiles: string[] = EXCLUDED_FILES ): Promise { let files = 0; let directories = 0; @@ -511,12 +523,17 @@ async function copyDirectory( continue; } + // Skip excluded files + if (excludeFiles.includes(entry)) { + continue; + } + const srcPath = join(src, entry); const destPath = join(dest, entry); const stat = statSync(srcPath); if (stat.isDirectory()) { - const result = await copyDirectory(srcPath, destPath, manifest); + const result = await copyDirectory(srcPath, destPath, manifest, [], excludeFiles); files += result.files; directories += result.directories; } else { diff --git a/ccw/src/core/routes/claude-routes.ts b/ccw/src/core/routes/claude-routes.ts index e6e8b527..3dad1853 100644 --- a/ccw/src/core/routes/claude-routes.ts +++ b/ccw/src/core/routes/claude-routes.ts @@ -815,13 +815,10 @@ export async function handleClaudeRoutes(ctx: RouteContext): Promise { enabled = chineseRefPattern.test(content); } - // Find guidelines file path (project or user level) - const projectGuidelinesPath = join(initialPath, '.claude', 'workflows', 'chinese-response.md'); + // Find guidelines file path - always use user-level path const userGuidelinesPath = join(homedir(), '.claude', 'workflows', 'chinese-response.md'); - if (existsSync(projectGuidelinesPath)) { - guidelinesPath = projectGuidelinesPath; - } else if (existsSync(userGuidelinesPath)) { + if (existsSync(userGuidelinesPath)) { guidelinesPath = userGuidelinesPath; } @@ -853,21 +850,15 @@ export async function handleClaudeRoutes(ctx: RouteContext): Promise { const userClaudePath = join(homedir(), '.claude', 'CLAUDE.md'); const userClaudeDir = join(homedir(), '.claude'); - // Find guidelines file path - const projectGuidelinesPath = join(initialPath, '.claude', 'workflows', 'chinese-response.md'); + // Find guidelines file path - always use user-level path with ~ shorthand const userGuidelinesPath = join(homedir(), '.claude', 'workflows', 'chinese-response.md'); - let guidelinesRef = ''; - if (existsSync(projectGuidelinesPath)) { - // Use project-level guidelines with absolute path - guidelinesRef = projectGuidelinesPath.replace(/\\/g, '/'); - } else if (existsSync(userGuidelinesPath)) { - // Use user-level guidelines with ~ shorthand - guidelinesRef = '~/.claude/workflows/chinese-response.md'; - } else { - return { error: 'Chinese response guidelines file not found', status: 404 }; + if (!existsSync(userGuidelinesPath)) { + return { error: 'Chinese response guidelines file not found at ~/.claude/workflows/chinese-response.md', status: 404 }; } + const guidelinesRef = '~/.claude/workflows/chinese-response.md'; + const chineseRefLine = `- **中文回复准则**: @${guidelinesRef}`; const chineseRefPattern = /^- \*\*中文回复准则\*\*:.*chinese-response\.md.*$/gm; @@ -922,5 +913,118 @@ export async function handleClaudeRoutes(ctx: RouteContext): Promise { return true; } + // API: Get Windows platform setting status + if (pathname === '/api/language/windows-platform' && req.method === 'GET') { + try { + const userClaudePath = join(homedir(), '.claude', 'CLAUDE.md'); + const windowsRefPattern = /@.*windows-platform\.md/i; + + let enabled = false; + let guidelinesPath = ''; + + // Check if user CLAUDE.md exists and contains Windows platform reference + if (existsSync(userClaudePath)) { + const content = readFileSync(userClaudePath, 'utf8'); + enabled = windowsRefPattern.test(content); + } + + // Find guidelines file path - always use user-level path + const userGuidelinesPath = join(homedir(), '.claude', 'workflows', 'windows-platform.md'); + + if (existsSync(userGuidelinesPath)) { + guidelinesPath = userGuidelinesPath; + } + + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + enabled, + guidelinesPath, + guidelinesExists: !!guidelinesPath, + userClaudeMdExists: existsSync(userClaudePath) + })); + return true; + } catch (error) { + res.writeHead(500, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: (error as Error).message })); + return true; + } + } + + // API: Toggle Windows platform setting + if (pathname === '/api/language/windows-platform' && req.method === 'POST') { + handlePostRequest(req, res, async (body: any) => { + const { enabled } = body; + + if (typeof enabled !== 'boolean') { + return { error: 'Missing or invalid enabled parameter', status: 400 }; + } + + try { + const userClaudePath = join(homedir(), '.claude', 'CLAUDE.md'); + const userClaudeDir = join(homedir(), '.claude'); + + // Find guidelines file path - always use user-level path with ~ shorthand + const userGuidelinesPath = join(homedir(), '.claude', 'workflows', 'windows-platform.md'); + + if (!existsSync(userGuidelinesPath)) { + return { error: 'Windows platform guidelines file not found at ~/.claude/workflows/windows-platform.md', status: 404 }; + } + + const guidelinesRef = '~/.claude/workflows/windows-platform.md'; + + const windowsRefLine = `- **Windows Platform**: @${guidelinesRef}`; + const windowsRefPattern = /^- \*\*Windows Platform\*\*:.*windows-platform\.md.*$/gm; + + // Ensure user .claude directory exists + if (!existsSync(userClaudeDir)) { + const fs = require('fs'); + fs.mkdirSync(userClaudeDir, { recursive: true }); + } + + let content = ''; + if (existsSync(userClaudePath)) { + content = readFileSync(userClaudePath, 'utf8'); + } else { + // Create new CLAUDE.md with header + content = '# Claude Instructions\n\n'; + } + + if (enabled) { + // Check if reference already exists + if (windowsRefPattern.test(content)) { + return { success: true, message: 'Already enabled' }; + } + + // Add reference after the header line or at the beginning + const headerMatch = content.match(/^# Claude Instructions\n\n?/); + if (headerMatch) { + const insertPosition = headerMatch[0].length; + content = content.slice(0, insertPosition) + windowsRefLine + '\n' + content.slice(insertPosition); + } else { + // Add header and reference + content = '# Claude Instructions\n\n' + windowsRefLine + '\n' + content; + } + } else { + // Remove reference + content = content.replace(windowsRefPattern, '').replace(/\n{3,}/g, '\n\n').trim(); + if (content) content += '\n'; + } + + writeFileSync(userClaudePath, content, 'utf8'); + + // Broadcast update + broadcastToClients({ + type: 'LANGUAGE_SETTING_CHANGED', + data: { windowsPlatform: enabled } + }); + + return { success: true, enabled }; + } catch (error) { + return { error: (error as Error).message, status: 500 }; + } + }); + return true; + } + return false; } diff --git a/ccw/src/core/routes/codexlens-routes.ts b/ccw/src/core/routes/codexlens-routes.ts index 72c17b6f..e91f198b 100644 --- a/ccw/src/core/routes/codexlens-routes.ts +++ b/ccw/src/core/routes/codexlens-routes.ts @@ -384,17 +384,23 @@ export async function handleCodexLensRoutes(ctx: RouteContext): Promise // API: CodexLens Init (Initialize workspace index) if (pathname === '/api/codexlens/init' && req.method === 'POST') { handlePostRequest(req, res, async (body) => { - const { path: projectPath } = body; + const { path: projectPath, indexType = 'vector' } = body; const targetPath = projectPath || initialPath; + // Build CLI arguments based on index type + const args = ['init', targetPath, '--json']; + if (indexType === 'normal') { + args.push('--no-embeddings'); + } + // Broadcast start event broadcastToClients({ type: 'CODEXLENS_INDEX_PROGRESS', - payload: { stage: 'start', message: 'Starting index...', percent: 0, path: targetPath } + payload: { stage: 'start', message: 'Starting index...', percent: 0, path: targetPath, indexType } }); try { - const result = await executeCodexLens(['init', targetPath, '--json'], { + const result = await executeCodexLens(args, { cwd: targetPath, timeout: 1800000, // 30 minutes for large codebases onProgress: (progress: ProgressInfo) => { diff --git a/ccw/src/templates/dashboard-js/components/index-manager.js b/ccw/src/templates/dashboard-js/components/index-manager.js index 4d796a92..cf4bfe90 100644 --- a/ccw/src/templates/dashboard-js/components/index-manager.js +++ b/ccw/src/templates/dashboard-js/components/index-manager.js @@ -177,11 +177,20 @@ function renderIndexCard() {
- +
+ + +