Commands
Lifecycle Hooks

Lifecycle Hooks

Commands for workflow automation with lifecycle hooks.

Overview

Lifecycle hooks allow you to execute custom scripts at specific command events (before or after execution).

Hook Execution Flow:

  1. Pre-Hook Phase - Runs before command execution (optional)
    • If blocking pre-hook fails → Command does not run
    • If non-blocking pre-hook fails → Command runs anyway
  2. Command Execution - The fspec command runs
  3. Post-Hook Phase - Runs after command execution (optional)
    • If blocking post-hook fails → Exit code set to 1
    • If non-blocking post-hook fails → Exit code remains 0

Use lifecycle hooks when you need to:

  • Execute custom scripts at command lifecycle events
  • Add quality gates with blocking pre-hooks
  • Automate testing with post-hooks
  • Send notifications on workflow events
  • Validate hook configurations
  • List and manage configured hooks

Lifecycle hooks allow you to integrate validation, testing, notifications, or any custom automation into your ACDD workflow. Hooks execute scripts at specific command events (before or after execution).

Hook Management

List Hooks

List all configured hooks.

fspec list-hooks

Examples:

fspec list-hooks

Output:

Lifecycle Hooks:
  pre-update-work-unit-status:
    - validate-feature-file (blocking, 30s)
  post-implementing:
    - run-tests (non-blocking, 60s)
    - notify-slack (non-blocking, 10s)

Validate Hooks

Validate hook configuration and script paths.

fspec validate-hooks

This command checks:

  • Hook configuration syntax in spec/fspec-hooks.json
  • Script paths exist and are executable
  • Event names are valid
  • Timeout values are reasonable

Examples:

fspec validate-hooks

Add Hook

Add a new hook via CLI.

fspec add-hook <event> <name> [options]

Options:

  • --command <path> - Path to hook script (required)
  • --blocking - Make hook blocking (failure prevents execution)
  • --timeout <seconds> - Timeout in seconds (default: 60)

Examples:

# Add blocking pre-hook to validate code before implementing
fspec add-hook pre-implementing lint \
  --command spec/hooks/lint.sh \
  --blocking \
  --timeout 30
 
# Add non-blocking post-hook to run tests
fspec add-hook post-implementing test \
  --command spec/hooks/test.sh
 
# Add notification hook
fspec add-hook post-validating notify \
  --command spec/hooks/notify-slack.js \
  --timeout 10

Remove Hook

Remove a hook from configuration.

fspec remove-hook <event> <name>

Examples:

fspec remove-hook pre-implementing lint
fspec remove-hook post-implementing test

Hook Configuration

Hooks are configured in spec/fspec-hooks.json:

{
  "global": {
    "timeout": 120,
    "shell": "/bin/bash"
  },
  "hooks": {
    "pre-update-work-unit-status": [
      {
        "name": "validate-feature-file",
        "command": "spec/hooks/validate-feature.sh",
        "blocking": true,
        "timeout": 30
      }
    ],
    "post-implementing": [
      {
        "name": "run-tests",
        "command": "spec/hooks/run-tests.sh",
        "blocking": false,
        "condition": {
          "tags": ["@security"],
          "prefix": ["AUTH", "SEC"]
        }
      }
    ]
  }
}

Hook Events

Hooks follow the pattern pre-<command> and post-<command>:

Common Events:

  • pre-update-work-unit-status - Before updating work unit status
  • post-update-work-unit-status - After updating work unit status
  • post-implementing - After moving work unit to implementing state
  • post-testing - After moving work unit to testing state
  • post-validating - After moving work unit to validating state
  • Any fspec command supports pre-/post- hooks

Hook Properties

  • name (required) - Unique hook identifier
  • command (required) - Path to hook script (relative to project root)
  • blocking (optional) - If true, hook failure prevents command execution (pre-hooks) or sets exit code to 1 (post-hooks). Default: false
  • timeout (optional) - Timeout in seconds. Overrides global timeout. Default: 60
  • condition (optional) - Conditions for when hook should run
    • tags - Hook runs if work unit has ANY of these tags (OR logic)
    • prefix - Hook runs if work unit ID starts with ANY of these prefixes (OR logic)
    • epic - Hook runs if work unit belongs to this epic
    • estimateMin - Hook runs if work unit estimate is greater than or equal to this value
    • estimateMax - Hook runs if work unit estimate is less than or equal to this value

