Commit c1ccbd1a5643

Vincent Demeester <vincent@sbr.pm>
2026-08-05 13:16:15
feat(skills): vendor our own using-git-worktrees
The upstream superpowers skill defaults to project-local .worktrees/ and knows nothing about the single global worktree root or the native tools available here, so it kept sending agents to the wrong place. Replaced it with a local version and added a suppression step, since the skills CLI offers no per-skill exclude and would otherwise restore the upstream copy on every update.
1 parent 43a7980
Changed files (2)
dots
agents
skills
using-git-worktrees
dots/agents/skills/using-git-worktrees/SKILL.md
@@ -0,0 +1,168 @@
+---
+name: using-git-worktrees
+description: Use when starting feature work that needs isolation from current workspace or before executing implementation plans - ensures an isolated workspace exists under the single global worktree root
+---
+
+# Using Git Worktrees
+
+## Overview
+
+Ensure work happens in an isolated workspace.
+
+**Core principle:** Detect existing isolation first. Then use a native tool. Then
+fall back to raw git. Never fight the harness.
+
+**Every worktree lives under `~/.local/share/worktrees/`.** There is exactly one
+worktree root on this machine. Never create a worktree in `/tmp`, directly in
+`$HOME`, or next to the repository.
+
+**Announce at start:** "I'm using the using-git-worktrees skill to set up an isolated workspace."
+
+## Step 0: Detect Existing Isolation
+
+**Before creating anything, check if you are already in an isolated workspace.**
+
+```bash
+GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
+GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
+BRANCH=$(git branch --show-current)
+```
+
+**Submodule guard:** `GIT_DIR != GIT_COMMON` is also true inside git submodules.
+Before concluding "already in a worktree," verify you are not in a submodule:
+
+```bash
+# If this returns a path, you're in a submodule, not a worktree — treat as normal repo
+git rev-parse --show-superproject-working-tree 2>/dev/null
+```
+
+**If `GIT_DIR != GIT_COMMON` (and not a submodule):** You are already in a linked
+worktree. Skip to Step 2 (Project Setup). Do NOT create another worktree.
+
+Report with branch state:
+- On a branch: "Already in isolated workspace at `<path>` on branch `<name>`."
+- Detached HEAD: "Already in isolated workspace at `<path>` (detached HEAD, externally managed). Branch creation needed at finish time."
+
+**If `GIT_DIR == GIT_COMMON` (or in a submodule):** You are in a normal repo checkout.
+
+Ask for consent before creating a worktree, unless the user already asked for one:
+
+> "Would you like me to set up an isolated worktree? It protects your current branch from changes."
+
+If the user declines, work in place and skip to Step 2.
+
+## Step 1: Create the Worktree
+
+Use the first mechanism that is available.
+
+### 1a. `lazyworktree` / the `git_worktree` tool (preferred)
+
+Both place worktrees under `~/.local/share/worktrees/` automatically and handle
+branch creation and cleanup. If you have the `git_worktree` tool, use it and skip
+to Step 2.
+
+### 1b. `herdr worktree create`
+
+Use this when the worktree should come with its own herdr workspace and agent
+pane. **It requires an explicit `--path`** — without it herdr falls back to its
+own `~/.herdr/worktrees` root:
+
+```bash
+herdr worktree create --cwd "$REPO" --branch "$BRANCH" --base main \
+  --path ~/.local/share/worktrees/<owner>/<repo>/"$BRANCH"
+```
+
+Note: the `herdr` MCP tool's `worktree-create` action exposes no `path`
+parameter. If you only have the MCP tool, either accept its default root or shell
+out to the CLI above.
+
+### 1c. Raw git (last resort)
+
+```bash
+owner=<github-owner>   # fork owner for fork workflows, else the org
+repo=$(basename "$(git rev-parse --show-toplevel)")
+path=~/.local/share/worktrees/$owner/$repo/$BRANCH_NAME
+
+git worktree add "$path" -b "$BRANCH_NAME"
+cd "$path"
+```
+
+The global root needs no gitignore verification — it is outside every repository.
+
+**Sandbox fallback:** If `git worktree add` fails with a permission error, tell
+the user the sandbox blocked worktree creation and you're working in the current
+directory instead. Then run setup and baseline tests in place.
+
+## Step 2: Project Setup
+
+Auto-detect and run appropriate setup:
+
+```bash
+if [ -f package.json ]; then npm install; fi        # Node.js
+if [ -f Cargo.toml ]; then cargo build; fi          # Rust
+if [ -f pyproject.toml ]; then uv sync; fi          # Python (uv, not pip)
+if [ -f go.mod ]; then go mod download; fi          # Go
+if [ -f flake.nix ]; then direnv allow 2>/dev/null || true; fi
+```
+
+## Step 3: Verify Clean Baseline
+
+Run tests to ensure the workspace starts clean, using the project-appropriate
+command (`make test`, `go test ./...`, `cargo test`, `npm test`, `pytest`).
+
+**If tests fail:** Report failures, ask whether to proceed or investigate.
+**If tests pass:** Report ready.
+
+### Report
+
+```
+Worktree ready at <full-path>
+Tests passing (<N> tests, 0 failures)
+Ready to implement <feature-name>
+```
+
+## Quick Reference
+
+| Situation | Action |
+|-----------|--------|
+| Already in linked worktree | Skip creation (Step 0) |
+| In a submodule | Treat as normal repo (Step 0 guard) |
+| `git_worktree` tool / `lwt` available | Use it (Step 1a) |
+| Need a herdr workspace too | `herdr worktree create --path ...` (Step 1b) |
+| Nothing else available | Raw git with explicit path (Step 1c) |
+| Tempted by `/tmp`, `$HOME`, `.worktrees/` | Never — use the global root |
+| Permission error on create | Sandbox fallback, work in place |
+| Tests fail during baseline | Report failures + ask |
+
+## Common Mistakes
+
+### Fighting the harness
+- **Problem:** Using `git worktree add` when a native tool is available
+- **Fix:** Step 0 detects existing isolation; Step 1a defers to native tools
+
+### Forgetting `--path` with herdr
+- **Problem:** Worktree silently lands in `~/.herdr/worktrees`, invisible to `lwt`
+- **Fix:** Always pass `--path` under the global root
+
+### Skipping detection
+- **Problem:** Creating a nested worktree inside an existing one
+- **Fix:** Always run Step 0 before creating anything
+
+### Proceeding with failing tests
+- **Problem:** Can't distinguish new bugs from pre-existing issues
+- **Fix:** Report failures, get explicit permission to proceed
+
+## Red Flags
+
+**Never:**
+- Create a worktree when Step 0 detects existing isolation
+- Create a worktree outside `~/.local/share/worktrees/`
+- Use raw `git worktree add` when `git_worktree` / `lwt` is available
+- Skip baseline test verification
+- Proceed with failing tests without asking
+
+**Always:**
+- Run Step 0 detection first
+- Prefer native tools over the git fallback
+- Pass `--path` to `herdr worktree create`
+- Verify a clean test baseline
dots/Makefile
@@ -118,7 +118,28 @@ agent-skills-link:
 # Installed to ~/.agents/skills/ and auto-linked to Claude, Pi, etc.
 SKILL_PACKAGES := brainstorming=obra/superpowers make-interfaces-feel-better=jakubkrehel/make-interfaces-feel-better emacsclient=xenodium/emacs-skills
 
