Summary
Extract all Git-related operations (repository detection, branch creation, branch numbering, branch validation, remote fetching, SPECIFY_FEATURE env var management) from core into a self-contained Spec Kit extension at extensions/git/. The extension auto-enables by default during the migration period (opt-out) to preserve backward compatibility. Before the 1.0.0 release, the extension will transition to opt-in — users will explicitly install it via specify extension add git or a preset that includes it.
Scope note: The --no-git flag on specify init is out of scope for this issue and will be handled separately.
Problem
Git branching logic is deeply interwoven across ~815+ lines in 10+ files spanning 4 languages (Python, Bash, PowerShell, Markdown). Analysis of upstream issues reveals this coupling causes problems across five distinct categories — not just maintenance burden, but workflow lock-in, blocked extensibility, spec iteration friction, and contributor barriers. See the original problem analysis in the collapsed section below for the full breakdown.
Delivered Solution
The extraction was implemented incrementally via a hook-based architecture rather than the originally planned "rip and replace" approach. This preserves backward compatibility while cleanly separating git operations from core.
Stage 1 — PR #1941 (merged)
Created extensions/git/ with the extension manifest, commands, hooks, and scripts:
extensions/git/
extension.yml
git-config.yml # Branch numbering config
commands/
speckit.git.feature.md # Branch creation
speckit.git.validate.md # Branch validation
speckit.git.commit.md # Auto-commit
speckit.git.remote.md # Remote URL detection
speckit.git.initialize.md # Git repo initialization
scripts/
bash/
create-new-feature.sh # Moved from scripts/bash/
git-common.sh # Git-specific subset of common.sh
initialize-repo.sh
powershell/
create-new-feature.ps1 # Moved from scripts/powershell/
git-common.ps1 # Git-specific subset of common.ps1
initialize-repo.ps1
Key deliverables:
Extension manifest with commands, hooks, and config.defaults
speckit.git.feature creates branches with sequential or timestamp numbering
speckit.git.validate validates feature branch naming conventions
speckit.git.remote detects Git remote URL for GitHub integration
speckit.git.commit auto-commits at hook points (optional)
speckit.git.initialize initializes Git repos
before_specify, before_constitution, and all other lifecycle hooks registered
Auto-install during specify init (unless --no-git)
Graceful degradation when git unavailable
Cross-platform bash and PowerShell scripts
Extension commands include explicit script paths (no {SCRIPT} placeholder reliance)
Extension reads branch_numbering from its own config (.specify/extensions/git/git-config.yml)
Stage 2 — PR #2117 (open)
Enhanced the extension and CLI with:
GIT_BRANCH_NAME env var override for exact branch naming (parallels SPECIFY_FEATURE_DIRECTORY)
--force flag for specify init <dir> into existing directories
Test coverage: TestGitExtensionAutoInstall (3 tests), TestFeatureDirectoryResolution (4 tests)
All 1,195 tests passing, 0 failures
What Moved Out of Core
From Core
To Extension
Status
Branch creation logic
speckit.git.feature command + scripts
✅ Done
Branch validation
speckit.git.validate command
✅ Done
Remote URL detection
speckit.git.remote command
✅ Done
create-new-feature.sh
extensions/git/scripts/bash/
✅ Done
create-new-feature.ps1
extensions/git/scripts/powershell/
✅ Done
check_feature_branch()
Duplicated to git-common.sh
✅ Done (core copy retained for backward compat)
--branch-numbering config
git-config.yml in extension
✅ Done (CLI flag retained for backward compat)
Branch creation section of specify.md
Hook-based via before_specify
✅ Done
What Stays in Core (by design)
Component
Reason
--no-git flag
Out of scope; handled separately
get_current_branch() in common.sh/.ps1
Called by all downstream commands; not git-specific
SPECIFY_FEATURE env var check
Core feature detection, not git-specific
specs/ directory scanning fallback
Non-git feature detection
find_feature_dir_by_prefix()
Spec directory resolution (works without git)
check_feature_branch() in core common.sh
Called by setup-plan.sh and check-prerequisites.sh; removing would break scripts when git extension is disabled
--branch-numbering CLI flag
Backward compatibility; writes to init-options.json which the extension reads as fallback
Extension Enable/Disable Behavior
The specify extension disable git and specify extension enable git commands already exist and work correctly. When disabled:
before_specify and other hooks don't fire (hook executor checks enabled status)
Spec directories are still created (core behavior, independent of git)
Identical to HAS_GIT=false behavior in scripts
Remaining Work (pre-1.0.0)
Acceptance Criteria
Related Issues & PRs
#
Title
Status
Relevance
Signal
#841
Support disabling automatic branch creation via parameter
open
Resolved: specify extension disable git
25 👍
#1382
Custom namespacing and branch naming conventions
open
Unblocked: GIT_BRANCH_NAME env var + extension config
18 👍
#1066
Branch numbering repeats 001- prefix
open
Fix scope narrowed to extension
13 👍
#1921
Make branch creation configurable (branchStrategy)
open
Resolved by extension config
1 👍
#1901
Allow agent-namespaced branches (copilot/**)
open
Enabled via GIT_BRANCH_NAME
—
#1165
Different commands produce/expect different branch name syntax
open
Validation now in extension
—
#1191
Can't easily update or refine existing specs
open
Hook-based branching is optional
107 👍
#1941
PR: Git extension stage 1
merged
Phase 1+2 delivery
—
#2117
PR: Git extension stage 2
open
GIT_BRANCH_NAME, --force, tests
—
Original Problem Analysis
The scope of the coupling
File
Language
Git Lines
Operations
src/specify_cli/__init__.py
Python
~80
is_git_repo() (L634), init_git_repo() (L654), --branch-numbering validation (L1826), git init orchestration (L2100-2135), branch_numbering in init-options (L2146)
scripts/bash/create-new-feature.sh
Bash
~130
git branch -a, git fetch --all --prune, git checkout -b, git branch --list, branch number extraction, name generation, 244-byte validation
scripts/bash/common.sh
Bash
~95
get_repo_root(), get_current_branch(), has_git(), check_feature_branch(), find_feature_dir_by_prefix(), SPECIFY_FEATURE env var handling
scripts/powershell/create-new-feature.ps1
PowerShell
~120
PowerShell equivalents of all bash git operations
scripts/powershell/common.ps1
PowerShell
~75
PowerShell equivalents of all common.sh git operations
templates/commands/specify.md
Markdown
~35
Branch creation instructions, init-options.json reading for branch_numbering, script invocation with --timestamp/--short-name
templates/commands/implement.md
Markdown
1
git rev-parse --git-dir for .gitignore creation
templates/commands/taskstoissues.md
Markdown
5
git config --get remote.origin.url for GitHub issue creation
tests/test_branch_numbering.py
Python
69
6 tests for branch numbering persistence and validation
tests/test_timestamp_branches.py
Python
280
15 tests covering timestamp/sequential branching end-to-end
Impact of this coupling:
Workflow lock-in — users who don't want branching can't adopt spec-kit (Feature Request: Support disabling automatic branch creation via parameter #841 , 25 reactions; [Feature]: Make branch creation configurable #1921 )
Blocked extensibility — users who want different branching need core surgery ([Feature request] Custom namespacing and branch naming conventions #1382 , 18 reactions; [Feature]: Allow agent-namespaced branches (claude/**, copilot/**, etc.) to bypass numeric prefix requirement #1901 )
Spec iteration friction — mandatory branching makes spec refinement expensive (Spec-Driven Editing Flow: Can't Easily Update or Refine Existing Specs #1191 , 107 reactions)
Maintenance burden — every branching enhancement touches 4+ files across 3 languages
Contributor barrier — PRs are large and complex because branching touches everything
Summary
Extract all Git-related operations (repository detection, branch creation, branch numbering, branch validation, remote fetching,
SPECIFY_FEATUREenv var management) from core into a self-contained Spec Kit extension atextensions/git/. The extension auto-enables by default during the migration period (opt-out) to preserve backward compatibility. Before the 1.0.0 release, the extension will transition to opt-in — users will explicitly install it viaspecify extension add gitor a preset that includes it.Problem
Git branching logic is deeply interwoven across ~815+ lines in 10+ files spanning 4 languages (Python, Bash, PowerShell, Markdown). Analysis of upstream issues reveals this coupling causes problems across five distinct categories — not just maintenance burden, but workflow lock-in, blocked extensibility, spec iteration friction, and contributor barriers. See the original problem analysis in the collapsed section below for the full breakdown.
Delivered Solution
The extraction was implemented incrementally via a hook-based architecture rather than the originally planned "rip and replace" approach. This preserves backward compatibility while cleanly separating git operations from core.
Stage 1 — PR #1941 (merged)
Created
extensions/git/with the extension manifest, commands, hooks, and scripts:Key deliverables:
config.defaultsspeckit.git.featurecreates branches with sequential or timestamp numberingspeckit.git.validatevalidates feature branch naming conventionsspeckit.git.remotedetects Git remote URL for GitHub integrationspeckit.git.commitauto-commits at hook points (optional)speckit.git.initializeinitializes Git reposbefore_specify,before_constitution, and all other lifecycle hooks registeredspecify init(unless--no-git){SCRIPT}placeholder reliance)branch_numberingfrom its own config (.specify/extensions/git/git-config.yml)Stage 2 — PR #2117 (open)
Enhanced the extension and CLI with:
GIT_BRANCH_NAMEenv var override for exact branch naming (parallelsSPECIFY_FEATURE_DIRECTORY)--forceflag forspecify init <dir>into existing directoriesTestGitExtensionAutoInstall(3 tests),TestFeatureDirectoryResolution(4 tests)What Moved Out of Core
speckit.git.featurecommand + scriptsspeckit.git.validatecommandspeckit.git.remotecommandcreate-new-feature.shextensions/git/scripts/bash/create-new-feature.ps1extensions/git/scripts/powershell/check_feature_branch()git-common.sh--branch-numberingconfiggit-config.ymlin extensionspecify.mdbefore_specifyWhat Stays in Core (by design)
--no-gitflagget_current_branch()incommon.sh/.ps1SPECIFY_FEATUREenv var checkspecs/directory scanning fallbackfind_feature_dir_by_prefix()check_feature_branch()in corecommon.shsetup-plan.shandcheck-prerequisites.sh; removing would break scripts when git extension is disabled--branch-numberingCLI flaginit-options.jsonwhich the extension reads as fallbackExtension Enable/Disable Behavior
The
specify extension disable gitandspecify extension enable gitcommands already exist and work correctly. When disabled:before_specifyand other hooks don't fire (hook executor checks enabled status)HAS_GIT=falsebehavior in scriptsRemaining Work (pre-1.0.0)
--branch-numberingCLI flag oninit()with a warning pointing to.specify/extensions/git/git-config.yml(breaking change deferred to 1.0.0)specify extension disable git→ verify no branch operations occurinit())Acceptance Criteria
extensions/git/extension.ymlmanifest with commands, hooks, andconfig.defaultsspeckit.git.featurecommand creates branches with sequential or timestamp numberingspeckit.git.validatecommand validates feature branch naming conventionsspeckit.git.remotecommand detects Git remote URL for GitHub integrationbefore_specifyhook registered;specify.mdtemplate conditionally runs branching via hooks{SCRIPT}placeholder)branch_numberingfrom its own config (.specify/extensions/git/git-config.yml)specify extension disable gitstops all branch operations without breaking spec creationspecify extension enable gitre-enables branch operationsspecify initfor bundled git extensionget_current_branch()remains intact in corecommon.sh/.ps1SPECIFY_FEATUREenvironment variable continues to work (stays in core)specify.mdtemplate conditionally includes branching based on hook system--no-gitbehaviorGIT_BRANCH_NAMEenv var override for exact branch naming--branch-numberingCLI flag deprecated (pre-1.0.0)Related Issues & PRs
specify extension disable gitGIT_BRANCH_NAMEenv var + extension config001-prefixbranchStrategy)copilot/**)GIT_BRANCH_NAMEOriginal Problem Analysis
The scope of the coupling
src/specify_cli/__init__.pyis_git_repo()(L634),init_git_repo()(L654),--branch-numberingvalidation (L1826), git init orchestration (L2100-2135),branch_numberingin init-options (L2146)scripts/bash/create-new-feature.shgit branch -a,git fetch --all --prune,git checkout -b,git branch --list, branch number extraction, name generation, 244-byte validationscripts/bash/common.shget_repo_root(),get_current_branch(),has_git(),check_feature_branch(),find_feature_dir_by_prefix(),SPECIFY_FEATUREenv var handlingscripts/powershell/create-new-feature.ps1scripts/powershell/common.ps1templates/commands/specify.mdinit-options.jsonreading forbranch_numbering, script invocation with--timestamp/--short-nametemplates/commands/implement.mdgit rev-parse --git-dirfor.gitignorecreationtemplates/commands/taskstoissues.mdgit config --get remote.origin.urlfor GitHub issue creationtests/test_branch_numbering.pytests/test_timestamp_branches.pyImpact of this coupling:
claude/**,copilot/**, etc.) to bypass numeric prefix requirement #1901)