Refactor cockpit to use DockerTmuxController pattern

Based on claude-code-tools TmuxCLIController, this refactor:

- Added DockerTmuxController class for robust tmux session management
- Implements send_keys() with configurable delay_enter
- Implements capture_pane() for output retrieval
- Implements wait_for_prompt() for pattern-based completion detection
- Implements wait_for_idle() for content-hash-based idle detection
- Implements wait_for_shell_prompt() for shell prompt detection

Also includes workflow improvements:
- Pre-task git snapshot before agent execution
- Post-task commit protocol in agent guidelines

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
admin
2026-01-14 10:42:16 -03:00
commit ec33ac1936
265 changed files with 92011 additions and 0 deletions

344
lib/tool_auto_loader.py Normal file
View File

@@ -0,0 +1,344 @@
#!/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]