@@ -1490,12 +1490,6 @@ def load_init_options(project_path: Path) -> dict[str, Any]:
14901490 return {}
14911491
14921492
1493- # Agent-specific skill directory overrides for agents whose skills directory
1494- # doesn't follow the standard <agent_folder>/skills/ pattern
1495- AGENT_SKILLS_DIR_OVERRIDES = {
1496- "codex" : ".agents/skills" , # Codex agent layout override
1497- }
1498-
14991493# Default skills directory for agents not in AGENT_CONFIG
15001494DEFAULT_SKILLS_DIR = ".agents/skills"
15011495
@@ -1528,13 +1522,9 @@ def load_init_options(project_path: Path) -> dict[str, Any]:
15281522def _get_skills_dir (project_path : Path , selected_ai : str ) -> Path :
15291523 """Resolve the agent-specific skills directory for the given AI assistant.
15301524
1531- Uses ``AGENT_SKILLS_DIR_OVERRIDES`` first, then falls back to
1532- ``AGENT_CONFIG[agent]["folder"] + "skills"``, and finally to
1533- ``DEFAULT_SKILLS_DIR``.
1525+ Uses ``AGENT_CONFIG[agent]["folder"] + "skills"`` and falls back to
1526+ ``DEFAULT_SKILLS_DIR`` for unknown agents.
15341527 """
1535- if selected_ai in AGENT_SKILLS_DIR_OVERRIDES :
1536- return project_path / AGENT_SKILLS_DIR_OVERRIDES [selected_ai ]
1537-
15381528 agent_config = AGENT_CONFIG .get (selected_ai , {})
15391529 agent_folder = agent_config .get ("folder" , "" )
15401530 if agent_folder :
@@ -1648,10 +1638,7 @@ def install_ai_skills(
16481638 command_name = command_name [len ("speckit." ):]
16491639 if command_name .endswith (".agent" ):
16501640 command_name = command_name [:- len (".agent" )]
1651- if selected_ai == "kimi" :
1652- skill_name = f"speckit.{ command_name } "
1653- else :
1654- skill_name = f"speckit-{ command_name } "
1641+ skill_name = f"speckit-{ command_name .replace ('.' , '-' )} "
16551642
16561643 # Create skill directory (additive — never removes existing content)
16571644 skill_dir = skills_dir / skill_name
@@ -1730,8 +1717,64 @@ def _has_bundled_skills(project_path: Path, selected_ai: str) -> bool:
17301717 if not skills_dir .is_dir ():
17311718 return False
17321719
1733- pattern = "speckit.*/SKILL.md" if selected_ai == "kimi" else "speckit-*/SKILL.md"
1734- return any (skills_dir .glob (pattern ))
1720+ return any (skills_dir .glob ("speckit-*/SKILL.md" ))
1721+
1722+
1723+ def _migrate_legacy_kimi_dotted_skills (skills_dir : Path ) -> tuple [int , int ]:
1724+ """Migrate legacy Kimi dotted skill dirs (speckit.xxx) to hyphenated format.
1725+
1726+ Temporary migration helper:
1727+ - Intended removal window: after 2026-06-25.
1728+ - Purpose: one-time cleanup for projects initialized before Kimi moved to
1729+ hyphenated skills (speckit-xxx).
1730+
1731+ Returns:
1732+ Tuple[migrated_count, removed_count]
1733+ - migrated_count: old dotted dir renamed to hyphenated dir
1734+ - removed_count: old dotted dir deleted when equivalent hyphenated dir existed
1735+ """
1736+ if not skills_dir .is_dir ():
1737+ return (0 , 0 )
1738+
1739+ migrated_count = 0
1740+ removed_count = 0
1741+
1742+ for legacy_dir in sorted (skills_dir .glob ("speckit.*" )):
1743+ if not legacy_dir .is_dir ():
1744+ continue
1745+ if not (legacy_dir / "SKILL.md" ).exists ():
1746+ continue
1747+
1748+ suffix = legacy_dir .name [len ("speckit." ):]
1749+ if not suffix :
1750+ continue
1751+
1752+ target_dir = skills_dir / f"speckit-{ suffix .replace ('.' , '-' )} "
1753+
1754+ if not target_dir .exists ():
1755+ shutil .move (str (legacy_dir ), str (target_dir ))
1756+ migrated_count += 1
1757+ continue
1758+
1759+ # If the new target already exists, avoid destructive cleanup unless
1760+ # both SKILL.md files are byte-identical.
1761+ target_skill = target_dir / "SKILL.md"
1762+ legacy_skill = legacy_dir / "SKILL.md"
1763+ if target_skill .is_file ():
1764+ try :
1765+ if target_skill .read_bytes () == legacy_skill .read_bytes ():
1766+ # Preserve legacy directory when it contains extra user files.
1767+ has_extra_entries = any (
1768+ child .name != "SKILL.md" for child in legacy_dir .iterdir ()
1769+ )
1770+ if not has_extra_entries :
1771+ shutil .rmtree (legacy_dir )
1772+ removed_count += 1
1773+ except OSError :
1774+ # Best-effort migration: preserve legacy dir on read failures.
1775+ pass
1776+
1777+ return (migrated_count , removed_count )
17351778
17361779
17371780AGENT_SKILLS_MIGRATIONS = {
@@ -2094,16 +2137,33 @@ def init(
20942137
20952138 ensure_constitution_from_template (project_path , tracker = tracker )
20962139
2140+ # Determine skills directory and migrate any legacy Kimi dotted skills.
2141+ migrated_legacy_kimi_skills = 0
2142+ removed_legacy_kimi_skills = 0
2143+ skills_dir : Optional [Path ] = None
2144+ if selected_ai in NATIVE_SKILLS_AGENTS :
2145+ skills_dir = _get_skills_dir (project_path , selected_ai )
2146+ if selected_ai == "kimi" and skills_dir .is_dir ():
2147+ (
2148+ migrated_legacy_kimi_skills ,
2149+ removed_legacy_kimi_skills ,
2150+ ) = _migrate_legacy_kimi_dotted_skills (skills_dir )
2151+
20972152 if ai_skills :
20982153 if selected_ai in NATIVE_SKILLS_AGENTS :
2099- skills_dir = _get_skills_dir (project_path , selected_ai )
21002154 bundled_found = _has_bundled_skills (project_path , selected_ai )
21012155 if bundled_found :
2156+ detail = f"bundled skills → { skills_dir .relative_to (project_path )} "
2157+ if migrated_legacy_kimi_skills or removed_legacy_kimi_skills :
2158+ detail += (
2159+ f" (migrated { migrated_legacy_kimi_skills } , "
2160+ f"removed { removed_legacy_kimi_skills } legacy Kimi dotted skills)"
2161+ )
21022162 if tracker :
21032163 tracker .start ("ai-skills" )
2104- tracker .complete ("ai-skills" , f"bundled skills → { skills_dir . relative_to ( project_path ) } " )
2164+ tracker .complete ("ai-skills" , detail )
21052165 else :
2106- console .print (f"[green]✓[/green] Using bundled agent skills in { skills_dir . relative_to ( project_path ) } / " )
2166+ console .print (f"[green]✓[/green] Using { detail } " )
21072167 else :
21082168 # Compatibility fallback: convert command templates to skills
21092169 # when an older template archive does not include native skills.
@@ -2288,7 +2348,7 @@ def _display_cmd(name: str) -> str:
22882348 if codex_skill_mode :
22892349 return f"$speckit-{ name } "
22902350 if kimi_skill_mode :
2291- return f"/skill:speckit. { name } "
2351+ return f"/skill:speckit- { name } "
22922352 return f"/speckit.{ name } "
22932353
22942354 steps_lines .append (f"{ step_num } . Start using { usage_label } with your AI agent:" )
0 commit comments