### 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](#original-problem-analysis) 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) - [ ] Deprecate `--branch-numbering` CLI flag on `init()` 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 - [x] `extensions/git/extension.yml` manifest with commands, hooks, and `config.defaults` - [x] `speckit.git.feature` command creates branches with sequential or timestamp numbering - [x] `speckit.git.validate` command validates feature branch naming conventions - [x] `speckit.git.remote` command detects Git remote URL for GitHub integration - [x] `before_specify` hook registered; `specify.md` template conditionally runs branching via hooks - [x] Extension commands include explicit script paths (no reliance on `{SCRIPT}` placeholder) - [x] Extension reads `branch_numbering` from its own config (`.specify/extensions/git/git-config.yml`) - [x] Extension degrades gracefully when git is not installed (warnings, no errors) - [x] `specify extension disable git` stops all branch operations without breaking spec creation - [x] `specify extension enable git` re-enables branch operations - [x] Auto-install logic added to `specify init` for bundled git extension - [x] All existing branching tests pass when run against the extension (1,195/1,195) - [x] Cross-platform: Bash and PowerShell scripts both in extension - [x] `get_current_branch()` remains intact in core `common.sh`/`.ps1` - [x] `SPECIFY_FEATURE` environment variable continues to work (stays in core) - [x] `specify.md` template conditionally includes branching based on hook system - [x] No regression in `--no-git` behavior - [x] `GIT_BRANCH_NAME` env var override for exact branch naming - [ ] `--branch-numbering` CLI flag deprecated (pre-1.0.0) - [ ] Auto-install transitions to opt-in (at 1.0.0) ### Related Issues & PRs | # | Title | Status | Relevance | Signal | |---|-------|--------|-----------|--------| | [#841](https://github.com/github/spec-kit/issues/841) | Support disabling automatic branch creation via parameter | open | Resolved: `specify extension disable git` | 25 👍 | | [#1382](https://github.com/github/spec-kit/issues/1382) | Custom namespacing and branch naming conventions | open | Unblocked: `GIT_BRANCH_NAME` env var + extension config | 18 👍 | | [#1066](https://github.com/github/spec-kit/issues/1066) | Branch numbering repeats `001-` prefix | open | Fix scope narrowed to extension | 13 👍 | | [#1921](https://github.com/github/spec-kit/issues/1921) | Make branch creation configurable (`branchStrategy`) | open | Resolved by extension config | 1 👍 | | [#1901](https://github.com/github/spec-kit/issues/1901) | Allow agent-namespaced branches (`copilot/**`) | open | Enabled via `GIT_BRANCH_NAME` | — | | [#1165](https://github.com/github/spec-kit/issues/1165) | Different commands produce/expect different branch name syntax | open | Validation now in extension | — | | [#1191](https://github.com/github/spec-kit/issues/1191) | Can't easily update or refine existing specs | open | Hook-based branching is optional | 107 👍 | | [#1941](https://github.com/github/spec-kit/pull/1941) | PR: Git extension stage 1 | **merged** | Phase 1+2 delivery | — | | [#2117](https://github.com/github/spec-kit/pull/2117) | PR: Git extension stage 2 | **open** | GIT_BRANCH_NAME, --force, tests | — | <details> <summary><h3 id="original-problem-analysis">Original Problem Analysis</h3></summary> #### 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:** 1. **Workflow lock-in** — users who don't want branching can't adopt spec-kit (#841, 25 reactions; #1921) 2. **Blocked extensibility** — users who want *different* branching need core surgery (#1382, 18 reactions; #1901) 3. **Spec iteration friction** — mandatory branching makes spec refinement expensive (#1191, 107 reactions) 4. **Maintenance burden** — every branching enhancement touches 4+ files across 3 languages 5. **Contributor barrier** — PRs are large and complex because branching touches everything </details>