* docs: Document dual-catalog system for extensions - Clarify distinction between catalog.json (curated) and catalog.community.json (reference) - Update EXTENSION-DEVELOPMENT-GUIDE.md to explain community catalog submission - Update EXTENSION-PUBLISHING-GUIDE.md with dual-catalog workflow - Update EXTENSION-USER-GUIDE.md with catalog selection guidance - Expand README.md with comprehensive catalog explanation - Update RFC-EXTENSION-SYSTEM.md with dual-catalog design and current implementation - Change GitHub references from statsperform to github - Add SPECKIT_CATALOG_URL environment variable documentation This clarifies how organizations can curate their own catalog while browsing community-contributed extensions for discovery. * Update extensions/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update extensions/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update extensions/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
13 KiB
Extension Development Guide
A guide for creating Spec Kit extensions.
Quick Start
1. Create Extension Directory
mkdir my-extension
cd my-extension
2. Create extension.yml Manifest
schema_version: "1.0"
extension:
id: "my-ext" # Lowercase, alphanumeric + hyphens only
name: "My Extension"
version: "1.0.0" # Semantic versioning
description: "My custom extension"
author: "Your Name"
repository: "https://github.com/you/spec-kit-my-ext"
license: "MIT"
requires:
speckit_version: ">=0.1.0" # Minimum spec-kit version
tools: # Optional: External tools required
- name: "my-tool"
required: true
version: ">=1.0.0"
commands: # Optional: Core commands needed
- "speckit.tasks"
provides:
commands:
- name: "speckit.my-ext.hello" # Must follow pattern: speckit.{ext-id}.{cmd}
file: "commands/hello.md"
description: "Say hello"
aliases: ["speckit.hello"] # Optional aliases
config: # Optional: Config files
- name: "my-ext-config.yml"
template: "my-ext-config.template.yml"
description: "Extension configuration"
required: false
hooks: # Optional: Integration hooks
after_tasks:
command: "speckit.my-ext.hello"
optional: true
prompt: "Run hello command?"
tags: # Optional: For catalog search
- "example"
- "utility"
3. Create Commands Directory
mkdir commands
4. Create Command File
File: commands/hello.md
---
description: "Say hello command"
tools: # Optional: AI tools this command uses
- 'some-tool/function'
scripts: # Optional: Helper scripts
sh: ../../scripts/bash/helper.sh
ps: ../../scripts/powershell/helper.ps1
---
# Hello Command
This command says hello!
## User Input
$ARGUMENTS
## Steps
1. Greet the user
2. Show extension is working
```bash
echo "Hello from my extension!"
echo "Arguments: $ARGUMENTS"
Extension Configuration
Load extension config from .specify/extensions/my-ext/my-ext-config.yml.
5. Test Locally
cd /path/to/spec-kit-project
specify extension add --dev /path/to/my-extension
6. Verify Installation
specify extension list
# Should show:
# ✓ My Extension (v1.0.0)
# My custom extension
# Commands: 1 | Hooks: 1 | Status: Enabled
7. Test Command
If using Claude:
claude
> /speckit.my-ext.hello world
The command will be available in .claude/commands/speckit.my-ext.hello.md.
Manifest Schema Reference
Required Fields
schema_version
Extension manifest schema version. Currently: "1.0"
extension
Extension metadata block.
Required sub-fields:
id: Extension identifier (lowercase, alphanumeric, hyphens)name: Human-readable nameversion: Semantic version (e.g., "1.0.0")description: Short description
Optional sub-fields:
author: Extension authorrepository: Source code URLlicense: SPDX license identifierhomepage: Extension homepage URL
requires
Compatibility requirements.
Required sub-fields:
speckit_version: Semantic version specifier (e.g., ">=0.1.0,<2.0.0")
Optional sub-fields:
tools: External tools required (array of tool objects)commands: Core spec-kit commands needed (array of command names)scripts: Core scripts required (array of script names)
provides
What the extension provides.
Required sub-fields:
commands: Array of command objects (must have at least one)
Command object:
name: Command name (must matchspeckit.{ext-id}.{command})file: Path to command file (relative to extension root)description: Command description (optional)aliases: Alternative command names (optional, array)
Optional Fields
hooks
Integration hooks for automatic execution.
Available hook points:
after_tasks: After/speckit.taskscompletesafter_implement: After/speckit.implementcompletes (future)
Hook object:
command: Command to execute (must be inprovides.commands)optional: If true, prompt user before executingprompt: Prompt text for optional hooksdescription: Hook descriptioncondition: Execution condition (future)
tags
Array of tags for catalog discovery.
defaults
Default extension configuration values.
config_schema
JSON Schema for validating extension configuration.
Command File Format
Frontmatter (YAML)
---
description: "Command description" # Required
tools: # Optional
- 'tool-name/function'
scripts: # Optional
sh: ../../scripts/bash/helper.sh
ps: ../../scripts/powershell/helper.ps1
---
Body (Markdown)
Use standard Markdown with special placeholders:
$ARGUMENTS: User-provided arguments{SCRIPT}: Replaced with script path during registration
Example:
## Steps
1. Parse arguments
2. Execute logic
```bash
args="$ARGUMENTS"
echo "Running with args: $args"
```
Script Path Rewriting
Extension commands use relative paths that get rewritten during registration:
In extension:
scripts:
sh: ../../scripts/bash/helper.sh
After registration:
scripts:
sh: .specify/scripts/bash/helper.sh
This allows scripts to reference core spec-kit scripts.
Configuration Files
Config Template
File: my-ext-config.template.yml
# My Extension Configuration
# Copy this to my-ext-config.yml and customize
# Example configuration
api:
endpoint: "https://api.example.com"
timeout: 30
features:
feature_a: true
feature_b: false
credentials:
# DO NOT commit credentials!
# Use environment variables instead
api_key: "${MY_EXT_API_KEY}"
Config Loading
In your command, load config with layered precedence:
- Extension defaults (
extension.yml→defaults) - Project config (
.specify/extensions/my-ext/my-ext-config.yml) - Local overrides (
.specify/extensions/my-ext/my-ext-config.local.yml- gitignored) - Environment variables (
SPECKIT_MY_EXT_*)
Example loading script:
#!/usr/bin/env bash
EXT_DIR=".specify/extensions/my-ext"
# Load and merge config
config=$(yq eval '.' "$EXT_DIR/my-ext-config.yml" -o=json)
# Apply env overrides
if [ -n "${SPECKIT_MY_EXT_API_KEY:-}" ]; then
config=$(echo "$config" | jq ".api.api_key = \"$SPECKIT_MY_EXT_API_KEY\"")
fi
echo "$config"
Validation Rules
Extension ID
- Pattern:
^[a-z0-9-]+$ - Valid:
my-ext,tool-123,awesome-plugin - Invalid:
MyExt(uppercase),my_ext(underscore),my ext(space)
Extension Version
- Format: Semantic versioning (MAJOR.MINOR.PATCH)
- Valid:
1.0.0,0.1.0,2.5.3 - Invalid:
1.0,v1.0.0,1.0.0-beta
Command Name
- Pattern:
^speckit\.[a-z0-9-]+\.[a-z0-9-]+$ - Valid:
speckit.my-ext.hello,speckit.tool.cmd - Invalid:
my-ext.hello(missing prefix),speckit.hello(no extension namespace)
Command File Path
- Must be relative to extension root
- Valid:
commands/hello.md,commands/subdir/cmd.md - Invalid:
/absolute/path.md,../outside.md
Testing Extensions
Manual Testing
-
Create test extension
-
Install locally:
specify extension add --dev /path/to/extension -
Verify installation:
specify extension list -
Test commands with your AI agent
-
Check command registration:
ls .claude/commands/speckit.my-ext.* -
Remove extension:
specify extension remove my-ext
Automated Testing
Create tests for your extension:
# tests/test_my_extension.py
import pytest
from pathlib import Path
from specify_cli.extensions import ExtensionManifest
def test_manifest_valid():
"""Test extension manifest is valid."""
manifest = ExtensionManifest(Path("extension.yml"))
assert manifest.id == "my-ext"
assert len(manifest.commands) >= 1
def test_command_files_exist():
"""Test all command files exist."""
manifest = ExtensionManifest(Path("extension.yml"))
for cmd in manifest.commands:
cmd_file = Path(cmd["file"])
assert cmd_file.exists(), f"Command file not found: {cmd_file}"
Distribution
Option 1: GitHub Repository
-
Create repository:
spec-kit-my-ext -
Add files:
spec-kit-my-ext/ ├── extension.yml ├── commands/ ├── scripts/ ├── docs/ ├── README.md ├── LICENSE └── CHANGELOG.md -
Create release: Tag with version (e.g.,
v1.0.0) -
Install from repo:
git clone https://github.com/you/spec-kit-my-ext specify extension add --dev spec-kit-my-ext/
Option 2: ZIP Archive (Future)
Create ZIP archive and host on GitHub Releases:
zip -r spec-kit-my-ext-1.0.0.zip extension.yml commands/ scripts/ docs/
Users install with:
specify extension add --from https://github.com/.../spec-kit-my-ext-1.0.0.zip
Option 3: Community Reference Catalog
Submit to the community catalog for public discovery:
- Fork spec-kit repository
- Add entry to
extensions/catalog.community.json - Update
extensions/README.mdwith your extension - Create PR following the Extension Publishing Guide
- After merge, your extension becomes available:
- Users can browse
catalog.community.jsonto discover your extension - Users copy the entry to their own
catalog.json - Users install with:
specify extension add my-ext(from their catalog)
- Users can browse
See the Extension Publishing Guide for detailed submission instructions.
Best Practices
Naming Conventions
- Extension ID: Use descriptive, hyphenated names (
jira-integration, notji) - Commands: Use verb-noun pattern (
create-issue,sync-status) - Config files: Match extension ID (
jira-config.yml)
Documentation
- README.md: Overview, installation, usage
- CHANGELOG.md: Version history
- docs/: Detailed guides
- Command descriptions: Clear, concise
Versioning
- Follow SemVer:
MAJOR.MINOR.PATCH - MAJOR: Breaking changes
- MINOR: New features
- PATCH: Bug fixes
Security
- Never commit secrets: Use environment variables
- Validate input: Sanitize user arguments
- Document permissions: What files/APIs are accessed
Compatibility
- Specify version range: Don't require exact version
- Test with multiple versions: Ensure compatibility
- Graceful degradation: Handle missing features
Example Extensions
Minimal Extension
Smallest possible extension:
# extension.yml
schema_version: "1.0"
extension:
id: "minimal"
name: "Minimal Extension"
version: "1.0.0"
description: "Minimal example"
requires:
speckit_version: ">=0.1.0"
provides:
commands:
- name: "speckit.minimal.hello"
file: "commands/hello.md"
<!-- commands/hello.md -->
---
description: "Hello command"
---
# Hello World
```bash
echo "Hello, $ARGUMENTS!"
```
Extension with Config
Extension using configuration:
# extension.yml
# ... metadata ...
provides:
config:
- name: "tool-config.yml"
template: "tool-config.template.yml"
required: true
# tool-config.template.yml
api_endpoint: "https://api.example.com"
timeout: 30
<!-- commands/use-config.md -->
# Use Config
Load config:
```bash
config_file=".specify/extensions/tool/tool-config.yml"
endpoint=$(yq eval '.api_endpoint' "$config_file")
echo "Using endpoint: $endpoint"
```
Extension with Hooks
Extension that runs automatically:
# extension.yml
hooks:
after_tasks:
command: "speckit.auto.analyze"
optional: false # Always run
description: "Analyze tasks after generation"
Troubleshooting
Extension won't install
Error: Invalid extension ID
- Fix: Use lowercase, alphanumeric + hyphens only
Error: Extension requires spec-kit >=0.2.0
- Fix: Update spec-kit with
uv tool install specify-cli --force
Error: Command file not found
- Fix: Ensure command files exist at paths specified in manifest
Commands not registered
Symptom: Commands don't appear in AI agent
Check:
-
.claude/commands/directory exists -
Extension installed successfully
-
Commands registered in registry:
cat .specify/extensions/.registry
Fix: Reinstall extension to trigger registration
Config not loading
Check:
- Config file exists:
.specify/extensions/{ext-id}/{ext-id}-config.yml - YAML syntax is valid:
yq eval '.' config.yml - Environment variables set correctly
Getting Help
- Issues: Report bugs at GitHub repository
- Discussions: Ask questions in GitHub Discussions
- Examples: See
spec-kit-jirafor full-featured example (Phase B)
Next Steps
- Create your extension following this guide
- Test locally with
--devflag - Share with community (GitHub, catalog)
- Iterate based on feedback
Happy extending! 🚀