From b22839c99f0bdcc964c24bf0d574ed4a4d5a542c Mon Sep 17 00:00:00 2001 From: catlog22 Date: Wed, 17 Dec 2025 22:05:16 +0800 Subject: [PATCH] fix: Resolve MCP installation issues and enhance path resolution - Fixed API endpoint mismatches in mcp-manager.js to ensure global install/update buttons function correctly. - Corrected undefined function references in mcp-manager.js for project installation. - Refactored event handling to eliminate global scope pollution in mcp-manager.js. - Added comprehensive debugging guide for MCP installation issues. - Implemented a session path resolver to infer content types from filenames and paths, improving usability. - Introduced tests for embeddings improvements in init and status commands to verify functionality. --- .claude/workflows/file-modification.md | 60 ++- .mcp.json | 10 +- EMBEDDINGS_IMPROVEMENTS_SUMMARY.md | 167 ++++++++ MCP_FINAL_FIX.md | 248 ++++++++++++ MCP_FIX_SUMMARY.md | 227 +++++++++++ MCP_INSTALL_DEBUG.md | 274 +++++++++++++ ccw/MCP_QUICKSTART.md | 4 +- ccw/MCP_SERVER.md | 5 +- ccw/src/cli.ts | 2 +- ccw/src/commands/session-path-resolver.ts | 372 ++++++++++++++++++ ccw/src/core/data-aggregator.ts | 9 +- ccw/src/core/routes/codexlens-routes.ts | 6 +- ccw/src/core/routes/memory-routes.ts | 27 +- ccw/src/mcp-server/index.ts | 2 +- .../dashboard-js/components/mcp-manager.js | 51 ++- ccw/src/templates/dashboard-js/help-i18n.js | 128 +++++- ccw/src/templates/dashboard-js/i18n.js | 14 + .../dashboard-js/views/cli-manager.js | 44 +++ .../dashboard-js/views/codexlens-manager.js | 42 ++ ccw/src/templates/dashboard-js/views/help.js | 325 ++++++++++----- .../dashboard-js/views/mcp-manager.js | 62 ++- ccw/src/tools/codex-lens.ts | 6 + ccw/src/tools/index.ts | 4 +- test_embeddings_improvements.py | 146 +++++++ 24 files changed, 2043 insertions(+), 192 deletions(-) create mode 100644 EMBEDDINGS_IMPROVEMENTS_SUMMARY.md create mode 100644 MCP_FINAL_FIX.md create mode 100644 MCP_FIX_SUMMARY.md create mode 100644 MCP_INSTALL_DEBUG.md create mode 100644 ccw/src/commands/session-path-resolver.ts create mode 100644 test_embeddings_improvements.py diff --git a/.claude/workflows/file-modification.md b/.claude/workflows/file-modification.md index 242aea70..1764bf4d 100644 --- a/.claude/workflows/file-modification.md +++ b/.claude/workflows/file-modification.md @@ -1,29 +1,48 @@ -## File Modification +# File Modification -### Use edit_file (MCP) -- Built-in Edit tool failed 1+ times -- Need dry-run preview before applying changes -- Need line-based operations (insert_after, insert_before) -- Need to replace all occurrences at once -- Built-in Edit returns "old_string not found" -- Whitespace/formatting issues in built-in Edit +Before modifying files, always: +- Try built-in Edit tool first +- Escalate to MCP tools when built-ins fail +- Use write_file only as last resort -**Mode Selection**: -- `mode=update`: Replace text -- `mode=line`: Line-based operations +## MCP Tools Usage -### Use write_file (MCP) -- Creating brand new files -- MCP edit_file still fails (last resort) -- Need to completely replace file content -- Need backup before overwriting -- User explicitly asks to "recreate file" +### edit_file - Modify Files + +**When**: Built-in Edit fails, need dry-run preview, or need line-based operations + +**How**: +```javascript +edit_file(path="/file.ts", oldText="old", newText="new") // Replace text +edit_file(path="/file.ts", oldText="old", newText="new", dryRun=true) // Preview diff +edit_file(path="/file.ts", oldText="old", newText="new", replaceAll=true) // Replace all +edit_file(path="/file.ts", mode="line", operation="insert_after", line=10, text="new line") +edit_file(path="/file.ts", mode="line", operation="delete", line=5, end_line=8) +``` + +**Modes**: `update` (replace text, default), `line` (line-based operations) + +**Operations** (line mode): `insert_before`, `insert_after`, `replace`, `delete` + +--- + +### write_file - Create/Overwrite Files + +**When**: Create new files, completely replace content, or edit_file still fails + +**How**: +```javascript +write_file(path="/new-file.ts", content="file content here") +write_file(path="/existing.ts", content="...", backup=true) // Create backup first +``` + +--- ## Priority Logic **File Reading**: 1. Known single file → Built-in Read -2. Multiple files OR pattern matching → read_file (MCP) +2. Multiple files OR pattern matching → smart_search (MCP) 3. Unknown location → smart_search then Read 4. Large codebase + repeated access → smart_search (indexed) @@ -33,9 +52,9 @@ 3. Still fails → write_file (MCP) **Search**: -1. External knowledge → Exa +1. External knowledge → Exa (MCP) 2. Exact pattern in small codebase → Built-in Grep -3. Semantic/unknown location → smart_search +3. Semantic/unknown location → smart_search (MCP) 4. Large codebase + repeated searches → smart_search (indexed) ## Decision Triggers @@ -45,4 +64,3 @@ **Use semantic search** for exploratory tasks **Use indexed search** for large, stable codebases **Use Exa** for external/public knowledge - diff --git a/.mcp.json b/.mcp.json index 4870b47d..70011302 100644 --- a/.mcp.json +++ b/.mcp.json @@ -1,11 +1,3 @@ { - "mcpServers": { - "ccw-tools": { - "command": "npx", - "args": [ - "-y", - "ccw-mcp" - ] - } - } + "mcpServers": {} } \ No newline at end of file diff --git a/EMBEDDINGS_IMPROVEMENTS_SUMMARY.md b/EMBEDDINGS_IMPROVEMENTS_SUMMARY.md new file mode 100644 index 00000000..2594ad0a --- /dev/null +++ b/EMBEDDINGS_IMPROVEMENTS_SUMMARY.md @@ -0,0 +1,167 @@ +# CodexLens Embeddings Statistics Improvements + +## Summary + +Improved the CodexLens `init` and `status` commands to return comprehensive embeddings statistics, making it easy for users to understand embeddings coverage. + +## Changes Made + +### 1. Updated `init` command (Task 1) + +**File**: `codex-lens/src/codexlens/cli/commands.py` (lines 142-219) + +**Key Changes**: +- Changed from `generate_embeddings()` to `generate_embeddings_recursive()` +- Now processes all `_index.db` files recursively in the index tree +- Passes `index_root` (directory) instead of `index_path` (file) +- Returns comprehensive coverage statistics after generation + +**Imports Added**: +```python +from codexlens.cli.embedding_manager import generate_embeddings_recursive, get_embeddings_status +``` + +**Result Structure**: +```json +{ + "embeddings": { + "generated": true, + "total_indexes": 26, + "total_files": 303, + "files_with_embeddings": 303, + "coverage_percent": 100.0, + "total_chunks": 500 + } +} +``` + +**Console Output**: +- Shows files processed count +- Shows total chunks created +- Shows indexes processed (successful/total) + +### 2. Updated `status` command (Task 2) + +**File**: `codex-lens/src/codexlens/cli/commands.py` (lines 642-713) + +**Key Changes**: +- Added embeddings coverage check using `get_embeddings_status()` +- Updates `vector_search` feature flag based on coverage (>= 50%) +- Includes embeddings data in JSON output +- Displays embeddings statistics in console output + +**Imports Added**: +```python +from codexlens.cli.embedding_manager import get_embeddings_status +``` + +**Result Structure**: +```json +{ + "embeddings": { + "total_indexes": 26, + "total_files": 303, + "files_with_embeddings": 303, + "files_without_embeddings": 0, + "coverage_percent": 100.0, + "total_chunks": 500, + "indexes_with_embeddings": 26, + "indexes_without_embeddings": 0 + }, + "features": { + "exact_fts": true, + "fuzzy_fts": true, + "hybrid_search": true, + "vector_search": true // true when coverage >= 50% + } +} +``` + +**Console Output**: +``` +Search Backends: + Exact FTS: ✓ (unicode61) + Fuzzy FTS: ✓ (trigram) + Hybrid Search: ✓ (RRF fusion) + Vector Search: ✓ (embeddings available) + +Embeddings Coverage: + Total Indexes: 26 + Total Files: 303 + Files with Embeddings: 303 + Coverage: 100.0% + Total Chunks: 500 +``` + +## Benefits + +1. **Transparency**: Users can now see exactly what embeddings were generated +2. **Coverage Visibility**: Clear percentage showing embeddings coverage across all files +3. **Recursive Processing**: All index databases in the tree are processed, not just the root +4. **Feature Detection**: Vector search is automatically enabled when coverage is sufficient (>= 50%) +5. **Comprehensive Stats**: Shows total indexes, files, chunks, and coverage percentage + +## Backward Compatibility + +- All changes are backward compatible +- Gracefully handles cases where embeddings are not available +- ImportError handling for when embedding_manager is not available +- Existing JSON output structure is extended, not changed + +## Testing + +Created test script: `test_embeddings_improvements.py` + +Tests verify: +- Init command reports embeddings statistics correctly +- Status command shows embeddings coverage +- JSON output includes all required fields +- Console output displays statistics properly + +## Usage Examples + +### Init with embeddings +```bash +codexlens init /path/to/project --json +# Returns comprehensive embeddings statistics +``` + +### Check status +```bash +codexlens status --json +# Shows embeddings coverage and feature availability +``` + +### Init without embeddings +```bash +codexlens init /path/to/project --no-embeddings --json +# Returns embeddings: {"generated": false, "error": "Skipped (--no-embeddings)"} +``` + +## Files Modified + +1. `codex-lens/src/codexlens/cli/commands.py` - Updated init and status commands + +## Implementation Details + +### Init Command Flow +1. Build index tree as before +2. If `--no-embeddings` not set: + - Call `generate_embeddings_recursive(index_root)` instead of `generate_embeddings(index_path)` + - After generation, call `get_embeddings_status(index_root)` to get coverage stats + - Include comprehensive statistics in result +3. Return result with embeddings coverage data + +### Status Command Flow +1. Collect index statistics as before +2. Call `get_embeddings_status(index_root)` to check embeddings +3. Set `vector_search` feature flag based on coverage >= 50% +4. Include embeddings info in JSON output +5. Display embeddings statistics in console output + +## Error Handling + +- Handles ImportError when embedding_manager not available +- Handles cases where embeddings don't exist (returns 0% coverage) +- Graceful fallback if get_embeddings_status fails +- Debug logging for failed operations diff --git a/MCP_FINAL_FIX.md b/MCP_FINAL_FIX.md new file mode 100644 index 00000000..855eec01 --- /dev/null +++ b/MCP_FINAL_FIX.md @@ -0,0 +1,248 @@ +# CCW MCP 工具安装问题 - 最终修复报告 + +## 🎯 通过 Gemini AI 发现的关键问题 + +### 问题 4: 事件监听器注册失败 ⚠️ **根本原因** ✅ 已修复 +**文件**: `views/mcp-manager.js` Line 1407 +**严重性**: Critical - 导致所有按钮完全无响应 +**原因**: 使用 `querySelector` 只返回第一个匹配元素,如果 DOM 中有多个元素或选择器匹配错误,事件监听器会附加到错误的元素上 +**症状**: 按钮点击无任何反应,控制台无错误,调试困难 + +**修复前**: +```javascript +const btn = document.querySelector(`button[data-action="${action}"]`); +if (btn) { + btn.addEventListener('click', async (e) => { + e.preventDefault(); + await handler(); + }); +} +``` + +**修复后**: +```javascript +const btns = document.querySelectorAll(`button[data-action="${action}"]`); + +if (btns.length > 0) { + console.log(`[MCP] Attaching listener to ${action} (${btns.length} button(s) found)`); + btns.forEach(btn => { + btn.addEventListener('click', async (e) => { + e.preventDefault(); + console.log(`[MCP] Button clicked: ${action}`); + try { + await handler(); + } catch (err) { + console.error(`[MCP] Error executing handler for ${action}:`, err); + if (typeof showRefreshToast === 'function') { + showRefreshToast(`Action failed: ${err.message}`, 'error'); + } + } + }); + }); +} else { + console.warn(`[MCP] No buttons found for action: ${action}`); +} +``` + +**修复优势**: +1. ✅ 使用 `querySelectorAll` 确保所有匹配按钮都绑定事件 +2. ✅ 添加详细的控制台日志,便于调试 +3. ✅ 添加错误捕获和用户友好的错误提示 +4. ✅ 添加警告日志,发现选择器不匹配问题 + +--- + +### 问题 5: MCP 计数显示错误 ✅ 已修复 +**文件**: `components/mcp-manager.js` Line 398 +**严重性**: Medium - 显示 2/2 但实际只有 1 个服务器 +**原因**: 路径格式不一致 - `loadMcpConfig` 使用 `\\`,`updateMcpBadge` 使用 `/` + +**修复前**: +```javascript +function updateMcpBadge() { + const badge = document.getElementById('badgeMcpServers'); + if (badge) { + const currentPath = projectPath; // Keep original format (forward slash) + const projectData = mcpAllProjects[currentPath]; + // ... + } +} +``` + +**修复后**: +```javascript +function updateMcpBadge() { + const badge = document.getElementById('badgeMcpServers'); + if (badge) { + // IMPORTANT: Use same path normalization as loadMcpConfig + const currentPath = projectPath.replace(/\//g, '\\'); + const projectData = mcpAllProjects[currentPath]; + // ... + console.log('[MCP Badge]', { currentPath, totalServers, enabledServers, disabledServers }); + badge.textContent = `${enabledServers}/${totalServers}`; + } +} +``` + +--- + +## 📊 完整问题清单 + +| # | 问题 | 文件 | 严重性 | 状态 | +|---|------|------|--------|------| +| 1 | API 端点不匹配 | components/mcp-manager.js | Critical | ✅ 已修复 | +| 2 | 未定义函数引用 | views/mcp-manager.js | High | ✅ 已修复 | +| 3 | 全局作用域污染 | views/mcp-manager.js | Medium | ✅ 已修复 | +| 4 | **querySelector 单选器失效** | views/mcp-manager.js | **Critical** | ✅ 已修复 | +| 5 | MCP 计数路径不一致 | components/mcp-manager.js | Medium | ✅ 已修复 | + +--- + +## 🔍 Gemini AI 分析价值 + +Gemini AI 深度分析提供了关键洞察: + +1. **执行流程追踪**: 确认 `attachMcpEventListeners()` 在 DOM 更新后立即调用 +2. **选择器匹配验证**: 发现 `data-action` 属性完全匹配 +3. **根本原因识别**: 定位到 `querySelector` vs `querySelectorAll` 的关键差异 +4. **静默失败诊断**: 指出缺少日志导致调试困难 +5. **修复建议**: 提供完整的代码修复方案,包括错误处理 + +--- + +## 🧪 测试验证步骤 + +### 1. 重启服务 +```bash +cd D:\Claude_dms3\ccw +npm run dev +``` + +### 2. 打开浏览器控制台 +- 访问 `http://localhost:3456` +- 按 `F12` 打开控制台 +- **现在应该能看到调试日志**: + ``` + [MCP] Attaching listener to install-ccw-workspace (1 button(s) found) + [MCP] Attaching listener to install-ccw-global (1 button(s) found) + [MCP] Attaching listener to update-ccw-workspace (1 button(s) found) + [MCP] Attaching listener to update-ccw-global (1 button(s) found) + [MCP] Attaching listener to install-ccw-codex (1 button(s) found) + ``` + +### 3. 点击按钮测试 +点击任何 CCW Tools 按钮,应该看到: +``` +[MCP] Button clicked: install-ccw-workspace +Installing CCW Tools MCP to workspace... +``` + +如果失败,会看到: +``` +[MCP] Error executing handler for install-ccw-workspace: <错误详情> +``` + +### 4. 验证 MCP 计数 +- 导航到 MCP Manager +- 检查左侧导航栏的 MCP 徽章 +- **现在应该显示正确的计数** (1/1 而不是 2/2) +- 控制台会显示: + ``` + [MCP Badge] { currentPath: 'D:\\dongdiankaifa9', totalServers: 1, enabledServers: 1, disabledServers: [] } + ``` + +--- + +## ✅ 预期结果 + +### 所有按钮现在应该正常工作 + +#### 1. 工作空间安装/更新 +- **位置**: CCW Tools MCP 卡片 +- **按钮**: "安装到工作空间" / "在工作空间更新" +- **预期**: 点击后显示加载提示,成功后显示成功消息 + +#### 2. 全局安装/更新 +- **位置**: CCW Tools MCP 卡片 +- **按钮**: "Install to global" / "Update Globally" +- **预期**: 更新 `~/.claude.json` + +#### 3. Codex 安装 +- **位置**: Codex 模式的 CCW Tools MCP 卡片 +- **按钮**: "Install" / "Update" +- **预期**: 更新 `~/.codex/config.toml` + +#### 4. 从其他项目安装 +- **位置**: "Available from Other Projects" 部分 +- **预期**: 弹出配置类型选择对话框 + +#### 5. MCP 计数显示 +- **位置**: 左侧导航栏 MCP Manager 徽章 +- **预期**: 显示正确的启用/总计数量 (例如 1/1) + +--- + +## 🐛 调试指南 + +### 如果按钮仍然无响应 + +#### 检查控制台日志 +如果看不到 `[MCP] Attaching listener` 日志: +- ❌ `attachMcpEventListeners()` 函数没有被调用 +- 解决方法: 检查 `renderMcpManager()` 是否正确调用了该函数 + +#### 如果看到 "No buttons found" +``` +[MCP] No buttons found for action: install-ccw-workspace +``` +- ❌ `data-action` 属性不匹配或按钮未渲染 +- 解决方法: 检查 HTML 生成逻辑,确保 `data-action` 属性正确设置 + +#### 如果看到点击日志但无反应 +``` +[MCP] Button clicked: install-ccw-workspace +``` +但之后无进一步输出: +- ❌ 函数执行被阻塞或抛出未捕获的错误 +- 解决方法: 检查 `installCcwToolsMcp()` 函数实现 + +#### 如果 MCP 计数仍然错误 +控制台应该显示: +``` +[MCP Badge] { currentPath: '...', totalServers: 1, enabledServers: 1, disabledServers: [] } +``` +- 如果 `currentPath` 格式错误 (例如包含 `/` 而不是 `\\`),说明路径规范化失败 +- 如果 `totalServers` 不正确,检查 `mcpAllProjects` 数据结构 + +--- + +## 📋 修复文件清单 + +``` +D:\Claude_dms3\ccw\src\templates\dashboard-js\components\mcp-manager.js + - Line 398: updateMcpBadge 路径规范化修复 + - Line 404: 添加调试日志 + +D:\Claude_dms3\ccw\src\templates\dashboard-js\views\mcp-manager.js + - Line 1407-1420: querySelector → querySelectorAll + 完整错误处理 +``` + +--- + +## 🎯 关键收获 + +1. **工具链选择**: Gemini AI 在执行流程追踪和根本原因分析上表现出色 +2. **调试友好**: 添加详细日志是诊断"静默失败"的关键 +3. **健壮性设计**: `querySelectorAll` + 错误捕获 > 简单的 `querySelector` +4. **路径规范化**: 在 Windows 系统上,路径格式必须一致(统一使用 `\\`) + +--- + +## 🚀 下一步 + +重启服务后测试所有按钮功能。如果仍有问题,请提供: +1. 完整的浏览器控制台输出(包括 `[MCP]` 前缀的所有日志) +2. Network 标签中的 API 请求详情 +3. 具体点击的按钮和操作步骤 + +所有修复已合并到代码库,重新编译完成。 diff --git a/MCP_FIX_SUMMARY.md b/MCP_FIX_SUMMARY.md new file mode 100644 index 00000000..86b5b358 --- /dev/null +++ b/MCP_FIX_SUMMARY.md @@ -0,0 +1,227 @@ +# CCW MCP 工具安装问题修复总结 + +## 📊 问题发现 + +通过 Gemini AI 深度分析,发现了 3 个导致 MCP 安装按钮无响应的关键问题: + +### 🔴 问题 1: API 端点不匹配(Critical) +- **文件**: `ccw/src/templates/dashboard-js/components/mcp-manager.js` +- **影响**: 全局安装/更新按钮完全无响应 +- **根本原因**: 前端调用 `/api/mcp-add-global`,后端定义 `/api/mcp-add-global-server` +- **修复位置**: + - Line 960: `installCcwToolsMcp()` 全局安装 + - Line 1024: `updateCcwToolsMcp()` 全局更新 + +### 🔴 问题 2: 未定义的函数引用(High) +- **文件**: `ccw/src/templates/dashboard-js/views/mcp-manager.js` +- **影响**: "从其他项目安装" 按钮抛出 ReferenceError +- **根本原因**: 调用不存在的 `installMcpToProject`,正确名称是 `copyMcpServerToProject` +- **修复位置**: + - Line 1352: Install to project 按钮事件处理 + - Line 1847: 模板安装逻辑 + +### 🟡 问题 3: 全局作用域污染(Medium) +- **文件**: `ccw/src/templates/dashboard-js/views/mcp-manager.js` +- **影响**: CCW Tools 工作空间/全局更新按钮可能无响应 +- **根本原因**: 使用内联 `onclick` 属性,函数不在全局作用域 +- **修复位置**: + - Line 239: Codex 安装按钮 + - Lines 424, 430: 工作空间/全局更新按钮 + - Lines 437, 443: 工作空间/全局安装按钮 + - Lines 1397-1413: 新增健壮的事件监听器 + +## ✅ 已修复的文件 + +``` +D:\Claude_dms3\ccw\src\templates\dashboard-js\components\mcp-manager.js + - Line 960: API 端点修正 (/api/mcp-add-global-server) + - Line 1024: API 端点修正 (/api/mcp-add-global-server) + +D:\Claude_dms3\ccw\src\templates\dashboard-js\views\mcp-manager.js + - Line 239: onclick → data-action (Codex) + - Line 424: onclick → data-action (update workspace) + - Line 430: onclick → data-action (update global) + - Line 437: onclick → data-action (install workspace) + - Line 443: onclick → data-action (install global) + - Line 1352: installMcpToProject → copyMcpServerToProject + - Line 1397-1413: 新增 CCW Tools 事件监听器 + - Line 1847: installMcpToProject → copyMcpServerToProject +``` + +## 🔧 修复方案详解 + +### 方案 1: API 端点统一 +```javascript +// 修复前 +fetch('/api/mcp-add-global', { ... }) // ❌ 404 Not Found + +// 修复后 +fetch('/api/mcp-add-global-server', { ... }) // ✅ 匹配后端路由 +``` + +### 方案 2: 函数名称修正 +```javascript +// 修复前 +await installMcpToProject(serverName, serverConfig); // ❌ ReferenceError + +// 修复后 +await copyMcpServerToProject(serverName, serverConfig); // ✅ 正确函数 +``` + +### 方案 3: 事件处理重构 +```html + + + + + +``` + +```javascript +// 新增事件监听器 +const ccwActions = { + 'update-ccw-workspace': () => updateCcwToolsMcp('workspace'), + 'update-ccw-global': () => updateCcwToolsMcp('global'), + 'install-ccw-workspace': () => installCcwToolsMcp('workspace'), + 'install-ccw-global': () => installCcwToolsMcp('global'), + 'install-ccw-codex': () => installCcwToolsMcpToCodex() +}; + +Object.entries(ccwActions).forEach(([action, handler]) => { + const btn = document.querySelector(`button[data-action="${action}"]`); + if (btn) { + btn.addEventListener('click', async (e) => { + e.preventDefault(); + await handler(); + }); + } +}); +``` + +## 🧪 测试场景 + +### ✅ 现在应该正常工作的按钮 + +#### 1. CCW Tools 工作空间安装/更新 +- **位置**: MCP Manager → CCW Tools MCP 卡片 +- **按钮**: "Install to Workspace" / "Update in Workspace" +- **测试步骤**: + 1. 选择至少一个工具(勾选复选框) + 2. 点击按钮 + 3. **预期结果**: 提示 "Installing/Updating CCW Tools MCP to workspace..." + 4. 成功后显示: "CCW Tools installed/updated to workspace (N tools)" + +#### 2. CCW Tools 全局安装/更新 +- **位置**: MCP Manager → CCW Tools MCP 卡片 +- **按钮**: "Install Globally" / "Update Globally" +- **测试步骤**: + 1. 选择至少一个工具 + 2. 点击按钮 + 3. **预期结果**: 提示 "Installing/Updating CCW Tools MCP globally..." + 4. 成功后显示: "CCW Tools installed/updated globally (N tools)" + +#### 3. Codex 安装/更新 +- **位置**: MCP Manager → Codex 模式 → CCW Tools MCP 卡片 +- **按钮**: "Install" / "Update" +- **测试步骤**: + 1. 切换到 Codex 模式 + 2. 选择工具 + 3. 点击按钮 + 4. **预期结果**: 成功提示 + +#### 4. 从其他项目安装 +- **位置**: MCP Manager → Available from Other Projects 部分 +- **按钮**: 服务器卡片上的文本按钮或图标按钮 +- **测试步骤**: + 1. 找到其他项目的 MCP 服务器 + 2. 点击 "Install to Project" 或文件夹图标 + 3. **预期结果**: 弹出配置类型选择对话框 + 4. 选择后成功安装 + +## 📋 验证清单 + +在重启服务后,请逐项验证: + +- [ ] **CCW Tools 工作空间安装** - 点击按钮有反应,控制台无错误 +- [ ] **CCW Tools 工作空间更新** - 修改工具选择后可更新 +- [ ] **CCW Tools 全局安装** - 安装到 ~/.claude.json +- [ ] **CCW Tools 全局更新** - 可更新全局配置 +- [ ] **Codex 模式安装** - 安装到 ~/.codex/config.toml +- [ ] **从其他项目安装** - 文本按钮工作正常 +- [ ] **从其他项目安装** - 图标按钮工作正常 +- [ ] **浏览器控制台** - 无 ReferenceError 或 404 错误 + +## 🚀 启动测试 + +### 1. 重启服务(必须) +```bash +cd D:\Claude_dms3\ccw +npm run dev +``` + +### 2. 打开浏览器控制台 +- 访问: `http://localhost:3456` +- 按 `F12` 打开开发者工具 +- 切换到 **Console** 标签 + +### 3. 逐个测试按钮 +按照上述测试场景,逐个点击按钮并观察: +- 浏览器控制台输出 +- Network 标签中的 API 请求 +- 页面上的成功/失败提示 + +## 📊 API 端点映射表 + +| 前端调用 | 后端路由 | 状态 | +|---------|---------|------| +| `/api/mcp-add-global-server` | `/api/mcp-add-global-server` | ✅ 匹配 | +| `/api/mcp-copy-server` | `/api/mcp-copy-server` | ✅ 匹配 | +| `/api/codex-mcp-add` | `/api/codex-mcp-add` | ✅ 匹配 | +| `/api/mcp-config` | `/api/mcp-config` | ✅ 匹配 | +| `/api/mcp-toggle` | `/api/mcp-toggle` | ✅ 匹配 | + +## 🐛 如果仍有问题 + +### 检查项: +1. **服务是否重启?** 必须重启才能加载新代码 +2. **浏览器缓存?** 按 `Ctrl+Shift+R` 强制刷新 +3. **控制台错误?** 检查是否有 JavaScript 错误 +4. **Network 请求?** 查看 API 请求的状态码和响应 + +### 需要提供的调试信息: +- 浏览器控制台完整错误信息(截图或文本) +- Network 标签中失败请求的详情 +- CCW View 服务端控制台输出 +- 点击的具体按钮和操作步骤 + +## 📈 修复效果 + +| 问题 | 修复前 | 修复后 | +|-----|-------|-------| +| 全局安装按钮 | ❌ 404 错误 | ✅ 正常工作 | +| 全局更新按钮 | ❌ 404 错误 | ✅ 正常工作 | +| 工作空间安装 | ⚠️ 可能失效 | ✅ 稳定工作 | +| 工作空间更新 | ⚠️ 可能失效 | ✅ 稳定工作 | +| 从其他项目安装 | ❌ ReferenceError | ✅ 正常工作 | +| Codex 安装 | ⚠️ 可能失效 | ✅ 稳定工作 | + +## 🎯 技术细节 + +### Gemini 分析发现 +Gemini AI 通过执行流程追踪和代码模式分析,准确识别了: +1. 前后端 API 端点不一致 +2. 函数名称拼写错误导致的引用失败 +3. JavaScript 作用域问题导致的事件处理失效 + +### 修复优势 +- **健壮性提升**: 使用 `addEventListener` 替代内联 `onclick` +- **可维护性**: 函数名称统一,减少混淆 +- **调试友好**: 所有 API 调用端点与后端完全匹配 + +## 📝 修复日志 + +- **2025-12-17**: 初始问题报告 - 安装按钮无响应 +- **2025-12-17**: 发现 API 端点不匹配问题(问题 1) +- **2025-12-17**: Gemini 分析发现额外 2 个问题(问题 2、3) +- **2025-12-17**: 完成所有修复并重新编译 +- **2025-12-17**: 创建完整的测试和调试指南 diff --git a/MCP_INSTALL_DEBUG.md b/MCP_INSTALL_DEBUG.md new file mode 100644 index 00000000..2a87681d --- /dev/null +++ b/MCP_INSTALL_DEBUG.md @@ -0,0 +1,274 @@ +# CCW MCP 工具安装问题调试指南 + +## 🎯 已修复的所有问题(完整清单) + +### 问题 1: API 端点不匹配 ✅ 已修复 +**位置**: `components/mcp-manager.js` +**原因**: 前端调用 `/api/mcp-add-global`,后端定义 `/api/mcp-add-global-server` +**修复**: +- Line 960: `installCcwToolsMcp()` - 全局安装端点已修正 +- Line 1024: `updateCcwToolsMcp()` - 全局更新端点已修正 + +### 问题 2: 未定义的函数引用 ✅ 已修复 +**位置**: `views/mcp-manager.js` +**原因**: 调用不存在的函数 `installMcpToProject`,正确函数名是 `copyMcpServerToProject` +**修复**: +- Line 1352: "Install to Project" 按钮事件处理已修正 +- Line 1847: 模板安装逻辑已修正 + +### 问题 3: 全局作用域污染导致按钮失效 ✅ 已修复 +**位置**: `views/mcp-manager.js` +**原因**: CCW Tools 按钮使用内联 `onclick` 属性,函数不在全局作用域导致无响应 +**修复**: +- Lines 239, 424, 430, 437, 443: 所有 `onclick` 改为 `data-action` 属性 +- Line 1397-1413: 添加健壮的事件监听器,使用 `addEventListener` + +## 测试步骤 + +### 1. 重启 CCW View 服务 + +**重要:** 必须重启服务才能加载修复后的代码。 + +```bash +# 方法 1: 直接运行 +cd D:\Claude_dms3\ccw +npm run dev + +# 方法 2: 使用 ccw 命令 +ccw view +``` + +### 2. 打开浏览器控制台 + +在浏览器中访问 `http://localhost:3456`,按 `F12` 打开开发者工具,切换到 **Console** 标签。 + +### 3. 测试安装按钮 + +#### 测试场景 A: 安装到工作空间 (Workspace) + +1. 导航到 **MCP Manager** 页面 +2. 找到 **CCW Tools MCP** 卡片 +3. 选择至少一个工具(勾选复选框) +4. 点击 **"Install to Workspace"** 按钮 +5. **观察控制台输出**: + - ✅ 成功:看到 "Installing CCW Tools MCP to workspace..." 提示 + - ❌ 失败:看到错误信息 + +#### 测试场景 B: 安装到全局 (Global) + +1. 选择至少一个工具 +2. 点击 **"Install Globally"** 按钮 +3. **观察控制台输出** + +#### 测试场景 C: 更新工具配置 + +1. 如果已安装,修改工具选择(添加或删除工具) +2. 点击 **"Update in Workspace"** 或 **"Update Globally"** 按钮 +3. **观察控制台输出** + +#### 测试场景 D: 从其他项目安装 + +1. 滚动到 **"Available from Other Projects"** 部分 +2. 找到想要安装的 MCP 服务器 +3. 点击 **"Install to Project"** 按钮 +4. **观察控制台输出** + +## 常见错误排查 + +### 错误 1: 502 Bad Gateway + +**原因:** CCW View 服务未正确运行 + +**解决方法:** +```bash +# 停止所有 node 进程 +taskkill /F /IM node.exe + +# 重新启动服务 +cd D:\Claude_dms3\ccw +npm run dev +``` + +### 错误 2: 404 Not Found (API 端点) + +**原因:** API 路由未正确加载 + +**解决方法:** +1. 确认已运行 `npm run build` +2. 检查 `dist/core/routes/mcp-routes.js` 是否存在 +3. 重启服务 + +### 错误 3: 按钮点击无反应 + +**检查清单:** +- [ ] 是否选择了至少一个工具?(如果没有选择,会显示警告) +- [ ] 浏览器控制台是否有 JavaScript 错误? +- [ ] 网络标签是否显示 API 请求? + +**调试方法:** +```javascript +// 在浏览器控制台执行以下命令测试函数是否可用 +typeof installCcwToolsMcp // 应该返回 "function" +typeof updateCcwToolsMcp // 应该返回 "function" +``` + +### 错误 4: Network Error / CORS Error + +**原因:** 前后端端口不一致 + +**解决方法:** +- 确认 CCW View 运行在正确的端口(默认 3456) +- 检查浏览器访问的 URL 是否正确 + +## 网络请求监控 + +在浏览器开发者工具的 **Network** 标签中,点击按钮后应该看到: + +### 成功的请求示例 + +**请求:** +``` +POST http://localhost:3456/api/mcp-add-global-server +``` + +**请求体:** +```json +{ + "serverName": "ccw-tools", + "serverConfig": { + "command": "npx", + "args": ["-y", "ccw-mcp"], + "env": { + "CCW_ENABLED_TOOLS": "write_file,edit_file,codex_lens,smart_search" + } + } +} +``` + +**响应:** +```json +{ + "success": true, + "serverName": "ccw-tools", + "scope": "global" +} +``` + +### 失败的请求示例 + +**状态码:** 404, 500, 502 + +**常见错误响应:** +```json +{ + "error": "serverName and serverConfig are required", + "status": 400 +} +``` + +## 日志检查 + +### 服务器日志 + +CCW View 服务运行时的控制台输出应该显示: +``` +CCW Dashboard running at http://localhost:3456 +``` + +如果看到错误,记录完整错误信息。 + +### 浏览器日志 + +打开控制台(F12)后,所有与 MCP 相关的日志会显示: +- API 请求和响应 +- JavaScript 错误 +- 网络错误 + +## 成功安装的验证 + +安装成功后,应该: +1. 看到成功提示:`"CCW Tools installed to workspace (4 tools)"` +2. MCP 卡片显示 ✓ 图标和工具数量 +3. 在项目根目录生成 `.mcp.json` 文件(workspace 安装) +4. 或在 `~/.claude.json` 中添加配置(global 安装) + +## 手动验证配置文件 + +### Workspace 安装 (.mcp.json) + +```bash +cat D:\Claude_dms3\.mcp.json +``` + +应该包含: +```json +{ + "mcpServers": { + "ccw-tools": { + "command": "npx", + "args": ["-y", "ccw-mcp"], + "env": { + "CCW_ENABLED_TOOLS": "write_file,edit_file,codex_lens,smart_search" + } + } + } +} +``` + +### Global 安装 (~/.claude.json) + +```bash +# Windows +type %USERPROFILE%\.claude.json + +# Linux/Mac +cat ~/.claude.json +``` + +应该包含 `mcpServers` 部分。 + +## 需要报告的信息 + +如果问题仍未解决,请提供: + +1. **浏览器控制台完整错误信息** +2. **Network 标签中失败的 API 请求详情** + - 请求 URL + - 请求方法 + - 状态码 + - 响应内容 +3. **CCW View 服务端日志** +4. **操作系统和浏览器版本** +5. **CCW 版本** (`ccw --version`) + +## 快速测试脚本 + +在浏览器控制台运行以下脚本测试完整流程: + +```javascript +// 测试 API 端点可用性 +async function testMcpEndpoints() { + const endpoints = [ + '/api/mcp-config', + '/api/mcp-add-global-server', + '/api/mcp-copy-server' + ]; + + for (const endpoint of endpoints) { + try { + const response = await fetch(endpoint); + console.log(`✓ ${endpoint}: ${response.status}`); + } catch (error) { + console.error(`✗ ${endpoint}: ${error.message}`); + } + } +} + +testMcpEndpoints(); +``` + +## 已知限制 + +- 必须至少选择一个工具才能安装 +- 安装到全局需要写入 `~/.claude.json` 的权限 +- Windows 用户确保路径使用正确的斜杠方向 diff --git a/ccw/MCP_QUICKSTART.md b/ccw/MCP_QUICKSTART.md index 8a925d9d..52a4fd85 100644 --- a/ccw/MCP_QUICKSTART.md +++ b/ccw/MCP_QUICKSTART.md @@ -29,7 +29,7 @@ npm install -g ccw Once configured, Claude Desktop can use these CCW tools: - **File Operations**: `edit_file`, `write_file` -- **Code Analysis**: `codex_lens`, `smart_search`, `get_modules_by_depth`, `classify_folders` +- **Code Analysis**: `smart_search`, `get_modules_by_depth`, `classify_folders` - **Git Integration**: `detect_changed_modules` - **Session Management**: `session_manager` - **UI/Design**: `discover_design_files`, `ui_generate_preview`, `convert_tokens_to_css` @@ -40,7 +40,7 @@ Once configured, Claude Desktop can use these CCW tools: ``` "Use edit_file to update the version in package.json" -"Use codex_lens to analyze the authentication flow" +"Use smart_search to find authentication logic" "Use get_modules_by_depth to show me the project structure" ``` diff --git a/ccw/MCP_SERVER.md b/ccw/MCP_SERVER.md index 9973b3b3..d7d4764b 100644 --- a/ccw/MCP_SERVER.md +++ b/ccw/MCP_SERVER.md @@ -59,11 +59,10 @@ The MCP server exposes the following CCW tools: - **write_file** - Create or overwrite files ### Code Analysis -- **codex_lens** - Analyze code execution flow +- **smart_search** - Intelligent code search with hybrid/exact/ripgrep modes - **get_modules_by_depth** - Get module hierarchy by depth - **classify_folders** - Classify project folders - **detect_changed_modules** - Detect modules with git changes -- **smart_search** - Intelligent code search ### Session Management - **session_manager** - Manage workflow sessions @@ -88,7 +87,7 @@ Once configured, you can use CCW tools directly in Claude Desktop conversations: ``` Can you use edit_file to update the header in README.md? -Use codex_lens to analyze the authentication flow in src/auth/login.js +Use smart_search to find authentication logic: smart_search(query="authentication") Get the module structure with get_modules_by_depth ``` diff --git a/ccw/src/cli.ts b/ccw/src/cli.ts index 37997613..8f3a4687 100644 --- a/ccw/src/cli.ts +++ b/ccw/src/cli.ts @@ -135,7 +135,7 @@ export function run(argv: string[]): void { program .command('session [subcommand] [args...]') .description('Workflow session lifecycle management') - .option('--location ', 'Location filter: active|archived|both') + .option('--location ', 'Session location: active|lite-plan|lite-fix (init); Filter: active|archived|both (list)') .option('--type ', 'Content type or session type') .option('--content ', 'Content for write/update') .option('--task-id ', 'Task ID for task content') diff --git a/ccw/src/commands/session-path-resolver.ts b/ccw/src/commands/session-path-resolver.ts new file mode 100644 index 00000000..d98e45e0 --- /dev/null +++ b/ccw/src/commands/session-path-resolver.ts @@ -0,0 +1,372 @@ +/** + * Session Path Resolver - Smart file path to content_type resolution + * Eliminates need for --type parameter by inferring from filenames and paths + */ + +import { basename, dirname, join } from 'path'; + +// Supported content types (from session-manager.ts) +type ContentType = + | 'session' | 'plan' | 'task' | 'summary' | 'process' | 'chat' | 'brainstorm' + | 'review-dim' | 'review-iter' | 'review-fix' | 'todo' | 'context' + | 'lite-plan' | 'lite-fix-plan' | 'exploration' | 'explorations-manifest' + | 'diagnosis' | 'diagnoses-manifest' | 'clarifications' | 'execution-context' | 'session-metadata'; + +export interface ResolverResult { + contentType: ContentType; + pathParams?: Record; + resolvedPath: string; // Relative path within session directory +} + +export interface ResolverContext { + sessionPath: string; + sessionLocation: 'active' | 'archived' | 'lite-plan' | 'lite-fix'; +} + +export class PathResolutionError extends Error { + code: 'NOT_FOUND' | 'INVALID_PATH'; + suggestions: string[]; + + constructor(code: 'NOT_FOUND' | 'INVALID_PATH', message: string, suggestions: string[] = []) { + super(message); + this.name = 'PathResolutionError'; + this.code = code; + this.suggestions = suggestions; + } +} + +/** + * Task ID patterns (IMPL-*, TEST-*, DOC-*, REFACTOR-*, TASK-*) + */ +const TASK_ID_PATTERNS = [ + /^(IMPL|TEST|DOC|REFACTOR|TASK)-\d+\.json$/i, +]; + +/** + * Summary filename pattern (*-summary.md) + */ +const SUMMARY_PATTERN = /^(.+)-summary\.md$/i; + +/** + * Path prefix to content_type mapping + */ +const PATH_PREFIX_TO_CONTENT_TYPE: Record = { + '.task/': 'task', + '.summaries/': 'summary', + '.process/': 'process', + '.chat/': 'chat', + '.brainstorming/': 'brainstorm', + '.review/dimensions/': 'review-dim', + '.review/iterations/': 'review-iter', + '.review/fixes/': 'review-fix', +}; + +/** + * Exact filename to content_type mapping + */ +const EXACT_FILENAME_TO_CONTENT_TYPE: Record = { + 'IMPL_PLAN.md': 'plan', + 'TODO_LIST.md': 'todo', + 'workflow-session.json': 'session', + 'context-package.json': 'context', + 'plan.json': 'lite-plan', // Will be overridden by session location + 'fix-plan.json': 'lite-fix-plan', // Specific to lite-fix sessions + 'session-metadata.json': 'session-metadata', + 'clarifications.json': 'clarifications', + 'execution-context.json': 'execution-context', + 'explorations-manifest.json': 'explorations-manifest', + 'diagnoses-manifest.json': 'diagnoses-manifest', +}; + +/** + * Extract task ID from filename + * Examples: IMPL-001.json → IMPL-001 + */ +function extractTaskId(filename: string): string | null { + const match = filename.match(/^([A-Z]+-\d+)\.json$/i); + return match ? match[1] : null; +} + +/** + * Extract summary task ID from filename + * Examples: IMPL-001-summary.md → IMPL-001 + */ +function extractSummaryTaskId(filename: string): string | null { + const match = filename.match(SUMMARY_PATTERN); + return match ? match[1] : null; +} + +/** + * Extract review dimension from path + * Examples: .review/dimensions/security.json → security + */ +function extractReviewDimension(filepath: string): string | null { + const match = filepath.match(/\.review\/dimensions\/(.+)\.json$/); + return match ? match[1] : null; +} + +/** + * Extract review iteration from path + * Examples: .review/iterations/1.json → 1 + */ +function extractReviewIteration(filepath: string): string | null { + const match = filepath.match(/\.review\/iterations\/(\d+)\.json$/); + return match ? match[1] : null; +} + +/** + * Extract filename from relative path within review fixes + * Examples: .review/fixes/finding-001.json → finding-001.json + */ +function extractReviewFixFilename(filepath: string): string | null { + const match = filepath.match(/\.review\/fixes\/(.+)$/); + return match ? match[1] : null; +} + +/** + * Extract filename from process/chat/brainstorm paths + * Examples: .process/archiving.json → archiving.json + */ +function extractProcessFilename(filepath: string, prefix: string): string | null { + const pattern = new RegExp(`^${prefix.replace(/\//g, '\\/')}(.+)$`); + const match = filepath.match(pattern); + return match ? match[1] : null; +} + +/** + * Resolve relative path (contains '/') to content_type + */ +function resolveRelativePath(filepath: string): ResolverResult | null { + // Security: Reject path traversal attempts + if (filepath.includes('..') || filepath.startsWith('/')) { + throw new PathResolutionError( + 'INVALID_PATH', + 'Path traversal and absolute paths are not allowed', + ['Use relative paths within the session directory'] + ); + } + + // Check path prefix matches + for (const [prefix, contentType] of Object.entries(PATH_PREFIX_TO_CONTENT_TYPE)) { + if (filepath.startsWith(prefix)) { + const pathParams: Record = {}; + + // Extract specific parameters based on content type + if (contentType === 'task') { + const taskId = extractTaskId(basename(filepath)); + if (taskId) { + pathParams.task_id = taskId; + } + } else if (contentType === 'summary') { + const taskId = extractSummaryTaskId(basename(filepath)); + if (taskId) { + pathParams.task_id = taskId; + } + } else if (contentType === 'review-dim') { + const dimension = extractReviewDimension(filepath); + if (dimension) { + pathParams.dimension = dimension; + } + } else if (contentType === 'review-iter') { + const iteration = extractReviewIteration(filepath); + if (iteration) { + pathParams.iteration = iteration; + } + } else if (contentType === 'review-fix') { + const filename = extractReviewFixFilename(filepath); + if (filename) { + pathParams.filename = filename; + } + } else if (contentType === 'process' || contentType === 'chat' || contentType === 'brainstorm') { + const filename = extractProcessFilename(filepath, prefix); + if (filename) { + pathParams.filename = filename; + } + } + + return { + contentType, + pathParams: Object.keys(pathParams).length > 0 ? pathParams : undefined, + resolvedPath: filepath, + }; + } + } + + return null; +} + +/** + * Resolve simple filename (no '/') to content_type + */ +function resolveFilename( + filename: string, + context: ResolverContext +): ResolverResult | null { + // Check exact filename matches first + if (EXACT_FILENAME_TO_CONTENT_TYPE[filename]) { + let contentType = EXACT_FILENAME_TO_CONTENT_TYPE[filename]; + + // Override plan.json based on session location + if (filename === 'plan.json') { + if (context.sessionLocation === 'lite-plan') { + contentType = 'lite-plan'; + } else if (context.sessionLocation === 'lite-fix') { + contentType = 'lite-fix-plan'; + } + } + + return { + contentType, + resolvedPath: filename, + }; + } + + // Check task ID patterns (IMPL-001.json, TEST-042.json, etc.) + for (const pattern of TASK_ID_PATTERNS) { + if (pattern.test(filename)) { + const taskId = extractTaskId(filename); + if (taskId) { + return { + contentType: 'task', + pathParams: { task_id: taskId }, + resolvedPath: `.task/${filename}`, + }; + } + } + } + + // Check summary pattern (*-summary.md) + const summaryTaskId = extractSummaryTaskId(filename); + if (summaryTaskId) { + return { + contentType: 'summary', + pathParams: { task_id: summaryTaskId }, + resolvedPath: `.summaries/${filename}`, + }; + } + + // Check exploration pattern (exploration-{angle}.json) + const explorationMatch = filename.match(/^exploration-(.+)\.json$/); + if (explorationMatch) { + return { + contentType: 'exploration', + pathParams: { angle: explorationMatch[1] }, + resolvedPath: filename, + }; + } + + // Check diagnosis pattern (diagnosis-{angle}.json) + const diagnosisMatch = filename.match(/^diagnosis-(.+)\.json$/); + if (diagnosisMatch) { + return { + contentType: 'diagnosis', + pathParams: { angle: diagnosisMatch[1] }, + resolvedPath: filename, + }; + } + + return null; +} + +/** + * Main resolver function - resolves filename/path to content_type + * + * @param filename - Filename or relative path + * @param context - Session context (path and location) + * @returns Resolver result with content type, path params, and resolved path + * @throws PathResolutionError if path is invalid or cannot be resolved + * + * @example + * // Filename examples + * resolveFilePath('IMPL-001.json', context) + * // → { contentType: 'task', pathParams: {task_id: 'IMPL-001'}, resolvedPath: '.task/IMPL-001.json' } + * + * resolveFilePath('IMPL_PLAN.md', context) + * // → { contentType: 'plan', resolvedPath: 'IMPL_PLAN.md' } + * + * // Relative path examples + * resolveFilePath('.task/IMPL-001.json', context) + * // → { contentType: 'task', pathParams: {task_id: 'IMPL-001'}, resolvedPath: '.task/IMPL-001.json' } + * + * resolveFilePath('.review/dimensions/security.json', context) + * // → { contentType: 'review-dim', pathParams: {dimension: 'security'}, resolvedPath: '...' } + */ +export function resolveFilePath( + filename: string, + context: ResolverContext +): ResolverResult { + if (!filename) { + throw new PathResolutionError( + 'INVALID_PATH', + 'Filename is required', + ['Usage: ccw session read '] + ); + } + + // Normalize path separators (handle Windows paths) + const normalizedFilename = filename.replace(/\\/g, '/'); + + // Security: Validate filename + if (normalizedFilename.includes('\0') || normalizedFilename.trim() === '') { + throw new PathResolutionError( + 'INVALID_PATH', + 'Invalid filename or path', + ['Use valid filename or relative path'] + ); + } + + // RULE 1: Relative path (contains '/') + if (normalizedFilename.includes('/')) { + const result = resolveRelativePath(normalizedFilename); + if (result) { + return result; + } + + throw new PathResolutionError( + 'NOT_FOUND', + `Unknown path pattern: ${normalizedFilename}`, + [ + 'Supported paths:', + ' .task/*.json (tasks)', + ' .summaries/*.md (summaries)', + ' .process/* (process files)', + ' .chat/* (chat files)', + ' .brainstorming/* (brainstorm files)', + ' .review/dimensions/*.json (review dimensions)', + ' .review/iterations/*.json (review iterations)', + ' .review/fixes/* (review fixes)', + ] + ); + } + + // RULE 2: Simple filename + const result = resolveFilename(normalizedFilename, context); + if (result) { + return result; + } + + // Resolution failed + throw new PathResolutionError( + 'NOT_FOUND', + `Unknown file: ${normalizedFilename}`, + [ + 'Supported filenames:', + ' IMPL-*.json, TEST-*.json, DOC-*.json, REFACTOR-*.json, TASK-*.json (tasks)', + ' IMPL_PLAN.md (plan)', + ' TODO_LIST.md (todo)', + ' workflow-session.json (session metadata)', + ' context-package.json (context)', + ' session-metadata.json (session metadata for lite sessions)', + ' plan.json (lite-plan or lite-fix-plan, depending on session location)', + ' fix-plan.json (lite-fix-plan)', + ' *-summary.md (summaries)', + ' exploration-{angle}.json (explorations)', + ' diagnosis-{angle}.json (diagnoses)', + ' explorations-manifest.json (exploration manifest)', + ' diagnoses-manifest.json (diagnosis manifest)', + ' clarifications.json (clarifications)', + ' execution-context.json (execution context)', + 'Or use a relative path: .task/IMPL-001.json', + ] + ); +} diff --git a/ccw/src/core/data-aggregator.ts b/ccw/src/core/data-aggregator.ts index 1d15c703..b7a44dc7 100644 --- a/ccw/src/core/data-aggregator.ts +++ b/ccw/src/core/data-aggregator.ts @@ -53,10 +53,13 @@ interface SessionInput { session_id?: string; id?: string; project?: string; + description?: string; status?: string; type?: string; workflow_type?: string | null; - created_at?: string | null; + created_at?: string | null; // For backward compatibility + created?: string; // From SessionMetadata + updated?: string; // From SessionMetadata archived_at?: string | null; path: string; } @@ -249,11 +252,11 @@ export async function aggregateData(sessions: ScanSessionsResult, workflowDir: s async function processSession(session: SessionInput, isActive: boolean): Promise { const result: SessionData = { session_id: session.session_id || session.id || '', - project: session.project || session.session_id || session.id || '', + project: session.project || session.description || session.session_id || session.id || '', status: session.status || (isActive ? 'active' : 'archived'), type: session.type || 'workflow', // Session type (workflow, review, test, docs) workflow_type: session.workflow_type || null, // Original workflow_type for reference - created_at: session.created_at || null, // Raw ISO string - let frontend format + created_at: session.created || session.created_at || null, // Prefer 'created' from SessionMetadata, fallback to 'created_at' archived_at: session.archived_at || null, // Raw ISO string - let frontend format path: session.path, tasks: [], diff --git a/ccw/src/core/routes/codexlens-routes.ts b/ccw/src/core/routes/codexlens-routes.ts index 3c01b314..499e6f17 100644 --- a/ccw/src/core/routes/codexlens-routes.ts +++ b/ccw/src/core/routes/codexlens-routes.ts @@ -192,9 +192,9 @@ export async function handleCodexLensRoutes(ctx: RouteContext): Promise const args = ['clean']; if (all) { args.push('--all'); - } - if (path) { - args.push('--path', path); + } else if (path) { + // Path is passed as a positional argument, not as a flag + args.push(path); } args.push('--json'); diff --git a/ccw/src/core/routes/memory-routes.ts b/ccw/src/core/routes/memory-routes.ts index d42e9e2d..c6d81aca 100644 --- a/ccw/src/core/routes/memory-routes.ts +++ b/ccw/src/core/routes/memory-routes.ts @@ -771,7 +771,8 @@ Return ONLY valid JSON in this exact format (no markdown, no code blocks, just p } try { - const configPath = join(projectPath, '.claude', 'CLAUDE.md'); + const rulesDir = join(projectPath, '.claude', 'rules'); + const configPath = join(rulesDir, 'active_memory.md'); const configJsonPath = join(projectPath, '.claude', 'active_memory_config.json'); const enabled = existsSync(configPath); let lastSync: string | null = null; @@ -823,7 +824,8 @@ Return ONLY valid JSON in this exact format (no markdown, no code blocks, just p } const claudeDir = join(projectPath, '.claude'); - const configPath = join(claudeDir, 'CLAUDE.md'); + const rulesDir = join(claudeDir, 'rules'); + const configPath = join(rulesDir, 'active_memory.md'); const configJsonPath = join(claudeDir, 'active_memory_config.json'); if (enabled) { @@ -831,14 +833,17 @@ Return ONLY valid JSON in this exact format (no markdown, no code blocks, just p if (!existsSync(claudeDir)) { mkdirSync(claudeDir, { recursive: true }); } + if (!existsSync(rulesDir)) { + mkdirSync(rulesDir, { recursive: true }); + } // Save config if (config) { writeFileSync(configJsonPath, JSON.stringify(config, null, 2), 'utf-8'); } - // Create initial CLAUDE.md with header - const initialContent = `# CLAUDE.md - Project Memory + // Create initial active_memory.md with header + const initialContent = `# Active Memory - Project Context > Auto-generated understanding of frequently accessed files. > Last updated: ${new Date().toISOString()} @@ -901,7 +906,7 @@ Return ONLY valid JSON in this exact format (no markdown, no code blocks, just p return true; } - // API: Active Memory - Sync (analyze hot files using CLI and update CLAUDE.md) + // API: Active Memory - Sync (analyze hot files using CLI and update active_memory.md) if (pathname === '/api/memory/active/sync' && req.method === 'POST') { let body = ''; req.on('data', (chunk: Buffer) => { body += chunk.toString(); }); @@ -917,7 +922,8 @@ Return ONLY valid JSON in this exact format (no markdown, no code blocks, just p } const claudeDir = join(projectPath, '.claude'); - const configPath = join(claudeDir, 'CLAUDE.md'); + const rulesDir = join(claudeDir, 'rules'); + const configPath = join(rulesDir, 'active_memory.md'); // Get hot files from memory store - with fallback let hotFiles: any[] = []; @@ -937,8 +943,8 @@ Return ONLY valid JSON in this exact format (no markdown, no code blocks, just p return isAbsolute(filePath) ? filePath : join(projectPath, filePath); }).filter((p: string) => existsSync(p)); - // Build the CLAUDE.md content header - let content = `# CLAUDE.md - Project Memory + // Build the active_memory.md content header + let content = `# Active Memory - Project Context > Auto-generated understanding of frequently accessed files using ${tool.toUpperCase()}. > Last updated: ${new Date().toISOString()} @@ -1055,10 +1061,13 @@ RULES: Be concise. Focus on practical understanding. Include function signatures } } - // Ensure directory exists + // Ensure directories exist if (!existsSync(claudeDir)) { mkdirSync(claudeDir, { recursive: true }); } + if (!existsSync(rulesDir)) { + mkdirSync(rulesDir, { recursive: true }); + } // Write the file writeFileSync(configPath, content, 'utf-8'); diff --git a/ccw/src/mcp-server/index.ts b/ccw/src/mcp-server/index.ts index 6d320dc3..32a8d855 100644 --- a/ccw/src/mcp-server/index.ts +++ b/ccw/src/mcp-server/index.ts @@ -17,7 +17,7 @@ const SERVER_NAME = 'ccw-tools'; const SERVER_VERSION = '6.1.4'; // Default enabled tools (core set) -const DEFAULT_TOOLS: string[] = ['write_file', 'edit_file', 'read_file', 'codex_lens', 'smart_search']; +const DEFAULT_TOOLS: string[] = ['write_file', 'edit_file', 'read_file', 'smart_search']; /** * Get list of enabled tools from environment or defaults diff --git a/ccw/src/templates/dashboard-js/components/mcp-manager.js b/ccw/src/templates/dashboard-js/components/mcp-manager.js index bb8e1b84..af3666fa 100644 --- a/ccw/src/templates/dashboard-js/components/mcp-manager.js +++ b/ccw/src/templates/dashboard-js/components/mcp-manager.js @@ -20,6 +20,11 @@ let currentCliMode = 'claude'; // 'claude' or 'codex' let codexMcpConfig = null; let codexMcpServers = {}; +// ========== Project Config Type Preference ========== +// 'mcp' = .mcp.json (project root file, recommended) +// 'claude' = claude.json projects[path].mcpServers (shared config) +let preferredProjectConfigType = 'mcp'; + // ========== Initialization ========== function initMcpManager() { // Initialize MCP navigation @@ -229,11 +234,9 @@ async function toggleMcpServer(serverName, enable) { async function copyMcpServerToProject(serverName, serverConfig, configType = null) { try { - // If configType not specified, ask user to choose + // If configType not specified, use the preferred config type (toggle setting) if (!configType) { - const choice = await showConfigTypeDialog(); - if (!choice) return null; // User cancelled - configType = choice; + configType = preferredProjectConfigType; } const response = await fetch('/api/mcp-copy-server', { @@ -982,14 +985,16 @@ async function installCcwToolsMcp(scope = 'workspace') { showRefreshToast(result.error || 'Failed to install CCW Tools MCP globally', 'error'); } } else { - // Install to workspace (.mcp.json) + // Install to workspace (use preferredProjectConfigType) + const configType = preferredProjectConfigType; const response = await fetch('/api/mcp-copy-server', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ projectPath: projectPath, serverName: 'ccw-tools', - serverConfig: ccwToolsConfig + serverConfig: ccwToolsConfig, + configType: configType }) }); @@ -999,7 +1004,8 @@ async function installCcwToolsMcp(scope = 'workspace') { if (result.success) { await loadMcpConfig(); renderMcpManager(); - showRefreshToast(`CCW Tools installed to workspace (${selectedTools.length} tools)`, 'success'); + const location = configType === 'mcp' ? '.mcp.json' : 'claude.json'; + showRefreshToast(`CCW Tools installed to ${location} (${selectedTools.length} tools)`, 'success'); } else { showRefreshToast(result.error || 'Failed to install CCW Tools MCP to workspace', 'error'); } @@ -1046,14 +1052,16 @@ async function updateCcwToolsMcp(scope = 'workspace') { showRefreshToast(result.error || 'Failed to update CCW Tools MCP globally', 'error'); } } else { - // Update workspace (.mcp.json) + // Update workspace (use preferredProjectConfigType) + const configType = preferredProjectConfigType; const response = await fetch('/api/mcp-copy-server', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ projectPath: projectPath, serverName: 'ccw-tools', - serverConfig: ccwToolsConfig + serverConfig: ccwToolsConfig, + configType: configType }) }); @@ -1063,7 +1071,8 @@ async function updateCcwToolsMcp(scope = 'workspace') { if (result.success) { await loadMcpConfig(); renderMcpManager(); - showRefreshToast(`CCW Tools updated in workspace (${selectedTools.length} tools)`, 'success'); + const location = configType === 'mcp' ? '.mcp.json' : 'claude.json'; + showRefreshToast(`CCW Tools updated in ${location} (${selectedTools.length} tools)`, 'success'); } else { showRefreshToast(result.error || 'Failed to update CCW Tools MCP in workspace', 'error'); } @@ -1130,6 +1139,25 @@ async function installCcwToolsMcpToCodex() { } } +// ========== Project Config Type Toggle ========== +function toggleProjectConfigType() { + preferredProjectConfigType = preferredProjectConfigType === 'mcp' ? 'claude' : 'mcp'; + console.log('[MCP] Preferred project config type changed to:', preferredProjectConfigType); + // Re-render to update toggle display + renderMcpManager(); +} + +function getPreferredProjectConfigType() { + return preferredProjectConfigType; +} + +function setPreferredProjectConfigType(type) { + if (type === 'mcp' || type === 'claude') { + preferredProjectConfigType = type; + console.log('[MCP] Preferred project config type set to:', preferredProjectConfigType); + } +} + // ========== Global Exports for onclick handlers ========== // Expose functions to global scope to support inline onclick handlers window.setCliMode = setCliMode; @@ -1137,3 +1165,6 @@ window.getCliMode = getCliMode; window.selectCcwTools = selectCcwTools; window.selectCcwToolsCodex = selectCcwToolsCodex; window.openMcpCreateModal = openMcpCreateModal; +window.toggleProjectConfigType = toggleProjectConfigType; +window.getPreferredProjectConfigType = getPreferredProjectConfigType; +window.setPreferredProjectConfigType = setPreferredProjectConfigType; diff --git a/ccw/src/templates/dashboard-js/help-i18n.js b/ccw/src/templates/dashboard-js/help-i18n.js index a5d5cbbe..a087bdc8 100644 --- a/ccw/src/templates/dashboard-js/help-i18n.js +++ b/ccw/src/templates/dashboard-js/help-i18n.js @@ -3,6 +3,8 @@ // Internationalization for help page (Chinese translations) // ========================================== +console.log('[Help i18n] File loading started'); + var helpI18n = { zh: { // Page Headers @@ -30,10 +32,13 @@ var helpI18n = { // Workflow Diagrams 'help.diagrams.title': '常见工作流场景', - 'help.diagrams.tdd': 'TDD 开发', - 'help.diagrams.feature': '功能开发', - 'help.diagrams.bugfix': 'Bug 调查', - 'help.diagrams.review': '代码审查', + 'help.diagrams.decision': '决策流程:选择规划方式', + 'help.diagrams.brainstorm': '头脑风暴', + 'help.diagrams.cliResume': 'CLI Resume机制', + 'help.diagrams.bugFix': 'Bug修复流程', + 'help.diagrams.lite': 'Lite轻量工作流', + 'help.diagrams.planFull': 'Plan完整规划', + 'help.diagrams.tdd': 'TDD测试驱动', 'help.diagrams.fit': '适应视图', 'help.diagrams.zoomIn': '放大', 'help.diagrams.zoomOut': '缩小', @@ -43,6 +48,59 @@ var helpI18n = { 'help.diagrams.legend.alternatives': '替代方案', 'help.diagrams.notLoaded': 'Cytoscape.js 未加载', + // Workflow Steps - Decision + 'help.workflows.decision.start': '任务开始', + 'help.workflows.decision.cliAnalyze': 'CLI分析理解项目', + 'help.workflows.decision.understand': '充分理解蓝图', + 'help.workflows.decision.simple': '简单任务', + 'help.workflows.decision.medium': '中等任务', + 'help.workflows.decision.complex': '复杂任务', + 'help.workflows.decision.claudeExec': 'Claude执行(优先)', + 'help.workflows.decision.cliExec': 'CLI执行', + 'help.workflows.decision.claudePlan': 'Claude自带Plan', + + // Workflow Steps - Brainstorm + 'help.workflows.brainstorm.start': '不确定方向', + 'help.workflows.brainstorm.question': '知道做什么吗?', + 'help.workflows.brainstorm.product': '不知道:探索产品', + 'help.workflows.brainstorm.design': '知道但不知怎么做', + 'help.workflows.brainstorm.next': '进入规划阶段', + + // Workflow Steps - CLI Resume + 'help.workflows.cliResume.firstExec': 'ccw cli exec "分析..."', + 'help.workflows.cliResume.saveContext': '保存会话上下文', + 'help.workflows.cliResume.resumeCmd': 'ccw cli exec --resume', + 'help.workflows.cliResume.merge': '合并历史对话', + 'help.workflows.cliResume.continue': '继续执行任务', + 'help.workflows.cliResume.splitOutput': '拆分结果存储', + 'help.workflows.cliResume.complete': '完成', + + // Workflow Steps - Bug Fix + 'help.workflows.bugFix.start': '发现Bug', + 'help.workflows.bugFix.cliAnalyze': 'CLI分析定位Bug', + 'help.workflows.bugFix.diagnosis': '诊断根因', + 'help.workflows.bugFix.impact': '影响评估', + 'help.workflows.bugFix.strategy': '修复策略', + 'help.workflows.bugFix.execute': '执行修复', + 'help.workflows.bugFix.complete': '完成', + + // Workflow Steps - Plan Full + 'help.workflows.planFull.start': '复杂项目开始', + 'help.workflows.planFull.cliAnalyze': 'CLI深度分析项目', + 'help.workflows.planFull.complete': '会话完成', + + // Workflow Steps - Lite + 'help.workflows.lite.start': '开始', + 'help.workflows.lite.confirm': '三维确认', + 'help.workflows.lite.complete': '完成', + + // Workflow Steps - TDD + 'help.workflows.tdd.start': '开始', + 'help.workflows.tdd.red': 'Red: 编写失败测试', + 'help.workflows.tdd.green': 'Green: 实现代码', + 'help.workflows.tdd.refactor': 'Refactor: 重构优化', + 'help.workflows.tdd.complete': '完成', + // CodexLens 'help.codexlens.title': 'CodexLens 快速入门', 'help.codexlens.subtitle': '强大的代码索引和语义搜索工具', @@ -95,10 +153,13 @@ var helpI18n = { // Workflow Diagrams 'help.diagrams.title': 'Common Workflow Scenarios', + 'help.diagrams.decision': 'Decision: Choose Planning Approach', + 'help.diagrams.brainstorm': 'Brainstorming', + 'help.diagrams.cliResume': 'CLI Resume Mechanism', + 'help.diagrams.bugFix': 'Bug Fix Workflow', + 'help.diagrams.lite': 'Lite Workflow', + 'help.diagrams.planFull': 'Full Planning', 'help.diagrams.tdd': 'TDD Development', - 'help.diagrams.feature': 'Feature Development', - 'help.diagrams.bugfix': 'Bug Investigation', - 'help.diagrams.review': 'Code Review', 'help.diagrams.fit': 'Fit to View', 'help.diagrams.zoomIn': 'Zoom In', 'help.diagrams.zoomOut': 'Zoom Out', @@ -108,6 +169,59 @@ var helpI18n = { 'help.diagrams.legend.alternatives': 'Alternatives', 'help.diagrams.notLoaded': 'Cytoscape.js not loaded', + // Workflow Steps - Decision + 'help.workflows.decision.start': 'Task Start', + 'help.workflows.decision.cliAnalyze': 'CLI Analyze Project', + 'help.workflows.decision.understand': 'Understand Blueprint', + 'help.workflows.decision.simple': 'Simple Task', + 'help.workflows.decision.medium': 'Medium Task', + 'help.workflows.decision.complex': 'Complex Task', + 'help.workflows.decision.claudeExec': 'Claude Execute (Preferred)', + 'help.workflows.decision.cliExec': 'CLI Execute', + 'help.workflows.decision.claudePlan': 'Claude Built-in Plan', + + // Workflow Steps - Brainstorm + 'help.workflows.brainstorm.start': 'Uncertain Direction', + 'help.workflows.brainstorm.question': 'Know What to Build?', + 'help.workflows.brainstorm.product': 'No: Explore Product', + 'help.workflows.brainstorm.design': 'Yes but Not How', + 'help.workflows.brainstorm.next': 'Enter Planning Phase', + + // Workflow Steps - CLI Resume + 'help.workflows.cliResume.firstExec': 'ccw cli exec "analyze..."', + 'help.workflows.cliResume.saveContext': 'Save Session Context', + 'help.workflows.cliResume.resumeCmd': 'ccw cli exec --resume', + 'help.workflows.cliResume.merge': 'Merge Conversation History', + 'help.workflows.cliResume.continue': 'Continue Execution', + 'help.workflows.cliResume.splitOutput': 'Split & Store Results', + 'help.workflows.cliResume.complete': 'Complete', + + // Workflow Steps - Bug Fix + 'help.workflows.bugFix.start': 'Bug Discovered', + 'help.workflows.bugFix.cliAnalyze': 'CLI Analyze & Locate Bug', + 'help.workflows.bugFix.diagnosis': 'Root Cause Analysis', + 'help.workflows.bugFix.impact': 'Impact Assessment', + 'help.workflows.bugFix.strategy': 'Fix Strategy', + 'help.workflows.bugFix.execute': 'Execute Fix', + 'help.workflows.bugFix.complete': 'Complete', + + // Workflow Steps - Plan Full + 'help.workflows.planFull.start': 'Complex Project Start', + 'help.workflows.planFull.cliAnalyze': 'CLI Deep Analysis', + 'help.workflows.planFull.complete': 'Session Complete', + + // Workflow Steps - Lite + 'help.workflows.lite.start': 'Start', + 'help.workflows.lite.confirm': '3D Confirmation', + 'help.workflows.lite.complete': 'Complete', + + // Workflow Steps - TDD + 'help.workflows.tdd.start': 'Start', + 'help.workflows.tdd.red': 'Red: Write Failing Test', + 'help.workflows.tdd.green': 'Green: Implement Code', + 'help.workflows.tdd.refactor': 'Refactor: Optimize', + 'help.workflows.tdd.complete': 'Complete', + // CodexLens 'help.codexlens.title': 'CodexLens Quick Start', 'help.codexlens.subtitle': 'Powerful code indexing and semantic search tool', diff --git a/ccw/src/templates/dashboard-js/i18n.js b/ccw/src/templates/dashboard-js/i18n.js index ee929c96..5662f000 100644 --- a/ccw/src/templates/dashboard-js/i18n.js +++ b/ccw/src/templates/dashboard-js/i18n.js @@ -226,6 +226,7 @@ const i18n = { 'codexlens.migrationWarning': 'After changing the path, existing indexes will need to be re-initialized for each workspace.', 'codexlens.actions': 'Actions', 'codexlens.initializeIndex': 'Initialize Index', + 'codexlens.cleanCurrentWorkspace': 'Clean Current Workspace', 'codexlens.cleanAllIndexes': 'Clean All Indexes', 'codexlens.installCodexLens': 'Install CodexLens', 'codexlens.testSearch': 'Test Search', @@ -249,8 +250,10 @@ const i18n = { 'codexlens.configSaved': 'Configuration saved successfully', 'codexlens.pathEmpty': 'Index directory path cannot be empty', 'codexlens.cleanConfirm': 'Are you sure you want to clean all CodexLens indexes? This cannot be undone.', + 'codexlens.cleanCurrentWorkspaceConfirm': 'Are you sure you want to clean the current workspace index? This cannot be undone.', 'codexlens.cleaning': 'Cleaning indexes...', 'codexlens.cleanSuccess': 'All indexes cleaned successfully', + 'codexlens.cleanCurrentWorkspaceSuccess': 'Current workspace index cleaned successfully', 'codexlens.cleanFailed': 'Failed to clean indexes', 'codexlens.loadingConfig': 'Loading configuration...', @@ -484,6 +487,10 @@ const i18n = { 'mcp.description': 'Description', 'mcp.category': 'Category', 'mcp.installTo': 'Install To', + 'mcp.configTarget': 'Config Target', + 'mcp.clickToSwitch': 'Click to switch', + 'mcp.usingMcpJson': 'Using .mcp.json', + 'mcp.usingClaudeJson': 'Using claude.json', 'mcp.cwd': 'Working Directory', 'mcp.httpHeaders': 'HTTP Headers', 'mcp.error': 'Error', @@ -1335,6 +1342,7 @@ const i18n = { 'codexlens.migrationWarning': '更改路径后,需要为每个工作区重新初始化索引。', 'codexlens.actions': '操作', 'codexlens.initializeIndex': '初始化索引', + 'codexlens.cleanCurrentWorkspace': '清理当前工作空间', 'codexlens.cleanAllIndexes': '清理所有索引', 'codexlens.installCodexLens': '安装 CodexLens', 'codexlens.testSearch': '测试搜索', @@ -1358,8 +1366,10 @@ const i18n = { 'codexlens.configSaved': '配置保存成功', 'codexlens.pathEmpty': '索引目录路径不能为空', 'codexlens.cleanConfirm': '确定要清理所有 CodexLens 索引吗?此操作无法撤销。', + 'codexlens.cleanCurrentWorkspaceConfirm': '确定要清理当前工作空间的索引吗?此操作无法撤销。', 'codexlens.cleaning': '清理索引中...', 'codexlens.cleanSuccess': '所有索引已成功清理', + 'codexlens.cleanCurrentWorkspaceSuccess': '当前工作空间索引已成功清理', 'codexlens.cleanFailed': '清理索引失败', 'codexlens.loadingConfig': '加载配置中...', @@ -1590,6 +1600,10 @@ const i18n = { 'mcp.description': '描述', 'mcp.category': '分类', 'mcp.installTo': '安装到', + 'mcp.configTarget': '配置目标', + 'mcp.clickToSwitch': '点击切换', + 'mcp.usingMcpJson': '使用 .mcp.json', + 'mcp.usingClaudeJson': '使用 claude.json', 'mcp.cwd': '工作目录', 'mcp.httpHeaders': 'HTTP 头', 'mcp.error': '错误', diff --git a/ccw/src/templates/dashboard-js/views/cli-manager.js b/ccw/src/templates/dashboard-js/views/cli-manager.js index 4180a21c..64c939ff 100644 --- a/ccw/src/templates/dashboard-js/views/cli-manager.js +++ b/ccw/src/templates/dashboard-js/views/cli-manager.js @@ -1613,6 +1613,9 @@ function buildCodexLensConfigContent(config) { ? '' + + '' + '' + @@ -2014,7 +2017,48 @@ async function deleteModel(profile) { if (window.lucide) lucide.createIcons(); } } +/** + * Clean current workspace index + */ +async function cleanCurrentWorkspaceIndex() { + if (!confirm(t('codexlens.cleanCurrentWorkspaceConfirm'))) { + return; + } + try { + showRefreshToast(t('codexlens.cleaning'), 'info'); + + // Get current workspace path (projectPath is a global variable from state.js) + var workspacePath = projectPath; + + var response = await fetch('/api/codexlens/clean', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ path: workspacePath }) + }); + + var result = await response.json(); + + if (result.success) { + showRefreshToast(t('codexlens.cleanCurrentWorkspaceSuccess'), 'success'); + + // Refresh status + if (typeof loadCodexLensStatus === 'function') { + await loadCodexLensStatus(); + renderToolsSection(); + if (window.lucide) lucide.createIcons(); + } + } else { + showRefreshToast(t('codexlens.cleanFailed') + ': ' + result.error, 'error'); + } + } catch (err) { + showRefreshToast(t('common.error') + ': ' + err.message, 'error'); + } +} + +/** + * Clean all CodexLens indexes + */ async function cleanCodexLensIndexes() { if (!confirm(t('codexlens.cleanConfirm'))) { return; diff --git a/ccw/src/templates/dashboard-js/views/codexlens-manager.js b/ccw/src/templates/dashboard-js/views/codexlens-manager.js index 9ecbb456..95b59363 100644 --- a/ccw/src/templates/dashboard-js/views/codexlens-manager.js +++ b/ccw/src/templates/dashboard-js/views/codexlens-manager.js @@ -109,6 +109,9 @@ function buildCodexLensConfigContent(config) { ? '' + + '' + '' + @@ -559,6 +562,45 @@ function uninstallCodexLens() { openCliUninstallWizard('codexlens'); } +/** + * Clean current workspace index + */ +async function cleanCurrentWorkspaceIndex() { + if (!confirm(t('codexlens.cleanCurrentWorkspaceConfirm'))) { + return; + } + + try { + showRefreshToast(t('codexlens.cleaning'), 'info'); + + // Get current workspace path (projectPath is a global variable from state.js) + var workspacePath = projectPath; + + var response = await fetch('/api/codexlens/clean', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ path: workspacePath }) + }); + + var result = await response.json(); + + if (result.success) { + showRefreshToast(t('codexlens.cleanCurrentWorkspaceSuccess'), 'success'); + + // Refresh status + if (typeof loadCodexLensStatus === 'function') { + await loadCodexLensStatus(); + renderToolsSection(); + if (window.lucide) lucide.createIcons(); + } + } else { + showRefreshToast(t('codexlens.cleanFailed') + ': ' + result.error, 'error'); + } + } catch (err) { + showRefreshToast(t('common.error') + ': ' + err.message, 'error'); + } +} + /** * Clean all CodexLens indexes */ diff --git a/ccw/src/templates/dashboard-js/views/help.js b/ccw/src/templates/dashboard-js/views/help.js index b9bd401d..cf7fd879 100644 --- a/ccw/src/templates/dashboard-js/views/help.js +++ b/ccw/src/templates/dashboard-js/views/help.js @@ -14,7 +14,7 @@ var activeHelpTab = 'cli'; var helpSearchQuery = ''; var helpSearchTimeout = null; var cytoscapeInstance = null; -var activeWorkflowDiagram = 'tdd'; +var activeWorkflowDiagram = 'decision'; // ========== Main Render Function ========== async function renderHelpView() { @@ -377,18 +377,27 @@ function renderWorkflowDiagrams() {

