#!/usr/bin/env python3 """AI Code Review Agent - Main Entry Point This is the main CLI for running AI code review agents. Can be invoked directly or through CI/CD workflows. """ import argparse import json import logging import os import sys import yaml # Add the package to path sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from agents.issue_agent import IssueAgent from agents.pr_agent import PRAgent from agents.codebase_agent import CodebaseAgent from agents.chat_agent import ChatAgent from dispatcher import Dispatcher, get_dispatcher def setup_logging(verbose: bool = False): """Configure logging.""" level = logging.DEBUG if verbose else logging.INFO logging.basicConfig( level=level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", ) def load_config(config_path: str | None = None) -> dict: """Load configuration from file.""" if config_path and os.path.exists(config_path): with open(config_path) as f: return yaml.safe_load(f) default_path = os.path.join(os.path.dirname(__file__), "config.yml") if os.path.exists(default_path): with open(default_path) as f: return yaml.safe_load(f) return {} def run_pr_review(args, config: dict): """Run PR review agent.""" from agents.base_agent import AgentContext agent = PRAgent(config=config) # Build context from environment or arguments owner, repo = args.repo.split("/") pr_number = args.pr_number context = AgentContext( owner=owner, repo=repo, event_type="pull_request", event_data={ "action": "opened", "pull_request": { "number": pr_number, "title": args.title or f"PR #{pr_number}", }, }, config=config, ) result = agent.run(context) if result.success: print(f"✅ PR Review Complete: {result.message}") print(f" Actions: {', '.join(result.actions_taken)}") else: print(f"❌ PR Review Failed: {result.message}") if result.error: print(f" Error: {result.error}") sys.exit(1) def run_issue_triage(args, config: dict): """Run issue triage agent.""" from agents.base_agent import AgentContext from clients.gitea_client import GiteaClient agent = IssueAgent(config=config) owner, repo = args.repo.split("/") issue_number = args.issue_number # Fetch the actual issue data from Gitea API to get the complete body gitea = GiteaClient() try: issue_data = gitea.get_issue(owner, repo, issue_number) except Exception as e: print(f"❌ Failed to fetch issue: {e}") sys.exit(1) context = AgentContext( owner=owner, repo=repo, event_type="issues", event_data={ "action": "opened", "issue": issue_data, }, config=config, ) result = agent.run(context) if result.success: print(f"✅ Issue Triage Complete: {result.message}") print(f" Actions: {', '.join(result.actions_taken)}") else: print(f"❌ Issue Triage Failed: {result.message}") if result.error: print(f" Error: {result.error}") sys.exit(1) def run_issue_comment(args, config: dict): """Handle @ai-bot command in issue comment.""" from agents.base_agent import AgentContext agent = IssueAgent(config=config) owner, repo = args.repo.split("/") issue_number = args.issue_number # Fetch the actual issue data from Gitea API from clients.gitea_client import GiteaClient gitea = GiteaClient() try: issue_data = gitea.get_issue(owner, repo, issue_number) except Exception as e: print(f"❌ Failed to fetch issue: {e}") sys.exit(1) context = AgentContext( owner=owner, repo=repo, event_type="issue_comment", event_data={ "action": "created", "issue": issue_data, "comment": { "body": args.comment_body, }, }, config=config, ) result = agent.run(context) if result.success: print(f"✅ Comment Response Complete: {result.message}") print(f" Actions: {', '.join(result.actions_taken)}") else: print(f"❌ Comment Response Failed: {result.message}") if result.error: print(f" Error: {result.error}") sys.exit(1) def run_codebase_analysis(args, config: dict): """Run codebase analysis agent.""" from agents.base_agent import AgentContext agent = CodebaseAgent(config=config) owner, repo = args.repo.split("/") context = AgentContext( owner=owner, repo=repo, event_type="workflow_dispatch", event_data={}, config=config, ) result = agent.run(context) if result.success: print(f"✅ Codebase Analysis Complete: {result.message}") print(f" Health Score: {result.data.get('health_score', 'N/A')}") print(f" Actions: {', '.join(result.actions_taken)}") else: print(f"❌ Codebase Analysis Failed: {result.message}") if result.error: print(f" Error: {result.error}") sys.exit(1) def run_chat(args, config: dict): """Run interactive chat with the Bartender bot.""" from agents.base_agent import AgentContext from clients.gitea_client import GiteaClient agent = ChatAgent(config=config) owner, repo = args.repo.split("/") # Build context event_data = {"message": args.message} # If issue number provided, add issue context if args.issue_number: gitea = GiteaClient() try: issue_data = gitea.get_issue(owner, repo, args.issue_number) event_data["issue"] = issue_data event_data["issue_number"] = args.issue_number except Exception as e: print(f"Warning: Could not fetch issue #{args.issue_number}: {e}") context = AgentContext( owner=owner, repo=repo, event_type="chat", event_data=event_data, config=config, ) result = agent.run(context) if result.success: print(f"\n🍸 Bartender says:\n") print(result.data.get("response", "")) print() if result.data.get("tools_used"): print(f" [Tools used: {', '.join(result.data['tools_used'])}]") else: print(f"❌ Chat Failed: {result.message}") if result.error: print(f" Error: {result.error}") sys.exit(1) def run_webhook_dispatch(args, config: dict): """Dispatch a webhook event.""" dispatcher = get_dispatcher() # Register all agents dispatcher.register_agent(IssueAgent(config=config)) dispatcher.register_agent(PRAgent(config=config)) dispatcher.register_agent(CodebaseAgent(config=config)) dispatcher.register_agent(ChatAgent(config=config)) # Parse event data event_data = json.loads(args.event_data) owner, repo = args.repo.split("/") result = dispatcher.dispatch( event_type=args.event_type, event_data=event_data, owner=owner, repo=repo, ) print(f"Dispatched event: {result.event_type}") print(f"Agents run: {result.agents_run}") for i, agent_result in enumerate(result.results): status = "✅" if agent_result.success else "❌" print(f" {status} {result.agents_run[i]}: {agent_result.message}") if result.errors: sys.exit(1) def main(): """Main entry point.""" parser = argparse.ArgumentParser( description="AI Code Review Agent", formatter_class=argparse.RawDescriptionHelpFormatter, ) parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output") parser.add_argument("-c", "--config", help="Path to config file") subparsers = parser.add_subparsers(dest="command", help="Available commands") # PR review command pr_parser = subparsers.add_parser("pr", help="Review a pull request") pr_parser.add_argument("repo", help="Repository (owner/repo)") pr_parser.add_argument("pr_number", type=int, help="PR number") pr_parser.add_argument("--title", help="PR title (optional)") # Issue triage command issue_parser = subparsers.add_parser("issue", help="Triage an issue") issue_parser.add_argument("repo", help="Repository (owner/repo)") issue_parser.add_argument("issue_number", type=int, help="Issue number") issue_parser.add_argument("--title", help="Issue title") issue_parser.add_argument("--body", help="Issue body") # Issue comment command (for @ai-bot mentions) comment_parser = subparsers.add_parser("comment", help="Respond to @ai-bot command") comment_parser.add_argument("repo", help="Repository (owner/repo)") comment_parser.add_argument("issue_number", type=int, help="Issue number") comment_parser.add_argument("comment_body", help="Comment body with @ai-bot command") # Codebase analysis command codebase_parser = subparsers.add_parser("codebase", help="Analyze codebase") codebase_parser.add_argument("repo", help="Repository (owner/repo)") # Chat command (Bartender) chat_parser = subparsers.add_parser("chat", help="Chat with Bartender bot") chat_parser.add_argument("repo", help="Repository (owner/repo)") chat_parser.add_argument("message", help="Message to send to Bartender") chat_parser.add_argument( "--issue", dest="issue_number", type=int, help="Optional issue number to post response to" ) # Webhook dispatch command webhook_parser = subparsers.add_parser("dispatch", help="Dispatch webhook event") webhook_parser.add_argument("repo", help="Repository (owner/repo)") webhook_parser.add_argument("event_type", help="Event type") webhook_parser.add_argument("event_data", help="Event data (JSON)") args = parser.parse_args() if not args.command: parser.print_help() sys.exit(1) setup_logging(args.verbose) config = load_config(args.config) if args.command == "pr": run_pr_review(args, config) elif args.command == "issue": run_issue_triage(args, config) elif args.command == "comment": run_issue_comment(args, config) elif args.command == "codebase": run_codebase_analysis(args, config) elif args.command == "chat": run_chat(args, config) elif args.command == "dispatch": run_webhook_dispatch(args, config) if __name__ == "__main__": main()