From 1c3c070db4911fb14089154d52ef641a33d16745 Mon Sep 17 00:00:00 2001 From: catlog22 Date: Mon, 8 Dec 2025 21:56:41 +0800 Subject: [PATCH] =?UTF-8?q?fix(tools):=20=E4=BF=AE=E5=A4=8DCLI=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E5=A4=9A=E8=A1=8Cprompt=E7=9A=84shell=E8=BD=AC?= =?UTF-8?q?=E4=B9=89=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复文件: - update-module-claude.js - generate-module-docs.js 主要修复: - 使用临时文件+stdin管道传递prompt,避免shell转义问题 - 添加Windows PowerShell兼容性支持 - 添加执行日志输出便于调试 - 添加临时文件清理逻辑 技术细节: - gemini/qwen: 使用 cat file | tool 方式 - codex: 使用 \ 命令替换 - Windows: 使用 Get-Content -Raw | tool --- ccw/src/tools/generate-module-docs.js | 72 ++++++++++++++++++++----- ccw/src/tools/update-module-claude.js | 78 ++++++++++++++++++++++----- 2 files changed, 125 insertions(+), 25 deletions(-) diff --git a/ccw/src/tools/generate-module-docs.js b/ccw/src/tools/generate-module-docs.js index 84f7bf52..11a813ae 100644 --- a/ccw/src/tools/generate-module-docs.js +++ b/ccw/src/tools/generate-module-docs.js @@ -3,9 +3,10 @@ * Generate documentation for modules and projects with multiple strategies */ -import { readdirSync, statSync, existsSync, readFileSync, mkdirSync } from 'fs'; +import { readdirSync, statSync, existsSync, readFileSync, mkdirSync, writeFileSync, unlinkSync } from 'fs'; import { join, resolve, basename, extname, relative } from 'path'; import { execSync } from 'child_process'; +import { tmpdir } from 'os'; // Directories to exclude const EXCLUDE_DIRS = [ @@ -26,7 +27,7 @@ const DEFAULT_MODELS = { codex: 'gpt5-codex' }; -// Template paths +// Template paths (relative to user home directory) const TEMPLATE_BASE = '.claude/workflows/cli-templates/prompts/documentation'; /** @@ -94,21 +95,40 @@ function loadTemplate(templateName) { } /** - * Build CLI command + * Create temporary prompt file and return path */ -function buildCliCommand(tool, prompt, model) { - const escapedPrompt = prompt.replace(/"/g, '\\"'); +function createPromptFile(prompt) { + const timestamp = Date.now(); + const randomSuffix = Math.random().toString(36).substring(2, 8); + const promptFile = join(tmpdir(), `docs-prompt-${timestamp}-${randomSuffix}.txt`); + writeFileSync(promptFile, prompt, 'utf8'); + return promptFile; +} +/** + * Build CLI command using stdin piping (avoids shell escaping issues) + */ +function buildCliCommand(tool, promptFile, model) { + const normalizedPath = promptFile.replace(/\\/g, '/'); + const isWindows = process.platform === 'win32'; + + // Build the cat/read command based on platform + const catCmd = isWindows ? `Get-Content -Raw "${normalizedPath}" | ` : `cat "${normalizedPath}" | `; + switch (tool) { case 'qwen': return model === 'coder-model' - ? `qwen -p "${escapedPrompt}" --yolo` - : `qwen -p "${escapedPrompt}" -m "${model}" --yolo`; + ? `${catCmd}qwen --yolo` + : `${catCmd}qwen -m "${model}" --yolo`; case 'codex': - return `codex --full-auto exec "${escapedPrompt}" -m "${model}" --skip-git-repo-check -s danger-full-access`; + // codex uses different syntax - prompt as exec argument + if (isWindows) { + return `codex --full-auto exec (Get-Content -Raw "${normalizedPath}") -m "${model}" --skip-git-repo-check -s danger-full-access`; + } + return `codex --full-auto exec "$(cat "${normalizedPath}")" -m "${model}" --skip-git-repo-check -s danger-full-access`; case 'gemini': default: - return `gemini -p "${escapedPrompt}" -m "${model}" --yolo`; + return `${catCmd}gemini -m "${model}" --yolo`; } } @@ -279,8 +299,17 @@ Output directory: .workflow/docs/${projectName}/api/`; break; } - // Build and execute command - const command = buildCliCommand(tool, prompt, actualModel); + // Create temporary prompt file (avoids shell escaping issues) + const promptFile = createPromptFile(prompt); + + // Build command using file-based prompt + const command = buildCliCommand(tool, promptFile, actualModel); + + // Log execution info + console.log(`📚 Generating docs: ${sourcePath}`); + console.log(` Strategy: ${strategy} | Tool: ${tool} | Model: ${actualModel}`); + console.log(` Output: ${outputPath}`); + console.log(` Prompt file: ${promptFile}`); try { const startTime = Date.now(); @@ -289,11 +318,21 @@ Output directory: .workflow/docs/${projectName}/api/`; cwd: targetPath, encoding: 'utf8', stdio: 'inherit', - timeout: 600000 // 10 minutes + timeout: 600000, // 10 minutes + shell: process.platform === 'win32' ? 'powershell.exe' : '/bin/bash' }); const duration = Math.round((Date.now() - startTime) / 1000); + // Cleanup prompt file + try { + unlinkSync(promptFile); + } catch (e) { + // Ignore cleanup errors + } + + console.log(` ✅ Completed in ${duration}s`); + return { success: true, strategy, @@ -307,6 +346,15 @@ Output directory: .workflow/docs/${projectName}/api/`; message: `Documentation generated successfully in ${duration}s` }; } catch (error) { + // Cleanup prompt file on error + try { + unlinkSync(promptFile); + } catch (e) { + // Ignore cleanup errors + } + + console.log(` ❌ Generation failed: ${error.message}`); + return { success: false, strategy, diff --git a/ccw/src/tools/update-module-claude.js b/ccw/src/tools/update-module-claude.js index bb9c07ec..2670b843 100644 --- a/ccw/src/tools/update-module-claude.js +++ b/ccw/src/tools/update-module-claude.js @@ -3,9 +3,10 @@ * Generate/update CLAUDE.md module documentation using CLI tools */ -import { readdirSync, statSync, existsSync, readFileSync } from 'fs'; +import { readdirSync, statSync, existsSync, readFileSync, writeFileSync, unlinkSync } from 'fs'; import { join, resolve, basename, extname } from 'path'; import { execSync } from 'child_process'; +import { tmpdir } from 'os'; // Directories to exclude const EXCLUDE_DIRS = [ @@ -147,21 +148,43 @@ function loadTemplate() { } /** - * Build CLI command + * Create temporary prompt file and return cleanup function */ -function buildCliCommand(tool, prompt, model) { - const escapedPrompt = prompt.replace(/"/g, '\\"'); +function createPromptFile(prompt) { + const timestamp = Date.now(); + const randomSuffix = Math.random().toString(36).substring(2, 8); + const promptFile = join(tmpdir(), `claude-prompt-${timestamp}-${randomSuffix}.txt`); + writeFileSync(promptFile, prompt, 'utf8'); + return promptFile; +} +/** + * Build CLI command using stdin piping for prompt (avoids shell escaping issues) + */ +function buildCliCommand(tool, promptFile, model) { + // Use stdin piping: cat file | tool or Get-Content | tool + // This avoids shell escaping issues with multiline prompts + const normalizedPath = promptFile.replace(/\\/g, '/'); + const isWindows = process.platform === 'win32'; + + // Build the cat/read command based on platform + const catCmd = isWindows ? `Get-Content -Raw "${normalizedPath}" | ` : `cat "${normalizedPath}" | `; + switch (tool) { case 'qwen': return model === 'coder-model' - ? `qwen -p "${escapedPrompt}" --yolo` - : `qwen -p "${escapedPrompt}" -m "${model}" --yolo`; + ? `${catCmd}qwen --yolo` + : `${catCmd}qwen -m "${model}" --yolo`; case 'codex': - return `codex --full-auto exec "${escapedPrompt}" -m "${model}" --skip-git-repo-check -s danger-full-access`; + // codex uses different syntax - prompt as exec argument + if (isWindows) { + return `codex --full-auto exec (Get-Content -Raw "${normalizedPath}") -m "${model}" --skip-git-repo-check -s danger-full-access`; + } + return `codex --full-auto exec "$(cat "${normalizedPath}")" -m "${model}" --skip-git-repo-check -s danger-full-access`; case 'gemini': default: - return `gemini -p "${escapedPrompt}" -m "${model}" --yolo`; + // gemini reads from stdin when no positional prompt is given + return `${catCmd}gemini -m "${model}" --yolo`; } } @@ -232,7 +255,8 @@ Instructions: - Work bottom-up: deepest directories first - Parent directories reference children - Each CLAUDE.md file must be in its respective directory -- Follow the template guidelines above for consistent structure`; +- Follow the template guidelines above for consistent structure +- Use the structure analysis to understand directory hierarchy`; } else { prompt = `Directory Structure Analysis: ${structureInfo} @@ -247,11 +271,20 @@ ${templateContent} Instructions: - Create exactly one CLAUDE.md file in the current directory - Reference child CLAUDE.md files, do not duplicate their content -- Follow the template guidelines above for consistent structure`; +- Follow the template guidelines above for consistent structure +- Use the structure analysis to understand the current directory context`; } - // Build and execute command - const command = buildCliCommand(tool, prompt, actualModel); + // Create temporary prompt file (avoids shell escaping issues with multiline prompts) + const promptFile = createPromptFile(prompt); + + // Build command using file-based prompt + const command = buildCliCommand(tool, promptFile, actualModel); + + // Log execution info + console.log(`⚡ Updating: ${modulePath}`); + console.log(` Strategy: ${strategy} | Tool: ${tool} | Model: ${actualModel} | Files: ${fileCount}`); + console.log(` Prompt file: ${promptFile}`); try { const startTime = Date.now(); @@ -260,11 +293,21 @@ Instructions: cwd: targetPath, encoding: 'utf8', stdio: 'inherit', - timeout: 300000 // 5 minutes + timeout: 300000, // 5 minutes + shell: process.platform === 'win32' ? 'powershell.exe' : '/bin/bash' }); const duration = Math.round((Date.now() - startTime) / 1000); + // Cleanup prompt file + try { + unlinkSync(promptFile); + } catch (e) { + // Ignore cleanup errors + } + + console.log(` ✅ Completed in ${duration}s`); + return { success: true, strategy, @@ -276,6 +319,15 @@ Instructions: message: `CLAUDE.md updated successfully in ${duration}s` }; } catch (error) { + // Cleanup prompt file on error + try { + unlinkSync(promptFile); + } catch (e) { + // Ignore cleanup errors + } + + console.log(` ❌ Update failed: ${error.message}`); + return { success: false, strategy,