From 22e2edbc7faa25b898c0eb43013b95b5594ac8ce Mon Sep 17 00:00:00 2001 From: YusufKaraaslanSpyke Date: Fri, 30 Jan 2026 10:48:49 +0300 Subject: [PATCH] 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 --- src/skill_seekers/cli/config_enhancer.py | 180 +++++++++++++++-------- 1 file changed, 122 insertions(+), 58 deletions(-) diff --git a/src/skill_seekers/cli/config_enhancer.py b/src/skill_seekers/cli/config_enhancer.py index b033ef9..59d9d84 100644 --- a/src/skill_seekers/cli/config_enhancer.py +++ b/src/skill_seekers/cli/config_enhancer.py @@ -256,126 +256,184 @@ Focus on actionable insights that help developers understand and improve their c def _enhance_via_local(self, result: dict) -> dict: """Enhance configs using Claude Code CLI""" try: - # Create temporary prompt file - with tempfile.NamedTemporaryFile(mode="w", suffix=".md", delete=False) as f: - prompt_file = Path(f.name) - f.write(self._create_local_prompt(result)) + # Create a temporary directory for this enhancement session + with tempfile.TemporaryDirectory(prefix="config_enhance_") as temp_dir: + temp_path = Path(temp_dir) - # Create output file path - output_file = prompt_file.parent / f"{prompt_file.stem}_enhanced.json" + # Define output file path (absolute path that Claude will write to) + output_file = temp_path / "config_enhancement.json" - logger.info("🖥️ Launching Claude Code CLI for config analysis...") - logger.info("⏱️ This will take 30-60 seconds...") + # Create prompt file with the output path embedded + 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 - result_data = self._run_claude_cli(prompt_file, output_file) + logger.info("🖥️ Launching Claude Code CLI for config analysis...") + logger.info("⏱️ This will take 30-60 seconds...") - # Clean up - prompt_file.unlink() - if output_file.exists(): - output_file.unlink() + # Run Claude Code CLI + result_data = self._run_claude_cli(prompt_file, output_file, temp_path) - if result_data: - # Merge LOCAL enhancements - result["ai_enhancements"] = result_data - logger.info("✅ LOCAL enhancement complete") - return result - else: - logger.warning("⚠️ LOCAL enhancement produced no results") - return result + if result_data: + # Merge LOCAL enhancements + result["ai_enhancements"] = result_data + logger.info("✅ LOCAL enhancement complete") + return result + else: + logger.warning("⚠️ LOCAL enhancement produced no results") + return result except Exception as e: logger.error(f"❌ LOCAL enhancement failed: {e}") return result - def _create_local_prompt(self, result: dict) -> str: - """Create prompt file for Claude Code CLI""" + def _create_local_prompt(self, result: dict, output_file: Path) -> str: + """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", []) - # Format config data for Claude + # Format config data for Claude (limit to 15 files for reasonable prompt size) config_data = [] - for cf in config_files[:10]: + for cf in config_files[:15]: # Support both "type" (from config_extractor) and "config_type" (legacy) 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""" ### {cf["relative_path"]} ({config_type}) - Purpose: {cf["purpose"]} -- Patterns: {", ".join(cf.get("patterns", []))} -- Settings count: {len(cf.get("settings", []))} +- Patterns: {", ".join(cf.get("patterns", [])) or "none detected"} +- Settings: {len(cf.get("settings", []))} total +{chr(10).join(settings_preview) if settings_preview else " (no settings)"} """) 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)} ## 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 {{ "file_enhancements": [ {{ - "file_path": "path/to/file", - "explanation": "What this config does", - "best_practice": "Suggested improvements", - "security_concern": "Security issues (if any)", - "migration_suggestion": "Consolidation opportunities", - "context": "Pattern explanation" + "file_path": "relative/path/to/config.json", + "explanation": "Brief explanation of what this config file does", + "best_practice": "Suggested improvement or 'None'", + "security_concern": "Security issue if any, or 'None'", + "migration_suggestion": "Consolidation opportunity or 'None'", + "context": "What pattern or purpose this serves" }} ], "overall_insights": {{ "config_count": {len(config_files)}, "security_issues_found": 0, - "consolidation_opportunities": [], - "recommended_actions": [] + "consolidation_opportunities": ["List of suggestions"], + "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. Explain what each config does -2. Suggest best practices -3. Identify security concerns (hardcoded secrets, exposed credentials) -4. Suggest consolidation opportunities -5. Explain the detected patterns +1. Use the Write tool to create the JSON file at: {output_file} +2. Include an enhancement entry for each config file shown above +3. Focus on actionable insights: + - Explain what each config does in 1-2 sentences + - Identify any hardcoded secrets or security issues + - 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 - def _run_claude_cli(self, prompt_file: Path, _output_file: Path) -> dict | None: - """Run Claude Code CLI and wait for completion""" + def _run_claude_cli( + 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: - # 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( - ["claude", str(prompt_file)], + ["claude", "--dangerously-skip-permissions", str(prompt_file)], capture_output=True, text=True, 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: - 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 - # Try to find output file (Claude might save it with different name) - # Look for JSON files created in the last minute - import time + # Check if the expected output file was created + if output_file.exists(): + 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() potential_files = [] - for json_file in prompt_file.parent.glob("*.json"): - if current_time - json_file.stat().st_mtime < 120: # Created in last 2 minutes + for json_file in working_dir.glob("*.json"): + # Check if created recently (within last 2 minutes) + if current_time - json_file.stat().st_mtime < 120: potential_files.append(json_file) - # Try to load the most recent JSON file - for json_file in sorted(potential_files, key=lambda f: f.stat().st_mtime, reverse=True): + # 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 + ): try: with open(json_file) as f: data = json.load(f) @@ -386,11 +444,17 @@ Focus on actionable insights: continue 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 except subprocess.TimeoutExpired: logger.error("❌ Claude CLI timeout (5 minutes)") 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: logger.error(f"❌ Error running Claude CLI: {e}") return None