The comments and Agent Note blamed macOS resolving /var through
private/var. That is only how the mismatch surfaced in temp fixtures, since
mktemp hands back a /var path there.
The real condition is broader: Git always reports resolved paths, so
comparing one against an unresolved path disagrees whenever a symlink sits
anywhere above the checkout. A symlinked home directory alone triggers it —
reproduced with no /var involved — which is common wherever homes live
behind a symlink or on a network mount.
Naming the cause correctly keeps a reader from dismissing resolve_dir as
macOS-only defensiveness.
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`.
DSH_SOURCE is an install-time shell variable the installer never exports, so
a skill reading ${DSH_SOURCE} at runtime reads nothing. Verified unset in a
running dsh process.
Git resolves the main clone identically for every install, so the curl-vs-
adopted distinction was never a branch point in these workflows. Verified
one launcher-then-Git recipe against three shapes: a curl install cloning
into the container, an adopted clone nested far outside any container, and
a custom DSH_SOURCE container.
dsh-customize now states that single procedure and warns off the installer
variables. dsh-upgrade's Layout describes what the resolution finds rather
than a path convention, and no longer teaches install shapes as cases.
Both skills said to derive the main clone from the checkout without saying
how, and dsh-upgrade names dsh-customize as the owner of checkout discovery
— so the technique belonged there and was missing.
dsh-customize now gives it: `git rev-parse --git-common-dir` from the
checkout yields the shared git directory, whose parent is the main clone.
It also names the two ways to get this wrong — the answer is relative for a
plain clone, and paths must be compared physically, since macOS reaches
/var through a symlink to /private/var.
dsh-upgrade links to that procedure rather than restating it.
Verified against both shapes: an adopted clone outside the container, and a
curl-shaped install whose clone is at <source>/master.
"Master clone" named the repository after a branch it need not be on. An
adopted clone keeps whatever branch it had — verified: adopting a clone
checked out on a feature branch leaves it there — so the name was wrong for
every install that did not come from curl.
Renamed to "main clone" in dsh-upgrade and dsh-customize, describing its
actual role: the one real clone whose object store every worktree shares.
dsh-upgrade also now says not to assume the main clone sits on `master` or
that its `origin` is authoritative upstream, since an adopted clone may
point at a fork. The fetch itself was already correct: step 1 resolves
authoritative upstream separately, and step 4 fetches upstream `master`
from it rather than from the clone's own branch.
Adoption installs the master wherever the adopted clone already is, so
dsh-upgrade and dsh-customize can no longer state <source>/master as fact.
Both skills already derive the master from the launcher, so the procedures
hold; only the layout description was wrong. dsh-upgrade now names
`git rev-parse --git-common-dir` as the way to resolve it.
The legacy-migration clauses stay: installs made before this change can
still link PATH straight at a worktree.
The native adapter's route was named deepseek, colliding with pi-ai's
catalog provider of the same name, so the two DeepSeek paths could never
be mounted side by side. The web settings page needs both configurable at
once. Compositions, fixtures, goldens, scaffolding defaults, and docs all
move together (pre-release, no shim); TUI/session-query-spill/
missing-credential goldens re-recorded through their keyless refresh
modes because provider-name length shifts box padding and spill
truncation points.
Add a headless create path: --config <file> / --config-json <json> supply a
structured project spec (answers + feature plan) that drives CreateWizard through
a HeadlessPromptPort, bypassing the TTY. --json emits NDJSON lifecycle events
(done / action-required / error) so an agent can fill a missing input and re-run.
Ship a thin SKILL.md playbook for agent-driven creation. Per-file 100% coverage.