Coverage for src/ollamapy/skill_generator.py: 15%

332 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-09-01 12:29 -0400

1"""Automated skill generation system using AI with multi-step prompts and safe execution.""" 

2 

3import json 

4import subprocess 

5import tempfile 

6import os 

7from dataclasses import dataclass 

8from typing import Dict, List, Any, Optional, Tuple, Union 

9from datetime import datetime 

10 

11from .ai_query import AIQuery 

12from .ollama_client import OllamaClient 

13from .skills import Skill, SkillRegistry 

14from .analysis_engine import AnalysisEngine 

15 

16 

17@dataclass 

18class SkillPlan: 

19 """A plan for generating a skill, created step by step.""" 

20 

21 idea: str = "" 

22 name: str = "" 

23 description: str = "" 

24 role: str = "" 

25 vibe_test_phrases: Optional[List[str]] = None 

26 parameters: Optional[Dict[str, Dict[str, Any]]] = None 

27 function_code: str = "" 

28 

29 def __post_init__(self): 

30 if self.vibe_test_phrases is None: 

31 self.vibe_test_phrases = [] 

32 if self.parameters is None: 

33 self.parameters = {} 

34 

35 

36@dataclass 

37class SkillGenerationResult: 

38 """Result of a skill generation attempt.""" 

39 

40 success: bool 

41 skill: Optional[Skill] 

42 plan: Optional[SkillPlan] 

43 step_results: Dict[str, bool] 

44 errors: List[str] 

45 generation_time: float 

46 attempts: int 

47 vibe_test_passed: bool = False 

48 vibe_test_results: Optional[Dict[str, Any]] = None 

49 

50 

51class SafeCodeExecutor: 

52 """Safely executes generated code in isolation.""" 

53 

54 def __init__(self): 

55 self.timeout_seconds = 10 

56 self.allowed_imports = ["math", "json", "datetime", "os", "re", "random"] 

57 

58 def test_code_safely( 

59 self, function_code: str, test_params: Optional[Dict[str, Any]] = None 

60 ) -> Tuple[bool, str]: 

61 """Test generated code safely without crashing the main process. 

62 

63 Args: 

64 function_code: The Python function code to test 

65 test_params: Optional parameters to test with 

66 

67 Returns: 

68 Tuple of (success, output_or_error) 

69 """ 

70 # Create a test script 

71 # Properly indent the function code 

72 indented_code = "\n".join( 

73 " " + line if line.strip() else line 

74 for line in function_code.split("\n") 

75 ) 

76 

77 test_script = f""" 

78import json 

79import sys 

80import traceback 

81 

82# Mock the log function 

83logged_messages = [] 

84def log(message): 

85 logged_messages.append(str(message)) 

86 

87# Allow basic imports only 

88allowed_imports = {self.allowed_imports} 

89 

90try: 

91 # The generated function code 

92{indented_code} 

93  

94 # Test if execute function exists and is callable 

95 if 'execute' not in locals(): 

96 print("ERROR: No 'execute' function found") 

97 sys.exit(1) 

98  

99 if not callable(execute): 

100 print("ERROR: 'execute' is not callable") 

101 sys.exit(1) 

102  

103 # Try calling the function with test parameters 

104 test_params = {test_params or {}} 

105 if test_params: 

106 result = execute(**test_params) 

107 else: 

108 result = execute() 

109  

110 # Output the logged messages 

111 print("SUCCESS") 

112 for msg in logged_messages: 

113 print(f"LOG: {{msg}}") 

114  

115except Exception as e: 

116 print(f"ERROR: {{str(e)}}") 

117 traceback.print_exc() 

118 sys.exit(1) 

119""" 

120 

121 # Write to a temporary file 

122 with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: 

123 f.write(test_script) 

124 temp_file = f.name 

125 

126 try: 

127 # Run the test script in isolation 

128 result = subprocess.run( 

129 ["python", temp_file], 

130 capture_output=True, 

131 text=True, 

132 timeout=self.timeout_seconds, 

133 ) 

134 

135 output = result.stdout.strip() 

136 

137 if result.returncode == 0 and output.startswith("SUCCESS"): 

138 return True, output 

139 else: 

