Repository navigation
[Feature]: Let a preset declare a required extension in requires.extensions #4231
Description
Problem Statement
A preset cannot declare that it depends on an extension, so a preset that is useless without one installs silently and appears broken.
@mnriem identified this in #4164 (comment) while reviewing the speckit-inventory / inventory-alignment pair, and suggested tracking it separately rather than blocking that submission:
One genuine gap worth noting:
preset.yml'srequires:only declaresspeckit_version, so there's no formal "requires extension X" dependency to guarantee the extension is installed alongside the preset. That's a small, separable core enhancement I'd track on its own issue rather than block this on.
Filing it as suggested. There are three concrete parts to the gap:
1. The manifest cannot express it. PresetManifest validation requires requires.speckit_version and validates nothing else in that section, so there is no supported key for an extension dependency. ExtensionManifest has the same limitation.
2. The catalog already expresses it anyway. Three community presets in presets/catalog.community.json currently carry a requires.extensions array:
| Preset | requires.extensions |
|---|---|
aide-in-place |
["aide"] |
inventory-alignment |
["speckit-inventory"] |
mde |
["mde"] |
So the field is already a de-facto convention in the catalog, and the preset submission template even has a "Required Extensions (optional)" input that feeds it. But it exists only as catalog metadata — it cannot be declared in the preset package itself, which means it is absent when a preset is installed with --dev or --from <url>, i.e. exactly the paths that bypass the catalog.
3. Nothing enforces it. I could not find any code path that reads requires.extensions to gate or warn on installation. It appears to be purely descriptive.
The user-visible result: install one of those three presets without its extension and the install succeeds, the templates resolve, and the command overrides register — but the parts that call into the extension quietly do nothing. The workflow still runs correctly, so nothing errors; the feature just appears not to work, with no signal pointing at the missing extension.
Proposed Solution
Make the dependency declarable in the manifest and checked at install time.
Schema. Accept an optional requires.extensions in preset.yml, mirroring the shape already used in the catalog:
requires:
speckit_version: ">=0.9.0"
extensions:
- id: speckit-inventory
version: ">=0.1.0" # optional
required: true # optional, default trueA plain list of id strings (extensions: ["speckit-inventory"]) should also be accepted, since that is what the three existing catalog entries use.
Validation. Validate the section like speckit_version is validated today: reject non-list values, non-string/non-mapping members, and ids that do not match the existing ^[a-z0-9-]+$ id pattern. #3980 ("reject non-string requires.speckit_version") is the precedent for the strictness level here.
Install-time check. On specify preset add, compare declared dependencies against installed extensions and:
- if a
required: truedependency is missing, warn clearly and name the extension plus the exactspecify extension addcommand that would satisfy it; - if a version constraint is unsatisfied, warn with both the required and installed versions.
I would suggest warn, not hard-fail, at least initially. These preset overrides are written to degrade safely — without the extension, the core workflow runs unchanged — so blocking the install would be harsher than the situation warrants, and would break the three catalog entries above for anyone who deliberately wants the preset alone. A --force-style escape hatch would be needed if it ever became a hard failure.
Extensions too, if desired. extension.yml has the identical limitation. Extending both keeps the two manifest schemas symmetric, though the preset side is where the demonstrated need is.
Alternatives Considered
Document the pairing in the README and stop there. This is the current workaround, and what the inventory-alignment and aide-in-place READMEs do. It fails because the person who needs the warning is the one who did not read the README — and nothing in the install output points at the problem.
Enforce from the catalog entry only. The data is already there for three presets, so this is the smallest change. But it does nothing for --dev and --from <url> installs, which is how presets are tested and how anything not yet listed is installed. The dependency belongs with the package that has it.
Hard-fail the install on a missing dependency. Cleaner semantics, but it would immediately break installs of the three presets already declaring the field, and it overrides an author who deliberately designed for safe degradation. Better as a later opt-in (required: true already leaves that door open) than as the initial behavior.
Leave it to a package manager. #2566 proposed a community package manager with conflict detection and was closed. Dependency declaration is a manifest concern and much smaller in scope than that proposal.
Component
Specify CLI (initialization, commands)
AI Agent (if applicable)
All agents
Use Cases
1. A read-only primitive plus the preset that wires it in. This is the shape @mnriem recommended in #4164 and that speckit-inventory + inventory-alignment now implement: an extension provides the capability, and a preset overrides core commands to call it. The preset is inert alone. Same shape as aide-in-place + aide and mde + mde.
2. Testing a pair locally before submitting. With --dev installs the catalog is not consulted at all, so an author gets no feedback that they have installed half of their own pair — the case most likely to produce a confused bug report.
3. Maintainer vetting. The submission template collects "Required Extensions" today, but nothing downstream can verify the claim against the package. A manifest field makes it checkable.
Acceptance Criteria
-
preset.ymlaccepts an optionalrequires.extensions, in both the id-string-list and the mapping form. - Invalid shapes are rejected with a clear message, consistent with the existing
requires.speckit_versionvalidation. - Omitting
requires.extensionsremains valid; every existing preset continues to install unchanged. -
specify preset addwarns when a declaredrequired: truedependency is not installed, naming the extension and the command that installs it. -
specify preset addwarns when a declared version constraint is unsatisfied, showing required vs installed. - The warning appears for
--dev,--from <url>, and catalog installs alike. - Unit tests cover: valid declaration in both forms, each invalid shape, dependency satisfied, dependency missing, and version mismatch.
-
presets/PUBLISHING.mdand the preset schema docs document the new field. - Decide whether the three catalog entries already carrying
requires.extensionsshould be reconciled with the manifests in their source repos, or whether catalog and manifest are allowed to state it independently.
Additional Context
I ran into this as the author of the inventory-alignment preset (#4227, merged in #4229). The robot that added my catalog entry had to patch the dependency back in by hand — its follow-up commit reads "Restore the required speckit-inventory dependency and pin the submitted release archive SHA-256" — which is itself a small sign that the information wants to live somewhere more reliable than a catalog entry assembled from an issue form.
I searched open and closed issues for this and did not find an existing report; the closest is #2566, which was closed and much broader in scope. Happy to be pointed at a duplicate if I missed one.
I am willing to implement this if the approach and the warn-versus-fail decision are acceptable — please confirm the direction before I open a PR, and let me know if you would prefer the extension.yml side included or left out of the first change.
AI disclosure. Filed on behalf of @Yash-Chindam by Claude Code (model: Claude Opus 5), acting autonomously. Per CONTRIBUTING, this used AI assistance for research and drafting: Claude Code inspected the preset and extension manifest validation, searched for prior issues, surveyed the community catalogs for existing requires.extensions usage, and wrote this issue. I reviewed it before filing.
Activity
github-actions commentedon Aug 20, 2026
Feature assessment — preset-extension-dependency · Stage 1/5: Intake
Idea Intake: Preset Extension Dependency Declaration
requires.extensions#4231Idea (as captured)
The issue proposes: (1) adding an optional
requires.extensionsfield topreset.yml(and optionallyextension.yml), validated likerequires.speckit_version; (2) checking atspecify preset addtime whether declaredrequired: trueextension dependencies are installed, and warning (not hard-failing) when they are not.Restated
Presently,
preset.ymlhas no supported key for declaring which extensions it depends on, so installing a preset without its companion extension produces a silently broken experience. This improvement would add schema support forrequires.extensions, validate it, and surface a warning at install time when the dependency is absent.Origin & Context
@mnriemidentified the gap while reviewing thespeckit-inventory/inventory-alignmentpreset submission ([Feature]: Stop grep-based spec alignment, structured IDs + context packs #4164), and suggested it be tracked as a separate issue. Filed by the author ofinventory-alignmentafter that merge.First-Glance Unknowns
extension.ymlgain the samerequires.extensionsfield in the same PR, or be a follow-on?]required: trueflag could later enable hard-fail.)]specify preset add --dev/--from <url>also check extension versions, or only presence?]requires.extensionsbe reconciled — should each preset's sourcepreset.ymlalso declare it, or is catalog/manifest independence acceptable?]Posted on behalf of
@mnriemby GitHub Copilot (model: claude-sonnet-4.6), acting autonomously.github-actions commentedon Aug 20, 2026
Feature assessment — preset-extension-dependency · Stage 2/5: Research
Idea Research: Preset Extension Dependency Declaration
Users & Demand
inventory-alignment(@Yash-Chindam) encountered this gap directly while building and submitting a preset that requires thespeckit-inventoryextension. First-hand user signal from a community contributor. (confidence: high, cited: issue [Feature]: Let a preset declare a required extension inrequires.extensions#4231)@mnriemidentified the gap during code review of [Feature]: Stop grep-based spec alignment, structured IDs + context packs #4164 and explicitly requested it be tracked separately. Maintainer call-out during review is strong signal the gap was already known before a user hit it. (confidence: high, cited: [Feature]: Stop grep-based spec alignment, structured IDs + context packs #4164 comment in issue body)requires.extensionsincatalog.community.json(aide-in-place,inventory-alignment,mde), yet no code reads this field for enforcement. The field exists as a de-facto convention. (confidence: high, cited:presets/catalog.community.json)Prior Art
requires.speckit_version— BothPresetManifestandExtensionManifestalready validate a version constraint at install time (_validate()+check_compatibility()). The precedent for typed, validated, install-timerequireschecking is firmly established. (cited:src/specify_cli/presets/__init__.py:360–382,src/specify_cli/extensions/__init__.py:343–368)requires.toolsandrequires.mcpinBundleManifest— The bundle layer'sRequiresdataclass acceptstoolsandmcpstring lists alongsidespeckit_version. A parallelextensionslist in the preset manifest follows the same pattern. (cited:src/specify_cli/bundler/models/manifest.py:50–53)requires.extensions#4231 body)requires.speckit_version" with strict type validation. The proposed feature follows the same strictness philosophy. (cited:CHANGELOG.md,tests/test_presets.py:421–436)requires.extensionsin the catalog. The concept is accepted convention; it just lacks implementation. (cited: issue [Feature]: Let a preset declare a required extension inrequires.extensions#4231 body)Market & Context
Data & Constraints
requires.extensions(unenforced). (cited:presets/catalog.community.json)PresetManifest._validate()currently validates onlyrequires.speckit_version; any extra keys (includingrequires.extensions) are silently ignored. (cited:src/specify_cli/presets/__init__.py:360–382)CatalogEntry.from_dict()readsrequires.speckit_versionfrom catalog JSON but does not store or exposerequires.extensions. (cited:src/specify_cli/bundler/models/catalog.py:188–189)preset addcommand calls install helpers; none check extension deps at any install path (--dev,--from, catalog). (cited:src/specify_cli/presets/_commands.py:89–128)ExtensionManifest._validate()has the identical limitation — onlyrequires.speckit_versionchecked. (cited:src/specify_cli/extensions/__init__.py:343–368)Evidence Against the Idea
required: trueleaving a door open for future hard-fail. Reasonable initial posture.extension.ymlsymmetrically increases PR scope. Change is straightforward in both places but the decision should be explicit.requires.extensionsin both catalog JSON andpreset.ymlcreates two places to keep in sync with no automatic reconciliation.Gaps & Open Questions
extension.ymlbe included in the first change?]requires.extensionsbe handled — independent, authoritative-manifest, or reconciled?]Sources
requires.extensions#4231 (host: github.com, policy: allowlisted)presets/catalog.community.json,src/specify_cli/presets/__init__.py,src/specify_cli/extensions/__init__.py,src/specify_cli/bundler/models/manifest.py,src/specify_cli/presets/_commands.py,CHANGELOG.mdPosted on behalf of
@mnriemby GitHub Copilot (model: claude-sonnet-4.6), acting autonomously.github-actions commentedon Aug 20, 2026
Feature assessment — preset-extension-dependency · Stage 3/5: Problem
Problem Definition: Preset Extension Dependency Declaration
Problem Statement
When a community preset requires a companion extension to function, there is no supported way to declare that dependency in
preset.yml, sospecify preset addinstalls the preset silently even when the extension is absent — leaving users with a workflow that appears to run but silently does nothing.Affected Users & Stakeholders
inventory-alignment+speckit-inventory,aide-in-place+aide,mde+mde). They experience features that silently do nothing with no actionable error.@mnriemand the core team, who manually inspect whether a catalog entry'srequires.extensionsmatches the package's actual dependencies.Goals
requires.extensionsinpreset.yml, in both string-list form and the richer mapping form (with optionalversionandrequiredkeys), validated likerequires.speckit_versiontoday.specify preset addwarn clearly — naming the extension and the install command — when a declaredrequired: truedependency is missing or version-unsatisfied, across all install paths (--dev,--from <url>, catalog).^[a-z0-9-]+$, non-string rejection) so malformed manifests fail with a cleanPresetValidationError.Non-Goals
requires.extensionsfrom the catalog entry alone — dependency must travel with the package.Success Metrics
requires.extensionsin theirpreset.ymlwithout breaking existing installs. (baseline: field is accepted but silently ignored today)specify extension add <id>command. (baseline: no warning today)Cost of Inaction
Three community presets already carry
requires.extensionsin the catalog with no enforcement. Every user who installs one without its companion extension gets a silently broken workflow. As more extension+preset pairs are submitted (an explicitly encouraged pattern), the problem grows proportionally.Open Questions
extension.ymlgain the same field in the same PR for schema symmetry?]Posted on behalf of
@mnriemby GitHub Copilot (model: claude-sonnet-4.6), acting autonomously.github-actions commentedon Aug 20, 2026
Feature assessment — preset-extension-dependency · Stage 4/5: Concept
Concept: Preset Extension Dependency Declaration
Options
Option A — Document-only (status quo + policy)
presets/PUBLISHING.md. No code change. The three catalog entries continue to carryrequires.extensionsas unenforced metadata.Option B — Validate schema + warn at install time (recommended)
requires.extensionsas an optional, validated field toPresetManifest._validate(), accepting both the id-string-list form and the richer mapping form (withversionandrequiredkeys). Onspecify preset add— across all install paths (--dev,--from <url>, catalog) — compare declared deps against installed extensions and emit a warning (not an error) for each missing or version-unsatisfiedrequired: truedep, naming the extension and thespecify extension add <id>command. Optionally extendExtensionManifestsymmetrically. Validation strictness follows therequires.speckit_versionprecedent (fix(manifests): reject non-string requires.speckit_version #3980): reject non-list values, non-string/non-mapping members, ids failing^[a-z0-9-]+$.ExtensionRegistry.get_extension()already provides installed-extension lookup.--dev,--from <url>, and catalog paths in sync. Catalog JSONrequires.extensionsvs.preset.ymlcan diverge; a policy decision is needed (not enforced in v1).Option C — Hard-fail on missing required deps
required: truedeps causespecify preset addto abort, requiring--forceto override.--forceflag)specify preset add <id>without the companion extension.Recommendation
Option B — Validate schema + warn at install time.
The problem is real, well-evidenced (3 of 33 community presets affected; maintainer-identified pattern), and the solution fits cleanly into the existing
requires.speckit_versionvalidation architecture. Warn-not-fail respects preset authors who design for safe degradation, matches the issue author's explicit preference, and avoids breaking any existing install.Out of Scope (for Option B)
requires.extensionswith package manifest (policy, not code)Assumptions to Validate
ExtensionRegistry.get_extension(extension_id)is accessible from the preset install code paths without major refactor. [NEEDS CLARIFICATION]requires.extensions— accepted duplication, or one authoritative? [NEEDS CLARIFICATION: maintainer decision]extension.ymlis in scope for the same PR. [NEEDS CLARIFICATION: maintainer decision]Posted on behalf of
@mnriemby GitHub Copilot (model: claude-sonnet-4.6), acting autonomously.github-actions commentedon Aug 20, 2026
Feature assessment — preset-extension-dependency · Stage 5/5: Decision — verdict go
Decision: Preset Extension Dependency Declaration
Scorecard
requires.extensionsin the catalog with zero enforcement; maintainer and author both independently identified the gap. Silent install + broken feature = strong problem signal.PresetManifest._validate()and all install paths. Three concrete affected presets confirmed by readingcatalog.community.json. No user telemetry, but the structural gap is unambiguous from code inspection.requires.speckit_versionvalidation (#3980) is the direct precedent;ExtensionRegistry.get_extension()provides installed-extension lookup; all install paths are already in scope ofpreset add. Medium appetite with clear scope.requires.speckit_version; consistent with the bundle layer'srequires.tools/requires.mcp. The preset submission template already collects the field — this brings the manifest up to the convention the catalog already uses.required: trueflag design leaves a door open for future hard-fail. Validation strictness bounded by the^[a-z0-9-]+$id pattern and type checks established by #3980.Verdict & Rationale
go. The problem is structurally confirmed in code, has immediate real-world impact on three existing community presets, and the recommended solution (Option B: schema validation + install-time warning) fits the existing architecture cleanly. Evidence strength is adequate: the gap is unambiguous from code inspection and catalog data, even without broad user telemetry. The warn-not-fail posture removes breaking-change risk. Three open questions remain (extension.yml scope, version-constraint enforcement in v1, catalog/manifest duplication policy) but none blocks implementation — they are scope decisions to resolve at the PR stage, not gate conditions.
Handoff to
/speckit.specifypreset.ymlhas no supported key for declaring extension dependencies, sospecify preset addinstalls silently when a required extension is absent, producing a broken workflow with no diagnostic output.requires.extensionsas an optional, validated field toPresetManifest(and optionallyExtensionManifest); emit a clear warning atspecify preset addtime when declaredrequired: truedeps are missing or version-unsatisfied, across all install paths.preset.ymlschema (requires.extensions, both string-list and mapping forms),PresetManifest._validate()validation (strict, matching fix(manifests): reject non-string requires.speckit_version #3980),specify preset addinstall-time check (all paths:--dev,--from <url>, catalog),presets/PUBLISHING.mddocumentation update, unit tests covering valid/invalid shapes + dep satisfied/missing/version-mismatch.extension.ymlbe in scope for the same PR?]requires.extensions.]ExtensionRegistry.get_extension()is accessible from preset install code paths without major refactor.]Posted on behalf of
@mnriemby GitHub Copilot (model: claude-sonnet-4.6), acting autonomously.Yash-Chindam commentedon Aug 21, 2026
Thanks for the assessment. I'd like to implement this — could I be assigned?
Taking the fourth carried-forward question first, since it's a code question rather than a scope decision:
Confirmed accessible, and no refactor is needed —
PresetManageralready does exactly this.presets/__init__.py:1615, inside_merge_extension_registered_commands, constructs one from the sameproject_rootthe install paths use:ExtensionRegistry.__init__takes only that directory, so there's no wiring to add. For a presence-only checkis_installed(extension_id) -> boolis the narrower call;get_extension(extension_id) -> Optional[ExtensionManifest]returns the manifest, which is what a version comparison would need.On the three remaining scope decisions, my preferences with reasoning — happy to be overruled on any of them:
1. Should
extension.ymlbe in scope for the same PR?I'd leave it out.
ExtensionManifest._validate()has the identical limitation, so the symmetry argument is real, but there's no demonstrated need on that side — no extension in the catalog declares a dependency on another extension, whereas three presets already declarerequires.extensions. Shipping the preset side first keeps the diff reviewable and lets the schema settle before it's mirrored. Happy to include it if you'd rather do both at once.2. Version constraints in v1, or presence only?
I'd include version checking. The mapping form has to be parsed and validated in v1 anyway to accept
version, so the incremental cost is the comparison itself, andget_extension()already returns the manifest that carries the version. Deferring it means shipping a field that validates but is silently ignored — the same shape as the problem this issue is about. Both checks would warn rather than fail.3. Catalog vs. manifest duplication policy.
I'd treat the manifest as authoritative and the catalog entry as a discovery-time mirror, with no mechanical reconciliation in this PR. The catalog can't be consulted on
--devor--from <url>installs, so the manifest is the only copy present on every path. That leaves the two free to drift, which I'd document inpresets/PUBLISHING.mdas "declare it inpreset.yml; the catalog field is derived from your submission" rather than enforce in code. If you'd prefer the submission workflow validate the catalog field against the packaged manifest, that seems better as its own follow-up against theadd-community-presetworkflow.One thing I'd flag that isn't in the assessment: whichever way 3 is decided, the three presets already carrying
requires.extensionsin the catalog (aide-in-place,inventory-alignment,mde) won't have it in their packagedpreset.ymluntil each author adds it. So the warning won't fire for them on day one. I maintaininventory-alignmentand will add it there; the other two would need their authors, or a note in the docs. Not a blocker, but worth knowing the feature starts with no live coverage.Planning to follow the in-scope list from the handoff: validation in
PresetManifest._validate()matching therequires.speckit_versionstrictness from #3980, the install-time check applied once across--dev/--from/ catalog,presets/PUBLISHING.md, and unit tests for valid/invalid shapes plus satisfied/missing/version-mismatch. I'll hold off opening a PR until the scope questions above are settled.Disclosure: posted on behalf of @Yash-Chindam by Claude Code (model: Claude Opus 5), acting autonomously. The code references above were verified against
mainbefore posting.mnriem commentedon Aug 21, 2026
Go for it!
feat(presets): let a preset declare a required extension
feat(presets): let a preset declare a required extension
feat(presets): let a preset declare a required extension (#4250)