#!/usr/bin/env python3 """ Agent Tooling Optimizer - Meta-agent for optimizing repository tooling usage. This tool analyzes repository tooling, identifies missed optimization opportunities, and provides actionable recommendations to improve agent effectiveness. """ import json import os import re import subprocess import sys from dataclasses import dataclass, asdict from pathlib import Path from typing import Dict, List, Optional, Set, Tuple import argparse from datetime import datetime @dataclass class ToolMetadata: """Metadata for a discovered tool.""" name: str type: str # 'makefile', 'cli', 'script', 'workflow' description: str usage: str file_path: str examples: List[str] dependencies: List[str] category: str @dataclass class MissedOpportunity: """Represents a missed tooling opportunity.""" context: str available_tool: str manual_approach: str efficiency_gain: str recommendation: str @dataclass class OptimizationReport: """Comprehensive optimization report.""" timestamp: str total_tools: int tools_by_category: Dict[str, int] missed_opportunities: List[MissedOpportunity] recommendations: List[str] quick_wins: List[str] class ToolingDiscoveryEngine: """Discovers and catalogs repository tooling.""" def __init__(self, repo_path: str = "."): self.repo_path = Path(repo_path) self.tools: List[ToolMetadata] = [] def discover_all_tools(self) -> List[ToolMetadata]: """Discover all available tools in the repository.""" self.tools = [] # Discover different types of tools self.tools.extend(self._discover_makefile_targets()) self.tools.extend(self._discover_cli_commands()) self.tools.extend(self._discover_scripts()) self.tools.extend(self._discover_workflow_automation()) return self.tools def _discover_makefile_targets(self) -> List[ToolMetadata]: """Discover Makefile targets and their purposes.""" makefile_path = self.repo_path / "Makefile" tools = [] if not makefile_path.exists(): return tools try: with open(makefile_path, 'r') as f: content = f.read() # Parse Makefile targets with descriptions target_pattern = r'^([a-zA-Z0-9_-]+):\s*(?:[^#\n]*?)(?:#\s*(.+?))?$' for match in re.finditer(target_pattern, content, re.MULTILINE): target_name = match.group(1) description = match.group(2) or "No description available" # Skip internal targets if target_name.startswith('.') or target_name in ['all', 'clean']: continue # Get usage example usage = f"make {target_name}" # Categorize target category = self._categorize_makefile_target(target_name) tools.append(ToolMetadata( name=target_name, type="makefile", description=description.strip(), usage=usage, file_path=str(makefile_path), examples=[usage], dependencies=[], category=category )) except Exception as e: print(f"Error parsing Makefile: {e}") return tools def _discover_cli_commands(self) -> List[ToolMetadata]: """Discover CLI commands and subcommands.""" tools = [] # Discover main CLI cli_path = self.repo_path / "markitect" / "cli.py" if cli_path.exists(): tools.extend(self._parse_click_cli(cli_path)) # Discover issue management CLI issues_cli_path = self.repo_path / "markitect" / "issues" / "commands.py" if issues_cli_path.exists(): tools.extend(self._parse_issues_cli(issues_cli_path)) return tools def _discover_scripts(self) -> List[ToolMetadata]: """Discover utility scripts.""" tools = [] # Common script directories script_dirs = ["scripts", "tools", "bin", "utils"] for script_dir in script_dirs: script_path = self.repo_path / script_dir if script_path.exists(): for script_file in script_path.glob("*.py"): if script_file.name.startswith("_"): continue description = self._extract_script_description(script_file) tools.append(ToolMetadata( name=script_file.stem, type="script", description=description, usage=f"python {script_file}", file_path=str(script_file), examples=[f"python {script_file} --help"], dependencies=[], category="utility" )) return tools def _discover_workflow_automation(self) -> List[ToolMetadata]: """Discover workflow automation tools.""" tools = [] # GitHub Actions gh_actions_path = self.repo_path / ".github" / "workflows" if gh_actions_path.exists(): for workflow_file in gh_actions_path.glob("*.yml"): description = self._extract_workflow_description(workflow_file) tools.append(ToolMetadata( name=workflow_file.stem, type="workflow", description=description, usage=f"Triggered by: {self._get_workflow_triggers(workflow_file)}", file_path=str(workflow_file), examples=[], dependencies=[], category="automation" )) # TDD AI CLI tddai_path = self.repo_path / "tddai_cli.py" if tddai_path.exists(): tools.append(ToolMetadata( name="tddai_cli", type="workflow", description="TDD8 workflow automation and issue management", usage="python tddai_cli.py [command]", file_path=str(tddai_path), examples=["python tddai_cli.py tdd-start 61", "python tddai_cli.py list-issues"], dependencies=[], category="development" )) return tools def _categorize_makefile_target(self, target_name: str) -> str: """Categorize Makefile targets.""" if any(keyword in target_name.lower() for keyword in ['test', 'check']): return "testing" elif any(keyword in target_name.lower() for keyword in ['build', 'compile', 'install']): return "build" elif any(keyword in target_name.lower() for keyword in ['issue', 'gitea']): return "issue_management" elif any(keyword in target_name.lower() for keyword in ['db', 'data', 'schema']): return "database" elif any(keyword in target_name.lower() for keyword in ['lint', 'format', 'quality']): return "code_quality" else: return "general" def _parse_click_cli(self, cli_path: Path) -> List[ToolMetadata]: """Parse Click CLI for commands.""" tools = [] try: with open(cli_path, 'r') as f: content = f.read() # Find Click commands and groups command_pattern = r'@click\.command\(\)\s*def\s+([a-zA-Z_][a-zA-Z0-9_]*)\([^)]*\):' group_pattern = r'@click\.group\(\)\s*def\s+([a-zA-Z_][a-zA-Z0-9_]*)\([^)]*\):' for match in re.finditer(command_pattern, content): command_name = match.group(1) description = self._extract_function_docstring(content, match.start()) tools.append(ToolMetadata( name=f"markitect {command_name}", type="cli", description=description, usage=f"markitect {command_name}", file_path=str(cli_path), examples=[f"markitect {command_name} --help"], dependencies=[], category="cli" )) except Exception as e: print(f"Error parsing CLI: {e}") return tools def _parse_issues_cli(self, cli_path: Path) -> List[ToolMetadata]: """Parse issues CLI for commands.""" tools = [] try: with open(cli_path, 'r') as f: content = f.read() # Find issue management commands issue_commands = ['list', 'show', 'create', 'comment', 'close'] for command in issue_commands: tools.append(ToolMetadata( name=f"markitect issues {command}", type="cli", description=f"Issue management: {command} operations", usage=f"markitect issues {command}", file_path=str(cli_path), examples=[f"markitect issues {command} --help"], dependencies=[], category="issue_management" )) except Exception as e: print(f"Error parsing issues CLI: {e}") return tools def _extract_script_description(self, script_path: Path) -> str: """Extract description from script docstring.""" try: with open(script_path, 'r') as f: content = f.read() # Look for module docstring docstring_pattern = r'"""([^"]+)"""' match = re.search(docstring_pattern, content) if match: return match.group(1).strip().split('\n')[0] except Exception: pass return "Utility script" def _extract_function_docstring(self, content: str, start_pos: int) -> str: """Extract function docstring from source.""" # Simple extraction - could be improved lines = content[start_pos:].split('\n') for i, line in enumerate(lines): if '"""' in line: if line.count('"""') == 2: return line.split('"""')[1].strip() # Multi-line docstring docstring_lines = [] for j in range(i + 1, len(lines)): if '"""' in lines[j]: break docstring_lines.append(lines[j].strip()) return ' '.join(docstring_lines) return "No description available" def _extract_workflow_description(self, workflow_path: Path) -> str: """Extract description from GitHub workflow.""" try: with open(workflow_path, 'r') as f: content = f.read() # Look for name or description in YAML name_pattern = r'name:\s*["\']*([^"\'\n]+)["\']*' match = re.search(name_pattern, content) if match: return match.group(1).strip() except Exception: pass return "GitHub workflow" def _get_workflow_triggers(self, workflow_path: Path) -> str: """Get workflow trigger information.""" try: with open(workflow_path, 'r') as f: content = f.read() # Look for 'on:' section on_pattern = r'on:\s*\n((?:\s+.+\n?)+)' match = re.search(on_pattern, content) if match: return match.group(1).strip().replace('\n', ', ') except Exception: pass return "Unknown" class SessionAnalyzer: """Analyzes coding sessions for tooling optimization opportunities.""" def __init__(self, repo_path: str = "."): self.repo_path = Path(repo_path) def analyze_recent_activities(self) -> List[MissedOpportunity]: """Analyze recent activities for missed tooling opportunities.""" opportunities = [] # Analyze git commits for patterns opportunities.extend(self._analyze_git_commits()) # Analyze file modifications opportunities.extend(self._analyze_file_patterns()) # Look for manual implementations opportunities.extend(self._find_manual_implementations()) return opportunities def _analyze_git_commits(self) -> List[MissedOpportunity]: """Analyze git commits for tooling opportunities.""" opportunities = [] try: # Get recent commits result = subprocess.run(['git', 'log', '--oneline', '-20'], capture_output=True, text=True, cwd=self.repo_path) commits = result.stdout.strip().split('\n') for commit in commits: if 'test' in commit.lower() and 'make test' not in commit: opportunities.append(MissedOpportunity( context=f"Commit: {commit}", available_tool="make test", manual_approach="Manual test execution", efficiency_gain="Standardized test running with proper setup", recommendation="Use 'make test' for consistent test execution" )) except Exception as e: print(f"Error analyzing git commits: {e}") return opportunities def _analyze_file_patterns(self) -> List[MissedOpportunity]: """Analyze file modification patterns.""" opportunities = [] # Check for direct database operations try: result = subprocess.run(['find', '.', '-name', '*.py', '-exec', 'grep', '-l', 'sqlite3', '{}', ';'], capture_output=True, text=True, cwd=self.repo_path) if result.stdout.strip(): opportunities.append(MissedOpportunity( context="Direct SQLite usage found", available_tool="markitect db-query, markitect db-schema", manual_approach="Direct database queries", efficiency_gain="Standardized database operations with error handling", recommendation="Use CLI database commands for database operations" )) except Exception: pass return opportunities def _find_manual_implementations(self) -> List[MissedOpportunity]: """Find patterns of manual implementations.""" opportunities = [] # Check for manual issue management python_files = list(self.repo_path.glob("**/*.py")) for py_file in python_files: try: with open(py_file, 'r') as f: content = f.read() # Look for manual HTTP requests to Gitea if 'requests.get' in content and 'gitea' in content.lower(): opportunities.append(MissedOpportunity( context=f"Manual HTTP requests in {py_file}", available_tool="markitect issues list/show/create", manual_approach="Direct HTTP requests", efficiency_gain="Error handling, retry logic, authentication", recommendation="Use issue management CLI commands" )) # Look for manual file operations that could use CLI if 'json.dump' in content and 'schema' in content: opportunities.append(MissedOpportunity( context=f"Manual JSON schema operations in {py_file}", available_tool="markitect schema-generate", manual_approach="Manual JSON manipulation", efficiency_gain="Validation, formatting, metaschema compliance", recommendation="Use schema generation CLI commands" )) except Exception: continue return opportunities class AgentPrimingOptimizer: """Optimizes agent priming for better tool utilization.""" def __init__(self, tools: List[ToolMetadata]): self.tools = tools def generate_tool_context(self) -> str: """Generate optimized tool context for agent priming.""" context_parts = [] # Group tools by category tools_by_category = {} for tool in self.tools: category = tool.category if category not in tools_by_category: tools_by_category[category] = [] tools_by_category[category].append(tool) context_parts.append("# Available Repository Tooling\n") context_parts.append("The following tools are available in this repository. ") context_parts.append("Always prefer using these existing tools over manual implementation.\n") for category, category_tools in tools_by_category.items(): context_parts.append(f"\n## {category.replace('_', ' ').title()} Tools\n") for tool in category_tools: context_parts.append(f"- **{tool.name}**: {tool.description}") context_parts.append(f" - Usage: `{tool.usage}`") if tool.examples: context_parts.append(f" - Example: `{tool.examples[0]}`") context_parts.append("") # Add usage guidelines context_parts.append("\n## Tool Usage Guidelines\n") context_parts.append("1. **Always check for existing tools** before implementing solutions manually") context_parts.append("2. **Use Makefile targets** for common development tasks") context_parts.append("3. **Prefer CLI commands** for data operations and issue management") context_parts.append("4. **Check scripts directory** for utility functions") context_parts.append("5. **Ask for help** if unsure which tool to use for a specific task") return "\n".join(context_parts) def create_decision_tree(self) -> str: """Create a decision tree for tool selection.""" decision_tree = """ # Tool Selection Decision Tree ## For Testing Tasks - Running tests → `make test` - Validating specific component → `pytest specific_test.py` - Code quality checks → `make lint` or `make format` ## For Database Operations - Querying data → `markitect db-query "SELECT ..."` - Schema inspection → `markitect db-schema` - Database statistics → `markitect db-stats` ## For Issue Management - Listing issues → `markitect issues list` or `make list-issues` - Viewing issue details → `markitect issues show NUM` or `make show-issue NUM=X` - Creating issues → `markitect issues create` - Closing issues → `markitect issues close NUM` or `make close-issue NUM=X` ## For Schema Operations - Generating schemas → `markitect schema-generate` - Validating documents → `markitect validate` - Creating stubs → `markitect generate-stub` ## For Development Workflow - Starting TDD cycle → `make tdd-start NUM=X` - Project status → `markitect db-stats` - File analysis → `markitect analyze` """ return decision_tree def generate_quick_reference(self) -> str: """Generate a quick reference guide.""" ref_parts = [] ref_parts.append("# Quick Tool Reference\n") # Most common commands common_commands = [ ("make test", "Run all tests"), ("make list-issues", "List all issues"), ("markitect issues show NUM", "Show issue details"), ("markitect schema-generate file.md", "Generate schema from markdown"), ("markitect db-query 'SQL'", "Query database"), ("make tdd-start NUM=X", "Start TDD cycle for issue X"), ] ref_parts.append("## Most Common Commands\n") for cmd, desc in common_commands: ref_parts.append(f"- `{cmd}` - {desc}") return "\n".join(ref_parts) def main(): """Main entry point for the agent tooling optimizer.""" parser = argparse.ArgumentParser(description="Agent Tooling Optimizer") parser.add_argument("command", choices=["discover", "analyze", "optimize", "report"], help="Command to execute") parser.add_argument("--format", choices=["json", "markdown", "text"], default="markdown", help="Output format") parser.add_argument("--output", help="Output file (default: stdout)") args = parser.parse_args() # Initialize components discovery_engine = ToolingDiscoveryEngine() session_analyzer = SessionAnalyzer() if args.command == "discover": # Discover all tools tools = discovery_engine.discover_all_tools() if args.format == "json": output = json.dumps([asdict(tool) for tool in tools], indent=2) else: output = f"# Repository Tooling Catalog\n\nFound {len(tools)} tools:\n\n" for tool in tools: output += f"## {tool.name} ({tool.type})\n" output += f"- **Description**: {tool.description}\n" output += f"- **Usage**: `{tool.usage}`\n" output += f"- **Category**: {tool.category}\n\n" elif args.command == "analyze": # Analyze for missed opportunities opportunities = session_analyzer.analyze_recent_activities() if args.format == "json": output = json.dumps([asdict(opp) for opp in opportunities], indent=2) else: output = f"# Tooling Optimization Opportunities\n\nFound {len(opportunities)} opportunities:\n\n" for i, opp in enumerate(opportunities, 1): output += f"## Opportunity {i}\n" output += f"- **Context**: {opp.context}\n" output += f"- **Available Tool**: {opp.available_tool}\n" output += f"- **Current Approach**: {opp.manual_approach}\n" output += f"- **Potential Gain**: {opp.efficiency_gain}\n" output += f"- **Recommendation**: {opp.recommendation}\n\n" elif args.command == "optimize": # Generate optimized priming tools = discovery_engine.discover_all_tools() optimizer = AgentPrimingOptimizer(tools) output = optimizer.generate_tool_context() output += "\n\n" + optimizer.create_decision_tree() output += "\n\n" + optimizer.generate_quick_reference() elif args.command == "report": # Generate comprehensive report tools = discovery_engine.discover_all_tools() opportunities = session_analyzer.analyze_recent_activities() # Create comprehensive report report = OptimizationReport( timestamp=datetime.now().isoformat(), total_tools=len(tools), tools_by_category={}, missed_opportunities=opportunities, recommendations=[], quick_wins=[] ) # Calculate tools by category for tool in tools: category = tool.category report.tools_by_category[category] = report.tools_by_category.get(category, 0) + 1 # Generate recommendations report.recommendations = [ "Include tool inventory in agent context", "Use decision trees for tool selection", "Implement tool usage monitoring", "Create quick reference guides", "Establish tool usage best practices" ] # Identify quick wins report.quick_wins = [ "Add Makefile targets to agent context", "Document CLI command patterns", "Create tool selection guidelines", "Implement tool discovery automation" ] if args.format == "json": output = json.dumps(asdict(report), indent=2) else: output = f"# Agent Tooling Optimization Report\n\n" output += f"**Generated**: {report.timestamp}\n" output += f"**Total Tools Found**: {report.total_tools}\n\n" output += "## Tools by Category\n" for category, count in report.tools_by_category.items(): output += f"- {category.replace('_', ' ').title()}: {count}\n" output += f"\n## Missed Opportunities ({len(report.missed_opportunities)})\n" for opp in report.missed_opportunities[:5]: # Top 5 output += f"- {opp.context}: Use {opp.available_tool}\n" output += "\n## Recommendations\n" for rec in report.recommendations: output += f"- {rec}\n" output += "\n## Quick Wins\n" for win in report.quick_wins: output += f"- {win}\n" # Output results if args.output: with open(args.output, 'w') as f: f.write(output) print(f"Output written to {args.output}") else: print(output) if __name__ == "__main__": main()