feat(agents,core,config): 写章 craft 教条块 + genre-aware 片段 + 开篇黄金三章特判

- 新增 write_craft.md 品类无关写章 craft 内核(网文语域/show-don't-tell/对话 voice/
  反注水/结构性文字质感,灵活原则非穷举清单)+ load_craft_doctrine()(import 期读盘,
  不入 SPECS/金标准)
- build_write_request 把 craft 教条作为最前的 cache=True system 块注入(craft→stable_core,
  守缓存前缀 #9,tier=writer 不写 model)
- 新增 packages/config GENRE_CRAFT 题材片段表,assemble 按 project.genre 注入 stable_core;
  ProjectSpecView + SqlProjectSpecRepo 透传 genre
- 开篇前 3 章在 volatile 注入黄金三章提示(含章号→每章易变,不进缓存前缀)
- 修正 toolbox_registry 注释:11 条 → 15 条(legacy 3 + 新 8 + Scope B 4)
This commit is contained in:
Yaojia Wang
2026-07-06 14:29:43 +02:00
parent 6854dac98f
commit 452bd56d95
15 changed files with 379 additions and 9 deletions

View File

@@ -0,0 +1,47 @@
"""GENRE_CRAFT 配置单测F1 · 品类相关 craft 片段表)。
GENRE_CRAFT 是「题材 → 语域/节奏/钩子/字数区间/常见套路」的**文本片段**映射;
品类无关内核在 `write_craft.md`品类相关片段在此表。lookup 对未知题材优雅返回 None。
"""
from __future__ import annotations
from ww_config import GENRE_CRAFT, genre_craft_fragment
# 与前端 wizard.ts 的 GENRES 预设保持一致(玄幻/仙侠/都市/科幻/历史/悬疑)。
_FRONTEND_GENRES = ["玄幻", "仙侠", "都市", "科幻", "历史", "悬疑"]
def test_genre_craft_covers_frontend_presets() -> None:
# Arrange / Act / Assert前端每个预设题材都有对应片段。
for genre in _FRONTEND_GENRES:
assert genre in GENRE_CRAFT, f"题材 {genre!r} 缺 GENRE_CRAFT 片段"
def test_genre_craft_fragments_are_nonempty_text() -> None:
for genre, fragment in GENRE_CRAFT.items():
assert isinstance(fragment, str)
assert fragment.strip(), f"题材 {genre!r} 片段为空"
def test_genre_craft_fragment_returns_matching_text() -> None:
# Act
fragment = genre_craft_fragment("玄幻")
# Assert
assert fragment is not None
assert fragment == GENRE_CRAFT["玄幻"]
def test_genre_craft_fragment_unknown_returns_none() -> None:
assert genre_craft_fragment("不存在的题材") is None
def test_genre_craft_fragment_none_returns_none() -> None:
assert genre_craft_fragment(None) is None
assert genre_craft_fragment("") is None
def test_genre_fragments_mention_wordcount_dimension() -> None:
# 每个片段应含字数/篇幅维度(灵活片段,非穷举清单)。
for genre, fragment in GENRE_CRAFT.items():
assert "字数" in fragment or "篇幅" in fragment, f"{genre!r} 片段未含字数区间"