2026-04-30-system-quality-fixes.md 39 KB

灵石庄测试发现的系统级修复 Spec

日期:2026-04-30 范围:webnovel-writer 系统层面问题(与 LLM 模型能力无关) 测试基础:DeepSeek v4pro 在 D:\wk\xiaoshuo\灵石庄 上跑完 init / plan / write 第 1-2 章 + 审查 事实校验:本版已对照当前 repo 代码行为与 D:\wk\xiaoshuo\灵石庄 实际 artifacts 复核,修正了过度归因和不存在的落点 已交付:commit f58d657 fix: align projection writers with real LLM commit schema(schema 漂移系列)


1. 背景与范围

灵石庄项目用 DeepSeek v4pro 跑通 init→plan→write→review→commit 完整链路两章后,对生成产物做端到端审视。本 spec 列出纯系统侧的 8 个未修问题(schema 漂移已在 f58d657 修完),按优先级 P0→P3 排序。每条包含:现状证据、影响、根因、修复方案(具体文件 + 行号)、验收标准、工作量估计。

不在本 spec 范围内:

  • LLM 输出本身的写作质量问题(AI 味、排比句、习惯动作过密等)—— 由审查 agent 本职拦截
  • 已在 f58d657 中修复的 schema 漂移问题(state_projection / memory_writer / vector_writer / index_manager schema、protagonist_state 镜像、index 主角识别、entity 别名兜底)
  • 已在历史 commit 修过的(init 项目根 9b8976e、entity alias fallback fc63628、安全写入项目经验记忆 d2571d2 等)

2. 执行计划

本节把下方 8 个已验证问题重新编排成可直接实现的工作流。问题本身仍以下方详细 spec 为事实源;这里负责明确顺序、依赖和落点,避免实现时在 prompt、runtime brief、init 模板、review 回流之间来回跳。