140 error_output = result.stderr if result.stderr else result.stdout 

141 return False, f"Code execution failed: {error_output}" 

142 

143 except subprocess.TimeoutExpired: 

144 return ( 

145 False, 

146 f"Code execution timed out after {self.timeout_seconds} seconds", 

147 ) 

148 except Exception as e: 

149 return False, f"Failed to test code: {str(e)}" 

150 finally: 

151 # Clean up temp file 

152 try: 

153 os.unlink(temp_file) 

154 except: 

155 pass 

156 

157 

158class IncrementalSkillGenerator: 

159 """Generates skills using multiple focused AI prompts.""" 

160 

161 def __init__(self, model: str = "gemma3:4b", analysis_model: Optional[str] = None): 

162 """Initialize the incremental skill generator.""" 

163 self.model = model 

164 self.analysis_model = analysis_model or model 

165 self.client = OllamaClient() 

166 self.ai_query = AIQuery(self.client, model) 

167 self.skill_registry = SkillRegistry() 

168 self.code_executor = SafeCodeExecutor() 

169 

170 def get_existing_skills_summary(self) -> str: 

171 """Get a summary of existing skills to avoid duplication.""" 

172 skills = self.skill_registry.get_all_skills() 

173 

174 if not skills: 

175 return "No existing skills found." 

176 

177 skill_summaries = [] 

178 for name, skill in skills.items(): 

179 role = skill.role if hasattr(skill, "role") else "unknown" 

180 skill_summaries.append(f"- {name} ({role}): {skill.description[:50]}...") 

181 

182 return f"Existing skills ({len(skills)} total):\n" + "\n".join( 

183 skill_summaries[:15] 

184 ) # Show max 15 for context 

185 

186 def generate_skill_idea(self) -> str: 

187 """Step 1: Generate a focused, simple, unique skill idea.""" 

188 existing_skills = self.get_existing_skills_summary() 

189 

190 prompt = f"""Generate ONE specific, simple, useful skill idea for an AI assistant. 

191 

192IMPORTANT GUIDELINES: 

1931. START SIMPLE - Choose basic, straightforward tasks first 

1942. FOLLOW DRY PRINCIPLE - Do NOT duplicate existing functionality 

1953. Be practical and focused on common user needs 

1964. Avoid complex operations that require multiple steps 

197 

198{existing_skills} 

199 

200Good SIMPLE skill ideas (start with these types): 

201- "Count words in text" 

202- "Reverse a string" 

203- "Convert temperature between Celsius and Fahrenheit" 

204- "Generate a simple random number within a range" 

205- "Check if a number is even or odd" 

206- "Remove whitespace from text" 

207- "Find the length of text" 

208 

209Avoid complex ideas like: 

210- Multi-step data analysis 

211- File system operations 

212- Web scraping or API calls 

213- Complex mathematical formulas 

214 

215Based on the existing skills above, generate ONE simple skill idea that doesn't already exist. 

216Respond with just the skill idea in one clear sentence. Keep it simple and basic.""" 

217 

218 result = self.ai_query.open(prompt, show_context=True) 

219 return result.content.strip() 

220 

221 def generate_skill_name(self, idea: str) -> str: 

222 """Step 2: Generate a skill name from the idea.""" 

223 existing_skills = self.get_existing_skills_summary() 

224 

225 prompt = f"""Based on this skill idea: "{idea}" 

226 

227Generate a good, UNIQUE function name for this skill. 

228 

229Rules: 

230- Use only lowercase letters, numbers, and underscores 

231- Be descriptive but concise  

232- Follow Python naming conventions 

233- Should be 2-3 words joined by underscores (keep it simple) 

234- Must NOT conflict with existing skill names 

235 

236Existing skill names to AVOID: 

237{existing_skills} 

238 

239Examples of good simple names: 

240- "count_words" 

241- "reverse_text" 

242- "check_even" 

243- "convert_temp" 

244 

245Respond with ONLY the function name, nothing else.""" 

246 

247 result = self.ai_query.single_word( 

248 question=f"Function name for: {idea}", show_context=True 

249 ) 

250 return result.word 

251 

252 def generate_skill_description(self, idea: str) -> str: 

253 """Step 3: Generate a clear skill description.""" 

