DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

kangtsang /

kangtsang/dsh-worktree-space

Verified

Worktree Space:将一个包含一个或多个 Git 仓库的工作区任务放进独立的任务空间目录——参与的每个仓库使用同一个任务分支各开一个 worktree;空间自动注册为 DSH 工作区,会话直接在其中开工,多个任务并行推进、互不干扰。收尾时可合并回各仓库当前分支、移除 worktree,并把 Git 未跟踪的文件归档到指定目录;结束任务时可选把未处理的提交和合并冲突交给 agent 处理。

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@ed650080

Worktree Space

DeepSeek Harness 的 Worktree Space 插件:一个包含单个/多个 Git 仓库的工作任务,在一个共同的任务空间 目录下每个仓库各开一份同名 worktree 分支,并注册成一个 Agent 工作区,可以独立创建会话执行任务,多任务 可以并行处理互不干扰,任务完成后合并回主干分支并删除 worktree 分支和任务空间。

DeepSeek Harness Plugin License

管理页面:任务 / 工作区 / 仓库三个视图

简体中文 · English · 更新日志

Beta(实验性):把提交和合并冲突交给 agent 处理的功能处于实验性验证阶段,行为可能继续调整;它默认 显示,不想看到这两个入口就把插件配置里的「授权 agent 处理入口」设为「隐藏」(见文末「实验性:把提交与 冲突交给 agent」);本文档其余部分描述的是不借助 agent 的标准流程。遇到问题请在 GitHub Issues 里反馈。

功能

  • 从会话里建任务。 选源码根、给任务起名、选分支前缀、勾选要横跨的仓库、指定任务空间放哪。每个仓库都会得到 一份位于 <分支前缀><任务名> 的 worktree(前缀默认 task/),起点可以是各仓库当前的 HEAD, 也可以指定某个分支或提交。

  • 注册成工作区,名字是 <上级>/<任务名>,同时开一个会话,工作目录就是这个任务空间 —— Agent 可以 跨仓库改代码,不会动到源码检出。

  • 管理页面,分三个视图(两种入口与各自默认值见「管理任务」一节):

    • 任务空间视图:把每个任务下面的仓库列在一起(分支、改动数、是否被锁定、是否可清理);
    • 工作区视图:哪些工作区可以建任务空间,以及每个工作区里有几个仓库;
    • Git 仓库视图:扫到的每个 Git 仓库,以及它链接的 worktree。

    三个视图都能搜索,也能用「待处理」筛出需要注意的行(有改动、被锁定、可清理,或状态读取失败); 每行左边的箭头能单独折叠,筛选旁的按钮可以一次全部展开或折叠。

  • 结束任务:在任务行上点「结束任务」。默认把各仓库的分支合并回该仓库当前检出的分支(也就是任务空间的 起点;插件不会切换源码仓库的检出),也可以在对话框里按仓库改成别的分支 —— 那会在一个临时 worktree 里 合并,同样不碰你的检出。worktree 里还没提交的改动得你自己先提交掉(插件不代写提交,见「结束任务」), 然后删掉 worktree,把任务空间里的文档归档到 <归档根目录>/<项目>/<任务名>-<YYYYMMDD-HHMMSS>/ (不勾选归档,就会连这些文件一起删掉),最后注销这个工作区。归档根目录默认就在容器根自己下面 (<容器根>/archived-docs)—— 各项目共用一份,与任务空间结构同形,整块也只多出这一个目录; 在「配置」里可以指定成你自己的目录。要是该工作区里还有会话在跑,会先拒绝,等它结束或停掉再试。

  • 结束任务空间也在这个工作区列表自己的 ⋯ 菜单里 —— 只对确实是任务空间的目录出现。

  • 新建任务空间同样在那个 ⋯ 菜单里 —— 只对挂有代码仓库的工作区出现,点开的就是同一个创建对话框。

  • 不需要额外服务。 入口开关、扫描深度和目录上限都在插件自己的配置里改(见「配置」)。支持 DSH 主题;界面语言跟着 DSH 的语言设置走;插件列表里的名称、描述和图标也由本插件提供。

任务目录结构

