@@ -1665,7 +1665,7 @@ def test_self_test_manifest_valid(self):
16651665 assert manifest .id == "self-test"
16661666 assert manifest .name == "Self-Test Preset"
16671667 assert manifest .version == "1.0.0"
1668- assert len (manifest .templates ) == 7 # 6 templates + 1 command
1668+ assert len (manifest .templates ) == 8 # 6 templates + 2 commands
16691669
16701670 def test_self_test_provides_all_core_templates (self ):
16711671 """Verify the self-test preset provides an override for every core template."""
@@ -3040,4 +3040,136 @@ def test_bundled_preset_missing_locally_cli_error(self, project_dir):
30403040 assert result .exit_code == 1
30413041 output = strip_ansi (result .output ).lower ()
30423042 assert "bundled" in output , result .output
3043- assert "reinstall" in output , result .output
3043+
3044+
3045+ class TestWrapStrategy :
3046+ """Tests for strategy: wrap preset command substitution."""
3047+
3048+ def test_substitute_core_template_replaces_placeholder (self , project_dir ):
3049+ """Core template body replaces {CORE_TEMPLATE} in preset command body."""
3050+ from specify_cli .presets import _substitute_core_template
3051+ from specify_cli .agents import CommandRegistrar
3052+
3053+ # Set up a core command template
3054+ core_dir = project_dir / ".specify" / "templates" / "commands"
3055+ core_dir .mkdir (parents = True , exist_ok = True )
3056+ (core_dir / "specify.md" ).write_text (
3057+ "---\n description: core\n ---\n \n # Core Specify\n \n Do the thing.\n "
3058+ )
3059+
3060+ registrar = CommandRegistrar ()
3061+ body = "## Pre-Logic\n \n Before stuff.\n \n {CORE_TEMPLATE}\n \n ## Post-Logic\n \n After stuff.\n "
3062+ result = _substitute_core_template (body , "specify" , project_dir , registrar )
3063+
3064+ assert "{CORE_TEMPLATE}" not in result
3065+ assert "# Core Specify" in result
3066+ assert "## Pre-Logic" in result
3067+ assert "## Post-Logic" in result
3068+
3069+ def test_substitute_core_template_no_op_when_placeholder_absent (self , project_dir ):
3070+ """Returns body unchanged when {CORE_TEMPLATE} is not present."""
3071+ from specify_cli .presets import _substitute_core_template
3072+ from specify_cli .agents import CommandRegistrar
3073+
3074+ core_dir = project_dir / ".specify" / "templates" / "commands"
3075+ core_dir .mkdir (parents = True , exist_ok = True )
3076+ (core_dir / "specify.md" ).write_text ("---\n description: core\n ---\n \n Core body.\n " )
3077+
3078+ registrar = CommandRegistrar ()
3079+ body = "## No placeholder here.\n "
3080+ result = _substitute_core_template (body , "specify" , project_dir , registrar )
3081+ assert result == body
3082+
3083+ def test_substitute_core_template_no_op_when_core_missing (self , project_dir ):
3084+ """Returns body unchanged when core template file does not exist."""
3085+ from specify_cli .presets import _substitute_core_template
3086+ from specify_cli .agents import CommandRegistrar
3087+
3088+ registrar = CommandRegistrar ()
3089+ body = "Pre.\n \n {CORE_TEMPLATE}\n \n Post.\n "
3090+ result = _substitute_core_template (body , "nonexistent" , project_dir , registrar )
3091+ assert result == body
3092+ assert "{CORE_TEMPLATE}" in result
3093+
3094+ def test_register_commands_substitutes_core_template_for_wrap_strategy (self , project_dir ):
3095+ """register_commands substitutes {CORE_TEMPLATE} when strategy: wrap."""
3096+ from specify_cli .agents import CommandRegistrar
3097+
3098+ # Set up core command template
3099+ core_dir = project_dir / ".specify" / "templates" / "commands"
3100+ core_dir .mkdir (parents = True , exist_ok = True )
3101+ (core_dir / "specify.md" ).write_text (
3102+ "---\n description: core\n ---\n \n # Core Specify\n \n Core body here.\n "
3103+ )
3104+
3105+ # Create a preset command dir with a wrap-strategy command
3106+ cmd_dir = project_dir / "preset" / "commands"
3107+ cmd_dir .mkdir (parents = True , exist_ok = True )
3108+ (cmd_dir / "speckit.specify.md" ).write_text (
3109+ "---\n description: wrap test\n strategy: wrap\n ---\n \n "
3110+ "## Pre\n \n {CORE_TEMPLATE}\n \n ## Post\n "
3111+ )
3112+
3113+ commands = [{"name" : "speckit.specify" , "file" : "commands/speckit.specify.md" }]
3114+ registrar = CommandRegistrar ()
3115+
3116+ # Use a generic agent that writes markdown to commands/
3117+ agent_dir = project_dir / ".claude" / "commands"
3118+ agent_dir .mkdir (parents = True , exist_ok = True )
3119+
3120+ # Patch AGENT_CONFIGS to use a simple markdown agent pointing at our dir
3121+ import copy
3122+ original = copy .deepcopy (registrar .AGENT_CONFIGS )
3123+ registrar .AGENT_CONFIGS ["test-agent" ] = {
3124+ "dir" : str (agent_dir .relative_to (project_dir )),
3125+ "format" : "markdown" ,
3126+ "args" : "$ARGUMENTS" ,
3127+ "extension" : ".md" ,
3128+ "strip_frontmatter_keys" : [],
3129+ }
3130+ try :
3131+ registrar .register_commands (
3132+ "test-agent" , commands , "test-preset" ,
3133+ project_dir / "preset" , project_dir
3134+ )
3135+ finally :
3136+ registrar .AGENT_CONFIGS = original
3137+
3138+ written = (agent_dir / "speckit.specify.md" ).read_text ()
3139+ assert "{CORE_TEMPLATE}" not in written
3140+ assert "# Core Specify" in written
3141+ assert "## Pre" in written
3142+ assert "## Post" in written
3143+
3144+ def test_end_to_end_wrap_via_self_test_preset (self , project_dir ):
3145+ """Installing self-test preset with a wrap command substitutes {CORE_TEMPLATE}."""
3146+ from specify_cli .presets import PresetManager
3147+
3148+ # Install a core template that wrap-test will wrap around
3149+ core_dir = project_dir / ".specify" / "templates" / "commands"
3150+ core_dir .mkdir (parents = True , exist_ok = True )
3151+ (core_dir / "wrap-test.md" ).write_text (
3152+ "---\n description: core wrap-test\n ---\n \n # Core Wrap-Test Body\n "
3153+ )
3154+
3155+ # Set up skills dir (simulating --ai claude)
3156+ skills_dir = project_dir / ".claude" / "skills"
3157+ skills_dir .mkdir (parents = True , exist_ok = True )
3158+ skill_subdir = skills_dir / "speckit-wrap-test"
3159+ skill_subdir .mkdir ()
3160+ (skill_subdir / "SKILL.md" ).write_text ("---\n name: speckit-wrap-test\n ---\n \n old content\n " )
3161+
3162+ # Write init-options so _register_skills finds the claude skills dir
3163+ import json
3164+ (project_dir / ".specify" / "init-options.json" ).write_text (
3165+ json .dumps ({"ai" : "claude" , "ai_skills" : True })
3166+ )
3167+
3168+ manager = PresetManager (project_dir )
3169+ manager .install_from_directory (SELF_TEST_PRESET_DIR , "0.1.5" )
3170+
3171+ written = (skill_subdir / "SKILL.md" ).read_text ()
3172+ assert "{CORE_TEMPLATE}" not in written
3173+ assert "# Core Wrap-Test Body" in written
3174+ assert "preset:self-test wrap-pre" in written
3175+ assert "preset:self-test wrap-post" in written
0 commit comments