问题概述
当调用方显式指定 genre、但 query 未命中任何路由行的关键词时,会走 explicit_genre_fallback。
此时生成的 MASTER_SETTING.route.primary_genre 变成了 fallback 行自己的子流派,而不是调用方指定的题材。
以「仙侠」为例:primary_genre 被写成 玄幻退婚流。
连带 base_context 注入了 TR-001「退婚三年之约」,而该条目的 inject_target 是
CHAPTER_BRIEF.writing_guidance —— 意味着此后每一章的写作指导里都会掺进退婚流桥段,
而作品本身与退婚流毫无关系。
影响范围
- Story System / CHAPTER_COMMIT
- Python CLI
触发命令或入口
python -X utf8 "<CLAUDE_PLUGIN_ROOT>/scripts/webnovel.py" --project-root "<PROJECT_ROOT>" story-system "仙侠" --persist
等价于 /webnovel-init 中选择「仙侠」题材后自动执行的路由。
复现步骤
- 新建项目,题材选择「仙侠」(query 与 genre 均为「仙侠」,不含任何路由行关键词)
- 执行上面的
story-system ... --persist
- 查看
.story-system/MASTER_SETTING.json 的 route 段与 base_context
预期行为
primary_genre 应为调用方显式指定的 仙侠,与同一份输出里的 canonical_genre 一致;
base_context 不应注入与该题材无关的桥段条目。
实际行为
"route": {
"primary_genre": "玄幻退婚流",
"canonical_genre": "仙侠",
"route_source": "explicit_genre_fallback",
"genre_filter": "仙侠"
}
同一个 route 对象里两个字段互相矛盾。base_context 中出现 TR-001「退婚三年之约」。
根因定位
webnovel-writer/scripts/data_modules/story_system_engine.py:192
primary_genre = str(matched.get("题材/流派") or genre or "").strip()
在 fallback 路径下,matched 来自 _fallback_row_for_genre()(同文件 :349),
该函数返回第一个「适用题材」包含目标 genre 的行。
而 references/csv/题材与调性推理.csv 的第一行 GR-001 是:
| 编号 |
题材/流派 |
适用题材 |
canonical_genre |
核心调性 |
节奏策略 |
| GR-001 |
玄幻退婚流 |
玄幻|仙侠 |
玄幻 |
先压后爆 |
三章内必须有首次有效反打 |
因为「适用题材」含「仙侠」,所以任何 genre=仙侠 且未命中关键词的项目都会命中它,
拿到 玄幻退婚流 作为 primary_genre。同一行还一并带出了 core_tone 与 pacing_strategy。
值得注意的是:canonical_genre 在同一函数 :201-203 已经实现了「显式 genre 优先」的逻辑,
所以它的输出是正确的(仙侠)。primary_genre 只是漏掉了这一步,
这也说明「显式指定优先」本来就是既定设计意图。
为什么现有测试没有发现
test_story_system_falls_back_to_explicit_genre 的 fixture 中,
fallback 行的「题材/流派」恰好等于传入的 genre(都是「现言」),
因此「取行的流派」和「取调用方的 genre」两种实现都能通过,测试无法区分。
运行环境
- 操作系统:Windows 11
- Python 版本:3.12.10
- Claude Code 版本:最新
- 插件版本:6.2.1
- 安装范围:user
使用模型
deepseek-v4-pro 与 claude-opus(两个项目分别使用,与本问题无关——路由是纯 Python 逻辑)
相关文件或状态
webnovel-writer/scripts/data_modules/story_system_engine.py:192、:349
webnovel-writer/references/csv/题材与调性推理.csv(GR-001)
- 受影响产物:
.story-system/MASTER_SETTING.json、.story-system/MASTER_SETTING.md
补充说明
在我本地两个独立的仙侠题材项目上均复现,输出完全一致(primary_genre / core_tone /
pacing_strategy 三个字段都相同),确认与具体作品内容无关。
inferred_genre_fallback 路径不应一并修改:那条路径下 genre 只是从自由文本猜出来的粗粒度类别,
fallback 行提供的子流派更具体,是有价值的。test_route_infers_canonical_genre_from_spaced_query
已经断言了这个行为(推断出「快穿」时保留行的「快穿任务」),应当保持。
我已准备好修复与回归测试,随后附 PR。
问题概述
当调用方显式指定 genre、但 query 未命中任何路由行的关键词时,会走
explicit_genre_fallback。此时生成的
MASTER_SETTING.route.primary_genre变成了 fallback 行自己的子流派,而不是调用方指定的题材。以「仙侠」为例:
primary_genre被写成玄幻退婚流。连带
base_context注入了 TR-001「退婚三年之约」,而该条目的inject_target是CHAPTER_BRIEF.writing_guidance—— 意味着此后每一章的写作指导里都会掺进退婚流桥段,而作品本身与退婚流毫无关系。
影响范围
触发命令或入口
等价于
/webnovel-init中选择「仙侠」题材后自动执行的路由。复现步骤
story-system ... --persist.story-system/MASTER_SETTING.json的route段与base_context预期行为
primary_genre应为调用方显式指定的仙侠,与同一份输出里的canonical_genre一致;base_context不应注入与该题材无关的桥段条目。实际行为
同一个
route对象里两个字段互相矛盾。base_context中出现 TR-001「退婚三年之约」。根因定位
webnovel-writer/scripts/data_modules/story_system_engine.py:192在 fallback 路径下,
matched来自_fallback_row_for_genre()(同文件 :349),该函数返回第一个「适用题材」包含目标 genre 的行。
而
references/csv/题材与调性推理.csv的第一行 GR-001 是:因为「适用题材」含「仙侠」,所以任何 genre=仙侠 且未命中关键词的项目都会命中它,
拿到
玄幻退婚流作为 primary_genre。同一行还一并带出了core_tone与pacing_strategy。值得注意的是:
canonical_genre在同一函数 :201-203 已经实现了「显式 genre 优先」的逻辑,所以它的输出是正确的(仙侠)。
primary_genre只是漏掉了这一步,这也说明「显式指定优先」本来就是既定设计意图。
为什么现有测试没有发现
test_story_system_falls_back_to_explicit_genre的 fixture 中,fallback 行的「题材/流派」恰好等于传入的 genre(都是「现言」),
因此「取行的流派」和「取调用方的 genre」两种实现都能通过,测试无法区分。
运行环境
使用模型
deepseek-v4-pro 与 claude-opus(两个项目分别使用,与本问题无关——路由是纯 Python 逻辑)
相关文件或状态
webnovel-writer/scripts/data_modules/story_system_engine.py:192、:349webnovel-writer/references/csv/题材与调性推理.csv(GR-001).story-system/MASTER_SETTING.json、.story-system/MASTER_SETTING.md补充说明
在我本地两个独立的仙侠题材项目上均复现,输出完全一致(
primary_genre/core_tone/pacing_strategy三个字段都相同),确认与具体作品内容无关。inferred_genre_fallback路径不应一并修改:那条路径下 genre 只是从自由文本猜出来的粗粒度类别,fallback 行提供的子流派更具体,是有价值的。
test_route_infers_canonical_genre_from_spaced_query已经断言了这个行为(推断出「快穿」时保留行的「快穿任务」),应当保持。
我已准备好修复与回归测试,随后附 PR。