-skills-install: agent-skills-link
+# Upstream skills we deliberately replace with our own dots/agents/skills/<name>.
+# The `skills` CLI has no per-skill exclude, so we delete the upstream copy after
+# install/update and let agent-skills-link put ours in its place.
+# using-git-worktrees: this machine has a single worktree root
+# (~/.local/share/worktrees) plus native tools (lwt, herdr); the upstream skill
+# defaults to project-local .worktrees/ and knows about neither.
+SKILL_SUPPRESS := using-git-worktrees
+
+skills-suppress:
+	@for name in $(SKILL_SUPPRESS); do \
+		if [ -d "$(HOME)/.agents/skills/$$name" ] && [ ! -L "$(HOME)/.agents/skills/$$name" ]; then \
+			echo "🚫 Removing upstream $$name (superseded by dots)"; \
+			rm -rf "$(HOME)/.agents/skills/$$name"; \
+		fi; \
+		for dir in $(AGENT_SKILL_DIRS); do \
+			if [ -L "$$dir/$$name" ] && [ ! -e "$$dir/$$name" ]; then \
+				rm -f "$$dir/$$name"; \
+			fi; \
+		done; \
+	done
+
+skills-install: skills-suppress agent-skills-link
 	@for entry in $(SKILL_PACKAGES); do \
 		sentinel=$${entry%%=*}; \
 		pkg=$${entry#*=}; \
@@ -128,11 +149,13 @@ skills-install: agent-skills-link
 				echo "  ⚠️  Failed to install $$pkg"; \
 		fi; \
 	done
+	@$(MAKE) --no-print-directory skills-suppress agent-skills-link
 	@echo "✅ Skill packages installed!"
 
 skills-update:
 	@echo "🔄 Updating all installed skills..."
 	@skills update -g -y
+	@$(MAKE) --no-print-directory skills-suppress agent-skills-link
 	@echo "✅ Skills updated!"
 
 ai-config : ~/.config/ai/skills ~/.config/ai/path-policies.json
@@ -241,5 +264,5 @@ help:
 	@echo "Individual components:"
 	@$(foreach target,$(all),echo "  $(target)";)
 
-.PHONY: all $(all) pi-extensions-install skills-update help
+.PHONY: all $(all) pi-extensions-install skills-update skills-suppress help
 .DEFAULT_GOAL := all