Skip to content

feat: Add cd command to navigate to worktree directories#45

Open
imayolas wants to merge 1 commit intojohnlindquist:mainfrom
imayolas:feat/cd-command
Open

feat: Add cd command to navigate to worktree directories#45
imayolas wants to merge 1 commit intojohnlindquist:mainfrom
imayolas:feat/cd-command

Conversation

@imayolas
Copy link

@imayolas imayolas commented Mar 3, 2026

Summary

  • Adds a new wt cd command that lets users quickly navigate to a worktree directory
  • Supports interactive worktree selection (fuzzy search) or direct lookup by branch name/path
  • Copies cd /path/to/worktree to clipboard so users can paste (Cmd+V) and Enter to navigate

Usage

wt cd              # Interactive: select from worktree list
wt cd feature-auth # Direct: by branch name
# Then Cmd+V + Enter to navigate

Summary by CodeRabbit

  • New Features
    • Adds a new "cd" CLI command to locate and navigate to a git worktree. Supports interactive selection or supplying a path/branch, validates the target exists, and provides clear error or cancellation messages. Produces a shell-ready cd command, attempts to copy it to the clipboard with sensible fallbacks, and displays usage/paste hints for convenience.

@coderabbitai
Copy link

coderabbitai bot commented Mar 3, 2026

📝 Walkthrough

Walkthrough

Adds a new top-level "cd" CLI command and handler that ensures the repo is inside a git worktree, resolves a target worktree interactively or by path/branch, validates the target path, and copies or prints a shell-ready cd command for user consumption.

Changes

Cohort / File(s) Summary
CD Command Handler
src/commands/cd.ts, build/commands/cd.js
New exported async function cdWorktreeHandler(pathOrBranch = ""): verifies in-worktree context, resolves target via interactive selection or path/branch lookup (including directory + .git detection), validates filesystem existence, constructs a shell-escaped cd command, attempts platform-specific clipboard copy (pbcopy/clip/xclip) with fallback to stdout, and handles errors and exit codes.
CLI Integration
src/index.ts, build/index.js
Imports and registers new top-level cd command (wt cd [pathOrBranch]) wired to cdWorktreeHandler, exposing the command in the CLI API alongside existing commands.

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI as "CLI Handler"
    participant Git as "Git Validator"
    participant Resolver as "Worktree Resolver / TUI"
    participant FS as "Filesystem"
    participant Clipboard as "Clipboard/Output"

    User->>CLI: wt cd [pathOrBranch]
    CLI->>Git: verify inside git worktree
    alt Not in worktree
        Git-->>CLI: error
        CLI-->>User: exit(1) with message
    else In worktree
        alt No argument
            CLI->>Resolver: prompt interactive selection
            Resolver-->>CLI: selected worktree path
        else Argument provided
            CLI->>Resolver: resolve by path or branch
            Resolver-->>CLI: resolved path or error
        end
        CLI->>FS: verify path exists
        alt Path missing
            FS-->>CLI: not found
            CLI-->>User: guidance + exit(1)
        else Path exists
            CLI->>Clipboard: try copy "cd <path>" (pbcopy/clip/xclip)
            alt Copy succeeds
                Clipboard-->>CLI: OK
                CLI-->>User: confirmation + paste hint
            else Copy fails
                Clipboard-->>CLI: fail
                CLI-->>User: print cd command to stdout
            end
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐇 I hopped through branches, paths, and trees,
I sniffed the worktree on a gentle breeze,
I copied a cd with a tiny hop—so spry,
Paste it quick, and off you fly! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately describes the main change: adding a new 'cd' command for worktree navigation, which is the core feature across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@imayolas
Copy link
Author

imayolas commented Mar 3, 2026

@coderabbitai review

@imayolas imayolas marked this pull request as ready for review March 3, 2026 13:13
@coderabbitai
Copy link

coderabbitai bot commented Mar 3, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

