"""软链创建、移除与健康检查。""" from __future__ import annotations import os import shutil from dataclasses import dataclass from pathlib import Path @dataclass class LinkStatus: link: Path expected: Path ok: bool issue: str | None = None def create_link(link: Path, target: Path) -> None: target = target.resolve() if not target.exists(): raise FileNotFoundError(f"目标不存在: {target}") link.parent.mkdir(parents=True, exist_ok=True) if link.is_symlink(): current = link.resolve() if current == target: return link.unlink() elif link.exists(): raise FileExistsError(f"已存在非软链路径: {link}") link.symlink_to(target, target_is_directory=target.is_dir()) def remove_link(link: Path) -> bool: if link.is_symlink(): link.unlink() return True if link.exists(): raise FileExistsError(f"不是软链,未删除: {link}") return False def check_link(link: Path, expected: Path) -> LinkStatus: expected = expected.resolve() if not link.exists() and not link.is_symlink(): return LinkStatus(link, expected, False, "缺失") if not link.is_symlink(): return LinkStatus(link, expected, False, "非软链(可能被 Agent 替换为普通目录)") actual = link.resolve() if actual != expected: return LinkStatus(link, expected, False, f"指向错误: {actual}") if not actual.exists(): return LinkStatus(link, expected, False, "目标不存在") return LinkStatus(link, expected, True) def copy_template(src: Path, dst: Path) -> None: if dst.exists(): raise FileExistsError(f"已存在: {dst}") shutil.copytree(src, dst) def find_repo_root(start: Path | None = None) -> Path | None: start = (start or Path.cwd()).resolve() for directory in [start, *start.parents]: if (directory / ".pouch.yaml").is_file() or (directory / ".skills.yaml").is_file(): return directory if (directory / "skills").is_dir() and ( (directory / "catalog.yaml").is_file() or (directory / "registry.yaml").is_file() ): return directory return None