作者场景路由与指令、完善故事基础设定/合并故事方案/制定作品规划的技能中文化重构及其脚本、对应旧测试修改、SOT 导读、技能目录与实现回顾。此为 W13 阶段对已跟踪文件的改动,随批次按阶段整理补提交。
110 lines
4.2 KiB
Python
110 lines
4.2 KiB
Python
#!/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()
|