import importlib.util import re import unittest from pathlib import Path, PurePosixPath ROOT = Path(__file__).resolve().parents[1] SKILL_DIR = ROOT / "skills" / "tsl-syntax-reference" SKILL_FILE = SKILL_DIR / "SKILL.md" REFERENCES_DIR = SKILL_DIR / "references" COMPATIBILITY_INDEX = ROOT / "docs" / "tsl" / "syntax" / "index.md" RULESET_FILE = ROOT / "rulesets" / "tsl" / "index.md" LOOKUP_SCRIPT = SKILL_DIR / "scripts" / "lookup.py" lookup_spec = importlib.util.spec_from_file_location( "tsl_syntax_reference_structure_lookup", LOOKUP_SCRIPT ) lookup = importlib.util.module_from_spec(lookup_spec) lookup_spec.loader.exec_module(lookup) TOPIC_REFERENCES = { "01_quickstart.md", "02_core_model.md", "03_values_and_literals.md", "04_variables_and_constants.md", "05_functions_and_calls.md", "06_expressions_and_operators.md", "07_control_flow.md", "08_objects_and_classes.md", "09_units_and_scope.md", "10_runtime_context_and_with.md", "11_pitfalls.md", "12_matrix_and_collections.md", "13_resultset_and_filters.md", "14_ts_sql.md", "15_debug_and_profiler.md", "16_lexical_structure_and_compile_options.md", "17_types_and_conversions.md", "18_external_calls_and_threads.md", "19_namespace_libpath_and_unit_runtime.md", "20_object_runtime_and_introspection.md", "21_builtin_runtime_objects.md", "22_matrix_deep_dive.md", "23_fmarray.md", "24_object_overloads_and_iteration.md", } MARKDOWN_LINK = re.compile(r"!?\[[^\]]*\]\(([^)]+)\)") OPERATIONAL_TOPIC_LINK = re.compile( r"(?:跳到|跳转到|转到|回到|回看|回(?=\s*\[)|进入|移到)" r"(?=[^。\n]*\[[^\]]+\]\((?:0[1-9]|1[0-9]|2[0-4])_[^)]+\.md(?:#[^)]+)?\))" ) def read_text(path: Path) -> str: return path.read_text(encoding="utf-8") def frontmatter(path: Path) -> dict[str, str]: text = read_text(path) match = re.match(r"\A---\s*\n(.*?)\n---\s*\n", text, re.DOTALL) if not match: raise AssertionError(f"missing YAML frontmatter: {path}") fields: dict[str, str] = {} for line in match.group(1).splitlines(): key, separator, value = line.partition(":") if separator: fields[key.strip()] = value.strip().strip("\"'") return fields def local_link_targets(text: str) -> list[str]: text = re.sub(r"```.*?```", "", text, flags=re.DOTALL) text = re.sub(r"`[^`\n]*`", "", text) targets = [] for match in MARKDOWN_LINK.finditer(text): target = match.group(1).strip().split(maxsplit=1)[0].strip("<>") if target.startswith(("#", "http://", "https://", "mailto:")): continue targets.append(target) return targets class TslSyntaxReferenceSkillStructureTest(unittest.TestCase): def test_skill_discovery_covers_natural_tinysoft_terms(self) -> None: description = frontmatter(SKILL_FILE)["description"] for term in ("TSL", "TSF", "TS-SQL", "Tinysoft", "天软", "脚本"): self.assertIn(term, description) def test_skill_requires_safe_two_stage_lookup(self) -> None: text = read_text(SKILL_FILE) self.assertRegex(text, r"--query[^\n]*(候选|Section ID)") self.assertRegex(text, r"--section[^\n]*(正文|事实|章节)") self.assertRegex(text, r"shell|命令注入|安全传参|原样拼接") self.assertRegex(text, r"询问[^\n]*运行|如何运行") self.assertIn("AGENTS.md", text) self.assertRegex(text, r"可直接照写[^\n]*(不等于|不代表)[^\n]*(验证|可用)") def test_concept_map_is_plain_text_without_navigation_or_code(self) -> None: rendered = lookup.render_concept_map(lookup.build_concept_map()) self.assertIsNone(re.search(r"\[[^\]]+\]\([^)]+\)", rendered)) self.assertNotIn("```", rendered) self.assertNotIn("`", rendered) def test_high_risk_reference_pages_defer_api_scope(self) -> None: for name in ( "10_runtime_context_and_with.md", "15_debug_and_profiler.md", "22_matrix_deep_dive.md", ): text = read_text(REFERENCES_DIR / name) with self.subTest(page=name): self.assertIn("tsl-api-reference", text) self.assertRegex(text, r"签名|参数") self.assertRegex(text, r"scope|环境|解释器|可用性") def test_local_link_targets_includes_local_images(self) -> None: text = "![local diagram](images/router.png)\n![remote](https://example.com/router.png)" self.assertEqual(local_link_targets(text), ["images/router.png"]) def test_skill_name_matches_directory(self) -> None: self.assertEqual(frontmatter(SKILL_FILE)["name"], SKILL_DIR.name) def test_frontmatter_description_covers_tsl_and_tsf_tasks(self) -> None: description = frontmatter(SKILL_FILE)["description"] self.assertRegex(description, r"(?i)\bTSL\b") self.assertRegex(description, r"(?i)\bTSF\b") self.assertRegex(description, r"写|编写|修改|审查|解释|语法错误") def test_reference_inventory_contains_topics_without_router(self) -> None: actual = {path.name for path in REFERENCES_DIR.iterdir() if path.is_file()} self.assertEqual(actual, TOPIC_REFERENCES) self.assertFalse((REFERENCES_DIR / "index.md").exists()) def test_lookup_script_is_bundled(self) -> None: self.assertTrue((SKILL_DIR / "scripts" / "lookup.py").is_file()) def test_manual_router_protocol_is_absent(self) -> None: corpus = read_text(SKILL_FILE) + "\n" + "\n".join( read_text(path) for path in REFERENCES_DIR.glob("*.md") ) for forbidden in ("路由中心", "选择一个主专题", "候选页继续判断"): self.assertNotIn(forbidden, corpus) def test_topic_pages_do_not_instruct_manual_navigation(self) -> None: for path in REFERENCES_DIR.glob("*.md"): for line_number, line in enumerate(read_text(path).splitlines(), start=1): with self.subTest(path=path.name, line=line_number): self.assertIsNone(OPERATIONAL_TOPIC_LINK.search(line), line) def test_json_router_is_absent(self) -> None: self.assertFalse((REFERENCES_DIR / "00_agent_index.json").exists()) self.assertFalse((ROOT / "docs" / "tsl" / "syntax" / "00_agent_index.json").exists()) def test_references_do_not_escape_skill(self) -> None: for path in REFERENCES_DIR.glob("*.md"): for target in local_link_targets(read_text(path)): link_path = target.split("#", 1)[0].replace("\\", "/") self.assertNotIn("docs/tsl/", link_path, f"{path}: {target}") self.assertFalse(link_path.startswith("/"), f"{path}: {target}") self.assertNotIn("..", PurePosixPath(link_path).parts, f"{path}: {target}") def test_reference_markdown_links_resolve(self) -> None: for path in REFERENCES_DIR.glob("*.md"): for target in local_link_targets(read_text(path)): link_path = target.split("#", 1)[0] self.assertTrue((REFERENCES_DIR / link_path).is_file(), f"{path}: {target}") def test_skill_uses_lookup_as_its_only_retrieval_entry(self) -> None: text = read_text(SKILL_FILE) self.assertIn("scripts/lookup.py", text) for mode in ("write", "diagnose", "explain"): self.assertIn(f"--mode {mode}", text) self.assertEqual(local_link_targets(text), []) self.assertNotIn("references/index.md", text) def test_skill_does_not_bundle_external_domains(self) -> None: forbidden = { "naming.md", "code_style.md", "toolchain.md", "modules", "tsl-api-reference", "00_agent_index.json", } actual = {path.name for path in SKILL_DIR.rglob("*")} self.assertTrue(forbidden.isdisjoint(actual), forbidden & actual) def test_compatibility_index_contains_no_syntax_facts(self) -> None: text = read_text(COMPATIBILITY_INDEX) self.assertIn("../../../skills/tsl-syntax-reference/SKILL.md", text) for forbidden in ( "```", "代码块身份", "01_quickstart.md", "02_core_model.md", "| 任务", ): self.assertNotIn(forbidden, text) def test_ruleset_file_remains_tsl_router(self) -> None: self.assertTrue(RULESET_FILE.is_file()) text = read_text(RULESET_FILE) for route in ( "tsl-syntax-reference", "tsl-api-reference", "naming.md", "code_style.md", "toolchain.md", "modules/index.md", "项目", ): self.assertIn(route, text) def test_ruleset_routes_both_tsl_skills(self) -> None: text = read_text(RULESET_FILE) self.assertRegex(text, r"语法[^\n]*tsl-syntax-reference|tsl-syntax-reference[^\n]*语法") self.assertRegex(text, r"API|函数") self.assertIn("tsl-api-reference", text) def test_ruleset_syntax_constraints_moved_to_skill(self) -> None: ruleset = read_text(RULESET_FILE) owner = read_text(SKILL_FILE) + "\n" + "\n".join( read_text(path) for path in REFERENCES_DIR.glob("*.md") ) self.assertRegex(ruleset, r"缺失|不可用") self.assertRegex(ruleset, r"停止|阻断") for forbidden in ( "```tsl", "代码块身份", ".tsl` / `.tsf` 后缀由用户指定时", ".tsl` 代码需要本文件内函数或类时", ): self.assertNotIn(forbidden, ruleset) self.assertIn("代码块身份", owner) self.assertIn(".tsl", owner) self.assertIn(".tsf", owner) def test_static_docs_route_to_fact_owner(self) -> None: routes = { ROOT / "docs" / "tsl" / "index.md": "../../skills/tsl-syntax-reference/SKILL.md", ROOT / "docs" / "tsl" / "naming.md": "../../skills/tsl-syntax-reference/SKILL.md", ROOT / "docs" / "tsl" / "code_style.md": "../../skills/tsl-syntax-reference/SKILL.md", ROOT / "docs" / "index.md": "../skills/tsl-syntax-reference/SKILL.md", } legacy_deep_link = re.compile(r"(?:docs/tsl/)?syntax/(?:0[1-9]|1[0-9]|2[0-4])_") for path, expected_link in routes.items(): text = read_text(path) self.assertIn(expected_link, text, path) self.assertIsNone(legacy_deep_link.search(text), path) if __name__ == "__main__": unittest.main()