These skills resolve the layout from the PATH launcher and Git, so how the checkout was installed never enters the procedure. Describing install shapes, the installer script, and its variables added detail a reader must hold and would go stale whenever the installer changes. Both skills now describe the observable state they resolve. The cases that mattered survive as properties of that state: the main clone may sit anywhere on any branch, and a launcher may link straight at a worktree with no `current`.
9.7 KiB
name, description
| name | description |
|---|---|
| dsh-upgrade | 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
currenttarget 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.lockto 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 thecurrentcutover. 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 anddsh-upgrade/prepare-<timestamp>from that tip. Fetch exact authoritative upstreammasterintorefs/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/excludeexcludes.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/dshdirectly. - Point
dsh-staging/<timestamp>at the validated prepared tip and check it out in the new worktree. Ensure its.agents/merge.lockexists (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 itsbin/dshfrom a clean temporary workspace. The preparation branch remains temporary; the timestamped staging branch owns the installed commit. - Recheck the old worktree, existing lock, launcher,
current, main clone, new worktree, refs, and exact tips. Recordcurrent's pre-cutover target, then repointcurrentat the new staging worktree in one atomic swap withln -sfn(the-nstopslnfrom dereferencing the existing directory symlink and writing the link inside the old worktree;mvbehaves the same way and is unusable). Leave the PATH launcher alone once it already resolves throughcurrent; if a legacy install still links PATH straight at a worktree, createcurrentand repoint PATH tocurrent/bin/dshas a one-time migration here. Thecurrenttarget must be a clean staging worktree on a staging branch and must never be the main clone or a preparation, feature, review, publication, or detached checkout. Smoke the installeddshcommand from a clean temporary workspace. - On failure before the
currentcutover, leavecurrent, the launcher, and the old worktree unchanged and remove only verified attempt artifacts created by this run (including the new worktree registration if empty). On failure during or after cutover, inspectcurrent's observed target before acting; if cutover did not verify, atomically repointcurrentback to its recorded pre-cutover target withln -sfnand verify thatdshstarts from the unchanged old staging worktree. This rollback is the sole exception allowingcurrentto return to the old staging worktree. Never retry a side-effecting operation blindly. - Release the old worktree's lock and tell the user to restart DSH through the installed launcher. The current process may continue from the old worktree, but no operation may mutate or remove it until the restarted process proves that it runs from
dsh-staging/<timestamp>and the user confirms stability. Avoid customization integration during this confirmation window; if rollback is required after new work lands, reconcile that work explicitly rather than silently stranding it. - After confirmation, remove the preparation branch if no process uses it. Keep the old staging worktree and branch, the recovery ref, and the recorded pre-cutover
currenttarget as rollback until the user explicitly approves their removal; leave the actualgit worktree removeand directory deletion to the user. Report old, upstream, prepared, and new staging commits; both staging worktree paths and branches; the main clone path; process-source evidence; thecurrentpre-cutover target and cutover; commands and checks; final status; recovery ref; and retained rollback artifacts.
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.