✨ feat(skills): add tsl syntax reference skill
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
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"
|
||||
|
||||
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",
|
||||
}
|
||||
EXPECTED_REFERENCES = {"index.md", *TOPIC_REFERENCES}
|
||||
MARKDOWN_LINK = re.compile(r"!?\[[^\]]*\]\(([^)]+)\)")
|
||||
|
||||
|
||||
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_local_link_targets_includes_local_images(self) -> None:
|
||||
text = "\n"
|
||||
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_is_exact(self) -> None:
|
||||
actual = {path.name for path in REFERENCES_DIR.iterdir()}
|
||||
self.assertEqual(actual, EXPECTED_REFERENCES)
|
||||
|
||||
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_index_is_only_complete_topic_router(self) -> None:
|
||||
index_text = read_text(REFERENCES_DIR / "index.md")
|
||||
self.assertEqual(
|
||||
{name for name in TOPIC_REFERENCES if f"]({name})" in index_text},
|
||||
TOPIC_REFERENCES,
|
||||
)
|
||||
for path in REFERENCES_DIR.glob("*.md"):
|
||||
if path.name == "index.md":
|
||||
continue
|
||||
linked_topics = {name for name in TOPIC_REFERENCES if f"]({name})" in read_text(path)}
|
||||
self.assertNotEqual(linked_topics, TOPIC_REFERENCES, path.name)
|
||||
|
||||
skill_links = set(local_link_targets(read_text(SKILL_FILE)))
|
||||
self.assertEqual(
|
||||
skill_links,
|
||||
{
|
||||
"references/01_quickstart.md",
|
||||
"references/index.md",
|
||||
"references/11_pitfalls.md",
|
||||
},
|
||||
)
|
||||
|
||||
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()
|
||||
Reference in New Issue
Block a user