JohnXu22786/worktree-mgr
DSH 插件:为任务自动创建隔离的 git 工作区,管理创建/同步/收尾全生命周期(面向 DeepSeek Harness 的任务隔离 git worktrees)
项目介绍Project Overview
worktree-mgr 是 DSH 插件,为并行任务提供基于 git worktree 的隔离工作区。它按任务名自动生成分支、路径与台账记录,覆盖创建、同步、查看、完成、批量清理全流程,结束时可自动提交、合并并清理。适合模型同时处理多个任务、避免污染主工作区时使用。注意:仓库配置中的 seed 和 triggers 可执行任意命令,仅应在受信任仓库启用。
worktree-mgr is a DSH plugin that provides task-isolated git worktree workspaces. It auto-generates branches, paths, and ledger records from task names, covering create, sync, overview, finish, and batch cleanup, with automatic commit, merge, and cleanup on completion. Use it when models handle multiple parallel tasks without polluting the main workspace. Caveat: repository seed and triggers can run arbitrary commands, so enable it only in trusted repositories.
请帮我了解并安装插件:【worktree-mgr】【https://github.com/JohnXu22786/worktree-mgr】
把上面这条消息直接发给当前会话里的 DSH,让它帮你了解并安装。安装命令不一定准确,发给 DSH 更稳。Send this message to DSH in your current session. CLI install commands may not be accurate across systems — DSH will figure it out for you.
或使用命令行安装(适合开发者)Or use CLI install (for developers)
命令行安装CLI Install
dsh plugin --profile demo add github:JohnXu22786/worktree-mgr
把 JohnXu22786/worktree-mgr 加入你的 DSH 配置(web profile)即可启用。
READMEREADME
worktree-mgr
A plugin that provides task-isolated workspaces for dsh (DeepSeek Harness, a plugin-based harness running on the Cordis plugin framework).
When a model processes multiple tasks in parallel, each task is completed in its own git workspace (git worktree) on its own branch, without polluting the main workspace; when the task ends, the plugin commits, merges, and cleans up automatically. The full lifecycle — create / sync / overview / finish / batch cleanup — is covered by 5 tools + 1 CLI, with zero manual git operations throughout.
Core Concepts
| Concept | Description |
|---|---|
| Task | A single unit of work, e.g. add-search-box. Tools take the task name as entry point |
| Branch | Derived automatically from the task name: <prefix>/<task-slug>, default wtm/add-search-box, or explicitly specified |
| Workspace | Located under the vault directory (default platform data dir wtm/vaults/<repo-slug>/), isolated from the main repository |
| Ledger | index.json under the vault, persisting the "task ↔ branch ↔ path" mapping (JSON format, atomic writes + mutex lock) |
| Base branch | The branch a task merges into; defaults to the main workspace's current branch |
Features
- Task-driven: give the model a task name and the branch name, path, and ledger record are all generated automatically — no need to specify a branch
- Branch name safety: task name → slug normalization (illegal forms such as
..within a segment,.locksuffix, leading dots in a segment are fixed at the source), with dual ref-validity checks; creation is rejected when different tasks derive the same slug - Uncommitted change detection: merge is refused when the base branch is dirty (prevents mixing in unfinished work); uncommitted changes in the task workspace trigger an automatic snapshot commit by default
- Merge target consistency: before merging, the plugin validates "main workspace current branch == ledger base branch" and "task workspace current branch == ledger task branch"; any mismatch is refused — eliminating the silent error of reporting success while changes land on the wrong branch
- Sync and finish separated:
wtm_mergeonly merges without cleanup, whilewtm_finishcommits → merges → deletes workspace → deletes branch → clears the record; retry scenarios automatically skip already-completed merges (no duplicate empty merge commits) - Batch cleanup:
wtm_purgefinishes multiple tasks at once; a single task failure does not interrupt the rest, and any failure yields a non-zero exit code - Repository-level configuration:
<repo-root>/.wtm.jsonsupports branch prefixes, message templates, seed files (with path-escape protection), and lifecycle triggers (fixed working directory) - Concurrency safety: ledger writes hold a mutex — the lock contains a unique token with heartbeat refresh, stale locks are reclaimed only after a process crash, and release never mistakenly deletes a successor's lock
- Failure recovery: a failed create rolls back the created worktree and branch automatically; merge conflicts provide a
git merge --abortrecovery guide - Cross-platform: triggers execute on Windows (cmd) and POSIX (sh); path comparison normalizes case and separators
- Zero dependencies, no build: pure Node ESM,
node >= 21is enough — install and use
Installing in DSH
Install the latest version into a profile from GitHub:
dsh plugin --profile demo add github:JohnXu22786/worktree-mgr
Remove it:
dsh plugin --profile demo remove worktree-mgr
Option 1: install as a dsh bundle (recommended)
From a directory containing this package:
dsh plugin --profile demo add ./worktree-mgr
package.jsondeclaresdsh.bundle.patch → cordis.patch.yml, so dsh automatically inserts the plugin line into the profile's configuration layer;- That layer defaults to
root: !!js process.cwd()(the dsh startup directory as the main repository), overridable as needed; - This package is pure JavaScript with no build step, so installing from git never misses build artifacts.
Option 2: overlay loading (without installing into a profile)
dsh --profile demo --patch ./examples/overlay.yml
overlay.yml shares the same structure as the plugin line configuration, suitable for temporary mounting or configuration tweaks.
Option 3: standalone CLI
npm link # or node bin/wtm.js ...
wtm begin "Add Search Box"
Quick Start
# 1. Create an isolated workspace for the task (auto-derives branch wtm/add-search-box)
wtm begin "Add Search Box"
# 2. Edit code freely inside <vault>/add-search-box
# (or let the model work inside the task workspace directory)
# 3. View the status of all tasks (dirty/ahead/behind)
wtm status
# 4. Sync only, without finishing: merge task changes back to the base branch, keeping the workspace
wtm merge "Add Search Box"
# 5. Finish: snapshot commit → merge → remove workspace → delete branch → clear ledger
wtm finish "Add Search Box"
# 6. Batch finish
wtm purge "Task A" "Task B" # specified tasks
wtm purge --all # all tasks
All commands support --json for structured output, making them easy to consume from scripts and the harness.
CLI Reference
bin/wtm.js runs standalone (wtm after npm link, or node bin/wtm.js); every subcommand accepts --json for structured output on stdout.
| Command | Description | Exit code |
|---|---|---|
wtm begin <task> |
Create an isolated workspace (--base, --branch, --note, --root) |
0 success / 1 failure |
wtm merge <task> |
Merge the task branch back to the base, keep the workspace (--mode, --message) |
0 / 1 |
wtm finish <task> |
Finish and clean up (--mode, --message) |
0 / 1 |
wtm status |
Overview of all tasks | 0 / 1 |
wtm purge [task...] |
Batch finish; --all for everything |
0, or 1 when any task fails |
wtm help |
Print usage | 0 (2 for a bare wtm) |
Exit codes: 0 success; 1 operation failure (with --json the failure lives in the JSON payload, and any failed sub-result of purge also yields 1); 2 usage errors (unknown or missing command). The WTM_* environment variables apply to the CLI as well.
Tool Interface (Model-Facing)
| Tool | Purpose | Key parameters |
|---|---|---|
wtm_begin |
Create an isolated workspace for a task | task(required), base, branch, note, root |
wtm_merge |
Sync: merge task branch back to base branch (workspace kept) | task(required), mode(commit/refuse), message, root |
wtm_finish |
Finish: commit→merge→clean up workspace and branch | task(required), mode(commit/abandon/keep), message, root |
wtm_status |
Task overview (existence/dirty state/ahead-behind) | root |
wtm_purge |
Batch finish | tasks, all, mode, message, root |
mode semantics
commit(default): first snapshot-commit the uncommitted changes in the task workspace, then merge back to the base branch, then clean uprefuse: refuse directly when the task workspace has uncommitted changes (onlywtm_merge)abandon: discard all task changes, force-clean the workspace and delete the branch (irrecoverable, use with care)keep: only release management; workspace and branch remain untouched (onlywtm_finish)
Safety boundaries (identical for tools and CLI):
- Base branch workspace has uncommitted changes → merge refused (
wtm_merge/wtm_finishin commit mode) - Main workspace current branch ≠ ledger base branch, or task workspace current branch ≠ ledger record → operation refused (prevents changes landing on the wrong branch)
- Task branch already exists, task already registered, workspace directory already exists, or different tasks deriving the same workspace path → creation refused
- Illegal task or branch name (git ref rules) → refused before any git operation
- Seed file path escapes (
../xescaping the repo/workspace) → intercepted with a warning - Call cancelled (
exec.signalabort) → clean return; a failed create rolls back created worktrees and branches automatically
Plugin Integration Notes (how the harness loads it)
This plugin follows dsh's standard plugin protocol, consisting of three pieces:
worktree-mgr/
├── package.json # ① dsh.bundle manifest: declares this package as a configuration layer
├── cordis.patch.yml # ② Configuration layer content: inserts the plugin line into the profile
├── index.js # ③ Entry module: exports name / inject / apply
└── src/ # Implementation: naming/config/vault/git/triggers/ops/tools
① Bundle manifest (package.json):
{
"name": "worktree-mgr",
"type": "module",
"main": "index.js",
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
}
② Configuration layer (cordis.patch.yml):
- insert:
- id: worktree-mgr
name: worktree-mgr # resolved by package name; Node module resolution finds index.js
config:
root: !!js process.cwd()
③ Entry module (index.js) exports:
export const name = 'worktree-mgr'
export const inject = ['tools'] // declares dependency on the tools registry
export function apply(ctx, config = {}) {
ctx.tools.register(...) // registers the 5 tools
}
Loading order: profile assembly → this bundle's patch layer inserts the plugin line → the loader waits for the tools service → calls apply(ctx, config) → tool schemas flow into the system prompt automatically, and the model can invoke them.
Tool definition shape (consistent with dsh tool conventions):
{
name: 'wtm_status',
description: '...', // model-visible description
parameters: { // flat property table; required: true means mandatory
root: { type: 'string', description: 'repository path' }
},
output: {
schema: { type: 'object', properties: { ok: { type: 'boolean', required: true }, ... } },
render: (args, value) => [{ type: 'text', text: '...' }] // model-visible content
},
async execute(args, exec) { ... } // returns canonical JSON; exec.signal supports cancellation
}
Events/hooks interface: the plugin itself does not subscribe to harness events; lifecycle extensions go through triggers in the repository-level configuration — at the on_begin / on_merge / on_finish nodes, repo-configured shell commands run with WTM_TASK / WTM_BRANCH / WTM_BASE / WTM_PATH / WTM_ROOT environment variables injected. Triggers have fixed working directories: on_begin runs inside the new workspace, on_merge / on_finish run at the main repository root. Trigger failures only log warnings and never interrupt the main flow.
Configuration
Precedence (low → high): built-in defaults < plugin line config < repo .wtm.json < environment variables WTM_*
| Key | Default | Description |
|---|---|---|
root |
process.cwd() |
Main repository path (plugin line / tool parameter only) |
vault |
platform data dir wtm/vaults/<repo-slug>/ |
Directory for task workspaces and the ledger; relative paths resolve against the repo path |
prefix |
wtm |
Branch prefix; derived branches are <prefix>/<slug> |
commitMessage |
chore(wtm): snapshot {task} |
Snapshot commit template, placeholders {task} {branch} {base} |
mergeMessage |
merge(wtm): fold {task} into {base} |
Merge commit template |
Environment variables: WTM_ROOT (effective for both tools and CLI), WTM_VAULT, WTM_PREFIX, WTM_COMMIT_MESSAGE, WTM_MERGE_MESSAGE (WTM_ROOT has lower precedence than tool parameters and plugin config).
Repository-level config <repo-root>/.wtm.json
{
"prefix": "wtm",
"vault": "D:/wtm-vaults",
"commitMessage": "chore(wtm): snapshot {task}",
"mergeMessage": "merge(wtm): fold {task} into {base}",
"seed": { "files": ["docs/AGENTS.md"] },
"triggers": {
"on_begin": ["pnpm install"],
"on_merge": ["pnpm lint"],
"on_finish": []
}
}
vaultmust be outside the repository working tree (otherwise the vault directory would keep dirtying the main workspace, and the plugin refuses outright);seed.files: files copied from the main repository into the workspace when the task workspace is created (e.g. team convention docs); paths must stay within the repo/workspace — out-of-bounds entries are intercepted with a warning;triggers.*: lifecycle hook command arrays, see "Events/hooks interface" above.
Unknown keys produce a warning and are ignored; a corrupted .wtm.json never blocks operations, only warns.
Security Notes (Important)
- Repository config is code:
.wtm.json'sseed.filescopies files from the repo into workspaces, andtriggers.*runs arbitrary shell commands with your user's privileges. Only enable this plugin in trusted repositories — when cloning and operating on untrusted repositories, a repo-supplied.wtm.jsonis equivalent to granting it your execution permissions. Leaveseed/triggersempty when you don't need this capability. - Snapshot commits include untracked files: when the task workspace is dirty, the default snapshot
git add -Acommits everything (including untracked files such as build artifacts). To keep large directories out of history, maintain a.gitignorein the task workspace, or handle it manually withrefusemode. - abandon is irrecoverable:
wtm_finish --mode abandonand batchwtm_purgeforce-delete workspaces and delete task branches (-D); changes there cannot be recovered, so only use them when you have confirmed the discard.
Ledger and Concurrency
- Ledger:
<vault>/index.json,{version: 1, records: [{task, branch, base, path, createdAt, updatedAt, note?}]} - Writes are atomic (temp file + rename) and hold a
.lockmutex for the entire operation; - The lock contains a unique holder token with a 30s heartbeat refresh: after a process crash, a lock older than 5 minutes is deemed stale and reclaimed; release validates the token so a successor's lock is never mistakenly deleted; the wait timeout defaults to 5 seconds.
Development and Testing
npm test # node --test, zero third-party dependencies
npm run typecheck # optional: requires dev-installed typescript + @types/node
Test coverage: naming rules, config merging, ledger (atomic writes/lock/stale reclamation/corruption recovery), git output parsing, triggers, lifecycle orchestration (fake git injection), tool schemas, plus integration tests against real git (full begin → modify files → status → finish chain).
Directory Structure
worktree-mgr/
├── package.json # bundle manifest + metadata
├── cordis.patch.yml # plugin configuration layer
├── index.js # dsh plugin entry (name/inject/apply)
├── bin/wtm.js # standalone CLI
├── README.md # documentation (EN)
├── README.zh.md # documentation (ZH)
├── LICENSE # MIT license
├── src/
│ ├── naming.js # task name → branch mapping and ref validation
│ ├── config.js # config merging and template rendering
│ ├── vault.js # ledger persistence (atomic writes/lock)
│ ├── git.js # git execution layer and output parsing
│ ├── triggers.js # lifecycle triggers
│ ├── ops.js # lifecycle orchestration (begin/merge/finish/status/purge)
│ └── tools.js # dsh tool definitions
├── examples/
│ ├── .wtm.json.example # repository config example
│ └── overlay.yml # dsh overlay example
└── tests/ # node:test unit + integration tests
License
MIT — see LICENSE.
nexu-io/open-design
ruvnet/ruflo
amruthpillai/reactive-resume
esengine/DeepSeek-Reasonix
volcengine/OpenViking
Molunerfinn/PicGo
titanwings/distilly
titanwings/colleague-skill