Files
Teknium 65a6b6831e Port from code-yeongyu/oh-my-openagent#7151: worktree audit gains --json, --older-than, and external-tree visibility
omo's omo-agent-toolkit worktree-sweep (their PR #7151) added three
capabilities our hermes worktree command lacked:

- --json on list and prune: machine-readable audit/result payloads so
  scripts and agents can consume verdicts without scraping table output.
- --older-than DAYS: an age floor that only ever RESTRICTS reaping
  (young-but-reapable trees are kept); it never widens eligibility, so
  the existing safety invariants are untouched.
- External-tree visibility: linked worktrees registered outside
  .worktrees/ are now reported read-only in the audit (branch, locked,
  missing) instead of being invisible, and registrations whose
  directory has vanished are dropped via git worktree prune (metadata
  only, no files touched) during prune.

Not ported: omo's ancestor-of-default-branch merge test (our git cherry
patch-equivalence is strictly stronger under rebase/squash merges), and
their hardcoded external-root exclusion list (we exclude by location:
everything outside .worktrees/ is hands-off).

Tests: 9 new contracts in tests/hermes_cli/test_worktree_gc.py (age gate
restrict-only, external trees never reaped, stale-registration prune
dry-run/real, JSON shapes, negative --older-than rejected). Live E2E on
a scratch repo verified all three flags end to end.
2026-09-13 20:55:29 -07:00

54 lines
2.7 KiB
Python

"""``hermes worktree`` subcommand parser."""
from __future__ import annotations
def build_worktree_parser(subparsers) -> None:
"""Attach the ``worktree`` subcommand to ``subparsers``."""
worktree_parser = subparsers.add_parser(
"worktree", help="Audit and reclaim accumulated git worktrees and merged branches",
description="Attended reclaim for the .worktrees/ directory hermes -w sessions "
"accumulate. Never deletes uncommitted tracked changes, unique "
"unpushed commits, or in-use trees; untracked-only scratch is "
"archived to ~/.hermes/archive/worktree-prune/ before removal. See: "
"https://hermes-agent.nousresearch.com/docs/user-guide/cli#worktree-cleanup")
worktree_subparsers = worktree_parser.add_subparsers(dest="worktree_action")
worktree_list = worktree_subparsers.add_parser(
"list", aliases=["ls", "audit"],
help="Classify every tree: age, size, verdict, reason (default action)")
worktree_list.add_argument("--repo", help="Repo root (default: current repo)")
worktree_list.add_argument(
"--json", action="store_true",
help="Machine-readable audit output (trees, external trees, branches)")
worktree_list.add_argument(
"--older-than", type=float, metavar="DAYS", dest="older_than",
help="Treat reapable trees younger than DAYS as keep")
worktree_prune = worktree_subparsers.add_parser(
"prune", help="Remove safe trees and delete fully-merged local branches")
worktree_prune.add_argument("--repo", help="Repo root (default: current repo)")
worktree_prune.add_argument(
"--json", action="store_true",
help="Machine-readable result (actions taken/planned, preserved trees)")
worktree_prune.add_argument(
"--older-than", type=float, metavar="DAYS", dest="older_than",
help="Only reap trees idle for at least DAYS days (safety gates still apply)")
worktree_prune.add_argument(
"--dry-run", action="store_true", help="Show the plan without changing anything")
worktree_prune.add_argument(
"--trees-only", action="store_true",
help="Only remove worktrees; leave local branches alone")
worktree_prune.add_argument(
"--branches-only", action="store_true",
help="Only delete merged local branches; leave worktrees alone")
def _dispatch_worktree(_args):
from hermes_cli.worktree_cmd import cmd_worktree
# argparse aliases set dest to the literal typed string ("ls"/"audit").
action = getattr(_args, "worktree_action", None)
if action in ("ls", "audit"):
_args.worktree_action = "list"
return cmd_worktree(_args)
worktree_parser.set_defaults(func=_dispatch_worktree)