Per-task isolated git worktrees with a full create/sync/finish lifecycle: task names auto-derive branches, merge targets are double-checked, and batch cleanup is built in — zero manual git operations.
Install
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:JohnXu22786/worktree-mgr
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time. Only install sources you trust, and pin a commit (github:owner/repo#sha).
README
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.
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
├── 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.
Links
More in this category
superdesigndev/treg★ 425
Tool catalog for agents: search ~2,600 external endpoints (SEO and SERP, backlinks, social, people and company enrichment, ad libraries, scraping) by the task you want done, read each one's parameters and per-call price, then call it with the credential injected server-side. Ships the skill plus an MCP row that stays disabled until TREG_TOKEN is set.
Lum1104/dsh-browser★ 198
Chrome sidebar extension that lets DSH operate your browser directly, no vision capabilities required.
zhaoolee/notes★ 142
Export DSH conversations as Smartisan Notes-style PNGs, or create and update Markdown notes in a configured account-scoped workspace.
liustack/modsearch★ 111
Web search bridge for text-only agents: ask the web or X, get structured JSON evidence (search, fetch, citations).
taxueseek/argo★ 91
Search built for agents: multilingual coverage across web, academic, code, shopping, finance, news, and encyclopedias.
Vladimir-Human/ru-marketplace-mcp#dsh★ 63
Skills and optional MCP rows for ten Russian marketplaces: price comparison across Wildberries, Detsky Mir and Yandex Market, plus per-source search, product cards and reviews. The 13 skills load on install; both MCP rows stay disabled until RU_MARKETPLACE_MCP_DIR points at a local clone, which needs Python 3.12+ and uv.