From 4064b02f164808a72a27509a804e953867f41449 Mon Sep 17 00:00:00 2001 From: DavidNiu Date: Wed, 11 Mar 2026 14:35:21 +0800 Subject: [PATCH 1/4] feat: wire before/after hooks for specify and plan commands (#1788) --- CHANGELOG.md | 1 + extensions/EXTENSION-DEVELOPMENT-GUIDE.md | 6 +++ extensions/EXTENSION-USER-GUIDE.md | 22 ++++++++ src/specify_cli/extensions.py | 4 +- templates/commands/plan.md | 63 +++++++++++++++++++++++ templates/commands/specify.md | 63 +++++++++++++++++++++++ 6 files changed, 157 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45b662160b..b5c41c3d45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- feat: wire `before_specify`, `after_specify`, `before_plan`, and `after_plan` hook events into command templates (#1788) - feat(extensions): support `.extensionignore` to exclude files/folders during `specify extension add` (#1781) ## [0.2.0] - 2026-03-09 diff --git a/extensions/EXTENSION-DEVELOPMENT-GUIDE.md b/extensions/EXTENSION-DEVELOPMENT-GUIDE.md index feea7b2782..644f2e743d 100644 --- a/extensions/EXTENSION-DEVELOPMENT-GUIDE.md +++ b/extensions/EXTENSION-DEVELOPMENT-GUIDE.md @@ -196,7 +196,13 @@ 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 +- `before_implement`: Before `/speckit.implement` starts - `after_implement`: After `/speckit.implement` completes (future) Hook object: diff --git a/extensions/EXTENSION-USER-GUIDE.md b/extensions/EXTENSION-USER-GUIDE.md index e551809ef1..4e1e5736d4 100644 --- a/extensions/EXTENSION-USER-GUIDE.md +++ b/extensions/EXTENSION-USER-GUIDE.md @@ -388,6 +388,28 @@ settings: # Hook configuration hooks: + before_specify: + - extension: contextual-research + command: speckit.research.pre-spec + enabled: true + optional: true + prompt: "Perform pre-specification research?" + after_specify: + - extension: linter + command: speckit.linter.check-spec + enabled: true + optional: false + before_plan: + - extension: setup-env + command: speckit.env.prepare + enabled: true + optional: false + after_plan: + - extension: architect + command: speckit.architect.validate-plan + enabled: true + optional: true + prompt: "Validate architecture before proceeding?" after_tasks: - extension: jira command: speckit.jira.specstoissues diff --git a/src/specify_cli/extensions.py b/src/specify_cli/extensions.py index 53777bd6b2..1725c625d2 100644 --- a/src/specify_cli/extensions.py +++ b/src/specify_cli/extensions.py @@ -1884,7 +1884,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 @@ -2036,7 +2036,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: diff --git a/templates/commands/plan.md b/templates/commands/plan.md index 00e83eabd0..6c3c2e41a7 100644 --- a/templates/commands/plan.md +++ b/templates/commands/plan.md @@ -24,6 +24,40 @@ $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 +- Filter to only hooks where `enabled: true` +- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions: + - If the hook has no `condition` field, or it is null/empty, treat the hook as executable + - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation +- For each executable hook, 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. + ``` +- 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"). @@ -41,6 +75,35 @@ 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 + - Filter to only hooks where `enabled: true` + - For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions: + - If the hook has no `condition` field, or it is null/empty, treat the hook as executable + - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation + - For each executable 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} + ``` + - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently + ## Phases ### Phase 0: Outline & Research diff --git a/templates/commands/specify.md b/templates/commands/specify.md index d66f3fcca0..7cf3653674 100644 --- a/templates/commands/specify.md +++ b/templates/commands/specify.md @@ -21,6 +21,40 @@ $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 +- Filter to only hooks where `enabled: true` +- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions: + - If the hook has no `condition` field, or it is null/empty, treat the hook as executable + - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation +- For each executable hook, 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. + ``` +- 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. @@ -176,6 +210,35 @@ 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 + - Filter to only hooks where `enabled: true` + - For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions: + - If the hook has no `condition` field, or it is null/empty, treat the hook as executable + - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation + - For each executable 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} + ``` + - 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. ## General Guidelines From 26eb79396a8e4f40e5bd768eab53387fbd23ecba Mon Sep 17 00:00:00 2001 From: DavidNiu Date: Fri, 13 Mar 2026 10:59:49 +0800 Subject: [PATCH 2/4] feat: align extension hooks documentation and templates for specify/plan (#1788) - Update EXTENSION-API-REFERENCE.md to reflect current hook implementation status. - Add before_tasks and before_implement hooks to EXTENSION-DEVELOPMENT-GUIDE.md. - Standardize CRITICAL execution instructions for mandatory hooks across all templates (specify, plan, tasks, implement). - Sync documentation versions and pyproject.toml to 0.2.1 to match upstream. - Update Last Updated dates in extension guides. --- extensions/EXTENSION-API-REFERENCE.md | 29 +++++++++++++++++++--- extensions/EXTENSION-DEVELOPMENT-GUIDE.md | 18 ++++++++++++-- extensions/EXTENSION-USER-GUIDE.md | 30 +++-------------------- pyproject.toml | 2 +- templates/commands/implement.md | 4 +++ templates/commands/plan.md | 4 +++ templates/commands/specify.md | 4 +++ templates/commands/tasks.md | 4 +++ 8 files changed, 62 insertions(+), 33 deletions(-) diff --git a/extensions/EXTENSION-API-REFERENCE.md b/extensions/EXTENSION-API-REFERENCE.md index bd25d4bb49..cdc8e94973 100644 --- a/extensions/EXTENSION-API-REFERENCE.md +++ b/extensions/EXTENSION-API-REFERENCE.md @@ -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 @@ -551,10 +557,16 @@ 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 @@ -562,6 +574,13 @@ Standard events (defined by core): ```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 @@ -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. ``` --- @@ -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.2.1* diff --git a/extensions/EXTENSION-DEVELOPMENT-GUIDE.md b/extensions/EXTENSION-DEVELOPMENT-GUIDE.md index 644f2e743d..c6123e1e69 100644 --- a/extensions/EXTENSION-DEVELOPMENT-GUIDE.md +++ b/extensions/EXTENSION-DEVELOPMENT-GUIDE.md @@ -203,7 +203,7 @@ Available hook points: - `before_tasks`: Before `/speckit.tasks` starts - `after_tasks`: After `/speckit.tasks` completes - `before_implement`: Before `/speckit.implement` starts -- `after_implement`: After `/speckit.implement` completes (future) +- `after_implement`: After `/speckit.implement` completes Hook object: @@ -645,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 diff --git a/extensions/EXTENSION-USER-GUIDE.md b/extensions/EXTENSION-USER-GUIDE.md index 4e1e5736d4..618b8e00e3 100644 --- a/extensions/EXTENSION-USER-GUIDE.md +++ b/extensions/EXTENSION-USER-GUIDE.md @@ -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) ### Check Your Version ```bash specify version -# Should show 0.1.0 or higher +# Should show 0.2.1 or higher ``` ### First Extension @@ -388,28 +388,6 @@ settings: # Hook configuration hooks: - before_specify: - - extension: contextual-research - command: speckit.research.pre-spec - enabled: true - optional: true - prompt: "Perform pre-specification research?" - after_specify: - - extension: linter - command: speckit.linter.check-spec - enabled: true - optional: false - before_plan: - - extension: setup-env - command: speckit.env.prepare - enabled: true - optional: false - after_plan: - - extension: architect - command: speckit.architect.validate-plan - enabled: true - optional: true - prompt: "Validate architecture before proceeding?" after_tasks: - extension: jira command: speckit.jira.specstoissues @@ -988,5 +966,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.2.1* diff --git a/pyproject.toml b/pyproject.toml index 0bb55ceaf2..04a6791ad9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "specify-cli" -version = "0.2.0" +version = "0.2.1" description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)." requires-python = ">=3.11" dependencies = [ diff --git a/templates/commands/implement.md b/templates/commands/implement.md index da58027d06..218c23c6f5 100644 --- a/templates/commands/implement.md +++ b/templates/commands/implement.md @@ -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 @@ -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 diff --git a/templates/commands/plan.md b/templates/commands/plan.md index 6c3c2e41a7..be0148867e 100644 --- a/templates/commands/plan.md +++ b/templates/commands/plan.md @@ -56,6 +56,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 @@ -102,6 +104,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 ## Phases diff --git a/templates/commands/specify.md b/templates/commands/specify.md index 7cf3653674..77c08da0c5 100644 --- a/templates/commands/specify.md +++ b/templates/commands/specify.md @@ -53,6 +53,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 @@ -237,6 +239,8 @@ Given that feature description, do this: 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. diff --git a/templates/commands/tasks.md b/templates/commands/tasks.md index 9ad199634d..3e99304190 100644 --- a/templates/commands/tasks.md +++ b/templates/commands/tasks.md @@ -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 @@ -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} From 21a142a52ad7f50abd77c4cf1a849bdca687894f Mon Sep 17 00:00:00 2001 From: davidniu-0914 Date: Mon, 16 Mar 2026 10:15:52 +0800 Subject: [PATCH 3/4] fix(commands): align hook filtering with HookExecutor defaults Updated the prompt to treat omitted `enabled` flags as true and pass all hooks (including those with `condition` fields) to the HookExecutor, correctly addressing the PR review feedback. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- templates/commands/specify.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/commands/specify.md b/templates/commands/specify.md index 77c08da0c5..0de0d41821 100644 --- a/templates/commands/specify.md +++ b/templates/commands/specify.md @@ -215,11 +215,11 @@ Given that feature description, do this: 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 - - Filter to only hooks where `enabled: true` - - For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions: - - If the hook has no `condition` field, or it is null/empty, treat the hook as executable - - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation + - 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** filter hooks based on the presence or contents of `condition`. Condition evaluation is handled by the `HookExecutor.should_execute_hook` implementation. + - Treat all hooks that are not explicitly disabled (that is, all hooks where `enabled` is not `false`) as executable candidates. - For each executable hook, output the following based on its `optional` flag: + - **Optional hook** (`optional: true`): - **Optional hook** (`optional: true`): ``` ## Extension Hooks From 30edb223275ce97dcee516b8003a535b561baea5 Mon Sep 17 00:00:00 2001 From: DavidNiu Date: Mon, 16 Mar 2026 10:32:31 +0800 Subject: [PATCH 4/4] fix(commands): align hook filtering with HookExecutor defaults Updated the prompt to treat omitted `enabled` flags as true and pass all hooks (including those with `condition` fields) to the HookExecutor, correctly addressing the PR review feedback. --- templates/commands/plan.md | 14 ++++++-------- templates/commands/specify.md | 8 +++----- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/templates/commands/plan.md b/templates/commands/plan.md index be0148867e..6c613ec794 100644 --- a/templates/commands/plan.md +++ b/templates/commands/plan.md @@ -30,10 +30,9 @@ You **MUST** consider the user input before proceeding (if not empty). - 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 -- Filter to only hooks where `enabled: true` -- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions: - - If the hook has no `condition` field, or it is null/empty, treat the hook as executable - - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation +- 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 all enabled hooks as executable for the purposes of this plan, regardless of whether they define a `condition` - For each executable hook, output the following based on its `optional` flag: - **Optional hook** (`optional: true`): ``` @@ -80,10 +79,9 @@ You **MUST** consider the user input before proceeding (if not empty). 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 - - Filter to only hooks where `enabled: true` - - For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions: - - If the hook has no `condition` field, or it is null/empty, treat the hook as executable - - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation + - 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** filter hooks based on the presence or contents of `condition`. Condition evaluation is handled by the `HookExecutor.should_execute_hook` implementation. + - Treat all hooks that are not explicitly disabled (that is, all hooks where `enabled` is not `false`) as executable candidates. - For each executable hook, output the following based on its `optional` flag: - **Optional hook** (`optional: true`): ``` diff --git a/templates/commands/specify.md b/templates/commands/specify.md index 3ab674eb25..eb2d874c89 100644 --- a/templates/commands/specify.md +++ b/templates/commands/specify.md @@ -27,10 +27,9 @@ You **MUST** consider the user input before proceeding (if not empty). - 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 -- Filter to only hooks where `enabled: true` -- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions: - - If the hook has no `condition` field, or it is null/empty, treat the hook as executable - - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation +- 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 all enabled hooks as executable for the purposes of this plan, regardless of whether they define a `condition` - For each executable hook, output the following based on its `optional` flag: - **Optional hook** (`optional: true`): ``` @@ -219,7 +218,6 @@ Given that feature description, do this: - Do **not** attempt to interpret or evaluate hook `condition` expressions, and do **not** filter hooks based on the presence or contents of `condition`. Condition evaluation is handled by the `HookExecutor.should_execute_hook` implementation. - Treat all hooks that are not explicitly disabled (that is, all hooks where `enabled` is not `false`) as executable candidates. - For each executable hook, output the following based on its `optional` flag: - - **Optional hook** (`optional: true`): - **Optional hook** (`optional: true`): ``` ## Extension Hooks