🧹 Nitpick comments (1)
src/commands/cd.ts (1)

78-81: Consider cross-platform clipboard support.

pbcopy is macOS-specific. For broader compatibility, consider using a cross-platform clipboard library like clipboardy, or detect the platform and use xclip/xsel on Linux or clip.exe on Windows.

The current fallback to stdout is functional for shell substitution (cd $(wt cd)), so this is a nice-to-have improvement rather than a blocker.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/cd.ts` around lines 78 - 81, The current try block calls
execa("pbcopy", { input: cdCommand }) (in the clipboard copy logic) which only
works on macOS; replace this with a cross-platform solution by either adding the
clipboardy library and using clipboardy.writeSync(cdCommand) (or async) or
implement platform detection around the execa call (process.platform ===
"darwin" → pbcopy, "win32" → clip.exe, Linux → xclip/xsel) and fall back to
printing to stdout when none are available; update the code paths that log
success (console.log(chalk.green(...)) and console.log(chalk.gray(...))) to
remain unchanged after the copy succeeds and ensure errors are caught and
handled the same way as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/commands/cd.ts`:
- Around line 76-85: Wrap and escape targetPath when building cdCommand so
spaces and shell metacharacters are preserved: construct cdCommand using single
quotes around the path and replace any single quote in targetPath with the
sequence '\'' to safely embed it (affects the string assigned to cdCommand used
by execa("pbcopy")). Also update the fallback in the catch block to write the
full cdCommand (not just targetPath) to stdout so clipboard and fallback
behavior are consistent; keep using the same cdCommand variable for both
clipboard and fallback outputs.

---

Nitpick comments:
In `@src/commands/cd.ts`:
- Around line 78-81: The current try block calls execa("pbcopy", { input:
cdCommand }) (in the clipboard copy logic) which only works on macOS; replace
this with a cross-platform solution by either adding the clipboardy library and
using clipboardy.writeSync(cdCommand) (or async) or implement platform detection
around the execa call (process.platform === "darwin" → pbcopy, "win32" →
clip.exe, Linux → xclip/xsel) and fall back to printing to stdout when none are
available; update the code paths that log success (console.log(chalk.green(...))
and console.log(chalk.gray(...))) to remain unchanged after the copy succeeds
and ensure errors are caught and handled the same way as before.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1301994 and f28a618.

