Skip to content

fix: wire after_tasks and after_implement hook events into command templates#1702

Open
mnriem wants to merge 2 commits intogithub:mainfrom
mnriem:fix/extension-hooks-not-triggered
Open

fix: wire after_tasks and after_implement hook events into command templates#1702
mnriem wants to merge 2 commits intogithub:mainfrom
mnriem:fix/extension-hooks-not-triggered

Conversation

@mnriem
Copy link
Collaborator

@mnriem mnriem commented Feb 26, 2026

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

  • templates/commands/tasks.md: added step 6 - after_tasks hook check
  • templates/commands/implement.md: added step 10 - after_implement hook check
  • pyproject.toml: version bump 0.1.6 to 0.1.7
  • CHANGELOG.md: entry for [0.1.7]

Testing

  1. Create an extension with an after_tasks hook and install it
  2. Confirm hook is registered in .specify/extensions.yml
  3. Run /speckit.tasks - the hook message should now appear at the end of the output

…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.
Copilot AI review requested due to automatic review settings February 26, 2026 16:39
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) and implement.md (step 10) templates
  • Version bump from 0.1.6 to 0.1.7 with corresponding changelog entry
  • Templates now parse .specify/extensions.yml and 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.

…emplates

- 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
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +70 to +72
- 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
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic will prevent hooks with conditions from ever executing. The instructions tell the AI agent to skip hooks with non-empty conditions and "leave condition evaluation to the HookExecutor implementation", but the HookExecutor is Python backend code that cannot be called from within the template execution context. AI agents executing these template instructions can only follow the instructions provided, not call Python functions.

Since the HookExecutor's condition evaluation logic is well-defined (supporting config.* and env.* patterns via regex), the template should instruct the AI agent to replicate this evaluation logic, or alternatively, hooks with conditions should be treated as executable and let the downstream system handle them. Consider either:

  1. Providing detailed instructions for evaluating common condition patterns (config.key is set, env.VAR == 'value', etc.), or
  2. Treating hooks with conditions as executable (output them) and rely on a downstream hook execution system to evaluate conditions before actual execution
Suggested change
- 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
- Do **not** attempt to interpret or evaluate hook `condition` expressions within this template:
- Treat all hooks that are `enabled: true` as executable for output purposes, regardless of whether a `condition` field is present or non-empty
- Actual evaluation of any `condition` expressions and the decision to run or skip a hook is deferred to the downstream HookExecutor implementation

Copilot uses AI. Check for mistakes.
Comment on lines +144 to +146
- 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
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic will prevent hooks with conditions from ever executing. The instructions tell the AI agent to skip hooks with non-empty conditions and "leave condition evaluation to the HookExecutor implementation", but the HookExecutor is Python backend code that cannot be called from within the template execution context. AI agents executing these template instructions can only follow the instructions provided, not call Python functions.

Since the HookExecutor's condition evaluation logic is well-defined (supporting config.* and env.* patterns via regex), the template should instruct the AI agent to replicate this evaluation logic, or alternatively, hooks with conditions should be treated as executable and let the downstream system handle them. Consider either:

  1. Providing detailed instructions for evaluating common condition patterns (config.key is set, env.VAR == 'value', etc.), or
  2. Treating hooks with conditions as executable (output them) and rely on a downstream hook execution system to evaluate conditions before actual execution
Suggested change
- 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 remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions yourself:
- Treat the hook as executable regardless of whether a `condition` field is present or non-empty
- Rely on the downstream HookExecutor or hook execution system to evaluate any `condition` before actual execution

Copilot uses AI. Check for mistakes.
### Fixed

- **Extension hooks never triggered (#1701)**: Wired `after_tasks` and `after_implement` hook events into their respective command templates
- `templates/commands/tasks.md` now includes a final step that reads `.specify/extensions.yml`, evaluates registered `after_tasks` hooks (respecting `enabled` flag and `condition`), and outputs the correct hook message format — including `EXECUTE_COMMAND:` markers for mandatory hooks
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changelog states that templates evaluate hooks "respecting enabled flag and condition", but the actual template logic (lines 70-72 in tasks.md and lines 144-146 in implement.md) instructs the AI agent to skip hooks with non-empty conditions entirely. This description is inaccurate.

The changelog should either:

  1. State that hooks with conditions are currently not supported/skipped, or
  2. The template logic should be updated to actually evaluate conditions (which would require providing detailed evaluation instructions in the templates)
Suggested change
- `templates/commands/tasks.md` now includes a final step that reads `.specify/extensions.yml`, evaluates registered `after_tasks` hooks (respecting `enabled` flag and `condition`), and outputs the correct hook message format — including `EXECUTE_COMMAND:` markers for mandatory hooks
- `templates/commands/tasks.md` now includes a final step that reads `.specify/extensions.yml`, evaluates registered `after_tasks` hooks that have no `condition` set (respecting the `enabled` flag), skips hooks with a non-empty `condition` (conditions are not yet supported in templates), and outputs the correct hook message format — including `EXECUTE_COMMAND:` markers for mandatory hooks

Copilot uses AI. Check for mistakes.
Comment on lines +75 to +84
```
## Extension Hooks

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

Prompt: {prompt}
To execute: `/{command}`
```
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The template always outputs the Description line even when the description field might be empty or missing, which could lead to awkward output like "Description: " with no content. The backend implementation at src/specify_cli/extensions.py:1670-1671 conditionally includes the description only if it's non-empty.

Consider adding conditional logic: "If description is non-empty, output 'Description: {description}'" to match the backend behavior and avoid empty description lines.

Suggested change
```
## Extension Hooks
**Optional Hook**: {extension}
Command: `/{command}`
Description: {description}
Prompt: {prompt}
To execute: `/{command}`
```
```
## Extension Hooks
**Optional Hook**: {extension}
Command: `/{command}`
{description}
Prompt: {prompt}
To execute: `/{command}`
```

Copilot uses AI. Check for mistakes.
Comment on lines +149 to +154
```
## Extension Hooks

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

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The template always outputs the Description line even when the description field might be empty or missing, which could lead to awkward output like "Description: " with no content. The backend implementation at src/specify_cli/extensions.py:1670-1671 conditionally includes the description only if it's non-empty.

Consider adding conditional logic: "If description is non-empty, output 'Description: {description}'" to match the backend behavior and avoid empty description lines.

Suggested change
```
## Extension Hooks
**Optional Hook**: {extension}
Command: `/{command}`
Description: {description}
- If the hook has a non-empty `description`, include a `Description: {description}` line in the output after the `Command` line; otherwise, omit the description line entirely.
```
## Extension Hooks
**Optional Hook**: {extension}
Command: `/{command}`

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extension hooks (after_tasks, after_implement) are never triggered

2 participants