254 existing_skills = self.get_existing_skills_summary() 

255 

256 prompt = f"""Based on this skill idea: "{idea}" 

257 

258Write a clear, simple description of when this skill should be used. 

259 

260The description should: 

261- Be 10-30 words (keep it simple) 

262- Explain WHEN to use this skill 

263- Be specific about what it does 

264- Use simple, clear language 

265- Be different from existing skills 

266 

267{existing_skills} 

268 

269Example good descriptions: 

270- "Use when the user wants to count words in a text" 

271- "Use when the user needs to reverse a string" 

272- "Use when the user wants to check if a number is even or odd" 

273 

274Respond with ONLY the description, nothing else.""" 

275 

276 result = self.ai_query.open(prompt, show_context=True) 

277 return result.content.strip().strip('"').strip("'") 

278 

279 def generate_skill_role(self, idea: str) -> str: 

280 """Step 4: Determine the skill's role category.""" 

281 prompt = f"""Based on this skill idea: "{idea}" 

282 

283What category does this skill belong to? 

284 

285Choose from these options: 

286A. text_processing 

287B. mathematics 

288C. data_analysis 

289D. file_operations 

290E. web_utilities 

291F. time_date 

292G. formatting 

293H. validation 

294I. general 

295 

296Respond with ONLY the letter (A, B, C, etc.)""" 

297 

298 result = self.ai_query.multiple_choice( 

299 question="What category does this skill belong to?", 

300 options=[ 

301 "text_processing", 

302 "mathematics", 

303 "data_analysis", 

304 "file_operations", 

305 "web_utilities", 

306 "time_date", 

307 "formatting", 

308 "validation", 

309 "general", 

310 ], 

311 show_context=True, 

312 ) 

313 return result.value 

314 

315 def generate_vibe_test_phrases(self, idea: str, name: str) -> List[str]: 

316 """Step 5: Generate vibe test phrases.""" 

317 existing_skills = self.get_existing_skills_summary() 

318 

319 prompt = f"""Based on this SIMPLE skill: "{idea}" (function name: {name}) 

320 

321Generate 5 realistic, simple things a user might say that should trigger this skill. 

322 

323Make them natural, varied user requests. For simple skills, users ask simple questions. 

324 

325IMPORTANT: Make sure these phrases are DIFFERENT from existing skills: 

326{existing_skills} 

327 

328For simple skills, users typically ask: 

329- Direct questions: "How many words are in this?" 

330- Simple commands: "Reverse this text" 

331- Basic requests: "Is 42 even?" 

332 

333Format as a simple numbered list: 

3341. First example 

3352. Second example  

3363. Third example 

3374. Fourth example 

3385. Fifth example 

339 

340Include the full list, nothing else.""" 

341 

342 result = self.ai_query.open(prompt) 

343 

344 # Parse the numbered list 

345 phrases = [] 

346 lines = result.content.strip().split("\n") 

347 for line in lines: 

348 line = line.strip() 

349 if line and (line[0].isdigit() or line.startswith("-")): 

350 # Remove number and clean up 

351 phrase = line.split(".", 1)[-1].strip() 

352 phrase = phrase.strip("-").strip() 

353 if phrase: 

354 phrases.append(phrase) 

355 

356 return phrases[:5] # Ensure max 5 phrases 

357 

358 def generate_parameters(self, idea: str, name: str) -> Dict[str, Dict[str, Any]]: 

359 """Step 6: Generate function parameters.""" 

360 prompt = f"""Based on this skill: "{idea}" (function name: {name}) 

361 

362What input parameters does this function need? 

363 

364If it needs parameters, respond in this EXACT JSON format: 

365{{"param_name": {{"type": "string", "description": "what this parameter is for", "required": true}}}} 

366 

367If it needs NO parameters, respond with: 

368{{}} 

369 

370Parameter types can only be: "string", "number", "boolean" 

371 

372Examples: 

373- For text processing: {{"text": {{"type": "string", "description": "The text to process", "required": true}}}} 

374- For calculations: {{"amount": {{"type": "number", "description": "The amount to calculate", "required": true}}, "rate": {{"type": "number", "description": "The interest rate", "required": true}}}} 

375- For no parameters: {{}} 

376 

377Respond with ONLY the JSON, nothing else.""" 

