Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ Recent changes to the Specify CLI and templates are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.7] - 2026-02-26

### 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.
- `templates/commands/implement.md` does the same for `after_implement` hooks
- The `HookExecutor` backend in `extensions.py` was already complete; only the template wiring was missing

## [0.1.6] - 2026-02-23

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "specify-cli"
version = "0.1.6"
version = "0.1.7"
description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)."
requires-python = ">=3.11"
dependencies = [
Expand Down
29 changes: 29 additions & 0 deletions templates/commands/implement.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,32 @@ You **MUST** consider the user input before proceeding (if not empty).
- Report final status with summary of completed work

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.

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
- 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
Comment on lines +144 to +146
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.
- 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}
Comment on lines +149 to +154
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.

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
29 changes: 29 additions & 0 deletions templates/commands/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,35 @@ You **MUST** consider the user input before proceeding (if not empty).
- Suggested MVP scope (typically just User Story 1)
- Format validation: Confirm ALL tasks follow the checklist format (checkbox, ID, labels, file paths)

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
- 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
Comment on lines +70 to +72
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.
- 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}`
```
Comment on lines +75 to +84
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.
- **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

Context for task generation: {ARGS}

The tasks.md should be immediately executable - each task must be specific enough that an LLM can complete it without additional context.
Expand Down