@@ -3101,6 +3101,86 @@ def mock_download(extension_id):
31013101 )
31023102
31033103
3104+ class TestExtensionRemoveCLI :
3105+ """CLI integration tests for extension remove confirmation output."""
3106+
3107+ def test_remove_confirmation_counts_primary_command_only (self , extension_dir , project_dir ):
3108+ """Removal confirmation shows correct count when extension has no aliases."""
3109+ from typer .testing import CliRunner
3110+ from unittest .mock import patch
3111+ from specify_cli import app
3112+
3113+ runner = CliRunner ()
3114+
3115+ # Install via manager so registry is fully populated
3116+ (project_dir / ".claude" / "skills" ).mkdir (parents = True )
3117+ manager = ExtensionManager (project_dir )
3118+ manager .install_from_directory (extension_dir , "0.1.0" , register_commands = True )
3119+
3120+ # Invoke remove with 'n' so we only see the confirmation prompt
3121+ with patch .object (Path , "cwd" , return_value = project_dir ):
3122+ result = runner .invoke (app , ["extension" , "remove" , "test-ext" ], input = "n\n " )
3123+
3124+ plain = strip_ansi (result .output )
3125+ # The fixture has 1 command and 0 aliases → "1 commands from AI agent"
3126+ assert "1 commands from AI agent" in plain
3127+
3128+ def test_remove_confirmation_counts_aliases (self , temp_dir , project_dir ):
3129+ """Removal confirmation shows primary + alias count, not just primary."""
3130+ import yaml
3131+ from typer .testing import CliRunner
3132+ from unittest .mock import patch
3133+ from specify_cli import app
3134+
3135+ runner = CliRunner ()
3136+
3137+ # Build an extension with 1 primary command + 1 alias
3138+ ext_dir = temp_dir / "ext-with-alias"
3139+ ext_dir .mkdir ()
3140+ (ext_dir / "commands" ).mkdir ()
3141+ manifest_data = {
3142+ "schema_version" : "1.0" ,
3143+ "extension" : {
3144+ "id" : "ext-with-alias" ,
3145+ "name" : "Extension With Alias" ,
3146+ "version" : "1.0.0" ,
3147+ "description" : "Test" ,
3148+ },
3149+ "requires" : {"speckit_version" : ">=0.1.0" },
3150+ "provides" : {
3151+ "commands" : [
3152+ {
3153+ "name" : "speckit.ext-with-alias.run" ,
3154+ "file" : "commands/run.md" ,
3155+ "aliases" : ["ext-with-alias.go" ],
3156+ }
3157+ ]
3158+ },
3159+ }
3160+ (ext_dir / "extension.yml" ).write_text (yaml .dump (manifest_data ))
3161+ (ext_dir / "commands" / "run.md" ).write_text ("---\n description: Run\n ---\n Body" )
3162+
3163+ # Install with command registration so registry captures both names
3164+ (project_dir / ".claude" / "skills" ).mkdir (parents = True )
3165+ manager = ExtensionManager (project_dir )
3166+ manager .install_from_directory (ext_dir , "0.1.0" , register_commands = True )
3167+
3168+ # Verify registry recorded both names
3169+ meta = manager .registry .get ("ext-with-alias" )
3170+ claude_cmds = meta ["registered_commands" ].get ("claude" , [])
3171+ assert "speckit.ext-with-alias.run" in claude_cmds
3172+ assert "ext-with-alias.go" in claude_cmds
3173+
3174+ # Invoke remove with 'n' so we only see the confirmation prompt
3175+ with patch .object (Path , "cwd" , return_value = project_dir ):
3176+ result = runner .invoke (app , ["extension" , "remove" , "ext-with-alias" ], input = "n\n " )
3177+
3178+ plain = strip_ansi (result .output )
3179+ # 1 primary + 1 alias = "2 commands from AI agent" (not "1")
3180+ assert "2 commands from AI agent" in plain
3181+ assert "1 commands from AI agent" not in plain
3182+
3183+
31043184class TestExtensionUpdateCLI :
31053185 """CLI integration tests for extension update command."""
31063186
0 commit comments