Skip to content

Conversation

@felipeS
Copy link
Contributor

@felipeS felipeS commented Oct 10, 2025

Add wt setup Command for Automated Worktree Configuration

Overview

This PR adds a new wt setup command that creates worktrees and automatically runs setup scripts from worktrees.json or .cursor/worktrees.json. The existing wt new command remains unchanged for simple, fast worktree creation.

Motivation

When working with multiple worktrees, developers often need to install dependencies, copy configuration files, initialize submodules, or run custom setup scripts. This feature automates these repetitive tasks while maintaining security through a blocklist of dangerous commands.

Command Structure

wt new (Unchanged)

  • Creates worktrees quickly without running setup scripts
  • Original behavior preserved for backward compatibility

wt setup (New)

  • Creates worktrees AND runs automated setup scripts
  • Explicit command name makes intent clear
  • Supports flexible configuration with security protections

How It Works

File Support

The implementation checks for setup files in this priority order:

  1. .cursor/worktrees.json (Cursor's native format)
  2. worktrees.json (generic format)

File Formats

Cursor Format (.cursor/worktrees.json):

[
  "npm install",
  "cp $ROOT_WORKTREE_PATH/.local.env .local.env"
]

Generic Format (worktrees.json):

{
  "setup-worktree": [
    "npm install",
    "bun install",
    "cp .env.example .env",
    "git submodule update --init --recursive",
    "pnpm build"
  ]
}

Execution Details

  • Commands run in the new worktree directory
  • $ROOT_WORKTREE_PATH environment variable provides access to the main repository root
  • Commands execute after worktree creation but before dependency installation (if --install flag is used)
  • Shell execution supports complex commands and piping

Security Features

Blocklist Approach (Not Allowlist)

Instead of maintaining an allowlist of approved commands, we use a blocklist of dangerous patterns. This provides:

  • Flexibility: Any legitimate command works by default (npm, yarn, pnpm, bun, custom scripts, etc.)
  • Maintainability: Only need to track dangerous patterns, not every safe command
  • Practicality: Supports all package managers and build tools without configuration

Blocked Patterns

  • rm -rf and recursive deletions
  • sudo and su (privilege escalation)
  • chmod, chown (permission changes)
  • Piping downloads to shell (curl | sh, wget | sh)
  • Disk operations (dd, mkfs, format)
  • System commands (shutdown, reboot, kill -9)
  • Redirects to /dev/
  • Fork bombs and malicious patterns

Error Handling

  • Failed commands don't stop the setup process
  • Dangerous commands are blocked with clear warning messages
  • Setup file parsing errors are logged but don't crash

Files Changed

New Files:

  • src/commands/setup.ts - New command handler with setup script execution
  • build/commands/setup.js - Compiled output

Modified Files:

  • src/commands/new.ts - Removed setup script logic (restored original behavior)
  • src/index.ts - Added setup command definition
  • README.md - Documented both wt new and wt setup commands

Testing Instructions

  1. Create a setup file in your repository root:

    echo '{"setup-worktree": ["npm install", "echo Setup complete"]}' > worktrees.json
  2. Test basic worktree creation (no scripts):

    wt new test-basic
    • Verify: No setup scripts run
  3. Test worktree with setup scripts:

    wt setup test-setup
    • Verify: npm install runs automatically
    • Verify: "Setup complete" message appears
  4. Test security blocklist:

    echo '{"setup-worktree": ["npm install", "rm -rf /tmp/test", "sudo apt install"]}' > worktrees.json
    wt setup test-security
    • Verify: npm install runs successfully
    • Verify: rm -rf is blocked with warning
    • Verify: sudo apt install is blocked with warning
  5. Test flexibility (various package managers):

    echo '{"setup-worktree": ["bun install", "pnpm build", "cp .env.example .env"]}' > worktrees.json
    wt setup test-flexible
    • Verify: All commands run successfully without needing allowlist updates

Examples

Before (No Setup Support)

wt new feature/login
cd ../myproject-login
npm install
cp .env.example .env
git submodule update --init

After (Automated Setup)

# Create worktrees.json once:
{
  "setup-worktree": [
    "npm install",
    "cp .env.example .env", 
    "git submodule update --init"
  ]
}

# Then just:
wt setup feature/login
# Everything is ready to go!

Checklist

  • TypeScript compilation passes
  • Backward compatibility maintained (wt new unchanged)
  • Security blocklist implemented
  • Comprehensive error handling
  • Documentation updated
  • Type safety improved
  • Build files included for distribution
  • Clear command separation (new vs setup)

Breaking Changes

None - this is a purely additive feature. The wt new command behavior is completely unchanged.

Benefits

  1. Clear Intent: Command name explicitly indicates setup scripts will run
  2. Flexibility: Blocklist approach supports any legitimate command
  3. Security: Dangerous patterns automatically blocked
  4. User Choice: Users decide when to use automation (setup) vs speed (new)
  5. Maintainability: No need to update allowlist for every new package manager or tool

Summary by CodeRabbit

  • New Features

    • Added a “setup” command to create/reuse a worktree and optionally run setup scripts, install dependencies, and open an editor.
    • Supports configuration via .cursor/worktrees.json or worktrees.json with fallback handling.
    • Includes safety controls: command blocklist, allowed patterns, controlled execution context, and continue-on-error for non-critical steps.
  • Documentation

    • Added usage docs for the setup command, including syntax, options (-p/--path, -c/--checkout, -i/--install, -e/--editor), and examples.
    • Documented configuration formats, security features, and execution details.

- Add getRepoRoot utility function to determine git repository root
- Support both .cursor/worktrees.json (Cursor format) and worktrees.json (generic format)
- Execute setup commands in new worktree directory with ROOT_WORKTREE_PATH env var
- Add comprehensive documentation with examples
- Commands run after worktree creation but before dependency installation
- Add getRepoRoot utility function to determine git repository root
- Support both .cursor/worktrees.json (Cursor format) and worktrees.json (generic format)
- Execute setup commands in new worktree directory with ROOT_WORKTREE_PATH env var
- Add security allowlist for safe commands only (npm/yarn/pnpm install, git submodule)
- Add comprehensive documentation with examples
- Remove build files from version control to keep only source files
- Commands run after worktree creation but before dependency installation
- Build files are needed for distribution and should remain tracked
- This prevents the pull request from showing unnecessary deletions
- Update WorktreeSetupData type to handle both object and array formats
- Remove unsafe 'as any' type assertions
- Add proper type guards for safer data access
- Maintain backward compatibility with both Cursor and generic formats
@coderabbitai
Copy link

coderabbitai bot commented Oct 10, 2025

Walkthrough

The change removes the legacy compiled CLI and command handlers under build/, introduces a new setup worktree command in src/, adds a Git utility to resolve repo root, wires the new command into the TypeScript CLI, and updates README documentation detailing setup behavior, configuration formats, security checks, and execution rules.

Changes

Cohort / File(s) Summary
Docs update
README.md
Added sections documenting wt setup, configuration files (.cursor/worktrees.json, worktrees.json), security rules (blocklist/allow patterns), execution details, and examples.
Remove legacy CLI entrypoint
build/index.js
Deleted Commander-based CLI wiring for all commands (new, list/ls, remove/rm, merge, purge, pr, config).
Remove legacy command handlers
build/commands/*.js
Removed handlers: new.js, list.js, remove.js, merge.js, purge.js, pr.js, config.js; eliminated associated logic and exports.
Remove legacy utils/config
build/utils/git.js, build/config.js
Deleted getCurrentBranch, isWorktreeClean, and config helpers (getDefaultEditor, setDefaultEditor, getConfigPath).
Add setup command (TS)
src/commands/setup.ts
Added setupWorktreeHandler to create/reuse worktrees, optionally checkout/create branch, run setup scripts from config files with denylist checks, optionally install deps, and open editor; includes validations and logging.
Add git utility (TS)
src/utils/git.ts
Added `getRepoRoot(cwd?: string): Promise<string
Wire setup into CLI (TS)
src/index.ts
Added setup subcommand with options -p/--path, -c/--checkout, -i/--install, -e/--editor, invoking setupWorktreeHandler.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant CLI as wt (setup)
  participant Git as Git
  participant FS as Filesystem
  participant Cfg as Config (.cursor/worktrees.json / worktrees.json)
  participant Sh as Shell (setup cmds)
  participant PM as Installer (npm/yarn/pnpm)
  participant Ed as Editor

  U->>CLI: wt setup [branch] [-p path] [-c] [-i pm] [-e editor]
  CLI->>Git: rev-parse --is-inside-work-tree
  Git-->>CLI: ok / error
  alt not in repo
    CLI-->>U: exit non-zero
  end

  CLI->>Git: status --porcelain (main worktree)
  CLI->>Git: worktree list --porcelain
  CLI->>FS: resolve/ensure target path
  alt worktree exists
    CLI-->>U: reuse existing worktree
  else create new
    CLI->>Git: branch/checkout as needed
    CLI->>Git: worktree add <path> <branch>
  end

  CLI->>Cfg: read .cursor/worktrees.json
  alt not found
    CLI->>Cfg: read worktrees.json
  end
  CLI->>Sh: run setup commands (filtered by blocklist/allow)
  Sh-->>CLI: per-command status (continue on error)

  opt install requested (-i)
    CLI->>PM: install at target path
    PM-->>CLI: done / error (non-fatal)
  end

  opt editor requested (-e)
    CLI->>Ed: open <path>
    Ed-->>CLI: launched / error (non-fatal)
  end

  CLI-->>U: completed (path, branch)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

I burrowed through builds, old tunnels I knew,
Swept out the stale, left only the true.
A fresh twig: setup—scripts neatly aligned,
Safe little hops with guards well-defined.
Now to the worktree I happily scoot—
nibble, install, and launch—how cute! 🐇✨

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 clearly captures the introduction of setup-worktree support, emphasizing the added security checks and type safety enhancements described in the pull request. It concisely conveys the essence of the primary change without extraneous detail and aligns with the implementation of the new setup command and accompanying documentation updates. The phrasing is specific and informative, enabling a reviewer to quickly understand the main purpose of the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 41c8c9e and cf218e0.

📒 Files selected for processing (10)
  • build/commands/config.js (0 hunks)
  • build/commands/list.js (0 hunks)
  • build/commands/merge.js (0 hunks)
  • build/commands/new.js (0 hunks)
  • build/commands/pr.js (0 hunks)
  • build/commands/purge.js (0 hunks)
  • build/commands/remove.js (0 hunks)
  • build/config.js (0 hunks)
  • build/index.js (0 hunks)
  • build/utils/git.js (0 hunks)
💤 Files with no reviewable changes (10)
  • build/utils/git.js
  • build/commands/config.js
  • build/config.js
  • build/commands/pr.js
  • build/commands/list.js
  • build/commands/remove.js
  • build/commands/new.js
  • build/commands/purge.js
  • build/index.js
  • build/commands/merge.js

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

@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: 5

Caution

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

⚠️ Outside diff range comments (2)
src/utils/git.ts (1)

44-67: Make bare-repo detection portable and simpler.

Current logic assumes /.git suffix and can break on Windows paths. Use git rev-parse --is-bare-repository instead.

-export async function isMainRepoBare(cwd: string = '.'): Promise<boolean> {
-    try {
-        // Find the root of the git repository
-        const { stdout: gitDir } = await execa('git', ['-C', cwd, 'rev-parse', '--git-dir']);
-        const mainRepoDir = gitDir.endsWith('/.git') ? gitDir.slice(0, -5) : gitDir; // Handle bare repo paths vs normal .git
-
-        // Check the core.bare setting specifically for that repository path
-        const { stdout: bareConfig } = await execa('git', ['config', '--get', '--bool', 'core.bare'], {
-            cwd: mainRepoDir, // Check config in the main repo dir, not the potentially detached worktree CWD
-        });
-
-        // stdout will be 'true' or 'false' as a string
-        return bareConfig.trim() === 'true';
-    } catch (error: any) {
-        // If the command fails (e.g., not a git repo, or config not set),
-        // assume it's not bare, but log a warning.
-        // A non-existent core.bare config defaults to false.
-        if (error.exitCode === 1 && error.stdout === '' && error.stderr === '') {
-            // This specific exit code/output means the config key doesn't exist, which is fine (defaults to false).
-            return false;
-        }
-        console.warn(chalk.yellow(`Could not reliably determine if the main repository is bare. Proceeding cautiously. Error:`), error.stderr || error.message);
-        return false; // Default to non-bare to avoid blocking unnecessarily, but warn the user.
-    }
-}
+export async function isMainRepoBare(cwd: string = '.'): Promise<boolean> {
+    try {
+        const { stdout } = await execa('git', ['-C', cwd, 'rev-parse', '--is-bare-repository']);
+        return stdout.trim() === 'true';
+    } catch (error: any) {
+        console.warn(
+            chalk.yellow('Could not reliably determine if the main repository is bare. Proceeding cautiously. Error:'),
+            error.stderr || error.message
+        );
+        return false;
+    }
+}
build/commands/pr.js (1)

41-51: Check for bare repo before cleanliness check.

On bare repos, isWorktreeClean yields a misleading "not clean" error. Guard earlier and exit.

Example insertion right after Line 40:

if (await isMainRepoBare()) {
  console.error(chalk.red("❌ Error: The main repository is configured as 'bare' (core.bare=true)."));
  console.error(chalk.red("   This prevents normal Git operations. Please fix the configuration:"));
  console.error(chalk.cyan("   git config core.bare false"));
  process.exit(1);
}
🧹 Nitpick comments (11)
README.md (1)

111-116: Clarify allowlist behavior in bullets.

Add a note that only allowlisted commands run by default.

 - Commands are executed in the new worktree directory.
 - The $ROOT_WORKTREE_PATH environment variable is available, pointing to the main repository root.
 - Commands run with shell execution, so complex commands and piping are supported.
 - If a command fails, the error is logged, but setup continues with the next command.
 - The setup runs after worktree creation but before dependency installation (if `--install` is used).
+ - Only allowlisted commands are executed. Default allowlist: `npm install`, `yarn install`, `pnpm install`, `git submodule update --init --recursive`. Others are skipped with a warning.
build/commands/merge.js (1)

58-63: Fail fast: run bare-repo guard before merge/remove steps.

Run the guard immediately after repo validation to avoid partial operations and clearer errors.

Example insertion near Line 8 (after rev-parse):

if (await isMainRepoBare()) {
  console.error(chalk.red("❌ Error: The main repository is configured as 'bare' (core.bare=true)."));
  console.error(chalk.red("   This prevents normal Git operations. Please fix the configuration:"));
  console.error(chalk.cyan("   git config core.bare false"));
  process.exit(1);
}
build/commands/remove.js (1)

45-54: Early bare check and add -- sentinel to git args.

  • Guard earlier for faster failure and clearer UX.
  • Use -- to disambiguate path arguments.
@@
-        console.log(chalk.blue(`Removing worktree: ${targetPath}`));
-        // >>> ADD SAFETY CHECK HERE <<<
-        if (await isMainRepoBare()) {
+        console.log(chalk.blue(`Removing worktree: ${targetPath}`));
+        // Fail fast if main repo is bare
+        if (await isMainRepoBare()) {
             console.error(chalk.red("❌ Error: The main repository is configured as 'bare' (core.bare=true)."));
             console.error(chalk.red("   This prevents normal Git operations. Please fix the configuration:"));
             console.error(chalk.cyan("   git config core.bare false"));
             process.exit(1);
         }
@@
-        await execa("git", ["worktree", "remove", ...(options.force ? ["--force"] : []), targetPath]);
+        await execa("git", ["worktree", "remove", ...(options.force ? ["--force"] : []), "--", targetPath]);
build/commands/pr.js (2)

165-167: Harden git args with -- separator.

- await execa("git", ["worktree", "add", resolvedPath, prBranchName]);
+ await execa("git", ["worktree", "add", "--", resolvedPath, prBranchName]);

197-198: Unify editor launch behavior across commands.

This command uses { stdio: "ignore", detached: true } while others use { stdio: "inherit" }. Pick one behavior for consistency (detach vs block). I can update all handlers to match. Based on learnings.

build/commands/open.js (2)

8-14: Add bare-repo guard for consistency and clearer errors.

Insert the standard isMainRepoBare() guard after repo validation to align with other commands and avoid confusing errors on bare repos.

// After rev-parse validation
if (await isMainRepoBare()) {
  console.error(chalk.red("❌ Error: The main repository is configured as 'bare' (core.bare=true)."));
  console.error(chalk.red("   This prevents normal Git operations. Please fix the configuration:"));
  console.error(chalk.cyan("   git config core.bare false"));
  process.exit(1);
}

70-75: Align editor spawn behavior across commands.

Decide whether handlers should detach or block and apply uniformly (this one blocks with inherit). I can submit a follow-up to normalize across new/pr/open. Based on learnings.

src/commands/new.ts (2)

98-178: Allowlist execution: avoid shell, map to file/args pairs.

You can keep the allowlist and improve safety by avoiding { shell: true }. Execute exact allowed commands via file+args.

-                        if (commands.length > 0) {
-                            // Define an allowlist of safe commands
-                            const allowedCommands = [
-                                "npm install",
-                                "yarn install", 
-                                "pnpm install",
-                                "git submodule update --init --recursive"
-                                // Add more allowed commands as needed
-                            ];
-                            const env = { ...process.env, ROOT_WORKTREE_PATH: repoRoot };
-                            for (const command of commands) {
-                                // Only execute if the command is in the allowlist
-                                if (allowedCommands.includes(command)) {
-                                    console.log(chalk.gray(`Executing: ${command}`));
-                                    try {
-                                        await execa(command, { shell: true, cwd: resolvedPath, env, stdio: "inherit" });
-                                    } catch (cmdError: unknown) {
+                        if (commands.length > 0) {
+                            // Define an allowlist of safe commands mapped to file/args
+                            const allowedMap: Record<string, { file: string; args: string[] }> = {
+                                "npm install": { file: "npm", args: ["install"] },
+                                "yarn install": { file: "yarn", args: ["install"] },
+                                "pnpm install": { file: "pnpm", args: ["install"] },
+                                "git submodule update --init --recursive": {
+                                    file: "git",
+                                    args: ["submodule", "update", "--init", "--recursive"],
+                                },
+                            };
+                            const env = { ...process.env, ROOT_WORKTREE_PATH: repoRoot };
+                            for (const command of commands) {
+                                const entry = allowedMap[command];
+                                if (entry) {
+                                    console.log(chalk.gray(`Executing: ${command}`));
+                                    try {
+                                        await execa(entry.file, entry.args, { cwd: resolvedPath, env, stdio: "inherit" });
+                                    } catch (cmdError: unknown) {
                                         if (cmdError instanceof Error) {
                                             console.error(chalk.red(`Setup command failed: ${command}`), cmdError.message);
                                         } else {
                                             console.error(chalk.red(`Setup command failed: ${command}`), cmdError);
                                         }
                                         // Continue with other commands
                                     }
                                 } else {
                                     console.warn(chalk.yellow(`Skipped unsafe or unapproved setup command: "${command}"`));
                                 }
                             }
                             console.log(chalk.green("Setup commands completed."));

82-96: Harden git args with -- separator.

- await execa("git", ["worktree", "add", "-b", branchName, resolvedPath]);
+ await execa("git", ["worktree", "add", "-b", branchName, "--", resolvedPath]);
@@
- await execa("git", ["worktree", "add", resolvedPath, branchName]);
+ await execa("git", ["worktree", "add", "--", resolvedPath, branchName]);
build/commands/new.js (1)

126-132: Consider expanding the allowlist to include common setup commands.

The current allowlist includes only 4 commands:

  • npm install
  • yarn install
  • pnpm install
  • git submodule update --init --recursive

Consider adding other common, safe setup commands such as:

  • bun install (for Bun package manager)
  • npm ci (for CI environments)
  • pnpm ci (if supported)
  • git lfs pull (for Git LFS repositories)

This would make the feature more flexible while maintaining security through the allowlist approach.

build/commands/extract.js (1)

60-84: Extract the path derivation logic into a shared utility function.

The path derivation logic (lines 66-72) is duplicated from build/commands/new.js (lines 29-36). This is a clear case of code duplication that could be refactored into a shared utility function.

Consider creating a shared utility function in build/utils/path.js or similar:

/**
 * Derives a worktree folder path based on branch name
 * @param {string} branchName - The branch name to derive path from
 * @param {string} [customPath] - Optional custom path override
 * @returns {string} The resolved absolute path for the worktree
 */
export function deriveWorktreePath(branchName, customPath) {
  if (customPath) {
    return resolve(customPath);
  }
  
  const shortBranchName = branchName.split('/').filter(part => part.length > 0).pop() || branchName;
  const currentDir = process.cwd();
  const parentDir = dirname(currentDir);
  const currentDirName = basename(currentDir);
  const folderName = join(parentDir, `${currentDirName}-${shortBranchName}`);
  
  return resolve(folderName);
}

Then update both new.js and extract.js to use this shared function.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 972a449 and 6e454ae.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (11)
  • README.md (1 hunks)
  • build/commands/extract.js (1 hunks)
  • build/commands/merge.js (2 hunks)
  • build/commands/new.js (3 hunks)
  • build/commands/open.js (1 hunks)
  • build/commands/pr.js (2 hunks)
  • build/commands/remove.js (2 hunks)
  • build/index.js (2 hunks)
  • build/utils/git.js (1 hunks)
  • src/commands/new.ts (2 hunks)
  • src/utils/git.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/commands/new.ts

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

Implement logic for creating new Git worktrees, including options for branch creation, dependency installation, and opening in an editor, in src/commands/new.ts.

Files:

  • src/commands/new.ts
src/commands/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

src/commands/*.ts: Leverage Execa to execute Git commands and other external processes.
Provide clear, colored console feedback for success and error messages in CLI commands.

Files:

  • src/commands/new.ts
🧠 Learnings (6)
📓 Common learnings
Learnt from: juristr
PR: johnlindquist/worktree-cli#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.
Learnt from: CR
PR: johnlindquist/worktree-cli#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.
📚 Learning: 2025-08-04T13:02:29.847Z
Learnt from: CR
PR: johnlindquist/worktree-cli#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.

Applied to files:

  • README.md
  • build/commands/extract.js
  • src/commands/new.ts
  • build/commands/open.js
  • build/index.js
  • build/commands/new.js
📚 Learning: 2025-08-04T13:02:29.847Z
Learnt from: CR
PR: johnlindquist/worktree-cli#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.

Applied to files:

  • README.md
  • src/commands/new.ts
  • build/index.js
📚 Learning: 2025-08-04T14:22:29.156Z
Learnt from: juristr
PR: johnlindquist/worktree-cli#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.

Applied to files:

  • build/commands/extract.js
  • build/index.js
📚 Learning: 2025-08-04T13:02:29.847Z
Learnt from: CR
PR: johnlindquist/worktree-cli#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.

Applied to files:

  • build/commands/extract.js
  • build/commands/remove.js
  • src/commands/new.ts
  • build/index.js
📚 Learning: 2025-08-04T13:02:29.847Z
Learnt from: CR
PR: johnlindquist/worktree-cli#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.

Applied to files:

  • src/commands/new.ts
🧬 Code graph analysis (10)
build/commands/extract.js (3)
build/utils/git.js (5)
  • execa (5-5)
  • execa (19-19)
  • execa (68-68)
  • isWorktreeClean (14-40)
  • isMainRepoBare (42-65)
build/commands/new.js (6)
  • folderName (24-24)
  • shortBranchName (31-31)
  • currentDir (32-32)
  • parentDir (33-33)
  • currentDirName (34-34)
  • resolvedPath (38-38)
build/config.js (1)
  • getDefaultEditor (23-25)
build/commands/remove.js (1)
build/utils/git.js (1)
  • isMainRepoBare (42-65)
build/commands/pr.js (2)
build/utils/git.js (1)
  • isMainRepoBare (42-65)
src/utils/git.ts (1)
  • isMainRepoBare (44-68)
src/utils/git.ts (1)
build/utils/git.js (4)
  • getRepoRoot (66-75)
  • execa (5-5)
  • execa (19-19)
  • execa (68-68)
src/commands/new.ts (2)
build/commands/new.js (10)
  • repoRoot (90-90)
  • setupFilePath (92-92)
  • setupData (93-93)
  • cursorSetupPath (95-95)
  • fallbackSetupPath (102-102)
  • setupContent (114-114)
  • commands (116-116)
  • allowedCommands (126-132)
  • env (133-133)
  • resolvedPath (38-38)
src/utils/git.ts (1)
  • getRepoRoot (70-78)
build/commands/open.js (2)
build/commands/remove.js (7)
  • targetPath (14-14)
  • isDirectory (16-16)
  • stats (18-18)
  • entries (28-28)
  • currentPath (29-29)
  • fullBranchRef (35-35)
  • shortBranch (36-36)
build/config.js (1)
  • getDefaultEditor (23-25)
build/utils/git.js (1)
src/utils/git.ts (2)
  • isMainRepoBare (44-68)
  • getRepoRoot (70-78)
build/commands/merge.js (1)
build/utils/git.js (1)
  • isMainRepoBare (42-65)
build/index.js (2)
build/commands/open.js (1)
  • openWorktreeHandler (6-87)
build/commands/extract.js (1)
  • extractWorktreeHandler (7-135)
build/commands/new.js (1)
src/utils/git.ts (3)
  • isWorktreeClean (15-40)
  • isMainRepoBare (44-68)
  • getRepoRoot (70-78)
🔇 Additional comments (18)
build/index.js (3)

10-11: LGTM!

The import statements for the new command handlers follow the established pattern and are correctly structured.


56-69: LGTM!

The CLI command registrations for open and extract are well-structured and consistent with existing command patterns in this file. The argument definitions, options, and descriptions are clear and match the functionality described in the PR objectives.


73-84: Confirm backward compatibility of config commands All README.md examples (lines 64–75) still use wt config set editor, wt config get editor, and wt config path; the restructuring doesn’t break existing usage.

build/commands/new.js (4)

4-4: LGTM!

The import of readFile is necessary for reading setup configuration files.


7-7: LGTM!

The additional utility imports (isMainRepoBare, getRepoRoot) are correctly added and used for repository validation.


14-18: LGTM!

The enhanced error messages provide clear, actionable guidance to users when the main worktree is not clean. The use of colored output (red, yellow, cyan) improves readability and user experience.


74-79: LGTM!

The bare repository check is correctly placed before worktree creation and provides clear remediation steps. This prevents users from encountering cryptic Git errors during worktree operations.

build/commands/extract.js (11)

1-6: LGTM!

The imports are appropriate and follow the established pattern. All required utilities are correctly imported.


7-21: LGTM!

The repository and worktree cleanliness validation follows the same pattern as in new.js, ensuring consistency across commands. The error messages are clear and actionable.

Based on learnings.


22-34: LGTM!

The branch determination logic correctly handles both explicit branch names and defaults to the current branch. The detached HEAD check is a good safety measure.


35-45: LGTM!

The existing worktree check prevents duplicate worktrees for the same branch. The use of --porcelain output for parsing is the correct approach.


46-59: LGTM!

The branch existence verification checks both local and remote branches, which is essential before attempting to create a worktree. The logic correctly handles both scenarios.


85-91: LGTM!

The bare repository check is correctly placed and provides clear remediation guidance. This matches the implementation in new.js.


92-104: LGTM!

The worktree creation logic correctly handles both local and remote-only branches. The use of --track flag for remote branches is appropriate.


105-109: LGTM!

The optional dependency installation follows the same pattern as other commands and uses stdio: "inherit" to show progress to the user.


110-120: LGTM!

The editor opening logic is robust with appropriate error handling. The command continues even if the editor fails to launch, which is the correct behavior.


121-124: LGTM!

The success messages are clear and provide useful feedback to the user. The conditional message for dependency installation is a nice touch.


126-134: LGTM!

The error handling correctly distinguishes between Error instances and other types of errors, providing appropriate error messages in both cases.

@johnlindquist
Copy link
Owner

@felipeS I'd prefer with this was under a "wt setup" command which represents "new and run scripts". Thoughts?

@felipeS
Copy link
Contributor Author

felipeS commented Oct 13, 2025

@felipeS I'd prefer with this was under a "wt setup" command which represents "new and run scripts". Thoughts?

Hey John, yes I think that is a better idea. Will do it

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: 5

♻️ Duplicate comments (1)
README.md (1)

113-131: Examples include commands that won’t be on an allowlist

If we enforce an allowlist (per PR objectives), cp and echo will be skipped. Update examples to only include allowed commands.

Apply this diff:

@@
   "setup-worktree": [
-    "npm install",
-    "cp $ROOT_WORKTREE_PATH/.local.env .local.env",
-    "echo 'Setup complete'"
+    "npm install",
+    "git submodule update --init --recursive"
   ]
@@
 [
-  "npm install",
-  "cp $ROOT_WORKTREE_PATH/.local.env .local.env",
-  "echo 'Setup complete'"
+  "npm install",
+  "git submodule update --init --recursive"
 ]
🧹 Nitpick comments (2)
src/commands/setup.ts (1)

89-96: Use of --checkout flag (optional)

Consider honoring options.checkout when the branch is missing locally/remotely:

  • If not present and branch missing, print guidance and exit.
  • If present, create the new branch with -b.

This aligns with the option description.

src/index.ts (1)

45-66: Remove or implement the unused -c/--checkout option
The setupWorktreeHandler never reads options.checkout, so the flag is a no-op. Either drop it from the CLI definition or add conditional checkout logic in the handler.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e454ae and 41c8c9e.

📒 Files selected for processing (6)
  • README.md (2 hunks)
  • build/commands/new.js (2 hunks)
  • build/commands/setup.js (1 hunks)
  • build/index.js (3 hunks)
  • src/commands/setup.ts (1 hunks)
  • src/index.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • build/commands/new.js
🧰 Additional context used
📓 Path-based instructions (2)
src/index.ts

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

src/index.ts: The main entry point for the CLI is src/index.ts, which sets up CLI commands and orchestrates command handlers.
Utilize Commander for parsing CLI commands and handling options.

Files:

  • src/index.ts
src/commands/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

src/commands/*.ts: Leverage Execa to execute Git commands and other external processes.
Provide clear, colored console feedback for success and error messages in CLI commands.

Files:

  • src/commands/setup.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
PR: johnlindquist/worktree-cli#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.
📚 Learning: 2025-08-04T13:02:29.847Z
Learnt from: CR
PR: johnlindquist/worktree-cli#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.

Applied to files:

  • src/index.ts
  • src/commands/setup.ts
  • README.md
  • build/commands/setup.js
  • build/index.js
📚 Learning: 2025-08-04T13:02:29.847Z
Learnt from: CR
PR: johnlindquist/worktree-cli#0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/index.ts : The main entry point for the CLI is src/index.ts, which sets up CLI commands and orchestrates command handlers.

Applied to files:

  • src/index.ts
📚 Learning: 2025-08-04T13:02:29.847Z
Learnt from: CR
PR: johnlindquist/worktree-cli#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.

Applied to files:

  • src/index.ts
  • src/commands/setup.ts
  • build/commands/setup.js
  • build/index.js
📚 Learning: 2025-08-04T13:02:29.847Z
Learnt from: CR
PR: johnlindquist/worktree-cli#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.

Applied to files:

  • src/commands/setup.ts
  • build/commands/setup.js
  • build/index.js
🧬 Code graph analysis (4)
src/index.ts (2)
build/commands/setup.js (1)
  • setupWorktreeHandler (8-223)
src/commands/setup.ts (1)
  • setupWorktreeHandler (9-232)
src/commands/setup.ts (3)
build/commands/setup.js (18)
  • setupWorktreeHandler (8-223)
  • isClean (13-13)
  • folderName (24-24)
  • shortBranchName (31-31)
  • currentDir (32-32)
  • parentDir (33-33)
  • currentDirName (34-34)
  • resolvedPath (38-38)
  • directoryExists (40-40)
  • localBranches (49-49)
  • remoteBranches (50-50)
  • branchExists (51-51)
  • isGitWorktree (56-56)
  • repoRoot (90-90)
  • commands (116-116)
  • env (146-146)
  • configuredEditor (197-197)
  • editorCommand (198-198)
build/commands/pr.js (1)
  • branchName (20-20)
build/commands/new.js (14)
  • isClean (12-12)
  • folderName (23-23)
  • shortBranchName (30-30)
  • currentDir (31-31)
  • parentDir (32-32)
  • currentDirName (33-33)
  • resolvedPath (37-37)
  • directoryExists (39-39)
  • localBranches (48-48)
  • remoteBranches (49-49)
  • branchExists (50-50)
  • isGitWorktree (55-55)
  • configuredEditor (95-95)
  • editorCommand (96-96)
build/commands/setup.js (1)
src/commands/setup.ts (1)
  • setupWorktreeHandler (9-232)
build/index.js (3)
build/commands/setup.js (1)
  • setupWorktreeHandler (8-223)
build/commands/open.js (1)
  • openWorktreeHandler (6-87)
build/commands/extract.js (1)
  • extractWorktreeHandler (7-135)
🔇 Additional comments (5)
src/index.ts (1)

6-6: Wiring OK

Importing setupWorktreeHandler looks correct and consistent with the new command.

README.md (1)

33-51: Docs for wt setup look good

Section introduces the new command and options clearly.

build/index.js (3)

4-4: Wiring OK

setupWorktreeHandler import is correct; CLI surface reflects the new command.


27-35: New setup command registered correctly

Options and description match src/index.ts.


67-79: open/extract commands registration looks good

Handlers and options align with respective implementations.

Comment on lines +55 to +57
const { stdout: localBranches } = await execa("git", ["branch", "--list", branchName]);
const { stdout: remoteBranches } = await execa("git", ["branch", "-r", "--list", `origin/${branchName}`]);
const branchExists = !!localBranches || !!remoteBranches;
Copy link

@coderabbitai coderabbitai bot Oct 13, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Remote-only branch case breaks: use --track for origin/

If the branch exists only on origin, current logic treats it as “exists” and runs git worktree add , which fails. Mirror extract’s behavior by handling remote-only branches.

Apply this diff:

-        const { stdout: localBranches } = await execa("git", ["branch", "--list", branchName]);
-        const { stdout: remoteBranches } = await execa("git", ["branch", "-r", "--list", `origin/${branchName}`]);
-        const branchExists = !!localBranches || !!remoteBranches;
+        const { stdout: localBranches } = await execa("git", ["branch", "--list", branchName]);
+        const { stdout: remoteBranches } = await execa("git", ["branch", "-r", "--list", `origin/${branchName}`]);
+        const branchExistsLocally = localBranches.trim() !== "";
+        const branchExistsRemotely = remoteBranches.trim() !== "";
@@
-            if (!branchExists) {
-                console.log(chalk.yellow(`Branch "${branchName}" doesn't exist. Creating new branch with worktree...`));
-                // Create a new branch and worktree in one command with -b flag
-                await execa("git", ["worktree", "add", "-b", branchName, resolvedPath]);
-            } else {
-                console.log(chalk.green(`Using existing branch "${branchName}".`));
-                await execa("git", ["worktree", "add", resolvedPath, branchName]);
-            }
+            if (!branchExistsLocally && branchExistsRemotely) {
+                console.log(chalk.yellow(`Branch "${branchName}" is remote-only. Creating local tracking branch...`));
+                await execa("git", ["worktree", "add", "--track", "-b", branchName, resolvedPath, `origin/${branchName}`]);
+            } else if (!branchExistsLocally && !branchExistsRemotely) {
+                console.log(chalk.yellow(`Branch "${branchName}" doesn't exist locally or remotely. Creating new branch with worktree...`));
+                await execa("git", ["worktree", "add", "-b", branchName, resolvedPath]);
+            } else {
+                console.log(chalk.green(`Using existing branch "${branchName}".`));
+                await execa("git", ["worktree", "add", resolvedPath, branchName]);
+            }

Also applies to: 89-96

🤖 Prompt for AI Agents
In src/commands/setup.ts around lines 55-57 (and similarly 89-96), the code
treats a branch that exists only on origin as if it exists locally and later
runs git worktree add <path> <branchName>, which fails; change the branch
detection to distinguish local vs remote: consider branchExistsLocal =
!!localBranches and branchExistsRemote = !!remoteBranches, and when the branch
is remote-only (branchExistsRemote && !branchExistsLocal) create the worktree
using the tracked remote branch (for example invoke git worktree add <path>
--track origin/<branchName> or use git worktree add -b <branchName> <path>
origin/<branchName>), otherwise proceed with the existing local-branch flow;
apply the same distinction/fix for the code at lines 89-96.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@johnlindquist this doesn't seem like a problem to me, wdyt?

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

@felipeS
Copy link
Contributor Author

felipeS commented Oct 13, 2025

@coderabbitai generate docstrings

@coderabbitai
Copy link

coderabbitai bot commented Oct 13, 2025

✅ Actions performed

Initiated docstring generation; will generate only if new commits exist.

@coderabbitai
Copy link

coderabbitai bot commented Oct 13, 2025

Note

Docstrings generation - SUCCESS
Generated docstrings for this pull request at #23

coderabbitai bot added a commit that referenced this pull request Oct 13, 2025
Docstrings generation was requested by @felipeS.

* #22 (comment)

The following files were modified:

* `src/commands/setup.ts`
* `src/utils/git.ts`
@felipeS felipeS mentioned this pull request Oct 15, 2025
@johnlindquist johnlindquist merged commit e1d95fe into johnlindquist:main Oct 15, 2025
1 check passed
@github-actions
Copy link

🎉 This PR is included in version 2.12.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants