Skip to content
Open
Show file tree
Hide file tree
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
83 changes: 73 additions & 10 deletions scripts/bash/create-new-feature.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,47 @@
set -e

JSON_MODE=false
FEATURE_NAME=""
ARGS=()
for arg in "$@"; do
case "$arg" in
--json) JSON_MODE=true ;;
--help|-h) echo "Usage: $0 [--json] <feature_description>"; exit 0 ;;
*) ARGS+=("$arg") ;;

# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--json)
JSON_MODE=true
shift
;;
--feature-name=*)
FEATURE_NAME="${1#*=}"
shift
;;
--feature-name)
if [[ -n "$2" && "$2" != --* ]]; then
FEATURE_NAME="$2"
shift 2
else
echo "Error: --feature-name requires a value" >&2
exit 1
fi
;;
--help|-h)
echo "Usage: $0 [--json] [--feature-name=<name>] <feature_description>"
exit 0
;;
-*)
echo "Unknown option: $1" >&2
exit 1
;;
*)
ARGS+=("$1")
shift
;;
esac
done

FEATURE_DESCRIPTION="${ARGS[*]}"
if [ -z "$FEATURE_DESCRIPTION" ]; then
echo "Usage: $0 [--json] <feature_description>" >&2
echo "Usage: $0 [--json] [--feature-name=<name>] <feature_description>" >&2
exit 1
fi

Expand All @@ -31,6 +60,34 @@ find_repo_root() {
return 1
}