378 

379 result = self.ai_query.open(prompt) 

380 

381 try: 

382 # Clean the response and parse JSON 

383 content = result.content.strip() 

384 if content.startswith("```json"): 

385 content = content.split("```json")[1].split("```")[0] 

386 elif content.startswith("```"): 

387 content = content.split("```")[1].split("```")[0] 

388 

389 return json.loads(content) 

390 except: 

391 return {} # Default to no parameters if parsing fails 

392 

393 def generate_function_code(self, plan: SkillPlan) -> str: 

394 """Step 7: Generate the function code.""" 

395 params_desc = "" 

396 if plan.parameters: 

397 param_list = [] 

398 for name, spec in plan.parameters.items(): 

399 param_type = spec.get("type", "str") 

400 python_type = { 

401 "string": "str", 

402 "number": "float", 

403 "boolean": "bool", 

404 }.get(param_type, "str") 

405 required = spec.get("required", True) 

406 if required: 

407 param_list.append(f"{name}: {python_type}") 

408 else: 

409 param_list.append(f"{name}: {python_type} = None") 

410 params_desc = f"Parameters: {', '.join(param_list)}" 

411 else: 

412 params_desc = "No parameters required" 

413 

414 existing_skills = self.get_existing_skills_summary() 

415 

416 prompt = f"""Write a SIMPLE Python function for this basic skill: 

417 

418Idea: {plan.idea} 

419Function name: execute 

420Description: {plan.description} 

421{params_desc} 

422 

423IMPORTANT - Keep it SIMPLE: 

4241. Function MUST be named 'execute' 

4252. Use log("message") to output results (log function is available) 

4263. Include basic error handling with try/except 

4274. Add clear log messages explaining what you're doing 

4285. Keep it short and focused (5-15 lines max) 

4296. No complex logic or algorithms 

4307. No dangerous operations (no subprocess, eval, exec) 

4318. Only import basic modules if needed: math, json, re 

4329. Make it different from existing skills 

433 

434Existing skills to avoid duplicating: 

435{existing_skills} 

436 

437For simple skills, the code should be straightforward: 

438- Input validation 

439- Simple operation 

440- Clear logging 

441- Return result 

442 

443Write ONLY the function code, no explanations:""" 

444 

445 result = self.ai_query.open(prompt) 

446 

447 # Clean the response 

448 content = result.content.strip() 

449 if content.startswith("```python"): 

450 content = content.split("```python")[1].split("```")[0] 

451 elif content.startswith("```"): 

452 content = content.split("```")[1].split("```")[0] 

453 

454 return content.strip() 

455 

456 def build_skill_plan(self, idea: Optional[str] = None) -> SkillPlan: 

457 """Build a complete skill plan step by step.""" 

458 plan = SkillPlan() 

459 

460 try: 

461 # Show existing skills context 

462 existing_count = len(self.skill_registry.get_all_skills()) 

463 print(f"📚 Context: {existing_count} existing skills in registry") 

464 print("🎯 Following DRY principle - avoiding duplication") 

465 print("🚀 Prioritizing SIMPLE skills first") 

466 print() 

467 

468 # Step 1: Generate or use provided idea 

469 if idea: 

470 plan.idea = idea 

471 print(f"📝 Using provided idea: {idea}") 

472 print("🔍 Checking against existing skills for uniqueness...") 

473 else: 

474 print("🎯 Generating simple, unique skill idea...") 

475 plan.idea = self.generate_skill_idea() 

476 print(f"💡 Generated idea: {plan.idea}") 

477 print("✅ Verified uniqueness against existing skills") 

478 

479 # Step 2: Generate name 

480 print("🔧 Generating skill name...") 

481 plan.name = self.generate_skill_name(plan.idea) 

482 print(f"📛 Name: {plan.name}") 

483 

484 # Step 3: Generate description 

485 print("📝 Generating description...") 

486 plan.description = self.generate_skill_description(plan.idea) 

487 print(f"📋 Description: {plan.description}") 

488 

489 # Step 4: Generate role 

490 print("🏷️ Determining role category...") 

