Skip to content

fix(scripts): prioritize .specify over git for repo root detection#1933

Open
mbachorik wants to merge 8 commits intogithub:mainfrom
mbachorik:fix/specify-root-detection
Open

fix(scripts): prioritize .specify over git for repo root detection#1933
mbachorik wants to merge 8 commits intogithub:mainfrom
mbachorik:fix/specify-root-detection

Conversation

@mbachorik
Copy link
Contributor

Summary

  • Fixes bug where spec-kit uses parent git repo instead of .specify directory as root
  • Adds find_specify_root() / Find-SpecifyRoot functions to search upward for .specify directory
  • Updates get_repo_root() / Get-RepoRoot to check .specify first, then fall back to git
  • Updates has_git() / Test-HasGit to verify git exists at the spec-kit root level (not parent)

Problem

When spec-kit is initialized in a subdirectory that doesn't have its own .git, but a parent directory does, git rev-parse --show-toplevel finds the parent's git repo and uses that as root. This causes specs to be created in the wrong location.

Example:

  • Parent /work/ has .git
  • Child /work/my-project/ has .specify (spec-kit initialized here)
  • Before: specs created in /work/specs/ (wrong)
  • After: specs created in /work/my-project/specs/ (correct)

Test plan

  • Verified bash syntax with bash -n
  • Verified PowerShell syntax with parser
  • Tested: child with .specify, parent with .git → returns child
  • Tested: project with both .specify and .git → returns project, has_git=true
  • Tested: only .git, no .specify → falls back to git correctly
  • Tested: deeply nested .specify → found correctly
  • Tested: .specify in parent, working in child → finds parent .specify
  • Tested: .specify as file (not directory) → correctly ignored

Fixes #1932

🤖 Generated with Claude Code

When spec-kit is initialized in a subdirectory that doesn't have its
own .git, but a parent directory does, spec-kit was incorrectly using
the parent's git repository root. This caused specs to be created in
the wrong location.

The fix changes repo root detection to prioritize .specify directory
over git rev-parse, ensuring spec-kit respects its own initialization
boundary rather than inheriting a parent git repo.

Fixes github#1932

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 21, 2026 18:17
@mbachorik mbachorik requested a review from mnriem as a code owner March 21, 2026 18:17
Copy link
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

This PR fixes repo-root detection in the bash and PowerShell helper scripts by prioritizing the presence of a .specify directory over git rev-parse, preventing spec-kit from mistakenly using a parent Git repo as the project root in nested-directory setups.

Changes:

  • Added upward-search helpers (find_specify_root / Find-SpecifyRoot) to locate the nearest .specify directory.
  • Updated root resolution (get_repo_root / Get-RepoRoot) to prefer .specify, then fall back to Git, then script location.
  • Updated Git-availability checks (has_git / Test-HasGit) to be root-scoped instead of inheriting parent Git repos.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
scripts/powershell/common.ps1 Adds .specify-first root discovery and updates Get-RepoRoot/Test-HasGit behavior.
scripts/powershell/create-new-feature.ps1 Switches root detection + git detection to rely on shared common.ps1 functions.
scripts/bash/common.sh Adds .specify-first root discovery and updates get_repo_root/has_git behavior.
scripts/bash/create-new-feature.sh Switches root detection + git detection to rely on shared common.sh functions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mbachorik mbachorik marked this pull request as draft March 21, 2026 18:26
- Normalize paths in find_specify_root to prevent infinite loop with relative paths
- Use -PathType Container in PowerShell to only match .specify directories
- Improve has_git/Test-HasGit to check git command availability and validate work tree
- Handle git worktrees/submodules where .git can be a file
- Remove dead fallback code in create-new-feature scripts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
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

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Fixes edge case where project root is at filesystem root (common in
containers). The loop now checks for .specify before checking the
termination condition.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
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

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

