Problem Statement
_validate_install_conflicts is documented as rejecting installs that "would shadow core or installed extension commands", but it only checks installed extensions. Core command names are never in the map it consults, so an extension can claim a core command name as an alias and install successfully.
Split out of #4421 at a maintainer's request, so that stage 1 (#4488) stays additive and no core-validation change is smuggled into it.
Reproduction
main at d848fb4. A manifest whose command carries speckit.taskstoissues — a live core command — as an alias:
schema_version: "1.0"
extension:
id: probe
name: "Probe"
version: "1.0.0"
description: "probe"
requires:
speckit_version: ">=0.2.0"
provides:
commands:
- name: speckit.probe.taskstoissues
file: commands/cmd.md
aliases: ["speckit.taskstoissues"] # core command name
Result:
1) manifest validation:
ACCEPTED — aliases: [['speckit.taskstoissues']]
2) install into a project (core taskstoissues exists):
ACCEPTED — installed: probe
Both stages accept it, with the core command present in the same project.
Cause
Two gaps that line up:
-
Aliases are not pattern-checked. EXTENSION_COMMAND_NAME_PATTERN and the core-namespace guard in src/specify_cli/extensions/__init__.py apply to primary command names only. A primary speckit.taskstoissues is correctly rejected (it fails the two-segment pattern, and an extension id equal to a core command name is rejected separately) — but an alias goes through neither check.
-
The conflict check does not know about core. _get_installed_command_name_map walks self.registry — installed extensions — so core command names never appear in the map that _validate_install_conflicts compares against. Confirmed by inspection: the function references self.registry and nothing that loads core command names (_load_core_command_names / _FALLBACK_CORE_COMMAND_NAMES).
So the docstring describes a guarantee ("shadow core or installed extension commands") that is only half implemented.
Impact
A community extension can claim a core command's name as an alias and shadow it. This is most likely to bite during the #4421 migration, where the whole point of stage 1 is that speckit.taskstoissues and speckit.github.taskstoissues coexist without either shadowing the other — but it applies to any core command, not just this one.
The bundled github extension in #4488 deliberately registers no alias, and has a test pinning that. Worth being explicit that this is currently author discipline, not an enforced invariant — which is the reason to file this separately rather than rely on the convention holding.
Suggested Fix
Either:
- extend the core-namespace guard to aliases as well as primary names, and include core command names in the map
_validate_install_conflicts consults; or
- if shadowing core via an alias is intended to be allowed (an override mechanism), narrow the docstring to describe what is actually checked.
Whichever way it goes, a test should pin the decision — an install that claims speckit.taskstoissues as an alias currently passes silently either way.
Component
Specify CLI (initialization, commands)
Disclosure: I used an AI assistant (Claude Code, model Claude Opus 5) to investigate this and draft this report. The results above came from running the probe manifest through ExtensionManifest and ExtensionManager.install_from_directory against main; I reviewed the findings before filing.
Problem Statement
_validate_install_conflictsis documented as rejecting installs that "would shadow core or installed extension commands", but it only checks installed extensions. Core command names are never in the map it consults, so an extension can claim a core command name as an alias and install successfully.Split out of #4421 at a maintainer's request, so that stage 1 (#4488) stays additive and no core-validation change is smuggled into it.
Reproduction
mainatd848fb4. A manifest whose command carriesspeckit.taskstoissues— a live core command — as an alias:Result:
Both stages accept it, with the core command present in the same project.
Cause
Two gaps that line up:
Aliases are not pattern-checked.
EXTENSION_COMMAND_NAME_PATTERNand the core-namespace guard insrc/specify_cli/extensions/__init__.pyapply to primary command names only. A primaryspeckit.taskstoissuesis correctly rejected (it fails the two-segment pattern, and an extensionidequal to a core command name is rejected separately) — but an alias goes through neither check.The conflict check does not know about core.
_get_installed_command_name_mapwalksself.registry— installed extensions — so core command names never appear in the map that_validate_install_conflictscompares against. Confirmed by inspection: the function referencesself.registryand nothing that loads core command names (_load_core_command_names/_FALLBACK_CORE_COMMAND_NAMES).So the docstring describes a guarantee ("shadow core or installed extension commands") that is only half implemented.
Impact
A community extension can claim a core command's name as an alias and shadow it. This is most likely to bite during the #4421 migration, where the whole point of stage 1 is that
speckit.taskstoissuesandspeckit.github.taskstoissuescoexist without either shadowing the other — but it applies to any core command, not just this one.The bundled
githubextension in #4488 deliberately registers no alias, and has a test pinning that. Worth being explicit that this is currently author discipline, not an enforced invariant — which is the reason to file this separately rather than rely on the convention holding.Suggested Fix
Either:
_validate_install_conflictsconsults; orWhichever way it goes, a test should pin the decision — an install that claims
speckit.taskstoissuesas an alias currently passes silently either way.Component
Specify CLI (initialization, commands)
Disclosure: I used an AI assistant (Claude Code, model Claude Opus 5) to investigate this and draft this report. The results above came from running the probe manifest through
ExtensionManifestandExtensionManager.install_from_directoryagainstmain; I reviewed the findings before filing.