diff --git a/sync_ai_rules/core/generator_interface.py b/sync_ai_rules/core/generator_interface.py index 49501c2..bc8e314 100644 --- a/sync_ai_rules/core/generator_interface.py +++ b/sync_ai_rules/core/generator_interface.py @@ -32,9 +32,7 @@ def is_multi_file(self) -> bool: """Whether this generator creates files directly via generate_files().""" return False - def generate_files( - self, rules: Dict[str, List[RuleMetadata]], project_root: str - ) -> None: + def generate_files(self, rules: Dict[str, List[RuleMetadata]], project_root: str) -> None: """Generate multiple files directly. Only called when is_multi_file is True.""" raise NotImplementedError( f"{type(self).__name__} sets is_multi_file=True but does not implement generate_files()" diff --git a/sync_ai_rules/generators/skills_generator.py b/sync_ai_rules/generators/skills_generator.py index a7dc077..8d2c7c2 100644 --- a/sync_ai_rules/generators/skills_generator.py +++ b/sync_ai_rules/generators/skills_generator.py @@ -40,9 +40,7 @@ def generate(self, rules: Dict[str, List[RuleMetadata]], config: Dict[str, Any]) def is_multi_file(self) -> bool: return True - def generate_files( - self, rules: Dict[str, List[RuleMetadata]], project_root: str - ) -> None: + def generate_files(self, rules: Dict[str, List[RuleMetadata]], project_root: str) -> None: """Generate skill files as direct children of .claude/skills/.""" skills_root = os.path.join(project_root, _SKILLS_DIR) @@ -74,19 +72,31 @@ def generate_files( logger.warning("Failed to write skill %s: %s", skill_name, e) print(f" ✗ Failed to create skill: {skill_name}") + _write_gitattributes(skills_root) + def get_section_markers(self) -> tuple[str, str]: return ("", "") +def _write_gitattributes(skills_root: str) -> None: + """Write .gitattributes to hide generated skills from GitHub PR diffs.""" + path = os.path.join(skills_root, ".gitattributes") + with open(path, "w", encoding="utf-8") as f: + f.write( + "# Auto-generated by sync-ai-rules hook. Do not edit.\n" + "generated_*/SKILL.md linguist-generated\n" + ) + + def _strip_source_prefix(relative_path: str) -> str: """Strip the .cursor/rules/ prefix from a rule's relative path.""" prefix = _SOURCE_DIR + os.sep if relative_path.startswith(prefix): - return relative_path[len(prefix):] + return relative_path[len(prefix) :] # Also handle forward-slash separators prefix_fwd = _SOURCE_DIR + "/" if relative_path.startswith(prefix_fwd): - return relative_path[len(prefix_fwd):] + return relative_path[len(prefix_fwd) :] return relative_path diff --git a/test/after/.claude/skills/.gitattributes b/test/after/.claude/skills/.gitattributes new file mode 100644 index 0000000..cf85641 --- /dev/null +++ b/test/after/.claude/skills/.gitattributes @@ -0,0 +1,2 @@ +# Auto-generated by sync-ai-rules hook. Do not edit. +generated_*/SKILL.md linguist-generated