Files
HappyOnigiri-ComfyUI-Meld/scripts/sync_agent_config.py
T

163 lines
5.5 KiB
Python

"""
Unified sync script: agent-config/ is the source of truth.
Synchronizes:
- agent-config/ -> .cursor/, .agents/ (for Cursor / Antigravity)
- agent-config/rules/ -> CLAUDE.md, CLAUDE.local.md
- agent-config/skills/ -> .claude/skills/
- agent-config/ignore -> .cursorignore, .aiignore, .claudeignore
Run: make sync
"""
import re
import shutil
import sys
from pathlib import Path
FRONTMATTER_PATTERN = re.compile(r"^---\n.*?\n---\n?", re.DOTALL)
ORDERED_RULES = [
"base.mdc",
"node.mdc",
"web.mdc",
"api-routing-and-module-layout.mdc",
"architecture-map.mdc",
"ui/css-policy.mdc",
"ui/keybinding-propagation-policy.mdc",
"ui/modal-escape-policy.mdc",
"ui/z-index-layer-policy.mdc",
]
CLAUDE_MD_HEADER = """\
<!-- This file is auto-generated by scripts/sync_agent_config.py from agent-config/rules/ -->
<!-- Do not edit directly. To update, run: make sync -->
"""
def sync_directories(src_dir: Path, dst_dir: Path) -> None:
"""
Synchronize src_dir to dst_dir, mirroring its content.
Similar to rsync -av --delete.
"""
if not src_dir.exists():
print(f"Error: Source directory {src_dir} does not exist.")
sys.exit(1)
dst_dir.mkdir(parents=True, exist_ok=True)
src_items = {p.relative_to(src_dir) for p in src_dir.rglob("*")}
dst_items = {p.relative_to(dst_dir) for p in dst_dir.rglob("*")}
for item in sorted(dst_items - src_items, reverse=True):
p = dst_dir / item
if p.is_dir():
shutil.rmtree(p)
print(f"Removed directory: {p}")
else:
p.unlink()
print(f"Removed file: {p}")
for item in sorted(src_items):
src_p = src_dir / item
dst_p = dst_dir / item
if src_p.is_dir():
dst_p.mkdir(parents=True, exist_ok=True)
else:
src_stat = src_p.stat()
dst_stat = dst_p.stat() if dst_p.exists() else None
if dst_stat is None or src_stat.st_mtime > dst_stat.st_mtime or src_stat.st_size != dst_stat.st_size:
dst_p.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src_p, dst_p)
print(f"Synced: {item}")
def strip_frontmatter(text: str) -> str:
return FRONTMATTER_PATTERN.sub("", text, count=1).strip()
def build_claude_md(rules_dir: Path, output_path: Path) -> None:
sections = []
for rule_rel in ORDERED_RULES:
rule_path = rules_dir / rule_rel
if not rule_path.exists():
print(f"Error: Rule file not found: {rule_path}", file=sys.stderr)
sys.exit(1)
content = strip_frontmatter(rule_path.read_text(encoding="utf-8"))
if content:
sections.append(content)
body = "\n\n".join(sections)
output = CLAUDE_MD_HEADER + "\n" + body + "\n"
output_path.write_text(output, encoding="utf-8", newline="\n")
print(f"Generated: {output_path.name}")
def build_claude_local_md(rules_dir: Path, output_path: Path) -> None:
local_rule = rules_dir / "base.local.mdc"
if not local_rule.exists():
return
content = strip_frontmatter(local_rule.read_text(encoding="utf-8"))
output_path.write_text(content + "\n", encoding="utf-8", newline="\n")
print(f"Generated: {output_path.name}")
def copy_ignore_file(project_root: Path, src: Path, dst_name: str) -> None:
"""Copy agent-config/ignore (master) to dst_name at project root."""
dst = project_root / dst_name
if src.exists():
shutil.copy2(src, dst)
print(f"Copied: agent-config/ignore -> {dst_name}")
elif dst.exists():
dst.unlink()
print(f"Removed: {dst_name} (source agent-config/ignore missing)")
def main() -> None:
project_root = Path(__file__).resolve().parent.parent
agent_config = project_root / "agent-config"
rules_dir = agent_config / "rules"
skills_src = agent_config / "skills"
ignore_src = agent_config / "ignore"
if not agent_config.exists():
print(f"Error: agent-config/ not found at {agent_config}")
sys.exit(1)
print("Synchronizing from agent-config/ (source of truth)...")
# agent-config/rules/ -> .cursor/rules/, .agents/rules/
cursor_rules = project_root / ".cursor" / "rules"
agent_rules = project_root / ".agents" / "rules"
sync_directories(rules_dir, cursor_rules)
print("Synced: agent-config/rules/ -> .cursor/rules/")
sync_directories(rules_dir, agent_rules)
print("Synced: agent-config/rules/ -> .agents/rules/")
# agent-config/skills/ -> .cursor/skills/, .agents/skills/, .claude/skills/
cursor_skills = project_root / ".cursor" / "skills"
agent_skills = project_root / ".agents" / "skills"
claude_skills = project_root / ".claude" / "skills"
sync_directories(skills_src, cursor_skills)
print("Synced: agent-config/skills/ -> .cursor/skills/")
sync_directories(skills_src, agent_skills)
print("Synced: agent-config/skills/ -> .agents/skills/")
sync_directories(skills_src, claude_skills)
print("Synced: agent-config/skills/ -> .claude/skills/")
# agent-config/rules/ -> CLAUDE.md, CLAUDE.local.md
build_claude_md(rules_dir, project_root / "CLAUDE.md")
build_claude_local_md(rules_dir, project_root / "CLAUDE.local.md")
# agent-config/ignore -> .cursorignore, .aiignore, .claudeignore
copy_ignore_file(project_root, ignore_src, ".cursorignore")
copy_ignore_file(project_root, ignore_src, ".aiignore")
copy_ignore_file(project_root, ignore_src, ".claudeignore")
print("Done.")
if __name__ == "__main__":
main()