Files
spec-kit/tests
chelsealongandGitHub 684b3d8e05 feat(extensions): accept provides.templates and provides.scripts in manifest (#4012)
* feat(extensions): accept provides.templates and provides.scripts in manifest

Extensions could only formally declare commands under `provides`
(plus config/hooks/events); templates and scripts shipped by an
extension were picked up purely by filename convention, with no id,
description, or metadata. Add optional `provides.templates` and
`provides.scripts` sections to the extension manifest schema, mirroring
the preset template shape minus an authorable `strategy` (extension
artifacts always resolve as replace, so a present `strategy` key is
now a validation error rather than a silently accepted no-op).

ExtensionManifest gains `templates`/`scripts` properties so tooling can
enumerate an extension's declared artifacts directly from the
manifest. An extension may now satisfy the "must provide something"
rule with only a template or script, not just a command/hook/event.

Addresses the manifest-schema portion of #4010; resolver
authoritative-vs-convention precedence for these new sections is left
for a follow-up.

* fix(presets): wire extension-declared templates/scripts into resolver

collect_all_layers only consulted ExtensionManifest for command
resolution, leaving provides.templates/.scripts purely decorative --
a declared entry whose file didn't sit at the conventional path was
validated but never resolved. Extend the existing manifest-fallback
branch to cover template_type "template" and "script" the same way
it already does "command": convention lookup first, manifest lookup
as fallback so undeclared on-disk files keep resolving unchanged.

* fix(presets): make extension manifest lookup authoritative over convention

Copilot review on #4012 found the manifest-declared template/script lookup
was gated on convention lookup missing first, so a stale conventional file
could shadow a declared entry at a non-conventional path, and resolve()
never consulted the manifest at all (only collect_all_layers() did). Add a
shared _extension_manifest_declared_template() helper and check it before
convention-based lookup in both resolve() and collect_all_layers(), mirroring
the preset manifest precedence. Also update EXTENSION-DEVELOPMENT-GUIDE.md,
which still claimed provides only supports commands and required a command
or hook.

* fix(presets): stop resolving symlinks in extension manifest candidate path

_extension_manifest_declared_template() resolved ext_dir/rel_path before
returning it, which follows symlinks in ext_dir's ancestors (e.g. macOS's
symlinked tmp dir) and diverges from the unresolved paths convention-based
lookup returns for the same directory. Resolve only for the traversal
containment check; return the unresolved candidate.

Fixes the 4 CI test failures across all OS/Python matrix jobs on #4012.
2026-08-07 12:55:55 -05:00
..