2026-04-15-chain-integrity-fixes.md 15 KB

Init→Plan→Write 链路完整性修复计划

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 修复 init→plan→write→plan→write 全链路中 7 个使用者视角的断裂问题,确保题材从 init 到裁决层的流通、story-system 在正确时机触发、plan 跨卷时能感知已写内容。

Architecture: 围绕"题材是 init 写入 state.json 的唯一真源"这个核心决策,从数据层(CSV 别名补齐)到流程层(SKILL.md 规范化)逐步修复。

Tech Stack: CSV (UTF-8 BOM), Python CLI, Markdown prompt files

已确认决策:

  1. 题材在 init 阶段写入 state.json,后续所有环节从此读取,不再允许 free-text query 决定路由
  2. CSV 检索结果是创作参考,大纲/章纲是最高权重(仅次于用户意见)
  3. chapter_brief.json 只承载裁决/路由元数据,不伪装成章级内容合同
  4. rejected chapter 不阻断下一章,交由用户决断
  5. plan 写卷依据:大纲 + 用户意见 + 已有剧情状态 + CSV 检索

Task 1: 裁决表补齐 init 题材别名

问题: init 支持的题材名(修仙、系统流、高武、西幻等)与裁决表的题材名(东方仙侠、西方奇幻等)不匹配,导致裁决层对部分题材不生效。

Files:

  • Modify: webnovel-writer/references/csv/裁决规则.csv
  • Modify: webnovel-writer/references/csv/题材与调性推理.csv

  • [ ] Step 1: 建立 init 题材 → 裁决题材的映射

init SKILL.md 第 110-113 行列出的题材集合:

玄幻修仙类:修仙 | 系统流 | 高武 | 西幻 | 无限流 | 末世 | 科幻
都市现代类:都市异能 | 都市日常 | 都市脑洞 | 现实题材 | 黑暗题材 | 电竞 | 直播文
言情类:古言 | 宫斗宅斗 | 青春甜宠 | 豪门总裁 | ...
特殊题材:规则怪谈 | 悬疑脑洞 | 悬疑灵异 | 历史古代 | 历史脑洞 | ...

映射到现有 7 个裁决题材:

init 题材名 → 裁决题材
修仙、仙侠 东方仙侠
西幻、西方奇幻 西方奇幻
末世、科幻 科幻末世
都市异能、都市脑洞、现实题材 都市日常
都市修真、现代修真 都市修真
高武、都市异能(高武向) 都市高武
历史古代、历史脑洞 历史古代
系统流、无限流 东方仙侠(fallback,多为修仙变体)

不在映射中的(言情类、规则怪谈、悬疑等)暂无裁决行,走空裁决 fallback。

  • Step 2: 在裁决规则.csv 的关键词列补齐别名

每行的「关键词」列追加 init 题材名:

裁决题材 当前关键词 追加
西方奇幻 西方奇幻\|奇幻 \|西幻
东方仙侠 东方仙侠\|仙侠 \|修仙\|系统流\|无限流
科幻末世 科幻末世\|末世\|科幻 (已覆盖)
都市日常 都市日常\|都市 \|都市脑洞\|现实题材\|黑暗题材
都市修真 都市修真\|修真\|现代修真 (已覆盖)
都市高武 都市高武\|高武\|都市异能 (已覆盖)
历史古代 历史古代\|历史\|古代 \|历史脑洞

编辑 CSV 文件,在对应行的「关键词」列追加。

  • Step 3: 同步更新路由表的别名覆盖

路由表 题材与调性推理.csv 当前 8 行是流派(退婚流、规则怪谈等),不是题材大类。不改现有行

但路由表目前缺少"东方仙侠""西方奇幻""科幻末世"等大类的路由行。当用户 genre 是"修仙"时,路由表的 _route() 可能匹配不上任何行(因为现有行都是具体流派)。

需要确认:路由表是否需要补"大类"行?检查 _route() 的 fallback 逻辑——如果匹配不上,用 --genre 参数做 explicit_genre_fallback,走默认推荐表。这个 fallback 够用。

