Skip to content

Conversation

@brianluby
Copy link

Summary

Add the ability to configure git worktree mode during project initialization via specify init, allowing users to develop multiple features simultaneously in parallel directories.

New CLI Options

Option Values Default Description
--git-mode branch / worktree branch Git workflow mode
--worktree-strategy sibling / nested / custom sibling Worktree location
--worktree-path absolute path - Custom path (if strategy is custom)

Key Features

  • Interactive selection for git workflow when options not provided
  • Git 2.5+ version check before allowing worktree mode
  • Conflict detection for --no-git + --git-mode worktree
  • Worktree location preview before confirmation
  • Auto-add .worktrees/ to .gitignore for nested strategy
  • Prominent agent notification when worktree mode is used (via specify.md template)
  • Naming convention: <repo>-<branch> for sibling/custom strategies

Usage Examples

# Interactive mode
specify init my-project --ai claude

# Explicit worktree with sibling strategy
specify init my-project --git-mode worktree --worktree-strategy sibling

# Worktree with nested strategy
specify init my-project --git-mode worktree --worktree-strategy nested

Test Plan

  • Test specify init with --git-mode worktree --worktree-strategy sibling
  • Test specify init with --git-mode worktree --worktree-strategy nested
  • Verify .gitignore updated for nested strategy
  • Test conflict detection: --no-git --git-mode worktree
  • Test with Git < 2.5 (should error gracefully)
  • Run /speckit.specify and verify worktree created with correct naming
  • Verify agent notification appears in worktree mode

AI Disclosure

This PR was developed with assistance from Claude Code (Claude Opus 4.5). Changes were tested and reviewed by the contributor.

🤖 Generated with Claude Code

brianluby and others added 3 commits January 15, 2026 17:54
Enable developers to work on multiple features simultaneously using git
worktrees instead of switching branches. Features can be created in
separate working directories, allowing independent development contexts.

Key changes:
- Add configure-worktree.sh/ps1 scripts for mode and strategy configuration
- Extend create-new-feature scripts with worktree creation and fallback logic
- Add read_config_value/Get-ConfigValue functions to common scripts
- Extend JSON output with FEATURE_ROOT and MODE fields for AI agent context
- Support nested, sibling, and custom worktree placement strategies
- Add graceful fallback to branch mode on worktree creation failure
- Detect and warn about uncommitted changes and orphaned worktrees

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add .specify/config.json to .gitignore to prevent committing local
  preferences with absolute paths
- Add warning when jq is not available and config file will be
  overwritten with only worktree settings
- Fix brittle JSON parsing in common.sh to support booleans and numbers
- Add nullglob handling for empty specs directory in get_highest_from_specs
- Improve fallback warning messages to clarify context switch when
  worktree creation fails and branch mode is used
- Sync updated scripts to .specify/scripts/bash/

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add the ability to configure git worktree mode during project
initialization, allowing users to develop multiple features in
parallel directories.

New CLI options:
- --git-mode (branch/worktree)
- --worktree-strategy (sibling/nested/custom)
- --worktree-path (for custom strategy)

Improvements:
- Interactive selection for git workflow when options not provided
- Git 2.5+ version check for worktree support
- Conflict detection for --no-git + --git-mode worktree
- Worktree location preview before confirmation
- Auto-add .worktrees/ to .gitignore for nested strategy
- Prominent agent notification when worktree mode is used

Worktree naming convention updated to <repo>-<branch> for
sibling and custom strategies for better clarity.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings January 17, 2026 15:46
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 adds comprehensive git worktree support to Spec Kit, enabling users to develop multiple features simultaneously in parallel directories instead of switching branches in a single working copy. The implementation allows project initialization with worktree configuration via new CLI options.

Changes:

  • Added CLI options for git workflow mode selection (branch vs worktree) with three worktree strategies (sibling, nested, custom)
  • Implemented git version checking to ensure Git 2.5+ for worktree support
  • Created configuration persistence in .specify/config.json with merge support
  • Updated shell scripts (bash and PowerShell) to support worktree creation with automatic fallback to branch mode
  • Modified agent instruction templates to notify about worktree directory switching requirements

Reviewed changes

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

