1. Architecture Overview
Spec Kit is a Python-based CLI toolkit that implements Spec-Driven Development (SDD) by scaffolding project structures, generating slash commands for AI coding agents, and orchestrating a multi-phase development workflow. The architecture is built around four composable subsystems: integrations (agent adapters), extensions (plugin commands and hooks), presets (template customization layers), and workflows (multi-step automation).
The system is designed as a local-first CLI tool with no server component, no database, and no runtime daemon. All state is stored in the filesystem under .specify/ within the project directory.
┌─────────────────────────────────────────────────────────┐
│ specify CLI (Typer) │
│ init │ check │ upgrade │ version │
├────────────┬──────────────┬─────────────┬───────────────┤
│ integration│ extension │ preset │ workflow │
│ subsystem │ subsystem │ subsystem │ engine │
├────────────┴──────────────┴─────────────┴───────────────┤
│ CommandRegistrar (agents.py) │
│ Renders commands into agent-native formats: │
│ Markdown │ TOML │ YAML │ Skills │
├─────────────────────────────────────────────────────────┤
│ Filesystem State │
│ .specify/ │ .claude/ │ .github/ │ .cursorrules/ │
└─────────────────────────────────────────────────────────┘
Component Inventory
| Component | Path | Role |
|---|---|---|
| CLI entry point | src/specify_cli/__init__.py |
Typer app with all commands (5,300+ lines) |
| Command registrar | src/specify_cli/agents.py |
Renders commands to agent-native formats |
| Extension system | src/specify_cli/extensions.py |
Extension lifecycle, catalog, hooks, config |
| Preset system | src/specify_cli/presets.py |
Preset lifecycle, catalog, template resolution |
| Integration base | src/specify_cli/integrations/base.py |
Abstract base classes for agent integrations |
| Integration catalog | src/specify_cli/integrations/catalog.py |
Integration discovery and metadata |
| Integration manifest | src/specify_cli/integrations/manifest.py |
File tracking with SHA-256 hashes |
| Workflow engine | src/specify_cli/workflows/engine.py |
Step execution, state machine, expression eval |
| Workflow base | src/specify_cli/workflows/base.py |
Workflow manifest parsing |
| Workflow catalog | src/specify_cli/workflows/catalog.py |
Workflow discovery |
| Shell scripts (Bash) | scripts/bash/ |
check-prerequisites.sh, common.sh, create-new-feature.sh, setup-plan.sh |
| Shell scripts (PowerShell) | scripts/powershell/ |
Same scripts in PowerShell format |
| Core templates | templates/ |
spec-template.md, plan-template.md, tasks-template.md, checklist-template.md, constitution-template.md |
| VS Code settings | templates/vscode-settings.json |
Copilot prompt file recommendations and script auto-approve |
Technology Stack
| Layer | Technology |
|---|---|
| Language | Python 3.11+ |
| CLI framework | Typer (Click-based) |
| Terminal UI | Rich (panels, tables, trees, live display) |
| Keyboard input | readchar (cross-platform) |
| JSON parsing | json5 (JSONC-compatible) |
| YAML parsing | PyYAML |
| Version comparison | packaging |
| Platform paths | platformdirs |
| Package manager | uv / pip |
| Build system | Hatch (hatchling) |
Data Flow: Full SDD Cycle
specify init --integration claude
│
├── Check prerequisites (git, claude CLI)
├── Create .specify/ directory structure
├── Copy bundled templates and scripts
├── Install integration (register commands via CommandRegistrar)
├── Auto-install Git extension (if git available)
├── Initialize git repo (unless --no-git)
├── Copy constitution template to .specify/memory/
└── Save init-options.json
User runs /speckit.specify in Claude Code
│
├── AI reads spec-template.md (resolved through preset stack)
├── Scripts: check-prerequisites.sh --paths-only
├── Creates feature branch (Git extension hook: before_specify)
├── Generates specs/<branch>/spec.md
└── Auto-commit (Git extension hook: after_specify, if configured)
/speckit.plan → /speckit.tasks → /speckit.implement
│
└── Each phase: check prerequisites → read templates → generate artifact → optional hooks
Note
The workflow engine (
workflows/engine.py) can automate this entire cycle with review gates at each step. The built-inspeckitworkflow requires an integration fromcopilot,claude, orgemini.
2. System Requirements
| Requirement | Version | Required | Notes |
|---|---|---|---|
| Python | ≥3.11 | Yes | Runtime for the CLI |
uv |
Latest | Recommended | Package management and tool installation |
| Git | Any recent | No | Required for the Git extension and branching features |
| AI agent CLI | Varies | At least one | Claude Code, Copilot (VS Code), Gemini CLI, etc. |
Python Dependencies
| Package | Purpose |
|---|---|
typer |
CLI framework (Click-based, with type hints) |
rich |
Terminal formatting (panels, tables, progress trees) |
platformdirs |
Cross-platform config/data directory resolution |
readchar |
Cross-platform keyboard input (arrow key selection) |
json5 |
JSONC parsing (comments and trailing commas) |
pyyaml |
YAML parsing for extensions, presets, workflows, configs |
packaging |
PEP 440 version parsing and comparison |
Important
These dependencies are declared in the script header of
__init__.pyand inpyproject.toml. They are installed automatically when usinguv tool install specify-cli.
3. Configuration Reference
Config Files
| File | Format | Location | Purpose |
|---|---|---|---|
init-options.json |
JSON | .specify/ |
Records CLI flags used during specify init |
speckit.manifest.json |
JSON | .specify/ |
Tracks all files installed by spec-kit with SHA-256 hashes |
extension.yml |
YAML | .specify/extensions/<id>/ |
Extension manifest (schema, commands, hooks, config) |
preset.yml |
YAML | .specify/presets/<id>/ |
Preset manifest (schema, templates, composition) |
git-config.yml |
YAML | .specify/extensions/git/ |
Git extension configuration |
catalog-config.json |
JSON | .specify/extensions/ |
Custom extension catalog sources |
preset-catalog-config.json |
JSON | .specify/presets/ |
Custom preset catalog sources |
registry.json |
JSON | .specify/extensions/ |
Extension registry (installed extensions metadata) |
registry.json |
JSON | .specify/presets/ |
Preset registry (installed presets metadata) |
vscode-settings.json |
JSON | .vscode/settings.json |
VS Code settings (merged, not overwritten) |
Environment Variables
| Variable | Type | Default | Description | Required |
|---|---|---|---|---|
SPECKIT_CATALOG_URL |
String | GitHub raw URL | Override the built-in extension catalog URL | No |
SPECKIT_PRESET_CATALOG_URL |
String | GitHub raw URL | Override the built-in preset catalog URL | No |
CLI Flags — specify init
| Flag | Type | Default | Description |
|---|---|---|---|
--integration |
String | None (interactive) | AI agent integration to activate |
--integration-options |
String | None | Options string for the integration |
--script |
sh | ps |
None (interactive) | Script type for shell scripts |
--preset |
String | None | Preset to install during init |
--branch-numbering |
sequential | timestamp |
sequential |
Branch numbering strategy |
--here |
Flag | False | Init in current directory |
--force |
Flag | False | Overwrite existing files |
--no-git |
Flag | False | Skip git init |
--ai-skills |
Flag | False | Install as agent skills |
-V / --version |
Flag | — | Show version and exit |
Git Extension Configuration (git-config.yml)
# Branch numbering strategy: "sequential" (001, 002, ...) or "timestamp" (YYYYMMDD-HHMMSS)
branch_numbering: sequential
# Commit message used by `git commit` during repository initialization
init_commit_message: "[Spec Kit] Initial commit"
# Auto-commit before/after core commands
auto_commit:
default: false
before_clarify:
enabled: false
message: "[Spec Kit] Save progress before clarification"
before_plan:
enabled: false
message: "[Spec Kit] Save progress before planning"
after_specify:
enabled: false
message: "[Spec Kit] Add specification"
after_plan:
enabled: false
message: "[Spec Kit] Add implementation plan"
after_implement:
enabled: false
message: "[Spec Kit] Implementation progress"
# ... (18 hook points total: before/after for 9 core commands)
Tip
Set
auto_commit.default: trueto enable automatic commits for all commands, then selectively disable specific ones. Each hook point can be individually configured with a custom commit message.
Extension Manifest Schema (extension.yml)
schema_version: "1.0"
extension:
id: "my-extension" # Lowercase alphanumeric with hyphens
name: "My Extension"
version: "1.0.0" # Semantic versioning
description: "Brief description"
author: "Your Name"
repository: "https://github.com/..."
license: "MIT"
requires:
speckit_version: ">=0.2.0" # PEP 440 version specifier
tools:
- name: "example-tool"
version: ">=1.0.0"
required: true
provides:
commands:
- name: "speckit.my-extension.example" # Must follow speckit.<ext>.<cmd> pattern
file: "commands/example.md"
description: "Example command"
aliases: ["speckit.my-extension.ex"] # Optional aliases
config:
- name: "my-config.yml"
template: "config-template.yml"
required: false
hooks:
after_tasks:
command: "speckit.my-extension.example"
optional: true # true = prompt user; false = auto-execute
prompt: "Run example command?"
description: "Hook description"
tags:
- "example"
Important
Extension IDs must be lowercase alphanumeric with hyphens only. Command names must follow the pattern
speckit.<extension-id>.<command-name>. Aliases must use the same namespace. Commands that conflict with core or other installed extension commands will be rejected.
Preset Manifest Schema (preset.yml)
schema_version: "1.0"
preset:
id: "my-preset"
name: "My Preset"
version: "1.0.0"
description: "Brief description"
author: "Your Name"
repository: "https://github.com/..."
license: "MIT"
requires:
speckit_version: ">=0.1.0"
provides:
templates:
- type: "template" # "template", "command", or "script"
name: "spec-template"
file: "templates/spec-template.md"
description: "Custom spec template"
replaces: "spec-template"
strategy: "replace" # "replace" (default), "prepend", "append", "wrap"
tags:
- "example"
Composition strategies:
| Strategy | Behavior |
|---|---|
replace |
Fully replaces the lower-priority template (default) |
prepend |
Places content BEFORE the lower-priority template |
append |
Places content AFTER the lower-priority template |
wrap |
Uses {CORE_TEMPLATE} placeholder; replaced with lower-priority content |
Note
Scripts only support
replaceandwrapstrategies. Thewrapstrategy for scripts uses$CORE_SCRIPTas the placeholder.
4. Deployment & Scaling
Spec Kit is a local CLI tool — there is no server deployment. Distribution is handled through PyPI.
Package Distribution
The package is built with Hatch (hatchling build backend). The pyproject.toml configures:
- Package name:
specify-cli - Entry point:
specify = "specify_cli:main" - Build includes
src/specify_cli/with acore_pack/directory containing bundled templates, scripts, extensions, presets, and workflows
Wheel Layout
specify_cli/
├── __init__.py # CLI entry point (5,300+ lines)
├── agents.py # CommandRegistrar
├── extensions.py # Extension system
├── presets.py # Preset system
├── integrations/ # 27+ agent integrations
│ ├── base.py
│ ├── catalog.py
│ ├── manifest.py
│ └── <agent>/__init__.py (one per integration)
├── workflows/ # Workflow engine
│ ├── engine.py
│ ├── base.py
│ ├── catalog.py
│ ├── expressions.py
│ └── steps/ # Step types (command, gate, shell, prompt, etc.)
└── core_pack/ # Bundled assets (templates, scripts, extensions, presets, workflows)
Note
The
core_pack/directory is created at build time by hatchling'sforce-includeconfiguration. It is not present in source checkouts or editable installs. The code falls back to repo-relative paths in those cases.
5. Operations
Catalog Infrastructure
Spec Kit uses JSON catalogs hosted on GitHub for extension, preset, and workflow discovery:
| Catalog | URL | Purpose |
|---|---|---|
| Extension (official) | github/spec-kit/main/extensions/catalog.json |
Bundled extensions (git, selftest) |
| Extension (community) | github/spec-kit/main/extensions/catalog.community.json |
Community extensions (50+) |
| Preset (official) | github/spec-kit/main/presets/catalog.json |
Bundled presets (lean) |
| Preset (community) | github/spec-kit/main/presets/catalog.community.json |
Community presets (11+) |
| Workflow (official) | github/spec-kit/main/workflows/catalog.json |
Bundled workflows (speckit) |
| Workflow (community) | github/spec-kit/main/workflows/catalog.community.json |
Community workflows (empty) |
Catalogs are fetched via HTTPS (HTTP only allowed for localhost). The cache is stored locally and can be cleared with specify extension catalog clear or equivalent preset commands.
Tip
Organizations can host private catalogs and configure them via
catalog-config.jsonin.specify/extensions/or.specify/presets/. Each catalog entry supportsurl,name,priority,install_allowed, anddescriptionfields.
File Integrity Tracking
The IntegrationManifest system (in integrations/manifest.py) tracks every file installed by spec-kit:
- Files are recorded with SHA-256 hashes
- On uninstall, only unmodified files are removed
- Modified files are preserved (unless
--forceis used) - Empty parent directories are cleaned up after file removal
- Path traversal attacks are blocked (no
../or absolute paths) - Symlinks are treated as modified files (skipped without
--force)
Note
The manifest is stored at
.specify/speckit.manifest.json. Each integration and extension maintains its own manifest for clean uninstall behavior.
Version Checking
The specify upgrade command checks the GitHub Releases API (https://api.github.com/repos/github/spec-kit/releases/latest) for newer versions. Version comparison uses the packaging library for PEP 440 compliance.
6. Performance
Spec Kit is a scaffolding tool — performance characteristics are dominated by I/O (file copying, git operations) rather than computation.
Key performance notes:
- The
initcommand copies bundled assets from the wheel'score_pack/directory, avoiding network requests - Extension/preset installation from catalogs requires one HTTPS fetch per catalog, with local caching
- The CommandRegistrar renders commands into agent-native formats at install time, not at command execution time
- The workflow engine executes steps sequentially with optional gate pauses
- Template resolution (
PresetResolver) walks the priority stack once per resolve call, merging composition layers
Tip
For large teams, consider pre-populating
.specify/directories in project templates or monorepo scaffolds to avoid per-developer initialization costs.
7. Security
Catalog URL Validation
All catalog URLs must use HTTPS. HTTP is only permitted for localhost (development/testing). This is enforced by ValidationError in both ExtensionCatalog and PresetCatalog.
if parsed.scheme != "https":
if parsed.scheme == "http" and parsed.hostname in ("localhost", "127.0.0.1"):
pass # Allowed for local development
else:
raise ValidationError(f"Catalog URL must use HTTPS (got {parsed.scheme}://)")
ZIP Archive Safety
Extension and preset installation from ZIP files includes path traversal protection:
- All archive members are checked for
..path components - Absolute paths in ZIP entries are rejected
- The extraction uses a safe path validation before writing
if '..' in member or os.path.isabs(member):
raise ValidationError(f"Unsafe path in ZIP archive: {member} (potential path traversal)")
Extension Command Namespace Isolation
Extensions must use their own namespace for commands: speckit.<extension-id>.<command>. The system rejects:
- Commands that conflict with core command namespaces
- Commands that conflict with other installed extensions
- Duplicate command names or aliases within a manifest
Integration Manifest Integrity
The manifest system prevents:
- Recording files outside the project root (path traversal via
../) - Recording files with absolute paths
- Uninstalling files that have been modified (hash mismatch)
- Symlink-based attacks (symlinks are treated as modified)
Warning
Extension hooks can execute arbitrary commands within the AI agent context. Review extension manifests before installation, especially hooks with
optional: false(auto-execute without prompting).
Version Compatibility
Extensions and presets declare a requires.speckit_version constraint using PEP 440 version specifiers. Installation is blocked if the running spec-kit version doesn't satisfy the constraint:
Extension requires spec-kit >=0.7.0, but 0.6.2 is installed.
Upgrade spec-kit with: uv tool install specify-cli --force
Caution
The
--forceflag onextension removeandintegration uninstalldeletes files even if they've been modified. Use with care in shared repositories.
8. Developer Internals
Repository Structure
github/spec-kit/
├── src/specify_cli/ # Main package
│ ├── __init__.py # CLI commands (5,300+ lines, Typer app)
│ ├── agents.py # CommandRegistrar — multi-format command renderer
│ ├── extensions.py # Extension system (2,600+ lines)
│ ├── presets.py # Preset system (3,000+ lines)
│ ├── integrations/ # 27+ agent integrations
│ │ ├── __init__.py # INTEGRATION_REGISTRY population
│ │ ├── base.py # IntegrationBase, MarkdownIntegration, TomlIntegration, SkillsIntegration
│ │ ├── catalog.py # IntegrationCatalogEntry, IntegrationDescriptor
│ │ ├── manifest.py # IntegrationManifest (file tracking + SHA-256)
│ │ ├── claude/__init__.py # Claude Code integration
│ │ ├── copilot/__init__.py # GitHub Copilot integration
│ │ └── ... # One module per integration
│ └── workflows/
│ ├── __init__.py
│ ├── base.py # WorkflowManifest parsing
│ ├── catalog.py # Workflow discovery
│ ├── engine.py # WorkflowEngine — step execution
│ ├── expressions.py # Template expression evaluation
│ └── steps/ # Step type implementations
│ ├── command/ # Command step (run a slash command)
│ ├── gate/ # Gate step (approve/reject)
│ ├── shell/ # Shell step
│ ├── prompt/ # Prompt step
│ ├── if_then/ # Conditional branching
│ ├── switch/ # Multi-branch conditional
│ ├── while_loop/ # While loop
│ ├── do_while/ # Do-while loop
│ ├── fan_out/ # Parallel fan-out
│ └── fan_in/ # Parallel fan-in (join)
├── extensions/
│ ├── git/ # Bundled Git extension
│ ├── selftest/ # Bundled self-test extension
│ └── template/ # Extension development template
├── presets/
│ ├── lean/ # Bundled Lean preset
│ ├── scaffold/ # Preset development scaffold
│ └── self-test/ # Bundled self-test preset
├── workflows/
│ └── speckit/ # Bundled Full SDD Cycle workflow
├── integrations/ # Integration catalog JSON files
├── templates/ # Core command templates
├── scripts/
│ ├── bash/ # Bash helper scripts
│ └── powershell/ # PowerShell helper scripts
├── tests/
│ ├── integrations/ # Integration tests (39 files, 264K chars)
│ ├── extensions/ # Extension tests
│ ├── hooks/ # Hook test fixtures
│ ├── test_workflows.py
│ ├── test_presets.py
│ ├── test_extensions.py
│ └── ...
├── docs/ # DocFX documentation source
├── media/ # Logos, screenshots, GIFs
├── newsletters/ # Community newsletters
├── pyproject.toml # Package configuration
├── AGENTS.md # Agent context file
├── CITATION.cff # Citation metadata
└── .zenodo.json # Zenodo deposit metadata
Architecture Deep Dive
CommandRegistrar (agents.py)
The CommandRegistrar is the bridge between Spec Kit's template system and the diverse formats required by AI coding agents. It renders a single command definition into multiple agent-native formats:
- Markdown: Standard
.mdfiles with YAML frontmatter (Claude, Copilot agent mode, Windsurf, most agents) - TOML:
.tomlfiles withdescriptionandpromptfields (Gemini, Tabnine) - YAML:
.yamlfiles withtitleandpromptfields (Goose) - Skills: Directory-based layout with
SKILL.mdfiles containing structured frontmatter (Claude skills, Copilot skills, Cursor, Codex, Kimi, Trae, Agy)
Key methods:
| Method | Purpose |
|---|---|
parse_frontmatter() |
Extract YAML frontmatter from markdown content |
render_frontmatter() |
Serialize frontmatter dict back to YAML |
render_markdown_command() |
Render for Markdown-format agents |
render_toml_command() |
Render for TOML-format agents |
render_yaml_command() |
Render for YAML-format agents |
render_skill_command() |
Render for Skills-format agents |
register_commands() |
Write command files to agent directory |
register_commands_for_all_agents() |
Register across all detected agent directories |
unregister_commands() |
Remove previously registered command files |
write_copilot_prompt() |
Write companion .prompt.md files for Copilot |
The registrar also handles agent-specific post-processing:
- Claude: Injects
disableModelInvocation: falseand hook command notes into skill frontmatter - Copilot: Injects
mode: agentinto skill frontmatter - Cursor: Upserts
.mdcfrontmatter withalwaysApply: false - Forge: Reformats command names from dotted to hyphenated (e.g.,
speckit.specify→speckit-specify)
Integration System (integrations/)
Each integration is a Python class inheriting from one of four base classes:
| Base Class | Format | Agents |
|---|---|---|
MarkdownIntegration |
.md with frontmatter |
Windsurf, Amp, Auggie, Bob, CodeBuddy, iFlow, Junie, Kilocode, Kiro, opencode, Pi, Qoder, Qwen, Roo, SHAI, Vibe |
TomlIntegration |
.toml files |
Gemini, Tabnine |
YamlIntegration |
.yaml files |
Goose |
SkillsIntegration |
Skill directories | Claude, Codex, Cursor, Kimi, Trae, Agy |
Special integrations with custom logic:
copilot: Supports both agent mode (.mdfiles with companion.prompt.md) and skills mode (directory-based). Manages.vscode/settings.jsonmergingclaude: Skills layout withdisableModelInvocationflag, BOM stripping for context files, argument hints in frontmatterforge: Custom command name formatting (dotted → hyphenated), parameter placeholder supportgeneric: Requires--commands-diroption; no agent config folder assumed
The INTEGRATION_REGISTRY is populated at import time by scanning all integration modules. Each integration's key must match its CLI tool name.
Extension System (extensions.py)
The extension system is the largest module (2,600+ lines) and provides:
Core classes:
| Class | Purpose |
|---|---|
ExtensionManifest |
Parses and validates extension.yml |
ExtensionRegistry |
JSON-backed registry of installed extensions |
ExtensionManager |
Install, remove, list, compatibility checking |
ExtensionCatalog |
Fetch/cache/search catalogs, download ZIPs |
CommandRegistrar |
Extension-specific command registration |
ConfigManager |
Extension config file management |
HookExecutor |
Event-driven hook system |
Validation rules enforced by ExtensionManifest:
- Schema version must be
"1.0" - Extension ID: lowercase alphanumeric with hyphens only
- Version: valid semantic version
- Must provide at least one command or hook
- Command names must follow
speckit.<ext-id>.<cmd>pattern - Aliases must use the same extension namespace
- No conflict with core command namespaces
- No conflict with other installed extensions' commands
Hook lifecycle:
The HookExecutor supports these event points:
before_constitution, after_constitution
before_specify, after_specify
before_clarify, after_clarify
before_plan, after_plan
before_tasks, after_tasks
before_implement, after_implement
before_checklist, after_checklist
before_analyze, after_analyze
before_taskstoissues, after_taskstoissues
Hooks with optional: true prompt the user before execution. Hooks with optional: false execute automatically.
Preset System (presets.py)
The preset system (3,000+ lines) mirrors the extension system architecture but adds template composition:
Core classes:
| Class | Purpose |
|---|---|
PresetManifest |
Parses and validates preset.yml |
PresetRegistry |
JSON-backed registry of installed presets |
PresetManager |
Install, remove, list, compatibility checking |
PresetCatalog |
Fetch/cache/search catalogs, download ZIPs |
PresetResolver |
Multi-layer template resolution with composition |
Template resolution order (highest to lowest priority):
- Installed presets (by priority number, lowest = highest priority)
- Extension-provided commands (via manifest)
- Core templates (bundled in
.specify/templates/)
Composition execution in PresetResolver.resolve_content():
- Collect all layers for the requested template
- Start with the lowest-priority layer's content
- Walk up the priority stack, applying each layer's strategy:
replace: discard everything below, use this layerprepend: concatenate this layer + lower contentappend: concatenate lower content + this layerwrap: replace{CORE_TEMPLATE}in this layer with lower content
Workflow Engine (workflows/)
The workflow engine supports YAML-defined automation with these step types:
| Step Type | Module | Purpose |
|---|---|---|
command |
steps/command/ |
Execute a spec-kit slash command |
gate |
steps/gate/ |
Pause for human review (approve/reject) |
shell |
steps/shell/ |
Execute a shell command |
prompt |
steps/prompt/ |
Prompt the user for input |
if_then |
steps/if_then/ |
Conditional branching |
switch |
steps/switch/ |
Multi-branch conditional |
while_loop |
steps/while_loop/ |
While loop |
do_while |
steps/do_while/ |
Do-while loop |
fan_out |
steps/fan_out/ |
Parallel execution |
fan_in |
steps/fan_in/ |
Join parallel branches |
The expressions.py module handles template expression evaluation (e.g., {{ inputs.spec }}, {{ steps.specify.output }}).
Module Reference
src/specify_cli/__init__.py (5,300+ lines)
The monolithic CLI module contains all Typer commands organized into groups:
| Command Group | Commands |
|---|---|
| Top-level | init, check, version, upgrade |
integration |
list, install, uninstall, switch, upgrade |
preset |
list, add, remove, search, resolve, info, set-priority, enable, disable |
preset catalog |
list, add, remove |
extension |
list, add, remove, search, info, update, enable, disable, set-priority |
extension catalog |
list, add, remove |
workflow |
run, resume, status, list, add, remove, search, info |
workflow catalog |
list, add, remove |
Key utility functions:
| Function | Purpose |
|---|---|
_locate_core_pack() |
Find bundled assets in wheel or source checkout |
_locate_bundled_extension() |
Find a bundled extension by ID |
_locate_bundled_preset() |
Find a bundled preset by ID |
_locate_bundled_workflow() |
Find a bundled workflow by ID |
_install_shared_infra() |
Install scripts and templates with manifest tracking |
ensure_executable_scripts() |
Set POSIX execute bits on .sh files |
ensure_constitution_from_template() |
Copy constitution template to memory directory |
check_tool() |
Check if a tool is installed (special handling for Claude local paths) |
select_with_arrows() |
Interactive arrow-key selection UI using Rich Live |
merge_json_files() |
Polite deep merge for JSON/JSONC files |
save_init_options() / load_init_options() |
Persist and restore init configuration |
src/specify_cli/agents.py
The CommandRegistrar class (620+ lines) with two distinct implementations:
- Core CommandRegistrar (
agents.py): Used duringspecify initto register core command templates - Extension CommandRegistrar (
extensions.py:1412): Extends the core registrar for extension command registration
Both share parse_frontmatter() and render_frontmatter() but differ in how they handle agent-specific rendering.
src/specify_cli/extensions.py (2,600+ lines)
Major classes and their line counts:
| Class | Start Line | Purpose |
|---|---|---|
ExtensionManifest |
119 | Manifest parsing with full validation |
ExtensionRegistry |
360 | JSON-backed installed extension tracking |
ExtensionManager |
597 | Install/remove lifecycle |
CommandRegistrar |
1412 | Extension command registration |
ExtensionCatalog |
1502 | Catalog fetch/cache/search/download |
ConfigManager |
2026 | Extension config file management |
HookExecutor |
2225 | Event-driven hook system |
src/specify_cli/presets.py (3,000+ lines)
Major classes and their line counts:
| Class | Start Line | Purpose |
|---|---|---|
PresetManifest |
117 | Manifest parsing with template type and strategy validation |
PresetRegistry |
296 | JSON-backed installed preset tracking |
PresetManager |
527 | Install/remove lifecycle |
PresetCatalog |
1786 | Catalog fetch/cache/search/download |
PresetResolver |
2307 | Multi-layer template resolution with composition |
Code Patterns and Conventions
File operations: Atomic writes via tempfile.NamedTemporaryFile + os.replace() for JSON files. Existing file permissions and ownership are preserved where possible.
Error handling: Custom exception hierarchies per subsystem:
- Extensions:
ExtensionError→ValidationError,CompatibilityError - Presets:
PresetError→PresetValidationError,PresetCompatibilityError
Registry pattern: Both extensions and presets use a JSON-backed registry with add/remove/update/restore/get/list operations. The registry persists to .specify/<subsystem>/registry.json.
Manifest pattern: IntegrationManifest uses SHA-256 hashing for file change detection. Files are recorded at install time and verified at uninstall.
Catalog pattern: Both subsystems use the same catalog architecture: HTTPS fetch → JSON parse → local cache → search/filter. Catalogs support priority ordering and multi-source configuration.
Version checking: Uses packaging.version.Version for PEP 440 compliance. The version_satisfies() function handles specifier ranges like >=0.7.0,<1.0.0.
CLI argument handling: Typer with Click-based commands. The --integration-options flag is parsed as a shell-style string using shlex.
Testing Strategy
The test suite (39 integration test files, 264K chars) is organized by subsystem:
| Test Area | Files | Focus |
|---|---|---|
tests/integrations/ |
39 | All 27 integrations with base class test mixins |
tests/extensions/ |
1 | Git extension tests |
tests/test_workflows.py |
1 | Workflow engine tests |
tests/test_presets.py |
1 | Preset system tests |
tests/test_extensions.py |
1 | Extension system tests |
tests/test_cli_version.py |
1 | Version command tests |
tests/test_check_tool.py |
1 | Tool detection tests |
tests/test_merge.py |
1 | JSON merge tests |
tests/test_upgrade.py |
1 | Upgrade command tests |
tests/test_branch_numbering.py |
1 | Branch naming tests |
tests/test_registrar_path_traversal.py |
1 | Security: path traversal prevention |
tests/test_agent_config_consistency.py |
1 | Config alignment tests |
tests/test_timestamp_branches.py |
1 | Timestamp branch format tests |
tests/test_extension_skills.py |
1 | Extension skills rendering tests |
Test mixins for integrations:
MarkdownIntegrationTests— 20+ tests for Markdown-format agentsSkillsIntegrationTests— 25+ tests for Skills-format agentsTomlIntegrationTests— 20+ tests for TOML-format agentsYamlIntegrationTests— 15+ tests for YAML-format agents
Each integration's test class inherits the appropriate mixin and runs the full suite against that specific agent adapter.
Potential Improvements
Based on the codebase analysis:
-
__init__.pyis 5,300+ lines — a single monolithic file containing all CLI commands. Splitting intocli_init.py,cli_integration.py,cli_extension.py,cli_preset.py,cli_workflow.pywould improve maintainability. -
No formal plugin interface for step types — workflow step types are discovered by module name convention. A registry pattern (like integrations use) would make this more extensible.
-
Duplicate catalog logic —
ExtensionCatalogandPresetCatalogshare nearly identical URL validation, fetch, cache, and search logic. A baseCatalogclass would reduce duplication. -
No dependency file detected — the AST extractor found 0 dependency files, suggesting
pyproject.tomlparsing isn't being picked up. Dependencies are declared in the script header comment block. -
Community catalog contribution workflow — currently requires manual PR to add JSON entries. A CLI command like
specify extension publishcould automate validation and PR creation.
Contributing
The project uses:
- License: MIT
- Build system: Hatch (hatchling)
- Test framework: pytest with
tmp_pathfixtures - Linting: markdownlint-cli2 (configured in
.markdownlint-cli2.jsonc) - Citation: CITATION.cff and .zenodo.json for academic referencing
- Documentation: DocFX (build with
cd docs && docfx docfx.json --serve)
To develop locally:
git clone https://github.com/github/spec-kit.git
cd spec-kit
uv sync
uv run pytest
Extension development:
- Copy
extensions/template/to a new directory - Edit
extension.ymlwith your extension ID, commands, and hooks - Create command files in
commands/ - Test locally with
specify extension add <id> --dev - Publish to GitHub and submit a PR to
extensions/catalog.community.json
Preset development:
- Copy
presets/scaffold/to a new directory - Edit
preset.ymlwith your preset ID and templates - Create template files with optional composition strategies
- Test locally with
specify preset add <id> --dev - Publish and submit a PR to
presets/catalog.community.json
9. Integration Points
Git Integration (Bundled Extension)
The Git extension is auto-installed during specify init when Git is detected. It provides:
- Feature branch creation with sequential (001, 002, …) or timestamp (YYYYMMDD-HHMMSS) numbering
- 18 lifecycle hooks (before/after for 9 core commands)
- Configurable auto-commit with per-command messages
- Branch validation and remote detection
- Configuration via
.specify/extensions/git/git-config.yml
VS Code Integration
When Copilot is the selected integration, Spec Kit manages .vscode/settings.json:
{
"chat.promptFilesRecommendations": {
"speckit.constitution": true,
"speckit.specify": true,
"speckit.plan": true,
"speckit.tasks": true,
"speckit.implement": true
},
"chat.tools.terminal.autoApprove": {
".specify/scripts/bash/": true,
".specify/scripts/powershell/": true
}
}
The merge is "polite" — existing keys are never overwritten, only new keys are added.
Agent Context Files
Each integration writes to a context file that the AI agent reads:
| Integration | Context File |
|---|---|
| Claude | CLAUDE.md |
| Copilot | .github/copilot-instructions.md |
| Generic | AGENTS.md |
| Others | Agent-specific (e.g., .cursorrules, .goose/config.yaml) |
A managed section delimited by <!-- SPECKIT START --> / <!-- SPECKIT END --> is upserted into these files. BOM characters are stripped during processing.
Shell Script Integration
The bundled scripts in .specify/scripts/ provide project-level utility:
| Script | Purpose |
|---|---|
check-prerequisites.sh / .ps1 |
Validate feature directory, spec, plan, tasks existence |
common.sh / .ps1 |
Shared functions: find repo root, resolve feature paths, JSON escape |
create-new-feature.sh / .ps1 |
Create a new feature branch and directory structure |
setup-plan.sh / .ps1 |
Set up the planning phase prerequisites |
Scripts support --json, --require-tasks, --include-tasks, and --paths-only flags.
Note
On POSIX systems,
ensure_executable_scripts()automatically sets execute bits on.shfiles during init. On Windows, this is a no-op.