public/New-WtwWorktree.ps1

function New-WtwWorktree {
    <#
    .SYNOPSIS
        Create a new git worktree with workspace and color assignment.
    .DESCRIPTION
        Creates a git worktree for the given task, generates a VS Code workspace
        file from the repo template, assigns a unique color, and registers it
        in the wtw registry.
    .PARAMETER Task
        Branch or task name for the new worktree.
    .PARAMETER Branch
        Branch to use. Without -NoBranch this overrides the *new* branch name
        (defaults to the task name). With -NoBranch this is the *existing* ref
        to adopt: a local branch name (e.g. `my-feature`) or a remote-tracking
        ref (e.g. `origin/my-feature`). Defaults to the task name when omitted.
    .PARAMETER Repo
        Target repo alias if not auto-detected from cwd.
    .PARAMETER Open
        Open the workspace in the configured editor after creation.
    .PARAMETER NoBranch
        Adopt an existing branch instead of creating a new one. The ref is
        taken from -Branch (or the task name when -Branch is omitted). If the
        ref is a remote-tracking branch (e.g. `origin/foo`), a local tracking
        branch is created automatically — the worktree is never detached.
        All the usual color/workspace/registry/cmux/Superset/SourceGit logic
        runs after, identical to the new-branch path.

        In most cases you don't need this switch: when -Branch points at a ref
        that already exists, adoption is inferred automatically. Use -NoBranch
        (or its alias -Adopt) only when you want to adopt with the task name
        as the ref (no explicit -Branch).
    .PARAMETER Adopt
        Friendlier alias for -NoBranch. "Adopt an existing branch." Same
        behavior; pick whichever name reads better at the call site.
    .PARAMETER PrettyName
        Human-readable display name stored in the registry and used as the
        Superset workspace name (e.g. "035 Context Building 🔵").
    .PARAMETER FolderName
        Override the worktree folder suffix (default: $Task).
        Final folder is "${repoName}_${FolderName}". Useful for long branch
        names: --folder p2 → snowmain1_p2.
    .PARAMETER Color
        Color assignment for the new workspace. Accepts:
          - 'random' (default when omitted): max-contrast pick from the palette
          - '#rrggbb' or 'rrggbb': literal hex
          - color name: looked up in the bundled colornames table (case-insensitive,
            spaces/hyphens ignored) — e.g. 'forest green', 'navy', 'sunset orange'.
    .PARAMETER From
        Start-point for the new branch. Any git ref reachable from the main
        repo: a branch name (local or `origin/<name>`), tag, or commit SHA.
        Special value `current` resolves to the branch checked out in the
        terminal's current directory (typically a worktree's branch) — use
        it to stack the new branch on top of in-flight work without having
        to type the long branch name.
        When omitted, git uses the main repo's current HEAD (typically the
        default branch). Ignored when -NoBranch is set (you're attaching
        to an existing branch).
    .PARAMETER GtTrack
        After creating the worktree, run `gt track` inside it to register
        the new branch with the Graphite CLI. When -From is also set, the
        parent is passed explicitly via --parent so the stack edge is
        recorded immediately. Requires `gt` on PATH; fails fast
        otherwise. If `gt track` errors with a stale trunk reference, the
        worktree is left intact and a remediation hint is printed
        (`gt init` + `gt repo sync`).
    .EXAMPLE
        wtw create auth
        Create a worktree and branch named "auth" for the current repo.
    .EXAMPLE
        wtw create "my feature name"
        Normalizes to my_feature_name for branch, folder, and registry key.
    .EXAMPLE
        wtw create 035-context-building-function --name "035 Context Building 🔵"
        Creates the worktree and registers a pretty name used for Superset.
    .EXAMPLE
        wtw create initiative-016-home --from MS-phase-5-swim-polish
        Stack initiative-016-home on top of MS-phase-5-swim-polish so the
        new branch + worktree start from that branch's tip (Graphite-style
        stack base).
    .EXAMPLE
        wtw create initiative-016-home --from current --gt-track
        Same as above but `current` resolves to the cwd's branch, and the
        new branch is also registered with Graphite (`gt branch track
        --parent <resolved-from>`) so `gt submit --stack` opens the PR
        with the right base immediately.
    .EXAMPLE
        wtw create my-feature --branch my-feature
        Adopt the existing local branch `my-feature`. Adoption is inferred
        automatically because the ref already exists — no --adopt needed.
    .EXAMPLE
        wtw create my-feature --branch origin/my-feature
        Adopt a remote branch. A local tracking branch is created from
        `origin/my-feature` so the worktree is on a real branch, not
        detached HEAD. Adoption is again inferred (the ref resolves).
    .EXAMPLE
        wtw create my-feature --adopt
        Adopt an existing local branch where the branch name equals the
        task name. Equivalent to `--branch my-feature` here, or to the
        legacy `--no-branch`.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position = 0)]
        [string] $Task,

        [string] $Branch,
        [string] $Repo,
        [string] $PrettyName,
        [string] $FolderName,
        [string] $Color,
        [string] $From,
        [switch] $Open,
        [switch] $NoBranch,
        [switch] $Adopt,
        [switch] $GtTrack
    )

    # --adopt is a friendlier alias for --no-branch (both opt into "adopt
    # an existing branch instead of creating one"). Collapse to a single
    # internal flag so downstream logic stays simple.
    if ($Adopt) { $NoBranch = $true }

    # Track whether the user explicitly passed --branch — used below to
    # decide if we should implicitly adopt when the ref already exists.
    $branchExplicit = -not [string]::IsNullOrWhiteSpace($Branch)

    $rawTask = $Task
    $Task = ConvertTo-WtwBranchSafeName -Name $Task
    if ([string]::IsNullOrWhiteSpace($Task)) {
        Write-Error "Task name is empty or invalid after normalization (input: '$rawTask')."
        return
    }
    if ($rawTask -ne $Task) {
        Write-Host " Normalized task/branch: $Task" -ForegroundColor DarkCyan
        Write-Host " (from: $rawTask)" -ForegroundColor DarkGray
    }

    $repoName, $repoEntry = Resolve-WtwRepo -RepoAlias $Repo
    if (-not $repoName) { return }

    if ($repoEntry.worktrees.PSObject.Properties.Name -contains $Task) {
        Write-Error "Worktree '$Task' already exists for $repoName. Use 'wtw go $Task' or 'wtw remove $Task' first."
        return
    }

    # Folder suffix (default: $Task). Normalized so spaces/casing don't sneak into paths.
    $folderSuffix = if ($FolderName) { ConvertTo-WtwBranchSafeName -Name $FolderName } else { $Task }
    # Reject a normalized empty suffix early — otherwise we'd build a path
    # like `${repoName}_` and silently collide with anything that already
    # uses the bare repo prefix.
    if ($FolderName -and [string]::IsNullOrWhiteSpace($folderSuffix)) {
        Write-Error "Folder name is empty or invalid after normalization (input: '$FolderName')."
        return
    }
    if ($FolderName -and $folderSuffix -ne $FolderName) {
        Write-Host " Normalized folder name: $folderSuffix" -ForegroundColor DarkCyan
        Write-Host " (from: $FolderName)" -ForegroundColor DarkGray
    }
    $worktreePath = Join-Path $repoEntry.worktreeParent "${repoName}_${folderSuffix}"

    if (Test-Path $worktreePath) {
        Write-Error "Path already exists: $worktreePath"
        return
    }

    if (-not $Branch) { $Branch = $Task }

    # Create git worktree
    Write-Host " Creating worktree..." -ForegroundColor Cyan
    $mainRepo = $repoEntry.mainPath

    # Resolve -From upfront so we fail fast with a clear message rather
    # than letting `git worktree add` complain mid-flight.
    if ($From) {
        if ($NoBranch) {
            Write-Error "--from is only meaningful when creating a new branch; drop -NoBranch or omit --from."
            return
        }
        # Special value `current` → resolve against the terminal's cwd.
        # Anywhere inside a worktree of this repo returns that worktree's
        # branch; from the main repo dir it returns the main checkout's
        # branch. Detached HEAD is rejected.
        if ($From -eq 'current') {
            $cwdBranch = git -C (Get-Location).Path rev-parse --abbrev-ref HEAD 2>&1
            if ($LASTEXITCODE -ne 0) {
                Write-Error "--from current: cwd is not inside a git worktree ($cwdBranch)."
                return
            }
            $cwdBranch = "$cwdBranch".Trim()
            if ($cwdBranch -eq 'HEAD') {
                Write-Error "--from current: cwd is on a detached HEAD; check out a branch first or pass an explicit ref."
                return
            }
            Write-Host " --from current → '$cwdBranch'" -ForegroundColor DarkGray
            $From = $cwdBranch
        }
        $resolved = git -C $mainRepo rev-parse --verify "$From^{commit}" 2>&1
        if ($LASTEXITCODE -ne 0) {
            Write-Error "--from '$From' is not a valid ref in $repoName : $resolved"
            return
        }
        Write-Host " Stack base: $From ($($resolved.Substring(0, 12)))" -ForegroundColor Cyan
    }

    # Fail fast if -GtTrack is set but `gt` isn't on PATH — otherwise the
    # worktree gets created and the tracking step silently no-ops.
    if ($GtTrack) {
        if (-not (Get-Command gt -ErrorAction SilentlyContinue)) {
            Write-Error "--gt-track requires the Graphite CLI on PATH (https://graphite.dev/docs/install)."
            return
        }
    }

    # Implicit-adopt: when the user passed --branch explicitly AND the ref
    # already points at an existing branch (local or remote-tracking), we
    # adopt it rather than trying to create a new branch with the same
    # name (which would just fail with "branch already exists" from git).
    # This lets `wtw create my-feature --branch origin/my-feature` Just
    # Work — no extra --adopt/--no-branch needed.
    if (-not $NoBranch -and -not $From -and $branchExplicit) {
        git -C $mainRepo show-ref --verify --quiet "refs/heads/$Branch" 2>$null
        $branchExists = ($LASTEXITCODE -eq 0)
        if (-not $branchExists -and $Branch -match '^[^/]+/.+') {
            # Looks remote-ish (e.g. origin/foo) — verify it resolves.
            git -C $mainRepo rev-parse --verify "$Branch^{commit}" 2>&1 | Out-Null
            $branchExists = ($LASTEXITCODE -eq 0)
        }
        if ($branchExists) {
            Write-Host " --branch '$Branch' refers to an existing ref; adopting (implies --adopt)." -ForegroundColor DarkCyan
            $NoBranch = $true
        }
    }

    if ($NoBranch) {
        # Adopt an existing branch. The ref in $Branch can be:
        # - a local branch → attach directly
        # - a remote-tracking ref → create a local tracking branch so the
        # worktree stays on a real branch, not
        # detached HEAD
        # Validate first so we fail fast with a clear message instead of
        # letting git emit a cryptic worktree-add error mid-flight.
        $refResolved = git -C $mainRepo rev-parse --verify "$Branch^{commit}" 2>&1
        if ($LASTEXITCODE -ne 0) {
            Write-Error "--no-branch: ref '$Branch' not found in $repoName. Try 'git fetch' first, or pass --branch <existing-branch> / --branch origin/<name>."
            return
        }

        git -C $mainRepo show-ref --verify --quiet "refs/heads/$Branch" 2>$null
        $isLocalBranch = ($LASTEXITCODE -eq 0)

        if ($isLocalBranch) {
            Write-Host " Adopting local branch: $Branch" -ForegroundColor Cyan
            $result = git -C $mainRepo worktree add $worktreePath $Branch 2>&1
        } else {
            # Treat as a remote-tracking ref (e.g. origin/foo). Derive the
            # local branch name by stripping the remote prefix — same DWIM
            # rule `git checkout --track origin/foo` uses.
            if ($Branch -notmatch '^[^/]+/.+') {
                Write-Error "--no-branch: ref '$Branch' resolves to a commit but is neither a local branch nor a remote-tracking ref. Use --from <ref> to start a new branch at a SHA/tag, or check out the branch first."
                return
            }
            $localName = $Branch -replace '^[^/]+/', ''
            git -C $mainRepo show-ref --verify --quiet "refs/heads/$localName" 2>$null
            if ($LASTEXITCODE -eq 0) {
                Write-Error "--no-branch: cannot create local branch '$localName' tracking '$Branch' — a local branch named '$localName' already exists. Run 'wtw create $Task --no-branch --branch $localName' to adopt the local one instead."
                return
            }
            Write-Host " Adopting remote branch: $Branch → local '$localName' (tracking)" -ForegroundColor Cyan
            $result = git -C $mainRepo worktree add -b $localName --track $worktreePath $Branch 2>&1
            if ($LASTEXITCODE -eq 0) { $Branch = $localName }
        }
    } elseif ($From) {
        $result = git -C $mainRepo worktree add -b $Branch $worktreePath $From 2>&1
    } else {
        $result = git -C $mainRepo worktree add -b $Branch $worktreePath 2>&1
    }

    if ($LASTEXITCODE -ne 0) {
        Write-Error "git worktree add failed: $result"
        return
    }

    Write-Host " Worktree: $worktreePath" -ForegroundColor Green
    Write-Host " Branch: $Branch" -ForegroundColor Green

    # All post-worktree-add setup (color, pretty name, workspace file, registry,
    # Superset/Codex/cmux/wmux/SourceGit/agentctl) is shared with `wtw add`.
    $meta = Initialize-WtwWorktreeMetadata `
        -RepoName $repoName -RepoEntry $repoEntry `
        -Task $Task -Branch $Branch `
        -WorktreePath $worktreePath -FolderSuffix $folderSuffix `
        -PrettyName $PrettyName -Color $Color

    if (-not $meta.Success) {
        # The only failure mode today is a bad --color. We just created the
        # worktree, so unwind it — `wtw add` doesn't do this because it
        # adopts a pre-existing on-disk worktree it doesn't own.
        git -C $mainRepo worktree remove $worktreePath --force 2>$null | Out-Null
        return
    }

    # Optionally register the new branch with Graphite. Done inside the
    # new worktree (gt operates on the cwd's branch) and passes --parent
    # when -From is set so the stack edge is recorded immediately.
    #
    # Note on command name: `gt branch track` was renamed to `gt track`
    # in current Graphite. The legacy name still works but prints a
    # deprecation warning that drowns the real output. Use the new name.
    if ($GtTrack) {
        Write-Host " Tracking with Graphite..." -ForegroundColor Cyan
        Push-Location -LiteralPath $worktreePath
        try {
            $gtArgs = @('track')
            if ($From) { $gtArgs += @('--parent', $From) }
            $gtOut = & gt @gtArgs 2>&1
            if ($LASTEXITCODE -eq 0) {
                $parentLabel = if ($From) { " (parent: $From)" } else { '' }
                Write-Host " Graphite: tracked${parentLabel}" -ForegroundColor Green
            } else {
                Write-Warning " gt track failed (exit $LASTEXITCODE): $gtOut"
                # The most common cause is a stale/missing trunk
                # configuration — point the user at the fix.
                if ("$gtOut" -match 'bad revision|trunk|init') {
                    Write-Host " Likely cause: Graphite trunk is unset or stale. From the main repo:" -ForegroundColor DarkGray
                    Write-Host " gt init # interactive: pick trunk (usually 'main')" -ForegroundColor DarkGray
                    Write-Host " gt repo sync # refresh remote state" -ForegroundColor DarkGray
                    Write-Host " Then inside ${worktreePath}:" -ForegroundColor DarkGray
                    $reTry = if ($From) { " gt track --parent $From" } else { ' gt track' }
                    Write-Host $reTry -ForegroundColor DarkGray
                } else {
                    Write-Host " Worktree is fine — re-run 'gt track' manually inside $worktreePath." -ForegroundColor DarkGray
                }
            }
        } finally {
            Pop-Location
        }
    }

    if ($Open) {
        Open-WtwWorkspace -Name $Task -Repo $repoName
    }

    Write-Host ''
    Write-Host " Done! Use 'wtw go $Task' to switch." -ForegroundColor Green
}