Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
297 changes: 297 additions & 0 deletions scripts/fish/create-new-feature.fish
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
#!/usr/bin/env fish

Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

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

The script does not set error handling mode. The bash version uses set -e to exit on errors. In fish, consider adding set -e at the beginning to ensure the script exits on command failures, providing consistent behavior with the bash implementation.

Suggested change
function __fail_on_error --on-job-fail %self
exit 1
end

Copilot uses AI. Check for mistakes.
set JSON_MODE false
set SHORT_NAME ""
set BRANCH_NUMBER ""
set ARGS

set i 1
while test $i -le (count $argv)
set arg $argv[$i]
switch $arg
case --json
set JSON_MODE true
case --short-name
if test (math $i + 1) -gt (count $argv)
echo 'Error: --short-name requires a value' >&2
exit 1
end
set i (math $i + 1)
set next_arg $argv[$i]
# Check if the next argument is another option
if string match -q -- '--*' $next_arg
echo 'Error: --short-name requires a value' >&2
exit 1
end
set SHORT_NAME $next_arg
case --number
if test (math $i + 1) -gt (count $argv)
echo 'Error: --number requires a value' >&2
exit 1
end
set i (math $i + 1)
set next_arg $argv[$i]
if string match -q -- '--*' $next_arg
echo 'Error: --number requires a value' >&2
exit 1
end
set BRANCH_NUMBER $next_arg
case --help -h
echo "Usage: create-new-feature.fish [--json] [--short-name <name>] [--number N] <feature_description>"
echo ""
echo "Options:"
echo " --json Output in JSON format"
echo " --short-name <name> Provide a custom short name (2-4 words) for the branch"
echo " --number N Specify branch number manually (overrides auto-detection)"
echo " --help, -h Show this help message"
echo ""
echo "Examples:"
echo " create-new-feature.fish 'Add user authentication system' --short-name 'user-auth'"
echo " create-new-feature.fish 'Implement OAuth2 integration for API' --number 5"
Comment on lines +40 to +50
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

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

The help text uses a hardcoded script name instead of a variable. The bash version uses $0 to dynamically show the script name that was invoked, which is more flexible and accurate. This fish version should use something like (status --current-filename) or basename of it to match the bash behavior and show the actual invoked script name.

Copilot uses AI. Check for mistakes.
exit 0
case '*'
set -a ARGS $arg
end
set i (math $i + 1)
end

set FEATURE_DESCRIPTION (string join " " $ARGS)
if test -z "$FEATURE_DESCRIPTION"
echo "Usage: create-new-feature.fish [--json] [--short-name <name>] [--number N] <feature_description>" >&2
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

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

The usage message in the error case also uses a hardcoded script name instead of dynamically referencing the invoked script. For consistency with the bash version and better usability, consider using a variable containing the script name.

Copilot uses AI. Check for mistakes.
exit 1
end

# Function to find the repository root
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

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

Missing explanatory comment. The bash version has "Function to find the repository root by searching for existing project markers" which is more descriptive than just "Function to find the repository root". The additional context about "searching for existing project markers" helps explain what the function does. Consider adding this for better documentation.

Suggested change
# Function to find the repository root
# Function to find the repository root by searching for existing project markers

Copilot uses AI. Check for mistakes.
function find_repo_root
set dir $argv[1]
while test "$dir" != "/"
if test -d "$dir/.git"; or test -d "$dir/.specify"
echo $dir
return 0
end
set dir (dirname $dir)
end
return 1
end

# Function to get highest number from specs directory
function get_highest_from_specs
set specs_dir $argv[1]
set highest 0

if test -d "$specs_dir"
for dir in $specs_dir/*
test -d "$dir"; or continue
set dirname (basename $dir)
set number (string match -r '^[0-9]+' $dirname; or echo "0")
set number (math $number + 0)
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

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

Missing base-10 interpretation prefix. The bash version uses number=$((10#$number)) to force base-10 interpretation. This fish version only uses (math $number + 0) which may not properly handle numbers with leading zeros (e.g., "008" could be misinterpreted). This could lead to incorrect identification of the highest spec number when directory names have leading zeros.

Copilot uses AI. Check for mistakes.
if test $number -gt $highest
set highest $number
end
end
end

echo $highest
end

# Function to get highest number from git branches
function get_highest_from_branches
set highest 0

# Get all branches (local and remote)
set branches (git branch -a 2>/dev/null; or echo "")

if test -n "$branches"
for branch in $branches
# Clean branch name: remove leading markers and remote prefixes
set clean_branch (echo $branch | sed 's/^[* ]*//; s|^remotes/[^/]*/||')

