From be9a1c76d4264f1230840c2e85b34239216aa972 Mon Sep 17 00:00:00 2001 From: catlog22 Date: Tue, 9 Dec 2025 15:50:35 +0800 Subject: [PATCH] refactor(tool): simplify edit_file to parameter-based input only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove JSON input support, use CLI parameters only - Remove line mode, keep text replacement only - Add sed as line operation alternative in tool-strategy.md - Update documentation to English Usage: ccw tool exec edit_file --path file.txt --old "old" --new "new" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .claude/workflows/tool-strategy.md | 109 +++++++++++++-------------- ccw/src/cli.js | 7 +- ccw/src/commands/tool.js | 115 +++++------------------------ 3 files changed, 75 insertions(+), 156 deletions(-) diff --git a/.claude/workflows/tool-strategy.md b/.claude/workflows/tool-strategy.md index e44a4980..53209232 100644 --- a/.claude/workflows/tool-strategy.md +++ b/.claude/workflows/tool-strategy.md @@ -14,71 +14,66 @@ **When to Use**: Edit tool fails 1+ times on same file -### update Mode (Default) +### Usage **Best for**: Code block replacements, function rewrites, multi-line changes ```bash -ccw tool exec edit_file '{ - "path": "file.py", - "oldText": "def old():\n pass", - "newText": "def new():\n return True" -}' +ccw tool exec edit_file --path "file.py" --old "def old(): + pass" --new "def new(): + return True" ``` -### line Mode (Precise Line Operations) +**Parameters**: +- `--path`: File path to edit +- `--old`: Text to find and replace +- `--new`: New text to insert -**Best for**: Config files, line insertions/deletions, precise line number control - -```bash -# Insert after specific line -ccw tool exec edit_file '{ - "path": "config.txt", - "mode": "line", - "operation": "insert_after", - "line": 10, - "text": "new config line" -}' - -# Delete line range -ccw tool exec edit_file '{ - "path": "log.txt", - "mode": "line", - "operation": "delete", - "line": 5, - "end_line": 8 -}' - -# Replace specific line -ccw tool exec edit_file '{ - "path": "script.sh", - "mode": "line", - "operation": "replace", - "line": 3, - "text": "#!/bin/bash" -}' -``` - -**Operations**: -- `insert_before`: Insert text before specified line -- `insert_after`: Insert text after specified line -- `replace`: Replace line or line range -- `delete`: Delete line or line range - -### Mode Selection Guide - -| Scenario | Mode | Reason | -|----------|------|--------| -| Code refactoring | update | Content-driven replacement | -| Function rewrite | update | Simple oldText/newText | -| Config line change | line | Precise line number control | -| Insert at specific position | line | Exact line number needed | -| Delete line range | line | Line-based operation | +**Features**: +- ✅ Exact text matching (precise and predictable) +- ✅ Auto line ending adaptation (CRLF/LF) +- ✅ No JSON escaping issues +- ✅ Multi-line text supported with quotes ### Fallback Strategy -1. **Edit fails 1+ times** → Use `ccw tool exec edit_file` (update mode) -2. **update mode fails** → Try line mode with precise line numbers -3. **All fails** → Use Write to recreate file +1. **Edit fails 1+ times** → Use `ccw tool exec edit_file` +2. **Still fails** → Use Write to recreate file -**Default mode**: update (exact matching with line ending adaptation) +## ⚡ sed Line Operations (Line Mode Alternative) + +**When to Use**: Precise line number control (insert, delete, replace specific lines) + +### Common Operations + +```bash +# Insert after line 10 +sed -i '10a\new line content' file.txt + +# Insert before line 5 +sed -i '5i\new line content' file.txt + +# Delete line 3 +sed -i '3d' file.txt + +# Delete lines 5-8 +sed -i '5,8d' file.txt + +# Replace line 3 content +sed -i '3c\replacement line' file.txt + +# Replace lines 3-5 content +sed -i '3,5c\single replacement line' file.txt +``` + +### Operation Reference + +| Operation | Command | Example | +|-----------|---------|---------| +| Insert after | `Na\text` | `sed -i '10a\new' file` | +| Insert before | `Ni\text` | `sed -i '5i\new' file` | +| Delete line | `Nd` | `sed -i '3d' file` | +| Delete range | `N,Md` | `sed -i '5,8d' file` | +| Replace line | `Nc\text` | `sed -i '3c\new' file` | + +**Note**: Use `sed -i` for in-place file modification (works in Git Bash on Windows) diff --git a/ccw/src/cli.js b/ccw/src/cli.js index 530ea7c7..0a69036d 100644 --- a/ccw/src/cli.js +++ b/ccw/src/cli.js @@ -108,9 +108,12 @@ export function run(argv) { // Tool command program - .command('tool [subcommand] [args] [json]') + .command('tool [subcommand] [args]') .description('Execute CCW tools') - .action((subcommand, args, json) => toolCommand(subcommand, args, { json })); + .option('--path ', 'File path (for edit_file)') + .option('--old ', 'Old text to replace (for edit_file)') + .option('--new ', 'New text (for edit_file)') + .action((subcommand, args, options) => toolCommand(subcommand, args, options)); program.parse(argv); } diff --git a/ccw/src/commands/tool.js b/ccw/src/commands/tool.js index 4ad4d818..c5600e86 100644 --- a/ccw/src/commands/tool.js +++ b/ccw/src/commands/tool.js @@ -66,80 +66,13 @@ async function schemaAction(options) { } } -/** - * Read from stdin if available - */ -async function readStdin() { - // Check if stdin is a TTY (interactive terminal) - if (process.stdin.isTTY) { - return null; - } - - return new Promise((resolve, reject) => { - let data = ''; - - process.stdin.setEncoding('utf8'); - - process.stdin.on('readable', () => { - let chunk; - while ((chunk = process.stdin.read()) !== null) { - data += chunk; - } - }); - - process.stdin.on('end', () => { - resolve(data.trim() || null); - }); - - process.stdin.on('error', (err) => { - reject(err); - }); - }); -} - -/** - * Smart JSON parser with Windows path handling - */ -function parseJsonWithPathFix(jsonString) { - try { - // Try normal parse first - return JSON.parse(jsonString); - } catch (firstError) { - // If parsing fails, try to fix Windows paths - try { - // Pattern: "path": "X:\..." or "path":"X:\..." - const fixedJson = jsonString.replace( - /("(?:path|file|target|source|dest|destination)":\s*")([A-Za-z]:[^"]+)"/g, - (match, prefix, path) => { - // Convert backslashes to forward slashes (universal) - const fixedPath = path.replace(/\\/g, '/'); - return `${prefix}${fixedPath}"`; - } - ); - - return JSON.parse(fixedJson); - } catch (secondError) { - // If still fails, throw original error with helpful message - const errorMsg = firstError.message; - const hint = errorMsg.includes('escaped character') || errorMsg.includes('position') - ? '\n\n' + chalk.yellow('Hint: Windows paths in JSON need forward slashes or double backslashes:') + - '\n ' + chalk.green('✓ "D:/Claude_dms3/file.md"') + - '\n ' + chalk.green('✓ "D:\\\\Claude_dms3\\\\file.md"') + - '\n ' + chalk.red('✗ "D:\\Claude_dms3\\file.md"') - : ''; - - throw new Error(errorMsg + hint); - } - } -} - /** * Execute a tool with given parameters */ -async function execAction(toolName, jsonInput, options) { +async function execAction(toolName, options) { if (!toolName) { console.error(chalk.red('Tool name is required')); - console.error(chalk.gray('Usage: ccw tool exec \'{"param": "value"}\'')); + console.error(chalk.gray('Usage: ccw tool exec edit_file --path file.txt --old "old" --new "new"')); process.exit(1); } @@ -150,34 +83,22 @@ async function execAction(toolName, jsonInput, options) { process.exit(1); } - // Parse JSON input (default format) - let params = {}; + // Build params from CLI options + const params = {}; - if (jsonInput) { - try { - params = parseJsonWithPathFix(jsonInput); - } catch (error) { - console.error(chalk.red(`Invalid JSON: ${error.message}`)); + if (toolName === 'edit_file') { + if (!options.path || !options.old || !options.new) { + console.error(chalk.red('edit_file requires --path, --old, and --new parameters')); + console.error(chalk.gray('Usage: ccw tool exec edit_file --path file.txt --old "old text" --new "new text"')); process.exit(1); } - } - - // Check for stdin input (for piped commands) - const stdinData = await readStdin(); - if (stdinData) { - // If tool has an 'input' parameter, use it - // Otherwise, try to parse stdin as JSON and merge with params - if (tool.parameters?.properties?.input) { - params.input = stdinData; - } else { - try { - const stdinJson = JSON.parse(stdinData); - params = { ...stdinJson, ...params }; - } catch { - // If not JSON, store as 'input' anyway - params.input = stdinData; - } - } + params.path = options.path; + params.oldText = options.old; + params.newText = options.new; + } else { + console.error(chalk.red(`Tool "${toolName}" is not supported via CLI parameters`)); + console.error(chalk.gray('Currently only edit_file is supported')); + process.exit(1); } // Execute tool @@ -200,7 +121,7 @@ export async function toolCommand(subcommand, args, options) { await schemaAction({ name: args }); break; case 'exec': - await execAction(args, options.json, options); + await execAction(args, options); break; default: console.log(chalk.bold.cyan('\nCCW Tool System\n')); @@ -209,9 +130,9 @@ export async function toolCommand(subcommand, args, options) { console.log(chalk.gray(' schema [name] Show tool schema (JSON)')); console.log(chalk.gray(' exec Execute a tool')); console.log(); - console.log('Examples:'); + console.log('Usage:'); console.log(chalk.gray(' ccw tool list')); console.log(chalk.gray(' ccw tool schema edit_file')); - console.log(chalk.gray(' ccw tool exec edit_file \'{"path":"file.txt","oldText":"old","newText":"new"}\'')); + console.log(chalk.gray(' ccw tool exec edit_file --path file.txt --old "old text" --new "new text"')); } }