#!/usr/bin/env python3 """ Command Line Interface for AP Git Worktree Automation """ import argparse import json import sys from pathlib import Path from typing import List # Handle imports - support both package and direct execution try: from .worktree_manager import WorktreeManager from .parallel_agent import ParallelAgent, ParallelTask, IssueParallelizer from .task_runner import TaskRunner except ImportError: from worktree_manager import WorktreeManager from parallel_agent import ParallelAgent, ParallelTask, IssueParallelizer from task_runner import TaskRunner def cmd_worktree_list(args): """List active worktrees""" wm = WorktreeManager() worktrees = wm.list_worktrees() if args.json: print(json.dumps([{ "path": w.path, "branch": w.branch, "commit": w.commit[:8] if w.commit else "", "is_main": w.is_main, "last_modified": w.last_modified.isoformat() } for w in worktrees], indent=2)) else: print(f"{'PATH':<50} {'BRANCH':<30} {'TYPE':<10}") print("-" * 90) for w in worktrees: wtype = "main" if w.is_main else "worktree" branch = w.branch[:28] if len(w.branch) > 28 else w.branch path = w.path[:48] if len(w.path) > 48 else w.path print(f"{path:<50} {branch:<30} {wtype:<10}") def cmd_worktree_create(args): """Create a new worktree""" wm = WorktreeManager() try: path = wm.create_worktree( issue_number=args.issue, description=args.description, base_branch=args.base or "main" ) print(f"✓ Created worktree for issue {args.issue}") print(f" Path: {path}") # Show status status = wm.get_worktree_status(args.issue) if status.get("exists"): print(f" Branch: {status['branch']}") except Exception as e: print(f"✗ Error: {e}", file=sys.stderr) sys.exit(1) def cmd_worktree_clean(args): """Clean stale worktrees""" wm = WorktreeManager() if args.all: # List all non-main worktrees and remove them worktrees = wm.list_worktrees() removed = [] for wt in worktrees: if not wt.is_main: import re match = re.search(r'issue-(\d+)', wt.path) if match: wm.remove_worktree(match.group(1)) removed.append(wt.path) else: max_age = args.days or 7 removed = wm.clean_stale_worktrees(max_age) print(f"✓ Removed {len(removed)} worktrees") for r in removed: print(f" - {r}") def cmd_worktree_remove(args): """Remove a specific worktree""" wm = WorktreeManager() if wm.remove_worktree(args.issue): print(f"✓ Removed worktree for issue {args.issue}") else: print(f"✗ No worktree found for issue {args.issue}") sys.exit(1) def cmd_worktree_status(args): """Show worktree status""" wm = WorktreeManager() status = wm.get_worktree_status(args.issue) if not status.get("exists"): print(f"No worktree found for issue {args.issue}") sys.exit(1) if args.json: print(json.dumps(status, indent=2)) else: print(f"Issue: {args.issue}") print(f"Path: {status['path']}") print(f"Branch: {status['branch']}") print(f"Last commit: {status['last_commit']}") print(f"Uncommitted changes: {'Yes' if status['has_uncommitted_changes'] else 'No'}") if status['files_changed']: print("Changed files:") for f in status['files_changed']: print(f" {f}") def cmd_parallel_run(args): """Run tasks in parallel""" agent = ParallelAgent(max_workers=args.workers) # Load tasks from file if provided if args.tasks_file: with open(args.tasks_file) as f: task_data = json.load(f) tasks = [] for td in task_data: task = ParallelTask( task_id=td["task_id"], issue_number=td["issue_number"], task_type=td.get("task_type", "command"), payload=td["payload"], dependencies=td.get("dependencies", []), priority=td.get("priority", 0), timeout=td.get("timeout", 300), auto_commit=td.get("auto_commit", False), metadata=td.get("metadata", {}) ) tasks.append(task) results = agent.run_parallel(tasks) else: # Simple command execution across issues if not args.issues: print("Error: No issues specified. Use --issues or --tasks-file") sys.exit(1) tasks = [] for issue in args.issues: task = ParallelTask( task_id=f"task-{issue}", issue_number=issue, task_type="command", payload=args.command, dependencies=[], auto_commit=args.auto_commit ) tasks.append(task) results = agent.run_parallel(tasks) # Output results if args.json: print(json.dumps(results, indent=2)) else: print(f"\n✓ Parallel execution complete") print(f" Total tasks: {results['stats']['total_tasks']}") print(f" Successful: {results['stats']['successful']}") print(f" Failed: {results['stats']['failed']}") print(f" Conflicts: {results['stats']['conflicts']}") if results['conflicts']: print("\n⚠ Conflicts detected:") for c in results['conflicts']: print(f" - {c['details']}") # Save report if requested if args.output: report = agent.generate_report() Path(args.output).write_text(report) print(f"\n✓ Report saved to {args.output}") def cmd_demo(args): """Run a demo with 3 parallel issues""" print("=" * 60) print("ALLEGRO-PRIMUS Git Worktree Automation Demo") print("=" * 60) # Setup wm = WorktreeManager() parallelizer = IssueParallelizer() # Define 3 issues to work on issues = [ ("42", "Fix login validation bug"), ("43", "Add user profile API endpoint"), ("44", "Update documentation for v2.0") ] print("\n1. Registering issues for parallel work...") for num, desc in issues: parallelizer.register_issue(num, desc) print(f" Issue #{num}: {desc}") print("\n2. Creating worktrees...") for num, desc in issues: try: path = wm.create_worktree(num, desc) print(f" ✓ Issue #{num}: {path}") except Exception as e: print(f" ✗ Issue #{num}: {e}") print("\n3. Listing worktrees...") worktrees = wm.list_worktrees() for wt in worktrees: if not wt.is_main: print(f" {wt.branch} @ {wt.path}") print("\n4. Adding tasks to queue...") # Add different tasks for each issue parallelizer.add_task_to_issue( "42", ["echo", "Running tests for login validation..."], auto_commit=False ) parallelizer.add_task_to_issue( "43", ["echo", "Building API endpoint..."], auto_commit=False ) parallelizer.add_task_to_issue( "44", ["echo", "Generating documentation..."], auto_commit=False ) print(" ✓ 3 tasks queued") print("\n5. Running tasks in parallel...") results = parallelizer.run() print(f"\n6. Results:") print(f" Total tasks: {results['stats']['total_tasks']}") print(f" Successful: {results['stats']['successful']}") print(f" Failed: {results['stats']['failed']}") print(f" Worktrees used: {results['stats']['worktrees_used']}") # Show individual results for r in results['results']: status = "✓" if r['success'] else "✗" print(f"\n {status} Task {r['task_id']}") print(f" Output: {r['stdout'].strip()[:50]}...") print(f" Duration: {r['execution_time']:.2f}s") print("\n7. Checking worktree status...") for num, _ in issues: status = wm.get_worktree_status(num) print(f" Issue #{num}: {status.get('branch', 'N/A')}") if not args.keep: print("\n8. Cleaning up worktrees...") for num, _ in issues: if wm.remove_worktree(num): print(f" ✓ Removed issue #{num}") else: print("\n8. Keeping worktrees (--keep specified)") print("\n" + "=" * 60) print("Demo complete!") print("=" * 60) def main(): parser = argparse.ArgumentParser( prog="ap-git", description="Allegro-Primus Git Worktree Automation" ) subparsers = parser.add_subparsers(dest="command", help="Commands") # Worktree commands worktree_parser = subparsers.add_parser("worktree", help="Worktree operations") worktree_subparsers = worktree_parser.add_subparsers(dest="worktree_cmd") # worktree list wt_list = worktree_subparsers.add_parser("list", help="List worktrees") wt_list.add_argument("--json", action="store_true", help="Output as JSON") wt_list.set_defaults(func=cmd_worktree_list) # worktree create wt_create = worktree_subparsers.add_parser("create", help="Create worktree") wt_create.add_argument("issue", help="Issue number") wt_create.add_argument("--description", "-d", help="Issue description") wt_create.add_argument("--base", "-b", default="main", help="Base branch") wt_create.set_defaults(func=cmd_worktree_create) # worktree clean wt_clean = worktree_subparsers.add_parser("clean", help="Clean stale worktrees") wt_clean.add_argument("--days", "-d", type=int, help="Max age in days") wt_clean.add_argument("--all", action="store_true", help="Remove all worktrees") wt_clean.set_defaults(func=cmd_worktree_clean) # worktree remove wt_remove = worktree_subparsers.add_parser("remove", help="Remove worktree") wt_remove.add_argument("issue", help="Issue number") wt_remove.set_defaults(func=cmd_worktree_remove) # worktree status wt_status = worktree_subparsers.add_parser("status", help="Worktree status") wt_status.add_argument("issue", help="Issue number") wt_status.add_argument("--json", action="store_true", help="Output as JSON") wt_status.set_defaults(func=cmd_worktree_status) # Parallel commands parallel_parser = subparsers.add_parser("parallel", help="Parallel execution") parallel_subparsers = parallel_parser.add_subparsers(dest="parallel_cmd") # parallel run parallel_run = parallel_subparsers.add_parser("run", help="Run tasks in parallel") parallel_run.add_argument("--tasks-file", "-f", help="JSON file with tasks") parallel_run.add_argument("--issues", "-i", nargs="+", help="Issue numbers") parallel_run.add_argument("--command", "-c", nargs="+", help="Command to run") parallel_run.add_argument("--workers", "-w", type=int, default=4, help="Max workers") parallel_run.add_argument("--output", "-o", help="Output report file") parallel_run.add_argument("--json", action="store_true", help="Output as JSON") parallel_run.add_argument("--auto-commit", action="store_true", help="Auto-commit on success") parallel_run.set_defaults(func=cmd_parallel_run) # Demo command demo_parser = subparsers.add_parser("demo", help="Run demo") demo_parser.add_argument("--keep", action="store_true", help="Keep worktrees after demo") demo_parser.set_defaults(func=cmd_demo) # Parse and execute args = parser.parse_args() if not args.command: parser.print_help() sys.exit(1) if args.command == "worktree" and not args.worktree_cmd: worktree_parser.print_help() sys.exit(1) if args.command == "parallel" and not args.parallel_cmd: parallel_parser.print_help() sys.exit(1) args.func(args) if __name__ == "__main__": main()