🐛 fix(tsl_syntax): repair lookup engine and reconcile reference facts
lookup.py: exempt code-anchored ASCII identifiers from the mixed zh/en gate so exact hits are no longer dropped; dedup parent/child sections in results; validate write-prelude anchors in --check. references: correct interpreter-verified facts (case-as-expression, control-flow semicolons, __line__/__stack_frame, tslObjects order, destroy timing, ErrDefine, truncated outputs), fix headings, scope qualifiers and reversed quotes; strip dead preamble metadata from all 24 pages. packaging: exclude __pycache__/*.pyc from playbook and bundle copies; update build test file-count assertion to match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,13 +6,22 @@ import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from scripts.build_tsl_playbook import build_agents_text
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SCRIPT = ROOT / "scripts" / "build_tsl_playbook.py"
|
||||
SYNC_WORKFLOW = ROOT / ".gitea" / "workflows" / "sync-tsl-playbook.yml"
|
||||
README = ROOT / "README.md"
|
||||
|
||||
|
||||
class BuildTslPlaybookTests(unittest.TestCase):
|
||||
def test_readme_names_tsl_syntax_skill_as_unique_fact_owner(self):
|
||||
text = README.read_text(encoding="utf-8")
|
||||
|
||||
self.assertIn("TSL 语法事实唯一由 `tsl-syntax-reference` 管理", text)
|
||||
self.assertIn("TSL 领域路由与事实边界", text)
|
||||
|
||||
def test_builds_minimal_tsl_playbook_tree(self):
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
output = Path(tmp_dir) / "tsl-playbook"
|
||||
@@ -37,6 +46,9 @@ class BuildTslPlaybookTests(unittest.TestCase):
|
||||
self.assertTrue((syntax_skill / "SKILL.md").is_file())
|
||||
self.assertTrue((syntax_skill / "scripts" / "lookup.py").is_file())
|
||||
self.assertFalse((syntax_skill / "references" / "index.md").exists())
|
||||
self.assertTrue(
|
||||
(output / "skills" / "tsl-syntax-reference" / "SKILL.md").is_file()
|
||||
)
|
||||
self.assertTrue(
|
||||
(output / "skills" / "tsl-api-reference" / "SKILL.md").is_file()
|
||||
)
|
||||
@@ -61,9 +73,54 @@ class BuildTslPlaybookTests(unittest.TestCase):
|
||||
output_docs = count_files(output / "docs" / "tsl")
|
||||
self.assertEqual(output_docs, source_docs)
|
||||
|
||||
source_skill = count_files(ROOT / "skills" / "tsl-api-reference")
|
||||
output_skill = count_files(output / "skills" / "tsl-api-reference")
|
||||
self.assertEqual(output_skill, source_skill)
|
||||
for skill_name in ("tsl-syntax-reference", "tsl-api-reference"):
|
||||
source_skill = count_files(ROOT / "skills" / skill_name)
|
||||
output_skill = count_files(output / "skills" / skill_name)
|
||||
self.assertEqual(output_skill, source_skill)
|
||||
|
||||
def test_missing_tsl_skill_reports_specific_source_path(self):
|
||||
for skill_name in ("tsl-syntax-reference", "tsl-api-reference"):
|
||||
with self.subTest(skill=skill_name), tempfile.TemporaryDirectory() as tmp_dir:
|
||||
repo = Path(tmp_dir) / "repo"
|
||||
repo.mkdir()
|
||||
copy_required_sources(repo)
|
||||
missing_path = repo / "skills" / skill_name
|
||||
shutil.rmtree(missing_path)
|
||||
|
||||
result = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(repo / "scripts" / "build_tsl_playbook.py"),
|
||||
"--output",
|
||||
str(repo / "output"),
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn(str(missing_path), result.stderr)
|
||||
|
||||
def test_build_agents_text_only_rewrites_title_and_trailing_newline(self):
|
||||
marker = (
|
||||
"- 需要任何 TSL 语法、文件模型、函数、模块或金融数据事实时,"
|
||||
"第一跳统一进 `docs/tsl/index.md`,由它路由到具体页;"
|
||||
"不在本文件内猜文件路径,也不全目录搜索。"
|
||||
)
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
ruleset = Path(tmp_dir) / "index.md"
|
||||
ruleset.write_text(
|
||||
f"# TSL 智能体规则\n\n{marker}\n\n",
|
||||
encoding="utf-8",
|
||||
newline="\n",
|
||||
)
|
||||
|
||||
agents_text = build_agents_text(ruleset)
|
||||
|
||||
self.assertEqual(
|
||||
agents_text,
|
||||
f"# TSL Agent Instructions\n\n{marker}\n",
|
||||
)
|
||||
|
||||
def test_sync_workflow_does_not_remove_entire_target_branch(self):
|
||||
text = SYNC_WORKFLOW.read_text(encoding="utf-8")
|
||||
@@ -109,6 +166,7 @@ class BuildTslPlaybookTests(unittest.TestCase):
|
||||
for path in (
|
||||
"AGENTS.md",
|
||||
"docs/tsl/index.md",
|
||||
"skills/tsl-syntax-reference/SKILL.md",
|
||||
"skills/tsl-api-reference/SKILL.md",
|
||||
):
|
||||
git(repo, "cat-file", "-e", f"HEAD:{path}")
|
||||
@@ -125,6 +183,7 @@ class BuildTslPlaybookTests(unittest.TestCase):
|
||||
for path in (
|
||||
"AGENTS.md",
|
||||
"docs/tsl/index.md",
|
||||
"skills/tsl-syntax-reference/SKILL.md",
|
||||
"skills/tsl-api-reference/SKILL.md",
|
||||
):
|
||||
git(repo, "cat-file", "-e", f"HEAD:{path}")
|
||||
@@ -164,20 +223,18 @@ def run_sync(repo: Path) -> None:
|
||||
env = os.environ.copy()
|
||||
env.update(
|
||||
{
|
||||
"REPO_DIR": str(repo),
|
||||
"TARGET_BRANCH": "tsl-playbook",
|
||||
"COMMIT_AUTHOR_NAME": "test",
|
||||
"COMMIT_AUTHOR_EMAIL": "test@example.invalid",
|
||||
}
|
||||
)
|
||||
with tempfile.NamedTemporaryFile(
|
||||
"w", suffix=".sh", encoding="utf-8", newline="\n", delete=False
|
||||
) as script_file:
|
||||
script_file.write(extract_sync_workflow_script())
|
||||
script_path = script_file.name
|
||||
script_path = repo / ".sync-test.sh"
|
||||
script_path.write_text(
|
||||
extract_sync_workflow_script(), encoding="utf-8", newline="\n"
|
||||
)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["bash", script_path],
|
||||
["bash", script_path.name],
|
||||
cwd=repo,
|
||||
env=env,
|
||||
capture_output=True,
|
||||
@@ -186,7 +243,7 @@ def run_sync(repo: Path) -> None:
|
||||
errors="replace",
|
||||
)
|
||||
finally:
|
||||
os.unlink(script_path)
|
||||
script_path.unlink(missing_ok=True)
|
||||
if result.returncode != 0:
|
||||
raise AssertionError(result.stderr + result.stdout)
|
||||
|
||||
@@ -223,6 +280,17 @@ def copy_required_sources(repo: Path) -> None:
|
||||
"print('lookup')\n", encoding="utf-8", newline="\n"
|
||||
)
|
||||
|
||||
syntax_skill = repo / "skills" / "tsl-syntax-reference"
|
||||
(syntax_skill / "references").mkdir(parents=True)
|
||||
(syntax_skill / "SKILL.md").write_text(
|
||||
"---\nname: tsl-syntax-reference\n---\n",
|
||||
encoding="utf-8",
|
||||
newline="\n",
|
||||
)
|
||||
(syntax_skill / "references" / "index.md").write_text(
|
||||
"# TSL Syntax Reference\n", encoding="utf-8", newline="\n"
|
||||
)
|
||||
|
||||
(repo / "rulesets" / "tsl").mkdir(parents=True)
|
||||
(repo / "rulesets" / "tsl" / "index.md").write_text(
|
||||
"# TSL Agent Instructions\n", encoding="utf-8", newline="\n"
|
||||
@@ -230,7 +298,13 @@ def copy_required_sources(repo: Path) -> None:
|
||||
|
||||
|
||||
def count_files(path: Path) -> int:
|
||||
return sum(1 for item in path.rglob("*") if item.is_file())
|
||||
return sum(
|
||||
1
|
||||
for item in path.rglob("*")
|
||||
if item.is_file()
|
||||
and "__pycache__" not in item.parts
|
||||
and item.suffix != ".pyc"
|
||||
)
|
||||
|
||||
|
||||
def extract_sync_workflow_script() -> str:
|
||||
|
||||
Reference in New Issue
Block a user