From 520bba36a35ace828bc6266f2750661585eae0a0 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 4 Oct 2025 14:29:34 +0800 Subject: [PATCH 1/2] fix: detect and warn about spec directory mismatch Fixes #733 When users delete a branch and create a new one, the spec directory name no longer matches the branch name, causing spec-kit to fail. This fix adds automatic detection of mismatched spec directories and provides clear instructions to fix the issue using git mv. Changes: - Add check_and_fix_spec_directory_mismatch() function to common.sh - Integrate mismatch check into setup-plan.sh and check-prerequisites.sh - Handle three scenarios: single orphaned dir, multiple orphaned dirs, no dir - Provide actionable git mv commands in warning messages --- scripts/bash/check-prerequisites.sh | 5 ++ scripts/bash/common.sh | 71 +++++++++++++++++++++++++++++ scripts/bash/setup-plan.sh | 3 ++ 3 files changed, 79 insertions(+) diff --git a/scripts/bash/check-prerequisites.sh b/scripts/bash/check-prerequisites.sh index f32b6245a..14a7ba803 100644 --- a/scripts/bash/check-prerequisites.sh +++ b/scripts/bash/check-prerequisites.sh @@ -82,6 +82,11 @@ source "$SCRIPT_DIR/common.sh" eval $(get_feature_paths) check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1 +# Check if branch and spec directory are in sync (skip in paths-only mode) +if ! $PATHS_ONLY; then + check_and_fix_spec_directory_mismatch || exit 1 +fi + # If paths-only mode, output paths and exit (support JSON + paths-only combined) if $PATHS_ONLY; then if $JSON_MODE; then diff --git a/scripts/bash/common.sh b/scripts/bash/common.sh index 34e5d4bb7..15ab31b4b 100644 --- a/scripts/bash/common.sh +++ b/scripts/bash/common.sh @@ -111,3 +111,74 @@ EOF check_file() { [[ -f "$1" ]] && echo " ✓ $2" || echo " ✗ $2"; } check_dir() { [[ -d "$1" && -n $(ls -A "$1" 2>/dev/null) ]] && echo " ✓ $2" || echo " ✗ $2"; } + +# Check if current branch matches a spec directory, and offer to fix mismatches +check_and_fix_spec_directory_mismatch() { + local repo_root=$(get_repo_root) + local current_branch=$(get_current_branch) + local expected_dir="$repo_root/specs/$current_branch" + + # Skip check for non-git repos or main branch + if [[ "$current_branch" == "main" ]] || ! has_git; then + return 0 + fi + + # Skip check if branch doesn't follow feature branch naming convention + if [[ ! "$current_branch" =~ ^[0-9]{3}- ]]; then + return 0 + fi + + # If expected directory exists, all good + if [[ -d "$expected_dir" ]]; then + return 0 + fi + + # Directory doesn't exist - look for orphaned spec directories + local specs_dir="$repo_root/specs" + local orphaned_dirs=() + + if [[ -d "$specs_dir" ]]; then + for dir in "$specs_dir"/*; do + if [[ -d "$dir" ]]; then + local dirname=$(basename "$dir") + # Check if this spec dir has no matching branch + if ! git rev-parse --verify "$dirname" >/dev/null 2>&1; then + orphaned_dirs+=("$dirname") + fi + fi + done + fi + + # If we found exactly one orphaned directory, suggest renaming it + if [[ ${#orphaned_dirs[@]} -eq 1 ]]; then + local orphaned="${orphaned_dirs[0]}" + echo "" >&2 + echo "⚠️ Warning: Branch '$current_branch' has no matching spec directory" >&2 + echo " Found orphaned spec directory: specs/$orphaned" >&2 + echo " This may be from a deleted or renamed branch." >&2 + echo "" >&2 + echo " To fix this issue, run:" >&2 + echo " git mv specs/$orphaned specs/$current_branch" >&2 + echo "" >&2 + return 1 + elif [[ ${#orphaned_dirs[@]} -gt 1 ]]; then + echo "" >&2 + echo "⚠️ Warning: Branch '$current_branch' has no matching spec directory" >&2 + echo " Found multiple orphaned spec directories:" >&2 + for dir in "${orphaned_dirs[@]}"; do + echo " - specs/$dir" >&2 + done + echo "" >&2 + echo " To fix this, manually rename the correct directory:" >&2 + echo " git mv specs/ specs/$current_branch" >&2 + echo "" >&2 + return 1 + else + # No spec directory exists at all - might be a new branch + echo "" >&2 + echo "⚠️ Warning: No spec directory found for branch '$current_branch'" >&2 + echo " Run /specify to create a new feature specification." >&2 + echo "" >&2 + return 1 + fi +} diff --git a/scripts/bash/setup-plan.sh b/scripts/bash/setup-plan.sh index 654ba50d7..b06dfc9d2 100644 --- a/scripts/bash/setup-plan.sh +++ b/scripts/bash/setup-plan.sh @@ -33,6 +33,9 @@ eval $(get_feature_paths) # Check if we're on a proper feature branch (only for git repos) check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1 +# Check if branch and spec directory are in sync +check_and_fix_spec_directory_mismatch || exit 1 + # Ensure the feature directory exists mkdir -p "$FEATURE_DIR" From 08de238ff1e1a12a5604b266c8abfb3dbf984972 Mon Sep 17 00:00:00 2001 From: Ray Tien Date: Sat, 4 Oct 2025 14:39:52 +0800 Subject: [PATCH 2/2] refactor: extract feature branch pattern to reusable constant --- scripts/bash/common.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/bash/common.sh b/scripts/bash/common.sh index 15ab31b4b..507c9a2e1 100644 --- a/scripts/bash/common.sh +++ b/scripts/bash/common.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash # Common functions and variables for all scripts +# Feature branch naming pattern (3 digits followed by hyphen) +readonly FEATURE_BRANCH_PATTERN='^[0-9]{3}-' + # Get repository root, with fallback for non-git repositories get_repo_root() { if git rev-parse --show-toplevel >/dev/null 2>&1; then @@ -72,7 +75,7 @@ check_feature_branch() { return 0 fi - if [[ ! "$branch" =~ ^[0-9]{3}- ]]; then + if [[ ! "$branch" =~ $FEATURE_BRANCH_PATTERN ]]; then echo "ERROR: Not on a feature branch. Current branch: $branch" >&2 echo "Feature branches should be named like: 001-feature-name" >&2 return 1 @@ -124,7 +127,7 @@ check_and_fix_spec_directory_mismatch() { fi # Skip check if branch doesn't follow feature branch naming convention - if [[ ! "$current_branch" =~ ^[0-9]{3}- ]]; then + if [[ ! "$current_branch" =~ $FEATURE_BRANCH_PATTERN ]]; then return 0 fi @@ -137,7 +140,7 @@ check_and_fix_spec_directory_mismatch() { local specs_dir="$repo_root/specs" local orphaned_dirs=() - if [[ -d "$specs_dir" ]]; then + if [[ -d "$specs_dir" ]] && has_git; then for dir in "$specs_dir"/*; do if [[ -d "$dir" ]]; then local dirname=$(basename "$dir")