Skip to content

Commit f494a8e

Browse files
authored
Support namespaced git feature branch templates (#3293)
* test: cover namespaced git branch templates Assisted-by: Codex (model: GPT-5, autonomous) * feat: support namespaced git branch templates Assisted-by: Codex (model: GPT-5, autonomous) * test: cover git branch template edge cases Assisted-by: Codex (model: GPT-5, autonomous) * fix: harden git branch template parsing Assisted-by: Codex (model: GPT-5, autonomous) * fix: address git branch template review feedback Address Copilot review feedback for branch_prefix help text, namespaced GIT_BRANCH_NAME fallback behavior, final-segment validation docs, and Bash UTF-8 byte reporting. Assisted-by: Codex (model: GPT-5, autonomous) * fix: reject slug-scoped branch templates Reject branch templates that place {slug} before {number}, because that makes namespace scanning depend on the generated feature slug and can reset numbering per feature name. Assisted-by: Codex (model: GPT-5, autonomous) * fix: ignore malformed timestamp refs when numbering Align branch-number scanning with feature-branch validation so malformed timestamp-looking refs do not inflate sequential numbering. Also updates the stale git-common comment called out in review. Assisted-by: Codex (model: GPT-5, autonomous)
1 parent 92b7cf7 commit f494a8e

11 files changed

Lines changed: 868 additions & 90 deletions

File tree

extensions/git/README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Git repository initialization, feature branch creation, numbering (sequential/ti
77
This extension provides Git operations as an optional, self-contained module. It manages:
88

99
- **Repository initialization** with configurable commit messages
10-
- **Feature branch creation** with sequential (`001-feature-name`) or timestamp (`20260319-143022-feature-name`) numbering
10+
- **Feature branch creation** with sequential (`001-feature-name`) or timestamp (`20260319-143022-feature-name`) numbering and optional templates for branch namespaces
1111
- **Branch validation** to ensure branches follow naming conventions
1212
- **Git remote detection** for GitHub integration (e.g., issue creation)
1313
- **Auto-commit** after core commands (configurable per-command with custom messages)
@@ -53,6 +53,16 @@ Configuration is stored in `.specify/extensions/git/git-config.yml`:
5353
# Branch numbering strategy: "sequential" or "timestamp"
5454
branch_numbering: sequential
5555

56+
# Optional branch name template. Leave empty for the default "{number}-{slug}".
57+
# Supported tokens: {author}, {app}, {number}, {slug}; {slug} must not appear
58+
# before {number}, and the final path segment must start with {number}-.
59+
# Example for monorepos: "{author}/{app}/{number}-{slug}"
60+
branch_template: ""
61+
62+
# Optional shorthand namespace. Leave empty to use branch_template/default behavior.
63+
# Example: "features/{app}" expands to "features/{app}/{number}-{slug}"
64+
branch_prefix: ""
65+
5666
# Custom commit message for git init
5767
init_commit_message: "[Spec Kit] Initial commit"
5868

@@ -65,6 +75,10 @@ auto_commit:
6575
message: "[Spec Kit] Add specification"
6676
```
6777
78+
`{author}` is derived from Git config and sanitized for branch names. `{app}` is derived from the Spec Kit init directory name. Custom templates must not put `{slug}` before `{number}`, and must put `{number}-` at the start of the final path segment so generated names remain valid feature branches. For a monorepo project at `apps/web/.specify/`, a template such as `{author}/{app}/{number}-{slug}` produces branches like `jdoe/web/008-guided-tour`.
79+
80+
For simple namespace-only customization, `branch_prefix` is also accepted as a shorthand and expands to `<branch_prefix>/{number}-{slug}`.
81+
6882
## Installation
6983

7084
```bash

extensions/git/commands/speckit.git.feature.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ You **MUST** consider the user input before proceeding (if not empty).
1919
If the user explicitly provided `GIT_BRANCH_NAME` (e.g., via environment variable, argument, or in their request), pass it through to the script by setting the `GIT_BRANCH_NAME` environment variable before invoking the script. When `GIT_BRANCH_NAME` is set:
2020
- The script uses the exact value as the branch name, bypassing all prefix/suffix generation
2121
- `--short-name`, `--number`, and `--timestamp` flags are ignored
22-
- `FEATURE_NUM` is extracted from the name if it starts with a numeric prefix, otherwise set to the full branch name
22+
- `FEATURE_NUM` is extracted when the final path segment starts with a numeric or timestamp feature marker (for example `042-name`, `feat/042-name`, or `jdoe/app/042-name`), otherwise set to the full branch name
2323

2424
## Prerequisites
2525

@@ -35,6 +35,19 @@ Determine the branch numbering strategy by checking configuration in this order:
3535
3. Check `.specify/init-options.json` for `branch_numbering` value (deprecated, backward compatibility — will be removed in a future release)
3636
4. Default to `sequential` if none of the above exist
3737

38+
## Branch Name Template
39+
40+
Check `.specify/extensions/git/git-config.yml` for an optional `branch_template` value. If it is empty or missing, use the default branch shape `{number}-{slug}`. If it is set, `{slug}` must not appear before `{number}`, its final path segment must start with `{number}-`, and the script expands these tokens:
41+
42+
- `{author}`: sanitized Git config author (`user.name`, falling back to the email local part)
43+
- `{app}`: sanitized Spec Kit init directory name
44+
- `{number}`: sequential number or timestamp
45+
- `{slug}`: generated short branch slug
46+
47+
For monorepos, a template such as `{author}/{app}/{number}-{slug}` creates names like `jdoe/web/008-guided-tour` while preserving per-project feature numbering.
48+
49+
The script also accepts `branch_prefix` as a shorthand for simple namespaces; it expands to `<branch_prefix>/{number}-{slug}`.
50+
3851
## Execution
3952

4053
Generate a concise short name (2-4 words) for the branch:
@@ -54,6 +67,7 @@ Run the appropriate script based on your platform:
5467
- Always include the JSON flag (`--json` for Bash, `-Json` for PowerShell) so the output can be parsed reliably
5568
- You must only ever run this script once per feature
5669
- The JSON output will contain `BRANCH_NAME` and `FEATURE_NUM`
70+
- Do not manually expand `branch_template`; the script reads the git extension config and applies it consistently
5771

5872
## Graceful Degradation
5973

@@ -64,5 +78,5 @@ If Git is not installed or the current directory is not a Git repository:
6478
## Output
6579

6680
The script outputs JSON with:
67-
- `BRANCH_NAME`: The branch name (e.g., `003-user-auth` or `20260319-143022-user-auth`)
81+
- `BRANCH_NAME`: The branch name (e.g., `003-user-auth`, `20260319-143022-user-auth`, or `jdoe/web/003-user-auth`)
6882
- `FEATURE_NUM`: The numeric or timestamp prefix used

extensions/git/commands/speckit.git.validate.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,24 @@ Get the current branch name:
2222
git rev-parse --abbrev-ref HEAD
2323
```
2424

25-
The branch name must match one of these patterns:
25+
The branch name's final path segment must start with one of these feature markers:
2626

27-
1. **Sequential**: `^[0-9]{3,}-` (e.g., `001-feature-name`, `042-fix-bug`, `1000-big-feature`)
28-
2. **Timestamp**: `^[0-9]{8}-[0-9]{6}-` (e.g., `20260319-143022-feature-name`)
27+
1. **Sequential**: `[0-9]{3,}-` (e.g., `001-feature-name`, `042-fix-bug`, `1000-big-feature`, `jdoe/web/008-guided-tour`)
28+
2. **Timestamp**: `[0-9]{8}-[0-9]{6}-` (e.g., `20260319-143022-feature-name`, `jdoe/web/20260319-143022-feature-name`)
2929

3030
## Execution
3131

3232
If on a feature branch (matches either pattern):
3333
- Output: `✓ On feature branch: <branch-name>`
3434
- Check if the corresponding spec directory exists under `specs/`:
35-
- For sequential branches, look for `specs/<prefix>-*` where prefix matches the numeric portion
36-
- For timestamp branches, look for `specs/<prefix>-*` where prefix matches the `YYYYMMDD-HHMMSS` portion
35+
- For sequential branches, look for `specs/<prefix>-*` where prefix matches the numeric portion, regardless of branch namespace prefixes
36+
- For timestamp branches, look for `specs/<prefix>-*` where prefix matches the `YYYYMMDD-HHMMSS` portion, regardless of branch namespace prefixes
3737
- If spec directory exists: `✓ Spec directory found: <path>`
3838
- If spec directory missing: `⚠ No spec directory found for prefix <prefix>`
3939

4040
If NOT on a feature branch:
4141
- Output: `✗ Not on a feature branch. Current branch: <branch-name>`
42-
- Output: `Feature branches should be named like: 001-feature-name or 20260319-143022-feature-name`
42+
- Output: `Feature branches should be named like: 001-feature-name, 20260319-143022-feature-name, or <namespace>/001-feature-name`
4343

4444
## Graceful Degradation
4545

extensions/git/config-template.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44
# Branch numbering strategy: "sequential" (001, 002, ...) or "timestamp" (YYYYMMDD-HHMMSS)
55
branch_numbering: sequential
66

7+
# Optional branch name template. Leave empty for the default "{number}-{slug}".
8+
# Supported tokens: {author}, {app}, {number}, {slug}
9+
# {slug} must not appear before {number}; final path segment must start with {number}-.
10+
# Example for monorepos: "{author}/{app}/{number}-{slug}"
11+
branch_template: ""
12+
13+
# Optional shorthand namespace. Leave empty to use branch_template/default behavior.
14+
# Example: "features/{app}" expands to "features/{app}/{number}-{slug}"
15+
branch_prefix: ""
16+
717
# Commit message used by `git commit` during repository initialization
818
init_commit_message: "[Spec Kit] Initial commit"
919

extensions/git/extension.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ extension:
44
id: git
55
name: "Git Branching Workflow"
66
version: "1.0.0"
7-
description: "Feature branch creation, numbering (sequential/timestamp), validation, and Git remote detection"
7+
description: "Feature branch creation, numbering (sequential/timestamp), templating, validation, and Git remote detection"
88
author: spec-kit-core
99
repository: https://github.com/github/spec-kit
1010
license: MIT
@@ -19,7 +19,7 @@ provides:
1919
commands:
2020
- name: speckit.git.feature
2121
file: commands/speckit.git.feature.md
22-
description: "Create a feature branch with sequential or timestamp numbering"
22+
description: "Create a feature branch with sequential or timestamp numbering and optional templates"
2323
- name: speckit.git.validate
2424
file: commands/speckit.git.validate.md
2525
description: "Validate current branch follows feature branch naming conventions"
@@ -137,4 +137,6 @@ tags:
137137
config:
138138
defaults:
139139
branch_numbering: sequential
140+
branch_template: ""
141+
branch_prefix: ""
140142
init_commit_message: "[Spec Kit] Initial commit"

extensions/git/git-config.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44
# Branch numbering strategy: "sequential" (001, 002, ...) or "timestamp" (YYYYMMDD-HHMMSS)
55
branch_numbering: sequential
66

7+
# Optional branch name template. Leave empty for the default "{number}-{slug}".
8+
# Supported tokens: {author}, {app}, {number}, {slug}
9+
# {slug} must not appear before {number}; final path segment must start with {number}-.
10+
# Example for monorepos: "{author}/{app}/{number}-{slug}"
11+
branch_template: ""
12+
13+
# Optional shorthand namespace. Leave empty to use branch_template/default behavior.
14+
# Example: "features/{app}" expands to "features/{app}/{number}-{slug}"
15+
branch_prefix: ""
16+
717
# Commit message used by `git commit` during repository initialization
818
init_commit_message: "[Spec Kit] Initial commit"
919

0 commit comments

Comments
 (0)