Skip to content

Conversation

@satococoa
Copy link
Owner

@satococoa satococoa commented Aug 20, 2025

Summary

  • Separate shell integration concerns into focused commands
  • Add wtp hook command for cd shell integration only
  • Add wtp shell-init command combining completion and hook functionality
  • Simplify wtp cd to always output absolute paths without environment variable dependencies
  • Remove shell integration logic from completion command for cleaner separation

Key Changes

  • New wtp hook command: Generates shell functions for seamless wtp cd experience
  • New wtp shell-init command: One-stop setup combining completion and hook functionality
  • Simplified wtp cd: Pure path output function without WTP_SHELL_INTEGRATION dependency
  • Improved tests: Reduced cd_test.go from 778 to 215 lines while maintaining coverage
  • Updated documentation: Clear setup instructions for both features

Test Plan

  • All unit tests pass with improved coverage
  • E2E tests updated for new behavior
  • Manual testing of shell integration in bash/zsh/fish
  • Verified backward compatibility

User Experience

Before: Complex environment variable setup
After: Simple one-line setup: eval "$(wtp shell-init bash)"

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • cd now prints an absolute worktree path; usable directly or via an optional shell hook.
    • Added hook and shell-init commands to install shell hooks and combined completion+hook setups (bash, zsh, fish).
    • Dynamic branch/worktree completions added for add/remove/cd via CLI-provided completion.
  • Documentation

    • Rewrote shell integration guidance: direct usage, hook-based setup, and lazy-loading for Homebrew; clarified cd behavior.
  • Refactor

    • Removed the old standalone completion command in favor of built-in CLI completion plus separate hook/shell-init flow.
  • Tests

    • Updated unit and e2e tests to validate path-output cd behavior and new completion/hook workflows.

- Separate completion and cd functionality for clarity
- Add wtp hook command for shell integration
- Add wtp shell-init command for combined setup
- Remove WTP_SHELL_INTEGRATION environment variable dependency
- Simplify wtp cd to pure path output function

Breaking Changes:
- wtp cd no longer requires WTP_SHELL_INTEGRATION=1
- wtp completion no longer includes cd functionality

🤖 Generated with Claude Code

Co-Authored-By: Claude <[email protected]>
Copilot AI review requested due to automatic review settings August 20, 2025 15:55
@coderabbitai
Copy link

coderabbitai bot commented Aug 20, 2025

Walkthrough

Replaces the custom completion subsystem with urfave/cli built-ins, makes wtp cd always print an absolute worktree path, adds hook and shell-init commands for shell integration, implements focused completion helpers for add/remove/cd, deletes legacy completion code and tests, and updates docs and e2e tests accordingly.

Changes

Cohort / File(s) Summary
Docs
CLAUDE.md, README.md
Overhauled shell-integration docs: added Shell Integration Architecture v1.2.0, removed old bullets, separated completion vs hook vs shell-init, added direct-usage and lazy-loading notes and migration guidance.
Completion removal
cmd/wtp/completion.go, cmd/wtp/completion_test.go, cmd/wtp/completion_worktrees_test.go
Removed the legacy custom completion subsystem and its tests, including NewCompletionCommand() and embedded Bash/Zsh/Fish completion scripts and hidden probe commands.
cd command + tests
cmd/wtp/cd.go, cmd/wtp/cd_test.go
Reworked cd to output absolute worktree paths (no in-process chdir), added worktree naming/listing helpers and ShellComplete for cd, and replaced CLI-heavy tests with unit tests for path resolution and completion.
hook command + tests
cmd/wtp/hook.go, cmd/wtp/hook_test.go
Added hook command producing shell-specific hook functions (bash/zsh/fish) that intercept wtp cd and perform shell-side cd when a path is returned; tests validate script structure and edge cases.
shell-init command + tests
cmd/wtp/shell_init.go, cmd/wtp/shell_init_test.go
Added shell-init to emit completion then append hook (runs the executable completion <shell> then prints hook); tests ensure command presence and execution.
add command completion + tests
cmd/wtp/add.go, cmd/wtp/add_test.go
Added Git-backed branch enumeration and ShellComplete integration for add, plus tests verifying safe execution and no panics.
remove command completion + tests
cmd/wtp/remove.go, cmd/wtp/remove_test.go
Added worktree naming/enumerator for remove, ShellComplete wrapper, tests for naming/enumeration/completion; test changes include executor behavior and duplicated test blocks to review.
App wiring + tests
cmd/wtp/main.go, cmd/wtp/main_test.go
Removed old completion command from root commands, rely on urfave/cli built-in completion, and added hook and shell-init; updated tests to reflect removed command.
E2E tests
test/e2e/error_test.go, test/e2e/shell_test.go
Updated E2E expectations: wtp cd now outputs an absolute path; tests assert path content and branch resolution instead of requiring shell integration errors.
Test helpers & import tidy
cmd/wtp/testhelpers_test.go, various import reorderings, .golangci.yml
Added reusable test helpers for writer/name tests, multiple import reorderings, and minor config linting updates.
Errors package
internal/errors/errors.go, internal/errors/errors_test.go
Added WorktreeNameRequiredForRemove() error helper and a unit test validating its message contents.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant Sh as Shell (bash/zsh/fish)
  participant H as Hook (shell function)
  participant W as wtp (cd)
  participant G as git

  U->>Sh: wtp cd <name>
  Sh->>H: intercept "wtp" invocation
  alt cd subcommand intercepted
    H->>W: run "wtp cd <name>"
    W->>G: enumerate/resolve worktrees
    G-->>W: worktree data
    W-->>H: absolute path (stdout)
    alt valid path
      H->>Sh: cd <absolute path>
    else no path / empty
      H->>Sh: delegate to original wtp
    end
  else other subcommands
    H->>Sh: delegate to original wtp
  end
Loading
sequenceDiagram
  autonumber
  participant U as User
  participant SI as wtp shell-init
  participant CE as wtp completion (executable)
  participant HK as hook printer

  U->>SI: wtp shell-init <shell>
  SI->>CE: invoke executable "completion <shell>"
  CE-->>SI: completion script text
  SI->>HK: append print<shell>Hook
  HK-->>U: combined completion + hook script
Loading
sequenceDiagram
  autonumber
  participant U as User
  participant W as wtp add/remove
  participant G as git

  U->>W: press [TAB] for completion
  W->>G: query branches/worktrees
  G-->>W: refs/worktrees
  W-->>U: completion items (lines)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

Possibly related PRs

Poem

I hop between the trees of work,
Paths now print where once was murk.
A clever hook nudges me to go,
Completions whisper what they know.
Less is more — I bound, I see,
Tab, leap, and into place I'm free. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly and accurately summarizes the primary change — a refactor of the shell integration architecture — and aligns with the PR objectives (new hook and shell-init commands, removal of integration from completion, and cd behavior changes); it is concise, specific, and developer-scannable.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor-shell-integration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR refactors the shell integration architecture by separating completion and shell hook concerns into distinct commands. The main purpose is to simplify the user experience and improve maintainability by removing environment variable dependencies and creating cleaner command boundaries.

Key changes:

  • Adds dedicated wtp hook and wtp shell-init commands for shell integration
  • Simplifies wtp cd to always output absolute paths without environment checks
  • Removes shell integration logic from completion commands for better separation of concerns

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.

Show a summary per file
File Description
cmd/wtp/shell_init.go New command combining completion and hook functionality for one-line setup
cmd/wtp/hook.go New command generating shell functions for cd functionality without legacy dependencies
cmd/wtp/cd.go Simplified to pure path output function, removed WTP_SHELL_INTEGRATION check
cmd/wtp/completion.go Removed shell integration logic, focused purely on tab completion
cmd/wtp/cd_test.go Streamlined tests focusing on core path resolution behavior
test/e2e/shell_test.go Updated tests to verify new simplified cd behavior
test/e2e/error_test.go Updated error handling tests for new cd command behavior
README.md Updated documentation with clearer setup instructions for separate features
CLAUDE.md Added architecture documentation for the new shell integration design

@satococoa satococoa self-assigned this Aug 21, 2025
satococoa and others added 4 commits August 23, 2025 00:13
…t-in

- Delete completion.go (750+ lines of custom completion scripts)
- Delete completion_test.go and completion_worktrees_test.go
- Switch to urfave/cli v3 built-in completion functionality
- Enable built-in completion with EnableShellCompletion: true

This change eliminates maintenance overhead of custom bash/zsh/fish
completion scripts by leveraging the standard library functionality.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
- Add branch completion to add.go (getBranches + completeBranches)
- Add worktree completion to remove.go (getWorktreesForRemove + completeWorktrees)
- Add worktree completion to cd.go (getWorktreesForCd + completeWorktreesForCd)

Each completion function follows the pattern:
- Internal function using io.Writer (testable)
- Wrapper function using fmt.Println (urfave/cli compatible)

This approach maintains testability while providing urfave/cli
compatibility through the ShellComplete field.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
- Add comprehensive tests for new ShellComplete functions
- Test both internal functions (using io.Writer) and wrappers
- Update existing tests to work with completion system refactor
- Add helper functions where needed (e.g., findSubcommand)
- Fix test expectations to match actual filepath behavior

All tests maintain the pattern of testing behavior rather than
implementation details, ensuring robust coverage.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
- Reimplement shell-init to work with urfave/cli built-in completion
- Use outputCompletion() helper to execute 'wtp completion <shell>'
- Combine completion output with hook scripts seamlessly
- Support all shells: bash, zsh, fish

The shell-init command now provides a complete one-command solution
for users to set up both tab completion and cd functionality:

  eval "$(wtp shell-init bash)"

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
CLAUDE.md (2)

42-49: Update function location reference: getWorktreeNameFromPath() is no longer in completion.go.

Doc still says it’s implemented in cmd/wtp/completion.go, which has been removed in this refactor. Point this to the new shared helper location (or the cd module) to avoid confusion.

Suggested change:

-The `getWorktreeNameFromPath()` function in `cmd/wtp/completion.go` implements this logic:
+The unified worktree naming helper implements this logic (currently lives alongside the cd command and is used by commands that render names):

116-117: Remove stale WTP_SHELL_INTEGRATION=1 mention (behavior changed).

Per this PR, wtp cd no longer depends on WTP_SHELL_INTEGRATION. Replace the quick-test example accordingly:

- WTP_SHELL_INTEGRATION=1 go run ../cmd/wtp cd @
+ # Either:
+ cd "$(go run ../cmd/wtp cd @)"
+ # Or enable hooks once per shell session:
+ eval "$(go run ../cmd/wtp hook bash)"
cmd/wtp/cd.go (1)

39-41: Make isWorktreeManagedCd path logic OS-safe.

  • strings.TrimSuffix(baseDir, "/") is POSIX-only. Use filepath.Clean.
  • Checking “outside base” via strings.HasPrefix(relPath, "..") can misclassify on Windows and edge cases. Prefer an OS-aware check.

Patch:

- // Remove trailing slash if it exists
- baseDir = strings.TrimSuffix(baseDir, "/")
+ // Normalize path separators for the current OS
+ baseDir = filepath.Clean(baseDir)

- // If relative path starts with "..", it's outside base directory
- return !strings.HasPrefix(relPath, "..")
+ // If relative path is ".." or starts with ".."+sep, it's outside base directory
+ sep := string(os.PathSeparator)
+ return relPath != ".." && !strings.HasPrefix(relPath, ".."+sep)

Also applies to: 59-61

cmd/wtp/main_test.go (1)

87-91: Also update help-output expectations to cover new commands

Extend the list so help output assertions cover hook and shell-init too.

-	expectedCommands := []string{"add", "list", "remove", "init", "cd"}
+	expectedCommands := []string{"add", "list", "remove", "init", "cd", "hook", "shell-init"}
cmd/wtp/cd_test.go (1)

1-306: Address lint failures: gofmt and duplicated blocks

  • gofmt: The CI reports this file isn’t formatted. Please run gofmt -s -w and goimports -w (see first comment for commands).
  • dupl: Sections 221-255 and 257-289 duplicate logic seen in other test files. Extracting shared helpers to internal/testutil (e.g., WithTempGitDir, CaptureStdout, NewConfigWithBaseDir) will resolve duplication and improve maintainability.
🧹 Nitpick comments (30)
cmd/wtp/add.go (2)

346-394: Generalize remote handling and HEAD filtering in getBranches.

Right now we:

  • Trim only origin/ but not other remotes.
  • Skip only origin/HEAD (misses upstream/HEAD, etc.).
  • Don’t guard against bare remote names (e.g., just origin).

This can yield noisy/duplicated suggestions and misses common setups with multiple remotes. Recommend:

  • Query remotes via git remote (best-effort).
  • Skip HEAD for any remote and bare remote names.
  • Strip the <remote>/ prefix for any known remote to dedupe local vs remote.