# Normalise AI-provided feature or description text into a kebab-case slug limited by an optional word cap.
# Assumes input contains valid alphanumeric characters. WordLimit of 0 removes the cap.
get_feature_slug() {
local raw="$1"
local word_limit="${2:-3}"

# Convert to lowercase and replace non-alphanumeric with dashes
local slug=$(echo "$raw" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//')

# Split into words and apply word limit
if [ "$word_limit" -le 0 ]; then
echo "$slug"
else
local words=()
IFS='-' read -ra ADDR <<< "$slug"
local count=0
for word in "${ADDR[@]}"; do
[ -n "$word" ] || continue
words+=("$word")
count=$((count + 1))
if [ "$count" -ge "$word_limit" ]; then
break
fi
done
IFS='-'; echo "${words[*]}"
fi
}

# 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.
Expand Down Expand Up @@ -67,9 +124,14 @@ fi
NEXT=$((HIGHEST + 1))
FEATURE_NUM=$(printf "%03d" "$NEXT")

BRANCH_NAME=$(echo "$FEATURE_DESCRIPTION" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//')
WORDS=$(echo "$BRANCH_NAME" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//')
BRANCH_NAME="${FEATURE_NUM}-${WORDS}"
# Generate slug for branch naming from AI-provided text: prioritize explicit feature name over description
if [ -n "$FEATURE_NAME" ]; then
SELECTED_SLUG=$(get_feature_slug "$FEATURE_NAME" 0)
else
SELECTED_SLUG=$(get_feature_slug "$FEATURE_DESCRIPTION" 3)
fi

BRANCH_NAME="${FEATURE_NUM}-${SELECTED_SLUG}"

if [ "$HAS_GIT" = true ]; then
git checkout -b "$BRANCH_NAME"
Expand All @@ -88,10 +150,11 @@ if [ -f "$TEMPLATE" ]; then cp "$TEMPLATE" "$SPEC_FILE"; else touch "$SPEC_FILE"
export SPECIFY_FEATURE="$BRANCH_NAME"

if $JSON_MODE; then
printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$BRANCH_NAME" "$SPEC_FILE" "$FEATURE_NUM"
printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s","HAS_GIT":%s}\n' "$BRANCH_NAME" "$SPEC_FILE" "$FEATURE_NUM" "$([ "$HAS_GIT" = true ] && echo 'true' || echo 'false')"
else
echo "BRANCH_NAME: $BRANCH_NAME"
echo "SPEC_FILE: $SPEC_FILE"
echo "FEATURE_NUM: $FEATURE_NUM"
echo "HAS_GIT: $HAS_GIT"
echo "SPECIFY_FEATURE environment variable set to: $BRANCH_NAME"
fi
39 changes: 36 additions & 3 deletions scripts/powershell/create-new-feature.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[CmdletBinding()]
param(
[switch]$Json,
[string]$FeatureName,
[Parameter(ValueFromRemainingArguments = $true)]
[string[]]$FeatureDescription
)
Expand All @@ -13,6 +14,7 @@ if (-not $FeatureDescription -or $FeatureDescription.Count -eq 0) {
exit 1
}
$featureDesc = ($FeatureDescription -join ' ').Trim()
$explicitFeatureName = if ($null -ne $FeatureName) { $FeatureName.Trim() } else { $null }

# Resolve repository root. Prefer git information when available, but fall back
# to searching for repository markers so the workflow still functions in repositories that
Expand All @@ -37,6 +39,31 @@ function Find-RepositoryRoot {
$current = $parent
}
}

# Normalise AI-provided feature or description text into a kebab-case slug limited by an optional word cap.
# Assumes input contains valid alphanumeric characters. WordLimit of 0 removes the cap.
function Get-FeatureSlug {
param(
[string]$Raw,
[int]$WordLimit = 3
)

$slug = $Raw.ToLower()
$slug = $slug -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', ''

$parts = ($slug -split '-') | Where-Object { $_ }

if ($WordLimit -le 0) {
return [string]::Join('-', $parts)
}

if ($parts.Count -gt $WordLimit) {
$parts = $parts[0..($WordLimit - 1)]
}

return [string]::Join('-', $parts)
}

$fallbackRoot = (Find-RepositoryRoot -StartDir $PSScriptRoot)
if (-not $fallbackRoot) {
Write-Error "Error: Could not determine repository root. Please run this script from within the repository."
Expand Down Expand Up @@ -72,9 +99,15 @@ if (Test-Path $specsDir) {
$next = $highest + 1
$featureNum = ('{0:000}' -f $next)

$branchName = $featureDesc.ToLower() -replace '[^a-z0-9]', '-' -replace '-{2,}', '-' -replace '^-', '' -replace '-$', ''
$words = ($branchName -split '-') | Where-Object { $_ } | Select-Object -First 3
$branchName = "$featureNum-$([string]::Join('-', $words))"
# Generate slug for branch naming from AI-provided text: prioritize explicit feature name over description
if (-not [string]::IsNullOrEmpty($explicitFeatureName)) {
$selectedSlug = Get-FeatureSlug -Raw $explicitFeatureName -WordLimit 0
}
else {
$selectedSlug = Get-FeatureSlug -Raw $featureDesc -WordLimit 3
}

$branchName = "$featureNum-$selectedSlug"

if ($hasGit) {
try {
Expand Down
19 changes: 13 additions & 6 deletions templates/commands/specify.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,21 @@ User input:

$ARGUMENTS

The text the user typed after `/specify` in the triggering message **is** the feature description. Assume you always have it available in this conversation even if `{ARGS}` appears literally below. Do not ask the user to repeat it unless they provided an empty command.
The text the user typed after `/specify` in the triggering message **is** the feature description. Assume you always have it available in this conversation even if `$ARGUMENTS` appears literally below. Do not ask the user to repeat it unless they provided an empty command.

Given that feature description, do this:

1. Run the script `{SCRIPT}` from repo root and parse its JSON output for BRANCH_NAME and SPEC_FILE. All file paths must be absolute.
**IMPORTANT** You must only ever run this script once. The JSON is provided in the terminal as output - always refer to it to get the actual content you're looking for.
2. Load `templates/spec-template.md` to understand required sections.
3. Write the specification to SPEC_FILE using the template structure, replacing placeholders with concrete details derived from the feature description (arguments) while preserving section order and headings.
4. Report completion with branch name, spec file path, and readiness for the next phase.
1. Inspect the user input for an explicit feature name directive. Treat any line matching (case-insensitive) `feature-name:`, `feature name:`, `branch-name:`, or `branch name:` as providing the feature name; trim the value. Remove that directive line from the text you consider the feature description. If the trimmed value is empty, treat it as though no directive was supplied.
2. If no explicit name was provided, synthesize a concise feature name from the description before continuing:
- Summarize the feature into a short 3–5 word phrase (Title Case, human readable).
- Avoid punctuation except spaces and basic hyphens.
- Use that generated phrase when passing `-FeatureName` in the next step.
3. From repo root, run the script `.specify/scripts/powershell/create-new-feature.ps1 -Json` **exactly once**:
- Pass the cleaned feature description as positional arguments.
- Always include `-FeatureName "<value>"` where `<value>` is either the explicit directive or the synthesized name.
- Parse the JSON output for `BRANCH_NAME` and `SPEC_FILE`. All file paths must be absolute.
4. Load `.specify/templates/spec-template.md` to understand required sections.
5. Write the specification to SPEC_FILE using the template structure, replacing placeholders with concrete details derived from the feature description while preserving section order and headings.
6. Report completion with branch name, spec file path, and readiness for the next phase.

Note: The script creates and checks out the new branch and initializes the spec file before writing.