#!/usr/bin/env python3 """ Tool Auto Loader - Dynamic tool discovery and documentation loading Features: 1. Auto-discover available tools from config 2. Load tool documentation from docstrings/comments 3. Cache tool info for performance 4. Provide tool recommendations based on task 5. Track tool usage patterns """ import json import re from pathlib import Path from typing import Dict, List, Set, Optional, Any from datetime import datetime import hashlib class ToolAutoLoader: """Dynamically discovers and loads tool documentation""" # Standard Claude Code tools STANDARD_TOOLS = { "Read": "Read file contents - use for examining code and documentation", "Write": "Write or create files - use to create new files or overwrite", "Edit": "Edit existing files with line-based replacements", "Glob": "Find files matching patterns - use for file discovery", "Grep": "Search file contents - use for code search", "Bash": "Execute shell commands - use for system operations", "Task": "Launch specialized agents - use for complex multi-step tasks", "TodoWrite": "Manage task lists and progress tracking", "AskUserQuestion": "Ask user for clarification or decisions", "WebSearch": "Search the web for current information", "WebFetch": "Fetch and analyze web content", "Skill": "Execute pre-configured skills", } # MCP Server tools (shared ecosystem) MCP_TOOLS = { "zen": "Deep reasoning, code review, debugging via Gemini 3", "dev-tools": "Browser debugging, screenshots, console access", "task-queue": "Queue and track async tasks", "sarlo-admin": "Server administration and configuration", "shared-projects-memory": "Shared knowledge graph across projects", "backup": "Create and manage backups", } def __init__(self, cache_dir: Optional[Path] = None): """Initialize tool loader Args: cache_dir: Optional directory for caching tool metadata """ self.cache_dir = cache_dir or Path("/tmp/.luzia-tool-cache") self.cache_dir.mkdir(parents=True, exist_ok=True) self.tools_cache: Dict[str, Dict] = {} self.usage_stats: Dict[str, int] = {} self.load_cache() def load_cache(self) -> None: """Load cached tool metadata from disk""" cache_file = self.cache_dir / "tools_cache.json" if cache_file.exists(): try: data = json.loads(cache_file.read_text()) self.tools_cache = data.get("tools", {}) self.usage_stats = data.get("usage", {}) except Exception as e: print(f"[Warning] Failed to load tool cache: {e}") def save_cache(self) -> None: """Save tool metadata to cache""" cache_file = self.cache_dir / "tools_cache.json" cache_file.write_text(json.dumps({ "tools": self.tools_cache, "usage": self.usage_stats, "timestamp": datetime.now().isoformat() }, indent=2)) def discover_tools(self, project_config: Dict[str, Any]) -> Dict[str, Dict]: """Discover available tools for a project Args: project_config: Project configuration dict Returns: Dict of available tools with metadata """ available = {} tools_list = project_config.get("tools", []) # Add standard tools for tool in tools_list: if tool in self.STANDARD_TOOLS: available[tool] = { "type": "standard", "description": self.STANDARD_TOOLS[tool], "category": self._categorize_tool(tool), "usage_count": self.usage_stats.get(tool, 0) } # Add MCP tools if available shared_tools = project_config.get("shared_tools", {}) for tool_name, description in shared_tools.items(): available[tool_name] = { "type": "mcp", "description": description, "category": self._categorize_tool(tool_name), "usage_count": self.usage_stats.get(tool_name, 0) } return available def _categorize_tool(self, tool: str) -> str: """Categorize a tool by function""" categories = { # File operations "Read": "file_read", "Write": "file_write", "Edit": "file_edit", "Glob": "file_search", "Grep": "file_search", # System operations "Bash": "system", "Task": "delegation", # Knowledge and reasoning "zen": "reasoning", "sarlo-admin": "administration", "shared-projects-memory": "knowledge", # User interaction "AskUserQuestion": "interaction", "WebSearch": "research", "WebFetch": "research", # Task management "TodoWrite": "planning", "task-queue": "async", # Execution "Skill": "special", } return categories.get(tool, "other") def recommend_tools(self, task: str, available_tools: Dict[str, Dict]) -> List[str]: """Recommend tools for a task Args: task: Task description available_tools: Dict of available tools Returns: Ordered list of recommended tool names """ recommendations = [] task_lower = task.lower() # Keyword-based recommendations patterns = { # File-based tasks r"(read|view|examine|check|look at|find|search|grep)": ["Read", "Glob", "Grep"], r"(create|write|generate|output)": ["Write", "Edit"], r"(modify|change|update|fix|edit)": ["Edit", "Read"], r"(delete|remove|clean)": ["Edit", "Bash"], # System tasks r"(run|execute|start|stop|restart|service|systemd|docker)": ["Bash"], r"(check|verify|status|health)": ["Bash", "Read"], # Code/analysis tasks r"(analyze|review|understand|debug|error)": ["Read", "Grep", "Bash"], r"(test|build|compile|lint)": ["Bash"], # Research/learning tasks r"(research|search|find|learn|lookup|stack|reference)": ["WebSearch", "WebFetch"], # Reasoning tasks r"(think|reason|analyze|design|plan)": ["zen"], # Project management r"(track|todo|progress|schedule)": ["TodoWrite"], # Knowledge/memory tasks r"(remember|store|document|knowledge)": ["shared-projects-memory"], } scored_tools = {} for pattern, tools in patterns.items(): if re.search(pattern, task_lower): for tool in tools: if tool in available_tools: scored_tools[tool] = scored_tools.get(tool, 0) + 1 # Sort by score and frequency of use recommendations = sorted( scored_tools.keys(), key=lambda t: (scored_tools[t], self.usage_stats.get(t, 0)), reverse=True ) # Add commonly used tools if not already included common = ["Read", "Bash", "Edit"] for tool in common: if tool in available_tools and tool not in recommendations: recommendations.append(tool) return recommendations[:5] # Top 5 recommendations def record_tool_usage(self, tool: str) -> None: """Record that a tool was used Args: tool: Tool name used """ self.usage_stats[tool] = self.usage_stats.get(tool, 0) + 1 def get_tool_documentation(self, tool: str) -> Dict[str, Any]: """Get detailed documentation for a tool Args: tool: Tool name Returns: Dict with tool documentation and usage info """ if tool in self.STANDARD_TOOLS: return { "name": tool, "type": "standard", "description": self.STANDARD_TOOLS[tool], "usage_count": self.usage_stats.get(tool, 0), "url": f"https://docs.anthropic.com/claude-code/{tool.lower()}" } if tool in self.MCP_TOOLS: return { "name": tool, "type": "mcp", "description": self.MCP_TOOLS[tool], "usage_count": self.usage_stats.get(tool, 0), } return { "name": tool, "type": "unknown", "description": "Unknown tool", "usage_count": self.usage_stats.get(tool, 0) } def generate_tool_reference(self, available_tools: List[str]) -> str: """Generate a markdown reference for available tools Args: available_tools: List of available tool names Returns: Markdown reference text """ sections = ["# Available Tools Reference\n"] # Group by category by_category = {} for tool in available_tools: doc = self.get_tool_documentation(tool) category = doc.get("type", "other") if category not in by_category: by_category[category] = [] by_category[category].append((tool, doc)) # Write sections for category in ["standard", "mcp", "other"]: if category in by_category: sections.append(f"\n## {category.title()} Tools\n") for tool, doc in by_category[category]: sections.append(f"**{tool}**: {doc['description']}") return "\n".join(sections) def load_project_documentation(self, project_path: Path) -> Dict[str, str]: """Load documentation from project files Args: project_path: Path to project Returns: Dict of documentation by topic """ docs = {} # Look for README, docs, etc doc_files = [ project_path / "README.md", project_path / "ARCHITECTURE.md", project_path / ".claude.md", project_path / "docs" / "index.md", ] for doc_file in doc_files: if doc_file.exists(): try: content = doc_file.read_text() # Extract first 500 chars as summary docs[doc_file.stem] = content[:500] except Exception as e: pass return docs def get_tools_by_category(self, available_tools: Dict[str, Dict]) -> Dict[str, List[str]]: """Group tools by category Args: available_tools: Dict of available tools Returns: Dict with category -> list of tool names """ by_category = {} for tool_name, tool_info in available_tools.items(): category = tool_info.get("category", "other") if category not in by_category: by_category[category] = [] by_category[category].append(tool_name) return by_category def get_top_tools(self, available_tools: Dict[str, Dict], limit: int = 5) -> List[str]: """Get most frequently used tools Args: available_tools: Dict of available tools limit: Max tools to return Returns: List of most-used tool names """ return sorted( available_tools.keys(), key=lambda t: self.usage_stats.get(t, 0), reverse=True )[:limit]