Hook Context

Hooks receive JSON context via stdin:

{
  "workUnitId": "AUTH-001",
  "event": "pre-update-work-unit-status",
  "timestamp": "2025-01-15T10:30:00.000Z"
}

Example Hook Scripts

Bash Hook

Validate feature files before status change (spec/hooks/validate-feature.sh):

#!/bin/bash
set -e
 
# Read context from stdin
CONTEXT=$(cat)
WORK_UNIT_ID=$(echo "$CONTEXT" | jq -r '.workUnitId')
 
echo "Validating feature files for $WORK_UNIT_ID..."
fspec validate
 
exit 0

Python Hook

Run tests after implementation (spec/hooks/run-tests.py):

#!/usr/bin/env python3
import sys
import json
import subprocess
 
# Read context from stdin
context = json.load(sys.stdin)
work_unit_id = context['workUnitId']
 
print(f"Running tests for {work_unit_id}...")
result = subprocess.run(['npm', 'test'], capture_output=True)
 
sys.exit(result.returncode)

JavaScript Hook

Send Slack notifications (spec/hooks/notify-slack.js):

#!/usr/bin/env node
const context = JSON.parse(require('fs').readFileSync(0, 'utf-8'));
 
console.log(`Notifying Slack about ${context.workUnitId}...`);
// Slack notification logic here
 
process.exit(0);

Blocking Hooks

Blocking hooks (with blocking: true) provide quality gates:

  • Pre-hooks - Hook failure prevents command from running
  • Post-hooks - Hook failure sets command exit code to 1

When a blocking hook fails, stderr output is wrapped in <system-reminder> tags for AI agents, making failures highly visible in Claude Code.

Example:

# This pre-hook blocks if linting fails
fspec add-hook pre-implementing lint \
  --command spec/hooks/lint.sh \
  --blocking

Conditional Execution

Hooks can run conditionally based on work unit properties:

{
  "name": "security-scan",
  "command": "spec/hooks/security-scan.sh",
  "blocking": true,
  "condition": {
    "tags": ["@security", "@critical"],
    "prefix": ["AUTH", "SEC"],
    "estimateMin": 5
  }
}

This hook runs ONLY when:

  • Work unit has @security OR @critical tag (OR logic)
  • AND work unit ID starts with AUTH OR SEC (OR logic)
  • AND estimate is 5 or greater

Common Use Cases

Quality Gates (Blocking Pre-Hooks)

Validate before advancing work:

fspec add-hook pre-implementing validate-code \
  --command spec/hooks/lint.sh \
  --blocking

Automated Testing (Post-Hooks)

Run tests after implementation:

fspec add-hook post-implementing test \
  --command spec/hooks/test.sh

Notifications (Non-Blocking Post-Hooks)

Send notifications on status changes:

fspec add-hook post-validating notify \
  --command spec/hooks/notify.sh

Code Quality Checks

Run linters, formatters, type checkers:

fspec add-hook pre-implementing format \
  --command spec/hooks/format.sh \
  --blocking

Documentation Generation

Auto-generate docs on completion:

fspec add-hook post-validating generate-docs \
  --command spec/hooks/generate-docs.sh

Metrics Recording

Record time, token usage, or custom metrics:

fspec add-hook post-implementing record-metrics \
  --command spec/hooks/record-metrics.sh

Benefits

  1. Quality Assurance - Enforce validation before advancing work
  2. Automation - Reduce manual steps in ACDD workflow
  3. Consistency - Same checks run every time
  4. Visibility - Hook failures are highly visible to AI agents
  5. Flexibility - Use any language (Bash, Python, JavaScript, etc.)
  6. Integration - Connect fspec to external tools and services

See Also