开工前先开隔离工作区:Agent 方法论框架 superpowers 的 worktree 流程与安全检查
本文基于 superpowers 仓库 commit 44c9b2d(2026-07-27)梳理,该项目仍在持续迭代,具体行为以仓库 https://github.com/obra/superpowers 最新代码与文档为准。
superpowers 在工作区隔离上真正花力气的地方,不是教你敲 git worktree add,而是把「开工前必须先确认自己在哪」和「收尾时只清理自己造出来的东西」写成了两条不许绕开的检查。 这两条检查解决的都不是 git 的问题,而是 Agent 的问题:一个能自主执行几十步的编码 Agent,最容易犯的错是在你正开着编辑器的那个分支上直接动手,以及干完活儿之后热心地把不属于它的目录也一起收了。
这篇讲的是具体实现。站内已经有两篇讲通用方法论的文章——Claude Code 用 worktree 做并行开发 讲的是为什么要并行、怎么切分任务,Agent 工作区隔离 讲的是隔离这件事本身的设计取向;本篇不重复那些判断,只看 superpowers 这个仓库把它落成了什么样的文件、什么样的命令顺序、什么样的兜底分支。你可以把这三篇当成「为什么做—怎么想—别人怎么写的」来读。
superpowers 采用 MIT 许可证,仓库根目录下的 skills/ 里放着 14 个技能目录,每个技能是一份写给 Agent 读的 SKILL.md。本文的事实来源集中在其中两个:skills/using-git-worktrees/SKILL.md 和 skills/finishing-a-development-branch/SKILL.md。
一、它要解决的问题:Agent 不知道自己站在哪儿
让 Agent 干活儿,第一个风险是它对当前环境的判断来自「看上去」。skills/using-git-worktrees/SKILL.md 里那张 Common Rationalizations(借口对照表)第一行就写了这个借口:「我显然不在 worktree 里,没必要检查」,对应的反驳是——宿主创建的隔离环境和 git 子模块都能骗过肉眼,只有检测命令能定论。
这不是假想。现在的编码 Agent 运行环境五花八门:有的宿主会先给你开好一个隔离工作区再把 Agent 放进去,有的直接在你的主检出里跑。Agent 如果不先探一下,可能出现两种反向的事故:在已经隔离好的环境里又套一层 worktree,制造出宿主管不到的幽灵状态;或者在主检出里以为自己是隔离的,直接往你正在用的分支上写。
所以这个技能的核心原则被写成了一句顺序声明:先检测已有隔离,再用原生工具,最后才退回 git,永远不要跟宿主对着干。
二、开工前的动作序列
Step 0:先检测,再问人
技能开头就要求跑三条只读命令:
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
BRANCH=$(git branch --show-current)
判据是 GIT_DIR 和 GIT_COMMON 是否相等。不等,说明你在一个链接工作区里;相等,说明是普通检出。
这里有个容易漏的坑,技能文档专门加了一道子模块守卫:在 git 子模块里,GIT_DIR != GIT_COMMON 同样成立。所以在下结论之前还要再跑一条:
# If this returns a path, you're in a submodule, not a worktree — treat as normal repo
git rev-parse --show-superproject-working-tree 2>/dev/null
有输出就当普通仓库处理。判定完还要按分支状态汇报:在具名分支上,报告已在隔离工作区的路径和分支名;处于 detached HEAD,则要额外说明这是外部托管的工作区,分支要留到收尾时再建。
确认是普通检出之后,技能不允许 Agent 自己动手建。它要求先看你的指令里有没有已经表达过的偏好,没有的话就问一句:要不要给你开一个隔离 worktree,它能保护你当前分支不被改动。你要是拒绝,就地干活儿,直接跳到项目安装那一步。这一层「先问」的设计,把创建工作区从默认行为变成了一次显式授权——关于这类停下来等人拍板的节点怎么设计,可以对照 Agent 流程里的人工介入点 来看。
Step 1:优先用宿主的原生工具
真正需要创建时,技能给的是两条路,且有明确先后。
第一条是宿主的原生工具。文档没有假设你用哪个平台,而是让 Agent 自己找:可能是一个叫 EnterWorktree、WorktreeCreate 之类名字的工具,也可能是一条 /worktree 命令或者一个 --worktree 参数。找得到就用它,然后直接进入项目安装。理由写得很直白:原生工具会自己处理目录放置、分支创建和清理,你在有原生工具的情况下硬用 git worktree add,等于制造一堆宿主看不见也管不了的状态。借口对照表里把这条标成了头号错误。
第二条才是手工 git 兜底。目录选择有三级优先级:你在指令里声明过的目录最大;其次看项目里是否已经存在 .worktrees 或 worktrees,两个都有时 .worktrees 优先;都没有就默认在项目根建 .worktrees/。
那道必做的安全检查
项目内目录在创建 worktree 之前必须过一道校验:
git check-ignore -q .worktrees 2>/dev/null || git check-ignore -q worktrees 2>/dev/null
没被忽略,就先写进 .gitignore 并提交这个改动,再继续。文档给的理由是防止把 worktree 里的内容整棵提交进仓库。这条检查看着琐碎,但一旦漏掉,后果是你的仓库里凭空多出一份完整副本,而且往往过好几天才被发现。
创建本身很朴素:
path="$LOCATION/$BRANCH_NAME"
git worktree add "$path" -b "$BRANCH_NAME"
cd "$path"
另有一条沙箱兜底:如果 git worktree add 因为权限被拒(沙箱拦截),要明确告诉用户沙箱挡住了工作区创建、现在改为就地工作,然后在当前目录跑安装和基线测试。这条的价值在于它把失败写成了一次汇报,而不是静默降级——Agent 静默降级是最难排查的一类问题。
Step 2 与 Step 3:装依赖,跑基线
技能给了一段按文件存在性探测的安装逻辑:有 package.json 跑 npm install,有 Cargo.toml 跑 cargo build,有 requirements.txt 跑 pip install -r requirements.txt,有 pyproject.toml 跑 poetry install,有 go.mod 跑 go mod download。
然后是基线测试:用项目对应的命令跑一遍全量测试。测试挂了要报告失败并问人是继续还是先查;测试过了才报告就绪,格式是工作区完整路径、测试通过数与失败数、准备实现哪个功能。
为什么非得在动手前跑一遍?借口对照表给的反驳是:基线脏掉之后,后面每一次失败都说不清是谁造成的;而要不要带着失败往前走,是人的判断不是 Agent 的判断。这跟 Agent 谎报成功 是同一类问题的两端——一端是开工前就把参照系钉死,另一端是收工前必须拿证据说话。
三、这套流程由哪些零件组成
| 组成部分 | 它负责什么 | 对应仓库位置 | 你什么时候会碰到它 |
|---|---|---|---|
| Step 0 环境检测 | 比对 --git-dir 与 --git-common-dir 判断是否已在链接工作区,并用 --show-superproject-working-tree 排除子模块误判 | skills/using-git-worktrees/SKILL.md | 每一次让 Agent 开始功能开发的第一秒 |
| 创建前征求同意 | 普通检出里不许隐式建 worktree,先问;已有声明的偏好则不再问 | skills/using-git-worktrees/SKILL.md | 你在主检出里随口说「开始做这个功能」时 |
| 原生工具优先 / git 兜底 | 先找宿主的 worktree 能力,找不到才手工 git worktree add -b | skills/using-git-worktrees/SKILL.md | 换到一个新的 Agent 客户端上时 |
| 目录选择与忽略校验 | 三级优先级定目录,git check-ignore 确认已忽略,未忽略先补 .gitignore 并提交 | skills/using-git-worktrees/SKILL.md | 项目里第一次出现 .worktrees/ 时 |
| 安装与基线测试 | 按 package.json / Cargo.toml / requirements.txt / pyproject.toml / go.mod 探测装依赖,然后跑全量测试确认起点是绿的 | skills/using-git-worktrees/SKILL.md | 每次新建工作区之后、写第一行代码之前 |
| 收尾菜单 | 测试绿之后给出固定选项:本地合并 / 推送并建 PR / 原样保留;detached HEAD 下只给后两项 | skills/finishing-a-development-branch/SKILL.md | 功能做完、准备交付时 |
| 按出身清理 | 只有 WORKTREE_PATH 落在 .worktrees/ 或 worktrees/ 下才执行 git worktree remove 加 git worktree prune,其余留给宿主 | skills/finishing-a-development-branch/SKILL.md | 选了本地合并、或你明确要求丢弃时 |
| 借口对照表 | 把 Agent 最容易给自己找的托辞逐条列出来并给出反驳 | 两份 SKILL.md 的 Common Rationalizations 小节 | Agent 想抄近路的每一次 |
| 跨环境检测说明 | 把同一套只读检测命令与 detached HEAD 的含义单独写成参考文档 | skills/using-superpowers/references/codex-tools.md | 在沙箱化的宿主里跑这套流程时 |
| 文档约定的回归测试 | 用断言脚本检查两份技能文档里的路径策略措辞没有跑偏 | tests/claude-code/test-worktree-path-policy.sh | 你 fork 之后改这两份文档时 |
上游还有两个调用方值得知道:skills/executing-plans/SKILL.md 的第一步、以及 skills/subagent-driven-development/SKILL.md 的 Setup 段落,都要求先用工作区技能创建或确认隔离环境,后者还额外写了一句——没有你的明确同意,绝不在 main/master 上开始实现。
四、收尾那一半:谁造的谁收
工作区隔离只讲开头是不完整的,因为留下一地没人认领的工作区,比不隔离更烦。skills/finishing-a-development-branch/SKILL.md 把收尾拆成六步,核心原则一句话:验证测试 → 检测环境 → 给出选项 → 执行选择 → 清理。
测试必须重跑。借口对照表里对「这个会话早些时候测试是过的」的反驳是:绿的那次只证明了它跑过的那棵树,你要合的是眼前这棵。
环境检测除了老两样,还多捕获一个值:
# Capture now, while still inside the workspace — Step 5 changes directory
# before cleanup (Step 6) needs this value
WORKTREE_PATH=$(git rev-parse --show-toplevel)
注释解释了为什么要提前捕获——执行选择那一步会切换目录,等到清理时再取就晚了。这种把时序陷阱直接写进注释的做法,比在别处写一句「注意顺序」有用得多。
菜单是固定的三项:本地合并回基线分支、推送并创建 PR、原样保留分支。detached HEAD 下减为两项,去掉本地合并。文档要求原样呈现这个菜单,不许自己加选项——特别是「丢弃」,它不在菜单里,只有你明确开口要求丢弃时才走那条路,而且要你原样输入 discard 这个词才算确认。借口对照表里把「『嗯,扔了吧』算确认」单列了一行否掉。
合并那条路的顺序也被钉死了:先 cd 回主仓库根,再 checkout、pull、merge,在合并结果上重跑测试;测试绿了才清理工作区、再 git branch -d 删分支。如果合并结果的测试挂了,就停下、工作区和分支原地不动、去查——文档还补了一句安慰的判断:什么都还没推,合并是本地的,可恢复。
清理的判据是出身而不是「看起来没用了」:只有工作区路径在 .worktrees/ 或 worktrees/ 下面才动手,跑 git worktree remove 加一条 git worktree prune 自愈掉陈旧登记;否则就是宿主的地盘,留在原处,有平台提供的退出工作区工具就用那个。选了建 PR 或原样保留,工作区一律保留——理由是 PR 的评审意见还要在那个工作区里改。这套「谁交付、谁负责后续」的边界,跟 把活儿交接给人 里讨论的交接面是同一件事。
五、边界与代价:它放弃了什么
开发会变慢,而且是设计上就接受的慢。 每开一个功能都要装一遍依赖、跑一遍全量测试,在大仓库里这就是实打实的几分钟到十几分钟。改一个错别字、调一行文案、修一个明显的空指针,走这套流程就是过度设计——它面向的场景是「一个需要隔离的功能开发」,技能自己的描述里写的就是 starting feature work that needs isolation。
Agent 会变啰嗦。 开始时要报一句自己在用哪个技能,创建前要问同意,收尾要摆菜单等你选,基线分支拿不准要跟你确认,丢弃要你打字。你要的是「安静地把活儿干了」,那这套流程会一路打断你。这些打断本身是它的产品主张,不是可调参数。
几件它明确不管的事:
- 不管你的未提交改动怎么过去。 两份技能文档里没有涉及把当前工作区的脏改动带到新工作区的流程,这件事仍然是你自己的。
- 不管被忽略的本地文件。
.env、本地配置、构建缓存这类不进版本库的东西,新工作区里不会自动出现,文档也没有承诺处理。 - 不管跨工作区的运行时冲突。 同时开着几个工作区各跑一份开发服务器时的端口占用、共享数据库、外部服务状态,不在这两个技能的职责里。
- 不管别人的工作区。 不在
.worktrees/或worktrees/下的一律不碰,哪怕看着像是陈旧残留。 - detached HEAD 下不提供本地合并。 外部托管的工作区里,菜单直接砍掉这个选项,把决定权交回宿主的原生控件。
还有一层性质上的边界:这些约束是用自然语言写在 Markdown 里、由 Agent 阅读并遵守的,不是运行时强制。仓库里 tests/claude-code/ 下确实有断言脚本对这两份文档的措辞做检查(比如 test-worktree-path-policy.sh 会断言工作区技能里保留着默认落到 .worktrees/ 的表述),但那验证的是文档没写跑偏,不等于每一次实际执行都必然按文档走。你把这套流程搬到自己项目里时,心里要有这个折扣。
六、上手与避坑清单
1. 有原生工具还硬敲 git worktree add。 会踩是因为手指比脑子快,git worktree add 是肌肉记忆。后果是宿主的工作区列表里看不到这个目录,它的自动清理也管不到,最后留一堆孤儿。避法:动手前先确认自己有没有 EnterWorktree、WorktreeCreate、/worktree 或 --worktree 这类能力,有就用它。
2. 默认 .worktrees/ 已经被忽略了。 会踩是因为这个目录名太像约定俗成,谁都以为别人已经加过了。后果是整棵工作区被提交进仓库,历史里塞进一份完整副本。避法:git check-ignore -q .worktrees 花不了一秒,没过就先补 .gitignore 再建。
3. 在子模块里把自己当成 worktree。 会踩是因为 GIT_DIR != GIT_COMMON 这个判据在子模块里同样成立,只看这一条必然误判。后果是该建隔离的时候跳过了创建,直接在人家的检出里写。避法:多跑一条 git rev-parse --show-superproject-working-tree,有输出就按普通仓库走。
4. 收尾时先删工作区再合并。 会踩是因为「先打扫再交付」符合直觉。后果是合并出冲突或者合并结果测试挂了,你已经没有那个现场可以回去看了。避法:按文档顺序来——合并、在合并结果上跑测试、绿了才清理、最后删分支。
5. 在工作区内部执行 git worktree remove。 会踩是因为 Agent 干完活儿就在那个目录里,顺手就删。后果是删除自己脚下的目录,行为不可预期。避法:先 cd 回主仓库根,且 WORKTREE_PATH 要在切目录之前就捕获好。
6. 想当然认为基线分支是 main。 会踩是因为多数项目的默认分支确实是 main,习惯成自然。后果按文档的说法是「合错基线分支,撤起来很贵」。避法:拿不准就问一句这个分支是从哪儿分出来的,合并前确认。
7. 把「差不多可以扔了」当成丢弃确认。 会踩是因为对话里语气很随意,Agent 又倾向于顺着你。后果是提交和分支一起没了。避法:坚持只有原样输入 discard 才执行,这条规则写进你的项目约定里。
8. 基线测试红着就往下干。 会踩是因为「这几个失败一直都有,跟我没关系」。后果是后面每一次失败都要先花时间排除是不是原来就有的。避法:报告失败并让人决定,这个决定权不在 Agent 手里。
9. 沙箱拒绝创建时静默降级。 会踩是因为报错被 Agent 自己吞掉,然后它在主检出里继续干。后果是你以为有隔离,其实没有。避法:把沙箱兜底写成一次明确汇报——说清楚是沙箱挡的、现在改在当前目录工作。
收束:拿它做什么
这套东西最值得抄的不是 worktree 本身,而是它对「Agent 会给自己找什么借口」的枚举方式:每条规则后面跟一条具体的托辞和一条具体的反驳。你自己写 Agent 约定时,与其写十条「必须」,不如把你过去半年被坑过的十次原话抄下来,一条一条驳回去。
一个可以现在就用的自检清单:Agent 开工的第一句话里有没有说清它在哪个目录、哪个分支?你的工作区目录进 .gitignore 了吗?基线测试的结果你看到了吗?收尾时是它替你决定了合并,还是你自己选的?清理动作的判据是「这个目录看着没用了」还是「这个目录是我建的」?
想继续往下读,顺序建议是 skills/using-git-worktrees/SKILL.md → skills/finishing-a-development-branch/SKILL.md → skills/using-superpowers/references/codex-tools.md,最后翻 RELEASE-NOTES.md 看这两个技能是怎么一步步改成现在这样的——那里面记着不少是被真实问题倒逼出来的调整。至于工作区隔离在多任务并发下要怎么排布,那是并发编排的题目,跟本篇讲的单条流程是两层事。
本文属于 superpowers 方法论专题(共 30 篇,含三篇与其它开源 Agent 项目的对照)。想看把资产铺满的另一种取向,见 ECC 开源 Agent 套件专题。