#!/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" ROOT_CONFIG_FILES = ( "package.json", "package-lock.json", ".prettierrc.json", ".prettierignore", ) def copy_tree(src: Path, dst: Path) -> None: shutil.copytree(src, dst, ignore=shutil.ignore_patterns("__pycache__", "*.pyc")) def build_agents_text(ruleset_path: Path) -> str: text = ruleset_path.read_text(encoding="utf-8") text = text.replace("# TSL 智能体规则", "# TSL Agent Instructions", 1) return text.rstrip("\n") + "\n" def ensure_sources(repo_root: Path) -> tuple[Path, Path, Path, Path, Path]: docs_tsl = repo_root / "docs" / "tsl" syntax_skill = repo_root / "skills" / "tsl-syntax-reference" api_skill = repo_root / "skills" / "tsl-api-reference" ruleset = repo_root / "rulesets" / "tsl" / "index.md" codegen_toolkit = repo_root / "tools" / "tsl-codegen" sources = (docs_tsl, syntax_skill, api_skill, ruleset, codegen_toolkit) root_configs = tuple(repo_root / filename for filename in ROOT_CONFIG_FILES) missing = [str(path) for path in sources if not path.exists()] missing.extend(str(path) for path in root_configs if not path.is_file()) if missing: raise FileNotFoundError("missing source path(s): " + ", ".join(missing)) return sources 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, syntax_skill, api_skill, ruleset, codegen_toolkit = ensure_sources( repo_root ) clean_output(output, repo_root) (output / "AGENTS.md").write_text( build_agents_text(ruleset), encoding="utf-8", newline="\n" ) for filename in ROOT_CONFIG_FILES: shutil.copy2(repo_root / filename, output / filename) copy_tree(docs_tsl, output / "docs" / "tsl") copy_tree(syntax_skill, output / "skills" / "tsl-syntax-reference") copy_tree(api_skill, output / "skills" / "tsl-api-reference") copy_tree(codegen_toolkit, output / "tools" / "tsl-codegen") 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())