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
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
92 changes: 92 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.<br/>**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 `--spec-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 --spec-number 42
/speckit.specify Fix payment bug for issue #123 --spec-number 123
/speckit.specify Implement search API --spec-number 1234 --branch-prefix feature/
```

**Spec number priority:**

1. `--spec-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

Expand Down Expand Up @@ -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
Expand All @@ -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 --spec-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 --spec-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:** `--spec-number <issue-number>` (e.g., `--spec-number 456`)
- **Jira Tickets:** `--spec-number <ticket-id>` (e.g., `--spec-number 789` for PROJ-789)
- **Linear Issues:** `--spec-number <issue-id>` (e.g., `--spec-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.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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 = [
Expand Down
154 changes: 137 additions & 17 deletions scripts/bash/create-new-feature.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ set -e

JSON_MODE=false
SHORT_NAME=""
BRANCH_PREFIX_ARG=""
SPEC_NUM_ARG=""
ARGS=()
i=1
while [ $i -le $# ]; do
Expand All @@ -26,17 +28,50 @@ 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"
;;
--spec-number)
if [ $((i + 1)) -gt $# ]; then
echo 'Error: --spec-number 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: --spec-number requires a value' >&2
exit 1
fi
SPEC_NUM_ARG="$next_arg"
;;
--help|-h)
echo "Usage: $0 [--json] [--short-name <name>] <feature_description>"
echo "Usage: $0 [--json] [--short-name <name>] [--branch-prefix <prefix>] [--spec-number <number>] <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 " --help, -h Show this help message"
echo " --json Output in JSON format"
echo " --short-name <name> Provide a custom short name (2-4 words) for the branch"
echo " --branch-prefix <prefix> Override branch prefix (e.g., 'feature/', 'bugfix/')"
echo " --spec-number <number> Specify a custom spec number (e.g., to match issue tracker)"
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'"
echo " $0 'Fix login bug' --branch-prefix 'bugfix/'"
echo " $0 'Add payment processing' --spec-number 42"
echo " $0 'Implement search feature' --spec-number 1234 --branch-prefix 'feature/'"
exit 0
;;
*)
Expand Down Expand Up @@ -87,19 +122,96 @@ cd "$REPO_ROOT"
SPECS_DIR="$REPO_ROOT/specs"
mkdir -p "$SPECS_DIR"

