Skip to content

[Feature]: Allow extensions to declare templates and scripts in their manifest #4010

Description

@mnriem

Problem Statement

Extensions today can only formally declare commands (plus config, hooks, events) under provides — see ExtensionManifest (src/specify_cli/extensions/__init__.py:606-621) and the validation requiring "at least one command, hook, or event" (:386-387). There is no provides.templates or provides.scripts.

An extension can still ship templates/<name>.md or scripts/<name>.sh files, which the resolver picks up by convention (PresetResolver.collect_all_layers_find_in_subdirs, src/specify_cli/presets/__init__.py:5417-5455). But those contributions are:

  • Undeclared — no id, name, description; matched purely by filename.
  • Forced to replace (:5452) — no author control, and no way to make the intent explicit.
  • Invisible to tooling — not counted or shown by any list/info command.

This blocks a consumer (e.g. a setup wizard) from enumerating extension-contributed templates/scripts with real metadata, and it means presets and extensions have asymmetric manifest schemas for the same kinds of artifacts.

Proposed Solution

Add opt-in, declarative provides.templates and provides.scripts sections to the extension manifest, mirroring the preset shape (provides.templates with type/name/file/description, src/specify_cli/presets/__init__.py:400-453) — minus any authorable strategy, since extension contributions are always replace (see below).

provides:
  commands:
    - name: speckit.myext.report
      file: commands/speckit.myext.report.md
      description: "..."
  templates:
    - name: myext-template
      file: templates/myext-template.md
      description: "Report scaffold contributed by myext"
  scripts:
    - name: myext-collect
      file: scripts/bash/myext-collect.sh
      description: "Data-collection helper"
      runtimes: [bash, powershell, python]   # declared, not inferred

Behavior:

  • Declared entries are authoritative; convention lookup remains the fallback — same pattern presets already use (:5380-5383), so nothing breaks for existing extensions.
  • New ExtensionManifest.templates / ExtensionManifest.scripts properties + validation (shape, path-safety via relative_extension_path_violation, name format), consistent with the existing command/preset validators.
  • Declared templates/scripts become countable and describable, so downstream list --json / info --json (tracked separately) can report per-kind provides counts and per-artifact detail.

Strategy: replace-only, not authorable

Extension-contributed templates/scripts are always replace, and strategy is not an authorable field for extensions:

  • This matches the resolver's current forced-replace behavior (:5452) — zero behavior change, just making the implicit rule explicit.
  • The dominant extension case is contributing a brand-new artifact nothing else provides (e.g. speckit.git.feature), where replace simply means "become the base layer."
  • Augmenting an existing artifact (wrap/prepend/append) is deliberately preset territory — presets are the "customize/compose what already exists" layer; extensions are the "add new capabilities" layer. Keeping composition strategies out of extensions preserves that split.
  • Rather than accept a strategy key constrained to a single value (which invites authors to try wrap and hit a validation error), the manifest should ignore/reject any strategy key on extension templates/scripts and document the replace semantics.

Alternatives Considered

  • Keep convention-only lookup. Rejected: no metadata, no counts, replace-only by accident, asymmetric with presets.
  • Give extensions the full preset strategy set (wrap/prepend/append). Rejected — augmentation is preset territory; extensions stay additive/replace to preserve the "presets sit above extensions" layering.
  • Accept an authorable strategy field constrained to replace. Rejected — a one-value field misleads authors; better to omit it entirely and document the rule.
  • Infer script runtimes from file extensions on disk. Workable but lossy; an explicit runtimes list is clearer.

Component

Specify CLI (initialization, commands)

Use Cases

  1. A wizard/UI enumerates everything an installed extension contributes — commands and templates and scripts — with names, descriptions, and (for scripts) runtimes, read straight from the manifest.
  2. An extension author wants their shipped template/script to appear in extension info with a real description instead of being an undeclared file.
  3. Tooling reports accurate per-kind provides counts for extensions ({ commands, templates, scripts, hooks }).

Acceptance Criteria

  • Extension manifest schema accepts optional provides.templates and provides.scripts with name/file/description, and runtimes for scripts.
  • ExtensionManifest exposes templates and scripts properties with validation parity to existing command/preset validation (shape, relative-path safety, name format, runtimes type-check).
  • Replace-only, strategy not authorable: the schema does not accept a strategy field on extension templates/scripts; a present strategy key is ignored or rejected with a clear message. Resolution continues to treat extension layers as replace (:5452).
  • Declared entries are authoritative in resolution; undeclared on-disk files still resolve via convention (no regression).
  • Decision recorded: whether an extension may provide only templates/scripts (i.e. whether the "at least one command/hook/event" rule at :386-387 is relaxed).
  • schema_version impact decided (additive under 1.0 vs. bump to 1.1).
  • Tests: manifest validation (valid + malformed, including a rejected/ignored strategy key), resolver authoritative-vs-convention precedence, runtimes parsing.
  • Docs updated: AGENTS.md and extensions/EXTENSION-API-REFERENCE.md (manifest schema section), including the replace-only rule.

Additional Context

  • Related wizard-data assessment: this closes the "extensions can't declare templates/scripts" gap and the runtimes half of the script-metadata gap.
  • Downstream dependents (separate issues): --json for extension list/info and per-kind provides counts; the specify artifact composition command benefits from the added provenance.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions