|
| 1 | +#!/usr/bin/env node |
| 2 | + |
| 3 | +/** |
| 4 | + * Intelligent Prompt Improvement/Optimizer Hook |
| 5 | + * |
| 6 | + * This hook uses Claude Code in non-interactive mode to intelligently analyze |
| 7 | + * and enhance user prompts with: |
| 8 | + * - Deep intent analysis and exploration |
| 9 | + * - Context-aware technical understanding |
| 10 | + * - Comprehensive requirement clarification |
| 11 | + * - Domain-specific improvements for software engineering |
| 12 | + * - Module federation and webpack-specific enhancements |
| 13 | + */ |
| 14 | + |
| 15 | +const fs = require('fs'); |
| 16 | +const path = require('path'); |
| 17 | +const { spawn } = require('child_process'); |
| 18 | + |
| 19 | +/** |
| 20 | + * Call Claude Code in non-interactive mode to analyze and improve a prompt |
| 21 | + */ |
| 22 | +async function callClaudeForPromptImprovement(originalPrompt, context) { |
| 23 | + return new Promise((resolve, reject) => { |
| 24 | + const analysisPrompt = ` |
| 25 | +You are a prompt optimization expert with deep understanding of software engineering and user psychology. Your task is to transform the user's prompt into a comprehensive, well-structured request that will yield the best possible response. |
| 26 | +
|
| 27 | +**DEEP INTENT ANALYSIS**: First, analyze what the user REALLY wants to achieve: |
| 28 | +- What is their underlying goal beyond the surface request? |
| 29 | +- What problems are they trying to solve? |
| 30 | +- What context or constraints might they be working within? |
| 31 | +- What level of expertise do they likely have? |
| 32 | +- What unstated assumptions or needs might they have? |
| 33 | +
|
| 34 | +**Original User Prompt**: "${originalPrompt}" |
| 35 | +
|
| 36 | +**Project Context for Enhancement**: |
| 37 | +${JSON.stringify(context, null, 2)} |
| 38 | +
|
| 39 | +**Your Enhancement Process**: |
| 40 | +1. **Understand User Intent Deeply**: Go beyond the literal words to understand their true needs, goals, and context |
| 41 | +2. **Identify Knowledge Gaps**: What information would make the response significantly more helpful? |
| 42 | +3. **Add Technical Context**: Incorporate relevant project-specific knowledge and best practices |
| 43 | +4. **Anticipate Follow-ups**: Include considerations that prevent the need for clarification questions |
| 44 | +5. **Structure for Clarity**: Organize the enhanced prompt in a logical, comprehensive way |
| 45 | +
|
| 46 | +**Critical Instructions**: |
| 47 | +- Return ONLY the enhanced prompt text (no meta-commentary) |
| 48 | +- Preserve the user's core intent while dramatically improving specificity and context |
| 49 | +- Make it comprehensive enough that an expert can provide the most helpful response possible |
| 50 | +- Include relevant technical considerations from the project context |
| 51 | +- If the original prompt is already excellent, enhance it with deeper context and considerations |
| 52 | +
|
| 53 | +**Enhanced Prompt**:`; |
| 54 | + |
| 55 | + const claudeProcess = spawn( |
| 56 | + 'claude', |
| 57 | + [ |
| 58 | + '-p', |
| 59 | + analysisPrompt, |
| 60 | + '--output-format', |
| 61 | + 'text', |
| 62 | + '--max-turns', |
| 63 | + '10', |
| 64 | + '--dangerously-skip-permissions', |
| 65 | + ], |
| 66 | + { |
| 67 | + cwd: process.env.CLAUDE_PROJECT_DIR || process.cwd(), |
| 68 | + stdio: ['pipe', 'pipe', 'pipe'], |
| 69 | + env: { ...process.env, CLAUDE_DISABLE_HOOKS: '1' }, // Prevent recursive hook calls |
| 70 | + }, |
| 71 | + ); |
| 72 | + |
| 73 | + let output = ''; |
| 74 | + let errorOutput = ''; |
| 75 | + |
| 76 | + claudeProcess.stdout.on('data', (data) => { |
| 77 | + output += data.toString(); |
| 78 | + }); |
| 79 | + |
| 80 | + claudeProcess.stderr.on('data', (data) => { |
| 81 | + errorOutput += data.toString(); |
| 82 | + }); |
| 83 | + |
| 84 | + claudeProcess.on('close', (code) => { |
| 85 | + if (code === 0) { |
| 86 | + // Clean up the output - remove any markdown formatting or extra text |
| 87 | + let improvedPrompt = output.trim(); |
| 88 | + |
| 89 | + // Remove common Claude response patterns |
| 90 | + improvedPrompt = improvedPrompt |
| 91 | + .replace(/^Enhanced Prompt:\s*/i, '') |
| 92 | + .replace(/^Here's an improved version.*?:/i, '') |
| 93 | + .replace(/^I'll improve.*?:/i, '') |
| 94 | + .replace(/^The improved prompt is:\s*/i, '') |
| 95 | + .trim(); |
| 96 | + |
| 97 | + resolve(improvedPrompt || originalPrompt); |
| 98 | + } else { |
| 99 | + console.error( |
| 100 | + `Claude process failed with code ${code}: ${errorOutput}`, |
| 101 | + ); |
| 102 | + resolve(originalPrompt); // Fallback to original prompt |
| 103 | + } |
| 104 | + }); |
| 105 | + |
| 106 | + claudeProcess.on('error', (error) => { |
| 107 | + console.error(`Failed to spawn Claude process: ${error.message}`); |
| 108 | + resolve(originalPrompt); // Fallback to original prompt |
| 109 | + }); |
| 110 | + |
| 111 | + // Set a timeout to prevent hanging |
| 112 | + const timeout = setTimeout(() => { |
| 113 | + claudeProcess.kill(); |
| 114 | + resolve(originalPrompt); |
| 115 | + }, 180000); // 3 minute timeout |
| 116 | + |
| 117 | + claudeProcess.on('close', () => { |
| 118 | + clearTimeout(timeout); |
| 119 | + }); |
| 120 | + }); |
| 121 | +} |
| 122 | + |
| 123 | +/** |
| 124 | + * Extract context from the current project structure |
| 125 | + */ |
| 126 | +function getProjectContext() { |
| 127 | + const projectDir = |
| 128 | + process.env.CLAUDE_PROJECT_DIR || '/Users/bytedance/dev/core'; |
| 129 | + |
| 130 | + const context = { |
| 131 | + isMonorepo: false, |
| 132 | + hasModuleFederation: false, |
| 133 | + technologies: [], |
| 134 | + testFramework: null, |
| 135 | + buildSystem: null, |
| 136 | + }; |
| 137 | + |
| 138 | + // Check for key files |
| 139 | + const keyFiles = [ |
| 140 | + 'nx.json', |
| 141 | + 'package.json', |
| 142 | + 'pnpm-workspace.yaml', |
| 143 | + 'jest.config.ts', |
| 144 | + 'webpack.config.js', |
| 145 | + 'tsconfig.json', |
| 146 | + ]; |
| 147 | + |
| 148 | + const existingFiles = keyFiles.filter((file) => |
| 149 | + fs.existsSync(path.join(projectDir, file)), |
| 150 | + ); |
| 151 | + |
| 152 | + // Analyze project structure |
| 153 | + if ( |
| 154 | + existingFiles.includes('nx.json') && |
| 155 | + existingFiles.includes('pnpm-workspace.yaml') |
| 156 | + ) { |
| 157 | + context.isMonorepo = true; |
| 158 | + } |
| 159 | + |
| 160 | + if (existingFiles.includes('package.json')) { |
| 161 | + try { |
| 162 | + const pkgPath = path.join(projectDir, 'package.json'); |
| 163 | + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); |
| 164 | + const deps = |
| 165 | + JSON.stringify(pkg.dependencies || {}) + |
| 166 | + JSON.stringify(pkg.devDependencies || {}); |
| 167 | + if (deps.includes('@module-federation')) { |
| 168 | + context.hasModuleFederation = true; |
| 169 | + } |
| 170 | + } catch (e) { |
| 171 | + // Ignore errors reading package.json |
| 172 | + } |
| 173 | + } |
| 174 | + |
| 175 | + if (existingFiles.includes('jest.config.ts')) { |
| 176 | + context.testFramework = 'jest'; |
| 177 | + } |
| 178 | + |
| 179 | + if (existingFiles.includes('webpack.config.js')) { |
| 180 | + context.buildSystem = 'webpack'; |
| 181 | + } |
| 182 | + |
| 183 | + // Check for TypeScript |
| 184 | + if (existingFiles.includes('tsconfig.json')) { |
| 185 | + context.technologies.push('typescript'); |
| 186 | + } |
| 187 | + |
| 188 | + return context; |
| 189 | +} |
| 190 | + |
| 191 | +/** |
| 192 | + * Main function |
| 193 | + */ |
| 194 | +async function main() { |
| 195 | + try { |
| 196 | + // Read input from stdin |
| 197 | + let inputData = ''; |
| 198 | + process.stdin.on('data', (chunk) => { |
| 199 | + inputData += chunk; |
| 200 | + }); |
| 201 | + |
| 202 | + process.stdin.on('end', async () => { |
| 203 | + try { |
| 204 | + const input = JSON.parse(inputData); |
| 205 | + const prompt = input.prompt || ''; |
| 206 | + |
| 207 | + if (!prompt) { |
| 208 | + process.exit(0); |
| 209 | + } |
| 210 | + |
| 211 | + // Skip optimization for very short or simple prompts that don't need enhancement |
| 212 | + if ( |
| 213 | + prompt.length < 10 || |
| 214 | + /^(hi|hello|hey|thanks|thank you)$/i.test(prompt.trim()) |
| 215 | + ) { |
| 216 | + process.exit(0); |
| 217 | + } |
| 218 | + |
| 219 | + // Get project context |
| 220 | + const context = getProjectContext(); |
| 221 | + |
| 222 | + // Use Claude Code to intelligently improve the prompt |
| 223 | + const improvedPrompt = await callClaudeForPromptImprovement( |
| 224 | + prompt, |
| 225 | + context, |
| 226 | + ); |
| 227 | + |
| 228 | + // Use the improved prompt if Claude provided a meaningful enhancement |
| 229 | + if ( |
| 230 | + improvedPrompt && |
| 231 | + improvedPrompt !== prompt && |
| 232 | + improvedPrompt.trim().length > 0 |
| 233 | + ) { |
| 234 | + const output = { |
| 235 | + hookSpecificOutput: { |
| 236 | + hookEventName: 'UserPromptSubmit', |
| 237 | + additionalContext: improvedPrompt, |
| 238 | + }, |
| 239 | + }; |
| 240 | + console.log(JSON.stringify(output)); |
| 241 | + } |
| 242 | + |
| 243 | + process.exit(0); |
| 244 | + } catch (e) { |
| 245 | + console.error(`Error parsing JSON input: ${e.message}`); |
| 246 | + process.exit(1); |
| 247 | + } |
| 248 | + }); |
| 249 | + } catch (e) { |
| 250 | + console.error(`Error in prompt optimizer: ${e.message}`); |
| 251 | + process.exit(1); |
| 252 | + } |
| 253 | +} |
| 254 | + |
| 255 | +// Handle stdin |
| 256 | +process.stdin.setEncoding('utf8'); |
| 257 | +main(); |
0 commit comments