Scionwood
Worktrees for opencode that pull down code you didn't write — each with its own index
0
27
近 7 天 2
27.4
生态多维模型
1 个月前
2026-09-01
快速安装与配置
opencode.json写入当前项目的 opencode.json,只对这个仓库生效。
opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["scionwood@0.1.0"]
}写入 ~/.config/opencode/opencode.json,对所有项目生效。
~/.config/opencode/opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["scionwood@0.1.0"]
}若你要在本地改造这个插件,先装到项目里再从本地路径引用。
shell
pnpm add -D scionwoodOpenCode 启动时会通过内嵌运行时自动加载 npm 依赖并缓存至本地目录,无需手动在全局环境执行安装。
scionwood
Worktrees for opencode that pull down code you didn't write — each with its own index.
In grafting, scionwood is the cuttings you take from another tree to join onto your own. That is what this does with a pull request.
wt new audit --pr 1234 # isolated worktree, own index, your .env linked, deps installed
wt teardown audit # gone — and it won't let you lose uncommitted work
Not only pull requests:
wt new spike-auth # branch from the default branch
wt new hotfix --from v2.1.0 # from a tag
wt new theirs --branch feat-x # track a remote branch
When you'd reach for this
You need to review a pull request without disturbing what you're working on.
wt new review --pr 1234 checks that PR out in a directory of its own, with its
own code index, while your branch and your uncommitted changes stay exactly where
they were. No stash, no git checkout, no losing your place.
Something urgent lands while you're mid-change. wt new hotfix --from main
gives you a clean tree in seconds, and you go back to the first one by changing
directory.
You want a coding agent working somewhere that isn't your main checkout. Each worktree is indexed separately, so what the agent searches is the code actually in front of it rather than whatever your main branch happens to hold.
You want it gone afterwards, completely. wt teardown removes the directory,
the branch and the tool data — and refuses if that would throw away work you have
not committed.
Git can already do most of this. What it cannot do is remember your .env,
install your dependencies, index the tree, and refuse to delete your work.
Why this one
Several worktree plugins exist for opencode. Four things this one does that they do not:
1. Worktrees for code you didn't write. --pr 1234 fetches the pull request
itself. Every other plugin branches from your local HEAD. prRef makes the same
flow work on GitLab, Gitea and Bitbucket.
2. Each worktree gets its own tools. A tools entry gives a worktree its own
code index, its own docker compose project, its own anything — six lines of
config, no code. wt runs each tool's setup in the worktree, exports its
environment to every shell and matching MCP server there, and removes its data on
teardown. See docs/extending.md.
3. Zero tokens. The plugin never invokes the model. It registers no tool,
injects nothing into any prompt, and ships no slash commands — its whole surface
is three non-model hooks that set environment variables and a permission rule.
Other plugins hand the agent a worktree_create tool and spend model tokens on
every decision. A test enforces this.
4. It refuses to destroy your work. wt teardown checks for uncommitted
changes and unmerged commits and stops, instead of discarding them.
What the others do better
opencode-worktree has cross-platform terminal spawning, auto-commit on delete, and tmux/cmux integration. open-trees keeps a cross-repo session registry. Both let the agent drive worktrees, which is the right choice if that is what you want. This one is macOS-first and deliberately user-driven.
Inspired by the worktree + bootstrap/teardown flow of Spotify's Xirp for Claude Code, reimplemented natively for opencode.
Install
Three steps, once per machine. Run them from anywhere — no need to be in a project yet.
1. Install it.
npm install -g scionwood
2. Tell opencode to load the plugin. Open your global opencode config —
~/.config/opencode/opencode.json, or opencode.jsonc if that is the one you
have — and add "scionwood" to the plugin array.
If you already have plugins, add it alongside them:
{
"plugin": [
"some-plugin-you-already-had",
"scionwood"
]
}
If the file has no plugin key yet, add the whole line:
{
"plugin": ["scionwood"]
}
This is what gives each worktree its own code index and its own environment.
The wt command works without it; the isolation does not.
3. Check it worked.
wt --version
That's it. wt new my-worktree works right now, with no config at all.
From here on, run wt inside the project you want a worktree for — it works
out which repository you are in the same way git does. Only wt --version
works anywhere.
What wt new does
It creates the worktree, links your ignored files into it, runs your setup hook,
indexes it — and then launches opencode inside it. On a Mac that is the same
terminal tab you typed the command in. Pass --no-open if you just want the
worktree.
Where worktrees live, and keeping git quiet
Worktrees are created in .git-worktrees/ inside your repo, so git sees an
untracked directory there. The first time wt creates it, it adds the path to
.git/info/exclude — git's local ignore file, which lives in your clone and is
never committed, so nobody else is affected. If your .gitignore already covers
the directory, wt changes nothing.
If you would rather your whole team share the rule, add it to .gitignore
yourself and wt will stay out of it entirely:
.git-worktrees/
The same applies if you point basePath somewhere else — ignore whatever path
you chose.
Tailoring one repository
Only when a repo needs its own settings — run this inside that repository:
cd ~/code/my-project
wt init
That writes .opencode/wt.json and two hook scripts, then explains what is worth
editing and why. Most repositories never need it.
Installing into one project instead of globally
npm install --save-dev scionwood
npx wt install # puts the global `wt` command in place
and add "plugin": ["scionwood"] to that project's opencode.json.
wt install also writes ~/.config/wt/config.json, where you can put defaults
that apply to every repository. It will not put a second wt on your PATH if
npm already put one there.
Requirements
Quick start
wt new review-pr-1234
That single command:
git fetch --pruneandgit pull --rebaseon main (configurable).- Fetches
pull/1234/headfrom origin into a localreview/review-pr-1234branch. - Creates the worktree
.git-worktrees/review-pr-1234checked out on that branch. - Symlinks the ignored files from your main repo root (see
filesToLinkin Config). - Runs
npm installin the configured dirs (root +apps/frontendby default). - Runs
codegraph init && codegraph syncwith an isolated data dir. - Launches opencode in the same terminal tab, inside the worktree.
When you're done:
wt teardown review-pr-1234
Removes the codegraph data, symlinks, worktree, and branch — but only after checking that you are not about to lose anything. If the worktree has uncommitted changes, or the branch has commits that are not merged, teardown refuses and tells you what it found. Pass --force to discard them anyway.
Commands
| Command | Description |
|---|---|
wt new <name> [--pr N | --branch <ref> | --from <ref>] [--no-open] |
Create .git-worktrees/<name>, bootstrap it, open opencode. With no source flag it branches from the repo's default branch. wt new --pr N auto-names the worktree pr-N. |
wt config [--layers] |
Print the effective config, and where each layer came from. |
wt init [--force] |
Write <repo>/.opencode/wt.json. |
wt list |
List open review worktrees and their branches. |
wt open <name> |
Open opencode in an existing worktree. |
wt bootstrap [name|path] |
Re-run symlinks + codegraph indexing. Idempotent. |
wt clean [name|path] |
Remove worktree artifacts only (symlinks + codegraph data), keep the worktree and branch. |
wt teardown [name|path] [--force] |
Remove worktree, branch, and codegraph data. Refuses if the worktree is dirty or the branch has unmerged commits; --force discards them. Bare wt teardown tears down the worktree you're standing in. |
wt install [--config-home <dir>] |
Machine setup, once: the global wt command and ~/.config/wt/config.json. Never writes into a repository. |
wt test |
Run the built-in smoke tests (throwaway repo, no side effects). |
wt --version |
Show the version and which wt you are running. |
Name-based commands work from any directory, and bare commands work from anywhere inside a worktree, including its subdirectories. Paths are also accepted (e.g. wt teardown .git-worktrees/review-pr-1234).
Zero tokens
The plugin never invokes the model. It registers no tool, injects nothing into any
prompt, and ships no slash commands — its whole surface is three non-model hooks
(config, shell.env, event) that set environment variables and a permission rule.
Worktree management is a command you run. Other opencode worktree plugins hand the
agent a worktree_create tool and spend model tokens on every decision; this one
cannot, because it never had the tool. A test enforces it.
Config
Six keys. Everything optional — with no config at all, wt new spike-auth works.
{
"basePath": ".git-worktrees",
"branchPrefix": "wt/",
"prRef": "pull/{n}/head",
"filesToLink": ["CLAUDE.local.md", ".claude/settings.local.json", ".env"],
"hooks": {
"postCreate": ".opencode/hooks/post-create.sh",
"preTeardown": ".opencode/hooks/pre-teardown.sh"
},
"tools": {
"codegraph": {
"detect": "codegraph",
"dataDir": ".codegraph",
"setup": "codegraph init -i && codegraph sync"
}
}
}
| Field | Default | Meaning |
|---|---|---|
basePath |
.git-worktrees |
Directory under the repo root where worktrees are created. |
branchPrefix |
wt/ |
Branch name = ${branchPrefix}${worktree-name}. |
prRef |
pull/{n}/head |
Ref that --pr N fetches. GitLab: merge-requests/{n}/head. |
filesToLink |
[CLAUDE.local.md, .claude/settings.local.json, .env] |
Repo-relative paths symlinked from the main repo into each worktree. |
hooks.postCreate |
— | Script or command run in a new worktree. |
hooks.preTeardown |
— | Script or command run before a worktree is removed. |
tools |
codegraph | Per-worktree tooling. See docs/extending.md. |
Placeholders usable in tools values: {name}, {worktree}, {mainRoot},
{branch}, {dataDir}.
Where config comes from
Four layers, lowest precedence first:
built-in defaults
← ~/.config/wt/config.json your defaults, every repo, not in git
← <repo>/.opencode/wt.json this project, committed, optional
← WT_* env vars this invocation
wt config prints the effective result; wt config --layers also shows which
file each layer came from.
Merge rules. Maps merge by key, arrays union preserving order. A !-prefixed
name removes something you inherited — an array entry or a map key:
Your global config:
{ "filesToLink": ["CLAUDE.local.md", ".env"] }
A repository's config:
{ "filesToLink": [".env.test", "!.env"], "tools": { "!codegraph": true } }
Effective result: filesToLink is ["CLAUDE.local.md", ".env.test"], and the
built-in codegraph tool is removed. So a repo can add to what you inherited,
and drop what it does not want, without restating the rest.
Environment overrides
| Variable | Overrides |
|---|---|
WT_BASE |
basePath |
WT_BRANCH_PREFIX |
branchPrefix |
WT_PR_REF |
prRef |
WT_FILES_TO_LINK |
filesToLink (comma-separated) |
Plus runtime controls:
| Variable | Default | Meaning |
|---|---|---|
WT_OPEN |
auto |
How opencode is launched after new/open. auto: same tab from a real terminal, otherwise a new tab (iTerm2) / window (Ghostty). exec: always same tab. tab: force new tab. none: print the cd line only. |
WT_MAIN_ROOT |
autodetected | Act on this repository instead of the one containing your current directory. |
WT_CONFIG_HOME |
~/.config |
Where the global config and trust record live. |
WT_BIN |
— | Install target directory for wt install. |
Precedence: env > repo file > global file > built-in default.
How isolation works
- Tools: each
toolsentry'senvis exported around itssetup/teardown, injected into every shell command in the worktree, and injected into any local MCP server whose name or command matches the tool key. The shippedcodegraphentry uses this to give each worktree its own index. Remove it with"tools": { "!codegraph": true }, or add your own — see docs/extending.md. - Ignored files: paths in
filesToLinkare symlinked from the main repo into each worktree. Real files in the worktree are never overwritten; only previously created symlinks are replaced or removed. - Permissions: the plugin adds an
external_directoryallow rule for the main repo root, so reads through the symlinks work inside the worktree. - Teardown safety: only symlinks are deleted (never real files); branches are removed only when git allows it.
Testing
# Bash CLI integration tests (37 groups, no side effects)
wt test
# Plugin unit tests (42 tests, requires bun)
npm test
# or: bun test ./.opencode/plugins/scionwood.test.ts
wt test builds a throwaway repo and exercises the full cycle — config write, install, worktree creation, symlinks, codegraph env contract, listing, clean, idempotent bootstrap, teardown. Run it after any change to the bash script.
Troubleshooting
wt: command not found— run.opencode/scripts/wt install, or invoke by its path.'jq' is required— install jq (brew install jq) and re-runwt install.- CodeGraph errors during bootstrap — bootstrap continues without the index; fix your CodeGraph install and run
wt bootstrap <name>. - No iTerm2/Ghostty tab opens — from a normal terminal prompt, opencode deliberately runs in the same tab; a new tab (iTerm2) / window (Ghostty) is opened only when
wtruns without a TTY andWT_OPEN=auto. Detection usesTERM_PROGRAM(iTerm.app/ghostty); anything else falls back to printing thecdcommand. git worktree addfails with "already checked out" — the branch is open in another worktree; pick a different branch or close that worktree first.
For contributors
git clone https://github.com/selcukusta/scionwood
cd scionwood
npm install # dev dependencies
npm run build # dist/ is built here, and again automatically on pack/publish
npm test # bun + bash
npm pack --dry-run # verify the published tarball
vhs assets/demo.tape # re-render the README demo after changing CLI output
The package is scionwood. The build emits dist/plugin.js, dist/plugin.d.ts, dist/index.js (default-export wrapper), and dist/scripts/wt (executable bash CLI).
License
同类生态推荐
Forge Session Title
opencode-forge-session-title
OpenCode plugin that prefixes session titles with forge issue/PR/MR references (GitHub & GitLab)
Fork Lane
@bojackduy/opencode-fork-lane
Fork-lane for OpenCode — lane-inspired copy-on-write git worktrees + session fork. /fork-lane (/lane) creates a new branch + worktree (reflink for ignored files) and forks the session into it. Agent-callable via fork_lane tool (alias lane).
Oc Relay
oc-relay
Declarative environments + cross-machine session relay for OpenCode