ADR 0103: Setup grounds itself in the repo's real state — detected default branch, git-init-first without git, a welcome everywhere
Amendments.
- Vocabulary: the retired shared-branch label now reads the trunk; the decision and reasoning are unchanged.
- ADR 0153 — configuration: setup now stamps the detected branch into
[repository].trunk; references below to[project].main_branchpreserve the original schema spelling.
Status: accepted; builds on ADR 0086 (the served relay messages this makes honest), ADR 0081 (the landing command whose dead ends this closes), and ADR 0100 (the single consent question whose promises this conditions)
Context
§Setup was written against a pristine repo — clean main, git present, identity configured — and the target user's repo is rarely that. Three of the resulting soft-degradations were structural, not cosmetic:
main_branchwas hardcoded to"main"at scaffold. On amasterrepo the gate's behind-main merge check found no localmainand silently self-skipped forever — a protection the user believes is armed — andsetup acceptdead-ended on a branch that never existed.- A non-git directory soft-degraded.
verifyfiled a "Considergit init" advisory while the served consent message unconditionally promised the isolateddiscern-setupbranch and worktree isolation — promises that are false without git, relayed verbatim to the human. ADR 0086 made the served messages load-bearing, which makes their honesty load-bearing too. - Bare
discernoutside a git work tree printed raw CLI help. The restriction was documented as deliberate ("to avoid touching a stray dir"), but the welcome writes nothing, so there was nothing to avoid — and the users most likely to lack git are exactly the novices the curated first contact exists for.
A constraint surfaced during implementation forced a deviation from the drafted detection order (origin/HEAD → init.defaultBranch → current branch): vendor git builds bake a default into init.defaultBranch at a scope no environment variable masks — Apple's git ships init.defaultBranch = main in Xcode's built-in gitconfig. Consulting it ahead of the checked-out branch would stamp main on every macOS master repo: the exact bug the detection exists to fix.
Decision
§begindetects the repo's real trunk and stamps it into the fresh config's[project].main_branch, in this order: the remote's declared default (git symbolic-ref refs/remotes/origin/HEAD), then the branch checked out when setup started (read before thediscern-setupcheckout; an unborn branch still names itself), theninit.defaultBranchas a detached-HEAD tiebreaker only. The checked-out branch outranksinit.defaultBranchdeliberately — it is the repo's ground truth, while the config key is vendor-polluted (see Context) and, for unborn repos, already reflected in the unborn branch's name. Declarative--configfills apply after the stamp, so an explicit choice still wins.setup accepton a missing trunk serves the exact creation-then-land step (git branch <target> && discern setup accept) in the refusal message itself, on both surfaces — a brand-new repo's first commits are born ondiscern-setup, so the unborn target is a normal state, not a dead end. Land does not create the branch itself: a missing target can also be a misconfigured[project].main_branch, and silently creating a branch named by a typo is worse than one served command.- The non-git posture is git-init-first. The consent context carries the git state, and every served promise conditions on it: without git the plan leads with
git init(offered as its own consent point inside the fenced message), the unconditional branch-isolation sentence is replaced by its conditioned form, and the served next action isgit initthen re-runningverify— never straight tobegin. Setup does not require git:beginstill proceeds in place if invoked without it, but no surface promises isolation it can't deliver. - Bare
discernwelcomes everywhere outside a set-up project, including non-git directories, overturning the documented restriction. The welcome grounds itself in the git state and leads the non-git case with thegit initstep; explicit help remains one--helpaway.
Consequences
§- On
master,trunk, and unborn-default repos, the merge check is armed from first setup andsetup acceptcompletes — the silent-self-skip class is closed at its source (the stamped config) rather than patched per consumer. - A repo whose
origin/HEADdisagrees with the local checkout follows the remote — the right call for clones, and the one case where the local branch is not ground truth. - Setup run from a feature branch with no remote stamps that feature branch — the best available guess, and a visible, comment-adjacent config value the user can correct, where the old behaviour was an invisible wrong default.
- The consent surface now has two more grounded inputs (git presence, identity presence) whose combinations the parity tests must cover; the served-message fixtures grow accordingly.
- A stray
discernin a random directory now shows a welcome rather than help. Accepted: the welcome writes nothing, names what discern is, and help remains reachable — the trade favours the novice over the operator, which is what a bare first contact is for.
Alternatives considered
§- The drafted detection order (
init.defaultBranchbefore the current branch). Rejected on evidence: Apple's git bakesinit.defaultBranch = maininto an unmaskable built-in config, so the drafted order re-introduces themaster-repo bug on every macOS machine. The user-preference reading ofinit.defaultBranchonly adds signal when no branch is checked out. setup acceptauto-creates an unborn target. Rejected: indistinguishable from a typo'd[project].main_branch, where auto-creation plants a wrong-named branch silently. The refusal serves the exact command instead.- Requiring git (refuse
beginwithout it). Rejected: git is not a hard requirement of an install (ADR 0081's landing summary already handles the no-repo case honestly); the failure was dishonest messaging, not the in-place path itself. - Keeping the bare-help restriction and printing a one-line hint. Rejected: a hint is advice, and the audience is the one least equipped to follow it — the full welcome is the designed first contact and costs nothing to show.