Skip to content

feat: Git extension stage 2 — GIT_BRANCH_NAME override, --force for existing dirs, auto-install tests (#1940)#2117

Open
mnriem wants to merge 13 commits intogithub:mainfrom
mnriem:git-extension-stage2
Open

feat: Git extension stage 2 — GIT_BRANCH_NAME override, --force for existing dirs, auto-install tests (#1940)#2117
mnriem wants to merge 13 commits intogithub:mainfrom
mnriem:git-extension-stage2

Conversation

@mnriem
Copy link
Copy Markdown
Collaborator

@mnriem mnriem commented Apr 7, 2026

Summary

Stage 2 of the git extension extraction (#1940). Adds GIT_BRANCH_NAME environment variable override for exact branch naming, fixes --force flag for specify init into existing directories, and adds test coverage for git extension auto-install and feature directory resolution.

Follows stage 1 (#1941) which created the bundled extensions/git with hooks on all core commands.

Changes

GIT_BRANCH_NAME environment variable override

When GIT_BRANCH_NAME is set, create-new-feature.sh and .ps1 use the exact value as the branch name, bypassing all prefix/suffix generation. This parallels SPECIFY_FEATURE_DIRECTORY for spec directories.

  • extensions/git/scripts/bash/create-new-feature.sh
  • extensions/git/scripts/powershell/create-new-feature.ps1
  • extensions/git/commands/speckit.git.feature.md — documented override
  • templates/commands/specify.md — documented passthrough to hook

--force flag for existing directories

specify init <dir> --force now merges into an existing directory instead of erroring. Previously --force only worked with --here.

  • src/specify_cli/__init__.py

New tests

  • tests/integrations/test_cli.pyTestGitExtensionAutoInstall (3 tests: auto-install, --no-git skip, commands registered)
  • tests/test_timestamp_branches.pyTestFeatureDirectoryResolution (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 resolution
  • tests/extensions/git/test_git_extension.py — test cleanup

Test Results

1,195 passed, 0 failed (43s)

All existing tests pass unchanged. No regressions.

…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
Copilot AI review requested due to automatic review settings April 7, 2026 18:21
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 git extension during specify init, and allow --force to merge into an existing target directory.
  • Add GIT_BRANCH_NAME override to git extension create-new-feature scripts (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_NAME override extracts FEATURE_NUM by checking ^[0-9]+- before the timestamp pattern. Timestamp-style names (e.g. 20260319-143022-foo) will match the first check and incorrectly set FEATURE_NUM to just 20260319 instead of 20260319-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_NAME override path, the timestamp prefix extraction (^(\d{8}-\d{6})-) is unreachable because the prior ^(\d+)- regex matches timestamps first. This causes FEATURE_NUM to 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

mnriem added 2 commits April 7, 2026 13:35
- 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
Copilot AI review requested due to automatic review settings April 7, 2026 18:40
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_NAME override to git extension branch creation scripts + documentation updates.
  • Update specify init to allow --force merges 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
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_NAME override support to git extension branch-creation scripts (bash + PowerShell) and document it in extension/core command templates.
  • Update specify init to allow --force merges 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.json feature 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)
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_NAME env var override support to extension branch-creation scripts (Bash + PowerShell) and document it.
  • Update specify init to auto-install the bundled git extension by default, and allow --force merges into existing non---here directories.
  • Add tests for git extension auto-install/registration and for feature directory resolution via SPECIFY_FEATURE_DIRECTORY and .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)
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_NAME env var override to git extension branch-creation scripts and document it.
  • Fix specify init <dir> --force to merge into existing directories (not only --here) and auto-install the bundled git extension 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-jq fallback parses feature.json with grep -o ... | sed ..., which only matches when "feature_directory": "..." appears on a single line. The updated specify.md instructions show a multi-line JSON example, and agents commonly pretty-print JSON—this will make _fd empty and silently fall back to branch lookup. Consider making the fallback newline-tolerant (e.g., strip newlines before grepping) or using a small Python json parse 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)
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 --force support for specify init <dir> when the target directory already exists (merge instead of error).
  • Auto-install the bundled git extension during specify init (unless --no-git) and update tests to validate install + hook/command registration.
  • Implement feature directory resolution via SPECIFY_FEATURE_DIRECTORY / .specify/feature.json and 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
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_NAME override support in git extension branch-creation scripts (bash + PowerShell) and document it.
  • Update specify init to support --force when 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.specify template 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)
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_NAME override to extension create-new-feature scripts (bash + PowerShell) and document the behavior.
  • Update specify init to support --force when targeting an existing (named) directory, and auto-install the bundled git extension during init (unless --no-git).
  • Extend tests for git extension auto-install, GIT_BRANCH_NAME, and feature directory resolution via SPECIFY_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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_NAME override support in git extension branch-creation scripts (Bash + PowerShell) and document it.
  • Update specify init to allow --force merges 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.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_NAME override support to the git extension’s branch creation scripts (Bash + PowerShell) and update related docs/templates.
  • Allow specify init <dir> --force to 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
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_NAME env var override to extension branch-creation scripts (bash + PowerShell) and document it.
  • Update specify init to allow --force when 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_DIRECTORY and .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 git step is marked as complete even when init_git_repo() fails (only a string like init failed: ... is appended). This makes the step look successful in the UI despite a failure. Consider calling tracker.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_messages and the step is still completed as done. 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

Comment on lines 372 to 378
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
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)"

Copilot uses AI. Check for mistakes.
Comment on lines 322 to 325
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)"
}
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)"

Copilot uses AI. Check for mistakes.
Comment on lines +206 to +221
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
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +175 to +183
} 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
}
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants