Summary
Preset commands that follow the speckit.<domain>.<cmd> naming convention are silently skipped during installation if .specify/extensions/<domain>/ does not exist as a directory. This means any preset that introduces new commands under a three-part speckit.*.* namespace can never register them — the filter intended for extension command overrides is incorrectly applied to preset-provided commands.
Affected preset: spec-kit-extended-flow — all three of its commands (speckit.extendedflow.reviewer, speckit.extendedflow.documentation, speckit.extendedflow.documentation-init) are dropped.
Steps to Reproduce
- Install any preset that declares new commands with a three-part
speckit.<domain>.<cmd> name, where <domain> is not an installed extension ID.
- Run
specify preset add --dev <path> or install from a ZIP.
- Check the agent command directories (e.g.
.opencode/commands/, .claude/commands/).
Expected: The preset's command files are written to all detected agent directories.
Actual: No command files are created. specify preset add reports success but silently drops all three-part commands.
Running the workflow later fails with:
Error: Command not found: "speckit.extendedflow.reviewer"
Root Cause
Three locations in src/specify_cli/presets.py share identical filter logic:
| Location |
Method |
Line (v0.9.4) |
| Command registration |
_register_commands() |
618–629 |
| Skill registration |
_register_skills() |
1237–1247 |
| Post-install reconciliation |
install_from_directory() |
1589–1602 |
All three contain:
# Filter out extension command overrides if the extension isn't installed.
extensions_dir = self.project_root / ".specify" / "extensions"
filtered = []
for cmd in command_templates:
parts = cmd["name"].split(".")
if len(parts) >= 3 and parts[0] == "speckit":
ext_id = parts[1]
if not (extensions_dir / ext_id).is_dir():
continue # ← silently skips the command
filtered.append(cmd)
The intent is correct for extension command overrides — commands a preset provides to wrap/replace a command that lives inside an extension (e.g. speckit.git.feature should only register when the git extension is installed). But the filter cannot distinguish:
- Extension command overrides — wrap an existing extension command; should be skipped when the extension is absent.
- Preset-provided commands — brand-new commands introduced by the preset; should always register.
Both use three-part names, so all three-part names are gated on an extension directory that will never exist for a purely preset-provided command.
Impact
- Any preset that introduces new agent commands under a
speckit.<domain>.<cmd> namespace is broken out of the box.
- Failure is completely silent — no warning, no error, install reports success.
- All downstream workflow steps that invoke those commands fail at runtime.
Workaround (until upstream fix)
Create an empty stub directory to satisfy the filter, then reinstall:
mkdir -p .specify/extensions/extendedflow
specify preset remove spec-kit-extended-flow
specify preset add --dev ../spec-kit-extended-flow
The filter only calls is_dir() — an empty directory passes.
Suggested Fix
The filter needs to distinguish extension command overrides from preset-provided commands. One clean approach: check whether the <domain> segment matches the name of any installed extension. If not, the command is preset-provided and should register unconditionally. A minimal, backward-compatible patch in all three locations:
for cmd in command_templates:
parts = cmd["name"].split(".")
if len(parts) >= 3 and parts[0] == "speckit":
ext_id = parts[1]
ext_dir = extensions_dir / ext_id
if not ext_dir.is_dir():
# Only skip if this command overrides an extension command (i.e.
# an extension with this ID exists elsewhere / was once installed).
# Pure preset-provided commands (no matching extension) always register.
if not any(
(self.project_root / ".specify" / "extensions" / ext_id).parent.exists()
for _ in [None]
):
pass # falls through to filtered.append(cmd)
# For a cleaner fix: gate only on cmd.get('strategy') != 'replace'
# (extension overrides typically use wrap/prepend/append).
filtered.append(cmd)
The simplest safe heuristic: if no extension directory with that ID exists anywhere in the installed extension set, treat the command as preset-provided and always register it.
Spec-Kit Version
v0.9.4 (latest release). The same filter code is present in main (v0.9.5.dev0) — not yet fixed.
References
Summary
Preset commands that follow the
speckit.<domain>.<cmd>naming convention are silently skipped during installation if.specify/extensions/<domain>/does not exist as a directory. This means any preset that introduces new commands under a three-partspeckit.*.*namespace can never register them — the filter intended for extension command overrides is incorrectly applied to preset-provided commands.Affected preset: spec-kit-extended-flow — all three of its commands (
speckit.extendedflow.reviewer,speckit.extendedflow.documentation,speckit.extendedflow.documentation-init) are dropped.Steps to Reproduce
speckit.<domain>.<cmd>name, where<domain>is not an installed extension ID.specify preset add --dev <path>or install from a ZIP..opencode/commands/,.claude/commands/).Expected: The preset's command files are written to all detected agent directories.
Actual: No command files are created.
specify preset addreports success but silently drops all three-part commands.Running the workflow later fails with:
Root Cause
Three locations in
src/specify_cli/presets.pyshare identical filter logic:_register_commands()_register_skills()install_from_directory()All three contain:
The intent is correct for extension command overrides — commands a preset provides to wrap/replace a command that lives inside an extension (e.g.
speckit.git.featureshould only register when thegitextension is installed). But the filter cannot distinguish:Both use three-part names, so all three-part names are gated on an extension directory that will never exist for a purely preset-provided command.
Impact
speckit.<domain>.<cmd>namespace is broken out of the box.Workaround (until upstream fix)
Create an empty stub directory to satisfy the filter, then reinstall:
The filter only calls
is_dir()— an empty directory passes.Suggested Fix
The filter needs to distinguish extension command overrides from preset-provided commands. One clean approach: check whether the
<domain>segment matches the name of any installed extension. If not, the command is preset-provided and should register unconditionally. A minimal, backward-compatible patch in all three locations:The simplest safe heuristic: if no extension directory with that ID exists anywhere in the installed extension set, treat the command as preset-provided and always register it.
Spec-Kit Version
v0.9.4 (latest release). The same filter code is present in
main(v0.9.5.dev0) — not yet fixed.References
src/specify_cli/presets.py, lines 618–629, 1237–1247, 1589–1602