-
Notifications
You must be signed in to change notification settings - Fork 7.4k
Extract Git/Branching Operations into extensions/git Extension (Opt-Out, Auto-Enabled) #1940
Description
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-gitflag onspecify initis 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.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 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_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 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_specifyand other hooks don't fire (hook executor checks enabled status)- Spec directories are still created (core behavior, independent of git)
- Identical to
HAS_GIT=falsebehavior in scripts
Remaining Work (pre-1.0.0)
- Deprecate
--branch-numberingCLI flag oninit()with a warning pointing to.specify/extensions/git/git-config.yml(breaking change deferred to 1.0.0) - Add explicit test for
specify extension disable git→ verify no branch operations occur - Transition extension from auto-install (opt-out) to opt-in at 1.0.0 (remove auto-install code from
init())
Acceptance Criteria
-
extensions/git/extension.ymlmanifest with commands, hooks, andconfig.defaults -
speckit.git.featurecommand creates branches with sequential or timestamp numbering -
speckit.git.validatecommand validates feature branch naming conventions -
speckit.git.remotecommand detects Git remote URL for GitHub integration -
before_specifyhook registered;specify.mdtemplate conditionally runs branching via hooks - Extension commands include explicit script paths (no reliance on
{SCRIPT}placeholder) - Extension reads
branch_numberingfrom its own config (.specify/extensions/git/git-config.yml) - Extension degrades gracefully when git is not installed (warnings, no errors)
-
specify extension disable gitstops all branch operations without breaking spec creation -
specify extension enable gitre-enables branch operations - Auto-install logic added to
specify initfor bundled git extension - All existing branching tests pass when run against the extension (1,195/1,195)
- Cross-platform: Bash and PowerShell scripts both in extension
-
get_current_branch()remains intact in corecommon.sh/.ps1 -
SPECIFY_FEATUREenvironment variable continues to work (stays in core) -
specify.mdtemplate conditionally includes branching based on hook system - No regression in
--no-gitbehavior -
GIT_BRANCH_NAMEenv var override for exact branch naming -
--branch-numberingCLI flag deprecated (pre-1.0.0) - Auto-install transitions to opt-in (at 1.0.0)
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