结论:路由表不改。裁决表补别名即可。路由匹配不上时 fallback 到默认推荐表 + 裁决表仍能通过 _load_reasoning(genre) 匹配到。

  • [ ] Step 4: 运行测试确认

    cd "D:\wk\novel skill\webnovel-writer\webnovel-writer" && python -m pytest scripts/data_modules/tests/test_csv_config.py scripts/data_modules/tests/test_reasoning_engine.py -v --tb=short
    
  • [ ] Step 5: Commit

    cd "D:\wk\novel skill\webnovel-writer" && git add webnovel-writer/references/csv/裁决规则.csv && git commit -m "fix: expand reasoning table genre aliases to cover init genre names"
    

Task 2: 规范 story-system 的 genre 输入为 state.json 唯一真源

问题: write SKILL.md 调 story-system "{chapter_goal}"{chapter_goal} 来源不明,导致路由质量不可控。

决策: genre 从 state.json 读取,作为唯一真源。query 参数改为章纲目标(用于 CSV 检索),genre 参数固定从 state.json 取。

Files:

  • Modify: webnovel-writer/skills/webnovel-write/SKILL.md
  • Modify: webnovel-writer/skills/webnovel-plan/SKILL.md

  • [ ] Step 1: 修改 write SKILL.md 准备阶段的 story-system 调用

当前(第 141-143 行):

python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${WORKSPACE_ROOT}" \
  story-system "{chapter_goal}" --chapter {chapter_num} --persist --emit-runtime-contracts --format both

改为:

# 从 state.json 读取题材(唯一真源)
GENRE="$(python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" state get-field --field project.genre)"

# 从章纲提取本章目标作为检索 query(若无章纲则用题材兜底)
CHAPTER_GOAL="{从章纲提取的本章目标,如'韩立进入坊市试探消息真伪'}"

python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${WORKSPACE_ROOT}" \
  story-system "${CHAPTER_GOAL}" --genre "${GENRE}" --chapter {chapter_num} --persist --emit-runtime-contracts --format both

在这段命令前加说明:

**genre 参数规范**:
- `--genre` 必须从 `state.json` 的 `project.genre` 读取,不得手动填写
- 第一个位置参数(query)填本章章纲的"目标"字段内容,用于 CSV 知识检索
- 若章纲无明确目标,fallback 到 `"{题材} 第{chapter_num}章"`
  • Step 2: 修改 plan SKILL.md 的 story-system 调用

当前(第 59-61 行):

python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${WORKSPACE_ROOT}" \
  story-system "{chapter_goal}" --chapter {chapter_num} --persist --emit-runtime-contracts --format both

同样改为带 --genre 的规范形式。

  • [ ] Step 3: 确认 state get-field CLI 命令存在

    grep -n "get-field" webnovel-writer/scripts/data_modules/webnovel.py
    

如果不存在,需要补一个简单的 state 子命令来读取任意 JSON path。或者用 jq/python 一行脚本替代:

GENRE="$(python -X utf8 -c "import json; s=json.load(open('${PROJECT_ROOT}/.webnovel/state.json')); print(s.get('project',{}).get('genre',''))")"
  • [ ] Step 4: Commit

    git add webnovel-writer/skills/webnovel-write/SKILL.md webnovel-writer/skills/webnovel-plan/SKILL.md
    git commit -m "fix: standardize story-system genre input from state.json as sole source"
    

Task 3: init 完成后触发 story-system 生成 MASTER_SETTING

问题: init 完成后 .story-system/ 目录不存在,plan 的卷级规划阶段缺少调性/禁忌参照。

Files:

  • Modify: webnovel-writer/skills/webnovel-init/SKILL.md

  • [ ] Step 1: 在 init SKILL.md 的"执行生成"段落末尾追加 story-system 触发

在"3) Patch 总纲"之后、"验证与交付"之前,新增:

### 4) 生成写前合同树(Story System 初始化)

