mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-14 02:42:04 +08:00
fix(tools): 修复CLI工具多行prompt的shell转义问题
修复文件: - update-module-claude.js - generate-module-docs.js 主要修复: - 使用临时文件+stdin管道传递prompt,避免shell转义问题 - 添加Windows PowerShell兼容性支持 - 添加执行日志输出便于调试 - 添加临时文件清理逻辑 技术细节: - gemini/qwen: 使用 cat file | tool 方式 - codex: 使用 \ 命令替换 - Windows: 使用 Get-Content -Raw | tool
This commit is contained in:
@@ -3,9 +3,10 @@
|
|||||||
* Generate documentation for modules and projects with multiple strategies
|
* 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 { join, resolve, basename, extname, relative } from 'path';
|
||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
|
import { tmpdir } from 'os';
|
||||||
|
|
||||||
// Directories to exclude
|
// Directories to exclude
|
||||||
const EXCLUDE_DIRS = [
|
const EXCLUDE_DIRS = [
|
||||||
@@ -26,7 +27,7 @@ const DEFAULT_MODELS = {
|
|||||||
codex: 'gpt5-codex'
|
codex: 'gpt5-codex'
|
||||||
};
|
};
|
||||||
|
|
||||||
// Template paths
|
// Template paths (relative to user home directory)
|
||||||
const TEMPLATE_BASE = '.claude/workflows/cli-templates/prompts/documentation';
|
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) {
|
function createPromptFile(prompt) {
|
||||||
const escapedPrompt = prompt.replace(/"/g, '\\"');
|
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) {
|
switch (tool) {
|
||||||
case 'qwen':
|
case 'qwen':
|
||||||
return model === 'coder-model'
|
return model === 'coder-model'
|
||||||
? `qwen -p "${escapedPrompt}" --yolo`
|
? `${catCmd}qwen --yolo`
|
||||||
: `qwen -p "${escapedPrompt}" -m "${model}" --yolo`;
|
: `${catCmd}qwen -m "${model}" --yolo`;
|
||||||
case 'codex':
|
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':
|
case 'gemini':
|
||||||
default:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build and execute command
|
// Create temporary prompt file (avoids shell escaping issues)
|
||||||
const command = buildCliCommand(tool, prompt, actualModel);
|
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 {
|
try {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
@@ -289,11 +318,21 @@ Output directory: .workflow/docs/${projectName}/api/`;
|
|||||||
cwd: targetPath,
|
cwd: targetPath,
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
stdio: 'inherit',
|
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);
|
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 {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
strategy,
|
strategy,
|
||||||
@@ -307,6 +346,15 @@ Output directory: .workflow/docs/${projectName}/api/`;
|
|||||||
message: `Documentation generated successfully in ${duration}s`
|
message: `Documentation generated successfully in ${duration}s`
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// Cleanup prompt file on error
|
||||||
|
try {
|
||||||
|
unlinkSync(promptFile);
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore cleanup errors
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(` ❌ Generation failed: ${error.message}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
strategy,
|
strategy,
|
||||||
|
|||||||
@@ -3,9 +3,10 @@
|
|||||||
* Generate/update CLAUDE.md module documentation using CLI tools
|
* 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 { join, resolve, basename, extname } from 'path';
|
||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
|
import { tmpdir } from 'os';
|
||||||
|
|
||||||
// Directories to exclude
|
// Directories to exclude
|
||||||
const EXCLUDE_DIRS = [
|
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) {
|
function createPromptFile(prompt) {
|
||||||
const escapedPrompt = prompt.replace(/"/g, '\\"');
|
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) {
|
switch (tool) {
|
||||||
case 'qwen':
|
case 'qwen':
|
||||||
return model === 'coder-model'
|
return model === 'coder-model'
|
||||||
? `qwen -p "${escapedPrompt}" --yolo`
|
? `${catCmd}qwen --yolo`
|
||||||
: `qwen -p "${escapedPrompt}" -m "${model}" --yolo`;
|
: `${catCmd}qwen -m "${model}" --yolo`;
|
||||||
case 'codex':
|
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':
|
case 'gemini':
|
||||||
default:
|
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
|
- Work bottom-up: deepest directories first
|
||||||
- Parent directories reference children
|
- Parent directories reference children
|
||||||
- Each CLAUDE.md file must be in its respective directory
|
- 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 {
|
} else {
|
||||||
prompt = `Directory Structure Analysis:
|
prompt = `Directory Structure Analysis:
|
||||||
${structureInfo}
|
${structureInfo}
|
||||||
@@ -247,11 +271,20 @@ ${templateContent}
|
|||||||
Instructions:
|
Instructions:
|
||||||
- Create exactly one CLAUDE.md file in the current directory
|
- Create exactly one CLAUDE.md file in the current directory
|
||||||
- Reference child CLAUDE.md files, do not duplicate their content
|
- 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
|
// Create temporary prompt file (avoids shell escaping issues with multiline prompts)
|
||||||
const command = buildCliCommand(tool, prompt, actualModel);
|
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 {
|
try {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
@@ -260,11 +293,21 @@ Instructions:
|
|||||||
cwd: targetPath,
|
cwd: targetPath,
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
stdio: 'inherit',
|
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);
|
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 {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
strategy,
|
strategy,
|
||||||
@@ -276,6 +319,15 @@ Instructions:
|
|||||||
message: `CLAUDE.md updated successfully in ${duration}s`
|
message: `CLAUDE.md updated successfully in ${duration}s`
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// Cleanup prompt file on error
|
||||||
|
try {
|
||||||
|
unlinkSync(promptFile);
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore cleanup errors
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(` ❌ Update failed: ${error.message}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
strategy,
|
strategy,
|
||||||
|
|||||||
Reference in New Issue
Block a user