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