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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ To upgrade Specify, see the [Upgrade Guide](./docs/upgrade.md) for detailed inst
uv tool install specify-cli --force --from git+https://github.com/github/spec-kit.git
```

**Note**: To use custom templates from your own fork, install from your fork URL:

```bash
uv tool install specify-cli --from git+https://github.com/gouzhuang/spec-kit.git
```

#### Option 2: One-time Usage

Run directly without installing:
Expand Down Expand Up @@ -188,6 +194,7 @@ The `specify` command supports the following options:
| `--skip-tls` | Flag | Skip SSL/TLS verification (not recommended) |
| `--debug` | Flag | Enable detailed debug output for troubleshooting |
| `--github-token` | Option | GitHub token for API requests (or set GH_TOKEN/GITHUB_TOKEN env variable) |
| `--template-repo` | Option | Custom template repository in `owner/repo` format (e.g., `gouzhuang/spec-kit`) |

### Examples

Expand Down Expand Up @@ -238,6 +245,9 @@ specify init my-project --ai claude --debug
# Use GitHub token for API requests (helpful for corporate environments)
specify init my-project --ai claude --github-token ghp_your_token_here

# Use custom template repository (for forks or private deployments)
specify init my-project --ai claude --template-repo gouzhuang/spec-kit

# Check system requirements
specify check
```
Expand Down
41 changes: 35 additions & 6 deletions src/specify_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -634,9 +634,32 @@ def deep_merge(base: dict, update: dict) -> dict:

return merged

def download_template_from_github(ai_assistant: str, download_dir: Path, *, script_type: str = "sh", verbose: bool = True, show_progress: bool = True, client: httpx.Client = None, debug: bool = False, github_token: str = None) -> Tuple[Path, dict]:
repo_owner = "github"
repo_name = "spec-kit"
def download_template_from_github(ai_assistant: str, download_dir: Path, *, script_type: str = "sh", verbose: bool = True, show_progress: bool = True, client: httpx.Client = None, debug: bool = False, github_token: str = None, template_repo: str = None) -> Tuple[Path, dict]:
"""Download template from GitHub releases.

Args:
ai_assistant: AI assistant type (e.g., 'claude', 'gemini')
download_dir: Directory to download the template to
script_type: Script type ('sh' or 'ps')
verbose: Print verbose output
show_progress: Show download progress bar
client: HTTP client to use
debug: Enable debug output
github_token: GitHub API token
template_repo: Custom repository in 'owner/repo' format (default: 'github/spec-kit')

Returns:
Tuple of (zip_path, metadata_dict)
"""
if template_repo:
parts = template_repo.split("/")
if len(parts) != 2 or not parts[0] or not parts[1]:
raise RuntimeError(f"Invalid template repository format: '{template_repo}'. Expected format: 'owner/repo' (e.g., 'gouzhuang/spec-kit')")
repo_owner = parts[0]
repo_name = parts[1]
else:
repo_owner = "github"
repo_name = "spec-kit"
if client is None:
client = httpx.Client(verify=ssl_context)

Expand Down Expand Up @@ -748,9 +771,12 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
}
return zip_path, metadata

def download_and_extract_template(project_path: Path, ai_assistant: str, script_type: str, is_current_dir: bool = False, *, verbose: bool = True, tracker: StepTracker | None = None, client: httpx.Client = None, debug: bool = False, github_token: str = None) -> Path:
def download_and_extract_template(project_path: Path, ai_assistant: str, script_type: str, is_current_dir: bool = False, *, verbose: bool = True, tracker: StepTracker | None = None, client: httpx.Client = None, debug: bool = False, github_token: str = None, template_repo: str = None) -> Path:
"""Download the latest release and extract it to create a new project.
Returns project_path. Uses tracker if provided (with keys: fetch, download, extract, cleanup)

Args:
template_repo: Custom repository in 'owner/repo' format (default: 'github/spec-kit')
"""
current_dir = Path.cwd()

Expand All @@ -765,7 +791,8 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
show_progress=(tracker is None),
client=client,
debug=debug,
github_token=github_token
github_token=github_token,
template_repo=template_repo
)
if tracker:
tracker.complete("fetch", f"release {meta['release']} ({meta['size']:,} bytes)")
Expand Down Expand Up @@ -954,6 +981,7 @@ def init(
skip_tls: bool = typer.Option(False, "--skip-tls", help="Skip SSL/TLS verification (not recommended)"),
debug: bool = typer.Option(False, "--debug", help="Show verbose diagnostic output for network and extraction failures"),
github_token: str = typer.Option(None, "--github-token", help="GitHub token to use for API requests (or set GH_TOKEN or GITHUB_TOKEN environment variable)"),
template_repo: str = typer.Option(None, "--template-repo", help="Custom template repository in 'owner/repo' format (e.g., 'gouzhuang/spec-kit')"),
):
"""
Initialize a new Specify project from the latest template.
Expand All @@ -978,6 +1006,7 @@ def init(
specify init --here --ai codebuddy
specify init --here
specify init --here --force # Skip confirmation when current directory not empty
specify init --template-repo gouzhuang/spec-kit my-project # Use custom fork
"""

show_banner()
Expand Down Expand Up @@ -1124,7 +1153,7 @@ def init(
local_ssl_context = ssl_context if verify else False
local_client = httpx.Client(verify=local_ssl_context)

download_and_extract_template(project_path, selected_ai, selected_script, here, verbose=False, tracker=tracker, client=local_client, debug=debug, github_token=github_token)
download_and_extract_template(project_path, selected_ai, selected_script, here, verbose=False, tracker=tracker, client=local_client, debug=debug, github_token=github_token, template_repo=template_repo)

ensure_executable_scripts(project_path, tracker=tracker)

Expand Down
29 changes: 5 additions & 24 deletions templates/commands/specify.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,33 +39,14 @@ Given that feature description, do this:
- "Create a dashboard for analytics" → "analytics-dashboard"
- "Fix payment processing timeout bug" → "fix-payment-timeout"

2. **Check for existing branches before creating new one**:
2. **Create new feature branch**:

a. First, fetch all remote branches to ensure we have the latest information:

```bash
git fetch --all --prune
```

b. Find the highest feature number across all sources for the short-name:
- Remote branches: `git ls-remote --heads origin | grep -E 'refs/heads/[0-9]+-<short-name>$'`
- Local branches: `git branch | grep -E '^[* ]*[0-9]+-<short-name>$'`
- Specs directories: Check for directories matching `specs/[0-9]+-<short-name>`

c. Determine the next available number:
- Extract all numbers from all three sources
- Find the highest number N
- Use N+1 for the new branch number

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"`
Run the script `{SCRIPT}` with short-name:
- Pass `--short-name "your-short-name"` along with the feature description
- Bash example: `{SCRIPT} --json --short-name "user-auth" "Add user authentication"`
- PowerShell example: `{SCRIPT} -Json -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
- 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
Expand Down