cleanup-repo

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

cleanup-repo

cleanup-repo

Remove merged Git worktrees and branches, then run a filesystem-hygiene pass — each pass behind its own confirmation gate, with a
--dry-run
preview.
The branch/worktree pass mirrors the behaviour of the
/cleanup-branches
slash command it was extracted from. The filesystem-hygiene pass (recursively-empty directories and orphan
node_modules/
) is new to this skill. See
references/design-notes.md
for the naming rationale and the deliberately-deferred future extensions.
移除已合并的Git工作树和分支,然后执行文件系统清理操作——每个操作阶段都有独立的确认环节,并支持
--dry-run
预览功能。
分支/工作树清理阶段的行为与它提取自的
/cleanup-branches
斜杠命令一致。文件系统清理阶段(递归清理空目录和孤立的
node_modules/
)是本技能新增的功能。命名依据和暂未实现的未来扩展可参考
references/design-notes.md

Configuration

配置

Five knobs live in
config.json
beside this file. Read it at the start of a run and use its values throughout. Edit your copied
config.json
to match the consuming repo:
KeyMeaningDefault
linearTeamName
Linear team name used to resolve the live
Done
state. Use the name, not the key — the key is renamed over time but the name is stable.
"ACME Skunkworks"
issueKeys
Team-key prefixes that may appear in branch names. The issue-ID regex is built from these.
["A"]
mainBranch
The trunk a branch must be merged into to count as merged — both passes diff against
origin/<mainBranch>
. Set it for repos whose trunk is
master
,
develop
, or similar.
"main"
protectedBranches
Branches that are never deleted, locally or remotely.
["main"]
linearWritebackDefault
Seeds the yes/no default of the Step 10 Linear
Done
writeback prompt —
"yes"
pre-fills yes,
"no"
pre-fills no. The interactive gate always stays; this never auto-applies. Absent or unrecognised → treated as
"no"
.
"no"
Build the issue-ID regex deterministically: escape each key's regex metacharacters, and when there is more than one key wrap the alternation in
(?:…)
so the
-\d+
binds to all of them —
\b(?:A|B)-\d+\b
, never the naive join
\bA|B-\d+\b
(which parses as
\bA
or
B-\d+\b
). A single key needs no wrapper:
\bA-\d+\b
. With no keys configured, match nothing. This mirrors the canonical
buildIssueRe
in the repo-root
lib/issue-keys.mjs
, which
pnpm vendor:sync
copies into each consuming bundle (ADR-0004). Match it against the upper-cased branch name (branches like
asw-7-as-acquired
carry the key in lower case).
If the Linear MCP server is not available, skip the Linear status check and the optional
Done
writeback silently — they are not required for branch cleanup.
本文件旁的
config.json
中有五个配置项。程序会在运行开始时读取该文件,并全程使用其中的值。请编辑你复制的
config.json
以匹配目标仓库的需求:
键名含义默认值
linearTeamName
用于解析实时「Done」状态的Linear团队名称。使用名称而非键——键会随时间变更,但名称保持稳定。
"ACME Skunkworks"
issueKeys
可能出现在分支名称中的团队键前缀。问题ID正则表达式会基于这些前缀构建。
["A"]
mainBranch
分支必须合并到的主干分支,只有合并到该分支的分支才会被判定为已合并——两个检测阶段都会与
origin/<mainBranch>
进行对比。对于主干分支为
master
develop
或类似名称的仓库,请设置此值。
"main"
protectedBranches
绝不会被删除的分支,无论本地还是远程。
["main"]
linearWritebackDefault
为步骤10的Linear「Done」状态回写提示设置默认的是/否选项——
"yes"
会预填“是”,
"no"
会预填“否”。交互确认环节始终保留;此配置绝不会自动应用。若配置缺失或无法识别,则视为
"no"
"no"
确定性构建问题ID正则表达式:转义每个键中的正则元字符,当存在多个键时,用
(?:…)
包裹备选分支,使
-\d+
绑定到所有键——即
\b(?:A|B)-\d+\b
,绝不能直接拼接为
\bA|B-\d+\b
(这会被解析为
\bA
B-\d+\b
)。单个键无需包裹:
\bA-\d+\b
。若未配置任何键,则不匹配任何内容。这与仓库根目录
lib/issue-keys.mjs
中的标准
buildIssueRe
逻辑一致,
pnpm vendor:sync
会将其复制到每个消费包中(ADR-0004)。正则表达式会与大写的分支名称进行匹配(类似
asw-7-as-acquired
的分支名称中键为小写)。
若Linear MCP服务器不可用,会静默跳过Linear状态检查和可选的「Done」状态回写——这些操作并非分支清理的必需步骤。

