[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M754HMBMFXS05YH2PEHTPFHR":3,"skill-same:01M754HMBMFXS05YH2PEHTPFHR":57},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":29,"commitSha":30,"skillDirectory":31,"forksCount":32,"repositoryHtmlUrl":33,"repositoryHomepageUrl":6,"ownerHtmlUrl":34,"ownerBlogUrl":35,"ownerDescription":36,"updatedAt":37,"install":38,"resources":50},"01M754HMBMFXS05YH2PEHTPFHR","x-cleanup-git-branches",null,"Cleans local git state in one pass: fetches origin with prune, removes all non-main worktrees (any path), and deletes all local branches except main\u002Fmaster\u002Fdevelop. Destructive by default with an interactive y\u002FN confirmation gate; supports --dry-run (preview) and --yes (non-interactive).","review-bench\u002Federcnj_ia-dev-environment","review-bench","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F286778065?v=4","01M7517F9NTEPDZDAXJBR30FWT","ReviewBench","01M7517FAC9F4P7TGWCNJ8VYVW",0,"MIT","https:\u002F\u002Fgithub.com\u002Freview-bench\u002Federcnj_ia-dev-environment\u002Fblob\u002Fmain\u002Fsrc\u002Ftest\u002Fresources\u002Fgolden\u002Fjava-spring-fintech-pci\u002F.claude\u002Fskills\u002Fx-cleanup-git-branches\u002FSKILL.md",[],[],"2026-11-12 13:55:43",false,"---\nname: x-cleanup-git-branches\ndescription: \"Cleans local git state in one pass: fetches origin with prune, removes all non-main worktrees (any path), and deletes all local branches except main\u002Fmaster\u002Fdevelop. Destructive by default with an interactive y\u002FN confirmation gate; supports --dry-run (preview) and --yes (non-interactive).\"\nuser-invocable: true\nallowed-tools: Bash, Read\nargument-hint: \"[--dry-run] [--yes]\"\ncontext-budget: medium\nrequires-capabilities: []\n---\n\n## Global Output Policy\n\n- **Language**: English ONLY.\n- **Tone**: Technical, Direct, and Concise.\n- **Efficiency**: Remove all conversational fillers and greetings to save tokens.\n\n# Skill: Local Git Cleanup (branches + worktrees + fetch)\n\n## Purpose\n\nCentralizes the \"reset local git state\" workflow for {{PROJECT_NAME}}: after a batch of merged PRs, stale worktrees and orphan local branches accumulate. This skill does the combined pass in one invocation — fetch with prune, remove every non-main worktree, delete every local branch outside the protected set (`main`, `master`, `develop`).\n\nDestructive by design (the user explicitly asked for a sweep). Safety comes from the confirmation gate (`y\u002FN`) before any deletion and from `--dry-run` preview mode.\n\n## When to Use\n\n- After merging a batch of feature PRs — flush remote-tracking refs and delete the merged locals.\n- Before starting a large refactor — clean slate local branches \u002F worktrees.\n- When `git worktree list` or `git branch -l` gets noisy.\n- NOT for surgical removal — use `\u002Fx-manage-worktrees remove --id \u003Cid>` or `git branch -D \u003Cname>` directly.\n\n## Triggers\n\n- `\u002Fx-cleanup-git-branches` — execute with interactive confirmation\n- `\u002Fx-cleanup-git-branches --dry-run` — preview candidates, no changes\n- `\u002Fx-cleanup-git-branches --yes` — execute, skip confirmation (CI \u002F scripted use)\n\n## Protected Set (Hard-Coded)\n\n| Name | Why protected |\n|------|---------------|\n| `main` | Production branch (Rule 09) |\n| `master` | Legacy production alias |\n| `develop` | Integration branch (Rule 09) |\n\nThe currently checked-out branch (HEAD) is **NOT** in the protected set. If HEAD points at a candidate branch, the skill checks out `develop` (fallback: `main`) before deletion.\n\n## Workflow\n\n```\n1.  PARSE FLAGS       -> validate --dry-run \u002F --yes mutual exclusion\n2.  DETECT CONTEXT    -> abort if running inside .claude\u002Fworktrees\u002F*\n3.  RESOLVE HEAD      -> capture current branch (empty if detached)\n4.  FETCH             -> git fetch --prune origin (skip if no origin)\n5.  ENUMERATE WTS     -> list non-main worktrees via git worktree list --porcelain\n6.  ENUMERATE BRS     -> list local branches minus protected set\n7.  PRINT PLAN        -> human-readable candidate table\n8.  CONFIRM GATE      -> y\u002FN prompt unless --yes \u002F --dry-run\n9.  SWITCH IF NEEDED  -> checkout develop\u002Fmain if HEAD is a candidate\n10. REMOVE WORKTREES  -> git worktree remove --force + git worktree prune\n11. DELETE BRANCHES   -> git branch -D per candidate\n12. REPORT SUMMARY    -> counts + exit 0\n```\n\n### Step 1 — Parse Flags\n\n```bash\nDRY_RUN=false\nASSUME_YES=false\nfor arg in \"$@\"; do\n  case \"$arg\" in\n    --dry-run) DRY_RUN=true ;;\n    --yes|-y)  ASSUME_YES=true ;;\n    -h|--help) echo \"Usage: x-cleanup-git-branches [--dry-run] [--yes]\"; exit 0 ;;\n    *) echo \"ERROR: unknown flag: $arg\" >&2; exit 2 ;;\n  esac\ndone\n\nif [ \"$DRY_RUN\" = \"true\" ] && [ \"$ASSUME_YES\" = \"true\" ]; then\n  echo \"ERROR: --dry-run and --yes are mutually exclusive\" >&2\n  exit 2\nfi\n```\n\n### Step 2 — Detect Worktree Context (abort if inside one)\n\nThis skill MUST run from the main repository. Running from inside a worktree would attempt to remove the host worktree while executing — unsafe.\n\nThis skill extends the canonical `detect_worktree_context()` check from `x-manage-worktrees` (Rule 14, non-nesting invariant). The canonical snippet only recognises worktrees under `.claude\u002Fworktrees\u002F*`; because this skill enumerates and removes **all** non-main worktrees via `git worktree list --porcelain` (any path), the guard also compares `git rev-parse --show-toplevel` to the main worktree path and inspects `git rev-parse --git-dir` for a `worktrees\u002F` suffix, so a linked worktree in any location triggers the abort.\n\n```bash\ndetect_worktree_context() {\n  local toplevel git_dir main_repo wt_path in_wt=\"false\"\n  toplevel=$(git rev-parse --show-toplevel 2>\u002Fdev\u002Fnull) || {\n    echo '{\"error\":\"NOT_A_REPO\"}' >&2\n    return 1\n  }\n  git_dir=$(git rev-parse --git-dir 2>\u002Fdev\u002Fnull) || {\n    echo '{\"error\":\"NOT_A_REPO\"}' >&2\n    return 1\n  }\n  json_escape() {\n    printf '%s' \"$1\" | sed -e 's\u002F\\\\\u002F\\\\\\\\\u002Fg' -e 's\u002F\"\u002F\\\\\"\u002Fg'\n  }\n\n  # Resolve main repo path (first `worktree` entry, stripping the\n  # `worktree ` prefix so paths containing spaces are preserved).\n  if ! main_repo=$(git worktree list --porcelain 2>\u002Fdev\u002Fnull \\\n              | sed -n 's\u002F^worktree \u002F\u002Fp' | head -n 1) \\\n       || [ -z \"$main_repo\" ]; then\n    main_repo=\"$toplevel\"\n  fi\n\n  # Classifier 1 — Rule 14 non-nesting invariant (substring check).\n  if printf '%s' \"$toplevel\" | grep -q \"\u002F\\.claude\u002Fworktrees\u002F\"; then\n    in_wt=\"true\"\n  fi\n  # Classifier 2 — git-dir of a linked worktree lives under\n  # `\u003Cmain>\u002F.git\u002Fworktrees\u002F\u003Cid>\u002F`.\n  case \"$git_dir\" in\n    *\u002Fworktrees\u002F*|.git\u002Fworktrees\u002F*) in_wt=\"true\" ;;\n  esac\n  # Classifier 3 — toplevel differs from the main repo path.\n  if [ \"$toplevel\" != \"$main_repo\" ]; then\n    in_wt=\"true\"\n  fi\n\n  if [ \"$in_wt\" = \"true\" ]; then\n    wt_path=$(json_escape \"$toplevel\")\n    main_repo=$(json_escape \"$main_repo\")\n    printf '{\"inWorktree\":%s,\"worktreePath\":\"%s\",\"mainRepoPath\":\"%s\"}\\n' \\\n      \"$in_wt\" \"$wt_path\" \"$main_repo\"\n  else\n    main_repo=$(json_escape \"$main_repo\")\n    printf '{\"inWorktree\":%s,\"worktreePath\":null,\"mainRepoPath\":\"%s\"}\\n' \\\n      \"$in_wt\" \"$main_repo\"\n  fi\n}\n\nCONTEXT_JSON=$(detect_worktree_context) || exit 1\nIN_WT=$(printf '%s' \"$CONTEXT_JSON\" | grep -o '\"inWorktree\":[^,]*' | cut -d: -f2)\n\nif [ \"$IN_WT\" = \"true\" ]; then\n  echo \"ERROR: IN_WORKTREE_UNSAFE — must run from main repo, not a worktree\" >&2\n  exit 1\nfi\n```\n\n### Step 3 — Resolve Current Branch\n\n```bash\nCURRENT_BRANCH=$(git symbolic-ref --short -q HEAD || true)\n# Empty string => detached HEAD (safe; no switch needed later)\n```\n\n### Step 4 — Fetch With Prune\n\n```bash\nif git remote | grep -q '^origin$'; then\n  echo \"→ git fetch --prune origin\"\n  git fetch --prune origin || echo \"WARNING: fetch failed, continuing\"\nelse\n  echo \"WARNING: no 'origin' remote configured, skipping fetch\"\nfi\n```\n\n### Step 5 — Enumerate Non-Main Worktrees\n\nThe first `worktree \u003Cpath>` entry in `git worktree list --porcelain` is always the main repository. All subsequent entries are removal candidates. Paths in that output can legally contain spaces, so extract them by stripping the literal `worktree ` prefix (nine characters) rather than by whitespace-splitting — `sed` preserves the full path.\n\n```bash\nWORKTREE_CANDIDATES=$(git worktree list --porcelain \\\n  | sed -n 's\u002F^worktree \u002F\u002Fp' \\\n  | tail -n +2)\n```\n\n### Step 6 — Enumerate Candidate Branches\n\n```bash\nPROTECTED_REGEX='^(main|master|develop)$'\n# Also preserve epic\u002F* branches (Rule 21) and docs\u002F* branches with open PRs (EPIC-0065 D-R6)\nRAW_CANDIDATES=$(git for-each-ref --format='%(refname:short)' refs\u002Fheads\u002F \\\n  | grep -Ev \"$PROTECTED_REGEX\" || true)\n\n# Filter out epic\u002F* (always protected per Rule 21 §Anti-Patterns)\n# Filter out docs\u002F* branches that have an open PR (preserve until PR is merged\u002Fclosed)\nBRANCH_CANDIDATES=\"\"\nwhile IFS= read -r br; do\n  [ -n \"$br\" ] || continue\n  # epic\u002F* branches: always skip (Rule 21)\n  if [[ \"$br\" == epic\u002F* ]]; then continue; fi\n  # docs\u002F* branches: skip if an open PR exists (gh CLI check)\n  # When gh is unavailable, treat docs\u002F* as protected by default (fail-safe — avoids deleting a branch with an open PR)\n  if [[ \"$br\" == docs\u002F* ]]; then\n    if ! command -v gh &>\u002Fdev\u002Fnull; then continue; fi\n    open_prs=$(gh pr list --head \"$br\" --state open --json number --jq '. | length' 2>\u002Fdev\u002Fnull || echo 0)\n    [ \"$open_prs\" -gt 0 ] && continue\n  fi\n  BRANCH_CANDIDATES=\"${BRANCH_CANDIDATES}${br}\"$'\\n'\ndone \u003C\u003C\u003C \"$RAW_CANDIDATES\"\n```\n\n`grep -Ev … || true` prevents a non-match (exit 1) from aborting the script under `set -e` style shells.\n\n**Epic branch protection (Rule 21):** `epic\u002F*` branches are NEVER deleted by this skill — they are protected until the manual epic-to-develop PR gate is merged.\n\n**Docs branch protection (EPIC-0065):** `docs\u002F*` branches with open PRs are preserved until the PR is merged or closed. Once merged, they become cleanup candidates on the next run.\n\n### Step 7 — Print Plan\n\n```bash\necho \"\"\necho \"=== Cleanup Plan ===\"\necho \"\"\necho \"Worktrees to remove (main worktree preserved):\"\nif [ -z \"$WORKTREE_CANDIDATES\" ]; then\n  echo \"  (none)\"\nelse\n  while IFS= read -r wt; do\n    [ -n \"$wt\" ] || continue\n    printf '  - %s\\n' \"$wt\"\n  done \u003C\u003CEOF\n$WORKTREE_CANDIDATES\nEOF\nfi\n\necho \"\"\necho \"Local branches to delete (protected: main, master, develop):\"\nif [ -z \"$BRANCH_CANDIDATES\" ]; then\n  echo \"  (none)\"\nelse\n  while IFS= read -r br; do\n    [ -n \"$br\" ] || continue\n    printf '  - %s\\n' \"$br\"\n  done \u003C\u003CEOF\n$BRANCH_CANDIDATES\nEOF\nfi\n\nif [ -z \"$WORKTREE_CANDIDATES\" ] && [ -z \"$BRANCH_CANDIDATES\" ]; then\n  echo \"\"\n  echo \"Nothing to clean. Exiting.\"\n  exit 0\nfi\n\nif [ \"$DRY_RUN\" = \"true\" ]; then\n  echo \"\"\n  echo \"Dry-run complete — no changes applied.\"\n  exit 0\nfi\n```\n\n### Step 8 — Confirmation Gate\n\n```bash\nif [ \"$ASSUME_YES\" != \"true\" ]; then\n  echo \"\"\n  read -r -p \"Proceed with deletion? [y\u002FN] \" ANS\n  case \"$ANS\" in\n    y|Y|yes|YES) ;;\n    *) echo \"Aborted by user.\"; exit 0 ;;\n  esac\nfi\n```\n\n### Step 9 — Switch Away From a Candidate HEAD\n\nIf the current branch is about to be deleted, git refuses `branch -D`. Switch to `develop` (fallback `main`) first.\n\n```bash\nneeds_switch=false\nif [ -n \"$CURRENT_BRANCH\" ]; then\n  while IFS= read -r b; do\n    [ -n \"$b\" ] || continue\n    if [ \"$b\" = \"$CURRENT_BRANCH\" ]; then\n      needs_switch=true\n      break\n    fi\n  done \u003C\u003CEOF\n$BRANCH_CANDIDATES\nEOF\nfi\n\nif [ \"$needs_switch\" = \"true\" ]; then\n  if git show-ref --verify --quiet refs\u002Fheads\u002Fdevelop; then\n    echo \"→ git checkout develop (HEAD was on a candidate branch)\"\n    git checkout develop\n  elif git show-ref --verify --quiet refs\u002Fheads\u002Fmain; then\n    echo \"→ git checkout main (develop missing; falling back)\"\n    git checkout main\n  else\n    echo \"ERROR: NO_SAFE_FALLBACK_BRANCH — neither develop nor main exists; cannot switch away from $CURRENT_BRANCH\" >&2\n    exit 1\n  fi\nfi\n```\n\n### Step 10 — Remove Worktrees\n\nIterate the candidate list with `while IFS= read -r` over a heredoc, so that worktree paths containing spaces (or glob metacharacters) are preserved as a single token. A plain `for` loop would word-split them.\n\n```bash\nWT_REMOVED=0\nwhile IFS= read -r wt; do\n  [ -n \"$wt\" ] || continue\n  echo \"→ git worktree remove --force $wt\"\n  if git worktree remove --force \"$wt\"; then\n    WT_REMOVED=$((WT_REMOVED + 1))\n  else\n    echo \"WARNING: failed to remove worktree: $wt\" >&2\n  fi\ndone \u003C\u003CEOF\n$WORKTREE_CANDIDATES\nEOF\ngit worktree prune\n```\n\n`--force` ensures worktrees with uncommitted changes are removed. This is intentional — the Print Plan step already showed them to the user.\n\n### Step 11 — Delete Local Branches\n\n```bash\nBR_DELETED=0\nwhile IFS= read -r br; do\n  [ -n \"$br\" ] || continue\n  echo \"→ git branch -D $br\"\n  if git branch -D \"$br\"; then\n    BR_DELETED=$((BR_DELETED + 1))\n  else\n    echo \"WARNING: failed to delete branch: $br\" >&2\n  fi\ndone \u003C\u003CEOF\n$BRANCH_CANDIDATES\nEOF\n```\n\n### Step 12 — Report Summary\n\n```bash\necho \"\"\necho \"=== Summary ===\"\necho \"Worktrees removed: $WT_REMOVED\"\necho \"Branches deleted:  $BR_DELETED\"\nexit 0\n```\n\n## Error Handling\n\n| Scenario | Action |\n|----------|--------|\n| `--dry-run` and `--yes` both set | Abort with exit 2 (usage error) |\n| Unknown flag passed | Abort with exit 2 (usage error) |\n| Running inside any linked worktree (path anywhere, not just `.claude\u002Fworktrees\u002F*`) | Abort with `IN_WORKTREE_UNSAFE`, exit 1 |\n| Not a git repo | Abort with `NOT_A_REPO` (from detect-context), exit 1 |\n| `origin` remote missing | Warn, skip fetch, continue |\n| HEAD is a candidate and neither `develop` nor `main` exists | Abort with `NO_SAFE_FALLBACK_BRANCH`, exit 1 |\n| Individual worktree remove fails | Warn, continue, count excludes it |\n| Individual branch delete fails | Warn, continue, count excludes it |\n| No candidates (worktrees empty and branches empty) | Print \"Nothing to clean\", exit 0 |\n\n## Security & Safety Notes\n\n- **Blast radius is local-only:** `git fetch` reads from origin; no `push`, no `--force-push`, no tag\u002Fremote-branch mutation. Remote state is untouched.\n- **Uncommitted work:** `git worktree remove --force` discards uncommitted changes inside secondary worktrees. This is surfaced in the Print Plan step and the user can decline at the confirmation gate.\n- **Protected set is literal:** filters use exact regex `^(main|master|develop)$` — no substring matches, no accidental protection of `my-develop-fix`.\n- **HEAD in main worktree is preserved implicitly:** `git worktree list` reports the main worktree first and the enumeration skips it.\n\n## Related Skills\n\n| Skill | Relationship |\n|-------|-------------|\n| `x-manage-worktrees` | `cleanup` operation is scoped to `.claude\u002Fworktrees\u002F*` with MERGED\u002FSTALE\u002FORPHAN criteria; this skill is broader and unconditional. |\n| `x-push-branch` | Typical upstream action after cleanup (push a fresh branch). |\n| `x-commit-changes` | Used to author commits — unrelated to cleanup, referenced here only for context. |\n\n## References\n\n- [Rule 09 — Branching Model](..\u002F..\u002F..\u002Frules\u002F09-branching-model.md): protected branches policy\n- [Rule 14 — Worktree Lifecycle](..\u002F..\u002F..\u002Frules\u002F14-worktree-lifecycle.md): non-nesting invariant driving step 2\n- [x-manage-worktrees\u002FSKILL.md](..\u002Fx-manage-worktrees\u002FSKILL.md): source of the canonical `detect_worktree_context()` snippet\n","## Global Output Policy\n\n- **Language**: English ONLY.\n- **Tone**: Technical, Direct, and Concise.\n- **Efficiency**: Remove all conversational fillers and greetings to save tokens.\n\n# Skill: Local Git Cleanup (branches + worktrees + fetch)\n\n## Purpose\n\nCentralizes the \"reset local git state\" workflow for {{PROJECT_NAME}}: after a batch of merged PRs, stale worktrees and orphan local branches accumulate. This skill does the combined pass in one invocation — fetch with prune, remove every non-main worktree, delete every local branch outside the protected set (`main`, `master`, `develop`).\n\nDestructive by design (the user explicitly asked for a sweep). Safety comes from the confirmation gate (`y\u002FN`) before any deletion and from `--dry-run` preview mode.\n\n## When to Use\n\n- After merging a batch of feature PRs — flush remote-tracking refs and delete the merged locals.\n- Before starting a large refactor — clean slate local branches \u002F worktrees.\n- When `git worktree list` or `git branch -l` gets noisy.\n- NOT for surgical removal — use `\u002Fx-manage-worktrees remove --id \u003Cid>` or `git branch -D \u003Cname>` directly.\n\n## Triggers\n\n- `\u002Fx-cleanup-git-branches` — execute with interactive confirmation\n- `\u002Fx-cleanup-git-branches --dry-run` — preview candidates, no changes\n- `\u002Fx-cleanup-git-branches --yes` — execute, skip confirmation (CI \u002F scripted use)\n\n## Protected Set (Hard-Coded)\n\n| Name | Why protected |\n|------|---------------|\n| `main` | Production branch (Rule 09) |\n| `master` | Legacy production alias |\n| `develop` | Integration branch (Rule 09) |\n\nThe currently checked-out branch (HEAD) is **NOT** in the protected set. If HEAD points at a candidate branch, the skill checks out `develop` (fallback: `main`) before deletion.\n\n## Workflow\n\n```\n1.  PARSE FLAGS       -> validate --dry-run \u002F --yes mutual exclusion\n2.  DETECT CONTEXT    -> abort if running inside .claude\u002Fworktrees\u002F*\n3.  RESOLVE HEAD      -> capture current branch (empty if detached)\n4.  FETCH             -> git fetch --prune origin (skip if no origin)\n5.  ENUMERATE WTS     -> list non-main worktrees via git worktree list --porcelain\n6.  ENUMERATE BRS     -> list local branches minus protected set\n7.  PRINT PLAN        -> human-readable candidate table\n8.  CONFIRM GATE      -> y\u002FN prompt unless --yes \u002F --dry-run\n9.  SWITCH IF NEEDED  -> checkout develop\u002Fmain if HEAD is a candidate\n10. REMOVE WORKTREES  -> git worktree remove --force + git worktree prune\n11. DELETE BRANCHES   -> git branch -D per candidate\n12. REPORT SUMMARY    -> counts + exit 0\n```\n\n### Step 1 — Parse Flags\n\n```bash\nDRY_RUN=false\nASSUME_YES=false\nfor arg in \"$@\"; do\n  case \"$arg\" in\n    --dry-run) DRY_RUN=true ;;\n    --yes|-y)  ASSUME_YES=true ;;\n    -h|--help) echo \"Usage: x-cleanup-git-branches [--dry-run] [--yes]\"; exit 0 ;;\n    *) echo \"ERROR: unknown flag: $arg\" >&2; exit 2 ;;\n  esac\ndone\n\nif [ \"$DRY_RUN\" = \"true\" ] && [ \"$ASSUME_YES\" = \"true\" ]; then\n  echo \"ERROR: --dry-run and --yes are mutually exclusive\" >&2\n  exit 2\nfi\n```\n\n### Step 2 — Detect Worktree Context (abort if inside one)\n\nThis skill MUST run from the main repository. Running from inside a worktree would attempt to remove the host worktree while executing — unsafe.\n\nThis skill extends the canonical `detect_worktree_context()` check from `x-manage-worktrees` (Rule 14, non-nesting invariant). The canonical snippet only recognises worktrees under `.claude\u002Fworktrees\u002F*`; because this skill enumerates and removes **all** non-main worktrees via `git worktree list --porcelain` (any path), the guard also compares `git rev-parse --show-toplevel` to the main worktree path and inspects `git rev-parse --git-dir` for a `worktrees\u002F` suffix, so a linked worktree in any location triggers the abort.\n\n```bash\ndetect_worktree_context() {\n  local toplevel git_dir main_repo wt_path in_wt=\"false\"\n  toplevel=$(git rev-parse --show-toplevel 2>\u002Fdev\u002Fnull) || {\n    echo '{\"error\":\"NOT_A_REPO\"}' >&2\n    return 1\n  }\n  git_dir=$(git rev-parse --git-dir 2>\u002Fdev\u002Fnull) || {\n    echo '{\"error\":\"NOT_A_REPO\"}' >&2\n    return 1\n  }\n  json_escape() {\n    printf '%s' \"$1\" | sed -e 's\u002F\\\\\u002F\\\\\\\\\u002Fg' -e 's\u002F\"\u002F\\\\\"\u002Fg'\n  }\n\n  # Resolve main repo path (first `worktree` entry, stripping the\n  # `worktree ` prefix so paths containing spaces are preserved).\n  if ! main_repo=$(git worktree list --porcelain 2>\u002Fdev\u002Fnull \\\n              | sed -n 's\u002F^worktree \u002F\u002Fp' | head -n 1) \\\n       || [ -z \"$main_repo\" ]; then\n    main_repo=\"$toplevel\"\n  fi\n\n  # Classifier 1 — Rule 14 non-nesting invariant (substring check).\n  if printf '%s' \"$toplevel\" | grep -q \"\u002F\\.claude\u002Fworktrees\u002F\"; then\n    in_wt=\"true\"\n  fi\n  # Classifier 2 — git-dir of a linked worktree lives under\n  # `\u003Cmain>\u002F.git\u002Fworktrees\u002F\u003Cid>\u002F`.\n  case \"$git_dir\" in\n    *\u002Fworktrees\u002F*|.git\u002Fworktrees\u002F*) in_wt=\"true\" ;;\n  esac\n  # Classifier 3 — toplevel differs from the main repo path.\n  if [ \"$toplevel\" != \"$main_repo\" ]; then\n    in_wt=\"true\"\n  fi\n\n  if [ \"$in_wt\" = \"true\" ]; then\n    wt_path=$(json_escape \"$toplevel\")\n    main_repo=$(json_escape \"$main_repo\")\n    printf '{\"inWorktree\":%s,\"worktreePath\":\"%s\",\"mainRepoPath\":\"%s\"}\\n' \\\n      \"$in_wt\" \"$wt_path\" \"$main_repo\"\n  else\n    main_repo=$(json_escape \"$main_repo\")\n    printf '{\"inWorktree\":%s,\"worktreePath\":null,\"mainRepoPath\":\"%s\"}\\n' \\\n      \"$in_wt\" \"$main_repo\"\n  fi\n}\n\nCONTEXT_JSON=$(detect_worktree_context) || exit 1\nIN_WT=$(printf '%s' \"$CONTEXT_JSON\" | grep -o '\"inWorktree\":[^,]*' | cut -d: -f2)\n\nif [ \"$IN_WT\" = \"true\" ]; then\n  echo \"ERROR: IN_WORKTREE_UNSAFE — must run from main repo, not a worktree\" >&2\n  exit 1\nfi\n```\n\n### Step 3 — Resolve Current Branch\n\n```bash\nCURRENT_BRANCH=$(git symbolic-ref --short -q HEAD || true)\n# Empty string => detached HEAD (safe; no switch needed later)\n```\n\n### Step 4 — Fetch With Prune\n\n```bash\nif git remote | grep -q '^origin$'; then\n  echo \"→ git fetch --prune origin\"\n  git fetch --prune origin || echo \"WARNING: fetch failed, continuing\"\nelse\n  echo \"WARNING: no 'origin' remote configured, skipping fetch\"\nfi\n```\n\n### Step 5 — Enumerate Non-Main Worktrees\n\nThe first `worktree \u003Cpath>` entry in `git worktree list --porcelain` is always the main repository. All subsequent entries are removal candidates. Paths in that output can legally contain spaces, so extract them by stripping the literal `worktree ` prefix (nine characters) rather than by whitespace-splitting — `sed` preserves the full path.\n\n```bash\nWORKTREE_CANDIDATES=$(git worktree list --porcelain \\\n  | sed -n 's\u002F^worktree \u002F\u002Fp' \\\n  | tail -n +2)\n```\n\n### Step 6 — Enumerate Candidate Branches\n\n```bash\nPROTECTED_REGEX='^(main|master|develop)$'\n# Also preserve epic\u002F* branches (Rule 21) and docs\u002F* branches with open PRs (EPIC-0065 D-R6)\nRAW_CANDIDATES=$(git for-each-ref --format='%(refname:short)' refs\u002Fheads\u002F \\\n  | grep -Ev \"$PROTECTED_REGEX\" || true)\n\n# Filter out epic\u002F* (always protected per Rule 21 §Anti-Patterns)\n# Filter out docs\u002F* branches that have an open PR (preserve until PR is merged\u002Fclosed)\nBRANCH_CANDIDATES=\"\"\nwhile IFS= read -r br; do\n  [ -n \"$br\" ] || continue\n  # epic\u002F* branches: always skip (Rule 21)\n  if [[ \"$br\" == epic\u002F* ]]; then continue; fi\n  # docs\u002F* branches: skip if an open PR exists (gh CLI check)\n  # When gh is unavailable, treat docs\u002F* as protected by default (fail-safe — avoids deleting a branch with an open PR)\n  if [[ \"$br\" == docs\u002F* ]]; then\n    if ! command -v gh &>\u002Fdev\u002Fnull; then continue; fi\n    open_prs=$(gh pr list --head \"$br\" --state open --json number --jq '. | length' 2>\u002Fdev\u002Fnull || echo 0)\n    [ \"$open_prs\" -gt 0 ] && continue\n  fi\n  BRANCH_CANDIDATES=\"${BRANCH_CANDIDATES}${br}\"$'\\n'\ndone \u003C\u003C\u003C \"$RAW_CANDIDATES\"\n```\n\n`grep -Ev … || true` prevents a non-match (exit 1) from aborting the script under `set -e` style shells.\n\n**Epic branch protection (Rule 21):** `epic\u002F*` branches are NEVER deleted by this skill — they are protected until the manual epic-to-develop PR gate is merged.\n\n**Docs branch protection (EPIC-0065):** `docs\u002F*` branches with open PRs are preserved until the PR is merged or closed. Once merged, they become cleanup candidates on the next run.\n\n### Step 7 — Print Plan\n\n```bash\necho \"\"\necho \"=== Cleanup Plan ===\"\necho \"\"\necho \"Worktrees to remove (main worktree preserved):\"\nif [ -z \"$WORKTREE_CANDIDATES\" ]; then\n  echo \"  (none)\"\nelse\n  while IFS= read -r wt; do\n    [ -n \"$wt\" ] || continue\n    printf '  - %s\\n' \"$wt\"\n  done \u003C\u003CEOF\n$WORKTREE_CANDIDATES\nEOF\nfi\n\necho \"\"\necho \"Local branches to delete (protected: main, master, develop):\"\nif [ -z \"$BRANCH_CANDIDATES\" ]; then\n  echo \"  (none)\"\nelse\n  while IFS= read -r br; do\n    [ -n \"$br\" ] || continue\n    printf '  - %s\\n' \"$br\"\n  done \u003C\u003CEOF\n$BRANCH_CANDIDATES\nEOF\nfi\n\nif [ -z \"$WORKTREE_CANDIDATES\" ] && [ -z \"$BRANCH_CANDIDATES\" ]; then\n  echo \"\"\n  echo \"Nothing to clean. Exiting.\"\n  exit 0\nfi\n\nif [ \"$DRY_RUN\" = \"true\" ]; then\n  echo \"\"\n  echo \"Dry-run complete — no changes applied.\"\n  exit 0\nfi\n```\n\n### Step 8 — Confirmation Gate\n\n```bash\nif [ \"$ASSUME_YES\" != \"true\" ]; then\n  echo \"\"\n  read -r -p \"Proceed with deletion? [y\u002FN] \" ANS\n  case \"$ANS\" in\n    y|Y|yes|YES) ;;\n    *) echo \"Aborted by user.\"; exit 0 ;;\n  esac\nfi\n```\n\n### Step 9 — Switch Away From a Candidate HEAD\n\nIf the current branch is about to be deleted, git refuses `branch -D`. Switch to `develop` (fallback `main`) first.\n\n```bash\nneeds_switch=false\nif [ -n \"$CURRENT_BRANCH\" ]; then\n  while IFS= read -r b; do\n    [ -n \"$b\" ] || continue\n    if [ \"$b\" = \"$CURRENT_BRANCH\" ]; then\n      needs_switch=true\n      break\n    fi\n  done \u003C\u003CEOF\n$BRANCH_CANDIDATES\nEOF\nfi\n\nif [ \"$needs_switch\" = \"true\" ]; then\n  if git show-ref --verify --quiet refs\u002Fheads\u002Fdevelop; then\n    echo \"→ git checkout develop (HEAD was on a candidate branch)\"\n    git checkout develop\n  elif git show-ref --verify --quiet refs\u002Fheads\u002Fmain; then\n    echo \"→ git checkout main (develop missing; falling back)\"\n    git checkout main\n  else\n    echo \"ERROR: NO_SAFE_FALLBACK_BRANCH — neither develop nor main exists; cannot switch away from $CURRENT_BRANCH\" >&2\n    exit 1\n  fi\nfi\n```\n\n### Step 10 — Remove Worktrees\n\nIterate the candidate list with `while IFS= read -r` over a heredoc, so that worktree paths containing spaces (or glob metacharacters) are preserved as a single token. A plain `for` loop would word-split them.\n\n```bash\nWT_REMOVED=0\nwhile IFS= read -r wt; do\n  [ -n \"$wt\" ] || continue\n  echo \"→ git worktree remove --force $wt\"\n  if git worktree remove --force \"$wt\"; then\n    WT_REMOVED=$((WT_REMOVED + 1))\n  else\n    echo \"WARNING: failed to remove worktree: $wt\" >&2\n  fi\ndone \u003C\u003CEOF\n$WORKTREE_CANDIDATES\nEOF\ngit worktree prune\n```\n\n`--force` ensures worktrees with uncommitted changes are removed. This is intentional — the Print Plan step already showed them to the user.\n\n### Step 11 — Delete Local Branches\n\n```bash\nBR_DELETED=0\nwhile IFS= read -r br; do\n  [ -n \"$br\" ] || continue\n  echo \"→ git branch -D $br\"\n  if git branch -D \"$br\"; then\n    BR_DELETED=$((BR_DELETED + 1))\n  else\n    echo \"WARNING: failed to delete branch: $br\" >&2\n  fi\ndone \u003C\u003CEOF\n$BRANCH_CANDIDATES\nEOF\n```\n\n### Step 12 — Report Summary\n\n```bash\necho \"\"\necho \"=== Summary ===\"\necho \"Worktrees removed: $WT_REMOVED\"\necho \"Branches deleted:  $BR_DELETED\"\nexit 0\n```\n\n## Error Handling\n\n| Scenario | Action |\n|----------|--------|\n| `--dry-run` and `--yes` both set | Abort with exit 2 (usage error) |\n| Unknown flag passed | Abort with exit 2 (usage error) |\n| Running inside any linked worktree (path anywhere, not just `.claude\u002Fworktrees\u002F*`) | Abort with `IN_WORKTREE_UNSAFE`, exit 1 |\n| Not a git repo | Abort with `NOT_A_REPO` (from detect-context), exit 1 |\n| `origin` remote missing | Warn, skip fetch, continue |\n| HEAD is a candidate and neither `develop` nor `main` exists | Abort with `NO_SAFE_FALLBACK_BRANCH`, exit 1 |\n| Individual worktree remove fails | Warn, continue, count excludes it |\n| Individual branch delete fails | Warn, continue, count excludes it |\n| No candidates (worktrees empty and branches empty) | Print \"Nothing to clean\", exit 0 |\n\n## Security & Safety Notes\n\n- **Blast radius is local-only:** `git fetch` reads from origin; no `push`, no `--force-push`, no tag\u002Fremote-branch mutation. Remote state is untouched.\n- **Uncommitted work:** `git worktree remove --force` discards uncommitted changes inside secondary worktrees. This is surfaced in the Print Plan step and the user can decline at the confirmation gate.\n- **Protected set is literal:** filters use exact regex `^(main|master|develop)$` — no substring matches, no accidental protection of `my-develop-fix`.\n- **HEAD in main worktree is preserved implicitly:** `git worktree list` reports the main worktree first and the enumeration skips it.\n\n## Related Skills\n\n| Skill | Relationship |\n|-------|-------------|\n| `x-manage-worktrees` | `cleanup` operation is scoped to `.claude\u002Fworktrees\u002F*` with MERGED\u002FSTALE\u002FORPHAN criteria; this skill is broader and unconditional. |\n| `x-push-branch` | Typical upstream action after cleanup (push a fresh branch). |\n| `x-commit-changes` | Used to author commits — unrelated to cleanup, referenced here only for context. |\n\n## References\n\n- [Rule 09 — Branching Model](..\u002F..\u002F..\u002Frules\u002F09-branching-model.md): protected branches policy\n- [Rule 14 — Worktree Lifecycle](..\u002F..\u002F..\u002Frules\u002F14-worktree-lifecycle.md): non-nesting invariant driving step 2\n- [x-manage-worktrees\u002FSKILL.md](..\u002Fx-manage-worktrees\u002FSKILL.md): source of the canonical `detect_worktree_context()` snippet\n",{"name":5,"description":7,"allowed-tools":24,"argument-hint":25,"context-budget":26,"user-invocable":27,"requires-capabilities":28},"Bash, Read","[--dry-run] [--yes]","medium",true,[],"main","952a630c6f38d193a349d86fa18db1de8843e837","src\u002Ftest\u002Fresources\u002Fgolden\u002Fjava-spring-fintech-pci\u002F.claude\u002Fskills\u002Fx-cleanup-git-branches",1,"https:\u002F\u002Fgithub.com\u002Freview-bench\u002Federcnj_ia-dev-environment","https:\u002F\u002Fgithub.com\u002Freview-bench","https:\u002F\u002Freview-bench.ai","An open benchmark for AI code review.","2026-10-27 06:56:37",{"source":8,"skillName":5,"commands":39,"prompt":49,"zipAvailable":27},[40,43,46],{"tool":41,"command":42},"npx","npx skills add review-bench\u002Federcnj_ia-dev-environment --skill x-cleanup-git-branches",{"tool":44,"command":45},"bunx","bunx skills add review-bench\u002Federcnj_ia-dev-environment --skill x-cleanup-git-branches",{"tool":47,"command":48},"pnpm","pnpm dlx skills add review-bench\u002Federcnj_ia-dev-environment --skill x-cleanup-git-branches","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Freview-bench\u002Federcnj_ia-dev-environment\u002Fblob\u002Fmain\u002Fsrc\u002Ftest\u002Fresources\u002Fgolden\u002Fjava-spring-fintech-pci\u002F.claude\u002Fskills\u002Fx-cleanup-git-branches\u002FSKILL.md",[51],{"path":52,"resourceType":53,"resourceTypeDesc":54,"sizeBytes":55,"sourceHtmlUrl":56},"README.md","REFERENCE","参考文档",2137,"https:\u002F\u002Fgithub.com\u002Freview-bench\u002Federcnj_ia-dev-environment\u002Fblob\u002Fmain\u002Fsrc\u002Ftest\u002Fresources\u002Fgolden\u002Fjava-spring-fintech-pci\u002F.claude\u002Fskills\u002Fx-cleanup-git-branches\u002FREADME.md",[58,65,72,79,86,93],{"skillNo":59,"name":60,"nameZh":6,"description":61,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":62,"categories":63,"keywords":64,"publishedAt":19},"01M757M8J48TQ1Y3X25GSJBXAS","x-parallel-eval","Detects and classifies file-collision risks between work units (tasks, stories, or epic phases) before parallel execution. Reads ## File Footprint blocks produced by x-task-plan \u002F x-story-plan, applies the parallelism-heuristics knowledge pack (hard \u002F regen \u002F soft categories + hotspot overrides), and emits a collision matrix + serialization recommendation in Markdown (default) or JSON.","https:\u002F\u002Fgithub.com\u002Freview-bench\u002Federcnj_ia-dev-environment\u002Fblob\u002Fmain\u002Fsrc\u002Ftest\u002Fresources\u002Fgolden\u002Fx-parallel-eval\u002FSKILL.md",[],[],{"skillNo":66,"name":67,"nameZh":6,"description":68,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":69,"categories":70,"keywords":71,"publishedAt":19},"01M757M57XMR9546S69466HDWT","parallelism-heuristics","Canonical catalog of file-collision heuristics for parallel task, story, and epic execution. Defines the File Footprint block format, the three Conflict Categories (hard \u002F regen \u002F soft), a hotspot list of high-contention paths, and the degrade-with-warning policy. Consumed by x-task-plan, x-story-plan, x-epic-map, x-parallel-eval, and x-dev-*-implement.","https:\u002F\u002Fgithub.com\u002Freview-bench\u002Federcnj_ia-dev-environment\u002Fblob\u002Fmain\u002Fsrc\u002Ftest\u002Fresources\u002Fgolden\u002Fparallelism-heuristics\u002FSKILL.md",[],[],{"skillNo":73,"name":74,"nameZh":6,"description":75,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":76,"categories":77,"keywords":78,"publishedAt":19},"01M757M11SZ8P1VCQABT08QVYA","x-watch-pr-ci","Polls a PR's CI checks and Copilot review status, blocking until checks complete or timeout. Returns one of 8 stable exit codes (SUCCESS=0, CI_PENDING_PROCEED=10, CI_FAILED=20, TIMEOUT=30, PR_ALREADY_MERGED=40, NO_CI_CONFIGURED=50, PR_CLOSED=60, PR_NOT_FOUND=70). Writes a versioned state-file for session resume.","https:\u002F\u002Fgithub.com\u002Freview-bench\u002Federcnj_ia-dev-environment\u002Fblob\u002Fmain\u002Fsrc\u002Ftest\u002Fresources\u002Fgolden\u002Fjava-spring\u002Fplatform-claude-code\u002F.claude\u002Fskills\u002Fx-watch-pr-ci\u002FSKILL.md",[],[],{"skillNo":80,"name":81,"nameZh":6,"description":82,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":83,"categories":84,"keywords":85,"publishedAt":19},"01M757KW53K8KFPGADFB4PH949","x-validate-docs","Documentation freshness gate: validates 6 dimensions (readme, api-specs, grpc-proto, adr, skill-docs, system-architecture) against code changes in a PR. Stack-aware — only validates targets declared\u002Fauto-detected from ProjectConfig.documentation.targets. Returns exit non-zero on staleness; produces structured report.","https:\u002F\u002Fgithub.com\u002Freview-bench\u002Federcnj_ia-dev-environment\u002Fblob\u002Fmain\u002Fsrc\u002Ftest\u002Fresources\u002Fgolden\u002Fjava-spring\u002Fplatform-claude-code\u002F.claude\u002Fskills\u002Fx-validate-docs\u002FSKILL.md",[],[],{"skillNo":87,"name":88,"nameZh":6,"description":89,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":90,"categories":91,"keywords":92,"publishedAt":19},"01M757KQZHSA10MKC0CR571D60","x-update-system-architecture","Incrementally updates docs\u002Farchitecture\u002Fsystem.md after an epic completes. Appends new entries to the Decision Log (§11) using x-internal-write-report --append (dedup by ## ID: marker) and surgically inserts component\u002Fintegration changes into sections 1-10 via Edit. Idempotent: re-running with the same epic produces a byte-identical system.md.","https:\u002F\u002Fgithub.com\u002Freview-bench\u002Federcnj_ia-dev-environment\u002Fblob\u002Fmain\u002Fsrc\u002Ftest\u002Fresources\u002Fgolden\u002Fjava-spring\u002Fplatform-claude-code\u002F.claude\u002Fskills\u002Fx-update-system-architecture\u002FSKILL.md",[],[],{"skillNo":94,"name":95,"nameZh":6,"description":96,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":97,"categories":98,"keywords":99,"publishedAt":19},"01M757KKBT5Q1SVF8DMJBVF46M","x-update-architecture","Incrementally updates the service architecture document with changes from architecture plans. Adds new components, integrations, flows, and ADR references without rewriting existing content. Use after implementation to keep architecture documentation current.","https:\u002F\u002Fgithub.com\u002Freview-bench\u002Federcnj_ia-dev-environment\u002Fblob\u002Fmain\u002Fsrc\u002Ftest\u002Fresources\u002Fgolden\u002Fjava-spring\u002Fplatform-claude-code\u002F.claude\u002Fskills\u002Fx-update-architecture\u002FSKILL.md",[],[]]