🔧 chore(tsl): add playbook sync workflow
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build the minimal TSL playbook bundle."""
|
||||
import argparse
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
DEFAULT_OUTPUT = Path("tmp") / "tsl-playbook"
|
||||
API_REFERENCE_BULLET = (
|
||||
"- 查 API / 函数的名字、参数、返回值和示例时,使用 "
|
||||
"`tsl-api-reference` skill;已知名走 `--name`,未知名走 `--kw`。"
|
||||
)
|
||||
|
||||
|
||||
def copy_tree(src: Path, dst: Path) -> None:
|
||||
shutil.copytree(src, dst)
|
||||
|
||||
|
||||
def build_agents_text(ruleset_path: Path) -> str:
|
||||
text = ruleset_path.read_text(encoding="utf-8")
|
||||
text = text.replace("# TSL 智能体规则", "# TSL Agent Instructions", 1)
|
||||
if API_REFERENCE_BULLET not in text:
|
||||
marker = (
|
||||
"- 需要任何 TSL 语法、文件模型、函数、模块或金融数据事实时,"
|
||||
"第一跳统一进 `docs/tsl/index.md`,由它路由到具体页;"
|
||||
"不在本文件内猜文件路径,也不全目录搜索。"
|
||||
)
|
||||
text = text.replace(marker, f"{marker}\n{API_REFERENCE_BULLET}", 1)
|
||||
return text.rstrip("\n") + "\n"
|
||||
|
||||
|
||||
def ensure_sources(repo_root: Path) -> tuple[Path, Path, Path]:
|
||||
docs_tsl = repo_root / "docs" / "tsl"
|
||||
skill = repo_root / "skills" / "tsl-api-reference"
|
||||
ruleset = repo_root / "rulesets" / "tsl" / "index.md"
|
||||
missing = [str(path) for path in (docs_tsl, skill, ruleset) if not path.exists()]
|
||||
if missing:
|
||||
raise FileNotFoundError("missing source path(s): " + ", ".join(missing))
|
||||
return docs_tsl, skill, ruleset
|
||||
|
||||
|
||||
def clean_output(output: Path, repo_root: Path) -> None:
|
||||
resolved = output.resolve()
|
||||
forbidden = {
|
||||
repo_root.resolve(),
|
||||
(repo_root / "docs").resolve(),
|
||||
(repo_root / "skills").resolve(),
|
||||
(repo_root / "rulesets").resolve(),
|
||||
}
|
||||
if resolved in forbidden:
|
||||
raise ValueError(f"refusing to replace source directory: {output}")
|
||||
if output.exists():
|
||||
shutil.rmtree(output)
|
||||
output.mkdir(parents=True)
|
||||
|
||||
|
||||
def build(output: Path, repo_root: Path) -> None:
|
||||
docs_tsl, skill, ruleset = ensure_sources(repo_root)
|
||||
clean_output(output, repo_root)
|
||||
|
||||
(output / "AGENTS.md").write_text(
|
||||
build_agents_text(ruleset), encoding="utf-8", newline="\n"
|
||||
)
|
||||
copy_tree(docs_tsl, output / "docs" / "tsl")
|
||||
copy_tree(skill, output / "skills" / "tsl-api-reference")
|
||||
|
||||
|
||||
def main(argv=None) -> int:
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
default=str(DEFAULT_OUTPUT),
|
||||
help=f"output directory (default: {DEFAULT_OUTPUT.as_posix()})",
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
repo_root = Path(__file__).resolve().parents[1]
|
||||
output = Path(args.output)
|
||||
if not output.is_absolute():
|
||||
output = repo_root / output
|
||||
|
||||
try:
|
||||
build(output, repo_root)
|
||||
except (FileNotFoundError, ValueError, OSError) as exc:
|
||||
print(f"ERROR: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print(f"wrote {output}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user