Usage modes

使用模式

Dry run — preview everything, change nothing:
bash
cleanup-repo --dry-run
Normal — preview, then delete after confirmation:
bash
cleanup-repo
Scope flags — run only one pass when you don't want the other (mutually exclusive):
bash
cleanup-repo --branches-only   # branch/worktree pass only; skip the filesystem pass
cleanup-repo --fs-only         # filesystem pass only; skip branches/worktrees
These are skill invocations, not a standalone CLI:
cleanup-repo
is the skill and the flags are passed through
$ARGUMENTS
(the agent reads them from there), so a bare
cleanup-repo --dry-run
in a shell does nothing.
The branch/worktree pass and the filesystem pass are confirmed separately (Step 8). Their blast radii differ — a branch prune is recoverable (reflog, re-push, the PR still exists) and an orphan
node_modules/
reinstalls, but a swept empty-directory tree may be something you meant to keep — so you can accept one pass and decline the other.
--dry-run
short-circuits before any confirmation;
--branches-only
/
--fs-only
drop the other pass (and its preview and prompt) entirely.
试运行——预览所有操作,不做任何变更:
bash
cleanup-repo --dry-run
正常模式——预览后,确认再执行删除操作:
bash
cleanup-repo
范围标记——仅运行其中一个阶段,跳过另一个(互斥):
bash
cleanup-repo --branches-only   # 仅运行分支/工作树清理阶段;跳过文件系统清理阶段
cleanup-repo --fs-only         # 仅运行文件系统清理阶段;跳过分支/工作树清理阶段
这些是技能调用命令,而非独立CLI:
cleanup-repo
是技能名称,标记会通过
$ARGUMENTS
传递(Agent会从该变量读取),因此在Shell中直接执行
cleanup-repo --dry-run
不会产生任何效果。
分支/工作树清理阶段和文件系统清理阶段会分别确认(步骤8)。它们的影响范围不同——分支修剪可恢复(通过reflog、重新推送,PR仍存在),孤立的
node_modules/
可重新安装,但清理空目录树可能会误删你想保留的内容——因此你可以选择接受其中一个阶段,拒绝另一个。
--dry-run
会在任何确认环节前终止流程;
--branches-only
/
--fs-only
会直接跳过另一个阶段(及其预览和提示)。

Process

流程

Scope flags. Under
--branches-only
, skip Step 5 (filesystem detection) and the filesystem removal — run only the branch/worktree pass. Under
--fs-only
, skip Steps 2–4 and 9.1–9.4 (worktrees, branches, remotes, Linear) — run only the filesystem pass. The two flags are mutually exclusive; with neither, both passes run and are confirmed separately (Step 8).
范围标记说明。在
--branches-only
模式下,跳过步骤5(文件系统检测)和文件系统删除操作——仅运行分支/工作树清理阶段。在
--fs-only
模式下,跳过步骤2–4和9.1–9.4(工作树、分支、远程仓库、Linear操作)——仅运行文件系统清理阶段。这两个标记互斥;若未设置任何标记,则会运行两个阶段,并分别确认(步骤8)。

Step 1 — Fetch latest from remote

步骤1 — 拉取远程仓库最新内容

bash
git fetch --prune origin
bash
git fetch --prune origin

Step 2 — Identify worktrees to remove

步骤2 — 识别待移除的工作树