scripts/powershell/common.ps1:111

  • Test-HasGit correctly scopes git detection to the spec-kit root, but Get-CurrentBranch still runs git rev-parse --abbrev-ref HEAD from the current working directory. In a .specify project nested under a parent git repo, that git call can succeed (via the parent) even though Test-HasGit returns false, which bypasses the non-git fallback (scanning specs/) and yields the wrong branch name. Consider updating Get-CurrentBranch to only call git when Test-HasGit is true, and run it as git -C $repoRoot ... using Get-RepoRoot.
# Check if we have git available at the spec-kit root level
# Returns true only if git is installed and the repo root is inside a git work tree
# Handles both regular repos (.git directory) and worktrees/submodules (.git file)
function Test-HasGit {
    $repoRoot = Get-RepoRoot
    # First check if git command is available
    if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
        return $false
    }
    # Check if .git exists (directory or file for worktrees/submodules)
    if (-not (Test-Path (Join-Path $repoRoot ".git"))) {
        return $false
    }
    # Verify it's actually a valid git work tree
    try {
        $null = git -C $repoRoot rev-parse --is-inside-work-tree 2>$null
        return ($LASTEXITCODE -eq 0)
    } catch {
        return $false
    }
}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- get_current_branch now uses has_git check and runs git with -C to
  prevent using parent git repo branch names in .specify-only projects
- Same fix applied to PowerShell Get-CurrentBranch
- Removed unused find_repo_root() from create-new-feature.sh
- Removed unused Find-RepositoryRoot from create-new-feature.ps1

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
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

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Prevents cd from interpreting directory names like -P or -L as options.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
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

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Avoids unnecessary work when git isn't installed since get_repo_root
may internally call git rev-parse.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
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

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Use -LiteralPath in Find-SpecifyRoot to handle paths with wildcard
  characters ([, ], *, ?)
- Check Get-Command git before calling Get-RepoRoot in Test-HasGit to
  avoid unnecessary work when git isn't installed

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
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

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

scripts/powershell/common.ps1:72

  • In Get-CurrentBranch, Test-Path $specsDir can interpret wildcard characters in $repoRoot/$specsDir as globs, which can break branch fallback logic for repos located in paths containing [, ], *, or ? (the same edge case Find-SpecifyRoot was updated to handle). Prefer Test-Path -LiteralPath $specsDir -PathType Container (and consider using Get-ChildItem -LiteralPath too for consistency).
    # For non-git repos, try to find the latest feature directory
    $specsDir = Join-Path $repoRoot "specs"
    
    if (Test-Path $specsDir) {
        $latestFeature = ""

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Prevents Test-Path from treating wildcard characters in paths as globs.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
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

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

scripts/powershell/create-new-feature.ps1:126

  • Set-Location $repoRoot uses the -Path parameter positionally, which means PowerShell can interpret wildcard characters (e.g. [, ], *, ?) in $repoRoot. Since this PR explicitly adds -LiteralPath handling for root detection, switching to Set-Location -LiteralPath $repoRoot (and similarly using -LiteralPath for subsequent filesystem ops rooted at $repoRoot) would prevent misnavigation when the repo lives under a path containing wildcard characters.
Set-Location $repoRoot

$specsDir = Join-Path $repoRoot 'specs'
New-Item -ItemType Directory -Path $specsDir -Force | Out-Null

scripts/powershell/common.ps1:76

  • In Get-CurrentBranch, $specsDir can contain wildcard characters if the repo path contains [, ], *, or ?. Using Test-Path $specsDir and Get-ChildItem -Path $specsDir will treat those as globs and may fail to detect an existing specs directory. Consider using Test-Path -LiteralPath $specsDir and Get-ChildItem -LiteralPath $specsDir to be consistent with the new literal-path handling in Find-SpecifyRoot / Test-HasGit.
    $specsDir = Join-Path $repoRoot "specs"
    
    if (Test-Path $specsDir) {
        $latestFeature = ""
        $highest = 0
        
        Get-ChildItem -Path $specsDir -Directory | ForEach-Object {
            if ($_.Name -match '^(\d{3})-') {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mbachorik mbachorik marked this pull request as ready for review March 22, 2026 14:53
@mbachorik
Copy link
Contributor Author

A small fix of annoying edge case.

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.

Bug: spec-kit uses parent git repo instead of .specify directory as root

2 participants