zizi 7f9114eca5 W13 旧用例承接补充:作者层路由与中文方法 Skill 的已跟踪文件改动
作者场景路由与指令、完善故事基础设定/合并故事方案/制定作品规划的技能中文化重构及其脚本、对应旧测试修改、SOT 导读、技能目录与实现回顾。此为 W13 阶段对已跟踪文件的改动,随批次按阶段整理补提交。
2026-09-10 19:39:47 +08:00

110 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""最终交付格式校验器的回归检查;不模拟或约束创作对话。"""
import sys
from pathlib import Path
import tempfile
import unittest
PROJECT_ROOT = Path(__file__).resolve().parents[3]
SCRIPT_DIR = PROJECT_ROOT / "muse/lifecycle/flow/skills/book/完善故事基础设定/scripts"
sys.path.insert(0, str(SCRIPT_DIR))
from validate_candidates import validate_candidates # noqa: E402
DRAFT = """# 雨伞|作品探索
## 根设定
作者希望保留雨天的兄妹关系。
## 一、作品定位与核心命题
待定:先从人物行动中寻找中心。
## 二、故事世界与社会系统
未探索:尚未讨论社会制度。
## 三、角色与关系
哥哥每天擦一把从不用的伞,妹妹故意把它拿走。
## 四、故事机制与关键实体
未探索:尚未决定是否有超自然力量。
## 五、阶段与状态变化
未探索:只有当前这一幕。
## 六、情节架构与全书大纲
未探索:兄妹争执后的事情还没想好。
## 七、主题与读者体验弧线
暂想保留亲近中带着疑问的感觉。
## 八、信息、悬念与伏笔管理
待定:哥哥为何留着伞。
## 九、叙事执行与一致性
先从动作里表现关系。
## 待决与取舍
保留争执这一幕,暂缓决定雨伞的来历。
"""
class CandidateValidatorTest(unittest.TestCase):
def setUp(self) -> None:
self.temp_dir = tempfile.TemporaryDirectory()
self.addCleanup(self.temp_dir.cleanup)
self.root = Path(self.temp_dir.name)
def codes(self, *contents: str) -> set[str]:
paths = []
for index, content in enumerate(contents):
path = self.root / f"draft-{index}.md"
path.write_text(content, encoding="utf-8")
paths.append(path)
_, problems = validate_candidates(paths)
return {problem.code for problem in problems}
def test_one_partial_draft_without_ids_or_length_limits_passes(self) -> None:
self.assertEqual(set(), self.codes(DRAFT))
def test_drafts_can_expand_different_subsets(self) -> None:
expanded = DRAFT.replace(
"待定:先从人物行动中寻找中心。",
"### 1.1 作品形态与定位\n\n### 1.1.1 题材、载体与目标读者\n\n暂定现实题材。",
)
self.assertEqual(set(), self.codes(DRAFT, expanded))
def test_unknown_missing_or_reordered_headings_fail(self) -> None:
for content in (
DRAFT.replace("## 三、角色与关系", "## 三、人物"),
DRAFT.replace("## 三、角色与关系\n", ""),
DRAFT.replace("待定:先从人物行动中寻找中心。",
"### 1.2 故事承诺与一句话梗概\n\n设想。\n\n### 1.1 作品形态与定位\n\n设想。"),
):
with self.subTest(content=content):
self.assertIn("TREE_MISMATCH", self.codes(content))
def test_empty_chapter_and_bare_placeholder_fail_but_explained_unknown_passes(self) -> None:
self.assertIn("SECTION_EMPTY", self.codes(DRAFT.replace("未探索:尚未讨论社会制度。", "<!-- 留空 -->")))
self.assertIn("CANDIDATE_PLACEHOLDER", self.codes(DRAFT.replace("待定:哥哥为何留着伞。", "TODO")))
self.assertEqual(set(), self.codes(DRAFT))
def test_optional_ids_are_unique_but_have_no_frozen_bands(self) -> None:
numbered = DRAFT.replace("哥哥每天", "- **S090|哥哥:** 哥哥每天").replace(
"先从动作里表现关系。", "- **S003|表达:** 先从动作里表现关系。"
)
self.assertEqual(set(), self.codes(numbered))
self.assertIn("SETTING_ID_DUPLICATE", self.codes(numbered.replace("S003", "S090")))
def test_missing_title_or_deep_heading_fails(self) -> None:
self.assertIn("CANDIDATE_STRUCTURE", self.codes(DRAFT.replace("# 雨伞|作品探索\n", "")))
self.assertIn("CANDIDATE_STRUCTURE", self.codes(DRAFT + "\n#### 额外层级\n说明。"))
def test_missing_file_is_reported(self) -> None:
_, problems = validate_candidates([self.root / "missing.md"])
self.assertEqual(["CANDIDATE_FILE_UNREADABLE"], [problem.code for problem in problems])
if __name__ == "__main__":
unittest.main()