Skip to content

Commit d0a112c

Browse files
mnriemdhilipkumars
andauthored
fix: wire after_tasks and after_implement hook events into command templates (#1702)
* fix: wire after_tasks and after_implement hook events into command templates (#1701) The HookExecutor backend in extensions.py was fully implemented but check_hooks_for_event() was never called by anything — the core command templates had no instructions to check .specify/extensions.yml. Add a final step to templates/commands/tasks.md (step 6, after_tasks) and templates/commands/implement.md (step 10, after_implement) that instructs the AI agent to: - Read .specify/extensions.yml if it exists - Filter hooks.{event} to enabled: true entries - Evaluate any condition fields and skip non-matching hooks - Output the RFC-specified hook message format, including EXECUTE_COMMAND: markers for mandatory (optional: false) hooks Bumps version to 0.1.7. * fix: clarify hook condition handling and add YAML error guidance in templates - Replace ambiguous "evaluate any condition value" instruction with explicit guidance to skip hooks with non-empty conditions, deferring evaluation to HookExecutor - Add instruction to skip hook checking silently if extensions.yml cannot be parsed or is invalid * Fix/extension hooks not triggered (#1) * feat(templates): implement before-hooks check as pre-execution phase * test(hooks): create scenario for LLMs/Agents on hooks --------- Co-authored-by: Dhilip <s.dhilipkumar@gmail.com>
1 parent c84756b commit d0a112c

File tree

7 files changed

+195
-0
lines changed

7 files changed

+195
-0
lines changed

templates/commands/implement.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,40 @@ $ARGUMENTS
1313

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

16+
## Pre-Execution Checks
17+
18+
**Check for extension hooks (before implementation)**:
19+
- Check if `.specify/extensions.yml` exists in the project root.
20+
- If it exists, read it and look for entries under the `hooks.before_implement` key
21+
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
22+
- Filter to only hooks where `enabled: true`
23+
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
24+
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
25+
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
26+
- For each executable hook, output the following based on its `optional` flag:
27+
- **Optional hook** (`optional: true`):
28+
```
29+
## Extension Hooks
30+
31+
**Optional Pre-Hook**: {extension}
32+
Command: `/{command}`
33+
Description: {description}
34+
35+
Prompt: {prompt}
36+
To execute: `/{command}`
37+
```
38+
- **Mandatory hook** (`optional: false`):
39+
```
40+
## Extension Hooks
41+
42+
**Automatic Pre-Hook**: {extension}
43+
Executing: `/{command}`
44+
EXECUTE_COMMAND: {command}
45+
46+
Wait for the result of the hook command before proceeding to the Outline.
47+
```
48+
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
49+
1650
## Outline
1751
1852
1. Run `{SCRIPT}` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute. 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").
@@ -136,3 +170,32 @@ You **MUST** consider the user input before proceeding (if not empty).
136170
- Report final status with summary of completed work
137171
138172
Note: This command assumes a complete task breakdown exists in tasks.md. If tasks are incomplete or missing, suggest running `/speckit.tasks` first to regenerate the task list.
173+
174+
10. **Check for extension hooks**: After completion validation, check if `.specify/extensions.yml` exists in the project root.
175+
- If it exists, read it and look for entries under the `hooks.after_implement` key
176+
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
177+
- Filter to only hooks where `enabled: true`
178+
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
179+
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
180+
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
181+
- For each executable hook, output the following based on its `optional` flag:
182+
- **Optional hook** (`optional: true`):
183+
```
184+
## Extension Hooks
185+
186+
**Optional Hook**: {extension}
187+
Command: `/{command}`
188+
Description: {description}
189+
190+
Prompt: {prompt}
191+
To execute: `/{command}`
192+
```
193+
- **Mandatory hook** (`optional: false`):
194+
```
195+
## Extension Hooks
196+
197+
**Automatic Hook**: {extension}
198+
Executing: `/{command}`
199+
EXECUTE_COMMAND: {command}
200+
```
201+
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently

templates/commands/tasks.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,40 @@ $ARGUMENTS
2222

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

25+
## Pre-Execution Checks
26+
27+
**Check for extension hooks (before tasks generation)**:
28+
- Check if `.specify/extensions.yml` exists in the project root.
29+
- If it exists, read it and look for entries under the `hooks.before_tasks` key
30+
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
31+
- Filter to only hooks where `enabled: true`
32+
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
33+
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
34+
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
35+
- For each executable hook, output the following based on its `optional` flag:
36+
- **Optional hook** (`optional: true`):
37+
```
38+
## Extension Hooks
39+
40+
**Optional Pre-Hook**: {extension}
41+
Command: `/{command}`
42+
Description: {description}
43+
44+
Prompt: {prompt}
45+
To execute: `/{command}`
46+
```
47+
- **Mandatory hook** (`optional: false`):
48+
```
49+
## Extension Hooks
50+
51+
**Automatic Pre-Hook**: {extension}
52+
Executing: `/{command}`
53+
EXECUTE_COMMAND: {command}
54+
55+
Wait for the result of the hook command before proceeding to the Outline.
56+
```
57+
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
58+
2559
## Outline
2660
2761
1. **Setup**: Run `{SCRIPT}` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute. 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").
@@ -63,6 +97,35 @@ You **MUST** consider the user input before proceeding (if not empty).
6397
- Suggested MVP scope (typically just User Story 1)
6498
- Format validation: Confirm ALL tasks follow the checklist format (checkbox, ID, labels, file paths)
6599
100+
6. **Check for extension hooks**: After tasks.md is generated, check if `.specify/extensions.yml` exists in the project root.
101+
- If it exists, read it and look for entries under the `hooks.after_tasks` key
102+
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
103+
- Filter to only hooks where `enabled: true`
104+
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
105+
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
106+
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
107+
- For each executable hook, output the following based on its `optional` flag:
108+
- **Optional hook** (`optional: true`):
109+
```
110+
## Extension Hooks
111+
112+
**Optional Hook**: {extension}
113+
Command: `/{command}`
114+
Description: {description}
115+
116+
Prompt: {prompt}
117+
To execute: `/{command}`
118+
```
119+
- **Mandatory hook** (`optional: false`):
120+
```
121+
## Extension Hooks
122+
123+
**Automatic Hook**: {extension}
124+
Executing: `/{command}`
125+
EXECUTE_COMMAND: {command}
126+
```
127+
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
128+
66129
Context for task generation: {ARGS}
67130
68131
The tasks.md should be immediately executable - each task must be specific enough that an LLM can complete it without additional context.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
hooks:
2+
before_implement:
3+
- id: pre_test
4+
enabled: true
5+
optional: false
6+
extension: "test-extension"
7+
command: "pre_implement_test"
8+
description: "Test before implement hook execution"
9+
10+
after_implement:
11+
- id: post_test
12+
enabled: true
13+
optional: true
14+
extension: "test-extension"
15+
command: "post_implement_test"
16+
description: "Test after implement hook execution"
17+
prompt: "Would you like to run the post-implement test?"
18+
19+
before_tasks:
20+
- id: pre_tasks_test
21+
enabled: true
22+
optional: false
23+
extension: "test-extension"
24+
command: "pre_tasks_test"
25+
description: "Test before tasks hook execution"
26+
27+
after_tasks:
28+
- id: post_tasks_test
29+
enabled: true
30+
optional: true
31+
extension: "test-extension"
32+
command: "post_tasks_test"
33+
description: "Test after tasks hook execution"
34+
prompt: "Would you like to run the post-tasks test?"

tests/hooks/TESTING.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Testing Extension Hooks
2+
3+
This directory contains a mock project to verify that LLM agents correctly identify and execute hook commands defined in `.specify/extensions.yml`.
4+
5+
## Test 1: Testing `before_tasks` and `after_tasks`
6+
7+
1. Open a chat with an LLM (like GitHub Copilot) in this project.
8+
2. Ask it to generate tasks for the current directory:
9+
> "Please follow `/speckit.tasks` for the `./tests/hooks` directory."
10+
3. **Expected Behavior**:
11+
- Before doing any generation, the LLM should notice the `AUTOMATIC Pre-Hook` in `.specify/extensions.yml` under `before_tasks`.
12+
- It should state it is executing `EXECUTE_COMMAND: pre_tasks_test`.
13+
- It should then proceed to read the `.md` docs and produce a `tasks.md`.
14+
- After generation, it should output the optional `after_tasks` hook (`post_tasks_test`) block, asking if you want to run it.
15+
16+
## Test 2: Testing `before_implement` and `after_implement`
17+
18+
*(Requires `tasks.md` from Test 1 to exist)*
19+
20+
1. In the same (or new) chat, ask the LLM to implement the tasks:
21+
> "Please follow `/speckit.implement` for the `./tests/hooks` directory."
22+
2. **Expected Behavior**:
23+
- The LLM should first check for `before_implement` hooks.
24+
- It should state it is executing `EXECUTE_COMMAND: pre_implement_test` BEFORE doing any actual task execution.
25+
- It should evaluate the checklists and execute the code writing tasks.
26+
- Upon completion, it should output the optional `after_implement` hook (`post_implement_test`) block.
27+
28+
## How it works
29+
30+
The templates for these commands in `templates/commands/tasks.md` and `templates/commands/implement.md` contains strict ordered lists. The new `before_*` hooks are explicitly formulated in a **Pre-Execution Checks** section prior to the outline to ensure they're evaluated first without breaking template step numbers.

tests/hooks/plan.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Test Setup for Hooks
2+
3+
This feature is designed to test if LLMs correctly invoke Spec Kit extensions hooks when generating tasks and implementing code.

tests/hooks/spec.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- **User Story 1:** I want a test script that prints "Hello hooks!".

tests/hooks/tasks.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- [ ] T001 [US1] Create script that prints 'Hello hooks!' in hello.py

0 commit comments

Comments
 (0)