update
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
# =================== META ====================
|
||||
# Name: delete.py
|
||||
# Description: Branch deletion — thin CLI layer for archive and deregister
|
||||
# Version: 1.1.0
|
||||
# Created: 2026-03-07
|
||||
# Modified: 2026-03-07
|
||||
# =============================================
|
||||
|
||||
"""Delete orchestrator for branch lifecycle management.
|
||||
|
||||
Thin CLI module that parses arguments and delegates to the delete handler.
|
||||
All implementation logic lives in apps/handlers/delete_ops.py.
|
||||
"""
|
||||
|
||||
from aipass.prax import logger
|
||||
# CLI service: from cli.apps.modules import console (via aipass namespace)
|
||||
from aipass.cli.apps.modules import console
|
||||
|
||||
from aipass.spawn.apps.handlers.delete_ops import delete_branch
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# PUBLIC API
|
||||
# =============================================================================
|
||||
|
||||
def handle_delete(args: list[str]) -> int:
|
||||
"""Parse args and execute delete.
|
||||
|
||||
Args patterns:
|
||||
["@branch"] -> delete with confirmation prompt
|
||||
["--yes", "@branch"] -> skip confirmation
|
||||
["--dry-run", "@branch"] -> preview only
|
||||
|
||||
Returns exit code (0=success, 1=failure).
|
||||
"""
|
||||
if not args:
|
||||
console.print("[yellow]Usage: drone @spawn delete <@branch> [--yes] [--dry-run][/yellow]")
|
||||
console.print()
|
||||
console.print(" [green]@branch[/green] Branch to archive and deregister")
|
||||
console.print(" [green]--yes[/green] Skip confirmation prompt")
|
||||
console.print(" [green]--dry-run[/green] Preview what would happen without changes")
|
||||
return 1
|
||||
|
||||
dry_run = "--dry-run" in args
|
||||
skip_confirm = "--yes" in args
|
||||
|
||||
# Filter out flags to find the target
|
||||
targets = [a for a in args if not a.startswith("--")]
|
||||
|
||||
if not targets:
|
||||
console.print("[red]Error: specify a branch name (e.g. @api)[/red]")
|
||||
return 1
|
||||
|
||||
branch_name = targets[0].lstrip("@").lower()
|
||||
|
||||
try:
|
||||
result = delete_branch(
|
||||
branch_name,
|
||||
confirm=not skip_confirm,
|
||||
dry_run=dry_run,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.error(f"[delete] Unexpected error deleting {branch_name}: {exc}")
|
||||
console.print(f"[red]Error deleting {branch_name}: {exc}[/red]")
|
||||
return 1
|
||||
|
||||
_print_summary(result, dry_run)
|
||||
return 0 if result.get("success") else 1
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# OUTPUT HELPERS
|
||||
# =============================================================================
|
||||
|
||||
def _print_summary(result: dict, dry_run: bool) -> None:
|
||||
"""Print a rich summary of the delete operation."""
|
||||
branch = result.get("branch", "unknown")
|
||||
success = result.get("success", False)
|
||||
mode = "[dim](dry-run)[/dim] " if dry_run else ""
|
||||
|
||||
console.print()
|
||||
if success:
|
||||
console.print(f"[green]Delete {mode}{branch}[/green]")
|
||||
else:
|
||||
console.print(f"[red]Delete FAILED {mode}{branch}[/red]")
|
||||
|
||||
archive_path = result.get("archive_path", "")
|
||||
if archive_path:
|
||||
console.print(f" Archive: {archive_path}")
|
||||
|
||||
console.print(f" Registry updated: {result.get('registry_updated', False)}")
|
||||
|
||||
error = result.get("error", "")
|
||||
if error:
|
||||
console.print(f" [red]Error: {error}[/red]")
|
||||
|
||||
console.print()
|
||||
@@ -0,0 +1,100 @@
|
||||
# =================== META ====================
|
||||
# Name: sync_registry.py
|
||||
# Description: Registry repair — thin CLI layer for registry synchronization
|
||||
# Version: 1.1.0
|
||||
# Created: 2026-03-07
|
||||
# Modified: 2026-03-07
|
||||
# =============================================
|
||||
|
||||
"""Registry synchronization for branch lifecycle management.
|
||||
|
||||
Thin CLI module that parses arguments and delegates to the sync handler.
|
||||
All implementation logic lives in apps/handlers/sync_registry_ops.py.
|
||||
"""
|
||||
|
||||
from aipass.prax import logger
|
||||
# CLI service: from cli.apps.modules import console (via aipass namespace)
|
||||
from aipass.cli.apps.modules import console
|
||||
|
||||
from aipass.spawn.apps.handlers.sync_registry_ops import sync_registry
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# PUBLIC API
|
||||
# =============================================================================
|
||||
|
||||
def handle_sync_registry(args: list[str]) -> int:
|
||||
"""Parse args and execute sync.
|
||||
|
||||
Args patterns:
|
||||
[] -> report only (show mismatches)
|
||||
["--fix"] -> auto-repair mismatches
|
||||
|
||||
Returns exit code (0=success, 1=failure).
|
||||
"""
|
||||
if args and args[0] in ["--help", "-h"]:
|
||||
console.print("[yellow]Usage: drone @spawn sync-registry [--fix][/yellow]")
|
||||
console.print()
|
||||
console.print(" [green](no args)[/green] Report mismatches between registry and filesystem")
|
||||
console.print(" [green]--fix[/green] Auto-repair: remove stale, add unregistered")
|
||||
return 0
|
||||
|
||||
fix = "--fix" in args
|
||||
|
||||
try:
|
||||
result = sync_registry(fix=fix)
|
||||
except Exception as exc:
|
||||
logger.error(f"[sync-registry] Unexpected error: {exc}")
|
||||
console.print(f"[red]Error: {exc}[/red]")
|
||||
return 1
|
||||
|
||||
_print_summary(result)
|
||||
return 0
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# OUTPUT HELPERS
|
||||
# =============================================================================
|
||||
|
||||
def _print_summary(result: dict) -> None:
|
||||
"""Print a rich summary of the sync operation."""
|
||||
stale = result.get("stale", [])
|
||||
unregistered = result.get("unregistered", [])
|
||||
healthy = result.get("healthy", [])
|
||||
fixed = result.get("fixed", False)
|
||||
|
||||
console.print()
|
||||
console.print("[bold]Registry Sync Report[/bold]")
|
||||
console.print()
|
||||
|
||||
# Healthy
|
||||
if healthy:
|
||||
console.print(f" [green]Healthy ({len(healthy)}):[/green]")
|
||||
for name in sorted(healthy):
|
||||
console.print(f" {name}")
|
||||
|
||||
# Stale
|
||||
if stale:
|
||||
console.print(f" [red]Stale ({len(stale)}):[/red] [dim]registered but missing/invalid[/dim]")
|
||||
for name in sorted(stale):
|
||||
console.print(f" {name}")
|
||||
else:
|
||||
console.print(" [green]No stale entries[/green]")
|
||||
|
||||
# Unregistered
|
||||
if unregistered:
|
||||
console.print(f" [yellow]Unregistered ({len(unregistered)}):[/yellow] [dim]on disk but not in registry[/dim]")
|
||||
for name in sorted(unregistered):
|
||||
console.print(f" {name}")
|
||||
else:
|
||||
console.print(" [green]No unregistered branches[/green]")
|
||||
|
||||
# Fix status
|
||||
if fixed:
|
||||
console.print()
|
||||
console.print(" [green]Registry has been repaired.[/green]")
|
||||
elif stale or unregistered:
|
||||
console.print()
|
||||
console.print(" [dim]Run with --fix to auto-repair.[/dim]")
|
||||
|
||||
console.print()
|
||||
@@ -0,0 +1,109 @@
|
||||
# =================== META ====================
|
||||
# Name: sync_templates.py
|
||||
# Description: Template sync — thin CLI layer for template synchronization
|
||||
# Version: 1.1.0
|
||||
# Created: 2026-03-07
|
||||
# Modified: 2026-03-07
|
||||
# =============================================
|
||||
|
||||
"""Template synchronization for branch lifecycle management.
|
||||
|
||||
Thin CLI module that parses arguments and delegates to the sync handler.
|
||||
All implementation logic lives in apps/handlers/sync_templates_ops.py.
|
||||
"""
|
||||
|
||||
from aipass.prax import logger
|
||||
# CLI service: from cli.apps.modules import console (via aipass namespace)
|
||||
from aipass.cli.apps.modules import console
|
||||
|
||||
from aipass.spawn.apps.handlers.sync_templates_ops import sync_templates
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# PUBLIC API
|
||||
# =============================================================================
|
||||
|
||||
def handle_sync_templates(args: list[str]) -> int:
|
||||
"""Parse args and execute template sync.
|
||||
|
||||
Args patterns:
|
||||
[] -> report status (which files are stale)
|
||||
["--status"] -> same as no args
|
||||
["--sync"] -> actually pull and update
|
||||
["--dry-run"] -> preview changes
|
||||
|
||||
Returns exit code (0=success, 1=failure).
|
||||
"""
|
||||
if args and args[0] in ["--help", "-h"]:
|
||||
console.print("[yellow]Usage: drone @spawn sync-templates [--status|--sync|--dry-run][/yellow]")
|
||||
console.print()
|
||||
console.print(" [green](no args)[/green] Report which managed template files are stale")
|
||||
console.print(" [green]--status[/green] Same as no args")
|
||||
console.print(" [green]--sync[/green] Pull updated files from source branches into template")
|
||||
console.print(" [green]--dry-run[/green] Preview what would be synced")
|
||||
return 0
|
||||
|
||||
sync = "--sync" in args
|
||||
dry_run = "--dry-run" in args
|
||||
|
||||
try:
|
||||
result = sync_templates(sync=sync, dry_run=dry_run)
|
||||
except Exception as exc:
|
||||
logger.error(f"[sync-templates] Unexpected error: {exc}")
|
||||
console.print(f"[red]Error: {exc}[/red]")
|
||||
return 1
|
||||
|
||||
_print_summary(result, dry_run)
|
||||
return 0
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# OUTPUT HELPERS
|
||||
# =============================================================================
|
||||
|
||||
def _print_summary(result: dict, dry_run: bool) -> None:
|
||||
"""Print a rich summary of the template sync operation."""
|
||||
managed = result.get("managed_files", 0)
|
||||
current = result.get("current", [])
|
||||
stale = result.get("stale", [])
|
||||
synced = result.get("synced", [])
|
||||
errors = result.get("errors", [])
|
||||
mode = "[dim](dry-run)[/dim] " if dry_run else ""
|
||||
|
||||
console.print()
|
||||
console.print(f"[bold]Template Sync Report {mode}[/bold]")
|
||||
console.print()
|
||||
|
||||
if managed == 0:
|
||||
console.print(" [dim]No managed files configured in template_owners.json[/dim]")
|
||||
console.print(" [dim]Add entries to spawn/apps/handlers/templates/template_owners.json[/dim]")
|
||||
console.print()
|
||||
return
|
||||
|
||||
console.print(f" Managed files: {managed}")
|
||||
|
||||
if current:
|
||||
console.print(f" [green]Current ({len(current)}):[/green]")
|
||||
for name in current:
|
||||
console.print(f" {name}")
|
||||
|
||||
if stale:
|
||||
console.print(f" [yellow]Stale ({len(stale)}):[/yellow]")
|
||||
for name in stale:
|
||||
console.print(f" {name}")
|
||||
|
||||
if synced:
|
||||
console.print(f" [green]Synced ({len(synced)}):[/green]")
|
||||
for name in synced:
|
||||
console.print(f" {name}")
|
||||
|
||||
if errors:
|
||||
console.print(f" [red]Errors ({len(errors)}):[/red]")
|
||||
for err in errors:
|
||||
console.print(f" {err}")
|
||||
|
||||
if stale and not synced and not dry_run:
|
||||
console.print()
|
||||
console.print(" [dim]Run with --sync to pull updates from source branches.[/dim]")
|
||||
|
||||
console.print()
|
||||
@@ -0,0 +1,177 @@
|
||||
# =================== META ====================
|
||||
# Name: update.py
|
||||
# Description: Update orchestrator — thin CLI layer for branch updates
|
||||
# Version: 1.1.0
|
||||
# Created: 2026-03-07
|
||||
# Modified: 2026-03-07
|
||||
# =============================================
|
||||
|
||||
"""Update orchestrator for branch lifecycle management.
|
||||
|
||||
Thin CLI module that parses arguments and delegates to the update handler.
|
||||
All implementation logic lives in apps/handlers/update_ops.py.
|
||||
"""
|
||||
|
||||
from aipass.prax import logger
|
||||
# CLI service: from cli.apps.modules import console (via aipass namespace)
|
||||
from aipass.cli.apps.modules import console
|
||||
|
||||
from aipass.spawn.apps.handlers.update_ops import (
|
||||
update_branch,
|
||||
update_all,
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# PUBLIC API
|
||||
# =============================================================================
|
||||
|
||||
def handle_update(args: list[str]) -> int:
|
||||
"""Parse args and dispatch to update_branch or update_all.
|
||||
|
||||
Args patterns:
|
||||
["@branch"] -> update single branch
|
||||
["--all"] -> update all branches
|
||||
["--dry-run", "@branch"] -> preview mode
|
||||
["--dry-run", "--all"] -> preview all
|
||||
["--trace", "@branch"] -> verbose logging
|
||||
|
||||
Returns exit code (0=success, 1=failure).
|
||||
"""
|
||||
if not args:
|
||||
console.print("[yellow]Usage: drone @spawn update <@branch|--all> [--dry-run] [--trace][/yellow]")
|
||||
console.print()
|
||||
console.print(" [green]@branch[/green] Update a single branch")
|
||||
console.print(" [green]--all[/green] Update all registered branches")
|
||||
console.print(" [green]--dry-run[/green] Preview changes without modifying files")
|
||||
console.print(" [green]--trace[/green] Enable verbose logging")
|
||||
return 1
|
||||
|
||||
dry_run = "--dry-run" in args
|
||||
trace = "--trace" in args
|
||||
|
||||
# Filter out flags to find the target
|
||||
targets = [a for a in args if not a.startswith("--")]
|
||||
|
||||
if "--all" in args:
|
||||
results = update_all(dry_run=dry_run, trace=trace)
|
||||
_print_all_summary(results, dry_run)
|
||||
# Return 0 if all succeeded, 1 if any failed
|
||||
return 0 if all(r.get("success") for r in results) else 1
|
||||
|
||||
if not targets:
|
||||
console.print("[red]Error: specify a branch name (e.g. @prax) or --all[/red]")
|
||||
return 1
|
||||
|
||||
# Take the first target, strip leading @
|
||||
branch_name = targets[0].lstrip("@")
|
||||
|
||||
try:
|
||||
result = update_branch(branch_name, dry_run=dry_run, trace=trace)
|
||||
except Exception as exc:
|
||||
logger.error(f"[update] Unexpected error updating {branch_name}: {exc}")
|
||||
console.print(f"[red]Error updating {branch_name}: {exc}[/red]")
|
||||
return 1
|
||||
|
||||
_print_branch_summary(result, dry_run)
|
||||
return 0 if result.get("success") else 1
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# OUTPUT HELPERS
|
||||
# =============================================================================
|
||||
|
||||
def _print_branch_summary(result: dict, dry_run: bool) -> None:
|
||||
"""Print a rich summary for a single branch update."""
|
||||
branch = result.get("branch", "unknown")
|
||||
success = result.get("success", False)
|
||||
mode = "[dim](dry-run)[/dim] " if dry_run else ""
|
||||
|
||||
console.print()
|
||||
if success:
|
||||
console.print(f"[green]Update {mode}{branch}[/green]")
|
||||
else:
|
||||
console.print(f"[red]Update FAILED {mode}{branch}[/red]")
|
||||
|
||||
console.print(f" Additions: {result.get('additions', 0)}")
|
||||
console.print(f" Renames: {result.get('renames', 0)}")
|
||||
console.print(f" Updates: {result.get('updates', 0)}")
|
||||
console.print(f" Pruned: {result.get('pruned', 0)}")
|
||||
console.print(f" Skipped py: {result.get('skipped_py', 0)}")
|
||||
|
||||
errs = result.get("errors", [])
|
||||
if errs:
|
||||
console.print(f" [red]Errors: {len(errs)}[/red]")
|
||||
for e in errs:
|
||||
console.print(f" - {e}")
|
||||
|
||||
# In dry-run mode, show details of what would change
|
||||
if dry_run:
|
||||
additions_detail = result.get("_additions_detail", [])
|
||||
if isinstance(additions_detail, list) and additions_detail:
|
||||
console.print()
|
||||
console.print(" [cyan]Would add:[/cyan]")
|
||||
for a in additions_detail:
|
||||
if isinstance(a, dict):
|
||||
console.print(f" + {a.get('template_path', '')}")
|
||||
|
||||
updates_detail = result.get("_updates_detail", [])
|
||||
if isinstance(updates_detail, list) and updates_detail:
|
||||
console.print(" [cyan]Would update/skip:[/cyan]")
|
||||
for u in updates_detail:
|
||||
if isinstance(u, dict):
|
||||
tp = u.get("template_path", "")
|
||||
bp = u.get("branch_path", tp)
|
||||
if tp.endswith(".py"):
|
||||
console.print(f" ! {bp} [dim](skipped - .py manual review)[/dim]")
|
||||
elif tp.endswith(".json"):
|
||||
console.print(f" ~ {bp} [dim](JSON deep merge)[/dim]")
|
||||
else:
|
||||
console.print(f" - {bp} [dim](skipped - non-JSON)[/dim]")
|
||||
|
||||
renames_detail = result.get("_renames_detail", [])
|
||||
if isinstance(renames_detail, list) and renames_detail:
|
||||
console.print(" [cyan]Would rename:[/cyan]")
|
||||
for r in renames_detail:
|
||||
if isinstance(r, dict):
|
||||
console.print(f" > {r.get('old_name', '')} -> {r.get('new_name', '')}")
|
||||
|
||||
pruned_detail = result.get("_pruned_detail", [])
|
||||
if isinstance(pruned_detail, list) and pruned_detail:
|
||||
console.print(" [cyan]Would archive (pruned):[/cyan]")
|
||||
for p in pruned_detail:
|
||||
if isinstance(p, dict):
|
||||
console.print(f" - {p.get('branch_path', '')}")
|
||||
|
||||
console.print()
|
||||
|
||||
|
||||
def _print_all_summary(results: list[dict], dry_run: bool) -> None:
|
||||
"""Print summary for update_all."""
|
||||
mode = "(dry-run) " if dry_run else ""
|
||||
|
||||
console.print()
|
||||
console.print(f"[bold]Update All {mode}— {len(results)} branches[/bold]")
|
||||
console.print()
|
||||
|
||||
total_add = sum(r.get("additions", 0) for r in results)
|
||||
total_ren = sum(r.get("renames", 0) for r in results)
|
||||
total_upd = sum(r.get("updates", 0) for r in results)
|
||||
total_prn = sum(r.get("pruned", 0) for r in results)
|
||||
total_skip = sum(r.get("skipped_py", 0) for r in results)
|
||||
total_err = sum(len(r.get("errors", [])) for r in results)
|
||||
|
||||
for r in results:
|
||||
branch = r.get("branch", "?")
|
||||
status = "[green]OK[/green]" if r.get("success") else "[red]FAIL[/red]"
|
||||
changes = r.get("additions", 0) + r.get("renames", 0) + r.get("updates", 0) + r.get("pruned", 0)
|
||||
console.print(f" {status} {branch}: {changes} changes")
|
||||
|
||||
console.print()
|
||||
console.print(f" Totals: +{total_add} added, ~{total_upd} updated, "
|
||||
f">{total_ren} renamed, -{total_prn} pruned, "
|
||||
f"!{total_skip} py-skipped")
|
||||
|
||||
if total_err:
|
||||
console.print(f" [red]{total_err} errors across all branches[/red]")
|
||||
console.print()
|
||||
Reference in New Issue
Block a user