Skip to content

Commit 928dcee

Browse files
committed
feat(hooks): add support for custom hooks
1 parent 919ba00 commit 928dcee

14 files changed

Lines changed: 465 additions & 62 deletions

.github/workflows/scripts/create-release-packages.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ rewrite_paths() {
3131
sed -E \
3232
-e 's@(/?)memory/@.specify/memory/@g' \
3333
-e 's@(/?)scripts/@.specify/scripts/@g' \
34-
-e 's@(/?)templates/@.specify/templates/@g'
34+
-e 's@(/?)templates/@.specify/templates/@g' \
35+
-e 's@(/?)hooks/@.specify/hooks/@g'
3536
}
3637

3738
generate_commands() {
@@ -110,6 +111,7 @@ build_variant() {
110111
fi
111112

112113
[[ -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"; }
114+
[[ -d hooks ]] && { cp -r hooks "$SPEC_DIR/"; echo "Copied hooks -> .specify"; }
113115
# Inject variant into plan-template.md within .specify/templates if present
114116
local plan_tpl="$base_dir/.specify/templates/plan-template.md"
115117
if [[ -f "$plan_tpl" ]]; then

hooks/README.md

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# Specify Hooks
2+
3+
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.
4+
5+
## Hook Activation
6+
7+
Hooks are provided as `.sample` files and must be activated by removing the `.sample` extension:
8+
9+
**Unix/Linux/macOS:**
10+
```bash
11+
# Activate bash hook (example: prepare-feature-num)
12+
cp .specify/hooks/prepare-feature-num.sample .specify/hooks/prepare-feature-num
13+
chmod +x .specify/hooks/prepare-feature-num
14+
```
15+
16+
**Windows PowerShell:**
17+
```powershell
18+
# Activate PowerShell hook
19+
Copy-Item .specify/hooks/prepare-feature-num.ps1.sample .specify/hooks/prepare-feature-num.ps1
20+
```
21+
22+
**Cross-platform support:** The system automatically detects and uses the appropriate hook format (`.ps1` for Windows, executable scripts for Unix).
23+
24+
## Available Hooks (Git-Style Naming)
25+
26+
### `pre-specify` - Pre-processing Hook
27+
- **When**: Before the entire specify workflow begins
28+
- **Purpose**: Validation, setup, or preprocessing tasks
29+
- **Arguments**: `$1` = feature description
30+
- **Exit codes**: Non-zero exit codes show warnings but don't stop execution
31+
32+
**Example uses:**
33+
- Validate feature description format and length
34+
- Check prerequisites or dependencies
35+
- Set up external resources or authenticate
36+
37+
### `prepare-feature-num` - Feature Number Preparation Hook
38+
- **When**: Before auto-incrementing feature number (similar to Git's `prepare-commit-msg`)
39+
- **Purpose**: Provide custom feature numbering from external sources
40+
- **Arguments**: `$1` = feature description
41+
- **Output**: Integer feature number (stdout)
42+
- **Fallback**: If hook fails or outputs nothing, auto-increment is used
43+
44+
**Example uses:**
45+
- Fetch feature number from external spec server
46+
- Create GitHub issue and use issue number
47+
- Implement custom numbering schemes
48+
49+
### `post-checkout` - Post-Checkout Hook
50+
- **When**: After branch creation and checkout (matches Git's `post-checkout`)
51+
- **Purpose**: Setup tasks after branch creation but before spec writing
52+
- **Arguments**: `$1` = feature description
53+
- **Environment**: `BRANCH_NAME`, `SPEC_FILE`, `FEATURE_NUM` are available
54+
- **Exit codes**: Non-zero exit codes show warnings but don't stop execution
55+
56+
**Example uses:**
57+
- Initialize additional project files
58+
- Set up branch-specific configurations
59+
- Create directory structures
60+
- Send branch creation notifications
61+
62+
### `post-specify` - Post-Specification Hook
63+
- **When**: After spec file is completely written (true post-specify)
64+
- **Purpose**: Final integration tasks and notifications
65+
- **Arguments**: `$1` = feature description
66+
- **Environment**: `BRANCH_NAME`, `SPEC_FILE`, `FEATURE_NUM` are available
67+
- **Exit codes**: Non-zero exit codes show warnings but don't stop execution
68+
69+
**Example uses:**
70+
- Create GitHub issues linking to completed specs
71+
- Send completion notifications
72+
- Trigger CI/CD pipelines for spec review
73+
- Update external tracking systems
74+
75+
## Hook Examples
76+
77+
### Custom Feature Numbering from Server
78+
79+
**Bash version:**
80+
```bash
81+
#!/bin/bash
82+
# .specify/hooks/prepare-feature-num
83+
FEATURE_DESC="$1"
84+
FEATURE_NUMBER=$(curl -s "$SPEC_SERVER/api/next-number")
85+
echo "$FEATURE_NUMBER"
86+
```
87+
88+
**PowerShell version:**
89+
```powershell
90+
#!/usr/bin/env pwsh
91+
# .specify/hooks/prepare-feature-num.ps1
92+
param([string]$FeatureDescription)
93+
$featureNumber = Invoke-RestMethod -Uri "$env:SPEC_SERVER/api/next-number"
94+
Write-Output $featureNumber
95+
```
96+
97+
### GitHub Issue for Feature Number
98+
99+
**Bash version:**
100+
```bash
101+
#!/bin/bash
102+
# .specify/hooks/prepare-feature-num
103+
FEATURE_DESC="$1"
104+
ISSUE_URL=$(gh issue create --title "Spec: $FEATURE_DESC" --body "Specification development")
105+
ISSUE_NUMBER=$(echo "$ISSUE_URL" | grep -o '[0-9]*$')
106+
echo "$ISSUE_NUMBER"
107+
```
108+
109+
**PowerShell version:**
110+
```powershell
111+
#!/usr/bin/env pwsh
112+
# .specify/hooks/prepare-feature-num.ps1
113+
param([string]$FeatureDescription)
114+
$issueUrl = gh issue create --title "Spec: $FeatureDescription" --body "Specification development"
115+
$issueNumber = [regex]::Match($issueUrl, '\d+$').Value
116+
Write-Output $issueNumber
117+
```
118+
119+
### Post-Checkout Setup
120+
121+
**Bash version:**
122+
```bash
123+
#!/bin/bash
124+
# .specify/hooks/post-checkout
125+
FEATURE_DESC="$1"
126+
# Create additional project directories
127+
mkdir -p "docs/$BRANCH_NAME"
128+
# Set up branch-specific configuration
129+
echo "Branch $BRANCH_NAME created for: $FEATURE_DESC" > "docs/$BRANCH_NAME/info.txt"
130+
```
131+
132+
### Post-Specification Notification
133+
134+
**Bash version:**
135+
```bash
136+
#!/bin/bash
137+
# .specify/hooks/post-specify
138+
FEATURE_DESC="$1"
139+
# Create completion issue
140+
gh issue create --title "Spec Complete: $FEATURE_DESC" --body "Specification ready for review: $SPEC_FILE"
141+
# Send notification
142+
echo "Specification $FEATURE_NUM completed: $SPEC_FILE" | mail -s "Spec Ready" team@company.com
143+
```
144+
145+
## Technical Notes
146+
147+
### Platform-Specific Behavior
148+
- **Unix/Linux/macOS**: Hooks must be executable (`chmod +x`). System looks for exact hook name.
149+
- **Windows**: PowerShell hooks use `.ps1` extension. No execute permission needed.
150+
- **Cross-platform**: System automatically detects and uses appropriate hook format.
151+
152+
### Hook Execution
153+
- Hooks are called with the feature description as the first argument
154+
- The `feature-num` hook should output only the number to stdout
155+
- The `post-specify` hook has access to environment variables set by the create script
156+
- Failed hooks generate warnings but don't stop the specification process
157+
- Non-existent or non-executable hook files are safely ignored
158+
159+
### Available Hook Formats
160+
- `hook-name` - Bash/shell script (Unix/Linux/macOS)
161+
- `hook-name.ps1` - PowerShell script (Windows/cross-platform)
162+
163+
### Hook Execution Order
164+
1. `pre-specify` - Workflow validation and setup
165+
2. `prepare-feature-num` - Custom feature numbering (optional)
166+
3. **Script execution** - Branch/directory creation
167+
4. `post-checkout` - Post-branch setup tasks
168+
5. **Spec writing** - Template processing and content generation
169+
6. `post-specify` - Completion notifications and final tasks
170+
171+
## Customization
172+
173+
**To activate and customize hooks:**
174+
175+
1. **Copy the sample**: Remove `.sample` from the appropriate hook file
176+
2. **Make executable** (Unix only): `chmod +x .specify/hooks/hook-name`
177+
3. **Edit the hook**: Customize the logic for your needs
178+
4. **Test**: Run the hook manually with test data
179+
180+
**Example activation:**
181+
```bash
182+
# Unix/Linux/macOS - Activate prepare-feature-num hook
183+
cp .specify/hooks/prepare-feature-num.sample .specify/hooks/prepare-feature-num
184+
chmod +x .specify/hooks/prepare-feature-num
185+
186+
# Windows PowerShell - Activate prepare-feature-num hook
187+
Copy-Item .specify/hooks/prepare-feature-num.ps1.sample .specify/hooks/prepare-feature-num.ps1
188+
```
189+
190+
The Git-style naming provides familiar patterns for developers already using Git hooks, making the system more intuitive and easier to understand.

hooks/post-checkout.ps1.sample

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env pwsh
2+
# Post-specify hook: Runs after feature branch and directory creation
3+
# Arguments: $args[0] = feature description
4+
# Environment: $env:BRANCH_NAME, $env:SPEC_FILE, $env:FEATURE_NUM available
5+
6+
param(
7+
[Parameter(Position=0)]
8+
[string]$FeatureDescription
9+
)
10+
11+
# Example: Create GitHub issue
12+
# if (Get-Command gh -ErrorAction SilentlyContinue) {
13+
# gh issue create --title "Spec: $FeatureDescription" --body "Branch: $env:BRANCH_NAME, Spec: $env:SPEC_FILE"
14+
# }
15+
16+
# Example: Send notification
17+
# $message = "Feature $env:FEATURE_NUM created: $env:BRANCH_NAME"
18+
# Send-MailMessage -To "team@company.com" -Subject "New Spec" -Body $message -SmtpServer "smtp.company.com"
19+
20+
# Default: Do nothing
21+
exit 0

hooks/post-checkout.sample

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/bin/bash
2+
# Post-specify hook: Runs after feature branch and directory creation
3+
# Arguments: $1 = feature description
4+
# Environment: BRANCH_NAME, SPEC_FILE, FEATURE_NUM available
5+
6+
# Example: Create GitHub issue
7+
# if command -v gh >/dev/null 2>&1; then
8+
# gh issue create --title "Spec: $1" --body "Branch: $BRANCH_NAME, Spec: $SPEC_FILE"
9+
# fi
10+
11+
# Example: Send notification
12+
# echo "Feature $FEATURE_NUM created: $BRANCH_NAME" | mail -s "New Spec" team@company.com
13+
14+
# Default: Do nothing
15+
exit 0

hooks/post-specify.ps1.sample

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env pwsh
2+
# Post-specify hook: Runs after spec file is written and completed
3+
# Arguments: $args[0] = feature description
4+
# Environment: $env:BRANCH_NAME, $env:SPEC_FILE, $env:FEATURE_NUM available
5+
6+
param(
7+
[Parameter(Position=0)]
8+
[string]$FeatureDescription
9+
)
10+
11+
# Example: Create GitHub issue linking to completed spec
12+
# if (Get-Command gh -ErrorAction SilentlyContinue) {
13+
# gh issue create --title "Spec Complete: $FeatureDescription" --body "Specification completed: $env:SPEC_FILE on branch $env:BRANCH_NAME"
14+
# }
15+
16+
# Example: Send completion notification
17+
# $message = "Specification $env:FEATURE_NUM completed: $env:SPEC_FILE"
18+
# Send-MailMessage -To "team@company.com" -Subject "Spec Ready for Review" -Body $message -SmtpServer "smtp.company.com"
19+
20+
# Example: Trigger CI/CD pipeline
21+
# $body = @{
22+
# event = "spec_completed"
23+
# branch = $env:BRANCH_NAME
24+
# spec = $env:SPEC_FILE
25+
# } | ConvertTo-Json
26+
# Invoke-RestMethod -Uri $env:CI_WEBHOOK_URL -Method Post -Body $body -ContentType "application/json"
27+
28+
# Default: Do nothing
29+
exit 0

hooks/post-specify.sample

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/bin/bash
2+
# Post-specify hook: Runs after spec file is written and completed
3+
# Arguments: $1 = feature description
4+
# Environment: BRANCH_NAME, SPEC_FILE, FEATURE_NUM available
5+
6+
# Example: Create GitHub issue linking to completed spec
7+
# if command -v gh >/dev/null 2>&1; then
8+
# gh issue create --title "Spec Complete: $1" --body "Specification completed: $SPEC_FILE on branch $BRANCH_NAME"
9+
# fi
10+
11+
# Example: Send completion notification
12+
# echo "Specification $FEATURE_NUM completed: $SPEC_FILE" | mail -s "Spec Ready for Review" team@company.com
13+
14+
# Example: Trigger CI/CD pipeline
15+
# curl -X POST "$CI_WEBHOOK_URL" -d '{"event":"spec_completed","branch":"'$BRANCH_NAME'","spec":"'$SPEC_FILE'"}'
16+
17+
# Default: Do nothing
18+
exit 0

hooks/pre-specify.ps1.sample

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env pwsh
2+
# Pre-specify hook: Runs before feature creation
3+
# Arguments: $args[0] = feature description
4+
# Customize this script to add pre-processing logic
5+
6+
param(
7+
[Parameter(Position=0)]
8+
[string]$FeatureDescription
9+
)
10+
11+
# Example: Validate feature description
12+
# if ($FeatureDescription.Length -lt 10) {
13+
# Write-Error "Error: Feature description too short"
14+
# exit 1
15+
# }
16+
17+
# Default: Do nothing
18+
exit 0

hooks/pre-specify.sample

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/bash
2+
# Pre-specify hook: Runs before feature creation
3+
# Arguments: $1 = feature description
4+
# Customize this script to add pre-processing logic
5+
6+
# Example: Validate feature description
7+
# if [[ ${#1} -lt 10 ]]; then
8+
# echo "Error: Feature description too short" >&2
9+
# exit 1
10+
# fi
11+
12+
# Default: Do nothing
13+
exit 0
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env pwsh
2+
# Feature-num hook: Override auto-generated feature number
3+
# Arguments: $args[0] = feature description
4+
# Output: Custom feature number (integer)
5+
6+
param(
7+
[Parameter(Position=0)]
8+
[string]$FeatureDescription
9+
)
10+
11+
# Example 1: Get from server
12+
# $featureNumber = Invoke-RestMethod -Uri "$env:SPEC_SERVER/api/next-number"
13+
# Write-Output $featureNumber
14+
15+
# Example 2: Use GitHub issue number
16+
# if (Get-Command gh -ErrorAction SilentlyContinue) {
17+
# $issueUrl = gh issue create --title "Spec: $FeatureDescription" --body "Specification development"
18+
# $issueNumber = [regex]::Match($issueUrl, '\d+$').Value
19+
# Write-Output $issueNumber
20+
# }
21+
22+
# Default: Let script auto-increment (output nothing)
23+
exit 0

hooks/prepare-feature-num.sample

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/bin/bash
2+
# Feature-num hook: Override auto-generated feature number
3+
# Arguments: $1 = feature description
4+
# Output: Custom feature number (integer)
5+
6+
# Example 1: Get from server
7+
# FEATURE_NUMBER=$(curl -s "$SPEC_SERVER/api/next-number")
8+
# echo "$FEATURE_NUMBER"
9+
10+
# Example 2: Use GitHub issue number
11+
# ISSUE_URL=$(gh issue create --title "Spec: $1" --body "Specification development")
12+
# ISSUE_NUMBER=$(echo "$ISSUE_URL" | grep -o '[0-9]*$')
13+
# echo "$ISSUE_NUMBER"
14+
15+
# Default: Let script auto-increment (output nothing)
16+
exit 0

0 commit comments

Comments
 (0)