Bundle
dsh-worktree-space
Worktree Space: Creates an isolated workspace per task with Git worktrees for multiple repositories. Agents work in parallel without interference. On completion, it merges branches, removes worktrees, and archives documents.
- Source
- kangtsang
- License
- MIT
- Updated
- Updated 7 hours ago
Readme
# Worktree Space
DeepSeek Harness 的 Worktree Space 插件:一个任务可以横跨多个仓库,每个仓库用 Git worktree 各开一份,
共用同一个分支,放在源码树之外的任务空间里,并注册成一个 DSH 工作区,自带独立会话。


<img src="docs/img/manage-worktree-space.png" alt="管理页面:任务 / 工作区 / 仓库三个视图" width="960">
**简体中文** · [English](README.en.md)
> **Beta(实验性)**:把提交和合并冲突交给 agent 处理的功能处于实验性验证阶段,行为可能继续调整(见文末
> 「实验性:把提交与冲突交给 agent」);本文档其余部分描述的是不借助 agent 的标准流程。遇到问题请在
> [GitHub Issues](https://github.com/kangtsang/dsh-worktree-space/issues) 里反馈。
## 功能
- **从会话里建任务。** 选源码根、给任务起名、选分支前缀、勾选要横跨的仓库、指定任务空间放哪。每个仓库都会得到
一份位于 `<分支前缀><任务名>` 的 worktree(前缀默认 `task/`),起点可以是各仓库当前的 HEAD,
也可以指定某个分支或提交。
- **注册成工作区**,名字是 `<上级>/<任务名>`,同时开一个会话,工作目录就是这个任务空间 —— Agent 可以
跨仓库改代码,不会动到源码检出。
- **管理页面**,分三个视图(两种入口与各自默认值见「管理任务」一节):
- **任务空间视图**:把每个任务下面的仓库列在一起(分支、改动数、是否被锁定、是否可清理);
- **工作区视图**:哪些工作区可以建任务空间,以及每个工作区里有几个仓库;
- **Git 仓库视图**:扫到的每个 Git 仓库,以及它链接的 worktree。
三个视图都能搜索,也能用「待处理」筛出需要注意的行(有改动、被锁定、可清理,或状态读取失败);
每行左边的箭头能单独折叠,筛选旁的按钮可以一次全部展开或折叠。
- **结束任务**:在任务行上点「结束任务」。默认把各仓库的分支合并回该仓库当前检出的分支(也就是任务空间的
起点;插件不会切换源码仓库的检出),也可以在对话框里按仓库改成别的分支 —— 那会在一个临时 worktree 里
合并,同样不碰你的检出。worktree 里还没提交的改动得你自己先提交掉(插件不代写提交,见「结束任务」),
然后删掉 worktree,把任务空间里的文档存到
`archived-docs/<工作区名>-<YYYYMMDD-HHMMSS>`(不勾选归档,就会连这些文件一起删掉),
最后注销这个工作区。要是该工作区里还有会话在跑,会先拒绝,等它结束或停掉再试。
- **结束任务空间**也在这个工作区列表自己的 `⋯` 菜单里 —— 只对确实是任务空间的目录出现。
- **新建任务空间**同样在那个 `⋯` 菜单里 —— 只对挂有代码仓库的工作区出现,点开的就是同一个创建对话框。
- **不需要额外服务。** 入口开关、扫描深度和目录上限都在插件自己的配置里改(见「配置」)。支持 DSH
主题;界面语言跟着 DSH 的语言设置走;插件列表里的名称、描述和图标也由本插件提供。
## 任务目录结构
```text
<任务空间根目录>/
├── <任务名>/ 任务空间 —— 同时是会话的工作目录
│ ├── worktree-space.json 任务的记录:分支、起点、仓库与创建时间
│ ├── worktree-space.md 由上面的记录渲染出来,给人读的分支、起点与约定
│ ├── <仓库 A>/ 位于 <分支前缀><任务名> 的 worktree
│ └── <仓库 B>/ 同名分支的 worktree
└── archived-docs/
└── <上级>-<任务名>-20260926-020933/ 归档文档时存到这里
```
删掉 worktree 不会删除对应的 Git 分支;结束任务会先把分支合并回去,再删 worktree。
## 兼容性
基于 DSH **0.1.7-rc.1** 的客户端契约开发。已在 `0.1.7-rc.1`、`0.1.7-rc.2` 上验证:宿主 RPC 路由能注册,客户端 bundle
不用改就能加载,插件列表里的名称、描述、图标和配置区都正常显示。
manifest(`package.json`)里显式声明的兼容范围:
| 字段 | 声明值 | 含义 |
| --- | --- | --- |
| `engines.node` | `>=22.19.0` | 需要的 Node.js 版本 |
| `engines.dsh` | `>=0.1.7-rc.1` | 兼容的 DSH 版本 |
| `dsh.manifestVersion` | `1` | DSH 清单格式版本 |
| `dsh.compatibility.profiles` | `["web"]` | 已验证的 profile |
**固定提交**:本版(`v1.0.6`)对应的源码提交是 `ae7bb4386069090f9f188937a4d4eeafaadc9407`;验收用的包正是
从这个提交打出来的,可逐字节核对。
`engines.dsh` 是**声明**而非强制:当前 DSH 的安装器与加载器都不校验它,写一个范围不会拒绝不兼容的宿主,
因此这个范围的含义只是「`0.1.7-rc.1` 及之后都按兼容处理」;其中真正逐一验证过的是 `0.1.7-rc.1` 与
`0.1.7-rc.2`。如果后续 DSH 版本改动了客户端契约、让插件失效,会把下界往上收,或在 `dsh.compatibility`
里如实标记;遇到版本相关的问题,请到
[Issues](https://github.com/kangtsang/dsh-worktree-space/issues) 反馈。
## 权限、依赖与失败边界
本插件在运行时会读写文件、并调用 `git`;这两类权限就是它的功能本身,无法裁剪为零。完整清单(逐条说明
读什么、写什么、执行哪些子命令、失败时怎么办)见 [PERMISSIONS.md](PERMISSIONS.md);一次性 Profile 的
安装 / 启动 / 卸载验收证据见 [docs/store-evidence.md](docs/store-evidence.md)。
**权限一览:**
| 权限 | 范围 |
| --- | --- |
| 文件读取 | 你选择的工作区目录(广度优先扫描,跳过 `node_modules`、`dist`、`build`、`vendor` 与隐藏目录,`.worktrees` 除外);任务空间里的 `worktree-space.json`、`worktree-space.md`;各 worktree 的 `.git` 标记文件;插件自带的 `assets/skill/task-worktree-space/SKILL.md`;结束任务时按 `git diff --name-only HEAD` / `--diff-filter=U` 读回那些文件的内容,只为判断合并是否还留着冲突标记 |
| 文件写入 | 只写任务空间容器:`<任务空间>/<任务名>/` 及其中的 worktree、`worktree-space.json`、`worktree-space.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-rc.1` | 客户端契约、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 安装:**
```sh
dsh plugin --profile web add dsh-worktree-space
```
插件的挂载行写在 profile 的 `cordis.patch.yml` —— 本仓库带着 DSH 的 bundle patch,一般会自动生效;
若插件列表里一直没出现,就手动补上:
```yaml
- 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 生效。
| 设置 | 取值 | 默认 | 说明 |
| --- | --- | --- | --- |
| 面板入口(新会话下方) | 显示 / 隐藏 | **隐藏** | 侧边栏面板列表里那一行,整页打开管理页面(页面自带左侧导航和「返回会话」);默认隐藏 |
| 侧边栏底部入口 | 显示 / 隐藏 | 显示 | 侧边栏底部那个快捷入口,以**对话框**打开同一个管理页面 |
| 扫描深度 | 1–5 层 | 2 层 | 从工作区目录(第 0 层)往下找 Git 仓库的层级数 |
| 最大遍历目录数 | 500 / 1000 / 2000 / 3000 / 5000 / 10000 | 1000 | 一次扫描最多读多少个目录;超过会提示你换一个更小的工作区 |
| 默认分支前缀 | 任意文本 | `task/` | 新建任务空间时默认用的前缀;在新建面板里改动并勾选「设为默认分支前缀」,点创建时一并写回这里 |
| 归档文档目录 | 任意路径 | 留空 | 结束任务归档文档时把文件存到哪里;留空则存到每个工作区标题下的默认目录 |
扫描覆盖全部工作区;按广度优先逐层进行,每层最多同时读 8 个目录。任何一层只要发现 `.git` 就认定是
仓库;`node_modules`、`dist`、`build`、`vendor` 等目录和隐藏目录会跳过(`.worktrees` 除外)。
### 创建任务
<img src="docs/img/new-worktree-space.png" alt="新建 Worktree Space" width="720">
1. 在会话里,点输入框上方的 **新建 Worktree Space**。
2. 给任务起名 —— 会转成小写,空格、中文和其它字符都换成连字符(`hotfix-placeorder`);名字不合法时
会告诉你哪里不合规。
3. 需要的话改 **分支前缀**。分支名就是这个前缀加上任务名;留空则用配置里的默认前缀(默认 `task/`),
输入框下面那行会告诉你当前用的是哪个。只有你填的前缀和默认不一致时,才会出现 **设为默认分支前缀**
勾选框 —— 勾上它,点「创建并打开」时会把这个前缀写回插件设置。
4. 设置任务空间放哪。必须在源码树之外,界面会填好推荐的路径:**源码根所在盘根下的第一个目录**里的
`worktree-space`(源码根是 `E:\workspace\public\dsh-worktree-space`,推荐就是
`E:\workspace\worktree-space`)。放在这一层,任务空间和源码树同在一个盘根下的共同祖先里,而推荐本身
也不会被拉宽到盘根。换个位置也能用,只是源码根直接就在盘根下(`E:\repo`)时,两者只剩盘根可共用
(把提交或冲突交给 agent 时,提权要到那个会话里批准,见文末实验性一节)。
5. 勾选任务要横跨的仓库(每张仓库卡片上标出它当前 HEAD 所在的分支),并选择分支起点。
6. 点 **创建并打开**。新工作区会直接开一个会话,工作目录就是任务空间。
如果任务空间已经建好、但工作区注册失败,对话框会说明原因,并让你重试注册。
### 管理任务
打开 **管理页面**,两种入口:
- **侧边栏底部的 Worktree Space**(默认显示)—— 以**对话框**打开;
- **侧边栏「新会话」下方的 Worktree Space 行**(默认隐藏,在**配置**里打开)—— 在主区域**整页**打开,
页面自带左侧导航:第一项是 **返回会话**(面板占用了会话所在的主区域,所以留一个明确的回头路),
下面三项切换视图。对话框形态的视图切换仍在工具栏里。
三个视图的区别见「功能」一节:任务空间视图看每个
任务和它的各个仓库,工作区视图看哪些工作区能建任务空间,Git 仓库视图看扫到的每个 Git 项目及其 worktree。
右侧的统计会跟着视图变(`N 个任务` / `N 个工作区` / `N 个仓库 · M 个 Worktree`),搜索或筛选时显示
`可见 / 总数`。
面板每次打开都会重新扫描,但会先用宿主记住的上一次扫描结果把界面画出来,所以打开就能看到数据,扫描
结束后自动换成新结果(标题栏会显示「正在扫描所有工作区…」)。这份记忆只放在 DSH 实例的内存里:既不写磁盘,
也会在实例关闭时随之清空。
### 结束任务
<img src="docs/img/finish-task.png" alt="结束任务对话框" width="612">
在任务行上点 **结束任务**,或在工作区列表的 `⋯` 菜单里点 **结束任务空间**。对话框会先说明接下来会
发生什么:未提交的文件、待合并的提交、合并到哪个分支,以及任务空间里的文档(确实有东西可归档时,
才会出现「归档文档」选项)。「合并回目标分支」默认选中;「删除分支」和「强制」默认不选。
结束任务空间是一条标准流程,分三步:
**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 的工作区里(例如
`E:\wt-demo\spaces\demo\alpha\src\app.ts`)。**目标分支一点没被碰**,源仓库的检出也不动。
为什么反过来试:真合并一旦冲突,现场就落在你的源仓库和它的检出上,还得中止、还得挑边;先反着试一次,
冲突就落在插件自己的 checkout(任务分支的 worktree)里——那正是可以就地处理、又不碰你检出的地方。
**3. 冲突:停下来,等你在现场解决。** 只要有仓库停在冲突上,整个结束操作就是部分完成
(`failed: true`,容器保留,其余仓库可能已经合并并移除)。结果里每个仓库行给出 `mergeInProgress`、
`mergeSite`(冲突现场所在目录)和 `conflictedFiles`。对话框此时显示「发生合并冲突,请处理后重新结束
任务。」和上面那段试合并的方向与现场说明,往下走的入口是面板底部的 **继续结束任务**。
现场就是任务分支自己的 worktree(例如 `E:\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
结束任务时,插件可以开一个 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 把目标分支合进任务分支并提交之后,按 **继续结束任务** 时「目标分支是否已经在
任务分支里」通常答「是」,试合并直接跳过;只有目标分支又有人推了新提交,才照样先试一遍。
面板底部的 **确认结束任务** 或 **继续结束任务** 始终由用户操作确认才会执行。
Install
dsh plugin --profile web add github:kangtsang/dsh-worktree-space
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-worktree-space from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.