Files
timmy-config/wizards/allegro-primus/git_tools/cli.py
2026-03-31 20:02:01 +00:00

362 lines
12 KiB
Python
Executable File

#!/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()