feat: Git extension stage 2 — GIT_BRANCH_NAME override, --force for existing dirs, auto-install tests (#1940)#2117
feat: Git extension stage 2 — GIT_BRANCH_NAME override, --force for existing dirs, auto-install tests (#1940)#2117mnriem wants to merge 13 commits intogithub:mainfrom
Conversation
…xisting dirs, auto-install tests (github#1940) - Add GIT_BRANCH_NAME env var override to create-new-feature.sh/.ps1 for exact branch naming (bypasses all prefix/suffix generation) - Fix --force flag for 'specify init <dir>' into existing directories - Add TestGitExtensionAutoInstall tests (auto-install, --no-git skip, commands registered) - Add TestFeatureDirectoryResolution tests (env var, feature.json, priority, branch fallback) - Document GIT_BRANCH_NAME in speckit.git.feature.md and specify.md
There was a problem hiding this comment.
Pull request overview
Stage 2 of the git extension extraction: moves git branching responsibilities further into the bundled extensions/git extension by (1) auto-installing it during specify init (unless --no-git), (2) adding GIT_BRANCH_NAME as an exact branch-name override for the branch-creation scripts, and (3) updating feature-directory resolution to prefer SPECIFY_FEATURE_DIRECTORY / .specify/feature.json over branch-based lookup, with accompanying docs and tests.
Changes:
- Auto-install bundled
gitextension duringspecify init, and allow--forceto merge into an existing target directory. - Add
GIT_BRANCH_NAMEoverride to git extensioncreate-new-featurescripts (bash + PowerShell) and update command/template docs accordingly. - Add/adjust tests for git extension auto-install and feature directory resolution precedence.
Show a summary per file
| File | Description |
|---|---|
src/specify_cli/__init__.py |
Implements --force merge behavior for existing dirs and installs bundled git extension during init. |
extensions/git/scripts/bash/create-new-feature.sh |
Adds GIT_BRANCH_NAME override and removes spec-dir/spec-file creation from branch script. |
extensions/git/scripts/powershell/create-new-feature.ps1 |
Same as bash: GIT_BRANCH_NAME override + branch-only behavior. |
scripts/bash/common.sh |
Updates get_feature_paths to prioritize SPECIFY_FEATURE_DIRECTORY and .specify/feature.json. |
scripts/powershell/common.ps1 |
Similar feature-dir resolution changes for PowerShell helper. |
extensions/git/commands/speckit.git.feature.md |
Documents branch-only responsibility + GIT_BRANCH_NAME behavior/output. |
templates/commands/specify.md |
Updates core /speckit.specify instructions to decouple spec-dir creation from git hook branch creation. |
tests/integrations/test_cli.py |
Adds CLI-level tests for git extension auto-install/skip/registration. |
tests/test_timestamp_branches.py |
Adds feature directory resolution precedence tests (env var, feature.json, branch fallback). |
tests/extensions/git/test_git_extension.py |
Updates expectations to reflect branch-only script output (no SPEC_FILE). |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comments suppressed due to low confidence (2)
extensions/git/scripts/bash/create-new-feature.sh:312
- The
GIT_BRANCH_NAMEoverride extractsFEATURE_NUMby checking^[0-9]+-before the timestamp pattern. Timestamp-style names (e.g.20260319-143022-foo) will match the first check and incorrectly setFEATURE_NUMto just20260319instead of20260319-143022. Swap the order (timestamp check first) or make the sequential regex exclude the timestamp form.
if echo "$BRANCH_NAME" | grep -Eq '^[0-9]+-'; then
FEATURE_NUM=$(echo "$BRANCH_NAME" | grep -Eo '^[0-9]+')
elif echo "$BRANCH_NAME" | grep -Eq '^[0-9]{8}-[0-9]{6}-'; then
FEATURE_NUM=$(echo "$BRANCH_NAME" | grep -Eo '^[0-9]{8}-[0-9]{6}')
extensions/git/scripts/powershell/create-new-feature.ps1:266
- In the
GIT_BRANCH_NAMEoverride path, the timestamp prefix extraction (^(\d{8}-\d{6})-) is unreachable because the prior^(\d+)-regex matches timestamps first. This causesFEATURE_NUMto be truncated to the date-only portion for timestamp branch names. Check the timestamp pattern first, or refine the sequential pattern so it won’t match timestamps.
if ($branchName -match '^(\d+)-') {
$featureNum = $matches[1]
} elseif ($branchName -match '^(\d{8}-\d{6})-') {
$featureNum = $matches[1]
- Files reviewed: 10/10 changed files
- Comments generated: 8
- Fix timestamp regex ordering: check YYYYMMDD-HHMMSS before generic numeric prefix in both bash and PowerShell - Set BRANCH_SUFFIX in GIT_BRANCH_NAME override path so 244-byte truncation logic works correctly - Add 244-byte length check for GIT_BRANCH_NAME in PowerShell - Use existing_items for non-empty dir warning with --force - Skip git extension install if already installed (idempotent --force) - Wrap PowerShell feature.json parsing in try/catch for malformed JSON - Fix PS comment: 'prefix lookup' -> 'exact mapping via Get-FeatureDir' - Remove non-functional SPECIFY_SPEC_DIRECTORY from specify.md template
There was a problem hiding this comment.
Pull request overview
Stage 2 of the Git extension extraction: shifts branch creation fully into the bundled git extension (with new GIT_BRANCH_NAME override), fixes specify init <dir> --force to merge into existing directories, and expands test coverage around git extension auto-install and feature directory resolution.
Changes:
- Add
GIT_BRANCH_NAMEoverride to git extension branch creation scripts + documentation updates. - Update
specify initto allow--forcemerges into existing target directories and to auto-install the bundled git extension. - Add tests for git extension auto-install and for feature directory resolution priority (env var, feature.json, branch fallback).
Show a summary per file
| File | Description |
|---|---|
src/specify_cli/__init__.py |
Enables --force merge into existing dirs and installs bundled git extension during init. |
extensions/git/scripts/bash/create-new-feature.sh |
Adds GIT_BRANCH_NAME override; removes spec dir/file creation; adjusts JSON output. |
extensions/git/scripts/powershell/create-new-feature.ps1 |
Adds GIT_BRANCH_NAME override; removes spec dir/file creation; adjusts JSON output. |
extensions/git/commands/speckit.git.feature.md |
Updates command docs for override behavior + branch-only responsibility. |
scripts/bash/common.sh |
Resolves feature directory via env var / .specify/feature.json / branch fallback. |
scripts/powershell/common.ps1 |
Same feature directory resolution priority as bash. |
templates/commands/specify.md |
Updates /speckit.specify workflow to rely on hooks for branching and persist feature dir in .specify/feature.json. |
tests/integrations/test_cli.py |
Adds TestGitExtensionAutoInstall verifying bundled git extension install/skip and command registration. |
tests/test_timestamp_branches.py |
Adds TestFeatureDirectoryResolution covering env var / feature.json / fallback behavior. |
tests/extensions/git/test_git_extension.py |
Updates tests to reflect branch-only outputs (no SPEC_FILE), removes spec-dir creation assertions. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 10/10 changed files
- Comments generated: 3
- Guard shutil.rmtree on init failure: skip cleanup when --force merged into a pre-existing directory (prevents data loss) - Bash: error on GIT_BRANCH_NAME >244 bytes instead of broken truncation - Fix malformed numbered list in specify.md (restore missing step 1) - Add claude_skills.exists() assert before iterdir() in test
There was a problem hiding this comment.
Pull request overview
Stage 2 of the git extension extraction work: shifts branch creation fully into the bundled extensions/git hook, adds an exact-branch-name override via GIT_BRANCH_NAME, fixes specify init <dir> --force to merge into existing directories, and expands automated test coverage around extension auto-install and feature directory resolution.
Changes:
- Add
GIT_BRANCH_NAMEoverride support to git extension branch-creation scripts (bash + PowerShell) and document it in extension/core command templates. - Update
specify initto allow--forcemerges into existing target directories and to auto-install the bundled git extension (unless--no-git). - Add/adjust tests for git extension auto-install + command registration and for
SPECIFY_FEATURE_DIRECTORY/.specify/feature.jsonfeature directory resolution.
Show a summary per file
| File | Description |
|---|---|
extensions/git/scripts/bash/create-new-feature.sh |
Add GIT_BRANCH_NAME exact override; stop creating spec dirs/files; JSON output now focuses on branch metadata. |
extensions/git/scripts/powershell/create-new-feature.ps1 |
Same as bash: GIT_BRANCH_NAME override + branch-only behavior + updated JSON output. |
extensions/git/commands/speckit.git.feature.md |
Document the override and clarify that this command is branch-only and outputs BRANCH_NAME/FEATURE_NUM. |
scripts/bash/common.sh |
Resolve feature directory via env var → .specify/feature.json → branch-prefix fallback. |
scripts/powershell/common.ps1 |
Resolve feature directory via env var → .specify/feature.json → branch fallback. |
templates/commands/specify.md |
Remove direct branching scripts; update workflow to treat branch creation as optional hook and move spec-dir creation into core flow. |
src/specify_cli/__init__.py |
Fix --force behavior for existing dirs and switch “git step” from repo-init to bundled git extension install. |
tests/integrations/test_cli.py |
Add integration-level tests validating git extension auto-install, opt-out (--no-git), and command registration. |
tests/test_timestamp_branches.py |
Add tests for feature directory resolution precedence (env var, feature.json, branch fallback). |
tests/extensions/git/test_git_extension.py |
Update expectations to match branch-only outputs (no SPEC_FILE creation from git scripts). |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 10/10 changed files
- Comments generated: 2
- Bash: use LC_ALL=C wc -c for byte length instead of ${#VAR}
- PowerShell: use [System.Text.Encoding]::UTF8.GetByteCount() instead
of .Length (UTF-16 code units)
There was a problem hiding this comment.
Pull request overview
Stage 2 of extracting git/branching behavior into the bundled extensions/git extension by adding an exact-branch-name override (GIT_BRANCH_NAME), fixing specify init <dir> --force to merge into existing directories, and expanding test coverage around git extension auto-install and feature directory resolution.
Changes:
- Add
GIT_BRANCH_NAMEenv var override support to extension branch-creation scripts (Bash + PowerShell) and document it. - Update
specify initto auto-install the bundled git extension by default, and allow--forcemerges into existing non---heredirectories. - Add tests for git extension auto-install/registration and for feature directory resolution via
SPECIFY_FEATURE_DIRECTORYand.specify/feature.json.
Show a summary per file
| File | Description |
|---|---|
src/specify_cli/__init__.py |
Implements --force merge behavior for existing dirs and auto-installs bundled git extension (unless --no-git). |
extensions/git/scripts/bash/create-new-feature.sh |
Adds GIT_BRANCH_NAME override handling and adjusts JSON output to branch-only fields. |
extensions/git/scripts/powershell/create-new-feature.ps1 |
Adds GIT_BRANCH_NAME override handling and adjusts JSON output to branch-only fields. |
extensions/git/commands/speckit.git.feature.md |
Documents branch-only responsibility + GIT_BRANCH_NAME override semantics. |
templates/commands/specify.md |
Removes direct branch script invocation and documents hook-based branch creation + persisted feature dir. |
scripts/bash/common.sh |
Adds feature directory resolution priority: env var → feature.json → branch lookup. |
scripts/powershell/common.ps1 |
Mirrors feature directory resolution priority logic for PowerShell. |
tests/integrations/test_cli.py |
Adds CLI-level tests validating git extension auto-install/skip/registration during init. |
tests/test_timestamp_branches.py |
Adds tests for feature directory resolution precedence (bash common.sh). |
tests/extensions/git/test_git_extension.py |
Updates extension tests to reflect branch-only output/behavior. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 10/10 changed files
- Comments generated: 5
- Update --dry-run help text in bash and PowerShell (branch name only) - Fix specify.md JSON example: use concrete path, not literal variable - Add TestForceExistingDirectory tests (merge + error without --force) - Add PowerShell Get-FeaturePathsEnv tests (env var + feature.json)
There was a problem hiding this comment.
Pull request overview
Stage 2 of the Git extension extraction: updates init behavior to auto-install the bundled git extension (opt-out), adds a GIT_BRANCH_NAME override for exact branch naming in extension scripts, and introduces feature-directory resolution via SPECIFY_FEATURE_DIRECTORY / .specify/feature.json with new tests.
Changes:
- Add
GIT_BRANCH_NAMEenv var override to git extension branch-creation scripts and document it. - Fix
specify init <dir> --forceto merge into existing directories (not only--here) and auto-install the bundledgitextension during init. - Add/adjust tests for extension auto-install and feature-directory resolution; update git extension tests for new script output (no
SPEC_FILE).
Show a summary per file
| File | Description |
|---|---|
src/specify_cli/__init__.py |
Enables --force merge into existing dirs; installs bundled git extension during init; adjusts cleanup logic. |
extensions/git/scripts/bash/create-new-feature.sh |
Adds GIT_BRANCH_NAME override; removes spec-dir/spec-file creation; updates JSON output fields. |
extensions/git/scripts/powershell/create-new-feature.ps1 |
Adds GIT_BRANCH_NAME override; removes spec-dir/spec-file creation; updates JSON output fields. |
extensions/git/commands/speckit.git.feature.md |
Documents override and updated behavior/output for git feature command. |
templates/commands/specify.md |
Removes direct branch-creation scripts; updates workflow instructions (hooks + feature dir persistence). |
scripts/bash/common.sh |
Adds feature directory resolution priority: env var → feature.json → branch prefix mapping. |
scripts/powershell/common.ps1 |
Adds feature directory resolution priority: env var → feature.json → branch mapping. |
tests/integrations/test_cli.py |
Adds tests for --force merge behavior and git extension auto-install during init. |
tests/test_timestamp_branches.py |
Adds tests for feature directory resolution (env var, feature.json, fallback) across bash/PowerShell. |
tests/extensions/git/test_git_extension.py |
Updates tests to match new branch script outputs (no spec file creation). |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comments suppressed due to low confidence (1)
scripts/bash/common.sh:212
- The no-
jqfallback parsesfeature.jsonwithgrep -o ... | sed ..., which only matches when"feature_directory": "..."appears on a single line. The updatedspecify.mdinstructions show a multi-line JSON example, and agents commonly pretty-print JSON—this will make_fdempty and silently fall back to branch lookup. Consider making the fallback newline-tolerant (e.g., strip newlines before grepping) or using a small Pythonjsonparse as a fallback.
elif [[ -f "$repo_root/.specify/feature.json" ]]; then
local _fd
if command -v jq >/dev/null 2>&1; then
_fd=$(jq -r '.feature_directory // empty' "$repo_root/.specify/feature.json" 2>/dev/null)
else
# Minimal fallback: extract value with grep/sed when jq is unavailable
_fd=$(grep -o '"feature_directory"[[:space:]]*:[[:space:]]*"[^"]*"' "$repo_root/.specify/feature.json" 2>/dev/null | sed 's/.*"\([^"]*\)"$/\1/')
fi
if [[ -n "$_fd" ]]; then
- Files reviewed: 10/10 changed files
- Comments generated: 3
- Bash common.sh: normalize SPECIFY_FEATURE_DIRECTORY and feature.json relative paths to absolute under repo root - PowerShell common.ps1: same normalization using IsPathRooted + Join-Path - PowerShell create-new-feature.ps1: call Test-HasGit without -RepoRoot for compatibility with core common.ps1 (no param) and git-common.ps1 (optional param with default)
There was a problem hiding this comment.
Pull request overview
Stage 2 of the git-extension extraction: updates the CLI to auto-install the bundled git extension during specify init, adds feature directory resolution precedence (SPECIFY_FEATURE_DIRECTORY → .specify/feature.json → branch fallback) across bash/PowerShell common scripts, and introduces an exact-branch-name override via GIT_BRANCH_NAME in the git extension’s branch-creation scripts/docs.
Changes:
- Add
--forcesupport forspecify init <dir>when the target directory already exists (merge instead of error). - Auto-install the bundled
gitextension duringspecify init(unless--no-git) and update tests to validate install + hook/command registration. - Implement feature directory resolution via
SPECIFY_FEATURE_DIRECTORY/.specify/feature.jsonand update templates/docs to decouple spec dir creation from git branch creation; add resolution tests.
Show a summary per file
| File | Description |
|---|---|
src/specify_cli/__init__.py |
Allow --force merges into existing dirs; replace init-time git repo initialization with bundled git extension install. |
tests/integrations/test_cli.py |
Add CLI tests for --force existing-dir merge and git extension auto-install/registration. |
scripts/bash/common.sh |
Resolve feature directory via env var / .specify/feature.json before branch-based lookup. |
scripts/powershell/common.ps1 |
PowerShell equivalent feature directory resolution precedence. |
tests/test_timestamp_branches.py |
Add cross-shell tests for feature directory resolution precedence and fallbacks. |
extensions/git/scripts/bash/create-new-feature.sh |
Add GIT_BRANCH_NAME exact override; adjust output to be branch-only (no spec file/dir creation). |
extensions/git/scripts/powershell/create-new-feature.ps1 |
Add GIT_BRANCH_NAME exact override; align behavior/output with branch-only responsibility. |
extensions/git/commands/speckit.git.feature.md |
Document override + clarify command handles branch creation only. |
templates/commands/specify.md |
Remove direct branch-creation scripts; document hook-driven branch creation + core-owned spec dir/file creation + feature.json persistence. |
tests/extensions/git/test_git_extension.py |
Update expectations to match “branch-only” behavior (no SPEC_FILE creation by git extension feature script). |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 10/10 changed files
- Comments generated: 2
…thub#2117) - TestGitBranchNameOverrideBash: 5 tests (exact name, sequential prefix, timestamp prefix, overlong rejection, dry-run) - TestGitBranchNameOverridePowerShell: 4 tests (exact name, sequential prefix, timestamp prefix, overlong rejection) - Tests use extension scripts (not core) via new ext_git_repo and ext_ps_git_repo fixtures
There was a problem hiding this comment.
Pull request overview
Stage 2 of the git-extension extraction: shifts git branching responsibilities further into the bundled extensions/git workflow, adds an exact branch-name override (GIT_BRANCH_NAME), improves specify init behavior for --force into existing directories, and expands test coverage around extension auto-install and feature-directory resolution.
Changes:
- Add
GIT_BRANCH_NAMEoverride support in git extension branch-creation scripts (bash + PowerShell) and document it. - Update
specify initto support--forcewhen initializing into an existing named directory, and add tests for this and git extension auto-install. - Update feature-directory resolution to prefer
SPECIFY_FEATURE_DIRECTORY, then.specify/feature.json, then branch-based fallback; update/speckit.specifytemplate accordingly and adjust tests.
Show a summary per file
| File | Description |
|---|---|
src/specify_cli/__init__.py |
Implements --force merge into existing dirs; changes “git” init step to install bundled git extension during init. |
templates/commands/specify.md |
Removes direct branch-creation script invocation; documents hook-based branching + feature dir persistence to .specify/feature.json. |
scripts/bash/common.sh |
Adds feature directory resolution via SPECIFY_FEATURE_DIRECTORY and .specify/feature.json before branch-prefix lookup. |
scripts/powershell/common.ps1 |
Mirrors feature directory resolution priority logic for PowerShell. |
extensions/git/scripts/bash/create-new-feature.sh |
Adds GIT_BRANCH_NAME override and aligns output to be branch-only (no spec file creation). |
extensions/git/scripts/powershell/create-new-feature.ps1 |
Adds GIT_BRANCH_NAME override; removes spec dir/file creation and outputs branch-only JSON. |
extensions/git/commands/speckit.git.feature.md |
Updates command docs to reflect override behavior and branch-only output. |
tests/integrations/test_cli.py |
Adds CLI-level tests for --force into existing dirs and git extension auto-install / registration. |
tests/test_timestamp_branches.py |
Adds fixtures + tests for GIT_BRANCH_NAME override and feature-directory resolution order. |
tests/extensions/git/test_git_extension.py |
Updates expectations to match branch-only output / behavior (no SPEC_FILE). |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 10/10 changed files
- Comments generated: 3
- Restore is_git_repo() and init_git_repo() functions removed in stage 2 - specify init now runs git init AND installs git extension (not just extension install alone) - Add is_dir() guard for non-here path to prevent uncontrolled error when target exists but is a file - Add python3 JSON fallback in common.sh for multi-line feature.json (grep pipeline fails on pretty-printed JSON without jq)
There was a problem hiding this comment.
Pull request overview
Stage 2 of the git-extension extraction: adds an exact GIT_BRANCH_NAME override for the git extension’s branch-creation scripts, fixes specify init <dir> --force to merge into existing directories, and expands test coverage around git extension auto-install and feature directory resolution.
Changes:
- Add
GIT_BRANCH_NAMEoverride to extensioncreate-new-featurescripts (bash + PowerShell) and document the behavior. - Update
specify initto support--forcewhen targeting an existing (named) directory, and auto-install the bundledgitextension during init (unless--no-git). - Extend tests for git extension auto-install,
GIT_BRANCH_NAME, and feature directory resolution viaSPECIFY_FEATURE_DIRECTORY/.specify/feature.json.
Show a summary per file
| File | Description |
|---|---|
src/specify_cli/__init__.py |
Implements --force merge behavior for existing dirs and auto-installs bundled git extension during init. |
extensions/git/scripts/bash/create-new-feature.sh |
Adds GIT_BRANCH_NAME exact-branch override; outputs branch-only JSON (no spec file creation). |
extensions/git/scripts/powershell/create-new-feature.ps1 |
PowerShell parity for GIT_BRANCH_NAME override; removes spec dir/file creation and output. |
extensions/git/commands/speckit.git.feature.md |
Updates extension command docs to reflect branch-only behavior and the override env var. |
templates/commands/specify.md |
Removes direct branch-creation scripts from core /speckit.specify; documents hook-based branching and feature dir persistence. |
scripts/bash/common.sh |
Enhances feature directory resolution priority: env var → .specify/feature.json → branch-based fallback. |
scripts/powershell/common.ps1 |
Mirrors feature directory resolution priority logic for PowerShell. |
tests/integrations/test_cli.py |
Adds CLI tests for --force on existing dirs and git extension auto-install/registration. |
tests/test_timestamp_branches.py |
Adds tests for GIT_BRANCH_NAME override (bash + PS) and feature directory resolution behavior. |
tests/extensions/git/test_git_extension.py |
Updates expectations for branch script output (no SPEC_FILE) and adjusts no-git behavior assertions. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 10/10 changed files
- Comments generated: 1
There was a problem hiding this comment.
Pull request overview
Stage 2 of the bundled extensions/git migration: adds an explicit GIT_BRANCH_NAME override for exact branch naming, fixes specify init <dir> --force to merge into existing directories, and expands test coverage around git extension auto-install and feature-directory resolution via SPECIFY_FEATURE_DIRECTORY / .specify/feature.json.
Changes:
- Add
GIT_BRANCH_NAMEoverride support in git extension branch-creation scripts (Bash + PowerShell) and document it. - Update
specify initto allow--forcemerges into an existing target directory and auto-install the bundled git extension by default (opt-out via--no-git). - Add/adjust tests for git extension auto-install, branch-name override behavior, and feature directory resolution priority.
Show a summary per file
| File | Description |
|---|---|
src/specify_cli/__init__.py |
Implements --force merge behavior for existing dirs and installs bundled git extension during init. |
extensions/git/scripts/bash/create-new-feature.sh |
Adds GIT_BRANCH_NAME exact-branch override + adjusts JSON output to branch-only fields. |
extensions/git/scripts/powershell/create-new-feature.ps1 |
Adds GIT_BRANCH_NAME exact-branch override + aligns behavior/output with Bash. |
extensions/git/commands/speckit.git.feature.md |
Documents override semantics and updated script output contract. |
scripts/bash/common.sh |
Resolves feature directory via SPECIFY_FEATURE_DIRECTORY → .specify/feature.json → branch lookup fallback. |
scripts/powershell/common.ps1 |
Mirrors feature directory resolution priority for PowerShell workflows. |
templates/commands/specify.md |
Moves spec dir/file creation responsibility to core workflow; treats git branch creation as hook-driven. |
tests/integrations/test_cli.py |
Adds coverage for --force merging and git extension auto-install/registration. |
tests/test_timestamp_branches.py |
Adds tests for GIT_BRANCH_NAME override and feature-directory resolution priority. |
tests/extensions/git/test_git_extension.py |
Updates expectations to reflect “branch-only” behavior (no spec file creation in git extension). |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 10/10 changed files
- Comments generated: 1
…ub#2117) Extension .sh scripts (e.g. create-new-feature.sh, initialize-repo.sh) may lack execute bits after install. Scan both .specify/scripts/ and .specify/extensions/ for permission fixing.
There was a problem hiding this comment.
Pull request overview
Stage 2 of the git-extension extraction: adds an exact branch-name override (GIT_BRANCH_NAME), fixes specify init <dir> --force behavior for existing directories, and expands tests around auto-install + feature directory resolution (SPECIFY_FEATURE_DIRECTORY / .specify/feature.json).
Changes:
- Add
GIT_BRANCH_NAMEoverride support to the git extension’s branch creation scripts (Bash + PowerShell) and update related docs/templates. - Allow
specify init <dir> --forceto merge into an existing directory (instead of erroring), with new integration tests. - Add feature directory resolution logic to core
common.sh/common.ps1(env var → feature.json → branch fallback) with new tests.
Show a summary per file
| File | Description |
|---|---|
src/specify_cli/__init__.py |
Implement --force merge into existing dirs; auto-install bundled git extension during init; expand chmod scanning to extensions. |
extensions/git/scripts/bash/create-new-feature.sh |
Add GIT_BRANCH_NAME override + byte-length validation; stop creating spec dirs/files; adjust JSON output. |
extensions/git/scripts/powershell/create-new-feature.ps1 |
Add GIT_BRANCH_NAME override + UTF-8 byte-length validation; stop creating spec dirs/files; adjust JSON output. |
extensions/git/commands/speckit.git.feature.md |
Document override behavior and updated outputs (branch-only). |
templates/commands/specify.md |
Remove direct branch-creation scripts; document hook-based branching + feature dir persistence to .specify/feature.json. |
scripts/bash/common.sh |
Resolve feature directory via env var / feature.json before branch-based lookup. |
scripts/powershell/common.ps1 |
Same feature directory resolution precedence as Bash. |
tests/integrations/test_cli.py |
Add tests for --force into existing dir and git extension auto-install (and --no-git skip). |
tests/test_timestamp_branches.py |
Add tests for GIT_BRANCH_NAME override (Bash + pwsh) and feature directory resolution order. |
tests/extensions/git/test_git_extension.py |
Update expectations now that extension branch scripts no longer create spec files/dirs. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 10/10 changed files
- Comments generated: 2
…2117) - ensure_executable_scripts() now runs after git extension install so extension .sh files get execute bits in the same init run - Sanitize init_git_repo error_msg to single line (replace newlines, truncate to 120 chars) to prevent garbled StepTracker output
There was a problem hiding this comment.
Pull request overview
Stage 2 of the git-extension extraction effort: adds an exact-branch override (GIT_BRANCH_NAME) for the git extension’s branch-creation scripts, fixes specify init <dir> --force to merge into existing directories, and expands test coverage for both git extension auto-install and feature directory resolution.
Changes:
- Add
GIT_BRANCH_NAMEenv var override to extension branch-creation scripts (bash + PowerShell) and document it. - Update
specify initto allow--forcewhen targeting an existing directory and to auto-install the bundled git extension (unless--no-git). - Add/adjust tests for git extension auto-install and for feature directory resolution via
SPECIFY_FEATURE_DIRECTORYand.specify/feature.json.
Show a summary per file
| File | Description |
|---|---|
src/specify_cli/__init__.py |
Implements --force merge behavior; adds bundled git extension auto-install during init; updates chmod scanning to include installed extensions. |
extensions/git/scripts/bash/create-new-feature.sh |
Adds GIT_BRANCH_NAME override, removes spec dir/file creation responsibilities, updates JSON output to branch-only fields. |
extensions/git/scripts/powershell/create-new-feature.ps1 |
Same as bash: GIT_BRANCH_NAME override and branch-only behavior/output. |
extensions/git/commands/speckit.git.feature.md |
Documents override semantics and updated script output/behavior. |
templates/commands/specify.md |
Shifts branch creation to hook-driven flow and documents persisting .specify/feature.json for feature dir discovery. |
scripts/bash/common.sh |
Adds feature dir resolution via SPECIFY_FEATURE_DIRECTORY and .specify/feature.json before branch-based fallback. |
scripts/powershell/common.ps1 |
Mirrors feature dir resolution priority logic in PowerShell. |
tests/integrations/test_cli.py |
Adds tests for init --force merge behavior and git extension auto-install/registration. |
tests/test_timestamp_branches.py |
Adds tests for GIT_BRANCH_NAME override (bash + pwsh) and for feature directory resolution priority order. |
tests/extensions/git/test_git_extension.py |
Updates tests to reflect that extension branch scripts no longer create spec files/directories. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comments suppressed due to low confidence (2)
src/specify_cli/init.py:1205
- The
gitstep is marked ascompleteeven wheninit_git_repo()fails (only a string likeinit failed: ...is appended). This makes the step look successful in the UI despite a failure. Consider callingtracker.error("git", ...)(while still continuing init if git is optional) and/or surfacing the full error somewhere (panel/log) instead of truncating it into the success detail.
success, error_msg = init_git_repo(project_path, quiet=True)
if success:
git_messages.append("initialized")
else:
# Sanitize multi-line error_msg to single line for tracker
if error_msg:
sanitized = error_msg.replace('\n', ' ').strip()
git_messages.append(f"init failed: {sanitized[:120]}")
else:
git_messages.append("init failed")
src/specify_cli/init.py:1225
- Extension install failures are swallowed into
git_messagesand the step is still completed asdone. If installation fails, this should be reflected as an error/warning state (e.g.,tracker.error("git", ...)), otherwise users may miss that the bundled git extension is not installed and hooks won’t run.
else:
git_messages.append("bundled extension not found")
except Exception as ext_err:
git_messages.append(f"extension install failed: {ext_err}")
tracker.complete("git", "; ".join(git_messages))
- Files reviewed: 10/10 changed files
- Comments generated: 4
| ORIGINAL_BRANCH_NAME="$BRANCH_NAME" | ||
| BRANCH_NAME="${FEATURE_NUM}-${TRUNCATED_SUFFIX}" | ||
|
|
||
| >&2 echo "[specify] Warning: Branch name exceeded GitHub's 244-byte limit" | ||
| >&2 echo "[specify] Original: $ORIGINAL_BRANCH_NAME (${#ORIGINAL_BRANCH_NAME} bytes)" | ||
| >&2 echo "[specify] Truncated to: $BRANCH_NAME (${#BRANCH_NAME} bytes)" | ||
| fi |
There was a problem hiding this comment.
The warning messages claim "bytes" but use ${#ORIGINAL_BRANCH_NAME} / ${#BRANCH_NAME} which is locale-dependent and not guaranteed to equal UTF-8 byte length. Since you already compute BRANCH_BYTE_LEN via _byte_length, use that (and _byte_length for the original/truncated strings) or change the wording to "characters" to avoid misleading output.
See below for a potential fix:
ORIGINAL_BRANCH_BYTE_LEN=$(_byte_length "$ORIGINAL_BRANCH_NAME")
TRUNCATED_BRANCH_BYTE_LEN=$(_byte_length "$BRANCH_NAME")
>&2 echo "[specify] Warning: Branch name exceeded GitHub's 244-byte limit"
>&2 echo "[specify] Original: $ORIGINAL_BRANCH_NAME (${ORIGINAL_BRANCH_BYTE_LEN} bytes)"
>&2 echo "[specify] Truncated to: $BRANCH_NAME (${TRUNCATED_BRANCH_BYTE_LEN} bytes)"
| Write-Warning "[specify] Branch name exceeded GitHub's 244-byte limit" | ||
| Write-Warning "[specify] Original: $originalBranchName ($($originalBranchName.Length) bytes)" | ||
| Write-Warning "[specify] Truncated to: $branchName ($($branchName.Length) bytes)" | ||
| } |
There was a problem hiding this comment.
These warnings report "bytes" but use $originalBranchName.Length / $branchName.Length (character count). To match the stated 244-byte GitHub limit (UTF-8), compute byte counts with [Text.Encoding]::UTF8.GetByteCount(...) for the warning output, or change the message to refer to characters instead of bytes.
See below for a potential fix:
$originalBranchNameBytes = [Text.Encoding]::UTF8.GetByteCount($originalBranchName)
$branchNameBytes = [Text.Encoding]::UTF8.GetByteCount($branchName)
Write-Warning "[specify] Branch name exceeded GitHub's 244-byte limit"
Write-Warning "[specify] Original: $originalBranchName ($originalBranchNameBytes bytes)"
Write-Warning "[specify] Truncated to: $branchName ($branchNameBytes bytes)"
| elif [[ -f "$repo_root/.specify/feature.json" ]]; then | ||
| local _fd | ||
| if command -v jq >/dev/null 2>&1; then | ||
| _fd=$(jq -r '.feature_directory // empty' "$repo_root/.specify/feature.json" 2>/dev/null) | ||
| elif command -v python3 >/dev/null 2>&1; then | ||
| # Fallback: use Python to parse JSON so pretty-printed/multi-line files work | ||
| _fd=$(python3 -c "import json,sys; d=json.load(open(sys.argv[1])); print(d.get('feature_directory',''))" "$repo_root/.specify/feature.json" 2>/dev/null) | ||
| else | ||
| # Last resort: single-line grep fallback (won't work on multi-line JSON) | ||
| _fd=$(grep -o '"feature_directory"[[:space:]]*:[[:space:]]*"[^"]*"' "$repo_root/.specify/feature.json" 2>/dev/null | sed 's/.*"\([^"]*\)"$/\1/') | ||
| fi | ||
| if [[ -n "$_fd" ]]; then | ||
| feature_dir="$_fd" | ||
| # Normalize relative paths to absolute under repo root | ||
| [[ "$feature_dir" != /* ]] && feature_dir="$repo_root/$feature_dir" | ||
| elif ! feature_dir=$(find_feature_dir_by_prefix "$repo_root" "$current_branch"); then |
There was a problem hiding this comment.
Reading .specify/feature.json and blindly using feature_directory allows path traversal outside the repo (e.g. ../../..) once get_feature_paths is eval’d by other scripts that mkdir -p "$FEATURE_DIR" / write files there. Consider resolving the path and enforcing that the result stays under $repo_root (or rejecting/ignoring unsafe values) to avoid writing outside the project tree.
| } elseif (Test-Path $featureJson) { | ||
| try { | ||
| $featureConfig = Get-Content $featureJson -Raw | ConvertFrom-Json | ||
| if ($featureConfig.feature_directory) { | ||
| $featureDir = $featureConfig.feature_directory | ||
| # Normalize relative paths to absolute under repo root | ||
| if (-not [System.IO.Path]::IsPathRooted($featureDir)) { | ||
| $featureDir = Join-Path $repoRoot $featureDir | ||
| } |
There was a problem hiding this comment.
feature.json can set feature_directory to a path that escapes the repo root (including .. segments or an absolute path), and downstream scripts will create/write files under that directory. Consider normalizing (Resolve-Path) and validating that the resolved directory is within $repoRoot before accepting it, otherwise ignore it and fall back to Get-FeatureDir / env override.
Git init failure and extension install failure were reported as tracker.complete (showing green) even on error. Now track a git_has_error flag and call tracker.error when any step fails, so the UI correctly reflects the failure state.
Summary
Stage 2 of the git extension extraction (#1940). Adds
GIT_BRANCH_NAMEenvironment variable override for exact branch naming, fixes--forceflag forspecify initinto existing directories, and adds test coverage for git extension auto-install and feature directory resolution.Follows stage 1 (#1941) which created the bundled
extensions/gitwith hooks on all core commands.Changes
GIT_BRANCH_NAMEenvironment variable overrideWhen
GIT_BRANCH_NAMEis set,create-new-feature.shand.ps1use the exact value as the branch name, bypassing all prefix/suffix generation. This parallelsSPECIFY_FEATURE_DIRECTORYfor spec directories.extensions/git/scripts/bash/create-new-feature.shextensions/git/scripts/powershell/create-new-feature.ps1extensions/git/commands/speckit.git.feature.md— documented overridetemplates/commands/specify.md— documented passthrough to hook--forceflag for existing directoriesspecify init <dir> --forcenow merges into an existing directory instead of erroring. Previously--forceonly worked with--here.src/specify_cli/__init__.pyNew tests
tests/integrations/test_cli.py—TestGitExtensionAutoInstall(3 tests: auto-install, --no-git skip, commands registered)tests/test_timestamp_branches.py—TestFeatureDirectoryResolution(4 tests: env var override, feature.json, priority, branch fallback)Pre-existing changes (from branch)
scripts/bash/common.sh,scripts/powershell/common.ps1— feature directory resolutiontests/extensions/git/test_git_extension.py— test cleanupTest Results
All existing tests pass unchanged. No regressions.