This document describes the Hook System, which enables extensions to integrate with Spec-Driven Development workflow events. Hooks allow extensions to execute custom commands at specific lifecycle points (e.g., after planning, before implementation) with conditional execution based on configuration or environment state.
For information about installing and managing extensions, see Extension Management Commands. For details about the extension manifest schema and hook registration format, see Creating Extensions. For configuration layer mechanics that hooks can evaluate, see Configuration Layers.
The Hook System provides a declarative mechanism for extensions to inject functionality into the core Spec-Driven Development workflow without modifying core commands. Extensions define hooks in their extension.yml manifest, which are registered in .specify/extensions.yml during installation. AI agents check for applicable hooks after executing core commands (e.g., /speckit.plan, /speckit.implement) and present them to users for execution.
Key Characteristics:
| Characteristic | Description |
|---|---|
| Declarative | Hooks defined in YAML manifest, not imperative code |
| Event-based | Triggered by workflow lifecycle events |
| Conditional | Execute only when configuration or environment conditions are met |
| Optional | User decides whether to execute hook suggestions |
| Multi-agent | Works with all 16+ supported AI agents |
Sources: CHANGELOG.md51-58 CHANGELOG.md88-100
Hooks can be triggered by the following workflow lifecycle events:
Supported Hook Events:
| Event Name | Description | Typical Use Cases |
|---|---|---|
post-plan | After /speckit.plan completes | Validate architecture, update external systems |
post-tasks | After /speckit.tasks completes | Create issue tracker items, estimate effort |
post-implement | After /speckit.implement completes | Run deployment checks, sync status to trackers |
post-specify | After /speckit.specify completes | Validate specification format, extract metadata |
Sources: CHANGELOG.md51-58 CHANGELOG.md96-100
The HookExecutor class in src/specify_cli/extensions.py manages all hook operations: registration, filtering, condition evaluation, and execution messaging.
| Method | Purpose | Returns |
|---|---|---|
check_hooks_for_event(event_name) | Query all enabled hooks for a workflow event, filtered by conditions | List[HookInfo] |
execute_hook(extension_id, hook_name) | Retrieve execution information for a specific hook | HookExecutionInfo |
enable_hooks(extension_id) | Enable all hooks for an extension | None |
disable_hooks(extension_id) | Disable all hooks for an extension without removing them | None |
Sources: CHANGELOG.md51-58 CHANGELOG.md96-100
Hooks are registered in .specify/extensions.yml during extension installation. The registry file uses a hierarchical structure:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique identifier within the extension |
event | string | Yes | Workflow event that triggers the hook (e.g., post-plan, post-tasks) |
command | string | Yes | AI agent command to execute (must be registered for the agent) |
description | string | Yes | Human-readable description shown to users |
required | boolean | No | Whether hook is mandatory (defaults to false) |
condition | string | No | Condition expression that must be true for hook to be presented |
Sources: CHANGELOG.md51-58
Hooks support condition expressions that control whether they are presented to users. Conditions are evaluated against configuration values and environment variables.
config.key.path is set
config.key.path == 'value'
config.key.path != 'value'
Examples:
| Condition | Meaning |
|---|---|
config.jira.enabled is set | Hook executes if jira.enabled key exists in configuration |
config.jira.project_key == 'SPEC' | Hook executes if project key equals 'SPEC' |
config.sync.mode != 'disabled' | Hook executes if sync mode is not 'disabled' |
env.VAR is set
env.VAR == 'value'
env.VAR != 'value'
Examples:
| Condition | Meaning |
|---|---|
env.JIRA_API_TOKEN is set | Hook executes if environment variable exists |
env.CI == 'true' | Hook executes in CI environment |
env.DEPLOYMENT != 'production' | Hook executes in non-production environments |
The HookExecutor._evaluate_condition() method implements pattern matching for condition expressions:
Sources: CHANGELOG.md88-93 CHANGELOG.md96-100
AI agents interact with the Hook System through two primary operations: checking for applicable hooks after workflow commands, and retrieving execution information when users accept hook suggestions.
When an AI agent calls execute_hook(), it receives a HookExecutionInfo object containing:
The message field provides formatted instructions that agents can display to users, ensuring consistent presentation across all 16+ supported agents.
Sources: CHANGELOG.md96-100
Sources: CHANGELOG.md51-58 CHANGELOG.md96-100
Extensions can have their hooks enabled or disabled without requiring reinstallation or removal. This allows users to temporarily suppress hook suggestions.
| Command | Operation | Effect on Hooks |
|---|---|---|
specify extension enable <ext-id> | Sets enabled: true in registry | Hooks become available for condition evaluation |
specify extension disable <ext-id> | Sets enabled: false in registry | All hooks skipped regardless of conditions |
specify extension remove <ext-id> | Removes extension entry | Hooks unregistered completely |
Key Behavior:
check_hooks_for_event() before condition evaluationSources: CHANGELOG.md96-100
The .specify/extensions.yml file maintains the complete hook registry:
| Component | Reads | Writes | Purpose |
|---|---|---|---|
ExtensionManager.add() | No | Yes | Adds extension entry and registers hooks |
ExtensionManager.remove() | Yes | Yes | Removes extension entry and unregisters hooks |
ExtensionManager.enable() | Yes | Yes | Sets enabled: true |
ExtensionManager.disable() | Yes | Yes | Sets enabled: false |
HookExecutor.check_hooks_for_event() | Yes | No | Filters hooks by event and conditions |
HookExecutor.execute_hook() | Yes | No | Retrieves hook execution information |
Sources: CHANGELOG.md51-58
Refresh this wiki