Skip to content
Open
68 changes: 34 additions & 34 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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

Expand All @@ -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 <path>` 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 <path>` 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

Expand Down
29 changes: 25 additions & 4 deletions extensions/EXTENSION-API-REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -551,17 +557,30 @@ 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

**In `.specify/extensions.yml`**:

```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
Expand Down Expand Up @@ -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.
```

---
Expand Down Expand Up @@ -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.3.1*
24 changes: 22 additions & 2 deletions extensions/EXTENSION-DEVELOPMENT-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions extensions/EXTENSION-USER-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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*
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.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 = [
Expand Down
4 changes: 2 additions & 2 deletions src/specify_cli/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions templates/commands/implement.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
65 changes: 65 additions & 0 deletions templates/commands/plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Comment on lines +35 to +36
- **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").
Expand All @@ -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
Expand Down
Loading