-
Notifications
You must be signed in to change notification settings - Fork 5.5k
feat: Add git workflow configuration to specify init #1483
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
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>
There was a problem hiding this 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.jsonwith 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.
src/specify_cli/__init__.py
Outdated
| 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]", |
Copilot
AI
Jan 17, 2026
There was a problem hiding this comment.
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.
| 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]", |
src/specify_cli/__init__.py
Outdated
| # 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') |
Copilot
AI
Jan 17, 2026
There was a problem hiding this comment.
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.
|
|
||
| # Git workflow mode selection | ||
| selected_git_mode = "branch" # Default | ||
| selected_worktree_strategy = "sibling" # Default |
Copilot
AI
Jan 17, 2026
There was a problem hiding this comment.
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.
| selected_worktree_strategy = "sibling" # Default | |
| selected_worktree_strategy = "nested" # Default |
- 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>
There was a problem hiding this 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.
| sibling) | ||
| # Sibling uses repo_name-branch_name for clarity | ||
| echo "$(dirname "$repo_root")/${repo_name}-${branch_name}" |
Copilot
AI
Jan 17, 2026
There was a problem hiding this comment.
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.
| 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
AI
Jan 17, 2026
There was a problem hiding this comment.
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".
| local strategy | ||
| local custom_path | ||
|
|
||
| strategy=$(read_config_value "worktree_strategy" "nested") |
Copilot
AI
Jan 17, 2026
There was a problem hiding this comment.
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".
| if [[ ! -f "$CONFIG_FILE" ]]; then | ||
| echo "No configuration file found. Using defaults:" | ||
| echo " git_mode: branch" | ||
| echo " worktree_strategy: sibling" |
Copilot
AI
Jan 17, 2026
There was a problem hiding this comment.
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".
|
|
||
| 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" |
Copilot
AI
Jan 17, 2026
There was a problem hiding this comment.
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.
| 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" |
| nested) | ||
| # Nested uses just branch name since it's inside the repo | ||
| echo "$repo_root/.worktrees/$branch_name" |
Copilot
AI
Jan 17, 2026
There was a problem hiding this comment.
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.
|
|
||
| # Read existing values | ||
| CURRENT_MODE=$(read_config_value "git_mode" "branch") | ||
| CURRENT_STRATEGY=$(read_config_value "worktree_strategy" "sibling") |
Copilot
AI
Jan 17, 2026
There was a problem hiding this comment.
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".
| 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")" |
Copilot
AI
Jan 17, 2026
There was a problem hiding this comment.
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".
| 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")" |
|
|
||
| # Read existing values | ||
| CURRENT_MODE=$(read_config_value "git_mode" "branch") | ||
| CURRENT_STRATEGY=$(read_config_value "worktree_strategy" "nested") |
Copilot
AI
Jan 17, 2026
There was a problem hiding this comment.
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".
|
|
||
| echo "Configuration updated:" | ||
| echo " git_mode: $(read_config_value "git_mode" "branch")" | ||
| echo " worktree_strategy: $(read_config_value "worktree_strategy" "nested")" |
Copilot
AI
Jan 17, 2026
There was a problem hiding this comment.
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".
- 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>
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
--git-modebranch/worktreebranch--worktree-strategysibling/nested/customsibling--worktree-pathcustom)Key Features
--no-git+--git-mode worktree.worktrees/to.gitignorefor nested strategyspecify.mdtemplate)<repo>-<branch>for sibling/custom strategiesUsage Examples
Test Plan
specify initwith--git-mode worktree --worktree-strategy siblingspecify initwith--git-mode worktree --worktree-strategy nested.gitignoreupdated for nested strategy--no-git --git-mode worktree/speckit.specifyand verify worktree created with correct namingAI 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