bash
git worktree list
  • List all worktrees except the main repository directory (the primary working directory is never removed).
  • Identify worktrees whose branch is fully merged into the trunk (
    origin/<mainBranch>
    , default
    origin/main
    ).
  • Identify worktrees in detached-HEAD state — treat as abandoned, safe to remove.
  • Identify worktrees with uncommitted changes:
    git -C <path> status --porcelain
    non-empty. These are surfaced separately in Step 6 and never removed automatically — the user handles them manually (
    git worktree remove --force <path>
    once they have moved or discarded the work).
  • Worktree location is irrelevant to detection;
    git worktree list
    enumerates them wherever they live (e.g. a gitignored
    .claude/worktrees/<branch>/
    ).
bash
git worktree list
  • 列出所有工作树,但排除主仓库目录(主工作目录绝不会被移除)。
  • 识别已完全合并到主干分支(
    origin/<mainBranch>
    ,默认
    origin/main
    )的工作树对应的分支。
  • 识别处于分离HEAD状态的工作树——视为已废弃,可安全移除。
  • 识别存在未提交变更的工作树:
    git -C <path> status --porcelain
    输出非空。这些工作树会在步骤6中单独列出,且绝不会被自动移除——用户需手动处理(在移动或丢弃变更后,执行
    git worktree remove --force <path>
    )。
  • 工作树的位置不影响检测;
    git worktree list
    会枚举所有位置的工作树(例如被git忽略的
    .claude/worktrees/<branch>/
    )。

Step 3 — Identify merged branches (two-pass)

步骤3 — 识别已合并的分支(双阶段)

Pass 1 — Git-merged branches:
  • Find local branches merged into the trunk:
    git branch --merged origin/<mainBranch>
    (default
    origin/main
    ).
  • Exclude every branch in
    protectedBranches
    .
  • Determine which of those branches also still exist on the remote.
Pass 2 — Squash-merged branches:
A squash merge lands a single new commit on the trunk, so the branch's own commits are never ancestors of
origin/<mainBranch>
and
git branch --merged
misses it. For each local branch not caught in Pass 1 (and not protected):
bash
gh pr list --head <branch-name> --base <mainBranch> --state merged \
  --json number,title,headRefOid --limit 1
gh
auto-detects the repository from the current directory's remote, so no
--repo
flag is needed.
  • --base <mainBranch>
    is required.
    gh pr list --head
    does not filter on base on its own, so without it a branch merged into a different base (a stacked/feature base, not the trunk) would be wrongly counted as merged-to-trunk and deleted. Scoping to
    --base <mainBranch>
    (default
    main
    ) restricts the match to PRs actually merged into the trunk.
  • A non-empty result means the branch has a merged-to-trunk PR. Record its
    number
    and
    title
    (for the summary) and its
    headRefOid
    (the exact commit the PR merged).
  • Guard against post-merge commits. Compare the local tip to the merged PR's head:
    git rev-parse <branch-name>
    vs
    headRefOid
    .
    • Equal → the branch is fully merged; add it to the squash-merged cleanup list (Step 9.3 force-deletes it, which is safe because the tip matched).
    • Not equal → the local branch carries commits added after the PR merged. Do not delete it; add it to a "Skipped — local tip ahead of merged PR" group so a plain
      -D
      can't silently discard unpushed work.
  • An empty result means the branch is genuinely unmerged — leave it alone.
阶段1 — Git合并的分支:
  • 找出已合并到主干分支的本地分支:
    git branch --merged origin/<mainBranch>
    (默认
    origin/main
    )。
  • 排除
    protectedBranches
    中的所有分支。
  • 确定这些分支中哪些仍存在于远程仓库。
阶段2 — Squash合并的分支:
Squash合并会在主干分支上生成一个全新的提交,因此分支自身的提交永远不会成为
origin/<mainBranch>
的祖先,
git branch --merged
会遗漏这类分支。对于所有未在阶段1中被检测到(且未受保护)的本地分支:
bash
gh pr list --head <branch-name> --base <mainBranch> --state merged \
  --json number,title,headRefOid --limit 1
