@@ -4727,6 +4727,82 @@ def test_argument_hint_not_added_for_non_claude_preset_command(self, project_dir
47274727 parsed = yaml.safe_load(skill_file.read_text(encoding="utf-8").split("---", 2)[1])
47284728 assert "argument-hint" not in parsed
47294729
4730+ def test_wrap_preset_inherits_argument_hint_from_core(self, project_dir, temp_dir):
4731+ """A wrap-strategy preset that omits argument-hint must inherit it from the core template.
4732+
4733+ Regression for issue #3991: the wrap-composition path in _register_skills
4734+ previously inherited only scripts/agent_scripts from core_frontmatter,
4735+ silently discarding argument-hint and leaking its value into description.
4736+ """
4737+ core_arg_hint = "Describe the feature you want to specify"
4738+ preset_description = "Wrapped speckit.specify — extra project context added"
4739+ self._write_init_options(project_dir, ai="claude")
4740+ skills_dir = project_dir / ".claude" / "skills"
4741+ self._create_skill(skills_dir, "speckit-specify")
4742+
4743+ # Place a core template that declares argument-hint
4744+ core_cmds = project_dir / ".specify" / "templates" / "commands"
4745+ core_cmds.mkdir(parents=True, exist_ok=True)
4746+ (core_cmds / "specify.md").write_text(
4747+ "---\n"
4748+ "description: Core specify description.\n"
4749+ f'argument-hint: "{core_arg_hint}"\n'
4750+ "---\n\n"
4751+ "Core specify body.\n",
4752+ encoding="utf-8",
4753+ )
4754+
4755+ # Wrap preset: only declares description (no argument-hint)
4756+ preset_dir = temp_dir / "wrap-hint-preset"
4757+ preset_dir.mkdir()
4758+ (preset_dir / "commands").mkdir()
4759+ (preset_dir / "commands" / "speckit.specify.md").write_text(
4760+ "---\n"
4761+ f'description: "{preset_description}"\n'
4762+ "strategy: wrap\n"
4763+ "---\n\n"
4764+ "{CORE_TEMPLATE}\n",
4765+ encoding="utf-8",
4766+ )
4767+ manifest_data = {
4768+ "schema_version": "1.0",
4769+ "preset": {
4770+ "id": "wrap-hint-preset",
4771+ "name": "Wrap Hint Preset",
4772+ "version": "1.0.0",
4773+ "description": "Test wrap hint inheritance",
4774+ },
4775+ "requires": {"speckit_version": ">=0.1.0"},
4776+ "provides": {
4777+ "templates": [
4778+ {
4779+ "type": "command",
4780+ "name": "speckit.specify",
4781+ "file": "commands/speckit.specify.md",
4782+ "strategy": "wrap",
4783+ }
4784+ ]
4785+ },
4786+ }
4787+ import yaml as _yaml
4788+ with open(preset_dir / "preset.yml", "w") as f:
4789+ _yaml.dump(manifest_data, f)
4790+
4791+ manager = PresetManager(project_dir)
4792+ manager.install_from_directory(preset_dir, "1.0.0")
4793+
4794+ skill_file = skills_dir / "speckit-specify" / "SKILL.md"
4795+ assert skill_file.exists()
4796+ parsed = yaml.safe_load(skill_file.read_text(encoding="utf-8").split("---", 2)[1])
4797+ # argument-hint must be inherited from core, not dropped
4798+ assert parsed.get("argument-hint") == core_arg_hint, (
4799+ f"argument-hint was not inherited from core; parsed={parsed}"
4800+ )
4801+ # description must be exactly the preset's declared value, not concatenated
4802+ assert parsed["description"] == preset_description, (
4803+ f"description was corrupted; parsed={parsed}"
4804+ )
4805+
47304806 def test_register_skills_resolves_command_refs(self, project_dir, temp_dir):
47314807 """Preset skill overrides must resolve __SPECKIT_COMMAND_*__ tokens (issue #2717).
47324808
0 commit comments