@@ -305,63 +305,85 @@ def test_command_name_mismatched_namespace_not_corrected(self, temp_dir, valid_m
305305 ExtensionManifest (manifest_path )
306306
307307 def test_alias_valid_two_part_no_prefix (self , temp_dir , valid_manifest_data ):
308- """Test that a 'test-ext.hello ' alias is accepted as-is with no warning."""
308+ """Test that a 'test-ext.greet ' alias is accepted as-is with no warning."""
309309 import yaml
310310
311- valid_manifest_data ["provides" ]["commands" ][0 ]["aliases" ] = ["test-ext.hello" ]
311+ # Use a name distinct from the primary command's suffix ('hello') to avoid
312+ # the SKILL.md output-name collision (both would map to speckit-test-ext-hello).
313+ valid_manifest_data ["provides" ]["commands" ][0 ]["aliases" ] = ["test-ext.greet" ]
312314
313315 manifest_path = temp_dir / "extension.yml"
314316 with open (manifest_path , 'w' ) as f :
315317 yaml .dump (valid_manifest_data , f )
316318
317319 manifest = ExtensionManifest (manifest_path )
318320
319- assert manifest .commands [0 ]["aliases" ] == ["test-ext.hello " ]
321+ assert manifest .commands [0 ]["aliases" ] == ["test-ext.greet " ]
320322 assert manifest .warnings == []
321323
322324 def test_alias_autocorrect_speckit_two_part (self , temp_dir , valid_manifest_data ):
323325 """Test that legacy 'speckit.command' alias is corrected to '{ext_id}.command'."""
324326 import yaml
325327
326- valid_manifest_data ["provides" ]["commands" ][0 ]["aliases" ] = ["speckit.hello" ]
328+ # Use 'speckit.greet' → 'test-ext.greet' to avoid colliding with the primary
329+ # command's SKILL output name (speckit.test-ext.hello → speckit-test-ext-hello).
330+ valid_manifest_data ["provides" ]["commands" ][0 ]["aliases" ] = ["speckit.greet" ]
327331
328332 manifest_path = temp_dir / "extension.yml"
329333 with open (manifest_path , 'w' ) as f :
330334 yaml .dump (valid_manifest_data , f )
331335
332336 manifest = ExtensionManifest (manifest_path )
333337
334- assert manifest .commands [0 ]["aliases" ] == ["test-ext.hello " ]
338+ assert manifest .commands [0 ]["aliases" ] == ["test-ext.greet " ]
335339 assert len (manifest .warnings ) == 1
336- assert "speckit.hello " in manifest .warnings [0 ]
337- assert "test-ext.hello " in manifest .warnings [0 ]
340+ assert "speckit.greet " in manifest .warnings [0 ]
341+ assert "test-ext.greet " in manifest .warnings [0 ]
338342
339343 def test_alias_autocorrect_speckit_three_part (self , temp_dir , valid_manifest_data ):
340344 """Test that a 3-part 'speckit.ext.command' alias is corrected to 'ext.command'."""
341345 import yaml
342346
343347 # Clear hooks so the alias rename doesn't also trigger a hook-reference warning.
348+ # Use 'speckit.test-ext.greet' → 'test-ext.greet' to avoid colliding with the
349+ # primary command's SKILL output name (speckit.test-ext.hello → speckit-test-ext-hello).
344350 valid_manifest_data .pop ("hooks" , None )
345- valid_manifest_data ["provides" ]["commands" ][0 ]["aliases" ] = ["speckit.test-ext.hello " ]
351+ valid_manifest_data ["provides" ]["commands" ][0 ]["aliases" ] = ["speckit.test-ext.greet " ]
346352
347353 manifest_path = temp_dir / "extension.yml"
348354 with open (manifest_path , 'w' ) as f :
349355 yaml .dump (valid_manifest_data , f )
350356
351357 manifest = ExtensionManifest (manifest_path )
352358
353- assert manifest .commands [0 ]["aliases" ] == ["test-ext.hello " ]
359+ assert manifest .commands [0 ]["aliases" ] == ["test-ext.greet " ]
354360 assert len (manifest .warnings ) == 1
355- assert "speckit.test-ext.hello" in manifest .warnings [0 ]
356- assert "test-ext.hello" in manifest .warnings [0 ]
361+ assert "speckit.test-ext.greet" in manifest .warnings [0 ]
362+ assert "test-ext.greet" in manifest .warnings [0 ]
363+
364+ def test_alias_collision_with_primary_skill_name_rejected (self , temp_dir , valid_manifest_data ):
365+ """Alias whose SKILL output name matches the primary command's is rejected."""
366+ import yaml
367+
368+ # Primary 'speckit.test-ext.hello' maps to skill 'speckit-test-ext-hello'.
369+ # Alias 'test-ext.hello' would map to the same skill name — collision.
370+ valid_manifest_data ["provides" ]["commands" ][0 ]["aliases" ] = ["test-ext.hello" ]
371+
372+ manifest_path = temp_dir / "extension.yml"
373+ with open (manifest_path , "w" ) as f :
374+ yaml .dump (valid_manifest_data , f )
375+
376+ with pytest .raises (ValidationError , match = "would collide with primary command" ):
377+ ExtensionManifest (manifest_path )
357378
358379 def test_hook_alias_ref_canonicalized_to_speckit_form (self , temp_dir , valid_manifest_data ):
359380 """Hook command refs in alias form are lifted to canonical speckit.ext.cmd form."""
360381 import yaml
361382
362- # Manifest uses a valid alias but the hook mistakenly references the alias name.
363- valid_manifest_data ["provides" ]["commands" ][0 ]["aliases" ] = ["test-ext.hello" ]
364- valid_manifest_data ["hooks" ]["after_tasks" ]["command" ] = "test-ext.hello"
383+ # Manifest uses a valid alias (distinct suffix to avoid SKILL name collision)
384+ # but the hook mistakenly references the alias name instead of the primary.
385+ valid_manifest_data ["provides" ]["commands" ][0 ]["aliases" ] = ["test-ext.greet" ]
386+ valid_manifest_data ["hooks" ]["after_tasks" ]["command" ] = "test-ext.greet"
365387
366388 manifest_path = temp_dir / "extension.yml"
367389 with open (manifest_path , "w" ) as f :
@@ -370,7 +392,7 @@ def test_hook_alias_ref_canonicalized_to_speckit_form(self, temp_dir, valid_mani
370392 manifest = ExtensionManifest (manifest_path )
371393
372394 # Hook ref should be lifted to canonical form for skill-mode invocation.
373- assert manifest .hooks ["after_tasks" ]["command" ] == "speckit.test-ext.hello "
395+ assert manifest .hooks ["after_tasks" ]["command" ] == "speckit.test-ext.greet "
374396
375397 def test_hook_non_dict_value_raises (self , temp_dir , valid_manifest_data ):
376398 """A non-mapping hooks value raises ValidationError."""
0 commit comments