fix: Rewrite config_enhancer LOCAL mode to use Claude CLI properly

The previous implementation had a design flaw - it ran `claude prompt_file`
and expected Claude to magically create a JSON file in a temp directory.
This never worked because Claude CLI is interactive and doesn't auto-save.

Changes:
- Use `--dangerously-skip-permissions` flag to bypass permission prompts
- Create a dedicated temp directory for each enhancement session
- Embed absolute output file path in the prompt so Claude knows where to write
- Run Claude from the temp directory as working_dir
- Improved prompt with explicit Write tool instruction
- Better error handling and logging (file not found, JSON parse errors)
- Show settings preview in prompt for better AI context

The LOCAL mode now follows the same pattern as enhance_skill_local.py
which works correctly.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
YusufKaraaslanSpyke
2026-01-30 10:48:49 +03:00
parent be2353cf2f
commit 22e2edbc7f

View File

@@ -256,126 +256,184 @@ Focus on actionable insights that help developers understand and improve their c
def _enhance_via_local(self, result: dict) -> dict: def _enhance_via_local(self, result: dict) -> dict:
"""Enhance configs using Claude Code CLI""" """Enhance configs using Claude Code CLI"""
try: try:
# Create temporary prompt file # Create a temporary directory for this enhancement session
with tempfile.NamedTemporaryFile(mode="w", suffix=".md", delete=False) as f: with tempfile.TemporaryDirectory(prefix="config_enhance_") as temp_dir:
prompt_file = Path(f.name) temp_path = Path(temp_dir)
f.write(self._create_local_prompt(result))
# Create output file path # Define output file path (absolute path that Claude will write to)
output_file = prompt_file.parent / f"{prompt_file.stem}_enhanced.json" output_file = temp_path / "config_enhancement.json"
logger.info("🖥️ Launching Claude Code CLI for config analysis...") # Create prompt file with the output path embedded
logger.info("⏱️ This will take 30-60 seconds...") prompt_file = temp_path / "enhance_prompt.md"
prompt_content = self._create_local_prompt(result, output_file)
prompt_file.write_text(prompt_content)
# Run Claude Code CLI logger.info("🖥️ Launching Claude Code CLI for config analysis...")
result_data = self._run_claude_cli(prompt_file, output_file) logger.info("⏱️ This will take 30-60 seconds...")
# Clean up # Run Claude Code CLI
prompt_file.unlink() result_data = self._run_claude_cli(prompt_file, output_file, temp_path)
if output_file.exists():
output_file.unlink()
if result_data: if result_data:
# Merge LOCAL enhancements # Merge LOCAL enhancements
result["ai_enhancements"] = result_data result["ai_enhancements"] = result_data
logger.info("✅ LOCAL enhancement complete") logger.info("✅ LOCAL enhancement complete")
return result return result
else: else:
logger.warning("⚠️ LOCAL enhancement produced no results") logger.warning("⚠️ LOCAL enhancement produced no results")
return result return result
except Exception as e: except Exception as e:
logger.error(f"❌ LOCAL enhancement failed: {e}") logger.error(f"❌ LOCAL enhancement failed: {e}")
return result return result
def _create_local_prompt(self, result: dict) -> str: def _create_local_prompt(self, result: dict, output_file: Path) -> str:
"""Create prompt file for Claude Code CLI""" """Create prompt file for Claude Code CLI
Args:
result: Config extraction result dict
output_file: Absolute path where Claude should write the JSON output
Returns:
Prompt content string
"""
config_files = result.get("config_files", []) config_files = result.get("config_files", [])
# Format config data for Claude # Format config data for Claude (limit to 15 files for reasonable prompt size)
config_data = [] config_data = []
for cf in config_files[:10]: for cf in config_files[:15]:
# Support both "type" (from config_extractor) and "config_type" (legacy) # Support both "type" (from config_extractor) and "config_type" (legacy)
config_type = cf.get("type", cf.get("config_type", "unknown")) config_type = cf.get("type", cf.get("config_type", "unknown"))
settings_preview = []
for s in cf.get("settings", [])[:3]: # Show first 3 settings
settings_preview.append(f" - {s.get('key', 'unknown')}: {str(s.get('value', ''))[:50]}")
config_data.append(f""" config_data.append(f"""
### {cf["relative_path"]} ({config_type}) ### {cf["relative_path"]} ({config_type})
- Purpose: {cf["purpose"]} - Purpose: {cf["purpose"]}
- Patterns: {", ".join(cf.get("patterns", []))} - Patterns: {", ".join(cf.get("patterns", [])) or "none detected"}
- Settings count: {len(cf.get("settings", []))} - Settings: {len(cf.get("settings", []))} total
{chr(10).join(settings_preview) if settings_preview else " (no settings)"}
""") """)
prompt = f"""# Configuration Analysis Task prompt = f"""# Configuration Analysis Task
I need you to analyze these configuration files and provide AI-enhanced insights. IMPORTANT: You MUST write the output to this EXACT file path:
{output_file}
## Configuration Files ({len(config_files)} total) ## Configuration Files ({len(config_files)} total, showing first 15)
{chr(10).join(config_data)} {chr(10).join(config_data)}
## Your Task ## Your Task
Analyze these configs and create a JSON file with the following structure: Analyze these configuration files and write a JSON file to the path specified above.
The JSON must have this EXACT structure:
```json ```json
{{ {{
"file_enhancements": [ "file_enhancements": [
{{ {{
"file_path": "path/to/file", "file_path": "relative/path/to/config.json",
"explanation": "What this config does", "explanation": "Brief explanation of what this config file does",
"best_practice": "Suggested improvements", "best_practice": "Suggested improvement or 'None'",
"security_concern": "Security issues (if any)", "security_concern": "Security issue if any, or 'None'",
"migration_suggestion": "Consolidation opportunities", "migration_suggestion": "Consolidation opportunity or 'None'",
"context": "Pattern explanation" "context": "What pattern or purpose this serves"
}} }}
], ],
"overall_insights": {{ "overall_insights": {{
"config_count": {len(config_files)}, "config_count": {len(config_files)},
"security_issues_found": 0, "security_issues_found": 0,
"consolidation_opportunities": [], "consolidation_opportunities": ["List of suggestions"],
"recommended_actions": [] "recommended_actions": ["List of actions"]
}} }}
}} }}
``` ```
Please save the JSON output to a file named `config_enhancement.json` in the current directory. ## Instructions
Focus on actionable insights: 1. Use the Write tool to create the JSON file at: {output_file}
1. Explain what each config does 2. Include an enhancement entry for each config file shown above
2. Suggest best practices 3. Focus on actionable insights:
3. Identify security concerns (hardcoded secrets, exposed credentials) - Explain what each config does in 1-2 sentences
4. Suggest consolidation opportunities - Identify any hardcoded secrets or security issues
5. Explain the detected patterns - Suggest consolidation if configs have overlapping settings
- Note any missing best practices
DO NOT explain your work - just write the JSON file directly.
""" """
return prompt return prompt
def _run_claude_cli(self, prompt_file: Path, _output_file: Path) -> dict | None: def _run_claude_cli(
"""Run Claude Code CLI and wait for completion""" self, prompt_file: Path, output_file: Path, working_dir: Path
) -> dict | None:
"""Run Claude Code CLI and wait for completion
Args:
prompt_file: Path to the prompt markdown file
output_file: Expected path where Claude will write the JSON output
working_dir: Working directory to run Claude from
Returns:
Parsed JSON dict if successful, None otherwise
"""
import time
try: try:
# Run claude command start_time = time.time()
# Run claude command with --dangerously-skip-permissions to bypass all prompts
# This allows Claude to write files without asking for confirmation
logger.info(f" Running: claude --dangerously-skip-permissions {prompt_file.name}")
logger.info(f" Output expected at: {output_file}")
result = subprocess.run( result = subprocess.run(
["claude", str(prompt_file)], ["claude", "--dangerously-skip-permissions", str(prompt_file)],
capture_output=True, capture_output=True,
text=True, text=True,
timeout=300, # 5 minute timeout timeout=300, # 5 minute timeout
cwd=str(working_dir),
) )
elapsed = time.time() - start_time
logger.info(f" Claude finished in {elapsed:.1f} seconds")
if result.returncode != 0: if result.returncode != 0:
logger.error(f"❌ Claude CLI failed: {result.stderr}") logger.error(f"❌ Claude CLI failed (exit code {result.returncode})")
if result.stderr:
logger.error(f" Error: {result.stderr[:200]}")
return None return None
# Try to find output file (Claude might save it with different name) # Check if the expected output file was created
# Look for JSON files created in the last minute if output_file.exists():
import time try:
with open(output_file) as f:
data = json.load(f)
if "file_enhancements" in data or "overall_insights" in data:
logger.info(f"✅ Found enhancement data in {output_file.name}")
return data
else:
logger.warning("⚠️ Output file exists but missing expected keys")
except json.JSONDecodeError as e:
logger.error(f"❌ Failed to parse output JSON: {e}")
return None
# Fallback: Look for any JSON files created in the working directory
logger.info(" Looking for JSON files in working directory...")
current_time = time.time() current_time = time.time()
potential_files = [] potential_files = []
for json_file in prompt_file.parent.glob("*.json"): for json_file in working_dir.glob("*.json"):
if current_time - json_file.stat().st_mtime < 120: # Created in last 2 minutes # Check if created recently (within last 2 minutes)
if current_time - json_file.stat().st_mtime < 120:
potential_files.append(json_file) potential_files.append(json_file)
# Try to load the most recent JSON file # Try to load the most recent JSON file with expected structure
for json_file in sorted(potential_files, key=lambda f: f.stat().st_mtime, reverse=True): for json_file in sorted(
potential_files, key=lambda f: f.stat().st_mtime, reverse=True
):
try: try:
with open(json_file) as f: with open(json_file) as f:
data = json.load(f) data = json.load(f)
@@ -386,11 +444,17 @@ Focus on actionable insights:
continue continue
logger.warning("⚠️ Could not find enhancement output file") logger.warning("⚠️ Could not find enhancement output file")
logger.info(f" Expected file: {output_file}")
logger.info(f" Files in dir: {list(working_dir.glob('*'))}")
return None return None
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
logger.error("❌ Claude CLI timeout (5 minutes)") logger.error("❌ Claude CLI timeout (5 minutes)")
return None return None
except FileNotFoundError:
logger.error("'claude' command not found. Is Claude Code CLI installed?")
logger.error(" Install with: npm install -g @anthropic-ai/claude-code")
return None
except Exception as e: except Exception as e:
logger.error(f"❌ Error running Claude CLI: {e}") logger.error(f"❌ Error running Claude CLI: {e}")
return None return None