📒 Files selected for processing (4)
  • build/commands/cd.js
  • build/index.js
  • src/commands/cd.ts
  • src/index.ts

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/commands/cd.ts`:
- Around line 28-48: The code currently exits if pathOrBranch is a directory
that isn't a worktree (it logs and calls process.exit when .git is missing),
which prevents treating the same token as a branch; change the logic in the
stat/dir handling so that when stats.isDirectory() and
findWorktreeByPath(pathOrBranch) returns null and resolve(pathOrBranch, ".git")
does not exist, you do NOT call console.error/process.exit but instead fall
through to the branch-resolution path so branch lookup still runs; update the
same pattern used around the second occurrence (the block covering lines 55-61)
so both places allow ambiguous inputs to be resolved as branches if they are not
actual git worktrees, referencing the variables/pathOrBranch and the helper
findWorktreeByPath used in src/commands/cd.ts.
- Around line 100-102: Add an explicit colored notice before the fallback
printed command when the clipboard command is unavailable: instead of only
calling process.stdout.write(cdCommand + "\n"), write a short colored message
(e.g., yellow/orange "Notice: clipboard unavailable — printing command:" or use
the project's existing color helper) followed by the command; update the block
that references cdCommand and currently calls process.stdout.write to emit the
colored notice first, then the cdCommand output so CLI feedback remains
consistent.
- Around line 76-78: The current POSIX-only escaping using escapedPath and
cdCommand must be made platform/shell-aware: detect the target shell/platform
(use the same detection used for clipboard logic) and build cdCommand
accordingly—on Windows cmd.exe produce cd /d "path" using double quotes and
escaping any internal double quotes, for PowerShell produce cd 'path' but escape
apostrophes by doubling them (replace ' with ''), and for POSIX keep the
existing single-quote strategy (escapedPath = "'" + path.replace(/'/g, "'\\''")
+ "'" and cdCommand = `cd ${escapedPath}`). Update the code that currently
defines escapedPath and cdCommand in src/commands/cd.ts to branch by shell type
and construct the correct command string.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f28a618 and 1a8a420.

📒 Files selected for processing (2)
  • build/commands/cd.js
  • src/commands/cd.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • build/commands/cd.js

Comment on lines +76 to +78
// Shell-escape the path by wrapping in single quotes
const escapedPath = "'" + targetPath.replace(/'/g, "'\\''") + "'";
const cdCommand = `cd ${escapedPath}`;
Copy link

@coderabbitai coderabbitai bot Mar 3, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd src/commands && head -n 95 cd.ts | tail -n 35

Repository: johnlindquist/worktree-cli

Length of output: 1281


🏁 Script executed:

cd src/commands && sed -n '86,88p' cd.ts

Repository: johnlindquist/worktree-cli

Length of output: 191


🏁 Script executed:

cd src/commands && sed -n '78,95p' cd.ts

Repository: johnlindquist/worktree-cli

Length of output: 692


🏁 Script executed:

cd src/commands && wc -l cd.ts && cat -n cd.ts

Repository: johnlindquist/worktree-cli

Length of output: 5286


🌐 Web query:

Windows cmd.exe cd command single quotes path escaping behavior

💡 Result:

In cmd.exe, single quotes (') have no special “string quoting” meaning for commands like cd. They’re treated as literal characters, so:

  • cd 'C:\Temp' tries to change into a directory literally named 'C:\Temp' (including the apostrophes) and will typically fail. [2], [3]

To handle spaces / special characters in a path in cmd.exe, use double quotes:

  • cd /d "C:\Program Files\My App" (use /d to also switch drive letters) [1]
  • In general, “single quotation marks won’t do … you have to add quotation marks around each path” in cmd.exe. [3]

Escaping note (cmd parser):

  • cmd.exe’s escape character is caret ^ for metacharacters like & | < > ( ), but it does not turn '...' into a quoted string. [1], [2]

Bottom line: for cd, don’t use single quotes; use double quotes around the path. [1], [2]

Sources:
[1] Microsoft Learn — cmd command documentation (quoting/escaping rules) (learn.microsoft.com)
[2] Stack Overflow — single quotes in batch/cmd; only double quotes are standard (stackoverflow.com)
[3] Stack Overflow — “Single quotation marks won’t do… add quotation marks around each path” (stackoverflow.com)

Citations:


🌐 Web query:

PowerShell cd command single quotes path escaping behavior Windows

💡 Result:

In Windows PowerShell / PowerShell on Windows, cd is an alias of Set-Location. Single quotes affect PowerShell’s string parsing, not path wildcard (globbing) behavior.

  • Single-quoted strings are verbatim. Nothing is expanded (no $var, no subexpressions), and the backtick ` is treated as a literal character. To embed a literal single-quote inside a single-quoted string, double it ('').[1]
    Examples:

    cd 'C:\Program Files'          # spaces are fine
    cd 'C:\Client''s Files'        # apostrophe escaped by doubling
  • Quoting does not disable wildcard interpretation for -Path. Set-Location -Path can treat wildcard metacharacters (notably * ? [ ]) as wildcards even if the path is in quotes. If you need the path interpreted literally, use -LiteralPath.[2]
    Example (folder name contains brackets):

    cd -LiteralPath 'C:\Test\[Hello]'
  • If you must use -Path with literal wildcard characters, you can escape wildcard metacharacters with the backtick (PowerShell’s escape character), but -LiteralPath is the simplest/most reliable option.[2][3]