gh
会自动从当前目录的远程仓库信息中识别仓库,因此无需
--repo
标记。
  • 必须设置
    --base <mainBranch>
    gh pr list --head
    本身不会基于base进行过滤,因此若未设置该标记,合并到其他base(堆叠/功能分支,而非主干分支)的分支会被错误判定为已合并到主干分支并被删除。将范围限定为
    --base <mainBranch>
    (默认
    main
    )可确保仅匹配真正合并到主干分支的PR。
  • 若返回结果非空,则表示该分支对应的PR已合并到主干分支。记录其
    number
    title
    (用于汇总)以及
    headRefOid
    (PR合并时的精确提交)。
  • 防止合并后提交。对比本地分支的最新提交与合并PR的头提交:
    git rev-parse <branch-name>
    vs
    headRefOid
    • 相等 → 分支已完全合并;将其添加到squash合并清理列表(步骤9.3会强制删除该分支,由于提交匹配,此操作是安全的)。
    • 不相等 → 本地分支包含PR合并后新增的提交。请勿删除;将其添加到「跳过——本地提交领先于合并PR」组,避免普通的
      -D
      命令静默丢弃未推送的工作内容。
  • 若返回结果为空,则表示该分支确实未合并——保留该分支。

Step 4 — Check Linear issue status for merged branches

步骤4 — 检查已合并分支对应的Linear问题状态

For each merged branch whose name contains an issue ID (extract with the regex built from
issueKeys
, matched against the upper-cased branch name):
  • Fetch the issue via
    mcp__linear-server__get_issue
    .
  • Track any issue that is not in
    Done
    status.
Skip this step silently if the Linear MCP server is unavailable.
对于名称中包含问题ID的已合并分支(使用基于
issueKeys
构建的正则表达式,与大写的分支名称匹配提取):
  • 通过
    mcp__linear-server__get_issue
    获取问题信息。
  • 跟踪所有未处于「Done」状态的问题。
若Linear MCP服务器不可用,会静默跳过此步骤。

Step 5 — Run the filesystem-hygiene detection

步骤5 — 执行文件系统清理检测

Run the bundled script against the repository root to get the candidate list. It is read-only without
--apply
:
bash
node scripts/filesystem-hygiene.mjs <repo-root> --json
Two paths here point at different places, so resolve each deliberately:
  • <repo-root>
    is the target repository being cleaned — obtain it with
    git rev-parse --show-toplevel
    . The script refuses to run against a root with no
    .git
    entry, so a mis-pointed root can't sweep arbitrary directories.
  • scripts/filesystem-hygiene.mjs
    is relative to this skill bundle (where this
    SKILL.md
    lives), not to
    <repo-root>
    . If your working directory is the target repo, give the script its absolute bundle path.
It prints
{ "emptyDirs": [...], "orphanNodeModules": [...] }
:
  • emptyDirs
    — top-most recursively-empty directories (no files anywhere in the subtree). Directories holding any file — including a
    .gitkeep
    /
    .gitignore
    placeholder — are left alone.
    .git/
    is never traversed.
  • orphanNodeModules
    node_modules/
    directories whose immediate parent has no
    package.json
    (strict; no workspace inference). Removing one re-installs is needed if the parent was not actually meant to be gone — which is why these are surfaced separately.
This detection is read-only and feeds the Step 6 preview. One subtlety: Step 9 removes worktrees before re-running the detection with
--apply
, so the apply pass can additionally sweep a parent that becomes empty only once its worktrees are gone (e.g.
.claude/worktrees/
). Such a directory won't appear in this pre-removal detect output — predict it from the worktree-removal list and label it as a post-removal sweep in the preview, so the user isn't surprised when
--apply
removes it.
针对仓库根目录运行捆绑脚本,获取候选清理列表。在未添加
--apply
标记时,脚本仅执行只读检测:
bash
node scripts/filesystem-hygiene.mjs <repo-root> --json
此处的两个路径指向不同位置,因此需明确解析:
  • <repo-root>
    待清理的目标仓库——通过
    git rev-parse --show-toplevel
    获取。若根目录中没有
    .git
    目录,脚本会拒绝运行,避免误清理任意目录。
  • scripts/filesystem-hygiene.mjs
    位于本技能包内(即本
    SKILL.md
    所在目录),而非
    <repo-root>
    。若你的工作目录是目标仓库,请提供该脚本的绝对路径。
