mirror of
https://github.com/github/spec-kit.git
synced 2026-07-03 12:28:06 +08:00
* feat(extensions): per-event hook lists with priority ordering The manifest validator restricted each hook event to a single mapping, even though HookExecutor stores entries as a list per event. This blocked an extension from running multiple commands on one event (e.g. a verification step plus a doc-generation step after speckit.plan), and get_hooks_for_event returned entries in raw insertion order with no way to influence execution order across or within extensions. This change: 1. Validator: accept hooks.<event> as either a single mapping or a list of mappings. Each entry is validated individually and may carry an optional integer `priority` (>= 1, default 10; bool rejected). 2. Command-ref normalization: apply rename / alias->canonical rewriting to every entry in the list, not just the head. 3. register_hooks: expand list entries, persist `priority`, and purge-and-replace all entries owned by the extension on each event so a reinstall whose shape changed (single<->list, or a shorter list) leaves no orphaned entries behind. 4. get_hooks_for_event: sort enabled entries by `priority` ascending with a stable sort (ties keep insertion order). The existing normalize_priority helper is reused as the sort key so corrupted on-disk values fall back to the default instead of raising. Backward compatible: existing single-mapping manifests parse and register unchanged with priority defaulting to 10. The extension-level `priority` used by preset/template resolution is independent of the new hook-entry `priority`. Implements #2378 * fix(extensions): harden register_hooks per PR review - Skip non-dict hook entries before .get() so a manifest that bypasses validation can't crash register_hooks with AttributeError. - Normalize `priority` on save via normalize_priority so the on-disk config stays clean, mirroring the read-side defense in get_hooks_for_event. - Tests: cover the non-dict-entry skip and add encoding="utf-8" to the new tests' manifest writes. * fix(extensions): purge dropped-event hook orphans on reinstall register_hooks only purged events the new manifest still declared, so an extension that dropped an event on reinstall left stale entries for it in the project config. Purge this extension's entries from undeclared events (and prune emptied events) before registering; scoped to this extension, and a no-op for the install/update flow where unregister_hooks runs first. * fix(extensions): reject boolean priority and complete orphan purge - normalize_priority falls back to default for bool values - dedup deletes duplicate commands before re-insert for last-wins ties - register_hooks purges orphans even when all hooks are dropped * docs(extensions): document per-event hook lists and priority - EXTENSION-API-REFERENCE: hook event accepts a mapping or list; add priority field reference and last-wins dedup note - EXTENSION-DEVELOPMENT-GUIDE: add list-form example with priority * docs(extensions): show both single and list hook forms in schema snippet * docs(extensions): reference DEFAULT_HOOK_PRIORITY in normalize_priority normalize_priority hard-coded the default as the literal 10 in both its signature and docstring, duplicating DEFAULT_HOOK_PRIORITY. Reference the constant in the signature and drop the literal from the docstring so the default has a single source of truth.
106 lines
3.3 KiB
YAML
106 lines
3.3 KiB
YAML
schema_version: "1.0"
|
|
|
|
extension:
|
|
# CUSTOMIZE: Change 'my-extension' to your extension ID (lowercase, hyphen-separated)
|
|
id: "my-extension"
|
|
|
|
# CUSTOMIZE: Human-readable name for your extension
|
|
name: "My Extension"
|
|
|
|
# CUSTOMIZE: Update version when releasing (semantic versioning: X.Y.Z)
|
|
version: "1.0.0"
|
|
|
|
# CUSTOMIZE: Brief description (under 200 characters)
|
|
description: "Brief description of what your extension does"
|
|
|
|
# CUSTOMIZE: Your name or organization name
|
|
author: "Your Name"
|
|
|
|
# CUSTOMIZE: GitHub repository URL (create before publishing)
|
|
repository: "https://github.com/your-org/spec-kit-my-extension"
|
|
|
|
# REVIEW: License (MIT is recommended for open source)
|
|
license: "MIT"
|
|
|
|
# CUSTOMIZE: Extension homepage (can be same as repository)
|
|
homepage: "https://github.com/your-org/spec-kit-my-extension"
|
|
|
|
# Requirements for this extension
|
|
requires:
|
|
# CUSTOMIZE: Minimum spec-kit version required
|
|
# Use >=X.Y.Z for minimum version
|
|
# Use >=X.Y.Z,<Y.0.0 for version range
|
|
speckit_version: ">=0.1.0"
|
|
|
|
# CUSTOMIZE: Add MCP tools or other dependencies
|
|
# Remove if no external tools required
|
|
tools:
|
|
- name: "example-mcp-server"
|
|
version: ">=1.0.0"
|
|
required: true
|
|
|
|
# Commands provided by this extension
|
|
provides:
|
|
commands:
|
|
# CUSTOMIZE: Define your commands
|
|
# Pattern: speckit.{extension-id}.{command-name}
|
|
- name: "speckit.my-extension.example"
|
|
file: "commands/example.md"
|
|
description: "Example command that demonstrates functionality"
|
|
# Optional: Add aliases in the same namespaced format
|
|
aliases: ["speckit.my-extension.example-short"]
|
|
|
|
# ADD MORE COMMANDS: Copy this block for each command
|
|
# - name: "speckit.my-extension.another-command"
|
|
# file: "commands/another-command.md"
|
|
# description: "Another command"
|
|
|
|
# CUSTOMIZE: Define configuration files
|
|
config:
|
|
- name: "my-extension-config.yml"
|
|
template: "config-template.yml"
|
|
description: "Extension configuration"
|
|
required: true # Set to false if config is optional
|
|
|
|
# CUSTOMIZE: Define hooks (optional)
|
|
# Remove if no hooks needed
|
|
hooks:
|
|
# Hook that runs after /speckit.tasks
|
|
after_tasks:
|
|
command: "speckit.my-extension.example"
|
|
optional: true # User will be prompted
|
|
prompt: "Run example command?"
|
|
description: "Demonstrates hook functionality"
|
|
condition: null # Future: conditional execution
|
|
|
|
# ADD MORE HOOKS: Copy this block for other events
|
|
# after_implement:
|
|
# command: "speckit.my-extension.another"
|
|
# optional: false # Auto-execute without prompting
|
|
# description: "Runs automatically after implementation"
|
|
|
|
# MULTIPLE COMMANDS ON ONE EVENT: use a list of entries.
|
|
# Add optional `priority` (integer >= 1, default 10) to order them, lowest first.
|
|
# after_plan:
|
|
# - command: "speckit.my-extension.verify"
|
|
# priority: 5
|
|
# - command: "speckit.my-extension.report"
|
|
# priority: 10
|
|
|
|
# CUSTOMIZE: Add relevant tags (2-5 recommended)
|
|
# Used for discovery in catalog
|
|
tags:
|
|
- "example"
|
|
- "template"
|
|
# ADD MORE: "category", "tool-name", etc.
|
|
|
|
# CUSTOMIZE: Default configuration values (optional)
|
|
# These are merged with user config
|
|
defaults:
|
|
# Example default values
|
|
feature:
|
|
enabled: true
|
|
auto_sync: false
|
|
|
|
# ADD MORE: Any default settings for your extension
|