Preset API reference

This reference defines the preset manifest, composition and delivery rules, and installed state. The CLI reference documents terminal commands; the developing guide walks through creating a package.

Manifest

A preset has a UTF-8 preset.yml whose root is a YAML mapping.

Field Required Contract
schema_version Yes String "1.0"
preset Yes Metadata mapping
preset.id Yes String matching [a-z0-9-]+
preset.name Yes Human-readable string
preset.version Yes Quoted string accepted by Python packaging.version.Version
preset.description Yes Description string
preset.author No Author metadata
preset.repository No Repository URL metadata
preset.license No License metadata
requires Yes Requirements mapping
requires.speckit_version Yes Non-empty string; checked as a PEP 440 specifier during installation
requires.extensions No List of extension IDs or dependency mappings
provides Yes Contribution mapping
provides.templates Yes Non-empty list of contribution mappings, including commands and scripts
tags No Discovery tags; use a list of lowercase strings

Use descriptive, letter-leading, lowercase-hyphenated IDs and X.Y.Z versions for published presets. The community submission contract is stricter than the runtime manifest validator: it requires an ID matching [a-z][a-z0-9-]*, a numeric X.Y.Z version, and repository, documentation, license, and release information. Runtime metadata validation alone is not proof that a preset meets catalog submission requirements.

Quote versions and constraints: YAML values such as version: 1.0 are numbers, not version strings. Incompatible Spec Kit versions or invalid Spec Kit specifiers fail installation.

Contribution fields

Every item in provides.templates uses this shape:

provides:
  templates:
    - type: "template"
      name: "spec-template"
      file: "templates/spec-addendum.md"
      description: "Adds team requirements"
      strategy: "append"
Field Required Contract
type Yes template, command, or script
name Yes Logical artifact name; not the source filename
file Yes Relative path within the preset, with no parent traversal
description No Contribution description
strategy No Defaults to replace; accepted strings are normalized to lowercase

Template and script names use lowercase letters, digits, and hyphens. Command names additionally permit dots; use names such as speckit.specify or speckit.myext.report. Duplicate (name, type) pairs are rejected, although different types can share a name.

The manifest's file is authoritative when an artifact is declared. A missing or non-file declared path does not fall back to a different conventional file in that same preset. Keep all declared files present: manifest structural validation is not a complete package-content check. Legacy lookup can use conventional paths when a manifest is absent or does not declare the requested name.

Extension dependencies

requires:
  speckit_version: ">=0.9.0"
  extensions:
    - "companion-extension"
    - id: "other-extension"
      version: ">=1.2.0,<2"
      required: false

A bare ID means required with no version constraint. Mapping entries require an id matching [a-z0-9-]+; optional version must be a non-empty valid PEP 440 specifier, and required must be a boolean, defaulting to true. Exact pins, exclusions, and upper bounds are valid.

Dependencies are advisory, not automatically installed. After installation, the CLI warns about required extensions that are missing, have corrupt registry entries, have missing files, are disabled, or have a comparable version that does not satisfy the constraint. Missing or disabled capabilities remain unavailable. A version mismatch does not disable the extension; it can still run and behave differently than expected.

Optional dependencies are never warned about. Unparseable or missing recorded versions are uncomparable, not reported as mismatches. An extension directory admitted by the resolver without a registry entry is treated as available, but has no recorded version to compare. An unreadable extension registry prevents this post-install dependency inspection; no warning is not proof that dependencies were verified.

Declare dependencies in the manifest, not just a catalog entry: --dev and --from do not consult catalogs. A self-contained namespaced replacement command is still delivered without its similarly named extension.

File resolution

Each (name, type) is evaluated independently:

flowchart TD
    A["Requested artifact"] --> B["Project-local override"]
    B --> C["Enabled presets: priority, then ID"]
    C --> D["Enabled extensions: priority, then ID"]
    D --> E["Core defaults"]

Within the preset and extension layers, lower priority numbers win, then alphabetical IDs break ties. Disabled entries are excluded. Safe unregistered extension directories participate at implicit priority 10; disabled, corrupt, or missing registered extensions are not re-admitted as unregistered contributions.