脚本会输出
{ "emptyDirs": [...], "orphanNodeModules": [...] }
  • emptyDirs
    — 最顶层的递归空目录(子树中无任何文件)。包含任何文件的目录——包括
    .gitkeep
    /
    .gitignore
    占位文件——都会被保留。绝不会遍历
    .git/
    目录。
  • orphanNodeModules
    — 父目录中无
    package.json
    node_modules/
    目录(严格匹配;不支持工作区推断)。若父目录并非真的需要删除,移除该目录后可重新安装——这也是为何要将这些目录单独列出的原因。
此检测为只读操作,结果会用于步骤6的预览。一个细节:步骤9会先移除工作树,然后重新运行带
--apply
标记的检测,因此应用阶段可额外清理工作树移除后变为空的父目录(例如
.claude/worktrees/
)。这类目录不会出现在预移除检测的输出中——需从工作树移除列表中预判,并在预览中标记为移除后清理,避免用户在
--apply
执行时感到意外。

Step 6 — Display everything to be deleted

步骤6 — 显示所有待删除内容

Show clear, counted lists. Keep the filesystem groups separate so the user can eyeball them:
text
undefined
展示清晰的计数列表。将文件系统相关分组单独列出,方便用户查看:
text
undefined

Worktrees to Remove (3)