Proposed patch (localized to this function):

 func getBranches(w io.Writer) error {
   // Get current directory
   cwd, err := os.Getwd()
   if err != nil {
     return err
   }

+  // Best-effort: gather declared remotes to normalize names
+  remCmd := exec.Command("git", "remote")
+  remCmd.Dir = cwd
+  remOut, _ := remCmd.Output() // ignore error; completion should be resilient
+  remotes := strings.Fields(strings.TrimSpace(string(remOut)))
+
   // Get all branches using git for-each-ref for better control
   gitCmd := exec.Command("git", "for-each-ref", "--format=%(refname:short)", "refs/heads", "refs/remotes")
   gitCmd.Dir = cwd
   output, err := gitCmd.Output()
   if err != nil {
     return err
   }

   branches := strings.Split(strings.TrimSpace(string(output)), "\n")

   // Use a map to avoid duplicates
   seen := make(map[string]bool)

   for _, branch := range branches {
     if branch == "" {
       continue
     }

-    // Skip HEAD references and bare origin
-    if branch == "origin/HEAD" || branch == "origin" {
-      continue
-    }
+    // Skip HEAD references (local or any remote) and bare remote names
+    if branch == "HEAD" || strings.HasSuffix(branch, "/HEAD") {
+      continue
+    }
+    isBareRemote := false
+    for _, r := range remotes {
+      if branch == r {
+        isBareRemote = true
+        break
+      }
+    }
+    if isBareRemote {
+      continue
+    }

     // Remove remote prefix for display, but keep track of what we've seen
     displayName := branch
-    if strings.HasPrefix(branch, "origin/") {
-      // For remote branches, show without the origin/ prefix
-      displayName = strings.TrimPrefix(branch, "origin/")
-    }
+    for _, r := range remotes {
+      prefix := r + "/"
+      if strings.HasPrefix(branch, prefix) {
+        // For remote branches, show without the <remote>/ prefix
+        displayName = strings.TrimPrefix(branch, prefix)
+        break
+      }
+    }

     // Skip if already seen (handles case where local and remote have same name)
     if seen[displayName] {
       continue
     }

     seen[displayName] = true
     fmt.Fprintln(w, displayName)
   }

   return nil
 }

346-394: Optional: add timeouts to git calls to avoid hung completions on large repos.

Use exec.CommandContext with a short context deadline (e.g., 200–500ms) so completion never blocks the user. If the deadline hits, just emit nothing.

cmd/wtp/add_test.go (1)

968-982: Broaden assertions to catch regressions (optional).

Consider asserting that the function writes zero or more lines and never includes origin/HEAD or bare remote names once the getBranches change is applied. This makes the completion contract explicit.

CLAUDE.md (1)

251-314: Minor copyedits for clarity (optional).

A few bullets use stacked bolds and hyphens that read oddly (“No side effects: …”). Consider consistent bullet structure and parallel phrasing. Not blocking.

cmd/wtp/cd.go (2)

271-294: Avoid duplicate naming logic across commands (optional).

getWorktreeNameFromPathCd mirrors the naming logic used by add success messaging (getWorktreeNameFromPath). Consider centralizing in a small shared helper (e.g., internal/naming) so all commands and errors use the same single source of truth.


113-167: Nit: compute mainRepoPath once.

findMainWorktreePath(worktrees) is called twice. Cache it to avoid recomputation and clarify intent.

test/e2e/error_test.go (1)

152-165: Strengthen assertion: verify absolute path semantics

Since wtp cd now guarantees an absolute path, assert that the output is absolute. This prevents regressions to relative paths.

@@
-import (
-	"strings"
-	"testing"
-
-	"github.com/satococoa/wtp/test/e2e/framework"
-)
+import (
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"github.com/satococoa/wtp/test/e2e/framework"
+)
@@
-		// Output should be a valid path
-		outputPath := strings.TrimSpace(output)
-		framework.AssertTrue(t, strings.Contains(outputPath, "test-branch"), "Should contain worktree name")
+		// Output should be an absolute path and contain the worktree name
+		outputPath := strings.TrimSpace(output)
+		framework.AssertTrue(t, filepath.IsAbs(outputPath), "Path should be absolute: %s", outputPath)
+		framework.AssertTrue(t, strings.Contains(outputPath, "test-branch"), "Should contain worktree name")
cmd/wtp/remove_test.go (4)

853-864: Rename misleading subtest title; it doesn’t test a fallback-on-error path.

filepath.Rel rarely errors on POSIX; this case asserts a long relative path, not a “fallback to directory name”. Rename for clarity.

