Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions plugins/compound-engineering/skills/git-worktree/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,19 @@ This skill provides a unified interface for managing Git worktrees across your d
- **Interactive confirmations** at each step
- **Automatic .gitignore management** for worktree directory
- **Automatic .env file copying** from main repo to new worktrees
- **Automatic dev tool trusting** for mise and direnv configs with review-safe guardrails

## CRITICAL: Always Use the Manager Script

**NEVER call `git worktree add` directly.** Always use the `worktree-manager.sh` script.

The script handles critical setup that raw git commands don't:
1. Copies `.env`, `.env.local`, `.env.test`, etc. from main repo
2. Ensures `.worktrees` is in `.gitignore`
3. Creates consistent directory structure
2. Trusts dev tool configs with branch-aware safety rules:
- mise: auto-trust only when unchanged from a trusted baseline branch
- direnv: auto-allow only for trusted base branches; review worktrees stay manual
3. Ensures `.worktrees` is in `.gitignore`
4. Creates consistent directory structure

```bash
# ✅ CORRECT - Always use the script
Expand Down Expand Up @@ -95,7 +99,11 @@ bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh creat
2. Updates the base branch from remote
3. Creates new worktree and branch
4. **Copies all .env files from main repo** (.env, .env.local, .env.test, etc.)
5. Shows path for cd-ing to the worktree
5. **Trusts dev tool configs** with branch-aware safety rules:
- trusted bases (`main`, `develop`, `dev`, `trunk`, `staging`, `release/*`) compare against themselves
- other branches compare against the default branch
- direnv auto-allow is skipped on non-trusted bases because `.envrc` can source unchecked files
6. Shows path for cd-ing to the worktree

### `list` or `ls`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,137 @@ copy_env_files() {
echo -e " ${GREEN}✓ Copied $copied environment file(s)${NC}"
}

# Resolve the repository default branch, falling back to main when origin/HEAD
# is unavailable (for example in single-branch clones).
get_default_branch() {
local head_ref
head_ref=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || true)

if [[ -n "$head_ref" ]]; then
echo "${head_ref#refs/remotes/origin/}"
else
echo "main"
fi
}

# Auto-trust is only safe when the worktree is created from a long-lived branch
# the developer already controls. Review/PR branches should fall back to the
# default branch baseline and require manual direnv approval.
is_trusted_base_branch() {
local branch="$1"
local default_branch="$2"

[[ "$branch" == "$default_branch" ]] && return 0

case "$branch" in
develop|dev|trunk|staging|release/*)
return 0
;;
*)
return 1
;;
esac
}

# Trust development tool configs in a new worktree.
# Worktrees get a new filesystem path that tools like mise and direnv
# have never seen. Without trusting, these tools block with interactive
# prompts or refuse to load configs, which breaks hooks and scripts.
#
# Safety: auto-trusts only configs unchanged from a trusted baseline branch.
# Review/PR branches fall back to the default-branch baseline, and direnv
# auto-allow is limited to trusted base branches because .envrc can source
# additional files that direnv does not validate.
#
# TOCTOU between hash-check and trust is acceptable for local dev use.
trust_dev_tools() {
local worktree_path="$1"
local base_ref="$2"
local allow_direnv_auto="$3"
local trusted=0
local skipped_messages=()
local manual_commands=()

# mise: trust the specific config file if present and unchanged
if command -v mise &>/dev/null; then
for f in .mise.toml mise.toml .tool-versions; do
if [[ -f "$worktree_path/$f" ]]; then
if _config_unchanged "$f" "$base_ref" "$worktree_path"; then
if (cd "$worktree_path" && mise trust "$f" --quiet); then
trusted=$((trusted + 1))
else
echo -e " ${YELLOW}Warning: 'mise trust $f' failed -- run manually in $worktree_path${NC}"
fi
else
skipped_messages+=("mise trust $f (config differs from $base_ref)")
manual_commands+=("mise trust $f")
fi
break
fi
done
fi

# direnv: allow .envrc
if command -v direnv &>/dev/null; then
if [[ -f "$worktree_path/.envrc" ]]; then
if [[ "$allow_direnv_auto" != "true" ]]; then
skipped_messages+=("direnv allow (.envrc auto-allow is disabled for non-trusted base branches)")
manual_commands+=("direnv allow")
elif _config_unchanged ".envrc" "$base_ref" "$worktree_path"; then
if (cd "$worktree_path" && direnv allow); then
trusted=$((trusted + 1))
else
echo -e " ${YELLOW}Warning: 'direnv allow' failed -- run manually in $worktree_path${NC}"
fi
else
skipped_messages+=("direnv allow (.envrc differs from $base_ref)")
manual_commands+=("direnv allow")
fi
fi
fi

if [[ $trusted -gt 0 ]]; then
echo -e " ${GREEN}✓ Trusted $trusted dev tool config(s)${NC}"
fi

if [[ ${#skipped_messages[@]} -gt 0 ]]; then
echo -e " ${YELLOW}Skipped auto-trust for config(s) requiring manual review:${NC}"
for item in "${skipped_messages[@]}"; do
echo -e " - $item"
done
if [[ ${#manual_commands[@]} -gt 0 ]]; then
local joined
joined=$(printf ' && %s' "${manual_commands[@]}")
echo -e " ${BLUE}Review the diff, then run manually: cd $worktree_path${joined}${NC}"
fi
fi
}

# Check if a config file is unchanged from the base branch.
# Returns 0 (true) if the file is identical to the base branch version.
# Returns 1 (false) if the file was added or modified by this branch.
#
# Note: rev-parse returns the stored blob hash; hash-object on a path applies
# gitattributes filters. A mismatch causes a false negative (trust skipped),
# which is the safe direction.
_config_unchanged() {
local file="$1"
local base_ref="$2"
local worktree_path="$3"

# Reject symlinks -- trust only regular files with verifiable content
[[ -L "$worktree_path/$file" ]] && return 1

# Get the blob hash directly from git's object database via rev-parse
local base_hash
base_hash=$(git rev-parse "$base_ref:$file" 2>/dev/null) || return 1

local worktree_hash
worktree_hash=$(git hash-object "$worktree_path/$file") || return 1

[[ "$base_hash" == "$worktree_hash" ]]
}

# Create a new worktree
create_worktree() {
local branch_name="$1"
Expand Down Expand Up @@ -107,6 +238,29 @@ create_worktree() {
# Copy environment files
copy_env_files "$worktree_path"

# Trust dev tool configs (mise, direnv) so hooks and scripts work immediately.
# Long-lived integration branches can use themselves as the trust baseline,
# while review/PR branches fall back to the default branch and require manual
# direnv approval.
local default_branch
default_branch=$(get_default_branch)
local trust_branch="$default_branch"
local allow_direnv_auto="false"
if is_trusted_base_branch "$from_branch" "$default_branch"; then
trust_branch="$from_branch"
allow_direnv_auto="true"
fi

if ! git fetch origin "$trust_branch" --quiet; then
echo -e " ${YELLOW}Warning: could not fetch origin/$trust_branch -- trust check may use stale data${NC}"
fi
# Skip trust entirely if the baseline ref doesn't exist locally.
if git rev-parse --verify "origin/$trust_branch" &>/dev/null; then
Comment on lines +254 to +258
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip auto-trust when baseline fetch fails

If git fetch origin "$trust_branch" fails (offline/auth/transient errors), the script still proceeds to trust_dev_tools as long as a local origin/$trust_branch ref exists, which may be stale; that can incorrectly classify changed .mise/.envrc files as unchanged and auto-trust them. Fresh evidence in this commit: after the failed fetch warning, the code still calls trust on the basis of git rev-parse --verify "origin/$trust_branch" rather than requiring a successful refresh. This should fail closed by skipping auto-trust whenever the fetch step fails.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an intentional design decision. I chose to proceed with stale-but-present refs (with a warning), skip entirely when the ref doesn't exist locally. Failing closed on every fetch failure would break auto-trust for offline developers or flaky VPN connections, which is a common scenario. The stale-ref risk is low: a false positive requires the config to have changed upstream while the local ref hasn't been updated, and even then the worktree was just created from from_branch so the developer is working with that content regardless. The rev-parse --verify check ensures we fail closed when there's truly no baseline to compare against.

trust_dev_tools "$worktree_path" "origin/$trust_branch" "$allow_direnv_auto"
else
echo -e " ${YELLOW}Skipping dev tool trust -- origin/$trust_branch not found locally${NC}"
fi

echo -e "${GREEN}✓ Worktree created successfully!${NC}"
echo ""
echo "To switch to this worktree:"
Expand Down Expand Up @@ -321,6 +475,15 @@ Environment Files:
- Creates .backup files if destination already exists
- Use 'copy-env' to refresh env files after main repo changes

Dev Tool Trust:
- Trusts mise config (.mise.toml, mise.toml, .tool-versions) and direnv (.envrc)
- Uses trusted base branches directly (main, develop, dev, trunk, staging, release/*)
- Other branches fall back to the default branch as the trust baseline
- direnv auto-allow is skipped on non-trusted base branches; review manually first
- Modified configs are flagged for manual review
- Only runs if the tool is installed and config exists
- Prevents hooks/scripts from hanging on interactive trust prompts

Examples:
worktree-manager.sh create feature-login
worktree-manager.sh create feature-auth develop
Expand Down