Skip to content

Commit 06a77ff

Browse files
committed
fix(kimi): guard symlinked SKILL.md during migration and teardown
1 parent e4fcb2f commit 06a77ff

2 files changed

Lines changed: 21 additions & 10 deletions

File tree

src/specify_cli/integrations/kimi/__init__.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
Kimi uses the ``.kimi-code/skills/speckit-<name>/SKILL.md`` layout with
44
``/skill:speckit-<name>`` invocation syntax.
55
6-
Includes legacy migration logic for projects initialised before Kimi
7-
Code CLI adopted the ``.kimi-code/`` directory, as well as for the
8-
older dotted skill directory naming (``speckit.xxx`` → ``speckit-xxx``).
6+
Legacy migration covers projects created before Kimi Code CLI moved to
7+
this layout and handles two distinct changes: the directory move from
8+
``.kimi/`` to ``.kimi-code/`` (including the ``KIMI.md`` → ``AGENTS.md``
9+
context file), and the dotted-to-hyphenated skill naming
10+
(``speckit.xxx`` → ``speckit-xxx``).
911
"""
1012

1113
from __future__ import annotations
@@ -204,7 +206,10 @@ def _migrate_legacy_kimi_skills_dir(
204206
for legacy_dir in legacy_dirs:
205207
if legacy_dir.is_symlink() or not legacy_dir.is_dir():
206208
continue
207-
if not (legacy_dir / "SKILL.md").exists():
209+
legacy_skill = legacy_dir / "SKILL.md"
210+
# Treat a symlinked SKILL.md as invalid: later read_bytes() would
211+
# otherwise follow it and read content from outside the project.
212+
if legacy_skill.is_symlink() or not legacy_skill.is_file():
208213
continue
209214

210215
target_name = _legacy_to_target_name(legacy_dir.name)
@@ -223,9 +228,13 @@ def _migrate_legacy_kimi_skills_dir(
223228
migrated_count += 1
224229
continue
225230

226-
# Target exists — only remove legacy if SKILL.md is identical
231+
# Target exists — only remove legacy if SKILL.md is identical.
232+
# Skip when the target SKILL.md is a symlink so the byte comparison
233+
# never follows it outside the project. (legacy_skill is already
234+
# guaranteed to be a real file by the guard above.)
227235
target_skill = target_dir / "SKILL.md"
228-
legacy_skill = legacy_dir / "SKILL.md"
236+
if target_skill.is_symlink():
237+
continue
229238
if target_skill.is_file():
230239
try:
231240
if target_skill.read_bytes() == legacy_skill.read_bytes():
@@ -265,7 +274,9 @@ def _is_speckit_generated_skill(skill_dir: Path) -> bool:
265274
``SkillsIntegration.setup()`` to avoid deleting user-authored skills.
266275
"""
267276
skill_file = skill_dir / "SKILL.md"
268-
if not skill_file.is_file():
277+
# A symlinked SKILL.md is never treated as Speckit-generated, so teardown
278+
# cleanup never follows it to read frontmatter from outside the project.
279+
if skill_file.is_symlink() or not skill_file.is_file():
269280
return False
270281

271282
try:

tests/test_presets.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3580,7 +3580,7 @@ def test_kimi_legacy_dotted_skill_override_still_applies(self, project_dir, temp
35803580
skills_dir = project_dir / ".kimi-code" / "skills"
35813581
self._create_skill(skills_dir, "speckit.specify", body="untouched")
35823582

3583-
(project_dir / ".kimi" / "commands").mkdir(parents=True, exist_ok=True)
3583+
(project_dir / ".kimi-code" / "commands").mkdir(parents=True, exist_ok=True)
35843584

35853585
manager = PresetManager(project_dir)
35863586
install_self_test_preset(manager)
@@ -3600,7 +3600,7 @@ def test_kimi_skill_updated_even_when_ai_skills_disabled(self, project_dir, temp
36003600
skills_dir = project_dir / ".kimi-code" / "skills"
36013601
self._create_skill(skills_dir, "speckit-specify", body="untouched")
36023602

3603-
(project_dir / ".kimi" / "commands").mkdir(parents=True, exist_ok=True)
3603+
(project_dir / ".kimi-code" / "commands").mkdir(parents=True, exist_ok=True)
36043604

36053605
manager = PresetManager(project_dir)
36063606
install_self_test_preset(manager)
@@ -3668,7 +3668,7 @@ def test_kimi_preset_skill_override_resolves_script_placeholders(self, project_d
36683668
self._write_init_options(project_dir, ai="kimi", ai_skills=False, script="sh")
36693669
skills_dir = project_dir / ".kimi-code" / "skills"
36703670
self._create_skill(skills_dir, "speckit-specify", body="untouched")
3671-
(project_dir / ".kimi" / "commands").mkdir(parents=True, exist_ok=True)
3671+
(project_dir / ".kimi-code" / "commands").mkdir(parents=True, exist_ok=True)
36723672

36733673
preset_dir = temp_dir / "kimi-placeholder-override"
36743674
preset_dir.mkdir()

0 commit comments

Comments
 (0)