Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- feat(cli): polite deep merge for VSCode settings.json with JSONC support via `json5` and zero-data-loss fallbacks
- feat: wire `before_specify`, `after_specify`, `before_plan`, and `after_plan` hook events into command templates (#1788)
- feat(presets): Pluggable preset system with preset catalog and template resolver
- Preset manifest (`preset.yml`) with validation for artifact, command, and script types
- `PresetManifest`, `PresetRegistry`, `PresetManager`, `PresetCatalog`, `PresetResolver` classes in `src/specify_cli/presets.py`
Expand Down
29 changes: 25 additions & 4 deletions extensions/EXTENSION-API-REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,12 @@ Examples:

```yaml
hooks:
before_specify:
command: "speckit.research.pre-spec"
optional: true
prompt: "Perform pre-specification research?"
description: "Search context before writing spec"

after_tasks:
command: "speckit.jira.specstoissues"
optional: true
Expand All @@ -551,17 +557,30 @@ hooks:

Standard events (defined by core):

- `before_specify` - Before specification generation
- `after_specify` - After specification generation
- `before_plan` - Before implementation planning
- `after_plan` - After implementation planning
- `before_tasks` - Before task generation
- `after_tasks` - After task generation
- `before_implement` - Before implementation
- `after_implement` - After implementation
- `before_commit` - Before git commit
- `after_commit` - After git commit
- `before_commit` - Before git commit (future)
- `after_commit` - After git commit (future)

### Hook Configuration

**In `.specify/extensions.yml`**:

```yaml
hooks:
before_specify:
- extension: research
command: speckit.research.pre-spec
enabled: true
optional: true
prompt: "Perform pre-specification research?"

after_tasks:
- extension: jira
command: speckit.jira.specstoissues
Expand Down Expand Up @@ -591,6 +610,8 @@ Or for mandatory hooks:
**Automatic Hook**: {extension}
Executing: `/{command}`
EXECUTE_COMMAND: {command}

**CRITICAL**: You MUST execute the command above immediately after outputting it. Do not end the response until the hook has been triggered.
```

---
Expand Down Expand Up @@ -811,6 +832,6 @@ satisfied = version_satisfies("1.2.3", ">=1.0.0,<2.0.0") # bool

---

*Last Updated: 2026-01-28*
*Last Updated: 2026-03-13*
*API Version: 1.0*
*Spec Kit Version: 0.1.0*
*Spec Kit Version: 0.3.1*
24 changes: 22 additions & 2 deletions extensions/EXTENSION-DEVELOPMENT-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,14 @@ Integration hooks for automatic execution.

Available hook points:

- `before_specify`: Before `/speckit.specify` starts
- `after_specify`: After `/speckit.specify` completes
- `before_plan`: Before `/speckit.plan` starts
- `after_plan`: After `/speckit.plan` completes
- `before_tasks`: Before `/speckit.tasks` starts
- `after_tasks`: After `/speckit.tasks` completes
- `after_implement`: After `/speckit.implement` completes (future)
- `before_implement`: Before `/speckit.implement` starts
- `after_implement`: After `/speckit.implement` completes

Hook object:

Expand Down Expand Up @@ -639,11 +645,25 @@ echo "Using endpoint: $endpoint"

### Extension with Hooks

Extension that runs automatically:
Extension that runs automatically at different lifecycle stages:

```yaml
# extension.yml
hooks:
# Pre-hook: runs before specification starts
before_specify:
command: "speckit.research.pre-spec"
optional: true
prompt: "Perform pre-specification research?"
description: "Gather context from codebase before writing spec"

# Post-hook: runs after planning completes
after_plan:
command: "speckit.architect.validate"
optional: false # Mandatory execution
description: "Validate architecture against project standards"

# Post-hook: runs after task generation
after_tasks:
command: "speckit.auto.analyze"
optional: false # Always run
Expand Down
8 changes: 4 additions & 4 deletions extensions/EXTENSION-USER-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ Extensions are modular packages that add new commands and functionality to Spec

### Prerequisites

- Spec Kit version 0.1.0 or higher
- Spec Kit version 0.2.1 or higher
- A spec-kit project (directory with `.specify/` folder)
Comment on lines 42 to 44

### Check Your Version

```bash
specify version
# Should show 0.1.0 or higher
# Should show 0.2.1 or higher
```

### First Extension
Expand Down Expand Up @@ -986,5 +986,5 @@ After creating tasks, sync to Jira:

---

*Last Updated: 2026-01-28*
*Spec Kit Version: 0.1.0*
*Last Updated: 2026-03-13*
*Spec Kit Version: 0.3.1*
4 changes: 2 additions & 2 deletions src/specify_cli/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1699,7 +1699,7 @@ def get_hooks_for_event(self, event_name: str) -> List[Dict[str, Any]]:
"""Get all registered hooks for a specific event.

Args:
event_name: Name of the event (e.g., 'after_tasks')
event_name: Name of the event (e.g., 'after_specify', 'after_plan', 'after_tasks')

Returns:
List of hook configurations
Expand Down Expand Up @@ -1851,7 +1851,7 @@ def check_hooks_for_event(self, event_name: str) -> Dict[str, Any]:
This method is designed to be called by AI agents after core commands complete.

Args:
event_name: Name of the event (e.g., 'after_spec', 'after_tasks')
event_name: Name of the event (e.g., 'after_specify', 'after_plan', 'after_tasks')

Returns:
Dictionary with hook information:
Expand Down
4 changes: 4 additions & 0 deletions templates/commands/implement.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ You **MUST** consider the user input before proceeding (if not empty).

Wait for the result of the hook command before proceeding to the Outline.
```

**CRITICAL**: You MUST execute the command above immediately after outputting it. Do not proceed until the hook has been triggered.
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently

## Outline
Expand Down Expand Up @@ -198,4 +200,6 @@ Note: This command assumes a complete task breakdown exists in tasks.md. If task
Executing: `/{command}`
EXECUTE_COMMAND: {command}
```

**CRITICAL**: You MUST execute the command above immediately after outputting it. Do not end the response until the hook has been triggered.
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
65 changes: 65 additions & 0 deletions templates/commands/plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,41 @@ $ARGUMENTS

You **MUST** consider the user input before proceeding (if not empty).

## Pre-Execution Checks

**Check for extension hooks (before planning generation)**:
- Check if `.specify/extensions.yml` exists in the project root.
- If it exists, read it and look for entries under the `hooks.before_plan` key
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
- Treat hooks as enabled unless they explicitly specify `enabled: false`
- For each enabled hook, do **not** attempt to interpret or evaluate hook `condition` expressions yourself; condition evaluation is handled by the HookExecutor implementation
- Treat an enabled hook as executable for the purposes of this plan **only if it does not define a `condition`**
- For each such executable (non-conditional) hook, output the following based on its `optional` flag:
Comment on lines +35 to +36
- **Optional hook** (`optional: true`):
```
## Extension Hooks

**Optional Pre-Hook**: {extension}
Command: `/{command}`
Description: {description}

Prompt: {prompt}
To execute: `/{command}`
```
- **Mandatory hook** (`optional: false`):
```
## Extension Hooks

**Automatic Pre-Hook**: {extension}
Executing: `/{command}`
EXECUTE_COMMAND: {command}

Wait for the result of the hook command before proceeding to the Outline.
```

**CRITICAL**: You MUST execute the command above immediately after outputting it. Do not proceed until the hook has been triggered.
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently

## Outline

1. **Setup**: Run `{SCRIPT}` from repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
Expand All @@ -41,6 +76,36 @@ You **MUST** consider the user input before proceeding (if not empty).

4. **Stop and report**: Command ends after Phase 2 planning. Report branch, IMPL_PLAN path, and generated artifacts.

5. **Check for extension hooks**: After reporting completion, check if `.specify/extensions.yml` exists in the project root.
- If it exists, read it and look for entries under the `hooks.after_plan` key
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
- From the discovered hooks, ignore only those where `enabled` is explicitly set to `false`. If `enabled` is omitted, treat the hook as enabled.
- Do **not** attempt to interpret or evaluate hook `condition` expressions, and do **not** implement any custom condition logic in this command. Condition evaluation is handled by the `HookExecutor.should_execute_hook` implementation.
- For the purposes of this plan, treat an enabled hook as executable if its `condition` field is missing, empty, or otherwise falsey (for example: `null`, `""`, or `[]`).
- For each such executable (non-conditional) hook, output the following based on its `optional` flag:
- **Optional hook** (`optional: true`):
```
## Extension Hooks

**Optional Hook**: {extension}
Command: `/{command}`
Description: {description}

Prompt: {prompt}
To execute: `/{command}`
```
- **Mandatory hook** (`optional: false`):
```
## Extension Hooks

**Automatic Hook**: {extension}
Executing: `/{command}`
EXECUTE_COMMAND: {command}
```

**CRITICAL**: You MUST execute the command above immediately after outputting it. Do not end the response until the hook has been triggered.
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently

## Phases

### Phase 0: Outline & Research
Expand Down
67 changes: 67 additions & 0 deletions templates/commands/specify.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,42 @@ $ARGUMENTS

You **MUST** consider the user input before proceeding (if not empty).

## Pre-Execution Checks

**Check for extension hooks (before specification generation)**:
- Check if `.specify/extensions.yml` exists in the project root.
- If it exists, read it and look for entries under the `hooks.before_specify` key
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
- Treat hooks as enabled unless they explicitly specify `enabled: false`
- For each enabled hook, do **not** attempt to interpret or evaluate hook `condition` expressions yourself; condition evaluation is handled by the HookExecutor implementation
- Consider a hook automatically executable only if it is enabled **and** it has no `condition` field (or its `condition` is empty/falsey)
- Hooks with a non-empty `condition` MUST NOT be auto-executed here; they are conditional and will be filtered by the HookExecutor at runtime
- For each executable hook (enabled and without a non-empty `condition`), output the following based on its `optional` flag:
- **Optional hook** (`optional: true`):
```
## Extension Hooks

**Optional Pre-Hook**: {extension}
Command: `/{command}`
Description: {description}

Prompt: {prompt}
To execute: `/{command}`
```
- **Mandatory hook** (`optional: false`):
```
## Extension Hooks

**Automatic Pre-Hook**: {extension}
Executing: `/{command}`
EXECUTE_COMMAND: {command}

Wait for the result of the hook command before proceeding to the Outline.
```

**CRITICAL**: You MUST execute the command above immediately after outputting it. Do not proceed until the hook has been triggered.
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently

## Outline

The text the user typed after `/speckit.specify` in the triggering message **is** the feature description. Assume you always have it available in this conversation even if `{ARGS}` appears literally below. Do not ask the user to repeat it unless they provided an empty command.
Expand Down Expand Up @@ -176,6 +212,37 @@ Given that feature description, do this:

7. Report completion with branch name, spec file path, checklist results, and readiness for the next phase (`/speckit.clarify` or `/speckit.plan`).

8. **Check for extension hooks**: After reporting completion, check if `.specify/extensions.yml` exists in the project root.
- If it exists, read it and look for entries under the `hooks.after_specify` key
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
- From the discovered hooks, ignore only those where `enabled` is explicitly set to `false`. If `enabled` is omitted, treat the hook as enabled.
- Do **not** attempt to interpret, evaluate, or decide the truth of hook `condition` expressions here. Condition evaluation is handled exclusively by the `HookExecutor.should_execute_hook` implementation at runtime.
- Consider a hook automatically executable only if it is enabled **and** it has no `condition` field (or its `condition` is empty/falsey)
- Hooks with a non-empty `condition` MUST NOT be auto-executed here; they are conditional and will be filtered by the HookExecutor at runtime
- For each executable hook (enabled and without a non-empty `condition`), output the following based on its `optional` flag:
- **Optional hook** (`optional: true`):
```
## Extension Hooks

**Optional Hook**: {extension}
Command: `/{command}`
Description: {description}

Prompt: {prompt}
To execute: `/{command}`
```
- **Mandatory hook** (`optional: false`):
```
## Extension Hooks

**Automatic Hook**: {extension}
Executing: `/{command}`
EXECUTE_COMMAND: {command}
```

**CRITICAL**: You MUST execute the command above immediately after outputting it. Do not end the response until the hook has been triggered.
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently

**NOTE:** The script creates and checks out the new branch and initializes the spec file before writing.

## Quick Guidelines
Expand Down
4 changes: 4 additions & 0 deletions templates/commands/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ You **MUST** consider the user input before proceeding (if not empty).

Wait for the result of the hook command before proceeding to the Outline.
```

**CRITICAL**: You MUST execute the command above immediately after outputting it. Do not proceed until the hook has been triggered.
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently

## Outline
Expand Down Expand Up @@ -124,6 +126,8 @@ You **MUST** consider the user input before proceeding (if not empty).
Executing: `/{command}`
EXECUTE_COMMAND: {command}
```

**CRITICAL**: You MUST execute the command above immediately after outputting it. Do not end the response until the hook has been triggered.
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently

Context for task generation: {ARGS}
Expand Down
Loading