Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
41 changes: 36 additions & 5 deletions .github/workflows/scripts/create-release-packages.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,17 @@ rewrite_paths() {
sed -E \
-e 's@(/?)memory/@.specify/memory/@g' \
-e 's@(/?)scripts/@.specify/scripts/@g' \
-e 's@(/?)templates/@.specify/templates/@g'
-e 's@(/?)templates/@.specify/templates/@g' \
-e 's@(/?)hooks/@.specify/hooks/@g'
}

get_hook_extension() {
local script_variant=$1
if [[ "$script_variant" == "ps" ]]; then
echo ".ps1"
else
echo ""
fi
}

generate_commands() {
Expand All @@ -57,8 +67,9 @@ generate_commands() {
script_command="(Missing script command for $script_variant)"
fi

# Replace {SCRIPT} placeholder with the script command
body=$(printf '%s\n' "$file_content" | sed "s|{SCRIPT}|${script_command}|g")
# Replace {SCRIPT} placeholder with the script command and {HOOK_EXT} with extension
local ext=$(get_hook_extension "$script_variant")
body=$(printf '%s\n' "$file_content" | sed "s|{SCRIPT}|${script_command}|g" | sed "s|{HOOK_EXT}|${ext}|g")

# Remove the scripts: section from frontmatter while preserving YAML structure
body=$(printf '%s\n' "$body" | awk '
Expand Down Expand Up @@ -113,6 +124,25 @@ build_variant() {
fi

[[ -d templates ]] && { mkdir -p "$SPEC_DIR/templates"; find templates -type f -not -path "templates/commands/*" -exec cp --parents {} "$SPEC_DIR"/ \; ; echo "Copied templates -> .specify/templates"; }

# Copy hooks selectively based on script variant
if [[ -d hooks ]]; then
mkdir -p "$SPEC_DIR/hooks"
# Always copy README.md
[[ -f hooks/README.md ]] && cp hooks/README.md "$SPEC_DIR/hooks/"

case $script in
sh)
# Copy bash hook samples (without .ps1)
find hooks -maxdepth 1 -type f -name "*.sample" ! -name "*.ps1.sample" -exec cp {} "$SPEC_DIR/hooks/" \;
;;
ps)
# Copy PowerShell hook samples (.ps1.sample)
find hooks -maxdepth 1 -type f -name "*.ps1.sample" -exec cp {} "$SPEC_DIR/hooks/" \;
;;
esac
echo "Copied hooks -> .specify/hooks"
fi
# Inject variant into plan-template.md within .specify/templates if present
local plan_tpl="$base_dir/.specify/templates/plan-template.md"
if [[ -f "$plan_tpl" ]]; then
Expand All @@ -122,8 +152,9 @@ build_variant() {
if [[ -n $script_command ]]; then
# Always prefix with .specify/ for plan usage
script_command=".specify/$script_command"
# Replace {SCRIPT} placeholder with the script command and __AGENT__ with agent name
substituted=$(sed "s|{SCRIPT}|${script_command}|g" "$plan_tpl" | tr -d '\r' | sed "s|__AGENT__|${agent}|g")
# Replace {SCRIPT} placeholder with the script command, {HOOK_EXT} with extension, and __AGENT__ with agent name
local ext=$(get_hook_extension "$script")
substituted=$(sed "s|{SCRIPT}|${script_command}|g" "$plan_tpl" | tr -d '\r' | sed "s|__AGENT__|${agent}|g" | sed "s|{HOOK_EXT}|${ext}|g")
# Strip YAML frontmatter from plan template output (keep body only)
stripped=$(printf '%s\n' "$substituted" | awk 'BEGIN{fm=0;dash=0} /^---$/ {dash++; if(dash==1){fm=1; next} else if(dash==2){fm=0; next}} {if(!fm) print}')
printf '%s\n' "$stripped" > "$plan_tpl"
Expand Down
205 changes: 205 additions & 0 deletions hooks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
# Specify Hooks

This directory contains Git-style hook script samples that customize the `/specify` command workflow. All hooks are optional and follow Git's naming conventions for familiar, intuitive usage.

## Hook Activation

Hooks are provided as `.sample` files and must be activated by removing the `.sample` extension:

**Unix/Linux/macOS:**
```bash
# Activate bash hook (example: prepare-feature-num)
cp .specify/hooks/prepare-feature-num.sample .specify/hooks/prepare-feature-num
chmod +x .specify/hooks/prepare-feature-num
```

**Windows PowerShell:**
```powershell
# Activate PowerShell hook
Copy-Item .specify/hooks/prepare-feature-num.ps1.sample .specify/hooks/prepare-feature-num.ps1
```

**Cross-platform support:** The system automatically detects and uses the appropriate hook format (`.ps1` for Windows, executable scripts for Unix).

## Available Hooks (Git-Style Naming)

### `pre-specify` - Pre-processing Hook
- **When**: Before the entire specify workflow begins
- **Purpose**: Validation, setup, or preprocessing tasks
- **Arguments**: `$1` = feature description
- **Exit codes**: Non-zero exit codes show warnings but don't stop execution

**Example uses:**
- Validate feature description format and length
- Check prerequisites or dependencies
- Set up external resources or authenticate

### `prepare-feature-num` - Feature Number Preparation Hook
- **When**: Before auto-incrementing feature number (similar to Git's `prepare-commit-msg`)
- **Purpose**: Provide custom feature numbering from external sources
- **Arguments**: `$1` = feature description
- **Output**: Integer feature number (stdout)
- **Fallback**: If hook fails or outputs nothing, auto-increment is used

**Example uses:**
- Fetch feature number from external spec server
- Create GitHub issue and use issue number
- Implement custom numbering schemes

### `post-checkout` - Post-Checkout Hook
- **When**: After branch creation and checkout (matches Git's `post-checkout`)
- **Purpose**: Setup tasks after branch creation but before spec writing
- **Arguments**:
- `$1` = feature description
- `$2` = feature number
- `$3` = branch name
- `$4` = spec file path
- **Environment**: `BRANCH_NAME`, `SPEC_FILE`, `FEATURE_NUM` also available
- **Exit codes**: Non-zero exit codes show warnings but don't stop execution

**Example uses:**
- Initialize additional project files
- Set up branch-specific configurations
- Create directory structures
- Send branch creation notifications

### `post-specify` - Post-Specification Hook
- **When**: After spec file is completely written (true post-specify)
- **Purpose**: Final integration tasks and notifications
- **Arguments**:
- `$1` = feature description
- `$2` = feature number
- `$3` = branch name
- `$4` = spec file path
- **Environment**: `BRANCH_NAME`, `SPEC_FILE`, `FEATURE_NUM` also available
- **Exit codes**: Non-zero exit codes show warnings but don't stop execution

**Example uses:**
- Create GitHub issues linking to completed specs
- Send completion notifications
- Trigger CI/CD pipelines for spec review
- Update external tracking systems

## Hook Examples

### Custom Feature Numbering from Server

**Bash version:**
```bash
#!/bin/bash
# .specify/hooks/prepare-feature-num
FEATURE_DESC="$1"
FEATURE_NUMBER=$(curl -s "$SPEC_SERVER/api/next-number")
echo "$FEATURE_NUMBER"
```

**PowerShell version:**
```powershell
#!/usr/bin/env pwsh
# .specify/hooks/prepare-feature-num.ps1
param([string]$FeatureDescription)
$featureNumber = Invoke-RestMethod -Uri "$env:SPEC_SERVER/api/next-number"
Write-Output $featureNumber
```

### GitHub Issue for Feature Number

**Bash version:**
```bash
#!/bin/bash
# .specify/hooks/prepare-feature-num
FEATURE_DESC="$1"
ISSUE_URL=$(gh issue create --title "Spec: $FEATURE_DESC" --body "Specification development")
ISSUE_NUMBER=$(echo "$ISSUE_URL" | grep -o '[0-9]*$')
echo "$ISSUE_NUMBER"
```

**PowerShell version:**
```powershell
#!/usr/bin/env pwsh
# .specify/hooks/prepare-feature-num.ps1
param([string]$FeatureDescription)
$issueUrl = gh issue create --title "Spec: $FeatureDescription" --body "Specification development"
$issueNumber = [regex]::Match($issueUrl, '\d+$').Value
Write-Output $issueNumber
```

### Post-Checkout Setup

**Bash version:**
```bash
#!/bin/bash
# .specify/hooks/post-checkout
FEATURE_DESC="$1"
FEATURE_NUM="$2"
BRANCH_NAME="$3"
SPEC_FILE="$4"
# Create additional project directories
mkdir -p "docs/$BRANCH_NAME"
# Set up branch-specific configuration
echo "Branch $BRANCH_NAME (#$FEATURE_NUM) created for: $FEATURE_DESC" > "docs/$BRANCH_NAME/info.txt"
```

### Post-Specification Notification

**Bash version:**
```bash
#!/bin/bash
# .specify/hooks/post-specify
FEATURE_DESC="$1"
FEATURE_NUM="$2"
BRANCH_NAME="$3"
SPEC_FILE="$4"
# Create completion issue
gh issue create --title "Spec Complete #$FEATURE_NUM: $FEATURE_DESC" --body "Specification ready for review: $SPEC_FILE on branch $BRANCH_NAME"
# Send notification
echo "Specification $FEATURE_NUM completed: $SPEC_FILE" | mail -s "Spec Ready" [email protected]
```

## Technical Notes

### Platform-Specific Behavior
- **Unix/Linux/macOS**: Hooks must be executable (`chmod +x`). System looks for exact hook name.
- **Windows**: PowerShell hooks use `.ps1` extension. No execute permission needed.
- **Cross-platform**: System automatically detects and uses appropriate hook format.

### Hook Execution
- Hook arguments vary by type:
- `pre-specify` and `prepare-feature-num`: receive only feature description (`$1`)
- `post-checkout` and `post-specify`: receive feature description (`$1`) and have access to environment variables (BRANCH_NAME, SPEC_FILE, FEATURE_NUM)
- The `prepare-feature-num` hook should output only the number to stdout
- Failed hooks generate warnings but don't stop the specification process
- Non-existent or non-executable hook files are safely ignored

### Available Hook Formats
- `hook-name` - Bash/shell script (Unix/Linux/macOS)
- `hook-name.ps1` - PowerShell script (Windows/cross-platform)

### Hook Execution Order
1. `pre-specify` - Workflow validation and setup
2. `prepare-feature-num` - Custom feature numbering (optional)
3. **Script execution** - Branch/directory creation
4. `post-checkout` - Post-branch setup tasks
5. **Spec writing** - Template processing and content generation
6. `post-specify` - Completion notifications and final tasks

## Customization

**To activate and customize hooks:**

1. **Copy the sample**: Remove `.sample` from the appropriate hook file
2. **Make executable** (Unix only): `chmod +x .specify/hooks/hook-name`
3. **Edit the hook**: Customize the logic for your needs
4. **Test**: Run the hook manually with test data

**Example activation:**
```bash
# Unix/Linux/macOS - Activate prepare-feature-num hook
cp .specify/hooks/prepare-feature-num.sample .specify/hooks/prepare-feature-num
chmod +x .specify/hooks/prepare-feature-num

# Windows PowerShell - Activate prepare-feature-num hook
Copy-Item .specify/hooks/prepare-feature-num.ps1.sample .specify/hooks/prepare-feature-num.ps1
```

The Git-style naming provides familiar patterns for developers already using Git hooks, making the system more intuitive and easier to understand.
21 changes: 21 additions & 0 deletions hooks/post-checkout.ps1.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env pwsh
# Post-checkout hook: Runs after feature branch and directory creation
# Arguments: $args[0] = feature description
# Environment: $env:BRANCH_NAME, $env:SPEC_FILE, $env:FEATURE_NUM available

param(
[Parameter(Position=0)]
[string]$FeatureDescription
)

# Example: Create GitHub issue
# if (Get-Command gh -ErrorAction SilentlyContinue) {
# gh issue create --title "Spec: $FeatureDescription" --body "Branch: $env:BRANCH_NAME, Spec: $env:SPEC_FILE"
# }

# Example: Send notification
# $message = "Feature $env:FEATURE_NUM created: $env:BRANCH_NAME"
# Send-MailMessage -To "[email protected]" -Subject "New Spec" -Body $message -SmtpServer "smtp.company.com"

# Default: Do nothing
exit 0
15 changes: 15 additions & 0 deletions hooks/post-checkout.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash
# Post-checkout hook: Runs after feature branch and directory creation
# Arguments: $1 = feature description
# Environment: BRANCH_NAME, SPEC_FILE, FEATURE_NUM available

# Example: Create GitHub issue
# if command -v gh >/dev/null 2>&1; then
# gh issue create --title "Spec: $1" --body "Branch: $BRANCH_NAME, Spec: $SPEC_FILE"
# fi

# Example: Send notification
# echo "Feature $FEATURE_NUM created: $BRANCH_NAME" | mail -s "New Spec" [email protected]

# Default: Do nothing
exit 0
29 changes: 29 additions & 0 deletions hooks/post-specify.ps1.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env pwsh
# Post-specify hook: Runs after spec file is written and completed
# Arguments: $args[0] = feature description
# Environment: $env:BRANCH_NAME, $env:SPEC_FILE, $env:FEATURE_NUM available

param(
[Parameter(Position=0)]
[string]$FeatureDescription
)

# Example: Create GitHub issue linking to completed spec
# if (Get-Command gh -ErrorAction SilentlyContinue) {
# gh issue create --title "Spec Complete: $FeatureDescription" --body "Specification completed: $env:SPEC_FILE on branch $env:BRANCH_NAME"
# }

# Example: Send completion notification
# $message = "Specification $env:FEATURE_NUM completed: $env:SPEC_FILE"
# Send-MailMessage -To "[email protected]" -Subject "Spec Ready for Review" -Body $message -SmtpServer "smtp.company.com"

# Example: Trigger CI/CD pipeline
# $body = @{
# event = "spec_completed"
# branch = $env:BRANCH_NAME
# spec = $env:SPEC_FILE
# } | ConvertTo-Json
# Invoke-RestMethod -Uri $env:CI_WEBHOOK_URL -Method Post -Body $body -ContentType "application/json"

# Default: Do nothing
exit 0
18 changes: 18 additions & 0 deletions hooks/post-specify.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash
# Post-specify hook: Runs after spec file is written and completed
# Arguments: $1 = feature description
# Environment: BRANCH_NAME, SPEC_FILE, FEATURE_NUM available

# Example: Create GitHub issue linking to completed spec
# if command -v gh >/dev/null 2>&1; then
# gh issue create --title "Spec Complete: $1" --body "Specification completed: $SPEC_FILE on branch $BRANCH_NAME"
# fi

# Example: Send completion notification
# echo "Specification $FEATURE_NUM completed: $SPEC_FILE" | mail -s "Spec Ready for Review" [email protected]

# Example: Trigger CI/CD pipeline
# curl -X POST "$CI_WEBHOOK_URL" -d '{"event":"spec_completed","branch":"'$BRANCH_NAME'","spec":"'$SPEC_FILE'"}'

# Default: Do nothing
exit 0
18 changes: 18 additions & 0 deletions hooks/pre-specify.ps1.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env pwsh
# Pre-specify hook: Runs before feature creation
# Arguments: $args[0] = feature description
# Customize this script to add pre-processing logic

param(
[Parameter(Position=0)]
[string]$FeatureDescription
)

# Example: Validate feature description
# if ($FeatureDescription.Length -lt 10) {
# Write-Error "Error: Feature description too short"
# exit 1
# }

# Default: Do nothing
exit 0
Loading