~/workspace/                           根工作空间:项目都挂在这一层,也存放容器根目录
├── repo-x/                            项目 x 的主工作区(本身就是仓库)
├── project1/                          项目 1 的主工作区(下面若干仓库)
│   ├── repo-a/
│   └── repo-b/
├── deep-path/project2/                项目 2 的主工作区,目录位置埋得更深
│   ├── repo-c/
│   └── repo-d/
└── worktree-space/                    容器根目录:插件专用区
    ├── repo-x/                        项目层,名字就是源工作区目录名
    │   └── task-x/                    任务空间 —— 同时是会话的工作目录
    │       ├── worktree-space.json    任务的记录:分支、起点、项目、仓库与创建时间
    │       ├── worktree-space.md      由上面的记录渲染出来,给人读的分支、起点与约定
    │       └── repo-x/                位于 <分支前缀>task-x 的 worktree
    ├── project1/
    │   ├── hotfix/
    │   │   ├── repo-a/                都以 <分支前缀>hotfix 开 worktree
    │   │   └── repo-b/
    │   └── task-a/
    │       ├── repo-a/
    │       └── repo-b/
    ├── project2/                      目录位置埋得深不影响它落在同一个容器里
    │   └── task-b/
    │       ├── repo-c/
    │       └── repo-d/
    ├── archived-docs/                 归档根(默认):非 Git 产物收在这里
    │   └── project1/                  按项目归档
    │       └── hotfix-20260926-020933/   「任务名-时间戳」,每个任务一层
    └── README.md                      第一次使用时由插件写入:这里是 Worktree 专用区

~/my-archive/                          归档根:可以在插件设置改成指定的目录
└── project2/                          结构一样:项目一层,任务-时间戳一层
    └── task-b-20260926-020933/

容器根推荐落在根工作空间的下一层(~/workspace/worktree-space),放在哪儿只看路径上盘根下面的 第一个目录:所以 ~/workspace/project1 和 ~/workspace/deep-path/project2 推荐的是同一个 容器,项目各自埋多深都不影响。创建时你仍可以把它改到别处。

项目名就是源工作区目录名,插件自己分层,不用你填:同一个容器里跑多个项目时,各项目下同名的任务 (都叫 hotfix)因此各占一层,不会挤在一起。源工作区本身就是仓库时(工作区目录 = ~/workspace/repo-x), 项目层与仓库层同名,得到 repo-x/task-x/repo-x —— 这处重复是有意接受的:布局深度恒定为三层,插件与 客户端都不需要额外信息就知道哪一层是哪一层。

容器根是插件的专用区:第一次在这底下建任务空间时,插件会写一份 README.md 说明这里是 Worktree 专用区、 不要直接 git init 或 clone(已经有了就不覆盖)。反过来说,容器根本身要已经是 Git 仓库(下面有 .git), 创建会被拒绝,且一个目录都不会建出来。

归档根底下再分两层:<项目>/<任务名>-<时间戳>/。归档结构因此与容器结构同形,一个项目的归档收在自己 那一层里;默认档就在容器根下,整块只多出 archived-docs 这一个目录。删掉 worktree 不会删除对应的 Git 分支;结束任务会先把分支合并回去,再删 worktree。

兼容性

基于 DSH 0.2.0-rc.2 的客户端契约开发,同时保留 0.1.7 线。当前官方窗口的五个版本 0.1.7-alpha.2、0.1.7-rc.1、0.1.7-rc.2、0.2.0-rc.1、0.2.0-rc.2 都逐个实测过:宿主 RPC 路由能注册, 客户端 bundle 不用改就能加载,插件列表里的名称、描述、图标和配置区都正常显示。

manifest(package.json)里显式声明的兼容范围:

字段 声明值 含义
engines.node >=22.19.0 需要的 Node.js 版本
engines.dsh >=0.1.7-alpha.2 <0.3.0-0 兼容的 DSH 版本(0.1.7 线 + 0.2.x,声明式)
dsh.manifestVersion 1 DSH 清单格式版本
dsh.compatibility.dsh >=0.1.7-alpha.2 <0.3.0-0 兼容的 DSH 范围(0.1.7 线 + 0.2.x)
dsh.compatibility.node >=22.19.0 兼容的 Node.js 范围
dsh.compatibility.profiles ["web"] 已验证的 profile
dsh.compatibility.dshReleases 逐版本精确声明(见下) 每个官方 DSH 完整版本的结论
dsh.compatibility.dshOperations 逐版本的四项操作结论(见下) 安装 / 启动 / 卸载 / 回滚的实测结果
peerDependencies @deepseek-ai/dsh-tools、@deepseek-ai/dsh-client-connection 各 >=0.1.7-alpha.2 <0.3.0-0 DSH 真的会校验的那一项,见下