Show a summary per file
File Description
src/specify_cli/init.py Added CLI options, git version validation, interactive selection, config writing, and user notifications for worktree workflow
templates/commands/specify.md Added instructions for agents to handle worktree mode and display directory switching warnings
scripts/bash/create-new-feature.sh Implemented worktree path calculation and creation logic with fallback to branch mode
scripts/powershell/create-new-feature.ps1 PowerShell equivalent of bash worktree implementation
scripts/bash/configure-worktree.sh New script for post-init worktree configuration changes
scripts/powershell/configure-worktree.ps1 PowerShell equivalent of configure-worktree script
scripts/bash/common.sh Added read_config_value function for config file parsing
scripts/powershell/common.ps1 Added Get-ConfigValue function for config file parsing
.specify/scripts/bash/* Template copies of bash scripts for distribution
WORKTREE_DESIGN.md Design documentation for worktree feature architecture
specs/001-git-worktrees/CHANGELOG.md Comprehensive feature changelog and usage documentation
.gitignore Added exclusions for config.json and .worktrees/ directory

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 1466 to 1470
worktree_notice = Panel(
f"[bold]Git Worktree Mode Enabled[/bold]\n\n"
f"When you run [cyan]/speckit.specify[/cyan], each feature will be created in its own directory {location_desc}.\n\n"
f"[yellow]Important:[/yellow] After creating a feature, you must switch your coding agent/IDE to the new worktree directory to continue working on that feature.\n\n"
f"To change this later, run: [cyan].specify/scripts/bash/configure-worktree.sh --show[/cyan]",
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

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

The worktree notice at line 1470 hardcodes the bash script path ".specify/scripts/bash/configure-worktree.sh", but users who selected PowerShell as their script type (via --script ps or interactive selection) would need to use ".specify/scripts/powershell/configure-worktree.ps1" instead. The notice should use the selected_script variable to show the appropriate script path for the user's chosen script type.

Suggested change
worktree_notice = Panel(
f"[bold]Git Worktree Mode Enabled[/bold]\n\n"
f"When you run [cyan]/speckit.specify[/cyan], each feature will be created in its own directory {location_desc}.\n\n"
f"[yellow]Important:[/yellow] After creating a feature, you must switch your coding agent/IDE to the new worktree directory to continue working on that feature.\n\n"
f"To change this later, run: [cyan].specify/scripts/bash/configure-worktree.sh --show[/cyan]",
configure_worktree_script_map = {
"bash": ".specify/scripts/bash/configure-worktree.sh",
"ps": ".specify/scripts/powershell/configure-worktree.ps1",
}
configure_worktree_script = configure_worktree_script_map.get(
selected_script,
configure_worktree_script_map["bash"],
)
worktree_notice = Panel(
f"[bold]Git Worktree Mode Enabled[/bold]\n\n"
f"When you run [cyan]/speckit.specify[/cyan], each feature will be created in its own directory {location_desc}.\n\n"
f"[yellow]Important:[/yellow] After creating a feature, you must switch your coding agent/IDE to the new worktree directory to continue working on that feature.\n\n"
f"To change this later, run: [cyan]{configure_worktree_script} --show[/cyan]",

Copilot uses AI. Check for mistakes.
Comment on lines 1376 to 1381
# Ensure we start on a new line
if gitignore_path.exists():
with open(gitignore_path, 'r', encoding='utf-8') as rf:
content = rf.read()
if content and not content.endswith('\n'):
f.write('\n')
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

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

There's a duplicate file read operation inside the gitignore update block. Lines 1377-1378 open and read the file to check if content ends with a newline, but this same file was just read at lines 1366-1367. The content from the first read should be reused to avoid redundant I/O operations.

Copilot uses AI. Check for mistakes.

# Git workflow mode selection
selected_git_mode = "branch" # Default
selected_worktree_strategy = "sibling" # Default
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

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

The worktree strategy default value inconsistency could lead to confusion. In the Python CLI at line 1161, the default is set to "sibling", but in multiple other locations (bash/PowerShell scripts at lines with read_config_value calls, and in WORKTREE_DESIGN.md at line 22), the default is "nested". This inconsistency means the behavior will differ depending on whether a user explicitly sets the strategy during init or relies on defaults in the scripts.

Suggested change
selected_worktree_strategy = "sibling" # Default
selected_worktree_strategy = "nested" # Default

Copilot uses AI. Check for mistakes.
brianluby and others added 2 commits January 17, 2026 08:01
- Use selected_script to show correct configure-worktree script path
  (bash vs PowerShell) in worktree mode notice
- Remove duplicate file read in gitignore update logic
- Standardize default worktree strategy to "sibling" across all files
  (CLI, bash scripts, PowerShell scripts, design doc)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previous commit incorrectly replaced all occurrences of "nested" with
"sibling". This fix restores the configure-worktree scripts and only
updates the default values shown when displaying configuration.

"nested" remains a valid strategy option alongside "sibling" and "custom".

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings January 17, 2026 16:39
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

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


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +186 to +188
sibling)
# Sibling uses repo_name-branch_name for clarity
echo "$(dirname "$repo_root")/${repo_name}-${branch_name}"
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

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

The naming convention for custom strategy is inconsistent with the Python init command. The init command uses repo_name-branch_name format (e.g., "/tmp/worktrees/spec-kit-001-user-auth"), but this calculate_worktree_path function uses just branch_name. This will cause worktrees to be created in different locations than what was previewed during init.

Copilot uses AI. Check for mistakes.
else
echo "Current configuration ($CONFIG_FILE):"
echo " git_mode: $(read_config_value "git_mode" "branch")"
echo " worktree_strategy: $(read_config_value "worktree_strategy" "sibling")"
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

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

The default value for worktree_strategy in the scripts is "nested", but the init command and documentation state the default is "sibling". This creates an inconsistency where the --show command will display "nested" as default instead of "sibling".

Copilot uses AI. Check for mistakes.
local strategy
local custom_path

strategy=$(read_config_value "worktree_strategy" "nested")
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

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

The default value for worktree_strategy should be "sibling" to match the constants and init command default, not "nested". This inconsistency could cause unexpected behavior where users select "sibling" during init but the script defaults to "nested".

Copilot uses AI. Check for mistakes.
if [[ ! -f "$CONFIG_FILE" ]]; then
echo "No configuration file found. Using defaults:"
echo " git_mode: branch"
echo " worktree_strategy: sibling"
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

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

The default value for worktree_strategy in the scripts is "nested", but the init command and documentation state the default is "sibling". This creates an inconsistency where the --show command will display "nested" as default instead of "sibling".

Copilot uses AI. Check for mistakes.
Comment on lines 174 to 196

strategy=$(read_config_value "worktree_strategy" "nested")
custom_path=$(read_config_value "worktree_custom_path" "")

case "$strategy" in
nested)
echo "$repo_root/.worktrees/$branch_name"
;;
sibling)
# Sibling to the repository
echo "$(dirname "$repo_root")/$branch_name"
;;
custom)
if [[ -n "$custom_path" ]]; then
echo "$custom_path/$branch_name"
else
# Fallback to nested if custom path not set
echo "$repo_root/.worktrees/$branch_name"
fi
;;
*)
# Default to nested
echo "$repo_root/.worktrees/$branch_name"
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

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

The naming convention for custom strategy is inconsistent with the Python init command. The init command uses repo_name-branch_name format (e.g., "/tmp/worktrees/spec-kit-001-user-auth"), but this calculate_worktree_path function uses just branch_name. This will cause worktrees to be created in different locations than what was previewed during init.

Suggested change
strategy=$(read_config_value "worktree_strategy" "nested")
custom_path=$(read_config_value "worktree_custom_path" "")
case "$strategy" in
nested)
echo "$repo_root/.worktrees/$branch_name"
;;
sibling)
# Sibling to the repository
echo "$(dirname "$repo_root")/$branch_name"
;;
custom)
if [[ -n "$custom_path" ]]; then
echo "$custom_path/$branch_name"
else
# Fallback to nested if custom path not set
echo "$repo_root/.worktrees/$branch_name"
fi
;;
*)
# Default to nested
echo "$repo_root/.worktrees/$branch_name"
local repo_name
local worktree_dir_name
strategy=$(read_config_value "worktree_strategy" "nested")
custom_path=$(read_config_value "worktree_custom_path" "")
# Use same naming convention as Python init: repo_name-branch_name
repo_name="${repo_root##*/}"
worktree_dir_name="${repo_name}-${branch_name}"
case "$strategy" in
nested)
echo "$repo_root/.worktrees/$worktree_dir_name"
;;
sibling)
# Sibling to the repository
echo "$(dirname "$repo_root")/$worktree_dir_name"
;;
custom)
if [[ -n "$custom_path" ]]; then
echo "$custom_path/$worktree_dir_name"
else
# Fallback to nested if custom path not set
echo "$repo_root/.worktrees/$worktree_dir_name"
fi
;;
*)
# Default to nested
echo "$repo_root/.worktrees/$worktree_dir_name"

