Skip to content

feat(presets): Pluggable preset system with catalog, resolver, and skills propagation#1787

Open
Copilot wants to merge 13 commits intomainfrom
copilot/add-pluggable-template-system
Open

feat(presets): Pluggable preset system with catalog, resolver, and skills propagation#1787
Copilot wants to merge 13 commits intomainfrom
copilot/add-pluggable-template-system

Conversation

Copy link
Contributor

Copilot AI commented Mar 9, 2026

Summary

Adds a pluggable preset system that lets users install curated template, command, and script overrides to customize the Spec-Driven Development workflow. Presets are versioned, cataloged, and resolved through a priority stack.

Also wires preset command overrides into the agent skills layer so that projects initialized with --ai-skills get their skills updated automatically when a preset is installed or removed.

Closes #1708

What's included

  • PresetManifest, PresetRegistry, PresetManager, PresetCatalog, PresetResolver classes in src/specify_cli/presets.py
  • Shared CommandRegistrar extracted to src/specify_cli/agents.py (used by both extensions and presets)
  • CLI commands: specify preset search, add, list, remove, resolve, info
  • CLI commands: specify preset catalog list, add, remove for multi-catalog management
  • --preset option on specify init to install a preset during project initialization
  • Priority-based template resolution: overrides → presets → extensions → core
  • Preset catalog files (presets/catalog.json, presets/catalog.community.json)
  • Preset scaffold directory and self-test preset with full test coverage
  • resolve_template() / Resolve-Template helpers in bash and PowerShell common scripts
  • .specify/init-options.json — persists CLI flags from specify init for downstream operations
  • Preset command overrides propagate to agent skills when --ai-skills was used
  • Skills restored from core templates on preset removal
  • Merged upstream: .extensionignore support (feat(extensions): support .extensionignore to exclude files during install #1781), Codex extension command registration (feat: add Codex support for extension command registration #1767)
  • 118 tests (110 preset + 8 init-options/skills)

…esolver, and CLI commands

Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
Co-authored-by: mnriem <15701806+mnriem@users.noreply.github.com>
Copilot AI changed the title [WIP] Add pluggable template system with catalog feat(templates): Pluggable template system with template packs, catalog, and resolver Mar 9, 2026
mnriem added 3 commits March 10, 2026 14:17
…s, catalog, and resolver

- Rename 'template packs' to 'presets' to avoid naming collision with core templates
- PresetManifest, PresetRegistry, PresetManager, PresetCatalog, PresetResolver in presets.py
- Extract CommandRegistrar to agents.py as shared infrastructure
- CLI: specify preset list/add/remove/search/resolve/info
- CLI: specify preset catalog list/add/remove
- --preset option on specify init
- Priority-based preset stacking (--priority, lower = higher precedence)
- Command overrides registered into all detected agent directories (17+ agents)
- Extension command safety: skip registration if target extension not installed
- Multi-catalog support: env var, project config, user config, built-in defaults
- resolve_template() / Resolve-Template in bash/PowerShell scripts
- Self-test preset: overrides all 6 core templates + 1 command
- Scaffold with 4 examples: core/extension template and command overrides
- Preset catalog (catalog.json, catalog.community.json)
- Documentation: README.md, ARCHITECTURE.md, PUBLISHING.md
- 110 preset tests, 253 total tests passing
# Conflicts:
#	CHANGELOG.md
#	src/specify_cli/extensions.py
- Add save_init_options() / load_init_options() helpers that persist
  CLI flags from 'specify init' to .specify/init-options.json
- PresetManager._register_skills() overwrites SKILL.md files when
  --ai-skills was used during init and corresponding skill dirs exist
- PresetManager._unregister_skills() restores core template content
  on preset removal
- registered_skills stored in preset registry metadata
- 8 new tests covering skill override, skip conditions, and restore
@mnriem mnriem changed the title feat(templates): Pluggable template system with template packs, catalog, and resolver feat(presets): Pluggable preset system with catalog, resolver, and skills propagation Mar 10, 2026
- Remove extraneous f-prefix from two f-strings without placeholders
- Replace substring URL check in test with startswith/endswith assertions
  to satisfy CodeQL incomplete URL substring sanitization rule
@mnriem mnriem marked this pull request as ready for review March 10, 2026 19:47
@mnriem mnriem self-requested a review as a code owner March 10, 2026 19:47
Copilot AI review requested due to automatic review settings March 10, 2026 19:47
@mnriem
Copy link
Collaborator

mnriem commented Mar 10, 2026

@dhilipkumars @mbachorik What do you think?

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new preset system to Spec Kit that lets projects install versioned bundles of template/command overrides (plus catalog + resolver support), and refactors command registration into a shared agent registrar used by both presets and extensions.

Changes:

  • Introduces PresetManifest/Registry/Manager/Catalog/Resolver plus CLI surface (specify preset ...) and init integration (specify init --preset).
  • Extracts shared agent command registration into src/specify_cli/agents.py and updates extensions to delegate to it.
  • Updates bash/PowerShell scripts to resolve templates via a priority stack (overrides → presets → extensions → core) and adds preset scaffold + self-test preset + catalogs + docs.

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
tests/test_presets.py Adds extensive unit/integration coverage for preset manifest/registry/manager/catalog/resolver, plus init-options + skills propagation tests.
src/specify_cli/presets.py Implements preset lifecycle, registry, resolver, and catalog (incl. multi-catalog configuration + caching helpers).
src/specify_cli/agents.py New shared CommandRegistrar for writing/removing agent command files across formats/agents.
src/specify_cli/extensions.py Refactors extension command registration/removal to delegate to the shared registrar wrapper.
src/specify_cli/init.py Adds init-options persistence, --preset on init, and the preset CLI command group (list/add/remove/search/resolve/info + catalog subcommands).
scripts/bash/common.sh Adds resolve_template() helper implementing the resolution stack for bash scripts.
scripts/bash/create-new-feature.sh Uses resolve_template for spec template resolution and sources common.sh.
scripts/bash/setup-plan.sh Uses resolve_template for plan template resolution.
scripts/powershell/common.ps1 Adds Resolve-Template helper implementing the resolution stack for PowerShell scripts.
scripts/powershell/create-new-feature.ps1 Uses Resolve-Template for spec template resolution and dot-sources common.ps1.
scripts/powershell/setup-plan.ps1 Uses Resolve-Template for plan template resolution.
presets/catalog.json Adds official preset catalog containing the self-test preset entry.
presets/catalog.community.json Adds empty community preset catalog stub.
presets/self-test/preset.yml Defines the shipped self-test preset manifest (templates + command override).
presets/self-test/commands/speckit.specify.md Self-test override command used for registration/skills propagation tests.
presets/self-test/templates/spec-template.md Self-test override for core spec template.
presets/self-test/templates/plan-template.md Self-test override for core plan template.
presets/self-test/templates/tasks-template.md Self-test override for core tasks template.
presets/self-test/templates/checklist-template.md Self-test override for core checklist template.
presets/self-test/templates/constitution-template.md Self-test override for core constitution template.
presets/self-test/templates/agent-file-template.md Self-test override for core agent-file template.
presets/scaffold/preset.yml Provides a scaffold preset manifest users can copy/customize.
presets/scaffold/README.md Documents how to customize/test a scaffolded preset.
presets/scaffold/templates/spec-template.md Example scaffold spec template.
presets/scaffold/templates/myext-template.md Example scaffold template overriding an extension template.
presets/scaffold/commands/speckit.specify.md Example scaffold command override for a core command.
presets/scaffold/commands/speckit.myext.myextcmd.md Example scaffold command override for an extension command.
presets/README.md User-facing documentation for presets, stacking, and catalog management.
presets/PUBLISHING.md Publishing guide for adding presets to catalogs.
presets/ARCHITECTURE.md Architecture doc for resolution/registration/catalog behavior.
pyproject.toml Bumps package version to 0.2.1.
CHANGELOG.md Documents 0.2.1 release notes for presets + init-options + script changes.
Comments suppressed due to low confidence (2)

src/specify_cli/presets.py:1144

  • fetch_catalog() always uses the legacy catalog.json cache files via is_cache_valid()/self.cache_file, even when a non-default catalog URL is active. This defeats the per-URL cache helpers (_get_cache_paths, _is_url_cache_valid) and can cause cross-catalog cache collisions.
    src/specify_cli/presets.py:1444
  • resolve_with_source() ignores the template_type parameter (it always checks overrides as <name>.md). For command/script lookups, this will misattribute or fail to find overrides. Either honor template_type here the same way as resolve() or drop the parameter to avoid a misleading API.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Move save_init_options() before preset install so skills propagation
  works during 'specify init --preset --ai-skills'
- Clean up downloaded ZIP after successful preset install during init
- Validate --from URL scheme (require HTTPS, HTTP only for localhost)
- Expose unregister_commands() on extensions.py CommandRegistrar wrapper
  instead of reaching into private _registrar field
- Use _get_merged_packs() for search() and get_pack_info() so all
  active catalogs are searched, not just the highest-priority one
- Fix fetch_catalog() cache to verify cached URL matches current URL
- Fix PresetResolver: script resolution uses .sh extension, consistent
  file extensions throughout resolve(), and resolve_with_source()
  delegates to resolve() to honor template_type parameter
- Fix bash common.sh: fall through to directory scan when python3
  returns empty preset list
- Fix PowerShell Resolve-Template: filter out dot-folders and sort
  extensions deterministically
@dhilipkumars
Copy link
Contributor

@dhilipkumars @mbachorik What do you think?

@mnriem
Good catch i noticed this yesterday while i was playing with the latest after your PR was merged , and one more thing i noticed is https://github.com/github/spec-kit/blob/main/extensions/EXTENSION-USER-GUIDE.md did not have up-to-date info about the specify extension catalog <sub-commands> do you want to add it in this PR? or i can add it in another PR.

@mnriem
Copy link
Collaborator

mnriem commented Mar 10, 2026

@dhilipkumars @mbachorik What do you think?

@mnriem Good catch i noticed this yesterday while i was playing with the latest after your PR was merged , and one more thing i noticed is https://github.com/github/spec-kit/blob/main/extensions/EXTENSION-USER-GUIDE.md did not have up-to-date info about the specify extension catalog <sub-commands> do you want to add it in this PR? or i can add it in another PR.

Different as this is presets ;) But yea we should make sure the docs are up-to-date

Copy link
Contributor

@dhilipkumars dhilipkumars left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@dhilipkumars
Copy link
Contributor

@mnriem yeah we also need ai-skill propagation for extension command installs i think. That's what i meant.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 32 out of 32 changed files in this pull request and generated 7 comments.

Comments suppressed due to low confidence (1)

src/specify_cli/init.py:1605

  • specify init --preset <id> installs from catalogs via PresetCatalog.download_pack() without enforcing the install_allowed policy. This can result in silently installing discovery-only community presets during init. Add the same _install_allowed gating used by extension installs before downloading/installing from a catalog.
                        preset_catalog = PresetCatalog(project_path)
                        try:
                            zip_path = preset_catalog.download_pack(preset)
                            preset_manager.install_from_zip(zip_path, speckit_ver)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 32 out of 32 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

mnriem added 2 commits March 10, 2026 16:35
- Fix init --preset error masking: distinguish "not found" from real errors
- Fix bash resolve_template: skip hidden dirs in extensions (match Python/PS)
- Fix temp dir leaks in tests: use temp_dir fixture instead of mkdtemp
- Fix self-test catalog entry: add note that it's local-only (no download_url)
- Fix Windows path issue in resolve_with_source: use Path.relative_to()
- Fix skill restore path: use project's .specify/templates/commands/ not source tree
- Add encoding="utf-8" to all file read/write in agents.py
- Update test to set up core command templates for skill restoration
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 32 out of 32 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Fix PS Resolve-Template fallback to skip dot-prefixed dirs (.cache)
- Rename _catalog to _catalog_name for consistency with extension system
- Enforce install_allowed policy in CLI preset add and download_pack()
- Fix shell injection: pass registry path via env var instead of string interpolation
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 32 out of 32 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(presets): Pluggable preset system with catalog, resolver, and skills propagation

4 participants