f3cd56b78e
Use ~/.pouch, the pouch CLI, and .pouch.yaml as the SSOT container. Keep the inner skills/ packages, and store ACK project state in .pouch/ack instead of docs/ack.
77 lines
2.2 KiB
Python
77 lines
2.2 KiB
Python
"""软链创建、移除与健康检查。"""
|
|
|
|
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
|