Copilot uses AI. Check for mistakes.
Comment on lines +182 to +184
nested)
# Nested uses just branch name since it's inside the repo
echo "$repo_root/.worktrees/$branch_name"
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

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

The naming convention for sibling strategy is inconsistent with the Python init command. The init command uses repo_name-branch_name format (e.g., "spec-kit-001-user-auth"), but this calculate_worktree_path function uses just branch_name for sibling strategy. This will cause worktrees to be created in different locations than what was previewed during init.

Copilot uses AI. Check for mistakes.

# Read existing values
CURRENT_MODE=$(read_config_value "git_mode" "branch")
CURRENT_STRATEGY=$(read_config_value "worktree_strategy" "sibling")
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

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

The default value for worktree_strategy in the scripts is "nested", but the init command and documentation state the default is "sibling". This creates an inconsistency where the --show command will display "nested" as default instead of "sibling".

Copilot uses AI. Check for mistakes.
Comment on lines 104 to 109
echo " worktree_strategy: nested"
echo " worktree_custom_path: (none)"
else
echo "Current configuration ($CONFIG_FILE):"
echo " git_mode: $(read_config_value "git_mode" "branch")"
echo " worktree_strategy: $(read_config_value "worktree_strategy" "nested")"
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

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

The default value for worktree_strategy in the scripts is "nested", but the init command and documentation state the default is "sibling". This creates an inconsistency where the --show command will display "nested" as default instead of "sibling".

