diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea6729145..371dcb8ce 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,27 @@ All notable changes to the Specify CLI and templates are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [Unreleased]
+
+## [0.0.21] - 2025-10-22
+
+### Added
+
+- **Configurable Branch Prefixes**: Branch names can now be prefixed with custom patterns (e.g., `feature/`, `bugfix/`)
+ - New `.specify/config.json` configuration file with `branch.prefix` setting
+ - Environment variable `SPECIFY_BRANCH_PREFIX` for per-session overrides
+ - **Per-feature override**: `--branch-prefix` / `-BranchPrefix` parameter for `create-new-feature` scripts
+ - Priority order: Command-line parameter > Environment variable > Config file > Default (no prefix)
+ - Automatically created during project initialization via `specify init`
+ - Examples:
+ - With `"prefix": "feature/"`: `001-user-auth` → `feature/001-user-auth`
+ - With `"prefix": "bugfix/"`: `001-fix-login` → `bugfix/001-fix-login`
+ - With `SPECIFY_BRANCH_PREFIX=dev/`: Overrides config file setting
+ - With `--branch-prefix "hotfix/"`: Overrides all other settings for that feature
+ - Supported in both bash and PowerShell variants of `create-new-feature` scripts
+ - AI agents can recognize and apply prefix from `/speckit.specify` command (e.g., `/speckit.specify Add login --branch-prefix feature/`)
+ - Python CLI includes `setup_config_file()` function to initialize configuration during project setup
+
## [0.0.20] - 2025-10-14
### Added
diff --git a/README.md b/README.md
index 1c7dda215..4ccc44648 100644
--- a/README.md
+++ b/README.md
@@ -252,6 +252,70 @@ Additional commands for enhanced quality and validation:
| Variable | Description |
|------------------|------------------------------------------------------------------------------------------------|
| `SPECIFY_FEATURE` | Override feature detection for non-Git repositories. Set to the feature directory name (e.g., `001-photo-albums`) to work on a specific feature when not using Git branches.
**Must be set in the context of the agent you're working with prior to using `/speckit.plan` or follow-up commands. |
+| `SPECIFY_BRANCH_PREFIX` | Configure a prefix for git branch names (e.g., `feature/`, `bugfix/`). When set, this prefix is prepended to auto-generated branch names. Overrides the `branch.prefix` setting in `.specify/config.json`. Example: With prefix `feature/`, branch `001-user-auth` becomes `feature/001-user-auth`. |
+| `SPECIFY_SPEC_NUMBER` | Override the auto-incremented spec number with a custom value (e.g., to match an issue tracker number). When set, the specified number is used instead of finding the next available number. Example: `SPECIFY_SPEC_NUMBER=42` creates spec `042-feature-name`. Can be overridden per-feature using the `--number` parameter in `/speckit.specify`. |
+
+### Configuration File
+
+The `.specify/config.json` file allows you to configure project-specific settings. This file is automatically created when you initialize a new project with `specify init`.
+
+#### Branch Prefix Configuration
+
+You can configure a default branch prefix for your project that will be applied to all auto-generated branch names:
+
+```json
+{
+ "branch": {
+ "prefix": "feature/"
+ }
+}
+```
+
+**Common patterns:**
+
+- **Feature branches:** `"prefix": "feature/"` → Creates branches like `feature/001-user-auth`
+- **Bugfix branches:** `"prefix": "bugfix/"` → Creates branches like `bugfix/001-fix-login`
+- **Development branches:** `"prefix": "dev/"` → Creates branches like `dev/001-new-api`
+- **No prefix (default):** `"prefix": ""` → Creates branches like `001-user-auth`
+
+**Priority order:**
+
+1. `--branch-prefix` command-line parameter (highest priority, per-feature override)
+2. `SPECIFY_BRANCH_PREFIX` environment variable (per-session override)
+3. `.specify/config.json` file setting (project-wide default)
+4. Default: no prefix (empty string)
+
+This allows you to set project-wide defaults in the config file, override them per-session using the environment variable, or specify them per-feature when creating a new specification.
+
+**Per-feature branch prefix:**
+
+When using the `/speckit.specify` command, you can specify a branch prefix for that specific feature:
+
+```text
+/speckit.specify Add user authentication --branch-prefix feature/
+/speckit.specify Fix login timeout --branch-prefix bugfix/
+/speckit.specify Update API endpoints with prefix hotfix/
+```
+
+The AI agent will recognize the prefix specification and pass it to the `create-new-feature` script.
+
+**Per-feature spec number:**
+
+You can also specify a custom spec number to match your issue tracker:
+
+```text
+/speckit.specify Add user authentication --number 42
+/speckit.specify Fix payment bug for issue #123 --number 123
+/speckit.specify Implement search API --number 1234 --branch-prefix feature/
+```
+
+**Spec number priority:**
+
+1. `--number` command-line parameter (highest priority, per-feature override)
+2. `SPECIFY_SPEC_NUMBER` environment variable (per-session override)
+3. Auto-increment from existing specs (default)
+
+This allows you to align spec numbers with your issue tracker (GitHub Issues, Jira, Linear, etc.) while maintaining the structured workflow.
## 📚 Core Philosophy
@@ -389,6 +453,8 @@ With your project principles established, you can now create the functional spec
>[!IMPORTANT]
>Be as explicit as possible about *what* you are trying to build and *why*. **Do not focus on the tech stack at this point**.
+#### Basic Example
+
An example prompt:
```text
@@ -410,6 +476,32 @@ see yours. You can edit any comments that you make, but you can't edit comments
delete any comments that you made, but you can't delete comments anybody else made.
```
+#### Issue Tracker Integration
+
+You can align spec numbers with your issue tracker (GitHub Issues, Jira, Linear, etc.) by specifying a custom spec number:
+
+```text
+/speckit.specify Add user authentication system --number 42
+```
+
+This creates spec `042-add-user-auth` matching issue #42 in your tracker. You can also combine with branch prefixes:
+
+```text
+/speckit.specify Fix payment processing timeout --number 123 --branch-prefix bugfix/
+```
+
+This creates:
+- Spec directory: `specs/123-fix-payment-timeout/`
+- Git branch: `bugfix/123-fix-payment-timeout`
+
+**Common workflows:**
+
+- **GitHub Issues:** `--number ` (e.g., `--number 456`)
+- **Jira Tickets:** `--number ` (e.g., `--number 789` for PROJ-789)
+- **Linear Issues:** `--number ` (e.g., `--number 1234`)
+
+#### After Running /speckit.specify
+
After this prompt is entered, you should see Claude Code kick off the planning and spec drafting process. Claude Code will also trigger some of the built-in scripts to set up the repository.
Once this step is completed, you should have a new branch created (e.g., `001-create-taskify`), as well as a new specification in the `specs/001-create-taskify` directory.
diff --git a/pyproject.toml b/pyproject.toml
index 567d48cd4..e4d2791bd 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "specify-cli"
-version = "0.0.20"
+version = "0.0.21"
description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
requires-python = ">=3.11"
dependencies = [
diff --git a/scripts/bash/common.sh b/scripts/bash/common.sh
index 6931eccc8..2839e7189 100644
--- a/scripts/bash/common.sh
+++ b/scripts/bash/common.sh
@@ -37,8 +37,9 @@ get_current_branch() {
for dir in "$specs_dir"/*; do
if [[ -d "$dir" ]]; then
local dirname=$(basename "$dir")
- if [[ "$dirname" =~ ^([0-9]{3})- ]]; then
- local number=${BASH_REMATCH[1]}
+ # Support both formats: 001-name or feature/001-name
+ if [[ "$dirname" =~ ^(([a-z]+/)?([0-9]{3,}))- ]]; then
+ local number=${BASH_REMATCH[3]}
number=$((10#$number))
if [[ "$number" -gt "$highest" ]]; then
highest=$number
@@ -72,9 +73,13 @@ check_feature_branch() {
return 0
fi
- if [[ ! "$branch" =~ ^[0-9]{3}- ]]; then
+ # Support both simple format (001-name) and prefixed format (feature/001-name)
+ if [[ ! "$branch" =~ ^([a-z]+/)?[0-9]{3,}- ]]; then
echo "ERROR: Not on a feature branch. Current branch: $branch" >&2
- echo "Feature branches should be named like: 001-feature-name" >&2
+ echo "Feature branches should be named like:" >&2
+ echo " - 001-feature-name" >&2
+ echo " - feature/001-feature-name" >&2
+ echo " - bugfix/042-fix-name" >&2
return 1
fi
@@ -85,26 +90,33 @@ get_feature_dir() { echo "$1/specs/$2"; }
# Find feature directory by numeric prefix instead of exact branch match
# This allows multiple branches to work on the same spec (e.g., 004-fix-bug, 004-add-feature)
+# Also handles branch names with prefixes like feature/004-name or bugfix/042-fix
find_feature_dir_by_prefix() {
local repo_root="$1"
local branch_name="$2"
local specs_dir="$repo_root/specs"
- # Extract numeric prefix from branch (e.g., "004" from "004-whatever")
- if [[ ! "$branch_name" =~ ^([0-9]{3})- ]]; then
+ # Extract numeric prefix from branch (e.g., "004" from "004-whatever" or "feature/004-whatever")
+ # Pattern: optional prefix (feature/, bugfix/, etc.) followed by at least 3 digits
+ if [[ ! "$branch_name" =~ ^(([a-z]+/)?([0-9]{3,}))- ]]; then
# If branch doesn't have numeric prefix, fall back to exact match
echo "$specs_dir/$branch_name"
return
fi
- local prefix="${BASH_REMATCH[1]}"
+ local number="${BASH_REMATCH[3]}" # Just the numeric part
- # Search for directories in specs/ that start with this prefix
+ # Search for directories in specs/ that contain this number
+ # Could be in format: 004-name or feature/004-name or bugfix/004-name
local matches=()
if [[ -d "$specs_dir" ]]; then
- for dir in "$specs_dir"/"$prefix"-*; do
+ for dir in "$specs_dir"/*"$number"-*; do
if [[ -d "$dir" ]]; then
- matches+=("$(basename "$dir")")
+ local dirname=$(basename "$dir")
+ # Verify it actually matches our pattern (not just contains the number)
+ if [[ "$dirname" =~ ^(([a-z]+/)?$number)- ]]; then
+ matches+=("$dirname")
+ fi
fi
done
fi
@@ -118,7 +130,7 @@ find_feature_dir_by_prefix() {
echo "$specs_dir/${matches[0]}"
else
# Multiple matches - this shouldn't happen with proper naming convention
- echo "ERROR: Multiple spec directories found with prefix '$prefix': ${matches[*]}" >&2
+ echo "ERROR: Multiple spec directories found with number '$number': ${matches[*]}" >&2
echo "Please ensure only one spec directory exists per numeric prefix." >&2
echo "$specs_dir/$branch_name" # Return something to avoid breaking the script
fi
diff --git a/scripts/bash/create-new-feature.sh b/scripts/bash/create-new-feature.sh
index 86d9ecf83..43839f931 100644
--- a/scripts/bash/create-new-feature.sh
+++ b/scripts/bash/create-new-feature.sh
@@ -4,6 +4,7 @@ set -e
JSON_MODE=false
SHORT_NAME=""
+BRANCH_PREFIX_ARG=""
BRANCH_NUMBER=""
ARGS=()
i=1
@@ -27,6 +28,20 @@ while [ $i -le $# ]; do
fi
SHORT_NAME="$next_arg"
;;
+ --branch-prefix)
+ if [ $((i + 1)) -gt $# ]; then
+ echo 'Error: --branch-prefix requires a value' >&2
+ exit 1
+ fi
+ i=$((i + 1))
+ next_arg="${!i}"
+ # Check if the next argument is another option (starts with --)
+ if [[ "$next_arg" == --* ]]; then
+ echo 'Error: --branch-prefix requires a value' >&2
+ exit 1
+ fi
+ BRANCH_PREFIX_ARG="$next_arg"
+ ;;
--number)
if [ $((i + 1)) -gt $# ]; then
echo 'Error: --number requires a value' >&2
@@ -41,17 +56,20 @@ while [ $i -le $# ]; do
BRANCH_NUMBER="$next_arg"
;;
--help|-h)
- echo "Usage: $0 [--json] [--short-name ] [--number N] "
+ echo "Usage: $0 [--json] [--short-name ] [--branch-prefix ] [--number N] "
echo ""
echo "Options:"
- echo " --json Output in JSON format"
- echo " --short-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 " --json Output in JSON format"
+ echo " --short-name Provide a custom short name (2-4 words) for the branch"
+ echo " --branch-prefix Override branch prefix (e.g., 'feature/', 'bugfix/')"
+ echo " --number N Specify branch number manually (overrides auto-detection)"
+ echo " --help, -h Show this help message"
echo ""
echo "Examples:"
echo " $0 'Add user authentication system' --short-name 'user-auth'"
echo " $0 'Implement OAuth2 integration for API' --number 5"
+ echo " $0 'Fix login bug' --branch-prefix 'bugfix/'"
+ echo " $0 'Add payment processing' --number 42 --branch-prefix 'feature/'"
exit 0
;;
*)
@@ -211,15 +229,56 @@ if [ -z "$BRANCH_NUMBER" ]; then
fi
FEATURE_NUM=$(printf "%03d" "$BRANCH_NUMBER")
-BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}"
+
+# Function to determine branch prefix
+get_branch_prefix() {
+ # Priority: CLI arg > environment variable > config file > empty string
+ if [ -n "$BRANCH_PREFIX_ARG" ]; then
+ echo "$BRANCH_PREFIX_ARG"
+ return
+ fi
+
+ if [ -n "$SPECIFY_BRANCH_PREFIX" ]; then
+ echo "$SPECIFY_BRANCH_PREFIX"
+ return
+ fi
+
+ # Check config file
+ local config_file="$REPO_ROOT/.specify/config.json"
+ if [ -f "$config_file" ]; then
+ # Extract branch_prefix from config (avoid jq dependency)
+ local branch_prefix=$(grep '"branch_prefix"' "$config_file" | sed 's/.*"branch_prefix"[^"]*"\([^"]*\)".*/\1/')
+ if [ -n "$branch_prefix" ]; then
+ echo "$branch_prefix"
+ return
+ fi
+ fi
+
+ echo ""
+}
+
+# Determine branch prefix
+BRANCH_PREFIX=$(get_branch_prefix)
+
+# Construct branch name with optional prefix
+if [ -n "$BRANCH_PREFIX" ]; then
+ # Ensure prefix ends with /
+ if [[ ! "$BRANCH_PREFIX" =~ /$ ]]; then
+ BRANCH_PREFIX="${BRANCH_PREFIX}/"
+ fi
+ BRANCH_NAME="${BRANCH_PREFIX}${FEATURE_NUM}-${BRANCH_SUFFIX}"
+else
+ BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}"
+fi
# GitHub enforces a 244-byte limit on branch names
# Validate and truncate if necessary
MAX_BRANCH_LENGTH=244
if [ ${#BRANCH_NAME} -gt $MAX_BRANCH_LENGTH ]; then
# Calculate how much we need to trim from suffix
- # Account for: feature number (3) + hyphen (1) = 4 chars
- MAX_SUFFIX_LENGTH=$((MAX_BRANCH_LENGTH - 4))
+ # Account for: prefix length + feature number (3) + hyphen (1)
+ local prefix_length=${#BRANCH_PREFIX}
+ MAX_SUFFIX_LENGTH=$((MAX_BRANCH_LENGTH - prefix_length - 4))
# Truncate suffix at word boundary if possible
TRUNCATED_SUFFIX=$(echo "$BRANCH_SUFFIX" | cut -c1-$MAX_SUFFIX_LENGTH)
@@ -227,7 +286,11 @@ if [ ${#BRANCH_NAME} -gt $MAX_BRANCH_LENGTH ]; then
TRUNCATED_SUFFIX=$(echo "$TRUNCATED_SUFFIX" | sed 's/-$//')
ORIGINAL_BRANCH_NAME="$BRANCH_NAME"
- BRANCH_NAME="${FEATURE_NUM}-${TRUNCATED_SUFFIX}"
+ if [ -n "$BRANCH_PREFIX" ]; then
+ BRANCH_NAME="${BRANCH_PREFIX}${FEATURE_NUM}-${TRUNCATED_SUFFIX}"
+ else
+ BRANCH_NAME="${FEATURE_NUM}-${TRUNCATED_SUFFIX}"
+ fi
>&2 echo "[specify] Warning: Branch name exceeded GitHub's 244-byte limit"
>&2 echo "[specify] Original: $ORIGINAL_BRANCH_NAME (${#ORIGINAL_BRANCH_NAME} bytes)"
diff --git a/scripts/powershell/common.ps1 b/scripts/powershell/common.ps1
index b0be27354..f9631ccf0 100644
--- a/scripts/powershell/common.ps1
+++ b/scripts/powershell/common.ps1
@@ -40,8 +40,9 @@ function Get-CurrentBranch {
$highest = 0
Get-ChildItem -Path $specsDir -Directory | ForEach-Object {
- if ($_.Name -match '^(\d{3})-') {
- $num = [int]$matches[1]
+ # Support both formats: 001-name or feature/001-name
+ if ($_.Name -match '^(([a-z]+/)?(\d{3,}))-') {
+ $num = [int]$matches[3]
if ($num -gt $highest) {
$highest = $num
$latestFeature = $_.Name
@@ -79,9 +80,13 @@ function Test-FeatureBranch {
return $true
}
- if ($Branch -notmatch '^[0-9]{3}-') {
+ # Support both simple format (001-name) and prefixed format (feature/001-name)
+ if ($Branch -notmatch '^([a-z]+/)?[0-9]{3,}-') {
Write-Output "ERROR: Not on a feature branch. Current branch: $Branch"
- Write-Output "Feature branches should be named like: 001-feature-name"
+ Write-Output "Feature branches should be named like:"
+ Write-Output " - 001-feature-name"
+ Write-Output " - feature/001-feature-name"
+ Write-Output " - bugfix/042-fix-name"
return $false
}
return $true
@@ -92,11 +97,61 @@ function Get-FeatureDir {
Join-Path $RepoRoot "specs/$Branch"
}
+# Find feature directory by numeric prefix instead of exact branch match
+# This allows multiple branches to work on the same spec (e.g., 004-fix-bug, 004-add-feature)
+# Also handles branch names with prefixes like feature/004-name or bugfix/042-fix
+function Find-FeatureDirByPrefix {
+ param(
+ [string]$RepoRoot,
+ [string]$BranchName
+ )
+
+ $specsDir = Join-Path $RepoRoot "specs"
+
+ # Extract numeric prefix from branch (e.g., "004" from "004-whatever" or "feature/004-whatever")
+ # Pattern: optional prefix (feature/, bugfix/, etc.) followed by at least 3 digits
+ if ($BranchName -notmatch '^(([a-z]+/)?(\d{3,}))-') {
+ # If branch doesn't have numeric prefix, fall back to exact match
+ return (Join-Path $specsDir $BranchName)
+ }
+
+ $number = $matches[3] # Just the numeric part
+
+ # Search for directories in specs/ that contain this number
+ # Could be in format: 004-name or feature/004-name or bugfix/004-name
+ $matchedDirs = @()
+
+ if (Test-Path $specsDir) {
+ Get-ChildItem -Path $specsDir -Directory | Where-Object {
+ # Check if directory name contains our number and matches the pattern
+ $_.Name -match "^(([a-z]+/)?$number)-"
+ } | ForEach-Object {
+ $matchedDirs += $_.Name
+ }
+ }
+
+ # Handle results
+ if ($matchedDirs.Count -eq 0) {
+ # No match found - return the branch name path (will fail later with clear error)
+ return (Join-Path $specsDir $BranchName)
+ } elseif ($matchedDirs.Count -eq 1) {
+ # Exactly one match - perfect!
+ return (Join-Path $specsDir $matchedDirs[0])
+ } else {
+ # Multiple matches - this shouldn't happen with proper naming convention
+ Write-Warning "ERROR: Multiple spec directories found with number '$number': $($matchedDirs -join ', ')"
+ Write-Warning "Please ensure only one spec directory exists per numeric prefix."
+ return (Join-Path $specsDir $BranchName) # Return something to avoid breaking the script
+ }
+}
+
function Get-FeaturePathsEnv {
$repoRoot = Get-RepoRoot
$currentBranch = Get-CurrentBranch
$hasGit = Test-HasGit
- $featureDir = Get-FeatureDir -RepoRoot $repoRoot -Branch $currentBranch
+
+ # Use prefix-based lookup to support multiple branches per spec and branch prefixes
+ $featureDir = Find-FeatureDirByPrefix -RepoRoot $repoRoot -BranchName $currentBranch
[PSCustomObject]@{
REPO_ROOT = $repoRoot
diff --git a/scripts/powershell/create-new-feature.ps1 b/scripts/powershell/create-new-feature.ps1
index 4daa6d2c0..64aa32111 100644
--- a/scripts/powershell/create-new-feature.ps1
+++ b/scripts/powershell/create-new-feature.ps1
@@ -5,6 +5,7 @@ param(
[switch]$Json,
[string]$ShortName,
[int]$Number = 0,
+ [string]$BranchPrefix,
[switch]$Help,
[Parameter(ValueFromRemainingArguments = $true)]
[string[]]$FeatureDescription
@@ -13,17 +14,23 @@ $ErrorActionPreference = 'Stop'
# Show help if requested
if ($Help) {
- Write-Host "Usage: ./create-new-feature.ps1 [-Json] [-ShortName ] [-Number N] "
+ Write-Host "Usage: ./create-new-feature.ps1 [-Json] [-ShortName ] [-Number N] [-BranchPrefix ] "
Write-Host ""
Write-Host "Options:"
- Write-Host " -Json Output in JSON format"
- Write-Host " -ShortName Provide a custom short name (2-4 words) for the branch"
- Write-Host " -Number N Specify branch number manually (overrides auto-detection)"
- Write-Host " -Help Show this help message"
+ Write-Host " -Json Output in JSON format"
+ Write-Host " -ShortName Provide a custom short name (2-4 words) for the branch"
+ Write-Host " -Number N Specify branch number manually (overrides auto-detection)"
+ Write-Host " -BranchPrefix Specify branch prefix (e.g., 'feature', 'bugfix')"
+ Write-Host " -Help Show this help message"
Write-Host ""
Write-Host "Examples:"
Write-Host " ./create-new-feature.ps1 'Add user authentication system' -ShortName 'user-auth'"
Write-Host " ./create-new-feature.ps1 'Implement OAuth2 integration for API'"
+ Write-Host " ./create-new-feature.ps1 'Fix login bug' -Number 42 -BranchPrefix 'bugfix'"
+ Write-Host ""
+ Write-Host "Environment Variables:"
+ Write-Host " SPECIFY_SPEC_NUMBER Set default spec/branch number"
+ Write-Host " SPECIFY_BRANCH_PREFIX Set default branch prefix"
exit 0
}
@@ -225,15 +232,61 @@ if ($Number -eq 0) {
}
$featureNum = ('{0:000}' -f $Number)
-$branchName = "$featureNum-$branchSuffix"
+
+# Function to determine branch prefix
+function Get-BranchPrefix {
+ param(
+ [string]$BranchPrefixArg,
+ [string]$RepoRoot
+ )
+
+ # Priority: CLI arg > environment variable > config file > empty string
+ if ($BranchPrefixArg) {
+ return $BranchPrefixArg
+ }
+
+ if ($env:SPECIFY_BRANCH_PREFIX) {
+ return $env:SPECIFY_BRANCH_PREFIX
+ }
+
+ # Check config file
+ $configFile = Join-Path $RepoRoot '.specify/config.json'
+ if (Test-Path $configFile) {
+ try {
+ $config = Get-Content $configFile -Raw | ConvertFrom-Json
+ if ($config.branch_prefix) {
+ return $config.branch_prefix
+ }
+ } catch {
+ # Ignore config parsing errors
+ }
+ }
+
+ return ""
+}
+
+# Determine branch prefix
+$branchPrefix = Get-BranchPrefix -BranchPrefixArg $BranchPrefix -RepoRoot $repoRoot
+
+# Construct branch name with optional prefix
+if ($branchPrefix) {
+ # Ensure prefix ends with /
+ if (-not $branchPrefix.EndsWith('/')) {
+ $branchPrefix = "$branchPrefix/"
+ }
+ $branchName = "$branchPrefix$featureNum-$branchSuffix"
+} else {
+ $branchName = "$featureNum-$branchSuffix"
+}
# GitHub enforces a 244-byte limit on branch names
# Validate and truncate if necessary
$maxBranchLength = 244
if ($branchName.Length -gt $maxBranchLength) {
# Calculate how much we need to trim from suffix
- # Account for: feature number (3) + hyphen (1) = 4 chars
- $maxSuffixLength = $maxBranchLength - 4
+ # Account for: prefix length + feature number (3) + hyphen (1)
+ $prefixLength = $branchPrefix.Length
+ $maxSuffixLength = $maxBranchLength - $prefixLength - 4
# Truncate suffix
$truncatedSuffix = $branchSuffix.Substring(0, [Math]::Min($branchSuffix.Length, $maxSuffixLength))
@@ -241,7 +294,11 @@ if ($branchName.Length -gt $maxBranchLength) {
$truncatedSuffix = $truncatedSuffix -replace '-$', ''
$originalBranchName = $branchName
- $branchName = "$featureNum-$truncatedSuffix"
+ if ($branchPrefix) {
+ $branchName = "$branchPrefix$featureNum-$truncatedSuffix"
+ } else {
+ $branchName = "$featureNum-$truncatedSuffix"
+ }
Write-Warning "[specify] Branch name exceeded GitHub's 244-byte limit"
Write-Warning "[specify] Original: $originalBranchName ($($originalBranchName.Length) bytes)"
diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py
index a33a1c61a..547433178 100644
--- a/src/specify_cli/__init__.py
+++ b/src/specify_cli/__init__.py
@@ -818,6 +818,23 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
return project_path
+def setup_config_file(project_path: Path, tracker: StepTracker | None = None) -> None:
+ """Copy config.json from templates to .specify root if it doesn't already exist."""
+ config_template = project_path / ".specify" / "templates" / "config.json"
+ config_dest = project_path / ".specify" / "config.json"
+
+ # Only copy if template exists and destination doesn't exist (preserve user config)
+ if config_template.exists() and not config_dest.exists():
+ try:
+ shutil.copy2(config_template, config_dest)
+ if tracker:
+ tracker.add("config", "Setup configuration file")
+ tracker.complete("config", "created")
+ except Exception as e:
+ if tracker:
+ tracker.add("config", "Setup configuration file")
+ tracker.error("config", str(e))
+
def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None = None) -> None:
"""Ensure POSIX .sh scripts under .specify/scripts (recursively) have execute bits (no-op on Windows)."""
if os.name == "nt":
@@ -1046,6 +1063,7 @@ def init(
download_and_extract_template(project_path, selected_ai, selected_script, here, verbose=False, tracker=tracker, client=local_client, debug=debug, github_token=github_token)
+ setup_config_file(project_path, tracker=tracker)
ensure_executable_scripts(project_path, tracker=tracker)
if not no_git:
diff --git a/templates/commands/specify.md b/templates/commands/specify.md
index 69258a8bf..8a0e18fc0 100644
--- a/templates/commands/specify.md
+++ b/templates/commands/specify.md
@@ -13,6 +13,152 @@ $ARGUMENTS
You **MUST** consider the user input before proceeding (if not empty).
+## Spec Number Option
+
+Users can optionally specify a custom spec number when creating a feature by including it in their command. This is particularly useful for matching issue tracker numbers (GitHub issues, Jira tickets, etc.).
+
+**How to recognize spec number in user input:**
+
+- `--number ` or `-SpecNumber ` format
+- Keywords like "issue #42", "ticket 123", "for issue 1234"
+- Direct number references: "spec 42", "number 99"
+- **Natural language patterns combining prefix and number:**
+
+ **Detection algorithm (use in order):**
+
+ 1. **First, look for adjacent prefix + number** (most common):
+ - Pattern: `[prefix_keyword] [number]` appearing together
+ - Examples: "feature 303", "bugfix 666", "hotfix 42", "fix 123"
+ - If found: Extract both prefix and number, done.
+
+ 2. **If not found, scan the entire input more broadly**:
+ - Search **anywhere** in the input for prefix keywords:
+ - "feature" or "features" → `feature/`
+ - "bugfix" or "bug fix" or "fix" → `bugfix/`
+ - "hotfix" or "hot fix" → `hotfix/`
+ - "chore" → `chore/`
+ - "refactor" or "refactoring" → `refactor/`
+ - Search **anywhere** in the input for number patterns: "#221", "221", "issue 221", "ticket 221", "spec 221", "number 221"
+ - If BOTH prefix keyword AND number found: Combine them
+ - If only number found: Extract just the number (auto-prefix from config)
+ - If only prefix keyword found: Ignore (not enough information)
+
+ 3. **Handle conflicts** (if multiple prefix keywords found):
+ - Use the keyword that appears closest to the number
+ - If equidistant, prefer more specific: "bugfix" > "fix"
+ - If still tied, use first occurrence (left to right)
+
+ **This handles all these patterns:**
+ - "feature 303 add cart" ✓ (adjacent)
+ - "This is feature 221" ✓ (adjacent within sentence)
+ - "For issue #221, make it a feature" ✓ (separated, closest keyword)
+ - "#221 feature" ✓ (separated, number first)
+ - "issue #221" ✓ (just number)
+ - "Add shopping cart feature 303" ✓ (adjacent but later in sentence)
+
+**Examples of user input with spec number:**
+
+- "Add user authentication --number 42" (explicit parameter)
+- "Fix login timeout for issue #123" (extract `123` only)
+- "Implement payment API as spec 1234" (extract `1234` only)
+- "Add search feature --number 99 --branch-prefix feature/" (explicit parameters)
+- "feature 303 add shopping cart" (extract `feature/` and `303` - adjacent pattern)
+- "bugfix 666 fix payment timeout" (extract `bugfix/` and `666` - adjacent pattern)
+- "This is feature 221" (extract `feature/` and `221` - adjacent pattern in sentence)
+- "For issue #221, make it a feature" (extract `feature/` and `221` - separated, keyword closest to number)
+- "Add hotfix 42 for critical bug" (extract `hotfix/` and `42` - adjacent pattern)
+- "#999 chore cleanup old files" (extract `chore/` and `999` - number first, then keyword)
+
+**If spec number is specified:**
+
+1. **Scan and extract** using the detection algorithm above:
+ - Look for adjacent patterns first (e.g., "feature 303")
+ - If not found, scan entire input for separated keywords and numbers
+ - Extract both prefix type and number if found together
+
+2. **Process extracted values:**
+ - Normalize "fix" to "bugfix/" for consistency
+ - Normalize "bug fix" to "bugfix/" for consistency
+ - Normalize "hot fix" to "hotfix/" for consistency
+ - Add trailing slash to create proper prefix (e.g., "feature" → "feature/")
+ - Validate the number is a positive integer
+
+3. **Clean the feature description:**
+ - Remove the spec number from the description (e.g., "221" or "#221" or "issue 221")
+ - Remove the prefix keyword if it was used as a branch type indicator (e.g., remove "feature" from "This is feature 221" to get "This is")
+ - Clean up any resulting double spaces or hanging prepositions
+
+4. **Pass to script:**
+ - Bash: `--number 42` and optionally `--branch-prefix "feature/"`
+ - PowerShell: `-SpecNumber 42` and optionally `-BranchPrefix "feature/"`
+
+**If no spec number is specified:** The script will auto-increment from the highest existing spec number (default behavior).
+
+**Priority order:**
+1. `--number` CLI parameter (highest priority)
+2. `SPECIFY_SPEC_NUMBER` environment variable
+3. Auto-increment (default)
+
+**Recognized prefix types for natural language patterns:**
+- `feature` or `features` → `feature/`
+- `bugfix` or `bug fix` → `bugfix/`
+- `fix` → `bugfix/` (normalized, lower priority if "bugfix" also present)
+- `hotfix` or `hot fix` → `hotfix/`
+- `chore` → `chore/`
+- `refactor` or `refactoring` → `refactor/`
+
+**Key principle:** Scan the ENTIRE user input for these keywords and numbers. They don't need to be adjacent or in any particular order. The algorithm will find them wherever they appear.
+
+## Branch Prefix Option
+
+Users can optionally specify a branch prefix when creating a feature by including it in their command. Look for these patterns in the user input:
+
+- `--branch-prefix ` or `-BranchPrefix ` format
+- Keywords like "use prefix", "with prefix", "as a feature branch", "as a bugfix", etc.
+- **Natural language patterns** (also extracts spec number if present):
+ - Scan the entire input for prefix keywords: "feature", "bugfix", "hotfix", "chore", "refactor"
+ - These keywords can appear anywhere in the sentence, not just adjacent to a number
+ - Examples:
+ - "feature 303" → prefix `feature/` and number `303` (adjacent)
+ - "This is feature 221" → prefix `feature/` and number `221` (adjacent in sentence)
+ - "bugfix 666 fix timeout" → prefix `bugfix/` and number `666` (adjacent)
+ - "For issue #42, make it a hotfix" → prefix `hotfix/` and number `42` (separated)
+ - "#999 chore task" → prefix `chore/` and number `999` (number first)
+
+ **Key:** The reference to prefix and number may come anywhere in the prompt - scan the entire input.
+
+**Common prefix patterns:**
+
+- `feature/` - For feature branches
+- `bugfix/` or `fix/` - For bug fixes
+- `hotfix/` - For urgent production fixes
+- `refactor/` - For refactoring work
+- `chore/` - For maintenance tasks
+
+**Examples of user input with branch prefix:**
+
+- "Add user authentication --branch-prefix feature/" (explicit parameter)
+- "Fix login timeout as a bugfix" (infer `bugfix/` prefix from keyword)
+- "Update payment API with prefix hotfix/" (explicit mention of prefix)
+- "feature 303 implement shopping cart" (extract `feature/` and `303` - adjacent)
+- "This is feature 221 for auth" (extract `feature/` and `221` - adjacent in sentence)
+- "bugfix 666 resolve payment issue" (extract `bugfix/` and `666` - adjacent)
+- "For issue #42, create hotfix branch" (extract `hotfix/` and `42` - separated)
+- "Make #100 a chore task" (extract `chore/` and `100` - separated)
+
+**If branch prefix is specified:**
+
+1. Extract the prefix from the user input
+2. **If using natural language pattern** (e.g., "feature 303"):
+ - The spec number will also be extracted (see "Spec Number Option" above)
+ - Both prefix and number are removed from the feature description before processing
+3. Remove the prefix specification from the feature description before processing
+4. Pass the prefix to the script using the appropriate parameter:
+ - Bash: `--branch-prefix "prefix-value"`
+ - PowerShell: `-BranchPrefix "prefix-value"`
+
+**If no prefix is specified:** The script will use the default from configuration (`.specify/config.json`) or environment variable.
+
## Outline
The text the user typed after `/speckit.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.
@@ -51,16 +197,35 @@ Given that feature description, do this:
d. Run the script `{SCRIPT}` with the calculated number and short-name:
- Pass `--number N+1` and `--short-name "your-short-name"` along with the feature description
- Bash example: `{SCRIPT} --json --number 5 --short-name "user-auth" "Add user authentication"`
- - PowerShell example: `{SCRIPT} -Json -Number 5 -ShortName "user-auth" "Add user authentication"`
-
+ - PowerShell example: `{SCRIPT} -Json -Number 5 -ShortName "user-auth" "Add user authentication"`
+
**IMPORTANT**:
+
- Check all three sources (remote branches, local branches, specs directories) to find the highest number
- Only match branches/directories with the exact short-name pattern
- If no existing branches/directories found with this short-name, start with number 1
- - You must only ever run this script once per feature
+ - Append the short-name argument to the `{SCRIPT}` command with the 2-4 word short name you created in step 1. Keep the feature description as the final argument.
+ - If a spec number was specified (see "Spec Number Option" above), include it as a parameter
+ - If a branch prefix was specified (see "Branch Prefix Option" above), include it as a parameter
+ - **Note:** Natural language patterns like "feature 303" or "bugfix 666" provide BOTH prefix and number - extract and pass both parameters
+ - Bash examples:
+ - `--short-name "your-generated-short-name" "Feature description here"`
+ - `--short-name "user-auth" "Add user authentication"`
+ - `--number 42 --short-name "payment-api" "Add payment processing"`
+ - `--number 1234 --short-name "user-auth" --branch-prefix "feature/" "Add user authentication"`
+ - `--number 303 --branch-prefix "feature/" --short-name "shopping-cart" "Add shopping cart"` (from "feature 303 add shopping cart")
+ - `--number 666 --branch-prefix "bugfix/" --short-name "payment-timeout" "Fix payment timeout"` (from "bugfix 666 fix payment timeout")
+ - PowerShell examples:
+ - `-ShortName "your-generated-short-name" "Feature description here"`
+ - `-ShortName "user-auth" "Add user authentication"`
+ - `-SpecNumber 42 -ShortName "payment-api" "Add payment processing"`
+ - `-SpecNumber 1234 -ShortName "user-auth" -BranchPrefix "feature/" "Add user authentication"`
+ - `-SpecNumber 303 -BranchPrefix "feature/" -ShortName "shopping-cart" "Add shopping cart"` (from "feature 303 add shopping cart")
+ - `-SpecNumber 666 -BranchPrefix "bugfix/" -ShortName "payment-timeout" "Fix payment timeout"` (from "bugfix 666 fix payment timeout")
- The JSON is provided in the terminal as output - always refer to it to get the actual content you're looking for
- The JSON output will contain BRANCH_NAME and SPEC_FILE paths
- For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot")
+ - You must only ever run this script once per feature
3. Load `templates/spec-template.md` to understand required sections.
diff --git a/templates/config.json b/templates/config.json
new file mode 100644
index 000000000..a0da0c0a4
--- /dev/null
+++ b/templates/config.json
@@ -0,0 +1,5 @@
+{
+ "branch": {
+ "prefix": ""
+ }
+}