diff --git a/scripts/playbook.py b/scripts/playbook.py index 7e28cfbf..500575db 100644 --- a/scripts/playbook.py +++ b/scripts/playbook.py @@ -6,6 +6,7 @@ from pathlib import Path from shutil import copy2, copytree, rmtree, which import subprocess import importlib.util +from typing import Optional try: import tomllib diff --git a/tests/README.md b/tests/README.md index 9e8f886e..312f63f5 100644 --- a/tests/README.md +++ b/tests/README.md @@ -14,6 +14,7 @@ tests/ ├── test_firstparty_skills_quality.py # first-party skills 元数据与结构质量测试 ├── test_gitattributes_modes.py # gitattr_mode 行为测试 ├── test_no_backup_flags.py # no_backup 行为测试 +├── test_playbook_typing_imports.py # playbook.py typing 导入兼容性测试 ├── test_sync_directory_actions.py # sync_memory_bank/sync_prompts 行为测试 ├── test_vendor_snapshot_templates.py # vendor 快照模板完整性测试 ├── test_main_loop_cli.py # main_loop CLI 测试 diff --git a/tests/test_playbook_typing_imports.py b/tests/test_playbook_typing_imports.py new file mode 100644 index 00000000..f9c40461 --- /dev/null +++ b/tests/test_playbook_typing_imports.py @@ -0,0 +1,32 @@ +import ast +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +PLAYBOOK_SCRIPT = ROOT / "scripts" / "playbook.py" + + +class PlaybookTypingImportTests(unittest.TestCase): + def test_optional_annotation_names_are_imported(self): + tree = ast.parse(PLAYBOOK_SCRIPT.read_text(encoding="utf-8")) + + imported_names: set[str] = set() + referenced_names: set[str] = set() + + for node in ast.walk(tree): + if isinstance(node, ast.Import): + for alias in node.names: + imported_names.add(alias.asname or alias.name.split(".")[0]) + elif isinstance(node, ast.ImportFrom): + for alias in node.names: + imported_names.add(alias.asname or alias.name) + elif isinstance(node, ast.Name): + referenced_names.add(node.id) + + if "Optional" in referenced_names: + self.assertIn("Optional", imported_names) + + +if __name__ == "__main__": + unittest.main()