Skip to content

Commit fc99452

Browse files
iamaeroplaneclaude
andcommitted
fix(extensions): extend auto-correction to aliases (#2017)
The upstream #1994 added alias validation in _collect_manifest_command_names, which also rejected legacy 2-part alias names (e.g. 'speckit.verify'). Extend the same auto-correction logic from _validate() to cover aliases, so both 'speckit.command' and 'extension.command' alias formats are corrected to 'speckit.{ext_id}.{command}' with a compatibility warning instead of hard-failing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 44d1996 commit fc99452

File tree

2 files changed

+43
-4
lines changed

2 files changed

+43
-4
lines changed

src/specify_cli/extensions.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,24 @@ def _validate(self):
208208
"must follow pattern 'speckit.{extension}.{command}'"
209209
)
210210

211+
# Validate and auto-correct alias name formats
212+
aliases = cmd.get("aliases") or []
213+
for i, alias in enumerate(aliases):
214+
if not EXTENSION_COMMAND_NAME_PATTERN.match(alias):
215+
corrected = self._try_correct_command_name(alias, ext["id"])
216+
if corrected:
217+
self.warnings.append(
218+
f"Alias '{alias}' does not follow the required pattern "
219+
f"'speckit.{{extension}}.{{command}}'. Registering as '{corrected}'. "
220+
f"The extension author should update the manifest to use this name."
221+
)
222+
aliases[i] = corrected
223+
else:
224+
raise ValidationError(
225+
f"Invalid alias '{alias}': "
226+
"must follow pattern 'speckit.{extension}.{command}'"
227+
)
228+
211229
@staticmethod
212230
def _try_correct_command_name(name: str, ext_id: str) -> Optional[str]:
213231
"""Try to auto-correct a non-conforming command name to the required pattern.

tests/test_extensions.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,23 @@ def test_command_name_autocorrect_no_speckit_prefix(self, temp_dir, valid_manife
287287
assert "docguard.guard" in manifest.warnings[0]
288288
assert "speckit.docguard.guard" in manifest.warnings[0]
289289

290+
def test_alias_autocorrect_speckit_prefix(self, temp_dir, valid_manifest_data):
291+
"""Test that a legacy 'speckit.command' alias is auto-corrected."""
292+
import yaml
293+
294+
valid_manifest_data["provides"]["commands"][0]["aliases"] = ["speckit.hello"]
295+
296+
manifest_path = temp_dir / "extension.yml"
297+
with open(manifest_path, 'w') as f:
298+
yaml.dump(valid_manifest_data, f)
299+
300+
manifest = ExtensionManifest(manifest_path)
301+
302+
assert manifest.commands[0]["aliases"] == ["speckit.test-ext.hello"]
303+
assert len(manifest.warnings) == 1
304+
assert "speckit.hello" in manifest.warnings[0]
305+
assert "speckit.test-ext.hello" in manifest.warnings[0]
306+
290307
def test_valid_command_name_has_no_warnings(self, temp_dir, valid_manifest_data):
291308
"""Test that a correctly-named command produces no warnings."""
292309
import yaml
@@ -681,8 +698,8 @@ def test_install_rejects_extension_id_in_core_namespace(self, temp_dir, project_
681698
with pytest.raises(ValidationError, match="conflicts with core command namespace"):
682699
manager.install_from_directory(ext_dir, "0.1.0", register_commands=False)
683700

684-
def test_install_rejects_alias_without_extension_namespace(self, temp_dir, project_dir):
685-
"""Install should reject legacy short aliases that can shadow core commands."""
701+
def test_install_autocorrects_alias_without_extension_namespace(self, temp_dir, project_dir):
702+
"""Legacy short aliases are auto-corrected to 'speckit.{ext_id}.{cmd}' with a warning."""
686703
import yaml
687704

688705
ext_dir = temp_dir / "alias-shortcut"
@@ -713,8 +730,12 @@ def test_install_rejects_alias_without_extension_namespace(self, temp_dir, proje
713730
(ext_dir / "commands" / "cmd.md").write_text("---\ndescription: Test\n---\n\nBody")
714731

715732
manager = ExtensionManager(project_dir)
716-
with pytest.raises(ValidationError, match="Invalid alias 'speckit.shortcut'"):
717-
manager.install_from_directory(ext_dir, "0.1.0", register_commands=False)
733+
manifest = manager.install_from_directory(ext_dir, "0.1.0", register_commands=False)
734+
735+
assert manifest.commands[0]["aliases"] == ["speckit.alias-shortcut.shortcut"]
736+
assert len(manifest.warnings) == 1
737+
assert "speckit.shortcut" in manifest.warnings[0]
738+
assert "speckit.alias-shortcut.shortcut" in manifest.warnings[0]
718739

719740
def test_install_rejects_namespace_squatting(self, temp_dir, project_dir):
720741
"""Install should reject commands and aliases outside the extension namespace."""

0 commit comments

Comments
 (0)