491 plan.role = self.generate_skill_role(plan.idea) 

492 print(f"🎭 Role: {plan.role}") 

493 

494 # Step 5: Generate vibe test phrases 

495 print("🧪 Generating vibe test phrases...") 

496 plan.vibe_test_phrases = self.generate_vibe_test_phrases( 

497 plan.idea, plan.name 

498 ) 

499 print(f"💬 Generated {len(plan.vibe_test_phrases)} test phrases") 

500 

501 # Step 6: Generate parameters 

502 print("⚙️ Generating parameters...") 

503 plan.parameters = self.generate_parameters(plan.idea, plan.name) 

504 param_count = len(plan.parameters) 

505 print( 

506 f"🔧 Parameters: {param_count} {'parameter' if param_count == 1 else 'parameters'}" 

507 ) 

508 

509 # Step 7: Generate function code 

510 print("💻 Generating simple function code...") 

511 plan.function_code = self.generate_function_code(plan) 

512 lines_count = len(plan.function_code.splitlines()) 

513 print(f"✅ Generated {lines_count} lines of code") 

514 

515 if lines_count > 20: 

516 print("⚠️ Code is longer than expected for a simple skill") 

517 else: 

518 print("✅ Code length appropriate for simple skill") 

519 

520 return plan 

521 

522 except Exception as e: 

523 print(f"❌ Error building plan: {e}") 

524 raise 

525 

526 def validate_and_test_plan(self, plan: SkillPlan) -> Tuple[bool, List[str]]: 

527 """Validate and safely test a skill plan.""" 

528 errors = [] 

529 

530 # Basic validation 

531 if not plan.name or not plan.name.replace("_", "").isalnum(): 

532 errors.append("Invalid skill name") 

533 

534 if not plan.description or len(plan.description) < 10: 

535 errors.append("Description too short") 

536 

537 if not plan.vibe_test_phrases or len(plan.vibe_test_phrases) < 3: 

538 errors.append("Need at least 3 vibe test phrases") 

539 

540 if not plan.function_code or "def execute" not in plan.function_code: 

541 errors.append("Function code missing or invalid") 

542 

543 if errors: 

544 return False, errors 

545 

546 # Test code safely 

547 print("🔒 Testing code safely...") 

548 success, output = self.code_executor.test_code_safely(plan.function_code, {}) 

549 

550 if not success: 

551 errors.append(f"Code execution failed: {output}") 

552 return False, errors 

553 

554 print("✅ Code tested successfully") 

555 return True, [] 

556 

557 def run_isolated_vibe_test(self, skill: Skill) -> Tuple[bool, Dict[str, Any]]: 

558 """Run vibe test for a single skill.""" 

559 print("🧪 Running vibe tests...") 

560 

561 # Create analysis engine 

562 analysis_engine = AnalysisEngine(self.analysis_model, self.client) 

563 

564 total_correct = 0 

565 total_tests = 0 

566 phrase_results = {} 

567 

568 for phrase in skill.vibe_test_phrases[:3]: # Test first 3 phrases 

569 correct = 0 

570 iterations = 2 # Keep it simple - 2 iterations per phrase 

571 

572 for i in range(iterations): 

573 try: 

574 prompt = f"""Should the '{skill.name}' skill be used for: "{phrase}"? 

575  

576Skill description: {skill.description} 

577 

578Answer only 'yes' or 'no'.""" 

579 

580 response = analysis_engine.ask_yes_no_question( 

581 prompt, show_context=False 

582 ) 

583 if response: 

584 correct += 1 

585 total_correct += 1 

586 total_tests += 1 

587 

588 except Exception as e: 

589 print(f"⚠️ Vibe test error: {e}") 

590 total_tests += 1 

591 

592 success_rate = (correct / iterations) * 100 if iterations > 0 else 0 

593 phrase_results[phrase] = { 

594 "correct": correct, 

595 "total": iterations, 

596 "success_rate": success_rate, 

597 } 

598 

599 status = "✅" if success_rate >= 50 else "❌" 

600 print(f" {status} '{phrase[:40]}...': {correct}/{iterations}") 

601 

602 overall_success = (total_correct / total_tests) * 100 if total_tests > 0 else 0 

