diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b1b6a47a..367b3e6d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added - +- feat: wire `before_specify`, `after_specify`, `before_plan`, and `after_plan` hook events into command templates (#1788) - feat(presets): Pluggable preset system with preset catalog and template resolver - Preset manifest (`preset.yml`) with validation for artifact, command, and script types - `PresetManifest`, `PresetRegistry`, `PresetManager`, `PresetCatalog`, `PresetResolver` classes in `src/specify_cli/presets.py` @@ -139,19 +139,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - feat: add Tabnine CLI agent support - **Multi-Catalog Support (#1707)**: Extension catalog system now supports multiple active catalogs simultaneously via a catalog stack - - New `specify extension catalog list` command lists all active catalogs with name, URL, priority, and `install_allowed` status - - New `specify extension catalog add` and `specify extension catalog remove` commands for project-scoped catalog management - - Default built-in stack includes `catalog.json` (default, installable) and `catalog.community.json` (community, discovery only) — community extensions are now surfaced in search results out of the box - - `specify extension search` aggregates results across all active catalogs, annotating each result with source catalog - - `specify extension add` enforces `install_allowed` policy — extensions from discovery-only catalogs cannot be installed directly - - Project-level `.specify/extension-catalogs.yml` and user-level `~/.specify/extension-catalogs.yml` config files supported, with project-level taking precedence - - `SPECKIT_CATALOG_URL` environment variable still works for backward compatibility (replaces full stack with single catalog) - - All catalog URLs require HTTPS (HTTP allowed for localhost development) - - New `CatalogEntry` dataclass in `extensions.py` for catalog stack representation - - Per-URL hash-based caching for non-default catalogs; legacy cache preserved for default catalog - - Higher-priority catalogs win on merge conflicts (same extension id in multiple catalogs) - - 13 new tests covering catalog stack resolution, merge conflicts, URL validation, and `install_allowed` enforcement - - Updated RFC, Extension User Guide, and Extension API Reference documentation + - New `specify extension catalog list` command lists all active catalogs with name, URL, priority, and `install_allowed` status + - New `specify extension catalog add` and `specify extension catalog remove` commands for project-scoped catalog management + - Default built-in stack includes `catalog.json` (default, installable) and `catalog.community.json` (community, discovery only) — community extensions are now surfaced in search results out of the box + - `specify extension search` aggregates results across all active catalogs, annotating each result with source catalog + - `specify extension add` enforces `install_allowed` policy — extensions from discovery-only catalogs cannot be installed directly + - Project-level `.specify/extension-catalogs.yml` and user-level `~/.specify/extension-catalogs.yml` config files supported, with project-level taking precedence + - `SPECKIT_CATALOG_URL` environment variable still works for backward compatibility (replaces full stack with single catalog) + - All catalog URLs require HTTPS (HTTP allowed for localhost development) + - New `CatalogEntry` dataclass in `extensions.py` for catalog stack representation + - Per-URL hash-based caching for non-default catalogs; legacy cache preserved for default catalog + - Higher-priority catalogs win on merge conflicts (same extension id in multiple catalogs) + - 13 new tests covering catalog stack resolution, merge conflicts, URL validation, and `install_allowed` enforcement + - Updated RFC, Extension User Guide, and Extension API Reference documentation ## [0.1.13] - 2026-03-03 @@ -177,9 +177,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - **Copilot Extension Commands Not Visible**: Fixed extension commands not appearing in GitHub Copilot when installed via `specify extension add --dev` - - Changed Copilot file extension from `.md` to `.agent.md` in `CommandRegistrar.AGENT_CONFIGS` so Copilot recognizes agent files - - Added generation of companion `.prompt.md` files in `.github/prompts/` during extension command registration, matching the release packaging behavior - - Added cleanup of `.prompt.md` companion files when removing extensions via `specify extension remove` + - Changed Copilot file extension from `.md` to `.agent.md` in `CommandRegistrar.AGENT_CONFIGS` so Copilot recognizes agent files + - Added generation of companion `.prompt.md` files in `.github/prompts/` during extension command registration, matching the release packaging behavior + - Added cleanup of `.prompt.md` companion files when removing extensions via `specify extension remove` - Fixed a syntax regression in `src/specify_cli/__init__.py` in `_build_ai_assistant_help()` that broke `ruff` and `pytest` collection in CI. ## [0.1.12] - 2026-03-02 @@ -195,11 +195,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - **Version Sync Issue (#1721)**: Fixed version mismatch between `pyproject.toml` and git release tags - - Split release process into two workflows: `release-trigger.yml` for version management and `release.yml` for artifact building - - Version bump now happens BEFORE tag creation, ensuring tags point to commits with correct version - - Supports both manual version specification and auto-increment (patch version) - - Git tags now accurately reflect the version in `pyproject.toml` at that commit - - Prevents confusion when installing from source + - Split release process into two workflows: `release-trigger.yml` for version management and `release.yml` for artifact building + - Version bump now happens BEFORE tag creation, ensuring tags point to commits with correct version + - Supports both manual version specification and auto-increment (patch version) + - Git tags now accurately reflect the version in `pyproject.toml` at that commit + - Prevents confusion when installing from source ## [0.1.9] - 2026-02-28 @@ -234,20 +234,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - **Parameter Ordering Issues (#1641)**: Fixed CLI parameter parsing issue where option flags were incorrectly consumed as values for preceding options - - Added validation to detect when `--ai` or `--ai-commands-dir` incorrectly consume following flags like `--here` or `--ai-skills` - - Now provides clear error messages: "Invalid value for --ai: '--here'" - - Includes helpful hints suggesting proper usage and listing available agents - - Commands like `specify init --ai-skills --ai --here` now fail with actionable feedback instead of confusing "Must specify project name" errors - - Added comprehensive test suite (5 new tests) to prevent regressions + - Added validation to detect when `--ai` or `--ai-commands-dir` incorrectly consume following flags like `--here` or `--ai-skills` + - Now provides clear error messages: "Invalid value for --ai: '--here'" + - Includes helpful hints suggesting proper usage and listing available agents + - Commands like `specify init --ai-skills --ai --here` now fail with actionable feedback instead of confusing "Must specify project name" errors + - Added comprehensive test suite (5 new tests) to prevent regressions ## [0.1.5] - 2026-02-21 ### Fixed - **AI Skills Installation Bug (#1658)**: Fixed `--ai-skills` flag not generating skill files for GitHub Copilot and other agents with non-standard command directory structures - - Added `commands_subdir` field to `AGENT_CONFIG` to explicitly specify the subdirectory name for each agent - - Affected agents now work correctly: copilot (`.github/agents/`), opencode (`.opencode/command/`), windsurf (`.windsurf/workflows/`), codex (`.codex/prompts/`), kilocode (`.kilocode/workflows/`), q (`.amazonq/prompts/`), and agy (`.agent/workflows/`) - - The `install_ai_skills()` function now uses the correct path for all agents instead of assuming `commands/` for everyone + - Added `commands_subdir` field to `AGENT_CONFIG` to explicitly specify the subdirectory name for each agent + - Affected agents now work correctly: copilot (`.github/agents/`), opencode (`.opencode/command/`), windsurf (`.windsurf/workflows/`), codex (`.codex/prompts/`), kilocode (`.kilocode/workflows/`), q (`.amazonq/prompts/`), and agy (`.agent/workflows/`) + - The `install_ai_skills()` function now uses the correct path for all agents instead of assuming `commands/` for everyone ## [0.1.4] - 2026-02-20 @@ -260,10 +260,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - **Generic Agent Support**: Added `--ai generic` option for unsupported AI agents ("bring your own agent") - - Requires `--ai-commands-dir ` to specify where the agent reads commands from - - Generates Markdown commands with `$ARGUMENTS` format (compatible with most agents) - - Example: `specify init my-project --ai generic --ai-commands-dir .myagent/commands/` - - Enables users to start with Spec Kit immediately while their agent awaits formal support + - Requires `--ai-commands-dir ` to specify where the agent reads commands from + - Generates Markdown commands with `$ARGUMENTS` format (compatible with most agents) + - Example: `specify init my-project --ai generic --ai-commands-dir .myagent/commands/` + - Enables users to start with Spec Kit immediately while their agent awaits formal support ## [0.0.102] - 2026-02-20 diff --git a/extensions/EXTENSION-API-REFERENCE.md b/extensions/EXTENSION-API-REFERENCE.md index bd25d4bb4..cdc8e9497 100644 --- a/extensions/EXTENSION-API-REFERENCE.md +++ b/extensions/EXTENSION-API-REFERENCE.md @@ -539,6 +539,12 @@ Examples: ```yaml hooks: + before_specify: + command: "speckit.research.pre-spec" + optional: true + prompt: "Perform pre-specification research?" + description: "Search context before writing spec" + after_tasks: command: "speckit.jira.specstoissues" optional: true @@ -551,10 +557,16 @@ hooks: Standard events (defined by core): +- `before_specify` - Before specification generation +- `after_specify` - After specification generation +- `before_plan` - Before implementation planning +- `after_plan` - After implementation planning +- `before_tasks` - Before task generation - `after_tasks` - After task generation +- `before_implement` - Before implementation - `after_implement` - After implementation -- `before_commit` - Before git commit -- `after_commit` - After git commit +- `before_commit` - Before git commit (future) +- `after_commit` - After git commit (future) ### Hook Configuration @@ -562,6 +574,13 @@ Standard events (defined by core): ```yaml hooks: + before_specify: + - extension: research + command: speckit.research.pre-spec + enabled: true + optional: true + prompt: "Perform pre-specification research?" + after_tasks: - extension: jira command: speckit.jira.specstoissues @@ -591,6 +610,8 @@ Or for mandatory hooks: **Automatic Hook**: {extension} Executing: `/{command}` EXECUTE_COMMAND: {command} + +**CRITICAL**: You MUST execute the command above immediately after outputting it. Do not end the response until the hook has been triggered. ``` --- @@ -811,6 +832,6 @@ satisfied = version_satisfies("1.2.3", ">=1.0.0,<2.0.0") # bool --- -*Last Updated: 2026-01-28* +*Last Updated: 2026-03-13* *API Version: 1.0* -*Spec Kit Version: 0.1.0* +*Spec Kit Version: 0.2.1* diff --git a/extensions/EXTENSION-DEVELOPMENT-GUIDE.md b/extensions/EXTENSION-DEVELOPMENT-GUIDE.md index feea7b278..c6123e1e6 100644 --- a/extensions/EXTENSION-DEVELOPMENT-GUIDE.md +++ b/extensions/EXTENSION-DEVELOPMENT-GUIDE.md @@ -196,8 +196,14 @@ Integration hooks for automatic execution. Available hook points: +- `before_specify`: Before `/speckit.specify` starts +- `after_specify`: After `/speckit.specify` completes +- `before_plan`: Before `/speckit.plan` starts +- `after_plan`: After `/speckit.plan` completes +- `before_tasks`: Before `/speckit.tasks` starts - `after_tasks`: After `/speckit.tasks` completes -- `after_implement`: After `/speckit.implement` completes (future) +- `before_implement`: Before `/speckit.implement` starts +- `after_implement`: After `/speckit.implement` completes Hook object: @@ -639,11 +645,25 @@ echo "Using endpoint: $endpoint" ### Extension with Hooks -Extension that runs automatically: +Extension that runs automatically at different lifecycle stages: ```yaml # extension.yml hooks: + # Pre-hook: runs before specification starts + before_specify: + command: "speckit.research.pre-spec" + optional: true + prompt: "Perform pre-specification research?" + description: "Gather context from codebase before writing spec" + + # Post-hook: runs after planning completes + after_plan: + command: "speckit.architect.validate" + optional: false # Mandatory execution + description: "Validate architecture against project standards" + + # Post-hook: runs after task generation after_tasks: command: "speckit.auto.analyze" optional: false # Always run diff --git a/extensions/EXTENSION-USER-GUIDE.md b/extensions/EXTENSION-USER-GUIDE.md index ae77860fe..e02be7a08 100644 --- a/extensions/EXTENSION-USER-GUIDE.md +++ b/extensions/EXTENSION-USER-GUIDE.md @@ -40,14 +40,14 @@ Extensions are modular packages that add new commands and functionality to Spec ### Prerequisites -- Spec Kit version 0.1.0 or higher +- Spec Kit version 0.2.1 or higher - A spec-kit project (directory with `.specify/` folder) ### Check Your Version ```bash specify version -# Should show 0.1.0 or higher +# Should show 0.2.1 or higher ``` ### First Extension @@ -986,5 +986,5 @@ After creating tasks, sync to Jira: --- -*Last Updated: 2026-01-28* -*Spec Kit Version: 0.1.0* +*Last Updated: 2026-03-13* +*Spec Kit Version: 0.2.1* diff --git a/pyproject.toml b/pyproject.toml index cdbad2e01..6c9a2a100 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "specify-cli" -version = "0.3.0" +version = "0.3.1" description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)." requires-python = ">=3.11" dependencies = [ diff --git a/src/specify_cli/extensions.py b/src/specify_cli/extensions.py index 0dfd40b7c..7d9e5ac48 100644 --- a/src/specify_cli/extensions.py +++ b/src/specify_cli/extensions.py @@ -1632,7 +1632,7 @@ def get_hooks_for_event(self, event_name: str) -> List[Dict[str, Any]]: """Get all registered hooks for a specific event. Args: - event_name: Name of the event (e.g., 'after_tasks') + event_name: Name of the event (e.g., 'after_specify', 'after_plan', 'after_tasks') Returns: List of hook configurations @@ -1784,7 +1784,7 @@ def check_hooks_for_event(self, event_name: str) -> Dict[str, Any]: This method is designed to be called by AI agents after core commands complete. Args: - event_name: Name of the event (e.g., 'after_spec', 'after_tasks') + event_name: Name of the event (e.g., 'after_specify', 'after_plan', 'after_tasks') Returns: Dictionary with hook information: diff --git a/templates/commands/implement.md b/templates/commands/implement.md index da58027d0..218c23c6f 100644 --- a/templates/commands/implement.md +++ b/templates/commands/implement.md @@ -45,6 +45,8 @@ You **MUST** consider the user input before proceeding (if not empty). Wait for the result of the hook command before proceeding to the Outline. ``` + + **CRITICAL**: You MUST execute the command above immediately after outputting it. Do not proceed until the hook has been triggered. - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently ## Outline @@ -198,4 +200,6 @@ Note: This command assumes a complete task breakdown exists in tasks.md. If task Executing: `/{command}` EXECUTE_COMMAND: {command} ``` + + **CRITICAL**: You MUST execute the command above immediately after outputting it. Do not end the response until the hook has been triggered. - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently diff --git a/templates/commands/plan.md b/templates/commands/plan.md index 00e83eabd..6c613ec79 100644 --- a/templates/commands/plan.md +++ b/templates/commands/plan.md @@ -24,6 +24,41 @@ $ARGUMENTS You **MUST** consider the user input before proceeding (if not empty). +## Pre-Execution Checks + +**Check for extension hooks (before planning generation)**: +- Check if `.specify/extensions.yml` exists in the project root. +- If it exists, read it and look for entries under the `hooks.before_plan` key +- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally +- Treat hooks as enabled unless they explicitly specify `enabled: false` +- For each enabled hook, do **not** attempt to interpret or evaluate hook `condition` expressions yourself; condition evaluation is handled by the HookExecutor implementation +- Treat all enabled hooks as executable for the purposes of this plan, regardless of whether they define a `condition` +- For each executable hook, output the following based on its `optional` flag: + - **Optional hook** (`optional: true`): + ``` + ## Extension Hooks + + **Optional Pre-Hook**: {extension} + Command: `/{command}` + Description: {description} + + Prompt: {prompt} + To execute: `/{command}` + ``` + - **Mandatory hook** (`optional: false`): + ``` + ## Extension Hooks + + **Automatic Pre-Hook**: {extension} + Executing: `/{command}` + EXECUTE_COMMAND: {command} + + Wait for the result of the hook command before proceeding to the Outline. + ``` + + **CRITICAL**: You MUST execute the command above immediately after outputting it. Do not proceed until the hook has been triggered. +- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently + ## Outline 1. **Setup**: Run `{SCRIPT}` from repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. 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"). @@ -41,6 +76,36 @@ You **MUST** consider the user input before proceeding (if not empty). 4. **Stop and report**: Command ends after Phase 2 planning. Report branch, IMPL_PLAN path, and generated artifacts. +5. **Check for extension hooks**: After reporting completion, check if `.specify/extensions.yml` exists in the project root. + - If it exists, read it and look for entries under the `hooks.after_plan` key + - If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally + - From the discovered hooks, ignore only those where `enabled` is explicitly set to `false`. If `enabled` is omitted, treat the hook as enabled. + - Do **not** attempt to interpret or evaluate hook `condition` expressions, and do **not** filter hooks based on the presence or contents of `condition`. Condition evaluation is handled by the `HookExecutor.should_execute_hook` implementation. + - Treat all hooks that are not explicitly disabled (that is, all hooks where `enabled` is not `false`) as executable candidates. + - 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}` + ``` + - **Mandatory hook** (`optional: false`): + ``` + ## Extension Hooks + + **Automatic Hook**: {extension} + Executing: `/{command}` + EXECUTE_COMMAND: {command} + ``` + + **CRITICAL**: You MUST execute the command above immediately after outputting it. Do not end the response until the hook has been triggered. + - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently + ## Phases ### Phase 0: Outline & Research diff --git a/templates/commands/specify.md b/templates/commands/specify.md index 0713b68e4..eb2d874c8 100644 --- a/templates/commands/specify.md +++ b/templates/commands/specify.md @@ -21,6 +21,41 @@ $ARGUMENTS You **MUST** consider the user input before proceeding (if not empty). +## Pre-Execution Checks + +**Check for extension hooks (before specification generation)**: +- Check if `.specify/extensions.yml` exists in the project root. +- If it exists, read it and look for entries under the `hooks.before_specify` key +- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally +- Treat hooks as enabled unless they explicitly specify `enabled: false` +- For each enabled hook, do **not** attempt to interpret or evaluate hook `condition` expressions yourself; condition evaluation is handled by the HookExecutor implementation +- Treat all enabled hooks as executable for the purposes of this plan, regardless of whether they define a `condition` +- For each executable hook, output the following based on its `optional` flag: + - **Optional hook** (`optional: true`): + ``` + ## Extension Hooks + + **Optional Pre-Hook**: {extension} + Command: `/{command}` + Description: {description} + + Prompt: {prompt} + To execute: `/{command}` + ``` + - **Mandatory hook** (`optional: false`): + ``` + ## Extension Hooks + + **Automatic Pre-Hook**: {extension} + Executing: `/{command}` + EXECUTE_COMMAND: {command} + + Wait for the result of the hook command before proceeding to the Outline. + ``` + + **CRITICAL**: You MUST execute the command above immediately after outputting it. Do not proceed until the hook has been triggered. +- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently + ## Outline The text the user typed after `/speckit.specify` in the triggering message **is** the feature description. Assume you always have it available in this conversation even if `{ARGS}` appears literally below. Do not ask the user to repeat it unless they provided an empty command. @@ -176,6 +211,36 @@ Given that feature description, do this: 7. Report completion with branch name, spec file path, checklist results, and readiness for the next phase (`/speckit.clarify` or `/speckit.plan`). +8. **Check for extension hooks**: After reporting completion, check if `.specify/extensions.yml` exists in the project root. + - If it exists, read it and look for entries under the `hooks.after_specify` key + - If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally + - From the discovered hooks, ignore only those where `enabled` is explicitly set to `false`. If `enabled` is omitted, treat the hook as enabled. + - Do **not** attempt to interpret or evaluate hook `condition` expressions, and do **not** filter hooks based on the presence or contents of `condition`. Condition evaluation is handled by the `HookExecutor.should_execute_hook` implementation. + - Treat all hooks that are not explicitly disabled (that is, all hooks where `enabled` is not `false`) as executable candidates. + - 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}` + ``` + - **Mandatory hook** (`optional: false`): + ``` + ## Extension Hooks + + **Automatic Hook**: {extension} + Executing: `/{command}` + EXECUTE_COMMAND: {command} + ``` + + **CRITICAL**: You MUST execute the command above immediately after outputting it. Do not end the response until the hook has been triggered. + - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently + **NOTE:** The script creates and checks out the new branch and initializes the spec file before writing. ## Quick Guidelines diff --git a/templates/commands/tasks.md b/templates/commands/tasks.md index 9ad199634..3e9930419 100644 --- a/templates/commands/tasks.md +++ b/templates/commands/tasks.md @@ -54,6 +54,8 @@ You **MUST** consider the user input before proceeding (if not empty). Wait for the result of the hook command before proceeding to the Outline. ``` + + **CRITICAL**: You MUST execute the command above immediately after outputting it. Do not proceed until the hook has been triggered. - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently ## Outline @@ -124,6 +126,8 @@ You **MUST** consider the user input before proceeding (if not empty). Executing: `/{command}` EXECUTE_COMMAND: {command} ``` + + **CRITICAL**: You MUST execute the command above immediately after outputting it. Do not end the response until the hook has been triggered. - If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently Context for task generation: {ARGS}