peerDependencies 里的 DSH 范围会被强制执行。 DSH 0.2.0 起,安装与 profile 启动会拿运行时的 dsh --version 去比对清单里每一个 @deepseek-ai/dsh / @deepseek-ai/dsh-* peer(SemVer,prerelease 参与 比较):不满足就在 pnpm 运行之前拒绝安装(installation rejected: … incompatible with dsh …),或在启动时 拒绝加载,并给出精确版本豁免 dsh plugin allow-version 的命令。0.1.7 及更早只看 dsh.compatibility 这类 纯声明字段,所以「只写声明、不管 peer」在那时不会暴露问题 —— 1.0.7 装到 0.2.0 上被拒,就是它的两个 peer 还停在 ^0.1.7-rc.1 的缘故。本版的 peer 与 engines.dsh、dsh.compatibility.dsh 写成同一个 >=0.1.7-alpha.2 <0.3.0-0:两条线都覆盖,三处声明不再各说各的,同时把 0.3.0 的 prerelease 也挡在外面。

dsh.compatibility.dshReleases 只声明实测过的 DSH 完整版本;未实测的版本不写进声明(即按 unknown 处理), 不用宽泛范围冒充精确证据。下表每一行都来自一次性 Profile 里真跑过的一次 安装 → 配置组合 → 冷启动 → 卸载 → 回滚,dshOperations 记录的就是这四项:

DSH 版本 dshReleases install start uninstall rollback
0.1.7-alpha.2 compatible passed passed passed passed
0.1.7-rc.1 compatible passed passed passed passed
0.1.7-rc.2 compatible passed passed passed passed
0.2.0-rc.1 compatible passed passed passed passed
0.2.0-rc.2 compatible passed passed passed passed

每个版本各自用该版本自己的 @deepseek-ai/dsh CLI 跑:临时 $DSH_HOME 里从官方 web 模板建一次性 profile, 装 npm pack 出来的 tarball,确认组合树里出现 worktree-space 行、冷启动无警告、页面带上插件模块、 POST /api/dsh-worktree-space/task.preference 作答,再卸载并确认条目归零、重新启动干净。 完整记录见 docs/store-evidence.md。

固定提交:本版(v1.0.8)的源码提交与逐版本验收记录都在 docs/store-evidence.md 里;哪一部分已经实测到 1.0.8、哪一部分还停在 1.0.7, 该文件第 6 节写明了。

engines.dsh / dsh.compatibility.dsh 是声明而非强制:DSH 的安装器与加载器都不校验它们,写一个范围 不会拒绝不兼容的宿主。真正拦人的是上面那条 peerDependencies。因此这两个范围的含义只是 「0.1.7-alpha.2 起、到 0.2.x 为止按兼容处理」;其中逐一验证过的是上表五个版本,其余版本保持不声明。 如果后续 DSH 版本改动了客户端契约、让插件失效,会把上界收住,或在 dsh.compatibility 里如实标记; 遇到版本相关的问题,请到 Issues 反馈。

权限、依赖与失败边界

本插件在运行时会读写文件、并调用 git;这两类权限就是它的功能本身,无法裁剪为零。完整清单(逐条说明 读什么、写什么、执行哪些子命令、失败时怎么办)见 PERMISSIONS.md;一次性 Profile 的 安装 / 启动 / 卸载验收证据见 docs/store-evidence.md。

权限一览:

权限 范围
文件读取 你选择的工作区目录(广度优先扫描,跳过 node_modules、dist、build、vendor 与隐藏目录,.worktrees 除外);任务空间里的 worktree-space.json、worktree-space.md;容器根的 .git 与 README.md(分别判断容器根是不是 Git 仓库、要不要写说明);各 worktree 的 .git 标记文件;插件自带的 assets/skill/task-worktree-space/SKILL.md;结束任务时按 git diff --name-only HEAD / --diff-filter=U 读回那些文件的内容,只为判断合并是否还留着冲突标记
文件写入 只写任务空间容器:<容器根>/<项目>/<任务名>/ 及其中的 worktree、worktree-space.json、worktree-space.md,以及容器根自己那份 README.md(只在不存在时写,绝不覆盖你写的);归档时另写配置选定的归档根目录下的 <项目>/<任务名>-<时间戳>/(默认 <容器根>/archived-docs,见「配置」)。结束任务时删除的是插件自己创建的 worktree、任务目录与文档;合并时另在系统临时目录里建一份临时检出(git worktree add → 合并 → worktree remove --force → 删除该目录)。不写源码仓库检出里的文件,也不写 DSH 数据目录与配置文件(配置由 DSH 的插件配置服务保存)
命令执行 只调用 git(git -C <目录> <子命令>,固定参数、不经 shell,全部走同一处 runGit)。查询类:rev-parse(含 rev-parse --verify --quiet MERGE_HEAD)、worktree list、status、rev-list、for-each-ref、show-ref、symbolic-ref、merge-base、diff --name-only(含 --diff-filter=U);变更类:worktree add / remove / prune、merge、merge --abort、reset --hard、branch -d / -D。add 与 commit 不在其中:插件不代写提交,未提交的改动会让该仓库停下;交给 agent 的提交由宿主里的 agent 在那个会话里执行(见失败边界)
网络 只有 git push -u origin <分支>,且仅在你于新建面板或工具里显式选择推送时才执行;插件自身不发任何 HTTP 请求、不下载任何东西
凭据 不读取、不保存、不转发任何凭据。推送时用的是你本机 Git 已配置的凭据(credential helper / SSH),插件不接触密钥,也不读环境变量
全局资源 不装全局包、不起常驻进程与服务、不写系统目录