Suggested change
echo " worktree_strategy: nested"
echo " worktree_custom_path: (none)"
else
echo "Current configuration ($CONFIG_FILE):"
echo " git_mode: $(read_config_value "git_mode" "branch")"
echo " worktree_strategy: $(read_config_value "worktree_strategy" "nested")"
echo " worktree_strategy: sibling"
echo " worktree_custom_path: (none)"
else
echo "Current configuration ($CONFIG_FILE):"
echo " git_mode: $(read_config_value "git_mode" "branch")"
echo " worktree_strategy: $(read_config_value "worktree_strategy" "sibling")"

Copilot uses AI. Check for mistakes.

# Read existing values
CURRENT_MODE=$(read_config_value "git_mode" "branch")
CURRENT_STRATEGY=$(read_config_value "worktree_strategy" "nested")
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

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

The default value for worktree_strategy in the scripts is "nested", but the init command and documentation state the default is "sibling". This creates an inconsistency where the --show command will display "nested" as default instead of "sibling".

Copilot uses AI. Check for mistakes.

echo "Configuration updated:"
echo " git_mode: $(read_config_value "git_mode" "branch")"
echo " worktree_strategy: $(read_config_value "worktree_strategy" "nested")"
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

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

The default value for worktree_strategy in the scripts is "nested", but the init command and documentation state the default is "sibling". This creates an inconsistency where the --show command will display "nested" as default instead of "sibling".

Copilot uses AI. Check for mistakes.
- Update .specify/scripts/bash/configure-worktree.sh with sibling as
  default strategy (matching scripts/bash/ and Python init command)
- Update .specify/scripts/bash/create-new-feature.sh with:
  - sibling as default strategy
  - repo_name-branch_name naming convention for sibling/custom strategies
- Ensures installed scripts match source templates

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@brianluby brianluby marked this pull request as draft January 17, 2026 17:46
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