待移除的工作树(3个)

  • /path/.claude/worktrees/A-7-as-acquired (merged)
  • /path/.claude/worktrees/A-9-button-styling (squash-merged, PR #42)
  • /path/.claude/worktrees/orphan-detached (detached HEAD)
  • /path/.claude/worktrees/A-7-as-acquired(已合并)
  • /path/.claude/worktrees/A-9-button-styling(Squash合并,PR #42)
  • /path/.claude/worktrees/orphan-detached(分离HEAD状态)

Worktrees Skipped — Uncommitted Changes (1)

跳过的工作树——存在未提交变更(1个)

  • /path/.claude/worktrees/A-12-wip (merged, but
    git status
    is non-empty; remove manually with
    git worktree remove --force <path>
    )
  • /path/.claude/worktrees/A-12-wip(已合并,但
    git status
    输出非空;请手动执行
    git worktree remove --force <path>
    移除)

Local Branches to Delete (3)

待删除的本地分支(3个)

  • A-7-as-acquired (merged)
  • A-9-button-styling (squash-merged, PR #42 "Fix button styling")
  • chore-update-deps (merged)
  • A-7-as-acquired(已合并)
  • A-9-button-styling(Squash合并,PR #42 "修复按钮样式")
  • chore-update-deps(已合并)

Local Branches Skipped — Tip Ahead of Merged PR (1)

跳过的本地分支——提交领先于合并PR(1个)

  • A-15-extra-tweaks (squash-merged PR #50, but local tip has commits added after the merge; delete manually with
    git branch -D A-15-extra-tweaks
    if they're disposable)
  • A-15-extra-tweaks(Squash合并PR #50,但本地提交包含合并后新增的内容;若这些内容可丢弃,请手动执行
    git branch -D A-15-extra-tweaks
    删除)

Remote Branches to Delete (2)

待删除的远程分支(2个)

  • A-7-as-acquired
  • A-9-button-styling
  • A-7-as-acquired
  • A-9-button-styling

Linear Issues Still Open (1)

仍处于打开状态的Linear问题(1个)

  • A-9 "Button styling" — currently In Review (branch: A-9-button-styling)
  • A-9 "按钮样式" — 当前状态为In Review(分支:A-9-button-styling)

Empty Directories to Remove (1)

待移除的空目录(1个)

  • /path/.claude/worktrees (predicted: empty once the worktrees above are removed)
  • /path/.claude/worktrees (预判:上述工作树移除后变为空)

Orphan node_modules to Remove (1)

待移除的孤立node_modules(1个)

  • /path/old-package/node_modules (no sibling package.json)
undefined
  • /path/old-package/node_modules (无同级package.json)
undefined

Step 7 — Dry-run handling

步骤7 — 试运行处理

If
--dry-run
is set, STOP here. Print
DRY RUN MODE - No changes were made
and exit without changing anything.
若设置了
--dry-run
,在此处终止流程。打印
DRY RUN MODE - No changes were made
并退出,不做任何变更。

Step 8 — Confirmation (normal mode only)

步骤8 — 确认(仅正常模式)

Confirm the two passes separately — their blast radii and reversibility differ, so the user may accept one and decline the other:
  1. Branch/worktree pass — ask
    Delete these worktrees and branches (local + remote)? (yes/no)
    . On
    no
    , skip the worktree/branch/remote deletion (Step 9.1–9.4) and the Linear
    Done
    writeback (Step 10).
  2. Filesystem pass — ask
    Remove these empty directories and orphan node_modules? (yes/no)
    . On
    no
    , skip the filesystem removal (Step 9.5).
Rules:
  • Skip the prompt for a pass a scope flag already excluded (
    --branches-only
    drops prompt 2;
    --fs-only
    drops prompt 1) — that pass never ran or previewed.
  • Skip the prompt for a pass that has nothing to do (all its Step 6 lists empty); note it and move on.
  • If both passes are declined (or empty), exit without deleting.
Proceed to Step 9 with only the passes the user confirmed.
分别确认两个阶段——它们的影响范围和可恢复性不同,因此用户可选择接受其中一个,拒绝另一个:
  1. 分支/工作树阶段 — 询问
    是否删除这些工作树和分支(本地+远程)?(是/否)
    。若选择“否”,则跳过工作树/分支/远程删除操作(步骤9.1–9.4)和Linear「Done」状态回写(步骤10)。
  2. 文件系统阶段 — 询问
    是否移除这些空目录和孤立node_modules?(是/否)
    。若选择“否”,则跳过文件系统移除操作(步骤9.5)。
规则:
  • 若范围标记已排除某个阶段(
    --branches-only
    会跳过提示2;
    --fs-only
    会跳过提示1),则跳过该阶段的提示——该阶段从未运行或预览。
  • 若某个阶段无任何操作可执行(步骤6中的所有列表均为空),则跳过该阶段的提示;记录此情况并继续流程。
  • 若两个阶段均被拒绝(或均无操作),则退出流程,不执行任何删除操作。
仅针对用户确认的阶段,进入步骤9。

Step 9 — Execute, in order

步骤9 — 按顺序执行

Order matters. Worktrees must go before their branches, and the filesystem pass runs after worktree removal so a just-emptied worktree parent (e.g.
.claude/worktrees/
) is swept in the same run.
  1. Remove worktrees (skip the uncommitted-changes group from Step 6):
    bash
    git worktree remove <path>
  2. Prune stale worktree references:
    bash
    git worktree prune
  3. Delete local branches (skip this whole sub-step if the branch/worktree pass was declined or excluded by
    --fs-only
    ):
    bash
    git branch -d <branch-name>   # Pass 1 (git-merged) — safe delete
    git branch -D <branch-name>   # Pass 2 (squash-merged) — force is safe ONLY for
                                  # branches whose local tip matched the merged PR's
                                  # headRefOid in Step 3
    Force-delete (
    -D
    ) only the squash-merged branches confirmed in Step 3 — those whose local tip equalled the merged PR's
    headRefOid
    . Branches in the "Skipped — local tip ahead of merged PR" group are never force-deleted here: they carry commits added after the merge, and
    -D
    would discard them. The base-scoped
    gh pr list --base <mainBranch>
    and the tip check together are what make the force safe; without them
    -D
    could drop a branch merged into a different base or one with unpushed work.
    The branch you are currently on — or one checked out in a worktree — cannot be deleted:
    git branch -d
    fails by design. The per-item error handling catches it and moves on, so it is auto-skipped; this is expected, not a failure.
  4. Delete remote branches that still exist:
    bash
    git push origin --delete <branch-name>
  5. Filesystem-hygiene removal — re-run the bundled script with
    --apply
    . It removes exactly the same set it detects and prints what it removed:
    bash
    node scripts/filesystem-hygiene.mjs <repo-root> --apply
    <repo-root>
    and the bundle-relative
    scripts/
    path resolve exactly as in Step 5 (
    git rev-parse --show-toplevel
    for the root; the script lives in this skill bundle). Removing an orphan
    node_modules/
    can leave its parent empty; that parent is intentionally left for a follow-up run rather than swept in this snapshot.
顺序至关重要。必须先移除工作树,再删除其对应的分支;文件系统阶段需在工作树移除之后运行,这样刚变为空的工作树父目录(例如
.claude/worktrees/
)可在同一次运行中被清理。
  1. 移除工作树(跳过步骤6中存在未提交变更的分组):
    bash
    git worktree remove <path>
  2. 修剪陈旧的工作树引用:
    bash
    git worktree prune
  3. 删除本地分支(若分支/工作树阶段被拒绝或被
    --fs-only
    排除,则跳过此子步骤):
    bash
    git branch -d <branch-name>   # 阶段1(Git合并的分支)——安全删除
    git branch -D <branch-name>   # 阶段2(Squash合并的分支)——仅当
                                  # 步骤3中本地分支的最新提交与合并PR的
                                  # headRefOid匹配时,强制删除才是安全的
    仅对步骤3中确认的Squash合并分支执行强制删除(
    -D
    )——即本地分支的最新提交与合并PR的
    headRefOid
    相等的分支。「跳过——本地提交领先于合并PR」组中的分支绝不会在此处被强制删除:它们包含合并后新增的提交,
    -D
    会丢弃这些提交。基于base范围的
    gh pr list --base <mainBranch>
    和提交检查共同确保了强制删除的安全性;若缺少这些检查,
    -D
    可能会删除合并到其他base的分支或包含未推送工作内容的分支。
    当前所在的分支——或在工作树中被检出的分支——无法被删除:
    git branch -d
    会默认失败。逐项错误处理会捕获此情况并继续流程,因此该分支会被自动跳过;这是预期行为,而非故障。
  4. 删除仍存在的远程分支:
    bash
    git push origin --delete <branch-name>
  5. 文件系统清理移除——重新运行捆绑脚本并添加
    --apply
    标记。脚本会移除其检测到的完全相同的内容,并输出已移除的内容:
    bash
    node scripts/filesystem-hygiene.mjs <repo-root> --apply
    <repo-root>
    和捆绑包相对路径
    scripts/
    的解析方式与步骤5完全一致(通过
    git rev-parse --show-toplevel
    获取根目录;脚本位于本技能包内)。移除孤立的
    node_modules/
    可能会使其父目录变为空;该父目录会留到后续运行中清理,而非在本次快照中被清理。

Step 10 — Optional Linear
Done
writeback

步骤10 — 可选的Linear「Done」状态回写

If any Linear issues from Step 4 are not
Done
:
  • Ask:
    These Linear issues are linked to merged branches but aren't Done. Set them to Done? (yes/no)
    . Seed the default from
    linearWritebackDefault
    "yes"
    pre-fills the prompt with yes, anything else —
    "no"
    , an absent key, or an unrecognised value — pre-fills no. The prompt is always shown and the answer always confirmed — the knob only moves the default, it never auto-applies. The default is
    no
    because Linear's GitHub integration normally handles this on PR merge, so the writeback exists only for the rare case where it didn't fire (e.g. the issue ID was added after the merge); a repo not wired to that integration can flip the default to
    yes
    .
  • If yes:
    • Resolve the live
      Done
      state ID once via
      mcp__linear-server__list_issue_statuses
      with
      team: <linearTeamName>
      — state IDs are per-team and the team key changes over time, so pass the team name.
    • For each open issue, call
      mcp__linear-server__save_issue
      with
      state: <Done state ID>
      .
  • If no, skip without changes.
若步骤4中存在未处于「Done」状态的Linear问题:
  • 询问:
    这些Linear问题已关联到已合并分支,但未标记为Done。是否将其设置为Done?(是/否)
    。默认选项由
    linearWritebackDefault
    配置——
    "yes"
    会将提示预填为“是”,其他值——
    "no"
    、配置缺失或无法识别的值——会预填为“否”。提示始终会显示,且答案始终需确认——该配置仅设置默认选项,绝不会自动应用。默认值为
    "no"
    ,因为Linear的GitHub集成通常会在PR合并时自动处理此操作,状态回写仅用于集成未触发的罕见情况(例如问题ID在合并后才添加);若仓库未接入该集成,可将默认值改为
    "yes"
  • 若选择“是”:
    • 通过
      mcp__linear-server__list_issue_statuses
      并传入
      team: <linearTeamName>
      一次性解析实时的「Done」状态ID——状态ID是按团队划分的,且团队键会随时间变更,因此需传入团队名称
    • 对于每个打开的问题,调用
      mcp__linear-server__save_issue
      并传入
      state: <Done状态ID>
  • 若选择“否”,则跳过此步骤,不做任何变更。

Step 11 — Summary

步骤11 — 汇总

Report counts: worktrees removed, local branches deleted, remote branches deleted, empty directories removed, orphan
node_modules/
removed, Linear issues set to
Done
(if any). List the names of deleted items.
报告计数:已移除的工作树数量、已删除的本地分支数量、已删除的远程分支数量、已移除的空目录数量、已移除的孤立
node_modules/
数量、已设置为「Done」的Linear问题数量(若有)。列出已删除项的名称。

Important rules

重要规则

  • Dry-run previews without deleting (
    --dry-run
    ).
  • Confirmation required before any deletion — the branch/worktree pass and the filesystem pass are confirmed separately, so each can be accepted or declined on its own.
  • Scope flags:
    --branches-only
    runs only the branch/worktree pass;
    --fs-only
    runs only the filesystem pass (mutually exclusive).
  • Protected branches (
    protectedBranches
    ) are never touched.
  • Merged only: a branch is deleted only if merged into the trunk (
    origin/<mainBranch>
    ) via git ancestry or a merged GitHub PR whose base is
    <mainBranch>
    (squash merges). A squash-merged branch is force-deleted only when its local tip still matches the merged PR's head commit — a branch with post-merge commits is surfaced and skipped, never
    -D
    'd.
  • Worktrees first, then branches; filesystem pass last.
  • Uncommitted worktrees are never force-removed automatically.
  • .git/
    and the main worktree
    are never touched.
  • 试运行:预览操作而不执行删除(
    --dry-run
    )。
  • 需确认:执行任何删除操作前必须确认——分支/工作树阶段和文件系统阶段会分别确认,因此可单独接受或拒绝每个阶段。
  • 范围标记
    --branches-only
    仅运行分支/工作树阶段;
    --fs-only
    仅运行文件系统阶段(互斥)。
  • 受保护分支
    protectedBranches
    )绝不会被改动。
  • 仅删除已合并分支:仅当分支通过Git祖先记录已合并的GitHub PR(base为
    <mainBranch>
    )合并到主干分支(
    origin/<mainBranch>
    )时,才会被删除。仅当Squash合并分支的本地最新提交仍与合并PR的头提交匹配时,才会强制删除该分支——包含合并后提交的分支会被列出并跳过,绝不会被
    -D
    删除。
  • 先处理工作树,再处理分支;最后执行文件系统阶段。
  • 存在未提交变更的工作树绝不会被自动强制移除。
  • .git/
    目录和主工作树
    绝不会被改动。

Error handling

错误处理

  • Skip (and report) any worktree or branch that fails to remove; continue with the rest.
  • Skip remote branches that no longer exist (already deleted).
  • If
    gh pr list
    fails for a branch (network, rate limit, auth), log a warning and continue — do not treat the branch as merged.
  • If the Linear MCP server is unavailable, skip the Linear steps silently.
  • 跳过(并报告)任何移除失败的工作树或分支;继续处理剩余项。
  • 跳过已不存在的远程分支(已被删除)。
  • 若某个分支的
    gh pr list
    执行失败(网络问题、速率限制、认证失败),记录警告并继续流程——请勿将该分支判定为已合并。
  • 若Linear MCP服务器不可用,会静默跳过Linear相关步骤。

Arguments

参数

$ARGUMENTS
$ARGUMENTS