11#!/usr/bin/env python3
22"""
33Skills Generator - generates Claude Code skills from .cursor/rules/.
4- Each rule becomes a separate SKILL.md file in .claude/skills/.generated/ ,
4+ Each rule becomes a separate SKILL.md file in .claude/skills/,
55with a flat name encoding the full path: generated_<category>_<skill-name>.
66"""
77
1414from sync_ai_rules .core .generator_interface import OutputGenerator
1515from sync_ai_rules .core .rule_metadata import RuleMetadata
1616
17- _SKILLS_DIR = ".claude/skills/.generated "
17+ _SKILLS_DIR = ".claude/skills"
1818_SOURCE_DIR = ".cursor/rules"
1919_YAML_UNSAFE = re .compile (r"[:\#\[\]\{\}&*!|>'\"%@`]" )
2020
@@ -43,28 +43,20 @@ def is_multi_file(self) -> bool:
4343 def generate_files (
4444 self , rules : Dict [str , List [RuleMetadata ]], project_root : str
4545 ) -> None :
46- """Generate skill files mirroring the .cursor/rules/ folder structure ."""
46+ """Generate skill files as direct children of .claude/skills/ ."""
4747 skills_root = os .path .join (project_root , _SKILLS_DIR )
4848
49- # Clean .generated directory on each run
50- if os .path .exists (skills_root ):
51- shutil .rmtree (skills_root )
52-
53- # Also remove any stale generated_* dirs that may exist directly in .claude/skills/
54- # (e.g. from a previous migration or manual copy)
55- skills_parent = os .path .dirname (skills_root )
56- if os .path .isdir (skills_parent ):
57- for entry in os .listdir (skills_parent ):
49+ # Remove any existing generated_* dirs before regenerating
50+ if os .path .isdir (skills_root ):
51+ for entry in os .listdir (skills_root ):
5852 if entry .startswith ("generated_" ):
59- stale = os .path .join (skills_parent , entry )
60- if os .path .isdir (stale ):
61- shutil .rmtree (stale )
53+ shutil .rmtree (os .path .join (skills_root , entry ))
6254
6355 for category_rules in rules .values ():
6456 for rule in category_rules :
65- # Flatten full path into skill name:
66- # .cursor/rules/arch/my-rule.mdc → .generated /generated_arch_my-rule/SKILL.md
67- # .cursor/rules/my-rule.mdc → .generated /generated_my-rule/SKILL.md
57+ # Flatten full path into a single skill dir name:
58+ # .cursor/rules/arch/my-rule.mdc → .claude/skills /generated_arch_my-rule/SKILL.md
59+ # .cursor/rules/my-rule.mdc → .claude/skills /generated_my-rule/SKILL.md
6860 rel_path = _strip_source_prefix (rule .relative_path )
6961 path_parts = rel_path .replace (os .sep , "/" ).split ("/" )
7062 path_parts [- 1 ] = os .path .splitext (path_parts [- 1 ])[0 ]
@@ -77,7 +69,7 @@ def generate_files(
7769 os .makedirs (skill_dir , exist_ok = True )
7870 with open (skill_file , "w" , encoding = "utf-8" ) as f :
7971 f .write (content )
80- print (f" ✓ Created skill: { os . path . relpath ( skill_dir , skills_root ) } " )
72+ print (f" ✓ Created skill: { skill_name } " )
8173 except OSError as e :
8274 logger .warning ("Failed to write skill %s: %s" , skill_name , e )
8375 print (f" ✗ Failed to create skill: { skill_name } " )
0 commit comments