Sources: about_Quoting_Rules[1], Set-Location (-LiteralPath semantics and why single quotes help when escape characters are present)[2], Microsoft Learn guidance on escaping wildcard metacharacters with backtick[3].

[1] (learn.microsoft.com)
[2] (learn.microsoft.com)
[3] (learn.microsoft.com)

Citations:


Adapt shell-escaping strategy to platform-specific requirements.

The current POSIX single-quote escaping (lines 76–77) fails on Windows cmd.exe because single quotes have no special string-quoting meaning—they are treated as literal characters. For cmd.exe, paths require double quotes; for PowerShell, single quotes work but apostrophes must be doubled (''), not escaped with backslash (\'). Since the code already detects the platform for clipboard commands (lines 86–88), apply the same logic to generate correct escaping for each shell:

  • cmd.exe: Use double quotes with /d flag (e.g., cd /d "C:\path with spaces")
  • PowerShell: Use single quotes with '' for apostrophes (e.g., cd 'C:\path with''s files')
  • POSIX (macOS, Linux): Keep current single-quote escaping
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/cd.ts` around lines 76 - 78, The current POSIX-only escaping
using escapedPath and cdCommand must be made platform/shell-aware: detect the
target shell/platform (use the same detection used for clipboard logic) and
build cdCommand accordingly—on Windows cmd.exe produce cd /d "path" using double
quotes and escaping any internal double quotes, for PowerShell produce cd 'path'
but escape apostrophes by doubling them (replace ' with ''), and for POSIX keep
the existing single-quote strategy (escapedPath = "'" + path.replace(/'/g,
"'\\''") + "'" and cdCommand = `cd ${escapedPath}`). Update the code that
currently defines escapedPath and cdCommand in src/commands/cd.ts to branch by
shell type and construct the correct command string.

Copy link
Author

Choose a reason for hiding this comment

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

Fixed.

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the current implementation of shell-escaping in src/commands/cd.ts
cat src/commands/cd.ts | sed -n '70,111p'

Length of output: 1795


@imayolas, I can see the code still uses the POSIX-only single-quote escaping approach on lines 76-78:

const escapedPath = "'" + targetPath.replace(/'/g, "'\\''") + "'";
const cdCommand = `cd ${escapedPath}`;

The platform-specific escaping logic hasn't been added yet. The code needs to branch based on the shell/platform (similar to how it detects the platform for clipboard operations on lines 81-90) to generate the correct cdCommand:

  • Windows cmd.exe: cd /d "C:\path" (double quotes)
  • PowerShell: cd 'C:\path''s' (single quotes with doubled apostrophes)
  • POSIX: Current approach (single quotes with '\'' escape)

Have you pushed the changes yet, or would you like help implementing the platform-specific escaping logic?


🧠 Learnings used
Learnt from: CR
Repo: johnlindquist/worktree-cli PR: 0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/commands/list.ts : Provide functionality to list existing Git worktrees in src/commands/list.ts.

Learnt from: CR
Repo: johnlindquist/worktree-cli PR: 0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/commands/new.ts : Implement logic for creating new Git worktrees, including options for branch creation, dependency installation, and opening in an editor, in src/commands/new.ts.

Learnt from: CR
Repo: johnlindquist/worktree-cli PR: 0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/commands/*.ts : Leverage Execa to execute Git commands and other external processes.

Learnt from: CR
Repo: johnlindquist/worktree-cli PR: 0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/commands/remove.ts : Handle removal of Git worktrees, including support for force deletion, in src/commands/remove.ts.

Learnt from: juristr
Repo: johnlindquist/worktree-cli PR: 20
File: src/commands/extract.ts:124-127
Timestamp: 2025-08-04T14:22:29.156Z
Learning: The worktree-cli project prioritizes consistency across commands. When implementing new commands like `extract`, developers follow existing patterns from similar commands like `new` to maintain API and implementation consistency.

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.

♻️ Duplicate comments (2)
src/commands/cd.ts (2)

28-61: ⚠️ Potential issue | 🟠 Major

Don’t exit before branch fallback for ambiguous directory names.

At Line 46Line 47, the handler exits when an existing directory is not a worktree, so valid branch lookup at Line 55 never runs for ambiguous inputs (e.g., folder name equals branch name).

🔧 Proposed fix
         } else {
             // Try to find by path first
+            let existingDirButNotWorktree = false;
             try {
                 const stats = await stat(pathOrBranch);
                 if (stats.isDirectory()) {
                     targetWorktree = await findWorktreeByPath(pathOrBranch);
                     if (!targetWorktree) {
                         try {
                             await stat(resolve(pathOrBranch, ".git"));
                             targetWorktree = {
                                 path: resolve(pathOrBranch),
                                 head: '',
                                 branch: null,
                                 detached: false,
                                 locked: false,
                                 prunable: false,
                                 isMain: false,
                                 bare: false,
                             };
                         } catch {
-                            console.error(chalk.red(`The path "${pathOrBranch}" exists but is not a git worktree.`));
-                            process.exit(1);
+                            existingDirButNotWorktree = true;
                         }
                     }
                 }
             } catch {
                 // Not a valid path, try as branch name
             }

             if (!targetWorktree) {
                 targetWorktree = await findWorktreeByBranch(pathOrBranch);
                 if (!targetWorktree) {
-                    console.error(chalk.red(`Could not find a worktree for branch "${pathOrBranch}".`));
+                    if (existingDirButNotWorktree) {
+                        console.error(chalk.red(`"${pathOrBranch}" is neither a git worktree path nor a known worktree branch.`));
+                    } else {
+                        console.error(chalk.red(`Could not find a worktree for branch "${pathOrBranch}".`));
+                    }
                     console.error(chalk.yellow("Use 'wt list' to see existing worktrees, or run 'wt cd' without arguments to select interactively."));
                     process.exit(1);
                 }
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/cd.ts` around lines 28 - 61, The code currently calls
process.exit(1) inside the inner catch after stat(resolve(pathOrBranch, ".git"))
fails in the findWorktreeByPath flow, which prevents falling back to branch
lookup via findWorktreeByBranch for ambiguous inputs; remove the immediate exit
and instead allow execution to continue to the branch lookup by replacing the
inner catch's process.exit call with a non-fatal handling (e.g., log a warning
or no-op) so that if targetWorktree remains undefined the subsequent
findWorktreeByBranch(pathOrBranch) is attempted; update logic around stat,
findWorktreeByPath, and the inner catch to ensure targetWorktree is only set
when a valid worktree is confirmed and errors do not prematurely terminate the
process.