- t.Run("should fallback to directory name on error", func(t *testing.T) {
+ t.Run("should return relative path when outside base_dir", func(t *testing.T) {
@@
-   // Use invalid path to trigger error
-   name := getWorktreeNameFromPath("/completely/different/path", cfg, "/path/to/repo", false)
-   assert.Equal(t, "../../../../completely/different/path", name) // actual filepath.Rel result
+   // Path outside base_dir should yield a relative path from base_dir
+   name := getWorktreeNameFromPath("/completely/different/path", cfg, "/path/to/repo", false)
+   assert.Equal(t, "../../../../completely/different/path", name)

830-830: Format the file (gofmt).

CI reports “File is not properly formatted (gofmt)”. Please run gofmt/goimports.


901-915: Prefer writer-based completion capture to avoid stdout redirection in tests.

The test silences os.Stdout to avoid noise. If completeWorktrees writes to cmd.Root().Writer (as other commands do), this redirection is unnecessary. See suggested change in remove.go comment for completeWorktrees.


830-864: Extract and Centralize getWorktreeNameFromPath Tests

Ripgrep confirms that the same three scenarios for getWorktreeNameFromPath are tested in two places:

  • cmd/wtp/remove_test.go (lines 830–864)
  • cmd/wtp/cd_test.go (lines 221–255)

To reduce duplication and maintenance overhead:

• Create a new test file cmd/wtp/get_worktree_name_test.go containing a helper function, e.g.:

package wtp

import (
    "path/filepath"
    "testing"

    "github.com/stretchr/testify/assert"
    "yourmodule/config"
)

func testGetWorktreeNameFromPath(t *testing.T) {
    cfg := &config.Config{ Defaults: config.Defaults{ BaseDir: ".worktrees" } }
    mainRepo := "/path/to/repo"

    t.Run("main worktree → @", func(t *testing.T) {
        name := getWorktreeNameFromPath(mainRepo, cfg, mainRepo, true)
        assert.Equal(t, "@", name)
    })
    t.Run("non-main worktree → relative path", func(t *testing.T) {
        p := filepath.Join(mainRepo, ".worktrees/feature/test")
        name := getWorktreeNameFromPath(p, cfg, mainRepo, false)
        assert.Equal(t, "feature/test", name)
    })
    t.Run("fallback on error → raw filepath.Rel", func(t *testing.T) {
        p := "/completely/different/path"
        rel, _ := filepath.Rel(mainRepo, p)
        name := getWorktreeNameFromPath(p, cfg, mainRepo, false)
        assert.Equal(t, rel, name)
    })
}

• In both remove_test.go and cd_test.go, replace their inline blocks with:

func TestGetWorktreeNameFromPath(t *testing.T) {
    testGetWorktreeNameFromPath(t)
}

• Delete the duplicated T.Run cases from lines 830–864 in remove_test.go and 221–255 in cd_test.go.

This centralization ensures any future change to the naming logic only needs to be updated in one place.

cmd/wtp/remove.go (4)

281-304: Unify base_dir resolution and normalize paths.

Use the same resolver used elsewhere (cfg.ResolveWorktreePath) and normalize inputs. This avoids subtle separator issues and keeps behavior consistent across commands.

 func getWorktreeNameFromPath(worktreePath string, cfg *config.Config, mainRepoPath string, isMain bool) string {
   if isMain {
     return "@"
   }

-  // Get base_dir path
-  baseDir := cfg.Defaults.BaseDir
-  if !filepath.IsAbs(baseDir) {
-    baseDir = filepath.Join(mainRepoPath, baseDir)
-  }
+  // Resolve and normalize base_dir consistently
+  baseDir := cfg.ResolveWorktreePath(mainRepoPath, "")
+  baseDir = filepath.Clean(baseDir)
+  worktreePath = filepath.Clean(worktreePath)

   // Calculate relative path from base_dir
   relPath, err := filepath.Rel(baseDir, worktreePath)
   if err != nil {
     // Fallback to directory name
     return filepath.Base(worktreePath)
   }

   return relPath
 }

41-44: Normalize with filepath.Clean instead of TrimSuffix.

TrimSuffix("/") won’t handle Windows separators and can leave redundant components. filepath.Clean is portable and reliable.

- // Remove trailing slash if it exists
- baseDir = strings.TrimSuffix(baseDir, "/")
+ // Normalize path
+ baseDir = filepath.Clean(baseDir)

350-362: Write completions to the CLI writer for consistency and easier testing.

urfave/cli’s ShellComplete prints to stdout, but using Root().Writer with a fallback preserves that behavior and keeps tests simpler.

-func completeWorktrees(_ context.Context, _ *cli.Command) {
+func completeWorktrees(_ context.Context, cmd *cli.Command) {
   var buf bytes.Buffer
   if err := getWorktreesForRemove(&buf); err != nil {
     return
   }

-  // Output each line using fmt.Println for urfave/cli compatibility
-  scanner := bufio.NewScanner(&buf)
+  w := cmd.Root().Writer
+  if w == nil {
+    w = os.Stdout
+  }
+  // Output each line using fmt.Fprintln for urfave/cli compatibility
+  scanner := bufio.NewScanner(&buf)
   for scanner.Scan() {
-    fmt.Println(scanner.Text())
+    fmt.Fprintln(w, scanner.Text())
   }
 }

214-279: Matching priority and error surfaces make sense; consider stable ordering in “available worktrees.”

The matching order (branch, basename, relative name) is reasonable. If error messages include availableWorktrees, consider sorting them for deterministic output (low priority).

cmd/wtp/hook_test.go (1)

102-146: Add zsh edge-case coverage to match bash/fish parity.

Edge-case handling is verified for bash and fish; include zsh to keep parity.

   tests := []struct {
     name          string
     shell         string
     requiredLogic []string
   }{
@@
     {
       name:  "fish hook handles empty argument",
       shell: "fish",
       requiredLogic: []string{
         "if test -z \"$argv[2]\"",
         "echo \"Usage:",
         "return 1",
       },
     },
+    {
+      name:  "zsh hook handles empty argument",
+      shell: "zsh",
+      requiredLogic: []string{
+        "[[ -z \"$2\" ]]", // zsh supports [[ ... ]]
+        "echo \"Usage:",
+        "return 1",
+      },
+    },
   }
@@
   switch tt.shell {
   case "bash":
     printBashHook(&buf)
   case "fish":
     printFishHook(&buf)
+  case "zsh":
+    printZshHook(&buf)
   }
cmd/wtp/shell_init.go (3)

101-119: Consider making completion execution overridable for tests.

exec.Command spawns the binary; allowing an overridable runner simplifies unit tests for shell-init without forking a process.

+// test seam
+var runCompletion = outputCompletion
@@
- if err := outputCompletion(w, "bash"); err != nil {
+ if err := runCompletion(w, "bash"); err != nil {
@@
- if err := outputCompletion(w, "zsh"); err != nil {
+ if err := runCompletion(w, "zsh"); err != nil {
@@
- if err := outputCompletion(w, "fish"); err != nil {
+ if err := runCompletion(w, "fish"); err != nil {

119-119: Format the file (gofmt).

CI reports formatting issues. Please run gofmt/goimports.


47-99: Add unit tests for “shell-init ” to assert combined output.

Recommend tests asserting that:

  • completion content is present (e.g., bash “complete -F …”)
  • hook content is present (e.g., “wtp()” and “cd "$target_dir"”)
  • a separating newline exists between the two blocks

I can draft cmd/wtp/shell_init_test.go table tests mirroring hook_test.go if helpful.

test/e2e/shell_test.go (4)

15-28: CD now outputs a path — assert it’s absolute.

Strengthen the assertion to ensure the new contract is upheld.

   // Output should be a valid path
   outputPath := strings.TrimSpace(output)
-  framework.AssertTrue(t, strings.Contains(outputPath, "test-branch"), "Should contain worktree name")
+  framework.AssertTrue(t, strings.Contains(outputPath, "test-branch"), "Should contain worktree name")
+  framework.AssertTrue(t, strings.HasPrefix(outputPath, "/") || strings.HasPrefix(outputPath, "\\"), "Should be an absolute path")

54-57: Remove legacy WTP_SHELL_INTEGRATION usage.

Env-based integration was removed; these lines are vestigial and can be dropped.

- // Simulate shell integration
- os.Setenv("WTP_SHELL_INTEGRATION", "1")
- defer os.Unsetenv("WTP_SHELL_INTEGRATION")
+ // No env-based integration needed; cd always returns a path

136-139: Same here: drop WTP_SHELL_INTEGRATION from completion behavior tests.

It no longer affects list/cd; removing reduces confusion.

- os.Setenv("WTP_SHELL_INTEGRATION", "1")
- defer os.Unsetenv("WTP_SHELL_INTEGRATION")
+ // No env-based integration needed

Also applies to: 155-157


68-120: Consider adding E2E for “wtp shell-init ”.

Verifies the one-liner UX promised in the PR: output contains both completion and hook parts. This complements the existing completion tests.

cmd/wtp/cd_test.go (7)

17-79: Make absolute-path tests platform-agnostic or skip on Windows

These cases hardcode POSIX absolute paths and use filepath.IsAbs on them. On Windows runners, these will fail. Either synthesize OS-specific absolute paths or skip on Windows.

Option A (recommended): guard on Windows.

 func TestCdCommand_AlwaysOutputsAbsolutePath(t *testing.T) {
+  if runtime.GOOS == "windows" {
+    t.Skip("POSIX paths in test data; skip on Windows.")
+  }
   // Setup a realistic worktree scenario

Add import:

+ "runtime"

Option B: construct paths via filepath.Join with a temp root and build worktreeList using filepath.ToSlash when needed. I can draft this if you want.


81-125: Simplify env handling with t.Setenv; avoid manual restore

Manual save/restore is verbose and error-prone. t.Setenv restores automatically and makes intent clearer.

- originalEnv := os.Getenv("WTP_SHELL_INTEGRATION")
- defer func() {
-   if originalEnv != "" {
-     os.Setenv("WTP_SHELL_INTEGRATION", originalEnv)
-   } else {
-     os.Unsetenv("WTP_SHELL_INTEGRATION")
-   }
- }()
...
   for _, env := range envStates {
     t.Run(env.name, func(t *testing.T) {
-      if env.value == "" {
-        os.Unsetenv("WTP_SHELL_INTEGRATION")
-      } else {
-        os.Setenv("WTP_SHELL_INTEGRATION", env.value)
-      }
+      if env.value == "" {
+        _ = os.Unsetenv("WTP_SHELL_INTEGRATION") // want truly unset
+      } else {
+        t.Setenv("WTP_SHELL_INTEGRATION", env.value)
+      }

126-160: Consider asserting CLI usage/args metadata for UX consistency

The error-path test looks good. To enforce the CLI UX contract, add asserts for ArgsUsage and a helpful Description snippet via TestCdCommand_CoreBehavior (see dedicated comment), or include a case for “unknown worktree” to ensure the user gets a clear, actionable error.


162-208: Edge-case tests are good; watch for POSIX path assumptions

These cover special characters and completion asterisk. Like the main test, they hardcode POSIX paths. If Windows support in CI is planned, use filepath utilities or skip there.


210-218: Strengthen command surface checks (ArgsUsage and Description)

To keep CLI UX aligned with README examples (per learnings), assert ArgsUsage and a key Description phrase so regressions are caught.

 func TestCdCommand_CoreBehavior(t *testing.T) {
   cmd := NewCdCommand()
   assert.Equal(t, "cd", cmd.Name)
   assert.Equal(t, "Output absolute path to worktree", cmd.Usage)
   assert.NotNil(t, cmd.ShellComplete)
+  assert.Equal(t, "<worktree-name>", cmd.ArgsUsage)
+  assert.Contains(t, cmd.Description, "Output the absolute path")
+  assert.Contains(t, cmd.Description, "eval \"$(wtp hook")
   // The rest is implementation detail - what matters is that it works
 }

221-255: Test name vs expectation mismatch; clarify fallback semantics and make cross-platform

  • The subtest title says “fallback to directory name on error,” but it asserts a long relative path. That’s confusing for future readers.
  • Per retrieved learnings for worktree naming, the intended fallback is directory name. If that’s still the product decision, both the implementation and this test should expect filepath.Base. If not, rename the test to reflect “relative path” fallback.
  • Also, the hardcoded "../../../../..." won’t be correct on Windows.

Option A (keep current behavior; fix name and make OS-agnostic):

- t.Run("should fallback to directory name on error", func(t *testing.T) {
+ t.Run("should fallback to relative path on error", func(t *testing.T) {
...
- // Use invalid path to trigger error
- name := getWorktreeNameFromPathCd("/completely/different/path", cfg, "/path/to/repo", false)
- assert.Equal(t, "../../../../completely/different/path", name) // actual filepath.Rel result
+ // Use invalid path to trigger error; compare using filepath.Rel for portability
+ name := getWorktreeNameFromPathCd("/completely/different/path", cfg, "/path/to/repo", false)
+ rel, _ := filepath.Rel("/path/to/repo", "/completely/different/path")
+ assert.Equal(t, rel, name)

Option B (align to learnings: fallback to directory name):

// In cmd/wtp/cd.go (implementation change)
func getWorktreeNameFromPathCd(path string, cfg *config.Config, repoRoot string, isMain bool) string {
    if isMain {
        return "@"
    }
    baseDir := cfg.Defaults.BaseDir
    rel := strings.TrimPrefix(strings.TrimPrefix(path, filepath.Join(repoRoot, baseDir)+string(os.PathSeparator)), string(os.PathSeparator))
    if rel != "" && rel != path {
        return filepath.ToSlash(rel)
    }
    // fallback to directory name
    return filepath.Base(path)
}

If you choose Option B, update this test to expect "path". I can send a follow-up patch.


257-289: Use require for setup errors and de-duplicate with shared test helpers

  • For mkdir/chdir setup, use require.NoError to fail fast.
  • Static analysis flagged duplication with add/remove tests. Extract helpers (e.g., withTempGitDir(t), captureStdout(t), newTestConfig(baseDir)) into a small internal/testutil package used by all tests.
 err := os.MkdirAll(gitDir, 0755)
-assert.NoError(t, err)
+require.NoError(t, err)

 ...
 err = os.Chdir(tempDir)
-assert.NoError(t, err)
+require.NoError(t, err)

Example helper (new file suggestion):

// internal/testutil/fs.go
package testutil

import (
  "os"
  "path/filepath"
  "testing"
)

func WithTempGitDir(t *testing.T) string {
  t.Helper()
  dir := t.TempDir()
  require.NoError(t, os.MkdirAll(filepath.Join(dir, ".git"), 0o755))
  return dir
}

I can factor these across test files to address the dupl finding.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9ecd26f and eca1d0e.

📒 Files selected for processing (19)
  • CLAUDE.md (1 hunks)
  • README.md (3 hunks)
  • cmd/wtp/add.go (2 hunks)
  • cmd/wtp/add_test.go (2 hunks)
  • cmd/wtp/cd.go (3 hunks)
  • cmd/wtp/cd_test.go (2 hunks)
  • cmd/wtp/completion.go (0 hunks)
  • cmd/wtp/completion_test.go (0 hunks)
  • cmd/wtp/completion_worktrees_test.go (0 hunks)
  • cmd/wtp/hook.go (1 hunks)
  • cmd/wtp/hook_test.go (1 hunks)
  • cmd/wtp/main.go (1 hunks)
  • cmd/wtp/main_test.go (3 hunks)
  • cmd/wtp/remove.go (2 hunks)
  • cmd/wtp/remove_test.go (2 hunks)
  • cmd/wtp/shell_init.go (1 hunks)
  • cmd/wtp/shell_init_test.go (1 hunks)
  • test/e2e/error_test.go (1 hunks)
  • test/e2e/shell_test.go (1 hunks)
💤 Files with no reviewable changes (3)
  • cmd/wtp/completion.go
  • cmd/wtp/completion_test.go
  • cmd/wtp/completion_worktrees_test.go
🧰 Additional context used
📓 Path-based instructions (5)
cmd/wtp/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Update command help text when adding new features.

Files:

  • cmd/wtp/main_test.go
  • cmd/wtp/hook_test.go
  • cmd/wtp/add.go
  • cmd/wtp/shell_init_test.go
  • cmd/wtp/remove_test.go
  • cmd/wtp/add_test.go
  • cmd/wtp/hook.go
  • cmd/wtp/main.go
  • cmd/wtp/remove.go
  • cmd/wtp/shell_init.go
  • cmd/wtp/cd_test.go
  • cmd/wtp/cd.go
**/*.go

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.go: Use gofmt and goimports for all Go code formatting
Follow lint rules (vet, staticcheck, gosec, lll=120, avoid ignored errors)
Keep functions focused and small; use Go idioms
Use short, lowercase package names

Files:

  • cmd/wtp/main_test.go
  • cmd/wtp/hook_test.go
  • test/e2e/error_test.go
  • cmd/wtp/add.go
  • cmd/wtp/shell_init_test.go
  • cmd/wtp/remove_test.go
  • cmd/wtp/add_test.go
  • test/e2e/shell_test.go
  • cmd/wtp/hook.go
  • cmd/wtp/main.go
  • cmd/wtp/remove.go
  • cmd/wtp/shell_init.go
  • cmd/wtp/cd_test.go
  • cmd/wtp/cd.go
**/*_test.go

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Write tests as table-driven tests and name test files with *_test.go

Files:

  • cmd/wtp/main_test.go
  • cmd/wtp/hook_test.go
  • test/e2e/error_test.go
  • cmd/wtp/shell_init_test.go
  • cmd/wtp/remove_test.go
  • cmd/wtp/add_test.go
  • test/e2e/shell_test.go
  • cmd/wtp/cd_test.go
cmd/wtp/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Keep CLI UX consistent with README examples and autocompletion behavior

Files:

  • cmd/wtp/main_test.go
  • cmd/wtp/hook_test.go
  • cmd/wtp/add.go
  • cmd/wtp/shell_init_test.go
  • cmd/wtp/remove_test.go
  • cmd/wtp/add_test.go
  • cmd/wtp/hook.go
  • cmd/wtp/main.go
  • cmd/wtp/remove.go
  • cmd/wtp/shell_init.go
  • cmd/wtp/cd_test.go
  • cmd/wtp/cd.go
README.md

📄 CodeRabbit inference engine (CLAUDE.md)

Update README.md documentation and examples when adding new features.

Files:

  • README.md
🧠 Learnings (6)
📓 Common learnings
Learnt from: CR
PR: satococoa/wtp#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-11T13:56:29.054Z
Learning: Applies to cmd/wtp/** : Keep CLI UX consistent with README examples and autocompletion behavior
📚 Learning: 2025-07-24T14:15:20.744Z
Learnt from: CR
PR: satococoa/wtp#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-24T14:15:20.744Z
Learning: Applies to cmd/wtp/*.go : Update command help text when adding new features.

Applied to files:

  • cmd/wtp/main_test.go
  • cmd/wtp/hook_test.go
  • cmd/wtp/add.go
  • cmd/wtp/shell_init_test.go
  • cmd/wtp/remove_test.go
  • cmd/wtp/add_test.go
  • cmd/wtp/hook.go
  • cmd/wtp/main.go
  • cmd/wtp/remove.go
  • cmd/wtp/shell_init.go
  • cmd/wtp/cd_test.go
  • cmd/wtp/cd.go
📚 Learning: 2025-08-11T13:56:29.054Z
Learnt from: CR
PR: satococoa/wtp#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-11T13:56:29.054Z
Learning: Applies to cmd/wtp/** : Keep CLI UX consistent with README examples and autocompletion behavior

Applied to files:

  • cmd/wtp/main_test.go
  • README.md
  • cmd/wtp/main.go
📚 Learning: 2025-07-24T14:15:20.744Z
Learnt from: CR
PR: satococoa/wtp#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-24T14:15:20.744Z
Learning: Applies to cmd/wtp/completion.go : The getWorktreeNameFromPath() function in cmd/wtp/completion.go must implement worktree naming logic: return '@' for main worktree, relative path from base_dir for non-main worktrees, and fall back to directory name if path calculation fails.

Applied to files:

  • test/e2e/error_test.go
  • cmd/wtp/add.go
  • cmd/wtp/remove_test.go
  • cmd/wtp/add_test.go
  • README.md
  • cmd/wtp/remove.go
  • cmd/wtp/cd_test.go
  • cmd/wtp/cd.go
📚 Learning: 2025-08-11T13:56:29.054Z
Learnt from: CR
PR: satococoa/wtp#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-11T13:56:29.054Z
Learning: Reference AGENTS.md for contributor workflow; cite CLAUDE.md for deep design questions

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-08-11T13:56:29.054Z
Learnt from: CR
PR: satococoa/wtp#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-11T13:56:29.054Z
Learning: Applies to .wtp.yml : .wtp.yml defines project hooks executed on worktree creation; keep defaults safe

Applied to files:

  • README.md
🧬 Code graph analysis (14)
cmd/wtp/main_test.go (2)
cmd/wtp/completion_test.go (6)
  • TestNewCompletionCommand (16-47)
  • TestCompletionScriptGeneration (333-375)
  • TestCompletionBash (49-68)
  • TestCompletionFish (91-110)
  • TestCompletionZsh (70-89)
  • TestCompletionPowerShell (112-123)
cmd/wtp/completion.go (1)
  • NewCompletionCommand (61-167)
cmd/wtp/hook_test.go (1)
cmd/wtp/hook.go (1)
  • NewHookCommand (13-44)
test/e2e/error_test.go (1)
test/e2e/framework/assertions.go (3)
  • AssertNoError (68-71)
  • AssertOutputContains (29-32)
  • AssertTrue (144-147)
cmd/wtp/add.go (2)
cmd/wtp/completion_test.go (3)
  • TestCompleteBranchesFunction (267-298)
  • TestCompleteBranches (125-155)
  • TestPrintBranches (189-204)
cmd/wtp/completion.go (1)
  • printBranches (650-695)
cmd/wtp/shell_init_test.go (1)
cmd/wtp/shell_init.go (1)
  • NewShellInitCommand (14-45)
cmd/wtp/remove_test.go (1)
internal/config/config.go (2)
  • Config (12-16)
  • Defaults (19-21)
cmd/wtp/add_test.go (1)
cmd/wtp/completion_test.go (4)
  • TestCompleteBranchesFunction (267-298)
  • TestCompleteBranches (125-155)
  • TestPrintBranches (189-204)
  • TestCompleteWorktrees (157-187)
test/e2e/shell_test.go (1)
test/e2e/framework/assertions.go (3)
  • AssertNoError (68-71)
  • AssertOutputContains (29-32)
  • AssertTrue (144-147)
cmd/wtp/hook.go (2)
cmd/wtp/completion_test.go (1)
  • TestCompletionBash (49-68)
cmd/wtp/completion.go (1)
  • NewCompletionCommand (61-167)
cmd/wtp/main.go (4)
cmd/wtp/hook.go (1)
  • NewHookCommand (13-44)
cmd/wtp/shell_init.go (1)
  • NewShellInitCommand (14-45)
cmd/wtp/completion.go (3)
  • NewCompletionCommand (61-167)
  • completionBash (169-382)
  • completionZsh (384-602)
cmd/wtp/completion_test.go (4)
  • TestNewCompletionCommand (16-47)
  • TestCompletionBash (49-68)
  • TestHiddenCommands (377-386)
  • TestCompletionScriptGeneration (333-375)
cmd/wtp/remove.go (3)
internal/config/config.go (3)
  • Config (12-16)
  • Defaults (19-21)
  • LoadConfig (48-78)
internal/git/repository.go (1)
  • NewRepository (16-21)
cmd/wtp/completion.go (2)
  • printWorktriesForRemove (799-808)
  • printWorktrees (707-741)
cmd/wtp/shell_init.go (2)
cmd/wtp/completion.go (1)
  • NewCompletionCommand (61-167)
cmd/wtp/completion_test.go (1)
  • TestCompletionBash (49-68)
cmd/wtp/cd_test.go (2)
cmd/wtp/cd.go (1)
  • NewCdCommand (64-80)
internal/config/config.go (2)
  • Config (12-16)
  • Defaults (19-21)
cmd/wtp/cd.go (2)
internal/config/config.go (3)
  • Config (12-16)
  • Defaults (19-21)
  • LoadConfig (48-78)
internal/git/repository.go (1)
  • NewRepository (16-21)
🪛 GitHub Check: Lint
cmd/wtp/shell_init_test.go

[failure] 14-14:
File is not properly formatted (gofmt)


[warning] 58-58:
unused-parameter: parameter 't' seems to be unused, consider removing or renaming it as _ (revive)

cmd/wtp/remove_test.go

[failure] 837-837:
File is not properly formatted (gofmt)


[failure] 830-830:
830-864 lines are duplicate of cmd/wtp/cd_test.go:221-255 (dupl)

cmd/wtp/add_test.go

[failure] 937-937:
File is not properly formatted (gofmt)


[failure] 934-934:
934-966 lines are duplicate of cmd/wtp/cd_test.go:257-289 (dupl)

cmd/wtp/shell_init.go

[failure] 119-119:
File is not properly formatted (gofmt)

cmd/wtp/cd_test.go

[failure] 228-228:
File is not properly formatted (gofmt)


[failure] 221-221:
221-255 lines are duplicate of cmd/wtp/remove_test.go:830-864 (dupl)


[failure] 257-257:
257-289 lines are duplicate of cmd/wtp/add_test.go:934-966 (dupl)

🪛 LanguageTool
CLAUDE.md

[grammar] ~260-~260: There might be a mistake here.
Context: ...bsolute path to worktree (pure function) - Behavior: Always outputs path, no envi...

(QB_NEW_EN)


[grammar] ~261-~261: There might be a mistake here.
Context: ...uts path, no environment variable checks - No side effects: Does not change direc...

(QB_NEW_EN)


[grammar] ~265-~265: There might be a mistake here.
Context: ...**: Generate pure tab completion scripts - Implementation: Uses urfave/cli v3 sta...

(QB_NEW_EN)


[grammar] ~266-~266: There might be a mistake here.
Context: ...li v3 standard completion where possible - No shell functions: Removed wtp() func...

(QB_NEW_EN)


[grammar] ~270-~270: There might be a mistake here.
Context: ...e shell integration for cd functionality - Implementation: Outputs shell function...

(QB_NEW_EN)


[grammar] ~271-~271: There might be a mistake here.
Context: ... function that intercepts wtp cd calls - Shells supported: bash, zsh, fish **`...

(QB_NEW_EN)


[grammar] ~275-~275: There might be a mistake here.
Context: ...ence command combining completion + hook - Use case: Manual setup and lazy loadin...

(QB_NEW_EN)


[grammar] ~280-~280: There might be a mistake here.
Context: ...od | Tab Completion | cd Functionality | |-------------------|----------------|--...

(QB_NEW_EN)


[grammar] ~281-~281: There might be a mistake here.
Context: ...---|----------------|------------------| | Homebrew | Automatic | `eval "$(wt...

(QB_NEW_EN)


[grammar] ~282-~282: There might be a mistake here.
Context: ...tomatic | eval "$(wtp hook <shell>)" | | go install | `eval "$(wtp completi...

(QB_NEW_EN)


[grammar] ~303-~303: There might be a mistake here.
Context: ... 200+ lines of custom completion scripts 2. Standards Compliance: Uses urfave/cli ...

(QB_NEW_EN)


[grammar] ~304-~304: There might be a mistake here.
Context: ...e**: Uses urfave/cli built-in completion 3. Clear Separation: Completion vs. shell...

(QB_NEW_EN)


[grammar] ~305-~305: There might be a mistake here.
Context: ...etion vs. shell integration are distinct 4. User Choice: Users can choose completi...

(QB_NEW_EN)


[grammar] ~306-~306: There might be a mistake here.
Context: ...oose completion-only or full integration 5. Future-Proof: Standard patterns used t...

(QB_NEW_EN)


[grammar] ~311-~311: There might be a mistake here.
Context: ...g Change**: wtp cd no longer requires WTP_SHELL_INTEGRATION=1 - Migration Path: Replace completion set...

(QB_NEW_EN)


[grammar] ~312-~312: There might be a mistake here.
Context: ...up with separate completion + hook setup - Backward Compatibility: Old completion...

(QB_NEW_EN)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: E2E Tests (macos-latest)
  • GitHub Check: E2E Tests (ubuntu-latest)
  • GitHub Check: Test (macos-latest, 1.24)
  • GitHub Check: Test (ubuntu-latest, 1.24)
🔇 Additional comments (14)
cmd/wtp/add.go (1)

35-41: Nice: dynamic branch completion is wired into wtp add.

Hooking ShellComplete: completeBranches aligns with the new architecture and improves UX. Keep README examples consistent with this behavior.

cmd/wtp/add_test.go (1)

934-966: Fix formatting and remove duplication flagged by the linter.

  • gofmt failure at Line 937 indicates formatting drift. Run gofmt -s -w cmd/wtp/add_test.go (or go tool task dev) to satisfy the pipeline.
  • Lines 934–966 duplicate logic also present in cmd/wtp/cd_test.go:257-289. Extract a tiny helper (e.g., in cmd/wtp/testutil) and reuse it in both tests to silence the “dupl” check.

Minimal change within this file (use a helper for silencing stdout):

 func TestCompleteBranches(t *testing.T) {
   t.Run("should not panic when called", func(t *testing.T) {
     cmd := &cli.Command{}
-
-    // Should not panic even without proper git setup
-    assert.NotPanics(t, func() {
-      // Capture stdout to avoid noise in tests
-      oldStdout := os.Stdout
-      os.Stdout = os.NewFile(0, os.DevNull)
-      defer func() { os.Stdout = oldStdout }()
-
-      completeBranches(context.Background(), cmd)
-    })
+    // Should not panic even without proper git setup
+    assert.NotPanics(t, func() {
+      withSilencedStdout(t, func() {
+        completeBranches(context.Background(), cmd)
+      })
+    })
   })
 }

Add this helper once and reuse across tests (new file suggestion):

// cmd/wtp/testutil/helpers_test.go
package testutil

import (
  "os"
  "testing"
)

func WithSilencedStdout(t *testing.T, fn func()) {
  t.Helper()
  oldStdout := os.Stdout
  os.Stdout = os.NewFile(0, os.DevNull)
  defer func() { os.Stdout = oldStdout }()
  fn()
}

Then import and call testutil.WithSilencedStdout from both test files. This addresses both the formatting violation (after gofmt) and the duplication warning.

⛔ Skipped due to learnings
Learnt from: CR
PR: satococoa/wtp#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-24T14:15:20.744Z
Learning: Applies to cmd/wtp/*.go : Update command help text when adding new features.
Learnt from: CR
PR: satococoa/wtp#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-24T14:15:20.744Z
Learning: Applies to cmd/wtp/completion.go : The getWorktreeNameFromPath() function in cmd/wtp/completion.go must implement worktree naming logic: return '@' for main worktree, relative path from base_dir for non-main worktrees, and fall back to directory name if path calculation fails.
cmd/wtp/hook.go (1)

12-44: Good UX: clear help text and per-shell subcommands.

The help/description text is explicit and aligns with the PR goals (“Less is More” separation). Nice.

cmd/wtp/cd.go (1)

67-79: Help and examples for cd look good.

Clear about printing an absolute path and how to enable hooks. Matches the new architecture.

README.md (3)

253-256: Confirm Homebrew auto-completions actually ship in the tap formula

“Tab completion is automatically installed! No manual setup required.” is great if the tap installs bash/zsh/fish completion files into the proper locations. Please verify the Homebrew formula generates and installs completions (including fish) on install/upgrade; otherwise this line can mislead users.

Would you like me to open a follow-up to update the tap formula and add CI to assert the completion files are present after brew install?


274-286: Docs for wtp cd behavior are clear and align with refactor

Explaining that wtp cd always prints an absolute path and showing the command-substitution pattern is exactly what users need without the hook. Nice.


315-318: “wtp once” auto-configuration claim needs validation or clarification

Auto-configuring both completion and hooks on first tab-press is a strong claim and non-standard for Homebrew-installed CLIs. If this relies on shell-specific lazy-loading or post-install hooks, please link to the mechanism or adjust wording to avoid overpromising.

If this behavior isn’t guaranteed across shells, propose changing to: “Homebrew users: tab completion is installed automatically. For wtp cd integration, run: eval '$(wtp hook )' or use the one-liner: eval '$(wtp shell-init )'.”

cmd/wtp/main.go (1)

38-40: Good wiring: adds ‘hook’ and ‘shell-init’; rely on built-in completion

Replacing the bespoke completion command with urfave/cli’s built-in completion and introducing focused hook/shell-init commands makes the surface area smaller and clearer. LGTM.

cmd/wtp/shell_init_test.go (1)

1-76: It looks like gofmt (and possibly revive) isn’t installed in this environment, so I can’t automatically verify that the file is properly formatted or lint-clean. Please manually run:

gofmt -s -l cmd/wtp/shell_init_test.go
revive -config revive.toml ./cmd/wtp/shell_init_test.go

and confirm there’s no output (i.e., no formatting or lint issues). Once that’s done, we’ll be sure the fixes have been applied correctly.

cmd/wtp/remove.go (1)

66-95: Help text and flags look solid.

Usage, examples, and new flags (with-branch, force-branch) align with the PR’s UX goals and retrieved learnings. Good job.

cmd/wtp/hook_test.go (1)

35-100: Good coverage of hook scripts’ essential behavior.

Validates per-shell fragments and ensures we’ve removed legacy WTP_SHELL_INTEGRATION. Nice.

cmd/wtp/shell_init.go (1)

13-45: Solid UX and docs in Description.

The one-liner instructions per shell align with the PR objectives and README UX guidance.

test/e2e/shell_test.go (1)

30-49: Branch resolution path checks look good.

The relaxed path checks cover both “worktrees/feature/test” and “feature/test”, matching the new behavior. Nice.

cmd/wtp/cd_test.go (1)

3-13: Import require and normalize imports/formatting

  • In cmd/wtp/cd_test.go, add the "github.com/stretchr/testify/require" import (right after assert).
  • Run the Go formatting tools locally to ensure imports are grouped and ordered correctly:
    gofmt -s -w cmd/wtp/cd_test.go
    goimports -w cmd/wtp/cd_test.go

Updated import block:

 import (
   "bytes"
   "context"
   "os"
   "path/filepath"
   "testing"

   "github.com/satococoa/wtp/internal/config"
   "github.com/stretchr/testify/assert"
+  "github.com/stretchr/testify/require"
   "github.com/urfave/cli/v3"
 )

Comment on lines +280 to +284
| Installation Method | Tab Completion | cd Functionality |
|-------------------|----------------|------------------|
| **Homebrew** | Automatic | `eval "$(wtp hook <shell>)"` |
| **go install** | `eval "$(wtp completion <shell>)"` | `eval "$(wtp hook <shell>)"` |

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Reflect the new one-liner shell-init UX in the matrix.

The PR advertises “one-line setup: eval '$(wtp shell-init bash)'”. Add that to the table:

-| **Homebrew** | Automatic | `eval "$(wtp hook <shell>)"` |
-| **go install** | `eval "$(wtp completion <shell>)"` | `eval "$(wtp hook <shell>)"` |
+| **Homebrew**   | Automatic                                   | `eval "$(wtp shell-init <shell>)"` |
+| **go install** | `eval "$(wtp shell-init <shell>)"`          | Included in `shell-init`           |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| Installation Method | Tab Completion | cd Functionality |
|-------------------|----------------|------------------|
| **Homebrew** | Automatic | `eval "$(wtp hook <shell>)"` |
| **go install** | `eval "$(wtp completion <shell>)"` | `eval "$(wtp hook <shell>)"` |
| Installation Method | Tab Completion | cd Functionality |
|---------------------|----------------|------------------|
| **Homebrew** | Automatic | `eval "$(wtp shell-init <shell>)"` |
| **go install** | `eval "$(wtp shell-init <shell>)"` | Included in `shell-init` |
🧰 Tools
🪛 LanguageTool

[grammar] ~280-~280: There might be a mistake here.
Context: ...od | Tab Completion | cd Functionality | |-------------------|----------------|--...

(QB_NEW_EN)


[grammar] ~281-~281: There might be a mistake here.
Context: ...---|----------------|------------------| | Homebrew | Automatic | `eval "$(wt...

(QB_NEW_EN)


[grammar] ~282-~282: There might be a mistake here.
Context: ...tomatic | eval "$(wtp hook <shell>)" | | go install | `eval "$(wtp completi...

(QB_NEW_EN)

🤖 Prompt for AI Agents
In CLAUDE.md around lines 280-284, the installation matrix still shows the old
commands for tab completion and cd hook; update the table cells for Homebrew and
go install to reflect the new one-line UX by replacing the existing commands
(e.g. eval "$(wtp completion <shell>)" and eval "$(wtp hook <shell>)") with the
new shell-init one-liner (eval '$(wtp shell-init <shell>)') so the table matches
the PR description.

Comment on lines +73 to +92
func printBashHook(w io.Writer) {
fmt.Fprintln(w, `# wtp cd command hook for bash
wtp() {
if [[ "$1" == "cd" ]]; then
if [[ -z "$2" ]]; then
echo "Usage: wtp cd <worktree>" >&2
return 1
fi
local target_dir
target_dir=$(command wtp cd "$2" 2>/dev/null)
if [[ $? -eq 0 && -n "$target_dir" ]]; then
cd "$target_dir"
else
command wtp cd "$2"
fi
else
command wtp "$@"
fi
}`)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Harden bash hook: support “--”, preserve flags, and keep safety.

  • Add -- before the worktree to guard against names starting with -.
  • Current logic drops extra args (e.g. wtp cd --help). Let those pass through.
  • Current logic is fine for 1 arg, but forward "$@" to preserve behavior.

Patch inside the printed script:

 wtp() {
     if [[ "$1" == "cd" ]]; then
-        if [[ -z "$2" ]]; then
+        if [[ -z "$2" ]]; then
             echo "Usage: wtp cd <worktree>" >&2
             return 1
         fi
         local target_dir
-        target_dir=$(command wtp cd "$2" 2>/dev/null)
+        # Support worktree names that look like flags with `--`
+        target_dir=$(command wtp cd -- "$2" 2>/dev/null)
         if [[ $? -eq 0 && -n "$target_dir" ]]; then
             cd "$target_dir"
         else
-            command wtp cd "$2"
+            # Forward flags/help and any extra args to the real command
+            command wtp "$@"
         fi
     else
         command wtp "$@"
     fi
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func printBashHook(w io.Writer) {
fmt.Fprintln(w, `# wtp cd command hook for bash
wtp() {
if [[ "$1" == "cd" ]]; then
if [[ -z "$2" ]]; then
echo "Usage: wtp cd <worktree>" >&2
return 1
fi
local target_dir
target_dir=$(command wtp cd "$2" 2>/dev/null)
if [[ $? -eq 0 && -n "$target_dir" ]]; then
cd "$target_dir"
else
command wtp cd "$2"
fi
else
command wtp "$@"
fi
}`)
}
func printBashHook(w io.Writer) {
fmt.Fprintln(w, `# wtp cd command hook for bash
wtp() {
if [[ "$1" == "cd" ]]; then
if [[ -z "$2" ]]; then
echo "Usage: wtp cd <worktree>" >&2
return 1
fi
local target_dir
# Support worktree names that look like flags with `--`
target_dir=$(command wtp cd -- "$2" 2>/dev/null)
if [[ $? -eq 0 && -n "$target_dir" ]]; then
cd "$target_dir"
else
# Forward flags/help and any extra args to the real command
command wtp "$@"
fi
else
command wtp "$@"
fi
}`)
}
🤖 Prompt for AI Agents
In cmd/wtp/hook.go around lines 73 to 92, the printed bash hook drops extra
args, fails to protect worktree names starting with '-', and doesn't forward
flags; update the function body so the cd branch preserves all arguments by
checking for at least one arg but forwarding "$@" to command wtp, invoke command
wtp cd with a "--" before the worktree when capturing target_dir to guard names
starting with '-', and when successful cd use the captured target_dir, otherwise
run command wtp cd "$@" (or command wtp "$@") so flags like --help and
additional args are preserved while keeping existing safety checks for empty
args.

Comment on lines +94 to +113
func printZshHook(w io.Writer) {
fmt.Fprintln(w, `# wtp cd command hook for zsh
wtp() {
if [[ "$1" == "cd" ]]; then
if [[ -z "$2" ]]; then
echo "Usage: wtp cd <worktree>" >&2
return 1
fi
local target_dir
target_dir=$(command wtp cd "$2" 2>/dev/null)
if [[ $? -eq 0 && -n "$target_dir" ]]; then
cd "$target_dir"
else
command wtp cd "$2"
fi
else
command wtp "$@"
fi
}`)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Apply the same hardening to zsh hook.

Mirror the bash changes in the zsh variant:

 wtp() {
     if [[ "$1" == "cd" ]]; then
-        if [[ -z "$2" ]]; then
+        if [[ -z "$2" ]]; then
             echo "Usage: wtp cd <worktree>" >&2
             return 1
         fi
         local target_dir
-        target_dir=$(command wtp cd "$2" 2>/dev/null)
+        target_dir=$(command wtp cd -- "$2" 2>/dev/null)
         if [[ $? -eq 0 && -n "$target_dir" ]]; then
             cd "$target_dir"
         else
-            command wtp cd "$2"
+            command wtp "$@"
         fi
     else
         command wtp "$@"
     fi
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func printZshHook(w io.Writer) {
fmt.Fprintln(w, `# wtp cd command hook for zsh
wtp() {
if [[ "$1" == "cd" ]]; then
if [[ -z "$2" ]]; then
echo "Usage: wtp cd <worktree>" >&2
return 1
fi
local target_dir
target_dir=$(command wtp cd "$2" 2>/dev/null)
if [[ $? -eq 0 && -n "$target_dir" ]]; then
cd "$target_dir"
else
command wtp cd "$2"
fi
else
command wtp "$@"
fi
}`)
}
func printZshHook(w io.Writer) {
fmt.Fprintln(w, `# wtp cd command hook for zsh
wtp() {
if [[ "$1" == "cd" ]]; then
if [[ -z "$2" ]]; then
echo "Usage: wtp cd <worktree>" >&2
return 1
fi
local target_dir
target_dir=$(command wtp cd -- "$2" 2>/dev/null)
if [[ $? -eq 0 && -n "$target_dir" ]]; then
cd "$target_dir"
else
command wtp "$@"
fi
else
command wtp "$@"
fi
}`)
}

Comment on lines +115 to +133
func printFishHook(w io.Writer) {
fmt.Fprintln(w, `# wtp cd command hook for fish
function wtp
if test "$argv[1]" = "cd"
if test -z "$argv[2]"
echo "Usage: wtp cd <worktree>" >&2
return 1
end
set -l target_dir (command wtp cd $argv[2] 2>/dev/null)
if test $status -eq 0 -a -n "$target_dir"
cd $target_dir
else
command wtp cd $argv[2]
end
else
command wtp $argv
end
end`)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Quote expansions and support -- in fish hook.

  • Quote $argv[2] and $target_dir to handle spaces.
  • Pass -- before $argv[2] to guard against names starting with -.
  • Forward unknown/extra args back to wtp for help/flags.
 function wtp
     if test "$argv[1]" = "cd"
         if test -z "$argv[2]"
             echo "Usage: wtp cd <worktree>" >&2
             return 1
         end
-        set -l target_dir (command wtp cd $argv[2] 2>/dev/null)
-        if test $status -eq 0 -a -n "$target_dir"
-            cd $target_dir
+        set -l target_dir (command wtp cd -- "$argv[2]" 2>/dev/null)
+        if test $status -eq 0 -a -n "$target_dir"
+            cd "$target_dir"
         else
-            command wtp cd $argv[2]
+            command wtp $argv
         end
     else
         command wtp $argv
     end
 end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func printFishHook(w io.Writer) {
fmt.Fprintln(w, `# wtp cd command hook for fish
function wtp
if test "$argv[1]" = "cd"
if test -z "$argv[2]"
echo "Usage: wtp cd <worktree>" >&2
return 1
end
set -l target_dir (command wtp cd $argv[2] 2>/dev/null)
if test $status -eq 0 -a -n "$target_dir"
cd $target_dir
else
command wtp cd $argv[2]
end
else
command wtp $argv
end
end`)
}
func printFishHook(w io.Writer) {
fmt.Fprintln(w, `# wtp cd command hook for fish
function wtp
if test "$argv[1]" = "cd"
if test -z "$argv[2]"
echo "Usage: wtp cd <worktree>" >&2
return 1
end
set -l target_dir (command wtp cd -- "$argv[2]" 2>/dev/null)
if test $status -eq 0 -a -n "$target_dir"
cd "$target_dir"
else
command wtp $argv
end
else
command wtp $argv
end
end`)
}

Comment on lines +30 to 33
expectedCommands := []string{"add", "list", "remove", "init", "cd"}
for _, expected := range expectedCommands {
assert.True(t, commandNames[expected], "Command %s should exist", expected)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Keep unit test command lists in sync with the real CLI (include hook and shell-init)

The app constructed in tests omits hook and shell-init, so tests won’t catch regressions for these new commands and will drift from the real binary’s UX.

Apply:

-		expectedCommands := []string{"add", "list", "remove", "init", "cd"}
+		expectedCommands := []string{"add", "list", "remove", "init", "cd", "hook", "shell-init"}

And also include the commands in createApp (see below).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expectedCommands := []string{"add", "list", "remove", "init", "cd"}
for _, expected := range expectedCommands {
assert.True(t, commandNames[expected], "Command %s should exist", expected)
}
expectedCommands := []string{"add", "list", "remove", "init", "cd", "hook", "shell-init"}
for _, expected := range expectedCommands {
assert.True(t, commandNames[expected], "Command %s should exist", expected)
}
🤖 Prompt for AI Agents
In cmd/wtp/main_test.go around lines 30 to 33, the test's expectedCommands slice
is missing the new "hook" and "shell-init" commands and the test app used in
createApp also omits them; update the expectedCommands list to include "hook"
and "shell-init", and modify the createApp helper to register the real CLI's
hook and shell-init commands so the test mirrors the real binary's command set.

Comment on lines +145 to 146
// NewCompletionCommand(), // Using built-in completion
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add new commands to the test app to avoid drift

Include the new commands in createApp() so test coverage reflects the real CLI’s command surface.

 			NewCdCommand(),
-			// NewCompletionCommand(), // Using built-in completion
+			// Built-in completion is enabled via EnableShellCompletion
+			NewHookCommand(),
+			NewShellInitCommand(),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// NewCompletionCommand(), // Using built-in completion
},
NewCdCommand(),
// Built-in completion is enabled via EnableShellCompletion
NewHookCommand(),
NewShellInitCommand(),
},
🤖 Prompt for AI Agents
In cmd/wtp/main_test.go around lines 145-146, the test app's createApp() is
missing recently added commands (e.g., the completion command); update
createApp() to register the new CLI commands such as NewCompletionCommand() and
any other commands added to the real CLI so the test harness mirrors the actual
command surface — modify the commands slice/registration in createApp() to
include these constructors and ensure tests exercise them.

Comment on lines +287 to +301
#### With Shell Hook (Recommended)

For a more seamless experience, enable the shell hook:

```bash
# Bash: Add to ~/.bashrc
eval "$(wtp hook bash)"
# Zsh: Add to ~/.zshrc
eval "$(wtp hook zsh)"
# Fish: Add to ~/.config/fish/config.fish
wtp hook fish | source
```

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Recommend adding the “shell-init” one-liner alongside “hook”

You introduced wtp shell-init, but this section only documents wtp hook. For parity and to match the PR goal of “one-line setup,” please include the shell-init variant right here so users don’t miss it.

Apply the following to add an “All-in-one setup” subsection:

@@
 #### With Shell Hook (Recommended)
@@
 For a more seamless experience, enable the shell hook:
@@
 wtp hook fish | source

+#### All-in-one Setup (Recommended)
+
+Use wtp shell-init to enable both tab completion and the wtp cd hook in one step:
+
+bash +# Bash: Add to ~/.bashrc +eval "$(wtp shell-init bash)" + +# Zsh: Add to ~/.zshrc +eval "$(wtp shell-init zsh)" + +# Fish: Add to ~/.config/fish/config.fish +wtp shell-init fish | source +
+
Then use the simplified syntax:


<details>
<summary>🤖 Prompt for AI Agents</summary>

In README.md around lines 287 to 301, the "With Shell Hook (Recommended)"
section documents only wtp hook but omits the new wtp shell-init one-liner;
add an "All-in-one Setup (Recommended)" subsection immediately before the line
"Then use the simplified syntax:" that shows the one-liner usage for Bash, Zsh,
and Fish (eval "$(wtp shell-init bash)", eval "$(wtp shell-init zsh)", and wtp
shell-init fish | source respectively) so users can enable both tab completion
and the wtp cd hook in a single step.


</details>

<!-- fingerprinting:phantom:poseidon:chinchilla -->

<!-- This is an auto-generated comment by CodeRabbit -->

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
cmd/wtp/cd.go (3)

28-43: Unify base_dir resolution and path checks; avoid Config.ResolveWorktreePath dependency.

Use the same resolution logic as getWorktreeNameFromPathCd and normalize paths with filepath.Clean for cross‑platform correctness. Also removes reliance on a method that may not exist.

Apply:

-	// Get base directory - use default config if config is not available
-	if cfg == nil {
-		// Create default config when none is available
-		defaultCfg := &config.Config{
-			Defaults: config.Defaults{
-				BaseDir: "../worktrees",
-			},
-		}
-		cfg = defaultCfg
-	}
-
-	baseDir := cfg.ResolveWorktreePath(mainRepoPath, "")
-	// Remove trailing slash if it exists
-	baseDir = strings.TrimSuffix(baseDir, "/")
+	// Get base directory - use default when cfg is nil
+	if cfg == nil {
+		cfg = &config.Config{Defaults: config.Defaults{BaseDir: "../worktrees"}}
+	}
+	baseDir := cfg.Defaults.BaseDir
+	if !filepath.IsAbs(baseDir) {
+		baseDir = filepath.Join(mainRepoPath, baseDir)
+	}
+	baseDir = filepath.Clean(baseDir)
@@
-	// If relative path starts with "..", it's outside base directory
-	return !strings.HasPrefix(relPath, "..")
+	// If relative path starts with "..", it's outside base directory
+	return !strings.HasPrefix(relPath, "..")

Also applies to: 49-62


156-160: Use cd-specific naming for suggestions.

Keep naming consistent with cd behavior (relative to base_dir), not any legacy/global variant.

Apply:

-					name := getWorktreeNameFromPath(worktrees[i].Path, cfg, mainRepoPath, worktrees[i].IsMain)
+					name := getWorktreeNameFromPathCd(worktrees[i].Path, cfg, mainRepoPath, worktrees[i].IsMain)

216-221: Bug risk: wrong helper used for direct matches.

This should match cd’s naming semantics; otherwise user input that matches cd-style names won’t resolve.

Apply:

-	if cfg != nil {
-		worktreeNameFromPath := getWorktreeNameFromPath(wt.Path, cfg, mainWorktreePath, wt.IsMain)
+	if cfg != nil {
+		worktreeNameFromPath := getWorktreeNameFromPathCd(wt.Path, cfg, mainWorktreePath, wt.IsMain)
♻️ Duplicate comments (1)
cmd/wtp/cd_test.go (1)

228-241: Bug: stdout redirection uses invalid FD; open /dev/null for writing.

This is a repeat of a prior note; fix to avoid flakiness and resource leaks.

Apply:

@@
 	assert.NotPanics(t, func() {
 		// Capture stdout to avoid noise in tests
-		oldStdout := os.Stdout
-		os.Stdout = os.NewFile(0, os.DevNull)
-		defer func() { os.Stdout = oldStdout }()
+		devNull, err := os.OpenFile(os.DevNull, os.O_WRONLY, 0)
+		require.NoError(t, err)
+		defer devNull.Close()
+		oldStdout := os.Stdout
+		os.Stdout = devNull
+		defer func() { os.Stdout = oldStdout }()
@@
 		completeWorktreesForCd(context.Background(), cmd)
 	})
 })

And add the import:

@@
-	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
🧹 Nitpick comments (16)
.golangci.yml (1)

90-90: Unlimited same-issue reporting is OK; confirm CI noise tolerance.

max-same-issues: 0 can create noisy reports on mass violations. If this was intentional for migration, ignore; otherwise consider restoring a small cap (e.g., 50).

cmd/wtp/remove.go (7)

34-39: Use config.DefaultBaseDir for consistency.

Hardcoding "../worktrees" diverges from config.DefaultBaseDir used elsewhere. Prefer the constant for one source of truth.

-        defaultCfg := &config.Config{
-            Defaults: config.Defaults{
-                BaseDir: "../worktrees",
-            },
-        }
+        defaultCfg := &config.Config{
+            Defaults: config.Defaults{
+                BaseDir: config.DefaultBaseDir,
+            },
+        }

42-45: Normalize baseDir with filepath.Clean instead of trimming "/" (Windows-safe).

strings.TrimSuffix(baseDir, "/") misses "" on Windows. Clean once and drop manual trimming.

- baseDir := cfg.ResolveWorktreePath(mainRepoPath, "")
- // Remove trailing slash if it exists
- baseDir = strings.TrimSuffix(baseDir, "/")
+ baseDir := filepath.Clean(cfg.ResolveWorktreePath(mainRepoPath, ""))

58-65: Tighten “inside base dir” check to avoid false positives.

Prefix test should consider path separator and the exact ".." case.

- // If relative path starts with "..", it's outside base directory
- return !strings.HasPrefix(relPath, "..")
+ // Outside if ".." or starts with "..{sep}"
+ sep := string(os.PathSeparator)
+ return relPath != ".." && !strings.HasPrefix(relPath, ".."+sep)

141-149: Guard against empty ExecutionResult to avoid index panic.

Executor contracts can change; add a sanity check before indexing Results[0].

 result, err := executor.Execute([]command.Command{listCmd})
 if err != nil {
     return errors.GitCommandFailed("git worktree list", err.Error())
 }
 
-// Parse worktrees from command output
-worktrees := parseWorktreesFromOutput(result.Results[0].Output)
+if len(result.Results) == 0 {
+    return errors.GitCommandFailed("git worktree list", "empty result")
+}
+// Parse worktrees from command output
+worktrees := parseWorktreesFromOutput(result.Results[0].Output)

290-304: Unify name derivation via ResolveWorktreePath and normalize slashes.

Keeps behavior consistent with isWorktreeManaged and produces portable display names (forward slashes) across platforms.

- // Get base_dir path
- baseDir := cfg.Defaults.BaseDir
- if !filepath.IsAbs(baseDir) {
-     baseDir = filepath.Join(mainRepoPath, baseDir)
- }
+ // Resolve base_dir path consistently with config
+ baseDir := cfg.ResolveWorktreePath(mainRepoPath, "")
 
 // Calculate relative path from base_dir
 relPath, err := filepath.Rel(baseDir, worktreePath)
 if err != nil {
     // Fallback to directory name
     return filepath.Base(worktreePath)
 }
 
-return relPath
+return filepath.ToSlash(relPath)

338-346: Optional: sort candidates for deterministic completion UX.

Sorting names before printing yields stable completion ordering.

- for i := range worktrees {
+ var names []string
+ for i := range worktrees {
     wt := &worktrees[i]
     if !wt.IsMain && isWorktreeManaged(wt.Path, cfg, mainRepoPath, wt.IsMain) {
         // Calculate worktree name as relative path from base_dir
         name := getWorktreeNameFromPath(wt.Path, cfg, mainRepoPath, wt.IsMain)
-        fmt.Fprintln(w, name)
+        names = append(names, name)
     }
 }
+ sort.Strings(names)
+ for _, n := range names {
+     fmt.Fprintln(w, n)
+ }

352-363: Surface scanner errors (non-fatal) for easier debugging.

Print a debug line on scanner error; keeps ShellComplete signature unchanged.

 scanner := bufio.NewScanner(&buf)
 for scanner.Scan() {
     fmt.Println(scanner.Text())
 }
+_ = scanner.Err() // intentionally ignored; consider logging if you add a logger
cmd/wtp/shell_init.go (2)

13-22: Capture stderr from completion generation (better diagnostics).

Use CombinedOutput so failures include stderr text from the completion command.

 var runCompletionCommand = func(shell string) ([]byte, error) {
     exe, err := os.Executable()
     if err != nil {
         // Fallback to "wtp" if we can't find the executable
         exe = "wtp"
     }
 
-    cmd := exec.Command(exe, "completion", shell)
-    return cmd.Output()
+    cmd := exec.Command(exe, "completion", shell)
+    return cmd.CombinedOutput()
 }

114-122: Include command output on error to ease troubleshooting.

Propagate the captured output when completion generation fails.

 func outputCompletion(w io.Writer, shell string) error {
     output, err := runCompletionCommand(shell)
     if err != nil {
-        return fmt.Errorf("failed to generate %s completion: %w", shell, err)
+        return fmt.Errorf("failed to generate %s completion: %w\n%s", shell, err, string(output))
     }
 
     _, err = w.Write(output)
     return err
 }
cmd/wtp/add_test.go (1)

943-947: Fix stdout redirection to /dev/null (fd must be writable; don’t use fd=0).

os.NewFile(0, ...) targets stdin. Open /dev/null for writing instead to avoid EBADF on writes.

-            oldStdout := os.Stdout
-            os.Stdout = os.NewFile(0, os.DevNull)
-            defer func() { os.Stdout = oldStdout }()
+            oldStdout := os.Stdout
+            devnull, _ := os.OpenFile(os.DevNull, os.O_WRONLY, 0)
+            defer func() {
+                _ = devnull.Close()
+                os.Stdout = oldStdout
+            }()
+            os.Stdout = devnull
cmd/wtp/testhelpers_test.go (2)

31-34: Don’t ignore errors from os.Getwd; assert and restore safely.

Capture and assert the error from os.Getwd to avoid masking failures.

Apply:

- oldDir, _ := os.Getwd()
- t.Cleanup(func() { _ = os.Chdir(oldDir) })
- assert.NoError(t, os.Chdir(tempDir))
+ oldDir, err := os.Getwd()
+ assert.NoError(t, err)
+ t.Cleanup(func() { _ = os.Chdir(oldDir) })
+ assert.NoError(t, os.Chdir(tempDir))

61-67: Make expected relative path robust across platforms.

Compute expected via filepath.Rel instead of hard‑coding "../../../../...".

Apply:

- name := fn("/completely/different/path", cfg, "/path/to/repo", false)
- assert.Equal(t, "../../../../completely/different/path", name)
+ baseDir := filepath.Join("/path/to/repo", ".worktrees")
+ name := fn("/completely/different/path", cfg, "/path/to/repo", false)
+ expected, err := filepath.Rel(baseDir, "/completely/different/path")
+ assert.NoError(t, err)
+ assert.Equal(t, expected, name)
cmd/wtp/cd.go (3)

69-80: Expose the new one‑liner in help: reference ‘shell-init’.

Align CLI help with the PR objective “eval "$(wtp shell-init bash)"”.

Apply:

 		Description: "Output the absolute path to the specified worktree.\n\n" +
 			"Usage:\n" +
 			"  Direct:     cd \"$(wtp cd feature)\"\n" +
-			"  With hook:  wtp cd feature\n\n" +
-			"To enable the hook for easier navigation:\n" +
-			"  Bash: eval \"$(wtp hook bash)\"\n" +
-			"  Zsh:  eval \"$(wtp hook zsh)\"\n" +
-			"  Fish: wtp hook fish | source",
+			"  With hook:  wtp cd feature\n\n" +
+			"Setup:\n" +
+			"  One‑liner:  eval \"$(wtp shell-init bash)\"\n" +
+			"  Hook only:  eval \"$(wtp hook bash)\"",

121-129: Guard against empty executor results to avoid panic.

Check Results length before indexing.

Apply:

 	result, err := executor.Execute([]command.Command{listCmd})
 	if err != nil {
 		return fmt.Errorf("failed to get worktrees: %w", err)
 	}
+	if len(result.Results) == 0 {
+		return fmt.Errorf("failed to get worktrees: no results returned")
+	}
 
 	// Parse worktrees from command output
 	worktrees := parseWorktreesFromOutput(result.Results[0].Output)

281-294: Handle relPath == "." gracefully.

When a non‑main worktree equals base_dir (edge case), avoid returning "." as a name; fall back to basename.

Apply:

 	relPath, err := filepath.Rel(baseDir, worktreePath)
 	if err != nil {
 		// Fallback to directory name
 		return filepath.Base(worktreePath)
 	}
 
-	return relPath
+	if relPath == "." {
+		return filepath.Base(worktreePath)
+	}
+	return relPath
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eca1d0e and 1acacc7.

📒 Files selected for processing (16)
  • .golangci.yml (3 hunks)
  • cmd/wtp/add.go (2 hunks)
  • cmd/wtp/add_test.go (2 hunks)
  • cmd/wtp/cd.go (3 hunks)
  • cmd/wtp/cd_test.go (2 hunks)
  • cmd/wtp/init.go (1 hunks)
  • cmd/wtp/list.go (1 hunks)
  • cmd/wtp/list_test.go (1 hunks)
  • cmd/wtp/remove.go (2 hunks)
  • cmd/wtp/remove_test.go (2 hunks)
  • cmd/wtp/shell_init.go (1 hunks)
  • cmd/wtp/shell_init_test.go (1 hunks)
  • cmd/wtp/testhelpers_test.go (1 hunks)
  • internal/hooks/executor_test.go (1 hunks)
  • test/e2e/hook_streaming_test.go (1 hunks)
  • test/e2e/integration_test.go (1 hunks)
✅ Files skipped from review due to trivial changes (5)
  • cmd/wtp/list_test.go
  • internal/hooks/executor_test.go
  • cmd/wtp/list.go
  • cmd/wtp/init.go
  • test/e2e/hook_streaming_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • cmd/wtp/shell_init_test.go
  • cmd/wtp/remove_test.go
  • cmd/wtp/add.go
🧰 Additional context used
📓 Path-based instructions (5)
**/*.go

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.go: Use gofmt and goimports for all Go code formatting
Follow lint rules (vet, staticcheck, gosec, lll=120, avoid ignored errors)
Keep functions focused and small; use Go idioms
Use short, lowercase package names

Files:

  • test/e2e/integration_test.go
  • cmd/wtp/shell_init.go
  • cmd/wtp/testhelpers_test.go
  • cmd/wtp/add_test.go
  • cmd/wtp/remove.go
  • cmd/wtp/cd_test.go
  • cmd/wtp/cd.go
**/*_test.go

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Write tests as table-driven tests and name test files with *_test.go

Files:

  • test/e2e/integration_test.go
  • cmd/wtp/testhelpers_test.go
  • cmd/wtp/add_test.go
  • cmd/wtp/cd_test.go
cmd/wtp/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Update command help text when adding new features.

Files:

  • cmd/wtp/shell_init.go
  • cmd/wtp/testhelpers_test.go
  • cmd/wtp/add_test.go
  • cmd/wtp/remove.go
  • cmd/wtp/cd_test.go
  • cmd/wtp/cd.go
cmd/wtp/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Keep CLI UX consistent with README examples and autocompletion behavior

Files:

  • cmd/wtp/shell_init.go
  • cmd/wtp/testhelpers_test.go
  • cmd/wtp/add_test.go
  • cmd/wtp/remove.go
  • cmd/wtp/cd_test.go
  • cmd/wtp/cd.go
.golangci.yml

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Respect lint configuration defined in .golangci.yml

Files:

  • .golangci.yml
🧠 Learnings (9)
📓 Common learnings
Learnt from: CR
PR: satococoa/wtp#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-11T13:56:29.054Z
Learning: Applies to cmd/wtp/** : Keep CLI UX consistent with README examples and autocompletion behavior
📚 Learning: 2025-07-24T14:15:20.744Z
Learnt from: CR
PR: satococoa/wtp#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-24T14:15:20.744Z
Learning: Applies to cmd/wtp/*.go : Update command help text when adding new features.

Applied to files:

  • cmd/wtp/shell_init.go
  • cmd/wtp/testhelpers_test.go
  • cmd/wtp/add_test.go
  • cmd/wtp/remove.go
  • cmd/wtp/cd_test.go
  • cmd/wtp/cd.go
📚 Learning: 2025-07-24T14:15:20.744Z
Learnt from: CR
PR: satococoa/wtp#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-24T14:15:20.744Z
Learning: Before committing any changes, always run 'go tool task dev' to ensure code is formatted, linted, and tested.

Applied to files:

  • .golangci.yml
📚 Learning: 2025-08-11T13:56:29.054Z
Learnt from: CR
PR: satococoa/wtp#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-11T13:56:29.054Z
Learning: Applies to **/*.go : Follow lint rules (vet, staticcheck, gosec, lll=120, avoid ignored errors)

Applied to files:

  • .golangci.yml
📚 Learning: 2025-08-11T13:56:29.054Z
Learnt from: CR
PR: satococoa/wtp#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-11T13:56:29.054Z
Learning: Applies to **/*.go : Keep functions focused and small; use Go idioms

Applied to files:

  • .golangci.yml
📚 Learning: 2025-08-11T13:56:29.054Z
Learnt from: CR
PR: satococoa/wtp#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-11T13:56:29.054Z
Learning: Applies to **/*.go : Use gofmt and goimports for all Go code formatting

Applied to files:

  • .golangci.yml
📚 Learning: 2025-08-11T13:56:29.054Z
Learnt from: CR
PR: satococoa/wtp#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-11T13:56:29.054Z
Learning: Applies to **/*.go : Use short, lowercase package names

Applied to files:

  • .golangci.yml
📚 Learning: 2025-08-11T13:56:29.054Z
Learnt from: CR
PR: satococoa/wtp#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-11T13:56:29.054Z
Learning: Applies to **/*_test.go : Write tests as table-driven tests and name test files with *_test.go

Applied to files:

  • cmd/wtp/testhelpers_test.go
📚 Learning: 2025-07-24T14:15:20.744Z
Learnt from: CR
PR: satococoa/wtp#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-24T14:15:20.744Z
Learning: Applies to cmd/wtp/completion.go : The getWorktreeNameFromPath() function in cmd/wtp/completion.go must implement worktree naming logic: return '@' for main worktree, relative path from base_dir for non-main worktrees, and fall back to directory name if path calculation fails.

Applied to files:

  • cmd/wtp/add_test.go
  • cmd/wtp/remove.go
  • cmd/wtp/cd_test.go
  • cmd/wtp/cd.go
🧬 Code graph analysis (5)
cmd/wtp/testhelpers_test.go (1)
internal/config/config.go (2)
  • Config (12-16)
  • Defaults (19-21)
cmd/wtp/add_test.go (1)
cmd/wtp/testhelpers_test.go (1)
  • RunWriterCommonTests (18-38)
cmd/wtp/remove.go (2)
internal/config/config.go (3)
  • Config (12-16)
  • Defaults (19-21)
  • LoadConfig (48-78)
internal/git/repository.go (1)
  • NewRepository (16-21)
cmd/wtp/cd_test.go (2)
cmd/wtp/cd.go (1)
  • NewCdCommand (65-81)
cmd/wtp/testhelpers_test.go (2)
  • RunNameFromPathTests (42-68)
  • RunWriterCommonTests (18-38)
cmd/wtp/cd.go (2)
internal/config/config.go (3)
  • Config (12-16)
  • Defaults (19-21)
  • LoadConfig (48-78)
internal/git/repository.go (1)
  • NewRepository (16-21)
🔇 Additional comments (13)
.golangci.yml (1)

29-29: goimports local-prefixes looks correct for module path.

Matches github.com/satococoa/wtp and will group imports as expected.

cmd/wtp/remove.go (1)

183-190: Confirm error wording for missing worktree name.

errors.BranchNameRequired(...) may produce “branch name is required”, which is misleading for remove. Ensure the message guides: “worktree name is required”.

Do we have errors.WorktreeNameRequired or a generic invalid‑argument helper to use here?

cmd/wtp/shell_init.go (1)

24-56: Verify that the root command registers a “completion” subcommand.

shell-init shells out to wtp completion . Ensure main wires cli.NewCompletionCommand(); otherwise this will fail at runtime.

test/e2e/integration_test.go (1)

8-10: Import reorder is fine.

No behavior change; keeps grouping consistent.

cmd/wtp/add_test.go (1)

933-935: Nice: writer-based helper coverage for getBranches.

Uses shared test harness and keeps completion logic decoupled.

cmd/wtp/testhelpers_test.go (1)

15-18: Helpful, reusable test scaffolding.

Good use of t.Helper and subtests to centralize common assertions for writer-based functions.

cmd/wtp/cd_test.go (5)

16-77: Core guarantee validated: absolute path output.

Nice, focused table tests covering main, branch, dir-name, and nonexistent cases.


80-123: Environment independence verified.

Good regression guard against WTP_SHELL_INTEGRATION affecting resolution.


125-159: Clear user-facing error path covered.

CLI wiring and error message are asserted appropriately.


161-207: Edge cases look solid.

Special chars, completion marker trimming, and empty list handling are exercised.


209-216: Usage string assertion aligns with refactor.

Metadata checks are concise and targeted.

cmd/wtp/cd.go (2)

358-369: Completion wrapper is compatible with urfave/cli v3.

Buffering then printing line‑by‑line is fine; silent return on error avoids shell noise.


28-43: Config.ResolveWorktreePath exists and is used (no compile issue).

internal/config/config.go defines func (c *Config) ResolveWorktreePath(repoRoot, worktreeName string) string; it’s called from cmd/wtp/{cd.go, add.go, list.go, remove.go} and exercised in internal/config/config_test.go. Verify cmd/wtp/completion.go (getWorktreeNameFromPath) uses the same base_dir resolution logic to avoid divergent behavior.

Comment on lines +15 to 18
- github.com/satococoa/wtp
- github.com/urfave/cli/v3
- go.yaml.in/yaml/v3
govet:
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix depguard allowlist import path for yaml.v3.

The allowed import path is misspelled; it should be gopkg.in/yaml.v3. As-is, depguard can block legitimate imports.

Apply this diff:

-          - go.yaml.in/yaml/v3
+          - gopkg.in/yaml.v3
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- github.com/satococoa/wtp
- github.com/urfave/cli/v3
- go.yaml.in/yaml/v3
govet:
- github.com/satococoa/wtp
- github.com/urfave/cli/v3
- gopkg.in/yaml.v3
govet:
🤖 Prompt for AI Agents
In .golangci.yml around lines 15 to 18, the depguard allowlist contains the
misspelled import path "go.yaml.in/yaml/v3"; replace that entry with the correct
"gopkg.in/yaml.v3" so legitimate yaml.v3 imports are allowed and depguard won't
block them.

@satococoa
Copy link
Owner Author

Additional Notes

  • Adjusted goimports local-prefixes to github.com/satococoa/wtp so local packages are grouped correctly.
  • Unified completion-related tests (add, cd, remove, shell-init) through shared helpers and made the completion command invocations mockable in tests.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/errors/errors.go (1)

260-269: Update shell integration guidance to wtp shell-init / wtp hook; avoid implying cd requires integration.

Change the message in internal/errors/errors.go as suggested and replace all remaining "wtp completion" references so messaging and tests remain consistent.

  • internal/errors/errors.go (lines ~260-269) — apply the new message (use wtp shell-init / wtp hook, remove "cd command requires" phrasing).
  • internal/errors/errors_test.go (lines ~362-364) — update assertions that expect "wtp completion".
  • README.md (262-269), CLAUDE.md (264-265), docs/development-history.md (39-41), docs/architecture.md (180-182) — update docs/examples referencing wtp completion.
  • cmd/wtp/shell_init.go (29-31, 112-113) — ensure help text/implementation align with shell-init/hook.

Re-run the earlier grep/search to confirm no remaining wtp completion references and update tests accordingly.

🧹 Nitpick comments (5)
internal/errors/errors_test.go (2)

357-365: Update expectations to shell-init.

Error copy should reference wtp shell-init, not wtp completion.

Apply:

 func TestShellIntegrationRequired(t *testing.T) {
 	err := ShellIntegrationRequired()

 	assert.Error(t, err)
 	assert.Contains(t, err.Error(), "shell integration")
 	assert.Contains(t, err.Error(), "eval")
-	assert.Contains(t, err.Error(), "wtp completion")
+	assert.Contains(t, err.Error(), "wtp shell-init")
 	assert.Contains(t, err.Error(), "Setup:")
 }

433-436: Keep generic “helpful content” test aligned with new command name.

Apply:

 		{
 			name:     "ShellIntegrationRequired contains setup",
 			errorFn:  ShellIntegrationRequired,
-			keywords: []string{"Setup:", "eval", "completion"},
+			keywords: []string{"Setup:", "eval", "shell-init"},
 		},
cmd/wtp/remove_test.go (3)

845-849: Fix stdout redirection: using fd 0 (stdin) is wrong; open /dev/null for write.

os.NewFile(0, os.DevNull) targets fd 0 and doesn’t open the path; writes may fail subtly. Open os.DevNull with O_WRONLY and assign that to os.Stdout.

Apply:

-			// Capture stdout to avoid noise in tests
-			oldStdout := os.Stdout
-			os.Stdout = os.NewFile(0, os.DevNull)
-			defer func() { os.Stdout = oldStdout }()
+			// Capture stdout to avoid noise in tests
+			oldStdout := os.Stdout
+			devNull, _ := os.OpenFile(os.DevNull, os.O_WRONLY, 0)
+			defer devNull.Close()
+			os.Stdout = devNull
+			defer func() { os.Stdout = oldStdout }()

790-818: Improve mock result sequencing across multiple commands in a single Execute call.

Current logic returns the same m.results[m.callCount] for every command in the batch. If production ever batches >1 command per call, results won’t map 1:1. Safer to advance per-command.

Apply:

 func (m *mockRemoveCommandExecutor) Execute(commands []command.Command) (*command.ExecutionResult, error) {
 	// Accumulate all commands instead of overwriting
 	m.executedCommands = append(m.executedCommands, commands...)

 	if m.shouldFail && m.callCount > 0 {
 		errorMsg := m.errorMsg
 		if errorMsg == "" {
 			errorMsg = "mock error"
 		}
 		return nil, &mockRemoveError{message: errorMsg}
 	}

-	results := make([]command.Result, len(commands))
-	for i, cmd := range commands {
-		if m.callCount < len(m.results) {
-			results[i] = m.results[m.callCount]
-			results[i].Command = cmd
-		} else {
-			results[i] = command.Result{
-				Command: cmd,
-				Output:  "",
-				Error:   nil,
-			}
-		}
-	}
+	results := make([]command.Result, len(commands))
+	for i, cmd := range commands {
+		idx := m.callCount + i
+		if idx < len(m.results) {
+			results[i] = m.results[idx]
+			results[i].Command = cmd
+		} else {
+			results[i] = command.Result{Command: cmd}
+		}
+	}

-	m.callCount++
+	m.callCount += len(commands)
 	return &command.ExecutionResult{Results: results}, nil
 }

121-124: This subtest doesn’t validate anything. Either assert real behavior or drop it.

It only asserts true. Consider validating that --force-branch without --branch is either rejected or no-ops with a user-facing hint, depending on your intended UX.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1acacc7 and d665ba5.

📒 Files selected for processing (4)
  • cmd/wtp/remove.go (3 hunks)
  • cmd/wtp/remove_test.go (3 hunks)
  • internal/errors/errors.go (1 hunks)
  • internal/errors/errors_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/wtp/remove.go
🧰 Additional context used
📓 Path-based instructions (4)
**/*.go

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.go: Use gofmt and goimports for all Go code formatting
Follow lint rules (vet, staticcheck, gosec, lll=120, avoid ignored errors)
Keep functions focused and small; use Go idioms
Use short, lowercase package names

Files:

  • internal/errors/errors.go
  • internal/errors/errors_test.go
  • cmd/wtp/remove_test.go
**/*_test.go

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Write tests as table-driven tests and name test files with *_test.go

Files:

  • internal/errors/errors_test.go
  • cmd/wtp/remove_test.go
cmd/wtp/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Update command help text when adding new features.

Files:

  • cmd/wtp/remove_test.go
cmd/wtp/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Keep CLI UX consistent with README examples and autocompletion behavior

Files:

  • cmd/wtp/remove_test.go
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
PR: satococoa/wtp#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-11T13:56:29.054Z
Learning: Applies to cmd/wtp/** : Keep CLI UX consistent with README examples and autocompletion behavior
📚 Learning: 2025-07-24T14:15:20.744Z
Learnt from: CR
PR: satococoa/wtp#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-24T14:15:20.744Z
Learning: Applies to cmd/wtp/completion.go : The getWorktreeNameFromPath() function in cmd/wtp/completion.go must implement worktree naming logic: return '@' for main worktree, relative path from base_dir for non-main worktrees, and fall back to directory name if path calculation fails.

Applied to files:

  • internal/errors/errors.go
  • internal/errors/errors_test.go
  • cmd/wtp/remove_test.go
📚 Learning: 2025-07-24T14:15:20.744Z
Learnt from: CR
PR: satococoa/wtp#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-24T14:15:20.744Z
Learning: Applies to cmd/wtp/*.go : Update command help text when adding new features.

Applied to files:

  • cmd/wtp/remove_test.go
🧬 Code graph analysis (2)
internal/errors/errors_test.go (1)
internal/errors/errors.go (1)
  • WorktreeNameRequiredForRemove (59-71)
cmd/wtp/remove_test.go (1)
cmd/wtp/testhelpers_test.go (2)
  • RunNameFromPathTests (42-68)
  • RunWriterCommonTests (18-38)
🔇 Additional comments (1)
cmd/wtp/remove_test.go (1)

830-836: Nice reuse of shared helpers for name/path and writer safety.

Keeps behavior consistent across commands and reduces duplication.

Comment on lines +307 to 310
name: "no worktree name",
args: []string{},
expectedError: "branch name is required",
expectedError: "worktree name is required",
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Good: error message expectation matches new helper.

Run to ensure no remaining tests still expect --with-branch:


🏁 Script executed:

#!/bin/bash
rg -n "\bwith-branch\b" --type=go -C1

Length of output: 1594


Remove remaining --with-branch references and update tests

rg shows live references; remove or update all --with-branch occurrences (help text, flag definition, and tests) in: cmd/wtp/remove.go, cmd/wtp/remove_test.go, internal/errors/errors.go, internal/errors/errors_test.go. Re-run rg -n '\bwith-branch\b' --type=go to confirm no hits.

🤖 Prompt for AI Agents
In cmd/wtp/remove_test.go around lines 307 to 310 and across cmd/wtp/remove.go,
internal/errors/errors.go, internal/errors/errors_test.go, there are leftover
references to the deprecated "--with-branch" flag; update code and tests to
remove or replace all occurrences. Remove the flag definition and any help text
mentioning "--with-branch" from cmd/wtp/remove.go, update tests in
cmd/wtp/remove_test.go and internal/errors/errors_test.go to no longer expect or
assert on that flag or its error messages, and update internal/errors/errors.go
to remove any related error constants/messages; after changes run rg -n
'\bwith-branch\b' --type=go to confirm zero hits and adjust assertions/messages
in tests to the current flag behavior.

Comment on lines +91 to +99
func TestWorktreeNameRequiredForRemove(t *testing.T) {
err := WorktreeNameRequiredForRemove()

assert.Error(t, err)
assert.Contains(t, err.Error(), "worktree name is required")
assert.Contains(t, err.Error(), "wtp remove")
assert.Contains(t, err.Error(), "wtp remove --with-branch")
assert.Contains(t, err.Error(), "wtp list")
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Keep tests in sync with CLI: expect --branch (not --with-branch).

The product flags are --branch / --force-branch. Update the assertion string accordingly.

Apply:

 func TestWorktreeNameRequiredForRemove(t *testing.T) {
 	err := WorktreeNameRequiredForRemove()

 	assert.Error(t, err)
 	assert.Contains(t, err.Error(), "worktree name is required")
 	assert.Contains(t, err.Error(), "wtp remove")
-	assert.Contains(t, err.Error(), "wtp remove --with-branch")
+	assert.Contains(t, err.Error(), "wtp remove --branch")
 	assert.Contains(t, err.Error(), "wtp list")
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func TestWorktreeNameRequiredForRemove(t *testing.T) {
err := WorktreeNameRequiredForRemove()
assert.Error(t, err)
assert.Contains(t, err.Error(), "worktree name is required")
assert.Contains(t, err.Error(), "wtp remove")
assert.Contains(t, err.Error(), "wtp remove --with-branch")
assert.Contains(t, err.Error(), "wtp list")
}
func TestWorktreeNameRequiredForRemove(t *testing.T) {
err := WorktreeNameRequiredForRemove()
assert.Error(t, err)
assert.Contains(t, err.Error(), "worktree name is required")
assert.Contains(t, err.Error(), "wtp remove")
assert.Contains(t, err.Error(), "wtp remove --branch")
assert.Contains(t, err.Error(), "wtp list")
}
🤖 Prompt for AI Agents
internal/errors/errors_test.go around lines 91 to 99: the test asserts the CLI
help text contains "wtp remove --with-branch" but the actual product flag is
"--branch" (and companion flag is "--force-branch"); update the assertion to
expect "wtp remove --branch" (and if desired also add an assertion for "wtp
remove --force-branch") so the test matches the current CLI flags.

Comment on lines +59 to +71
func WorktreeNameRequiredForRemove() error {
msg := `worktree name is required
Usage: wtp remove <worktree-name>
Examples:
• wtp remove feature/auth
• wtp remove --with-branch feature/auth
• wtp remove --force feature/auth
Tip: Run 'wtp list' to see available worktrees`
return errors.New(msg)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Align remove examples with actual flag names (--branch, --force-branch).

The error text shows --with-branch, but the CLI tests and command flags use --branch and --force-branch. This mismatch will confuse users and breaks the “CLI UX consistent with README” guideline.

Apply:

 func WorktreeNameRequiredForRemove() error {
-	msg := `worktree name is required
+	msg := `worktree name is required

 Usage: wtp remove <worktree-name>

 Examples:
-  • wtp remove feature/auth
-  • wtp remove --with-branch feature/auth
-  • wtp remove --force feature/auth
+  • wtp remove feature/auth
+  • wtp remove --branch feature/auth
+  • wtp remove --force feature/auth
+  • wtp remove --branch --force-branch feature/auth

 Tip: Run 'wtp list' to see available worktrees`
 	return errors.New(msg)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func WorktreeNameRequiredForRemove() error {
msg := `worktree name is required
Usage: wtp remove <worktree-name>
Examples:
wtp remove feature/auth
wtp remove --with-branch feature/auth
wtp remove --force feature/auth
Tip: Run 'wtp list' to see available worktrees`
return errors.New(msg)
}
func WorktreeNameRequiredForRemove() error {
msg := `worktree name is required
Usage: wtp remove <worktree-name>
Examples:
wtp remove feature/auth
wtp remove --branch feature/auth
wtp remove --force feature/auth
wtp remove --branch --force-branch feature/auth
Tip: Run 'wtp list' to see available worktrees`
return errors.New(msg)
}
🤖 Prompt for AI Agents
In internal/errors/errors.go around lines 59 to 71, the usage examples mention
the non-existent flag `--with-branch`; update the error message so examples use
the actual CLI flags `--branch` and `--force-branch` (e.g., replace
`--with-branch` with `--branch` and include an example showing `--force-branch`)
to keep the help text consistent with the command implementation and tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants