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} 片段未含字数区间"

View File

@@ -1,3 +1,4 @@
from ww_config.genre_craft import GENRE_CRAFT, genre_craft_fragment
from ww_config.settings import Settings, get_settings
__all__ = ["Settings", "get_settings"]
__all__ = ["GENRE_CRAFT", "Settings", "genre_craft_fragment", "get_settings"]

View File

@@ -0,0 +1,67 @@
"""品类相关 craft 配置(灵感 F1
`GENRE_CRAFT`:题材 → 「语域/节奏/钩子偏好/默认字数区间/常见套路」的**文本片段**。
品类无关内核show-don't-tell / 对话个性化 / 反注水原则)在 `write_craft.md`
本表只装**随题材而异**的片段,由 assemble 按 `project.genre` 选片段注入缓存前缀。
字数写成**区间参考**(软引导,非每章硬目标)——具体篇幅仍按情节功能分配(见内核)。
键与前端 wizard 的 GENRES 预设对齐(玄幻/仙侠/都市/科幻/历史/悬疑)+常见言情;
自由填写的题材命不中即优雅返回 None不注入片段不报错。
"""
from __future__ import annotations
from typing import Final
# 每条片段是灵活提示,不是穷举清单——覆盖语域/节奏/钩子/字数/套路等维度,供模型自行取舍。
GENRE_CRAFT: Final[dict[str, str]] = {
"玄幻": (
"语域偏热血爽快、画面感强;节奏偏快,冲突密集,实力成长线要清晰可感。"
"钩子偏好等级压制、打脸反转、机缘奇遇与悬念前置。"
"单章篇幅参考约 25003500 字,按情节功能弹性调整。"
"常见套路:升级流、扮猪吃虎、系统/金手指、宗门家族争斗——用得要有新意,别照搬。"
),
"仙侠": (
"语域偏古雅大气,兼顾意境与热血;节奏中到快,以境界突破为张力节点。"
"钩子偏好机缘造化、道心考验、恩怨情仇与天骄争锋。"
"单章篇幅参考约 25003500 字,按情节功能弹性调整。"
"常见套路:修真等级、飞升渡劫、师门情义、逆境翻盘——古意要真、别堆砌辞藻。"
),
"都市": (
"语域偏现代口语、贴近生活、爽感直接;节奏快,即时反馈要强。"
"钩子偏好装逼打脸、身份反差、隐藏实力与逆袭翻身。"
"单章篇幅参考约 20003000 字(移动端碎片阅读),按情节功能弹性调整。"
"常见套路:扮猪吃虎、隐藏身份、都市异能、商战权斗——落点要接地气。"
),
"科幻": (
"语域偏冷静、有逻辑感、由设定驱动;节奏偏中,重悬念与推演,慎防设定倾泻。"
"钩子偏好谜题危机、科技奇观与命运抉择。"
"单章篇幅参考约 25003500 字,按情节功能弹性调整。"
"常见套路:末世、星际、赛博、时间循环——设定服务剧情,避免大段科普打断叙事。"
),
"历史": (
"语域偏厚重考究、代入感强;节奏偏中,以权谋博弈与人物弧线为主。"
"钩子偏好权谋算计、命运转折与家国大势。"
"单章篇幅参考约 30004000 字,按情节功能弹性调整。"
"常见套路:穿越架空、争霸天下、朝堂权谋、种田发展——细节要经得起推敲。"
),
"悬疑": (
"语域偏冷峻克制、讲究信息控制;节奏张弛有度,靠悬念与反转拉动。"
"钩子偏好谜题、危险迫近、误导与真相错位。"
"单章篇幅参考约 25003500 字,按情节功能弹性调整。"
"常见套路:多线叙事、伏笔回收、层层反转、不可靠叙述——保持信息差,慎过早揭底。"
),
"言情": (
"语域偏细腻、注重情绪张力与关系互动;节奏偏中,以情感推拉为主线。"
"钩子偏好暧昧拉扯、误会错过、情感转折与命运羁绊。"
"单章篇幅参考约 25003500 字,按情节功能弹性调整。"
"常见套路:先婚后爱、破镜重圆、双向奔赴、追妻火葬场——情绪外化于互动细节,别空喊。"
),
}
def genre_craft_fragment(genre: str | None) -> str | None:
"""按题材取 craft 片段;未指定或命不中预设题材时返回 None不注入、不报错"""
if not genre:
return None
return GENRE_CRAFT.get(genre)