Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/specify_cli/presets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1048,9 +1048,9 @@ def _reconcile_skills(self, command_names: List[str]) -> None:
short_name = cmd_name
if short_name.startswith("speckit."):
short_name = short_name[len("speckit."):]
desc = SKILL_DESCRIPTIONS.get(
desc = fm.get("description", "") or SKILL_DESCRIPTIONS.get(
short_name.replace(".", "-"),
fm.get("description", f"Command: {short_name}"),
f"Command: {short_name}",
)
init_opts = load_init_options(self.project_root)
selected_ai = init_opts.get("ai") if isinstance(init_opts, dict) else ""
Expand Down Expand Up @@ -1314,9 +1314,9 @@ def _register_skills(
frontmatter[key] = core_frontmatter[key]

original_desc = frontmatter.get("description", "")
enhanced_desc = SKILL_DESCRIPTIONS.get(
enhanced_desc = original_desc or SKILL_DESCRIPTIONS.get(
short_name,
original_desc or f"Spec-kit workflow command: {short_name}",
f"Spec-kit workflow command: {short_name}",
)
Comment thread
mnriem marked this conversation as resolved.
frontmatter = dict(frontmatter)
frontmatter["description"] = enhanced_desc
Expand Down Expand Up @@ -1417,9 +1417,9 @@ def _unregister_skills(self, skill_names: List[str], preset_dir: Path) -> None:
)

original_desc = frontmatter.get("description", "")
enhanced_desc = SKILL_DESCRIPTIONS.get(
enhanced_desc = original_desc or SKILL_DESCRIPTIONS.get(
short_name,
original_desc or f"Spec-kit workflow command: {short_name}",
f"Spec-kit workflow command: {short_name}",
)

frontmatter_data = registrar.build_skill_frontmatter(
Expand Down
145 changes: 145 additions & 0 deletions tests/test_presets.py
Original file line number Diff line number Diff line change
Expand Up @@ -2266,6 +2266,37 @@ def _create_skill(self, skills_dir, skill_name, body="original body"):
)
return skill_dir

def _create_command_preset(self, temp_dir, preset_id, command_name, description, body):
preset_dir = temp_dir / preset_id
preset_dir.mkdir()
(preset_dir / "commands").mkdir()
command_file = f"{command_name}.md"
(preset_dir / "commands" / command_file).write_text(
f"---\ndescription: {description}\n---\n\n{body}\n"
)
manifest_data = {
"schema_version": "1.0",
"preset": {
"id": preset_id,
"name": preset_id,
"version": "1.0.0",
"description": "Test",
},
"requires": {"speckit_version": ">=0.1.0"},
"provides": {
"templates": [
{
"type": "command",
"name": command_name,
"file": f"commands/{command_file}",
}
]
},
}
with open(preset_dir / "preset.yml", "w") as f:
yaml.dump(manifest_data, f)
return preset_dir

def test_skill_overridden_on_preset_install(self, project_dir, temp_dir):
"""When --ai-skills was used, a preset command override should update the skill."""
# Simulate --ai-skills having been used: write init-options + create skill
Expand All @@ -2290,6 +2321,120 @@ def test_skill_overridden_on_preset_install(self, project_dir, temp_dir):
metadata = manager.registry.get("self-test")
assert "speckit-specify" in metadata.get("registered_skills", [])

def test_core_command_override_skill_uses_preset_command_description(self, project_dir, temp_dir):
"""Preset skill overrides for core commands should keep preset frontmatter descriptions."""
self._write_init_options(project_dir, ai="claude")
skills_dir = project_dir / ".claude" / "skills"
self._create_skill(skills_dir, "speckit-taskstoissues")

preset_dir = temp_dir / "taskstoissues-description"
preset_dir.mkdir()
(preset_dir / "commands").mkdir()
(preset_dir / "commands" / "speckit.repro.taskstoissues.md").write_text(
"---\n"
"description: COMMAND-FRONTMATTER-DESCRIPTION\n"
"---\n\n"
"# Repro command body\n"
)
manifest_data = {
"schema_version": "1.0",
"preset": {
"id": "taskstoissues-description",
"name": "Taskstoissues Description",
"version": "1.0.0",
"description": "Test",
},
"requires": {"speckit_version": ">=0.1.0"},
"provides": {
"templates": [
{
"type": "command",
"name": "speckit.taskstoissues",
"file": "commands/speckit.repro.taskstoissues.md",
"description": "MANIFEST-DESCRIPTION",
"replaces": "speckit.taskstoissues",
"strategy": "replace",
}
]
},
}
with open(preset_dir / "preset.yml", "w") as f:
yaml.dump(manifest_data, f)

manager = PresetManager(project_dir)
manager.install_from_directory(preset_dir, "0.1.5")

skill_file = skills_dir / "speckit-taskstoissues" / "SKILL.md"
content = skill_file.read_text()
assert "description: COMMAND-FRONTMATTER-DESCRIPTION" in content
assert "Convert tasks from tasks.md into GitHub issues." not in content
assert "source: preset:taskstoissues-description" in content

def test_core_skill_restore_uses_core_command_description(self, project_dir, temp_dir):
"""Core skill restore should keep core command frontmatter descriptions."""
self._write_init_options(project_dir, ai="claude")
skills_dir = project_dir / ".claude" / "skills"
self._create_skill(skills_dir, "speckit-taskstoissues")

core_cmds = project_dir / ".specify" / "templates" / "commands"
core_cmds.mkdir(parents=True, exist_ok=True)
(core_cmds / "taskstoissues.md").write_text(
"---\n"
"description: CORE-FRONTMATTER-DESCRIPTION\n"
"---\n\n"
"core taskstoissues body\n"
)
preset_dir = self._create_command_preset(
temp_dir,
"taskstoissues-restore",
"speckit.taskstoissues",
"PRESET-FRONTMATTER-DESCRIPTION",
"preset taskstoissues body\n",
)

manager = PresetManager(project_dir)
manager.install_from_directory(preset_dir, "0.1.5")
manager.remove("taskstoissues-restore")

skill_file = skills_dir / "speckit-taskstoissues" / "SKILL.md"
content = skill_file.read_text()
assert "description: CORE-FRONTMATTER-DESCRIPTION" in content
assert "Convert tasks from tasks.md into GitHub issues." not in content
assert "source: templates/commands/taskstoissues.md" in content
assert "core taskstoissues body" in content

def test_override_skill_reconcile_uses_override_command_description(self, project_dir, temp_dir):
"""Override skill reconciliation should keep override frontmatter descriptions."""
self._write_init_options(project_dir, ai="claude")
skills_dir = project_dir / ".claude" / "skills"
self._create_skill(skills_dir, "speckit-taskstoissues")

overrides_dir = project_dir / ".specify" / "templates" / "overrides"
overrides_dir.mkdir(parents=True)
(overrides_dir / "speckit.taskstoissues.md").write_text(
"---\n"
"description: OVERRIDE-FRONTMATTER-DESCRIPTION\n"
"---\n\n"
"override taskstoissues body\n"
)
preset_dir = self._create_command_preset(
temp_dir,
"taskstoissues-reconcile",
"speckit.taskstoissues",
"PRESET-FRONTMATTER-DESCRIPTION",
"preset taskstoissues body\n",
)

manager = PresetManager(project_dir)
manager.install_from_directory(preset_dir, "0.1.5")

skill_file = skills_dir / "speckit-taskstoissues" / "SKILL.md"
content = skill_file.read_text()
assert "description: OVERRIDE-FRONTMATTER-DESCRIPTION" in content
assert "Convert tasks from tasks.md into GitHub issues." not in content
assert "source: override:speckit.taskstoissues" in content
assert "override taskstoissues body" in content

def test_skill_not_updated_when_ai_skills_disabled(self, project_dir, temp_dir):
"""When --ai-skills was NOT used, preset install should not touch skills."""
self._write_init_options(project_dir, ai="qwen", ai_skills=False)
Expand Down
Loading