关于 worktree
用于管理隔离 Git worktree 创建与交接的技能,通过 doctrine 命令创建、配置并验证工作区。支持 solo、worker 和独立的 coordination worktree 流程,各自具有不同的隔离要求。
主要功能
- 01检测并采用已有的 linked worktree
- 02使用 doctrine worktree fork 创建并配置分支工作区
- 03支持 solo、worker 和 coordination 流程
- 04派生工作区前检查源目录是否干净
- 05交接前验证项目基线通过
- 06配置时仅复制允许列表中的忽略文件
适用场景
- 01为 solo 工作建立隔离工作区
- 02从明确的基准提交准备 worker 分支
- 03创建或恢复 dispatch coordination worktree
- 04交给 Agent 前验证工作区基线
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ davidlee/ doctrine --skill worktree安装后在 Claude Code 中输入/worktree确认已加载。
附带文件NOTICE.md
详细介绍
Worktree
管理隔离式 git worktree 的生命周期:检测现有隔离状态,通过 doctrine worktree fork 创建分支(该命令会添加 worktree、为其配置环境,并在可行时将其绑定到对应阶段;所有操作均支持补偿回滚;fork 会在自身树内构建到 target/),运行生成前检查,并确认基线正常后再移交。
开始时宣布:“正在使用 worktree 技能设置隔离工作区。”
这是一个子技能。 /execute(mode=solo)会在选择启用隔离时调用它;dispatch 生成脚本会为每个 worker 调用 fork,不受执行环境影响;/dispatch 路由器会针对每次运行的 dispatch/<slice> 树调用协调路径(见下文的 worktree coordinate)。它不是 /route 的目标——请通过调用方进入。
模式约定
此技能采用参数化设计,使 funnel 能复用它,而不继承 solo 语义(分离边界)。
输入:
mode = solo | workerallow_work_in_place: bool— 仅solo时为 true- 请求的分支 / 路径
base— 创建 fork 所基于的引用。solo默认使用HEAD;worker必须显式指定 base(编排器的协调 HEADB), 参见lib:reference/dispatch-mechanics.md§ Fork 的 base 必须显式指定,绝不使用会话 HEAD。
行为:
solo— 在沙箱拒绝时,可以降级到原地工作路径(不创建 fork;这是认可的主干路径)。worker— 绝不能降级。 worker 没有真实 fork 即视为硬性失败;funnel 必须保持隔离。fork --worker --base <B>会显式固定 base——绝不使用隐式会话 HEAD,因为 worker 的会话 HEAD 不是B,会破坏S.parent == B。创建完成后,worker 部分会运行受限的编辑→验证→将单个S提交到 fork循环;参见Worker 模式。
输出: { fork_path, branch, head_sha, provision_report, baseline_result }。
worker 在提交其唯一的差异 S 后会增加 { fork_branch, head_sha_after }——编排器会从中导入 B..head_sha_after。
协调模式 — doctrine worktree coordinate(第三条路径)
上文的 solo / worker 都通过 fork 处理。dispatch 协调 worktree 使用单独的创建命令,不是 fork 的一种模式——/dispatch 路由器会在批次 1 之前,每次运行调用一次:
doctrine worktree coordinate --slice <N> --dir <path>
它会在已解析的主干上,于独立 worktree 中创建(或恢复)dispatch/<slice>——这是 funnel 唯一的写入目标。它与 fork 的关键区别如下:
- 编排器自己的树。 协调树就是编排器(worker 模式关闭——worker 身份属于进程,由 worker 隔离环境中的
DOCTRINE_WORKER标识,而不是由目录决定)。 它属于编排器类别,与该类别的其他命令一样,worker 模式下会拒绝执行。 - 创建或恢复,不创建第二个分支。 如果已有活跃 worktree 使用
dispatch/<slice>,则拒绝(coordination-live——同一 slice 的并发 dispatch 不合法)。如果分支存在但没有活跃 worktree,则会恢复(重新关联)——这样,/handover后启动的新编排器就能使用同一分支(恢复稳定;若使用每次运行的标识符,会破坏恢复)。 - 从已提交的
plan.toml重新生成运行时阶段表(配置轴),并通过唯一的复制器完成——不执行协调层级的复制。
这不是 worker 差异路径:没有 S,也没有导入。生命周期关系是 worktree-life < branch-life——结束时会移除目录,但会保留 dispatch/<slice> 以及投影后的 review / phase 引用,作为交付物(参见 /dispatch)。下文的 solo / worker 创建方式保持不变。
检测(适配,不要重新创建)
如果当前工作目录(CWD)已位于关联工作树中,则适配该工作树;不要再创建一个。
git rev-parse --git-dir # differs from common-dir ⇒ linked worktree
git rev-parse --git-common-dir
--git-dir ≠ --git-common-dir ⇒ 已位于关联工作树中 → 跳过创建。
子模块防护。 子模块也会触发此不等式。请区分:
.gitgitdir 的解析路径位于…/worktrees/下 ⇒ 工作树;位于…/modules/下 ⇒ 子模块;或者git rev-parse --show-superproject-working-tree返回非空值 ⇒ 子模块。
子模块不是我们需要的隔离方式——将其视为“尚未分叉”。
创建 — doctrine worktree fork(受控路径)
选择分叉目录,运行提交前置检查,然后调用该动词:
doctrine worktree fork --base <B> --branch <name> --dir <path> [--worker]
fork 是唯一的创建 + 配置 +(标记)+ 输出动词,通过补偿回滚实现原子性(git worktree add 后的任何失败都会触发尽力执行的 worktree remove --force / branch -D / 目录清理;如果回滚本身失败,会指出残留项并以非零状态退出——绝不会悄悄留下半回滚状态)。它会:
git worktree add -b <branch> <dir> <base>— 如果<dir>/<branch>已存在,或<base>不是提交,则拒绝执行。请显式传入<base>:独立运行时用HEAD, 工作进程用B——显式指定基准正是此路径受控的原因。- 配置分叉(唯一的复制器——在复制边界处排除协调/运行时层;参见配置)。
- 使用
--worker时,必须同时提供--slice和--phase,并使用<coord>/.worktrees/<name>目录,才能将分叉绑定到其(slice, phase)。 缺少其中任何一项,分叉都只是未绑定;下游动词会如实报告,而不会猜测处理(独立运行时省略--worker)。 - 将面向用户的状态信息输出到 stderr;stdout 保持为空(便于机器处理)。分叉会在自身工作树中的
<dir>/target目录下构建——不会输出环境契约:平台已不再负责构建环境。
fork 属于Orchestrator 类操作——工作进程模式下拒绝执行(工作进程不会分叉;由协调器/独立运行模式在工作进程模式关闭时调用)。
创建后端梯队——按顺序降级:
- 已有隔离环境(见上文检测)→ 跳过创建,采用该分叉。
- 如果存在且可调用,则使用框架原生创建功能,但仅限创建(不自动复制)——仅作为可选方案。(Claude Code 的
WorktreeCreatehook 就是一个实例,但其载荷缺少基准/路径/类型,因此 doctrine 无法驱动它,dispatch 也不会这样做:而是显式创建分叉。)围绕它进行设计; 绝不要依赖它。 doctrine worktree fork— 经过测试并获认可的默认方案。优先使用。- 沙箱拒绝时,原地工作(仅限
solo,allow_work_in_place=true)— 不创建分叉,直接使用主干路径。worker则中止。
工作树目录选择
在调用 fork 前,按优先级选择分叉目录
(superpowers:using-git-worktrees):
- 重用现有目录 — 如果
.worktrees和worktrees都存在,优先使用.worktrees。 - 检查 CLAUDE.md 中是否注明偏好——如有,直接使用,无需询问。
- 如果目录不存在且未注明偏好,询问用户。
分叉路径为 <dir>/<branch>。**安全要求:**创建前,目录 MUST 被 git 忽略
(git check-ignore -q .worktrees),否则分叉内容会被跟踪——如果未被忽略,请将该行添加到 .gitignore 并先提交。(位于项目目录之外的全局目录无需此项检查。)
配置
fork 会自动配置;绝不要再运行第二个复制器。 doctrine worktree provision <fork> 是独立的唯一复制器(从源根目录运行,绝不要在分叉内运行),用于少见的跳过了 fork 的采用已有工作树路径。它会读取 .worktreeinclude,遇到命名层级的模式时默认拒绝,并且只复制允许列表中的、被 git 忽略的文件——即使模式宽泛地使用 **,也会在复制边界处排除协调/运行时层。
诚实地说明不变量(不要夸大)
副本隔离的保证之所以成立,是因为 provision 是唯一的复制者。
doctrine worktree check-allowlist是一项静态异味检查——检查通过表示没有任何模式名称涉及该层级;这不代表内容完整。真正的保证来自select_copies(位于 provision 中)。- 如果某个 harness 在创建时强制复制,且无法仅运行创建流程,doctrine 就无法阻止该复制——保证会退化为仅依赖
check-allowlist,项目必须确保.worktreeinclude精确无误。因此,默认使用完全可控的fork动词,且绝不依赖 harness 原生层级。
防护措施
在生成工作树前后运行这些检查。检查失败时立即中止——报告问题,不要自行变通。
- 提交后再生成。 执行
fork前,源树必须干净,以确保分叉只获取已提交的 HEAD:git status --porcelain -z。如果存在任何已跟踪的脏文件或任何未被忽略的未跟踪文件(它们会悄无声息地缺失于分叉中),则中止。被忽略的文件没问题——provision 会处理允许列表中的子集。 - 分支起点检查。
fork按设计会将工作树置于<base>;并发扩展的边界是批量提交:当协调 HEAD 仍等于编排器生成工作树前的B时,doctrine worktree branch-point-check --base <B>退出码为 0;否则为 1(→ 重新分派)。这是编排器在导入时执行的防护检查,worker 模式为 OFF——不是 worker 执行的检查。这是 HEAD 静止状态比较,不是 merge-base。 - 基线验证。 创建后,在分叉内运行项目的重新生成与验证命令,并要求检查通过后才能交接(本仓库使用:
doctrine check gate)。该命令由项目提供——绝不能硬编码为cargo …。无法构建的分叉必须在 provision 阶段修复,绝不能交接。
Worker 模式(漏斗的另一半)
mode=worker 是分派 worker 在其 fork --worker 工作树中运行的路径。创建、provision 和上述防护措施先照常执行;基线检查通过后,worker 会沿此路径运行,以取代单独交接。Worker 是受约束的写入者:只返回一个可导入的增量,绝不触碰协调/运行时层(fork 已将其隔离)。
lib:reference/dispatch-mechanics.md § Worker 身份是进程的一项属性,由生成过程设定。
lib:reference/dispatch-mechanics.md §§ “Worker 返回的是工作树,而不是提交” 和 “Workers can silently discard their own work”。
绝不能退化为原地工作。 没有真实 fork 的 worker 必须立即中止,绝不能悄悄在树内编辑(与 solo 第 4 级相反)。如果创建失败,报告问题并停止。
返回一份结构化报告(保存在编排器上下文中,绝不是 doctrine 产物):变更内容、验证结果、值得记入记忆的要点,以及 fork 分支。知识记录应跟随编排器确认的提交,而不是 fork(见下文“记录到 trunk”)。
Squash 后成为孤儿的注意事项(记录到 trunk)
在工作树分支中记录的记忆会在 squash-merge 后成为孤儿(内容仍然存在,但 Git 锚点指向一个从未进入目标分支的提交;因此会触发过时判定)。如果持久记忆必须在 fork 结束后继续保留,应将其记录到 trunk,而不是工作树分支中。
.worktreeinclude 模板(由项目所有,不随安装提供)
Doctrine 没有密钥或不可或缺的本地文件 → 它自己的默认设置是不复制任何内容,安装程序也不会附带 .worktreeinclude(安装根目录文件会覆盖使用该项目的文件)。Provision 能容忍文件缺失(不复制任何内容,退出码为 0)。
需要将被 Git 忽略的本地文件带入分叉的项目,可以在仓库根目录添加 .worktreeinclude:空行、# 注释、仓库相对路径字面值、简单的 glob 模式(*、**、?)。不支持 ! 否定规则,也不支持锚定(解析器会拒绝)。无论如何都会排除协调/运行时层。进行静态验证(气味测试,并非完整性检查):doctrine worktree check-allowlist。
快速参考
| 情况 | 操作 |
|---|---|
--git-dir ≠ --git-common-dir,且不是子模块 | 已经分叉 → 直接采用,跳过创建 |
子模块(modules/ gitdir / 超级项目) | 不具备隔离性 → 按未分叉处理 |
| 调度协调树(每次运行) | doctrine worktree coordinate --slice <N> --dir <path> — 基于已解析的主干创建或恢复,不是 fork 模式 |
| 默认分叉(单人或调度工作进程) | doctrine worktree fork --base <B> --branch <name> --dir <path> [--worker] |
| 工作进程分叉基点 | 显式指定 --base <B>,绝不使用隐式会话 HEAD(它不是 B) |
| 工作树目录未被忽略 | 添加到 .gitignore 并提交,然后再执行 fork |
沙盒禁止分叉,mode=solo | 在原地工作(不分叉) |
沙盒禁止分叉,mode=worker | 中止(必须隔离) |
采用了跳过 fork 的分叉 | 从源根目录运行 doctrine worktree provision <fork>(唯一的文件复制器) |
| 工作树有未提交更改 / 未跟踪且未被忽略的文件 | 中止,在启动前提交 |
| 批量提交边界 | doctrine worktree branch-point-check --base <B> → 1 = 重新调度 |
| 分叉中的基线为红 | 在配置阶段修复;绝不交接 |
| 工作进程启动 | export DOCTRINE_WORKER=1(自行启用,失败时放行) |
| 工作进程验证通过 | 提交一个非合并提交 S;返回 {fork_branch, head_sha_after} |
| 工作进程有多个提交 / 合并提交 / 已变基的分叉 | 违反约定 — 协调器会在导入前拒绝 |
| 工作进程验证未通过 | 报告问题;不要提交未通过的差异 |
风险提示
绝不:
- 通过
fork/provision以外的任何方式将被 Git 忽略的文件复制到分叉中(它们是唯一的复制器——排除保证依赖于它们)。 - 在
fork后运行第二个复制器,或在分叉内部运行provision。 - 暗示
check-allowlist通过就表示允许列表完整(它只是气味测试)。 - 让
worker降级为在原地工作。 - 在
worker模式下:写入.doctrine/编写树、跳过export DOCTRINE_WORKER=1,或提交多个非合并提交S。 - 从有未提交更改的工作树创建分叉,或交接基线未通过的分叉。
- 从隐式会话 HEAD 而非
--base <B>创建worker分叉——对工作进程而言,会话 HEAD 不是B,这是一个会破坏S.parent == B的分歧基点。 - 编写或编辑此技能的主版本;重新安装时,被 Git 忽略的
.doctrine/skills/安装副本会被覆盖。
务必:
- 创建前先检测;适配已有的隔离环境。
- 优先使用
fork动词;将宿主原生层级视为机会性方案。 - 运行全部三个防护检查;交接前确认基线通过。
- 在主干上记录持久记忆,而不是在分叉分支中记录。
结果
报告以下输出:fork_path、branch、head_sha、provision 报告(已复制 / 已排除的文件)以及基线结果。分叉分支是交还给调用方的交付物。