603 passed = overall_success >= 50.0 

604 

605 print( 

606 f"🎯 Overall vibe test: {total_correct}/{total_tests} ({overall_success:.0f}%)" 

607 ) 

608 

609 return passed, { 

610 "total_correct": total_correct, 

611 "total_tests": total_tests, 

612 "success_rate": overall_success, 

613 "phrase_results": phrase_results, 

614 } 

615 

616 def generate_skill( 

617 self, idea: Optional[str] = None, max_attempts: int = 3 

618 ) -> SkillGenerationResult: 

619 """Generate a complete skill using the incremental approach.""" 

620 import time 

621 

622 start_time = time.time() 

623 

624 step_results = { 

625 "plan_created": False, 

626 "validation_passed": False, 

627 "skill_registered": False, 

628 "vibe_test_passed": False, 

629 } 

630 

631 for attempt in range(1, max_attempts + 1): 

632 print(f"\n🚀 Generation attempt {attempt}/{max_attempts}") 

633 print("=" * 50) 

634 

635 try: 

636 # Build the skill plan 

637 plan = self.build_skill_plan(idea) 

638 step_results["plan_created"] = True 

639 

640 # Validate and test the plan 

641 valid, errors = self.validate_and_test_plan(plan) 

642 if not valid: 

643 print(f"❌ Validation failed: {errors}") 

644 if attempt == max_attempts: 

645 return SkillGenerationResult( 

646 success=False, 

647 skill=None, 

648 plan=plan, 

649 step_results=step_results, 

650 errors=errors, 

651 generation_time=time.time() - start_time, 

652 attempts=attempt, 

653 ) 

654 continue 

655 

656 step_results["validation_passed"] = True 

657 

658 # Create and register the skill 

659 skill = Skill( 

660 name=plan.name, 

661 description=plan.description, 

662 vibe_test_phrases=plan.vibe_test_phrases or [], 

663 parameters=plan.parameters or {}, 

664 function_code=plan.function_code, 

665 verified=False, 

666 scope="local", 

667 role=plan.role, 

668 ) 

669 

670 # Try to register safely 

671 try: 

672 success = self.skill_registry.register_skill(skill) 

673 if not success: 

674 raise Exception("Failed to register skill") 

675 step_results["skill_registered"] = True 

676 print(f"✅ Skill '{skill.name}' registered successfully") 

677 except Exception as e: 

678 print(f"❌ Registration failed: {e}") 

679 continue 

680 

681 # Run vibe tests 

682 vibe_passed, vibe_results = self.run_isolated_vibe_test(skill) 

683 step_results["vibe_test_passed"] = vibe_passed 

684 

685 generation_time = time.time() - start_time 

686 

687 return SkillGenerationResult( 

688 success=True, 

689 skill=skill, 

690 plan=plan, 

691 step_results=step_results, 

692 errors=[], 

693 generation_time=generation_time, 

694 attempts=attempt, 

695 vibe_test_passed=vibe_passed, 

696 vibe_test_results=vibe_results, 

697 ) 

698 

699 except Exception as e: 

700 print(f"❌ Attempt {attempt} failed: {e}") 

701 if attempt == max_attempts: 

702 return SkillGenerationResult( 

703 success=False, 

704 skill=None, 

705 plan=None, 

706 step_results=step_results, 

707 errors=[f"Generation failed: {str(e)}"], 

708 generation_time=time.time() - start_time, 

709 attempts=attempt, 

710 ) 

711 

712 # Should not reach here 

713 return SkillGenerationResult( 

714 success=False, 

715 skill=None, 

716 plan=None, 

717 step_results=step_results, 

718 errors=["Max attempts exceeded"], 

719 generation_time=time.time() - start_time, 

720 attempts=max_attempts, 

721 ) 

722 

723 

724def run_skill_generation( 

725 model: str = "gemma3:4b", 

726 analysis_model: Optional[str] = None, 

727 count: int = 1, 

728 ideas: Optional[List[str]] = None, 

729 generate_report: bool = True, 

730) -> bool: 

