feat: add skiff
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
"""软链创建、移除与健康检查。"""
|
||||
|
||||
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 / ".skills.yaml").is_file():
|
||||
return directory
|
||||
if (directory / "skills").is_dir() and (directory / "registry.yaml").is_file():
|
||||
return directory
|
||||
return None
|
||||
Reference in New Issue
Block a user