Initial commit
This commit is contained in:
201
hooks/README.md
Normal file
201
hooks/README.md
Normal file
@@ -0,0 +1,201 @@
|
||||
# SpecWeave JIRA Plugin Hooks
|
||||
|
||||
**Plugin**: `specweave-jira`
|
||||
**Location**: `plugins/specweave-jira/hooks/`
|
||||
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
This hook automatically syncs SpecWeave increment progress to JIRA Issues after each task completion.
|
||||
|
||||
**Key Features**:
|
||||
- ✅ Updates JIRA issue status based on increment progress
|
||||
- ✅ Syncs task completion state to JIRA
|
||||
- ✅ Bidirectional sync (local → JIRA)
|
||||
- ✅ Non-blocking (failures don't stop core workflow)
|
||||
- ✅ Self-contained (no dependencies on core plugin)
|
||||
|
||||
---
|
||||
|
||||
## Available Hooks
|
||||
|
||||
### 1. `post-task-completion.sh`
|
||||
|
||||
**Triggers**: After ANY task is marked complete (via TodoWrite tool)
|
||||
|
||||
**Preconditions**:
|
||||
- ✅ Active increment exists (`.specweave/increments/####/`)
|
||||
- ✅ `metadata.json` has `.jira.issue` field
|
||||
- ✅ Node.js installed
|
||||
- ✅ JIRA sync script exists (`dist/commands/jira-sync.js`)
|
||||
- ✅ JIRA API credentials in `.env`
|
||||
|
||||
**Actions**:
|
||||
1. Reads increment metadata
|
||||
2. Calls JIRA sync script (Node.js)
|
||||
3. Updates JIRA issue status
|
||||
4. Logs all actions to `.specweave/logs/hooks-debug.log`
|
||||
|
||||
**Example Flow**:
|
||||
```
|
||||
Task T-003 completed in tasks.md
|
||||
↓
|
||||
Hook fires (PostToolUse + TodoWrite matcher)
|
||||
↓
|
||||
JIRA sync script updates issue PROJ-123
|
||||
↓
|
||||
Log: "[JIRA] ✅ JIRA sync complete"
|
||||
```
|
||||
|
||||
**Dependencies**:
|
||||
- Node.js 18+
|
||||
- JIRA API credentials (`.env`)
|
||||
- `jq` for JSON parsing
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Hook Registration (`hooks.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "TodoWrite",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/post-task-completion.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Metadata Format
|
||||
|
||||
Increments must have `metadata.json` with JIRA issue link:
|
||||
|
||||
```json
|
||||
{
|
||||
"jira": {
|
||||
"issue": "PROJ-123",
|
||||
"url": "https://company.atlassian.net/browse/PROJ-123",
|
||||
"project": "PROJ"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Required in `.env`:
|
||||
|
||||
```bash
|
||||
JIRA_HOST=https://company.atlassian.net
|
||||
JIRA_EMAIL=user@company.com
|
||||
JIRA_API_TOKEN=your_api_token_here
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### Automatic (Recommended)
|
||||
|
||||
```bash
|
||||
npx specweave init my-project
|
||||
# Installs all plugins including specweave-jira
|
||||
# Hooks auto-register via hooks.json
|
||||
```
|
||||
|
||||
### Manual (Development)
|
||||
|
||||
```bash
|
||||
# Install plugin
|
||||
/plugin install specweave-jira
|
||||
|
||||
# Verify hook registration
|
||||
cat ~/.claude/settings.json | grep specweave-jira
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
### Test Hook Independently
|
||||
|
||||
```bash
|
||||
# Test syntax
|
||||
bash -n plugins/specweave-jira/hooks/post-task-completion.sh
|
||||
|
||||
# Test execution (requires active increment + JIRA issue)
|
||||
./plugins/specweave-jira/hooks/post-task-completion.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Logging
|
||||
|
||||
All JIRA sync actions are logged to:
|
||||
|
||||
```
|
||||
.specweave/logs/hooks-debug.log
|
||||
```
|
||||
|
||||
**Example Log Output**:
|
||||
```
|
||||
[2025-11-10] [JIRA] 🔗 JIRA sync hook fired
|
||||
[2025-11-10] [JIRA] 🔄 Syncing to JIRA issue PROJ-123
|
||||
[2025-11-10] [JIRA] ✅ JIRA sync complete
|
||||
```
|
||||
|
||||
**Log Prefixes**:
|
||||
- `[JIRA]` - All messages from this hook
|
||||
- `🔗` - Hook fired
|
||||
- `🔄` - Syncing started
|
||||
- `✅` - Success
|
||||
- `⚠️` - Warning (non-blocking failure)
|
||||
- `ℹ️` - Info (skipped due to precondition)
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### Separation from Core Plugin
|
||||
|
||||
**Before (v0.12.x)**:
|
||||
```
|
||||
Core hook (500+ lines)
|
||||
├── JIRA sync ← Embedded in core!
|
||||
```
|
||||
|
||||
**After (v0.13.0+)**:
|
||||
```
|
||||
Core hook (330 lines) JIRA plugin hook (150 lines)
|
||||
├── Core concerns ├── Check for JIRA issue
|
||||
├── Call Node.js sync script
|
||||
└── Log actions
|
||||
```
|
||||
|
||||
**Key Benefits**:
|
||||
- ✅ **Optional plugin**: JIRA sync only runs if `specweave-jira` installed
|
||||
- ✅ **Independent testing**: Test JIRA sync in isolation
|
||||
- ✅ **No core dependencies**: Core plugin doesn't depend on JIRA API
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- **Core Plugin Hooks**: `plugins/specweave/hooks/README.md`
|
||||
- **Architecture Analysis**: `.specweave/increments/0018-strict-increment-discipline-enforcement/reports/HOOKS-ARCHITECTURE-ANALYSIS.md`
|
||||
- **JIRA Sync Command**: `plugins/specweave-jira/commands/specweave-jira-sync.md`
|
||||
|
||||
---
|
||||
|
||||
**Version**: v0.13.0+
|
||||
**Last Updated**: 2025-11-10
|
||||
180
hooks/post-task-completion.sh
Executable file
180
hooks/post-task-completion.sh
Executable file
@@ -0,0 +1,180 @@
|
||||
#!/bin/bash
|
||||
|
||||
# SpecWeave JIRA Sync Hook
|
||||
# Runs after task completion to sync progress to JIRA Issues
|
||||
#
|
||||
# This hook is part of the specweave-jira plugin and handles:
|
||||
# - Syncing task completion state to JIRA issues
|
||||
# - Updating JIRA issue status based on increment progress
|
||||
#
|
||||
# Dependencies:
|
||||
# - Node.js for running sync scripts
|
||||
# - jq for JSON parsing
|
||||
# - metadata.json must have .jira.issue field
|
||||
# - JIRA API credentials in .env
|
||||
|
||||
set +e # EMERGENCY FIX: Prevents Claude Code crashes
|
||||
|
||||
# EMERGENCY KILL SWITCH
|
||||
if [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# PROJECT ROOT DETECTION
|
||||
# ============================================================================
|
||||
|
||||
# Find project root by searching upward for .specweave/ directory
|
||||
find_project_root() {
|
||||
local dir="$1"
|
||||
while [ "$dir" != "/" ]; do
|
||||
if [ -d "$dir/.specweave" ]; then
|
||||
echo "$dir"
|
||||
return 0
|
||||
fi
|
||||
dir="$(dirname "$dir")"
|
||||
done
|
||||
# Fallback: try current directory
|
||||
if [ -d "$(pwd)/.specweave" ]; then
|
||||
pwd
|
||||
else
|
||||
echo "$(pwd)"
|
||||
fi
|
||||
}
|
||||
|
||||
PROJECT_ROOT="$(find_project_root "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")"
|
||||
cd "$PROJECT_ROOT" 2>/dev/null || true
|
||||
|
||||
# ============================================================================
|
||||
# CONFIGURATION
|
||||
# ============================================================================
|
||||
|
||||
LOGS_DIR=".specweave/logs"
|
||||
DEBUG_LOG="$LOGS_DIR/hooks-debug.log"
|
||||
|
||||
mkdir -p "$LOGS_DIR" 2>/dev/null || true
|
||||
|
||||
# ============================================================================
|
||||
# PRECONDITIONS CHECK
|
||||
# ============================================================================
|
||||
|
||||
echo "[$(date)] [JIRA] 🔗 JIRA sync hook fired" >> "$DEBUG_LOG" 2>/dev/null || true
|
||||
|
||||
# Detect current increment
|
||||
CURRENT_INCREMENT=$(ls -td .specweave/increments/*/ 2>/dev/null | xargs -n1 basename | grep -v "_backlog" | grep -v "_archive" | grep -v "_working" | head -1)
|
||||
|
||||
if [ -z "$CURRENT_INCREMENT" ]; then
|
||||
echo "[$(date)] [JIRA] ℹ️ No active increment, skipping JIRA sync" >> "$DEBUG_LOG" 2>/dev/null || true
|
||||
cat <<EOF
|
||||
{
|
||||
"continue": true
|
||||
}
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check for metadata.json
|
||||
METADATA_FILE=".specweave/increments/$CURRENT_INCREMENT/metadata.json"
|
||||
|
||||
if [ ! -f "$METADATA_FILE" ]; then
|
||||
echo "[$(date)] [JIRA] ℹ️ No metadata.json for $CURRENT_INCREMENT, skipping JIRA sync" >> "$DEBUG_LOG" 2>/dev/null || true
|
||||
cat <<EOF
|
||||
{
|
||||
"continue": true
|
||||
}
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check for JIRA issue link
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo "[$(date)] [JIRA] ⚠️ jq not found, skipping JIRA sync" >> "$DEBUG_LOG" 2>/dev/null || true
|
||||
cat <<EOF
|
||||
{
|
||||
"continue": true
|
||||
}
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
JIRA_ISSUE=$(jq -r '.jira.issue // empty' "$METADATA_FILE" 2>/dev/null)
|
||||
|
||||
if [ -z "$JIRA_ISSUE" ]; then
|
||||
echo "[$(date)] [JIRA] ℹ️ No JIRA issue linked to $CURRENT_INCREMENT, skipping sync" >> "$DEBUG_LOG" 2>/dev/null || true
|
||||
cat <<EOF
|
||||
{
|
||||
"continue": true
|
||||
}
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check for Node.js
|
||||
if ! command -v node &> /dev/null; then
|
||||
echo "[$(date)] [JIRA] ⚠️ Node.js not found, skipping JIRA sync" >> "$DEBUG_LOG" 2>/dev/null || true
|
||||
cat <<EOF
|
||||
{
|
||||
"continue": true
|
||||
}
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check for JIRA sync script
|
||||
if [ ! -f "dist/commands/jira-sync.js" ]; then
|
||||
echo "[$(date)] [JIRA] ⚠️ jira-sync.js not found, skipping JIRA sync" >> "$DEBUG_LOG" 2>/dev/null || true
|
||||
cat <<EOF
|
||||
{
|
||||
"continue": true
|
||||
}
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# JIRA SYNC LOGIC
|
||||
# ============================================================================
|
||||
|
||||
echo "[$(date)] [JIRA] 🔄 Syncing to JIRA issue $JIRA_ISSUE" >> "$DEBUG_LOG" 2>/dev/null || true
|
||||
|
||||
# Run JIRA sync command (non-blocking)
|
||||
node dist/commands/jira-sync.js "$CURRENT_INCREMENT" 2>&1 | tee -a "$DEBUG_LOG" >/dev/null || {
|
||||
echo "[$(date)] [JIRA] ⚠️ Failed to sync to JIRA (non-blocking)" >> "$DEBUG_LOG" 2>/dev/null || true
|
||||
}
|
||||
|
||||
echo "[$(date)] [JIRA] ✅ JIRA sync complete" >> "$DEBUG_LOG" 2>/dev/null || true
|
||||
|
||||
# ============================================================================
|
||||
# SPEC COMMIT SYNC (NEW!)
|
||||
# ============================================================================
|
||||
|
||||
echo "[$(date)] [JIRA] 🔗 Checking for spec commit sync..." >> "$DEBUG_LOG" 2>/dev/null || true
|
||||
|
||||
# Call TypeScript CLI to sync commits
|
||||
if command -v node &> /dev/null && [ -f "$PROJECT_ROOT/dist/cli/commands/sync-spec-commits.js" ]; then
|
||||
echo "[$(date)] [JIRA] 🚀 Running spec commit sync..." >> "$DEBUG_LOG" 2>/dev/null || true
|
||||
|
||||
node "$PROJECT_ROOT/dist/cli/commands/sync-spec-commits.js" \
|
||||
--increment "$PROJECT_ROOT/.specweave/increments/$CURRENT_INCREMENT" \
|
||||
--provider jira \
|
||||
2>&1 | tee -a "$DEBUG_LOG" >/dev/null || {
|
||||
echo "[$(date)] [JIRA] ⚠️ Spec commit sync failed (non-blocking)" >> "$DEBUG_LOG" 2>/dev/null || true
|
||||
}
|
||||
|
||||
echo "[$(date)] [JIRA] ✅ Spec commit sync complete" >> "$DEBUG_LOG" 2>/dev/null || true
|
||||
else
|
||||
echo "[$(date)] [JIRA] ℹ️ Spec commit sync not available (node or script not found)" >> "$DEBUG_LOG" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# OUTPUT TO CLAUDE
|
||||
# ============================================================================
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"continue": true
|
||||
}
|
||||
EOF
|
||||
|
||||
# ALWAYS exit 0 - NEVER let hook errors crash Claude Code
|
||||
exit 0
|
||||
Reference in New Issue
Block a user