From cfbfb6710a846419cc07ad17d9dbb5b5a065801c Mon Sep 17 00:00:00 2001 From: Adam Zywicki <81277290+NimbleEngineer21@users.noreply.github.com> Date: Sat, 21 Mar 2026 01:29:11 -0400 Subject: [PATCH] feat(git-worktree): auto-trust mise and direnv configs in new worktrees (#312) --- .../skills/git-worktree/SKILL.md | 14 +- .../git-worktree/scripts/worktree-manager.sh | 163 ++++++++++++++++++ 2 files changed, 174 insertions(+), 3 deletions(-) diff --git a/plugins/compound-engineering/skills/git-worktree/SKILL.md b/plugins/compound-engineering/skills/git-worktree/SKILL.md index 19b8806..12c0e29 100644 --- a/plugins/compound-engineering/skills/git-worktree/SKILL.md +++ b/plugins/compound-engineering/skills/git-worktree/SKILL.md @@ -16,6 +16,7 @@ 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 @@ -23,8 +24,11 @@ This skill provides a unified interface for managing Git worktrees across your d 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 @@ -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` diff --git a/plugins/compound-engineering/skills/git-worktree/scripts/worktree-manager.sh b/plugins/compound-engineering/skills/git-worktree/scripts/worktree-manager.sh index 181d6d1..3a05944 100755 --- a/plugins/compound-engineering/skills/git-worktree/scripts/worktree-manager.sh +++ b/plugins/compound-engineering/skills/git-worktree/scripts/worktree-manager.sh @@ -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" @@ -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 + 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:" @@ -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