| name | dsh-upgrade |
| description | Upgrades a source-installed, personally customized DSH checkout to upstream master while preserving local changes and an unchanged rollback worktree. Use when the user asks to update or upgrade DSH. |
DSH Upgrade
Prepare and validate the upgrade in a fresh staging worktree of the main clone, leave the worktree the installed launcher currently uses unchanged, then atomically repoint the stable current symlink once. Read and follow dsh-customize before starting; it owns checkout discovery and lock handling.
Layout
Resolve the layout, never assume it. dsh-customize owns the procedure: follow the PATH launcher to the staging worktree, then derive the main clone from that checkout with Git. One resolution covers every checkout, so this workflow needs no special case and depends on no environment variable.
The resolved layout is one container directory <source> holding each staging checkout as a git worktree <source>/staging-<timestamp> on branch dsh-staging/<timestamp>, plus the stable symlink <source>/current pointing at the active one; the PATH launcher links to <source>/current/bin/dsh, so it resolves PATH -> current -> staging worktree. The main clone is the one real clone whose object store every worktree shares, and is never a launcher target. It may live inside <source> or anywhere else on disk, on any branch, with remotes that may point at a fork — so treat it strictly as the object store and worktree host, and take authoritative upstream from step 1 instead. Cutover repoints current alone, so the PATH launcher itself never moves. The main clone's .git/info/exclude is inherited by every linked worktree, so one .agents/merge.lock entry there excludes the lock in all of them. When the launcher links straight at a worktree with no current in the chain, the same resolution finds it, and cutover creates current and repoints PATH to current/bin/dsh as a one-time migration.
Names
One upgrade attempt uses one UTC basic timestamp YYYYMMDDTHHMMSSZ for all names:
- new staging worktree:
<source>/staging-<timestamp>;
- preparation branch:
dsh-upgrade/prepare-<timestamp>;
- installed staging branch:
dsh-staging/<timestamp>;
- fetched upstream ref:
refs/dsh-upgrade/upstream-<timestamp>;
- recovery ref:
refs/dsh-upgrade/recovery-<timestamp>;
- recorded
current target before cutover: the old staging worktree path, kept for symlink rollback.
The worktree name is always staging-<timestamp> under <source>, never derived from the current staging directory name, so successive upgrades stay in one place and do not accumulate timestamps. The preparation branch and private refs are local-only and must never be pushed. Before starting, reject a current staging branch named exactly dsh-staging, because Git cannot also create dsh-staging/<timestamp>; require the user to choose a non-conflicting staging namespace rather than silently renaming it. If the new staging worktree path exists, resume only when it is a clean worktree of this main clone whose recorded old tip, upstream ref, recovery ref, and named branches exactly match this attempt; otherwise stop. Never add an ad hoc suffix or delete an unknown directory.
Upgrade
- Resolve the installed launcher, its staging worktree and branch, the main clone, the current DSH process source, and authoritative upstream. Record exact tips, paths, clean status, remotes, dependencies, worktrees, and in-progress Git operations. Require the installed staging worktree to be clean and its
.agents/merge.lock to exist and be Git-excluded. Never stash automatically.
- Treat the staging worktree behind the installed launcher as immutable for the whole attempt: do not touch its branch, HEAD, index, tracked or untracked files, dependencies, worktree registration, or lock file. Fetching into the shared main clone and creating new branches, worktrees, and private refs there are allowed because they are append-only and never alter the old worktree's checkout; opening and holding the existing lock is the only operation on the old worktree.
- Allocate the timestamp and new staging worktree path. Acquire the installed worktree's existing
.agents/merge.lock, repeat every precondition, and keep it through preparation, validation, and the current cutover. If staging moves while waiting, unlock and restart with a new timestamp; remove only attempt artifacts that this run created and verified as disposable.
- In the main clone, create
refs/dsh-upgrade/recovery-<timestamp> at the recorded old staging tip and dsh-upgrade/prepare-<timestamp> from that tip. Fetch exact authoritative upstream master into refs/dsh-upgrade/upstream-<timestamp> and record its object ID. Add a fresh worktree <source>/staging-<timestamp> checked out on the preparation branch. Confirm the main clone's .git/info/exclude excludes .agents/merge.lock, which the new worktree inherits.
- Inspect the Git log and commit ranges between the staging base, old staging tip, and fetched upstream tip. Identify incoming upstream changes, personal commits to preserve, likely duplicates, and conflict-prone areas before rebasing.
- In the new worktree, rebase the preparation branch onto the fetched upstream commit. Preserve intentional customizations and drop behavior already upstream. If upstream contains the customization and its remaining local diff only documents that customization, prefer upstream and drop the documentary diff rather than retaining a stale local account. Preserve documentation only when it adds a current, independently useful contract absent upstream. Abort without changing the installed launcher when resolution is uncertain.
- Install dependencies in the new worktree, review the resulting diff, and run the repository-required checks. Fix failures and rerun affected checks. Test the new worktree's
bin/dsh directly.
- Point
dsh-staging/<timestamp> at the validated prepared tip and check it out in the new worktree. Ensure its exists (Git-excluded through the shared main-clone exclude). Verify its branch, exact commit, clean status, remotes, dependencies, and absence of in-progress Git operations, then smoke its from a clean temporary workspace. The preparation branch remains temporary; the timestamped staging branch owns the installed commit.
The installed launcher always resolves through current to a staging worktree, never the main clone. Upgrade preparation adds a new worktree that shares the main clone's object store while leaving the old worktree's checkout untouched; cutover is one atomic current repoint to the separately validated timestamped staging worktree, and the PATH launcher never moves.
Recommend upstream candidates
After a successful upgrade, load dsh-upstream-customization and classify each remaining personal customization by its rules. For each candidate, explain its classification and upstream value and recommend whether to propose it, then ask which named candidate, if any, the user wants to upstream. The answer selects a candidate to start that skill's publication workflow; it is not publishing approval, which that workflow still requires.