HIGHEST=0
if [ -d "$SPECS_DIR" ]; then
for dir in "$SPECS_DIR"/*; do
[ -d "$dir" ] || continue
dirname=$(basename "$dir")
number=$(echo "$dirname" | grep -o '^[0-9]\+' || echo "0")
number=$((10#$number))
if [ "$number" -gt "$HIGHEST" ]; then HIGHEST=$number; fi
done
fi
# Function to get spec number from various sources
get_spec_number() {
# Priority: 1. Command-line argument, 2. Environment variable, 3. Auto-increment
if [ -n "$SPEC_NUM_ARG" ]; then
# Validate it's a positive integer
if ! [[ "$SPEC_NUM_ARG" =~ ^[0-9]+$ ]]; then
echo "Error: --spec-number must be a positive integer" >&2
Comment on lines +130 to +131
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

The bash validation allows zero as a valid spec number (regex ^[0-9]+$ matches "0"), but this could create ambiguous directory names like 000-feature-name. The PowerShell version has the same issue. Both scripts should reject zero to ensure spec numbers start from 1.

Suggested change
if ! [[ "$SPEC_NUM_ARG" =~ ^[0-9]+$ ]]; then
echo "Error: --spec-number must be a positive integer" >&2
if ! [[ "$SPEC_NUM_ARG" =~ ^[1-9][0-9]*$ ]]; then
echo "Error: --spec-number must be a positive integer greater than zero" >&2

Copilot uses AI. Check for mistakes.
exit 1
fi

# Pad to at least 3 digits
if [ "$SPEC_NUM_ARG" -lt 100 ]; then
printf "%03d" "$SPEC_NUM_ARG"
else
echo "$SPEC_NUM_ARG"
fi
return
fi

if [ -n "$SPECIFY_SPEC_NUMBER" ]; then
# Validate it's a positive integer
if ! [[ "$SPECIFY_SPEC_NUMBER" =~ ^[0-9]+$ ]]; then
echo "Error: SPECIFY_SPEC_NUMBER must be a positive integer" >&2
exit 1
fi

# Pad to at least 3 digits
if [ "$SPECIFY_SPEC_NUMBER" -lt 100 ]; then
printf "%03d" "$SPECIFY_SPEC_NUMBER"
else
echo "$SPECIFY_SPEC_NUMBER"
fi
return
fi

# Auto-increment: find highest existing number
local highest=0
if [ -d "$SPECS_DIR" ]; then
for dir in "$SPECS_DIR"/*; do
[ -d "$dir" ] || continue
local dirname=$(basename "$dir")
local number=$(echo "$dirname" | grep -o '^[0-9]\+' || echo "0")
number=$((10#$number))
if [ "$number" -gt "$highest" ]; then highest=$number; fi
done
fi

local next=$((highest + 1))
printf "%03d" "$next"
}

FEATURE_NUM=$(get_spec_number)

# Check for conflicts with existing spec numbers
for dir in "$SPECS_DIR"/*; do
[ -d "$dir" ] || continue
dirname=$(basename "$dir")
if [[ "$dirname" =~ ^${FEATURE_NUM}- ]]; then
echo "Error: Spec number $FEATURE_NUM already exists in directory: $dirname" >&2
echo "Please choose a different spec number or remove the existing spec." >&2
exit 1
fi
done

NEXT=$((HIGHEST + 1))
FEATURE_NUM=$(printf "%03d" "$NEXT")
# Function to get branch prefix from config or environment variable
get_branch_prefix() {
# Priority: 1. Command-line argument, 2. Environment variable, 3. Config file, 4. Default (empty)
if [ -n "$BRANCH_PREFIX_ARG" ]; then
echo "$BRANCH_PREFIX_ARG"
return
fi

if [ -n "$SPECIFY_BRANCH_PREFIX" ]; then
echo "$SPECIFY_BRANCH_PREFIX"
return
fi

local config_file="$REPO_ROOT/.specify/config.json"
if [ -f "$config_file" ]; then
# Use grep and sed to extract the prefix value from JSON
# This avoids requiring jq to be installed
local prefix=$(grep -o '"prefix"[[:space:]]*:[[:space:]]*"[^"]*"' "$config_file" | sed 's/.*"prefix"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
Comment on lines +204 to +206
Copy link

Copilot AI Oct 22, 2025

Choose a reason for hiding this comment

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

The JSON parsing logic fails when the prefix value contains escaped quotes or special characters. For example, a prefix value of "test\"value" would break the regex pattern. While unlikely in typical branch prefix usage, consider adding validation or a comment warning about this limitation.

Suggested change
# Use grep and sed to extract the prefix value from JSON
# This avoids requiring jq to be installed
local prefix=$(grep -o '"prefix"[[:space:]]*:[[:space:]]*"[^"]*"' "$config_file" | sed 's/.*"prefix"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
# Use jq to extract the prefix value from JSON. jq correctly handles escaped quotes and special characters.
# If jq is not available, fall back to grep/sed but warn about limitations.
local prefix=""
if command -v jq >/dev/null 2>&1; then
prefix=$(jq -r '.prefix // empty' "$config_file")
else
echo "Warning: jq not found. Falling back to regex-based JSON parsing, which may fail for escaped quotes or special characters in the prefix value." >&2
# Fallback: regex-based extraction (does not handle escaped quotes/special chars)
prefix=$(grep -o '"prefix"[[:space:]]*:[[:space:]]*"[^"]*"' "$config_file" | sed 's/.*"prefix"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
fi

Copilot uses AI. Check for mistakes.
if [ -n "$prefix" ]; then
echo "$prefix"
return
fi
fi

echo ""
}

# Function to generate branch name with stop word filtering and length filtering
generate_branch_name() {
Expand Down Expand Up @@ -157,7 +269,15 @@ else
BRANCH_SUFFIX=$(generate_branch_name "$FEATURE_DESCRIPTION")
fi

BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}"
# Get branch prefix from config or environment
BRANCH_PREFIX=$(get_branch_prefix)

# Construct full branch name with optional prefix
if [ -n "$BRANCH_PREFIX" ]; then
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
Expand Down
Loading