feat: Add LOCAL mode fallback for all AI enhancements

When no ANTHROPIC_API_KEY is set, ai_enhancer.py now falls back to LOCAL
mode (Claude Code CLI) instead of disabling AI enhancement entirely.

Changes to AIEnhancer base class:
- AUTO mode now falls back to LOCAL instead of disabling
- Added _check_claude_cli() to verify Claude CLI is available
- Added _call_claude_local() method using Claude Code CLI
- Refactored _call_claude() to dispatch to API or LOCAL mode
- 2 minute timeout per LOCAL call (reasonable for batch processing)

This affects all AI enhancements:
- Design pattern enhancement (C3.1) → LOCAL fallback 
- Test example enhancement (C3.2) → LOCAL fallback 
- Architectural analysis (C3.7) → LOCAL fallback 

Before (bad UX):
  ℹ️  AI enhancement disabled (no API key found)

After (good UX):
  ℹ️  No API key found, using LOCAL mode (Claude Code CLI)
   AI enhancement enabled (using LOCAL mode - Claude Code CLI)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
YusufKaraaslanSpyke
2026-01-30 11:15:54 +03:00
parent a8372b8f9d
commit 8a0c1f5fc6

View File

@@ -12,14 +12,23 @@ Features:
- Groups related examples into tutorials
- Identifies best practices
Modes:
- API mode: Uses Claude API (requires ANTHROPIC_API_KEY)
- LOCAL mode: Uses Claude Code CLI (no API key needed, uses your Claude Max plan)
- AUTO mode: Tries API first, falls back to LOCAL
Credits:
- Uses Claude AI (Anthropic) for analysis
- Graceful degradation if API unavailable
"""
import json
import logging
import os
import subprocess
import tempfile
from dataclasses import dataclass
from pathlib import Path
logger = logging.getLogger(__name__)
@@ -47,9 +56,9 @@ class AIEnhancer:
api_key: Anthropic API key (uses ANTHROPIC_API_KEY env if None)
enabled: Enable AI enhancement (default: True)
mode: Enhancement mode - "auto" (default), "api", or "local"
- "auto": Use API if key available, otherwise disable
- "auto": Use API if key available, otherwise fall back to LOCAL
- "api": Force API mode (fails if no key)
- "local": Use Claude Code local mode (opens terminal)
- "local": Use Claude Code CLI (no API key needed)
"""
self.enabled = enabled
self.mode = mode
@@ -61,15 +70,9 @@ class AIEnhancer:
if self.api_key:
self.mode = "api"
else:
# For now, disable if no API key
# LOCAL mode for batch processing is complex
self.mode = "disabled"
self.enabled = False
logger.info(" AI enhancement disabled (no API key found)")
logger.info(
" Set ANTHROPIC_API_KEY to enable, or use 'skill-seekers enhance' for SKILL.md"
)
return
# Fall back to LOCAL mode (Claude Code CLI)
self.mode = "local"
logger.info(" No API key found, using LOCAL mode (Claude Code CLI)")
if self.mode == "api" and self.enabled:
try:
@@ -84,23 +87,44 @@ class AIEnhancer:
self.client = anthropic.Anthropic(**client_kwargs)
logger.info("✅ AI enhancement enabled (using Claude API)")
except ImportError:
logger.warning("⚠️ anthropic package not installed. AI enhancement disabled.")
logger.warning(" Install with: pip install anthropic")
self.enabled = False
logger.warning("⚠️ anthropic package not installed, falling back to LOCAL mode")
self.mode = "local"
except Exception as e:
logger.warning(f"⚠️ Failed to initialize AI client: {e}")
logger.warning(f"⚠️ Failed to initialize API client: {e}, falling back to LOCAL mode")
self.mode = "local"
if self.mode == "local" and self.enabled:
# Verify Claude CLI is available
if self._check_claude_cli():
logger.info("✅ AI enhancement enabled (using LOCAL mode - Claude Code CLI)")
else:
logger.warning("⚠️ Claude Code CLI not found. AI enhancement disabled.")
logger.warning(" Install with: npm install -g @anthropic-ai/claude-code")
self.enabled = False
elif self.mode == "local":
# LOCAL mode requires Claude Code to be available
# For patterns/examples, this is less practical than API mode
logger.info(" LOCAL mode not yet supported for pattern/example enhancement")
logger.info(
" Use API mode (set ANTHROPIC_API_KEY) or 'skill-seekers enhance' for SKILL.md"
def _check_claude_cli(self) -> bool:
"""Check if Claude Code CLI is available"""
try:
result = subprocess.run(
["claude", "--version"],
capture_output=True,
text=True,
timeout=5,
)
self.enabled = False
return result.returncode == 0
except (FileNotFoundError, subprocess.TimeoutExpired):
return False
def _call_claude(self, prompt: str, max_tokens: int = 1000) -> str | None:
"""Call Claude API with error handling"""
"""Call Claude (API or LOCAL mode) with error handling"""
if self.mode == "api":
return self._call_claude_api(prompt, max_tokens)
elif self.mode == "local":
return self._call_claude_local(prompt)
return None
def _call_claude_api(self, prompt: str, max_tokens: int = 1000) -> str | None:
"""Call Claude API"""
if not self.client:
return None
@@ -115,6 +139,82 @@ class AIEnhancer:
logger.warning(f"⚠️ AI API call failed: {e}")
return None
def _call_claude_local(self, prompt: str) -> str | None:
"""Call Claude using LOCAL mode (Claude Code CLI)"""
try:
# Create a temporary directory for this enhancement
with tempfile.TemporaryDirectory(prefix="ai_enhance_") as temp_dir:
temp_path = Path(temp_dir)
# Create prompt file
prompt_file = temp_path / "prompt.md"
output_file = temp_path / "response.json"
# Write prompt with instructions to output JSON
full_prompt = f"""# AI Analysis Task
IMPORTANT: You MUST write your response as valid JSON to this file:
{output_file}
## Task
{prompt}
## Instructions
1. Analyze the input carefully
2. Generate the JSON response as specified
3. Use the Write tool to save the JSON to: {output_file}
4. The JSON must be valid and parseable
DO NOT include any explanation - just write the JSON file.
"""
prompt_file.write_text(full_prompt)
# Run Claude CLI
result = subprocess.run(
["claude", "--dangerously-skip-permissions", str(prompt_file)],
capture_output=True,
text=True,
timeout=120, # 2 minute timeout per call
cwd=str(temp_path),
)
if result.returncode != 0:
logger.warning(f"⚠️ Claude CLI returned error: {result.returncode}")
return None
# Read output file
if output_file.exists():
response_text = output_file.read_text()
# Try to extract JSON from response
try:
# Validate it's valid JSON
json.loads(response_text)
return response_text
except json.JSONDecodeError:
# Try to find JSON in the response
import re
json_match = re.search(r'\[[\s\S]*\]|\{[\s\S]*\}', response_text)
if json_match:
return json_match.group()
logger.warning("⚠️ Could not parse JSON from LOCAL response")
return None
else:
# Look for any JSON file created
for json_file in temp_path.glob("*.json"):
if json_file.name != "prompt.json":
return json_file.read_text()
logger.warning("⚠️ No output file from LOCAL mode")
return None
except subprocess.TimeoutExpired:
logger.warning("⚠️ Claude CLI timeout (2 minutes)")
return None
except Exception as e:
logger.warning(f"⚠️ LOCAL mode error: {e}")
return None
class PatternEnhancer(AIEnhancer):
"""Enhance design pattern detection with AI analysis"""
@@ -176,8 +276,6 @@ Format as JSON array matching input order. Be concise and actionable.
return patterns
try:
import json
analyses = json.loads(response)
# Merge AI analysis into patterns
@@ -268,8 +366,6 @@ Format as JSON array matching input order. Focus on educational value.
return examples
try:
import json
analyses = json.loads(response)
# Merge AI analysis into examples