76-78: ⚠️ Potential issue | 🟠 Major

Make cdCommand shell/platform aware instead of POSIX-only escaping.

At Line 77, single-quote POSIX escaping is applied unconditionally. This is not correct for Windows cmd.exe, and apostrophe handling differs in PowerShell.

On Windows, what is the correct quoting/escaping for changing directories in:
1) cmd.exe, and
2) PowerShell,
especially for paths with spaces and apostrophes?
🔧 Proposed direction
-        // Shell-escape the path by wrapping in single quotes
-        const escapedPath = "'" + targetPath.replace(/'/g, "'\\''") + "'";
-        const cdCommand = `cd ${escapedPath}`;
+        let cdCommand: string;
+        if (process.platform === "win32") {
+            // Build a Windows-safe command (verify exact shell behavior for cmd.exe vs PowerShell)
+            const escaped = targetPath.replace(/"/g, `""`);
+            cdCommand = `cd /d "${escaped}"`;
+        } else {
+            const escapedPath = "'" + targetPath.replace(/'/g, "'\\''") + "'";
+            cdCommand = `cd ${escapedPath}`;
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/cd.ts` around lines 76 - 78, The current unconditional POSIX
single-quote escaping for targetPath (building escapedPath and cdCommand) is
wrong on Windows; update the code that constructs cdCommand to be
shell/platform-aware: detect Windows vs POSIX (e.g., process.platform ===
'win32') and further detect whether running under cmd.exe (ComSpec/PROCESS.ENV)
or PowerShell, then build escapedPath accordingly — for POSIX keep the
single-quote approach used in cdCommand, for cmd.exe wrap the path in double
quotes and escape embedded double-quotes appropriately, and for PowerShell
prefer single-quoted strings but escape any single quote by doubling it (e.g.,
'O''Reilly'); update the code that sets escapedPath and cdCommand (the variables
escapedPath and cdCommand) to choose the right quoting/escaping branch based on
the detected shell/platform.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/commands/cd.ts`:
- Around line 28-61: The code currently calls process.exit(1) inside the inner
catch after stat(resolve(pathOrBranch, ".git")) fails in the findWorktreeByPath
flow, which prevents falling back to branch lookup via findWorktreeByBranch for
ambiguous inputs; remove the immediate exit and instead allow execution to
continue to the branch lookup by replacing the inner catch's process.exit call
with a non-fatal handling (e.g., log a warning or no-op) so that if
targetWorktree remains undefined the subsequent
findWorktreeByBranch(pathOrBranch) is attempted; update logic around stat,
findWorktreeByPath, and the inner catch to ensure targetWorktree is only set
when a valid worktree is confirmed and errors do not prematurely terminate the
process.
- Around line 76-78: The current unconditional POSIX single-quote escaping for
targetPath (building escapedPath and cdCommand) is wrong on Windows; update the
code that constructs cdCommand to be shell/platform-aware: detect Windows vs
POSIX (e.g., process.platform === 'win32') and further detect whether running
under cmd.exe (ComSpec/PROCESS.ENV) or PowerShell, then build escapedPath
accordingly — for POSIX keep the single-quote approach used in cdCommand, for
cmd.exe wrap the path in double quotes and escape embedded double-quotes
appropriately, and for PowerShell prefer single-quoted strings but escape any
single quote by doubling it (e.g., 'O''Reilly'); update the code that sets
escapedPath and cdCommand (the variables escapedPath and cdCommand) to choose
the right quoting/escaping branch based on the detected shell/platform.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a8a420 and 77b327e.

📒 Files selected for processing (2)
  • build/commands/cd.js
  • src/commands/cd.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • build/commands/cd.js

Adds a new `wt cd` command that copies `cd /path/to/worktree` to the
clipboard for quick navigation. Supports interactive worktree selection
(no args) or direct lookup by branch name or path.

- Shell-escapes paths to handle spaces and special characters
- Cross-platform clipboard support (pbcopy, clip.exe, xclip)
- Falls back to stdout with a message if clipboard is unavailable

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@imayolas
Copy link
Author

imayolas commented Mar 3, 2026

All review comments have been addressed:

  • Path quoting: Paths are now shell-escaped with single quotes to handle spaces and special characters
  • Cross-platform clipboard: Added support for pbcopy (macOS), clip.exe (Windows), and xclip (Linux), with fallback to stdout
  • Fallback message: Added a colored notice when clipboard is unavailable
  • Branch/dir collision: Existing behavior inherited from the open command; better addressed as a separate issue

Commits have been squashed into a single clean commit.

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.

♻️ Duplicate comments (1)
src/commands/cd.ts (1)

76-78: ⚠️ Potential issue | 🟠 Major

Platform command escaping is still POSIX-only, which breaks Windows cmd.exe.

The generated command uses POSIX single-quote escaping. In cmd.exe, single quotes are literal, so users can get an invalid cd command on Windows despite Windows clipboard support being implemented.

🔧 Suggested direction
-        // Shell-escape the path by wrapping in single quotes
-        const escapedPath = "'" + targetPath.replace(/'/g, "'\\''") + "'";
-        const cdCommand = `cd ${escapedPath}`;
+        // Build shell-aware cd command
+        const cdCommand =
+            process.platform === "win32"
+                ? `cd /d "${targetPath.replace(/"/g, '""')}"`
+                : `cd '${targetPath.replace(/'/g, "'\\''")}'`;
In Windows cmd.exe, does `cd 'C:\Program Files'` work, or must paths be double-quoted (e.g. `cd /d "C:\Program Files"`)? Also confirm how apostrophes must be escaped in PowerShell single-quoted paths for `Set-Location -LiteralPath`.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/cd.ts` around lines 76 - 78, The current POSIX-only escaping
(building escapedPath and cdCommand from targetPath) breaks Windows shells;
update src/commands/cd.ts to branch on platform: when process.platform ===
'win32' produce a Windows-friendly command (use cd /d "..." for cmd.exe and wrap
targetPath in double quotes, escaping internal double quotes), and for
PowerShell produce Set-Location -LiteralPath with single-quote literal escaping
(double any single quotes inside the path); otherwise keep the existing POSIX
single-quote escaping. Ensure you update the code that builds escapedPath and
cdCommand (and any PowerShell branch) to apply the correct escaping rules per
shell.
🧹 Nitpick comments (1)
src/commands/cd.ts (1)

27-63: Extract target-resolution logic into a shared helper to prevent command drift.

This block is substantial and likely duplicated across commands (open/cd). A shared resolver would keep edge-case behavior consistent and make future fixes one-touch.

Based on learnings: "The worktree-cli project prioritizes consistency across commands. When implementing new commands like extract, developers follow existing patterns from similar commands like new to maintain API and implementation consistency."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/cd.ts` around lines 27 - 63, Extract the target-resolution logic
in the cd command into a shared helper (e.g.,
resolveTargetWorktree(pathOrBranch)) that encapsulates the current flow: try
stat(pathOrBranch) and use findWorktreeByPath if it's a directory, fall back to
checking resolve(pathOrBranch, ".git") and constructing the minimal worktree
object when present, and if those fail call findWorktreeByBranch(pathOrBranch)
and return an error result when none found; replace the inline block in
src/commands/cd.ts with a call to this helper and refactor other commands like
open to call the same helper so findWorktreeByPath, findWorktreeByBranch, stat,
and resolve-based fallback behavior remain consistent across commands.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/commands/cd.ts`:
- Around line 76-78: The current POSIX-only escaping (building escapedPath and
cdCommand from targetPath) breaks Windows shells; update src/commands/cd.ts to
branch on platform: when process.platform === 'win32' produce a Windows-friendly
command (use cd /d "..." for cmd.exe and wrap targetPath in double quotes,
escaping internal double quotes), and for PowerShell produce Set-Location
-LiteralPath with single-quote literal escaping (double any single quotes inside
the path); otherwise keep the existing POSIX single-quote escaping. Ensure you
update the code that builds escapedPath and cdCommand (and any PowerShell
branch) to apply the correct escaping rules per shell.

---

Nitpick comments:
In `@src/commands/cd.ts`:
- Around line 27-63: Extract the target-resolution logic in the cd command into
a shared helper (e.g., resolveTargetWorktree(pathOrBranch)) that encapsulates
the current flow: try stat(pathOrBranch) and use findWorktreeByPath if it's a
directory, fall back to checking resolve(pathOrBranch, ".git") and constructing
the minimal worktree object when present, and if those fail call
findWorktreeByBranch(pathOrBranch) and return an error result when none found;
replace the inline block in src/commands/cd.ts with a call to this helper and
refactor other commands like open to call the same helper so findWorktreeByPath,
findWorktreeByBranch, stat, and resolve-based fallback behavior remain
consistent across commands.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 77b327e and 87d7e98.

📒 Files selected for processing (4)
  • build/commands/cd.js
  • build/index.js
  • src/commands/cd.ts
  • src/index.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • build/commands/cd.js
  • build/index.js
  • src/index.ts

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.

1 participant