${ht('help.diagrams.title')}

+ + + + + + - - -
@@ -507,6 +516,17 @@ function initializeCytoscapeDiagram(workflow) { return; } + // Get computed CSS variable values + var rootStyles = getComputedStyle(document.documentElement); + var primaryColor = rootStyles.getPropertyValue('--primary').trim(); + var foregroundColor = rootStyles.getPropertyValue('--foreground').trim(); + var mutedColor = rootStyles.getPropertyValue('--muted-foreground').trim(); + + // Convert HSL values to usable format + var primaryHsl = primaryColor ? 'hsl(' + primaryColor + ')' : '#3B82F6'; + var foregroundHsl = foregroundColor ? 'hsl(' + foregroundColor + ')' : '#1F2937'; + var mutedHsl = mutedColor ? 'hsl(' + mutedColor + ')' : '#6B7280'; + // Initialize Cytoscape cytoscapeInstance = cytoscape({ container: container, @@ -515,43 +535,68 @@ function initializeCytoscapeDiagram(workflow) { { selector: 'node', style: { - 'background-color': 'hsl(var(--primary))', + 'shape': 'roundrectangle', + 'background-color': primaryHsl, + 'background-opacity': 0.9, + 'border-width': 2, + 'border-color': primaryHsl, + 'border-opacity': 1, 'label': 'data(label)', - 'color': 'hsl(var(--foreground))', + 'color': '#FFFFFF', 'text-valign': 'center', 'text-halign': 'center', - 'font-size': '12px', - 'width': '80px', - 'height': '80px', + 'font-size': '14px', + 'font-weight': '600', + 'width': '140px', + 'height': '60px', 'text-wrap': 'wrap', - 'text-max-width': '70px' + 'text-max-width': '130px', + 'padding': '8px', + 'shadow-blur': 10, + 'shadow-color': '#000000', + 'shadow-opacity': 0.2, + 'shadow-offset-x': 0, + 'shadow-offset-y': 2 } }, { selector: 'edge', style: { - 'width': 2, - 'line-color': 'hsl(var(--muted-foreground))', - 'target-arrow-color': 'hsl(var(--muted-foreground))', + 'width': 3, + 'line-color': mutedHsl, + 'target-arrow-color': mutedHsl, 'target-arrow-shape': 'triangle', + 'target-arrow-fill': 'filled', + 'arrow-scale': 1.5, 'curve-style': 'bezier', 'label': 'data(label)', - 'font-size': '10px', - 'color': 'hsl(var(--muted-foreground))' + 'font-size': '12px', + 'font-weight': '500', + 'color': foregroundHsl, + 'text-background-color': '#FFFFFF', + 'text-background-opacity': 0.9, + 'text-background-padding': '4px', + 'text-background-shape': 'roundrectangle', + 'text-border-width': 1, + 'text-border-color': mutedHsl, + 'text-border-opacity': 0.3 } }, { selector: 'edge.prerequisite', style: { - 'line-color': 'hsl(var(--primary))', - 'target-arrow-color': 'hsl(var(--primary))' + 'line-color': primaryHsl, + 'target-arrow-color': primaryHsl, + 'width': 3 } }, { selector: 'edge.next-step', style: { 'line-color': '#10B981', - 'target-arrow-color': '#10B981' + 'target-arrow-color': '#10B981', + 'width': 3, + 'line-style': 'solid' } }, { @@ -559,15 +604,20 @@ function initializeCytoscapeDiagram(workflow) { style: { 'line-color': '#F59E0B', 'target-arrow-color': '#F59E0B', - 'line-style': 'dashed' + 'line-style': 'dashed', + 'line-dash-pattern': [10, 5], + 'width': 2.5 } } ], layout: { - name: 'dagre', - rankDir: 'TB', - nodeSep: 50, - rankSep: 80 + name: 'breadthfirst', + directed: true, + padding: 80, + spacingFactor: 2, + avoidOverlap: true, + nodeDimensionsIncludeLabels: true, + animate: false } }); @@ -583,83 +633,156 @@ function initializeCytoscapeDiagram(workflow) { } function getWorkflowGraphData(workflow) { - var nodes = []; - var edges = []; - var workflows = { - 'tdd': ['workflow:tdd-plan', 'workflow:execute', 'workflow:tdd-verify'], - 'feature': ['workflow:plan', 'workflow:action-plan-verify', 'workflow:execute', 'workflow:review'], - 'bugfix': ['workflow:lite-fix', 'workflow:lite-execute', 'workflow:test-cycle-execute'], - 'review': ['workflow:review-session-cycle', 'workflow:review-fix', 'workflow:test-cycle-execute'] + 'decision': { + nodes: [ + { data: { id: 'start', label: ht('help.workflows.decision.start') } }, + { data: { id: 'cli-analyze', label: ht('help.workflows.decision.cliAnalyze') } }, + { data: { id: 'understand', label: ht('help.workflows.decision.understand') } }, + { data: { id: 'simple', label: ht('help.workflows.decision.simple') } }, + { data: { id: 'medium', label: ht('help.workflows.decision.medium') } }, + { data: { id: 'complex', label: ht('help.workflows.decision.complex') } }, + { data: { id: 'claude-exec', label: ht('help.workflows.decision.claudeExec') } }, + { data: { id: 'cli-exec', label: ht('help.workflows.decision.cliExec') } }, + { data: { id: 'claude-plan', label: ht('help.workflows.decision.claudePlan') } }, + { data: { id: 'lite-plan', label: '/workflow:lite-plan' } }, + { data: { id: 'full-plan', label: '/workflow:plan' } } + ], + edges: [ + { data: { source: 'start', target: 'cli-analyze' }, classes: 'next-step' }, + { data: { source: 'cli-analyze', target: 'understand' }, classes: 'next-step' }, + { data: { source: 'understand', target: 'simple' }, classes: 'alternative' }, + { data: { source: 'understand', target: 'medium' }, classes: 'alternative' }, + { data: { source: 'understand', target: 'complex' }, classes: 'alternative' }, + { data: { source: 'simple', target: 'claude-exec', label: '优先' }, classes: 'next-step' }, + { data: { source: 'simple', target: 'cli-exec' }, classes: 'alternative' }, + { data: { source: 'medium', target: 'claude-plan' }, classes: 'next-step' }, + { data: { source: 'medium', target: 'lite-plan' }, classes: 'alternative' }, + { data: { source: 'complex', target: 'full-plan' }, classes: 'next-step' } + ] + }, + 'brainstorm': { + nodes: [ + { data: { id: 'start', label: ht('help.workflows.brainstorm.start') } }, + { data: { id: 'question', label: ht('help.workflows.brainstorm.question') } }, + { data: { id: 'product', label: ht('help.workflows.brainstorm.product') } }, + { data: { id: 'design', label: ht('help.workflows.brainstorm.design') } }, + { data: { id: 'brainstorm-product', label: '/workflow:brainstorm:auto-parallel' } }, + { data: { id: 'brainstorm-design', label: '/workflow:brainstorm:auto-parallel' } }, + { data: { id: 'next', label: ht('help.workflows.brainstorm.next') } } + ], + edges: [ + { data: { source: 'start', target: 'question' }, classes: 'next-step' }, + { data: { source: 'question', target: 'product' }, classes: 'alternative' }, + { data: { source: 'question', target: 'design' }, classes: 'alternative' }, + { data: { source: 'product', target: 'brainstorm-product' }, classes: 'next-step' }, + { data: { source: 'design', target: 'brainstorm-design' }, classes: 'next-step' }, + { data: { source: 'brainstorm-product', target: 'next' }, classes: 'next-step' }, + { data: { source: 'brainstorm-design', target: 'next' }, classes: 'next-step' } + ] + }, + 'cli-resume': { + nodes: [ + { data: { id: 'first-exec', label: ht('help.workflows.cliResume.firstExec') } }, + { data: { id: 'save-context', label: ht('help.workflows.cliResume.saveContext') } }, + { data: { id: 'resume-cmd', label: ht('help.workflows.cliResume.resumeCmd') } }, + { data: { id: 'merge', label: ht('help.workflows.cliResume.merge') } }, + { data: { id: 'continue', label: ht('help.workflows.cliResume.continue') } }, + { data: { id: 'split-output', label: ht('help.workflows.cliResume.splitOutput') } }, + { data: { id: 'complete', label: ht('help.workflows.cliResume.complete') } } + ], + edges: [ + { data: { source: 'first-exec', target: 'save-context' }, classes: 'next-step' }, + { data: { source: 'save-context', target: 'resume-cmd' }, classes: 'next-step' }, + { data: { source: 'resume-cmd', target: 'merge' }, classes: 'next-step' }, + { data: { source: 'merge', target: 'continue' }, classes: 'next-step' }, + { data: { source: 'continue', target: 'split-output' }, classes: 'next-step' }, + { data: { source: 'split-output', target: 'complete' }, classes: 'next-step' } + ] + }, + 'bug-fix': { + nodes: [ + { data: { id: 'start', label: ht('help.workflows.bugFix.start') } }, + { data: { id: 'cli-analyze', label: ht('help.workflows.bugFix.cliAnalyze') } }, + { data: { id: 'lite-fix', label: '/workflow:lite-fix' } }, + { data: { id: 'diagnosis', label: ht('help.workflows.bugFix.diagnosis') } }, + { data: { id: 'impact', label: ht('help.workflows.bugFix.impact') } }, + { data: { id: 'strategy', label: ht('help.workflows.bugFix.strategy') } }, + { data: { id: 'execute', label: ht('help.workflows.bugFix.execute') } }, + { data: { id: 'complete', label: ht('help.workflows.bugFix.complete') } } + ], + edges: [ + { data: { source: 'start', target: 'cli-analyze' }, classes: 'next-step' }, + { data: { source: 'cli-analyze', target: 'lite-fix' }, classes: 'next-step' }, + { data: { source: 'lite-fix', target: 'diagnosis' }, classes: 'next-step' }, + { data: { source: 'diagnosis', target: 'impact' }, classes: 'next-step' }, + { data: { source: 'impact', target: 'strategy' }, classes: 'next-step' }, + { data: { source: 'strategy', target: 'execute' }, classes: 'next-step' }, + { data: { source: 'execute', target: 'complete' }, classes: 'next-step' } + ] + }, + 'plan-full': { + nodes: [ + { data: { id: 'start', label: ht('help.workflows.planFull.start') } }, + { data: { id: 'cli-analyze', label: ht('help.workflows.planFull.cliAnalyze') } }, + { data: { id: 'plan', label: '/workflow:plan' } }, + { data: { id: 'verify', label: '/workflow:action-plan-verify' } }, + { data: { id: 'execute', label: '/workflow:execute' } }, + { data: { id: 'test', label: '/workflow:test-gen' } }, + { data: { id: 'review', label: '/workflow:review' } }, + { data: { id: 'complete', label: '/workflow:session:complete' } } + ], + edges: [ + { data: { source: 'start', target: 'cli-analyze' }, classes: 'next-step' }, + { data: { source: 'cli-analyze', target: 'plan' }, classes: 'next-step' }, + { data: { source: 'plan', target: 'verify' }, classes: 'next-step' }, + { data: { source: 'verify', target: 'execute' }, classes: 'next-step' }, + { data: { source: 'execute', target: 'test' }, classes: 'next-step' }, + { data: { source: 'test', target: 'review' }, classes: 'next-step' }, + { data: { source: 'review', target: 'complete' }, classes: 'next-step' } + ] + }, + 'lite': { + nodes: [ + { data: { id: 'start', label: ht('help.workflows.lite.start') } }, + { data: { id: 'lite-plan', label: '/workflow:lite-plan' } }, + { data: { id: 'confirm', label: ht('help.workflows.lite.confirm') } }, + { data: { id: 'lite-execute', label: '/workflow:lite-execute' } }, + { data: { id: 'complete', label: ht('help.workflows.lite.complete') } } + ], + edges: [ + { data: { source: 'start', target: 'lite-plan' }, classes: 'next-step' }, + { data: { source: 'lite-plan', target: 'confirm' }, classes: 'next-step' }, + { data: { source: 'confirm', target: 'lite-execute' }, classes: 'next-step' }, + { data: { source: 'lite-execute', target: 'complete' }, classes: 'next-step' } + ] + }, + 'tdd': { + nodes: [ + { data: { id: 'start', label: ht('help.workflows.tdd.start') } }, + { data: { id: 'tdd-plan', label: '/workflow:tdd-plan' } }, + { data: { id: 'red', label: ht('help.workflows.tdd.red') } }, + { data: { id: 'green', label: ht('help.workflows.tdd.green') } }, + { data: { id: 'refactor', label: ht('help.workflows.tdd.refactor') } }, + { data: { id: 'verify', label: '/workflow:tdd-verify' } }, + { data: { id: 'complete', label: ht('help.workflows.tdd.complete') } } + ], + edges: [ + { data: { source: 'start', target: 'tdd-plan' }, classes: 'next-step' }, + { data: { source: 'tdd-plan', target: 'red' }, classes: 'next-step' }, + { data: { source: 'red', target: 'green' }, classes: 'next-step' }, + { data: { source: 'green', target: 'refactor' }, classes: 'next-step' }, + { data: { source: 'refactor', target: 'verify' }, classes: 'next-step' }, + { data: { source: 'verify', target: 'complete' }, classes: 'next-step' } + ] + } }; - var workflowCommands = workflows[workflow] || workflows['tdd']; - + var workflowData = workflows[workflow] || workflows['decision']; console.log('Building workflow diagram for:', workflow); - console.log('Commands:', workflowCommands); - console.log('Available workflows data:', helpData.workflows ? Object.keys(helpData.workflows).length + ' commands' : 'no data'); + console.log('Generated graph:', workflowData.nodes.length, 'nodes,', workflowData.edges.length, 'edges'); - // Build graph from workflow relationships - workflowCommands.forEach(function(cmd) { - nodes.push({ data: { id: cmd, label: cmd.replace('workflow:', '').replace('task:', '') } }); - - var relationships = helpData.workflows ? helpData.workflows[cmd] : null; - if (relationships) { - // Add prerequisites - if (relationships.prerequisites) { - relationships.prerequisites.forEach(function(prereq) { - if (!nodes.find(n => n.data.id === prereq)) { - nodes.push({ data: { id: prereq, label: prereq.replace('workflow:', '').replace('task:', '') } }); - } - edges.push({ - data: { source: prereq, target: cmd, label: 'requires' }, - classes: 'prerequisite' - }); - }); - } - - // Add next steps - if (relationships.next_steps) { - relationships.next_steps.forEach(function(next) { - if (!nodes.find(n => n.data.id === next)) { - nodes.push({ data: { id: next, label: next.replace('workflow:', '').replace('task:', '') } }); - } - edges.push({ - data: { source: cmd, target: next, label: 'then' }, - classes: 'next-step' - }); - }); - } - - // Add alternatives - if (relationships.alternatives) { - relationships.alternatives.forEach(function(alt) { - if (!nodes.find(n => n.data.id === alt)) { - nodes.push({ data: { id: alt, label: alt.replace('workflow:', '').replace('task:', '') } }); - } - edges.push({ - data: { source: cmd, target: alt, label: 'or' }, - classes: 'alternative' - }); - }); - } - } - }); - - console.log('Generated graph:', nodes.length, 'nodes,', edges.length, 'edges'); - - // If no edges but we have nodes, create a simple chain - if (edges.length === 0 && nodes.length > 1) { - console.log('No relationships found, creating simple chain'); - for (var i = 0; i < nodes.length - 1; i++) { - edges.push({ - data: { source: nodes[i].data.id, target: nodes[i + 1].data.id }, - classes: 'next-step' - }); - } - } - - return nodes.concat(edges); + return workflowData.nodes.concat(workflowData.edges); } function showCommandTooltip(commandName, node) { diff --git a/ccw/src/templates/dashboard-js/views/mcp-manager.js b/ccw/src/templates/dashboard-js/views/mcp-manager.js index 4e0b09d3..49de6036 100644 --- a/ccw/src/templates/dashboard-js/views/mcp-manager.js +++ b/ccw/src/templates/dashboard-js/views/mcp-manager.js @@ -461,17 +461,23 @@ async function renderMcpManager() { onclick="openMcpCreateModal('project')"> + ${t('mcp.newProjectServer')} + + ${hasMcpJson ? ` - - - .mcp.json + + + exists - ` : ` - - - Will use .mcp.json - - `} + ` : ''} ${projectAvailableEntries.length} ${t('mcp.serversAvailable')} @@ -569,30 +575,46 @@ async function renderMcpManager() { ${mcpTemplates.length} ${t('mcp.savedTemplates')} -
+
${mcpTemplates.map(template => ` -
+
-
-

- - ${escapeHtml(template.name)} -

+
+ +

${escapeHtml(template.name)}

${template.description ? ` -

${escapeHtml(template.description)}

+ + ${escapeHtml(template.description)} + ` : ''}
+
+ + +
-
+
${t('mcp.cmd')} - ${escapeHtml(template.serverConfig.command)} + ${escapeHtml(template.serverConfig.command)}
${template.serverConfig.args && template.serverConfig.args.length > 0 ? `
${t('mcp.args')} - ${escapeHtml(template.serverConfig.args.slice(0, 2).join(' '))}${template.serverConfig.args.length > 2 ? '...' : ''} + ${escapeHtml(template.serverConfig.args.slice(0, 3).join(' '))}${template.serverConfig.args.length > 3 ? '...' : ''}
` : ''}
diff --git a/ccw/src/tools/codex-lens.ts b/ccw/src/tools/codex-lens.ts index edb0a3df..5920fb19 100644 --- a/ccw/src/tools/codex-lens.ts +++ b/ccw/src/tools/codex-lens.ts @@ -45,6 +45,12 @@ const ParamsSchema = z.object({ mode: z.enum(['auto', 'text', 'semantic', 'exact', 'fuzzy', 'hybrid', 'vector', 'pure-vector']).default('auto'), languages: z.array(z.string()).optional(), limit: z.number().default(20), + // Additional fields for internal functions + file: z.string().optional(), + key: z.string().optional(), + value: z.string().optional(), + newPath: z.string().optional(), + all: z.boolean().optional(), }); type Params = z.infer; diff --git a/ccw/src/tools/index.ts b/ccw/src/tools/index.ts index 9ef0650c..a1828ab5 100644 --- a/ccw/src/tools/index.ts +++ b/ccw/src/tools/index.ts @@ -18,7 +18,7 @@ import * as convertTokensToCssMod from './convert-tokens-to-css.js'; import * as sessionManagerMod from './session-manager.js'; import * as cliExecutorMod from './cli-executor.js'; import * as smartSearchMod from './smart-search.js'; -import * as codexLensMod from './codex-lens.js'; +// codex_lens removed - functionality integrated into smart_search import * as readFileMod from './read-file.js'; // Import legacy JS tools @@ -297,7 +297,7 @@ registerTool(toLegacyTool(convertTokensToCssMod)); registerTool(toLegacyTool(sessionManagerMod)); registerTool(toLegacyTool(cliExecutorMod)); registerTool(toLegacyTool(smartSearchMod)); -registerTool(toLegacyTool(codexLensMod)); +// codex_lens removed - functionality integrated into smart_search registerTool(toLegacyTool(readFileMod)); // Register legacy JS tools diff --git a/test_embeddings_improvements.py b/test_embeddings_improvements.py new file mode 100644 index 00000000..aa78c886 --- /dev/null +++ b/test_embeddings_improvements.py @@ -0,0 +1,146 @@ +"""Test script to verify embeddings improvements in init and status commands.""" + +import json +import subprocess +import sys +from pathlib import Path + +def run_command(cmd): + """Run a command and capture output.""" + result = subprocess.run( + cmd, + shell=True, + capture_output=True, + text=True, + ) + return result.stdout, result.stderr, result.returncode + +def test_init_embeddings(): + """Test that init command reports embeddings statistics.""" + print("Testing init command with embeddings reporting...") + + # Create a temporary test directory + test_dir = Path("test_temp_project") + test_dir.mkdir(exist_ok=True) + + # Create a simple Python file + test_file = test_dir / "test.py" + test_file.write_text(""" +def hello_world(): + print("Hello, World!") + +def add_numbers(a, b): + return a + b +""") + + # Run init with JSON output + cmd = f"codexlens init {test_dir} --json --no-embeddings" + stdout, stderr, returncode = run_command(cmd) + + if returncode != 0: + print(f"❌ Init command failed: {stderr}") + return False + + try: + result = json.loads(stdout) + + # Check for embeddings field + if "embeddings" not in result.get("result", {}): + print("❌ Missing 'embeddings' field in result") + return False + + embeddings = result["result"]["embeddings"] + + # Check required fields + required_fields = ["generated", "error"] + for field in required_fields: + if field not in embeddings: + print(f"❌ Missing required field '{field}' in embeddings") + return False + + # Since we used --no-embeddings, it should show as not generated + if embeddings["generated"]: + print("❌ Expected embeddings to not be generated with --no-embeddings") + return False + + print("✅ Init command embeddings reporting works correctly") + return True + + except json.JSONDecodeError as e: + print(f"❌ Failed to parse JSON output: {e}") + print(f"Output: {stdout}") + return False + finally: + # Cleanup + import shutil + if test_dir.exists(): + shutil.rmtree(test_dir) + +def test_status_embeddings(): + """Test that status command reports embeddings coverage.""" + print("\nTesting status command with embeddings coverage...") + + # Run status with JSON output + cmd = "codexlens status --json" + stdout, stderr, returncode = run_command(cmd) + + if returncode != 0: + print(f"❌ Status command failed: {stderr}") + return False + + try: + result = json.loads(stdout) + + # Check for features field + if "features" not in result.get("result", {}): + print("❌ Missing 'features' field in result") + return False + + features = result["result"]["features"] + + # Check that vector_search field exists (may be true or false) + if "vector_search" not in features: + print("❌ Missing 'vector_search' field in features") + return False + + # Check if embeddings info is present (optional, depends on whether embeddings exist) + embeddings = result["result"].get("embeddings") + if embeddings: + print(f" Embeddings coverage: {embeddings.get('coverage_percent', 0):.1f}%") + print(f" Files with embeddings: {embeddings.get('files_with_embeddings', 0)}/{embeddings.get('total_files', 0)}") + print(f" Total chunks: {embeddings.get('total_chunks', 0)}") + else: + print(" No embeddings found (this is OK if none were generated)") + + print("✅ Status command embeddings reporting works correctly") + return True + + except json.JSONDecodeError as e: + print(f"❌ Failed to parse JSON output: {e}") + print(f"Output: {stdout}") + return False + +def main(): + """Run all tests.""" + print("=== Testing CodexLens Embeddings Improvements ===\n") + + results = [] + + # Test init command + results.append(("Init embeddings reporting", test_init_embeddings())) + + # Test status command + results.append(("Status embeddings coverage", test_status_embeddings())) + + # Print summary + print("\n=== Test Summary ===") + for name, passed in results: + status = "✅ PASS" if passed else "❌ FAIL" + print(f"{status}: {name}") + + # Return exit code + all_passed = all(passed for _, passed in results) + return 0 if all_passed else 1 + +if __name__ == "__main__": + sys.exit(main())