Skip to content

Commit 00647fd

Browse files
iamaeroplaneclaude
andcommitted
fix(scripts): prioritize .specify over git for repo root detection
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 #1932 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 6644f69 commit 00647fd

4 files changed

Lines changed: 88 additions & 38 deletions

File tree

scripts/bash/common.sh

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,39 @@
11
#!/usr/bin/env bash
22
# Common functions and variables for all scripts
33

4-
# Get repository root, with fallback for non-git repositories
4+
# Find repository root by searching upward for .specify directory
5+
# This is the primary marker for spec-kit projects
6+
find_specify_root() {
7+
local dir="${1:-$(pwd)}"
8+
while [ "$dir" != "/" ]; do
9+
if [ -d "$dir/.specify" ]; then
10+
echo "$dir"
11+
return 0
12+
fi
13+
dir="$(dirname "$dir")"
14+
done
15+
return 1
16+
}
17+
18+
# Get repository root, prioritizing .specify directory over git
19+
# This prevents using a parent git repo when spec-kit is initialized in a subdirectory
520
get_repo_root() {
21+
# First, look for .specify directory (spec-kit's own marker)
22+
local specify_root
23+
if specify_root=$(find_specify_root); then
24+
echo "$specify_root"
25+
return
26+
fi
27+
28+
# Fallback to git if no .specify found
629
if git rev-parse --show-toplevel >/dev/null 2>&1; then
730
git rev-parse --show-toplevel
8-
else
9-
# Fall back to script location for non-git repos
10-
local script_dir="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11-
(cd "$script_dir/../../.." && pwd)
31+
return
1232
fi
33+
34+
# Final fallback to script location for non-git repos
35+
local script_dir="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
36+
(cd "$script_dir/../../.." && pwd)
1337
}
1438

1539
# Get current branch, with fallback for non-git repositories
@@ -57,9 +81,11 @@ get_current_branch() {
5781
echo "main" # Final fallback
5882
}
5983

60-
# Check if we have git available
84+
# Check if we have git available at the spec-kit root level
85+
# Returns true only if the .specify root has its own .git directory
6186
has_git() {
62-
git rev-parse --show-toplevel >/dev/null 2>&1
87+
local repo_root=$(get_repo_root)
88+
[ -d "$repo_root/.git" ]
6389
}
6490

6591
check_feature_branch() {

scripts/bash/create-new-feature.sh

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -162,21 +162,24 @@ clean_branch_name() {
162162
echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//'
163163
}
164164

165-
# Resolve repository root. Prefer git information when available, but fall back
166-
# to searching for repository markers so the workflow still functions in repositories that
167-
# were initialised with --no-git.
165+
# Resolve repository root using common.sh functions which prioritize .specify over git
168166
SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
169167
source "$SCRIPT_DIR/common.sh"
170168

171-
if git rev-parse --show-toplevel >/dev/null 2>&1; then
172-
REPO_ROOT=$(git rev-parse --show-toplevel)
173-
HAS_GIT=true
174-
else
169+
REPO_ROOT=$(get_repo_root)
170+
if [ -z "$REPO_ROOT" ]; then
171+
# Fallback to local find_repo_root if common.sh couldn't find it
175172
REPO_ROOT="$(find_repo_root "$SCRIPT_DIR")"
176173
if [ -z "$REPO_ROOT" ]; then
177174
echo "Error: Could not determine repository root. Please run this script from within the repository." >&2
178175
exit 1
179176
fi
177+
fi
178+
179+
# Check if git is available at this repo root (not a parent)
180+
if has_git; then
181+
HAS_GIT=true
182+
else
180183
HAS_GIT=false
181184
fi
182185

scripts/powershell/common.ps1

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,34 @@
11
#!/usr/bin/env pwsh
22
# Common PowerShell functions analogous to common.sh
33

4+
# Find repository root by searching upward for .specify directory
5+
# This is the primary marker for spec-kit projects
6+
function Find-SpecifyRoot {
7+
param([string]$StartDir = (Get-Location).Path)
8+
9+
$current = $StartDir
10+
while ($true) {
11+
if (Test-Path (Join-Path $current ".specify")) {
12+
return $current
13+
}
14+
$parent = Split-Path $current -Parent
15+
if ([string]::IsNullOrEmpty($parent) -or $parent -eq $current) {
16+
return $null
17+
}
18+
$current = $parent
19+
}
20+
}
21+
22+
# Get repository root, prioritizing .specify directory over git
23+
# This prevents using a parent git repo when spec-kit is initialized in a subdirectory
424
function Get-RepoRoot {
25+
# First, look for .specify directory (spec-kit's own marker)
26+
$specifyRoot = Find-SpecifyRoot
27+
if ($specifyRoot) {
28+
return $specifyRoot
29+
}
30+
31+
# Fallback to git if no .specify found
532
try {
633
$result = git rev-parse --show-toplevel 2>$null
734
if ($LASTEXITCODE -eq 0) {
@@ -10,8 +37,8 @@ function Get-RepoRoot {
1037
} catch {
1138
# Git command failed
1239
}
13-
14-
# Fall back to script location for non-git repos
40+
41+
# Final fallback to script location for non-git repos
1542
return (Resolve-Path (Join-Path $PSScriptRoot "../../..")).Path
1643
}
1744

@@ -58,13 +85,11 @@ function Get-CurrentBranch {
5885
return "main"
5986
}
6087

88+
# Check if we have git available at the spec-kit root level
89+
# Returns true only if the .specify root has its own .git directory
6190
function Test-HasGit {
62-
try {
63-
git rev-parse --show-toplevel 2>$null | Out-Null
64-
return ($LASTEXITCODE -eq 0)
65-
} catch {
66-
return $false
67-
}
91+
$repoRoot = Get-RepoRoot
92+
return (Test-Path (Join-Path $repoRoot ".git"))
6893
}
6994

7095
function Test-FeatureBranch {

scripts/powershell/create-new-feature.ps1

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -135,27 +135,23 @@ function ConvertTo-CleanBranchName {
135135

136136
return $Name.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', ''
137137
}
138-
$fallbackRoot = (Find-RepositoryRoot -StartDir $PSScriptRoot)
139-
if (-not $fallbackRoot) {
140-
Write-Error "Error: Could not determine repository root. Please run this script from within the repository."
141-
exit 1
142-
}
143-
144-
# Load common functions (includes Resolve-Template)
138+
# Load common functions (includes Get-RepoRoot, Test-HasGit, Resolve-Template)
145139
. "$PSScriptRoot/common.ps1"
146140

147-
try {
148-
$repoRoot = git rev-parse --show-toplevel 2>$null
149-
if ($LASTEXITCODE -eq 0) {
150-
$hasGit = $true
151-
} else {
152-
throw "Git not available"
141+
# Use common.ps1 functions which prioritize .specify over git
142+
$repoRoot = Get-RepoRoot
143+
if (-not $repoRoot) {
144+
# Fallback to local Find-RepositoryRoot if common.ps1 couldn't find it
145+
$repoRoot = (Find-RepositoryRoot -StartDir $PSScriptRoot)
146+
if (-not $repoRoot) {
147+
Write-Error "Error: Could not determine repository root. Please run this script from within the repository."
148+
exit 1
153149
}
154-
} catch {
155-
$repoRoot = $fallbackRoot
156-
$hasGit = $false
157150
}
158151

152+
# Check if git is available at this repo root (not a parent)
153+
$hasGit = Test-HasGit
154+
159155
Set-Location $repoRoot
160156

161157
$specsDir = Join-Path $repoRoot 'specs'

0 commit comments

Comments
 (0)