# Extract feature number if branch matches pattern ###-*
if string match -qr '^[0-9]{3}-' $clean_branch
set number (string match -r '^[0-9]{3}' $clean_branch; or echo "0")
set number (math $number + 0)
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

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

Missing base-10 interpretation prefix. The bash version uses number=$((10#$number)) to force base-10 interpretation. This fish version only uses (math $number + 0) which may not properly handle numbers with leading zeros (e.g., "008" could be misinterpreted). This could lead to incorrect identification of the highest branch number when branch names have leading zeros.

Copilot uses AI. Check for mistakes.
if test $number -gt $highest
set highest $number
end
end
end
end

echo $highest
end

# Function to check existing branches
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

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

Missing detailed comment. The bash version has "Function to check existing branches (local and remote) and return next available number" which is more descriptive. The fish version only has "Function to check existing branches" without mentioning "local and remote" or "return next available number". Consider adding the full description for better clarity.

Suggested change
# Function to check existing branches
# Function to check existing branches (local and remote) and return next available number

Copilot uses AI. Check for mistakes.
function check_existing_branches
set specs_dir $argv[1]

# Fetch all remotes
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

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

Missing comment about remote fetching. The bash version has "Fetch all remotes to get latest branch info (suppress errors if no remotes)" which explains both the action and the error suppression strategy. The fish version lacks this comment. Consider adding it for better documentation.

Suggested change
# Fetch all remotes
# Fetch all remotes to get latest branch info (suppress errors if no remotes)

Copilot uses AI. Check for mistakes.
git fetch --all --prune 2>/dev/null; or true

# Get highest number from ALL branches
set highest_branch (get_highest_from_branches)

# Get highest number from ALL specs
set highest_spec (get_highest_from_specs $specs_dir)

# Take the maximum of both
set max_num $highest_branch
if test $highest_spec -gt $max_num
set max_num $highest_spec
end

# Return next number
echo (math $max_num + 1)
end

# Function to clean and format a branch name
function clean_branch_name
set name $argv[1]
echo $name | string lower | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//'
end

# Resolve repository root
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

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

Missing detailed comment about repository root detection. The bash version includes an explanatory comment: "Resolve repository root. Prefer git information when available, but fall back to searching for repository markers so the workflow still functions in repositories that were initialised with --no-git." This provides important context about the design decision. Consider adding this comment for better maintainability.

Suggested change
# Resolve repository root
# Resolve repository root. Prefer git information when available, but fall back
# to searching for repository markers so the workflow still functions in
# repositories that were initialised with --no-git.

Copilot uses AI. Check for mistakes.
set SCRIPT_DIR (dirname (status --current-filename))

if git rev-parse --show-toplevel >/dev/null 2>&1
set REPO_ROOT (git rev-parse --show-toplevel)
set HAS_GIT true
else
set REPO_ROOT (find_repo_root $SCRIPT_DIR)
if test -z "$REPO_ROOT"
echo "Error: Could not determine repository root. Please run this script from within the repository." >&2
exit 1
end
set HAS_GIT false
end

cd $REPO_ROOT

set SPECS_DIR "$REPO_ROOT/specs"
mkdir -p $SPECS_DIR

# Function to generate branch name with stop word filtering
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

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

Missing detailed comment explaining generation logic. The bash version has: "Function to generate branch name with stop word filtering and length filtering" while fish version only says "Function to generate branch name with stop word filtering". The "and length filtering" part describes an important aspect of the function's behavior. Consider adding this for accuracy and consistency.

Suggested change
# Function to generate branch name with stop word filtering
# Function to generate branch name with stop word and length filtering

Copilot uses AI. Check for mistakes.
function generate_branch_name
set description $argv[1]

# Common stop words to filter out
set stop_words '^(i|a|an|the|to|for|of|in|on|at|by|with|from|is|are|was|were|be|been|being|have|has|had|do|does|did|will|would|should|could|can|may|might|must|shall|this|that|these|those|my|your|our|their|want|need|add|get|set)$'
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

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

The stop words regex pattern is missing the word boundary anchors that are present in the bash version. The bash version uses grep -qiE "$stop_words" which effectively matches whole words. Without proper word boundary handling in fish, partial matches could occur. Consider revising the regex pattern to ensure only complete word matches are filtered.

Copilot uses AI. Check for mistakes.

# Convert to lowercase and split into words
set clean_name (echo $description | string lower | sed 's/[^a-z0-9]/ /g')

# Filter words
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

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

Comment differs from bash version. The bash version says "Filter words: remove stop words and words shorter than 3 chars (unless they're uppercase acronyms in original)" which is more descriptive. The fish version says "Filter words" followed by "Keep words that are NOT stop words AND (length >= 3 OR are potential acronyms)" in a later comment. Consider matching the bash version's comment style for consistency.

Suggested change
# Filter words
# Filter words: remove stop words and words shorter than 3 chars (unless they're uppercase acronyms in original)

Copilot uses AI. Check for mistakes.
set meaningful_words
for word in $clean_name
# Skip empty words
test -z "$word"; and continue

# Keep words that are NOT stop words AND (length >= 3 OR are potential acronyms)
if not echo $word | string match -qir $stop_words
if test (string length $word) -ge 3
set -a meaningful_words $word
else if echo $description | string match -q "*"(string upper $word)"*"
# Keep short words if they appear as uppercase in original
Comment on lines +192 to +193
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

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

The acronym detection logic differs from the bash version. The bash version checks if the word appears as uppercase in the original description using grep -q "\b${word^^}\b" with word boundaries. The fish version uses string match -q "*"(string upper $word)"*" which performs a substring match and could produce false positives. For example, if the word "id" appears anywhere in "valid", it would match. Consider using a more precise matching approach.

Suggested change
else if echo $description | string match -q "*"(string upper $word)"*"
# Keep short words if they appear as uppercase in original
else if echo $description | grep -q -w (string upper $word)
# Keep short words if they appear as uppercase in original as a whole word

Copilot uses AI. Check for mistakes.
set -a meaningful_words $word
end
end
end

# If we have meaningful words, use first 3-4 of them
if test (count $meaningful_words) -gt 0
set max_words 3
if test (count $meaningful_words) -eq 4
set max_words 4
end

set result ""
set count 0
for word in $meaningful_words
if test $count -ge $max_words
break
end
if test -n "$result"
set result "$result-"
end
set result "$result$word"
set count (math $count + 1)
end
echo $result
else
# Fallback to original logic
set cleaned (clean_branch_name $description)
echo $cleaned | string split '-' | string match -v '' | head -3 | string join '-'
Comment on lines +220 to +222
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

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

The fallback branch name generation logic differs from the bash version. The bash version uses tr '\n' '-' | sed 's/-$//' to join words and remove trailing hyphens, while the fish version uses string join '-'. While both achieve similar results, the bash version explicitly processes the cleaned output through tr '-' '\n' | grep -v '^$' | head -3 to split, filter empty strings, take first 3, then rejoin. The fish version uses string split '-' | string match -v '' | head -3 | string join '-' which should work equivalently. However, verify this produces identical results in edge cases.

Suggested change
# Fallback to original logic
set cleaned (clean_branch_name $description)
echo $cleaned | string split '-' | string match -v '' | head -3 | string join '-'
# Fallback to original logic (match bash implementation)
set cleaned (clean_branch_name $description)
echo $cleaned | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//'

Copilot uses AI. Check for mistakes.
end
end

# Generate branch name
if test -n "$SHORT_NAME"
# Use provided short name, just clean it up
set BRANCH_SUFFIX (clean_branch_name $SHORT_NAME)
else
# Generate from description with smart filtering
set BRANCH_SUFFIX (generate_branch_name $FEATURE_DESCRIPTION)
end

# Determine branch number
if test -z "$BRANCH_NUMBER"
if test $HAS_GIT = true
# Check existing branches on remotes
set BRANCH_NUMBER (check_existing_branches $SPECS_DIR)
else
# Fall back to local directory check
set HIGHEST (get_highest_from_specs $SPECS_DIR)
set BRANCH_NUMBER (math $HIGHEST + 1)
end
end

# Force base-10 interpretation
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

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

Inconsistent comment format. The bash version has a more detailed comment explaining "Force base-10 interpretation to prevent octal conversion (e.g., 010 → 8 in octal, but should be 10 in decimal)". This fish version only says "Force base-10 interpretation" without the helpful explanation. For maintainability and clarity, consider adding the full explanation as in the bash version.

Suggested change
# Force base-10 interpretation
# Force base-10 interpretation to prevent octal conversion (e.g., 010 → 8 in octal, but should be 10 in decimal)

Copilot uses AI. Check for mistakes.
set FEATURE_NUM (printf "%03d" (math $BRANCH_NUMBER + 0))
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

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

Missing base-10 interpretation prefix. The bash version uses $((10#$BRANCH_NUMBER)) to force base-10 interpretation and prevent octal conversion (e.g., "010" being interpreted as octal 8 instead of decimal 10). The fish version only uses (math $BRANCH_NUMBER + 0) which may not handle this edge case correctly. This could lead to incorrect branch numbering when branch numbers like 008, 009, 010, etc. are provided.

Copilot uses AI. Check for mistakes.
set BRANCH_NAME "$FEATURE_NUM-$BRANCH_SUFFIX"

# GitHub enforces a 244-byte limit on branch names
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

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

Missing comment for branch name validation section. The bash version includes a comment "Validate and truncate if necessary" after the main comment about GitHub's 244-byte limit. This helps clarify the purpose of the following code block. Consider adding this comment for consistency and clarity.

Suggested change
# GitHub enforces a 244-byte limit on branch names
# GitHub enforces a 244-byte limit on branch names
# Validate and truncate if necessary

Copilot uses AI. Check for mistakes.
set MAX_BRANCH_LENGTH 244
if test (string length $BRANCH_NAME) -gt $MAX_BRANCH_LENGTH
# Calculate how much we need to trim from suffix
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

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

Comment style differs from bash version. The bash version says "Calculate how much we need to trim from suffix" followed by "Account for: feature number (3) + hyphen (1) = 4 chars" which provides helpful arithmetic. The fish version only has the first comment. Consider adding the detailed breakdown for better clarity.

Suggested change
# Calculate how much we need to trim from suffix
# Calculate how much we need to trim from suffix
# Account for: feature number (3) + hyphen (1) = 4 chars

Copilot uses AI. Check for mistakes.
set MAX_SUFFIX_LENGTH (math $MAX_BRANCH_LENGTH - 4)

# Truncate suffix
Copy link

Copilot AI Dec 20, 2025

Choose a reason for hiding this comment

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

Missing comment about truncation strategy. The bash version has "Truncate suffix at word boundary if possible" before the truncation line, explaining the intent. This comment is missing in the fish version. Consider adding it for better code documentation.

Suggested change
# Truncate suffix
# Truncate suffix at word boundary if possible

Copilot uses AI. Check for mistakes.
set TRUNCATED_SUFFIX (string sub -l $MAX_SUFFIX_LENGTH $BRANCH_SUFFIX)
# Remove trailing hyphen if truncation created one
set TRUNCATED_SUFFIX (echo $TRUNCATED_SUFFIX | sed 's/-$//')

set ORIGINAL_BRANCH_NAME $BRANCH_NAME
set BRANCH_NAME "$FEATURE_NUM-$TRUNCATED_SUFFIX"

echo "[specify] Warning: Branch name exceeded GitHub's 244-byte limit" >&2
echo "[specify] Original: $ORIGINAL_BRANCH_NAME ("(string length $ORIGINAL_BRANCH_NAME)" bytes)" >&2
echo "[specify] Truncated to: $BRANCH_NAME ("(string length $BRANCH_NAME)" bytes)" >&2
end

if test $HAS_GIT = true
git checkout -b $BRANCH_NAME
else
echo "[specify] Warning: Git repository not detected; skipped branch creation for $BRANCH_NAME" >&2
end

set FEATURE_DIR "$SPECS_DIR/$BRANCH_NAME"
mkdir -p $FEATURE_DIR

set TEMPLATE "$REPO_ROOT/.specify/templates/spec-template.md"
set SPEC_FILE "$FEATURE_DIR/spec.md"
if test -f "$TEMPLATE"
cp $TEMPLATE $SPEC_FILE
else
touch $SPEC_FILE
end

# Set the SPECIFY_FEATURE environment variable for the current session
set -gx SPECIFY_FEATURE $BRANCH_NAME

if test $JSON_MODE = true
printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' $BRANCH_NAME $SPEC_FILE $FEATURE_NUM
else
echo "BRANCH_NAME: $BRANCH_NAME"
echo "SPEC_FILE: $SPEC_FILE"
echo "FEATURE_NUM: $FEATURE_NUM"
echo "SPECIFY_FEATURE environment variable set to: $BRANCH_NAME"
end