repo-setup · diff

git:20260628.87646a5 to git:20260926.30c4426

29 added, 15 removed. Audit A to A.

---
name: repo-setup
- description: Use at the start of /implement's repo path to land in a clean working state — fetch, fast-forward main, prune merged branches/worktrees/plan-files, then create the feature branch + worktree off fresh main. Repo-only; project-overridable for deep provisioning.
+ description: Use at the start of /implement's repo path to land in a clean working state — fetch, fast-forward main, prune merged branches/worktrees/plan-files, then create the feature branch + worktree off fresh main and switch the session into it. Repo-only; project-overridable for deep provisioning.
---
# Repo Setup
- First step of `/implement`'s repo path. Takes the repo from whatever state it's in to a fresh feature branch + worktree cut off an up-to-date `main`, clearing the debris of previously-merged work on the way. Assumes a git repo — the recipe's repo-vs-vault context branch runs before this.
+ First step of `/implement`'s repo path. Takes the repo from whatever state it's in to a fresh feature branch + worktree cut off an up-to-date `main` — with the session standing in that worktree — clearing the debris of previously-merged work on the way. Assumes a git repo — the recipe's repo-vs-vault context branch runs before this.
## Input
- **branch** — the feature branch to create, as `<type>/<desc>` (`feat/`, `fix/`, `chore/`, `story/S-<id>-<desc>`). The recipe derives it from the ticket. A bare description is acceptable — default the type to `feat/`.
- (implicit) the current repo and its `origin` remote.
## Steps
+ 0. **Start from the main checkout.** Steps 1–6 resolve relative paths and "current branch" against the main checkout, so get there first — a session may still be standing in a previous run's worktree (a second `/implement` in the same session), have been launched inside one, or sit in a subdirectory.
+ - **Resolve the root:** `git worktree list --porcelain | sed -n '1s/^worktree //p'` — the first entry is the main checkout, call it `<root>`. If that entry is followed by a `bare` line, the repo is a bare-repo layout with no main working tree: stop — that layout needs a project override.
+ - **Record where you started:** the starting worktree (`git rev-parse --show-toplevel`) and its branch (`git branch --show-current`), before moving. Step 4 must not prune them — they may be a worktree the user owns and the session was launched in.
+ - **Move:** if `pwd -P` isn't `<root>`, first try `ExitWorktree(action: "keep")` (load via `ToolSearch` `select:ExitWorktree` if deferred; never `remove` — that's a prior branch's work). Don't rely on remembering whether you entered one — after `/clear` or compaction you may not; the tool reports when no worktree session is active. Then, if `pwd -P` still isn't `<root>`, `cd <root>` — and re-check `pwd -P`: the harness may reset a `cd` that leaves the launch directory, so if it didn't take, stop and surface it. No-op on a normal first run. Steps 4–6 anchor their file paths to `<root>` anyway, so they hold even if cwd drifts.
1. **Guard a dirty tree.** If `git status --porcelain` is non-empty, stop and surface the changes — don't fetch/prune over dirty state. (A project override may stash-and-restore instead.)
2. **Fetch.** `git fetch --prune origin` — updates remote-tracking refs and drops refs for branches deleted on the remote.
3. **Fast-forward `main`.** Bring local `main` to `origin/main`, never a merge commit:
- on `main` → `git merge --ff-only origin/main`
- not on `main` → `git fetch origin main:main` (fast-forward-only update of the local ref; refuses if it would diverge).
- 4. **Prune merged work.** Enumerate merged branches with `git branch --merged origin/main`, add the squash-merged ones (see caveat), and for each — excluding `main` and the current branch:
- - Remove its worktree if present: `git worktree remove .worktrees/<branch>` (`--force` only if an override opts in).
- - Delete its plan file: `rm -f .claude/plans/<branch>.md` using the `/`→`-` flattened branch name (`feat/foo` → `.claude/plans/feat-foo.md`, matching `plan-file`); gitignored, per-branch, safe.
+ 4. **Prune merged work.** Enumerate merged branches with `git branch --merged origin/main`, add the squash-merged ones (see caveat), and for each — excluding `main`, the current branch, and the starting branch/worktree recorded in step 0:
+ - Remove its worktree if present: `git worktree remove <root>/.worktrees/<branch>` (`--force` only if an override opts in).
+ - Delete its plan file: `rm -f <root>/.claude/plans/<branch>.md` using the `/`→`-` flattened branch name (`feat/foo` → `.claude/plans/feat-foo.md`, matching `plan-file`); gitignored, per-branch, safe.
- Delete the branch: `git branch -d <branch>` for merge-commit / fast-forward merges; `git branch -D <branch>` for squash-merged branches (`-d` refuses them — `-D` is safe because the caveat's check already confirmed the content landed).
Then `git worktree prune` to clear stale administrative entries.
**Squash-merge caveat.** `git branch --merged origin/main` catches merge-commit and fast-forward merges but **not squash-merges** — and `/implement` squashes by default, so a just-merged branch won't show as merged (and `git branch -d` would refuse to delete it). Detect those separately, in order of reliability:
- `gh pr list --state merged --head <branch>` reports it merged — robust, but needs network + auth.
- `git diff origin/main..<branch>` (two-dot — compares the tip *trees*) is empty when the branch's change already landed and `main` hasn't moved since. Network-free fallback; a `main` that has advanced since the squash defeats it.
Delete the matches with `git branch -D`. Without `gh` auth and with an advanced `main`, a squash-merged branch may survive the prune — acceptable; the next clean run catches it.
- 5. **Create the feature branch + worktree off fresh main.** `git worktree add .worktrees/<branch> -b <branch> origin/main` — one step: branch cut from up-to-date `main`, checked out in an isolated worktree.
- 6. **Ensure ignores.** From the main checkout, make sure `.worktrees/` and `.claude/plans/` are in the repo's `.gitignore` — append each if missing, no-op if present — so the `.worktrees/` dir never shows as untracked here:
+ 5. **Create the feature branch + worktree off fresh main.** `git worktree add <root>/.worktrees/<branch> -b <branch> origin/main` — one step: branch cut from up-to-date `main`, checked out in an isolated worktree.
+ 6. **Ensure ignores.** Make sure `.worktrees/` and `.claude/plans/` are in the main checkout's `<root>/.gitignore` — append each if missing, no-op if present — so the `.worktrees/` dir never shows as untracked here:
```
- [ -s .gitignore ] && [ -n "$(tail -c1 .gitignore)" ] && printf '\n' >> .gitignore
- for p in '.worktrees/' '.claude/plans/'; do grep -qxF "$p" .gitignore 2>/dev/null || printf '%s\n' "$p" >> .gitignore; done
+ [ -s "<root>/.gitignore" ] && [ -n "$(tail -c1 "<root>/.gitignore")" ] && printf '\n' >> "<root>/.gitignore"
+ for p in '.worktrees/' '.claude/plans/'; do grep -qxF "$p" "<root>/.gitignore" 2>/dev/null || printf '%s\n' "$p" >> "<root>/.gitignore"; done
```
The leading guard appends a newline first if the file doesn't end in one, so an entry isn't glued onto the last line. Idempotent — a one-time addition per repo, committed via a normal branch/PR. (`.claude/plans/` is also ensured independently by `plan-file` in the worktree where plans are actually written — step 6 covers the root checkout; `plan-file` is the real plan-leak backstop.)
+ 7. **Enter the worktree.** Switch the session's working directory into it, using the absolute path `<root>/.worktrees/<branch>`:
+ - **Claude Code** → `EnterWorktree(path: "<root>/.worktrees/<branch>")` — the `path` form accepts a worktree made by `git worktree add`. Invoking `/implement` / `repo-setup` *is* the explicit instruction to work in a worktree that the tool's usage rule asks for; don't skip it on those grounds. If the tool is deferred, load it first (`ToolSearch` `select:EnterWorktree`).
+ - **Harness without an equivalent tool** → one persistent `cd <root>/.worktrees/<branch>`.
+ - **Main session only.** Run this step in the main session, not a subagent — a subagent's cwd is pinned, so the switch is rejected or affects only that agent. Subagents dispatched later get the worktree's absolute path in their prompt.
+ **Verify the switch:** `realpath "$(git rev-parse --show-toplevel)"` must equal `realpath "<root>/.worktrees/<branch>"` (canonicalize both — a symlinked path would otherwise mismatch) and `git branch --show-current` must print `<branch>`. If either doesn't match (or the tool call was rejected), **stop and surface it to the user** — don't fall back to prefixing commands.
+
+ From here on every command runs bare: **never prefix commands with `cd .worktrees/<branch> &&` or `git -C …`** — prefixed commands are harder to read and break hooks that match on the command's prefix or cwd.
+
+ **Re-entering later.** To resume an existing branch's worktree (e.g. addressing human review comments after `/implement` exited), don't re-run this skill — repo-setup only creates new branches. Re-enter with the same enter-and-verify as this step: `EnterWorktree(path: "<root>/.worktrees/<branch>")` (or `cd`), then the verify.
+
## Output / contract
- **In:** repo state + a branch name.
- - **Out:** the created branch name and its worktree path (`.worktrees/<branch>`), surfaced so `plan-file` and the later primitives operate inside the worktree.
- - **Side effects:** local `main` fast-forwarded; previously-merged branches + their worktrees + their plan files removed; new branch + worktree created; `.worktrees/` and `.claude/plans/` ensured in `.gitignore`. No pushes or other network *writes*; the optional squash-merge `gh pr list` check is a network *read* and needs auth.
+ - **Out:** the created branch name and its absolute worktree path (`<root>/.worktrees/<branch>`), with the session's working directory switched into it so `plan-file` and the later primitives operate inside the worktree.
+ - **Side effects:** local `main` fast-forwarded; previously-merged branches + their worktrees + their plan files removed; new branch + worktree created; `.worktrees/` and `.claude/plans/` ensured in `.gitignore`; session cwd moved out of any prior worktree (step 0) and into the new one (step 7). No pushes or other network *writes*; the optional squash-merge `gh pr list` check is a network *read* and needs auth.
## Project overrides
- This primitive stops at "branch + worktree exist." Deep, stack-specific provisioning is project-override territory, layered *after* the generic steps:
+ This primitive stops at "branch + worktree exist, session inside it." Deep, stack-specific provisioning is project-override territory, layered *after* the generic steps:
- **Port / secret / compose setup** — e.g. domainator's `setup-feature.sh` (slot-based ports, `.env` secret-gen, `compose up`).
- - **Worktree policy** — a repo that doesn't want worktrees overrides step 5 with a plain `git switch -c <branch> origin/main`.
+ - **Worktree policy** — a repo that doesn't want worktrees overrides step 5 with a plain `git switch -c <branch> origin/main` (and drops step 7).
- **Dirty-tree handling** — stash-and-restore instead of stop.
- - **Containerized verification seam** — when tests run via `podman/docker compose`, a bare worktree breaks two ways: the gitignored `.env` (and other secrets) won't exist in `.worktrees/<branch>`, so compose's `env_file` fails — symlink or copy them in; and compose must be run **from inside the worktree dir**, because bind-mounts are relative and running from the repo root silently exercises `main`, not your branch. Override `repo-setup` (and see `/implement`'s execute step) to set this up.
+ - **Containerized verification seam** — when tests run via `podman/docker compose`, a bare worktree breaks two ways: the gitignored `.env` (and other secrets) won't exist in `.worktrees/<branch>`, so compose's `env_file` fails — symlink or copy them in; and compose must be run **from inside the worktree dir** (step 7 puts the session there), because bind-mounts are relative and running from the repo root silently exercises `main`, not your branch. Override `repo-setup` (and see `/implement`'s execute step) to set this up.
- Overrides must honor the contract (same name, same "fresh branch + worktree off updated main" outcome) so the rest of `/implement` keeps working. (Step 6 already ensures `.worktrees/` and `.claude/plans/` are gitignored.)
+ Overrides must honor the contract (same name, same "fresh branch + worktree off updated main, session cwd inside it" outcome) so the rest of `/implement` keeps working. (Step 6 already ensures `.worktrees/` and `.claude/plans/` are gitignored.)
## Future scope
- Vault / non-repo path (deferred out of v1 — `/implement` is repo-shaped for now).
- Branch-name derivation from the ticket (currently the recipe's job, passed in).