Skip to content

Commit a0f339c

Browse files
mnriemdhilipkumars
authored andcommitted
fix: wire after_tasks and after_implement hook events into command templates (github#1702)
* fix: wire after_tasks and after_implement hook events into command templates (github#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 1141b62 commit a0f339c

7 files changed

Lines changed: 196 additions & 0 deletions

File tree

templates/commands/implement.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,40 @@ $ARGUMENTS
3535

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

38+
## Pre-Execution Checks
39+
40+
**Check for extension hooks (before implementation)**:
41+
- Check if `.specify/extensions.yml` exists in the project root.
42+
- If it exists, read it and look for entries under the `hooks.before_implement` key
43+
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
44+
- Filter to only hooks where `enabled: true`
45+
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
46+
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
47+
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
48+
- For each executable hook, output the following based on its `optional` flag:
49+
- **Optional hook** (`optional: true`):
50+
```
51+
## Extension Hooks
52+
53+
**Optional Pre-Hook**: {extension}
54+
Command: `/{command}`
55+
Description: {description}
56+
57+
Prompt: {prompt}
58+
To execute: `/{command}`
59+
```
60+
- **Mandatory hook** (`optional: false`):
61+
```
62+
## Extension Hooks
63+
64+
**Automatic Pre-Hook**: {extension}
65+
Executing: `/{command}`
66+
EXECUTE_COMMAND: {command}
67+
68+
Wait for the result of the hook command before proceeding to the Outline.
69+
```
70+
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
71+
3872
## Multi-Repository Workspace Detection
3973
4074
**Before executing any tasks:**
@@ -51,6 +85,7 @@ You **MUST** consider the user input before proceeding (if not empty).
5185
- **Single-repo**: Work continues on the current branch in the active repository
5286
- **Multi-repo**: Create matching feature branches in all affected repositories using the same branch name as the `*-document` repository
5387
88+
5489
## Outline
5590
5691
0. **Multi-Repository Branch Setup**:
@@ -239,3 +274,32 @@ You **MUST** consider the user input before proceeding (if not empty).
239274
**IMPORTANT**: Do NOT commit any changes. All changes must remain uncommitted (staged or unstaged) for user review before committing. This applies to both single-repository and multi-repository implementations.
240275
241276
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.
277+
278+
10. **Check for extension hooks**: After completion validation, check if `.specify/extensions.yml` exists in the project root.
279+
- If it exists, read it and look for entries under the `hooks.after_implement` key
280+
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
281+
- Filter to only hooks where `enabled: true`
282+
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
283+
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
284+
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
285+
- For each executable hook, output the following based on its `optional` flag:
286+
- **Optional hook** (`optional: true`):
287+
```
288+
## Extension Hooks
289+
290+
**Optional Hook**: {extension}
291+
Command: `/{command}`
292+
Description: {description}
293+
294+
Prompt: {prompt}
295+
To execute: `/{command}`
296+
```
297+
- **Mandatory hook** (`optional: false`):
298+
```
299+
## Extension Hooks
300+
301+
**Automatic Hook**: {extension}
302+
Executing: `/{command}`
303+
EXECUTE_COMMAND: {command}
304+
```
305+
- 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
@@ -68,6 +68,40 @@ $ARGUMENTS
6868

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

71+
## Pre-Execution Checks
72+
73+
**Check for extension hooks (before tasks generation)**:
74+
- Check if `.specify/extensions.yml` exists in the project root.
75+
- If it exists, read it and look for entries under the `hooks.before_tasks` key
76+
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
77+
- Filter to only hooks where `enabled: true`
78+
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
79+
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
80+
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
81+
- For each executable hook, output the following based on its `optional` flag:
82+
- **Optional hook** (`optional: true`):
83+
```
84+
## Extension Hooks
85+
86+
**Optional Pre-Hook**: {extension}
87+
Command: `/{command}`
88+
Description: {description}
89+
90+
Prompt: {prompt}
91+
To execute: `/{command}`
92+
```
93+
- **Mandatory hook** (`optional: false`):
94+
```
95+
## Extension Hooks
96+
97+
**Automatic Pre-Hook**: {extension}
98+
Executing: `/{command}`
99+
EXECUTE_COMMAND: {command}
100+
101+
Wait for the result of the hook command before proceeding to the Outline.
102+
```
103+
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
104+
71105
## Outline
72106
73107
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").
@@ -146,6 +180,35 @@ You **MUST** consider the user input before proceeding (if not empty).
146180
- Document contract compatibility tasks
147181
- Recommend coordination points between repositories during implementation
148182
183+
6. **Check for extension hooks**: After tasks.md is generated, check if `.specify/extensions.yml` exists in the project root.
184+
- If it exists, read it and look for entries under the `hooks.after_tasks` key
185+
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
186+
- Filter to only hooks where `enabled: true`
187+
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
188+
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
189+
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
190+
- For each executable hook, output the following based on its `optional` flag:
191+
- **Optional hook** (`optional: true`):
192+
```
193+
## Extension Hooks
194+
195+
**Optional Hook**: {extension}
196+
Command: `/{command}`
197+
Description: {description}
198+
199+
Prompt: {prompt}
200+
To execute: `/{command}`
201+
```
202+
- **Mandatory hook** (`optional: false`):
203+
```
204+
## Extension Hooks
205+
206+
**Automatic Hook**: {extension}
207+
Executing: `/{command}`
208+
EXECUTE_COMMAND: {command}
209+
```
210+
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
211+
149212
Context for task generation: {ARGS}
150213
151214
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)