init 完成后,立即生成 MASTER_SETTING,让后续 plan 有调性/禁忌参照:

bash GENRE="$(python -X utf8 -c "import json; s=json.load(open('{project_root}/.webnovel/state.json')); print(s.get('project',{}).get('genre',''))")"

python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "{project_root}" \ story-system "${GENRE}" --genre "${GENRE}" --persist --format json


说明:
- 此时不传 `--chapter`,只生成 `MASTER_SETTING.json` 和 `anti_patterns.json`
- 不传 `--emit-runtime-contracts`(还没有卷/章级数据)
- plan 阶段拆到具体章节时再生成 volume/chapter/review 合同
  • Step 2: 更新 init 的验证与交付段落

在验证检查中新增:

test -f "{project_root}/.story-system/MASTER_SETTING.json"

在成功标准中新增:

  • .story-system/MASTER_SETTING.json 存在且 route.primary_genre 非空

  • [ ] Step 3: Commit

    git add webnovel-writer/skills/webnovel-init/SKILL.md
    git commit -m "feat: init triggers story-system to generate MASTER_SETTING after project creation"
    

Task 4: 明确 chapter_brief 只承载裁决元数据

问题: chapter_brief.jsonchapter_focus 是从 CSV 检索结果凑的,跟实际章纲无关,误导 context-agent。

决策: chapter_brief 只承载裁决/路由元数据。章纲是最高权重(仅次于用户意见),由 context-agent 直接读取。

Files:

  • Modify: webnovel-writer/agents/context-agent.md
  • Modify: webnovel-writer/skills/webnovel-write/SKILL.md

  • [ ] Step 1: 在 context-agent.md 的"Story System 主链"段落明确权重

在写前真源列表后新增权重说明:

**数据权重(高→低)**:
1. 用户明确要求
2. 大纲/章纲原文(`大纲/第X卷-详细大纲.md` 中的本章内容)
3. Story Contracts 中的 `MASTER_SETTING`(题材、调性、核心禁忌)
4. `chapter_{NNN}.json` 的 `reasoning` 字段(裁决层的风格/节奏/毒点建议)
5. accepted `CHAPTER_COMMIT`(写后事实)
6. CSV 检索结果(创作参考,不覆盖大纲)

`chapter_{NNN}.json` 的 `chapter_focus` 字段仅为 CSV 检索派生的参考,不代表本章实际目标。本章目标以章纲原文为准。
  • Step 2: 在 write SKILL.md 的"合同树必备文件"段落补充说明

在第 146-148 行的合同树说明后加:

**注意**:`.story-system/chapters/chapter_{NNN}.json` 的 `chapter_focus` 是 CSV 检索派生的参考建议,不是本章的实际目标。本章目标以 `大纲/第X卷-详细大纲.md` 中的章纲原文为最高权重。`chapter_{NNN}.json` 的核心价值是 `reasoning` 字段中的裁决元数据(风格优先级、节奏策略、反模式)。
  • [ ] Step 3: Commit

    git add webnovel-writer/agents/context-agent.md webnovel-writer/skills/webnovel-write/SKILL.md
    git commit -m "docs: clarify chapter_brief carries reasoning metadata, outline is authority"
    

Task 5: plan 跨卷时读取已写内容

问题: plan 第2卷时不读 commit 历史、summaries、实体状态,导致章纲可能与已写内容矛盾。

决策: plan 步骤 1 加载大纲 + 用户意见 + 已有剧情状态 + CSV 检索。

Files:

  • Modify: webnovel-writer/skills/webnovel-plan/SKILL.md

  • [ ] Step 1: 扩展 plan Step 1 的数据加载

当前 Step 1(第 97-113 行)只读 state.json 和总纲。改为:

### Step 1:加载项目数据并确认前置条件

**必须加载**:

bash

项目状态与题材

cat "$PROJECT_ROOT/.webnovel/state.json"

总纲(全局蓝图)

cat "$PROJECT_ROOT/大纲/总纲.md"


**已有卷的剧情状态**(跨卷规划时必须加载):

若已有已完成卷(`.webnovel/summaries/` 下有文件),加载以下数据感知已写内容:

bash

最近 5 章摘要(了解剧情走向)

for ch in $(seq $((START_CH - 5)) $((START_CH - 1))); do cat "$PROJECT_ROOT/.webnovel/summaries/ch$(printf '%04d' $ch).md" 2>/dev/null done

核心角色当前状态(知道主角走到哪了)

python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" \ knowledge query-entity-state --entity "{protagonist_id}" --at-chapter {上一卷最后章}

核心关系当前状态

python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" \ knowledge query-relationships --entity "{protagonist_id}" --at-chapter {上一卷最后章}

活跃伏笔(跨卷未回收的伏笔)

python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" \ memory-contract get-open-loops


**CSV 创作参考**(卷级规划时按需检索):

bash

本卷题材相关的节奏和桥段参考

python -X utf8 "${SCRIPTS_DIR}/reference_search.py" --skill plan --table 爽点与节奏 --query "{卷级核心冲突}" --genre "${GENRE}" python -X utf8 "${SCRIPTS_DIR}/reference_search.py" --skill plan --table 桥段套路 --query "{卷级核心冲突}" --genre "${GENRE}"

按需读取(保持不变):

  • 设定集/世界观.md
  • 设定集/力量体系.md
  • 设定集/主角卡.md
  • 设定集/反派设计.md
  • .webnovel/idea_bank.json

  • [ ] Step 2: 在 plan Step 6(卷纲骨架)加入已写状态参照

在 Step 6 的"卷纲必须明确"列表后新增:

跨卷一致性检查:
- 上一卷未回收的伏笔必须出现在新卷的伏笔规划中(继续推进或标记回收)
- 角色关系变化必须延续(不能当上一卷没发生过)
- 主角能力/境界必须承接(不能回退也不能跳级,除非有剧情解释)
  • [ ] Step 3: Commit

    git add webnovel-writer/skills/webnovel-plan/SKILL.md
    git commit -m "feat: plan reads write history for cross-volume awareness"
    

Task 6: 运行测试 + 最终验证

Files: (read-only verification)

  • [ ] Step 1: 运行全量 prompt integrity 测试

    cd "D:\wk\novel skill\webnovel-writer\webnovel-writer" && python -m pytest scripts/data_modules/tests/test_prompt_integrity.py -v --tb=short
    
  • [ ] Step 2: 运行 CSV 配置对齐测试

    cd "D:\wk\novel skill\webnovel-writer\webnovel-writer" && python -m pytest scripts/data_modules/tests/test_csv_config.py -v --tb=short
    
  • [ ] Step 3: 验证裁决表别名覆盖

    python3 -c "
    import csv
    from pathlib import Path
    path = Path('webnovel-writer/references/csv/裁决规则.csv')
    with open(path, 'r', encoding='utf-8-sig') as f:
    for row in csv.DictReader(f):
        genre = row['题材']
        keywords = row['关键词']
        print(f'{genre}: {keywords}')
    "
    

确认"修仙""西幻""系统流""都市脑洞""历史脑洞"等 init 题材名都出现在对应行的关键词中。

  • Step 4: 手动走一遍 init→write 关键路径

模拟检查:

  1. init 设 genre="修仙" → state.json.project.genre="修仙"
  2. init 触发 story-system "修仙" --genre "修仙" → MASTER_SETTING.json 生成
  3. write 读 state.json 的 genre → 传 --genre "修仙" 给 story-system
  4. story-system _route() 匹配"修仙" → 路由表 fallback(无精确匹配,但有 explicit_genre_fallback)
  5. story-system _load_reasoning("修仙") → 裁决表匹配到"东方仙侠"行(通过别名"修仙")
  6. 裁决层生效 → chapter_brief.reasoning 有值

确认这条路径不断裂。

  • [ ] Step 5: Commit final

    git add -A && git commit -m "chore: final verification for chain integrity fixes"