Phase 1 — chapter directive / chapter_focus / 写章 prompt 排序(Issues #1 #2 #5

Objective:让详细大纲里的章节执行约束进入 chapter_brief 顶层,并在写章任务书中压过 dynamic_context;同时切断 chapter_focus 从 reference summary 继承的误导链路。

Files to change

  • webnovel-writer/scripts/chapter_outline_loader.py
  • webnovel-writer/scripts/story_system.py
  • webnovel-writer/scripts/data_modules/story_system_engine.py
  • webnovel-writer/scripts/data_modules/runtime_contract_builder.py(确认不混写 directive 与 review/runtime contract)
  • webnovel-writer/skills/webnovel-write/SKILL.md
  • 相关测试文件(优先沿用现有 story-system / outline loader 测试目录)

Concrete tasks

  • chapter_outline_loader.py 增加或扩展 load_chapter_execution_directive(project_root: Path, chapter: int),从 大纲/第{vol}卷-详细大纲.md 提取 goalobstaclescosttime_anchorchapter_spancountdowncbncpnscenmust_cover_nodesforbidden_zoneschapter_end_open_questionhook_typehook_strengthkey_entitiesstrandantagonist_tier
  • 在 story-system brief 组装链路中写入顶层 chapter_directive;找不到章节块时保持老路径兼容。
  • 修改 _suggest_chapter_focus():优先使用 chapter_directive.goal;没有 directive 时使用非占位 query;禁止从 dynamic_context[0]["核心摘要"] 派生。
  • 调整 webnovel-write/SKILL.md 任务书顺序:本章硬性约束CBN/CPNs/CEN本章禁区风格指引dynamic_context 风格参考。
  • 增加测试覆盖:directive 字段落盘、chapter_focus 来源、无 directive 时不抄 reference summary、prompt 顺序人工 fixture。

Dependencies

  • 依赖现有 chapter_outline_loader.py 已能识别 CBN / CPNs / CEN / mandatory_nodes / prohibitions
  • Phase 2 的 reference ranking 会复用本阶段产出的 chapter_directive.goal/key_entities/strand/antagonist_tier,所以本阶段必须先做。

Verification/tests

  • pytest --no-cov 中新增的 outline/story-system 相关测试通过。
  • 抽样生成第 1 章 brief,确认 chapter_directive.time_anchormust_cover_nodesforbidden_zones 存在。
  • 人工检查写章任务书,确认 dynamic_context 被标注为补充参考且排在 directive 后。

Estimated scope/risk:中,约 4-6 小时。主要风险是详细大纲 markdown 标题/字段格式变体导致解析漏抓;需要正则兼容 第1章第一章、空格变体。

Phase 2 — query truthiness / reference chapter-aware ranking(Issue #3

Objective:保证 story-system 收到真实章节目标 query,并在 reference 检索排序中放大章节语义,避免“题材对、场景错”的卡片压过借贷/调查类卡片。

Files to change

  • webnovel-writer/scripts/story_system.py
  • webnovel-writer/scripts/data_modules/story_system_engine.py
  • webnovel-writer/skills/webnovel-plan/SKILL.md
  • webnovel-writer/skills/webnovel-write/SKILL.md
  • webnovel-writer/skills/webnovel-review/SKILL.md
  • reference/story-system 相关测试 fixture

Concrete tasks

  • story_system.py CLI 入口增加 placeholder query 保护:识别 {章纲目标}第N章章纲目标、空泛模板文本,输出诊断或降级警告。
  • 更新 plan/write skill:调用 story-system 前必须解析真实章目标;不能把占位字符串传入 query
  • 更新 review skill:审查 brief 时对照 chapter_brief.meta.query 与详细大纲目标,把 placeholder query 标成系统问题。
  • story_system_engine.py 的 query 扩展/表收集/排序附近加入 chapter-aware scoring,输入来自 chapter_directive.goal/key_entities/strand/antagonist_tier
  • 将最终排序调整为题材匹配 + 章节关键词命中的组合分;同优先级下章节关键词命中更多者靠前。
  • 保留 reference_search.py 通用能力,不新增不存在的路由层。

Dependencies

  • 依赖 Phase 1 提供 chapter_directive。如果 Phase 2 先做,只能先使用 query 文本,排序收益会明显降低。
  • 需要一份包含金融/借据/利息关键词的 reference fixture,才能证明第 1 章场景召回改善。

Verification/tests

  • test_story_system_rejects_or_warns_placeholder_query:占位 query 被诊断。
  • test_story_system_reference_matching_prefers_chapter_keywords:借贷类卡片排在论道/丹药/宗门经营泛卡前。
  • 用灵石庄第 1 章 brief 抽样验证:dynamic_context 至少有一条与“借据/利息/复利/债”相关。

Estimated scope/risk:中,约 4 小时。主要风险是 reference CSV 字段不稳定,需要对 关键词意图与同义词适用场景 缺字段做容错。

Phase 3 — init template pruning / conditional generation(Issue #4

Objective:减少 init 后空壳设定文件和空目录,避免单主角项目生成 主角组.md,并把事实源集中到已有主角卡、世界观、卷纲等文件。

Files to change

  • webnovel-writer/scripts/init_project.py
  • webnovel-writer/templates/output/设定集-金手指.md
  • webnovel-writer/templates/output/复合题材-融合逻辑.md
  • webnovel-writer/templates/output/设定集-主角组.md
  • webnovel-writer/templates/output/设定集-女主卡.md
  • webnovel-writer/skills/webnovel-init/SKILL.md
  • init 相关测试

Concrete tasks

  • 从默认生成列表中移除被其它文件覆盖的空壳模板:金手指、爽点规划、复合题材融合逻辑(除非项目显式需要)。
  • idea_bank.protagonist_structure 条件生成 主角组.md:仅 主角组双主角多主角 类项目生成。
  • heroine_config 条件生成 女主卡.md无女主 时不生成。
  • 角色库/物品库/其他设定/ 改为第一次实体增量写入时再创建。
  • 更新 init skill 文档,避免 prompt 继续要求创建已剪掉的模板。

Dependencies

  • 与 Phase 1/2 无代码依赖,可并行实现。
  • 需要先搜索 init_project.pywebnovel-init/SKILL.md、模板路径中的生成清单,避免只删模板不改调用方。

Verification/tests

  • 单主角 fixture 下不生成 设定集/主角组.md,仍生成 设定集/主角卡.md
  • 无女主 fixture 下不生成 设定集/女主卡.md
  • 默认 init 后不生成 金手指设计.md爽点规划.md、空 角色库/物品库/其他设定 目录。

Estimated scope/risk:小,约 2 小时。主要风险是模板文件名和输出文件名不完全一致,需要以 init_project.py 的真实映射为准。

Phase 4 — placeholder guardrails / cross-volume anchor writeback(Issues #6 #7

Objective:在 plan/write 边界发现 [待...]暂名{占位} 等漂移;同时把 Phase 7 定义为当前卷规划完成后的最小跨卷锚点写回:只在 webnovel-plan 已完成并验证当前卷规划 artifacts 后,向 总纲.md 写回 V+1 的卷名、核心冲突、卷末高潮,以及当前规划产物显式结构化给出的伏笔/开放环。Phase 7 不自动详细规划下一卷。

Files to change

  • webnovel-writer/scripts/data_modules/placeholder_scanner.py(新增)
  • webnovel-writer/scripts/webnovel.py
  • webnovel-writer/skills/webnovel-plan/SKILL.md
  • webnovel-writer/skills/webnovel-write/SKILL.md
  • webnovel-writer/templates/output/大纲-总纲.md
  • plan/write/CLI 相关测试

Concrete tasks

  • 新增 scan_placeholders(project_root),扫描 大纲/*.md设定集/*.md 中的 \[待[^\]]*\](暂名)(暂名)(待补充)、全字段 {占位} / <占位>
  • webnovel.py 增加 placeholder-scan --project-root <dir> CLI。
  • 在 plan skill 开始/结束处加入占位扫描:plan 阶段警告但不阻断。
  • 在 write skill 开始处加入章节相关实体的占位阻断:当前章涉及实体的设定文件仍有 pending 标记时阻断。
  • 调整 大纲-总纲.md 模板:init 不预生成 V2-V20 空行。
  • webnovel-plan 成功完成当前卷规划且 artifacts 已完整/通过验证后,向 总纲.md 写回 V+1 的最小跨卷锚点:卷名、核心冲突、卷末高潮。
  • 伏笔表只追加当前规划输出中显式结构化产出的新伏笔/持续开放环;禁止从自由文本推断伏笔。
  • Phase 7 禁止生成下一卷详细大纲、beat sheet、timeline 或章节级规划。

Dependencies

  • 与 Phase 3 有模板交叉:如果两者并行,必须协调 templates/output/大纲-总纲.md 与 init 生成逻辑。
  • write 阻断要复用 Phase 1 的 chapter_directive.key_entities 或现有章节实体解析结果,否则只能做全项目扫描,误报风险较高。

Verification/tests

  • test_placeholder_scanner_finds_pending_marks 覆盖 [待...]暂名{占位}
  • test_write_chapter_blocks_on_pending_related_entity:本章涉及实体有占位则阻断,不涉及则不阻断。
  • test_plan_flow_writes_minimal_next_volume_anchor:完成并验证 V1 规划后只补 V2 的卷名、核心冲突、卷末高潮,不预填 V3-V20 空表,也不生成 V2 详细大纲/beat sheet/timeline。
  • CLI smoke test:webnovel.py placeholder-scan --project-root <fixture> 返回结构化结果。

Estimated scope/risk:中,约 6 小时。主要风险是占位误报和“当前章节涉及实体”判定不准;应优先做警告/阻断分级。

Phase 5 — anti-pattern feedback loop(Issue #8

Objective:把 review 抓到的中高严重度 ai_flavor 问题回流到 .story-system/anti_patterns.json,让后续写章 brief 主动避开已发现句式。

Files to change

  • webnovel-writer/scripts/data_modules/review_schema.py
  • webnovel-writer/scripts/data_modules/story_system_engine.py
  • webnovel-writer/skills/webnovel-review/SKILL.md
  • webnovel-writer/skills/webnovel-write/SKILL.md
  • review/story-system 相关测试

Concrete tasks

  • 在审查报告持久化后增加 hook:category == "ai_flavor"severity in {"medium", "high", "critical"} 时追加 anti-pattern。
  • anti-pattern 记录包含 textsource_table="review_extracted"source_id="ch0002_issue_N"categoryadded_at
  • 增加去重:同一 evidence/text 不重复写入。
  • brief 构建时读取 review-extracted anti-pattern,并在写章任务书中作为“避雷模式”输入。
  • 更新 review/write skill 文档,明确哪些审查发现会进入后续写章约束。

Dependencies

  • 与 Phase 1 无硬依赖,但 Phase 1 的 prompt 排序完成后,anti-pattern 更容易被放到正确位置。
  • 需要确认现有 .story-system/anti_patterns.json schema,不要破坏 reference 初始化写入的旧字段。

Verification/tests

  • test_ai_flavor_review_issue_added_to_anti_patterns:中等以上 ai_flavor 被追加。
  • test_anti_pattern_review_feedback_dedupes_evidence:重复 evidence 不重复入库。
  • brief fixture 验证 review-extracted text 能出现在后续写章避雷列表。

Estimated scope/risk:小,约 2 小时。主要风险是 review report 保存入口不唯一,需要先定位所有审查持久化路径。


3. 待办问题清单(速览)

# 优先级 问题 影响面 工作量
1 P0 chapter_brief 缺少详细大纲的丰富执行约束 每一章质量
2 P0 写章 prompt 中 dynamic_context 占主位、大纲约束次要 每一章质量
3 P1 reference 检索对真实章目标感知不足,导致题材路由压过章节语义 每一章 brief 相关性
4 P1 设定模板批量生成空壳;单主角项目仍生成主角组文件 init 后用户混淆、占位 noise
5 P1 chapter_focus 字段被错塞 dynamic_context 里的无关 summary brief 误导
6 P2 设定文件缺少占位/漂移防护(卷纲 [待...] 占位 vs 主角卡已具名) plan 阶段不一致
7 P2 当前卷完成后缺少 V+1 最小锚点 / 结构化伏笔写回 跨卷承诺易脱节
8 P2 审查抓到的 ai_flavor 模式不回流 anti_patterns.json 同类问题反复出现

4. 详细修复 Spec

Issue #1 — chapter_brief 缺少详细大纲的丰富执行约束(P0)

现状证据

D:\wk\xiaoshuo\灵石庄\.story-system\chapters\chapter_001.json(136 行)字段构成:

{
  "meta": {...},
  "override_allowed": {"chapter_focus": "<从 SP-087 卡片抄来>"},
  "dynamic_context": [SP-087, NR-077, SY-009, CH-072],
  "source_trace": [...],
  "reasoning": {...}
}

D:\wk\xiaoshuo\灵石庄\大纲\第1卷-详细大纲.md 的第 1 章节有完整的:目标 / 阻力 / 代价 / 时间锚点 / 倒计时状态 / Strand / 反派层级 / CBN / CPNs / CEN / 必须覆盖节点 / 本章禁区 / 章末未闭合问题 / 钩子类型

其中 CBN / CPNs / CEN / 必须覆盖节点 / 本章禁区 已能被下游结构化解析器识别,但 chapter brief 本身没有携带目标 / 阻力 / 代价 / 时间锚点 / 倒计时 / 章末未闭合问题等执行约束

影响

  • 第 2 章审查报告抓到的"孙旺说'明儿个' vs 时间线表 D-Day 傍晚"矛盾 —— 写作前没有把时间锚点、章内跨度、倒计时状态放进主任务书
  • "本章禁区"可进入 review contract 的阻断规则,但没有作为写作前的正向执行约束展示,仍偏事后发现
  • fulfillment_result.json 是事后履约校验,等正文写完才发现节点缺失

根因

当前链路是分裂的:

  • webnovel-writer/scripts/chapter_outline_loader.py 已能从详细大纲中提取 cbn / cpns / cen / mandatory_nodes / prohibitions
  • webnovel-writer/scripts/data_modules/runtime_contract_builder.py 也已经消费了这些结构化字段,用于 selected_scenes / must_check / blocking_rules
  • CHAPTER_BRIEF 本身仍主要来自 story-system 的 dynamic_context + chapter_focus,没有把章节执行约束字段提升为写作主输入

问题不是“完全没读详细大纲”,而是只读了结构化骨架,没有把章节执行指令注入 chapter brief 主链

修复方案

  1. 在现有 webnovel-writer/scripts/chapter_outline_loader.py 能力之上补一层执行约束提取,优先扩展同一模块或新增相邻 helper:

    • load_chapter_execution_directive(project_root: Path, chapter: int) -> dict
    • 大纲/第{vol}卷-详细大纲.md 解析章节 markdown 块,提取字段:
      • goal / obstacles / cost / time_anchor / chapter_span / countdown
      • cbn / cpns: list[str] / cen
      • must_cover_nodes: list[str]("必须覆盖节点"行)
      • forbidden_zones: list[str]("本章禁区"行,分号分隔)
      • chapter_end_open_question / hook_type / hook_strength
      • key_entities: list[str]strandantagonist_tier
    • 解析容错:找不到本章块返回 None,让上层走老路径
  2. 修改 story-system 持久化的 chapter brief 组装链路(webnovel-writer/scripts/story_system.py + webnovel-writer/scripts/data_modules/story_system_engine.py,并让 runtime_contract_builder.py 继续负责 volume/review runtime contracts):

    from chapter_outline_loader import load_chapter_execution_directive
    directive = load_chapter_execution_directive(project_root, chapter)
    brief["chapter_directive"] = directive  # 新增顶层字段
    if directive and directive.get("goal"):
       # 用大纲目标覆盖从 dynamic_context 抄来的 chapter_focus
       brief["override_allowed"]["chapter_focus"] = directive["goal"]
    
  3. 修改写章 skill (webnovel-writer/skills/webnovel-write/SKILL.md) 中 prompt 模板,把 chapter_directive 摆在 dynamic_context 之前,并加显式约束语:

    ## 本章硬性约束(优先级最高)
    - 时间锚点:{directive.time_anchor}
    - 倒计时状态:{directive.countdown}
    - 必须覆盖节点:{directive.must_cover_nodes}
    - 章末必须留下:{directive.chapter_end_open_question}
    - 本章禁区(违反即不通过):{directive.forbidden_zones}
    

验收标准

def test_chapter_brief_contains_outline_directive(tmp_path):
    # 准备测试用大纲文件,写一章节块
    outline = tmp_path / "大纲" / "第1卷-详细大纲.md"
    outline.parent.mkdir(parents=True)
    outline.write_text(SAMPLE_OUTLINE_CHAPTER_1, encoding="utf-8")
    # 跑 brief 构建
    brief = build_chapter_brief(tmp_path, chapter=1)
    # 关键字段必须存在
    assert brief["chapter_directive"]["time_anchor"] == "D-Day 清晨"
    assert "杂役不能随意离开宗门" in brief["chapter_directive"]["forbidden_zones"][0]
    assert len(brief["chapter_directive"]["cpns"]) >= 1
    # chapter_focus 来自大纲目标,不是 SP 卡 summary
    assert "搞清楚" in brief["override_allowed"]["chapter_focus"]

工作量:中(约 4-6 小时含测试)


Issue #2 — 写章 prompt 中 dynamic_context 占主位(P0)

现状证据

webnovel-writer/skills/webnovel-write/SKILL.md 现在已经声明 chapter_focus 仅为 CSV 参考、本章目标以章纲为准,并要求有结构化节点时围绕 CBN→CPNs→CEN 展开。但任务书生成仍没有一个稳定的 chapter_directive 展示模板,dynamic_context 这类 reference 补充容易在实际任务书里比大纲执行约束更显眼。

影响:与 #1 联动 —— 即使 brief 里加了 directive,如果 context-agent / 写章任务书没有明确排序,LLM 仍可能把 generic reference 当成主要写作方向。

修复方案

修改 webnovel-writer/skills/webnovel-write/SKILL.md prompt 段落顺序:

  1. 标题:# 第N章 写作任务
  2. 本章硬性约束(来自 chapter_directive,新增)—— 列点形式
  3. 本章必须覆盖节点(CBN/CPNs/CEN)—— 编号列表
  4. 本章禁区(禁区项目)—— 显眼提示
  5. 风格指引(来自 reasoning + 主角卡 OOC 警戒,从 master setting 抽取)
  6. 场景写法补充(dynamic_context,仅作风格参考,明确标注非强制)

验收标准

人工 review 一次写章 prompt,确认 directive > dynamic_context 顺序与权重。

工作量:小(约 1 小时,纯改 markdown 模板 + 1 个 fixture 测试)


Issue #3 — reference 检索对真实章目标感知不足(P1)

现状证据

第 1 章 brief 注入:

  • SP-087 论道场景 → 第 1 章是杂役通铺醒来,无关
  • NR-077 丹药法宝命名 → 第 1 章无丹药法宝
  • SY-009 宗门经营 → 第 1 章不涉及组织经营
  • CH-072 宗门天骄 → 第 1 章无天骄出场

当前产物表现为:被注入的卡片高度偏向“仙侠通用写法”,与第 1-2 章的借贷调查场景不对位。

影响:每章 brief 中无关知识占位,挤走真正有用的卡片(金融/经济类 reference 完全没出现),同时 chapter_focus 还会被这些误召回卡片继续放大偏差。

根因

这是两个问题叠加:

  1. 上游章目标注入不实:现有 webnovel-plan / webnovel-write skill 文档把 story-system 调用写成 story-system "{章纲目标}" ...。灵石庄实际落盘的 MASTER_SETTING.json 里,query 甚至是字面量 "第2章章纲目标",说明运行链路并未稳定传入真实章目标;webnovel-review 则在后续审查中暴露这种弱 query 带来的偏差。
  2. 下游检索仍偏题材路由webnovel-writer/scripts/data_modules/story_system_engine.py 虽然会把 query 传给 reference_search.py,但检索入口先由题材路由决定推荐表,再做简化 BM25。上游 query 一旦是占位或泛词,结果就会退化为“题材对了、场景不对”。

所以根因不是“只按 genre_filter 检索”,而是真实章目标没有稳定进入 query,且现有排序对章节语义的放大不够

修复方案

  1. 先修上游 query 真值:
    • webnovel-writer/skills/webnovel-plan/SKILL.mdwebnovel-writer/skills/webnovel-write/SKILL.md 中,把 story-system "{章纲目标}" ... 从占位提示升级为必须先解析真实章目标再调用
    • webnovel-writer/skills/webnovel-review/SKILL.md 的审查输入说明里强调需对照真实 chapter_brief.meta.query / 大纲目标,避免把 placeholder query 产物当正常 brief
    • webnovel-writer/scripts/story_system.py 入口附近补一层保护:若 query 仍是 {章纲目标}第N章章纲目标 这类占位文本,给出诊断或降级警告
  2. 在 reference 检索阶段加入 chapter-aware 信号:
    • 输入:chapter_directive.goalchapter_directive.key_entitieschapter_directive.strandchapter_directive.antagonist_tier
    • 提取关键词(jieba 分词或简单 split)
    • 与 reference 卡片的 关键词 / 意图与同义词 / 适用场景 字段做 token overlap 评分
  3. webnovel-writer/scripts/data_modules/story_system_engine.py_collect_tables / _expand_query 周边补排序信号:
    • 最终排序:(genre_match_score * 0.4) + (chapter_keyword_score * 0.6)
    • priority_rank 的卡片优先取章节关键词命中数高的
  4. 保留 reference_search.py 的通用 BM25 能力,不另造不存在的 genre_router.py

验收标准

def test_story_system_reference_matching_prefers_chapter_keywords():
    directive = {"goal": "看穿借据条款的荒谬", "key_entities": ["借据", "利息", "复利"]}
    result = StorySystemEngine(csv_dir=fixture_csv_dir).build(
        query="看穿借据条款的荒谬 借据 利息 复利",
        genre="仙侠",
        chapter=1,
    )
    selected = result["chapter_brief"]["dynamic_context"]
    # 借贷类卡片应排在论道类前面
    assert selected[0]["编号"] == "FIN-001"

工作量:中(约 4 小时,含一份金融场景 reference 卡片样例)


Issue #4 — 设定模板空壳 + 单主角误生成主角组(P1)

现状证据

灵石庄设定集:

  • 金手指设计.md:全是 {占位} 和空字段(信息已在主角卡 §金手指 + 总纲 §创意约束)
  • 复合题材-融合逻辑.md:全空(信息已在世界观 §融合逻辑 + 总纲 §复合题材融合)
  • 主角组.md:全空,state.protagonist_structure="单主角+辅助视角"
  • 爽点规划.md:只有 1-5/6-10 两条示例(卷纲 §爽点密度规划已有更详细的)
  • 角色库/{次要角色,反派角色,主要角色}/物品库/其他设定/:全是空目录

影响

  • 用户 init 后看一堆空文件不知是该填还是不该填
  • brief 里的 master setting 提取器若无脑读所有设定文件会误解空字段为"未确定"
  • 项目目录显得冗余

根因

webnovel-writer/skills/webnovel-init/SKILL.md 里硬编码生成所有模板,没按 idea_bankprotagonist_structure / heroine_config / 复合题材标志 做分支。

修复方案

  1. 删除以下"已被其他文件覆盖"的模板(不再生成):
    • 金手指设计.md —— 信息在主角卡 §金手指
    • 复合题材-融合逻辑.md —— 信息在世界观 §复合题材融合(且仅复合题材项目才有意义)
    • 爽点规划.md —— 卷纲爽点表是单一事实源
  2. 按项目配置条件生成
    • 主角组.md:仅当 protagonist_structure ∈ {"主角组", "双主角", "多主角"} 时生成
    • 女主卡.md:仅当 heroine_config != "无女主" 时生成
    • 反派设计.md 始终生成(必填)
    • 复合题材-融合逻辑.md(如果保留):仅当 genre_combination 标记非空时生成
  3. 空目录改为按需创建
    • 角色库/ 在第一次 apply_entity_delta 写新角色时创建(按 entity_type/tier 分子目录)
    • 物品库/其他设定/ 同理

修改文件:webnovel-writer/scripts/init_project.pywebnovel-writer/templates/output/ 中仍需保留的模板,以及 webnovel-writer/skills/webnovel-init/SKILL.md 生成步骤段。

验收标准

def test_init_skips_protagonist_group_for_single_protagonist(tmp_path):
    init_project(tmp_path, idea_bank={"protagonist_structure": "单主角+辅助视角", ...})
    assert not (tmp_path / "设定集" / "主角组.md").exists()
    assert (tmp_path / "设定集" / "主角卡.md").exists()

def test_init_skips_dead_template_files(tmp_path):
    init_project(tmp_path, idea_bank={...})
    for dead in ("金手指设计.md", "爽点规划.md"):
        assert not (tmp_path / "设定集" / dead).exists()

工作量:小(约 2 小时)


Issue #5 — chapter_focus 字段被错塞无关 summary(P1)

现状证据

chapter_002.json:override_allowed.chapter_focus = "文斗场面的张力来自观点击中修行根基。"

这是从 dynamic_context 里 SP-087 卡的 核心摘要 直抄。但第 2 章是"井边对话收集借贷情报",不是文斗场景。

影响:当 LLM 把 chapter_focus 当本章核心方向时,会被误导走偏。

根因

webnovel-writer/scripts/data_modules/story_system_engine.py_suggest_chapter_focus() 在没有显式大纲目标时,直接返回第一条 dynamic_context核心摘要。由于 reasoning 排序会把 SP-087 这类泛题材卡排到前面,chapter_focus 就被无关 summary 污染;这不是 runtime_contract_builder.py 的行为。

修复方案

#1 联动 —— 一旦 chapter_directive 接入,chapter_focus 取自 directive.goal没有 directive 时取 query(前提是非占位)或留空,绝不从 dynamic_context 抄

验收标准

test_chapter_brief_contains_outline_directive 已涵盖(chapter_focus 来自大纲目标)。补充:

def test_chapter_focus_never_taken_from_dynamic_context_summary(tmp_path):
    brief = build_chapter_brief(tmp_path, chapter=99)  # 大纲没有第 99 章
    # 没大纲时 chapter_focus 应为空字符串或 None,而非 SP 卡 summary
    assert brief["override_allowed"].get("chapter_focus", "") == ""

工作量:小(约 30 分钟,包含在 #1 改动里)


Issue #6 — 设定文件缺少占位/漂移防护(P2)

现状证据

  • 卷纲 第1卷-卷纲.md:25 行:第一位女主(暂名) | 女主 | [待章纲拆分时具体设计] | 第20章起
  • 主角卡:苏云——第一位女主...
  • 女主卡:完整 苏云 人物卡

主角卡 / 女主卡是 plan 阶段的某次产出后填好的,卷纲表格停留在较早版本,没有后续一致性检查

主角卡里还有:

  • [待补充]:能打但脑子不够用的兄弟型角色(后续卷引入)

未来发现这个角色没填,到写到该卷时才暴露。

影响:plan 阶段产出文件之间数据漂移;写章前没提醒导致用户没意识到未填项。

根因

这不是某条“自动回填流水线失效”,而是当前系统只有“增量写回现有设定集”的流程承诺,没有占位扫描、跨文件一致性检查、也没有写章前的 pending blockerwebnovel-plan/SKILL.md 明确要求“卷纲完成后,把新增设定增量写回现有设定集”,但仓库里没有对应的 placeholder guardrail。

修复方案

  1. 新增 webnovel-writer/scripts/data_modules/placeholder_scanner.py

    • 扫描 大纲/*.md设定集/*.md 中匹配以下模式的占位:
      • \[待[^\]]*\](如 [待补充][待章纲拆分时具体设计][待定]
      • (暂名)(暂名)(待补充)
      • 全字段 {占位}<占位>
    • 输出每条占位的 {file, line, context, suggested_fill_phase}
  2. 在以下流程加阻断检查:

    • webnovel-plan 开始或结束时:扫所有 plan 文件,警告但不阻断
    • webnovel-write 开始时:扫当前章节涉及的实体对应的设定文件,[待...] 阻断写章
  3. 加一个 CLI 命令 webnovel.py placeholder-scan --project-root <dir> 让用户主动检查

验收标准

def test_placeholder_scanner_finds_pending_marks(tmp_path):
    f = tmp_path / "大纲" / "第1卷-卷纲.md"
    f.write_text("第一位女主(暂名)| [待章纲拆分时具体设计]")
    results = scan_placeholders(tmp_path)
    assert len(results) == 2
    assert any("(暂名)" in r["pattern"] for r in results)
    assert any("[待章纲" in r["pattern"] for r in results)

def test_write_chapter_blocks_on_pending_protagonist_card(tmp_path):
    # 主角卡有 [待补充] 但本章没涉及该实体 → 不阻断
    # 主角卡有 [待补充] 且本章涉及该实体 → 阻断
    ...

工作量:小(约 2 小时)


Issue #7 — 当前卷完成后的最小跨卷锚点缺失(P2)

现状证据

大纲/总纲.md:46-62

| 卷号 | 卷名 | 章节范围 | 核心冲突 | 卷末高潮 |
| 1 | 阎王债 | 第1-50章 | <已填> | <已填> |
| 2 | | 第51-100章 | | |
...
| 20 | | 第951-1000章 | | |

伏笔表只有 2 条核心伏笔 + 1 行空。

影响:plan 阶段的 LLM 没有跨卷视野,每次开新卷都从头规划,可能跟前期承诺/伏笔脱节。

修复方案

  1. 不在 init 时就生成 V2-V20 行(避免一堆空表格视觉污染)
  2. 把 Phase 7 定义为当前卷规划成功后的最小跨卷锚点写回,不是自动详细规划下一卷:
    • 触发条件:webnovel-plan 已成功完成当前卷规划,且当前卷规划 artifacts 已完整/通过验证
    • 写回内容:只填 V+1 的 卷名核心冲突卷末高潮
    • 写入位置:大纲/总纲.md 的下一卷行;若不存在则追加一行
  3. 伏笔表加受限追加:每次 webnovel-plan 完成一卷规划时,只追加当前规划输出中显式结构化产出的:
    • 新伏笔
    • 继续开放的 open loops
  4. 明确禁止:
    • 不从自由文本推断伏笔或开放环
    • 不生成下一卷详细大纲
    • 不生成下一卷 beat sheet
    • 不生成下一卷 timeline
    • 不预生成 V2-V20 空行

这一路径最贴合写作流:当前卷规划完成时,系统只把必要的跨卷承诺固化到总纲,保留下一卷正式规划时的创作空间。

验收标准

def test_plan_flow_writes_minimal_next_volume_anchor(tmp_path):
    plan_volume(tmp_path, volume=1)  # 这里代表 webnovel-plan 完成 V1 规划
    # 完成后总纲应自动多了 V2 最小锚点(V3-V20 不存在/不预填)
    summary = (tmp_path / "大纲" / "总纲.md").read_text(encoding="utf-8")
    assert "| 2 |" in summary
    v2_line = [l for l in summary.split("\n") if l.startswith("| 2 |")][0]
    assert v2_line.count("|") == 6
    assert "第2卷-详细大纲.md" not in list_outline_files(tmp_path)
    assert "第2卷-beat-sheet.md" not in list_outline_files(tmp_path)
    assert "第2卷-timeline.md" not in list_outline_files(tmp_path)

def test_plan_flow_appends_only_structured_foreshadow_items(tmp_path):
    plan_volume(tmp_path, volume=1, structured_foreshadow=[
        {"type": "new_foreshadowing", "text": "债契背面的红印仍未解释"},
        {"type": "continued_open_loop", "text": "苏云身份与阎王债源头仍未闭合"},
    ])
    summary = (tmp_path / "大纲" / "总纲.md").read_text(encoding="utf-8")
    assert "债契背面的红印仍未解释" in summary
    assert "苏云身份与阎王债源头仍未闭合" in summary

工作量:中(约 4 小时,包含 webnovel-plan prompt / 流程改动)


Issue #8 — 审查发现的 ai_flavor 模式不回流 anti_patterns(P2)

现状证据

灵石庄/.story-system/anti_patterns.json:10 条全部来自 reference 库初始化(GR-011 / NR-077 / CH-072 / SP-087 / RS-002 的"毒点"字段)。

第 2 章审查报告 抓到的 ai_flavor:

  • "唯一一个知道 X 的人" 三连排比
  • "第一片 / 第二片 / 第三片" 三段式枚举

这些模式没进入 anti_patterns.json。后续章节没有"上一章已经被点出的句式不要重复"的避雷清单。

修复方案

  1. webnovel-writer/scripts/data_modules/review_schema.py(或对应处)在持久化审查报告后,新增 hook:

    if issue["category"] == "ai_flavor" and issue["severity"] in ("medium", "high", "critical"):
       anti_patterns_store.append({
           "text": issue["evidence"][:200],
           "source_table": "review_extracted",
           "source_id": f"ch{chapter:04d}_issue_{issue_idx}",
           "category": issue["category"],
           "added_at": now_iso(),
       })
    
  2. 写章前 brief 注入 anti_patterns 的 text 列表作为"避雷模式"

  3. 加去重机制(同一 evidence 不重复入库)

验收标准

def test_ai_flavor_review_issue_added_to_anti_patterns(tmp_path):
    save_review_report(tmp_path, chapter=2, issues=[{
        "category": "ai_flavor",
        "severity": "medium",
        "evidence": "唯一一个知道复利公式的人。唯一一个知道..."
    }])
    patterns = json.loads((tmp_path / ".story-system" / "anti_patterns.json").read_text())
    assert any("唯一一个知道" in p["text"] for p in patterns)
    assert any(p["source_id"].startswith("ch0002_") for p in patterns)

工作量:小(约 2 小时)


5. 整体回归方案

完成后必须通过:

  1. pytest --no-cov 全过(当前 508 个用例)
  2. 灵石庄目录用 init 流程重跑(新建 灵石庄2)—— 验证:
    • 不生成 主角组.md金手指设计.md复合题材-融合逻辑.md爽点规划.md
    • 不生成空目录
  3. 第 1 章重写一次,对比 brief:
    • 必须包含 chapter_directive 字段
    • dynamic_context 至少有 1 条与"借贷/借据/利息"相关
    • chapter_focus 来自大纲第 1 章的 goal
  4. 写完第 1 章后审查 → 故意制造 ai_flavor 句式 → 确认 anti_patterns.json 有新增
  5. 完成并验证第 1 卷规划后 → 总纲只应写回 V2 的卷名 / 核心冲突 / 卷末高潮;不得生成 V2 详细大纲、beat sheet、timeline,也不得预填 V3-V20 空行
  6. 当前卷规划输出含显式结构化的新伏笔 / 持续开放环时 → 总纲伏笔表追加这些结构化条目;自由文本里的暗示不得被推断追加
  7. 主角卡保留 [待补充] 然后写第 50 章(涉及该角色) → 应阻断

6. 风险与已知限制

  • #1 的 outline 解析依赖 markdown 章节块格式稳定。若 plan agent 输出格式变体(如标题用 第1章 vs 第一章),需要兼容。建议在 outline_extractor 用 regex 匹配 第\s*\d+\s*章 全部变体。
  • #6 的占位扫描可能误报真实文本里的方括号(如"[原文]")。建议白名单 + 模式严格化(必须含中文"待"字才算占位)。
  • #7 的 V+1 写回必须保持最小锚点边界。建议只接受当前规划产物中结构化输出的卷名 / 核心冲突 / 卷末高潮,不从自由文本扩写,也不生成下一卷详细规划;真正的下一卷详细大纲、beat sheet、timeline 留到用户进入下一卷规划时再产出。
  • 与已修的 protagonist_state 镜像(f58d657)可能在 #1 启用 directive 后有交互:directive 里有 time_anchor,state.protagonist_state 里有 location.last_chapter,两者不应被混写。需在 #1 实现时显式分离命名空间。

7. 不在本 spec 范围

类型 例子 责任
LLM 输出本身的写作瑕疵 AI 味、习惯动作过密、句式重复 审查 agent 已抓
init 时用户未填的项目元数据 heroine_names 空 用户责任,UI 应提示
模型选型 / 上下文窗口大小 一次生成不下大章 → 增量更新 模型/平台层
数据 agent 调用慢(NOT_FOUND 重试) data_agent_timing.jsonl 已在 f58d657 修主因

8. 推荐实施顺序(可直接开工)

  1. chapter_outline_loader.py 先实现 load_chapter_execution_directive(),用最小 fixture 覆盖详细大纲第 1 章字段提取。
  2. chapter_directive 接入 story_system.py / story_system_engine.py 的 brief 组装,并让 chapter_focus 优先取 directive.goal
  3. 更新 webnovel-write/SKILL.md,把“本章硬性约束 / CBN-CPNs-CEN / 本章禁区”排到 dynamic_context 前。
  4. story_system.py 增加 placeholder query 诊断,并同步更新 webnovel-planwebnovel-writewebnovel-review 三个 skill 的真实 query 要求。
  5. story_system_engine.py 增加 chapter-aware reference ranking,用 goal/key_entities/strand/antagonist_tier 影响排序。
  6. 修改 init_project.pywebnovel-init/SKILL.md 的模板生成清单,先完成单主角 / 无女主 / 死模板剪枝测试。
  7. 新增 placeholder_scanner.pywebnovel.py placeholder-scan,先实现全项目扫描,再接入 plan 警告。
  8. 将 placeholder guardrail 接入 write 前检查,使用当前章节相关实体缩小阻断范围。
  9. 修改总纲模板与 webnovel-plan/SKILL.md,移除 V2-V20 预生成空行;在当前卷规划完成并验证后,只写回 V+1 的卷名 / 核心冲突 / 卷末高潮,并仅追加当前规划输出显式结构化给出的新伏笔 / 持续开放环。
  10. review_schema.py 增加 ai_flavoranti_patterns.json 回流与去重,再验证下一章 brief 能读到 review-extracted 避雷模式。