Type Project-local override Conventional preset subdirectory
Template .specify/templates/overrides/<name>.md templates/, with legacy root lookup
Command .specify/templates/overrides/<name>.md commands/
Script .specify/templates/overrides/scripts/<name>.<ext> scripts/

Core lookup includes project defaults and packaged core assets, or the repository assets for editable/source installations. Command lookup can map speckit.<stem> to the core <stem>.md filename. This allows composing against core commands even when a project does not have a .specify/templates/commands/ copy.

Composition strategies

Type replace prepend append wrap
Template Default Supported Supported {CORE_TEMPLATE}
Command Default Supported Supported {CORE_TEMPLATE}
Script Default Not supported Not supported $CORE_SCRIPT

A highest-precedence replace layer wins entirely. Otherwise, resolution finds the nearest lower replace base and applies only the layers above it, bottom-up. Prepend and append separate the two contents with a blank line. The wrapper placeholder is replaced with the effective lower content, which may already contain other preset customizations.

Without a replace base, composition cannot produce output. Command registration warns and skips an uncomposable command rather than requiring an extension based solely on its namespace. A wrapper missing its required placeholder raises a preset validation error during composition.

Composed command bodies do not embed every layer's YAML frontmatter. Resolution uses the highest-priority available frontmatter and inherits scripts, agent_scripts, and argument-hint from the base when absent. The internal strategy key is removed from composed output. The active integration then renders the result in its supported command or skill format.

Command and skill lifecycle

Installation copies preset files into .specify/presets/<id>/ and records the preset before composing its commands. Initial registration and post-install reconciliation materialize the effective stack, so install order does not determine the final winning command.

For initialized projects, delivery targets the recorded active integration only. Skills mode emits skills rather than a second command representation for command-backed integrations; native skill integrations use their native format. The generic integration does not register preset overrides. Switching with specify integration use <key> or switch <key> rescaffolds enabled presets for the newly active integration.

Legacy projects with no init-options.json retain detection-based command registration. An existing but corrupt or malformed options file does not trigger that all-agent fallback.

Removal cleans up recorded outputs and reconciles surviving preset, extension, override, or core contributions. It can restore affected command or skill outputs in previously active integration directories recorded by the removed preset as well as the current integration. Enable, disable, and priority changes update resolution state but do not themselves rescaffold already delivered commands or skills.

The constitution lifecycle is distinct from agent command delivery. By default the constitution is resolved by the constitution command, not rewritten on every stack change. With constitution-sync enabled, guarded reconciliation uses generated-content provenance to avoid overwriting an authored constitution.

Installed state

Location Role
.specify/presets/<id>/preset.yml Installed manifest
.specify/presets/<id>/ Copied package files
.specify/presets/.registry JSON registry with schema_version: "1.0" and a presets mapping
.specify/presets/.cache/ Catalog cache and download working files
.specify/memory/.constitution-template.json Generated constitution provenance when materialized

Registry metadata records the version, manifest hash, enabled state, priority, installation timestamp, source, and registered command/skill outputs. Modern output tracking maps integration keys to lists of names; legacy skill tracking can be a flat list. Treat this as CLI-managed state, not the package manifest or the stable JSON inspection API.

Catalog installs record {"kind":"catalog","catalog":"<name>"} provenance. Local directories, direct URLs, and locally bundled installs are recorded as local rather than as a reusable download URL. Registry updates preserve the existing installed_at; remove-then-add replacement creates a new one.

Implementation map

The Python implementation is the src/specify_cli/presets/ package: _manifest.py validates manifests; _registry.py tracks installations; _manager.py, _manager_commands.py, and _manager_skills.py own lifecycle and output reconciliation; _resolver.py resolves and composes content; _catalog.py and _catalog_versions.py handle discovery and releases. command_*.py modules and catalog/command_*.py expose the CLI.

Shared command rendering lives in agents.py and the integration packages. Bash resolve_template / resolve_template_content and PowerShell Resolve-Template / Resolve-TemplateContent in the respective common scripts expose template resolution to shell consumers. Agent command and skill materialization is handled in Python, not independently by each agent.

See also