731 """Main entry point for incremental skill generation with reporting. 

732 

733 Args: 

734 model: Generation model to use 

735 analysis_model: Analysis model for vibe tests 

736 count: Number of skills to generate 

737 ideas: Optional list of skill ideas 

738 generate_report: Whether to generate HTML documentation report 

739 

740 Returns: 

741 True if at least one skill was successfully generated 

742 """ 

743 from .skillgen_report import SkillGenerationReporter 

744 

745 print("🤖 OllamaPy Incremental Skill Generation") 

746 print("=" * 60) 

747 print(f"Generation model: {model}") 

748 print(f"Analysis model: {analysis_model or model}") 

749 print(f"Target count: {count}") 

750 print() 

751 print("🎯 Strategy: Generate SIMPLE skills first") 

752 print("🔄 Following DRY principle - no duplication") 

753 print("🚀 Multi-step prompts for better success rate") 

754 print("🔒 Safe execution with crash protection") 

755 print() 

756 

757 generator = IncrementalSkillGenerator(model, analysis_model) 

758 reporter = ( 

759 SkillGenerationReporter(model, analysis_model) if generate_report else None 

760 ) 

761 

762 successful_skills = [] 

763 failed_attempts = [] 

764 all_results = [] 

765 

766 for i in range(count): 

767 print(f"\n🎯 Generating skill {i+1}/{count}") 

768 print("=" * 40) 

769 

770 idea = ideas[i] if ideas and i < len(ideas) else None 

771 result = generator.generate_skill(idea, max_attempts=3) 

772 

773 # Store result for reporting 

774 all_results.append(result) 

775 if reporter: 

776 reporter.add_result( 

777 { 

778 "success": result.success, 

779 "skill": result.skill, 

780 "plan": result.plan, 

781 "errors": result.errors, 

782 "attempts": result.attempts, 

783 "generation_time": result.generation_time, 

784 "step_results": result.step_results, 

785 "vibe_test_passed": result.vibe_test_passed, 

786 "vibe_test_results": result.vibe_test_results, 

787 } 

788 ) 

789 

790 if result.success and result.skill: 

791 successful_skills.append(result.skill) 

792 status = "✅" if result.vibe_test_passed else "⚠️" 

793 print(f"\n{status} SUCCESS: {result.skill.name}") 

794 print(f" Description: {result.skill.description}") 

795 print(f" Role: {result.skill.role}") 

796 print(f" Vibe test: {'PASSED' if result.vibe_test_passed else 'FAILED'}") 

797 print( 

798 f" Time: {result.generation_time:.1f}s, Attempts: {result.attempts}" 

799 ) 

800 else: 

801 failed_attempts.append(result) 

802 print(f"\n❌ FAILED after {result.attempts} attempts") 

803 if result.errors: 

804 print(f" Errors: {', '.join(result.errors)}") 

805 

806 # Summary 

807 print(f"\n📊 Generation Complete!") 

808 print("=" * 40) 

809 print(f"✅ Successful: {len(successful_skills)}/{count}") 

810 print(f"❌ Failed: {len(failed_attempts)}/{count}") 

811 

812 if successful_skills: 

813 print(f"\n🎉 Generated Skills:") 

814 for skill in successful_skills: 

815 role_emoji = { 

816 "text_processing": "📝", 

817 "mathematics": "🔢", 

818 "formatting": "✨", 

819 "validation": "✅", 

820 "general": "🔧", 

821 }.get(skill.role, "🔧") 

822 print(f" {role_emoji} {skill.name}: {skill.description}") 

823 

824 print( 

825 f"\n💡 Tip: These simple skills provide a foundation for more complex ones!" 

826 ) 

827 print(f"🔄 Next generations will continue following DRY principle") 

828 else: 

829 print(f"\n😞 No skills were successfully generated") 

830 print(f"💡 Try again - the system learns from each attempt") 

831 

832 # Generate report if requested 

833 if generate_report and reporter: 

834 print(f"\n📄 Generating skill documentation report...") 

835 try: 

836 report_path = reporter.generate_report() 

837 print(f"✅ Report generated: {report_path}") 

838 print(f"📂 Open the report in your browser to view:") 

839 print(f" file://{os.path.abspath(report_path)}") 

840 except Exception as e: 

841 print(f"⚠️ Failed to generate report: {e}") 

842 

843 return len(successful_skills) > 0