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#1702mnriem wants to merge 1 commit intogithub:mainfrom
Conversation
…mplates (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.
There was a problem hiding this comment.
Pull request overview
This PR fixes issue #1701 by wiring extension hooks into command templates. The HookExecutor class in extensions.py was fully implemented but never invoked - core command templates had no instructions to check .specify/extensions.yml. This meant hooks silently did nothing after command completion. The fix adds step-by-step instructions to tasks.md and implement.md templates to read the extensions config, filter enabled hooks, evaluate conditions, and output the appropriate hook messages (including EXECUTE_COMMAND: markers for mandatory hooks).
Changes:
- Added hook checking instructions to
tasks.md(step 6) andimplement.md(step 10) templates - Version bump from 0.1.6 to 0.1.7 with corresponding changelog entry
- Templates now parse
.specify/extensions.ymland output formatted hook messages for AI agents to execute
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| templates/commands/tasks.md | Added step 6 to check for after_tasks hooks after task generation completes |
| templates/commands/implement.md | Added step 10 to check for after_implement hooks after implementation validation |
| pyproject.toml | Version bump from 0.1.6 to 0.1.7 |
| CHANGELOG.md | Added entry for version 0.1.7 documenting the hook wiring fix |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| 6. **Check for extension hooks**: After tasks.md is generated, check if `.specify/extensions.yml` exists in the project root. | ||
| - If it exists, read it and look for entries under the `hooks.after_tasks` key | ||
| - Filter to only hooks where `enabled: true` | ||
| - For each remaining hook, evaluate any `condition` value; skip the hook if the condition is not met |
There was a problem hiding this comment.
The instruction to "evaluate any condition value" is ambiguous and lacks implementation details. The HookExecutor class supports complex condition expressions like "config.project.key is set" and "env.VAR_NAME == 'value'" (see extensions.py lines 1567-1640), but the template doesn't explain how AI agents should evaluate these conditions. Consider either:
- Adding a detailed explanation of the condition syntax and evaluation rules
- Simplifying to just state "skip hooks with non-null condition values for now" if condition evaluation is meant to be handled by HookExecutor
- Providing examples of common condition patterns
| - For each remaining hook, evaluate any `condition` value; skip the hook if the condition is not met | |
| - Do not attempt to interpret or evaluate hook `condition` expressions; if a hook has a non-null `condition` value, skip it and assume the HookExecutor will handle condition evaluation |
| 10. **Check for extension hooks**: After completion validation, check if `.specify/extensions.yml` exists in the project root. | ||
| - If it exists, read it and look for entries under the `hooks.after_implement` key | ||
| - Filter to only hooks where `enabled: true` | ||
| - For each remaining hook, evaluate any `condition` value; skip the hook if the condition is not met |
There was a problem hiding this comment.
The instruction to "evaluate any condition value" is ambiguous and lacks implementation details. The HookExecutor class supports complex condition expressions like "config.project.key is set" and "env.VAR_NAME == 'value'" (see extensions.py lines 1567-1640), but the template doesn't explain how AI agents should evaluate these conditions. Consider either:
- Adding a detailed explanation of the condition syntax and evaluation rules
- Simplifying to just state "skip hooks with non-null condition values for now" if condition evaluation is meant to be handled by HookExecutor
- Providing examples of common condition patterns
| - For each remaining hook, evaluate any `condition` value; skip the hook if the condition is not met | |
| - For each remaining hook, do **not** attempt to interpret complex `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 for now and leave condition evaluation to the HookExecutor implementation |
| - Filter to only hooks where `enabled: true` | ||
| - For each remaining hook, evaluate any `condition` value; skip the hook if the condition is not met | ||
| - For each executable hook, output the following based on its `optional` flag: | ||
| - **Optional hook** (`optional: true`): |
There was a problem hiding this comment.
The template doesn't provide guidance for handling malformed YAML or parsing errors when reading .specify/extensions.yml. Consider adding a note about gracefully handling parse failures, for example: "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, evaluate any `condition` value; skip the hook if the condition is not met | |
| - For each executable hook, output the following based on its `optional` flag: | |
| - **Optional hook** (`optional: true`): | |
| - 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, evaluate any `condition` value; skip the hook if the condition is not met | |
| - For each executable hook, output the following based on its `optional` flag: |
| 10. **Check for extension hooks**: After completion validation, check if `.specify/extensions.yml` exists in the project root. | ||
| - If it exists, read it and look for entries under the `hooks.after_implement` key |
There was a problem hiding this comment.
The template doesn't provide guidance for handling malformed YAML or parsing errors when reading .specify/extensions.yml. Consider adding a note about gracefully handling parse failures, for example: "If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally."
Summary
Fixes #1701.
The HookExecutor class in extensions.py was fully implemented but check_hooks_for_event() was never invoked - the core command templates had no instructions to check .specify/extensions.yml. Hooks silently did nothing after every command run.
Changes
Testing