依赖:

依赖 用途 由谁提供
Node.js >=22.19.0 运行宿主代码 你安装的 DSH
DSH >=0.1.7-alpha.2 <0.3.0-0 客户端契约、RPC 与工作区 API 你安装的 DSH
@deepseek-ai/cordis、@deepseek-ai/schemastery 插件框架与配置 schema DSH profile 提供的 peer 依赖
@deepseek-ai/dsh-client-connection、@deepseek-ai/dsh-tools 宿主 RPC 注册、工具定义 DSH profile 提供的 peer 依赖
React 18 管理页面 UI DSH web 运行时提供
git 全部 worktree 与分支操作 你本机已装的 Git(不随插件分发,PATH 上找不到就会报错)
@hugeicons/*、@radix-ui/react-dialog 图标与对话框组件,仅构建期使用,构建时已内联进 client/client.js 不随插件安装运行期包;安装本插件不会引入新的运行期第三方依赖

外部服务: 无。插件不连接任何第三方服务,也不上报遥测。

失败边界(绝不静默):

情形 行为
扫描目录数超过上限 抛错并提示 Worktree scan limit reached; choose a more specific Workspace.,请换一个更具体的工作区
任一 git 命令失败 抛出 git <参数> failed (exit N): <stderr>,把 Git 自己的诊断原样带出
结束任务时某仓库还有未提交的改动 停下该仓库(force 才丢弃):uncommitted work is waiting in <worktree>; commit it before the task can be finished;插件不代写提交,其余仓库继续,结果里逐条报出
合并已解决但没有提交 the merge in <worktree> is resolved but not committed,现场原样保留
解决后的文件里仍有冲突标记 the resolved merge still has conflict markers in <files>,原样保留,不替任何一方取舍
合并冲突 不自动 merge --abort,保留合并现场(mergeSite 与 conflictedFiles),等你授权后再处理
worktree 删除失败 报 failed to remove the worktree (uncommitted changes? force it deliberately),保留该 worktree 并报告,可用 git worktree prune 清理
任务目录非空 保留目录与工作区注册,不强行删除
无法确认的事实 在文档里写「未知」,不把「没有搜到」推断成「不访问」

使用

安装

从 npm 安装:

dsh plugin --profile web add dsh-worktree-space

插件的挂载行写在 profile 的 cordis.patch.yml —— 本仓库带着 DSH 的 bundle patch,一般会自动生效; 若插件列表里一直没出现,就手动补上:

- id: worktree-space
  name: dsh-worktree-space
  config:
    panelEntry: hide         # 「新会话」下方的管理页面入口(默认隐藏)
    sidebarEntry: show       # 侧边栏底部的快捷入口(默认显示)
    scanDepth: 2
    maxScanDirectories: 1000

卸载:dsh plugin --profile web remove dsh-worktree-space;更新时先卸载再重新安装。

配置

在界面里改(推荐):侧边栏 →「插件」→ Worktree Space → 配置区,和其它插件的配置在同一处, 改完立刻生效,不用重启。也可以直接改配置文件:DSH 数据目录下 profiles/<profile>/cordis.patch.yml 里这个插件的 config: 区块(见上方示例),改完重启 DSH 生效。

设置 取值 默认 说明
面板入口(新会话下方) 显示 / 隐藏 隐藏 侧边栏面板列表里那一行,整页打开管理页面(页面自带左侧导航和「返回会话」);默认隐藏
侧边栏底部入口 显示 / 隐藏 显示 侧边栏底部那个快捷入口,以对话框打开同一个管理页面
授权 agent 处理入口 显示 / 隐藏 显示 结束任务对话框里「授权 agent 提交」「授权 agent 处理」两个实验性入口;隐藏时走标准流程:自己提交、自己解决冲突,再点继续结束任务
扫描深度 1–5 层 2 层 从工作区目录(第 0 层)往下找 Git 仓库的层级数
最大遍历目录数 500 / 1000 / 2000 / 3000 / 5000 / 10000 1000 一次扫描最多读多少个目录;超过会提示你换一个更小的工作区
默认分支前缀 任意文本 task/ 新建任务空间时默认用的前缀;在新建面板里改动并勾选「设为默认分支前缀」,点创建时一并写回这里
任务空间位置 默认 / 指定目录 默认 新建任务空间默认放哪。默认配置即最佳实践:按下面的推荐规则从源工作区推导;指定目录若与项目目录没有公共目录,agent 处理提交和冲突时会话需要手动授权
指定任务空间根目录 任意路径 留空 只在「指定目录」这一档生效;留空则按推荐规则推导。新建面板里改动容器根并勾选「设为默认任务空间根目录」,点创建时把这两项一并写回
归档文档位置 跟随容器根 / 指定目录 跟随容器根 归档文档存到哪个根目录下;两档都在该根目录下再建一层 <项目>/<任务名>-<时间戳>。默认档收在容器根自己的 archived-docs 里,与任务空间建在哪一层无关
指定归档目录 任意路径 留空 只在「指定目录」这一档生效;留空则收在容器根的 archived-docs 里

扫描覆盖全部工作区;按广度优先逐层进行,每层最多同时读 8 个目录。任何一层只要发现 .git 就认定是 仓库;node_modules、dist、build、vendor 等目录和隐藏目录会跳过(.worktrees 除外)。

创建任务

新建 Worktree Space

术语:源码根=你交给插件的那个工作区目录(它自己是一个仓库,或者它顶层装着若干仓库);源码树=该 目录连同下面的一切。新建对话框里的提示把它称作「仓库目录」。

  1. 在会话里,点输入框上方的 新建 Worktree Space。

  2. 给任务起名 —— 会转成小写,空格、中文和其它字符都换成连字符(hotfix-placeorder);名字不合法时 会告诉你哪里不合规。

  3. 需要的话改 分支前缀。分支名就是这个前缀加上任务名;留空则用配置里的默认前缀(默认 task/), 输入框下面那行会告诉你当前用的是哪个。只有你填的前缀和默认不一致时,才会出现 设为默认分支前缀 勾选框 —— 勾上它,点「创建并打开」时会把这个前缀写回插件设置。

  4. 设置任务空间放哪 —— 也就是容器根。这个目录要和仓库目录平级——不在它里面,也不是它的上层目录 ——界面会填好推荐值:根工作空间的下一层 worktree-space(配置的「任务空间位置」选「指定目录」时, 填的是那里指定的目录,见「配置」)。所谓根工作空间,就是路径上盘根下面的 第一个目录:源码根是 ~/workspace/project1,推荐就是 ~/workspace/worktree-space;源码根埋得 更深(~/workspace/deep-path/project2),推荐的还是同一个 ~/workspace/worktree-space。 容器就落在这一层,任务空间和源码树因此共有一个共同祖先:一次提交既写进 worktree 也写进源码仓库的 git 目录,会话的工作目录得覆盖到这两边。比盘根再往上一层是不行的(盘根不能当工作目录);仓库目录 直接就在盘根下(~/repo)时下面没有别的目录可取,推荐 <盘根>/worktree-space(把提交或冲突交给 agent 时,提权要到那个会话里批准,见文末实验性一节)。容器名在所有情形下都叫 worktree-space; 只有当这个名字正好会落在仓库目录上(仓库目录自己就叫 worktree-space)时才改用 dsh-worktree-space。换成别的位置也能用。你把容器根改成推荐值以外的路径时,输入框下面会出现 设为默认任务空间根目录 勾选框 —— 勾上它,点「创建并打开」时会把这个路径写回插件设置(配置 里的「指定任务空间根目录」加上「指定目录」这一档),下次新建就默认落在这里;旁边的说明写着这个 选择的代价:目录若与项目目录没有公共目录,agent 处理提交和冲突时会话需要手动授权。

    容器根底下由插件自己分层,你只需要选容器的位置:任务空间的完整路径是 <容器根>/<项目>/<任务名>,项目名取源工作区目录名(见「任务目录结构」)。若容器根本身已经是 Git 仓库,创建会被拒绝;第一次使用时插件会在容器根写一份 README.md 声明这里是 Worktree 专用区。

  5. 勾选任务要横跨的仓库(每张仓库卡片上标出它当前 HEAD 所在的分支),并选择分支起点。

  6. 点 创建并打开。新工作区会直接开一个会话,工作目录就是任务空间。

如果任务空间已经建好、但工作区注册失败,对话框会说明原因,并让你重试注册。

管理任务

打开 管理页面,两种入口:

  • 侧边栏底部的 Worktree Space(默认显示)—— 以对话框打开;
  • 侧边栏「新会话」下方的 Worktree Space 行(默认隐藏,在配置里打开)—— 在主区域整页打开, 页面自带左侧导航:第一项是 返回会话(面板占用了会话所在的主区域,所以留一个明确的回头路), 下面三项切换视图。对话框形态的视图切换仍在工具栏里。

三个视图的区别见「功能」一节:任务空间视图看每个 任务和它的各个仓库,工作区视图看哪些工作区能建任务空间,Git 仓库视图看扫到的每个 Git 项目及其 worktree。 右侧的统计会跟着视图变(N 个任务 / N 个工作区 / N 个仓库 · M 个 Worktree),搜索或筛选时显示 可见 / 总数。

面板每次打开都会重新扫描,但会先用宿主记住的上一次扫描结果把界面画出来,所以打开就能看到数据,扫描 结束后自动换成新结果(标题栏会显示「正在扫描所有工作区…」)。这份记忆只放在 DSH 实例的内存里:既不写磁盘, 也会在实例关闭时随之清空。

结束任务

结束任务对话框

在任务行上点 结束任务,或在工作区列表的 ⋯ 菜单里点 结束任务空间。对话框会先说明接下来会 发生什么:未提交的文件、待合并的提交、合并到哪个分支,以及任务空间里的文档(确实有东西可归档时, 才会出现「归档文档」选项)。「合并回目标分支」默认选中;「删除分支」和「强制」默认不选。

结束任务空间是一条标准流程,分三步:

1. 提交:未提交的改动得先由你提交,插件不经手。 只要计划里还有未提交改动的仓库、而「强制」没勾, 「确认结束任务」就一直不可用——这些改动不是插件这一侧能写的,worktree 也不会带着它们被移除。计划下方 会点名这些仓库,并列出各自有几处改动。到各自的 worktree 里自己 git add + git commit (提交信息由你写);提交完把对话框关掉再打开一次——它每次打开都会重读计划,那些仓库就不再拦着 「确认结束任务」了。也可以勾上 强制:那就是「这些改动不要了」,它们会随 worktree 一起被丢弃。 插件不代写提交,也不替你动索引——这些改动是你写的,提交信息该由你来写。要是你自己那次提交没成 (钩子拒绝、缺 user.name/user.email、gpg 签名、index.lock、文件被占用等),仓库仍然是脏的, 插件照样拦着不让确认——修好再提交即可。不想自己动手的话,授权 agent 提交会把这件事交给一个 agent 会话(见文末实验性一节)。 跳过这一步只有两种情况:明确要「删除分支且不合并」(放弃任务空间,deleteBranch 加 force)—— 那次提交随即会跟着分支一起被删掉,所以不做;以及某个 worktree 正处在未完成的合并中(有 MERGE_HEAD),那一份交给第 3 步。

2. 合并:先反着试一次,再正着合。 真正的合并是把任务分支合并进目标分支(默认是该仓库源检出 所在的分支),落点在源仓库身上。但在动目标分支之前,插件先在任务空间自己的 worktree 里反着试 一次:把目标分支合进任务分支——git merge --no-ff --no-edit <目标分支>,工作目录是 <任务空间>/<仓库>(就是该仓库的 worktree)。两种结果:

  • 干净:把这次试合并撤销掉(git reset --hard <试合并前的 HEAD>,worktree 回到试合并前的样子), 再按常规把任务分支 --no-ff 合并进目标分支——目标分支上留下的是正常的合并提交,历史顺序不变。 目标分支若没被任何 checkout 占用,就在一个临时 worktree 里合并(合并完丢弃),源检出不动。
  • 冲突:不中止、不还原,这次试合并就停在任务分支的 worktree 里——该 worktree 保持 MERGE_HEAD,冲突文件带着冲突标记、原样躺在该 worktree 的工作区里(例如 ~/wt-demo/spaces/demo/alpha/src/app.ts)。目标分支一点没被碰,源仓库的检出也不动。

为什么反过来试:真合并一旦冲突,现场就落在你的源仓库和它的检出上,还得中止、还得挑边;先反着试一次, 冲突就落在插件自己的 checkout(任务分支的 worktree)里——那正是可以就地处理、又不碰你检出的地方。

3. 冲突:停下来,等你在现场解决。 只要有仓库停在冲突上,整个结束操作就是部分完成 (failed: true,容器保留,其余仓库可能已经合并并移除)。结果里每个仓库行给出 mergeInProgress、 mergeSite(冲突现场所在目录)和 conflictedFiles。对话框此时显示「发生合并冲突,请处理后重新结束 任务。」和上面那段试合并的方向与现场说明,往下走的入口是面板底部的 继续结束任务。

现场就是任务分支自己的 worktree(例如 ~/wt-demo/spaces/demo/alpha),它正处在一次未完成的合并里: git -C <现场> status 会列出 both modified: 的文件,git -C <现场> rev-parse MERGE_HEAD 有值。 在那里把冲突解决掉,git add 之后用一条说清取舍的 git commit 完成这次合并提交就可以——目标分支和 源仓库的检出全程没被碰过;worktree 的索引在源仓库的 .git/worktrees/<名字>/ 下,提交要落在那一份上。 想让 agent 去解决这一场冲突,就用 授权 agent 处理(见文末实验性一节)。

按下 继续结束任务 之后仍然走同一套判断:

  • 先确认现场里没有残留冲突标记,再确认这次合并已经提交(MERGE_HEAD 已经消失)。
  • 然后照旧先问「目标分支是不是已经在任务分支里」(git merge-base --is-ancestor <目标分支> <任务分支>): 你刚把目标分支合进任务分支并提交,答案通常是「是」,于是试合并这一步直接跳过,直接做 第 2 步里那个真正的合并(把任务分支 --no-ff 合并进目标分支),然后删 worktree、按选项删分支。
  • 但如果这期间别人往目标分支推了新提交,目标分支就不再是任务分支的祖先,试合并照样先做一遍 (这次是把那些新提交合进任务分支):干净就撤销后真合并;冲突就还是前面那套——现场原样留在任务分支 的 worktree 里,你在那里再解决一次、再点 继续结束任务。所以不存在「解决过一次就绕过试合并」: 跳过试合并的条件只有一个,就是目标分支确实已经在任务分支里。
  • 唯一的例外是竞态:试合并干净之后、真正的合并(在源仓库里对目标分支 git merge --no-ff)那一 瞬间目标分支又动了并撞出冲突——插件会 git merge --abort,把源仓库恢复原样并报出 git 的原话 (这是「已经彩排过、这里再留下现场只可能是别人刚提交」的判断),worktree 和分支都保留;把新的 目标分支再合进任务分支,然后重新结束即可。

如果现场还留着没提交的解决结果,插件不替它提交,而是把这份现场原样留下、把话交回用户:结果里写明 the merge in <路径> is resolved but not committed(或者仍残留冲突标记),修好之后再点面板底部的 继续结束任务。

退出不丢进度。 报告和开过的会话行都留着,重新打开对话框就是刚才那一页,计划会重新读一次(每个 仓库自己选的合并目标分支和几个勾选不在其中,重选一次即可)。点遮罩、按 Esc、右上角 ✕ 退出同理;只有 一次结束真的在跑时,这几个出口才会被拦住。

「删除分支」平时需要先有合并:不选合并,它也一起不可用。要放弃一个任务空间而不是结束它 ——什么都不合并,worktree 移除、分支连同上面的提交一起丢弃——同时选中「强制」即可,那是唯一允许 删除未合并分支的方式;放弃也是唯一跳过第一步提交的组合。

各选项组合的行为

合并回目标分支 删除分支 强制 会发生什么
✓ 「确认结束任务」先不可用:自己把每个 worktree 里的未提交改动提交到各自的任务分支(不提交就一直停在这一步,结果点名那个 worktree),再把任务分支以 --no-ff 合并进它那一行选定的目标分支(默认是该仓库源检出所在的分支);移除 worktree;分支保留。停在冲突上的仓库原地保留、其余照常结束
✓ ✓ 同上,并在合并成功后删除分支(git branch -d,所以未合并的分支删不掉)
✓ ✓ 合并照做,但强制跳过提交那一步:worktree 里未提交的改动随 worktree 一起丢弃,插件不会替你提交它们;分支保留
✓ ✓ ✓ 合并、强制删除分支(git branch -D);分支已合并,所以并不额外丢东西
什么都不合并:「确认结束任务」先不可用;自己把改动提交掉(改动留在保留下来的分支上),再移除 worktree 和任务空间,分支保留(随时可以自己合)
✓ 同上但不提交:未提交的改动随 worktree 一起丢弃;分支保留
✓ ✓ 放弃:不合并、不提交,强删分支,分支上未合并的提交连同 worktree 里未提交的改动一并丢弃

无论哪种组合:任务空间里插件自己的元数据 —— worktree-space.json 和由它生成的 worktree-space.md(旧空间还可能有 README.en.md,更早的空间会有一份插件写的 README.md,从 1.0.5 起不再被自动清除)—— 前两者总是被清除;任务空间里其它内容按「归档文档」的选择处理(不选就直接丢弃)。改动没人提交、或者合并停在冲突上的仓库会原样保留、记为未完成(其余仓库照常结束),任务空间目录和它的工作区注册也因此都留下。反过来,只有所有仓库都真的移除了、容器空了,任务空间目录和工作区注册才会一起删除,其中的会话落到「未分组」但对话记录保留。

「结束任务」按钮的颜色跟着这件事走:橙色是常规收尾(合并可以回退,没有东西被丢);只有对话框能 点名说清「什么会被丢掉」时才是红色——也就是选择放弃任务空间(不合并、强制删分支),而 worktree 里还有未提交的文件、或者分支上还有未合并的提交。

实验性:把提交与冲突交给 agent

这一段描述的功能默认显示:不想看到它就把插件配置里的「授权 agent 处理入口」设为「隐藏」。隐藏时结束任务走 标准流程,插件不代写提交、也不替你取舍冲突——它会停下并把现场交代清楚:未提交的改动要在各自 worktree 里自己 提交,冲突解决并提交后点「继续结束任务」,提示行说的就是这条路径。

结束任务时,插件可以开一个 DSH agent 会话替你做完两件事:提交未提交的改动、解决停在冲突上的合并。

两个按钮和它们做什么

  • 计划里有未提交改动的仓库 → 授权 agent 提交:开一个会话,把每个 worktree 的改动 git add 并提交, 提交信息说清改了什么、为什么这么改(语言和风格随该仓库已有的提交)。
  • 有仓库停在合并冲突上 → 授权 agent 处理:开一个会话,读两边的变更、弄清各自想做什么,写出同时保留 双方意图的版本,git add 之后用一条说清如何取舍的提交信息 git commit 完成这次合并提交。

两种情形都:不 push,不动其它仓库或任务空间,不把分支合并回目标分支——那一步是插件的。

这些仓库共用一个会话(冲突阶段同一个仓库已有第 1 步开的那个会话时,直接复用)。工作目录取它们边界的共同 祖先:仓库的边界是它的 worktree,宿主报出了主检出路径时是「worktree 与主检出」的共同祖先。共同祖先只剩 盘根时(仓库跨盘,或任务空间与仓库都直接放在盘根下)也仍然只有一个会话,工作目录取任务空间,提权 在那个会话里批准。

流程和步骤

  1. 计划里有未提交改动时点 授权 agent 提交:插件开一个会话并交办。勾了 强制 时这一步跳过,也不开 会话。
  2. 等会话停下。停下后插件只重读一次计划(每个 job 一次;会话在跑时会重新武装这次读)。
  3. 读到的计划里那些仓库都没有未提交文件时,标题换成绿状态灯加绿字:提交阶段「已完成提交,可以继续 结束任务」,冲突阶段「合并冲突已处理完成,可以继续结束任务。」
  4. 点 确认结束任务 或 继续结束任务 接着走。
  5. 停在冲突上时点 授权 agent 处理,重复第 2-4 步。
  6. 只改了文件却没提交(或冲突标记还在)时插件不替它提交:现场原样留着,结果里写明没做完,你收尾或自己 接手,再点 继续结束任务。
  7. 第三步的试合并:agent 把目标分支合进任务分支并提交之后,按 继续结束任务 时「目标分支是否已经在 任务分支里」通常答「是」,试合并直接跳过;只有目标分支又有人推了新提交,才照样先试一遍。

面板底部的 确认结束任务 或 继续结束任务 始终由用户操作确认才会执行。

—/ 5

No ratings yet

Verified DSH bundle

Commit ed6500803373

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout