dsh-plugin-worktrees 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.zh.md ADDED
@@ -0,0 +1,151 @@
1
+ # dsh-worktrees
2
+
3
+ [English](README.md) | **简体中文**
4
+
5
+ > 适用于 DeepSeek Harness (dsh) `0.1.0-rc.6` / `0.1.0-rc.7` · Node ≥ 18 · PATH 上有 git · MIT
6
+
7
+ **让多个 agent 并行写代码而互不打架。** 每个任务拿到自己的目录 —— 一个挂在独立分支上的 git worktree —— 多个 agent 可以同时改代码而互不干扰。任务完成后,其分支经串行队列**一次一个**地合回可供审查的集成分支。如果两个任务改了同几行,冲突现场会原样保留给你 —— 绝不自动解决、绝不强推。
8
+
9
+ 六件工具、零宿主补丁、不接管任何工具名 —— 装上即对所有会话可见 `worktree_*` 工具族。
10
+
11
+ ## 为什么需要它
12
+
13
+ 并行派几个带写权限的子代理,它们会互相踩脚:大家改的是同一个工作目录,每个文件都是一场竞态。经典解法就是 git worktree —— 本插件把它变成 agent 工作流里顺手的一步:
14
+
15
+ - `worktree_create("api-types")` → 在 `~/.dsh/worktrees/<repo>/<session>/api-types` 得到一个全新检出,分支 `dsh-wt/<session>/<api-types>`。
16
+ - 用 `cwd` 把 agent 派进去(借助
17
+ [dsh-plugin-subagents](https://github.com/Luck9Star/dsh-plugin-subagents))—— 你自己进去改也行。
18
+ - `worktree_merge(id)` → 未提交的改动自动提交,然后分支合入 `dsh-wt/integration/<session>` —— 严格一次一个合并,集成之间不会互相撞车。
19
+
20
+ ## 安装
21
+
22
+ ```sh
23
+ # 1. 安装进 dsh profile
24
+ dsh plugin --profile web add dsh-plugin-worktrees # 或者:add /path/to/本地检出
25
+
26
+ # 2. 重启并开一个新会话
27
+ dsh --profile web
28
+ ```
29
+
30
+ **预期结果:** 新会话里出现 `worktree_create`、`worktree_list`、`worktree_status`、`worktree_merge`、`worktree_queue`、`worktree_cleanup` 六个工具。
31
+
32
+ > **用本地检出而不是 npm?** 先在仓库里跑 `npm install && npm run setup:peer`(避免出现第二份 dsh-tools,否则每次工具调用都会崩),再 `dsh plugin --profile web add "$(pwd)"`。
33
+
34
+ > **想让 agent 直接在 worktree 里干活?** 官方宿主会静默丢弃子代理的 `cwd`。安装
35
+ > [dsh-plugin-subagents](https://github.com/Luck9Star/dsh-plugin-subagents)
36
+ > 并跑它的 `patches/install.sh`(每次 dsh 升级后重跑)。
37
+
38
+ ## 快速上手
39
+
40
+ 完整闭环 —— 两个并行任务、一次干净合并、一次冲突:
41
+
42
+ ```jsonc
43
+ worktree_create({ task: "api-types" }) // → { id: id1, path: P1, branch, base_commit, … }
44
+ worktree_create({ task: "docs-refresh" }) // → { id: id2, path: P2, … }
45
+
46
+ // 并行干活 —— 每个 agent(或你自己)在自己的 path 里改
47
+ subagent({ prompt: "实现新的 API 类型。", cwd: P1 })
48
+ subagent({ prompt: "刷新文档。", cwd: P2 })
49
+
50
+ worktree_merge({ worktree_id: id1 })
51
+ // → { state: "succeeded", integrated_commit, integration_branch }
52
+
53
+ worktree_merge({ worktree_id: id2 })
54
+ // → { state: "conflicted", conflict_files: [...],
55
+ // integration_worktree: "<保留的现场>", resolution_hint: "…" }
56
+
57
+ // 在保留的 worktree 里手工解决(冲突标记都在),然后:
58
+ worktree_queue({ action: "list" })
59
+ worktree_queue({ action: "resolve", job_id: "…" }) // 释放集成分支
60
+ worktree_cleanup({ worktree_id: id1 }) // 移除 worktree 和分支
61
+ ```
62
+
63
+ ## 六个工具
64
+
65
+ | 工具 | 作用 |
66
+ | --- | --- |
67
+ | `worktree_create` | 为任务新建 worktree + 专属分支。返回 `path`(直接交给 `cwd`)、`branch`、`base_commit`、`integration_branch`。 |
68
+ | `worktree_list` | 列出某仓库的 worktree(可选包含保留的冲突现场),附合并队列摘要。 |
69
+ | `worktree_status` | 单个 worktree:HEAD、脏状态、逐文件变更、领先基线多少、当前合并作业。 |
70
+ | `worktree_merge` | 自动提交未提交改动,然后入队并合入集成分支。五种可能结果(见下)。 |
71
+ | `worktree_queue` | 查看队列;对作业 `resolve` / `retry` / `cancel`。 |
72
+ | `worktree_cleanup` | 移除 worktree(及分支,除非 `keep_branch`)。未经双重确认,拒绝销毁未合并的工作。 |
73
+
74
+ ### 合并结果
75
+
76
+ | `state` | 含义 |
77
+ | --- | --- |
78
+ | `succeeded` | 合并成功;集成分支上出现 `integrated_commit`。 |
79
+ | `conflicted` | 有冲突。集成 worktree 连同冲突标记**保留**;该作业占住集成分支,直到你 `resolve` 或 `retry`。 |
80
+ | `queued` | 排在其他合并后面(`queued_ahead` 告诉你前面有几个)。 |
81
+ | `no_changes` | 工作树相对基线是干净的 —— 无可集成(正常,不是错误)。 |
82
+ | `failed` | 硬失败(如 `autoCollect: false` 时工作树脏 → `dirty_not_collected`)。 |
83
+
84
+ ### 冲突处理,一句话版
85
+
86
+ 不自动解决、不 rebase、不强推。冲突的合并保留现场(一个带冲突标记的 worktree),列出冲突文件,并**占住集成分支**,直到你:在保留的 worktree 里改完文件后 `worktree_queue(action: "resolve")`;或 `action: "retry"` 重新排队;或放弃。
87
+
88
+ ## 内建的安全机制
89
+
90
+ - **未合并的工作很难丢。** 清理一个提交还没进集成分支的 worktree,需要**两**个独立确认(`force: true` **且** `acknowledge: true`),否则带证据拒绝。已合并的 worktree 直接清理,没有仪式。
91
+ - **仓库门禁,从严失败。** 只在会话 cwd 子树、已注册工作区或 `allowedRoots` 里建 worktree —— 没有"任意根"开关。分支名由 git 本身校验。
92
+ - **只在本地。** 插件会合并,但绝不 push/fetch/clone —— 结果留在本地集成分支上,等人审查后再推。
93
+ - **脏工作树有归宿。** `autoCollect: true`(默认)在合并前自动提交未提交改动(含未跟踪文件)。关掉它,脏树会以明确报错拦下合并,而不是硬来。
94
+ - **重启安全。** 启动时,仓库/路径已消失的 worktree 标记为 `orphaned`,在途合并判失败并附说明 —— 只做标记,不做破坏性删除。
95
+
96
+ ## 配置
97
+
98
+ 全部可选 —— 下表每项都有可用默认值。配置写在 profile 的 `cordis.patch.yml` 中本插件的行上;写错的键会在启动时大声报错。
99
+
100
+ | 键 | 默认值 | 含义 |
101
+ | --- | --- | --- |
102
+ | `worktreeRoot` | `~/.dsh/worktrees/` | 所有任务 worktree 的根目录。 |
103
+ | `maxWorktrees` | `16` | 跨仓库的非终态 worktree 上限。 |
104
+ | `defaultBaseRef` | `HEAD` | 新任务分支的起点(总是解析成具体 commit)。 |
105
+ | `autoCollect` | `true` | 合并前自动提交未提交改动。 |
106
+ | `gitTimeoutMs` / `mergeTimeoutMs` | `15000` / `120000` | 单条 git 命令 / 合并步骤的超时。 |
107
+ | `allowedRoots` | `[]` | 门禁额外放行的仓库根。 |
108
+ | `requireWorkspaceRegistration` | `true` | 同时放行已注册工作区(关闭后仅靠会话 cwd + `allowedRoots`)。 |
109
+ | `statePath` | `~/.dsh/dsh-worktrees/state.json` | 状态文件(原子写、仅所有者)。 |
110
+ | `retainJobHistory` | `200` | 保留的终态合并作业记录数。 |
111
+ | `register.*` | `true` | 按工具注册开关。 |
112
+
113
+ ## 搭配使用
114
+
115
+ - [dsh-plugin-subagents](https://github.com/Luck9Star/dsh-plugin-subagents)
116
+ —— 它的逐次调用 `cwd` 就是接合点:把 `worktree_create` 返回的 `path` 直接交给子代理,它就写进 worktree。
117
+ - [dsh-dag-orchestrator](https://github.com/Luck9Star/dsh-dag-orchestrator)
118
+ —— 本插件对外暴露 `worktreesEngine` 服务,编排器探测到即可用,DAG 里的 `worktree:` 任务隔离与 `merge` 节点零接线开启。
119
+
120
+ ## 边界与常见问题
121
+
122
+ - **一个仓库的合并只走一个 dsh 会话。** 队列是进程内的;不支持两个宿主并发合并同一仓库。
123
+ - **`cwd` 被忽略了?** 官方宿主会丢弃它 —— 装 dsh-plugin-subagents 并跑它的 `patches/install.sh`。
124
+ - **`active_job_exists` 报错** —— 更早的一个冲突作业占着集成分支;`worktree_queue(action: "list")`,然后 `resolve` 或 `retry` 它。
125
+ - **每次工具调用都报 `Cannot read properties of undefined (reading 'prepare')`** —— 本地检出没跑 `npm run setup:peer`;在本仓库 `npm install` 后、或 dsh 升级后重跑。
126
+
127
+ ## 开发
128
+
129
+ ```sh
130
+ npm install && npm run setup:peer # 链接正在运行的宿主的 peers
131
+ npm test # node --test,真实本地 git 夹具 —— 不碰网络与远端
132
+ npm run lint
133
+ ```
134
+
135
+ 设计记录:[docs/DESIGN.md](docs/DESIGN.md)。
136
+
137
+ ## 参考与致谢
138
+
139
+ - **git worktree** —— 这一切所构建的底层机制。
140
+ - **task-weaver**(`packages/workspaces/`)—— 核心引擎(git 端口、合并队列、冲突现场保留)移植自它。
141
+ - [dsh-plugin-subagents](https://github.com/Luck9Star/dsh-plugin-subagents)
142
+ 与 [dsh-dag-orchestrator](https://github.com/Luck9Star/dsh-dag-orchestrator)
143
+ —— 为组合而生的伙伴插件。
144
+
145
+ ## 安全
146
+
147
+ 见 [SECURITY.md](SECURITY.md)。
148
+
149
+ ## 许可证
150
+
151
+ [MIT](LICENSE)
package/SECURITY.md ADDED
@@ -0,0 +1,56 @@
1
+ # Security
2
+
3
+ This plugin drives **local git repositories only**. Please read this before
4
+ deploying it.
5
+
6
+ ## What the plugin does
7
+
8
+ - **No network operations — by construction.** Every git call goes through
9
+ `lib/git-port.js`, whose interface has no `push`, `fetch`, or `clone`
10
+ method at all. The plugin cannot reach a remote even by accident;
11
+ integration results stay on the local integration branch for a human to
12
+ review and push. Each git invocation also sets `GIT_TERMINAL_PROMPT=0`,
13
+ so git never prompts for credentials interactively.
14
+ - **No credential handling.** The plugin reads no secrets, stores no
15
+ secrets, and passes no credentials anywhere. Git subprocesses inherit the
16
+ parent environment (plus `GIT_TERMINAL_PROMPT=0`); do not rely on the
17
+ plugin to scrub anything from it.
18
+ - **Command-injection resistant.** Git is spawned as
19
+ `spawn("git", argvArray, {shell: false})` — argv is never a shell string.
20
+ Caller-controllable refs are resolved with
21
+ `rev-parse --verify --end-of-options`, branch names (task slugs AND
22
+ explicitly supplied integration branches — including the merge-time
23
+ override and the engine-seam enqueue, re-vetted at apply time) are
24
+ pre-vetted with `check-ref-format --branch`, and positional refs sit
25
+ after the `--` end-of-options terminator (`scripts/lint.js` enforces the
26
+ single-ingress rule statically: only `git-port.js` may spawn).
27
+ - **Repo admission fails closed.** A repository root is only admitted after
28
+ realpath canonicalisation when it lives inside the session-cwd subtree, a
29
+ registered workspace, or an explicitly configured `allowedRoots` entry —
30
+ there is no any-root switch.
31
+ - **Destructive actions are gated.** Deleting unmerged work requires the
32
+ double confirmation `force: true` AND `acknowledge: true` — "unmerged"
33
+ covers BOTH the task branch not having reached the integration branch
34
+ AND the integration branch not existing yet while the worktree holds
35
+ commits past its base (work that exists nowhere else). With
36
+ `autoCollect: false` a dirty worktree is never silently skipped: the
37
+ merge fails `dirty_not_collected` instead of reporting success over
38
+ unintegrated files. Conflicted merge scenes (worktree + source branch)
39
+ are retained until explicitly cleaned up; startup crash reconciliation
40
+ marks and never destroys.
41
+
42
+ ## What to keep private
43
+
44
+ - The state file (default `~/.dsh/dsh-worktrees/state.json`) records repo
45
+ paths, branch names, and merge-job history. It is written atomically with
46
+ owner-only `0600` permissions and a `__proto__`-key guard. Treat it as
47
+ private runtime state and never commit it.
48
+ - Worktrees created under `~/.dsh/worktrees/` contain full copies of your
49
+ repositories' working trees — the same sensitivity as the repositories
50
+ themselves.
51
+
52
+ ## Reporting
53
+
54
+ Report vulnerabilities via GitHub Issues on this repository's tracker
55
+ (placeholder until a private channel is set up). Do not open public issues
56
+ for exploitable flaws if a private channel is available to you.
@@ -0,0 +1,30 @@
1
+ # dsh-worktrees bundle patch (T11, DESIGN §8.3).
2
+ #
3
+ # 用途:把 dsh-worktrees 插件实例挂进 profile 的层序。本插件是纯 tool
4
+ # 插件——六件工具(worktree_create / list / status / merge / queue /
5
+ # cleanup)全部是新名字,注册在全局工具层,对所有会话天然可见:
6
+ # 无需 disable 任何官方行(不接管官方工具名)、无需 preset 适配、
7
+ # 零宿主补丁(本仓库没有 patches/ 目录)。形态对齐 dsh-ssh 的单
8
+ # insert 写法。
9
+ #
10
+ # 安装方式:`dsh plugin --profile <name> add dsh-worktrees`(或 add
11
+ # <本地路径>)——reconcile 解析 package.json 的 dsh.bundle.patch 字段,
12
+ # 自动把本 patch 层追加到 bundles 尾部,insert 行随即生效。之后在本
13
+ # 仓库跑 `npm run setup:peer`(dsh-tools 单实例 symlink,peer 纪律见
14
+ # README),再重启 dsh 开新会话。要覆盖配置,在 profile 层同 id 行
15
+ # 写 config(profile 层覆盖 bundle 层),键表见 README「配置」。
16
+ #
17
+ # 互斥:无。新名字工具不与官方行或同族插件冲突,可与 dsh-plugin-
18
+ # subagents 等任意 bundle 并存(正交组合正是本插件的卖点)。
19
+
20
+ - insert:
21
+ # 本行即 host-plane Cordis 插件实例;六件 worktree_* 工具由它注册。
22
+ - id: worktrees
23
+ name: dsh-worktrees
24
+ config:
25
+ # —— 全部键省略即走 lib/config.js 的 zod strict 默认;示例(注释):
26
+ # maxWorktrees: 16 # 非终态 worktree 全局上限(跨 repo 计数)
27
+ # allowedRoots: ["~/Documents/dev/Agents"] # repo 门禁额外放行的根(realpath 子树判定)
28
+ # 其余键:worktreeRoot / statePath / defaultBaseRef / gitTimeoutMs /
29
+ # mergeTimeoutMs / requireWorkspaceRegistration / autoCollect /
30
+ # retainJobHistory / register 六开关 —— 逐键说明见 README。