فهرست منبع

docs: prune v6-era docs on v7 branch

v6 remains fully intact on master; the v7 branch keeps only the v7
specs, inherited assets (genre templates guide), release process, and
research that informed the v7 design. Removed: docs/archive/, v6
architecture plans/audits, v6 command/RAG guides, memory architecture,
v6 operations docs, superpowers plans/specs. docs/README.md rewritten
as the v7 index.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lingfengQAQ 1 هفته پیش
والد
کامیت
b6f26ba31f
48فایلهای تغییر یافته به همراه16 افزوده شده و 28776 حذف شده
  1. 16 32
      docs/README.md
  2. 0 1320
      docs/architecture/author-friendly-reporting-plan-2026-06-07.md
  3. 0 1132
      docs/architecture/context-minimal-writing-flow-plan-2026-06-05.md
  4. 0 109
      docs/architecture/overview.md
  5. 0 162
      docs/architecture/phase0-slimming-and-read-audit-2026-06-06.md
  6. 0 228
      docs/architecture/phase0-tool-and-registration-audit-2026-06-06.md
  7. 0 1147
      docs/architecture/plugin-runtime-hardening-plan-2026-06-04.md
  8. 0 1388
      docs/architecture/plugin-runtime-hardening-spec-2026-06-04.md
  9. 0 107
      docs/archive/architecture/current-system-diagnosis.md
  10. 0 356
      docs/archive/architecture/dashboard-demo.html
  11. 0 977
      docs/archive/architecture/dashboard-prototype.html
  12. 0 140
      docs/archive/architecture/narrative-intelligence-roadmap-2026-06-10.md
  13. 0 72
      docs/archive/architecture/story-system-phase4.md
  14. 0 46
      docs/archive/architecture/story-system-phase5.md
  15. 0 264
      docs/archive/architecture/system-architecture-diagram.html
  16. 0 430
      docs/archive/architecture/system-architecture.md
  17. 0 28
      docs/archive/superpowers/README.md
  18. 0 875
      docs/archive/superpowers/plans/2026-04-03-phase1-cleanup.md
  19. 0 605
      docs/archive/superpowers/plans/2026-04-09-v6-migration-completion.md
  20. 0 1324
      docs/archive/superpowers/plans/2026-04-12-story-system-phase1-contract-seed.md
  21. 0 809
      docs/archive/superpowers/plans/2026-04-12-story-system-phase2-contract-first-runtime.md
  22. 0 669
      docs/archive/superpowers/plans/2026-04-12-story-system-phase3-chapter-commit-chain.md
  23. 0 757
      docs/archive/superpowers/plans/2026-04-12-story-system-phase4-event-log-and-override-ledger.md
  24. 0 966
      docs/archive/superpowers/plans/2026-04-13-story-system-phase5-legacy-downgrade.md
  25. 0 325
      docs/archive/superpowers/plans/2026-04-14-context-agent-writing-brief-implementation.md
  26. 0 388
      docs/archive/superpowers/plans/2026-04-15-chain-integrity-fixes.md
  27. 0 1960
      docs/archive/superpowers/plans/2026-04-15-story-system-final-convergence.md
  28. 0 247
      docs/archive/superpowers/plans/2026-04-16-dashboard-frontend-rebuild.md
  29. 0 1008
      docs/archive/superpowers/plans/2026-04-16-references-genre-alignment.md
  30. 0 1253
      docs/archive/superpowers/plans/2026-06-01-review-critical-fixes.md
  31. 0 381
      docs/archive/superpowers/specs/2026-04-02-harness-v6-design.md
  32. 0 148
      docs/archive/superpowers/specs/2026-04-03-ai-writing-quirks.md
  33. 0 863
      docs/archive/superpowers/specs/2026-04-09-skills-restructure-and-reference-gaps.md
  34. 0 1198
      docs/archive/superpowers/specs/2026-04-12-story-system-evolution-spec.md
  35. 0 878
      docs/archive/superpowers/specs/2026-04-12-story-system-pro-max-retrofit-spec.md
  36. 0 1845
      docs/archive/superpowers/specs/2026-04-12-webnovel-story-intelligence-system-spec.md
  37. 0 398
      docs/archive/superpowers/specs/2026-04-14-context-agent-writing-brief-design.md
  38. 0 701
      docs/archive/superpowers/specs/2026-04-14-story-system-final-convergence-spec.md
  39. 0 475
      docs/archive/superpowers/specs/2026-04-16-references-completion-spec.md
  40. 0 681
      docs/archive/superpowers/specs/2026-04-30-system-quality-fixes.md
  41. 0 237
      docs/guides/commands.md
  42. 0 54
      docs/guides/rag-and-config.md
  43. 0 385
      docs/memory/long-term-memory-architecture-v2.md
  44. 0 482
      docs/operations/genre-taxonomy-convergence-plan-2026-06-04.md
  45. 0 223
      docs/operations/operations.md
  46. 0 79
      docs/operations/plugin-best-practices-deep-review-2026-06-03.md
  47. 0 404
      docs/superpowers/plans/2026-06-10-audit-fix-plan.md
  48. 0 220
      docs/superpowers/specs/2026-06-07-author-friendly-experience-design.md

+ 16 - 32
docs/README.md

@@ -2,57 +2,41 @@
 
 `docs/` 目录按功能分区整理,方便查阅。
 
+> 本分支(`v7`)是 v7 的主开发分支:运行时架构推翻重来,文档只保留 v7 规格与仍有效的资产/研究。v6 的全部文档与代码保留在 `master` 分支。
+
 ## 目录索引
 
-### 架构
+### 架构(v7 规格)
 
-- [`architecture/overview.md`](./architecture/overview.md):系统架构、Agent 分工、Story System 设计
 - [`architecture/story-repo-spec-2026-06-10.md`](./architecture/story-repo-spec-2026-06-10.md):v7 story repo 格式规格(法律文本,0.5 冻结)
-- [`architecture/v7-design-discussion-notes-2026-06-11.md`](./architecture/v7-design-discussion-notes-2026-06-11.md):v7 设计讨论纪要(v6 病根诊断、问题空间、多平台调研)
-- [`architecture/story-repo-spec-feedback-2026-06-11.md`](./architecture/story-repo-spec-feedback-2026-06-11.md):另一线对 story repo spec 0.4 的差异意见(已在 0.5 吸收)
-- [`architecture/plugin-runtime-hardening-spec-2026-06-04.md`](./architecture/plugin-runtime-hardening-spec-2026-06-04.md):基于优秀 Claude Code 插件调研的运行时可靠性重构 spec
-- [`architecture/plugin-runtime-hardening-plan-2026-06-04.md`](./architecture/plugin-runtime-hardening-plan-2026-06-04.md):运行时可靠性重构实施计划、修改范围与影响分析
-- [`architecture/multi-agent-adaptation-spec-2026-06-05.md`](./architecture/multi-agent-adaptation-spec-2026-06-05.md):多宿主与多智能体适配 spec(v3,已反转到 v7 story repo 基线)
-- [`architecture/context-minimal-writing-flow-plan-2026-06-05.md`](./architecture/context-minimal-writing-flow-plan-2026-06-05.md):Skills / Agents / References 上下文减负、读取方式与 token 优化重构计划(v3)
-- [`archive/architecture/current-system-diagnosis.md`](./archive/architecture/current-system-diagnosis.md):历史系统状态诊断
+- [`architecture/multi-agent-adaptation-spec-2026-06-05.md`](./architecture/multi-agent-adaptation-spec-2026-06-05.md):多宿主与多智能体适配 spec(v3.1,基线为 v7 story repo)
+- [`architecture/v7-design-discussion-notes-2026-06-11.md`](./architecture/v7-design-discussion-notes-2026-06-11.md):v7 设计讨论纪要(v6 病根诊断、问题空间 16 项、多平台调研)
+- [`architecture/story-repo-spec-feedback-2026-06-11.md`](./architecture/story-repo-spec-feedback-2026-06-11.md):另一线对 story repo spec 0.4 的差异意见(已在 0.5 吸收,留档)
 
-### 使用指南
+### 继承资产
 
-- [`guides/commands.md`](./guides/commands.md):Skill 命令与 CLI 子命令速查
-- [`guides/rag-and-config.md`](./guides/rag-and-config.md):RAG 检索链路、环境变量与配置
-- [`guides/genres.md`](./guides/genres.md):37 个题材模板与复合题材规则
+- [`guides/genres.md`](./guides/genres.md):37 个题材模板与复合题材规则(v7 继承)
 
 ### 运维
 
-- [`operations/operations.md`](./operations/operations.md):项目目录结构、运维命令、备份恢复
 - [`operations/plugin-release.md`](./operations/plugin-release.md):插件发版流程与版本同步
 
-### 记忆系统
-
-- [`memory/long-term-memory-architecture-v2.md`](./memory/long-term-memory-architecture-v2.md):长期记忆架构说明
-
 ### 研究与外部方案
 
 - [`research/long-term-memory-research-report.md`](./research/long-term-memory-research-report.md):长期记忆论文与开源方案调研
 - [`research/storyteller-paper-summary.md`](./research/storyteller-paper-summary.md):STORYTELLER 论文总结
-
-### 归档
-
-- [`archive/superpowers/README.md`](./archive/superpowers/README.md):历史架构 spec 与设计文档导航
+- [`research/2026-04-14-ui-ux-pro-max-skill-architecture-research.md`](./research/2026-04-14-ui-ux-pro-max-skill-architecture-research.md):skill 架构调研
 
 ## 分类原则
 
-- `architecture/`:系统结构与技术架构
-- `guides/`:使用者需要查阅的命令、配置、题材说明
-- `operations/`:运维、发版、备份与恢复
-- `memory/`:长期记忆架构说明
+- `architecture/`:v7 规格与设计讨论
+- `guides/`:使用者需要查阅的题材说明(v7 实现落地后补命令/配置指南)
+- `operations/`:运维与发版
 - `research/`:论文总结与外部方案调研
-- `archive/`:历史架构快照、spec 与设计计划
+- v6 历史文档:见 `master` 分支的 `docs/`(含 `archive/`)
 
 ## 推荐阅读顺序
 
-1. 先看 [`../README.md`](../README.md) 了解安装与基本使用
-2. 再看 [`architecture/overview.md`](./architecture/overview.md) 了解整体架构
-3. 需要配置检索时看 [`guides/rag-and-config.md`](./guides/rag-and-config.md)
-4. 需要使用命令时看 [`guides/commands.md`](./guides/commands.md)
-5. 排查运行问题时看 [`operations/operations.md`](./operations/operations.md)
+1. 先看 [`architecture/story-repo-spec-2026-06-10.md`](./architecture/story-repo-spec-2026-06-10.md)——v7 的法律文本
+2. 再看 [`architecture/v7-design-discussion-notes-2026-06-11.md`](./architecture/v7-design-discussion-notes-2026-06-11.md)——为什么这样设计
+3. 关心多平台支持看 [`architecture/multi-agent-adaptation-spec-2026-06-05.md`](./architecture/multi-agent-adaptation-spec-2026-06-05.md)

+ 0 - 1320
docs/architecture/author-friendly-reporting-plan-2026-06-07.md

@@ -1,1320 +0,0 @@
-# 作者友好报告与异常可见性改造 Plan
-
-> 日期:2026-06-07
-> 状态:草案 v2 · 已对齐 `docs/superpowers/specs/2026-06-07-author-friendly-experience-design.md`
-> 范围:承接 spec 的「作者外壳 / 作者界面层」七组件,统一 `init / plan / write / review` 的最终汇报、subagent 返回协议、错误目录、审查作者视图、下一步建议、异常分类、耗时呈现与作者友好术语;先取消 token 统计
-> 核心原则:问题不静默、自动处理要说明、技术细节默认隐藏、最终报告面向作者而不是工程日志;工程内核不动
-> 实施方式:先用 Skill / Agent 契约固定行为,再用 runtime helper 收敛格式,避免只靠提示词导致输出漂移
-
----
-
-## 0. 与产品 Spec 的对齐关系
-
-本计划是 `docs/superpowers/specs/2026-06-07-author-friendly-experience-design.md` 的工程落地计划,不再另起一套产品口径。
-
-分工如下:
-
-| 文档 | 负责什么 | 不负责什么 |
-|---|---|---|
-| 产品 spec | 定义「工程内核 + 作者外壳」分层、七个组件、错误恢复红线、分期价值 | 不展开具体文件、测试与施工顺序 |
-| 本 plan | 把七组件落到 Skill / Agent / runtime helper / 测试 / 文档 | 不重新定义产品目标,不放宽 spec 红线 |
-
-对齐后的共同边界:
-
-- Story System、`write-gate`、`chapter-commit`、projection、RAG 等工程内核不改语义、不降校验强度。
-- 作者默认看到里程碑、结论、影响和下一步;工程命令、JSON、schema、traceback 默认写入日志或技术详情。
-- 自动处理只限幂等、可重试、不碰作者内容的过程性问题;最终报告必须说明处理过什么,不能静默。
-- 不新增 UI、按钮、进度条、命令别名或自动修复大循环。
-
-七组件在本计划中的落点:
-
-| spec 组件 | 本 plan 落点 |
-|---|---|
-| 术语对照表 | §5 单一事实源;Phase 1 先落结构化词表 |
-| 进度播报规范 | §8 过程易用性;Phase 5A |
-| 错误→行动映射表 | §15 异常分类 + Phase 4 runtime helper;新增 `error_catalog.py` 与 `author_error_catalog.json` |
-| 审查作者视图 | §16 helper / `review_pipeline.py` 渲染;Phase 4 优先接 `review` |
-| 导航尾巴 | §4 三段式报告第三段;§20 推荐施工顺序中作为早期交付 |
-| 命令任务化 | §4 / §19 / §23 的下一步建议;只改提示语言,不新增别名 |
-| 可自动处理项 + 默认不展示工程细节 | §3.2-3.4、§11、§15、§21;第三期前只做说明和日志,不扩白名单 |
-
-## 1. 目标
-
-本计划把 `webnovel-writer` 的交付体验从“流程执行完以后由主 agent 自行总结”改成“每次都有稳定、可读、可信的作者回执”。
-
-核心交付:
-
-1. 统一最终报告规范:所有主 Skill 结尾都输出固定三段式报告,并以一句总状态开头。
-2. 全局作者友好约束:中文、少术语、不静默、自动处理要说明、技术细节按需展开。
-3. 阶段化报告模板:分别补强 `/webnovel-init`、`/webnovel-plan`、`/webnovel-write`、`/webnovel-review` 的最终汇报要求。
-4. Subagent 返回协议:主流程能汇总 `context-agent`、`reviewer`、`data-agent`、`deconstruction-agent` 的状态、问题、自动处理与耗时。
-5. 异常分类:所有问题归入“已自动处理 / 建议确认 / 必须处理”,重点暴露 subagent 失败、跳过、重试和输出不完整。
-6. 耗时可见:记录已耗时和关键步骤耗时;长时间无进展时说明可能原因和是否影响结果,不承诺固定完成时间。
-7. 术语翻译:工程词默认翻译成作者能理解的写作语义。
-8. Runtime helper:新增统一报告 helper,优先消费已有 `project-status`、`doctor`、`write-gate`、`review-pipeline`、`chapter-commit`、`projection_log` 等结构化输出。
-9. 过程易用性:在长流程执行中提供清晰进度、少打扰确认、可恢复状态和作者可理解的卡点说明。
-10. 断点续跑:重复执行同一主命令时,能识别已完成步骤,从最近可信断点继续,而不是要求作者记补跑命令。
-11. 交互式裁决:必须用户处理的问题优先给有限选项,减少“自己去改文件”的认知负担。
-12. 技术溯源:作者报告保持清爽,同时把工程细节写入本地日志,便于故障反馈和开发排查。
-13. 错误目录:把 runtime 错误码映射为作者能理解的原因、影响和下一步动作。
-14. 审查作者视图:在审查报告顶部提供一句结论和最多 3 条可执行修改建议。
-15. 下一步建议:每条主命令结束后给出任务化说明和可复制命令,不新增命令别名。
-
----
-
-## 2. 背景
-
-当前项目底层流程已经比较完整:
-
-- `project-status` 能判断项目阶段和下一步。
-- `doctor` 能给阶段感知体检。
-- `write-gate` 已覆盖写前、提交前、提交后三个边界。
-- `review-pipeline` 能规范化审查结果、生成报告并落库。
-- `chapter-commit` 能生成写后事实并驱动 state / index / summary / memory / vector 投影。
-- `projection_log` 能定位投影状态。
-
-问题不在于缺少检查,而在于这些检查结果没有稳定地翻译给作者:
-
-1. 最终回复格式不稳定,作者每次要重新判断“到底完成了没有”。
-2. 技术细节、JSON、命令输出容易直接暴露,阅读负担偏重。
-3. 某些降级、跳过、重试或自动处理只存在于流程内部,最终汇报不一定说明。
-4. subagent 成败、输出完整性和耗时没有统一汇总。
-5. `init / plan / write / review` 四条主流程的成功标准清楚,但“如何交付给作者”的要求不够清楚。
-6. 长流程执行过程中,作者不一定知道当前做到哪一步、为什么在等、是否需要介入、能不能中断后继续。
-7. 偶发失败后,作者往往需要理解内部步骤和补跑命令,恢复成本偏高。
-8. 必须用户裁决的问题容易变成“报错退出 + 长解释”,没有被收敛成清晰选择。
-
-本轮改造不重写写作主链,不新增自动修复大循环;重点补“作者可理解的交付层”。
-
----
-
-## 3. 设计原则
-
-### 3.0 Claude Code 能力边界
-
-所有改造必须基于 Claude Code 已有能力和本插件可实现的 runtime 能力,不编造宿主不存在的 UI、后台任务或交互机制。
-
-已可依赖的能力:
-
-- Skill 通过 `SKILL.md` 约束流程和最终输出。
-- 主流程可使用 Claude Code 的 `Agent` 工具调用已注册 subagent。
-- 主流程可使用 `AskUserQuestion` 做关键裁决,前提是该 Skill frontmatter 已允许并且宿主环境支持。
-- 主流程可通过 `Read` / `Write` / `Edit` / `Bash` 等工具读取、写入和运行本插件脚本。
-- 插件 runtime 可通过 Python CLI 读写 `.webnovel/`、`.story-system/`、日志和中间产物。
-
-不能假设的能力:
-
-- 不能假设 Claude Code 有实时进度条、图形化按钮、后台任务队列或终端内原生选择菜单。
-- 不能假设 subagent 会自动返回结构化元数据;需要通过 prompt 协议和主流程记录来实现。
-- 不能假设重复运行 Skill 会天然断点续跑;断点续跑必须由本插件 runtime 读取产物、run ledger 和 gate 结果实现。
-- 不能假设 AskUserQuestion 可以承担复杂表单;裁决选项应保持 2-3 个短选项。
-- 不能假设日志、耗时、恢复点会自动存在;这些必须由脚本或主流程显式记录。
-
-因此,本计划中的“过程提示”是主流程在关键节点输出短提示;“交互式裁决”是基于 `AskUserQuestion` 或普通对话提问的有限选择;“断点续跑”是插件 runtime 的文件与状态检查能力,不是 Claude Code 内置工作流引擎。
-
-### 3.1 作者友好,不工程炫技
-
-默认用作者能理解的中文表达,不直接输出 `subagent`、`artifact`、`projection`、`schema`、`runtime contract` 等工程词。
-
-必要时可以在问题详情中保留文件路径和命令,但默认先讲影响:
-
-```text
-“保存本章故事事实”失败,会影响后续查询本章发生过什么。
-```
-
-而不是:
-
-```text
-data-agent artifact schema_error: extraction_result missing accepted_events
-```
-
-### 3.2 问题不静默
-
-以下情况必须在最终报告中出现:
-
-- subagent 调用失败。
-- subagent 被用户模式选择跳过。
-- subagent 输出不完整。
-- reviewer 跳过或 `--minimal` 写 no-review artifact。
-- data-agent 三份结果缺失或 schema 不合格。
-- `write-gate` 任一阶段 failed。
-- `chapter-commit` rejected。
-- projection failed / pending / missing。
-- RAG 降级。
-- 备份失败。
-- 耗时异常。
-
-### 3.3 自动处理必须说明
-
-系统自动做过的事要简短说明,包括:
-
-- 自动补跑 projection。
-- 自动降级到关键词检索。
-- 自动应用白名单内的非阻断定点处理,例如格式整理或明确的错别字修正。
-- 自动覆盖旧的 no-review artifact。
-- 自动初始化 Git 或退回本地备份。
-
-说明不需要讲全部过程,只说明“处理了什么、是否影响结果”。
-
-### 3.4 技术细节默认隐藏
-
-最终报告优先给结论和影响。只有当用户需要处理时,再给路径、命令或错误类型。
-
-### 3.5 Runtime 优先,提示词兜底
-
-能由 runtime 确认的状态,不靠主 agent 口头判断。
-
-最终报告 helper 优先消费结构化数据:
-
-- `project-status --format json`
-- `doctor --format json`
-- `write-gate --format json`
-- `review-pipeline` payload
-- `chapter-commit` payload
-- `projection_log`
-- subagent run summary
-
-Skill 文档只规定“必须汇报什么”,不让每个 Skill 自己发明报告格式。
-
-### 3.6 过程易用性也是交付的一部分
-
-最终报告只能降低“结束后的不确定感”,不能解决执行过程中的焦虑。
-
-长流程必须让作者持续知道四件事:
-
-1. 当前在做哪一步。
-2. 这一步大概会产生什么。
-3. 是否需要作者做决定。
-4. 如果卡住,卡点是什么、会不会影响已有成果。
-
-过程提示应该短、少术语、少打扰。默认由系统继续推进,只有真正影响创作方向、事实一致性或文件安全时才打断用户。
-
-### 3.7 恢复应优先自动化
-
-“告诉作者怎么恢复”是底线,“作者重新执行同一个命令即可自动续跑”才是目标。
-
-主流程应逐步做到幂等:
-
-- 已完成且可信的产物不重复生成。
-- 失败后保留已有正文、审查报告和中间结果。
-- 再次执行同一命令时,先检查断点状态,再从最近失败步骤继续。
-- 只有产物过期、参数变更或用户明确要求重写时,才重新执行前置步骤。
-
-### 3.8 技术溯源不打扰作者
-
-作者默认看不到 JSON、schema、traceback 和完整命令日志。
-
-但系统需要保留本地溯源材料:
-
-- 用于开发者定位问题。
-- 用于用户反馈不可恢复故障。
-- 用于 runtime helper 判断断点和步骤状态。
-
-普通报告只给一条低干扰提示:
-
-```text
-如需反馈故障,可附上 .webnovel/logs/run_last.log。
-```
-
----
-
-## 4. 统一最终报告规范
-
-所有主 Skill 最终输出统一为:
-
-```text
-总状态:已完成 / 部分完成 / 需要你处理 / 未完成。
-
-一、产生的文件与完成情况
-- ...
-
-二、过程中遇到的问题与异常耗时
-- 已自动处理:...
-- 建议确认:...
-- 必须处理:...
-- 耗时异常:...
-
-三、下一步建议
-- ...
-```
-
-状态含义:
-
-| 状态 | 含义 |
-|---|---|
-| 已完成 | 当前阶段产物齐全,关键检查通过,可以进入下一步 |
-| 部分完成 | 有主要产物,但存在跳过、降级、未完成项或非阻断问题 |
-| 需要你处理 | 当前结果可保存,但必须由用户确认、裁决或补充信息 |
-| 未完成 | 关键产物缺失或阻断失败,不能继续下一阶段 |
-
----
-
-## 5. 术语翻译表(单一事实源)
-
-术语翻译不是每个 Skill 各写一份,而是作为 Author Layer 的单一事实源维护。
-
-首版采用结构化数据,便于 runtime helper 和 prompt integrity 测试复用:
-
-```text
-webnovel-writer/references/author_glossary.json
-```
-
-可选补一份面向文档阅读的 Markdown 摘要,但实现与测试只以 JSON 为准。
-
-维护规则:
-
-- Skill / Agent 文档可以引用术语,但不得自造不同译法。
-- `user_report.py`、`error_catalog.py`、审查作者视图都从同一术语表取作者友好表达。
-- 未登记的新工程词默认保留原词,并在报告中优先解释影响;后续再补词表,不临场硬翻。
-- 词表测试只检查关键术语是否存在、是否有作者解释,不锁死完整文案。
-
-| 工程词 | 作者友好表达 |
-|---|---|
-| subagent | 写作助手 / 审查助手 / 资料整理助手 / 拆书助手 |
-| context-agent | 写前准备 |
-| reviewer | 写作检查 |
-| data-agent | 保存本章故事事实 |
-| deconstruction-agent | 参考作品拆解 |
-| artifact | 中间结果文件 |
-| review_results | 写作检查结果 |
-| fulfillment_result | 本章目标完成情况 |
-| disambiguation_result | 待确认的人名/设定歧义 |
-| extraction_result | 本章新发生的故事事实 |
-| chapter-commit | 提交本章事实 |
-| projection | 更新故事资料 |
-| state / index / summary / memory / vector | 状态 / 索引 / 摘要 / 长期记忆 / 检索库 |
-| blocking issue | 会影响继续写作的问题 |
-| fallback | 临时降级读取 |
-| runtime contract | 本章写作要求 |
-| schema error | 中间结果格式不完整 |
-| pending | 等待确认 |
-| rejected | 本章事实未通过提交 |
-| accepted | 本章事实已通过提交 |
-
----
-
-## 6. 修改范围
-
-### 6.1 Skill / Agent 文档
-
-- `webnovel-writer/skills/webnovel-init/SKILL.md`
-- `webnovel-writer/skills/webnovel-plan/SKILL.md`
-- `webnovel-writer/skills/webnovel-write/SKILL.md`
-- `webnovel-writer/skills/webnovel-review/SKILL.md`
-- `webnovel-writer/agents/context-agent.md`
-- `webnovel-writer/agents/reviewer.md`
-- `webnovel-writer/agents/data-agent.md`
-- `webnovel-writer/agents/deconstruction-agent.md`
-
-### 6.2 Runtime helper
-
-新增:
-
-- `webnovel-writer/references/author_glossary.json`
-- `webnovel-writer/references/author_error_catalog.json`
-- `webnovel-writer/scripts/data_modules/author_glossary.py`
-- `webnovel-writer/scripts/data_modules/error_catalog.py`
-- `webnovel-writer/scripts/data_modules/review_author_view.py`
-- `webnovel-writer/scripts/data_modules/user_report.py`
-- `webnovel-writer/scripts/data_modules/run_ledger.py`
-- `webnovel-writer/scripts/data_modules/run_logger.py`
-- `webnovel-writer/scripts/data_modules/tests/test_author_glossary.py`
-- `webnovel-writer/scripts/data_modules/tests/test_error_catalog.py`
-- `webnovel-writer/scripts/data_modules/tests/test_review_author_view.py`
-- `webnovel-writer/scripts/data_modules/tests/test_user_report.py`
-- `webnovel-writer/scripts/data_modules/tests/test_run_ledger.py`
-- `webnovel-writer/scripts/data_modules/tests/test_run_logger.py`
-
-修改:
-
-- `webnovel-writer/scripts/data_modules/webnovel.py`
-- `webnovel-writer/scripts/review_pipeline.py`
-- `webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py`
-- `webnovel-writer/skills/webnovel-write/SKILL.md`
-
-### 6.3 Prompt / behavior 测试
-
-修改或新增:
-
-- `webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py`
-- `webnovel-writer/evals/fixtures/behavior/fast.json`
-
----
-
-## 7. 能力映射与实现边界
-
-| 易用性目标 | 可用 Claude Code 能力 | 插件需新增 / 修改 | 不依赖 |
-|---|---|---|---|
-| 过程提示 | 主流程自然语言输出 | Skill 增加关键节点提示要求 | 实时进度条 |
-| 开始前预期管理 | 主流程开头输出短说明 | Skill 增加流程概览模板 | 后台任务估时系统 |
-| 最终报告 | 主流程最终回复 | `user_report.py` 渲染 text/json | Claude Code 自动格式化 |
-| subagent 状态汇总 | `Agent` 调用 + 主流程记录 | `SubagentRun` 协议和主流程汇总 | subagent 自动 telemetry |
-| 异常分类 | 主流程读取 runtime JSON | `user_report.py` 分类逻辑 | 宿主自动错误分类 |
-| 耗时记录 | Bash / Python 计时或主流程记录 | run ledger / helper 记录步骤时间 | Claude Code 内置性能面板 |
-| 断点续跑 | Bash 运行 Python CLI,读写本地文件 | `run_ledger.py`、产物可信度检查、gate 复用 | Claude Code 内置 resume 引擎 |
-| 交互式裁决 | `AskUserQuestion` 或普通对话提问 | Skill 定义有限选项和处理分支 | 图形化按钮 / 复杂表单 |
-| 技术溯源 | Python 写本地日志 | `run_logger.py`、敏感信息过滤 | 宿主自动日志导出 |
-| 下一步命令 | 最终报告文本 | `user_report.py` 填入建议命令 | 一键按钮 |
-
-实现原则:
-
-1. 先用现有 Skill / Agent / Bash / Python CLI 能力落地。
-2. 任何需要 runtime 判断的能力,都必须有本地文件、JSON 或命令输出作为依据。
-3. 任何看起来像 UI 的体验,都只能表现为文本提示、有限提问或最终报告,除非未来另做 Dashboard 改造。
-4. 不把 Claude Code 未承诺的行为写成验收标准。
-
----
-
-## 8. 过程易用性设计
-
-### 8.1 目标
-
-让作者在流程运行中不需要理解内部工程链路,也能知道:
-
-- 现在系统在做什么。
-- 这一步为什么必要。
-- 是否仍在推进。
-- 什么时候需要自己拍板。
-- 中途失败时已经完成了哪些部分,能从哪里继续。
-
-过程体验不是把每条命令都打印出来,而是把长流程拆成作者能理解的“当前动作”。
-
-### 8.2 开始前预期管理
-
-长流程开始前,先给作者一个短概览:
-
-```text
-开始写第 13 章。本次会经过:整理依据 -> 起草正文 -> 写作检查 -> 润色 -> 保存本章故事事实 -> 更新资料并备份。
-不同 API、模型和网络速度差异很大,本流程不承诺固定耗时;中途只有遇到创作裁决或事实冲突时才会问你。
-```
-
-预期管理必须包含:
-
-- 本次目标。
-- 主要步骤。
-- 不承诺固定耗时的说明。
-- 是否需要用户守在旁边。
-
-### 8.3 统一过程提示格式
-
-过程提示使用短句,不超过两行:
-
-```text
-正在整理本章写作依据:会读取章纲、最近剧情和未回收伏笔。
-```
-
-```text
-正在保存本章故事事实:这一步会更新摘要、角色状态和后续检索资料。
-```
-
-避免:
-
-```text
-Running write-gate --stage precommit and validating artifacts...
-```
-
-### 8.4 阶段名翻译
-
-| 内部步骤 | 过程提示名称 |
-|---|---|
-| preflight | 检查项目环境 |
-| placeholder-scan | 检查未补齐占位 |
-| story-system | 刷新本章写作要求 |
-| write-gate prewrite | 写前检查 |
-| context-agent | 整理写作依据 |
-| draft | 起草正文 |
-| reviewer | 写作检查 |
-| review-pipeline | 生成检查报告 |
-| polish | 润色与排版 |
-| data-agent | 保存本章故事事实 |
-| write-gate precommit | 提交前检查 |
-| chapter-commit | 提交本章事实 |
-| write-gate postcommit | 提交后确认 |
-| projections retry | 补跑故事资料更新 |
-| backup | 备份本章成果 |
-
-### 8.5 少打扰确认策略
-
-默认不打断作者,除非出现以下情况:
-
-| 必须询问 | 原因 |
-|---|---|
-| init 最终方案确认 | 会写入新书核心设定 |
-| 参考作品拆解质量不足但用户想采用 | 可能污染新书创意 |
-| plan 发现总纲 / 设定冲突 | 需要创作裁决 |
-| write 发现无法定点修复的 blocking issue | 会影响本章继续提交 |
-| data-agent 出现低置信度歧义且会影响事实入库 | 后续状态可能写错 |
-| commit rejected 后用户仍想继续 | 需要明确风险接受 |
-| 文件写入范围异常 | 可能污染其他章节或项目 |
-
-不应询问:
-
-- 普通非阻断审查问题,系统可在润色中处理。
-- RAG 降级但不影响当前写作。
-- projection retry 可以自动补跑。
-- 备份从 Git 降级到本地备份且成功。
-- 单纯耗时偏长但结果正常。
-
-### 8.6 长流程进度节点
-
-每个主 Skill 建议最多展示 3-6 个过程节点,不展示每个内部命令。
-
-`/webnovel-init`:
-
-1. 收集故事核心。
-2. 整理创意约束。
-3. 等待最终确认。
-4. 创建项目文件。
-5. 生成写作主链基础资料。
-6. 验证项目能否进入规划。
-
-`/webnovel-plan`:
-
-1. 读取总纲和已有剧情状态。
-2. 补齐设定基线。
-3. 规划卷节奏和时间线。
-4. 拆分章纲。
-5. 写回新增设定。
-6. 刷新写作要求。
-
-`/webnovel-write`:
-
-1. 检查写前条件。
-2. 整理写作依据。
-3. 起草正文。
-4. 写作检查与润色。
-5. 保存本章故事事实。
-6. 提交、更新资料并备份。
-
-`/webnovel-review`:
-
-1. 确认待审章节。
-2. 整理审查依据。
-3. 执行写作检查。
-4. 生成审查报告并落库。
-5. 如有阻断问题,等待用户裁决。
-
-### 8.7 卡住时的过程反馈
-
-流程卡住时不要只报错误,要说明三件事:
-
-1. 卡在哪一步。
-2. 已经完成了什么。
-3. 下一步怎么恢复。
-
-示例:
-
-```text
-卡在“保存本章故事事实”:正文和审查报告已经完成,但本章事实提取结果缺少摘要字段。
-我会重跑资料整理助手;如果仍失败,会保留正文和审查报告,不会提交不完整事实。
-```
-
-### 8.8 可恢复状态提示
-
-流程中断或失败后,最终报告和过程反馈都应说明恢复点:
-
-| 卡点 | 恢复建议 |
-|---|---|
-| context-agent 失败 | 补齐章纲 / 合同后重跑写章 |
-| 起草后 review 失败 | 保留正文,重跑写作检查 |
-| review 有 blocking | 定点修复或用户裁决后继续润色 |
-| data-agent artifact 缺失 | 重跑保存本章故事事实 |
-| precommit failed | 修复中间结果后重跑提交前检查 |
-| commit rejected | 修复 missed_nodes / pending / blocking 后重新提交 |
-| projection failed | 补跑 `projections retry` |
-| backup failed | 手动或重跑 backup,不影响已提交事实 |
-
-### 8.9 作者可控的详细程度
-
-后续可增加可选参数:
-
-```text
---quiet      只显示关键确认和最终报告
---verbose    显示过程节点、异常原因和关键命令
-```
-
-首版不强制实现参数,但 Skill 文档应遵循默认“简洁过程提示 + 详细最终报告”的体验。
-
----
-
-## 9. 断点续跑设计
-
-### 9.1 目标
-
-让作者遇到偶发失败后,不需要理解内部补跑命令。重复执行同一主命令时,系统应自动识别已完成步骤,从最近可信断点继续。
-
-示例:
-
-```text
-检测到上一次第 13 章已完成“起草正文”和“写作检查”,但卡在“保存本章故事事实”。
-本次将从“保存本章故事事实”继续,不会重写正文。
-```
-
-### 9.2 断点状态来源
-
-优先复用现有产物和 gate:
-
-| 步骤 | 可信完成判据 |
-|---|---|
-| 写前检查 | `write-gate prewrite ok=true` 或当前重新运行通过 |
-| 写作依据 | `context-agent` 返回任务书且未过期 |
-| 正文起草 | 目标章节正文文件存在且非空 |
-| 写作检查 | `review_results.json` 标记目标章节,且 `review-pipeline` 已生成报告 |
-| 润色 | 正文修改时间晚于审查报告,且无 anti-ai 阻断记录 |
-| 保存事实 | 三份 data artifacts 存在且 `write-gate precommit` 通过 |
-| 提交事实 | commit 文件存在且 status accepted |
-| 更新资料 | `write-gate postcommit` 通过,projection 五项 done/skipped |
-| 备份 | backup 返回成功或存在本章备份记录 |
-
-### 9.3 Run Ledger
-
-首版可以不新增复杂状态机,但建议新增轻量运行账本:
-
-```text
-.webnovel/runs/write_ch0013.json
-.webnovel/logs/run_last.log
-```
-
-`write_ch0013.json` 保存机器可读进度:
-
-```json
-{
-  "schema_version": "webnovel-run-ledger/v1",
-  "command": "webnovel-write",
-  "chapter": 13,
-  "started_at": "",
-  "updated_at": "",
-  "steps": [
-    {"id": "draft", "label": "起草正文", "status": "done", "outputs": ["正文/第0013章.md"]},
-    {"id": "data", "label": "保存本章故事事实", "status": "failed", "problem": "API timeout"}
-  ]
-}
-```
-
-`run_last.log` 保存工程细节:
-
-- 命令。
-- JSON 输出摘要。
-- traceback。
-- subagent 原始异常。
-- 耗时。
-
-作者报告不直接展开 `run_last.log`,只在不可恢复故障时提示路径。
-
-### 9.4 幂等策略
-
-重复执行主命令时:
-
-1. 先解析 `PROJECT_ROOT`、章节号和模式参数。
-2. 读取 run ledger 和现有产物。
-3. 校验已完成步骤是否仍可信。
-4. 从第一个不可信或失败步骤继续。
-5. 若用户参数改变、正文被手动修改、章纲更新时间晚于正文,则提示是否重跑前置步骤。
-
-### 9.5 必须询问的续跑分支
-
-| 场景 | 处理 |
-|---|---|
-| 正文存在但本次用户要求重写 | 询问覆盖 / 另存 / 取消 |
-| 章纲更新晚于正文 | 询问沿用旧正文还是重新起草 |
-| 审查报告来自旧正文 | 自动重跑审查 |
-| commit 已 accepted,但用户再次执行写同章 | 询问是否重写本章或只查看状态 |
-| backup 失败但 commit 已完成 | 自动重跑 backup,不重写正文 |
-
-### 9.6 阶段落地
-
-第一阶段只做 `/webnovel-write` 的断点续跑,因为它步骤最长、失败点最多。
-
-后续再扩展:
-
-- `/webnovel-plan`:批次级续跑,失败批次重做,不覆盖整卷。
-- `/webnovel-init`:用户确认前问答态不强行续跑;生成阶段可按文件补齐。
-- `/webnovel-review`:按章节范围跳过已审且正文未变更的章节。
-
----
-
-## 10. 交互式裁决设计
-
-### 10.1 目标
-
-遇到必须用户处理的问题时,优先给有限选项,而不是让作者自己理解错误并手改文件。
-
-### 10.2 裁决呈现格式
-
-```text
-需要你裁决:本章“沈照”的法宝与大纲冲突。
-
-大纲记录:青锋剑
-正文写成:紫金葫芦
-
-请选择处理方式:
-1. 坚持大纲:自动把正文相关段落改回“青锋剑”
-2. 采用新设定:保留“紫金葫芦”,并把设定变更写入故事资料
-3. 我手动处理:暂停流程,修改后继续
-```
-
-### 10.3 标准裁决类型
-
-| 类型 | 选项 |
-|---|---|
-| 设定冲突 | 坚持既有设定 / 采用新设定 / 手动处理 |
-| 时间线冲突 | 按时间线修正文 / 调整时间线 / 手动处理 |
-| 角色 OOC | 按角色卡修正文 / 更新角色变化理由 / 手动处理 |
-| 低置信度消歧 | 采用 A / 采用 B / 暂不入库 |
-| commit rejected | 修复后重提 / 接受风险但不提交 / 手动处理 |
-| 文件写入范围异常 | 取消写入 / 只保留安全文件 / 查看详情 |
-
-### 10.4 与 AskUserQuestion 的关系
-
-在 Claude Code 环境中,优先使用 `AskUserQuestion` 做关键裁决。
-
-选项必须短,并说明影响:
-
-- 推荐项放第一。
-- 每个选项说明会改什么。
-- 不出现“其他”作为固定选项;用户可自由补充。
-
-### 10.5 不做自动裁决的红线
-
-以下情况不能由系统擅自决定:
-
-- 改变主角长期能力路线。
-- 改变核心反派身份。
-- 改变卷末高潮结果。
-- 将参考作品内容写入新书 canon。
-- 覆盖用户手动编辑的正文。
-- 把 rejected commit 当作 accepted 继续推进。
-
----
-
-## 11. 技术溯源与日志
-
-### 11.1 目标
-
-作者报告保持清爽,工程排查材料保留完整。
-
-### 11.2 日志位置
-
-建议:
-
-```text
-.webnovel/logs/run_last.log
-.webnovel/logs/runs/YYYYMMDD-HHMMSS-{command}.log
-```
-
-### 11.3 日志内容
-
-日志包含:
-
-- 命令与参数。
-- 解析后的项目根。
-- 每个过程节点开始 / 结束时间。
-- subagent run summary。
-- runtime JSON 输出摘要。
-- 异常 traceback。
-- 最终 `user-report --format json`。
-
-日志不应包含:
-
-- API key。
-- `.env` 原文。
-- 用户未确认写入的新书核心设定草稿,除非它已经作为本次运行输入出现。
-
-### 11.4 最终报告中的呈现
-
-只在以下情况展示日志路径:
-
-- 未完成。
-- 需要用户处理但问题不容易描述。
-- 用户使用 `--verbose`。
-
-示例:
-
-```text
-技术详情已保存:.webnovel/logs/run_last.log。反馈故障时可以附上它。
-```
-
----
-
-## 12. Phase 0:基线审计
-
-### 12.1 目标
-
-先确认现有报告、gate 和 agent 输出边界,避免改造后不知道格式漂移来自哪里。
-
-### 12.2 工作项
-
-- [ ] 记录四个主 Skill 的当前最终输出要求。
-- [ ] 记录四个 agent 当前输出格式与写入责任。
-- [ ] 记录 `project-status`、`doctor`、`write-gate`、`review-pipeline`、`chapter-commit` 的 JSON 字段。
-- [ ] 记录现有错误码、repair 文案、gate failure 特征与 projection 状态,作为 `author_error_catalog.json` 初始素材。
-- [ ] 记录 `review-pipeline` 可用于作者视图的字段:总分、blocking 数、维度问题、建议项、报告路径。
-- [ ] 记录当前文档和 Skill 中已出现的工程词,和 §5 术语表做一次去重。
-- [ ] 确认现有测试中哪些是文案级断言,哪些能改为行为级断言。
-
-### 12.3 验收
-
-- 当前可复用的结构化数据源已列明。
-- 明确哪些问题只能由 prompt 记录,哪些可以由 runtime helper 读取。
-- `author_glossary.json` 与 `author_error_catalog.json` 的首批条目来源清楚,不靠实现时临场猜。
-
----
-
-## 13. Phase 1:Skill 最终报告契约
-
-### 13.1 目标
-
-先用最小改动让四个主流程在最终回复中遵守统一格式。
-
-### 13.2 `/webnovel-init`
-
-必须汇报:
-
-- 项目目录。
-- `.webnovel/state.json`。
-- `设定集/世界观.md`、`设定集/力量体系.md`、`设定集/主角卡.md`、`设定集/反派设计.md`。
-- `大纲/总纲.md`。
-- `.webnovel/idea_bank.json`。
-- `.story-system/MASTER_SETTING.json`。
-- 是否使用参考作品拆解。
-- 用户确认前未写入 canon 的情况。
-- 缺失信息是否会影响后续 plan。
-
-工作项:
-
-- [ ] 在 `webnovel-init/SKILL.md` 增加“最终报告要求”段。
-- [ ] 将成功标准映射到“三段式报告”。
-- [ ] 明确参考作品拆解失败、输入不足或质量不过线时必须进入“建议确认 / 必须处理”。
-
-### 13.3 `/webnovel-plan`
-
-必须汇报:
-
-- `大纲/第{volume_id}卷-节拍表.md`。
-- `大纲/第{volume_id}卷-时间线.md`。
-- `大纲/第{volume_id}卷-详细大纲.md`。
-- 新增设定写回了哪些设定集文件。
-- `大纲/第{volume_id}卷-总纲写回.json`。
-- `master-outline-sync` 是否完成。
-- `update-state` 是否完成。
-- Story System 合同是否刷新。
-- 占位符、时间线、节点承接是否通过。
-
-工作项:
-
-- [ ] 在 `webnovel-plan/SKILL.md` 增加“最终报告要求”段。
-- [ ] 明确时间线回跳、BLOCKER、占位符残留必须报告。
-- [ ] 明确只重做失败批次时要说明自动处理内容。
-
-### 13.4 `/webnovel-write`
-
-必须汇报:
-
-- 正文文件路径。
-- 写作检查报告路径。
-- `.webnovel/tmp/review_results.json`。
-- `.webnovel/tmp/fulfillment_result.json`。
-- `.webnovel/tmp/disambiguation_result.json`。
-- `.webnovel/tmp/extraction_result.json`。
-- `.story-system/commits/chapter_{NNN}.commit.json`。
-- state / index / summary / memory / vector 更新状态。
-- 备份状态。
-- 是否可以继续写下一章。
-
-工作项:
-
-- [ ] 在 `webnovel-write/SKILL.md` 增加“最终报告要求”段。
-- [ ] 明确 `--fast` 和 `--minimal` 的跳过项必须说明。
-- [ ] 明确 `chapter-commit rejected` 时最终状态不得写“已完成”。
-- [ ] 明确 projection retry 发生时要说明已自动处理和结果。
-
-### 13.5 `/webnovel-review`
-
-必须汇报:
-
-- 审查报告文件。
-- `review_metrics.json`。
-- `review_metrics` 是否落库。
-- 阻断问题数量。
-- 用户裁决状态。
-- 如果无阻断,明确可以继续写作。
-
-工作项:
-
-- [ ] 在 `webnovel-review/SKILL.md` 增加“最终报告要求”段。
-- [ ] 明确 blocking 问题必须进入“必须处理”或“建议确认”。
-- [ ] 明确只保存报告、稍后处理时最终状态为“需要你处理”或“部分完成”。
-
----
-
-## 14. Phase 2:Subagent 返回协议
-
-### 14.1 目标
-
-让主流程可以稳定汇总每个 subagent 的完成状态、问题、自动处理内容和耗时。
-
-### 14.2 统一协议
-
-主流程为每次 subagent 调用记录一份 `SubagentRun`:
-
-```json
-{
-  "name": "data-agent",
-  "user_label": "保存本章故事事实",
-  "status": "completed | partial | failed | skipped",
-  "problems": [],
-  "auto_handled": [],
-  "needs_user_action": false,
-  "duration_ms": 0,
-  "outputs": []
-}
-```
-
-字段说明:
-
-| 字段 | 含义 |
-|---|---|
-| `name` | agent 名称 |
-| `user_label` | 作者友好名称 |
-| `status` | 完成状态 |
-| `problems` | 遇到的问题 |
-| `auto_handled` | 自动处理内容 |
-| `needs_user_action` | 是否需要用户处理 |
-| `duration_ms` | 耗时 |
-| `outputs` | 产生或返回的关键产物 |
-
-### 14.3 工作项
-
-- [ ] `context-agent`:上下文不足、legacy fallback、伏笔数据缺失必须可被主流程记录。
-- [ ] `reviewer`:正文为空、读取状态失败、维度跳过必须写入 summary 或问题字段。
-- [ ] `data-agent`:三份 artifact 写入状态、长时间无进展、pending 消歧必须可被汇总。
-- [ ] `deconstruction-agent`:输入不足、质量不过线、降级 quick mode 必须可被汇总。
-- [ ] 主 Skill 调用 agent 后,必须记录 `SubagentRun` 供最终报告使用。
-
-### 14.4 验收
-
-- 写章流程最终报告能列出写前准备、写作检查、保存本章故事事实三个助手的状态。
-- 任一 agent 跳过、失败、输出不完整时,最终报告不会写成完全成功。
-
----
-
-## 15. Phase 3:异常分类与耗时呈现
-
-### 15.1 异常分类
-
-所有问题归为三类:
-
-| 类型 | 定义 | 示例 |
-|---|---|---|
-| 已自动处理 | 系统已补跑、降级或完成白名单内定点处理,不需要用户处理 | projection retry 成功、RAG 降级但不影响结果 |
-| 建议确认 | 结果可用,但建议用户看一眼 | 参考拆解质量略低、某个角色命名有歧义但已采用 |
-| 必须处理 | 不处理会影响继续写作、提交或一致性 | blocking issue、正文缺失、commit rejected、projection failed |
-
-### 15.2 Error Catalog
-
-`author_error_catalog.json` 是错误到作者行动的映射表,供 `error_catalog.py` 和 `user_report.py` 共同使用。它不改变错误判定,只负责把已知错误翻译成:
-
-- 人话原因。
-- 对当前章节 / 后续写作的影响。
-- 下一步动作或可复制命令。
-- 严重度与异常分类。
-- 是否允许自动处理。
-
-未知错误必须诚实降级:
-
-```text
-这里遇到一个系统还没有登记过的问题。当前不会把它当成已完成;请先运行 /webnovel-doctor,或反馈时附上日志。
-```
-
-错误目录只做翻译和分类;自动处理白名单必须单独显式登记,且第三期前不扩大现有自动处理范围。
-
-### 15.3 耗时呈现
-
-默认只展示:
-
-- 已耗时。
-- 当前步骤是否仍在推进。
-- 长时间无进展时的可能原因。
-- 是否影响已完成结果。
-
-不设置固定耗时阈值。不同 API、模型、网络、章节长度和审查复杂度差异过大,固定阈值会误导作者。
-
-过程提示可以使用相对表达:
-
-```text
-“保存本章故事事实”已经运行了一段时间,可能是接口响应较慢或本章新增事实较多;当前不会影响已生成正文。
-```
-
-### 15.4 工作项
-
-- [ ] 在 Skill 最终报告要求中加入“异常分类”。
-- [ ] 新增 `author_error_catalog.json` 与 `error_catalog.py`。
-- [ ] 给 `mainline_ready=false`、`write-gate failed`、`chapter-commit rejected`、projection failed / pending、RAG 降级、artifact schema 不完整等场景建立首批条目。
-- [ ] 未命中错误码时降级到“诚实报错 + `/webnovel-doctor` + 日志路径”,不得崩溃或乱翻译。
-- [ ] 在 `data-agent.md` 中保留并规范“长时间无进展需说明原因和影响”。
-- [ ] 在 runtime helper 中实现耗时格式化。
-- [ ] 不做 token 统计,不在最终报告展示 token。
-
-### 15.5 验收
-
-- 最终报告不会把 warning、blocking、auto-handled 混在一起。
-- 已知错误能映射为作者可执行下一步,未知错误能诚实降级。
-- 长时间无进展的步骤必须有原因推测和影响判断。
-- token 不作为用户可见报告项。
-
----
-
-## 16. Phase 4:Runtime 报告 Helper
-
-### 16.1 目标
-
-新增统一 helper,把结构化运行结果渲染成作者友好报告。
-
-本阶段同时落地 spec 的 Review Author View:在现有审查报告顶部增加作者视图,不改变 reviewer schema、不改变评分和 blocking 判定。
-
-作者视图格式:
-
-```text
-本章结论:✅可以继续 / ⚠️建议改 / ⛔必须先改
-
-最值得处理的 1-3 件事:
-- ...
-```
-
-生成规则:
-
-- `blocking_count > 0`:结论为“必须先改”,最多列 3 条 blocking 或高风险问题。
-- 无 blocking 但存在明显建议:结论为“建议改”,最多列 3 条对剧情、人物、节奏最有收益的建议。
-- 无 blocking 且建议较轻:结论为“可以继续”,只保留一句说明。
-- 技术指标、schema、原始 reviewer 维度放在下方报告细节,不放进顶部结论。
-
-### 16.2 CLI 形态
-
-新增:
-
-```bash
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" user-report \
-  --stage write \
-  --chapter {chapter_num} \
-  --format text
-```
-
-支持:
-
-```bash
---stage init|plan|write|review
---chapter N
---volume N
---format text|json
-```
-
-### 16.3 数据模型
-
-`user_report.py` 内部使用:
-
-```json
-{
-  "schema_version": "webnovel-user-report/v1",
-  "stage": "write",
-  "overall_status": "completed | partial | needs_user | failed",
-  "project_root": "",
-  "chapter": 0,
-  "volume": 0,
-  "files": [],
-  "issues": {
-    "auto_handled": [],
-    "needs_confirmation": [],
-    "must_handle": []
-  },
-  "timing": {
-    "total_ms": 0,
-    "steps": []
-  },
-  "next_actions": []
-}
-```
-
-### 16.4 数据来源
-
-| 阶段 | 数据来源 |
-|---|---|
-| init | 文件存在性、`project-status`、`.story-system/MASTER_SETTING.json` |
-| plan | 规划产物文件、`placeholder-scan`、`master-outline-sync` 输出、Story System 合同文件 |
-| write | `write-gate` 三阶段、review artifacts、commit payload、projection log、backup 输出、subagent runs |
-| review | `review_results.json`、`review_metrics.json`、报告文件、`review_metrics` 表 |
-
-### 16.5 工作项
-
-- [ ] 新增 `review_author_view.py`,从现有 review payload 渲染一句结论 + 最多 3 条可执行建议。
-- [ ] 在 `review_pipeline.py` 的报告渲染环节插入作者视图段,不改 reviewer schema。
-- [ ] 新增 `user_report.py`。
-- [ ] 新增 `webnovel.py user-report` 子命令。
-- [ ] 先实现 `write` 阶段报告,因为它最复杂、收益最大。
-- [ ] 同步实现 `review` 阶段报告,复用审查作者视图。
-- [ ] 最后实现 `init` 和 `plan`。
-- [ ] 给 helper 加单元测试,不依赖真实 LLM。
-
-### 16.6 验收
-
-- 审查报告顶部有一句作者可判读结论,且最多 3 条可执行建议。
-- 作者视图保留 blocking 数等关键事实,不把必须处理的问题写成“可以继续”。
-- `user-report --stage write --format json` 输出稳定 schema。
-- `user-report --stage write --format text` 输出三段式中文报告。
-- projection failed、commit rejected、missing artifact 等场景能被正确归类。
-- helper 输出不包含 token 统计。
-
----
-
-## 17. Phase 5:断点续跑与交互验证
-
-### 17.1 Prompt integrity
-
-新增或调整断言:
-
-- [ ] 四个主 Skill 都包含过程提示要求。
-- [ ] 四个主 Skill 都包含“少打扰确认策略”。
-- [ ] `/webnovel-write` 的过程节点不超过 6 个作者可理解阶段。
-- [ ] 过程提示使用作者友好阶段名,不直接暴露工程命令作为主提示。
-- [ ] 卡住时必须说明卡点、已完成内容和恢复建议。
-- [ ] `/webnovel-write` 必须说明重复执行同一命令时可从可信断点继续。
-- [ ] 必须用户裁决的问题应优先给有限选项。
-- [ ] 技术详情默认写入 `.webnovel/logs/run_last.log`,不直接污染作者报告。
-
-### 17.2 Runtime tests
-
-新增:
-
-- [ ] `test_run_ledger_records_write_step_status`
-- [ ] `test_write_resume_skips_completed_draft_and_review`
-- [ ] `test_write_resume_rechecks_review_when_chapter_file_changed`
-- [ ] `test_write_resume_retries_backup_after_commit_done`
-- [ ] `test_run_log_redacts_env_values`
-- [ ] `test_user_report_includes_log_path_only_on_failure`
-
-首版如果暂不实现 run ledger,则这些测试先作为 Phase 5 待实现契约,不并入默认必过集合。
-
-### 17.3 Behavior eval
-
-新增:
-
-- [ ] `/webnovel-write` 执行过程中能说明当前处于写前检查、起草、审查、保存事实、提交备份中的哪一段。
-- [ ] RAG 降级不打断用户,但最终报告说明。
-- [ ] projection retry 自动补跑不询问用户,但过程提示说明正在补跑。
-- [ ] blocking issue 无法定点处理时才询问用户。
-- [ ] data-agent 输出不完整时说明已保留正文和审查报告,不提交不完整事实。
-- [ ] 同章重复运行 `/webnovel-write` 时,不重写已可信正文,自动从失败步骤继续。
-- [ ] 设定冲突类 blocking issue 使用有限选项裁决。
-- [ ] 不可恢复故障报告中给出 `.webnovel/logs/run_last.log`。
-
-### 17.4 验收
-
-- 作者在长流程中能判断“现在在做什么”。
-- 非关键问题不会频繁打断作者。
-- 关键创作裁决不会被系统擅自跳过。
-- 失败后能看到明确恢复点。
-- 偶发失败后,重复执行同一主命令可以从最近可信步骤继续。
-- 工程日志可用于排查,但默认不打扰作者。
-
----
-
-## 18. Phase 6:测试与行为验证
-
-### 18.1 Prompt integrity
-
-新增或调整断言:
-
-- [ ] 四个主 Skill 都包含最终报告要求。
-- [ ] 四个主 Skill 都要求总状态 + 三段式报告。
-- [ ] `webnovel-write` 必须汇报正文、审查、data artifacts、commit、projection、backup。
-- [ ] `webnovel-review` 必须汇报审查报告、metrics 和 blocking 裁决。
-- [ ] Agent 协议中必须可汇总 status / problems / auto_handled / needs_user_action / duration。
-- [ ] 不要求具体措辞,只检查契约是否存在。
-
-### 18.2 Runtime tests
-
-新增:
-
-- [ ] `test_user_report.py::test_render_write_report_success`
-- [ ] `test_user_report.py::test_render_write_report_commit_rejected`
-- [ ] `test_user_report.py::test_render_write_report_projection_failed`
-- [ ] `test_user_report.py::test_render_review_report_blocking`
-- [ ] `test_webnovel_unified_cli.py` 覆盖 `user-report` 注册。
-
-### 18.3 Behavior eval
-
-在 fast eval 中补:
-
-- [ ] `/webnovel-write --minimal` 最终报告必须说明跳过写作检查。
-- [ ] data-agent 输出缺失时最终报告不能写“已完成”。
-- [ ] projection retry 成功时最终报告归入“已自动处理”。
-- [ ] reviewer 有 blocking 时最终报告归入“必须处理”。
-
-### 18.4 验证命令
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py -q --no-cov
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_user_report.py -q --no-cov
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py -q --no-cov
-python -X utf8 webnovel-writer/scripts/run_behavior_evals.py --format json
-```
-
----
-
-## 19. Phase 7:文档与 README
-
-### 19.1 目标
-
-让用户知道每次命令结束后应该如何理解最终报告。
-
-### 19.2 修改范围
-
-- `README.md`
-- `docs/guides/commands.md`
-- `docs/operations/operations.md`
-
-### 19.3 工作项
-
-- [ ] 在 README 的写章工作流中补“最终报告怎么看”。
-- [ ] 在 README 或 commands 文档中补“执行过程中会看到哪些提示”。
-- [ ] 在 commands 文档中说明重复执行主命令会优先尝试断点续跑。
-- [ ] 在 commands 文档中说明四种总状态。
-- [ ] 在 operations 文档中说明哪些情况会询问用户,哪些会自动处理。
-- [ ] 在 operations 文档中说明 `.webnovel/logs/run_last.log` 的用途和敏感信息规则。
-- [ ] 在 operations 文档中说明异常分类和常见处理。
-- [ ] 不写 token 统计说明。
-
-### 19.4 验收
-
-- 用户能从文档理解“已完成 / 部分完成 / 需要你处理 / 未完成”的区别。
-- 用户能知道哪些问题可以忽略,哪些必须处理。
-- 用户知道遇到偶发失败后可以重新执行原命令。
-- 用户知道反馈故障时可以附日志,但平时不需要看日志。
-
----
-
-## 20. 推荐施工顺序
-
-1. Phase 0:基线审计。
-2. Phase 1A:建立 `author_glossary.json`,四个主 Skill 引用同一术语口径。
-3. Phase 1B:先改四个主 Skill 的最终报告要求,并补“下一步建议”的任务化尾巴。
-4. Phase 3A:建立 `author_error_catalog.json` / `error_catalog.py`,固定异常分类与未知错误降级。
-5. Phase 4A:实现 `review_author_view.py`,在审查报告顶部给一句结论 + 最多 3 条建议。
-6. Phase 2:补 subagent 返回协议,保证主流程有素材可汇总。
-7. Phase 4B:实现 `user_report.py`,先接 `/webnovel-write` 和 `/webnovel-review`。
-8. Phase 5A:补过程提示、开始前预期、确认策略和恢复点。
-9. Phase 5B:实现 `/webnovel-write` 的 run ledger、日志和断点续跑。
-10. Phase 5C:把 blocking 类问题收敛为交互式裁决。
-11. Phase 6:补 prompt integrity、unit tests、behavior eval。
-12. Phase 7:更新 README / docs。
-
-原因:
-
-- 术语表和错误目录是 Author Layer 的单一事实源,先做能避免后续文案和 helper 各翻各的。
-- Skill 契约和下一步尾巴能立刻改善用户可见输出,且风险最低。
-- 审查作者视图是最短路径高收益改造,独立于写章主链,适合早交付。
-- runtime helper 解决长期一致性,但首版保持只读渲染,不改变写作链路。
-- run ledger、自动处理白名单和断点续跑风险更高,放到后面,并优先只覆盖 `/webnovel-write`。
-
----
-
-## 21. 风险与控制
-
-| 风险 | 影响 | 控制 |
-|---|---|---|
-| 只改提示词导致格式漂移 | 不同 Skill 最终报告仍不一致 | Phase 4 增加 runtime helper |
-| 报告太长 | 作者不想看 | 默认只给三段式,技术细节只在必须处理时展开 |
-| 报告太短 | 问题被隐藏 | 明确不可静默场景 |
-| 工程词太多 | 作者读不懂 | 使用术语翻译表 |
-| helper 过早侵入主流程 | 引入新故障点 | 先做只读渲染,不改变写作链路 |
-| 耗时记录不准 | 误导用户 | 先记录步骤级粗粒度耗时,不做精确性能诊断 |
-| subagent 无法直接返回协议字段 | 主流程难汇总 | 主流程包一层 `SubagentRun`,不强迫 agent 改原始产物格式 |
-| token 统计取消后缺少成本感知 | 少一项工程指标 | token 只留内部观察,不做作者最终报告项 |
-| 过程提示太频繁 | 打断作者沉浸感 | 每个主流程最多展示 3-6 个过程节点 |
-| 该问不问 | 创作方向或事实状态被系统擅自决定 | 少打扰策略中列明必须询问场景 |
-| 不该问却问 | 作者被细碎技术问题打断 | 自动处理类问题默认不询问,只在最终报告说明 |
-| 卡住时只报错误 | 作者不知道成果是否丢失 | 卡住反馈必须包含已完成内容和恢复点 |
-| 断点续跑误判产物可信 | 用旧正文或旧审查继续提交 | 断点恢复必须检查文件更新时间、章节号、模式参数和 gate 状态 |
-| 自动续跑覆盖用户手改 | 用户创作被覆盖 | 检测到正文或章纲有新修改时必须询问 |
-| 交互式裁决选项过多 | 用户仍然困惑 | 每次只给 2-3 个明确选择 |
-| 日志泄露敏感信息 | API key 或私密配置外泄 | 日志写入前过滤 `.env`、API key、secret 类字段 |
-
----
-
-## 22. Out of Scope
-
-本计划不包含:
-
-- 自动修复 / 自动重审所有 review blocking issue。
-- 新增多轮 reviewer 重审循环。
-- 改写 chapter commit 或 projection 主链。
-- Dashboard 前端大改版。
-- token 成本统计。
-- 自动生成 PR / git commit。
-- 重构所有 Skill 文案瘦身。
-- 实时进度条 UI。
-- Dashboard 流程进度可视化。
-- 完整事务型工作流引擎。
-- 跨所有 Skill 的全量断点续跑;首版优先 `/webnovel-write`。
-
-这些应单独规划。
-
----
-
-## 23. 最小可落地版本
-
-如果要快速得到收益,建议先做最小版本:
-
-1. 建立 `author_glossary.json`,把术语翻译收成单一事实源。
-2. 建立 `author_error_catalog.json`,至少覆盖 `mainline_ready=false`、`write-gate failed`、`chapter-commit rejected`、projection failed / pending、RAG 降级、artifact schema 不完整;未知错误诚实降级到 `/webnovel-doctor`。
-3. 给四个主 Skill 增加最终报告要求:总状态 + 三段式报告 + 下一步建议。
-4. 给四个 agent 增加可汇总的状态 / 问题 / 自动处理 / 耗时协议。
-5. 在 `review_pipeline.py` 顶部增加审查作者视图:一句结论 + 最多 3 条可执行建议。
-6. 在 `webnovel-write` 的最终报告中强制汇报:
-   - 正文
-   - 审查报告
-   - data artifacts
-   - commit 状态
-   - projection 状态
-   - 备份状态
-   - 下一章是否可继续
-7. 暂不实现完整 `user_report.py`,但把 helper 作为下一阶段明确目标。
-8. 给 `/webnovel-write` 增加 6 个作者友好过程节点和卡住恢复说明。
-9. 在 `/webnovel-write` 中说明重复执行同一命令会尽量从失败步骤继续;实际 run ledger 作为下一阶段。
-10. 最终报告的下一步建议包含任务化说明和可复制命令。
-
-最小版本完成后,作者至少能稳定知道:
-
-- 文件有没有生成。
-- 本章能不能继续往下写。
-- 哪些问题系统已经处理。
-- 哪些问题必须自己确认。
-- 流程中当前走到哪一步。
-- 失败后从哪里恢复。
-- 下一步可以直接执行什么命令。
-
----
-
-## 24. 最终效果
-
-完成后,作者看到的不是一串命令和 JSON,而是一份稳定交付单:
-
-```text
-总状态:已完成,可以继续写第 13 章。
-
-一、产生的文件与完成情况
-- 正文/第0012章-风雪夜归人.md:已生成并通过写作检查。
-- 审查报告/第12章审查报告.md:已生成,无阻断问题。
-- 本章故事事实:已保存,状态、摘要、长期记忆和检索库已更新。
-- 备份:已完成。
-
-二、过程中遇到的问题与异常耗时
-- 已自动处理:检索库更新较慢,系统已等待完成,不影响结果。
-- 建议确认:本章新增角色“沈照”已写入故事资料,建议你看一眼名字是否满意。
-- 必须处理:无。
-
-三、下一步建议
-- 可以继续执行:
-  /webnovel-write 13
-```
-
-这份报告的价值不是“更好看”,而是让作者每次都能安心判断:这一轮到底靠不靠谱,下一步能不能继续。

+ 0 - 1132
docs/architecture/context-minimal-writing-flow-plan-2026-06-05.md

@@ -1,1132 +0,0 @@
-# 写作流程上下文减负重构 Plan
-
-> 日期:2026-06-05
-> 状态:草案 v3
-> 范围:重构 `skills/`、`agents/` 与 `references/` 的提示词与读取方式,减少主 agent 不必要上下文,降低 token 消耗
-> 核心原则:先保住端到端流程,再压缩提示词;Skill 只写调度合同,Agent 自带专业流程,Runtime 负责硬校验
-> 裁剪总纲:所有保留 / 下沉 / 删除决策由第 4 节四条裁剪判据(职责、token、噪音、读取方式)推导,不靠逐条拍清单
-> v3 变更:① 新增第 4 节裁剪判据;② 第 5 节不可删清单收敛为跨层红线;③ 第 6 节纳入 references 与读取方式优化(以 reference-loading-map 为基线);④ 第 12 节验收从文案级断言改为行为 / 契约级;⑤ 明确宿主固定为 Claude Code,工具能力以官方 docs + 本机 Claude Code 版本 + 插件注册名为准
-> 工具能力基线:本 plan 宿主固定为 Claude Code;默认使用本轮涉及的 Claude Code 内置工具(`Read` offset/limit、`Grep`、`Glob`、`Agent`、`Skill`、`AskUserQuestion`、`Write`、`Edit`、`WebSearch`、`WebFetch`)和跨平台 Bash;不建议使用 PowerShell;subagent / Skill 行为按官方 docs 固化,再复核本插件注册名
-
----
-
-## 0. 本版读法
-
-这份 plan 不是为了把所有文件压成某个固定格式,也不是为了让现有测试继续变绿。
-
-本轮真正要做的是:
-
-1. 先确认 init / plan / write / review / query / learn / dashboard / doctor 的完整业务链路(第 3 节)。
-2. 把跨层业务红线提成行为断言(第 5 节、第 12 节)。
-3. 按第 4 节四条裁剪判据精简 Skill、Agent、references,并为每个读取动作定下读取方式(第 4 节、第 6 节)。
-4. 用 runtime、prompt integrity、behavior eval 和 package validator 验收;验收对象是行为与契约,不是提示词文案(第 12 节)。
-
-两条立场必须贯穿全程:
-
-- **测试是探针,不是约束。** 瘦身会让一批文案级断言(`assert "字符串" in text`)变红,这些断言本身就是要清掉的噪音;它们保护的若是真红线,就改写成行为级断言并迁到生产方,而不是为了过测试保留废话。
-- **不能删除第 3 节的端到端流程。** 任何格式化、瘦身、下沉 reference、压缩 schema、改读取方式的动作,都不得删掉第 3 节的业务步骤。第 3 节是红线来源,第 4 节是裁剪依据,二者不冲突。
-- **工具能力以 Claude Code 为准,不臆造也不过度防御。** 本 plan 的执行宿主固定是 Claude Code;不得用临时聊天壳、其他 agent 环境或记忆里的旧工具名替代 Claude Code 官方定义。设计读取与调度时,默认使用 Claude Code 内置工具(`Read` offset/limit、`Grep`、`Glob`、`Agent`、`Skill`、`AskUserQuestion`、`Write`、`Edit`)和 Bash,不写死依赖宿主是否装了 `sed` / `jq`。PowerShell 不作为默认执行方式,避免引入 Windows-only 兼容问题;只有明确 Windows-only 兜底时才使用,并记录兼容风险。subagent / Skill 的已知行为按官方 docs 固化:subagent 不能 spawn subagent,`tools` 是 subagent allowlist,`skills` 字段可把完整 Skill 预加载进 subagent 上下文,Skill 的 `allowed-tools` 是预批准而不是限制。Phase 0 只复核当前 Claude Code 版本、当前插件的真实注册名、frontmatter 是否符合本机 plugin-dev、以及本仓库是否启用了相关工具。
-
----
-
-## 1. 背景
-
-当前 Webnovel Writer 已经不是单一提示词 Skill,而是一个有运行时主链的写作插件:
-
-- `project-status` 判断项目短状态。
-- `doctor` 做阶段感知体检。
-- `placeholder-scan` 捕捉占位符与未补齐内容。
-- `story-system` 生成 `.story-system/` 写前合同树。
-- `write-gate` 在写前、提交前、提交后做批量校验。
-- `context-agent` 负责写前上下文组装。
-- `reviewer` 负责结构化审查。
-- `review-pipeline` 生成报告、指标并落库。
-- `data-agent` 负责提取 commit artifacts。
-- `chapter-commit` 负责写后事实提交和 projection。
-- `projections retry` 负责失败投影补跑。
-- `backup` 负责按书项目根备份。
-
-问题不在于缺少上下文,而是:
-
-1. 主 agent 知道了太多 subagent 内部流程。
-2. Skill 文本混有调度、教程、schema、示例和失败规则。
-3. 有些信息应该由工具按需获取,却提前塞进主上下文。
-4. 同一批上下文在 Skill、Agent、runtime 之间重复出现。
-5. 长 schema 和长示例占用 token,但真正执行时只需要输入、输出、验收。
-
-本轮重构要把写作流程从“主 agent 背完整教程”改成“主 agent 调度,subagent 专业执行,runtime 验收”。
-
----
-
-## 2. 一句话目标
-
-> 主 agent 不传教程,只传任务;subagent 自带教程;runtime 负责验收;流程完整性由断言表兜底。
-
----
-
-## 3. 端到端流程基准
-
-本节是重构的业务基准。后续任何压缩都必须先对照本节。
-
-### 3.1 全局不可变式
-
-所有 Skill / Agent 改写必须保留这些规则:
-
-- 现有项目类 Skill 必须先解析真实书项目根,不能在插件目录写项目文件。
-- `/webnovel-init` 在新项目尚未生成前不能用 `where` 把工作区解析成旧项目;必须用书名安全化得到目标目录。
-- `.story-system/` 是写前合同与写后 commit 的主链事实源。
-- `.webnovel/state.json` 是兼容投影 / read model,不重新变成写后事实真源。
-- 调用 `story-system` 时,章级 query 必须来自详细大纲中的真实本章目标,禁止传 `{章纲目标}`、`第N章章纲目标` 等占位文本。
-- 有具体章节写作 / 审查 / 合同刷新时,必须生成或确认 `.story-system/MASTER_SETTING.json`、`.story-system/volumes/`、`.story-system/chapters/`、`.story-system/reviews/`。
-- 写章主链必须保留 `write-gate --stage prewrite`、`precommit`、`postcommit` 三道 gate。
-- 必须用 `Agent` 工具显式调用 subagent,不得由主流程口头替代 `context-agent`、`reviewer`、`data-agent`、`deconstruction-agent` 的产物。
-- 失败只补跑失败步骤,不全量回退。
-- 能由 runtime 确定性校验的内容,提示词只保留最小说明和阻断边界。
-- reference 只按需读取;不要为了“结构好看”新增没有真实复用价值的 reference。
-- 每个文件产物必须有唯一写入者;提示词要明确“谁写入、谁只返回、谁只校验”。禁止同一产物由主流程和 subagent/runtime 重复写,也禁止大家都只口头产出没人落盘。
-- 每章 `chapter-commit` 前必须做一次项目根内 `git diff` 变更面校验,确认可见文本 / 文件路径变更只出现当前章节和本章流程应产生的文件;它是写入所有权 sanity check,不替代 `write-gate precommit`,也不能证明 SQLite / `.webnovel/` 内部语义正确。
-
-### 3.2 `/webnovel-init` 完整流程
-
-init 不是单纯采集器。精简时必须保留完整生成链:
-
-1. 确认 `CLAUDE_PLUGIN_ROOT` 与 `${CLAUDE_PLUGIN_ROOT}/scripts/webnovel.py` 可用。
-2. 初始化前不使用 `where` 解析旧项目。
-3. 加载最小参考:数据流规范、题材套路库、题材画像;其他 reference 按需加载。
-4. 进入故事核采集前,询问灵感来源;参考书拆解是可选项,不默认执行。
-5. 用户提供参考文本路径或摘录时,必须调用 `webnovel-writer:deconstruction-agent`,不得由 init 主流程口头替代。
-6. `deconstruction-agent` 只返回 `init_reference_research`,不写任何文件,不创建 `.story-system`、`.webnovel`、`设定集`、`大纲`、`正文` 或 canon/read model。
-7. 拆书结果只消费可迁移模式和差异化要求;`quality.passed=false`、`confidence < 0.85` 或有 warnings 时,不能折叠进创意约束包,只能展示风险并让用户确认。
-8. Step 2-6 只能使用用户确认过、并已变形为本书差异化表达的模式。
-9. 采集故事核、角色、金手指、世界观、力量规则、创意约束包。
-10. 输出初始化摘要草案并等待用户明确确认。
-11. 用书名安全化生成 `PROJECT_SLUG` 和 `PROJECT_ROOT`,展示 `WORKSPACE_ROOT`、`PROJECT_SLUG`、`PROJECT_ROOT`,确认后再写文件。
-12. 运行 `webnovel.py init`。
-13. 写入 `.webnovel/idea_bank.json`,只写最终确认的创意约束。
-14. patch `大纲/总纲.md`,补齐故事一句话、核心主线 / 暗线、创意约束、反派分层、爽点里程碑。
-15. init 完成后立即生成 MASTER 合同:
-
-```bash
-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`。
-
-16. 验证 `state.json`、核心设定集、`大纲/总纲.md`、`idea_bank.json`、`.story-system/MASTER_SETTING.json`。
-17. 失败恢复只补缺失字段、重跑最小步骤,不全量重问。
-
-### 3.3 `/webnovel-plan` 完整流程
-
-plan 不是只生成章纲。精简时必须保留规划到写作合同的桥:
-
-1. 解析真实项目根,运行 `placeholder-scan`。
-2. 读取 `.webnovel/state.json` 的初始化配置快照获取 genre;后续写作真源仍是 `.story-system/`。
-3. 读取 `大纲/总纲.md`,确认卷名、章节范围、核心冲突、卷末高潮,不足则阻断。
-4. 跨卷规划时读取最近摘要、核心角色状态、核心关系、活跃伏笔。
-5. 补齐设定基线:世界观、力量体系、主角卡、反派设计;发现冲突先阻断。
-6. 选择目标卷并确认范围。
-7. 生成卷节拍表,必须有中段反转或明确无反转理由,危机链至少递增 3 次。
-8. 生成卷时间线表,必须明确时间体系、时间跨度、倒计时事件。
-9. 生成卷纲骨架,包含卷摘要、人物与反派层级、Strand、爽点、伏笔、约束触发。
-10. 批量生成章纲,默认 `10章/批`,复杂题材可降到 `8章/批`,不建议超过 `12章/批`。
-11. 每章必须包含目标、阻力、代价、时间锚点、章内跨度、与上章时间差、倒计时、爽点、Strand、反派层级、视角 / 主角、关键实体、本章变化、章末未闭合问题、钩子。
-12. 结构化节点必须保留:`CBN`、`CPNs`、`CEN`、`必须覆盖节点`、`本章禁区`。
-13. 新设定只增量写回现有设定集。
-14. 验证节拍表、时间线、详细大纲、时间字段、倒计时、BLOCKER、节点承接。
-15. 生成显式结构化写回文件 `大纲/第{volume_id}卷-总纲写回.json`。
-16. 调用 `master-outline-sync`,只允许更新 V+1 卷锚点与显式伏笔 / open loop,不从自由文本推断。
-17. 调用 `update-state -- --volume-planned ... --chapters-range ...`。
-18. 当本次规划已落到具体章节后,必须用真实章纲目标刷新 Story System runtime 合同:
-
-```bash
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" \
-  story-system "${CHAPTER_GOAL}" --genre "${GENRE}" --chapter {chapter_num} \
-  --persist --emit-runtime-contracts --format both
-```
-
-进入写章前不得保留当前章相关实体的 `[待...]`、`暂名`、`{占位}`。
-
-### 3.4 `/webnovel-write` 完整流程
-
-write 是本轮最重要的验收对象。精简时必须保留:
-
-#### 准备
-
-1. 设置 `WORKSPACE_ROOT`、`SCRIPTS_DIR`、`SKILL_ROOT`。
-2. 运行 `preflight`。
-3. 用 `where` 解析真实 `PROJECT_ROOT`。
-4. 运行 `placeholder-scan`。
-5. 从详细大纲解析真实 `CHAPTER_GOAL`。
-6. 从 `.webnovel/state.json` 的初始化配置快照读取 genre。
-7. 刷新章级 Story System runtime 合同:
-
-```bash
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" \
-  story-system "${CHAPTER_GOAL}" --genre "${GENRE}" --chapter {chapter_num} \
-  --persist --emit-runtime-contracts --format both
-```
-
-8. 运行 prewrite gate:
-
-```bash
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" \
-  write-gate --chapter {chapter_num} --stage prewrite --format json
-```
-
-prewrite 必备:`MASTER_SETTING.json`、`volume_{NNN}.json`、`chapter_{NNN}.json`、`chapter_{NNN}.review.json`。
-
-#### Step 1:写作任务书
-
-必须调用 `webnovel-writer:context-agent`。
-
-输入只给必要参数:章节号、项目根、脚本目录、存储路径 / state 兼容读取路径、输出要求。
-
-输出必须是一份可独立支撑起草的五段写作任务书。上下文不足时返回 blocker,不让主流程自行补脑。
-
-#### Step 2:起草正文
-
-只根据任务书起草。不要重新加载长篇 core constraints 或 anti-AI guide。
-
-有结构化节点时围绕 `CBN -> CPNs -> CEN` 展开。正文必须无占位符。
-
-#### Step 3:审查
-
-默认与 `--fast` 必须调用 `webnovel-writer:reviewer`,`--minimal` 可跳过。
-
-默认写入权责:reviewer 只返回严格 JSON;主流程负责把返回值写入 `${PROJECT_ROOT}/.webnovel/tmp/review_results.json`。如果未来改成 reviewer 直接落盘,必须给 reviewer frontmatter 增加 `Write`,并删除主流程写入动作,二者不能同时存在。
-
-`review_results.json` 落盘后,必须调用:
-
-```bash
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" review-pipeline \
-  --chapter {chapter_num} \
-  --review-results "${PROJECT_ROOT}/.webnovel/tmp/review_results.json" \
-  --metrics-out "${PROJECT_ROOT}/.webnovel/tmp/review_metrics.json" \
-  --report-file "审查报告/第{chapter_num}章审查报告.md" \
-  --save-metrics
-```
-
-写章主链中 reviewer 只调用一轮。`blocking=true` 的问题必须定点修复,或经用户裁决后才进入润色 / 提交。非 blocking issue 交给润色。
-
-#### Step 4:润色
-
-只改表达,不改事实。
-
-可保留现有 reference 加载,但不要让主 Skill 携带长教程:
-
-- `references/polish-guide.md`
-- `references/writing/typesetting.md`
-- `references/style-adapter.md`
-
-顺序:修复非 blocking issue -> 风格适配 -> 排版 -> Anti-AI 终检。
-
-`anti_ai_force_check=fail` 时不进入提交。`--minimal` 仅排版。
-
-#### Step 5:提交
-
-必须调用 `webnovel-writer:data-agent` 生成三份 artifacts:
-
-- `.webnovel/tmp/fulfillment_result.json`
-- `.webnovel/tmp/disambiguation_result.json`
-- `.webnovel/tmp/extraction_result.json`
-
-data-agent 是三份 tmp artifact 的唯一写入者。主流程只检查文件存在与 schema,不重写、不补写 artifact;若 artifact 不合格,定点要求 data-agent 重跑对应产物。
-
-data-agent 不直接写 state / index / summaries / memory / vectors,也不直接写 projection。
-
-随后运行 precommit gate:
-
-```bash
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" \
-  write-gate --chapter {chapter_num} --stage precommit --format json
-```
-
-`write-gate precommit` 通过后,运行提交前 `git diff` 最终校验,确认写入范围正确:
-
-```bash
-if git -C "${PROJECT_ROOT}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
-  git -C "${PROJECT_ROOT}" diff --name-status -- .
-  git -C "${PROJECT_ROOT}" diff --check -- .
-fi
-```
-
-校验规则:
-
-- `diff --name-status` 中不得出现插件目录、其他书项目、其他章节正文或不属于本章流程的手写状态文件。
-- `git diff` 只能检查 git 可见的文件路径和文本差异;如果 `.webnovel/` 被 `.gitignore` 忽略,或 `index.db` / `vectors.db` 是二进制数据库,`git diff` 不能看见其中的表级 / 行级变化。
-- `chapter-commit` 前不应出现由 projection 独占的写入,例如 summaries / memory / vectors;这些只能由 `chapter-commit` 或 `projections retry` 产生。该规则不能只靠 `git diff` 证明,必须结合 runtime gate / read-model 查询。
-- 若项目根不是 git worktree,明确记录“跳过 git diff 校验”,不得因此跳过 `write-gate precommit`。
-- 禁止在这里执行 `git add` / `git commit`;本步骤只读。
-
-数据库 / read-model 语义另走只读校验:
-
-- `review-pipeline --save-metrics` 后,用 runtime 输出和只读查询确认 `review_metrics` 已写入目标章;不要用 `git diff` 判断 SQLite 内容。
-- `chapter-commit` 后,用 `write-gate postcommit` 的 `projection_status` 验证 state / index / summary / memory / vector 全部 `done` 或 `skipped`。
-- 需要查 SQLite 时优先用现有 runtime 查询命令;runtime 暂无命令时,可用 Python `sqlite3` 做只读查询,但不能把查询脚本变成写入路径。
-
-再运行 `chapter-commit`:
-
-```bash
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" chapter-commit \
-  --chapter {chapter_num} \
-  --review-result "${PROJECT_ROOT}/.webnovel/tmp/review_results.json" \
-  --fulfillment-result "${PROJECT_ROOT}/.webnovel/tmp/fulfillment_result.json" \
-  --disambiguation-result "${PROJECT_ROOT}/.webnovel/tmp/disambiguation_result.json" \
-  --extraction-result "${PROJECT_ROOT}/.webnovel/tmp/extraction_result.json"
-```
-
-自动判定:`blocking_count > 0`、`missed_nodes` 非空或 `pending` 非空 -> rejected,否则 accepted。
-
-#### Step 6:提交后验证与备份
-
-必须运行 postcommit gate:
-
-```bash
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" \
-  write-gate --chapter {chapter_num} --stage postcommit --format json
-```
-
-projection_status 五项 `state/index/summary/memory/vector` 必须全部 `done` 或 `skipped`。
-
-projection 失败只补跑:
-
-```bash
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" \
-  projections retry --chapter {chapter_num} --format json
-```
-
-最后备份必须以解析后的 `PROJECT_ROOT` 为准:
-
-```bash
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" backup \
-  --chapter {chapter_num} \
-  --chapter-title "{title}"
-```
-
-禁止从工作区父目录执行裸 `git add .`。
-
-### 3.5 `/webnovel-review` 完整流程
-
-独立 review Skill 可以比写章 Step 3 更完整,但不能伪造 reviewer 结果:
-
-1. 解析真实项目根。
-2. 如目标章缺少 runtime 合同,先用真实 `CHAPTER_GOAL` 刷新 `story-system --emit-runtime-contracts`。
-3. 读取必要 reference:core constraints、review schema;其他按 issue 需要读取。
-4. 加载 `.webnovel/state.json` 兼容投影和待审正文。
-5. 调用 `webnovel-writer:reviewer` 返回严格 JSON,并由主流程写入 `.webnovel/tmp/review_results.json`。
-6. 调用 `review-pipeline --save-metrics` 生成报告与 metrics 并写入 `review_metrics`。
-7. 调用 `update-state -- --add-review ...` 写兼容审查记录。
-8. 存在 `blocking=true` 时,询问用户立即修复或稍后处理。
-
-### 3.6 查询、学习、面板、体检完整流程
-
-`webnovel-query`:
-
-- 只读。
-- 先解析项目根。
-- 先识别查询类型,再用最窄工具。
-- 数据源优先级固定:`.story-system` 写前合同 -> latest accepted `CHAPTER_COMMIT` -> `memory-contract` -> `.webnovel/state.json` / `index.db` fallback。
-- 时序查询优先用 `knowledge query-entity-state` / `knowledge query-relationships`。
-- 降级到 legacy fallback 时必须明说。
-
-`webnovel-learn`:
-
-- 解析项目根。
-- 读取 state 获取当前章节号,失败可用 `source_chapter: null`。
-- 必须调用 `project-memory add-pattern`,不得手写 JSON。
-
-`webnovel-dashboard`:
-
-- 只读。
-- 解析 dashboard 模块与项目根。
-- 前端 dist 缺失时提示,不写项目文件。
-- 面板必须暴露 Story Runtime 主链状态,例如 `/api/story-runtime/health`。
-
-`webnovel-doctor`:
-
-- 只读诊断,不修复、不安装依赖、不启动 dashboard。
-- 先 `project-status --format summary`,再 `doctor --format text`。
-- 缺失项按 runtime 推导的阶段解释,不把 init 项目误判成已写多章项目。
-
----
-
-## 4. 裁剪判据与职责边界
-
-本节是后续所有 Phase 的总纲。每一处“保留 / 下沉 / 删除 / 怎么读”的决策都由下面四条判据推导,不靠在各 Phase 里逐条枚举“必须保留”。
-
-### 4.1 四条裁剪判据
-
-**判据一·职责(谁生产、谁消费,信息就归谁)**
-
-- 主 agent 是调度者,只保留**契约的形状**:调哪个 subagent、得到哪几份产物、产物流向哪个 runtime 命令、什么情况算 blocker。
-- 字段级细则归生产方:artifact 字段名由 data-agent 生产、由 runtime validator 校验,归 `data-agent.md` 与 runtime,不进主 Skill。
-- 一段信息只该有一个真源;出现在第二处即为重复,删到只剩生产方。
-
-**判据二·token(测主 agent 的常驻输入,不测文件行数)**
-
-- 优化对象是“主 agent 写一章实际加载的上下文 token”,不是文件有多长。
-- 只在某 subagent / 某 step 执行时才需要的内容,绝不进主 agent 常驻上下文。
-- 短文件全文读没问题,别为瘦而瘦;靶心是“`always` 全文读的大文件”(见第 6 节)。
-
-**判据三·噪音(只留指令与红线,删元叙述与叮嘱)**
-
-| 噪音类型 | 例子 | 处理 |
-|---|---|---|
-| 重复 | schema 字段在 Skill / Agent 各写一遍 | 删一处,留生产方 |
-| 元叙述(教它怎么想) | reviewer 的“思维链(ReAct):先读→对比→判断” | 删,不改变输出 |
-| 过度否定堆叠 | 一连串“不要…禁止…” | 区分红线与叮嘱:红线留,叮嘱删 |
-| 给错对象 | 主 agent 拿到只有 subagent 用的细则 | 按判据一迁走 |
-| 凑结构 | 为段落模板硬撑的空段 | 删,并松绑对应的结构型测试 |
-
-**判据四·读取方式(该全文读的全文读,该部分读的按需读)**
-
-每个“读文件”动作必须标注读取方式,不默认全文 `cat`:
-
-- **全文读**:短文件,且必须整体理解(schema、铁律、方法论总纲)。
-- **区段读**:只需某节时,用内置 `Grep` 的 content 输出定位标题锚点行号,再用 `Read` 的 offset/limit 取片段——两者都是 Claude Code 内置工具,平台无关、不赌宿主是否装 `sed` / `jq`;提示词已点到节名的直接给锚点。
-- **检索读**:结构化数据(CSV / JSON)优先用本项目 runtime 工具(`reference_search.py`、`knowledge query-*`、专用 schema/validator);runtime 无对应命令需临时按字段取时,优先用 Bash,绝不默认 `cat` 整表;PowerShell 仅作 Windows-only 兜底。
-- **不读**:内容已迁走或不再被消费的文件,清理(见第 6 节)。
-
-### 4.2 职责边界
-
-| 层 | 保留什么 | 不做什么 |
-|---|---|---|
-| Skill | 项目根保护、调度顺序、runtime 命令、Agent 输入契约形状、成功标准、失败恢复 | 不讲 subagent 内部教程,不复制长 schema,不吞掉 runtime gate |
-| Agent | 专业流程、最小必要规则、输出合同、边界 | 不依赖 `agents/references/*`,不写主链投影,不替 runtime commit |
-| Runtime | schema、gate、commit、projection、backup、状态推进 | 不承载 LLM 写作判断 |
-| Skill references | 长示例、详细采集字段、章节节点细则、润色清单 | 不作为 subagent 的隐藏说明书;不被无差别全文加载 |
-
-### 4.3 Agent 单文件约束
-
-本项目默认不新增 `agents/references/*`,避免把 subagent 的说明书拆成不可见碎片。Agent 需要的专业规则(含其产物的完整 schema)必须压缩后保留在单文件内——这正是判据一“字段细则归生产方”的落点。
-
-Claude Code 官方能力允许 subagent 通过 `skills` 字段预加载 Skills;如果 `Skill` 工具留在 subagent 可用工具集中,subagent 也可以在执行中调用未预加载的 project / user / plugin Skills。但本轮不把这作为默认方案:
-
-- 预加载 Skill 会把完整 Skill 内容注入 subagent 上下文,可能抵消本轮上下文减负收益。
-- `skills` 是预加载上下文;`tools: Skill` 是允许运行期调用 Skill,二者不是一回事。
-- subagent 不能再 spawn 其他 subagents;多 agent 串联必须由主流程调度。
-- `Agent` 与 `AskUserQuestion` 不可作为 subagent 可用工具设计;需要用户裁决时回到主流程。
-- 若未来确实需要 subagent 使用某个 Skill,必须在 Phase 0 记录:是通过 `skills` 预加载,还是通过 `tools: Skill` 运行期调用;为什么不能内联进 Agent;增加多少上下文;是否仍低于 token 预算。
-
-### 4.4 Claude Code 工具调用最佳实践
-
-本节用于约束后续 `SKILL.md` / `agents/*.md` 的工具写法。原则:frontmatter 写官方工具名与权限规则;正文用自然语言指派工具任务;不要在提示词里伪造不稳定的内部函数 API。
-
-#### 4.4.1 frontmatter 写法
-
-Skill 的 `allowed-tools` 是预批准规则,不是工具限制。未列出的工具仍可被调用,只是继续受权限设置控制。
-
-```yaml
-allowed-tools: Read, Grep, Glob, Bash, Agent, AskUserQuestion
-```
-
-需要缩窄权限时,用官方 permission rule 格式,并先确认实际注册名:
-
-```yaml
-allowed-tools: Read, Grep, Glob, Bash(python -X utf8 *), Agent
-```
-
-不要在未核实前写猜测式规则,例如 `Agent(webnovel-writer:context-agent)`。plugin scoped agent 的真实注册名必须在 Phase 0 复核。
-
-Agent 的 `tools` 是 allowlist;省略时继承主会话工具,不适合作为生产 agent 默认。生产 agent 按职责列最小工具集,而不是套一份固定模板:
-
-```yaml
-# 只读 research,或只返回 JSON、由主流程落盘的 review
-tools: Read, Grep, Glob, Bash
-
-# 需要把 tmp JSON artifact 或 reviewer raw JSON 落盘时
-tools: Read, Grep, Bash, Write
-```
-
-如果 agent 要预加载 Skill,用 `skills:` 字段;不要把 `Skill` 当成“预加载 reference”写进 `tools`。只有确实要让 subagent 在运行时调用其他 Skills,才把 `Skill` 留在 `tools` 中,并记录原因:
-
-```yaml
-skills:
-  - api-conventions
-```
-
-#### 4.4.2 正文中的 Agent 调用写法
-
-不要写成伪函数:
-
-```text
-Agent(
-  subagent_type: "...",
-  prompt: "..."
-)
-```
-
-推荐写成任务指令:
-
-```text
-Use the Agent tool to run `webnovel-writer:context-agent`.
-
-Task:
-- chapter={chapter_num}
-- project_root=${PROJECT_ROOT}
-- scripts_dir=${SCRIPTS_DIR}
-- Return a five-part writing brief.
-- If context is insufficient, return a blocker.
-```
-
-这样保留了“必须使用 Agent 工具”的红线,但不把 Claude Code 内部 tool-call schema 写死进 Skill 文本。
-
-#### 4.4.3 工具使用表
-
-| 工具 | 默认用途 | 最佳实践 |
-|---|---|---|
-| `Read` | 读取文件正文 | 传绝对路径;长文件优先 offset/limit 区段读;先用 `Grep` 定位锚点;不要让主流程全文读大 reference |
-| `Grep` | 搜索文本和定位标题锚点 | 用 content 输出拿文件路径与行号;用 glob/type 缩小范围;不要把 `Grep -n` 当作工具 API |
-| `Glob` | 找文件 | 用窄 pattern;结果过多时继续收窄;不要用 shell 枚举替代简单文件发现 |
-| `Bash` | 运行跨平台 shell 命令和 runtime 脚本 | 每次调用是独立进程,环境变量不会跨调用保留;一组依赖同一环境变量的命令放在同一次调用里,或每次重新解析变量;优先调用 `webnovel.py` 等 runtime,不用临时脚本替代已有命令 |
-| `PowerShell` | Windows-only 兜底 | 不作为默认流程;不在插件 Skill / hook 中主动写 `shell: powershell`;只在 Bash / 内置工具无法满足且任务明确 Windows-only 时使用,并记录兼容风险 |
-| `Agent` | 调用 subagent | 主流程调度 subagent;正文使用 “Use the Agent tool to run ... / Task ...” 写法;subagent 只返回最终结果,主流程不能依赖其中间 tool 输出 |
-| `Skill` | 调用可复用 Skill | 主会话可按需调用;subagent 预加载用 `skills`,运行期调用才需要 `tools: Skill`;避免为了让 subagent 看到长参考而滥用预加载;`allowed-tools` 中的 `Skill(...)` 是 permission rule,不是正文调用模板 |
-| `AskUserQuestion` | 用户裁决与关键分歧确认 | 只在阻断、剧情裁决、初始化确认等需要用户选择时使用;不放进 subagent 设计 |
-| `Write` / `Edit` | LLM 直接写文件 | 优先让 runtime 写结构化状态和 projection;确需 LLM 写 markdown 时,先明确目标文件与最小修改范围;若提示词要求 subagent 保存 tmp JSON,frontmatter 必须给 `Write`,否则改成“subagent 返回 JSON,由主流程写入” |
-| `WebSearch` / `WebFetch` | 外部信息检索 | 只在用户要求市场趋势、平台风向或时间敏感资料时使用;先 search 后 fetch 确定来源;不得把未经用户确认的外部信息写入 canon |
-
-### 4.5 写入所有权矩阵
-
-本轮瘦身不能只写“产出某文件”,必须写清唯一写入者。默认矩阵如下;后续改任何 Skill / Agent 时必须同步检查这一表。
-
-| 产物 / 路径 | 唯一写入者 | 其他层职责 |
-|---|---|---|
-| 新项目目录、基础 `.webnovel/`、设定集骨架 | `webnovel.py init` | init Skill 只采集、确认、调用 runtime |
-| `.webnovel/idea_bank.json`、`大纲/总纲.md` 初始化补丁 | init 主流程 | deconstruction-agent 不写 canon;runtime 只校验 |
-| `.story-system/MASTER_SETTING.json`、卷 / 章 runtime contracts | `story-system --persist` | Skill / Agent 不手写合同 JSON |
-| `正文/第{chapter}章-*.md` | write 主流程 Step 2 / Step 4 | context-agent 只返回任务书;reviewer/data-agent 不改正文 |
-| `.webnovel/tmp/review_results.json` | 默认 write/review 主流程写入 reviewer 返回的 JSON | reviewer 只返回严格 JSON;若改成 reviewer 写入,必须给 `Write` 并删除主流程写入 |
-| `.webnovel/tmp/review_metrics.json`、审查报告、`review_metrics` 表 | `review-pipeline --save-metrics` | 主流程只调用;reviewer 不写 metrics / report |
-| `.webnovel/tmp/fulfillment_result.json`、`disambiguation_result.json`、`extraction_result.json` | data-agent | 主流程只检查存在与 schema;不重写 artifact |
-| accepted commit、projection、`.webnovel/state.json`、index、summaries、memory、vectors | `chapter-commit` / `projections retry` | data-agent 不直接写;主流程不手写 read-model |
-| 备份产物 | `backup --project-root "${PROJECT_ROOT}"` | 主流程只调用并检查结果 |
-
-验收口径:
-
-- 每个产物必须能回答“谁写、谁不能写、失败谁补跑”。
-- 同一产物若出现两个写入者,删到只剩默认写入者。
-- 若某 agent 被要求写文件,frontmatter `tools` 必须包含 `Write`;否则该 agent 只能返回内容,由主流程写。
-- 每章 `chapter-commit` 前用 `git diff --name-status` 做 git 可见变更面最终校验,只读、不 stage、不 commit;数据库内部变化由 runtime / SQLite 只读查询确认。
-
----
-
-## 5. 不可删红线与可下沉内容
-
-本节不再逐条枚举字段级“必须保留”。字段、示例、采集细则的去留交给第 4 节判据;本节只钉两件事:可下沉对象的范围,和**绝不可删的跨层红线**。
-
-### 5.1 可下沉 / 压缩 / 改按需读(示例,非穷举,按第 4 节判据裁)
-
-- subagent 内部查询流程与推断说明。
-- reviewer 维度解释、元叙述(ReAct)与长示例。
-- data artifact 的完整 payload 细则(迁到 data-agent 单文件,不在主 Skill 重复)。
-- init 详细采集字段、题材列表、反套路库。
-- plan 的 CBN / CPN / CEN 详细规则与示例。
-- polish 长 checklist。
-- 第 6 节列出的 `always` 全文读大 reference(改区段读 / 检索读 / 条目化)。
-
-### 5.2 不可删的跨层红线(穷举,由第 12 节行为测试守护)
-
-这些是跨 Skill / Agent / Runtime 的业务红线,任何瘦身都不得删,且必须有对应的行为 / 契约级断言(第 12 节)。按“能否在 Phase 0 瘦身前先变绿”分两类:
-
-**A. 守护现状(行为已实现,Phase 0 补 / 强化断言即可变绿)**
-
-- 项目根保护、init 目录安全化、用户确认前不写 canon。
-- `placeholder-scan` 出现在 plan / write 关键节点。
-- 真实 `CHAPTER_GOAL` 解析,禁止占位 query。
-- `story-system --persist --emit-runtime-contracts` 的章级刷新。
-- `write-gate` prewrite / precommit / postcommit 三道 gate,顺序不可乱。
-- 必须用 `Agent` 工具显式调用 subagent,不得主流程口头替代。
-- reviewer 原始 JSON 经 `review-pipeline --save-metrics` 落库。
-- data-agent 产三份 artifacts;artifact 字段由 runtime validator 守,不靠主 Skill 文案。
-- `chapter-commit` 是唯一事实提交入口,驱动 projection。
-- postcommit projection 五项验证;失败只 `projections retry`,不回退写作步骤。
-- `backup --project-root "${PROJECT_ROOT}"`,禁止裸 `git add .`。
-- plan 的节拍表、时间线、章纲节点、设定写回、结构化总纲写回、状态更新。
-
-**B. 本轮新契约(当前无实现与断言,随对应 Phase 落地后才转绿;Phase 0 只写断言并标记待实现)**
-
-- 写入所有权矩阵必须成立:reviewer 结果、data artifacts、review metrics、commit/projection/read-model 各有唯一写入者,不重复写、不漏写;含 agent `tools` 与落盘责任一致(reviewer 不授 `Write` 只返回 JSON、data-agent 授 `Write` 写三份 artifact)。(4.5 引入;随 Phase 1–3 对齐 prompt / frontmatter 后转绿)
-- 每章 `chapter-commit` 前必须执行只读 `git diff` 变更面校验;不得出现插件目录、其他章节、其他书项目或未授权状态文件变更;数据库内部变化另用 runtime / SQLite 只读查询确认。(write Skill 当前无此步;随 Phase 1 加入后转绿)
-
-> 字段级条目(如 `planned_nodes` 等具体字段名)**不在本清单**——它们由判据一归到生产方 agent 与 runtime schema,由第 12 节契约级测试守护,不再作为主 Skill 的文案红线。
-
----
-
-## 6. 修改范围
-
-### 6.1 重点文件
-
-| 类型 | 文件 |
-|---|---|
-| 写章 Skill | `webnovel-writer/skills/webnovel-write/SKILL.md` |
-| 写前 Agent | `webnovel-writer/agents/context-agent.md` |
-| 数据 Agent | `webnovel-writer/agents/data-agent.md` |
-| 审查 Agent | `webnovel-writer/agents/reviewer.md` |
-| 拆书 Agent | `webnovel-writer/agents/deconstruction-agent.md` |
-| 初始化 Skill | `webnovel-writer/skills/webnovel-init/SKILL.md` |
-| 规划 Skill | `webnovel-writer/skills/webnovel-plan/SKILL.md` |
-| 审查 Skill | `webnovel-writer/skills/webnovel-review/SKILL.md` |
-| 查询 Skill | `webnovel-writer/skills/webnovel-query/SKILL.md` |
-| 轻量 Skill | `webnovel-learn`、`webnovel-dashboard`、`webnovel-doctor` |
-
-### 6.2 references 与读取方式优化
-
-references 是本轮被低估的 token 面:顶层 `references/` 加各 Skill 的 `references/` 合计 60+ 个文件。优化不靠新增,而靠三件事——以现有加载映射为基线、给每个读取动作定读取方式、清掉已迁走的死文件。
-
-#### 6.2.1 基线:reference-loading-map
-
-`references/index/reference-loading-map.md` 已登记每个 Skill 每个 step 的实际 reference 消费,并已区分三类:
-
-- **直接 Read 的 md**(整文件加载)——问题集中在这里。
-- `reference_search.py` 检索 CSV(按 `--table --query --genre` 返回条目)——已是“按字段读”的范本。
-- `story-system` 间接消费 CSV——已是按需。
-
-CSV 那条线已经做对了,本轮**不重做检索层**;只治“直接 Read 的 md 全文加载”,并把读取方式登记进 loading-map,使其从“读哪些文件”升级为“怎么读这些文件”。
-
-#### 6.2.2 token 靶心:`always` 全文读的大 md(行数已实测、读取方式已核结构)
-
-以下是“直接 Read 且 always / 高频触发”的大文件,是 init / plan / write 每跑必吞的常驻成本。行数为逐行读取脚本实测,「读哪段」已逐个核过文件标题结构,可直接照做:
-
-| 文件 | 行数 | 谁全文读 | 读哪段(而非全文) |
-|---|---|---|---|
-| `references/genre-profiles.md` | 696 | init + plan 双重 always | 当前 genre 的**单个** `### 2.x` 题材段(13 个题材各约 44 行,必要时加「一、字段说明」);单本书只用 1 个题材 → 省约 90% |
-| `skills/webnovel-init/references/creativity/selling-points.md` | 687 | init Step5 always | 「## 9 核心卖点定位模板」做骨架,按需补「### 1.3 黄金公式」「## 7 实战检查清单」 |
-| `references/reading-power-taxonomy.md` | 361 | plan Step7 | 按需分析的类型段:「## 一 钩子类型」/「## 二 爽点模式」/「## 三 微兑现」 |
-| `skills/webnovel-plan/references/outlining/chapter-planning.md` | 322 | plan Step7 | 末尾「## 10 结构化节点规范(CBN/CPNs/CEN)」(+ 需模板时「## 7 章节规划模板」) |
-| `skills/webnovel-init/references/creativity/creativity-constraints.md` | 327 | init Step5 always | 展示评分只取「### 8.1 五维评分」(约 10 行);创意采集读「一 Schema / 六 硬约束 / 八 评分」 |
-| `skills/webnovel-write/references/polish-guide.md` | 351 | write Step4 always | 按「## 2 执行顺序」走,「Phase 1 增补:Anti-AI 规范」词库段单独区段取;**不可条目化进 CSV(csv/README 硬边界)** |
-| `references/shared/cool-points-guide.md` | 313 | plan / review 触发 | 所需爽点维度段;题材适配取「## 九 题材适配」对应题材 |
-
-短文件(如 `references/shared/strand-weave-pattern.md` 111 行)维持全文读,不动。
-
-> 区段读标准手法:先用内置 `Grep` 的 content 输出匹配 `^#{2,4} `,拿到标题锚点行号,再用 `Read` offset/limit 取目标段。上表锚点(含「8.1 五维评分」「结构化节点规范」)已核实真实存在,可直接定位。
-> 注:早先某次 shell 管道统计的行数系统性偏小,本表已改用逐行读取脚本实测;其余 references 入文档时同样以实测为准。
-
-#### 6.2.3 清理死 reference(处置前先核验 CSV 覆盖)
-
-`reference-gap-register.md` 记录的 `skills/webnovel-write/references/writing/*.md → CSV` 迁移已部分完成:loading-map 的「当前非直接调用项」确认 `combat-scenes`、`dialogue-writing`、`emotion-psychology`、`scene-description`、`desire-description`、`genre-hook-payoff-library` 等已不再被直接 Read(由 CSV 承担触发),但文件仍在,合计约 1400 行死内容。处置步骤:
-
-1. 对每个候选 md,先核验 `场景写法.csv` / `写作技法.csv` 是否真覆盖其内容——不盲删。
-2. 已覆盖:删除,或保留为指向 CSV 的空壳。
-3. 未覆盖:先把缺口条目人工补进 CSV(遵循 `csv/README.md` 手动迁移规则),再处置 md。
-
-#### 6.2.4 修正过时的“新增候选”
-
-v2 第 6.2 列的候选已与现状脱节,按现状重判:
-
-| v2 候选 | 现状 | 处置 |
-|---|---|---|
-| `blocking-override-guidelines.md` | 已存在并落位(gap-register 2026-04-16) | 删候选,改为“沿用现有” |
-| `chapter-node-rules.md` | 与现有 `skills/webnovel-plan/references/outlining/chapter-planning.md`「结构化节点规范」重复 | 不新建,对该节做区段读 |
-| `init-flow.md` | 与现有 `skills/webnovel-init/references/init-collection-schema.md` 重复 | 不新建,沿用并改区段读 |
-| `subagent-contracts.md` | 与判据一冲突(契约形状在主 Skill,schema 在生产方 agent) | 不新建 |
-| `polish-checklist.md` | 可作为 `polish-guide.md` 的短 checklist 摘要 | 本轮默认不新建;如确需生成,只做 Skill 内短清单或区段读索引,不迁入 CSV |
-
-原则不变:不为“三段式结构”强行新增 reference;优先改读取方式与清死文件,而非加文件。
-
----
-
-## 7. Phase 0:基线统计、读取审计与红线测试
-
-### 7.1 目标
-
-先确认哪些文本该保留、下沉、删除、改读取方式,并把跨层红线先补成行为测试,形成瘦身前的绿色基线。
-
-### 7.2 要做
-
-1. 统计 8 个 Skill、4 个 Agent 与 references 的体量;references 直接用 reference-loading-map + 行数表(见第 6 节),不另起炉灶。
-2. 对每个文件按第 4 节判据标出归属:主 agent(契约形状)/ subagent / runtime / 下沉 references / 改读取方式 / 第 3 节红线。
-3. **token 基线(方向性参考,非硬考核)**:可选地估算 webnovel-write 主链一次 default 写章时“主 agent 实际加载的上下文”(主 Skill + 内联内容 + 全文读 reference)的近似 token,仅用于判断瘦身方向,不作为通过 / 失败门槛。
-4. **读取审计**:对照 loading-map「直接 Read 的 md」,逐条标 全文 / 区段 / 检索 / 不读(第 6.2.2 靶心优先)。
-5. **先补红线测试再瘦身(区分 5.2 的 A / B 两类)**:A 类(守护现状)中当前只有文案级断言或无断言的,补成行为 / 契约级断言(第 12 节)并在改动前先变绿;B 类(本轮新契约:写入所有权矩阵、agent `tools` 与落盘责任一致性、提交前 `git diff` 只读校验)当前无实现,Phase 0 只写好断言并显式标记为待实现(pytest `xfail` / `skip`,或 eval 中标注 pending),随对应 Phase 落地后转正,不要求 Phase 0 通过。
-6. **工具能力确认(固化官方结论 + 复核本插件)**:记录 Claude Code 版本、官方 docs URL / 日期、Claude Code 当前运行会话的工具摘要、插件实际注册的 agent / skill 名称。内置工具(`Read` offset/limit、`Grep`、`Glob`、`Agent`、`Skill`、`AskUserQuestion`、`Write`、`Edit`)和 Bash 按官方行为设计;PowerShell 不进入默认流程,只记录是否可用、是否启用 PowerShell tool、以及 Windows-only 兜底边界;把官方结论落盘:subagent 不能 spawn subagent,`Agent` / `AskUserQuestion` 不作为 subagent 工具设计,`tools` / `disallowedTools` 控制 subagent 工具边界,`skills` 字段可预加载完整 Skill,Skill 的 `allowed-tools` 是预批准而非限制;复核本插件实际注册名、frontmatter 与 `allowed-tools` 是否符合本机 plugin-dev。
-7. 跑基线验证:
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py -q --no-cov
-python -X utf8 webnovel-writer/scripts/run_behavior_evals.py --format json
-python -X utf8 webnovel-writer/scripts/validate_plugin_package.py --format json
-```
-
-### 7.3 验收
-
-- 得到每个 Skill / Agent / reference 的“保留 / 下沉 / 删除 / 读取方式”清单,**落盘为可追踪文件**,不只是口头结论。
-- (可选)token 基线作为方向性参考已记录;token 不作硬考核指标,缺该数值不阻断验收。
-- 第 5.2 红线都有行为 / 契约级断言:A 类(守护现状)全部通过;B 类(本轮新契约)断言已写好并标记为待实现,不要求 Phase 0 通过。
-- 写入所有权矩阵落盘为可追踪文件;其强制断言归 5.2 B 类,Phase 0 写好待实现,随对应 Phase 落地后由 prompt integrity / behavior eval 检查通过。
-- 当前验证命令通过。
-
----
-
-## 8. Phase 1:精简 `webnovel-write`
-
-### 8.1 目标
-
-让写章 Skill 从“详细教程”变成“调度合同”,但保留完整写章主链。
-
-### 8.2 必须保留
-
-- 模式:默认 / `--fast` / `--minimal`。
-- 准备:`preflight`、`where`、`placeholder-scan`、真实 `CHAPTER_GOAL`、`story-system` 合同刷新、`write-gate prewrite`。
-- 三个 Agent 调用:`context-agent`、`reviewer`、`data-agent`。
-- 起草只吃五段写作任务书。
-- reviewer 原始 JSON + `review-pipeline --save-metrics`。
-- blocking issue 只定点修复或用户裁决,不伪造通过。
-- 润色顺序和 anti-AI 终检。
-- data-agent 三份 artifacts。
-- `write-gate precommit`。
-- `chapter-commit`。
-- `write-gate postcommit`。
-- `projections retry`。
-- `backup --project-root "${PROJECT_ROOT}"`。
-- 成功标准与失败恢复。
-
-### 8.3 可压缩
-
-- context-agent 怎么查上下文。
-- reviewer 怎么逐维度审查。
-- data-agent 完整 payload schema。
-- 长润色教程和大段示例。
-
-### 8.4 Agent 调用目标形态
-
-context-agent:
-
-```text
-Use the Agent tool to run `webnovel-writer:context-agent`.
-
-Task:
-- chapter={chapter_num}
-- project_root=${PROJECT_ROOT}
-- scripts_dir=${SCRIPTS_DIR}
-- storage_path=${PROJECT_ROOT}/.webnovel
-- state_file=${PROJECT_ROOT}/.webnovel/state.json(projection/read-model,仅兼容读取)
-- 先 research,再按 本章硬性约束 -> CBN/CPNs/CEN -> 本章禁区 -> 风格指引 -> dynamic_context 补充参考 的顺序输出五段写作任务书。
-- 上下文不足时返回 blocker。
-```
-
-reviewer:
-
-```text
-Use the Agent tool to run `webnovel-writer:reviewer`.
-
-Task:
-- chapter={chapter_num}
-- chapter_file=${CHAPTER_FILE}
-- project_root=${PROJECT_ROOT}
-- scripts_dir=${SCRIPTS_DIR}
-- Return strict raw JSON only; do not write files.
-- Main flow writes the returned JSON to ${PROJECT_ROOT}/.webnovel/tmp/review_results.json.
-- 不评分,不口头总结。
-```
-
-data-agent:
-
-```text
-Use the Agent tool to run `webnovel-writer:data-agent`.
-
-Task:
-- chapter={chapter_num}
-- chapter_file=${CHAPTER_FILE}
-- project_root=${PROJECT_ROOT}
-- scripts_dir=${SCRIPTS_DIR}
-- output_dir=${PROJECT_ROOT}/.webnovel/tmp
-- 生成 fulfillment_result.json、disambiguation_result.json、extraction_result.json。
-- 不直接写 state/index/summaries/memory/vectors/projection。
-```
-
-落盘责任必须和 agent frontmatter 对齐:本 plan 默认 reviewer 不授予 `Write`,只返回 JSON,由主流程写 `review_results.json`;data-agent 授予 `Write`,直接写三份 artifact。若未来调整,必须同时改模板、frontmatter、prompt integrity,保证单产物单写入者。
-
-### 8.5 风险控制
-
-- `write-gate precommit` 与 `artifact_validator` 兜底 schema。
-- behavior eval 检查三道 gate、三类 artifacts、提交前 git diff 变更面校验、chapter-commit、postcommit、backup。
-- prompt integrity 检查禁止裸 `git add .`、禁止主流程口头替代 subagent、写入所有权矩阵与 agent `tools` 对齐。
-
----
-
-## 9. Phase 2:精简 4 个 Agent
-
-### 9.1 `context-agent`
-
-目标:成为上下文压缩器,输出稳定 `chapter_task_brief`。
-
-必须保留:
-
-- `memory-contract load-context`。
-- `query-entity`、`query-rules`、`get-timeline` 按需查询。
-- load-context 已包含内容不重复查。
-- `.story-system/` 合同优先,`state.json` 仅兼容读取。
-- `chapter_directive.goal` / 章纲真实目标优先,`dynamic_context` 只作写法参考。
-- 五段任务书:开篇委托、这章的故事、这章的人物、怎么写更顺、收在哪里。
-- 红线校验和上下文不足 blocker。
-
-可以删除或压缩:
-
-- 长示例。
-- 过细推断说明。
-- 不必要术语解释。
-
-### 9.2 `data-agent`
-
-目标:只做事实提取和 artifacts 生成。
-
-必须保留:
-
-- 读取正文、实体索引和别名。
-- 三份 artifact 文件名。
-- `fulfillment_result.json` 顶层 `planned_nodes`、`covered_nodes`、`missed_nodes`、`extra_nodes`。
-- `disambiguation_result.json` 顶层 `pending`。
-- `extraction_result.json` 顶层 `accepted_events`、`state_deltas`、`entity_deltas`、`entities_appeared`、`scenes`、`summary_text`。
-- `accepted_events` 子项最小字段:`event_id`、`chapter`、`event_type`、`subject`、`payload`。
-- `state_deltas` 字段命名:`field`、`old`、`new`。
-- `entity_deltas` 字段命名:`entity_type`。
-- 禁止直接写 state / index / summaries / memory / vectors / projection。
-
-可以删除或压缩:
-
-- 各 event_type 完整 payload 长说明。
-- 长 JSON 示例。
-- 兼容旧字段名的详细解释。
-
-### 9.3 `reviewer`
-
-目标:只做可验证事实审查。
-
-必须保留:
-
-- 五个维度:setting、timeline、continuity、character、logic。
-- 每个维度都给 `dimension_results`,无问题也写 `pass`。
-- 每个 issue 有 evidence 和 fix_hint。
-- 不评分、不评价文笔、不建议新增剧情、不暴露未发生大纲。
-- 输出严格 JSON。
-
-必须删除或改写:
-
-- “思维链 / ReAct”类表述。
-- 过长审查教程。
-
-### 9.4 `deconstruction-agent`
-
-目标:拆参考书的可迁移模式,不污染新书 canon。
-
-必须保留:
-
-- quick / deep / auto 路由。
-- 只有书名/平台无文本时,不得凭记忆编造黄金三章、角色、设定、剧情。
-- 不写任何文件。
-- 不生成新书 canon。
-- 输出 `init_reference_research` JSON。
-- `quality`、`resume_state`、`do_not_copy`、`canon_contamination_warnings`。
-- 快速模式、深度模式、情节点、质量门控、抽象转化规则。
-
-可以压缩:
-
-- 长质量门控表。
-- 超长 schema 细节。
-- 深度拆解分阶段长说明。
-
----
-
-## 10. Phase 3:精简 init / plan / review Skills
-
-### 10.1 `webnovel-init`
-
-Skill 可以更短,但必须保留第 3.2 节完整链。
-
-压缩方向:
-
-- 详细采集字段保留在现有 `skills/webnovel-init/references/init-collection-schema.md`,对其做区段读;不新建 init-flow.md(见 6.2.4)。
-- 题材列表只保留 canonical 集合和少量示例。
-- CLI 参数长表可收缩为“参数来自采集对象”,但要保留执行 init 的事实。
-- 创意约束细则、反套路库、世界观设计指南按需读取。
-
-不可删:
-
-- Step 1.5 灵感来源询问。
-- deconstruction-agent 调用边界。
-- 用户确认前不写 canon。
-- project root 安全化和确认。
-- `idea_bank.json`。
-- patch 总纲。
-- init 后 MASTER_SETTING 生成。
-- 验证与最小回滚。
-
-### 10.2 `webnovel-plan`
-
-Skill 可以更短,但必须保留第 3.3 节完整链。
-
-压缩方向:
-
-- CBN / CPN / CEN 细则保留在现有 `skills/webnovel-plan/references/outlining/chapter-planning.md`「结构化节点规范」,对该节做区段读;不新建 chapter-node-rules.md(见 6.2.4)。
-- 长 reference 表可改为“按阶段触发读取”。
-- 结构化节点示例下沉。
-
-不可删:
-
-- placeholder-scan。
-- 跨卷状态读取。
-- 设定基线补齐。
-- 卷节拍表。
-- 卷时间线。
-- 卷纲。
-- 批量章纲。
-- 设定写回。
-- 显式 `大纲/第{volume_id}卷-总纲写回.json`。
-- `master-outline-sync`。
-- `update-state`。
-- 真实 `CHAPTER_GOAL` 刷新 Story System 合同。
-
-### 10.3 `webnovel-review`
-
-Skill 可以更短,但必须保留第 3.5 节完整链。
-
-压缩方向:
-
-- reviewer 审查方法留给 reviewer。
-- 证据查询过程不在 Skill 展开。
-
-不可删:
-
-- 合同缺失时补 `story-system`。
-- reviewer Agent 调用。
-- `review-pipeline --save-metrics`。
-- `update-state --add-review`。
-- blocking 用户裁决。
-
----
-
-## 11. Phase 4:精简轻量 Skills
-
-### 11.1 `webnovel-query`
-
-目标:查询先分类,再用最窄工具。
-
-保留:
-
-- 只读。
-- 项目根保护。
-- `.story-system` -> latest accepted commit -> memory-contract -> projection fallback 的优先级。
-- 降级说明。
-
-优化:
-
-- 不默认全量 `memory-contract load-context`;按查询类型调用最窄工具。
-- 角色状态用 `knowledge query-entity-state`。
-- 关系用 `knowledge query-relationships`。
-- 规则用 `memory-contract query-rules`。
-- 伏笔用 open-loop 查询。
-
-### 11.2 `webnovel-learn`
-
-目标:保持极简。
-
-保留:
-
-- 项目根保护。
-- 读当前章节号。
-- `project-memory add-pattern`。
-- 不手写 JSON。
-
-### 11.3 `webnovel-dashboard`
-
-目标:保持只读面板。
-
-保留:
-
-- 只读边界。
-- `story-runtime/health`。
-- 项目根解析。
-- 前端 dist 校验。
-
-可调整:
-
-- 不默认安装依赖;缺依赖时提示命令。
-- 启动前可运行轻量检查。
-
-### 11.4 `webnovel-doctor`
-
-目标:保持只读诊断。
-
-保留:
-
-- `project-status` 先行。
-- `doctor` 阶段感知检查。
-- 不修复、不安装、不启动 dashboard。
-
-可调整:
-
-- frontmatter description 改成简洁中文触发型描述。
-
----
-
-## 12. Phase 5:测试与行为验证
-
-### 12.1 修改文件
-
-- `webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py`
-- `webnovel-writer/evals/fixtures/behavior/fast.json`
-- 新增行为 / 契约级断言;删除或迁移过时的文案级断言(见 12.2)。
-
-### 12.2 验收原则:行为 / 契约级,不锚文案
-
-总原则:断言“做没做对”,不断言“文案里有没有某串字”。`fast.json` 的 `commit_projection_runtime`(真跑 commit → projection 并断言状态)是样板;`assert "字符串" in text` 是要逐步退役的形式。
-
-#### 必保行为(用行为 / 契约断言守,不锚具体措辞)
-
-- 写章主链:三道 gate(prewrite / precommit / postcommit)按序落地;reviewer 一轮;blocking 只定点修复或用户裁决;data-agent 三份 artifacts;`write-gate precommit` 后、`chapter-commit` 前执行只读 `git diff` 变更面校验;数据库 / `.webnovel/` read-model 的表级语义用 runtime / SQLite 只读查询确认;`chapter-commit` 提交并驱动 projection;postcommit 五项 projection 全 done/skipped;失败只 `projections retry`;`backup --project-root`。
-- artifact 契约:三份 artifact 字段由 runtime schema(`chapter_commit_schema` / `story_event_schema` / `schemas`)校验;**新增 precommit 负向用例**——缺 `missed_nodes` / `pending` / 关键字段时 precommit 必须拦截。这取代“主 Skill 文案里必须出现字段名”的检查。
-- 8 个 Skill 可发现、frontmatter 合法、description 可触发、中文优先。
-- 4 个 Agent 单文件、frontmatter 含 `name`/`description`/`model`/`color`、`tools` 最小集且与落盘责任匹配、不依赖 `agents/references/*`。
-- Agent 边界:data-agent 不直接写 projection;reviewer 只输出结构化 JSON 且覆盖 5 维;context-agent 输出五段任务书、用 `load-context`;deconstruction-agent 不写文件、产 `init_reference_research`、防 canon 污染、保留 Step 1.5 与确认门。
-- 写入所有权:`review_results.json`、三份 data artifacts、review metrics/report、state/index/summaries/memory/vector 都有唯一写入者;prompt 中不得同时要求两个层写同一文件,也不得只要求“生成”而不说明落盘方;对 SQLite / 二进制 read-model,验收看 runtime 查询结果,不看 `git diff` 内容。
-- plan 保留 `.story-system/` 主链与节拍表/时间线/章纲节点/总纲写回/状态更新;query / dashboard / doctor 只读不写项目文件。
-
-#### 该删 / 该迁 / 该松绑的现有断言
-
-| 现有断言 | 问题 | 处置 |
-|---|---|---|
-| `test_webnovel_write_data_agent_prompt_requires_extraction_schema` | 逐字要求主 Skill 写出 schema 字段名,与判据一冲突 | **删**;字段保障迁到 data-agent 单文件 + precommit 负向用例 |
-| `test_data_agent_is_described_as_extraction_only...` 的字段名清单 | 检查 data-agent.md 含字段名(文案级) | 保留并加强为 data-agent 单文件 schema 的契约校验 |
-| `test_agent_template_structure`(要求 1–8 连续编号段) | 强迫凑结构;删 reviewer 的 ReAct 节会误伤 | **松绑**:删 ReAct 后重排编号或下调段数要求,别为过测试留空段 |
-| 各 `assert "字符串" in SKILL.md` 措辞锚定项 | 锚文案、阻碍瘦身 | 改为行为 / 契约断言,或迁到生产方 |
-
-### 12.3 验证命令
-
-文档与提示词层:
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py -q --no-cov
-python -X utf8 webnovel-writer/scripts/run_behavior_evals.py --format json
-python -X utf8 webnovel-writer/scripts/validate_plugin_package.py --format json
-```
-
-稳妥回归:
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests webnovel-writer/scripts/tests -q --no-cov
-```
-
-### 12.4 验收标准
-
-- 8 个 Skill 仍可发现;4 个 Agent frontmatter 合法。
-- 插件结构符合官方 `plugin-structure`;Skill / Agent 修改符合 `plugin-dev`。
-- behavior eval、package validator、prompt integrity 全过(在退役文案断言、补齐行为 / 契约断言之后)。
-- 第 5.2 跨层红线全部有行为 / 契约级断言守护。
-- 写入所有权矩阵通过检查:每个产物唯一写入者明确,agent `tools` 与写入责任一致,提交前 git diff 校验存在且只读;数据库语义校验通过 runtime / SQLite 只读查询覆盖。
-- token:写章主链“主 agent 写一章加载的上下文”方向上应较 Phase 0 下降;token 为方向性参考,不作硬性数值门槛,不因缺数值或降幅不足而判定验收失败。
-- 主 Skill 不再携带 subagent 长流程与 data schema;schema 真源唯一在生产方 agent + runtime。
-- references:第 6.2.2 靶心大文件已改按需读;死 reference 已核验并处置。
-- 第 3 节端到端流程未被瘦身删掉。
-
----
-
-## 13. 官方 plugin-dev 约束
-
-后续所有修改必须遵循本机官方插件指导:
-
-```text
-C:\Users\lcy\.claude\plugins\marketplaces\claude-plugins-official\plugins\plugin-dev
-```
-
-落地规则:
-
-- 插件结构遵循 `plugin-structure`:`.claude-plugin/plugin.json` 位于插件根的 `.claude-plugin/`;`skills/`、`agents/`、`hooks/`、`scripts/` 保持插件根层级。
-- Skill 遵循 `skill-development`:每个 Skill 一个目录,必须有 `SKILL.md`;frontmatter 至少包含 `name` 和具体触发型 `description`;详细资料可以放入该 Skill 自己的 `references/`,按需读取。
-- Agent 遵循 `agent-development`:每个 Agent 是 `agents/*.md` 单文件;frontmatter 包含 `name`、`description`、`model`、`color`,`tools` 限定到最小必要集合。
-- 本项目的 4 个 Agent 由 Skill 显式调用,不依赖自动触发;Agent `description` 只需说明调用方、职责和交付产物。
-- Agent 不使用外部 reference 作为隐藏说明书。
-- Hook 相关修改继续遵循 `hook-development`:插件级 `hooks/hooks.json` 使用 wrapper 格式,命令路径使用 `${CLAUDE_PLUGIN_ROOT}`。
-- 每轮改完插件组件后,按 `plugin-validator` 思路校验 manifest、skills、agents、hooks、README、LICENSE、敏感信息和路径可移植性。
-
----
-
-## 14. 风险与控制
-
-| 风险 | 影响 | 控制 |
-|---|---|---|
-| 为了格式精简删掉真实流程 | 写作链断裂 | 第 3 节作为流程断言,prompt integrity / behavior eval 覆盖 |
-| 主 Skill 过度精简 | Agent 输入不足 | Skill 保留 Agent 最小输入合同 |
-| schema 压缩后 agent 漏字段 | commit 前失败 | `artifact_validator` 与 `write-gate precommit` 阻断 |
-| Agent 单文件过短 | 专业执行质量下降 | 单文件保留最小必要流程、边界和输出合同 |
-| subagent 落盘责任与 `tools` 不一致 | reviewer / data-agent 产物缺失,后续 runtime 找不到 tmp JSON | 要么给对应 agent `Write`,要么由主流程接收 JSON 后写文件;prompt integrity 检查两者一致 |
-| 产物重复写入或没人写入 | 结果互相覆盖,或后续 runtime 找不到文件 | 第 4.5 写入所有权矩阵;行为测试检查每个产物唯一写入者 |
-| `git diff` 校验误变成 git 提交流程 | 污染工作区或把无关文件 stage | 只允许 `git diff --name-status` / `git diff --check`;禁止 `git add` / `git commit` |
-| 误以为 `git diff` 能检查数据库内容 | SQLite / 向量库 / ignored `.webnovel/` 的内部错误漏检 | `git diff` 只查文件面;数据库与 read-model 必须由 runtime gate、projection_status、SQLite 只读查询验证 |
-| reference 下沉过多 | 执行时忘记读取 | reference 只给长细则,主流程保留触发条件 |
-| 区段读锚点漂移 | 读到错段或空内容 | reference 用稳定标题锚点;区段读失败时回退全文读并告警 |
-| 文案级断言阻碍瘦身 | 误把测试当约束 | 退役文案断言、改行为 / 契约级(第 12 节);删 / 迁前确认非红线 |
-| query 默认少查导致答案不完整 | 查询质量下降 | 分类后按需补查,回答中说明降级 |
-| state.json 被误当事实源 | 写后事实漂移 | 明确 `.story-system` 与 accepted commit 优先级 |
-| init 拆书污染 canon | 新书设定侵权或撞梗 | deconstruction-agent 不写文件,init 用户确认后才写入差异化模式 |
-
----
-
-## 15. 推荐施工顺序
-
-1. Phase 0:基线统计、读取审计、token 基线,**先把第 5.2 A 类红线补成行为测试形成绿色基线(B 类新契约写好断言、标记待实现)**。
-2. `webnovel-write`:先保全写章主链,再瘦主 Skill;同步退役锚文案的 schema 断言。
-3. `context-agent`:稳定五段任务书和上下文压缩。
-4. `data-agent`:schema 作为单文件唯一真源,删长 payload 教程;加 precommit 负向用例。
-5. `reviewer` + `webnovel-review`:删 ReAct 元叙述并重排段号,统一结构化审查链。
-6. `webnovel-init` + `deconstruction-agent`:保留确认门和防 canon 污染。
-7. `webnovel-plan`:保留规划到合同的桥。
-8. references 与读取方式:按第 6.2 改靶心大文件读取方式、清死文件、登记进 loading-map。
-9. `webnovel-query` / `webnovel-learn` / `webnovel-dashboard` / `webnovel-doctor`。
-10. 测试与 eval 收口。
-
-原因:
-
-- 写章链路最吃 token,也最容易受上下文污染影响;`webnovel-write` 承载最多 subagent 内部细节,先改收益最大。
-- 先补红线行为测试、固定绿色基线,后续每个 Skill 才能放心瘦身且不误删红线。
-- reference 读取优化独立成步,因为它跨多个 Skill 且以 loading-map 为统一基线。
-
----
-
-## 16. 最终效果
-
-目标完成后,写章链路应该是:
-
-```text
-preflight / where / placeholder-scan
-  ↓
-解析真实 CHAPTER_GOAL
-  ↓
-story-system 刷新 runtime contracts
-  ↓
-write-gate prewrite
-  ↓
-context-agent 生成五段写作任务书
-  ↓
-主 agent 根据任务书起草
-  ↓
-reviewer 审查
-  ↓
-review-pipeline 落库
-  ↓
-主 agent 定点修复和润色
-  ↓
-data-agent 生成 artifacts
-  ↓
-write-gate precommit
-  ↓
-chapter-commit
-  ↓
-write-gate postcommit
-  ↓
-projection retry(仅失败时)
-  ↓
-backup
-```
-
-每一层只知道自己需要知道的东西:
-
-- 主 agent 知道调度和不可跳过的 runtime 命令。
-- context-agent 知道上下文压缩。
-- reviewer 知道审查。
-- data-agent 知道事实提取。
-- runtime 知道校验、提交、投影和状态推进。
-
-这才是本轮上下文减负的核心:不是把文档变短,而是让每一段上下文只在正确的时刻出现。

+ 0 - 109
docs/architecture/overview.md

@@ -1,109 +0,0 @@
-# 系统架构与模块设计
-
-## 核心理念
-
-### 真源划分
-
-- 写前真源:`.story-system/MASTER_SETTING.json`、`volumes/`、`chapters/`、`reviews/`
-- 写后真源:accepted `CHAPTER_COMMIT`
-- `.webnovel/state.json`、`index.db`、`summaries/`、`memory_scratchpad.json`:只作为投影 / read-model
-- `references/genre-profiles.md`:fallback-only
-
-### 防幻觉三定律
-
-| 定律 | 说明 | 执行方式 |
-|------|------|----------|
-| **大纲即法律** | 遵循大纲,不擅自发挥 | Context Agent 强制加载章节大纲 |
-| **设定即物理** | 遵守设定,不自相矛盾 | Reviewer Agent 内置一致性审查 |
-| **发明需识别** | 新实体必须入库管理 | Data Agent 自动提取并消歧 |
-
-### Strand Weave 节奏系统
-
-| Strand | 含义 | 理想占比 | 说明 |
-|--------|------|----------|------|
-| **Quest** | 主线剧情 | 60% | 推动核心冲突 |
-| **Fire** | 感情线 | 20% | 人物关系发展 |
-| **Constellation** | 世界观扩展 | 20% | 背景/势力/设定 |
-
-节奏红线:
-
-- Quest 连续不超过 5 章
-- Fire 断档不超过 10 章
-- Constellation 断档不超过 15 章
-
-## 总体架构图
-
-```text
-┌─────────────────────────────────────────────────────────────┐
-│                      Claude Code                           │
-├─────────────────────────────────────────────────────────────┤
-│  Skills (7个):                                             │
-│    init / plan / write / review / query / learn / dashboard │
-├─────────────────────────────────────────────────────────────┤
-│  Agents (3个):                                             │
-│    Context Agent / Data Agent / Reviewer (含六维审查)        │
-├─────────────────────────────────────────────────────────────┤
-│  Data Layer:                                               │
-│    state.json / index.db (SQLite) / vectors.db             │
-├─────────────────────────────────────────────────────────────┤
-│  Story System:                                             │
-│    .story-system/ (合同·提交·事件)                           │
-└─────────────────────────────────────────────────────────────┘
-```
-
-## Agent 分工
-
-### Context Agent(读)
-
-- 文件:`agents/context-agent.md`
-- 职责:在写作前构建"创作任务书",提供本章上下文、约束和追读力策略。
-
-### Data Agent(写)
-
-- 文件:`agents/data-agent.md`
-- 职责:从正文提取 `accepted_events / state_deltas / entity_deltas / summary_text` 等 commit artifacts,交给 `chapter-commit` 驱动 projection writers 更新 `state.json`、`index.db`、摘要与长期记忆。
-
-### Reviewer(审)
-
-- 文件:`agents/reviewer.md`
-- 职责:章节质量审查,内部包含以下六个审查维度:
-
-| 审查维度 | 检查重点 |
-|----------|----------|
-| High-point Checker | 爽点密度与质量 |
-| Consistency Checker | 设定一致性(战力/地点/时间线) |
-| Pacing Checker | Strand 比例与断档 |
-| OOC Checker | 人物行为是否偏离人设 |
-| Continuity Checker | 场景与叙事连贯性 |
-| Reader-pull Checker | 钩子强度、期待管理、追读力 |
-
-## Story System(合同驱动体系)
-
-Story System 以 `.story-system/` 为独立运行面,由以下几部分组成:
-
-- **合同种子**:`MASTER_SETTING.json` + 章节合同 + 反模式配置
-- **合同优先运行时**:卷合同 (`volumes/`) + 审查合同 (`reviews/`) + 写前校验
-- **章节提交链**:`commits/chapter_XXX.commit.json` + state/index/summary/memory 投影
-- **事件审计链**:`events/chapter_XXX.events.json` + 修订提案 + 覆写账本
-
-当前默认即 contract-first + commit-first:`.story-system/` 为主链真源,旧的 `.webnovel/*` 降级为投影 / read-model,`preflight` 与 dashboard 暴露 runtime health。
-
-核心链路:
-
-```text
-story-system --persist
-    -> 写入合同种子(MASTER_SETTING.json 等)
-story-system --emit-runtime-contracts --chapter N
-    -> 生成运行时合同 + 写前校验
-chapter-commit --chapter N
-    -> 提交 accepted commit + 执行各投影写入
-story-events --chapter N / --health
-    -> 事件审计与健康检查
-preflight / dashboard
-    -> story runtime health / fallback 状态 / latest commit 状态
-```
-
-事件审计链不另起第二套投影循环,事件路由仅负责声明式激活 writer,
-实际执行入口仍是 `ChapterCommitService.apply_projections()`。
-
-详细设计见:`docs/archive/architecture/story-system-phase5.md`

+ 0 - 162
docs/architecture/phase0-slimming-and-read-audit-2026-06-06.md

@@ -1,162 +0,0 @@
-# Phase 0 审计:瘦身与读取方式清单(2026-06-06)
-
-> 只读审计 + 实测。本文件只「记录可追踪结论」,不修改任何 skill / agent / reference / 测试文件。
-> 目的:把 `context-minimal-writing-flow-plan-2026-06-05.md`(下称 Plan)第 4 节四条裁剪判据、§4.2 职责表、§6.2 读取方式,落成各瘦身 Phase 可直接照做的操作清单。
-> 不复述 Plan,只给「文件 → 读取方式 / 归属 / 处理 Task」的可执行行。
->
-> 工作目录(git 仓库根 = worktree):`D:/wk/novel skill/webnovel-writer/.worktrees/context-minimal-flow`
-> 插件目录(嵌套):仓库根下 `webnovel-writer/` 子目录。下表路径均相对插件目录。
-> 行数列为 `wc -l` 实测(2026-06-06,本机 Bash),不沿用 Plan 任何预填数字。
-
----
-
-## 用法
-
-- **Section A** 覆盖 Plan §6.2.1 reference-loading-map「直接 Read 的 md」全部条目,逐条定读取方式;Phase 4(Task #13/#14)按此改读取、清死文件。
-- **Section B** 覆盖 8 Skill + 4 Agent,按 §4.2 职责表给保留要点与可压缩项;Phase 1–5(Task #5–#16)按此瘦身。
-- 读取方式四分类(判据四 + §6.2.2):**全文读** / **区段读** / **检索读**(CSV,单列于 A.3) / **不读**(清理候选)。
-- 区段读锚点列给的是**文件里的真实标题原文**(已 Grep `^#{1,4} ` 核实)。Plan 正文锚点用了简写(去掉「、」、缩短标题),与真实标题**不逐字相等**;区段读时必须按本表真实锚点匹配,否则 `Grep ^## 一 ` 会匹配不到 `## 一、`。差异见 A.4「锚点校验与 FLAG」。
-
----
-
-## Section A — 读取方式审计(逐个「直接 Read 的 md」)
-
-行数 = 实测。「处理 Task」:靶心大文件区段读 = #13;死文件清理 = #14;其余「按需 / 全文」随其消费 Skill 的瘦身 Task 走(见 Section B),读取方式本身不单独占 Task,但 Phase 4 登记 loading-map 时一并写入(#13)。
-
-### A.1 七个靶心大文件(区段读 → Task #13)
-
-`always` / 高频触发的大 md,是 init / plan / write 每跑必吞的常驻成本。锚点已逐个 Grep 核实存在。
-
-| 文件 | 实测行数 | 读取方式 | 区段:真实稳定标题锚点(原文) | 处理 Task |
-|---|---|---|---|---|
-| `references/genre-profiles.md` | 696 | 区段读 | 当前 genre 的**单个** `### 2.x`(`### 2.1`…`### 2.13`,13 题材各约 44 行);按需加 `## 一、Profile 字段说明`。单本书只用 1 题材 → 省约 90% | #13 |
-| `skills/webnovel-init/references/creativity/selling-points.md` | 687 | 区段读 | `## 9. 核心卖点定位模板`(骨架);按需补 `### 1.3 核心卖点黄金公式`、`## 7. 实战检查清单` | #13 |
-| `references/reading-power-taxonomy.md` | 361 | 区段读 | 按需取 `## 一、钩子类型` / `## 二、爽点模式` / `## 三、即时满足/微兑现` | #13 |
-| `skills/webnovel-plan/references/outlining/chapter-planning.md` | 322 | 区段读 | `## 10. 结构化节点规范(CBN/CPNs/CEN)`(L299,至文件末);需模板时加 `## 7. 章节规划模板` | #13 |
-| `skills/webnovel-init/references/creativity/creativity-constraints.md` | 327 | 区段读 | 展示评分取 `### 8.1 五维评分`(L262,约 10 行);创意采集读 `## 一、创意包 Schema (Idea Package)` / `## 六、硬约束驱动创意 (Hard Constraints)` / `## 八、评分系统 (Scoring System)` | #13 |
-| `skills/webnovel-write/references/polish-guide.md` | 351 | 区段读 | 主路径 `## 2. 执行顺序(必须按序)`;Anti-AI 词库单独区段 `## 98:Phase 1 增补:Anti-AI 规范`(另有 `## 2A. Anti-AI 检测细则`)。**不可条目化进 CSV(csv/README 硬边界)** | #13 |
-| `references/shared/cool-points-guide.md` | 313 | 区段读 | 所需爽点维度段;题材适配取 `## 九、题材适配`(L194)下对应题材 | #13 |
-
-> 区段读手法:先 `Grep` content 输出匹配 `^#{1,4} ` 拿锚点行号,再 `Read` offset/limit 取段。两者均 Claude Code 内置,平台无关。
-
-### A.2 其余「直接 Read 的 md」(全文 / 区段 / 不读)
-
-判据:≤ ~150 行且需整体理解 → 全文读;明显大且只取某节、且 Plan 标了「按需 / 需X」触发 → 区段读;当前非直接调用 → 不读。
-
-| 文件 | 实测行数 | 读取方式 | 区段:真实锚点(若区段读) | 处理 Task |
-|---|---|---|---|---|
-| **webnovel-init** | | | | |
-| `skills/webnovel-init/references/system-data-flow.md` | 43 | 全文读 | — | #10 |
-| `skills/webnovel-init/references/genre-tropes.md` | 183 | 区段读 | 当前题材段(题材套路库,按 genre 取对应小节) | #10 |
-| `skills/webnovel-init/references/worldbuilding/world-rules.md` | 86 | 全文读 | — | #10 |
-| `skills/webnovel-init/references/worldbuilding/faction-systems.md` | 179 | 区段读(按需) | always 触发但偏长;按当前世界观需要的小节取 | #10 |
-| `skills/webnovel-init/references/worldbuilding/power-systems.md` | 160 | 区段读(按需) | 仅「力量体系」相关项触发;取对应小节 | #10 |
-| `skills/webnovel-init/references/worldbuilding/character-design.md` | 111 | 全文读 | — | #10 |
-| `skills/webnovel-init/references/worldbuilding/setting-consistency.md` | 215 | 区段读 | always 触发但偏长;取一致性校验小节 | #10 |
-| `skills/webnovel-init/references/creativity/creative-combination.md` | 510 | 区段读 | **非 7 靶心但 510 行**:仅「需创意组合」触发,按当前混搭轴取小节,禁止全文 | #10 |
-| `skills/webnovel-init/references/creativity/inspiration-collection.md` | 298 | 区段读 | 仅 Step1.5 灵感来源询问触发;取所需采集小节 | #10 |
-| `skills/webnovel-init/references/creativity/anti-trope-game.md` | 170 | 区段读(按需) | 仅 game 题材 + 「需反套路」触发;取对应反套路项 | #10 |
-| `skills/webnovel-init/references/creativity/anti-trope-rules-mystery.md` | 214 | 区段读(按需) | 仅 rules-mystery 题材触发 | #10 |
-| `skills/webnovel-init/references/creativity/anti-trope-urban.md` | 169 | 区段读(按需) | 仅 urban 题材触发 | #10 |
-| `skills/webnovel-init/references/creativity/anti-trope-xianxia.md` | 159 | 区段读(按需) | 仅 xianxia 题材触发 | #10 |
-| **webnovel-plan** | | | | |
-| `templates/output/大纲-卷节拍表.md` | 38 | 全文读 | — 模板,须整体套用 | #11 |
-| `templates/output/大纲-卷时间线.md` | 51 | 全文读 | — 模板,须整体套用 | #11 |
-| `references/shared/strand-weave-pattern.md` | 111 | 全文读 | — Plan §6.2.2 明示短文件维持全文 | #11 |
-| `references/outlining/plot-signal-vs-spoiler.md` | 53 | 全文读 | — | #11 |
-| `skills/webnovel-plan/references/outlining/conflict-design.md` | 277 | 区段读 | 仅「需冲突」触发;取对应冲突类型小节 | #11 |
-| `skills/webnovel-plan/references/outlining/genre-volume-pacing.md` | 84 | 全文读 | — 短,卷级节奏整体读 | #11 |
-| **webnovel-write** | | | | |
-| `skills/webnovel-write/references/writing/typesetting.md` | 60 | 全文读 | — Step4 always,短 | #5 |
-| `skills/webnovel-write/references/style-adapter.md` | 71 | 全文读 | — Step4 always,短 | #5 |
-| **webnovel-review** | | | | |
-| `references/shared/core-constraints.md` | 111 | 全文读 | — always 铁律,须整体 | #8/#12 |
-| `references/review-schema.md` | 59 | 全文读 | — schema,须整体 | #8/#12 |
-| `references/review/blocking-override-guidelines.md` | 47 | 全文读 | — 按需,短 | #12 |
-| **webnovel-query** | | | | |
-| `skills/webnovel-query/references/system-data-flow.md` | 343 | 区段读 | 偏长,按查询类型取数据源优先级小节 | #15 |
-| `skills/webnovel-query/references/advanced/foreshadowing.md` | 120 | 全文读 | — 按需,伏笔查询整体读 | #15 |
-| `skills/webnovel-query/references/tag-specification.md` | 154 | 全文读(边界) | — 154 行、tag 规范须整体;如证明只用单段可降区段读 | #15 |
-
-> `cool-points-guide.md`(313)、`strand-weave-pattern.md`(111) 同时被 plan/review 直接 Read:前者已入 A.1 靶心区段读;后者全文读,跨 Skill 共用同一结论。
-
-### A.3 检索读(CSV-backed,不在「直接 Read md」清单,单列)
-
-Plan §6.2.1 明示 CSV 那条线「已做对,本轮不重做」。这里只登记,**不改造**;Phase 4 登记 loading-map 时保持现状。
-
-| 数据源 | 读取方式 | 调用 | 备注 |
-|---|---|---|---|
-| `references/csv/场景写法.csv` | 检索读 | `reference_search.py --table 场景写法 --query ... --genre ...` | write Step2 战斗/对峙/桥段;承接已下线的 combat-scenes 等 md |
-| `references/csv/写作技法.csv` | 检索读 | `reference_search.py --table 写作技法 --query ...` | write Step2 对话/情感;承接 dialogue-writing / emotion-psychology |
-| `references/csv/题材与调性推理.csv`、`裁决规则.csv` 等 8 表 | 检索读(间接) | `story-system` 内部 `_route()` / `_collect_tables()` / `_load_reasoning()` | init/write 经 story-system 间接消费,已按需 |
-
-> 现存 CSV:`裁决规则 / 场景写法 / 金手指与设定 / 命名规则 / 桥段套路 / 人设与关系 / 爽点与节奏 / 题材与调性推理 / 写作技法`(9 表)+ `genre-canonical.md` + `README.md`。
-
-### A.4 不读(Phase 4 清理候选 → Task #14)
-
-loading-map「当前非直接调用项」+ Plan §6.2.3 点名的 writing/* 迁移候选。合计约 1402 行死内容(实测)。处置:先核 CSV 覆盖,已覆盖则删或留空壳指向 CSV,未覆盖先补 CSV 再处置。
-
-| 文件 | 实测行数 | 现状 / CSV 承接 | 处理 Task |
-|---|---|---|---|
-| `skills/webnovel-write/references/writing/combat-scenes.md` | 229 | 战斗触发已由 `场景写法.csv` 承接,不直接 Read | #14 |
-| `skills/webnovel-write/references/writing/dialogue-writing.md` | 231 | 对话触发已由 `写作技法.csv` 承接 | #14 |
-| `skills/webnovel-write/references/writing/emotion-psychology.md` | 265 | 情感触发已由 `写作技法.csv` 承接 | #14 |
-| `skills/webnovel-write/references/writing/scene-description.md` | 263 | Plan §6.2.3 点名;不在直接 Read 清单,核 `场景写法.csv` 覆盖后处置 | #14 |
-| `skills/webnovel-write/references/writing/desire-description.md` | 311 | 同上;核 CSV 覆盖后处置 | #14 |
-| `skills/webnovel-write/references/writing/genre-hook-payoff-library.md` | 85 | Plan §6.2.3 点名;不在直接 Read 清单,核 CSV 覆盖后处置 | #14 |
-| `skills/webnovel-write/references/style-variants.md` | 38 | 非直接调用项 | #14 |
-| `skills/webnovel-review/references/common-mistakes.md` | 96 | 非直接调用项 | #14 |
-| `skills/webnovel-review/references/pacing-control.md` | 129 | 非直接调用项 | #14 |
-
-> `scene-description.md`(263)、`desire-description.md`(311)、`genre-hook-payoff-library.md`(85) 三者**存在且当前非直接 Read**,按 Plan 要求纳入清理核验。
-> 其余存在但本轮无显式处置的 reference(不直接 Read、Plan 未点名):`skills/webnovel-write/references/anti-ai-guide.md`(74,内容已并入 polish-guide §98/§2A,建议 #14 一并核验是否死)、`skills/webnovel-init/references/init-collection-schema.md`(74,Plan §6.2.4 指定 init 区段读真源,**保留**)、`skills/webnovel-init/references/creativity/market-positioning.md`(424,未登记直接 Read,建议 #14 核验)、`references/shared/naming-and-voice-gaps.md`(63)、`skills/webnovel-plan/references/outlining/outline-structure.md`、`plot-frameworks.md`(未登记直接 Read,建议 #13/#14 核验消费方)。
-
-### A.5 锚点校验与 FLAG
-
-所有 7 靶心锚点经 Grep `^#{1,4} ` 核实**真实存在**,无缺失锚点。需注意的「Plan 简写 vs 真实标题」差异(不是缺失,是匹配口径,区段读时必须用右列):
-
-| 文件 | Plan 正文写法 | 真实标题(区段读须匹配此) |
-|---|---|---|
-| genre-profiles | 「一、字段说明」 | `## 一、Profile 字段说明` |
-| reading-power-taxonomy | 「## 一 钩子类型 / ## 二 爽点模式 / ## 三 微兑现」 | `## 一、钩子类型` / `## 二、爽点模式` / `## 三、即时满足/微兑现` |
-| selling-points | 「### 1.3」「## 7」「## 9」 | `### 1.3 核心卖点黄金公式` / `## 7. 实战检查清单` / `## 9. 核心卖点定位模板` |
-| chapter-planning | 「## 10 …」「## 7」 | `## 10. 结构化节点规范(CBN/CPNs/CEN)` / `## 7. 章节规划模板` |
-| creativity-constraints | 「### 8.1」「一 Schema / 六 硬约束 / 八 评分」 | `### 8.1 五维评分` / `## 一、创意包 Schema (Idea Package)` / `## 六、硬约束驱动创意 (Hard Constraints)` / `## 八、评分系统 (Scoring System)` |
-| polish-guide | 「## 2 执行顺序」「Anti-AI 词库段」 | `## 2. 执行顺序(必须按序)` / `## 98:Phase 1 增补:Anti-AI 规范`(另 `## 2A. Anti-AI 检测细则`) |
-| cool-points-guide | 「## 九 题材适配」 | `## 九、题材适配` |
-
-**FLAG:无缺失锚点。** 唯一注意点:Plan 锚点去掉了中文序号点「、」并缩短标题,按字面 `Grep` 会漏匹配——Phase 4 区段读与 loading-map 登记一律以本表右列真实标题为准。
-
----
-
-## Section B — 逐组件归属(8 Skill + 4 Agent)
-
-依 §4.2 职责表。「层」标主 agent 契约形状(Skill)/ subagent(Agent)/ runtime 边界。每格一句话,细节交叉引用 Plan,不复述。
-
-| 组件 | 层 | 保留要点(一句话) | 主要可压缩 / 下沉项(一句话) | 处理 Task |
-|---|---|---|---|---|
-| `skills/webnovel-write/SKILL.md`(202) | Skill:主 agent 契约形状 | 三模式 + 准备链 + 三 Agent 调用合同 + 三道 gate/commit/postcommit/backup(§3.4、§8.2),新增提交前只读 `git diff`(§5.2 B) | context/reviewer/data 内部教程、data payload schema、长润色教程下沉 Agent/reference(§8.3) | #5 |
-| `agents/context-agent.md`(181) | subagent | 五段写作任务书 + `memory-contract load-context` + 按需 query + `.story-system` 优先 + blocker(§9.1) | 长示例、过细推断说明、术语解释删(§9.1) | #6 |
-| `agents/data-agent.md`(120) | subagent(含完整 artifact schema 真源) | 三份 artifact 顶层/子项最小字段 + 禁写 state/projection(§9.2、§4.3);`tools` 含 `Write` | 各 event_type 长 payload 说明、长 JSON 示例、旧字段名解释压缩(§9.2) | #7 |
-| `agents/reviewer.md`(135) | subagent | 五维 `dimension_results`(pass 也写) + evidence/fix_hint + 严格 JSON、不评分(§9.3);不授 `Write`,只返 JSON | 删「思维链/ReAct」元叙述、删过长审查教程(§9.3、§12.2 松绑段号测试) | #8 |
-| `agents/deconstruction-agent.md`(296) | subagent | quick/deep/auto 路由 + 不写文件 + 不造 canon + `init_reference_research`/quality/防污染(§9.4、§3.2) | 长质量门控表、超长 schema、深度分阶段长说明压缩(§9.4) | #9 |
-| `skills/webnovel-init/SKILL.md`(402) | Skill:主 agent 契约形状 | §3.2 全链:Step1.5 灵感询问、deconstruction 调用边界、确认前不写 canon、root 安全化、idea_bank、patch 总纲、init 后 MASTER、验证回滚(§10.1) | 采集字段→`init-collection-schema.md` 区段读、题材列表收敛、CLI 长表收缩、创意/反套路/世界观按需读(§10.1、§6.2.4) | #10 |
-| `skills/webnovel-plan/SKILL.md`(394) | Skill:主 agent 契约形状 | §3.3 全链:placeholder-scan、跨卷状态、设定基线、节拍表/时间线/卷纲/批量章纲、设定写回、总纲写回 JSON、master-outline-sync、update-state、真实 CHAPTER_GOAL 刷合同(§10.2) | CBN/CPN/CEN 细则→`chapter-planning.md`§10 区段读、长 reference 表改按阶段触发、节点示例下沉(§10.2、§6.2.4) | #11 |
-| `skills/webnovel-review/SKILL.md`(170) | Skill:主 agent 契约形状 | §3.5 全链:合同缺失补 story-system、reviewer 调用、`review-pipeline --save-metrics`、`update-state --add-review`、blocking 用户裁决(§10.3) | reviewer 审查方法 / 证据查询过程不在 Skill 展开(§10.3) | #12 |
-| `skills/webnovel-query/SKILL.md`(109) | Skill:主 agent 契约形状(只读) | 只读 + root 保护 + 数据源优先级(`.story-system`→accepted commit→memory-contract→projection) + 降级说明(§11.1、§3.6) | 默认全量 `load-context` 改按查询类型用最窄工具(entity-state/relationships/query-rules/open-loop)(§11.1) | #15 |
-| `skills/webnovel-learn/SKILL.md`(82) | Skill:主 agent 契约形状 | root 保护 + 读当前章节号 + `project-memory add-pattern` + 不手写 JSON(§11.2) | 已极简,基本无可压缩;维持(§11.2) | #16 |
-| `skills/webnovel-dashboard/SKILL.md`(101) | Skill:主 agent 契约形状(只读) | 只读边界 + `story-runtime/health` + root 解析 + dist 校验(§11.3) | 不默认装依赖(缺依赖提示命令)、启动前轻量检查(§11.3) | #16 |
-| `skills/webnovel-doctor/SKILL.md`(70) | Skill:主 agent 契约形状(只读) | `project-status` 先行 + `doctor` 阶段感知 + 不修复/不装/不启 dashboard(§11.4、§3.6) | frontmatter description 改简洁中文触发型(§11.4) | #16 |
-
-> Runtime 层(`webnovel.py` 各命令、artifact_validator、write-gate、chapter-commit、projection)承载 schema/gate/commit/projection/backup/状态推进,不在本轮 prompt 瘦身改动范围;各 Skill/Agent 只保留「调用形状」,校验留给 runtime(§4.2、§4.5 写入所有权矩阵)。
-> Agent 单文件约束(§4.3):4 个 Agent 均为 `agents/*.md` 单文件,不得新增 `agents/references/*`;其产物 schema 作为单文件唯一真源(data-agent 尤为关键)。
-
----
-
-## 体量小结(实测)
-
-- 8 Skill 合计 **1530 行**(write 202 / init 402 / plan 394 / review 170 / query 109 / learn 82 / dashboard 101 / doctor 70);init、plan 最大,是 §10 主战场。
-- 4 Agent 合计 **732 行**(context 181 / data 120 / reviewer 135 / deconstruction 296);deconstruction 最大。
-- 7 靶心 reference 合计 **3057 行**(区段读后单本书常驻锐减,genre-profiles 单题材约省 90%)。
-- 不读清理候选合计 **约 1402 行**(A.4 九个核心候选,待核 CSV 覆盖后处置)。
-- 读取方式分布:靶心区段读 7;A.2 区段读 11;全文读 15(含模板/schema/铁律/短文件);检索读 CSV 3 类(不改造);不读 9(+ 建议核验若干)。

+ 0 - 228
docs/architecture/phase0-tool-and-registration-audit-2026-06-06.md

@@ -1,228 +0,0 @@
-# Phase 0 审计:工具能力与插件注册名(2026-06-06)
-
-> 只读审计。本文件只「记录证据」,不修改任何 skill / agent / plugin.json / 测试文件。
-> 目的:为 `context-minimal-writing-flow-plan` 提供可信的运行基线,复核计划中假设的
-> `webnovel-writer:<agent>` 注册名、agent/skill frontmatter、以及计划依赖的官方工具行为结论。
->
-> 工作目录(git 仓库根 = worktree):`D:/wk/novel skill/webnovel-writer/.worktrees/context-minimal-flow`
-> 插件目录(嵌套):仓库根下的 `webnovel-writer/` 子目录。
-
----
-
-## 1. Claude Code 版本与环境
-
-| 项 | 值 | 证据 |
-|----|----|------|
-| 宿主 | Claude Code(固定,本插件唯一目标宿主) | — |
-| `claude --version` | `2.1.161 (Claude Code)` | Bash 直接执行成功(非「subagent 不可用」) |
-| 平台 | Windows(win32),PowerShell + Bash 双可用 | 环境说明 |
-| 本审计执行体 | subagent(Task 1);本机 `claude --version` 可正常返回 | — |
-
-结论:版本/环境均可确认,无降级记录。
-
----
-
-## 2. 插件注册(真实名)
-
-### 2.1 plugin.json
-
-文件:`webnovel-writer/.claude-plugin/plugin.json`(全文 18 行)。
-
-```json
-{
-  "name": "webnovel-writer",
-  "version": "6.1.0",
-  "description": "长篇网文创作系统(skills + agents + data chain + RAG)",
-  "author": { "name": "lingfengQAQ" },
-  "homepage": "https://github.com/lingfengQAQ/webnovel-writer",
-  "repository": "https://github.com/lingfengQAQ/webnovel-writer",
-  "license": "GPL-3.0",
-  "keywords": ["webnovel", "claude-code", "skills", "agents", "rag"]
-}
-```
-
-- 插件名(namespace 前缀来源):**`webnovel-writer`**。
-- manifest **没有**显式 `agents` / `skills` / `commands` 路径数组。
-  → 因此 agents 与 skills **全部走约定式 auto-discovery**:
-  - 官方 `plugin-structure/SKILL.md:11` —「automatic component discovery」;
-  - 同文件第 28-32 行:`agents/`(`.md` 文件)与 `skills/`(子目录,每个含 `SKILL.md`)。
-  - 官方 `skill-development/SKILL.md:271-273` —「Claude Code automatically discovers skills: Scans `skills/` directory; Finds subdirectories containing `SKILL.md`」。
-
-### 2.2 真实 on-disk agent 名(4 个)
-
-每个 agent 是单一 `.md` 文件,frontmatter `name:` 与文件名(去 `.md`)一致:
-
-| 文件 | frontmatter `name:` (行 2) | 一致? |
-|------|---------------------------|--------|
-| `webnovel-writer/agents/context-agent.md` | `context-agent` | ✅ |
-| `webnovel-writer/agents/data-agent.md` | `data-agent` | ✅ |
-| `webnovel-writer/agents/deconstruction-agent.md` | `deconstruction-agent` | ✅ |
-| `webnovel-writer/agents/reviewer.md` | `reviewer` | ✅ |
-
-另有 `webnovel-writer/agents/evals/`(含 `evals.json` + `files/`)——非 agent 定义,是评测夹具,auto-discovery 不会把它当 agent(不是顶层 `.md`)。无 `agents/references/` 目录。
-
-### 2.3 真实 on-disk skill 名(8 个)
-
-每个 skill 是含 `SKILL.md` 的子目录,frontmatter `name:`(均在行 2)与目录名一致:
-
-| 目录 | frontmatter `name:` | 一致? |
-|------|--------------------|--------|
-| `skills/webnovel-write/` | `webnovel-write` | ✅ |
-| `skills/webnovel-init/` | `webnovel-init` | ✅ |
-| `skills/webnovel-plan/` | `webnovel-plan` | ✅ |
-| `skills/webnovel-review/` | `webnovel-review` | ✅ |
-| `skills/webnovel-query/` | `webnovel-query` | ✅ |
-| `skills/webnovel-learn/` | `webnovel-learn` | ✅ |
-| `skills/webnovel-dashboard/` | `webnovel-dashboard` | ✅ |
-| `skills/webnovel-doctor/` | `webnovel-doctor` | ✅ |
-
-(路径前缀省略 `webnovel-writer/`。)
-
-### 2.4 注册名 ↔ 计划引用名 复核(关键)
-
-计划 `docs/architecture/context-minimal-writing-flow-plan-2026-06-05.md` 在多处用
-`webnovel-writer:<agent>` 形式引用 4 个 agent,例如:
-
-- L181 `必须调用 webnovel-writer:context-agent`
-- L195 `... webnovel-writer:reviewer`
-- L228 `... webnovel-writer:data-agent`
-- L97 `... webnovel-writer:deconstruction-agent`
-- L463/L698/L713/L728 `Use the Agent tool to run webnovel-writer:context-agent` 等
-- L430 计划自带告警:「plugin scoped agent 的真实注册名必须在 Phase 0 复核」(即本节)。
-
-复核结论:
-
-- **on-disk agent 名 = `context-agent` / `data-agent` / `deconstruction-agent` / `reviewer`**,
-  与计划里冒号后半段完全一致,**无错名**。
-- 关于前缀:官方 `agent-development/SKILL.md:281-285`「Namespacing」原文:
-  - 「Agents are namespaced automatically: Single plugin: `agent-name`; With subdirectories: `plugin:subdir:agent-name`」。
-  - 即官方文档把「单插件、无子目录」的 agent 标识写作**裸名 `agent-name`**,把带前缀的形态留给「子目录」场景(`plugin:subdir:agent`)。
-  - 命令侧旁证:`command-development` 与 `plugin-features-reference.md:30` 显示插件组件在 `/help` 里带 `(plugin:plugin-name)` 标签,说明「插件名:组件名」是 marketplace 场景下的实际可见限定形态。
-  - ⚠️ **轻微风险(命名形态,非错名)**:官方 agent-development 文档对「单插件无子目录」给出的范式是裸 `agent-name`,并未把 `plugin:agent`(无 subdir)列为 canonical 形态。计划统一用 `webnovel-writer:context-agent` 这类**带插件前缀、无 subdir** 的写法,是合理且更显式的(在多插件并存时消歧),但官方文档**没有把这一形态写成单插件的标准范式**。
-  - 建议:Phase 1 真正接线时,以**运行时实测**(`Agent`/Task 工具能否用 `webnovel-writer:context-agent` 解析到该 agent)为准;若实测裸名 `context-agent` 才解析得到、带前缀失败,则按裸名修正计划。本审计无法在 subagent 内实跑 Agent 工具来终判,故标注为「待运行时验证」。
-
----
-
-## 3. Agent frontmatter 审计(4 个)
-
-逐项抄录 frontmatter(行号以各文件为准;所有 4 个 agent 的 frontmatter 块均为第 1-7 行):
-
-| agent | name | description(摘) | model | color | tools |
-|-------|------|------------------|-------|-------|-------|
-| context-agent | `context-agent` | 写前 research,输出写作任务书。 | `inherit` | `blue` | `Read, Grep, Bash` |
-| data-agent | `data-agent` | 从正文提取事实,生成 commit artifacts。 | `inherit` | `green` | `Read, Write, Bash` |
-| deconstruction-agent | `deconstruction-agent` | /webnovel-init 的参考书拆解子代理…… | `inherit` | `purple` | `Read, Grep, Bash` |
-| reviewer | `reviewer` | 统一审查 agent。逐维度检查…… | `inherit` | `yellow` | `Read, Grep, Bash` |
-
-对照 agent-development 规则:
-
-- **单一 `.md` 文件**:✅ 全部满足。
-- **必备字段 name/description/model/color**:✅ 4 个全齐。
-  - `model: inherit` 符合官方推荐(`agent-development/SKILL.md:100,105`)。
-  - `color` 取值:`blue/green/purple/yellow`。⚠️ 官方 `agent-development/SKILL.md:111` 列出的合法色为
-    `blue, cyan, green, yellow, magenta, red`,**未列 `purple`**。`deconstruction-agent` 的
-    `color: purple` 不在官方枚举内(应为 `magenta`)。属**轻微规范偏差**,不影响功能/注册(color 仅 UI 着色)。
-- **`tools` 最小集**:✅ 都是 3 个工具的小集合(least privilege,符合 `SKILL.md:134`)。
-- **FLAG:`tools` 含 `Agent` 或 `AskUserQuestion`?** → ❌ **均不含**。
-  4 个 agent 的 tools 仅为 `{Read, Grep/Write, Bash}` 组合,**没有任何一个**带 `Agent`/`AskUserQuestion`/`Task`。
-  → 与计划「subagent 不得用 `Agent`/`AskUserQuestion`」一致,**无违例**。
-- **FLAG:缺必备 frontmatter 字段?** → 无。4 个 agent 字段齐全。
-- **FLAG:依赖外部 `agents/references/*` 当隐藏手册?** → 无。`agents/references/` 目录不存在;
-  4 个 agent 正文未引用 `agents/references/...`。(注:正文大量引用 `${SCRIPTS_DIR}/webnovel.py` 子命令,
-  那是运行时脚本调用,不是「隐藏 manual 文件」。)
-- **`skills:` frontmatter 字段?** → 4 个 agent **均无** `skills:` 字段(Grep `^(name|skills):` 仅命中
-  `name:`)。即当前没有任何 agent 预载 Skill 内容。
-
----
-
-## 4. Skill frontmatter 审计(8 个)
-
-逐项抄录(`name`/`allowed-tools` 行号见下;所有 skill `name:` 均在行 2):
-
-| skill | name | description 触发型 | allowed-tools |
-|-------|------|-------------------|---------------|
-| webnovel-write | `webnovel-write` | 产出可发布章节,完整执行上下文→起草→审查→润色→提交→备份。 | `Read Write Edit Grep Bash Agent AskUserQuestion` |
-| webnovel-init | `webnovel-init` | 深度初始化网文项目。分阶段交互收集…… | `Read Write Edit Grep Bash Agent AskUserQuestion WebSearch WebFetch` |
-| webnovel-plan | `webnovel-plan` | 基于总纲生成卷纲、时间线和章纲…… | `Read Write Edit Bash AskUserQuestion` |
-| webnovel-review | `webnovel-review` | 使用审查 Agent 评估章节质量…… | `Read Grep Write Edit Bash Agent AskUserQuestion` |
-| webnovel-query | `webnovel-query` | 查询项目设定、角色、力量体系、势力、伏笔…… | `Read Grep Bash` |
-| webnovel-learn | `webnovel-learn` | 从当前会话提取成功模式并写入 project_memory.json | `Read Bash` |
-| webnovel-dashboard | `webnovel-dashboard` | 启动只读小说管理面板…… | `Bash Read` |
-| webnovel-doctor | `webnovel-doctor` | This skill should be used when the user asks to "/webnovel-doctor", "检查项目环境"…… | `Read Bash` |
-
-对照 skill-development 规则:
-
-- **目录 + SKILL.md 结构**:✅ 8 个目录均含 `SKILL.md`(逐一 `test -f` 通过)。
-- **frontmatter 含 `name` + 触发型 `description`**:✅ 全部有 `name` + `description`。
-  - 官方只强制 `name` + `description`(`skill-development/SKILL.md:33-34`)。
-  - 触发表述:7 个用中文动作/场景式触发短语(计划偏好),`webnovel-doctor` 用英文官方范式
-    「This skill should be used when…」并把中文触发词内嵌其中——两类都满足「具体触发」要求。
-  - `webnovel-doctor` 还带 `version: 0.1.0`(额外字段,无害)。
-- **`allowed-tools` 是否存在及其值**:✅ **8 个 skill 全部声明了 `allowed-tools`**(值见上表)。
-  - 4 个编排型 skill(write/init/plan/review)的 `allowed-tools` 含 **`Agent`**(write/init/review)
-    与 **`AskUserQuestion`**(write/init/plan/review)——这是**顶层 skill / 命令层**用来「预批准」调度子代理
-    与向用户提问的工具集,**不是 subagent 的 tools**。与计划角色分工(编排在 skill 层、执行在 agent 层)一致。
-  - 只读型 skill(query/learn/dashboard/doctor)的 `allowed-tools` 收敛为 `Read/Bash(/Grep)`,无 `Agent`/`AskUserQuestion`。
-
-> 说明:官方 `skill-development/SKILL.md` 通篇**未出现** `allowed-tools` 字段名(仅强制 `name`+`description`)。
-> 因此「`allowed-tools` 是预批准而非限制」这一语义**无法从本地官方 skill 文档直接证实**,见 §5。
-
----
-
-## 5. 计划依赖的官方工具行为结论(运行基线)
-
-交叉核对源(实际使用路径):
-**`C:/Users/lcy/.claude/plugins/marketplaces/claude-plugins-official/plugins/plugin-dev/skills/`**
-(primary 路径存在;fallback `.tmp/plugin-dev-official/...` **不存在**,未使用)。
-核对的三个 skill:`agent-development/`、`skill-development/`、`plugin-structure/`(含其 `references/`、`examples/`)。
-
-逐条结论与本地可证实性:
-
-| # | 计划依赖的结论 | 本地官方文档可证实? | 证据 / 标注 |
-|---|----------------|---------------------|-------------|
-| (a) | **subagent 不能再生成另一个 subagent** | ❌ 未找到 | 在 plugin-dev 全树 Grep `spawn / another (sub)?agent / nested / recursi` 仅命中 MCP 进程「spawn」,**无 subagent 嵌套禁令**条文。→ **「计划断言,本地未证实」**。(与之相容的旁证:`command-development/SKILL.md:720`「Claude uses **Task tool** to launch agent」——launch agent 是上层/命令侧动作;官方未给出 subagent 自身可再调 Task 的任何示例,但「未给出」≠「明文禁止」。) |
-| (b) | **`Agent` / `AskUserQuestion` 不作为 subagent 的 tools** | ⚠️ 部分(间接) | 官方 agent `tools` 文档(`agent-development/SKILL.md:122-140`)给的「常用 tool set」全是 `Read/Write/Grep/Glob/Bash`,**从不**把 `Agent`/`AskUserQuestion` 列进 agent 的 `tools`。`AskUserQuestion` 出现处均在**命令/skill 层**的 `allowed-tools`(`plugin-dev/commands/create-plugin.md:12`、`plugin-settings/examples/create-settings-command.md:3`),不在任何 agent frontmatter。→ 官方**惯例支持**该结论,但**无一句明文「禁止」**。标注为「官方惯例支持,无明文禁令」。本仓 4 个 agent 实测也无人含 `Agent`/`AskUserQuestion`(§3)。 |
-| (c) | **`tools` / `disallowedTools` 控制 subagent 工具边界** | ⚠️ 一半 | `tools` 控制边界:✅ 明文——`agent-development/SKILL.md:124`「Restrict agent to specific tools」、`:132`「If omitted, agent has access to all tools」、`:134` least-privilege。`disallowedTools`:❌ 在 plugin-dev 全树 **Grep 无任何命中**。→ `tools` 部分「本地已证实」;`disallowedTools` 部分「计划断言,本地未证实」。 |
-| (d) | **agent `skills:` frontmatter 会把整份 Skill 内容预载进 subagent** | ❌ 未找到 | 官方 agent frontmatter 文档只列 `name/description/model/color/tools`(`SKILL.md:122` 区段 + `:340-342` 表),**无 `skills:` 字段**说明;全树 Grep `skills:`(agent 语境)无命中。→ **「计划断言,本地未证实」**。当前本仓也无 agent 使用该字段(§3),故即便成立也属「未来才会用到」的能力。 |
-| (e) | **Skill 的 `allowed-tools` 是预批准、不是限制** | ❌ 未找到 | `skill-development/SKILL.md` 全篇**无** `allowed-tools` 字样;官方只把 `allowed-tools` 用在**命令** frontmatter(`command-development` / `plugin-settings` 示例)。其「预批准/非限制」语义在本地官方文档中**无直接定义**。→ **「计划断言,本地未证实」**。(注:经验上 skill/command 的 `allowed-tools` 行为确为预批准,但本审计按要求不凭记忆造引用,仅据本地文档判定为「未证实」。) |
-
-补充已证实的注册/调度事实(支撑计划接线):
-
-- 自动发现:agents 扫 `agents/*.md`、skills 扫 `skills/*/SKILL.md`(`plugin-structure/SKILL.md:11,28-32`;`skill-development/SKILL.md:271-273`)。✅
-- agent 命名空间:单插件裸名 `agent-name`,带子目录 `plugin:subdir:agent-name`(`agent-development/SKILL.md:283-285`)。✅
-- 上层用 **Task 工具** 启动 agent(`command-development/SKILL.md:720`)。✅
-
----
-
-## 6. 结论 / 风险清单
-
-### 与计划假设一致的部分(无阻断)
-
-1. 4 个 agent 的 on-disk 名(`context-agent` / `data-agent` / `deconstruction-agent` / `reviewer`)与计划
-   `webnovel-writer:<agent>` 引用的冒号后半段**完全一致**,无错名、无缺失。
-2. 8 个 skill 目录名与各自 `name:` 一致,结构合规(目录 + SKILL.md,name + 触发型 description)。
-3. **没有任何 agent 的 `tools` 含 `Agent` / `AskUserQuestion` / `Task`** → 计划「subagent 不持调度工具」的红线
-   在现状下**已天然满足**,无需先修。
-4. 没有 agent 缺必备 frontmatter 字段;没有 agent 依赖 `agents/references/*` 隐藏手册(该目录不存在)。
-5. 没有 agent 使用 `skills:` 预载字段(即 (d) 即便成立,也不会与现状冲突)。
-6. 8 个 skill 均显式声明 `allowed-tools`,且编排型/只读型分工与计划角色划分吻合。
-
-### 会动摇计划假设的风险点(需后续处理 / 实测)
-
-- **R1(命名形态,待运行时验证)**:官方 agent-development 文档把「单插件无子目录」agent 的 canonical 标识写成
-  **裸 `agent-name`**,未把 `plugin:agent`(无 subdir)列为标准范式。计划统一用 `webnovel-writer:context-agent`
-  这类带前缀写法。**名字本身没错**,但前缀形态能否被 Agent/Task 工具解析到,需 Phase 1 **运行时实测**确认;
-  若实测仅裸名可解析,应回改计划为裸名。本审计(subagent 内)无法实跑 Agent 工具终判。
-- **R2(工具行为,本地未证实——见 §5)**:计划依赖的 (a) subagent 不能再开 subagent、(d) agent `skills:` 预载整份
-  Skill、(e) skill `allowed-tools` 为预批准——这三条在本地官方 plugin-dev 文档中**找不到出处**;(c) 的
-  `disallowedTools`、(b) 的「明文禁止」也**无出处**。这些应作为**「计划断言、本地未证实」**对待:
-  计划据此设计无妨,但任何「以 `skills:` 预载替代 reference 大块文本」「靠 subagent 链式分发」的关键改动,
-  应在实现期以小样例**实测**,不能仅以官方文档为据。
-- **R3(轻微规范偏差,不阻断)**:`deconstruction-agent` 的 `color: purple` 不在官方合法色枚举
-  (`blue/cyan/green/yellow/magenta/red`)内。仅 UI 着色,无功能影响;如要严格合规,应改 `magenta`。
-  本审计为只读,不修改。
-
-### 一句话总览
-
-注册名与 frontmatter 现状**与计划高度一致、无错名、无 `Agent`/`AskUserQuestion` 违例**;唯一需在实现期落实的是
-**R1 的 `webnovel-writer:<agent>` 前缀形态运行时实测** 与 **R2 中那几条「本地未证实」的工具行为以实测兜底**。

+ 0 - 1147
docs/architecture/plugin-runtime-hardening-plan-2026-06-04.md

@@ -1,1147 +0,0 @@
-# Plugin Runtime Hardening Implementation Plan
-
-> 日期:2026-06-04
-> 状态:草案 v1
-> 对应 spec:`docs/architecture/plugin-runtime-hardening-spec-2026-06-04.md`
-> 范围:把 spec 拆成可实施、可验收、可回退的工程计划,重点说明修改范围与影响面
-
----
-
-## 1. 目标
-
-本计划把 `webnovel-writer` 从“主要靠 Skill 文档约束流程”推进到“关键边界由 runtime 可验证”的插件形态。
-
-核心交付:
-
-1. `project_phase` / `project-status`:统一项目阶段推导和短状态摘要,保留现有 `status_reporter.py` 的宏观创作健康报告语义。
-2. `/webnovel-doctor`:阶段感知的项目体检,检查目录、文件、数据库、RAG、Python 依赖、Dashboard 配置,并给修复建议。
-3. `artifact_validator`:统一校验 agent 产物,避免字段漂移和 schema 错误。
-4. `write-gate`:在写前、提交前、提交后三个自然边界做批量校验。
-5. `projection_log`:把 commit 事实和 projection 执行日志拆开。
-6. `projections retry/replay`:投影失败后可补跑。
-7. Skill / Agent 契约补强:按官方 `plugin-dev` 规范收束 frontmatter、description、tools、输出约束。
-8. Behavior evals 与 package validator:验证插件行为和发布物一致性。
-9. 可选轻量 hook:SessionStart 状态提示与 PreToolUse 危险动作兜底提醒 / 阻断。
-
----
-
-## 2. 实施原则
-
-### 2.1 先观察,后阻断,再迁移
-
-顺序必须是:
-
-1. 先提供只读诊断能力。
-2. 再加入 schema / gate 阻断。
-3. 最后处理 projection log 与 replay。
-
-这样可以减少一次性大改对现有写作流程的冲击。
-
-### 2.2 不破坏现有用户项目
-
-所有新增能力默认兼容旧数据:
-
-- 保留现有 `.story-system/commits/*.commit.json` 结构。
-- 保留 commit 内 `projection_status` 至少一个版本周期。
-- `.webnovel/state.json`、`index.db`、`summaries/`、`memory_scratchpad.json` 继续作为 projection / read-model。
-- Dashboard 先兼容旧字段,再逐步读取新 projection log。
-
-### 2.3 遵循官方 `plugin-dev`
-
-所有插件组件改动必须遵循:
-
-```text
-C:\Users\lcy\.claude\plugins\marketplaces\claude-plugins-official\plugins\plugin-dev
-```
-
-落地要求:
-
-- 插件结构按 `plugin-structure`。
-- Skill 按 `skill-development`,保持 `SKILL.md` 精简,详细规则放 `references/`。
-- Command 按 `command-development`。
-- Agent 按 `agent-development`。
-- Hook 按 `hook-development`,插件级 `hooks/hooks.json` 使用 wrapper 格式。
-- 每轮插件组件改动后按 `plugin-validator` 思路校验 manifest、skills、agents、hooks、README、LICENSE、路径可移植性。
-
-### 2.4 每阶段独立可回退
-
-每阶段应尽量做到:
-
-- 新增文件多于修改旧文件。
-- 旧入口可继续工作。
-- 新 CLI 子命令失败不影响旧命令。
-- 可通过删除新增入口或关闭 hook 回退。
-
-### 2.5 新入口统一 UTF-8
-
-所有新增 CLI / hook / 子进程入口必须兼容 Windows 中文路径:
-
-- CLI 入口调用 `enable_windows_utf8_stdio()` 或等价逻辑。
-- 文件读写显式 `encoding="utf-8"`。
-- hook / 子进程使用 `python -X utf8` 或设置 `PYTHONUTF8=1`。
-- 不依赖系统默认编码。
-
----
-
-## 3. 总体依赖顺序
-
-```text
-Phase 0 基线审计
-  ↓
-Phase 1 project_phase + project-status + doctor
-  ↓
-Phase 2 artifact_validator
-  ↓
-Phase 3 write-gate
-  ↓
-Phase 4 projection_log
-  ↓
-Phase 5 projection retry/replay
-  ↓
-Phase 6 skill / agent 契约补强
-  ↓
-Phase 7 behavior evals
-  ↓
-Phase 8 package validator
-  ↓
-Phase 9 hooks
-```
-
-说明:
-
-- `project_phase` / `project-status` / `doctor` 可以先做,因为它们只读、风险最低,并且后续 gates 和 hooks 都依赖统一 phase。
-- `artifact_validator` 应早于 `write-gate`,否则 gate 会重复写 schema 判断。
-- `projection_log` 应早于 retry/replay,否则失败记录不稳定。
-- hooks 放后面,因为它们会改变 Claude Code 会话体验。
-
----
-
-## 4. Phase 0:基线审计与测试冻结
-
-### 4.1 目标
-
-在动代码前确认当前功能基线,避免重构时不知道哪里被破坏。
-
-### 4.2 修改范围
-
-优先不改 runtime 代码,只新增或更新文档 / 测试清单:
-
-- `docs/architecture/plugin-runtime-hardening-plan-2026-06-04.md`
-- 可选更新 `docs/README.md`
-
-### 4.3 工作项
-
-1. 记录当前 CLI 命令表。
-2. 记录现有 Skills、Agents、Dashboard API。
-3. 跑一组最小测试:
-   - `test_webnovel_unified_cli.py`
-   - `test_story_runtime_health.py`
-   - `test_chapter_commit_service.py`
-   - `test_event_projection_router.py`
-   - `test_rag_adapter.py`
-   - `test_dashboard_app.py`
-4. 确认当前 repo 是否已有未提交改动,避免误覆盖用户修改。
-
-### 4.4 影响
-
-无用户可见行为变化。
-
-### 4.5 验收
-
-- 记录基线测试结果。
-- 明确当前失败项是否为既有问题。
-
----
-
-## 5. Phase 1:`project_phase` / `project-status` / `webnovel-doctor`
-
-### 5.1 目标
-
-新增统一 phase resolver、短状态入口和只读项目体检入口,回答:
-
-- 当前项目处于什么阶段。
-- 这个阶段应该有哪些文件。
-- 目录、JSON、SQLite、RAG、Python 依赖、Dashboard 配置是否完整。
-- 缺失或异常时如何修复。
-
-当前代码已有两个相关入口,必须先明确关系:
-
-- `webnovel.py preflight`:已有快速环境检查,保留并复用。
-- `webnovel.py status`:已转发到 `scripts/status_reporter.py`,语义是宏观创作健康报告,保留不占用。
-
-### 5.2 修改范围
-
-新增:
-
-- `webnovel-writer/scripts/data_modules/project_phase.py`
-- `webnovel-writer/scripts/data_modules/project_status.py`
-- `webnovel-writer/scripts/data_modules/doctor.py`
-- `webnovel-writer/scripts/data_modules/tests/test_project_phase.py`
-- `webnovel-writer/scripts/data_modules/tests/test_project_status.py`
-- `webnovel-writer/skills/webnovel-doctor/SKILL.md`
-- `webnovel-writer/scripts/data_modules/tests/test_doctor.py`
-
-修改:
-
-- `webnovel-writer/scripts/data_modules/webnovel.py`
-- `docs/guides/commands.md`
-- `docs/README.md`
-
-可复用:
-
-- `webnovel.py` 中现有 `_build_preflight_report()`
-- `story_runtime_health.py`
-- `story_runtime_sources.py`
-- `config.py`
-- Dashboard 里的 `_inspect_vector_db()` / `_build_env_status()` 思路
-
-### 5.3 具体工作
-
-1. 实现共享 `project_phase.py`:
-   - 单一 phase 词表。
-   - 不写状态文件。
-   - doctor / project-status / write-gate 共用。
-2. 实现 `project-status`:
-   - `webnovel.py project-status --format json|summary`
-   - 保留 `webnovel.py status` 现有转发,不改 `status_reporter.py` 语义。
-   - 输出 latest accepted chapter、target chapter、phase、warnings、next action。
-3. 实现 `doctor` 数据模型:
-   - `DoctorReport`
-   - `DoctorCheck`
-   - `RepairSuggestion`
-   - `ExpectedFiles`
-4. 由 `project_phase.py` 实现 phase 推导:
-   - `no_project`
-   - `unknown`
-   - `init_scaffolded`
-   - `init_ready`
-   - `plan_in_progress`
-   - `chapter_contract_ready`
-   - `draft_in_progress`
-   - `ready_to_commit`
-   - `chapter_committed`
-   - `projection_failed`
-5. 实现阶段感知 expected files:
-   - init 后只要求骨架、`state.json`、设定集、总纲、`.env.example`。
-   - init 阶段不要求 commit、summary、memory、vectors。
-   - plan / write / commit 阶段再逐步提高要求。
-6. 实现文件检查:
-   - 目录存在性。
-   - JSON 可读性。
-   - 关键字段检查。
-7. 实现 SQLite 检查:
-   - `index.db` 是否可打开。
-   - 关键表是否存在。
-   - 行数统计。
-   - `vectors.db` 是否可打开、是否有 `vectors` 表。
-8. 实现系统配置检查:
-   - Python 版本。
-   - 核心包 import。
-   - RAG env / `.env` 配置。
-   - Dashboard dist / requirements / package.json。
-9. 在统一 CLI 注册:
-   - `webnovel.py project-status --format json|summary`
-   - `webnovel.py doctor --format json|text`
-   - `webnovel.py doctor --chapter N --format json|text`
-   - `webnovel.py doctor --deep --format json|text`
-10. 新增 `/webnovel-doctor` Skill:
-   - 只读。
-   - 不修复。
-   - 输出结论、影响、建议命令。
-
-### 5.4 影响
-
-用户影响:
-
-- 新增一个体检命令,不改变旧流程。
-- 新增 `project-status` 短状态命令,不改变既有 `status` 健康报告。
-- 出问题时用户能看到缺什么、影响什么、怎么修。
-
-代码影响:
-
-- `webnovel.py` 增加 `project-status` 和 `doctor` 子命令。
-- 新增 `doctor.py` 只读模块。
-- 新增共享 phase resolver。
-- 不修改 commit、state、index、summary、memory。
-
-风险:
-
-- phase 推导不准会导致误报。
-- 数据库表清单如果过严,会把旧项目误判为坏。
-- `project-status` 与现有 `status_reporter.py` 混淆。
-
-控制:
-
-- phase 不确定时只做低风险检查。
-- init 阶段缺后续产物只返回 `skip/info`。
-- 数据库检查分 `required` 和 `observed`,避免旧表缺失直接阻断。
-- 命令名使用 `project-status`,保留 `status` 原语义。
-- doctor 快检部分复用 `_build_preflight_report()`,避免 preflight / doctor 两套环境检查漂移。
-
-### 5.5 验收
-
-- 空目录返回 `no_project`,无 traceback。
-- `webnovel.py status` 仍运行现有 `status_reporter.py`。
-- `webnovel.py project-status --format json` 返回统一 phase。
-- `preflight` 仍可运行,并与 doctor 快检结果不冲突。
-- init 刚结束返回 `init_scaffolded` 或 `init_ready`。
-- init 刚结束缺 commit / summary / vectors 不报 blocker。
-- `index.db` 缺关键表时能显示表名、影响、修复建议。
-- 缺 RAG key 返回 warning,并说明降级 BM25。
-- 默认模式不联网、不写文件、不安装依赖、不启动服务。
-- Windows 中文路径下不因默认编码失败。
-
-### 5.6 回退
-
-- 移除 CLI `doctor` / `project-status` 注册。
-- 保留 `doctor.py` 不被调用也不影响现有流程。
-- 保留 `project_phase.py` 不被调用也不影响现有流程。
-- 删除 `/webnovel-doctor` Skill 后插件仍可按原方式运行。
-
----
-
-## 6. Phase 2:Artifact Validator
-
-### 6.1 目标
-
-统一校验 agent 产物,避免 `review_result`、`fulfillment_result`、`disambiguation_result`、`extraction_result` 字段漂移。
-
-### 6.2 修改范围
-
-新增:
-
-- `webnovel-writer/scripts/data_modules/artifact_validator.py`
-- `webnovel-writer/scripts/data_modules/tests/test_artifact_validator.py`
-
-可能修改:
-
-- `chapter_commit_service.py`
-- `chapter_commit.py`
-- `chapter_commit_schema.py`
-
-### 6.3 具体工作
-
-1. 定义统一错误类型:
-   - `schema_error`
-   - `missing_artifact`
-   - `blocking_review`
-   - `missed_outline_node`
-   - `pending_disambiguation`
-   - `projection_failure`
-2. 包装现有 Pydantic schema。权威源统一为 `chapter_commit_schema.py` 中 commit 所需模型:
-   - `ReviewResult`
-   - `FulfillmentResult`
-   - `DisambiguationResult`
-   - `ExtractionResult`
-3. 明确同名 / 近名模型边界:
-   - `review_schema.py` 是 reviewer / review pipeline 局部模型。
-   - `entity_linker.py` 中的消歧模型是实体链接局部模型。
-   - artifact_validator 只以 commit artifact schema 作为最终提交权威。
-4. 提供统一入口:
-   - `validate_review_result(path)`
-   - `validate_fulfillment_result(path)`
-   - `validate_disambiguation_result(path)`
-   - `validate_extraction_result(path)`
-   - `validate_chapter_commit(path)`
-5. 允许兼容已知旧字段,无法兼容时给明确诊断。
-
-### 6.4 影响
-
-用户影响:
-
-- 提交前更早发现 agent 输出错误。
-- 报错从 Python traceback 变成结构化说明。
-
-代码影响:
-
-- `chapter_commit_service` 可以逐步改为依赖 validator。
-- 后续 `write-gate` 复用 validator,减少重复校验。
-
-风险:
-
-- 过严 schema 可能阻断旧产物。
-- 同名模型选错会制造新的 schema 漂移。
-
-控制:
-
-- 首版对旧字段做兼容或 warning。
-- 只有明确影响 commit 正确性的错误才 blocker。
-- 在代码注释和测试中固定权威源为 `chapter_commit_schema.py`。
-
-### 6.5 验收
-
-- 缺 artifact 返回 `missing_artifact`。
-- JSON 外层包错返回 `schema_error`。
-- `disambiguation.pending` 非空返回 blocker。
-- reviewer blocking issue 返回 blocker。
-- `ReviewResult` / `DisambiguationResult` 同名模型不混用。
-
-### 6.6 回退
-
-- `chapter_commit_service` 保留旧校验路径。
-- 如果 validator 有误,可先只用于 doctor / gate 报告,不阻断 commit。
-
----
-
-## 7. Phase 3:Runtime Gates
-
-### 7.1 目标
-
-新增写章关键边界校验:
-
-- `prewrite`
-- `precommit`
-- `postcommit`
-
-### 7.2 修改范围
-
-新增:
-
-- `webnovel-writer/scripts/data_modules/write_gates/__init__.py`
-- `webnovel-writer/scripts/data_modules/write_gates/prewrite.py`
-- `webnovel-writer/scripts/data_modules/write_gates/precommit.py`
-- `webnovel-writer/scripts/data_modules/write_gates/postcommit.py`
-- `webnovel-writer/scripts/data_modules/tests/test_write_gates.py`
-
-修改:
-
-- `webnovel-writer/scripts/data_modules/webnovel.py`
-- `webnovel-writer/skills/webnovel-write/SKILL.md`
-- `docs/guides/commands.md`
-
-复用:
-
-- `webnovel-writer/scripts/data_modules/prewrite_validator.py`
-- `webnovel-writer/scripts/data_modules/tests/test_prewrite_validator.py`
-
-### 7.3 具体工作
-
-1. 注册 CLI:
-   - `webnovel.py write-gate --chapter N --stage prewrite --format json`
-   - `webnovel.py write-gate --chapter N --stage precommit --format json`
-   - `webnovel.py write-gate --chapter N --stage postcommit --format json`
-2. `prewrite` 检查必须包装 `PrewriteValidator`:
-   - project root。
-   - phase 是否允许写。
-   - Story System 合同是否齐。
-   - 占位符 blocker。
-3. `precommit` 检查:
-   - 正文文件。
-   - review / fulfillment / disambiguation / extraction artifacts。
-   - artifact validator。
-   - blocking issue。
-4. `postcommit` 检查:
-   - commit 文件。
-   - `projection_status`。
-   - summary / index / memory / backup 基本存在性。
-5. 更新 `/webnovel-write`:
-   - 用 Claude Code Todo 管过程。
-   - 只在自然边界调用 gate。
-
-### 7.4 影响
-
-用户影响:
-
-- 写章流程增加 2-3 次确定性检查。
-- 提交前错误更清晰。
-
-代码影响:
-
-- `/webnovel-write` 的执行说明会改变。
-- `webnovel.py` 增加子命令。
-- 现有 `PrewriteValidator` 成为 prewrite gate 的底层实现,避免两套逻辑漂移。
-
-风险:
-
-- gate 太严格会打断写作体验。
-- gate 太宽松则无法提升可靠性。
-- 如果重写 prewrite 逻辑,会和现有 `PrewriteValidator` 漂移。
-
-控制:
-
-- 首版只阻断明确不可信状态。
-- warning 不阻断。
-- 所有 gate 输出 repair 建议。
-- prewrite 不重写,先适配现有 validator 输出。
-
-### 7.5 验收
-
-- 缺 review artifact 时 `precommit.ok=false`。
-- blocking review 时 `precommit.ok=false`。
-- disambiguation pending 时 `precommit.ok=false`。
-- projection failed 时 `postcommit.ok=false`。
-- init 阶段调用 `prewrite` 能给出明确下一步建议。
-- 现有 `test_prewrite_validator.py` 继续通过。
-
-### 7.6 回退
-
-- `/webnovel-write` 可临时回到旧流程。
-- CLI 子命令保留但不被 Skill 调用。
-
----
-
-## 8. Phase 4:Projection Log
-
-### 8.1 目标
-
-将 commit 事实和 projection 执行状态拆开,降低 commit 文件同时承载事实与执行日志的混乱。
-
-开工前必须先确认现状痛点:
-
-- 是否出现过 projection failed 后无法定位 writer。
-- 是否出现过 commit 内 `projection_status` 与实际 read-model 不一致。
-- Dashboard / doctor 是否确实需要跨 writer 的执行历史。
-
-如果没有真实痛点,本阶段可以延后,只保留 doctor 对现有 `projection_status` 的诊断。
-
-### 8.2 修改范围
-
-新增:
-
-- `webnovel-writer/scripts/data_modules/projection_log.py`
-- `webnovel-writer/scripts/data_modules/tests/test_projection_log.py`
-
-修改:
-
-- `chapter_commit_service.py`
-- `event_projection_router.py`
-- `story_runtime_health.py`
-- `doctor.py`
-- `dashboard/app.py`
-
-### 8.3 具体工作
-
-1. 新增 JSONL projection log:
-   - `.webnovel/projection_log.jsonl`
-2. 定义 run schema:
-   - `run_id`
-   - `chapter`
-   - `commit_path`
-   - `commit_hash`
-   - `writer`
-   - `status`
-   - `started_at`
-   - `finished_at`
-   - `error`
-3. `chapter_commit_service.apply_projections()` 每个 writer 写一条 log。
-4. 保留 commit 内 `projection_status` 双写。
-5. doctor 优先读取 projection log,缺失时 fallback 到 commit 内字段。
-6. Dashboard 先兼容读取,不做大改版。
-
-### 8.4 影响
-
-用户影响:
-
-- 投影失败时能看到具体哪个 writer 失败。
-- 不改变章节提交命令。
-
-数据影响:
-
-- 新增 `.webnovel/projection_log.jsonl`。
-- commit 文件结构暂时不删字段。
-
-风险:
-
-- 双写不一致。
-- Dashboard 读取逻辑复杂一点。
-- 新增 projection log 会形成第二份执行状态来源。
-
-控制:
-
-- projection log 写失败不应影响 commit 主流程,但必须 warning。
-- doctor 报告双写不一致。
-- 保留一个明确决策点:确认收益大于双写复杂度后再施工。
-
-### 8.5 验收
-
-- 每个 writer 有 projection log。
-- 单 writer failed 不影响其他 writer 记录。
-- doctor 能指出 failed writer。
-- commit 内 `projection_status` 仍存在。
-
-### 8.6 回退
-
-- Dashboard 和 doctor fallback 到 commit 内 `projection_status`。
-- 删除 projection log 不影响 commit 读取。
-
----
-
-## 9. Phase 5:Projection Retry / Replay
-
-### 9.1 目标
-
-投影失败后能按 writer 补跑,尤其是 vector / summary / memory。
-
-本阶段风险最高,必须先完成 writer 幂等性审计和测试。
-
-### 9.2 修改范围
-
-新增:
-
-- `webnovel-writer/scripts/data_modules/projection_runner.py`
-- `webnovel-writer/scripts/data_modules/tests/test_projection_runner.py`
-
-修改:
-
-- `event_projection_router.py`
-- `webnovel.py`
-- projection writer 测试
-
-### 9.3 具体工作
-
-1. 新增 CLI:
-   - `webnovel.py projections status --chapter N`
-   - `webnovel.py projections retry --chapter N --writer vector`
-   - `webnovel.py projections retry-failed --chapter N`
-   - `webnovel.py projections replay --from 1 --to 20 --writers state,index,summary`
-2. 先审计 5 个 writer 幂等性:
-   - `state`
-   - `index`
-   - `summary`
-   - `memory`
-   - `vector`
-3. 补齐 writer 幂等测试,尤其关注:
-   - 字数重复累计。
-   - 关系 / 事件重复插入。
-   - memory 重复沉淀。
-   - vector chunk 重复。
-4. runner 只读取 accepted commit。
-5. retry 不修改 commit 事实内容。
-6. retry 结果写 projection log,并兼容更新旧 `projection_status`。
-
-### 9.4 影响
-
-用户影响:
-
-- 外部依赖失败后不用重写整章。
-- 可单独补 vector / summary。
-
-数据影响:
-
-- projection read-model 可能被重建。
-- 需要保证幂等,避免重复累计字数或重复关系。
-
-风险:
-
-- 幂等不足导致重复数据。
-- replay 命令一旦写错,影响范围比单章 commit 大。
-
-控制:
-
-- 先对 state/index/summary/memory/vector writer 分别补幂等测试。
-- replay 默认要求明确 chapter 范围。
-- 默认不提供全书无边界 replay。
-
-### 9.5 验收
-
-- 删除 summary 后 retry summary 可恢复。
-- 重复 replay 同一章节不会重复累计 state / index / memory / vector 数据。
-- vector key 缺失时 vector failed,其余 writer done。
-- 配置 key 后 retry vector 只补 vector。
-- replay 后 state/index/summary 与 commit 链一致。
-
-### 9.6 回退
-
-- 隐藏或下线 projections CLI。
-- 继续使用旧 chapter commit 流程。
-
----
-
-## 10. Phase 6:Skill / Agent 契约补强
-
-### 10.1 目标
-
-按官方 `plugin-dev` 规范增强 Skill / Agent 的触发、工具范围、输出契约。
-
-### 10.2 修改范围
-
-修改:
-
-- `webnovel-writer/skills/*/SKILL.md`
-- `webnovel-writer/agents/*.md`
-- `webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py`
-
-可选新增:
-
-- `webnovel-writer/agents/continuity-reviewer.md`
-- `webnovel-writer/agents/style-reviewer.md`
-- `webnovel-writer/agents/reader-pull-reviewer.md`
-
-### 10.3 具体工作
-
-1. Skill frontmatter:
-   - 补强 `description`,写清触发场景和不适用场景。
-   - 保持 `SKILL.md` 精简。
-   - 大段规则移动到 `references/`。
-2. Agent frontmatter:
-   - 补 `name`。
-   - 补具体 `description`。
-   - 明确 `tools`。
-   - 需要时补 `model`。
-3. Agent 输出契约:
-   - reviewer 输出维度固定。
-   - data-agent 明确只产出 artifacts,不写 projection。
-   - context-agent 明确上下文优先级。
-4. 用 plugin-dev 的 validate-agent 规则做人工或脚本校验。
-
-### 10.4 影响
-
-用户影响:
-
-- Claude Code 触发技能和代理更稳定。
-- 误触发和漏触发减少。
-
-代码影响:
-
-- 主要是 prompt / markdown 文件。
-- 可能影响 Claude Code 的选择行为。
-
-风险:
-
-- description 改动导致触发习惯变化。
-
-控制:
-
-- 改一组测一组。
-- 增加 prompt integrity 测试。
-- 保留命令名称不变。
-
-### 10.5 验收
-
-- 7 个 Skill 都有明确触发型 description。
-- 4 个现有 Agent frontmatter 符合 plugin-dev 要求。
-- prompt integrity 测试通过。
-- data-agent 文档仍明确不写 state/index/summary/memory。
-
-### 10.6 回退
-
-- 单个 Skill / Agent 可独立回滚 frontmatter。
-- 不影响 Python runtime。
-
----
-
-## 11. Phase 7:Behavior Evals
-
-### 11.1 目标
-
-验证插件在真实行为层面是否按协议执行,而不是只验证 Python 函数。
-
-### 11.2 修改范围
-
-新增:
-
-- `webnovel-writer/evals/`
-- `webnovel-writer/scripts/run_behavior_evals.py`
-- `webnovel-writer/evals/fixtures/`
-
-修改:
-
-- CI / 本地测试文档。
-- `docs/operations/operations.md`
-
-### 11.3 具体工作
-
-1. 建立 eval 分类:
-   - skill triggering
-   - workflow behavior
-   - agent output schema
-   - continuity conflict
-   - memory commit
-2. 首批用例:
-   - init 不污染插件目录。
-   - write 遇 blocking issue 不进入 commit。
-   - data-agent 不写 projection。
-   - commit 驱动 projection。
-   - dashboard 只读。
-3. Runner 输出 JSON 报告。
-4. 区分 fast fixture eval 和 slow transcript eval。
-
-### 11.4 影响
-
-用户影响:
-
-- 无直接使用变化。
-- 发布前可靠性更高。
-
-代码影响:
-
-- 增加测试资产。
-- CI 时间可能增加。
-
-风险:
-
-- transcript eval 成本高、慢。
-
-控制:
-
-- 默认只跑 fast。
-- slow 只在发布前或手动运行。
-
-### 11.5 验收
-
-- 每个 Skill 至少一个 eval。
-- `/webnovel-write` 覆盖成功链路和 blocking 链路。
-- eval report 有 pass/fail/reason/artifacts。
-
-### 11.6 回退
-
-- eval 不参与默认流程时可暂时跳过。
-- 不影响 runtime。
-
----
-
-## 12. Phase 8:Package Validator
-
-### 12.1 目标
-
-防止 manifest、marketplace、README、version、frontmatter 漂移。
-
-### 12.2 修改范围
-
-新增:
-
-- `webnovel-writer/scripts/validate_plugin_package.py`
-- `webnovel-writer/scripts/tests/test_validate_plugin_package.py`
-
-修改:
-
-- `docs/operations/plugin-release.md`
-- `docs/guides/commands.md`
-
-### 12.3 具体工作
-
-1. 检查 `.claude-plugin/plugin.json`:
-   - name kebab-case。
-   - version semver。
-   - description 非空。
-2. 检查 marketplace 文件。
-3. 复用或对齐现有 Plugin Version Check:
-   - marketplace version。
-   - plugin.json version。
-   - README 中既有版本位置 / 版本表。
-   - 不新增一套与现有 CI 冲突的 README badge 规则。
-4. 检查 skills frontmatter。
-5. 检查 agents frontmatter。
-6. 检查 hooks schema。
-7. 检查 LICENSE。
-8. 检查 Dashboard dist。
-9. 检查无硬编码本机绝对路径。
-
-### 12.4 影响
-
-用户影响:
-
-- 发布包更稳定。
-
-代码影响:
-
-- 新增发布前校验脚本。
-
-风险:
-
-- 校验规则过严影响开发阶段。
-- 版本校验与现有 CI 不一致,导致本地通过但 CI 失败,或相反。
-
-控制:
-
-- 区分 `--strict` 和默认模式。
-- 默认只阻断明显错误。
-- 先读现有 CI / release 文档,再实现版本检查。
-
-### 12.5 验收
-
-- clean clone 校验通过。
-- version 任一处漂移时失败。
-- 版本漂移规则与现有 CI 一致。
-- 删除 Skill frontmatter 时失败。
-- hooks 路径不用 `${CLAUDE_PLUGIN_ROOT}` 时 warning 或失败。
-
-### 12.6 回退
-
-- release 流程暂不调用 validator。
-- 不影响插件运行。
-
----
-
-## 13. Phase 9:Hooks
-
-### 13.1 目标
-
-基于 Phase 1 的 `project-status` 提供轻量状态摘要,并对最危险的绕过 runtime 写入做兜底提醒 / 阻断。
-
-### 13.2 修改范围
-
-新增:
-
-- `webnovel-writer/hooks/hooks.json`
-- `webnovel-writer/hooks/session_start.py`
-- `webnovel-writer/hooks/scripts/guard-runtime-write.py`
-
-修改:
-
-- `webnovel-writer/scripts/data_modules/webnovel.py`
-- `webnovel-writer/.claude-plugin/plugin.json`,仅在需要显式声明 hook 路径时修改。
-- `docs/operations/operations.md`
-
-### 13.3 具体工作
-
-1. `SessionStart` hook:
-   - 只输出短摘要。
-   - 不写文件。
-   - 不运行完整 doctor。
-   - 调用 `webnovel.py project-status --format summary`。
-   - 可通过 env 关闭。
-2. `PreToolUse` hook:
-   - 阻断直接写 `.story-system/commits`。
-   - 阻断直接写 `.webnovel/state.json`、`index.db`、`memory_scratchpad.json`。
-   - 对 Bash 中绕过 gate 的危险 commit / projection 命令做 best-effort 检测。
-   - 不把 Bash 字符串解析当作唯一硬保证。
-3. 按 plugin-dev hook-development 校验:
-   - `hooks/hooks.json` 使用 wrapper 格式。
-   - hook 命令使用 `${CLAUDE_PLUGIN_ROOT}`。
-   - hook 脚本校验 stdin JSON。
-
-### 13.4 影响
-
-用户影响:
-
-- 新对话能看到短状态。
-- 直接改主链 / projection 文件会被阻断或要求显式走 runtime。
-
-代码影响:
-
-- 新增 hooks 目录。
-- Claude Code 会话启动多一次轻量命令。
-
-风险:
-
-- hook 输出太长影响上下文。
-- hook 误阻断开发者调试。
-- Bash 命令变体太多,hook 无法可靠识别全部绕过方式。
-
-控制:
-
-- 输出限制 8 行 / 1000 字符。
-- 提供关闭 env。
-- 先只阻断最危险路径。
-- 真正可靠性仍由 runtime gate 和 commit 入口保证。
-
-### 13.5 验收
-
-- 无项目根时不报错。
-- 有项目根时输出 latest chapter / phase / next action。
-- 设置 disable env 后无输出。
-- 直接写 commit 文件被阻断。
-- 合法 runtime 命令不被阻断。
-- `webnovel.py status` 仍保留宏观创作健康报告语义。
-
-### 13.6 回退
-
-- 删除或禁用 `hooks/hooks.json`。
-- 保留 `project-status` CLI 不影响旧流程。
-
----
-
-## 14. 横向影响分析
-
-### 14.1 项目健康入口归属
-
-| 入口 | 当前/目标职责 | 输出 | 是否深检 | 是否写文件 |
-|---|---|---|---|---|
-| `preflight` | 快速环境检查,保留兼容 | text/json | 否 | 否 |
-| `project-status` | 机器可读短状态、phase、下一步 | summary/json | 否 | 否 |
-| `doctor` | 文件/数据库/配置体检和修复建议 | text/json | 默认否,`--deep` 可选 | 否 |
-| `status` / `status_reporter.py` | 宏观创作健康报告,如角色、伏笔、爽点、关系图谱 | markdown/text | 是,偏创作分析 | 现状可能输出报告文件,语义保持 |
-| `build_story_runtime_health()` | 内部主链就绪度 helper | dict | 否 | 否 |
-
-原则:
-
-- 不把所有问题塞进一个命令。
-- 不改变现有 `status` 语义。
-- doctor 复用 preflight 和 story runtime health,不复制逻辑。
-
-### 14.2 对用户命令的影响
-
-新增命令:
-
-- `/webnovel-doctor`
-- `webnovel.py doctor`
-- `webnovel.py write-gate`
-- `webnovel.py projections`
-- `webnovel.py project-status`
-
-现有命令保持:
-
-- `/webnovel-init`
-- `/webnovel-plan`
-- `/webnovel-write`
-- `/webnovel-review`
-- `/webnovel-query`
-- `/webnovel-dashboard`
-- `/webnovel-learn`
-
-现有 `webnovel.py status` 保持转发到 `status_reporter.py`。
-
-### 14.3 对项目数据的影响
-
-新增文件:
-
-- `.webnovel/projection_log.jsonl`
-- 可能新增 `.webnovel/tmp/*` 的校验约定。
-
-不改变:
-
-- `.story-system/` 主链真源地位。
-- accepted commit 是写后事实入口。
-- `.webnovel/*` 是 projection / read-model。
-
-### 14.4 对 Dashboard 的影响
-
-短期:
-
-- Dashboard 保持只读。
-- 继续兼容 commit 内 `projection_status`。
-
-中期:
-
-- Dashboard 可展示 projection log。
-- System 页可展示 doctor / project-status 摘要。
-
-风险:
-
-- Dashboard 前端 bundle 可能需要 rebuild。
-
-### 14.5 对 RAG 的影响
-
-默认:
-
-- 缺 key 降级 BM25。
-- `vectors.db` 缺失只 warning。
-
-深度检查:
-
-- 才测试 API 连通。
-
-### 14.6 对测试的影响
-
-新增测试量较大,需要分层:
-
-- 单元测试:doctor / validator / gates / projection log。
-- 集成测试:chapter commit + projection。
-- 行为测试:skill / agent 协议。
-- 发布测试:package validator。
-
----
-
-## 15. 建议 PR 切分
-
-### PR 1:Phase Resolver + Project Status + Doctor
-
-包含:
-
-- shared `project_phase`。
-- `project-status` CLI。
-- doctor runtime。
-- doctor CLI。
-- `/webnovel-doctor` Skill。
-- doctor tests。
-- preflight 快检复用关系。
-
-不包含:
-
-- write-gate。
-- projection log。
-- hooks。
-
-### PR 2:Validator + Gates
-
-包含:
-
-- artifact validator。
-- write-gates。
-- prewrite gate 包装现有 `PrewriteValidator`。
-- `/webnovel-write` 更新。
-- gate tests。
-
-### PR 3:Projection Writer 幂等审计
-
-包含:
-
-- state / index / summary / memory / vector writer 幂等测试。
-- replay 风险评估。
-
-### PR 4:Projection Log
-
-包含:
-
-- projection log。
-- chapter commit 双写。
-- doctor / dashboard 兼容读取。
-
-### PR 5:Projection Retry / Replay
-
-包含:
-
-- projection runner。
-- projections CLI。
-- writer 幂等测试。
-
-### PR 6:Skill / Agent 契约
-
-包含:
-
-- skill frontmatter。
-- agent frontmatter。
-- prompt integrity tests。
-
-### PR 7:Evals + Package Validator
-
-包含:
-
-- behavior eval runner。
-- validate plugin package。
-- release docs。
-
-### PR 8:Hooks
-
-包含:
-
-- SessionStart hook。
-- PreToolUse guard。
-- plugin-dev hook validation。
-
----
-
-## 16. 总体验收
-
-完成后应满足:
-
-1. 用户能运行 `/webnovel-doctor` 看懂项目文件、数据库、配置是否正常。
-2. `project-status` 能给短状态,且不占用现有 `status_reporter.py`。
-3. init 刚结束不会因为缺 commit / summary / vectors 被误报。
-4. 写章只在三个自然边界增加 gate 检查。
-5. agent 产物 schema 错误能被统一报告。
-6. projection 失败能定位 writer,并能补跑。
-7. commit 事实和 projection 执行日志可区分。
-8. Skill / Agent / Hook 结构符合官方 `plugin-dev` 规范。
-9. 发布前能校验插件包一致性。
-
----
-
-## 17. 最小先行版本
-
-如果要尽快落地一版高收益版本,建议只做前三项:
-
-1. `doctor`
-2. `project-status` / `project_phase`
-3. `artifact_validator`
-4. `write-gate`
-
-这三项能先解决最核心的问题:
-
-- 用户知道项目哪里坏。
-- runtime 和短状态共用同一套 phase。
-- runtime 知道 agent 产物是否可信。
-- 写章关键边界不再只靠文档约束。
-
-Projection log、retry/replay、hooks 和 evals 可以在基础稳定后继续推进。

+ 0 - 1388
docs/architecture/plugin-runtime-hardening-spec-2026-06-04.md

@@ -1,1388 +0,0 @@
-# Plugin Runtime Hardening Spec
-
-> 日期:2026-06-04
-> 状态:草案 v1
-> 范围:基于优秀 Claude Code 插件调研,对 `webnovel-writer` 的插件形态、运行时可靠性、workflow 编排、doctor 自检、hook 状态感知、eval 与发布治理做系统收束
-> 调研样本:`anthropics/claude-plugins-official`、`anthropics/skills`、`obra/superpowers`、`SonarSource/sonarqube-agent-plugins`、`appwrite/claude-plugin`、`aws-samples/sample-claude-code-plugins-for-startups`、社区多插件 marketplace
-
----
-
-## 1. 背景
-
-`webnovel-writer` 当前已经不是普通单一 Skill,而是一个完整的长篇写作运行时插件:
-
-- 7 个 Skill 命令负责 init / plan / write / review / query / learn / dashboard。
-- 4 个 Agent 负责写前上下文、审查、事实提取、参考拆解。
-- Python CLI 与 `data_modules` 承担 Story System、commit、projection、RAG、memory、Dashboard 数据层。
-- `.story-system/` 是合同与提交主链,`.webnovel/*` 是 projection / read-model。
-
-优秀 Claude Code 插件的共同经验是:
-
-1. `SKILL.md` 做路由和流程,不承载全部知识。
-2. 确定性动作下沉到脚本 / runtime / MCP,而不是靠 prompt 约束。
-3. `commands / skills / agents / hooks / MCP` 边界清楚。
-4. hooks 只做轻量状态提示、自检或接线,不做重业务。
-5. 复杂 workflow 有可验证的输入、输出、停止条件和验收标准。
-6. 有 `doctor / integrate / setup` 类环境自检入口。
-7. 有真实行为 eval,证明 agent 会按协议执行。
-8. manifest、marketplace、README、版本、LICENSE 有校验,避免漂移。
-
-本 spec 的目标是把这些经验转化为 `webnovel-writer` 的下一阶段架构改造路线。
-
----
-
-## 2. 一句话目标
-
-把 `webnovel-writer` 从“强 Skill 包 + Python 工具链”升级为:
-
-> 可自检、可验证、可恢复、可重放、可发版治理的长篇写作运行时插件。
-
----
-
-## 3. 设计原则
-
-### 3.1 Runtime First
-
-写章、提交、投影、校验等关键链路必须由 runtime 保证,不再主要依赖 Skill 文档中的自然语言步骤。
-
-### 3.2 Skill as Router
-
-`SKILL.md` 保留:
-
-- 何时触发
-- 决策树
-- 高层流程
-- 必读/按需引用路由
-- 失败处理边界
-
-`SKILL.md` 不应承担:
-
-- 长命令拼接细节
-- schema 校验逻辑
-- projection 修复逻辑
-- 大段题材知识
-- 可程序化验证规则
-
-### 3.3 Commit Is Fact
-
-`CHAPTER_COMMIT` 是写后事实,不应和 projection 执行日志混在一起。事实记录与投影执行状态要逐步解耦。
-
-### 3.4 Hooks Are Advisory Guards
-
-hooks 可以承担“自动触发的轻量守卫”,但不能成为隐藏业务流程。
-
-允许:
-
-- SessionStart 项目状态摘要
-- 依赖 / 配置提醒
-- doctor 入口提示
-- dashboard / RAG / Story System 健康提示
-- skill-scoped 固定预检,且通过时静默
-- PreToolUse 对危险写入 / commit 命令做硬阻断
-
-禁止:
-
-- 自动写 state / commit / memory
-- 自动安装外部依赖
-- 自动修改正文或设定
-- 注入大段创作方法论
-- 作为章节主状态机写入 step state
-- 每个步骤都用 hook 自动打点
-- 在用户不可见的情况下推进写作流程
-
-### 3.5 Behavior Must Be Tested
-
-现有 Python 单元测试继续保留,但不足以证明插件行为。必须增加 skill / agent 工作流级 eval。
-
-### 3.6 UTF-8 First
-
-本项目大量读取中文路径和中文文件名,新入口必须显式 UTF-8:
-
-- Python CLI 入口调用 `enable_windows_utf8_stdio()` 或等价逻辑。
-- 所有文本读取 / 写入显式 `encoding="utf-8"`。
-- hook / 子进程命令优先使用 `python -X utf8`,或显式设置 `PYTHONUTF8=1`。
-- doctor / project-status / write-gate / hook 脚本不得依赖系统默认编码。
-
-### 3.7 Follow Official `plugin-dev`
-
-后续对本插件的任何新增或修改,必须先遵循官方 `plugin-dev` 插件的指导:
-
-```text
-C:\Users\lcy\.claude\plugins\marketplaces\claude-plugins-official\plugins\plugin-dev
-```
-
-落地约束:
-
-- 插件结构遵循 `plugin-structure`:`.claude-plugin/plugin.json` 必须在插件根的 `.claude-plugin/` 下;`commands/`、`agents/`、`skills/`、`hooks/` 位于插件根层级。
-- 所有插件内路径使用 `${CLAUDE_PLUGIN_ROOT}`,不在 manifest / hook / command 中写死本机绝对路径。
-- 新增 Skill 遵循 `skill-development`:`SKILL.md` 必须有 `name`、具体触发型 `description`、可选 `version`;正文保持精简,详细规则放入 `references/`,确定性脚本放入 `scripts/`。
-- 新增 Command 遵循 `command-development`:使用 markdown + YAML frontmatter,包含清晰 `description`、必要时声明 `argument-hint` 与 `allowed-tools`。
-- 新增 Agent 遵循 `agent-development`:frontmatter 补齐 `name`、`description`、`model` / `tools` 等字段;复杂触发场景用示例描述;修改后用 validate-agent 规则检查。
-- 新增 Hook 遵循 `hook-development`:插件级 `hooks/hooks.json` 使用 wrapper 格式,即外层包含 `description` 与 `hooks`;命令 hook 使用 `${CLAUDE_PLUGIN_ROOT}`;轻量确定性检查用 command hook,上下文判断才用 prompt hook。
-- 修改插件组件后,必须按 `plugin-validator` 思路做结构校验:manifest、commands、agents、skills、hooks、MCP、README、LICENSE、敏感信息与路径可移植性。
-
-这条优先级高于本 spec 中任何自定义落点建议;如果冲突,以官方 `plugin-dev` 约束为准。
-
----
-
-## 4. 非目标
-
-本轮不做:
-
-- 不重写 Story System 主链语义。
-- 不引入大规模新 MCP 服务。
-- 不把 37 个题材模板拆成 37 个独立 Skill。
-- 不照搬 Superpowers 的高频 git commit 机制。
-- 不让 hook 承担写作业务。
-- 不在本轮重构 Dashboard 前端信息架构。
-- 不改变已有用户项目的数据格式,除非提供兼容读取。
-
----
-
-## 5. 目标架构
-
-### 5.1 组件边界
-
-```text
-commands/ 或 Slash Skill 入口
-        ↓
-Skill router(流程、引用路由、失败边界)
-        ↓
-Claude Code Todo(过程约束,由宿主管理)
-        ↓
-Runtime Gates(写前 / 提交前 / 提交后批量校验)
-        ↓
-Agents(context / draft / review / data extract)
-        ↓
-Artifact Validator
-        ↓
-CHAPTER_COMMIT(事实主链)
-        ↓
-Projection Engine(state/index/summary/memory/vector)
-        ↓
-Dashboard / Query / Doctor(只读消费)
-```
-
-### 5.2 写章过程管理
-
-不新增独立 resume / step mark / workflow state。Claude Code 本身已经有 Todo 和会话恢复能力,写章过程的步骤约束交给宿主 Todo 管理。
-
-推荐 Todo 形态:
-
-```text
-[ ] 写前预检与合同刷新
-[ ] context-agent 生成写作任务书
-[ ] 起草正文
-[ ] reviewer 审查
-[ ] blocking issue 裁决 / 定点修复
-[ ] 润色与排版
-[ ] data-agent 提取事实 artifacts
-[ ] chapter-commit 提交事实
-[ ] 验证 projection 与备份
-```
-
-runtime 不维护每一步状态,只提供三个自然边界的批量 gate:
-
-- `prewrite`:写前检查项目根、占位符、Story Runtime、章节合同。
-- `precommit`:提交前检查正文、review、fulfillment、disambiguation、extraction artifacts。
-- `postcommit`:提交后检查 commit、projection、summary、memory、backup。
-
-这样一章最多增加 2-3 次确定性脚本调用,不做每一步打点。
-
-### 5.3 状态感知模型
-
-项目状态分两层:
-
-| 层级 | 负责者 | 持久性 | 用途 |
-|---|---|---|---|
-| 会话内进度 | Claude Code Task / Todo | 会话级 | 约束本轮写作步骤、显示当前正在做什么 |
-| 项目真实状态 | Story System / commit / projection / artifacts | 项目级 | 新对话、resume、doctor 判断下一步 |
-
-不新增独立 workflow state。项目真实状态由 runtime 现场推导:
-
-- `.story-system/commits/*.commit.json` 判断最新 accepted/rejected 章节。
-- `.story-system/MASTER_SETTING.json` 和章节合同判断下一章目标。
-- `.webnovel/tmp/*` artifacts 判断是否已经 review / fulfillment / extraction。
-- `.webnovel/projection_log.jsonl` 或兼容字段判断 projection 是否失败。
-- draft 文件和 chapter artifact 判断是否存在未提交正文。
-
-新增机器可读的项目状态入口,避免占用现有 `webnovel.py status`。当前 `status` 已转发到 `status_reporter.py`,语义是宏观创作健康报告;本 spec 需要的是短状态摘要,因此使用新命令:
-
-```bash
-webnovel.py project-status --format json
-webnovel.py project-status --format summary
-```
-
-示例状态:
-
-```json
-{
-  "schema_version": "webnovel-project-status/v1",
-  "project": "灵石庄",
-  "latest_accepted_chapter": 12,
-  "target_chapter": 13,
-  "phase": "chapter_contract_ready",
-  "blocking": [],
-  "warnings": ["rag_vector_missing"],
-  "next_action": "run /webnovel-write chapter 13"
-}
-```
-
-`phase` 是一个可推导状态,不是 hook 写入的状态机。phase 词表必须只有一个权威来源,建议新增 `project_phase.py`,由 doctor、project-status、write-gate 共同消费。推荐最小集合:
-
-- `no_project`
-- `unknown`
-- `init_scaffolded`
-- `init_ready`
-- `plan_in_progress`
-- `chapter_contract_ready`
-- `draft_in_progress`
-- `ready_to_commit`
-- `chapter_committed`
-- `projection_failed`
-
-### 5.4 Hook 与状态的边界
-
-hook 只读状态、注入短上下文或阻断危险动作:
-
-- `SessionStart`:调用 `project-status --format summary`,在新对话、resume、clear、compact 后告诉 Claude 当前项目写到哪里。
-- `PreToolUse`:在 `webnovel-write` skill 激活期间,阻断绕过 gate 的 commit / projection 写入。
-- `PostToolUse`:可用于把 gate 失败原因补充给 Claude,但不能防止已经发生的副作用。
-
-状态转换只能来自显式 runtime 命令:
-
-- `write-gate --stage prewrite/precommit/postcommit`
-- `chapter-commit`
-- `projections retry/replay`
-- 用户显式裁决 blocking issue
-
-这保证流程推进发生在显式 skill / runtime 命令中,而不是 hook 暗中推进。
-
----
-
-## 6. Phase 1:`webnovel-doctor` 项目体检入口
-
-### 6.1 目标
-
-新增只读体检命令,作为现有 `preflight` 的上位诊断入口。`preflight` 已经负责 CLI 环境、project_root 与 `story_runtime` 摘要;`doctor` 必须复用或吸收这些检查,不另造一套并行环境检查。
-
-重点解决三类问题:
-
-1. **文件层面**:目录是否规范、关键文件是否缺失、JSON / SQLite / Markdown 等内容是否符合预期。
-2. **系统配置层面**:RAG API / key、Python 依赖、Dashboard 构建产物等运行条件是否完整。
-3. **错误解释与修复建议**:缺失或异常时说明影响范围,并给出可执行修复命令或人工处理建议。
-
-`doctor` 不负责判断一章具体该怎么写,也不替代 `write-gate`。它回答的是:
-
-> 这个书项目和当前插件运行环境是否完整、可读、可运行;如果不完整,哪里坏了,怎么修。
-
-### 6.2 入口
-
-CLI:
-
-```bash
-python -X utf8 webnovel-writer/scripts/webnovel.py --project-root "<PROJECT_ROOT>" doctor --format json
-```
-
-Skill:
-
-```text
-/webnovel-doctor
-```
-
-与现有入口关系:
-
-- `preflight`:保留为快速环境检查和兼容入口。
-- `doctor`:覆盖 `preflight` 的快检能力,并追加阶段感知文件清单、SQLite、RAG、Python 依赖、Dashboard、修复建议。
-- `project-status`:只输出短状态和下一步,不做深度体检。
-- `status`:保留现有 `status_reporter.py` 的宏观创作健康报告语义。
-
-可选后续 hook:
-
-```text
-SessionStart -> 打印 project-status 摘要;异常时提示运行 /webnovel-doctor
-```
-
-### 6.3 模式
-
-默认模式必须只做本地只读检查:
-
-```bash
-webnovel.py doctor --format json
-webnovel.py doctor --format text
-```
-
-可选深度模式才允许做慢检查或外部连通性检查:
-
-```bash
-webnovel.py doctor --deep --format json
-```
-
-可选章节模式用于检查指定章节相关 artifacts:
-
-```bash
-webnovel.py doctor --chapter 13 --format json
-```
-
-默认 `doctor` 禁止:
-
-- 写任何文件。
-- 自动修复。
-- 自动安装 Python / Node 依赖。
-- 自动启动 Dashboard。
-- 默认联网测试 RAG API。
-
-### 6.4 阶段感知的期望文件清单
-
-`doctor` 必须先判断项目当前阶段,再决定“这个阶段应该有哪些文件”。不能用最终态清单检查所有项目。
-
-#### 6.4.1 阶段推导
-
-阶段由共享 `project_phase.py` 现场推导,不写任何状态文件。doctor、project-status、write-gate 必须消费同一个 resolver,避免出现多套 phase 词表:
-
-| phase | 判定依据 | 含义 |
-|---|---|---|
-| `no_project` | project root 无效,或没有 `.webnovel/state.json` | 尚未初始化或未绑定书项目 |
-| `unknown` | 文件状态不足以稳定判断 | 只做低风险检查 |
-| `init_scaffolded` | 有 `.webnovel/state.json`、基础目录、设定集/总纲,但没有 `.story-system/MASTER_SETTING.json` | `webnovel.py init` 刚结束,Story System 尚未生成 |
-| `init_ready` | 有 `.webnovel/state.json`、基础设定集、`大纲/总纲.md`、`.story-system/MASTER_SETTING.json` | init 完成,可进入 plan |
-| `plan_in_progress` | 有 MASTER_SETTING,但卷/章合同不完整 | 正在规划,尚不能直接写章 |
-| `chapter_contract_ready` | 指定章节有 volume / chapter / review 合同 | 可进入写前上下文和起草 |
-| `draft_in_progress` | 指定章节有正文草稿或 `.webnovel/tmp` artifacts | 写章中或审查中 |
-| `ready_to_commit` | review / fulfillment / disambiguation / extraction artifacts 都存在 | 可进入 precommit gate |
-| `chapter_committed` | 指定章节有 commit | 章节已提交,检查 projection |
-| `projection_failed` | latest commit 有 `projection_status.failed:*` | read-model 不可信,需要修复 |
-
-如果无法确定阶段,返回 `phase=unknown`,并只做低风险文件可读性检查。
-
-#### 6.4.2 阶段期望清单
-
-`doctor` 输出必须包含当前阶段的期望清单:
-
-```json
-{
-  "phase": "init_ready",
-  "expected_profile": "after_init",
-  "expected_files": {
-    "required": [
-      ".webnovel/state.json",
-      ".webnovel/summaries/",
-      "设定集/世界观.md",
-      "设定集/力量体系.md",
-      "设定集/主角卡.md",
-      "设定集/反派设计.md",
-      "大纲/总纲.md",
-      ".env.example",
-      ".story-system/MASTER_SETTING.json"
-    ],
-    "conditional": [
-      "设定集/主角组.md",
-      "设定集/女主卡.md"
-    ],
-    "not_expected_yet": [
-      ".story-system/volumes/volume_001.json",
-      ".story-system/chapters/chapter_001.json",
-      ".story-system/reviews/chapter_001.review.json",
-      ".story-system/commits/chapter_001.commit.json",
-      ".webnovel/summaries/chapter_001.md",
-      ".webnovel/memory_scratchpad.json"
-    ]
-  }
-}
-```
-
-`conditional` 文件必须根据 `state.json` 判断。例如:
-
-- `protagonist_structure` 是多主角 / 主角组时,才要求 `设定集/主角组.md`。
-- `heroine_config` 不是无女主时,才要求 `设定集/女主卡.md`。
-- 无金手指项目不要求单独 `金手指设计.md`。
-
-#### 6.4.3 init 刚结束的判定
-
-`webnovel.py init` 刚结束时,合理期望是项目骨架完整,但不要求写作后产物。
-
-必须存在:
-
-```text
-.webnovel/
-.webnovel/backups/
-.webnovel/archive/
-.webnovel/summaries/
-.webnovel/state.json
-设定集/
-设定集/世界观.md
-设定集/力量体系.md
-设定集/主角卡.md
-设定集/反派设计.md
-大纲/
-大纲/总纲.md
-正文/
-审查报告/
-.env.example
-```
-
-如果 `/webnovel-init` 已完成 Story System 初始化,还必须存在:
-
-```text
-.story-system/
-.story-system/MASTER_SETTING.json
-.story-system/anti_patterns.json
-```
-
-init 阶段不应该要求:
-
-```text
-.story-system/volumes/volume_001.json
-.story-system/chapters/chapter_001.json
-.story-system/reviews/chapter_001.review.json
-.story-system/commits/chapter_001.commit.json
-.webnovel/summaries/chapter_001.md
-.webnovel/memory_scratchpad.json
-.webnovel/vectors.db
-```
-
-缺这些只能返回 `skip` 或 `info`,不能作为 warning / blocker。
-
-#### 6.4.4 plan / write / commit 阶段清单
-
-规划完成后,才开始要求:
-
-```text
-.story-system/volumes/volume_001.json
-.story-system/chapters/chapter_001.json
-.story-system/reviews/chapter_001.review.json
-```
-
-写章中,才开始检查:
-
-```text
-.webnovel/tmp/review_results.json
-.webnovel/tmp/fulfillment_result.json
-.webnovel/tmp/disambiguation_result.json
-.webnovel/tmp/extraction_result.json
-```
-
-commit 后,才开始要求:
-
-```text
-.story-system/commits/chapter_001.commit.json
-.webnovel/summaries/chapter_001.md
-.webnovel/index.db
-```
-
-RAG 向量库永远是增强项:
-
-```text
-.webnovel/vectors.db
-```
-
-缺失或为空默认只返回 warning,并说明会降级 BM25;在用户显式要求语义检索或 `--deep --require-rag` 时才可升级为 blocker。
-
-#### 6.4.5 误报控制
-
-`doctor` 的严重级别必须基于“当前阶段 + 用户目标”判断:
-
-| 情况 | 阶段 | 结果 |
-|---|---|---|
-| 缺 commit | `init_ready` | `skip` / `info` |
-| 缺 commit | `ready_to_commit` | `blocker` |
-| 缺 summary | `init_ready` | `skip` / `info` |
-| 缺 summary | `chapter_committed` 且 projection summary=done | `blocker` |
-| 缺 vectors.db | 任意默认模式 | `warning` |
-| 缺 MASTER_SETTING | `init_scaffolded` | `warning`,提示运行 story-system persist |
-| 缺 MASTER_SETTING | `plan_in_progress` 或之后 | `blocker` |
-
-### 6.5 文件 / 数据结构检查
-
-`doctor` 必须把“肉眼难看见”的项目文件和数据库结构变成可读报告。
-
-#### 6.5.1 目录结构
-
-检查:
-
-- project root 是否有效,且不是插件目录本身。
-- `.webnovel/` 是否存在。
-- `.story-system/` 是否存在。
-- `正文/`、`大纲/`、`设定集/` 等书项目目录是否存在。
-- 用户项目文件是否误写入插件目录。
-
-判定:
-
-- project root 无效:`blocker`。
-- 缺 `.webnovel/` 或 `.story-system/`:`blocker` 或 `warning`,取决于是否是刚 init 的项目。
-- 缺正文/大纲/设定集目录:`warning`,并提示初始化或补建。
-
-#### 6.5.2 Story System 主链文件
-
-检查:
-
-- `.story-system/MASTER_SETTING.json` 是否存在、JSON 可读、`meta.contract_type` 是否正确。
-- `volumes/volume_*.json` 是否存在、JSON 可读。
-- `chapters/chapter_*.json` 是否存在、JSON 可读。
-- `reviews/chapter_*.review.json` 是否存在、JSON 可读。
-- `commits/chapter_*.commit.json` 是否存在、JSON 可读。
-- latest commit 的 `meta.status` 是否是 `accepted` / `rejected`。
-- latest commit 的 `provenance.write_fact_role` 是否为 `chapter_commit`。
-
-判定:
-
-- 主链 JSON 读不出来:`blocker`。
-- 已进入写作流程但缺 MASTER_SETTING:`blocker`。
-- latest commit schema 明显不合法:`blocker`。
-- 新项目尚无 commit:`info` 或 `warning`,不能误报为错误。
-
-#### 6.5.3 Projection / Read-model 文件
-
-检查:
-
-- `.webnovel/state.json` 是否存在、JSON 可读、基础字段可解析。
-- `.webnovel/summaries/` 是否存在,最新 accepted 章节是否有 summary。
-- `.webnovel/memory_scratchpad.json` 是否存在、JSON 可读、基础结构可解析。
-- latest commit 的 `projection_status` 是否有 `pending` / `failed:*`。
-
-判定:
-
-- `state.json` 不可读:`blocker`。
-- projection writer failed:`blocker`,因为后续查询和 dashboard 可能不可信。
-- summary / memory 缺失:通常 `warning`,除非对应 projection 标记为 done 但实物不存在。
-
-#### 6.5.4 SQLite 数据库
-
-检查 `.webnovel/index.db`:
-
-- 文件是否存在。
-- SQLite 是否可打开。
-- 关键表是否存在。
-- 关键表行数是否异常。
-- 基础查询是否能执行。
-
-建议首批关键表:
-
-```text
-entities
-relationships
-story_events
-review_metrics
-writing_checklist_scores
-override_ledger
-```
-
-检查 `.webnovel/vectors.db`:
-
-- 文件是否存在。
-- SQLite 是否可打开。
-- `vectors` 表是否存在。
-- vector 行数。
-- `bm25_index` / `doc_stats` 是否存在。
-
-数据库报告必须显式展示表和行数,例如:
-
-```json
-{
-  "id": "db.index.tables",
-  "status": "ok",
-  "severity": "info",
-  "path": ".webnovel/index.db",
-  "tables": {
-    "entities": 128,
-    "relationships": 42,
-    "story_events": 36,
-    "review_metrics": 12
-  }
-}
-```
-
-判定:
-
-- `index.db` 不存在或打不开:`blocker`。
-- `story_events` 缺失:`warning` 或 `blocker`,取决于当前是否已经有 accepted commit。
-- `vectors.db` 缺失:`warning`,RAG 可降级 BM25。
-- `vectors` 行数为 0:`warning`。
-
-#### 6.5.5 Reference / CSV 文件
-
-检查:
-
-- `references/csv/*.csv` 是否存在。
-- 必要 CSV 表头是否符合预期。
-- 题材别名、题材与调性推理、反模式等核心表是否可读。
-- 明显占位符是否残留。
-
-判定:
-
-- 核心 CSV 不可读或表头缺失:`warning`。
-- 会导致 story-system 无法生成 MASTER_SETTING 的缺失:`blocker`。
-
-### 6.6 系统 / 配置检查
-
-#### 6.6.1 Python 依赖
-
-检查:
-
-- 当前 Python 版本。
-- `scripts/requirements.txt` 是否存在。
-- 核心包是否可 import。
-
-首批核心包:
-
-```text
-pydantic
-numpy
-requests
-fastapi
-uvicorn
-watchdog
-```
-
-判定:
-
-- 运行 CLI 必需包缺失:`blocker`。
-- Dashboard 专用包缺失:`warning`,除非用户正在运行 dashboard skill。
-
-#### 6.6.2 RAG 配置
-
-默认模式检查:
-
-- `.env` / 环境变量是否能读到 embedding 配置。
-- embed base_url / model 是否配置。
-- embed api_key 是否存在。
-- rerank base_url / model / api_key 是否存在。
-- `vectors.db` 是否存在且有数据。
-- 当前推断 RAG 模式:`full` / `embed_only` / `bm25_only`。
-
-`--deep` 模式才检查:
-
-- embed API 是否真实可调用。
-- rerank API 是否真实可调用。
-- API 返回维度是否与已有 vectors 兼容。
-
-判定:
-
-- 缺 RAG key:`warning`,必须明确说明会降级到 BM25。
-- API 连通失败:`warning` 或 `blocker`,取决于用户是否要求必须语义检索。
-- base_url / model 明显空缺:`warning`。
-
-#### 6.6.3 Dashboard / Node
-
-检查:
-
-- `dashboard/frontend/dist/index.html` 是否存在。
-- dashboard 后端模块是否能 import。
-- `dashboard/requirements.txt` 是否存在。
-- `dashboard/frontend/package.json` 是否存在。
-
-默认不检查:
-
-- 不自动 `npm install`。
-- 不自动启动服务。
-- 不默认检查 localhost 端口。
-
-判定:
-
-- dist 缺失:`warning`,提示重新 build。
-- FastAPI 依赖缺失:`warning`。
-
-### 6.7 输出格式
-
-每条检查必须包含:
-
-- `id`:稳定错误码,方便测试和 UI 展示。
-- `status`:`ok` / `fail` / `warn` / `skip`。
-- `severity`:`blocker` / `warning` / `info`。
-- `path`:相关文件路径,没有则为空。
-- `expected`:预期状态。
-- `actual`:实际状态。
-- `impact`:对用户有什么影响。
-- `repair`:修复命令或人工修复建议。
-
-```json
-{
-  "ok": false,
-  "project_root": "...",
-  "mode": "default",
-  "phase": "chapter_committed",
-  "expected_profile": "after_commit",
-  "blocking_count": 1,
-  "warning_count": 2,
-  "expected_files": {
-    "required": [".webnovel/state.json", ".story-system/commits/chapter_001.commit.json"],
-    "not_expected_yet": []
-  },
-  "checks": [
-    {
-      "id": "db.index.missing_table",
-      "status": "fail",
-      "severity": "blocker",
-      "path": ".webnovel/index.db",
-      "expected": "table story_events exists",
-      "actual": "table missing",
-      "impact": "无法确认 accepted commit 的事件链是否完成投影",
-      "repair": {
-        "command": "webnovel.py projections replay --from 1 --to latest --writers index",
-        "manual": "如果 replay 尚未实现,先重新执行最近章节的 chapter-commit 或从备份恢复 index.db"
-      }
-    }
-  ],
-  "recommended_actions": [
-    {
-      "command": "webnovel.py rag stats",
-      "reason": "vectors.db missing; semantic retrieval will fall back to BM25",
-      "severity": "warning"
-    }
-  ]
-}
-```
-
-### 6.8 错误码命名
-
-错误码按域划分:
-
-```text
-project.root.invalid
-project.phase.unknown
-project.expected_file.missing
-project.structure.missing_dir
-story.master.missing
-story.commit.invalid_json
-story.commit.invalid_status
-projection.status.failed
-projection.file.missing
-db.index.unreadable
-db.index.missing_table
-db.vector.empty
-rag.embed.key_missing
-rag.embed.api_unreachable
-python.import_missing
-dashboard.dist_missing
-reference.csv.invalid_header
-artifact.schema_error
-```
-
-### 6.9 文件落点
-
-- `webnovel-writer/scripts/data_modules/doctor.py`
-- `webnovel-writer/scripts/data_modules/project_phase.py`
-- `webnovel-writer/scripts/data_modules/project_status.py`
-- `webnovel-writer/scripts/data_modules/webnovel.py`
-- `webnovel-writer/skills/webnovel-doctor/SKILL.md`
-- `webnovel-writer/scripts/data_modules/tests/test_doctor.py`
-- `webnovel-writer/scripts/data_modules/tests/test_project_phase.py`
-- `webnovel-writer/scripts/data_modules/tests/test_project_status.py`
-- `docs/guides/commands.md`
-
-### 6.10 验收
-
-- 空项目返回 `ok=false`,但不写任何文件。
-- init 刚结束时能识别 `phase=init_scaffolded` 或 `phase=init_ready`,并返回该阶段的 `expected_files`。
-- init 刚结束时缺 commit / summary / memory / vectors.db 不得返回 blocker。
-- init 刚结束时缺 `state.json`、`设定集/世界观.md`、`大纲/总纲.md` 必须返回 blocker 或 warning,并给出补救命令。
-- `MASTER_SETTING.json` 在 `init_scaffolded` 阶段缺失是 warning,在 plan/write 阶段缺失是 blocker。
-- 正常项目返回 `ok=true`,并显示 `index.db` / `vectors.db` 的关键表和行数。
-- 缺 `state.json` 返回 `project.structure` 或 `projection.file` 类 blocker。
-- `index.db` 缺关键表时返回稳定错误码、影响说明和修复建议。
-- `vectors.db` 缺失或为空时返回 warning,并明确说明 RAG 会降级到 BM25。
-- 缺 RAG key 时返回 warning,不阻断普通写作。
-- Python 必需包缺失时返回 blocker,并提示安装 `scripts/requirements.txt`。
-- Dashboard dist 缺失时返回 warning,并提示 build 命令。
-- latest commit projection failed 时返回 actionable command。
-- 默认模式不联网、不安装依赖、不启动服务、不写文件。
-- `--deep` 模式可进行 RAG API ping,但必须明确标记为 deep check。
-- `preflight` 仍可运行;其结果与 doctor 的快检部分不冲突。
-- 所有中文路径和中文文件读取在 Windows 下使用 UTF-8,不因默认 GBK 失败。
-
----
-
-## 7. Phase 2:章节 Runtime Gates
-
-### 7.1 目标
-
-不重造一套 workflow/resume 系统。把 `/webnovel-write` 中最容易出错的关键边界下沉为批量校验 gate,过程顺序由 Claude Code Todo 约束。
-
-实施顺序上,Runtime Gates 必须依赖 Artifact Validator 的统一错误语义;本节描述 gate 设计,不代表先于 validator 开工。
-
-### 7.2 新增模块
-
-建议新增 gate 外壳,但 `prewrite` 必须包装或迁移现有 `PrewriteValidator`,不得重写一套占位符和合同判断逻辑:
-
-```text
-webnovel-writer/scripts/data_modules/write_gates/
-  __init__.py
-  prewrite.py
-  precommit.py
-  postcommit.py
-```
-
-已有复用点:
-
-- `webnovel-writer/scripts/data_modules/prewrite_validator.py`
-- `webnovel-writer/scripts/data_modules/tests/test_prewrite_validator.py`
-
-### 7.3 Gate 设计
-
-不写 `.workflow.json`,不维护 step state。每次 gate 根据现有项目文件和 artifacts 现场计算结果。
-
-统一输出:
-
-```json
-{
-  "schema_version": "write-gate/v1",
-  "chapter": 12,
-  "stage": "precommit",
-  "ok": false,
-  "blocking": [
-    {
-      "type": "pending_disambiguation",
-      "detail": "disambiguation_result.pending is not empty"
-    }
-  ],
-  "warnings": [],
-  "artifacts": {
-    "review_result": ".webnovel/tmp/review_results.json",
-    "fulfillment_result": ".webnovel/tmp/fulfillment_result.json",
-    "disambiguation_result": ".webnovel/tmp/disambiguation_result.json",
-    "extraction_result": ".webnovel/tmp/extraction_result.json"
-  }
-}
-```
-
-### 7.4 Gate 职责
-
-runtime gate 负责:
-
-- 校验必要文件存在。
-- 校验 JSON schema。
-- prewrite 阶段复用 `PrewriteValidator`。
-- 判定 blocking issue。
-- 判定是否允许进入下一自然阶段。
-- 输出明确的失败原因和建议命令。
-
-runtime gate 不负责:
-
-- 代替 LLM 起草正文。
-- 代替 Agent 做审查。
-- 自动决定用户裁决。
-- 记录每一步进度。
-- 替代 Claude Code Todo / 会话恢复能力。
-
-### 7.5 CLI 子命令
-
-```bash
-webnovel.py write-gate --chapter N --stage prewrite --format json
-webnovel.py write-gate --chapter N --stage precommit --format json
-webnovel.py write-gate --chapter N --stage postcommit --format json
-```
-
-### 7.6 Skill 改动
-
-`webnovel-write/SKILL.md` 改为:
-
-1. 使用 Claude Code Todo 建立本章流程清单。
-2. 调 `write-gate --stage prewrite`,通过后才写。
-3. 调 context-agent。
-4. 起草正文。
-5. 调 reviewer。
-6. blocking issue 由 Todo 记录并裁决 / 定点修复。
-7. 润色后调 data-agent。
-8. 调 `write-gate --stage precommit`,通过后才提交。
-9. 调 chapter-commit。
-10. 调 `write-gate --stage postcommit`,通过后才宣布完成。
-
-### 7.7 验收
-
-- 缺 `review_results.json` 时不允许进入 commit。
-- reviewer 有 blocking issue 时 `precommit.ok=false`。
-- disambiguation pending 非空时 `precommit.ok=false`。
-- projection failed 时 `postcommit.ok=false`。
-- gate 调用次数控制在每章 2-3 次,不做逐步 mark。
-
----
-
-## 8. Phase 3:Artifact Validator
-
-### 8.1 目标
-
-统一校验所有 agent 产物,避免字段名漂移、包错外层、缺 required 字段。
-
-### 8.2 校验对象
-
-- `review_results.json`
-- `fulfillment_result.json`
-- `disambiguation_result.json`
-- `extraction_result.json`
-- `chapter_XXX.commit.json`
-- `projection_status`
-
-权威 schema 来源:
-
-- `review_results.json`、`fulfillment_result.json`、`disambiguation_result.json`、`extraction_result.json` 默认以 `chapter_commit_schema.py` 中 commit 所需的 Pydantic model 为准。
-- `review_schema.py` 和 `entity_linker.py` 中同名 / 近名模型只作为上游工具局部模型,不作为 commit artifact 的最终权威。
-- 如需兼容上游局部模型输出,必须在 `artifact_validator.py` 显式做 normalize,并在输出中标注兼容来源。
-
-### 8.3 输出错误分类
-
-```text
-schema_error
-missing_artifact
-blocking_review
-missed_outline_node
-pending_disambiguation
-commit_rejected
-projection_failure
-unsafe_project_root
-placeholder_blocker
-```
-
-### 8.4 文件落点
-
-- `webnovel-writer/scripts/data_modules/artifact_validator.py`
-- `webnovel-writer/scripts/data_modules/tests/test_artifact_validator.py`
-- `webnovel-writer/scripts/data_modules/write_gates/precommit.py`
-
-### 8.5 验收
-
-- `extraction_result.json` 外层包成 `{"extraction": ...}` 时返回 schema_error。
-- `state_deltas` 使用旧字段名时能兼容或给出明确诊断。
-- `disambiguation_result.pending` 非空时阻断 commit。
-- `fulfillment_result.missed_nodes` 非空时阻断 accepted commit。
-- `ReviewResult` / `DisambiguationResult` 等同名模型不再各自漂移,validator 明确以 commit artifact schema 为准。
-
----
-
-## 9. Phase 4:Commit 不可变与 Projection Log 外置
-
-### 9.1 当前问题
-
-当前 `ChapterCommitService` 会:
-
-1. build commit。
-2. persist commit。
-3. apply projections。
-4. 将 `projection_status` 写回 commit。
-
-这让 commit 同时承担“事实记录”和“投影执行日志”两个职责。
-
-### 9.2 目标
-
-将事实与投影执行状态拆开:
-
-```text
-.story-system/commits/chapter_012.commit.json     # 不可变事实
-.webnovel/projection_log.jsonl                    # 投影执行日志
-index.db.projection_runs                           # 可查询投影状态
-```
-
-### 9.3 迁移策略
-
-Phase 4 不强制立刻删除 commit 内 `projection_status`,采用双写过渡:
-
-1. 保留 commit 内 projection_status 兼容 Dashboard。
-2. 新增 projection log。
-3. Dashboard / doctor 优先读取 projection log。
-4. 后续版本再将 commit 内 projection_status 标记 deprecated。
-
-### 9.4 Projection Run Schema
-
-```json
-{
-  "run_id": "ch012-20260604T102233",
-  "chapter": 12,
-  "commit_path": ".story-system/commits/chapter_012.commit.json",
-  "commit_hash": "sha256:...",
-  "writer": "memory",
-  "status": "done",
-  "started_at": "...",
-  "finished_at": "...",
-  "error": "",
-  "retry_of": ""
-}
-```
-
-### 9.5 文件落点
-
-- `webnovel-writer/scripts/data_modules/projection_log.py`
-- `webnovel-writer/scripts/data_modules/chapter_commit_service.py`
-- `webnovel-writer/scripts/data_modules/tests/test_projection_log.py`
-- `webnovel-writer/dashboard/app.py`
-- `webnovel-writer/scripts/data_modules/story_runtime_health.py`
-
-### 9.6 验收
-
-- 每个 writer 执行后都有 projection log。
-- 单 writer failed 不影响其他 writer 记录。
-- doctor 能指出 failed writer 和建议重跑命令。
-- commit 文件 hash 在 projection log 中可追溯。
-
----
-
-## 10. Phase 5:Projection Replay / Retry
-
-### 10.1 目标
-
-投影失败时可以只补跑失败 writer,尤其是 vector / RAG 等外部依赖。
-
-### 10.2 CLI
-
-```bash
-webnovel.py projections status --chapter N
-webnovel.py projections retry --chapter N --writer vector
-webnovel.py projections retry-failed --chapter N
-webnovel.py projections replay --from 1 --to 20 --writers state,index,summary
-```
-
-### 10.3 约束
-
-- replay 只能读取 accepted commit。
-- rejected commit 只允许 state writer 更新状态。
-- writer 必须幂等。
-- retry 不得修改 commit 事实内容。
-
-### 10.4 文件落点
-
-- `webnovel-writer/scripts/data_modules/projection_runner.py`
-- `webnovel-writer/scripts/data_modules/event_projection_router.py`
-- projection writer 幂等性测试
-
-### 10.5 验收
-
-- 删除 `summaries/chapter_012.md` 后 retry summary 可恢复。
-- vector API key 缺失时 vector failed,其余 writer done。
-- 配置 key 后 retry vector 只补 vector。
-- replay 1-5 后 state/index/summary 与 commit 链一致。
-
----
-
-## 11. Phase 6:Skill / Agent 契约补强
-
-### 11.1 Skill Frontmatter
-
-7 个现有 Skill 的 `description` 要从单句说明升级为召回规则:
-
-- 何时使用。
-- 典型触发词。
-- 不适用场景。
-- 是否有副作用。
-
-示例:
-
-```yaml
-description: Use when the user wants to draft, continue, rewrite, or commit a numbered webnovel chapter. Runs the full context -> draft -> review -> polish -> fact extraction -> chapter commit workflow. Do not use for pure status queries, project initialization, or dashboard-only requests.
-```
-
-### 11.2 Agent Frontmatter
-
-所有 agent 补齐:
-
-- `name`
-- `description`
-- `tools`
-- `model` 可选
-- `output_schema`
-- `failure_statuses`
-
-### 11.3 Agent 分工调整
-
-现有:
-
-- `context-agent`
-- `reviewer`
-- `data-agent`
-- `deconstruction-agent`
-
-建议新增或拆分:
-
-- `continuity-reviewer`:设定 / 时间线 / 人物状态 / 伏笔合规。
-- `style-reviewer`:文风 / AI 味 / 句式重复 / 排版。
-- `reader-pull-reviewer`:爽点 / 钩子 / 微兑现 / 追读力。
-
-短期可以先不新增文件,而是在 `reviewer` 输出 schema 中拆维度;中期再拆 agent。
-
-### 11.4 验收
-
-- prompt integrity 测试确认所有 Skill 有足够长的 description。
-- agent 输出 schema 可被 artifact validator 校验。
-- data-agent 文档中仍明确“不直接写 state/index/summaries/memory”。
-
----
-
-## 12. Phase 7:Behavior Evals
-
-### 12.1 目标
-
-学习 Superpowers 的 headless 行为测试思路,补上“插件是否按协议执行”的验证层。
-
-### 12.2 Eval 类型
-
-新增:
-
-```text
-evals/
-  skill-triggering/
-  workflow-behavior/
-  agent-output-schema/
-  continuity-conflict/
-  memory-commit/
-```
-
-### 12.3 首批用例
-
-| Eval | 目标 |
-|---|---|
-| init_project_safety | 不在插件目录生成项目,不污染 canon |
-| plan_outputs_executable_chapter_tasks | 章纲包含目标情绪、人物变化、伏笔、禁写事项 |
-| write_blocks_on_review_blocking_issue | blocking issue 不进入 commit |
-| data_agent_never_writes_projection | data-agent 只产出 artifacts |
-| commit_drives_projection | accepted commit 后 projection writer 被触发 |
-| query_falls_back_explicitly | 主链缺失时 query 明确说明 fallback |
-| dashboard_readonly | Dashboard API 不提供写接口 |
-
-### 12.4 Runner
-
-先做轻量 runner:
-
-```bash
-python webnovel-writer/scripts/run_behavior_evals.py --case write_blocks_on_review_blocking_issue
-```
-
-如果本地没有 Claude Code CLI,则 eval 可跳过 transcript 测试,只跑 artifact fixture 测试。
-
-### 12.5 验收
-
-- 每个 Skill 至少有 1 个 eval。
-- `webnovel-write` 至少覆盖成功链路和 blocking 链路。
-- eval 输出 JSON 报告,包含 pass/fail/reason/artifacts。
-
----
-
-## 13. Phase 8:Manifest / Marketplace / 发布治理
-
-### 13.1 目标
-
-防止插件元数据、README、marketplace、version 之间漂移。
-
-### 13.2 校验脚本
-
-新增:
-
-```bash
-python webnovel-writer/scripts/validate_plugin_package.py
-```
-
-检查:
-
-- 根 `.claude-plugin/marketplace.json` 存在。
-- 插件 `.claude-plugin/plugin.json` 存在。
-- marketplace version 与 plugin.json version 一致。
-- README / 现有 CI 使用的版本位置与 plugin.json 一致;不得新增一套与既有 Plugin Version Check 冲突的 README 版本规则。
-- 每个 `skills/*/SKILL.md` 有 frontmatter。
-- 每个 `agents/*.md` 有 frontmatter。
-- LICENSE 存在。
-- Dashboard dist 存在。
-- `scripts/requirements.txt` 与根 `requirements.txt` 可解析。
-- docs 命令表与实际 Skill 名称一致。
-
-### 13.3 可选 manifest 增强
-
-如 Claude Code manifest 支持,可补:
-
-- `commands`
-- `agents`
-- `hooks`
-- `mcpServers`
-- user config schema
-- screenshots / assets
-
-如果当前宿主不需要显式声明,则保持默认目录发现,避免过度配置。
-
-### 13.4 验收
-
-- clean clone 后 validate 通过。
-- 修改 version 任一处导致 validate 失败。
-- 删除一个 Skill frontmatter 导致 validate 失败。
-- 版本校验复用或对齐现有 CI 规则,不与 README 版本表 / badge 检查互相打架。
-
----
-
-## 14. Phase 9:轻量 SessionStart Hook(可选)
-
-### 14.1 目标
-
-新增可选 hook,在会话启动、resume、clear、compact 时提示项目状态。该 hook 是状态观察器,不是状态机。
-
-### 14.2 输出
-
-```text
-Webnovel Writer initialized.
-  project: 灵石庄
-  story runtime: mainline ready, latest chapter 12 accepted
-  projections: 4 done, 1 failed(vector)
-  rag: BM25 fallback; EMBED_API_KEY missing
-  next: run /webnovel-doctor for details
-```
-
-### 14.3 约束
-
-- 只读。
-- 不安装依赖。
-- 不写任何文件。
-- 输出不超过 8 行。
-- 正常状态下只输出摘要,不输出完整 JSON。
-- 失败时给出一个下一步命令,不展开长诊断。
-- 可通过环境变量关闭:
-
-```text
-WEBNOVEL_DISABLE_SESSION_HOOK=1
-```
-
-### 14.4 文件落点
-
-- `webnovel-writer/hooks/hooks.json`
-- `webnovel-writer/hooks/session_start.py`
-- `webnovel-writer/.claude-plugin/plugin.json` 或默认 hook 发现路径
-- `docs/operations/operations.md`
-
-### 14.5 验收
-
-- 无项目根时不报错,只提示未绑定项目。
-- 有项目根时调用 `project-status --format summary` 或 doctor summary。
-- 设置 disable env 后无输出。
-- resume 后能刷新 latest chapter / projection 状态。
-- 输出不会超过 1000 字符。
-
-### 14.6 Skill-scoped 预检 Hook(可选)
-
-对 `/webnovel-write` 这类高风险 skill,可以在 skill frontmatter 中挂轻量 hook:
-
-- `PreToolUse(Bash)`:对直接运行 `chapter_commit.py`、`webnovel.py chapter-commit` 或 projection 写入命令做 best-effort 提醒 / 兜底阻断。Bash 字符串解析不能作为唯一可靠保证,真正的强保证必须在 runtime gate 和 commit 入口中实现。
-- `PreToolUse(Write|Edit)`:如果目标路径是 `.story-system/` commit、`.webnovel/state.json`、`index.db`、`memory_scratchpad.json` 等 projection 产物,则要求走 runtime 命令。
-- hook 通过时必须静默;只在阻断时返回简短原因。
-
-不建议把所有固定预检都放进 hook。推荐分层:
-
-| 预检类型 | 放在哪里 |
-|---|---|
-| 新会话状态摘要 | plugin-level `SessionStart` hook |
-| 是否能开始写本章 | `write-gate --stage prewrite` |
-| 是否能提交本章 | `write-gate --stage precommit` |
-| 是否能宣布完成 | `write-gate --stage postcommit` |
-| 禁止绕过 runtime 写主链 | skill-scoped `PreToolUse` hook |
-| 复杂修复建议 | `/webnovel-doctor` skill |
-
-## 15. 推荐实施顺序
-
-1. `project_phase` + `project-status` + `webnovel-doctor`:先建立统一阶段推导、短状态和只读自检基本盘。
-2. `Artifact Validator`:统一错误语义。
-3. `Runtime Gates`:用写前 / 提交前 / 提交后批量校验约束关键边界,其中 prewrite 复用 `PrewriteValidator`。
-4. `Projection Log`:事实与投影日志解耦。
-5. `Projection Retry / Replay`:补恢复能力。
-6. `Skill / Agent 契约补强`:降低 prompt 漂移。
-7. `Behavior Evals`:证明插件协议有效。
-8. `Plugin Package Validator`:发布治理。
-9. 可选 `SessionStart Hook`:只读状态提示。
-10. 可选 `Skill-scoped PreToolUse Hook`:阻断绕过 runtime 的危险写入。
-
----
-
-## 16. 验收总表
-
-| 能力 | 验收标准 |
-|---|---|
-| Doctor | 能只读报告目录/文件/数据库完整性、RAG/Python/Dashboard 配置,并给出修复建议 |
-| Project Status | 能从主链和 artifacts 推导当前章节阶段,不占用既有 `status_reporter.py` 语义,不写 workflow state |
-| Runtime Gates | `/webnovel-write` 在写前、提交前、提交后三个自然边界有批量校验 |
-| Validator | agent 产物 schema 漂移能被统一诊断 |
-| Commit | commit 事实与 projection log 可分离追溯 |
-| Replay | vector/summary 等投影失败后可单独 retry |
-| Skills | 7 个 Skill description 足够路由,长知识按需加载 |
-| Agents | agent 有工具范围、输出 schema、失败状态 |
-| Evals | 每个 Skill 至少 1 个行为 eval |
-| Package | manifest / marketplace / README / version 可校验 |
-| Hook | 如果启用,SessionStart 只读短输出,PreToolUse 只做危险动作阻断 |
-
----
-
-## 17. 风险
-
-### 17.1 过度工程化
-
-风险:为了学习优秀插件,把当前系统拆得太碎。
-控制:先做 doctor / validator / runtime gates 三个高收益模块,不急着拆 37 个题材 Skill。
-
-### 17.2 Hook 副作用
-
-风险:hook 自动执行导致用户不信任插件。
-控制:SessionStart hook 只读,PreToolUse hook 只阻断危险动作;所有修复和推进必须由 skill / runtime 显式触发。
-
-### 17.3 Hook 状态机漂移
-
-风险:如果 hook 自己写状态,可能与 commit / projection / Todo 产生三套真相。
-控制:状态由共享 `project_phase` / `project-status` 现场推导;hook 不写状态;流程推进只由显式 skill / runtime 命令触发。
-
-### 17.4 Commit 迁移破坏 Dashboard
-
-风险:projection_status 外置后 Dashboard 读不到状态。
-控制:先双写,Dashboard 优先读新 projection log,旧字段保留一个版本周期。
-
-### 17.5 Eval 成本高
-
-风险:headless Claude 行为 eval 慢且贵。
-控制:分 fast fixture eval 和 slow transcript eval;CI 默认只跑 fast。
-
-### 17.6 Skill 触发变化
-
-风险:description 改长后触发行为变化。
-控制:增加 skill-triggering eval,先验证再发布。
-
----
-
-## 18. 不变量
-
-无论如何重构,必须保持:
-
-1. `.story-system/` 是主链真源。
-2. accepted `CHAPTER_COMMIT` 是写后事实入口。
-3. `.webnovel/state.json`、`index.db`、`summaries/`、`memory_scratchpad.json` 是 projection / read-model。
-4. `data-agent` 不直接写 projection。
-5. Dashboard 默认只读。
-6. RAG key 缺失必须可降级到 BM25。
-7. 用户项目文件不能写到插件目录。
-8. hook 不是项目状态真源。
-9. `webnovel.py status` 继续保留宏观创作健康报告语义,短状态使用 `project-status`。
-
----
-
-## 19. 第一批可开工任务
-
-1. 新增 `project_phase.py`,统一 doctor / project-status / gates 的 phase 推导。
-2. 新增 `project_status.py`,注册 `project-status` 子命令,保留现有 `status` 转发到 `status_reporter.py`。
-3. 新增 `doctor.py`,复用现有 `preflight` / `build_story_runtime_health()`。
-4. 在统一 CLI 注册 `doctor` 子命令。
-5. 新增 `/webnovel-doctor` Skill。
-6. 新增 `artifact_validator.py`,先包装 `chapter_commit_schema.py` 中的 commit artifact Pydantic schema。
-7. 给 `webnovel-write` 的四类 agent artifact 增加 validator 测试 fixture。
-8. 新增 `write_gates/prewrite.py`、`write_gates/precommit.py`、`write_gates/postcommit.py`,其中 prewrite 包装 `PrewriteValidator`。
-9. 修改 `webnovel-write/SKILL.md`,开始引用 `write-gate --stage prewrite/precommit/postcommit`,过程管理仍使用 Claude Code Todo。
-10. 先审计 5 个 projection writer 的幂等性,再新增 `projection_log.py`。
-11. 给 7 个 Skill 补 description。
-12. 新增 `validate_plugin_package.py`,先对齐现有版本 CI,再校验 frontmatter / LICENSE / dist。
-13. 新增可选 SessionStart hook,只注入 project-status summary。
-14. 新增可选 skill-scoped PreToolUse hook,作为 best-effort 兜底提醒 / 阻断。
-
----
-
-## 20. 最终判断
-
-`webnovel-writer` 当前最大短板不是知识库不足,也不是题材模板不够,而是:
-
-> 关键流程仍有一部分靠 Skill 文档和 Agent 遵守协议来保证。
-
-本 spec 的核心就是把这些协议逐步变成 runtime 可验证机制:
-
-- doctor 负责知道项目文件、数据库和系统配置是否完整可用;
-- project-status 负责用统一 phase resolver 知道项目现在写到哪里;
-- runtime gates 负责知道关键边界是否可继续;
-- validator 负责知道产物是否可信;
-- projection log 负责知道 read-model 是否同步;
-- eval 负责证明 agent 真的按协议执行;
-- package validator 负责发布物没有漂移。
-
-做到这些,`webnovel-writer` 才会真正具备优秀 Claude Code 插件的工程稳定性。

+ 0 - 107
docs/archive/architecture/current-system-diagnosis.md

@@ -1,107 +0,0 @@
-# 当前系统架构诊断报告 (Current System Diagnosis)
-
-> **文档状态**: 诊断报告 (未引入 Story Intelligence System Spec 前)
-> **诊断前提**: 假设大语言模型(如 Opus 4.6)的指令遵循能力完美,能够完美解析并执行所有 Bash 命令和格式要求。
-> **核心结论**: 系统的痛点不在于模型写不好或执行失误,而在于**系统提供给模型的信息结构、校验时机和数据流转机制存在根本性硬伤**。系统试图用一套松散的管道脚本(Pipeline)去管理极其复杂的网文世界状态机(State Machine),最终不可避免地导致长篇连载走向崩盘。
-> 同时需要补一层更准确的校准:当前系统并不是完全没有结构化能力,而是**已经具备多个半成品中枢,但仍缺少统一故事合同(SSOT)与统一章节提交主链**。
-
----
-
-## 一、 核心架构与真理源缺陷 (Architecture & Single Source of Truth)
-
-### 1. 多头真理(Multiple Sources of Truth)导致的认知撕裂
-
-* **现象**:系统的设定散落在 `state.json`(动态状态)、`genre-profiles.md`(静态调性)、`index.db`(碎片化实体)和 `memory`(长期事实)中。虽然 `context_manager`、`genre_*` 已经在尝试聚合这些信息,但它们本质上仍是运行时装配器和局部中枢,而不是统一的单一真理源(SSOT)。
-* **危害**:当剧情发生合理反转或设定升级时,系统无法做到全局同步。模型会同时接到冲突的指令(例如 md 要求主角废柴隐忍,而 state 显示已经天下无敌)。极度聪明的模型为了兼顾双方,反而会写出精神分裂般的割裂剧情。
-
-### 2. 设定演进账本仅有雏形,尚未覆盖核心世界设定(Override Ledger Gap)
-
-* **现象**:网文的核心规则是会随着剧情推进被打破的(如主角打破了某项世界限制)。当前系统已在 v5.3 引入了 `override_contracts` 表(见 `index_debt_mixin.py`),具备完整的 CRUD 操作(含 `constraint_type`、`rationale_type`、`rationale_text`、`payback_plan`、`due_chapter`、`status`)。但该机制目前**仅服务于追读力债务系统的软建议违背记录**,尚未扩展为核心世界设定(力量体系、角色命运、世界规则)的演进账本。对于这些核心设定的修改,系统本质上仍只有静默覆盖(Overwrite)或局部投影更新。
-* **危害**:没有机制明确告诉 AI 注意,卷五已经合法推翻了卷一的隐忍设定,现在的最高准则是无敌爽文。系统把所有的旧规则和新状态一锅端地喂给 AI,造成严重的上下文污染和逻辑死锁。Override Contract 的基础设施已经存在,但其应用范围的局限性使得它无法解决这个核心问题。
-
----
-
-## 二、 上下文装配与知识供给痛点 (Context & Knowledge Assembly)
-
-### 3. 机械截断导致的信息黑洞(The Blind Spot of Context Truncation)
-
-* **现象**:`context_manager.py` 确实已经在做上下文聚合,且 `context_ranker.py` 已经对 alerts 等信息做了优先级排序与筛选(说明系统并非完全无脑塞上下文)。但最终输出仍采用按权重(`TEMPLATE_WEIGHTS`)和字数预算,强行截断字符串(`_compact_json_text`)的方式来组装上下文。
-* **危害**:模型再聪明,也无法遵守它没看到的规则。关键毒点、核心力量限制或重要伏笔,极容易因为刚好超出字符串预算而被底层脚本静默丢弃。导致模型不得不靠幻觉(Hallucination)填补空白,引发设定崩塌。问题不在“没有聚合”,而在于**聚合后的输入仍可能被预算逻辑打成信息黑洞**。
-
-### 4. 知识延迟绑定(Late-Binding)与泛化割裂
-
-* **现象**:系统并不只是中途临时查 CSV,它其实已经有 `md 必读 + CSV 检索 + genre_profile` 的双轨资料体系。但在写作中,系统仍要求大模型在具体阶段按需调用 `reference_search.py` 查通用条目(如如何写战斗)。
-* **危害**:查出来的大多是通用网文技巧,而不是本书专属设定。系统缺乏一个前置的聚合层把通用套路和本书主角特质、题材调性、系统边界揉合在一起,导致写出的剧情虽然套路标准,但千篇一律,缺乏本书的灵魂与特色。
-
----
-
-## 三、 流程编排与校验机制漏洞 (Workflow & Verification)
-
-### 5. 事后验尸而非事前避坑的防线设计
-
-* **现象**:系统高度依赖 Step 3 的 Reviewer Agent 去审查**已经写完的正文**,发现 Blocking 毒点后再打回 Step 4(润色)修复。
-* **危害**:防线设得太晚。如果模型在起草时犯了方向性或结构性错误(如写死了不该死的核心角色,或触碰了严重毒点),依靠润色(改表达不改事实)是根本救不回来的。这会导致无解的死循环,或只能产出打补丁式的劣质文本。
-
-### 6. 缺乏大纲履约的强制校验(No Fulfillment Verification)
-
-* **现象**:系统已经能读取 `chapter outline`、`plot_structure`、`mandatory_nodes`、`prohibitions` 这类计划信息,但在写后提交阶段,只会提取记录实际写了什么(What is),不会严谨地进行结构化 Diff,对比大纲要求写什么(What was planned vs. achieved,如 CBN/CEN 节点)。
-* **危害**:如果模型因为篇幅原因漏写了一个关键的暗杀伏笔,系统只会完美地提取已写的内容,而不会报错大纲要求未履约。这会悄无声息地引发后续章节大纲的连锁崩盘。
-
----
-
-## 四、 数据回写与后置处理隐患 (Data Write-back & Disambiguation)
-
-### 7. 事务割裂与幽灵状态(Fragmented DB Transactions)
-
-* **现象**:Step 5 要求将结果分别写入 `state.json`、`index.db`、`summaries` 和 `memory` 四个不同的地方。需要校准的是:当前 `state_manager` 对自身写盘已经有局部原子写和 pending 快照保护,但这仍然不是跨存储的章节级事务一致性(ACID)。此外,`StateManager` 内部还维护了 `state.json` + SQLite(`index.db`)的**双写同步逻辑**(`_sync_to_sqlite`、`_sync_pending_patches_to_sqlite`),包含 pending 快照恢复机制,这在单个模块内部就已经引入了额外的一致性复杂度。
-* **危害**:风险存在于两个层面。**内层**:`StateManager` 的 JSON + SQLite 双写如果部分失败,虽然有快照恢复,但恢复逻辑本身也可能在极端情况下不完整。**外层**:四个存储的跨库写操作一旦中途发生中断,仍然会导致 DB 记录角色已死,而 `state.json` 里角色还活着,或摘要和长期记忆没有同步更新。这种幽灵状态会在生成下一章时把模型彻底搞疯。
-
-### 8. 幻觉错误被后置消歧放大为索引污染风险(Index Pollution via Disambiguation)
-
-* **现象**:如果在 Step 2 模型产生了幻觉,把主角林辰错写成了张辰,后置消歧未必会直接报错打回。需要校准的是:当前系统对消歧有置信度分层,不是无脑把所有错误都写死;但它确实存在一个风险窗口,**一旦错误命中“可采用”区间,后置消歧就可能把错误实体归并写入后续索引链路。**
-* **危害**:这意味着纯粹的写作幻觉,不一定会被当场拦截,反而可能以“低置信但被采用”的形式被沉淀为别名、出场记录、关系记录或状态更新。长期来看,这仍会污染数据库,使后续消歧越来越乱。
-
-### 9. 消歧警告的向后传染(Context Leaking)
-
-* **现象**:如果 Step 5 发现模棱两可的名字且无法自动消歧,会将其记入 `state.json` 的 `disambiguation_pending`,高于阈值但仍不稳的则进入 `disambiguation_warnings`,并在写**下一章**时通过上下文装配链被继续读取。
-* **危害**:上一章的消歧烂账,变成了下一章起草时的噪音。这极大地分散了模型写新剧情的注意力,甚至诱导模型在新章节中强行加戏去解释上一章的名字问题,破坏行文流畅度。
-
-### 10. 状态投影主导,语义事件只有痕迹没有主链(State Overwrite vs. Delta Events)
-
-* **现象**:Data Agent 和数据链并不是完全没有 Delta 事件,实际上已经存在:
-  - `state_changes`
-  - `relationship_events`
-  - `timeline_events`
-  - `world_rules`
-  - `open_loops`
-  - `reader_promises`
-
-  其中关系事件层面,代码里已经有比 `relationships_new` 更完整的 `relationship_events` 表,具备较强的结构化能力;但这些事件整体仍分散在 `state`、`index`、`memory` 等不同层里,没有统一的 canonical event log。系统主链仍然更偏向“状态投影更新”,而不是“事件驱动更新”。
-* **危害**:结果就是系统能记录“已经变成金丹”,却很难以统一方式表达“如何突破、消耗了什么资源、引发了什么异象、这次突破应如何修改上层世界规则”。这使得系统无法自动感知并触发设定升级或力量体系阈值扩展,模型仍然需要像瞎子摸象一样去猜目前的战力天花板。
-
----
-
-## 总结
-
-在没有引入 `Story Intelligence System Spec` 的前置契约约束之前,系统并不是完全靠大模型“裸奔”,而是已经靠多个半成品中枢在**缝缝补补**:
-
-- `context_manager` + `context_ranker` 在做上下文聚合与优先级排序
-- `genre_*` 在做题材归一化与画像构建
-- `state_manager` 在做结构化状态回写(含 JSON + SQLite 双写同步)
-- `memory writer / orchestrator` 在做长期事实沉淀
-- `override_contracts` 在做追读力债务的违背记录(Override Ledger 雏形)
-
-但这些能力还没有被收束成一个统一的故事事实系统。
-
-因此,当前系统最根本的问题不是“模型不够强”,而是:
-
-**系统已经有多个局部结构层(包括 Override Contract 雏形和 Delta 事件底座),但还没有统一故事合同(Story Contract)、统一章节提交主链(Commit Chain)和覆盖全局世界设定的统一演进账本(Override Ledger)。**
-
-这也正是新架构的核心价值所在:
-
-- 生成强约束的 `Master / Volume / Chapter` JSON 合同
-- 建立显式的 Override 账本
-- 事前给足绝对红线、系统边界和消歧域
-- 把 `state / index / summary / memory / RAG` 从“散落真理源”降级为“合同提交后的投影层”
-
-只有提供完美、无歧义、可追溯且前后一致的输入,大模型才能持续、稳定地输出高质量的长篇连载。

+ 0 - 356
docs/archive/architecture/dashboard-demo.html

@@ -1,356 +0,0 @@
-<!DOCTYPE html>
-<html lang="zh-CN">
-<head>
-<meta charset="UTF-8">
-<title>PIXEL WRITER HUB - 500章 Demo</title>
-<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Noto+Sans+SC:wght@400;500;700&display=swap" rel="stylesheet">
-<style>
-:root{--bg-main:#fff7e8;--bg-panel:#fffdf6;--bg-card:#fffaf0;--bg-card-2:#fff3d5;--text-main:#2a220f;--text-sub:#5d5035;--text-mute:#8f7f5c;--accent-blue:#26a8ff;--accent-purple:#7f5af0;--accent-green:#2ec27e;--accent-amber:#f5a524;--accent-red:#d7263d;--accent-cyan:#00b8d4;--border-main:#2a220f;--border-soft:#8f7f5c;--shadow-main:6px 6px 0 #2a220f;--shadow-soft:3px 3px 0 #8f7f5c;--font-display:'Press Start 2P',monospace;--font-body:'Noto Sans SC','Microsoft YaHei',sans-serif}
-*{margin:0;padding:0;box-sizing:border-box}
-body{font-family:var(--font-body);color:var(--text-main);background:var(--bg-main);background-image:linear-gradient(90deg,rgba(42,34,15,.05) 1px,transparent 1px),linear-gradient(rgba(42,34,15,.05) 1px,transparent 1px);background-size:14px 14px;height:100vh}
-.app{display:grid;grid-template-columns:240px 1fr;height:100vh}
-.sidebar{border-right:3px solid var(--border-main);background:linear-gradient(180deg,#ffe8b8,#ffe19f);display:flex;flex-direction:column}
-.sidebar-hd{padding:16px;border-bottom:3px solid var(--border-main)}
-.sidebar-hd h1{font-family:var(--font-display);font-size:11px;letter-spacing:.08em;line-height:1.45}
-.sidebar-hd .sub{margin-top:10px;font-size:14px;font-weight:500;color:var(--text-sub)}
-.nav{flex:1;overflow-y:auto;padding:10px;display:flex;flex-direction:column;gap:8px}
-.nav-btn{width:100%;border:2px solid var(--border-main);background:#fff9e8;color:var(--text-main);text-align:left;display:flex;align-items:center;gap:8px;padding:10px 12px;font-size:14px;font-weight:600;cursor:pointer;box-shadow:var(--shadow-soft);font-family:var(--font-body);transition:transform .08s}
-.nav-btn:hover{transform:translate(-1px,-1px)}
-.nav-btn.active{background:#dff3ff;border-color:var(--accent-blue)}
-.nav-btn .ico{width:22px;text-align:center}
-.live{border-top:3px solid var(--border-main);padding:10px 12px;font-size:13px;font-weight:500;display:flex;align-items:center;gap:8px}
-.live-dot{width:10px;height:10px;background:var(--accent-green);border:2px solid var(--border-main)}
-.main{overflow-y:auto;padding:22px}
-.page-hd{display:flex;align-items:center;gap:12px;margin-bottom:14px;flex-wrap:wrap}
-.page-hd h2{font-size:22px;font-weight:700}
-.badge{border:2px solid var(--border-main);font-size:12px;font-weight:700;padding:3px 8px;display:inline-block}
-.b-blue{background:#dff3ff;color:#055d8b}.b-green{background:#dcfce7;color:#0f5132}.b-amber{background:#fff1cd;color:#8a5b00}.b-red{background:#ffe0e5;color:#8f1d30}.b-purple{background:#ece3ff;color:#4a2ea8}.b-cyan{background:#dcfafe;color:#155e75}
-.card{background:var(--bg-card);border:3px solid var(--border-main);box-shadow:var(--shadow-main);padding:16px;margin-bottom:16px}
-.card-hd{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:10px;flex-wrap:wrap}
-.card-title{font-size:17px;font-weight:700}
-.stats{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin-bottom:14px}
-.stat .label{font-size:13px;font-weight:600;color:var(--text-mute)}
-.stat .val{font-size:28px;line-height:1.15;margin:6px 0 2px;color:var(--accent-blue);font-variant-numeric:tabular-nums}
-.stat .val.plain{color:var(--text-main)}
-.stat .sub{font-size:13px;font-weight:500;color:var(--text-sub)}
-.progress{margin-top:8px;height:12px;border:2px solid var(--border-main);background:#f8e3b8}
-.progress-bar{height:100%;background:linear-gradient(90deg,#26a8ff,#7f5af0)}
-.two-col{display:grid;grid-template-columns:1fr 1fr;gap:16px}
-.tbl-wrap{overflow-x:auto;border:2px solid var(--border-soft);background:var(--bg-panel)}
-table{width:100%;border-collapse:collapse;font-size:14px;font-variant-numeric:tabular-nums}
-th{text-align:left;padding:8px 10px;border-bottom:2px solid var(--border-soft);background:var(--bg-card-2);font-weight:700;white-space:nowrap}
-td{padding:8px 10px;border-bottom:1px solid #d8ccb2;font-weight:500}
-tr:hover td{background:#fff4d8}
-.page{display:none}.page.active{display:block}
-.legend-dot{display:inline-block;width:12px;height:12px;border:2px solid var(--border-main);margin-right:4px;vertical-align:-1px}
-
-/* === 翻页控制条 === */
-.pager{display:flex;align-items:center;gap:8px;margin-bottom:10px;flex-wrap:wrap}
-.pager-btn{border:2px solid var(--border-main);background:#fff8e6;color:var(--text-main);font-family:var(--font-body);font-size:13px;font-weight:600;padding:4px 10px;cursor:pointer;box-shadow:2px 2px 0 var(--border-main)}
-.pager-btn:hover:not(:disabled){background:#e6f7ff;border-color:var(--accent-blue)}
-.pager-btn:disabled{opacity:.4;cursor:not-allowed}
-.pager-info{font-size:13px;font-weight:600;color:var(--text-sub)}
-
-/* === 像素柱状图 === */
-.pixel-bars{display:flex;align-items:flex-end;gap:3px;height:120px;padding:14px 4px 0;overflow:hidden}
-.pixel-bar-col{flex:1;display:flex;flex-direction:column;align-items:center;gap:2px;min-width:0}
-.pixel-bar{width:100%;border:2px solid var(--border-main);min-width:4px;position:relative}
-.pixel-bar .bar-val{position:absolute;top:-16px;left:50%;transform:translateX(-50%);font-size:9px;font-weight:700;white-space:nowrap;display:none}
-.pixel-bar-col:hover .bar-val{display:block}
-.pixel-bar-label{font-size:9px;color:var(--text-mute);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
-.bar-green{background:var(--accent-green)}.bar-amber{background:var(--accent-amber)}.bar-red{background:var(--accent-red)}
-
-/* === 热力格按卷分组 === */
-.heatmap-vol{margin-bottom:10px}
-.heatmap-vol-title{font-size:13px;font-weight:700;color:var(--text-sub);margin-bottom:4px}
-.heatmap-row{display:flex;gap:2px;flex-wrap:wrap}
-.heat-cell{width:18px;height:18px;border:1px solid var(--border-soft);cursor:pointer;position:relative}
-.heat-cell:hover::after{content:attr(data-tip);position:absolute;bottom:22px;left:50%;transform:translateX(-50%);background:var(--text-main);color:#fff;font-size:11px;padding:2px 6px;white-space:nowrap;z-index:10;border:2px solid var(--border-main)}
-.h-high{background:#26a8ff}.h-mid{background:#a8d8ff}.h-low{background:#dff3ff}.h-vlow{background:#ffe0e5}.h-none{background:#f5f0e0}
-
-/* === 伏笔甘特 === */
-.gantt-row{display:flex;align-items:center;gap:8px;margin-bottom:5px;font-size:13px}
-.gantt-label{width:130px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600;flex-shrink:0}
-.gantt-track{flex:1;height:14px;border:2px solid var(--border-soft);background:var(--bg-panel);position:relative}
-.gantt-fill{height:100%;position:absolute}
-.g-active{background:var(--accent-amber)}.g-urgent{background:var(--accent-red)}.g-done{background:var(--accent-green)}
-.gantt-ch{font-size:11px;color:var(--text-mute);width:60px;text-align:right;flex-shrink:0}
-.gantt-now{position:absolute;top:0;bottom:0;width:2px;background:var(--accent-blue);z-index:2}
-
-/* === Strand === */
-.strand-bar{height:12px;border:2px solid var(--border-main);display:flex;margin-bottom:6px}
-.strand-bar .seg{height:100%}.sq{background:#26a8ff}.sf{background:#ff5c8a}.sc{background:#7f5af0}
-.strand-legend{display:flex;gap:14px;font-size:13px;color:var(--text-sub)}
-
-/* === 系统 === */
-.sys-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
-.status-row{display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px dashed #d8ccb2;font-size:14px}
-.status-row:last-child{border:none}
-.status-label{font-weight:600;color:var(--text-sub)}
-
-.ch-card{border:2px solid var(--border-soft);background:var(--bg-panel);padding:10px 12px;margin-bottom:6px;display:flex;justify-content:space-between;align-items:center}
-.ch-card:hover{background:#fff4d8}
-.ch-title{font-weight:700;font-size:14px}.ch-meta{font-size:13px;color:var(--text-sub);margin-top:2px}
-</style>
-</head>
-<body>
-<div class="app">
-  <aside class="sidebar">
-    <div class="sidebar-hd"><h1>PIXEL WRITER HUB</h1><div class="sub">斗破苍穹</div></div>
-    <div class="nav">
-      <button class="nav-btn active" onclick="showPage('overview',this)"><span class="ico">📊</span><span>总览</span></button>
-      <button class="nav-btn" onclick="showPage('pacing',this)"><span class="ico">📈</span><span>节奏雷达</span></button>
-      <button class="nav-btn" onclick="showPage('foreshadow',this)"><span class="ico">🔖</span><span>伏笔追踪</span></button>
-      <button class="nav-btn" onclick="showPage('system',this)"><span class="ico">⚙️</span><span>系统状态</span></button>
-    </div>
-    <div class="live"><span class="live-dot"></span> 实时同步中</div>
-  </aside>
-  <main class="main">
-    <div id="p-overview" class="page active"></div>
-    <div id="p-pacing" class="page"></div>
-    <div id="p-foreshadow" class="page"></div>
-    <div id="p-system" class="page"></div>
-  </main>
-</div>
-<script>
-// ========== 模拟 500 章数据 ==========
-const TOTAL_CH = 487, TOTAL_VOL = 5;
-const VOL_RANGES = [[1,100],[101,200],[201,320],[321,420],[421,487]];
-const VOL_NAMES = ['第1卷 废材崛起','第2卷 迦南学院','第3卷 黑角域','第4卷 中州大陆','第5卷 星域之争'];
-
-function randInt(a,b){return Math.floor(Math.random()*(b-a+1))+a}
-function genChapters(){
-  const arr=[];
-  for(let i=1;i<=TOTAL_CH;i++){
-    const score=randInt(55,98);
-    const wc=randInt(1400,3200);
-    const hooks=['strong','medium','weak'];
-    const hook=hooks[score>82?0:score>68?1:2];
-    const strands=['quest','fire','constellation'];
-    arr.push({ch:i,score,wc,hook,strand:strands[randInt(0,2)]});
-  }
-  return arr;
-}
-const DATA=genChapters();
-
-function genForeshadowing(){
-  const names=['三年之约','药老身世','萧家复仇','青莲地心火','灵根置换术','墨蛟契约','星陨阁秘密','魂殿暗线','迦南学院内鬼','炎帝传承','古河谷主的遗嘱','美杜莎蛇族','远古龙族血脉','虚无吞炎','净莲妖火','陀舍古帝遗迹','异火榜终极','联盟暗棋','萧炎父亲下落','荒古遗迹入口'];
-  return names.map((n,i)=>{
-    const planted=randInt(1,Math.min(400,TOTAL_CH-20));
-    const target=planted+randInt(15,120);
-    const resolved=target<TOTAL_CH-30?Math.random()>.5:false;
-    const urgent=!resolved&&target-TOTAL_CH<10;
-    return{name:n,planted,target:Math.min(target,TOTAL_CH+60),status:resolved?'done':urgent?'urgent':'active'};
-  });
-}
-const FORESHADOWING=genForeshadowing();
-
-// ========== 柱状图 ==========
-let barPage=0;
-const BAR_SIZE=20;
-function renderBars(containerId){
-  const total=Math.ceil(TOTAL_CH/BAR_SIZE);
-  const start=barPage*BAR_SIZE+1;
-  const end=Math.min(start+BAR_SIZE-1,TOTAL_CH);
-  const slice=DATA.slice(start-1,end);
-  let html=`<div class="pager">
-    <button class="pager-btn" onclick="barPage=Math.max(0,barPage-1);renderBars('${containerId}')" ${barPage<=0?'disabled':''}>◀</button>
-    <span class="pager-info">第 ${start}-${end} 章 / 共 ${TOTAL_CH} 章</span>
-    <button class="pager-btn" onclick="barPage=Math.min(${total-1},barPage+1);renderBars('${containerId}')" ${barPage>=total-1?'disabled':''}>▶</button>
-    <button class="pager-btn" onclick="barPage=${total-1};renderBars('${containerId}')">最新 ▶▶</button>
-  </div><div class="pixel-bars">`;
-  slice.forEach(d=>{
-    const cls=d.score>=80?'bar-green':d.score>=70?'bar-amber':'bar-red';
-    html+=`<div class="pixel-bar-col"><div class="pixel-bar ${cls}" style="height:${d.score}%"><span class="bar-val">${d.score}</span></div><div class="pixel-bar-label">${d.ch}</div></div>`;
-  });
-  html+=`</div><div style="margin-top:6px;font-size:12px;color:var(--text-mute)">🟢 ≥80 🟡 70-79 🔴 &lt;70 鼠标悬停看分数</div>`;
-  document.getElementById(containerId).innerHTML=html;
-}
-
-// ========== 热力格按卷 ==========
-function renderHeatmap(containerId){
-  let html='';
-  VOL_RANGES.forEach(([s,e],vi)=>{
-    html+=`<div class="heatmap-vol"><div class="heatmap-vol-title">${VOL_NAMES[vi]}(${s}-${e}章)</div><div class="heatmap-row">`;
-    for(let i=s;i<=e;i++){
-      const d=DATA[i-1];
-      const cls=d.wc>=2500?'h-high':d.wc>=2000?'h-mid':d.wc>=1800?'h-low':d.wc>0?'h-vlow':'h-none';
-      html+=`<div class="heat-cell ${cls}" data-tip="第${d.ch}章 ${d.wc}字"></div>`;
-    }
-    html+=`</div></div>`;
-  });
-  html+=`<div style="margin-top:6px;display:flex;gap:10px;font-size:12px;color:var(--text-mute)">
-    <span><span class="legend-dot h-high" style="border-color:var(--border-main)"></span>≥2.5k</span>
-    <span><span class="legend-dot h-mid" style="border-color:var(--border-main)"></span>2.0-2.5k</span>
-    <span><span class="legend-dot h-low" style="border-color:var(--border-main)"></span>1.8-2.0k</span>
-    <span><span class="legend-dot h-vlow" style="border-color:var(--border-main)"></span>&lt;1.8k</span>
-  </div>`;
-  document.getElementById(containerId).innerHTML=html;
-}
-
-// ========== 伏笔甘特 ==========
-let foreshadowFilter='all';
-function renderGantt(){
-  const items=foreshadowFilter==='all'?FORESHADOWING:FORESHADOWING.filter(f=>f.status===foreshadowFilter);
-  const maxCh=Math.max(TOTAL_CH+60,...items.map(f=>f.target));
-  const nowPct=(TOTAL_CH/maxCh*100).toFixed(1);
-  const counts={done:FORESHADOWING.filter(f=>f.status==='done').length,active:FORESHADOWING.filter(f=>f.status==='active').length,urgent:FORESHADOWING.filter(f=>f.status==='urgent').length};
-
-  let statsHtml=`<div class="stats" style="grid-template-columns:repeat(4,1fr)">
-    <div class="card stat"><div class="label">总伏笔</div><div class="val plain">${FORESHADOWING.length}</div></div>
-    <div class="card stat"><div class="label">活跃</div><div class="val" style="color:var(--accent-amber)">${counts.active}</div></div>
-    <div class="card stat"><div class="label">已回收</div><div class="val" style="color:var(--accent-green)">${counts.done}</div></div>
-    <div class="card stat"><div class="label">紧急</div><div class="val" style="color:var(--accent-red)">${counts.urgent}</div></div>
-  </div>`;
-
-  let filterHtml=`<div class="pager" style="margin-bottom:10px">
-    <button class="pager-btn${foreshadowFilter==='all'?' active':''}" onclick="foreshadowFilter='all';renderGantt()" style="${foreshadowFilter==='all'?'background:#dff3ff;border-color:var(--accent-blue)':''}">全部</button>
-    <button class="pager-btn" onclick="foreshadowFilter='urgent';renderGantt()" style="${foreshadowFilter==='urgent'?'background:#ffe0e5;border-color:var(--accent-red)':''}">🔴 紧急</button>
-    <button class="pager-btn" onclick="foreshadowFilter='active';renderGantt()" style="${foreshadowFilter==='active'?'background:#fff1cd;border-color:var(--accent-amber)':''}">🟡 活跃</button>
-    <button class="pager-btn" onclick="foreshadowFilter='done';renderGantt()" style="${foreshadowFilter==='done'?'background:#dcfce7;border-color:var(--accent-green)':''}">🟢 已回收</button>
-  </div>`;
-
-  let ganttHtml='';
-  items.sort((a,b)=>a.status==='urgent'?-1:b.status==='urgent'?1:a.planted-b.planted).forEach(f=>{
-    const left=(f.planted/maxCh*100).toFixed(1);
-    const width=((f.target-f.planted)/maxCh*100).toFixed(1);
-    const cls=f.status==='done'?'g-done':f.status==='urgent'?'g-urgent':'g-active';
-    ganttHtml+=`<div class="gantt-row"><div class="gantt-label">${f.name}</div><div class="gantt-track"><div class="gantt-fill ${cls}" style="left:${left}%;width:${width}%"></div><div class="gantt-now" style="left:${nowPct}%"></div></div><div class="gantt-ch">${f.planted}→${f.target}</div></div>`;
-  });
-
-  document.getElementById('p-foreshadow').innerHTML=`
-    <div class="page-hd"><h2>🔖 伏笔追踪</h2><span class="badge b-cyan">当前第 ${TOTAL_CH} 章</span></div>
-    ${statsHtml}
-    ${filterHtml}
-    <div class="card"><div class="card-hd"><span class="card-title">伏笔时间线</span><span class="badge b-cyan">章节 1 — ${maxCh}</span></div>
-    ${ganttHtml}
-    <div style="margin-top:8px;display:flex;gap:14px;font-size:12px">
-      <span><span class="legend-dot" style="background:var(--accent-green)"></span>已回收</span>
-      <span><span class="legend-dot" style="background:var(--accent-amber)"></span>活跃</span>
-      <span><span class="legend-dot" style="background:var(--accent-red)"></span>紧急</span>
-      <span style="color:var(--accent-blue)">| 蓝线 = 当前章</span>
-    </div></div>`;
-}
-
-// ========== 总览页 ==========
-function renderOverview(){
-  const totalWords=DATA.reduce((s,d)=>s+d.wc,0);
-  const recent5=DATA.slice(-5);
-  const avgScore=(recent5.reduce((s,d)=>s+d.score,0)/5).toFixed(1);
-  const urgentCount=FORESHADOWING.filter(f=>f.status==='urgent').length;
-  const pct=(totalWords/1200000*100).toFixed(1);
-
-  document.getElementById('p-overview').innerHTML=`
-    <div class="page-hd"><h2>📊 总览</h2><span class="badge b-blue">玄幻修仙 · 500章级</span></div>
-    <div class="stats">
-      <div class="card stat"><div class="label">总字数</div><div class="val">${(totalWords/10000).toFixed(1)} 万</div><div class="sub">目标 120 万 · ${pct}%</div><div class="progress"><div class="progress-bar" style="width:${pct}%"></div></div></div>
-      <div class="card stat"><div class="label">当前章节</div><div class="val plain">第 ${TOTAL_CH} 章</div><div class="sub">卷 ${TOTAL_VOL} · ${VOL_NAMES[TOTAL_VOL-1].split(' ')[1]}</div></div>
-      <div class="card stat"><div class="label">Story Runtime</div><div class="val plain" style="font-size:18px"><span class="badge b-green">Mainline ✓</span></div><div class="sub">accepted · no fallback</div></div>
-      <div class="card stat"><div class="label">审查均分</div><div class="val" style="color:${avgScore>=80?'var(--accent-green)':avgScore>=70?'var(--accent-amber)':'var(--accent-red)'}">${avgScore}</div><div class="sub">最近 5 章均分</div></div>
-      <div class="card stat"><div class="label">紧急伏笔</div><div class="val" style="color:var(--accent-amber)">${urgentCount}</div><div class="sub">总计 ${FORESHADOWING.length} 条伏笔</div></div>
-    </div>
-    <div class="card"><div class="card-hd"><span class="card-title">📊 审查得分</span></div><div id="overview-bars"></div></div>
-    <div class="card"><div class="card-hd"><span class="card-title">📝 字数热力图(按卷)</span></div><div id="overview-heat"></div></div>
-    <div class="two-col">
-      <div class="card">
-        <div class="card-hd"><span class="card-title">⚠️ 紧急伏笔</span></div>
-        <div class="tbl-wrap"><table><thead><tr><th>内容</th><th>状态</th><th>埋设</th><th>目标</th></tr></thead><tbody>
-        ${FORESHADOWING.filter(f=>f.status==='urgent').slice(0,5).map(f=>`<tr><td>${f.name}</td><td><span class="badge b-red">紧急</span></td><td>${f.planted}</td><td>${f.target}</td></tr>`).join('')}
-        </tbody></table></div>
-      </div>
-      <div class="card">
-        <div class="card-hd"><span class="card-title">最近章节</span><span class="badge b-blue">LATEST</span></div>
-        ${DATA.slice(-3).reverse().map(d=>{
-          const cls=d.hook==='strong'?'b-green':d.hook==='medium'?'b-amber':'b-red';
-          return `<div class="ch-card"><div><div class="ch-title">📖 第${d.ch}章</div><div class="ch-meta">审查 ${d.score} · 字数 ${d.wc}</div></div><div style="text-align:right"><span class="badge ${cls}">${d.hook}</span></div></div>`;
-        }).join('')}
-      </div>
-    </div>`;
-  barPage=Math.ceil(TOTAL_CH/BAR_SIZE)-1;
-  renderBars('overview-bars');
-  renderHeatmap('overview-heat');
-}
-
-// ========== 节奏雷达页 ==========
-let pacingPage=0;
-const PACING_SIZE=20;
-function renderPacing(){
-  const total=Math.ceil(TOTAL_CH/PACING_SIZE);
-  const start=pacingPage*PACING_SIZE+1;
-  const end=Math.min(start+PACING_SIZE-1,TOTAL_CH);
-  const slice=DATA.slice(start-1,end);
-
-  let hookHtml=`<div class="pager">
-    <button class="pager-btn" onclick="pacingPage=Math.max(0,pacingPage-1);renderPacing()" ${pacingPage<=0?'disabled':''}>◀</button>
-    <span class="pager-info">第 ${start}-${end} 章</span>
-    <button class="pager-btn" onclick="pacingPage=Math.min(${total-1},pacingPage+1);renderPacing()" ${pacingPage>=total-1?'disabled':''}>▶</button>
-    <button class="pager-btn" onclick="pacingPage=${total-1};renderPacing()">最新 ▶▶</button>
-  </div>`;
-  slice.forEach(d=>{
-    const cls=d.hook==='strong'?'hb-strong':d.hook==='medium'?'hb-medium':'hb-weak';
-    const bcls=d.hook==='strong'?'b-green':d.hook==='medium'?'b-amber':'b-red';
-    hookHtml+=`<div style="display:flex;align-items:center;gap:6px;margin-bottom:4px"><span style="width:50px;font-size:12px;color:var(--text-mute);text-align:right">${d.ch}</span><div style="flex:1;height:16px;border:2px solid var(--border-soft);background:var(--bg-panel)"><div style="height:100%;width:${d.hook==='strong'?100:d.hook==='medium'?66:33}%;background:${d.hook==='strong'?'var(--accent-green)':d.hook==='medium'?'var(--accent-amber)':'var(--accent-red)'}"></div></div><span style="width:65px;font-size:12px"><span class="badge ${bcls}">${d.hook}</span></span></div>`;
-  });
-
-  let strandHtml='';
-  slice.forEach(d=>{
-    const q=d.strand==='quest'?randInt(40,70):randInt(10,30);
-    const f=d.strand==='fire'?randInt(40,60):randInt(10,30);
-    const c=100-q-f;
-    strandHtml+=`<div style="display:flex;align-items:center;gap:6px"><span style="width:40px;font-size:11px;color:var(--text-mute);text-align:right">${d.ch}</span><div class="strand-bar" style="flex:1;margin:0;height:10px"><div class="seg sq" style="width:${q}%"></div><div class="seg sf" style="width:${f}%"></div><div class="seg sc" style="width:${c}%"></div></div></div>`;
-  });
-
-  document.getElementById('p-pacing').innerHTML=`
-    <div class="page-hd"><h2>📈 节奏雷达</h2><span class="badge b-purple">共 ${TOTAL_CH} 章</span></div>
-    <div class="card"><div class="card-hd"><span class="card-title">钩子强度</span></div>${hookHtml}</div>
-    <div class="two-col">
-      <div class="card"><div class="card-hd"><span class="card-title">Strand 分布</span></div>${strandHtml}
-        <div class="strand-legend" style="margin-top:8px"><span>🔵 Quest</span><span>🔴 Fire</span><span>🟣 Constellation</span></div>
-      </div>
-      <div class="card"><div class="card-hd"><span class="card-title">字数热力图</span></div><div id="pacing-heat"></div></div>
-    </div>`;
-  renderHeatmap('pacing-heat');
-}
-
-// ========== 系统页 ==========
-function renderSystem(){
-  document.getElementById('p-system').innerHTML=`
-    <div class="page-hd"><h2>⚙️ 系统状态</h2></div>
-    <div class="sys-grid">
-      <div class="card"><div class="card-hd"><span class="card-title">Story Runtime</span><span class="badge b-green">Mainline ✓</span></div>
-        <div class="status-row"><span class="status-label">Latest Commit</span><span><span class="badge b-green">accepted</span> 第${TOTAL_CH}章</span></div>
-        <div class="status-row"><span class="status-label">Fallback</span><span>none</span></div></div>
-      <div class="card"><div class="card-hd"><span class="card-title">合同树</span></div>
-        <div class="status-row"><span class="status-label">MASTER_SETTING</span><span><span class="badge b-green">✓</span> 玄幻修仙</span></div>
-        <div class="status-row"><span class="status-label">Volume</span><span>${TOTAL_VOL} 份</span></div>
-        <div class="status-row"><span class="status-label">Chapter</span><span>${TOTAL_CH} 份</span></div>
-        <div class="status-row"><span class="status-label">Review</span><span>${TOTAL_CH} 份</span></div></div>
-      <div class="card"><div class="card-hd"><span class="card-title">最近 Commit</span></div>
-        <div class="tbl-wrap"><table><thead><tr><th>章</th><th>状态</th><th>state</th><th>index</th><th>summ</th><th>mem</th><th>vec</th></tr></thead><tbody>
-        ${DATA.slice(-5).reverse().map(d=>`<tr><td>${d.ch}</td><td><span class="badge b-green">accepted</span></td><td>done</td><td>done</td><td>done</td><td>done</td><td>done</td></tr>`).join('')}
-        </tbody></table></div></div>
-      <div class="card"><div class="card-hd"><span class="card-title">RAG 环境</span></div>
-        <div class="status-row"><span class="status-label">Embed Key</span><span><span class="badge b-green">✓</span></span></div>
-        <div class="status-row"><span class="status-label">Rerank Key</span><span><span class="badge b-amber">未配</span></span></div>
-        <div class="status-row"><span class="status-label">Vector DB</span><span>24.3 MB</span></div>
-        <div class="status-row"><span class="status-label">RAG Mode</span><span><span class="badge b-blue">vector_only</span></span></div></div>
-    </div>`;
-}
-
-// ========== 页面切换 ==========
-function showPage(id,btn){
-  document.querySelectorAll('.page').forEach(p=>p.classList.remove('active'));
-  document.getElementById('p-'+id).classList.add('active');
-  document.querySelectorAll('.nav-btn').forEach(b=>b.classList.remove('active'));
-  if(btn)btn.classList.add('active');
-  if(id==='overview')renderOverview();
-  if(id==='pacing')renderPacing();
-  if(id==='foreshadow')renderGantt();
-  if(id==='system')renderSystem();
-}
-
-// 初始渲染
-renderOverview();
-</script>
-</body>
-</html>

+ 0 - 977
docs/archive/architecture/dashboard-prototype.html

@@ -1,977 +0,0 @@
-<!DOCTYPE html>
-<html lang="zh-CN">
-<head>
-<meta charset="UTF-8">
-<meta name="viewport" content="width=device-width,initial-scale=1">
-<title>PIXEL WRITER HUB - Dashboard Prototype</title>
-<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Noto+Sans+SC:wght@400;500;700&display=swap" rel="stylesheet">
-<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
-<style>
-/* ===== PIXEL WRITER HUB DESIGN SYSTEM ===== */
-:root {
-  --bg-main: #fff7e8;
-  --bg-panel: #fffdf6;
-  --bg-card: #fffaf0;
-  --bg-card-2: #fff3d5;
-  --text-main: #2a220f;
-  --text-sub: #5d5035;
-  --text-mute: #8f7f5c;
-  --accent-blue: #26a8ff;
-  --accent-purple: #7f5af0;
-  --accent-green: #2ec27e;
-  --accent-amber: #f5a524;
-  --accent-red: #d7263d;
-  --accent-cyan: #00b8d4;
-  --border-main: #2a220f;
-  --border-soft: #8f7f5c;
-  --shadow-main: 6px 6px 0 #2a220f;
-  --shadow-soft: 3px 3px 0 #8f7f5c;
-  --font-display: 'Press Start 2P', monospace;
-  --font-body: 'Noto Sans SC', 'Microsoft YaHei', sans-serif;
-}
-
-* { margin:0; padding:0; box-sizing:border-box; }
-html, body { height:100%; }
-
-body {
-  font-family: var(--font-body);
-  color: var(--text-main);
-  background: var(--bg-main);
-  background-image:
-    linear-gradient(90deg, rgba(42,34,15,.05) 1px, transparent 1px),
-    linear-gradient(rgba(42,34,15,.05) 1px, transparent 1px);
-  background-size: 14px 14px;
-}
-
-.app-layout {
-  display: grid;
-  grid-template-columns: 240px minmax(0,1fr);
-  height: 100vh;
-}
-
-/* ===== SIDEBAR ===== */
-.sidebar {
-  border-right: 3px solid var(--border-main);
-  background: linear-gradient(180deg, #ffe8b8, #ffe19f);
-  display: flex;
-  flex-direction: column;
-}
-.sidebar-header {
-  padding: 16px;
-  border-bottom: 3px solid var(--border-main);
-}
-.sidebar-header h1 {
-  font-family: var(--font-display);
-  font-size: 11px;
-  letter-spacing: .08em;
-  line-height: 1.45;
-}
-.sidebar-header .subtitle {
-  margin-top: 10px;
-  font-size: 14px;
-  font-weight: 500;
-  color: var(--text-sub);
-}
-.sidebar-nav {
-  flex: 1;
-  overflow-y: auto;
-  padding: 10px;
-  display: flex;
-  flex-direction: column;
-  gap: 8px;
-}
-.nav-item {
-  width: 100%;
-  border: 2px solid var(--border-main);
-  background: #fff9e8;
-  color: var(--text-main);
-  text-align: left;
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  padding: 10px 12px;
-  font-size: 14px;
-  font-weight: 600;
-  cursor: pointer;
-  box-shadow: var(--shadow-soft);
-  transition: transform .08s;
-  font-family: var(--font-body);
-}
-.nav-item:hover { transform: translate(-1px,-1px); }
-.nav-item.active { background: #dff3ff; border-color: var(--accent-blue); }
-.nav-item .icon { width: 22px; text-align: center; }
-
-.live-indicator {
-  border-top: 3px solid var(--border-main);
-  padding: 10px 12px;
-  font-size: 13px;
-  font-weight: 500;
-  display: flex;
-  align-items: center;
-  gap: 8px;
-}
-.live-dot {
-  width: 10px; height: 10px;
-  background: var(--accent-green);
-  border: 2px solid var(--border-main);
-}
-
-/* ===== MAIN ===== */
-.main-content {
-  overflow-y: auto;
-  padding: 22px;
-}
-
-.page { display: none; }
-.page.active { display: block; }
-
-.page-header {
-  display: flex;
-  align-items: center;
-  gap: 12px;
-  margin-bottom: 14px;
-}
-.page-header h2 { font-size: 22px; font-weight: 700; }
-
-/* ===== CARD ===== */
-.card {
-  background: var(--bg-card);
-  border: 3px solid var(--border-main);
-  box-shadow: var(--shadow-main);
-  padding: 16px;
-  margin-bottom: 16px;
-}
-.card-header {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  gap: 12px;
-  margin-bottom: 10px;
-}
-.card-title { font-size: 17px; font-weight: 700; }
-
-.badge {
-  border: 2px solid var(--border-main);
-  font-size: 12px;
-  font-weight: 700;
-  padding: 3px 8px;
-  background: #fff;
-  display: inline-block;
-}
-.badge-blue { background: #dff3ff; color: #055d8b; }
-.badge-green { background: #dcfce7; color: #0f5132; }
-.badge-amber { background: #fff1cd; color: #8a5b00; }
-.badge-red { background: #ffe0e5; color: #8f1d30; }
-.badge-purple { background: #ece3ff; color: #4a2ea8; }
-.badge-cyan { background: #dcfafe; color: #155e75; }
-
-/* ===== STAT GRID ===== */
-.stat-grid {
-  display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
-  gap: 12px;
-  margin-bottom: 14px;
-}
-.stat-card .stat-label {
-  font-size: 13px;
-  font-weight: 600;
-  color: var(--text-mute);
-}
-.stat-card .stat-value {
-  font-size: 28px;
-  line-height: 1.15;
-  margin: 6px 0 2px;
-  color: var(--accent-blue);
-  font-variant-numeric: tabular-nums;
-}
-.stat-card .stat-value.plain { color: var(--text-main); }
-.stat-sub { font-size: 13px; font-weight: 500; color: var(--text-sub); }
-
-.progress-track {
-  margin-top: 8px;
-  height: 12px;
-  border: 2px solid var(--border-main);
-  background: #f8e3b8;
-}
-.progress-fill {
-  height: 100%;
-  background: linear-gradient(90deg, #26a8ff, #7f5af0);
-}
-
-/* ===== CHART CONTAINER ===== */
-.chart-box {
-  width: 100%;
-  height: 320px;
-}
-.chart-box.tall { height: 420px; }
-
-/* ===== TABLE ===== */
-.table-wrap {
-  overflow-x: auto;
-  border: 2px solid var(--border-soft);
-  background: var(--bg-panel);
-}
-.data-table {
-  width: 100%;
-  min-width: 580px;
-  border-collapse: collapse;
-  font-size: 14px;
-  font-variant-numeric: tabular-nums;
-}
-.data-table th {
-  text-align: left;
-  padding: 8px 10px;
-  border-bottom: 2px solid var(--border-soft);
-  background: var(--bg-card-2);
-  white-space: nowrap;
-  font-weight: 700;
-}
-.data-table td {
-  padding: 8px 10px;
-  border-bottom: 1px solid #d8ccb2;
-  font-weight: 500;
-}
-.data-table tbody tr:hover td { background: #fff4d8; }
-
-/* ===== FILTER BUTTONS ===== */
-.filter-group {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 8px;
-  margin-bottom: 12px;
-}
-.filter-btn {
-  border: 2px solid var(--border-main);
-  background: #fff8e6;
-  color: var(--text-main);
-  font-family: var(--font-body);
-  font-size: 13px;
-  font-weight: 600;
-  padding: 5px 10px;
-  cursor: pointer;
-}
-.filter-btn.active { background: #e6f7ff; border-color: var(--accent-blue); }
-
-/* ===== PAGER ===== */
-.pager {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  gap: 10px;
-  margin-top: 8px;
-}
-.page-btn {
-  border: 2px solid var(--border-main);
-  background: #fff8e6;
-  font-family: var(--font-body);
-  font-size: 13px;
-  font-weight: 600;
-  padding: 4px 10px;
-  cursor: pointer;
-}
-.page-btn:hover { background: #e6f7ff; border-color: var(--accent-blue); }
-.page-info { font-size: 13px; font-weight: 600; color: var(--text-sub); }
-
-/* ===== SPLIT LAYOUT ===== */
-.split-layout {
-  display: grid;
-  grid-template-columns: minmax(0,1fr) 340px;
-  gap: 14px;
-}
-
-/* ===== GANTT ===== */
-.chart-box.gantt { height: 380px; }
-
-/* ===== SECTION LABEL ===== */
-.section-label {
-  font-family: var(--font-display);
-  font-size: 9px;
-  letter-spacing: .1em;
-  color: var(--text-mute);
-  margin-bottom: 8px;
-  text-transform: uppercase;
-}
-
-/* ===== PAGE NOTE ===== */
-.proto-note {
-  background: #fff3d5;
-  border: 2px dashed var(--border-soft);
-  padding: 10px 14px;
-  font-size: 13px;
-  color: var(--text-sub);
-  margin-bottom: 14px;
-}
-</style>
-</head>
-<body>
-<div class="app-layout">
-  <!-- SIDEBAR -->
-  <aside class="sidebar">
-    <div class="sidebar-header">
-      <h1>PIXEL WRITER<br>HUB</h1>
-      <div class="subtitle">《仙道长青》</div>
-    </div>
-    <nav class="sidebar-nav" id="nav">
-      <button class="nav-item active" data-page="overview"><span class="icon">📊</span><span>总览</span></button>
-      <button class="nav-item" data-page="characters"><span class="icon">👤</span><span>角色图鉴</span></button>
-      <button class="nav-item" data-page="pacing"><span class="icon">📈</span><span>节奏雷达</span></button>
-      <button class="nav-item" data-page="foreshadowing"><span class="icon">🔖</span><span>伏笔追踪</span></button>
-      <button class="nav-item" data-page="files"><span class="icon">📁</span><span>文档浏览</span></button>
-      <button class="nav-item" data-page="system"><span class="icon">⚙️</span><span>系统状态</span></button>
-    </nav>
-    <div class="live-indicator">
-      <span class="live-dot"></span>
-      实时同步中
-    </div>
-  </aside>
-
-  <!-- MAIN -->
-  <main class="main-content">
-
-    <!-- ==================== PAGE 1: 总览 ==================== -->
-    <div class="page active" id="page-overview">
-      <div class="page-header">
-        <h2>📊 总览</h2>
-        <span class="badge badge-blue">仙侠</span>
-      </div>
-
-      <div class="stat-grid">
-        <div class="card stat-card">
-          <span class="stat-label">总字数</span>
-          <span class="stat-value">128.6 万</span>
-          <span class="stat-sub">目标 200 万字 · 64.3%</span>
-          <div class="progress-track"><div class="progress-fill" style="width:64.3%"></div></div>
-        </div>
-        <div class="card stat-card">
-          <span class="stat-label">当前章节</span>
-          <span class="stat-value">第 412 章</span>
-          <span class="stat-sub">目标 800 章 · 卷 5</span>
-        </div>
-        <div class="card stat-card">
-          <span class="stat-label">Story Runtime</span>
-          <span class="stat-value plain">Mainline</span>
-          <span class="stat-sub">accepted · projection OK</span>
-        </div>
-        <div class="card stat-card">
-          <span class="stat-label">审查均分</span>
-          <span class="stat-value">7.8</span>
-          <span class="stat-sub">最近 50 章平均</span>
-        </div>
-        <div class="card stat-card">
-          <span class="stat-label">紧急伏笔</span>
-          <span class="stat-value" style="color:var(--accent-amber)">4</span>
-          <span class="stat-sub">总计 37 条伏笔</span>
-        </div>
-      </div>
-
-      <!-- 审查得分折线图 -->
-      <div class="card">
-        <div class="card-header">
-          <span class="card-title">审查得分趋势</span>
-          <div>
-            <span class="badge badge-green">最近 50 章</span>
-            <button class="page-btn" style="margin-left:6px">← 前 50</button>
-            <button class="page-btn">跳到最新 →</button>
-          </div>
-        </div>
-        <div class="chart-box" id="chart-review-score"></div>
-      </div>
-
-      <!-- 字数分布柱状图 -->
-      <div class="card">
-        <div class="card-header">
-          <span class="card-title">字数分布(按卷)</span>
-          <span class="badge badge-purple">5 卷</span>
-        </div>
-        <div class="chart-box" id="chart-word-dist"></div>
-      </div>
-
-      <!-- Strand Weave -->
-      <div class="card">
-        <div class="card-header">
-          <span class="card-title">Strand Weave 整体分布</span>
-          <span class="badge badge-purple">constellation</span>
-        </div>
-        <div class="chart-box" id="chart-strand-overview" style="height:260px"></div>
-      </div>
-
-      <!-- 紧急伏笔 Top 5 -->
-      <div class="card">
-        <div class="card-header">
-          <span class="card-title">紧急伏笔 Top 5</span>
-        </div>
-        <div class="table-wrap">
-          <table class="data-table">
-            <thead><tr><th>内容</th><th>状态</th><th>埋设章</th><th>目标章</th><th>紧急度</th></tr></thead>
-            <tbody>
-              <tr><td>青元秘境的钥匙碎片下落</td><td><span class="badge badge-red">超期</span></td><td>285</td><td>350</td><td><span class="badge badge-red">critical</span></td></tr>
-              <tr><td>凤灵儿真实身份暗示</td><td><span class="badge badge-amber">紧急</span></td><td>312</td><td>420</td><td><span class="badge badge-amber">high</span></td></tr>
-              <tr><td>老道士临终遗言中的数字</td><td><span class="badge badge-amber">紧急</span></td><td>356</td><td>430</td><td><span class="badge badge-amber">high</span></td></tr>
-              <tr><td>黑市拍卖会幕后势力</td><td><span class="badge badge-amber">紧急</span></td><td>389</td><td>440</td><td><span class="badge badge-amber">medium</span></td></tr>
-              <tr><td>主角功法异变的真实原因</td><td><span class="badge badge-blue">活跃</span></td><td>401</td><td>500</td><td><span class="badge badge-blue">normal</span></td></tr>
-            </tbody>
-          </table>
-        </div>
-      </div>
-    </div>
-
-    <!-- ==================== PAGE 2: 角色图鉴 ==================== -->
-    <div class="page" id="page-characters">
-      <div class="page-header">
-        <h2>👤 角色图鉴</h2>
-        <span class="badge badge-green">48 / 127 个实体</span>
-      </div>
-
-      <div class="filter-group">
-        <button class="filter-btn active">全部</button>
-        <button class="filter-btn">角色</button>
-        <button class="filter-btn">势力</button>
-        <button class="filter-btn">地点</button>
-        <button class="filter-btn">法宝</button>
-      </div>
-
-      <div class="proto-note">Tab 1: 实体列表 + 详情面板(保留现有逻辑) | Tab 2: 关系图谱(下方预览)</div>
-
-      <!-- 关系图谱 -->
-      <div class="card">
-        <div class="card-header">
-          <span class="card-title">关系图谱</span>
-          <span class="badge badge-blue">ECharts graph · 力导向 · 时间轴</span>
-        </div>
-        <!-- 时间轴控制器 -->
-        <div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;flex-wrap:wrap;">
-          <button class="page-btn" id="graph-play-btn" style="min-width:60px">▶ 播放</button>
-          <span style="font-size:13px;font-weight:600;color:var(--text-mute);white-space:nowrap">第 1 章</span>
-          <input type="range" id="graph-timeline" min="1" max="412" value="412"
-            style="flex:1;min-width:200px;height:12px;accent-color:#26a8ff;cursor:pointer">
-          <span style="font-size:13px;font-weight:600;color:var(--text-mute);white-space:nowrap">第 412 章</span>
-          <span id="graph-chapter-label" class="badge badge-blue" style="min-width:90px;text-align:center">第 412 章</span>
-          <span id="graph-node-count" class="badge badge-green" style="min-width:60px;text-align:center">8 人</span>
-        </div>
-        <div class="chart-box tall" id="chart-relation-graph"></div>
-      </div>
-    </div>
-
-    <!-- ==================== PAGE 3: 节奏雷达 ==================== -->
-    <div class="page" id="page-pacing">
-      <div class="page-header">
-        <h2>📈 节奏雷达</h2>
-        <span class="badge badge-amber">412 章数据</span>
-      </div>
-
-      <!-- 钩子强度面积图 -->
-      <div class="card">
-        <div class="card-header">
-          <span class="card-title">钩子强度走势</span>
-          <div>
-            <span class="badge badge-green">第 363-412 章</span>
-            <button class="page-btn" style="margin-left:6px">← 前 50</button>
-            <button class="page-btn">跳到最新 →</button>
-          </div>
-        </div>
-        <div class="chart-box" id="chart-hook-strength"></div>
-      </div>
-
-      <!-- Strand 堆叠柱状图 -->
-      <div class="card">
-        <div class="card-header">
-          <span class="card-title">Strand 分布(逐章)</span>
-          <span class="badge badge-purple">堆叠柱状图</span>
-        </div>
-        <div class="chart-box" id="chart-strand-stack"></div>
-      </div>
-
-      <!-- 字数分布 -->
-      <div class="card">
-        <div class="card-header">
-          <span class="card-title">章节字数分布</span>
-          <span class="badge badge-blue">按卷分组</span>
-        </div>
-        <div class="chart-box" id="chart-pacing-words"></div>
-      </div>
-    </div>
-
-    <!-- ==================== PAGE 4: 伏笔追踪 ==================== -->
-    <div class="page" id="page-foreshadowing">
-      <div class="page-header">
-        <h2>🔖 伏笔追踪</h2>
-      </div>
-
-      <div class="stat-grid">
-        <div class="card stat-card">
-          <span class="stat-label">总伏笔</span>
-          <span class="stat-value plain">37</span>
-        </div>
-        <div class="card stat-card">
-          <span class="stat-label">活跃</span>
-          <span class="stat-value" style="color:var(--accent-blue)">18</span>
-        </div>
-        <div class="card stat-card">
-          <span class="stat-label">已回收</span>
-          <span class="stat-value" style="color:var(--accent-green)">15</span>
-        </div>
-        <div class="card stat-card">
-          <span class="stat-label">紧急/超期</span>
-          <span class="stat-value" style="color:var(--accent-red)">4</span>
-        </div>
-      </div>
-
-      <div class="filter-group">
-        <button class="filter-btn active">全部</button>
-        <button class="filter-btn">紧急</button>
-        <button class="filter-btn">活跃</button>
-        <button class="filter-btn">已回收</button>
-      </div>
-
-      <!-- 甘特时间线 -->
-      <div class="card">
-        <div class="card-header">
-          <span class="card-title">伏笔时间线</span>
-          <span class="badge badge-cyan">ECharts 自定义 bar · 甘特</span>
-        </div>
-        <div class="chart-box gantt" id="chart-foreshadow-gantt"></div>
-      </div>
-
-      <!-- 伏笔完整表格 -->
-      <div class="card">
-        <div class="card-header">
-          <span class="card-title">完整伏笔列表</span>
-        </div>
-        <div class="table-wrap">
-          <table class="data-table">
-            <thead><tr><th>内容</th><th>状态</th><th>埋设章</th><th>目标章</th><th>紧急度</th></tr></thead>
-            <tbody>
-              <tr><td>青元秘境的钥匙碎片下落</td><td><span class="badge badge-red">超期</span></td><td>285</td><td>350</td><td><span class="badge badge-red">critical</span></td></tr>
-              <tr><td>凤灵儿真实身份暗示</td><td><span class="badge badge-amber">紧急</span></td><td>312</td><td>420</td><td><span class="badge badge-amber">high</span></td></tr>
-              <tr><td>天魔血脉觉醒征兆</td><td><span class="badge badge-blue">活跃</span></td><td>345</td><td>500</td><td><span class="badge badge-blue">normal</span></td></tr>
-              <tr><td>第一卷师门灭门线索</td><td><span class="badge badge-green">已回收</span></td><td>12</td><td>180</td><td>—</td></tr>
-            </tbody>
-          </table>
-        </div>
-        <div class="pager">
-          <button class="page-btn">上一页</button>
-          <span class="page-info">第 1 / 4 页 · 共 37 条</span>
-          <button class="page-btn">下一页</button>
-        </div>
-      </div>
-    </div>
-
-    <!-- ==================== PAGE 5: 文档浏览 ==================== -->
-    <div class="page" id="page-files">
-      <div class="page-header">
-        <h2>📁 文档浏览</h2>
-      </div>
-      <div class="proto-note">逻辑不变,从现有 App.jsx 迁移。左侧文件树 + 右侧内容预览。</div>
-      <div class="card" style="height:500px;display:flex;align-items:center;justify-content:center;">
-        <span style="font-size:40px;margin-right:12px">📂</span>
-        <span style="color:var(--text-mute);font-size:16px;font-weight:600">文件树 + 内容预览(直接迁移,无变化)</span>
-      </div>
-    </div>
-
-    <!-- ==================== PAGE 6: 系统状态 ==================== -->
-    <div class="page" id="page-system">
-      <div class="page-header">
-        <h2>⚙️ 系统状态</h2>
-      </div>
-
-      <div class="stat-grid">
-        <div class="card stat-card">
-          <span class="stat-label">Story Runtime</span>
-          <span class="stat-value plain">Mainline</span>
-          <span class="stat-sub">fallback: state.json, index.db</span>
-        </div>
-        <div class="card stat-card">
-          <span class="stat-label">Latest Commit</span>
-          <span class="stat-value plain">accepted</span>
-          <span class="stat-sub">第 412 章 · 5 路 projection OK</span>
-        </div>
-        <div class="card stat-card">
-          <span class="stat-label">RAG Mode</span>
-          <span class="stat-value" style="color:var(--accent-green)">full</span>
-          <span class="stat-sub">embed + rerank 就绪</span>
-        </div>
-        <div class="card stat-card">
-          <span class="stat-label">Vector DB</span>
-          <span class="stat-value plain">2,847</span>
-          <span class="stat-sub">条向量记录</span>
-        </div>
-      </div>
-
-      <!-- 合同树概览 -->
-      <div class="card">
-        <div class="card-header">
-          <span class="card-title">合同树概览</span>
-        </div>
-        <div class="table-wrap">
-          <table class="data-table">
-            <thead><tr><th>类型</th><th>数量</th><th>说明</th></tr></thead>
-            <tbody>
-              <tr><td>MASTER_SETTING</td><td><span class="badge badge-green">1</span></td><td>仙侠 · 沉稳厚重</td></tr>
-              <tr><td>VOLUME_BRIEF</td><td><span class="badge badge-blue">5</span></td><td>卷 1-5</td></tr>
-              <tr><td>CHAPTER_BRIEF</td><td><span class="badge badge-blue">412</span></td><td>全章</td></tr>
-              <tr><td>REVIEW_CONTRACT</td><td><span class="badge badge-purple">412</span></td><td>全章审查合同</td></tr>
-            </tbody>
-          </table>
-        </div>
-      </div>
-
-      <!-- 最近 Commit -->
-      <div class="card">
-        <div class="card-header">
-          <span class="card-title">最近 Commit 历史</span>
-        </div>
-        <div class="table-wrap">
-          <table class="data-table">
-            <thead><tr><th>章节</th><th>状态</th><th>state</th><th>index</th><th>summary</th><th>memory</th><th>dashboard</th></tr></thead>
-            <tbody>
-              <tr><td>第 412 章</td><td><span class="badge badge-green">accepted</span></td><td><span class="badge badge-green">OK</span></td><td><span class="badge badge-green">OK</span></td><td><span class="badge badge-green">OK</span></td><td><span class="badge badge-green">OK</span></td><td><span class="badge badge-green">OK</span></td></tr>
-              <tr><td>第 411 章</td><td><span class="badge badge-green">accepted</span></td><td><span class="badge badge-green">OK</span></td><td><span class="badge badge-green">OK</span></td><td><span class="badge badge-green">OK</span></td><td><span class="badge badge-green">OK</span></td><td><span class="badge badge-green">OK</span></td></tr>
-              <tr><td>第 410 章</td><td><span class="badge badge-red">rejected</span></td><td><span class="badge badge-amber">skip</span></td><td><span class="badge badge-amber">skip</span></td><td><span class="badge badge-amber">skip</span></td><td><span class="badge badge-amber">skip</span></td><td><span class="badge badge-amber">skip</span></td></tr>
-            </tbody>
-          </table>
-        </div>
-      </div>
-
-      <!-- RAG 诊断 -->
-      <div class="card">
-        <div class="card-header">
-          <span class="card-title">RAG 环境</span>
-          <button class="page-btn">运行诊断</button>
-        </div>
-        <div class="table-wrap">
-          <table class="data-table">
-            <thead><tr><th>组件</th><th>状态</th><th>详情</th></tr></thead>
-            <tbody>
-              <tr><td>Embedding Key</td><td><span class="badge badge-green">OK</span></td><td>VOYAGE_API_KEY 已配置</td></tr>
-              <tr><td>Rerank Key</td><td><span class="badge badge-green">OK</span></td><td>COHERE_API_KEY 已配置</td></tr>
-              <tr><td>Vector DB</td><td><span class="badge badge-green">OK</span></td><td>2,847 records · 128 MB</td></tr>
-              <tr><td>RAG Mode</td><td><span class="badge badge-green">full</span></td><td>embed + rerank</td></tr>
-            </tbody>
-          </table>
-        </div>
-      </div>
-    </div>
-
-  </main>
-</div>
-
-<script>
-// ===== NAV SWITCHING =====
-document.getElementById('nav').addEventListener('click', e => {
-  const btn = e.target.closest('.nav-item');
-  if (!btn) return;
-  document.querySelectorAll('.nav-item').forEach(b => b.classList.remove('active'));
-  btn.classList.add('active');
-  document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
-  document.getElementById('page-' + btn.dataset.page).classList.add('active');
-  // re-render charts on page switch
-  setTimeout(() => window.dispatchEvent(new Event('resize')), 50);
-});
-
-// ===== ECHARTS PIXEL THEME =====
-const PIXEL_THEME = {
-  color: ['#26a8ff','#f5a524','#7f5af0','#2ec27e','#d7263d','#00b8d4','#ff5c8a'],
-  backgroundColor: 'transparent',
-  textStyle: { fontFamily: "'Noto Sans SC', sans-serif", color: '#2a220f' },
-  title: { textStyle: { fontFamily: "'Press Start 2P', monospace", fontSize: 11, color: '#2a220f' } },
-  legend: { textStyle: { fontSize: 13, fontWeight: 600, color: '#5d5035' } },
-  tooltip: {
-    backgroundColor: '#fffaf0',
-    borderColor: '#2a220f',
-    borderWidth: 2,
-    textStyle: { color: '#2a220f', fontSize: 13 },
-    extraCssText: 'border-radius:0;box-shadow:3px 3px 0 #2a220f;'
-  },
-  categoryAxis: {
-    axisLine: { lineStyle: { color: '#8f7f5c', width: 2 } },
-    axisTick: { lineStyle: { color: '#8f7f5c' } },
-    axisLabel: { color: '#8f7f5c', fontSize: 12 },
-    splitLine: { lineStyle: { color: '#e8dcc4', type: 'dashed' } }
-  },
-  valueAxis: {
-    axisLine: { lineStyle: { color: '#8f7f5c', width: 2 } },
-    axisTick: { lineStyle: { color: '#8f7f5c' } },
-    axisLabel: { color: '#8f7f5c', fontSize: 12 },
-    splitLine: { lineStyle: { color: '#e8dcc4', type: 'dashed' } }
-  },
-  grid: { left: 50, right: 20, top: 30, bottom: 40 }
-};
-echarts.registerTheme('pixel', PIXEL_THEME);
-
-function px(id, opt) {
-  const el = document.getElementById(id);
-  if (!el) return null;
-  const chart = echarts.init(el, 'pixel');
-  chart.setOption(opt);
-  window.addEventListener('resize', () => chart.resize());
-  return chart;
-}
-
-// ===== SAMPLE DATA =====
-const chapters50 = Array.from({length:50}, (_,i) => i+363);
-const reviewScores = [7.2,7.5,6.8,7.9,8.1,7.4,7.0,7.8,8.3,7.6,6.5,7.2,7.8,8.0,7.1,7.5,7.9,8.2,7.3,6.9,7.7,8.0,7.4,7.6,8.1,7.8,7.2,7.5,8.4,7.0,7.3,7.9,8.0,7.6,7.1,7.8,8.2,7.5,7.3,8.0,7.7,7.4,8.1,7.9,7.2,7.6,8.3,7.8,7.5,7.8];
-
-// 1. Review Score Line
-px('chart-review-score', {
-  xAxis: { type: 'category', data: chapters50.map(c => '第'+c+'章'), axisLabel: { interval: 9 } },
-  yAxis: { type: 'value', min: 5, max: 10 },
-  series: [{
-    type: 'line', data: reviewScores, smooth: false, step: false,
-    lineStyle: { width: 3, color: '#26a8ff' },
-    itemStyle: { color: '#26a8ff', borderColor: '#2a220f', borderWidth: 2 },
-    symbol: 'rect', symbolSize: 8,
-    markLine: { data: [{ type: 'average', label: { formatter: '均值 {c}' } }], lineStyle: { color: '#f5a524', width: 2, type: 'dashed' } },
-    areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [{ offset: 0, color: 'rgba(38,168,255,.2)' }, { offset: 1, color: 'rgba(38,168,255,0)' }] } }
-  }]
-});
-
-// 2. Word Distribution by Volume
-px('chart-word-dist', {
-  xAxis: { type: 'category', data: ['卷一\n求道篇','卷二\n筑基篇','卷三\n历练篇','卷四\n金丹篇','卷五\n元婴篇'] },
-  yAxis: { type: 'value', axisLabel: { formatter: v => (v/10000).toFixed(0)+'万' } },
-  series: [{
-    type: 'bar', data: [
-      { value: 280000, itemStyle: { color: '#26a8ff' } },
-      { value: 310000, itemStyle: { color: '#7f5af0' } },
-      { value: 265000, itemStyle: { color: '#2ec27e' } },
-      { value: 290000, itemStyle: { color: '#f5a524' } },
-      { value: 141000, itemStyle: { color: '#00b8d4' } }
-    ],
-    barWidth: '50%',
-    label: { show: true, position: 'top', formatter: p => (p.value/10000).toFixed(1)+'万', fontSize: 12, fontWeight: 700 },
-    itemStyle: { borderColor: '#2a220f', borderWidth: 2 }
-  }]
-});
-
-// 3. Strand Overview Pie
-px('chart-strand-overview', {
-  legend: { bottom: 0, data: ['Quest','Fire','Constellation'] },
-  series: [{
-    type: 'pie', radius: ['40%','70%'], center: ['50%','45%'],
-    data: [
-      { value: 145, name: 'Quest', itemStyle: { color: '#26a8ff', borderColor: '#2a220f', borderWidth: 2 } },
-      { value: 128, name: 'Fire', itemStyle: { color: '#ff5c8a', borderColor: '#2a220f', borderWidth: 2 } },
-      { value: 139, name: 'Constellation', itemStyle: { color: '#7f5af0', borderColor: '#2a220f', borderWidth: 2 } }
-    ],
-    label: { formatter: '{b}\n{d}%', fontSize: 13, fontWeight: 600 },
-    itemStyle: { borderColor: '#2a220f', borderWidth: 2 }
-  }]
-});
-
-// 4. Relation Graph with Timeline
-const graphNodes = [
-  { name: '林长青', category: 0, appear: 1,   symbolSize: [70,35], itemStyle: { color: '#f5a524' } },
-  { name: '老道士', category: 0, appear: 1 },
-  { name: '太虚宗', category: 1, appear: 5 },
-  { name: '凤灵儿', category: 0, appear: 28 },
-  { name: '初代掌门遗物', category: 2, appear: 45 },
-  { name: '青元秘境', category: 2, appear: 80 },
-  { name: '白玉京', category: 0, appear: 120 },
-  { name: '天魔教', category: 1, appear: 150 },
-  { name: '东海仙城', category: 2, appear: 220 },
-  { name: '龙脉封印', category: 2, appear: 260 },
-  { name: '黑市掮客', category: 0, appear: 310 },
-  { name: '剑灵', category: 0, appear: 380 }
-];
-const graphLinks = [
-  { source: '林长青', target: '老道士', name: '师徒', appear: 1 },
-  { source: '老道士', target: '太虚宗', name: '前长老', appear: 5 },
-  { source: '林长青', target: '太虚宗', name: '入门', appear: 12 },
-  { source: '林长青', target: '凤灵儿', name: '初识', appear: 28, changeTo: '师兄妹', changeAt: 60 },
-  { source: '林长青', target: '初代掌门遗物', name: '获得', appear: 50 },
-  { source: '太虚宗', target: '青元秘境', name: '管辖', appear: 80 },
-  { source: '林长青', target: '青元秘境', name: '历练', appear: 85 },
-  { source: '凤灵儿', target: '太虚宗', name: '弟子', appear: 35 },
-  { source: '林长青', target: '白玉京', name: '初遇', appear: 120, changeTo: '宿敌', changeAt: 200 },
-  { source: '白玉京', target: '天魔教', name: '加入', appear: 180, changeTo: '长老', changeAt: 300 },
-  { source: '太虚宗', target: '天魔教', name: '敌对', appear: 200 },
-  { source: '老道士', target: '东海仙城', name: '隐居', appear: 220 },
-  { source: '林长青', target: '东海仙城', name: '造访', appear: 235 },
-  { source: '林长青', target: '龙脉封印', name: '发现', appear: 260 },
-  { source: '黑市掮客', target: '天魔教', name: '线人', appear: 320 },
-  { source: '林长青', target: '黑市掮客', name: '交易', appear: 330 },
-  { source: '林长青', target: '剑灵', name: '契约', appear: 385 },
-  { source: '剑灵', target: '初代掌门遗物', name: '寄宿', appear: 390 }
-];
-const graphCategories = [
-  { name: '角色', itemStyle: { color: '#26a8ff' } },
-  { name: '势力', itemStyle: { color: '#7f5af0' } },
-  { name: '地点', itemStyle: { color: '#2ec27e' } }
-];
-
-function getGraphDataAtChapter(ch) {
-  const nodes = graphNodes
-    .filter(n => n.appear <= ch)
-    .map(n => ({
-      ...n,
-      symbol: 'rect',
-      symbolSize: n.symbolSize || [60, 30],
-      label: { show: true, fontSize: 12, fontWeight: 700, color: '#fff' },
-      itemStyle: { ...(n.itemStyle || {}), borderColor: '#2a220f', borderWidth: 2 }
-    }));
-  const nodeNames = new Set(nodes.map(n => n.name));
-  const links = graphLinks
-    .filter(l => l.appear <= ch && nodeNames.has(l.source) && nodeNames.has(l.target))
-    .map(l => ({
-      source: l.source,
-      target: l.target,
-      name: (l.changeTo && ch >= l.changeAt) ? l.changeTo : l.name
-    }));
-  return { nodes, links };
-}
-
-const graphEl = document.getElementById('chart-relation-graph');
-const graphChart = echarts.init(graphEl, 'pixel');
-const slider = document.getElementById('graph-timeline');
-const chLabel = document.getElementById('graph-chapter-label');
-const nodeCount = document.getElementById('graph-node-count');
-const playBtn = document.getElementById('graph-play-btn');
-let playing = false, playTimer = null;
-
-function renderGraph(ch) {
-  const { nodes, links } = getGraphDataAtChapter(ch);
-  chLabel.textContent = '第 ' + ch + ' 章';
-  nodeCount.textContent = nodes.length + ' 人';
-  graphChart.setOption({
-    animationDuration: 300,
-    animationEasingUpdate: 'cubicOut',
-    series: [{
-      type: 'graph', layout: 'force', roam: true,
-      symbol: 'rect',
-      edgeLabel: { show: true, fontSize: 11, formatter: p => p.data.name, color: '#5d5035' },
-      force: { repulsion: 350, edgeLength: [120, 200], gravity: 0.1 },
-      lineStyle: { color: '#8f7f5c', width: 2, curveness: 0.1 },
-      categories: graphCategories,
-      nodes: nodes,
-      links: links
-    }]
-  });
-}
-
-slider.addEventListener('input', () => {
-  renderGraph(parseInt(slider.value));
-});
-
-playBtn.addEventListener('click', () => {
-  if (playing) {
-    playing = false;
-    clearInterval(playTimer);
-    playBtn.textContent = '▶ 播放';
-  } else {
-    playing = true;
-    playBtn.textContent = '⏸ 暂停';
-    if (parseInt(slider.value) >= 412) slider.value = 1;
-    playTimer = setInterval(() => {
-      let v = parseInt(slider.value) + 5;
-      if (v > 412) { v = 412; playing = false; clearInterval(playTimer); playBtn.textContent = '▶ 播放'; }
-      slider.value = v;
-      renderGraph(v);
-    }, 120);
-  }
-});
-
-renderGraph(412);
-window.addEventListener('resize', () => graphChart.resize());
-
-// 5. Hook Strength Area
-const hookValues = [3,4,3,5,4,3,2,5,4,3,4,5,3,4,5,4,3,5,4,3,2,4,5,3,4,5,4,3,5,4,3,4,5,3,4,2,5,4,3,5,4,3,5,4,3,4,5,4,3,4];
-px('chart-hook-strength', {
-  xAxis: { type: 'category', data: chapters50.map(c => c+''), axisLabel: { interval: 9 } },
-  yAxis: { type: 'value', min: 0, max: 5, axisLabel: { formatter: v => ['','weak','','medium','','strong'][v] || '' } },
-  series: [{
-    type: 'line', data: hookValues, smooth: false,
-    lineStyle: { width: 3, color: '#f5a524' },
-    itemStyle: { color: '#f5a524', borderColor: '#2a220f', borderWidth: 2 },
-    symbol: 'rect', symbolSize: 6,
-    areaStyle: { color: { type: 'linear', x:0,y:0,x2:0,y2:1, colorStops: [{offset:0,color:'rgba(245,165,36,.3)'},{offset:1,color:'rgba(245,165,36,0)'}] } }
-  }]
-});
-
-// 6. Strand Stack Bar
-const strandChapters = chapters50.map(c => c+'');
-const questData = chapters50.map(() => Math.floor(Math.random()*3)+1);
-const fireData = chapters50.map(() => Math.floor(Math.random()*3)+1);
-const constData = chapters50.map(() => Math.floor(Math.random()*3)+1);
-px('chart-strand-stack', {
-  legend: { data: ['Quest','Fire','Constellation'], bottom: 0 },
-  xAxis: { type: 'category', data: strandChapters, axisLabel: { interval: 9 } },
-  yAxis: { type: 'value' },
-  series: [
-    { name: 'Quest', type: 'bar', stack: 'strand', data: questData, itemStyle: { color: '#26a8ff', borderColor: '#2a220f', borderWidth: 1 }, barWidth: '60%' },
-    { name: 'Fire', type: 'bar', stack: 'strand', data: fireData, itemStyle: { color: '#ff5c8a', borderColor: '#2a220f', borderWidth: 1 } },
-    { name: 'Constellation', type: 'bar', stack: 'strand', data: constData, itemStyle: { color: '#7f5af0', borderColor: '#2a220f', borderWidth: 1 } }
-  ]
-});
-
-// 7. Pacing Words by Volume
-const vol1 = Array.from({length:80}, () => 2800+Math.random()*1200);
-const vol2 = Array.from({length:90}, () => 3000+Math.random()*1500);
-const vol3 = Array.from({length:75}, () => 2500+Math.random()*1800);
-const vol4 = Array.from({length:85}, () => 3200+Math.random()*1200);
-const vol5 = Array.from({length:82}, () => 2600+Math.random()*1600);
-function volBoxData(arr) {
-  const s = [...arr].sort((a,b)=>a-b);
-  return [s[0], s[Math.floor(s.length*.25)], s[Math.floor(s.length*.5)], s[Math.floor(s.length*.75)], s[s.length-1]].map(Math.round);
-}
-px('chart-pacing-words', {
-  xAxis: { type: 'category', data: ['卷一','卷二','卷三','卷四','卷五'] },
-  yAxis: { type: 'value', axisLabel: { formatter: v => (v/1000).toFixed(0)+'k' } },
-  series: [{
-    type: 'boxplot',
-    data: [volBoxData(vol1), volBoxData(vol2), volBoxData(vol3), volBoxData(vol4), volBoxData(vol5)],
-    itemStyle: { color: '#fffaf0', borderColor: '#26a8ff', borderWidth: 2 }
-  }]
-});
-
-// 8. Foreshadowing Gantt
-const foreshadowData = [
-  { name: '青元秘境钥匙碎片', start: 285, end: 350, status: 'overdue' },
-  { name: '凤灵儿真实身份', start: 312, end: 420, status: 'urgent' },
-  { name: '老道士遗言数字', start: 356, end: 430, status: 'urgent' },
-  { name: '黑市幕后势力', start: 389, end: 440, status: 'urgent' },
-  { name: '功法异变原因', start: 401, end: 500, status: 'active' },
-  { name: '天魔血脉觉醒', start: 345, end: 500, status: 'active' },
-  { name: '仙城禁地秘密', start: 220, end: 480, status: 'active' },
-  { name: '师门灭门线索', start: 12, end: 180, status: 'resolved' },
-  { name: '初代掌门遗物', start: 45, end: 150, status: 'resolved' },
-  { name: '龙脉封印', start: 100, end: 260, status: 'resolved' }
-];
-const statusColor = { overdue: '#d7263d', urgent: '#f5a524', active: '#26a8ff', resolved: '#2ec27e' };
-const yLabels = foreshadowData.map(d => d.name);
-
-px('chart-foreshadow-gantt', {
-  grid: { left: 140, right: 30, top: 10, bottom: 40 },
-  xAxis: { type: 'value', min: 0, max: 520, axisLabel: { formatter: v => '第'+v+'章' }, splitLine: { lineStyle: { color: '#e8dcc4', type: 'dashed' } } },
-  yAxis: { type: 'category', data: yLabels, inverse: true, axisLabel: { fontSize: 12, fontWeight: 600 } },
-  series: [
-    {
-      type: 'custom',
-      renderItem: function(params, api) {
-        const catIdx = api.value(0);
-        const start = api.coord([api.value(1), catIdx]);
-        const end = api.coord([api.value(2), catIdx]);
-        const height = api.size([0, 1])[1] * 0.5;
-        return {
-          type: 'rect',
-          shape: { x: start[0], y: start[1] - height/2, width: end[0] - start[0], height: height },
-          style: { fill: api.value(3), stroke: '#2a220f', lineWidth: 2 }
-        };
-      },
-      encode: { x: [1, 2], y: 0 },
-      data: foreshadowData.map((d, i) => [i, d.start, d.end, statusColor[d.status]])
-    },
-    {
-      type: 'line', z: 10,
-      markLine: {
-        silent: true, symbol: 'none',
-        lineStyle: { color: '#26a8ff', width: 3, type: 'solid' },
-        data: [{ xAxis: 412 }],
-        label: { formatter: '当前 412章', position: 'end', fontSize: 11, fontWeight: 700, color: '#26a8ff' }
-      },
-      data: []
-    }
-  ]
-});
-</script>
-</body>
-</html>

+ 0 - 140
docs/archive/architecture/narrative-intelligence-roadmap-2026-06-10.md

@@ -1,140 +0,0 @@
-# 叙事智能升级路线图(2026-06-10)
-
-> **状态(2026-06-11 归档):** 本路线图已被 v7 story repo 规格吸收,作为独立计划取消。条目归宿:M1-1 被否决(v7 spec §13,信息差/+时间线在场列取代 witnesses 投影);M1-2 → 承诺派生指标(§5)+ 句式体检;M1-3 → 风格宪法否决入宪(§6.1);M1-4 → 记忆层卷摘要(§4.6);M2-1 → 体检事务(§9);M2-2 → 三镜头评审(§8);M3-1 → 卷复盘伏笔机会扫描(§9);M2-3/M3-2/M3-3 随冻结令搁置。本文"不做清单"中的"禁全自动"已被 spec 0.5 撤销(§8.1),以 spec 为准。见 `docs/architecture/story-repo-spec-2026-06-10.md`。
-
-> 来源:2026-06-10 全项目审查 + 「AI 写长篇网文系统」架构讨论。
-> 前置依赖:`docs/superpowers/plans/2026-06-10-audit-fix-plan.md`(修复计划)完成 Phase 0/1 后启动本路线图;两者改同一批模块,不要并行。
-
-## 设计原则(讨论共识)
-
-1. **状态分三层**:事实状态(已有:event log + projection + contract)、叙事状态(张力/信息差/爽点节奏——当前最大空白)、文体状态(口癖/声音/漂移——第二空白)。本路线图主攻后两层。
-2. **能机检的不用模型**:节奏规则做成"写完后的度量"而非"写作时的指导";确定性脚本量出指标,超阈值才打回。
-3. **重脚本、轻模型**:每次模型调用前由确定性代码把上下文压到最小最准;token 成本是真实作者的硬约束。
-4. **作者品味是唯一不可再生资源**:高杠杆决策点(大纲拍板、弧光转折、伏笔埋收)永远留给人;系统从否决中学习而不是要求作者重复纠正。
-
----
-
-## M1 近期(修复计划完成后立即可做,纯增量)
-
-### M1-1 角色知识边界(信息差管理)
-
-**动机**:悬念 = 作者已知/读者已知/角色已知三个集合的差。AI 两类隐蔽错误——泄密(角色说出不该知道的事)和废笔(向读者复述已知信息)——现有 review 维度抓不住。
-
-**设计**:
-- event log 已是事件事实源,为 `story_event_schema` 增加可选字段 `witnesses: [entity_id]`(哪些角色在场/得知)。data-agent 提取事件时顺带标注,无标注默认"仅在场角色"。
-- 新增投影 `knowledge_projection_writer`:按角色累积"已知事件集",落 `index.db` 新表 `entity_knowledge(entity_id, event_id, learned_chapter)`。
-- prewrite 阶段(`write_gates/prewrite.py` 或 context pack 新 section `knowledge_boundaries`)注入:本章在场角色各自**不知道**的关键事件 top-N(按与本章大纲的实体重叠筛选)。
-- review 新增机检维度 `info_leak`:扫描本章对白/内心戏中出现的实体与事件关键词,比对说话角色的已知集,疑似泄密列为 warning(不自动 blocking——误报率先观察一个月)。
-
-**落点**:`story_event_schema.py`、新 `knowledge_projection_writer.py`、`event_projection_router.py` 注册、`context_manager.py` 新 section、`review_pipeline.py` 新检查器。
-**验收**:构造"角色 A 不在场的密谋事件"测试项目,A 的对白引用该事件时 review 报 info_leak。
-
-### M1-2 叙事节奏度量脚本化(写后量化,不是写前说教)
-
-**动机**:爽点密度/钩子强度目前是 SKILL 里的指导性文字,模型打折执行;`chapter_reading_power` 表已有 hook_type/hook_strength 数据但没人消费成硬门禁。
-
-**设计**:
-- 新增 `pacing_metrics.py`:输入章号,输出确定性指标——距上次主线推进章数(strand_tracker)、距上次 strong hook 章数、本章爽点事件数(event log 中 payoff 类事件)、连续过渡章计数。
-- `write_gates/postcommit.py` 增加软门禁:连续 N 章(默认 3)无 strong hook 或主线停滞超过配置阈值 → commit 通过但输出醒目"节奏债"警告并写入 `chase_debt`(表已存在,复用)。
-- `webnovel-review` SKILL 把"节奏检查"从模型评审项改为:先读 pacing_metrics 输出,模型只解释数字、不重新估计数字。
-
-**落点**:新 `data_modules/pacing_metrics.py`、`write_gates/postcommit.py`、review SKILL Step 调整、dashboard PacingPage 直接展示该指标(前端已有页面骨架)。
-**验收**:构造连续 4 章 weak hook 的测试数据,postcommit 输出节奏债警告。
-
-### M1-3 Override 泛化(从否决中学规则)
-
-**动机**:作者连续改掉同类表达,系统应学到风格规则,而不是等第四次。override ledger 与 `project-memory add-pattern` 机制都已存在,缺的是连接。
-
-**设计**:
-- `override_ledger_service.py` 新增 `summarize_recurring(min_count=3)`:按 override 的 category/target 聚类,输出"同类否决 ≥3 次"的候选规则。
-- `webnovel-review` SKILL 收尾步骤加一条指令:调用上述命令,存在候选时向作者展示"检测到你多次否决 X,是否固化为风格规则?",确认后写入 `project_memory.json` 的 patterns(已有 add-pattern 命令),后续 context pack 的 preferences section 自动携带。
-
-**落点**:`override_ledger_service.py`、review SKILL、无新存储。
-**验收**:3 条同类 override 后 `summarize_recurring` 返回聚类项。
-
-### M1-4 分层摘要(卷级中间层)
-
-**动机**:现有 recent_summaries(近 3 章全文摘要)+ story_skeleton(间隔采样)之间缺"卷级摘要",写 800 章时中距离剧情(50-200 章前)的召回靠 RAG 撞运气。
-
-**设计**:
-- 卷完结时(update_master_outline 或 plan skill 的卷复盘步骤)生成 `.webnovel/summaries/volume_NN.md`(500 字内:主线推进、关系变化、未回收伏笔清单)。
-- `context_manager._build_pack` 的 core 增加 `volume_summaries`:当前卷之前的全部卷摘要(每卷 500 字,30 卷也只有 1.5 万字,且随距离可再截断)。
-
-**落点**:plan SKILL 卷复盘步骤、`context_manager.py`、`summary_projection_writer.py` 不动(卷摘要由 skill 流程产出而非投影)。
-**验收**:构造含 volume_01.md 的项目,context pack 出现 volume_summaries section。
-
----
-
-## M2 中期(M1 验证后)
-
-### M2-1 文体指纹与漂移检测
-
-**动机**:千章尺度的声音漂移无法靠上下文策略根治(逐章累积、每步都在容差内),只能靠周期性度量校准。`style_sampler.py` 已有采样底子。
-
-**设计**:
-- `style_sampler` 扩展 `fingerprint` 子命令:对指定章节区间计算——句长分布(均值/方差/分位)、对白占比、高频口癖词 top-20(按角色分组)、段落长度分布、标点密度。结果存 `index.db` 新表 `style_fingerprints(range_start, range_end, metrics_json)`。
-- 基线 = 第 1-30 章(或作者指定的"手感最好的区间");每 50 章自动对比最新窗口 vs 基线,KL 散度/简单百分比偏移超阈值 → doctor 与 dashboard 报警,并输出具体漂移项("主角对白平均句长 +40%")。
-- 主角/核心配角的口癖词表进 context pack 的 `voice_contract` section(写前注入),漂移报警时由作者决定校准方向(回拉 or 接受演化并更新基线)。
-
-**验收**:人工构造前后文风差异明显的两批章节,fingerprint diff 报告捕捉到句长与口癖偏移。
-
-### M2-2 评审人格拆分
-
-**动机**:同一模型写+评共享盲区;单一 reviewer 的注意力平均分配等于都不深。
-
-**设计**:
-- reviewer agent 改为按"镜头"多次调用(不增加 agent 数量,改 prompt 参数化):`--lens reader`(毒舌白嫖读者:只回答爽不爽、第几段想划走、会不会弃)、`--lens editor`(结构与商业性)、`--lens fact`(只比对事实,输入为机检结果+contract)。
-- 镜头裁决规则:fact 镜头由脚本结果主导(模型只解释);reader 镜头产出不进 blocking,只进 reading_power/弃书风险标注;editor 镜头维持现有 blocking 语义。
-- review SKILL 流程改为:机检 → 三镜头并行 → 汇总(明确"文笔好坏"不在任何镜头职责内,防止模型口味自我强化)。
-
-**验收**:同一测试章三镜头输出可区分(reader 镜头不含结构术语,editor 镜头不含"这段不够爽"类表述)。
-
-### M2-3 Token 成本预算线
-
-**动机**:multi-agent 是拿 token 换确定性,写百万字的成本会劝退真实作者;context 减负重构(refactor/context-minimal-flow 分支)已在做单次调用瘦身,缺总量视角。
-
-**设计**:
-- `run_ledger` 已记录每次 SubagentRun,扩展记录 prompt/output 字符数(近似 token);`quality_trend_report` 增加"每章成本"曲线。
-- 设定每章字符预算(config 项,默认宽松),超预算章在报告中标黄并列出最大头的调用——给作者看见"钱花在哪",不做硬限制。
-- 对照实验钩子:同一章纲跑 `--profile minimal/full` 两种 context 模板(TEMPLATE_WEIGHTS 已支持多模板),review 分数与成本一起入库,用数据决定默认模板瘦到什么程度。
-
-**验收**:quality_trend_report 输出含每章成本列与超预算标记。
-
----
-
-## M3 实验(高风险高回报,单独立项验证)
-
-### M3-1 长线伏笔机会扫描
-
-**动机**:AI 不会自己起意"这里埋个 300 章后才响的雷";目前伏笔全部依赖大纲显式声明。这是开放问题,谁解了谁有护城河。
-
-**设计草图**:plan skill 的卷规划步骤新增"机会扫描":输入 = 未回收伏笔清单 + 总纲远期节点 + 本卷章纲;让模型提议 3-5 个"本卷可顺手埋、N 卷后回收"的候选(必须引用总纲的具体远期节点作为回收点,防止悬空),作者勾选后写入 plot_threads.foreshadowing(带 target_chapter)。**人选 AI 提**,不全自动。
-
-**验证标准**:连续两卷使用后,作者采纳率 ≥1/卷 才值得保留;否则砍掉。
-
-### M3-2 读者模拟器(弃书点预测)
-
-**设计草图**:reader 镜头(M2-2)的延伸——按"白嫖党/老书虫/女频读者"等画像参数化,对每章输出 0-100 弃书风险与触发段落。先只做趋势可视化(dashboard 已有 reading-power 页),积累 100+ 章人工对照后再决定是否进入门禁。明确定位:参考信号,永不 blocking。
-
-### M3-3 review 评分与真实数据对齐(远期)
-
-若作者发书,把章节追读/留存数据(手动导入 CSV 即可)与 review 分数、pacing_metrics 做相关性分析(`quality_trend_report` 扩展)。哪个内部指标与真实留存相关性最高,哪个就升权——让质量闭环最终锚定在读者行为而不是模型口味上。
-
----
-
-## 不做清单(明确否决,避免回潮)
-
-- ❌ 全文向量化作为事实召回主路径(语义相似 ≠ 叙事相关;向量只服务文风/场景参考)。
-- ❌ 让模型自由评"文笔好坏"(口味自我强化,全书越来越 AI 味)。
-- ❌ 全自动无人值守写作模式(作者品味流失 = 系统失去校准源)。
-- ❌ 增加新的常驻 agent(现有 4 个职责已清晰;新能力优先做成脚本或现有 agent 的参数化镜头)。
-
-## 里程碑顺序与依赖
-
-```
-修复计划 Phase 0/1 ──> M1-1 知识边界 ──> M2-2 评审人格(fact镜头吃M1-1产出)
-                  ├──> M1-2 节奏度量 ──> M2-3 成本预算线(共用run_ledger/报告)
-                  ├──> M1-3 Override泛化
-                  └──> M1-4 卷级摘要 ──> M2-1 文体指纹(共用采样基建)
-M3 全部独立立项,任一 M2 完成后可启动
-```

+ 0 - 72
docs/archive/architecture/story-system-phase4.md

@@ -1,72 +0,0 @@
-# Story System Phase 4
-
-## 目标
-
-Phase 4 把 Phase 3 的 `accepted_events` 升级为正式事件主链,并把
-`override_contracts` 扩成统一 override ledger。
-
-本阶段新增两条稳定链路:
-
-1. `CHAPTER_COMMIT.accepted_events -> .story-system/events/*.events.json`
-2. `world_rule_broken -> amend_proposal -> override_contracts`
-
-## 产物
-
-运行后会出现这些核心文件或表:
-
-- `.story-system/events/chapter_XXX.events.json`
-- `.webnovel/index.db.story_events`
-- `.webnovel/index.db.override_contracts.record_type=*`
-
-`story_events` 是 canonical 审计镜像,`override_contracts` 继续保留旧
-Override Contract / 债务链,同时新增:
-
-- `soft_deviation`
-- `contract_override`
-- `amend_proposal`
-
-## 执行关系
-
-```text
-review / fulfillment / disambiguation / extraction
-                │
-                ▼
-         CHAPTER_COMMIT.accepted
-                │
-                ├── apply_projections()
-                │     ├── state/index/summary/memory
-                │     └── EventProjectionRouter 决定激活哪些 writer
-                │
-                ├── EventLogStore.write_events()
-                │     ├── JSON 文件
-                │     └── SQLite story_events 镜像
-                │
-                └── AmendProposalTrigger.check()
-                      └── persist_amend_proposals() -> override_contracts
-```
-
-## CLI
-
-统一入口仍然是 `webnovel.py`:
-
-```bash
-python -X utf8 "<CLAUDE_PLUGIN_ROOT>/scripts/webnovel.py" --project-root "<PROJECT_ROOT>" story-system "玄幻退婚流" --persist
-python -X utf8 "<CLAUDE_PLUGIN_ROOT>/scripts/webnovel.py" --project-root "<PROJECT_ROOT>" story-system "玄幻退婚流" --emit-runtime-contracts --chapter 12
-python -X utf8 "<CLAUDE_PLUGIN_ROOT>/scripts/webnovel.py" --project-root "<PROJECT_ROOT>" chapter-commit --chapter 12 --review-result .webnovel/tmp/review.json --fulfillment-result .webnovel/tmp/fulfillment.json --disambiguation-result .webnovel/tmp/disambiguation.json --extraction-result .webnovel/tmp/extraction.json
-python -X utf8 "<CLAUDE_PLUGIN_ROOT>/scripts/webnovel.py" --project-root "<PROJECT_ROOT>" story-events --chapter 12
-python -X utf8 "<CLAUDE_PLUGIN_ROOT>/scripts/webnovel.py" --project-root "<PROJECT_ROOT>" story-events --health
-```
-
-## 最小运维检查
-
-- `story-system --persist` 后应存在 `MASTER_SETTING.json`
-- `story-system --emit-runtime-contracts --chapter N` 后应存在 `volume_XXX.json`
-  与 `chapter_XXX.review.json`
-- `chapter-commit` accepted 后应存在 `chapter_XXX.commit.json`
-- `story-events --health` 应返回 `sqlite_rows` 与 `event_files`
-
-## 当前边界
-
-- 事件路由仍由 `ChapterCommitService.apply_projections()` 统一调度
-- Phase 4 不新增第二套独立投影循环
-- 旧链路降级与完全切换留到 Phase 5

+ 0 - 46
docs/archive/architecture/story-system-phase5.md

@@ -1,46 +0,0 @@
-# Story System Phase 5
-
-## 核心结论
-
-- 写前真源:`.story-system/MASTER_SETTING.json`、`volumes/*.json`、`chapters/*.json`、`reviews/*.review.json`
-- 写后真源:accepted `CHAPTER_COMMIT`
-- `.webnovel/state.json`、`index.db`、`summaries/`、`memory_scratchpad.json`:投影 / read-model
-- `references/genre-profiles.md`:fallback-only
-
-## 默认链路
-
-```text
-story-system --persist/--emit-runtime-contracts
-    -> 生成 MASTER / VOLUME / CHAPTER / REVIEW 合同
-context / query / write / review
-    -> 默认读取合同主链
-chapter-commit --chapter N
-    -> accepted CHAPTER_COMMIT
-    -> state / index / summary / memory projection writers
-preflight + dashboard
-    -> 暴露 story runtime health / fallback 状态 / latest commit 状态
-```
-
-## 运行时优先级
-
-1. Story Contracts
-2. latest accepted `CHAPTER_COMMIT`
-3. `.webnovel/*` read-model
-4. `genre-profiles.md` 等 legacy fallback
-
-## Phase 5 落地结果
-
-- `ContextManager`、`memory_contract_adapter`、`extract_chapter_context` 已默认走 contract-first + commit-first
-- `webnovel-write` / `webnovel-query` / `webnovel-review` / `webnovel-plan` 与 `context-agent` / `data-agent` 已切到新主链叙述
-- `preflight` 与 dashboard 已直接暴露 `story_runtime` / `story-runtime/health`
-- 旧 state-first 心智模型降级为兼容层,不再伪装为主链
-
-## 运维含义
-
-- 看到 `.webnovel/state.json` 与 `.story-system/commits/` 不一致时,优先检查 commit 链与 projection 状态
-- `fallback_sources` 非空表示主链不完整,系统仍可兼容运行,但不能视为 fully-mainline-ready
-- 排查写后问题时,优先检查:
-  1. `.story-system/commits/chapter_XXX.commit.json`
-  2. `projection_status`
-  3. `story-events --health`
-  4. `.webnovel/*` 投影结果

+ 0 - 264
docs/archive/architecture/system-architecture-diagram.html

@@ -1,264 +0,0 @@
-<!DOCTYPE html>
-<html lang="zh-CN">
-<head>
-<meta charset="UTF-8">
-<meta name="viewport" content="width=device-width, initial-scale=1.0">
-<title>Webnovel Writer 系统架构</title>
-<style>
-body { margin: 20px; background: #f0eef6; font-family: -apple-system, "Microsoft YaHei", sans-serif; }
-.arch-wrapper { display: flex; gap: 12px; }
-.arch-sidebar { width: 175px; flex-shrink: 0; }
-.arch-main { flex: 1; min-width: 0; }
-.arch-title { text-align: center; font-size: 22px; font-weight: bold; color: #312e81; margin-bottom: 4px; }
-.arch-subtitle { text-align: center; font-size: 12px; color: #6366f1; margin-bottom: 14px; }
-.arch-layer { margin: 7px 0; padding: 12px; border-radius: 6px; box-shadow: 0 2px 8px rgba(67,56,202,0.06); }
-.arch-layer-title { font-size: 13px; font-weight: bold; margin-bottom: 8px; text-align: center; }
-.arch-grid { display: grid; gap: 6px; }
-.arch-grid-2 { grid-template-columns: repeat(2,1fr); }
-.arch-grid-3 { grid-template-columns: repeat(3,1fr); }
-.arch-grid-4 { grid-template-columns: repeat(4,1fr); }
-.arch-grid-5 { grid-template-columns: repeat(5,1fr); }
-.arch-box { border-radius: 5px; padding: 7px; text-align: center; font-size: 10.5px; font-weight: 600; line-height: 1.3; color: #312e81; background: rgba(255,255,255,0.85); border: 1px solid #c7d2fe; }
-.arch-box.highlight { background: linear-gradient(135deg,#e0e7ff,#c7d2fe); border: 2px solid #4f46e5; }
-.arch-box.tech { font-size: 10px; color: #4338ca; background: rgba(238,242,255,0.8); }
-.arch-box.write { border-left: 3px solid #dc2626; }
-.arch-box.read { border-left: 3px solid #16a34a; }
-.arch-box.rw { border-left: 3px solid #d97706; }
-.arch-layer.user { background: linear-gradient(135deg,#dbeafe,#bfdbfe); border: 2px solid #3b82f6; }
-.arch-layer.user .arch-layer-title { color: #1e40af; }
-.arch-layer.application { background: linear-gradient(135deg,#e0e7ff,#c7d2fe); border: 2px solid #4f46e5; }
-.arch-layer.application .arch-layer-title { color: #3730a3; }
-.arch-layer.ai { background: linear-gradient(135deg,#ede9fe,#ddd6fe); border: 2px solid #7c3aed; }
-.arch-layer.ai .arch-layer-title { color: #5b21b6; }
-.arch-layer.data { background: linear-gradient(135deg,#f3e8ff,#e9d5ff); border: 2px solid #9333ea; }
-.arch-layer.data .arch-layer-title { color: #7e22ce; }
-.arch-layer.infra { background: linear-gradient(135deg,#fae8ff,#f5d0fe); border: 2px solid #a855f7; }
-.arch-layer.infra .arch-layer-title { color: #86198f; }
-.arch-layer.external { background: linear-gradient(135deg,#f1f5f9,#e2e8f0); border: 2px dashed #94a3b8; }
-.arch-layer.external .arch-layer-title { color: #64748b; }
-.arch-sidebar-panel { border-radius: 6px; padding: 10px; background: linear-gradient(135deg,#eef2ff,#e0e7ff); border: 2px solid #a5b4fc; margin-bottom: 7px; box-shadow: 0 1px 3px rgba(67,56,202,0.04); }
-.arch-sidebar-title { font-size: 11px; font-weight: bold; text-align: center; color: #312e81; margin-bottom: 5px; }
-.arch-sidebar-item { font-size: 9.5px; text-align: center; color: #3730a3; background: rgba(255,255,255,0.8); padding: 4px; border-radius: 4px; margin: 3px 0; border: 1px solid #c7d2fe; }
-.arch-sidebar-item.metric { background: #e0e7ff; border: 1px solid #6366f1; color: #3730a3; font-weight: 600; }
-.arch-subgroup { display: flex; gap: 6px; margin-top: 6px; }
-.arch-subgroup-box { flex: 1; border-radius: 6px; padding: 7px; background: rgba(255,255,255,0.5); border: 1px solid rgba(0,0,0,0.08); }
-.arch-subgroup-title { font-size: 10px; font-weight: bold; color: #374151; text-align: center; margin-bottom: 5px; }
-.legend { display: flex; gap: 14px; justify-content: center; margin: 8px 0; font-size: 10px; color: #4b5563; }
-.legend-item { display: flex; align-items: center; gap: 4px; }
-.legend-bar { width: 14px; height: 10px; border-radius: 2px; }
-</style>
-</head>
-<body>
-<div style="width: 1280px; margin: 0 auto; background: #f5f3ff; padding: 20px; border-radius: 8px; border: 1px solid #c7c2ea;">
-  <div class="arch-title">📖 Webnovel Writer 系统架构</div>
-  <div class="arch-subtitle">init → plan → write 六层主链 | 全读写映射</div>
-  <div class="legend">
-    <div class="legend-item"><div class="legend-bar" style="background:#dc2626;"></div> 写入(Write)</div>
-    <div class="legend-item"><div class="legend-bar" style="background:#16a34a;"></div> 读取(Read)</div>
-    <div class="legend-item"><div class="legend-bar" style="background:#d97706;"></div> 读写(R/W)</div>
-    <div class="legend-item"><div class="legend-bar" style="background:#4f46e5; border: 2px solid #312e81;"></div> 核心组件</div>
-  </div>
-  <div class="arch-wrapper">
-    <div class="arch-sidebar">
-      <div class="arch-sidebar-panel">
-        <div class="arch-sidebar-title">📂 项目文件(Project Files)</div>
-        <div class="arch-sidebar-item">大纲/总纲.md</div>
-        <div class="arch-sidebar-item">大纲/第X卷-详细大纲.md</div>
-        <div class="arch-sidebar-item">大纲/第X卷-时间线.md</div>
-        <div class="arch-sidebar-item">大纲/第X卷-节拍表.md</div>
-        <div class="arch-sidebar-item">设定集/*.md</div>
-        <div class="arch-sidebar-item">正文/第NNNN章.md</div>
-        <div class="arch-sidebar-item metric">.webnovel/idea_bank.json</div>
-      </div>
-      <div class="arch-sidebar-panel">
-        <div class="arch-sidebar-title">📋 合同树(.story-system/)</div>
-        <div class="arch-sidebar-item metric">MASTER_SETTING.json</div>
-        <div class="arch-sidebar-item">volumes/volume_NNN.json</div>
-        <div class="arch-sidebar-item">chapters/chapter_NNN.json</div>
-        <div class="arch-sidebar-item">reviews/chapter_NNN.review.json</div>
-        <div class="arch-sidebar-item">anti_patterns.json</div>
-        <div class="arch-sidebar-item metric">commits/chapter_NNN.commit.json</div>
-      </div>
-      <div class="arch-sidebar-panel">
-        <div class="arch-sidebar-title">💾 投影存储(Projection Stores)</div>
-        <div class="arch-sidebar-item">state.json(状态)</div>
-        <div class="arch-sidebar-item">index.db(实体/关系)</div>
-        <div class="arch-sidebar-item">summaries/chNNNN.md(摘要)</div>
-        <div class="arch-sidebar-item">memory_scratchpad.json(记忆)</div>
-        <div class="arch-sidebar-item">vector_db(向量索引)</div>
-      </div>
-    </div>
-    <div class="arch-main">
-      <div class="arch-layer user">
-        <div class="arch-layer-title">🎯 用户入口(User Entry): /webnovel-init → /webnovel-plan → /webnovel-write</div>
-        <div class="arch-subgroup">
-          <div class="arch-subgroup-box">
-            <div class="arch-subgroup-title">/webnovel-init(初始化)</div>
-            <div class="arch-grid arch-grid-3">
-              <div class="arch-box read">Read<br><small>genre-tropes.md<br>genre-profiles.md</small></div>
-              <div class="arch-box highlight">交互收集 7 步<br><small>题材/角色/金手指<br>世界观/创意约束</small></div>
-              <div class="arch-box write">Write<br><small>state.json<br>设定集/*.md<br>总纲.md<br>idea_bank.json</small></div>
-            </div>
-          </div>
-          <div class="arch-subgroup-box">
-            <div class="arch-subgroup-title">/webnovel-plan(规划)</div>
-            <div class="arch-grid arch-grid-3">
-              <div class="arch-box read">Read<br><small>state.json(genre)<br>总纲.md<br>设定集/*.md<br>summaries/(跨卷)</small></div>
-              <div class="arch-box highlight">卷纲 → 时间线<br>→ 拆章纲<br><small>节拍表/CBN/CPNs/CEN</small></div>
-              <div class="arch-box write">Write<br><small>节拍表.md<br>时间线.md<br>详细大纲.md<br>设定集(增量)</small></div>
-            </div>
-          </div>
-          <div class="arch-subgroup-box">
-            <div class="arch-subgroup-title">/webnovel-write(写章)</div>
-            <div class="arch-grid arch-grid-2">
-              <div class="arch-box highlight">Step 1→6<br><small>上下文→起草→审查<br>→润色→提交→备份</small></div>
-              <div class="arch-box write">Write<br><small>正文/第NNNN章.md<br>commit.json<br>→ 5路投影</small></div>
-            </div>
-          </div>
-        </div>
-      </div>
-      <div class="arch-layer external">
-        <div class="arch-layer-title">📚 Layer 1: 知识层(Knowledge)— CSV 表 + 参考文档</div>
-        <div class="arch-grid arch-grid-5">
-          <div class="arch-box highlight">题材与调性推理.csv<br><small>路由表(Route)<br>8 行流派</small></div>
-          <div class="arch-box highlight">裁决规则.csv<br><small>裁决表(Reasoning)<br>7 题材</small></div>
-          <div class="arch-box tech">命名规则.csv<br><small>基础表(Base)</small></div>
-          <div class="arch-box tech">人设与关系.csv<br><small>基础表(Base)</small></div>
-          <div class="arch-box tech">写作技法.csv<br><small>基础表(Base)</small></div>
-        </div>
-        <div class="arch-grid arch-grid-5" style="margin-top: 6px;">
-          <div class="arch-box tech">场景写法.csv<br><small>基础表(Base)</small></div>
-          <div class="arch-box tech">金手指与设定.csv<br><small>基础表(Base)</small></div>
-          <div class="arch-box tech">桥段套路.csv<br><small>动态表(Dynamic)</small></div>
-          <div class="arch-box tech">爽点与节奏.csv<br><small>动态表(Dynamic)</small></div>
-          <div class="arch-box">CSV_CONFIG<br><small>per-table 注册<br>search_cols / output_cols<br>poison_col / role</small></div>
-        </div>
-      </div>
-      <div class="arch-layer ai">
-        <div class="arch-layer-title">🧠 Layer 2: 裁决层(Reasoning)— story_system_engine.py</div>
-        <div class="arch-grid arch-grid-4">
-          <div class="arch-box read">_route()<br><small>Read: 题材与调性推理.csv<br>匹配题材 → 推荐表</small></div>
-          <div class="arch-box read">_collect_tables()<br><small>Read: 基础表 + 动态表<br>BM25 检索</small></div>
-          <div class="arch-box highlight">_load_reasoning()<br><small>Read: 裁决规则.csv<br>别名匹配(修仙→东方仙侠)<br>fallback 到原始 genre</small></div>
-          <div class="arch-box highlight">_apply_reasoning()<br><small>冲突裁决排序<br>毒点加权<br>反模式注入</small></div>
-        </div>
-      </div>
-      <div class="arch-layer application">
-        <div class="arch-layer-title">📜 Layer 3: 合同层(Contract)— .story-system/</div>
-        <div class="arch-subgroup">
-          <div class="arch-subgroup-box">
-            <div class="arch-subgroup-title">写前合同(story-system --persist)</div>
-            <div class="arch-grid arch-grid-4">
-              <div class="arch-box write">MASTER_SETTING<br><small>Write: 题材/调性/禁忌</small></div>
-              <div class="arch-box write">VOLUME_BRIEF<br><small>Write: 卷目标/节奏</small></div>
-              <div class="arch-box write">CHAPTER_BRIEF<br><small>Write: reasoning 裁决<br>chapter_focus(参考)</small></div>
-              <div class="arch-box write">REVIEW_CONTRACT<br><small>Write: 必须节点/禁区</small></div>
-            </div>
-          </div>
-          <div class="arch-subgroup-box">
-            <div class="arch-subgroup-title">写后真源(chapter-commit)</div>
-            <div class="arch-grid arch-grid-2">
-              <div class="arch-box highlight write">CHAPTER_COMMIT<br><small>Write: accepted / rejected<br>events + deltas + summary</small></div>
-              <div class="arch-box">anti_patterns.json<br><small>Write: 题材毒点集合</small></div>
-            </div>
-          </div>
-        </div>
-      </div>
-      <div class="arch-layer data">
-        <div class="arch-layer-title">🔍 Layer 4: 上下文层(Context)— 写前组装</div>
-        <div class="arch-subgroup">
-          <div class="arch-subgroup-box">
-            <div class="arch-subgroup-title">context-agent(Step 1 子代理)</div>
-            <div class="arch-grid arch-grid-4">
-              <div class="arch-box read">load-context<br><small>Read: contracts<br>summaries / protagonist<br>rules / loops / memory_pack<br>genre_profile_excerpt</small></div>
-              <div class="arch-box read">Read 章纲原文<br><small>大纲/第X卷-详细大纲.md<br>(最高权重)</small></div>
-              <div class="arch-box read">按需深查<br><small>query-entity<br>query-rules<br>get-timeline<br>get-reader-signals</small></div>
-              <div class="arch-box highlight write">输出: 写作任务书<br><small>五段格式<br>消费 reasoning 裁决<br>内化 Anti-AI 铁律</small></div>
-            </div>
-          </div>
-          <div class="arch-subgroup-box">
-            <div class="arch-subgroup-title">辅助查询</div>
-            <div class="arch-grid arch-grid-3">
-              <div class="arch-box tech read">context_manager<br><small>纯 JSON 组装器<br>Read: contracts / state<br>runtime / guidance</small></div>
-              <div class="arch-box tech read">knowledge_query<br><small>时序查询<br>entity_state_at_chapter<br>relationships_at_chapter</small></div>
-              <div class="arch-box tech read">reference_search<br><small>CSV BM25 检索<br>per-table search_cols</small></div>
-            </div>
-          </div>
-        </div>
-      </div>
-      <div class="arch-layer ai" style="border-color: #9333ea;">
-        <div class="arch-layer-title" style="color: #7e22ce;">⚙️ Layer 5: 提交层(Commit)— 写后事实入口</div>
-        <div class="arch-subgroup">
-          <div class="arch-subgroup-box">
-            <div class="arch-subgroup-title">data-agent(Step 5.1 子代理)</div>
-            <div class="arch-grid arch-grid-4">
-              <div class="arch-box read">Read 正文<br><small>正文/第NNNN章.md</small></div>
-              <div class="arch-box read">查询实体<br><small>get-core-entities<br>recent-appearances</small></div>
-              <div class="arch-box highlight write">提取事实<br><small>Write: extraction_result<br>fulfillment_result<br>disambiguation_result</small></div>
-              <div class="arch-box write">摘要生成<br><small>summary_text<br>100-150字 + 钩子</small></div>
-            </div>
-          </div>
-          <div class="arch-subgroup-box">
-            <div class="arch-subgroup-title">chapter-commit CLI(Step 5.2)</div>
-            <div class="arch-grid arch-grid-3">
-              <div class="arch-box read">Read 4份 artifacts<br><small>tmp/*.json</small></div>
-              <div class="arch-box highlight">判定 accepted / rejected<br><small>blocking_count = 0<br>missed_nodes = 空<br>pending = 空</small></div>
-              <div class="arch-box write">Write commit.json<br><small>→ 触发投影链</small></div>
-            </div>
-          </div>
-        </div>
-      </div>
-      <div class="arch-layer infra">
-        <div class="arch-layer-title">💿 Layer 6: 投影层(Projection)— EventProjectionRouter → 5 Writers</div>
-        <div class="arch-grid arch-grid-5">
-          <div class="arch-box write">state_projection<br><small>Write: state.json<br>entity_state<br>chapter_status<br>(committed / rejected)</small></div>
-          <div class="arch-box write">index_projection<br><small>Write: index.db<br>entity_deltas<br>relationships</small></div>
-          <div class="arch-box write">summary_projection<br><small>Write: summaries/<br>chNNNN.md<br>剧情摘要</small></div>
-          <div class="arch-box write">memory_projection<br><small>Write: memory_<br>scratchpad.json<br>长期记忆事实</small></div>
-          <div class="arch-box write">vector_projection<br><small>Write: vector_db<br>event → text → embed<br>delta → text → embed</small></div>
-        </div>
-      </div>
-    </div>
-    <div class="arch-sidebar">
-      <div class="arch-sidebar-panel">
-        <div class="arch-sidebar-title">🔗 题材流通路径</div>
-        <div class="arch-sidebar-item metric">① init 写入 state.json</div>
-        <div class="arch-sidebar-item">② story-system 读 genre</div>
-        <div class="arch-sidebar-item">③ 路由表匹配流派</div>
-        <div class="arch-sidebar-item metric">④ 裁决表别名匹配题材</div>
-        <div class="arch-sidebar-item">⑤ reasoning 写入 chapter.json</div>
-        <div class="arch-sidebar-item">⑥ context-agent 消费</div>
-        <div class="arch-sidebar-item metric">⑦ 任务书第4段输出</div>
-      </div>
-      <div class="arch-sidebar-panel">
-        <div class="arch-sidebar-title">📏 数据权重(高→低)</div>
-        <div class="arch-sidebar-item metric">① 用户要求</div>
-        <div class="arch-sidebar-item metric">② 章纲原文</div>
-        <div class="arch-sidebar-item">③ MASTER_SETTING</div>
-        <div class="arch-sidebar-item">④ reasoning 裁决</div>
-        <div class="arch-sidebar-item">⑤ CHAPTER_COMMIT</div>
-        <div class="arch-sidebar-item">⑥ CSV 检索(参考)</div>
-      </div>
-      <div class="arch-sidebar-panel">
-        <div class="arch-sidebar-title">⚡ Token 优化</div>
-        <div class="arch-sidebar-item metric">context-agent: 2.9k tokens</div>
-        <div class="arch-sidebar-item metric">write SKILL: 1.8k tokens</div>
-        <div class="arch-sidebar-item metric">data-agent: 1.2k tokens</div>
-        <div class="arch-sidebar-item">load-context 含 contracts</div>
-        <div class="arch-sidebar-item">内化 core-constraints</div>
-        <div class="arch-sidebar-item">内化 anti-ai-guide</div>
-        <div class="arch-sidebar-item">get-reader-signals 合并</div>
-        <div class="arch-sidebar-item">review-pipeline --save-metrics</div>
-      </div>
-      <div class="arch-sidebar-panel">
-        <div class="arch-sidebar-title">🛡️ 写入保护</div>
-        <div class="arch-sidebar-item metric">唯一写后入口: COMMIT</div>
-        <div class="arch-sidebar-item">state / index / summary / memory<br>只由 projection 写入</div>
-        <div class="arch-sidebar-item">skill 不直接 set-chapter-status</div>
-        <div class="arch-sidebar-item">data-agent 不直写存储</div>
-      </div>
-    </div>
-  </div>
-</div>
-</body>
-</html>

+ 0 - 430
docs/archive/architecture/system-architecture.md

@@ -1,430 +0,0 @@
-# Webnovel Writer 系统架构图
-
-> 生成日期:2026-04-15
-> 覆盖范围:init → plan → write 全链路的读写关系
-
-## 总链路
-
-```mermaid
-graph TB
-    subgraph USER["用户(User)"]
-        U_INPUT["用户输入"]
-    end
-
-    subgraph INIT["/webnovel-init(项目初始化)"]
-        INIT_COLLECT["Step 1-7: 交互收集"]
-        INIT_GEN["执行生成"]
-        INIT_STORY["story-system 初始化"]
-    end
-
-    subgraph PLAN["/webnovel-plan(大纲规划)"]
-        PLAN_LOAD["Step 1: 加载数据"]
-        PLAN_SETTING["Step 2: 补齐设定"]
-        PLAN_VOLUME["Step 3-6: 卷级规划"]
-        PLAN_CHAPTER["Step 7: 拆章纲"]
-        PLAN_WRITEBACK["Step 8: 设定写回"]
-        PLAN_STORY["story-system 刷新"]
-    end
-
-    subgraph WRITE["/webnovel-write(写章流程)"]
-        W_PREFLIGHT["准备: preflight"]
-        W_STORY["准备: story-system 刷新合同树"]
-        W_STEP1["Step 1: context-agent"]
-        W_STEP2["Step 2: 起草正文"]
-        W_STEP3["Step 3: reviewer 审查"]
-        W_STEP4["Step 4: 润色"]
-        W_STEP5["Step 5: data-agent + commit"]
-        W_STEP6["Step 6: git 备份"]
-    end
-
-    U_INPUT --> INIT_COLLECT
-    INIT_COLLECT --> INIT_GEN --> INIT_STORY
-    INIT_STORY --> PLAN_LOAD
-    PLAN_LOAD --> PLAN_SETTING --> PLAN_VOLUME --> PLAN_CHAPTER --> PLAN_WRITEBACK
-    PLAN_CHAPTER --> PLAN_STORY
-    PLAN_STORY --> W_PREFLIGHT
-    W_PREFLIGHT --> W_STORY --> W_STEP1 --> W_STEP2 --> W_STEP3 --> W_STEP4 --> W_STEP5 --> W_STEP6
-```
-
-## Init 阶段读写
-
-```mermaid
-graph LR
-    subgraph INIT["/webnovel-init"]
-        I1["交互收集(7 步)"]
-        I2["init_project.py"]
-        I3["Patch 总纲"]
-        I4["story-system CLI"]
-    end
-
-    subgraph STORE_INIT["产出文件"]
-        S_STATE["state.json"]
-        S_SETTING["设定集/*.md"]
-        S_OUTLINE["大纲/总纲.md"]
-        S_IDEA["idea_bank.json"]
-        S_MASTER["MASTER_SETTING.json"]
-        S_ANTI["anti_patterns.json"]
-    end
-
-    subgraph REF_INIT["读取参考"]
-        R_GENRE_TROPES["genre-tropes.md"]
-        R_GENRE_PROFILE["genre-profiles.md"]
-        R_WORLD["worldbuilding/*.md"]
-        R_CREATIVE["creativity/*.md"]
-        R_CSV_NAME["命名规则.csv"]
-    end
-
-    R_GENRE_TROPES -->|"Read"| I1
-    R_GENRE_PROFILE -->|"Read"| I1
-    R_WORLD -->|"Read(按需)"| I1
-    R_CREATIVE -->|"Read(按需)"| I1
-    R_CSV_NAME -->|"reference_search(命名)"| I1
-
-    I1 --> I2
-    I2 -->|"Write"| S_STATE
-    I2 -->|"Write"| S_SETTING
-    I2 -->|"Write"| S_OUTLINE
-    I1 --> I3
-    I3 -->|"Write"| S_OUTLINE
-    I1 -->|"Write"| S_IDEA
-
-    I2 --> I4
-    S_STATE -->|"Read genre"| I4
-    I4 -->|"Write"| S_MASTER
-    I4 -->|"Write"| S_ANTI
-
-    subgraph CSV_ENGINE["story-system 引擎"]
-        CSV_ROUTE["题材与调性推理.csv(路由)"]
-        CSV_REASON["裁决规则.csv(裁决)"]
-        CSV_BASE["基础表 x5(命名/人设/技法/设定/场景)"]
-        CSV_DYN["动态表 x2(桥段/爽点)"]
-    end
-
-    CSV_ROUTE -->|"Read(路由匹配)"| I4
-    CSV_REASON -->|"Read(裁决匹配)"| I4
-    CSV_BASE -->|"Read(BM25 检索)"| I4
-    CSV_DYN -->|"Read(BM25 检索)"| I4
-```
-
-## Plan 阶段读写
-
-```mermaid
-graph LR
-    subgraph PLAN["/webnovel-plan"]
-        P1["Step 1: 加载"]
-        P2["Step 2: 补设定"]
-        P3["Step 4-5: 节拍表+时间线"]
-        P4["Step 6: 卷纲"]
-        P5["Step 7: 拆章纲"]
-        P6["Step 8: 设定写回"]
-        P7["story-system"]
-    end
-
-    subgraph READ_PLAN["读取"]
-        R_STATE2["state.json"]
-        R_OUTLINE2["大纲/总纲.md"]
-        R_SETTING2["设定集/*.md"]
-        R_IDEA2["idea_bank.json"]
-        R_SUMMARY["summaries/(跨卷时)"]
-        R_KNOWLEDGE["knowledge query(跨卷时)"]
-        R_LOOPS["memory-contract get-open-loops(跨卷时)"]
-        R_MASTER2["MASTER_SETTING.json"]
-    end
-
-    subgraph WRITE_PLAN["写入"]
-        W_BEAT["大纲/第X卷-节拍表.md"]
-        W_TIMELINE["大纲/第X卷-时间线.md"]
-        W_DETAIL["大纲/第X卷-详细大纲.md"]
-        W_SETTING2["设定集/*.md(增量)"]
-        W_VOLUME["volumes/volume_NNN.json"]
-        W_CHAP_CONTRACT["chapters/chapter_NNN.json"]
-        W_REVIEW_CONTRACT["reviews/chapter_NNN.review.json"]
-    end
-
-    subgraph REF_PLAN["参考"]
-        R_GP["genre-profiles.md"]
-        R_STRAND["strand-weave-pattern.md"]
-        R_COOL["cool-points-guide.md"]
-        R_CSV_PLAN["CSV 检索(场景/命名)"]
-    end
-
-    R_STATE2 -->|"Read"| P1
-    R_OUTLINE2 -->|"Read"| P1
-    R_SETTING2 -->|"Read"| P1
-    R_IDEA2 -->|"Read(按需)"| P1
-    R_SUMMARY -->|"Read(跨卷)"| P1
-    R_KNOWLEDGE -->|"Bash(跨卷)"| P1
-    R_LOOPS -->|"Bash(跨卷)"| P1
-    R_MASTER2 -->|"Read(调性参照)"| P4
-
-    P2 -->|"Write(增量)"| W_SETTING2
-    P3 -->|"Write"| W_BEAT
-    P3 -->|"Write"| W_TIMELINE
-    P4 --> P5
-    P5 -->|"Write"| W_DETAIL
-
-    R_GP -->|"Read"| P4
-    R_STRAND -->|"Read"| P4
-    R_COOL -->|"Read(按需)"| P4
-    R_CSV_PLAN -->|"Bash(检索)"| P4
-
-    P6 -->|"Write(增量)"| W_SETTING2
-
-    P5 --> P7
-    R_STATE2 -->|"Read genre"| P7
-    P7 -->|"Write"| W_VOLUME
-    P7 -->|"Write"| W_CHAP_CONTRACT
-    P7 -->|"Write"| W_REVIEW_CONTRACT
-```
-
-## Write 阶段读写(核心链路)
-
-```mermaid
-graph TB
-    subgraph PREP["准备阶段"]
-        PRE1["preflight + where"]
-        PRE2["story-system CLI"]
-    end
-
-    subgraph STEP1["Step 1: context-agent(子代理)"]
-        CA_LOAD["load-context"]
-        CA_READ["Read 章纲原文"]
-        CA_QUERY["按需 query-entity / query-rules"]
-        CA_OUT["输出: 写作任务书"]
-    end
-
-    subgraph STEP2["Step 2: 起草正文"]
-        S2_WRITE["Write 正文"]
-        S2_CSV["reference_search(按需)"]
-    end
-
-    subgraph STEP3["Step 3: reviewer(子代理)"]
-        REV_READ["Read 正文"]
-        REV_CONTRACT["Read 审查合同"]
-        REV_OUT["Write review_results.json"]
-        REV_PIPE["review-pipeline --save-metrics"]
-    end
-
-    subgraph STEP4["Step 4: 润色"]
-        S4_REF["Read polish-guide / typesetting / style-adapter"]
-        S4_EDIT["Edit 正文"]
-    end
-
-    subgraph STEP5["Step 5: data-agent + commit"]
-        DA_READ["Read 正文"]
-        DA_ENTITY["Bash: get-core-entities / recent-appearances"]
-        DA_ARTIFACTS["Write 4份 artifacts"]
-        COMMIT["chapter-commit CLI"]
-        PROJ["projection writers x5"]
-    end
-
-    subgraph STEP6["Step 6: Git"]
-        GIT["git add + commit"]
-    end
-
-    %% 数据存储
-    subgraph STORES["数据存储"]
-        ST_STATE["state.json(状态)"]
-        ST_INDEX["index.db(实体/关系)"]
-        ST_SUMMARY["summaries/chNNNN.md(摘要)"]
-        ST_MEMORY["memory_scratchpad.json(记忆)"]
-        ST_VECTOR["vector_db(向量索引)"]
-        ST_COMMIT["commits/chapter_NNN.commit.json(写后真源)"]
-        ST_CHAPTER["正文/第NNNN章.md"]
-        ST_TMP["tmp/*.json(中间产物)"]
-    end
-
-    subgraph CONTRACTS["合同树(.story-system/)"]
-        CT_MASTER["MASTER_SETTING.json"]
-        CT_VOLUME["volumes/volume_NNN.json"]
-        CT_CHAPTER["chapters/chapter_NNN.json"]
-        CT_REVIEW["reviews/chapter_NNN.review.json"]
-        CT_ANTI["anti_patterns.json"]
-    end
-
-    subgraph OUTLINE["大纲"]
-        OL_DETAIL["大纲/第X卷-详细大纲.md"]
-    end
-
-    subgraph CSV["CSV 知识层"]
-        CSV_R["题材与调性推理.csv(路由)"]
-        CSV_J["裁决规则.csv(裁决)"]
-        CSV_ALL["基础表+动态表 x7"]
-    end
-
-    subgraph REFS["润色参考"]
-        REF_POLISH["polish-guide.md"]
-        REF_TYPE["typesetting.md"]
-        REF_STYLE["style-adapter.md"]
-    end
-
-    %% 准备阶段
-    ST_STATE -->|"Read genre"| PRE2
-    CSV_R -->|"Read(路由)"| PRE2
-    CSV_J -->|"Read(裁决)"| PRE2
-    CSV_ALL -->|"Read(BM25)"| PRE2
-    PRE2 -->|"Write"| CT_MASTER
-    PRE2 -->|"Write"| CT_VOLUME
-    PRE2 -->|"Write"| CT_CHAPTER
-    PRE2 -->|"Write"| CT_REVIEW
-    PRE2 -->|"Write"| CT_ANTI
-
-    %% Step 1
-    PRE2 --> CA_LOAD
-    CA_LOAD -->|"Bash: load-context"| ST_STATE
-    CA_LOAD -.->|"内含 contracts"| CT_MASTER
-    CA_LOAD -.->|"内含 summaries"| ST_SUMMARY
-    CA_LOAD -.->|"内含 protagonist"| ST_STATE
-    CA_LOAD -.->|"内含 loops"| ST_STATE
-    CA_READ -->|"Read"| OL_DETAIL
-    CA_QUERY -->|"Bash(按需)"| ST_INDEX
-    CA_OUT -->|"输出任务书"| STEP2
-
-    %% Step 2
-    S2_CSV -->|"Bash: reference_search"| CSV_ALL
-    S2_WRITE -->|"Write"| ST_CHAPTER
-
-    %% Step 3
-    REV_READ -->|"Read"| ST_CHAPTER
-    REV_CONTRACT -->|"Read"| CT_REVIEW
-    REV_OUT -->|"Write"| ST_TMP
-    REV_PIPE -->|"Bash + Write index.db"| ST_INDEX
-
-    %% Step 4
-    S4_REF -->|"Read"| REF_POLISH
-    S4_REF -->|"Read"| REF_TYPE
-    S4_REF -->|"Read"| REF_STYLE
-    S4_EDIT -->|"Edit"| ST_CHAPTER
-
-    %% Step 5
-    DA_READ -->|"Read"| ST_CHAPTER
-    DA_ENTITY -->|"Bash"| ST_INDEX
-    DA_ARTIFACTS -->|"Write x4"| ST_TMP
-    ST_TMP -->|"Read artifacts"| COMMIT
-    COMMIT -->|"Write"| ST_COMMIT
-
-    %% Projection
-    COMMIT --> PROJ
-    PROJ -->|"Write state_deltas"| ST_STATE
-    PROJ -->|"Write entity_deltas"| ST_INDEX
-    PROJ -->|"Write summary_text"| ST_SUMMARY
-    PROJ -->|"Write memory_facts"| ST_MEMORY
-    PROJ -->|"Write event+delta chunks"| ST_VECTOR
-
-    %% Step 6
-    PROJ --> GIT
-```
-
-## 六层主链总览
-
-```mermaid
-graph TB
-    subgraph L1["Layer 1: 知识层(Knowledge)"]
-        CSV_TABLES["9 张 CSV 表"]
-        CSV_CONFIG["CSV_CONFIG(per-table 注册)"]
-        REF_MD["reference md 文件"]
-    end
-
-    subgraph L2["Layer 2: 裁决层(Reasoning)"]
-        ROUTE["题材与调性推理.csv → _route()"]
-        REASON["裁决规则.csv → _load_reasoning()"]
-        APPLY["_apply_reasoning() + _rank_anti_patterns()"]
-    end
-
-    subgraph L3["Layer 3: 合同层(Contract)"]
-        MASTER["MASTER_SETTING.json"]
-        VOLUME["VOLUME_BRIEF"]
-        CHAPTER["CHAPTER_BRIEF + reasoning"]
-        REVIEW["REVIEW_CONTRACT"]
-        ANTI_P["anti_patterns.json"]
-    end
-
-    subgraph L4["Layer 4: 上下文层(Context)"]
-        CTX_MGR["context_manager.py(纯 JSON)"]
-        LOAD_CTX["load-context(轻量基础包)"]
-        KNOW_Q["knowledge_query.py(时序查询)"]
-    end
-
-    subgraph L5["Layer 5: 提交层(Commit)"]
-        DA["data-agent(提取事实)"]
-        COMMIT_SVC["chapter-commit(写后真源)"]
-        PROJ_ROUTER["EventProjectionRouter"]
-    end
-
-    subgraph L6["Layer 6: 投影层(Projection)"]
-        PW_STATE["state_projection_writer"]
-        PW_INDEX["index_projection_writer"]
-        PW_SUMMARY["summary_projection_writer"]
-        PW_MEMORY["memory_projection_writer"]
-        PW_VECTOR["vector_projection_writer"]
-    end
-
-    subgraph STORES["存储"]
-        S_STATE["state.json"]
-        S_INDEX["index.db"]
-        S_SUMMARY["summaries/"]
-        S_MEMORY["memory_scratchpad"]
-        S_VECTOR["vector_db"]
-    end
-
-    CSV_TABLES --> ROUTE
-    CSV_TABLES --> REASON
-    ROUTE --> APPLY
-    REASON --> APPLY
-    APPLY --> MASTER
-    APPLY --> CHAPTER
-    APPLY --> ANTI_P
-
-    MASTER --> CTX_MGR
-    VOLUME --> CTX_MGR
-    CHAPTER --> CTX_MGR
-    REVIEW --> CTX_MGR
-    CTX_MGR --> LOAD_CTX
-
-    S_STATE --> KNOW_Q
-    S_INDEX --> KNOW_Q
-
-    DA --> COMMIT_SVC
-    COMMIT_SVC --> PROJ_ROUTER
-
-    PROJ_ROUTER --> PW_STATE
-    PROJ_ROUTER --> PW_INDEX
-    PROJ_ROUTER --> PW_SUMMARY
-    PROJ_ROUTER --> PW_MEMORY
-    PROJ_ROUTER --> PW_VECTOR
-
-    PW_STATE -->|"Write"| S_STATE
-    PW_INDEX -->|"Write"| S_INDEX
-    PW_SUMMARY -->|"Write"| S_SUMMARY
-    PW_MEMORY -->|"Write"| S_MEMORY
-    PW_VECTOR -->|"Write"| S_VECTOR
-```
-
-## 题材流通路径
-
-```mermaid
-graph LR
-    INIT_USER["用户选择题材(如'修仙')"]
-    STATE_GENRE["state.json<br/>project.genre='修仙'<br/>(init 配置快照 / read-model)"]
-    
-    STORY_CLI["story-system CLI<br/>--genre '修仙'"]
-    
-    ROUTE_TABLE["题材与调性推理.csv<br/>_route(): fallback"]
-    REASON_TABLE["裁决规则.csv<br/>_load_reasoning('修仙')<br/>别名匹配→东方仙侠"]
-    
-    MASTER_OUT["MASTER_SETTING.json<br/>route.primary_genre"]
-    CHAPTER_OUT["chapter_NNN.json<br/>reasoning.genre='东方仙侠'<br/>reasoning.style_priority<br/>reasoning.pacing_strategy"]
-    
-    CTX_AGENT["context-agent<br/>从 load-context 读取<br/>reasoning 字段"]
-    
-    BRIEF["写作任务书 第4段<br/>'保持冷硬算计感...'"]
-
-    INIT_USER -->|"init 写入"| STATE_GENRE
-    STATE_GENRE -->|"Read genre"| STORY_CLI
-    STORY_CLI -->|"query"| ROUTE_TABLE
-    STORY_CLI -->|"genre fallback"| REASON_TABLE
-    STORY_CLI -->|"persist"| MASTER_OUT
-    STORY_CLI -->|"persist"| CHAPTER_OUT
-    CHAPTER_OUT -->|"load-context"| CTX_AGENT
-    CTX_AGENT -->|"翻译为自然语言"| BRIEF
-```

+ 0 - 28
docs/archive/superpowers/README.md

@@ -1,28 +0,0 @@
-# Superpowers 文档导航
-
-本目录存放通过 `superpowers` 工作流沉淀的架构 spec、设计文档与实施计划。
-
-## 目录说明
-
-- [`specs/`](./specs/):架构设计、重构方案、评审后收敛的规范文档
-- [`plans/`](./plans/):按 spec 拆解后的实施计划与阶段执行文档
-
-## 当前重点文档
-
-- [`specs/2026-04-09-skills-restructure-and-reference-gaps.md`](./specs/2026-04-09-skills-restructure-and-reference-gaps.md):技能重构与参考资料缺口规范
-- [`specs/2026-04-12-story-system-pro-max-retrofit-spec.md`](./specs/2026-04-12-story-system-pro-max-retrofit-spec.md):现有链路上的 Pro Max 架构改造规范
-- [`specs/2026-04-12-webnovel-story-intelligence-system-spec.md`](./specs/2026-04-12-webnovel-story-intelligence-system-spec.md):最终状态的理想态架构蓝图
-- [`specs/2026-04-12-story-system-evolution-spec.md`](./specs/2026-04-12-story-system-evolution-spec.md):基于当前系统诊断的现状导向演进规范
-- [`plans/2026-04-12-story-system-phase1-contract-seed.md`](./plans/2026-04-12-story-system-phase1-contract-seed.md):Story System Phase 1 合同种子层实施计划
-- [`plans/2026-04-12-story-system-phase2-contract-first-runtime.md`](./plans/2026-04-12-story-system-phase2-contract-first-runtime.md):Story System Phase 2 合同优先运行时实施计划
-- [`plans/2026-04-12-story-system-phase3-chapter-commit-chain.md`](./plans/2026-04-12-story-system-phase3-chapter-commit-chain.md):Story System Phase 3 章节提交主链实施计划
-- [`plans/2026-04-12-story-system-phase4-event-log-and-override-ledger.md`](./plans/2026-04-12-story-system-phase4-event-log-and-override-ledger.md):Story System Phase 4 统一事件主链与 Override Ledger 实施计划
-- [`plans/2026-04-13-story-system-phase5-legacy-downgrade.md`](./plans/2026-04-13-story-system-phase5-legacy-downgrade.md):Story System Phase 5 旧链路降级与主链收口实施计划
-- [`../architecture/story-system-phase4.md`](../architecture/story-system-phase4.md):Phase 4 落地后的事件主链与 override ledger 运行说明
-- [`../architecture/story-system-phase5.md`](../architecture/story-system-phase5.md):Phase 5 落地后的主链/投影/fallback 运行说明
-
-## 使用约定
-
-- 新的 spec 统一放到 `specs/`
-- 对应实施计划统一放到 `plans/`
-- 后续写 implementation plan 时,默认需要同步列出相关文档更新项

+ 0 - 875
docs/archive/superpowers/plans/2026-04-03-phase1-cleanup.md

@@ -1,875 +0,0 @@
-# Phase 1: 清理废弃模块 + 审查合并 + 流程精简
-
-> **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:** 砍掉不产生价值的模块(workflow、resume),将 6 个 checker 合并为 1 个审查 agent,Step 2B 合并到 Step 4,预计单章 Token 降低 60-70%。
-
-**Architecture:** 纯减法重构。删除 workflow_manager.py 及其测试、resume skill 及其引用。将 6 个独立 checker agent 合并为 1 个 `reviewer.md`,输出新的结构化问题清单 schema。更新 webnovel-write SKILL.md 流程从 8 步变 7 步。更新 review_pipeline.py 适配新 schema。更新 webnovel.py CLI 移除 workflow 命令。
-
-**Tech Stack:** Python 3.13, pytest, Claude Code plugin (markdown agents/skills)
-
-**Spec:** `docs/superpowers/specs/2026-04-02-harness-v6-design.md`
-
----
-
-## File Structure
-
-### 要删除的文件
-
-| 文件 | 原因 |
-|------|------|
-| `scripts/workflow_manager.py` | Claude Code 原生 /resume 替代 |
-| `scripts/data_modules/tests/test_workflow_manager.py` | 对应模块删除 |
-| `skills/webnovel-resume/SKILL.md` | 同上 |
-| `skills/webnovel-resume/references/workflow-resume.md` | 同上 |
-| `agents/consistency-checker.md` | 合并到 reviewer.md |
-| `agents/continuity-checker.md` | 合并到 reviewer.md |
-| `agents/ooc-checker.md` | 合并到 reviewer.md |
-| `agents/high-point-checker.md` | 合并到 reviewer.md |
-| `agents/pacing-checker.md` | 合并到 reviewer.md |
-| `agents/reader-pull-checker.md` | 合并到 reviewer.md |
-| `references/checker-output-schema.md` | 被新 schema 替代 |
-| `skills/webnovel-write/references/step-3-review-gate.md` | 逻辑内联到 SKILL.md |
-| `skills/webnovel-write/references/step-5-debt-switch.md` | 0.6KB,内联到 SKILL.md |
-| `skills/webnovel-write/references/workflow-details.md` | 已标记 deprecated |
-| `skills/webnovel-write/references/step-1.5-contract.md` | context-agent 将重构 |
-
-### 要创建的文件
-
-| 文件 | 职责 |
-|------|------|
-| `agents/reviewer.md` | 统一审查 agent,输出结构化问题清单 |
-| `references/review-schema.md` | 新审查输出 schema 定义 |
-| `scripts/data_modules/review_schema.py` | 新 schema 的 Python 数据类 + 校验 |
-| `scripts/data_modules/tests/test_review_schema.py` | 新 schema 测试 |
-
-### 要修改的文件
-
-| 文件 | 改什么 |
-|------|--------|
-| `skills/webnovel-write/SKILL.md` | 7 步新流程,去 workflow 记录,去 Step 2B,合并审查 |
-| `scripts/review_pipeline.py` | 适配新 schema(无 overall_score,有 blocking_count) |
-| `scripts/data_modules/webnovel.py` | 移除 workflow 命令路由 |
-| `scripts/data_modules/index_manager.py` | review_metrics 表结构适配(去 overall_score,加 issues_count/blocking_count) |
-| `scripts/data_modules/tests/test_webnovel_unified_cli.py` | 移除 workflow 相关测试 |
-| `scripts/data_modules/tests/test_coverage_boost.py` | 移除 workflow 相关引用 |
-
----
-
-## Task 1: 定义新审查 schema
-
-**Files:**
-- Create: `scripts/data_modules/review_schema.py`
-- Create: `scripts/data_modules/tests/test_review_schema.py`
-- Create: `references/review-schema.md`
-
-- [ ] **Step 1: 写 schema 测试**
-
-```python
-# scripts/data_modules/tests/test_review_schema.py
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""审查 schema 测试"""
-import pytest
-from data_modules.review_schema import ReviewIssue, ReviewResult, parse_review_output
-
-
-def test_review_issue_blocking_defaults():
-    """critical severity 默认 blocking=True"""
-    issue = ReviewIssue(
-        severity="critical",
-        category="continuity",
-        location="第3段",
-        description="主角使用了已失去的能力",
-    )
-    assert issue.blocking is True
-
-
-def test_review_issue_non_critical_not_blocking():
-    """非 critical 默认 blocking=False"""
-    issue = ReviewIssue(
-        severity="high",
-        category="setting",
-        location="第7段",
-        description="时间线矛盾",
-    )
-    assert issue.blocking is False
-
-
-def test_review_result_counts():
-    """blocking_count 自动计算"""
-    result = ReviewResult(
-        chapter=10,
-        issues=[
-            ReviewIssue(severity="critical", category="continuity", location="p1", description="d1"),
-            ReviewIssue(severity="high", category="setting", location="p2", description="d2"),
-            ReviewIssue(severity="high", category="timeline", location="p3", description="d3", blocking=True),
-        ],
-        summary="测试",
-    )
-    assert result.blocking_count == 2
-    assert result.issues_count == 3
-    assert result.has_blocking is True
-
-
-def test_review_result_no_issues():
-    result = ReviewResult(chapter=10, issues=[], summary="无问题")
-    assert result.blocking_count == 0
-    assert result.has_blocking is False
-
-
-def test_review_result_to_dict_roundtrip():
-    result = ReviewResult(
-        chapter=10,
-        issues=[
-            ReviewIssue(severity="medium", category="ai_flavor", location="p5", description="AI味重",
-                        evidence="'稳住心神'出现3次", fix_hint="替换为具体动作描写"),
-        ],
-        summary="1个AI味问题",
-    )
-    d = result.to_dict()
-    assert d["chapter"] == 10
-    assert d["blocking_count"] == 0
-    assert len(d["issues"]) == 1
-    assert d["issues"][0]["category"] == "ai_flavor"
-    assert d["issues"][0]["fix_hint"] == "替换为具体动作描写"
-
-
-def test_parse_review_output_from_dict():
-    raw = {
-        "issues": [
-            {"severity": "critical", "category": "continuity", "location": "p1",
-             "description": "矛盾", "evidence": "证据", "fix_hint": "修复"},
-        ],
-        "summary": "1个严重问题",
-    }
-    result = parse_review_output(chapter=5, raw=raw)
-    assert result.chapter == 5
-    assert result.blocking_count == 1
-
-
-def test_parse_review_output_tolerates_missing_fields():
-    raw = {
-        "issues": [
-            {"severity": "low", "description": "小问题"},
-        ],
-        "summary": "轻微",
-    }
-    result = parse_review_output(chapter=1, raw=raw)
-    assert result.issues[0].category == "other"
-    assert result.issues[0].location == ""
-
-
-def test_review_result_to_metrics_dict():
-    result = ReviewResult(
-        chapter=10,
-        issues=[
-            ReviewIssue(severity="critical", category="continuity", location="p1", description="d1"),
-            ReviewIssue(severity="high", category="ai_flavor", location="p2", description="d2"),
-        ],
-        summary="测试",
-    )
-    metrics = result.to_metrics_dict()
-    assert metrics["chapter"] == 10
-    assert metrics["issues_count"] == 2
-    assert metrics["blocking_count"] == 1
-    assert "continuity" in metrics["categories"]
-    assert "ai_flavor" in metrics["categories"]
-```
-
-- [ ] **Step 2: 运行测试确认失败**
-
-Run: `cd webnovel-writer/scripts && python -m pytest data_modules/tests/test_review_schema.py -v --no-cov`
-Expected: FAIL — `ModuleNotFoundError: No module named 'data_modules.review_schema'`
-
-- [ ] **Step 3: 实现 review_schema.py**
-
-```python
-# scripts/data_modules/review_schema.py
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-审查结果 schema(v6)。
-
-替代原 checker-output-schema.md 的评分制,改为结构化问题清单。
-"""
-from __future__ import annotations
-
-from dataclasses import asdict, dataclass, field
-from datetime import datetime
-from typing import Any, Dict, List, Optional
-
-VALID_SEVERITIES = {"critical", "high", "medium", "low"}
-VALID_CATEGORIES = {
-    "continuity", "setting", "character", "timeline",
-    "ai_flavor", "logic", "pacing", "other",
-}
-
-
-@dataclass
-class ReviewIssue:
-    severity: str
-    category: str = "other"
-    location: str = ""
-    description: str = ""
-    evidence: str = ""
-    fix_hint: str = ""
-    blocking: Optional[bool] = None
-
-    def __post_init__(self):
-        if self.severity not in VALID_SEVERITIES:
-            self.severity = "medium"
-        if self.category not in VALID_CATEGORIES:
-            self.category = "other"
-        if self.blocking is None:
-            self.blocking = self.severity == "critical"
-
-    def to_dict(self) -> Dict[str, Any]:
-        return asdict(self)
-
-
-@dataclass
-class ReviewResult:
-    chapter: int
-    issues: List[ReviewIssue] = field(default_factory=list)
-    summary: str = ""
-
-    @property
-    def issues_count(self) -> int:
-        return len(self.issues)
-
-    @property
-    def blocking_count(self) -> int:
-        return sum(1 for i in self.issues if i.blocking)
-
-    @property
-    def has_blocking(self) -> bool:
-        return self.blocking_count > 0
-
-    def to_dict(self) -> Dict[str, Any]:
-        return {
-            "chapter": self.chapter,
-            "issues": [i.to_dict() for i in self.issues],
-            "issues_count": self.issues_count,
-            "blocking_count": self.blocking_count,
-            "has_blocking": self.has_blocking,
-            "summary": self.summary,
-        }
-
-    def to_metrics_dict(self) -> Dict[str, Any]:
-        categories = sorted(set(i.category for i in self.issues))
-        return {
-            "chapter": self.chapter,
-            "issues_count": self.issues_count,
-            "blocking_count": self.blocking_count,
-            "categories": categories,
-            "timestamp": datetime.now().isoformat(timespec="seconds"),
-        }
-
-
-def parse_review_output(chapter: int, raw: Dict[str, Any]) -> ReviewResult:
-    issues = []
-    for item in raw.get("issues", []):
-        if not isinstance(item, dict):
-            continue
-        issues.append(ReviewIssue(
-            severity=str(item.get("severity", "medium")),
-            category=str(item.get("category", "other")),
-            location=str(item.get("location", "")),
-            description=str(item.get("description", "")),
-            evidence=str(item.get("evidence", "")),
-            fix_hint=str(item.get("fix_hint", "")),
-            blocking=item.get("blocking"),
-        ))
-    return ReviewResult(
-        chapter=chapter,
-        issues=issues,
-        summary=str(raw.get("summary", "")),
-    )
-```
-
-- [ ] **Step 4: 运行测试确认通过**
-
-Run: `cd webnovel-writer/scripts && python -m pytest data_modules/tests/test_review_schema.py -v --no-cov`
-Expected: 8 passed
-
-- [ ] **Step 5: 写 review-schema.md 参考文档**
-
-```markdown
-# 审查输出 Schema(v6)
-
-统一审查 Agent 输出格式。替代原 checker-output-schema.md 的评分制。
-
-## 核心变化
-
-- **无总分**:不再输出 overall_score,改为结构化问题清单
-- **blocking 语义**:替代原 timeline_gate,severity=critical 默认阻断
-- **单 agent**:不再区分 6 个 checker,统一由 reviewer agent 输出
-
-## Issue Schema
-
-| 字段 | 类型 | 必填 | 说明 |
-|------|------|------|------|
-| severity | critical/high/medium/low | ✅ | 严重度 |
-| category | continuity/setting/character/timeline/ai_flavor/logic/pacing/other | ✅ | 问题分类 |
-| location | string | ✅ | 位置(如"第3段") |
-| description | string | ✅ | 问题描述 |
-| evidence | string | ❌ | 原文引用或记忆对比 |
-| fix_hint | string | ❌ | 修复建议 |
-| blocking | bool | ❌ | 是否阻断(critical 默认 true) |
-
-## 阻断规则
-
-- 存在任何 `blocking=true` 的 issue → Step 4 不得开始
-- `severity=critical` 自动 `blocking=true`
-- 其余 severity 由审查 agent 根据上下文判断
-
-## 指标沉淀
-
-每次审查写入 `index.db.review_metrics`:
-- `chapter, issues_count, blocking_count, categories, timestamp`
-- 用于趋势观测,不用于 gate 决策
-```
-
-- [ ] **Step 6: 提交**
-
-```bash
-git add scripts/data_modules/review_schema.py scripts/data_modules/tests/test_review_schema.py references/review-schema.md
-git commit -m "feat: 新审查 schema(v6)——结构化问题清单替代评分制"
-```
-
----
-
-## Task 2: 创建统一审查 agent
-
-**Files:**
-- Create: `agents/reviewer.md`
-
-- [ ] **Step 1: 写 reviewer.md**
-
-```markdown
----
-name: reviewer
-description: 统一审查 agent。检查正文的设定一致性、叙事连贯性、角色一致性、时间线、AI味,输出结构化问题清单。
-tools: Read, Grep, Bash
-model: inherit
----
-
-# reviewer(统一审查 agent)
-
-## 身份与目标
-
-你是章节审查员。你的职责是读完正文后,找出所有可验证的问题,输出结构化问题清单。
-
-你不评分、不给建议、不写摘要性评价。你只找问题、给证据、给修复方向。
-
-## 可用工具
-
-- `Read`:读取正文、设定集、记忆数据
-- `Grep`:在正文中搜索关键词
-- `Bash`:调用记忆模块查询
-
-```bash
-# 查询角色当前状态
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" state get-entity --id "{entity_id}"
-
-# 查询最近状态变更
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" index get-recent-state-changes --limit 20
-```
-
-## 思维链(ReAct)
-
-对每个检查维度:
-1. **读取**相关数据(角色状态、世界规则、上章摘要)
-2. **对比**正文内容与数据
-3. **判断**是否存在矛盾/问题
-4. **记录**问题到清单(含 evidence 和 fix_hint)
-
-## 输入
-
-- `chapter`:章节号
-- `chapter_file`:正文文件路径
-- `project_root`:项目根目录
-- `scripts_dir`:脚本目录
-
-## 检查维度(按顺序执行)
-
-### 1. 设定一致性(category: setting)
-- 角色能力是否与当前境界匹配
-- 地点描述是否与世界观一致
-- 物品/货币使用是否符合已建立规则
-
-### 2. 时间线(category: timeline)
-- 本章时间是否与上章衔接(无回跳或有合理解释)
-- 倒计时/截止日期是否正确推进
-- 角色同时出现在两个地点
-
-### 3. 叙事连贯(category: continuity)
-- 上章钩子是否有回应
-- 场景转换是否有过渡
-- 情绪弧是否连续(上章愤怒本章突然平静无过渡)
-
-### 4. 角色一致性(category: character)
-- 对话风格是否符合角色特征
-- 行为是否与已建立的性格/动机一致
-- 角色知识边界——角色是否使用了不应知道的信息
-
-### 5. 逻辑(category: logic)
-- 因果关系是否成立
-- 角色决策是否有合理动机
-- 战斗/冲突结果是否符合已建立的力量对比
-
-### 6. AI味(category: ai_flavor)
-- 是否存在禁用词/禁用句式(稳住心神、不禁XXX、嘴角微微上扬等)
-- 是否存在每段"起因→经过→结果→感悟"的四段式结构
-- 是否存在过度解释(展示而非讲述的缺失)
-- 情绪描写是否模板化("眼中闪过一丝XXX")
-
-## 边界与禁区
-
-- **不评分**——不输出 overall_score、不输出 pass/fail
-- **不评价文笔质量**——"写得不够好"不是 issue,"与角色性格矛盾"才是
-- **不建议情节改动**——"这里应该加个反转"不是 issue
-- **不重复大纲内容**——不在 issue 中暴露未发生的剧情
-- **只报可验证的问题**——必须有 evidence(原文引用 or 数据对比)
-
-## 检查清单
-
-完成审查前自检:
-- [ ] 每个 issue 都有 evidence
-- [ ] 没有"感觉"类的主观评价
-- [ ] severity 分级合理(critical 仅用于确定的事实矛盾)
-- [ ] category 归类正确
-- [ ] blocking 字段只在 critical 或确认阻断时为 true
-
-## 输出格式
-
-严格按以下 JSON 格式输出(无其他文本):
-
-```json
-{
-  "issues": [
-    {
-      "severity": "critical | high | medium | low",
-      "category": "continuity | setting | character | timeline | ai_flavor | logic | pacing | other",
-      "location": "第N段 或 具体引用",
-      "description": "问题描述",
-      "evidence": "原文引用 vs 数据记录",
-      "fix_hint": "修复方向",
-      "blocking": true
-    }
-  ],
-  "summary": "N个问题:X个阻断,Y个高优"
-}
-```
-
-## 错误处理
-
-- 无法读取角色状态 → 跳过设定一致性检查,在 summary 中标注"无法校验设定一致性:数据读取失败"
-- 无法读取上章摘要 → 跳过连贯性检查中的"上章钩子回应"项
-- 正文为空 → 输出单条 critical issue:"正文为空"
-```
-
-- [ ] **Step 2: 提交**
-
-```bash
-git add agents/reviewer.md
-git commit -m "feat: 统一审查 agent reviewer.md——合并6个checker为1个"
-```
-
----
-
-## Task 3: 删除 workflow 模块和 resume skill
-
-**Files:**
-- Delete: `scripts/workflow_manager.py`
-- Delete: `scripts/data_modules/tests/test_workflow_manager.py`
-- Delete: `skills/webnovel-resume/SKILL.md`
-- Delete: `skills/webnovel-resume/references/workflow-resume.md`
-- Modify: `scripts/data_modules/webnovel.py`
-- Modify: `scripts/data_modules/tests/test_coverage_boost.py`
-
-- [ ] **Step 1: 从 webnovel.py 移除 workflow 命令路由**
-
-在 `scripts/data_modules/webnovel.py` 中删除 workflow 相关的 parser 和路由:
-
-删除 parser 定义:
-```python
-# 删除这两行
-p_workflow = sub.add_parser("workflow", help="转发到 workflow_manager.py")
-p_workflow.add_argument("args", nargs=argparse.REMAINDER)
-```
-
-删除路由分支:
-```python
-# 删除这两行
-if tool == "workflow":
-    raise SystemExit(_run_script("workflow_manager.py", [*forward_args, *rest]))
-```
-
-- [ ] **Step 2: 从 test_coverage_boost.py 移除 workflow 相关测试**
-
-删除 `test_webnovel_passthrough_workflow_script` 测试函数。
-
-- [ ] **Step 3: 删除 workflow_manager.py 和测试**
-
-```bash
-git rm scripts/workflow_manager.py
-git rm scripts/data_modules/tests/test_workflow_manager.py
-```
-
-- [ ] **Step 4: 删除 resume skill**
-
-```bash
-git rm skills/webnovel-resume/SKILL.md
-git rm skills/webnovel-resume/references/workflow-resume.md
-rmdir skills/webnovel-resume/references 2>/dev/null || true
-rmdir skills/webnovel-resume 2>/dev/null || true
-```
-
-- [ ] **Step 5: 运行测试确认无破损**
-
-Run: `cd "D:\wk\novel skill\webnovel-writer" && python -m pytest --no-cov --tb=short`
-Expected: 全部通过(数量会减少,因为删了 test_workflow_manager.py 的 10 个测试)
-
-- [ ] **Step 6: 提交**
-
-```bash
-git add -A
-git commit -m "refactor: 移除 workflow_manager + resume skill,由 Claude Code /resume 替代"
-```
-
----
-
-## Task 4: 删除 6 个旧 checker agent 和旧 schema
-
-**Files:**
-- Delete: `agents/consistency-checker.md`
-- Delete: `agents/continuity-checker.md`
-- Delete: `agents/ooc-checker.md`
-- Delete: `agents/high-point-checker.md`
-- Delete: `agents/pacing-checker.md`
-- Delete: `agents/reader-pull-checker.md`
-- Delete: `references/checker-output-schema.md`
-- Delete: `skills/webnovel-write/references/step-3-review-gate.md`
-
-- [ ] **Step 1: 删除旧 checker agents**
-
-```bash
-git rm agents/consistency-checker.md
-git rm agents/continuity-checker.md
-git rm agents/ooc-checker.md
-git rm agents/high-point-checker.md
-git rm agents/pacing-checker.md
-git rm agents/reader-pull-checker.md
-```
-
-- [ ] **Step 2: 删除旧 schema 和 review gate**
-
-```bash
-git rm references/checker-output-schema.md
-git rm skills/webnovel-write/references/step-3-review-gate.md
-```
-
-- [ ] **Step 3: 提交**
-
-```bash
-git add -A
-git commit -m "refactor: 移除6个旧checker agent和旧schema,由reviewer.md替代"
-```
-
----
-
-## Task 5: 更新 review_pipeline.py 适配新 schema
-
-**Files:**
-- Modify: `scripts/review_pipeline.py`
-- Modify: `scripts/data_modules/tests/test_webnovel_unified_cli.py`
-
-- [ ] **Step 1: 写测试——review_pipeline 适配新 schema**
-
-在 `test_webnovel_unified_cli.py` 中修改 `test_review_pipeline_builds_artifacts`,将旧的 checker 多结果格式改为新的单 reviewer 输出:
-
-```python
-def test_review_pipeline_builds_artifacts_v6(tmp_path):
-    _ensure_scripts_on_path()
-    import review_pipeline as review_pipeline_module
-
-    project_root = (tmp_path / "book").resolve()
-    (project_root / ".webnovel").mkdir(parents=True, exist_ok=True)
-    (project_root / ".webnovel" / "state.json").write_text("{}", encoding="utf-8")
-
-    review_results_path = tmp_path / "review_results.json"
-    review_results_path.write_text(
-        json.dumps(
-            {
-                "issues": [
-                    {
-                        "severity": "critical",
-                        "category": "timeline",
-                        "location": "第2段",
-                        "description": "时间线回跳",
-                        "evidence": "上章深夜,本章突然中午",
-                        "fix_hint": "补时间过渡",
-                        "blocking": True,
-                    },
-                    {
-                        "severity": "medium",
-                        "category": "ai_flavor",
-                        "location": "第5段",
-                        "description": "'稳住心神'出现2次",
-                        "fix_hint": "替换为具体动作",
-                    },
-                ],
-                "summary": "1个阻断,1个中等",
-            },
-            ensure_ascii=False,
-        ),
-        encoding="utf-8",
-    )
-
-    payload = review_pipeline_module.build_review_artifacts(
-        project_root=project_root,
-        chapter=20,
-        review_results_path=review_results_path,
-        report_file="",
-    )
-
-    assert payload["review_result"]["blocking_count"] == 1
-    assert payload["review_result"]["has_blocking"] is True
-    assert payload["review_result"]["issues_count"] == 2
-    assert payload["metrics"]["issues_count"] == 2
-    assert payload["metrics"]["blocking_count"] == 1
-```
-
-- [ ] **Step 2: 运行测试确认失败**
-
-Run: `cd webnovel-writer/scripts && python -m pytest data_modules/tests/test_webnovel_unified_cli.py::test_review_pipeline_builds_artifacts_v6 -v --no-cov`
-Expected: FAIL(review_pipeline 还是旧逻辑)
-
-- [ ] **Step 3: 重写 review_pipeline.py**
-
-```python
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-Step 3 审查结果处理。
-
-读取 reviewer agent 的原始输出 JSON,解析为 ReviewResult,
-生成 metrics 用于 index.db 沉淀。
-"""
-from __future__ import annotations
-
-import json
-import sys
-from pathlib import Path
-from typing import Any, Dict, Optional
-
-
-def _ensure_scripts_path() -> None:
-    scripts_dir = Path(__file__).resolve().parent
-    if str(scripts_dir) not in sys.path:
-        sys.path.insert(0, str(scripts_dir))
-
-
-_ensure_scripts_path()
-
-from data_modules.review_schema import ReviewResult, parse_review_output
-
-
-def build_review_artifacts(
-    project_root: Path,
-    chapter: int,
-    review_results_path: Path,
-    report_file: str = "",
-) -> Dict[str, Any]:
-    raw = json.loads(review_results_path.read_text(encoding="utf-8"))
-    result = parse_review_output(chapter=chapter, raw=raw)
-    metrics = result.to_metrics_dict()
-    if report_file:
-        metrics["report_file"] = report_file
-
-    return {
-        "chapter": chapter,
-        "review_result": result.to_dict(),
-        "metrics": metrics,
-    }
-
-
-def main() -> None:
-    import argparse
-
-    parser = argparse.ArgumentParser(description="Review pipeline v6")
-    parser.add_argument("--project-root", required=True)
-    parser.add_argument("--chapter", type=int, required=True)
-    parser.add_argument("--review-results", required=True)
-    parser.add_argument("--metrics-out", default="")
-    parser.add_argument("--report-file", default="")
-
-    args = parser.parse_args()
-    project_root = Path(args.project_root)
-    review_results_path = Path(args.review_results)
-
-    payload = build_review_artifacts(
-        project_root=project_root,
-        chapter=args.chapter,
-        review_results_path=review_results_path,
-        report_file=args.report_file,
-    )
-
-    if args.metrics_out:
-        out_path = Path(args.metrics_out)
-        out_path.parent.mkdir(parents=True, exist_ok=True)
-        out_path.write_text(
-            json.dumps(payload["metrics"], ensure_ascii=False, indent=2),
-            encoding="utf-8",
-        )
-
-    print(json.dumps(payload, ensure_ascii=False, indent=2))
-
-
-if __name__ == "__main__":
-    main()
-```
-
-- [ ] **Step 4: 运行测试确认通过**
-
-Run: `cd webnovel-writer/scripts && python -m pytest data_modules/tests/test_webnovel_unified_cli.py -v --no-cov`
-Expected: 全部通过
-
-- [ ] **Step 5: 提交**
-
-```bash
-git add scripts/review_pipeline.py scripts/data_modules/tests/test_webnovel_unified_cli.py
-git commit -m "refactor: review_pipeline 适配 v6 schema——无评分,结构化问题清单"
-```
-
----
-
-## Task 6: 更新 webnovel-write SKILL.md(新 7 步流程)
-
-**Files:**
-- Modify: `skills/webnovel-write/SKILL.md`
-- Delete: `skills/webnovel-write/references/step-5-debt-switch.md`
-- Delete: `skills/webnovel-write/references/workflow-details.md`
-- Delete: `skills/webnovel-write/references/step-1.5-contract.md`
-
-- [ ] **Step 1: 删除废弃引用文件**
-
-```bash
-git rm skills/webnovel-write/references/step-5-debt-switch.md
-git rm skills/webnovel-write/references/workflow-details.md
-git rm skills/webnovel-write/references/step-1.5-contract.md
-```
-
-- [ ] **Step 2: 重写 SKILL.md**
-
-完整重写 `skills/webnovel-write/SKILL.md`,核心变化:
-
-1. **流程从 8 步变 7 步**:
-```
-Step 0.5 预检 → Step 1 上下文搜集 → Step 2 起草 → Step 3 审查 → Step 4 润色+风格+anti-AI → Step 5 数据回写 → Step 6 Git
-```
-
-2. **去掉所有 workflow 记录命令**(删除每步前后的 `workflow start-step` / `complete-step`)
-
-3. **Step 2B 合并到 Step 4**:Step 4 职责变为"润色 + 风格适配 + anti-AI 修复"
-
-4. **Step 3 改为单 reviewer agent**:
-```
-使用 Task 调用 reviewer agent(不再调用 6 个独立 checker)
-输出:review_results.json(新 schema)
-通过 review_pipeline 生成 metrics
-blocking issue 存在时阻断
-```
-
-5. **Step 4 增加 anti-AI 职责**:
-```
-- 消费 Step 3 的问题清单,逐条修复
-- 执行风格适配(原 Step 2B 的工作)
-- anti-AI 最终 gate:修复后复检,确认无 blocking 残留
-```
-
-6. **模式定义更新**:
-```
-标准:Step 0.5 → 1 → 2 → 3 → 4 → 5 → 6
---fast:Step 0.5 → 1 → 2 → 3(轻量) → 4 → 5 → 6
---minimal:Step 0.5 → 1 → 2 → 4(仅排版) → 5 → 6
-```
-
-7. **References 更新**:移除对已删文件的引用,添加 `review-schema.md` 引用
-
-- [ ] **Step 3: 提交**
-
-```bash
-git add -A
-git commit -m "refactor: webnovel-write SKILL.md v6——7步流程,单reviewer,合并风格适配"
-```
-
----
-
-## Task 7: 清理旧 review_pipeline 测试并跑全量回归
-
-**Files:**
-- Modify: `scripts/data_modules/tests/test_webnovel_unified_cli.py`
-
-- [ ] **Step 1: 移除旧 review_pipeline 测试中不兼容的断言**
-
-更新 `test_review_pipeline_builds_artifacts` 和 `test_review_pipeline_main_creates_output_directories` 以适配新 schema。旧测试依赖 `overall_score`、`timeline_gate` 等已移除的字段。
-
-如果 Task 5 的新测试已覆盖,直接删除旧版测试。
-
-- [ ] **Step 2: 全量回归测试**
-
-Run: `cd "D:\wk\novel skill\webnovel-writer" && python -m pytest --tb=short`
-Expected: 全部通过,覆盖率 ≥ 90%
-
-- [ ] **Step 3: 如有失败修复后提交**
-
-```bash
-git add -A
-git commit -m "test: 清理旧 review 测试,全量回归通过"
-```
-
----
-
-## Task 8: 最终验证
-
-- [ ] **Step 1: 确认删除完整性**
-
-```bash
-# 这些文件应该不存在
-test ! -f webnovel-writer/scripts/workflow_manager.py
-test ! -f webnovel-writer/skills/webnovel-resume/SKILL.md
-test ! -f webnovel-writer/agents/consistency-checker.md
-test ! -f webnovel-writer/agents/continuity-checker.md
-test ! -f webnovel-writer/agents/ooc-checker.md
-test ! -f webnovel-writer/agents/high-point-checker.md
-test ! -f webnovel-writer/agents/pacing-checker.md
-test ! -f webnovel-writer/agents/reader-pull-checker.md
-test ! -f webnovel-writer/references/checker-output-schema.md
-
-# 这些文件应该存在
-test -f webnovel-writer/agents/reviewer.md
-test -f webnovel-writer/references/review-schema.md
-test -f webnovel-writer/scripts/data_modules/review_schema.py
-```
-
-- [ ] **Step 2: 全量测试 + 覆盖率**
-
-Run: `cd "D:\wk\novel skill\webnovel-writer" && python -m pytest`
-Expected: 全部通过,覆盖率 ≥ 90%
-
-- [ ] **Step 3: 确认 agents/ 目录只剩 3 个 agent**
-
-```bash
-ls webnovel-writer/agents/
-# 期望:context-agent.md  data-agent.md  reviewer.md
-```
-
-- [ ] **Step 4: 最终提交(如有遗漏修复)**
-
-```bash
-git add -A
-git commit -m "chore: Phase 1 完成——清理验证通过"
-```

+ 0 - 605
docs/archive/superpowers/plans/2026-04-09-v6-migration-completion.md

@@ -1,605 +0,0 @@
-# v6 Migration Completion Plan
-
-> **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:** 完成 v6 迁移剩余工作——清理 legacy 代码、落地章节状态模型、修正 spec 与实现的不一致、通过退出标准验证。
-
-**Architecture:** 三条并行工作线:(A) legacy 代码删除与清理,(B) 章节状态模型新增,(C) spec/参考资料修正。A 和 C 无依赖可并行,B 是新功能需独立开发测试。
-
-**Tech Stack:** Python 3.13, pytest, SQLite (state.json + index.db), Claude Code plugin (markdown skills/agents)
-
-**Spec:** `docs/superpowers/specs/2026-04-02-harness-v6-design.md` (v6)
-
-**前置条件:** Phase 1 的"创建新模块"部分已完成(reviewer.md、review_schema.py、review_pipeline.py 均已存在且通过测试)。Phase 3(memory_contract)和 Phase 4(context-agent research 模式)已完成。
-
----
-
-## 当前差距摘要
-
-| 类别 | 残留项 | 文件 |
-|------|--------|------|
-| 旧 checker 函数 | `_normalize_checker_issue`, `_build_timeline_gate`, `_aggregate_checker_results`, `ReviewAggregateResult` | `index_manager.py:208-232, 678-808` |
-| 旧 checker CLI | `aggregate-review-results`, `materialize-review-metrics` | `index_manager.py:963-969, 1318-1327` |
-| 旧 checker 脚本 | 整文件 570 行 | `golden_three_checker.py` |
-| 旧 checker 测试 | `test_aggregate_checker_results_cli` (L1428-1551), `test_aggregate_checker_results_blocks_...` (L1553-1596) | `test_data_modules.py` |
-| 旧 checker 测试 | `test_index_aggregate_review_results_forwards_...` (L173) | `test_webnovel_unified_cli.py:173-218` |
-| 旧 checker 引用 | `continuity-checker` 在已知 checker 列表 | `test_prompt_integrity.py:247` |
-| workflow 残留 | 注释引用 + 测试白名单 | `webnovel.py:94`, `test_prompt_integrity.py:221` |
-| Step 2B 残留 | 职责边界说明 | `polish-guide.md:13-17` |
-| legacy 引用 | `continuity-checker` 映射表 | `reading-power-taxonomy.md:343-348` |
-| legacy 消费 | `overall_score` 用于低分告警 | `context_manager.py:310-318` |
-| 缺失功能 | 章节状态模型 | 无(需新增到 `state_manager.py`) |
-| spec 不一致 | "v0 接口尚未实现"但实际已实现 | spec 4.5 |
-
----
-
-## File Structure
-
-### 要删除的文件
-
-| 文件 | 原因 |
-|------|------|
-| `scripts/golden_three_checker.py` | 旧 checker 模式,570 行,已被 reviewer 替代 |
-
-### 要修改的文件
-
-| 文件 | 改什么 |
-|------|--------|
-| `scripts/data_modules/index_manager.py` | 删除 `ReviewAggregateResult`、旧 checker 函数、旧 CLI 命令 |
-| `scripts/data_modules/context_manager.py` | `overall_score` 低分判断改为 severity_counts |
-| `scripts/data_modules/tests/test_data_modules.py` | 删除旧 checker 聚合测试(~170 行) |
-| `scripts/data_modules/tests/test_webnovel_unified_cli.py` | 删除旧 aggregate-review-results 转发测试 |
-| `scripts/data_modules/tests/test_prompt_integrity.py` | 清理 checker 白名单、workflow_manager 白名单 |
-| `scripts/data_modules/webnovel.py` | 清理 workflow_manager 注释 |
-| `scripts/data_modules/state_manager.py` | 新增 chapter_status 管理 |
-| `skills/webnovel-write/references/polish-guide.md` | 删除 Step 2B 边界段落 |
-| `references/reading-power-taxonomy.md` | 更新 checker 映射表 |
-| `docs/superpowers/specs/2026-04-02-harness-v6-design.md` | 修正 spec 与实现不一致 |
-
-### 要创建的文件
-
-| 文件 | 职责 |
-|------|------|
-| `scripts/data_modules/tests/test_chapter_status.py` | 章节状态模型测试 |
-
----
-
-## Task 1: 删除旧 checker 聚合函数与 CLI
-
-**Files:**
-- Modify: `scripts/data_modules/index_manager.py`
-- Modify: `scripts/data_modules/tests/test_data_modules.py`
-- Modify: `scripts/data_modules/tests/test_webnovel_unified_cli.py`
-
-- [ ] **Step 1: 从 index_manager.py 删除 ReviewAggregateResult dataclass**
-
-删除 `index_manager.py` 中 L208-232 的 `ReviewAggregateResult` dataclass 及其 `to_review_metrics` 方法。保留 `ReviewMetrics` dataclass(仍被 `save-review-metrics` CLI 使用)。
-
-```python
-# 删除以下代码块(约 25 行):
-@dataclass
-class ReviewAggregateResult:
-    """Step 3 审查聚合结果"""
-    chapter: int
-    start_chapter: int
-    end_chapter: int
-    selected_checkers: List[str] = field(default_factory=list)
-    checkers: Dict[str, Dict[str, Any]] = field(default_factory=dict)
-    issues: List[Dict[str, Any]] = field(default_factory=list)
-    overall_score: float = 0.0
-    severity_counts: Dict[str, int] = field(default_factory=dict)
-    timeline_gate: Dict[str, Any] = field(default_factory=dict)
-    # ... 及其所有方法
-```
-
-- [ ] **Step 2: 从 index_manager.py 删除旧 checker 函数**
-
-删除以下 3 个函数(L678-808):
-
-```python
-# 删除这 3 个函数:
-def _normalize_checker_issue(issue: object) -> dict: ...
-def _build_timeline_gate(issues: ...) -> Dict[str, Any]: ...
-def _aggregate_checker_results(chapter: int, raw_data: object) -> dict: ...
-```
-
-- [ ] **Step 3: 从 index_manager.py 删除旧 CLI 命令注册**
-
-删除 `aggregate-review-results` 和 `materialize-review-metrics` 的 parser 注册(L963-969):
-
-```python
-# 删除:
-review_aggregate_parser = subparsers.add_parser("aggregate-review-results")
-review_aggregate_parser.add_argument("--chapter", ...)
-review_aggregate_parser.add_argument("--data", ...)
-
-review_materialize_parser = subparsers.add_parser("materialize-review-metrics")
-review_materialize_parser.add_argument("--chapter", ...)
-review_materialize_parser.add_argument("--data", ...)
-```
-
-删除对应的 CLI 处理分支(L1318-1327):
-
-```python
-# 删除:
-elif args.command == "aggregate-review-results":
-    ...
-elif args.command == "materialize-review-metrics":
-    ...
-```
-
-- [ ] **Step 4: 删除旧 checker 测试**
-
-从 `test_data_modules.py` 删除以下测试函数(L1428-1596,~170 行):
-
-```python
-# 删除这 2 个测试函数:
-def test_aggregate_checker_results_cli(temp_project, monkeypatch, capsys): ...
-def test_aggregate_checker_results_blocks_overall_pass_for_high_timeline_issue(temp_project, monkeypatch, capsys): ...
-```
-
-从 `test_webnovel_unified_cli.py` 删除旧 aggregate 转发测试(L173-218):
-
-```python
-# 删除:
-def test_index_aggregate_review_results_forwards_with_resolved_project_root(monkeypatch, tmp_path): ...
-```
-
-- [ ] **Step 5: 运行测试确认无破损**
-
-Run: `cd "D:/wk/novel skill/webnovel-writer" && python -m pytest webnovel-writer/scripts -x --tb=short --no-cov`
-Expected: 全部通过
-
-- [ ] **Step 6: 提交**
-
-```bash
-cd "D:/wk/novel skill/webnovel-writer"
-git add webnovel-writer/scripts/data_modules/index_manager.py webnovel-writer/scripts/data_modules/tests/test_data_modules.py webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py
-git commit -m "$(cat <<'EOF'
-refactor: 移除旧 checker 聚合函数和 CLI
-
-删除 ReviewAggregateResult、_aggregate_checker_results、
-_build_timeline_gate、_normalize_checker_issue 及对应 CLI
-和测试。reviewer + review_pipeline 已完全替代。
-EOF
-)"
-```
-
----
-
-## Task 2: 删除 golden_three_checker.py
-
-**Files:**
-- Delete: `scripts/golden_three_checker.py`
-
-- [ ] **Step 1: 确认无运行时依赖**
-
-Run: `cd "D:/wk/novel skill/webnovel-writer" && grep -r "golden_three" webnovel-writer/ --include="*.py" --include="*.md" | grep -v golden_three_checker.py`
-Expected: 无命中(或仅在 test_prompt_integrity 白名单中)
-
-- [ ] **Step 2: 删除文件**
-
-```bash
-cd "D:/wk/novel skill/webnovel-writer"
-git rm webnovel-writer/scripts/golden_three_checker.py
-```
-
-- [ ] **Step 3: 运行测试**
-
-Run: `cd "D:/wk/novel skill/webnovel-writer" && python -m pytest webnovel-writer/scripts -x --tb=short --no-cov`
-Expected: 全部通过
-
-- [ ] **Step 4: 提交**
-
-```bash
-git commit -m "refactor: 删除 golden_three_checker.py(570行),已被 reviewer 替代"
-```
-
----
-
-## Task 3: 清理散落的 legacy 引用
-
-**Files:**
-- Modify: `scripts/data_modules/webnovel.py`
-- Modify: `scripts/data_modules/tests/test_prompt_integrity.py`
-- Modify: `skills/webnovel-write/references/polish-guide.md`
-- Modify: `references/reading-power-taxonomy.md`
-
-- [ ] **Step 1: 清理 webnovel.py 的 workflow_manager 注释**
-
-`webnovel.py:94` 改为不再提及 workflow_manager:
-
-```python
-# 旧:
-#     用途:兼容没有 main() 的脚本(例如 workflow_manager.py)。
-# 新:
-#     用途:兼容没有 main() 的脚本。
-```
-
-- [ ] **Step 2: 清理 test_prompt_integrity.py**
-
-从 `KNOWN_DELETED_FILES` 列表(L215-223)中添加 `golden_three_checker.py`(如果需要),并从任何 checker 白名单中移除 `continuity-checker` 等旧 checker 名称(L247)。
-
-确认 L247 处 `continuity-checker` 引用的上下文,如果是"不应出现在 prompt 中"的检查,则保留;如果是"允许出现"的白名单,则移除。
-
-- [ ] **Step 3: 清理 polish-guide.md 的 Step 2B 段落**
-
-删除 `polish-guide.md:13-17` 中关于 Step 2B 的职责边界说明:
-
-```markdown
-# 删除以下内容:
-与 Step 2B 的职责边界:
-- Step 2B:风格转译(表达层)
-- Step 4:问题修复(质量层),包括审查问题修复、Anti-AI 终检、毒点规避
-
-若已执行 Step 2B,本步骤不重复全量句式改写,只做"必要修改"。
-```
-
-替换为:
-
-```markdown
-职责定义:
-- Step 4 同时负责风格适配(消除模板腔、说明腔、机械腔)和问题修复
-- 包括审查问题修复、Anti-AI 终检、毒点规避
-```
-
-- [ ] **Step 4: 清理 reading-power-taxonomy.md 的 checker 映射表**
-
-更新 `reading-power-taxonomy.md:343-349` 的旧 checker 映射表:
-
-```markdown
-# 旧:
-| 现有 Checker | 使用的 Taxonomy |
-|--------------|----------------|
-| `reader-pull-checker` | 钩子类型、钩子强度、Hard-002 |
-| `high-point-checker` | 爽点模式、微兑现 |
-| `pacing-checker` | Hard-003 (节奏灾难) |
-| `continuity-checker` | Hard-001 (可读性底线) |
-
-# 新:
-| 审查维度 (reviewer) | 使用的 Taxonomy |
-|---------------------|----------------|
-| continuity | Hard-001 (可读性底线)、Hard-002 (结构完整) |
-| pacing | Hard-003 (节奏灾难)、爽点模式、微兑现 |
-| ai_flavor | 钩子类型、钩子强度 |
-```
-
-- [ ] **Step 5: 运行测试**
-
-Run: `cd "D:/wk/novel skill/webnovel-writer" && python -m pytest webnovel-writer/scripts -x --tb=short --no-cov`
-Expected: 全部通过
-
-- [ ] **Step 6: 提交**
-
-```bash
-cd "D:/wk/novel skill/webnovel-writer"
-git add webnovel-writer/scripts/data_modules/webnovel.py webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py webnovel-writer/skills/webnovel-write/references/polish-guide.md webnovel-writer/references/reading-power-taxonomy.md
-git commit -m "$(cat <<'EOF'
-refactor: 清理 legacy 引用——workflow_manager 注释、Step 2B 边界、旧 checker 映射
-EOF
-)"
-```
-
----
-
-## Task 4: context_manager.py 的 overall_score 消费迁移
-
-**Files:**
-- Modify: `scripts/data_modules/context_manager.py`
-
-- [ ] **Step 1: 检查 overall_score 消费点**
-
-`context_manager.py:310-318` 用 `overall_score < 75` 判断低分告警。改为使用 `severity_counts` 或 `notes` 字段判断:
-
-```python
-# 旧逻辑(L310-318):
-for row in review_trend.get("recent_ranges", []):
-    score = row.get("overall_score")
-    if isinstance(score, (int, float)) and float(score) < 75:
-        low_score_ranges.append({
-            "start_chapter": row.get("start_chapter"),
-            "end_chapter": row.get("end_chapter"),
-            "overall_score": score,
-        })
-
-# 新逻辑:
-for row in review_trend.get("recent_ranges", []):
-    score = row.get("overall_score")
-    notes = row.get("notes", "")
-    has_issues = "blocking=" in notes and "blocking=0" not in notes
-    is_low_score = isinstance(score, (int, float)) and float(score) < 75
-    if is_low_score or has_issues:
-        low_score_ranges.append({
-            "start_chapter": row.get("start_chapter"),
-            "end_chapter": row.get("end_chapter"),
-            "overall_score": score if isinstance(score, (int, float)) else 0.0,
-            "notes": notes,
-        })
-```
-
-- [ ] **Step 2: 运行相关测试**
-
-Run: `cd "D:/wk/novel skill/webnovel-writer" && python -m pytest webnovel-writer/scripts/data_modules/tests/test_context_manager.py -x --tb=short --no-cov`
-Expected: 通过
-
-- [ ] **Step 3: 提交**
-
-```bash
-cd "D:/wk/novel skill/webnovel-writer"
-git add webnovel-writer/scripts/data_modules/context_manager.py
-git commit -m "refactor: context_manager 低分告警兼容 v6 severity_counts"
-```
-
----
-
-## Task 5: 章节状态模型落地
-
-**Files:**
-- Modify: `scripts/data_modules/state_manager.py`
-- Create: `scripts/data_modules/tests/test_chapter_status.py`
-
-- [ ] **Step 1: 写测试**
-
-```python
-# scripts/data_modules/tests/test_chapter_status.py
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""章节状态模型测试"""
-import json
-import pytest
-from pathlib import Path
-
-
-@pytest.fixture
-def state_project(tmp_path):
-    webnovel_dir = tmp_path / ".webnovel"
-    webnovel_dir.mkdir()
-    state_file = webnovel_dir / "state.json"
-    state_file.write_text(json.dumps({
-        "progress": {"current_chapter": 5}
-    }), encoding="utf-8")
-    return tmp_path
-
-
-def _make_manager(project_root):
-    import sys
-    scripts_dir = str(Path(__file__).resolve().parent.parent.parent)
-    if scripts_dir not in sys.path:
-        sys.path.insert(0, scripts_dir)
-    from data_modules.config import DataModulesConfig
-    from data_modules.state_manager import StateManager
-    config = DataModulesConfig(
-        project_root=project_root,
-        webnovel_dir=project_root / ".webnovel",
-    )
-    return StateManager(config)
-
-
-def test_get_chapter_status_default(state_project):
-    sm = _make_manager(state_project)
-    sm._load_state()
-    status = sm.get_chapter_status(5)
-    assert status is None  # 未设置过
-
-
-def test_set_chapter_status_drafted(state_project):
-    sm = _make_manager(state_project)
-    sm._load_state()
-    sm.set_chapter_status(5, "chapter_drafted")
-    status = sm.get_chapter_status(5)
-    assert status == "chapter_drafted"
-
-
-def test_set_chapter_status_monotonic(state_project):
-    sm = _make_manager(state_project)
-    sm._load_state()
-    sm.set_chapter_status(5, "chapter_reviewed")
-    # 不能回退到 drafted
-    with pytest.raises(ValueError, match="不可回退"):
-        sm.set_chapter_status(5, "chapter_drafted")
-
-
-def test_set_chapter_status_progression(state_project):
-    sm = _make_manager(state_project)
-    sm._load_state()
-    sm.set_chapter_status(5, "chapter_drafted")
-    sm.set_chapter_status(5, "chapter_reviewed")
-    sm.set_chapter_status(5, "chapter_committed")
-    assert sm.get_chapter_status(5) == "chapter_committed"
-
-
-def test_chapter_status_persists(state_project):
-    sm = _make_manager(state_project)
-    sm._load_state()
-    sm.set_chapter_status(3, "chapter_drafted")
-    sm._save_state()
-
-    # 重新加载
-    sm2 = _make_manager(state_project)
-    sm2._load_state()
-    assert sm2.get_chapter_status(3) == "chapter_drafted"
-```
-
-- [ ] **Step 2: 运行测试确认失败**
-
-Run: `cd "D:/wk/novel skill/webnovel-writer/webnovel-writer/scripts" && python -m pytest data_modules/tests/test_chapter_status.py -v --no-cov`
-Expected: FAIL — `AttributeError: 'StateManager' object has no attribute 'get_chapter_status'`
-
-- [ ] **Step 3: 在 state_manager.py 中实现 chapter_status**
-
-在 `StateManager` 类中添加以下方法:
-
-```python
-CHAPTER_STATUS_ORDER = ["chapter_drafted", "chapter_reviewed", "chapter_committed"]
-
-def get_chapter_status(self, chapter: int) -> Optional[str]:
-    """查询章节状态。"""
-    statuses = self._state.get("progress", {}).get("chapter_status", {})
-    return statuses.get(str(chapter))
-
-def set_chapter_status(self, chapter: int, status: str) -> None:
-    """设置章节状态(单调递进,不可回退)。"""
-    if status not in self.CHAPTER_STATUS_ORDER:
-        raise ValueError(f"无效状态: {status},有效值: {self.CHAPTER_STATUS_ORDER}")
-
-    current = self.get_chapter_status(chapter)
-    if current is not None:
-        current_idx = self.CHAPTER_STATUS_ORDER.index(current)
-        new_idx = self.CHAPTER_STATUS_ORDER.index(status)
-        if new_idx < current_idx:
-            raise ValueError(
-                f"章节 {chapter} 状态不可回退: {current} -> {status}"
-            )
-        if new_idx == current_idx:
-            return  # 幂等
-
-    progress = self._state.setdefault("progress", {})
-    chapter_status = progress.setdefault("chapter_status", {})
-    chapter_status[str(chapter)] = status
-    self._save_state()
-```
-
-- [ ] **Step 4: 运行测试确认通过**
-
-Run: `cd "D:/wk/novel skill/webnovel-writer/webnovel-writer/scripts" && python -m pytest data_modules/tests/test_chapter_status.py -v --no-cov`
-Expected: 5 passed
-
-- [ ] **Step 5: 添加 CLI 子命令**
-
-在 `state_manager.py` 的 CLI 部分添加 `get-chapter-status` 和 `set-chapter-status` 子命令:
-
-```python
-# parser 注册
-status_get_parser = subparsers.add_parser("get-chapter-status")
-status_get_parser.add_argument("--chapter", type=int, required=True)
-
-status_set_parser = subparsers.add_parser("set-chapter-status")
-status_set_parser.add_argument("--chapter", type=int, required=True)
-status_set_parser.add_argument("--status", required=True,
-    choices=["chapter_drafted", "chapter_reviewed", "chapter_committed"])
-```
-
-```python
-# 命令处理
-elif args.command == "get-chapter-status":
-    manager._load_state()
-    status = manager.get_chapter_status(args.chapter)
-    emit_success({"chapter": args.chapter, "status": status},
-                 message="chapter_status")
-
-elif args.command == "set-chapter-status":
-    manager._load_state()
-    manager.set_chapter_status(args.chapter, args.status)
-    emit_success({"chapter": args.chapter, "status": args.status},
-                 message="chapter_status_set")
-```
-
-- [ ] **Step 6: 提交**
-
-```bash
-cd "D:/wk/novel skill/webnovel-writer"
-git add webnovel-writer/scripts/data_modules/state_manager.py webnovel-writer/scripts/data_modules/tests/test_chapter_status.py
-git commit -m "$(cat <<'EOF'
-feat: 章节状态模型——chapter_drafted/reviewed/committed
-
-单调递进状态机,支持 CLI 查询和设置。
-用于 v6 Write 流程的充分性闸门。
-EOF
-)"
-```
-
----
-
-## Task 6: 修正 spec 与实现不一致
-
-**Files:**
-- Modify: `docs/superpowers/specs/2026-04-02-harness-v6-design.md`
-
-- [ ] **Step 1: 修正 memory contract v0 描述**
-
-将 4.5 节中的:
-
-```
-**v0(目标接口,供 Phase 1/2A prompt 重构面向编程):**
-
-> 注:v0 接口当前尚未实现,现有实现通过 `webnovel.py` CLI 子命令(如 `state get-entity`、`index get-recent-state-changes`)充当。Phase 3 将这些 CLI 收口为以下统一契约。
-```
-
-改为:
-
-```
-**v0(已实现,当前冻结):**
-
-> v0 接口已通过 `memory_contract.py`(Protocol + 类型)和 `memory_contract_adapter.py`(适配器)实现,CLI 入口为 `webnovel.py memory-contract` 子命令。context-agent 已在使用。
-```
-
-- [ ] **Step 2: 更新 Phase 表状态**
-
-在实施路径表格中,Phase 3 和 Phase 4 的状态列加注"已完成":
-
-```markdown
-| Phase 3 | 记忆模块接口契约设计 | 无 | **✅ 已完成** |
-| Phase 4 | Context-agent research 模式重构 | Phase 3(契约) | **✅ 已完成** |
-```
-
-- [ ] **Step 3: 更新版本号**
-
-```
-> 状态:草案 v7(v6 + 实现对齐修正)
-```
-
-- [ ] **Step 4: 提交**
-
-```bash
-cd "D:/wk/novel skill/webnovel-writer"
-git add docs/superpowers/specs/2026-04-02-harness-v6-design.md
-git commit -m "docs: spec v7——修正 memory contract 状态,Phase 3/4 标记已完成"
-```
-
----
-
-## Task 7: 全量回归验证 + 退出标准检查
-
-**Files:** 无新文件
-
-- [ ] **Step 1: 全量测试**
-
-Run: `cd "D:/wk/novel skill/webnovel-writer" && python -m pytest webnovel-writer/scripts --tb=short`
-Expected: 全部通过
-
-- [ ] **Step 2: 退出标准逐条验证**
-
-```bash
-cd "D:/wk/novel skill/webnovel-writer"
-
-# 标准 1: 无旧 checker 运行时引用
-echo "--- 标准 1: 旧 checker 引用 ---"
-grep -rn "continuity-checker\|setting-checker\|ooc-checker\|high-point-checker\|pacing-checker\|reader-pull-checker" \
-  webnovel-writer/skills/ webnovel-writer/agents/ webnovel-writer/scripts/*.py \
-  --include="*.md" --include="*.py" || echo "PASS: 无旧 checker 引用"
-
-# 标准 2: 审查路径唯一
-echo "--- 标准 2: 审查路径 ---"
-grep -l "reviewer" webnovel-writer/skills/webnovel-write/SKILL.md webnovel-writer/skills/webnovel-review/SKILL.md && echo "PASS: 均走 reviewer"
-
-# 标准 3: workflow_manager 移除
-echo "--- 标准 3: workflow_manager ---"
-test ! -f webnovel-writer/scripts/workflow_manager.py && echo "PASS: 文件已删除" || echo "FAIL: 文件仍存在"
-
-# 标准 4: legacy 术语(运行时路径)
-echo "--- 标准 4: legacy 术语 ---"
-grep -rn "timeline_gate\|_aggregate_checker\|_normalize_checker\|_build_timeline_gate" \
-  webnovel-writer/scripts/ --include="*.py" | grep -v "test_" | grep -v "__pycache__" || echo "PASS: 运行时无 legacy"
-
-# 标准 6: 章节状态模型
-echo "--- 标准 6: 章节状态 CLI ---"
-cd webnovel-writer/scripts && python -X utf8 data_modules/state_manager.py --help 2>&1 | grep -q "get-chapter-status" && echo "PASS: CLI 已注册" || echo "FAIL: CLI 未注册"
-```
-
-- [ ] **Step 3: 如有失败项,修复后提交**
-
-```bash
-cd "D:/wk/novel skill/webnovel-writer"
-git add -A
-git commit -m "chore: v6 迁移退出标准验证通过"
-```

+ 0 - 1324
docs/archive/superpowers/plans/2026-04-12-story-system-phase1-contract-seed.md

@@ -1,1324 +0,0 @@
-# Story System Phase 1 Contract Seed Implementation Plan
-
-> **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:** 在不破坏现有 `reference_search.py`、`context_manager.py` 与写作主流程的前提下,落地 Story System Phase 1 合同种子层:`题材与调性推理.csv`、最小 `MASTER_SETTING` / `CHAPTER_BRIEF` / `anti_patterns` 持久化,以及 `context_manager` 的合同读取入口。
-
-**Architecture:** 采用“数据层 -> 合同聚合器 -> `.story-system/` 持久化 -> runtime 注入”的四段式。Phase 1 只建立最小合同真源,不引入 `VOLUME_BRIEF`、`REVIEW_CONTRACT`、`CHAPTER_COMMIT`,并继续允许 `genre-profiles.md` 作为回退源存在。
-
-**Tech Stack:** Python 3.13, argparse, pytest, CSV(UTF-8 with BOM), Markdown + JSON 合同文件, unified CLI `webnovel.py`
-
-**Spec:** `docs/superpowers/specs/2026-04-12-story-system-evolution-spec.md`
-
-**Companion Spec:** `docs/superpowers/specs/2026-04-12-story-system-pro-max-retrofit-spec.md`
-
----
-
-## Scope Split
-
-这份 plan **只覆盖 evolution spec 的 Phase 1**,原因如下:
-
-- `Phase 2` 以后会引入 `VOLUME_BRIEF`、`REVIEW_CONTRACT`、大纲履约 diff、review blocking rules。
-- `Phase 3` 会新增 `CHAPTER_COMMIT` 与四类 projection writers。
-- `Phase 4` 会新增 canonical event log。
-- `Phase 5` 才能安全降级旧链路。
-
-把这些内容塞进一份实现计划会导致:
-
-- 文件责任边界失真
-- TDD 粒度失控
-- 文档与代码修改无法形成可验证的阶段产物
-
-因此本计划的退出标准固定为:
-
-1. `PROJECT_ROOT/.story-system/` 能生成最小 `MASTER_SETTING`、`chapter_XXX`、`anti_patterns`
-2. `context_manager` 能读取并注入 `story_contract` section
-3. 旧 `genre_profile` 仍可作为回退层保留
-4. 文档已明确 Phase 1 的路径语义、schema 与使用方式
-
-后续应另写三份计划:
-
-- `Phase 2 Contract-First Runtime`
-- `Phase 3 Chapter Commit Chain`
-- `Phase 4 Event Log + Override Ledger`
-
-文档边界也在本阶段定死:
-
-- `README.md` 只新增 `Story System` 一级段落与基础目录说明
-- 后续 Phase 2/3/4 只能在既有段落下追加,不重写整段结构
-
----
-
-## File Structure
-
-### 要创建的文件
-
-- `webnovel-writer/references/csv/题材与调性推理.csv`
-- `webnovel-writer/scripts/data_modules/story_contracts.py`
-- `webnovel-writer/scripts/data_modules/story_system_engine.py`
-- `webnovel-writer/scripts/data_modules/tests/test_story_contracts.py`
-- `webnovel-writer/scripts/data_modules/tests/test_story_system_engine.py`
-- `webnovel-writer/scripts/data_modules/tests/test_story_system_cli.py`
-- `webnovel-writer/scripts/story_system.py`
-- `docs/architecture/story-system-phase1.md`
-
-### 要修改的文件
-
-- `webnovel-writer/references/csv/README.md`
-- `webnovel-writer/references/csv/桥段套路.csv`
-- `webnovel-writer/scripts/data_modules/config.py`
-- `webnovel-writer/scripts/data_modules/context_manager.py`
-- `webnovel-writer/scripts/data_modules/tests/test_context_manager.py`
-- `webnovel-writer/scripts/extract_chapter_context.py`
-- `webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py`
-- `webnovel-writer/scripts/data_modules/webnovel.py`
-- `webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py`
-- `README.md`
-- `docs/architecture/overview.md`
-- `docs/guides/commands.md`
-- `docs/superpowers/README.md`
-
-### 文件职责
-
-- `story_contracts.py`:合同路径、merge 规则、JSON/Markdown 持久化、marker 安全更新
-- `story_system_engine.py`:题材路由、多表检索编排、anti-pattern 聚合、最小合同字典构造
-- `story_system.py`:CLI 入口,负责 `query -> build -> render -> persist`
-- `context_manager.py`:读取 `MASTER_SETTING` / `chapter_XXX` / `anti_patterns` 并注入 `story_contract` section
-- `extract_chapter_context.py`:把 `story_contract` 纳入可视化文本/JSON 提取结果
-- `docs/architecture/story-system-phase1.md`:Phase 1 合同 schema、目录结构、覆盖规则、迁移说明
-
----
-
-## Task 1: 建立合同路径层与最小 merge 规则
-
-**Files:**
-- Create: `webnovel-writer/scripts/data_modules/story_contracts.py`
-- Create: `webnovel-writer/scripts/data_modules/tests/test_story_contracts.py`
-- Modify: `webnovel-writer/scripts/data_modules/config.py`
-
-- [ ] **Step 1: 先写 `story_contracts` 的失败测试**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_story_contracts.py
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-from data_modules.config import DataModulesConfig
-from data_modules.story_contracts import StoryContractPaths, merge_contract_layers, merge_anti_patterns
-
-
-def test_story_contract_paths_live_under_project_root(tmp_path):
-    cfg = DataModulesConfig.from_project_root(tmp_path)
-    paths = StoryContractPaths.from_project_root(cfg.project_root)
-
-    assert paths.root == tmp_path / ".story-system"
-    assert paths.master_json == tmp_path / ".story-system" / "MASTER_SETTING.json"
-    assert paths.anti_patterns_json == tmp_path / ".story-system" / "anti_patterns.json"
-    assert paths.chapter_json(1) == tmp_path / ".story-system" / "chapters" / "chapter_001.json"
-
-
-def test_merge_contract_layers_respects_lock_categories():
-    master = {
-        "locked": {
-            "core_tone": "冷硬升级",
-            "golden_finger_limit": "每天只能触发一次",
-        },
-        "append_only": {
-            "anti_patterns": ["配角连续抢戏超过 300 字"],
-        },
-        "override_allowed": {
-            "scene_focus": "拍卖会打脸",
-        },
-    }
-    chapter = {
-        "locked": {
-            "core_tone": "轻喜日常",
-        },
-        "append_only": {
-            "anti_patterns": ["本章禁止解释性旁白"],
-        },
-        "override_allowed": {
-            "scene_focus": "退婚当场反杀",
-        },
-    }
-
-    merged = merge_contract_layers(master, chapter)
-
-    assert merged["locked"]["core_tone"] == "冷硬升级"
-    assert merged["locked"]["golden_finger_limit"] == "每天只能触发一次"
-    assert merged["append_only"]["anti_patterns"] == [
-        "配角连续抢戏超过 300 字",
-        "本章禁止解释性旁白",
-    ]
-    assert merged["override_allowed"]["scene_focus"] == "退婚当场反杀"
-
-
-def test_merge_anti_patterns_deduplicates_by_text():
-    rows = merge_anti_patterns(
-        [{"text": "打脸节奏不能缺补刀", "source_table": "题材与调性推理", "source_id": "GR-001"}],
-        [{"text": "打脸节奏不能缺补刀", "source_table": "爽点与节奏", "source_id": "PA-002"}],
-    )
-
-    assert [item["text"] for item in rows] == ["打脸节奏不能缺补刀"]
-    assert rows[0]["source_table"] == "题材与调性推理"
-```
-
-- [ ] **Step 2: 运行测试,确认是正确的红灯**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_story_contracts.py -q --no-cov`
-
-Expected: `ModuleNotFoundError: No module named 'data_modules.story_contracts'`
-
-- [ ] **Step 3: 在 `config.py` 增加 `.story-system` 路径属性**
-
-```python
-# webnovel-writer/scripts/data_modules/config.py
-@property
-def story_system_dir(self) -> Path:
-    return self.project_root / ".story-system"
-
-@property
-def story_system_chapters_dir(self) -> Path:
-    return self.story_system_dir / "chapters"
-
-@property
-def story_system_master_json(self) -> Path:
-    return self.story_system_dir / "MASTER_SETTING.json"
-
-@property
-def story_system_anti_patterns_json(self) -> Path:
-    return self.story_system_dir / "anti_patterns.json"
-```
-
-- [ ] **Step 4: 实现 `StoryContractPaths`、merge 规则与 marker 更新工具**
-
-```python
-# webnovel-writer/scripts/data_modules/story_contracts.py
-from __future__ import annotations
-
-import json
-from dataclasses import dataclass
-from pathlib import Path
-from typing import Any, Dict, Iterable, List
-
-
-MARKER_BEGIN = "<!-- STORY-SYSTEM:BEGIN -->"
-MARKER_END = "<!-- STORY-SYSTEM:END -->"
-
-
-@dataclass(frozen=True)
-class StoryContractPaths:
-    project_root: Path
-
-    @classmethod
-    def from_project_root(cls, project_root: str | Path) -> "StoryContractPaths":
-        return cls(Path(project_root).expanduser().resolve())
-
-    @property
-    def root(self) -> Path:
-        return self.project_root / ".story-system"
-
-    @property
-    def chapters_dir(self) -> Path:
-        return self.root / "chapters"
-
-    @property
-    def master_json(self) -> Path:
-        return self.root / "MASTER_SETTING.json"
-
-    @property
-    def anti_patterns_json(self) -> Path:
-        return self.root / "anti_patterns.json"
-
-    def chapter_json(self, chapter: int) -> Path:
-        return self.chapters_dir / f"chapter_{chapter:03d}.json"
-
-
-def merge_contract_layers(master: Dict[str, Any], chapter: Dict[str, Any] | None) -> Dict[str, Any]:
-    chapter = chapter or {}
-    return {
-        "locked": dict(master.get("locked") or {}),
-        "append_only": _merge_append_only(master.get("append_only") or {}, chapter.get("append_only") or {}),
-        "override_allowed": {
-            **(master.get("override_allowed") or {}),
-            **(chapter.get("override_allowed") or {}),
-        },
-    }
-
-
-def merge_anti_patterns(*groups: Iterable[Dict[str, Any]]) -> List[Dict[str, Any]]:
-    seen: set[str] = set()
-    merged: List[Dict[str, Any]] = []
-    for group in groups:
-        for row in group:
-            text = str(row.get("text") or "").strip()
-            if not text or text in seen:
-                continue
-            seen.add(text)
-            merged.append(dict(row))
-    return merged
-
-
-def read_json_if_exists(path: Path) -> Any | None:
-    if not path.is_file():
-        return None
-    try:
-        return json.loads(path.read_text(encoding="utf-8"))
-    except json.JSONDecodeError as exc:
-        raise ValueError(f"Bad JSON in {path}") from exc
-```
-
-同时补一个统一读取约定,供后续 Phase 2-4 复用:
-
-- `read_json_if_exists(path) -> dict | list | None`
-- 文件不存在时返回 `None`
-- JSON 格式错误时抛带路径的 `ValueError`
-- 本阶段先在 `story_contracts.py` 落这个 helper,后续 projection / runtime builder 不再各写一套吞错逻辑
-
-- [ ] **Step 5: 回跑测试,确认基础层转绿**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_story_contracts.py -q --no-cov`
-
-Expected: 通过
-
-- [ ] **Step 6: 提交**
-
-```bash
-git add webnovel-writer/scripts/data_modules/config.py \
-        webnovel-writer/scripts/data_modules/story_contracts.py \
-        webnovel-writer/scripts/data_modules/tests/test_story_contracts.py
-git commit -m "feat: add story contract path helpers and merge rules"
-```
-
----
-
-## Task 2: 落地题材路由表与合同聚合器
-
-**Files:**
-- Create: `webnovel-writer/references/csv/题材与调性推理.csv`
-- Create: `webnovel-writer/scripts/data_modules/story_system_engine.py`
-- Create: `webnovel-writer/scripts/data_modules/tests/test_story_system_engine.py`
-- Modify: `webnovel-writer/references/csv/README.md`
-- Modify: `webnovel-writer/references/csv/桥段套路.csv`
-
-- [ ] **Step 1: 先写 `story_system_engine` 的失败测试**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_story_system_engine.py
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-import csv
-
-from data_modules.story_system_engine import StorySystemEngine
-
-
-def _write_csv(path, headers, rows):
-    with open(path, "w", encoding="utf-8-sig", newline="") as f:
-        writer = csv.DictWriter(f, fieldnames=headers)
-        writer.writeheader()
-        writer.writerows(rows)
-
-
-def test_story_system_routes_explicit_genre_and_collects_anti_patterns(tmp_path):
-    csv_dir = tmp_path / "csv"
-    csv_dir.mkdir()
-
-    _write_csv(
-        csv_dir / "题材与调性推理.csv",
-        [
-            "编号", "适用技能", "分类", "层级", "关键词", "意图与同义词", "适用题材",
-            "大模型指令", "核心摘要", "详细展开", "题材/流派", "题材别名", "核心调性",
-            "节奏策略", "强制禁忌/毒点", "推荐基础检索表", "推荐动态检索表", "默认查询词",
-        ],
-        [
-            {
-                "编号": "GR-001",
-                "适用技能": "write|plan",
-                "分类": "题材路由",
-                "层级": "知识补充",
-                "关键词": "玄幻退婚流|退婚打脸",
-                "意图与同义词": "退婚流|废材逆袭",
-                "适用题材": "玄幻",
-                "大模型指令": "先给压抑,再给爆发兑现。",
-                "核心摘要": "玄幻退婚流需要耻辱起手和强兑现。",
-                "详细展开": "",
-                "题材/流派": "玄幻退婚流",
-                "题材别名": "退婚流|废材逆袭",
-                "核心调性": "压抑蓄势后爆裂反击",
-                "节奏策略": "前压后爆,三章内必须首个反打",
-                "强制禁忌/毒点": "打脸节奏不能缺最后一拍补刀|配角不能压过主角兑现",
-                "推荐基础检索表": "命名规则|人设与关系|金手指与设定",
-                "推荐动态检索表": "桥段套路|爽点与节奏|场景写法",
-                "默认查询词": "退婚|打脸|废材逆袭",
-            }
-        ],
-    )
-
-    _write_csv(
-        csv_dir / "桥段套路.csv",
-        ["编号", "适用技能", "分类", "层级", "关键词", "适用题材", "核心摘要", "桥段名称", "忌讳写法"],
-        [
-            {
-                "编号": "TR-001",
-                "适用技能": "write",
-                "分类": "桥段",
-                "层级": "知识补充",
-                "关键词": "退婚|打脸",
-                "适用题材": "玄幻",
-                "核心摘要": "退婚现场要给足羞辱和反击空间",
-                "桥段名称": "退婚反击",
-                "忌讳写法": "主角还没反打就被配角替他出手",
-            }
-        ],
-    )
-
-    _write_csv(
-        csv_dir / "爽点与节奏.csv",
-        ["编号", "适用技能", "分类", "层级", "关键词", "适用题材", "核心摘要", "常见崩盘误区", "节奏类型"],
-        [
-            {
-                "编号": "PA-001",
-                "适用技能": "write",
-                "分类": "节奏",
-                "层级": "知识补充",
-                "关键词": "打脸|兑现",
-                "适用题材": "玄幻",
-                "核心摘要": "兑现必须补刀",
-                "常见崩盘误区": "打脸收尾太软,没有读者情绪补刀",
-                "节奏类型": "爆发期",
-            }
-        ],
-    )
-
-    engine = StorySystemEngine(csv_dir=csv_dir)
-    contract = engine.build(query="玄幻退婚流", genre=None, chapter=None)
-
-    assert contract["master_setting"]["route"]["primary_genre"] == "玄幻退婚流"
-    assert contract["master_setting"]["master_constraints"]["core_tone"] == "压抑蓄势后爆裂反击"
-    assert "命名规则" in contract["master_setting"]["route"]["recommended_base_tables"]
-    assert {
-        item["text"] for item in contract["anti_patterns"]
-    } >= {
-        "打脸节奏不能缺最后一拍补刀",
-        "主角还没反打就被配角替他出手",
-        "打脸收尾太软,没有读者情绪补刀",
-    }
-
-
-def test_story_system_falls_back_to_explicit_genre(tmp_path):
-    csv_dir = tmp_path / "csv"
-    csv_dir.mkdir()
-
-    _write_csv(
-        csv_dir / "题材与调性推理.csv",
-        [
-            "编号", "适用技能", "分类", "层级", "关键词", "意图与同义词", "适用题材",
-            "大模型指令", "核心摘要", "详细展开", "题材/流派", "题材别名", "核心调性",
-            "节奏策略", "强制禁忌/毒点", "推荐基础检索表", "推荐动态检索表", "默认查询词",
-        ],
-        [],
-    )
-
-    engine = StorySystemEngine(csv_dir=csv_dir)
-    contract = engine.build(query="压抑一点,后面爆", genre="现言", chapter=None)
-
-    assert contract["master_setting"]["route"]["primary_genre"] == "现言"
-    assert contract["master_setting"]["route"]["route_source"] == "explicit_genre_fallback"
-    assert contract["master_setting"]["route"]["recommended_dynamic_tables"] == ["桥段套路", "爽点与节奏", "场景写法"]
-```
-
-- [ ] **Step 2: 跑红灯,确认缺的是聚合器而不是测试本身**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_story_system_engine.py -q --no-cov`
-
-Expected: `ModuleNotFoundError: No module named 'data_modules.story_system_engine'`
-
-- [ ] **Step 3: 实现 `StorySystemEngine` 与 anti-pattern 归一化映射**
-
-```python
-# webnovel-writer/scripts/data_modules/story_system_engine.py
-from __future__ import annotations
-
-import csv
-from pathlib import Path
-from typing import Any, Dict, List, Optional
-
-from reference_search import search as search_reference
-
-from .story_contracts import merge_anti_patterns
-
-
-ANTI_PATTERN_SOURCE_FIELDS = {
-    "场景写法": ["反面写法"],
-    "写作技法": ["常见误区"],
-    "爽点与节奏": ["常见崩盘误区"],
-    "人设与关系": ["忌讳写法"],
-    "桥段套路": ["忌讳写法"],
-    "题材与调性推理": ["强制禁忌/毒点"],
-}
-
-
-class StorySystemEngine:
-    def __init__(self, csv_dir: str | Path):
-        self.csv_dir = Path(csv_dir)
-
-    def build(self, query: str, genre: Optional[str], chapter: Optional[int]) -> Dict[str, Any]:
-        route = self._route(query=query, genre=genre)
-        base_context = self._collect_tables(query, route["recommended_base_tables"], genre=route["genre_filter"], top_k=1)
-        dynamic_context = self._collect_tables(query, route["recommended_dynamic_tables"], genre=route["genre_filter"], top_k=2)
-        source_trace = route["source_trace"] + self._build_source_trace(base_context, dynamic_context)
-        anti_patterns = merge_anti_patterns(
-            route["route_anti_patterns"],
-            self._extract_anti_patterns(base_context),
-            self._extract_anti_patterns(dynamic_context),
-        )
-        return {
-            "meta": {"query": query, "chapter": chapter, "explicit_genre": genre or ""},
-            "master_setting": {
-                "meta": {
-                    "schema_version": "story-system/v1",
-                    "contract_type": "MASTER_SETTING",
-                    "generator_version": "phase1",
-                    "query": query,
-                },
-                "route": route["meta"],
-                "master_constraints": {
-                    "core_tone": route["core_tone"],
-                    "pacing_strategy": route["pacing_strategy"],
-                },
-                "base_context": base_context,
-                "source_trace": source_trace,
-                "override_policy": {
-                    "locked": ["route.primary_genre", "master_constraints.core_tone"],
-                    "append_only": ["anti_patterns"],
-                    "override_allowed": [],
-                },
-            },
-            "chapter_brief": (
-                {
-                    "meta": {
-                        "schema_version": "story-system/v1",
-                        "contract_type": "CHAPTER_BRIEF",
-                        "generator_version": "phase1",
-                        "chapter": chapter,
-                    },
-                    "override_allowed": {
-                        "chapter_focus": self._suggest_chapter_focus(query, dynamic_context),
-                    },
-                    "dynamic_context": dynamic_context,
-                    "source_trace": source_trace,
-                }
-                if chapter is not None
-                else None
-            ),
-            "anti_patterns": anti_patterns,
-        }
-
-    def _route(self, query: str, genre: Optional[str]) -> Dict[str, Any]:
-        route_rows = self._load_csv_rows("题材与调性推理")
-        query_text = self._normalize_text(" ".join([query or "", genre or ""]))
-
-        # 命中顺序固定:关键词/同义词命中 -> 显式 genre 回退 -> 默认首行回退
-        matched = None
-        for row in route_rows:
-            aliases = self._split_multi_value(row.get("关键词")) + self._split_multi_value(row.get("意图与同义词")) + self._split_multi_value(row.get("题材别名"))
-            if any(alias and alias in query_text for alias in aliases):
-                matched = row
-                route_source = "keyword_or_alias_match"
-                break
-        if matched is None and genre:
-            matched = self._fallback_row_for_genre(route_rows, genre)
-            route_source = "explicit_genre_fallback"
-        if matched is None and route_rows:
-            matched = route_rows[0]
-            route_source = "default_seed_fallback"
-        if matched is None:
-            return self._empty_route(query=query, genre=genre)
-
-        primary_genre = str(matched.get("题材/流派") or genre or "").strip()
-        genre_filter = str(matched.get("适用题材") or genre or primary_genre).strip()
-        return {
-            "meta": {
-                "primary_genre": primary_genre,
-                "route_source": route_source,
-                "genre_filter": genre_filter,
-                "recommended_base_tables": self._split_multi_value(matched.get("推荐基础检索表")),
-                "recommended_dynamic_tables": self._split_multi_value(matched.get("推荐动态检索表")),
-            },
-            "core_tone": str(matched.get("核心调性") or "").strip(),
-            "pacing_strategy": str(matched.get("节奏策略") or "").strip(),
-            "route_anti_patterns": self._extract_route_anti_patterns(matched),
-            "recommended_base_tables": self._split_multi_value(matched.get("推荐基础检索表")),
-            "recommended_dynamic_tables": self._split_multi_value(matched.get("推荐动态检索表")),
-            "genre_filter": genre_filter,
-            "source_trace": [{"table": "题材与调性推理", "id": matched.get("编号", ""), "reason": route_source}],
-        }
-
-    def _collect_tables(self, query: str, tables: List[str], genre: str, top_k: int) -> List[Dict[str, Any]]:
-        rows: List[Dict[str, Any]] = []
-        for table_name in tables:
-            result = search_reference(
-                csv_dir=self.csv_dir,
-                skill="write",
-                query=query,
-                table=table_name,
-                genre=genre,
-                max_results=top_k,
-            )
-            rows.extend(result.get("data", {}).get("results", []))
-        return rows
-
-    def _extract_anti_patterns(self, rows: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
-        extracted: List[Dict[str, Any]] = []
-        for row in rows:
-            table_name = str(row.get("_table") or "")
-            for field_name in ANTI_PATTERN_SOURCE_FIELDS.get(table_name, []):
-                for text in self._split_multi_value(row.get(field_name)):
-                    extracted.append({"text": text, "source_table": table_name, "source_id": row.get("编号", "")})
-        return extracted
-
-    def _suggest_chapter_focus(self, query: str, dynamic_rows: List[Dict[str, Any]]) -> str:
-        for row in dynamic_rows:
-            summary = str(row.get("核心摘要") or "").strip()
-            if summary:
-                return summary
-        return query
-
-    def _build_source_trace(self, *groups: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
-        trace: List[Dict[str, Any]] = []
-        for group in groups:
-            for row in group:
-                trace.append(
-                    {
-                        "table": row.get("_table", ""),
-                        "id": row.get("编号", ""),
-                        "summary": row.get("核心摘要", ""),
-                    }
-                )
-        return trace
-
-    def _load_csv_rows(self, table_name: str) -> List[Dict[str, Any]]:
-        csv_path = self.csv_dir / f"{table_name}.csv"
-        if not csv_path.is_file():
-            return []
-        with csv_path.open("r", encoding="utf-8-sig", newline="") as f:
-            return list(csv.DictReader(f))
-
-    def _normalize_text(self, text: str) -> str:
-        return str(text or "").strip().lower()
-
-    def _split_multi_value(self, raw: Any) -> List[str]:
-        return [item.strip() for item in str(raw or "").split("|") if item.strip()]
-
-    def _fallback_row_for_genre(self, rows: List[Dict[str, Any]], genre: str) -> Dict[str, Any] | None:
-        genre = str(genre or "").strip()
-        for row in rows:
-            if genre and genre in self._split_multi_value(row.get("适用题材")):
-                return row
-        return None
-
-    def _extract_route_anti_patterns(self, row: Dict[str, Any]) -> List[Dict[str, Any]]:
-        return [
-            {"text": text, "source_table": "题材与调性推理", "source_id": row.get("编号", "")}
-            for text in self._split_multi_value(row.get("强制禁忌/毒点"))
-        ]
-
-    def _empty_route(self, query: str, genre: Optional[str]) -> Dict[str, Any]:
-        fallback_genre = str(genre or "未命中题材").strip()
-        return {
-            "meta": {
-                "primary_genre": fallback_genre,
-                "route_source": "empty_csv_fallback",
-                "genre_filter": fallback_genre,
-                "recommended_base_tables": ["命名规则", "人设与关系"],
-                "recommended_dynamic_tables": ["桥段套路", "爽点与节奏", "场景写法"],
-            },
-            "core_tone": "",
-            "pacing_strategy": "",
-            "route_anti_patterns": [],
-            "recommended_base_tables": ["命名规则", "人设与关系"],
-            "recommended_dynamic_tables": ["桥段套路", "爽点与节奏", "场景写法"],
-            "genre_filter": fallback_genre,
-            "source_trace": [{"table": "题材与调性推理", "id": "", "reason": f"empty_route_for:{query}"}],
-        }
-```
-
-这里显式约束测试策略:
-
-- 直接使用 `tmp_path / csv` 喂给 `reference_search.search()`
-- 不需要 monkeypatch 搜索函数
-- 如果后续 `reference_search.search()` 签名变化,优先同步这里的聚合器封装,不在测试层绕过真实接口
-
-- [ ] **Step 4: 落地真实 CSV 数据和字段文档**
-
-`题材与调性推理.csv` 至少先录入 3 条手工种子数据,覆盖:
-
-```csv
-编号,适用技能,分类,层级,关键词,意图与同义词,适用题材,大模型指令,核心摘要,详细展开,题材/流派,题材别名,核心调性,节奏策略,主冲突模板,必选爽点,强制禁忌/毒点,推荐基础检索表,推荐动态检索表,基础检索权重,动态检索权重,默认查询词
-GR-001,write|plan,题材路由,知识补充,玄幻退婚流|退婚打脸,退婚流|废材逆袭,玄幻,先压后爆,首个反打必须有羞辱反弹,玄幻退婚流需要耻辱起手和强兑现,,玄幻退婚流,退婚流|废材逆袭,压抑蓄势后爆裂反击,前压后爆,三章内必须首个反打,退婚羞辱→资源争夺→当场反杀,当众反打|身份翻盘,打脸节奏不能缺最后一拍补刀|配角不能压过主角兑现,命名规则|人设与关系|金手指与设定,桥段套路|爽点与节奏|场景写法,命名规则:1.0|人设与关系:0.9,桥段套路:1.0|爽点与节奏:0.9,退婚|打脸|废材逆袭
-GR-002,write|plan,题材路由,知识补充,规则动物园|怪谈副本,规则怪谈|动物园规则,悬疑|轻小说,规则先立死,代价必须兑现,规则动物园重在规则压迫与试错代价,,规则动物园,规则怪谈|动物园规则,诡异压迫与冷感观察,每章至少一个规则验证或误判后果,入园规则→试错牺牲→发现隐藏规则,规则反转|错误成本兑现,规则解释过量|系统提前剧透真相,命名规则|人设与关系,桥段套路|场景写法|写作技法,命名规则:0.7|人设与关系:0.8,场景写法:1.0|写作技法:0.9,规则|动物园|副本
-GR-003,write|plan,题材路由,知识补充,压抑后爆|后期翻盘,压抑一点后面爆|前面憋屈后面翻盘,现言|都市,压抑不能空耗,必须绑定后续兑现资产,压抑后爆路线需要持续累积反弹势能,,压抑后爆,前憋后爆|后期翻盘,持续压迫后的集中爆发,每 2-3 章必须补一个可见反抗信号,压迫累积→误判反扑→情绪总兑现,情绪爆点|身份反转,压抑没有收益|委屈全靠旁白硬说,命名规则|人设与关系|写作技法,爽点与节奏|场景写法|桥段套路,写作技法:0.9|人设与关系:0.8,爽点与节奏:1.0|场景写法:0.8,压抑|翻盘|反弹
-```
-
-同时给 `桥段套路.csv` 增加 `忌讳写法` 列,并在 `references/csv/README.md` 新增:
-
-```markdown
-### 题材与调性推理.csv
-
-| 列名 | 说明 |
-|------|------|
-| `题材/流派` | 路由主标签 |
-| `题材别名` | 同义词 / 平台黑话 |
-| `核心调性` | 全局情绪基调 |
-| `节奏策略` | 开局与兑现节奏 |
-| `强制禁忌/毒点` | 题材级绝对红线 |
-| `推荐基础检索表` | 默认基础检索表 |
-| `推荐动态检索表` | 默认动态检索表 |
-```
-
-- [ ] **Step 5: 回跑测试,确认路由 + 聚合契约转绿**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_story_system_engine.py -q --no-cov`
-
-Expected: 通过
-
-- [ ] **Step 6: 提交**
-
-```bash
-git add webnovel-writer/references/csv/题材与调性推理.csv \
-        webnovel-writer/references/csv/桥段套路.csv \
-        webnovel-writer/references/csv/README.md \
-        webnovel-writer/scripts/data_modules/story_system_engine.py \
-        webnovel-writer/scripts/data_modules/tests/test_story_system_engine.py
-git commit -m "feat: add genre routing csv and story system engine"
-```
-
----
-
-## Task 3: 实现 `.story-system` 持久化与统一 CLI 接入
-
-**Files:**
-- Create: `webnovel-writer/scripts/story_system.py`
-- Create: `webnovel-writer/scripts/data_modules/tests/test_story_system_cli.py`
-- Modify: `webnovel-writer/scripts/data_modules/story_contracts.py`
-- Modify: `webnovel-writer/scripts/data_modules/webnovel.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py`
-
-- [ ] **Step 1: 先写 `--persist` 和统一 CLI 转发的失败测试**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_story_system_cli.py
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-import csv
-import json
-import sys
-
-
-def _write_csv(path, headers, rows):
-    with open(path, "w", encoding="utf-8-sig", newline="") as f:
-        writer = csv.DictWriter(f, fieldnames=headers)
-        writer.writeheader()
-        writer.writerows(rows)
-
-
-def test_story_system_persist_writes_master_chapter_and_anti_patterns(tmp_path, monkeypatch):
-    project_root = tmp_path / "book"
-    (project_root / ".webnovel").mkdir(parents=True, exist_ok=True)
-    (project_root / ".webnovel" / "state.json").write_text("{}", encoding="utf-8")
-
-    csv_dir = tmp_path / "csv"
-    csv_dir.mkdir()
-    _write_csv(
-        csv_dir / "题材与调性推理.csv",
-        [
-            "编号", "适用技能", "分类", "层级", "关键词", "意图与同义词", "适用题材",
-            "大模型指令", "核心摘要", "详细展开", "题材/流派", "题材别名", "核心调性",
-            "节奏策略", "强制禁忌/毒点", "推荐基础检索表", "推荐动态检索表", "默认查询词",
-        ],
-        [
-            {
-                "编号": "GR-001",
-                "适用技能": "write",
-                "分类": "题材路由",
-                "层级": "知识补充",
-                "关键词": "玄幻退婚流",
-                "意图与同义词": "退婚流",
-                "适用题材": "玄幻",
-                "大模型指令": "先压后爆",
-                "核心摘要": "退婚起手",
-                "详细展开": "",
-                "题材/流派": "玄幻退婚流",
-                "题材别名": "退婚流",
-                "核心调性": "先压后爆",
-                "节奏策略": "三章内反打",
-                "强制禁忌/毒点": "打脸不能软收尾",
-                "推荐基础检索表": "命名规则",
-                "推荐动态检索表": "桥段套路",
-                "默认查询词": "退婚|打脸",
-            }
-        ],
-    )
-    _write_csv(csv_dir / "命名规则.csv", ["编号", "适用技能", "分类", "层级", "关键词", "适用题材", "核心摘要"], [])
-    _write_csv(csv_dir / "桥段套路.csv", ["编号", "适用技能", "分类", "层级", "关键词", "适用题材", "核心摘要", "忌讳写法"], [])
-
-    from story_system import main
-
-    monkeypatch.setattr(
-        sys,
-        "argv",
-        [
-            "story_system",
-            "玄幻退婚流",
-            "--project-root",
-            str(project_root),
-            "--chapter",
-            "1",
-            "--persist",
-            "--csv-dir",
-            str(csv_dir),
-            "--format",
-            "both",
-        ],
-    )
-    main()
-
-    story_root = project_root / ".story-system"
-    assert (story_root / "MASTER_SETTING.json").is_file()
-    assert (story_root / "MASTER_SETTING.md").is_file()
-    assert (story_root / "anti_patterns.json").is_file()
-    assert (story_root / "chapters" / "chapter_001.json").is_file()
-    assert (story_root / "chapters" / "chapter_001.md").is_file()
-
-    payload = json.loads((story_root / "MASTER_SETTING.json").read_text(encoding="utf-8"))
-    assert payload["route"]["primary_genre"] == "玄幻退婚流"
-
-
-def test_markdown_writer_preserves_manual_notes_outside_markers(tmp_path):
-    from data_modules.story_contracts import write_marked_markdown
-
-    target = tmp_path / "MASTER_SETTING.md"
-    target.write_text(
-        "# 手工说明\n手工备注\n<!-- STORY-SYSTEM:BEGIN -->\n旧内容\n<!-- STORY-SYSTEM:END -->\n",
-        encoding="utf-8",
-    )
-
-    write_marked_markdown(target, "## Auto\n新内容\n")
-
-    text = target.read_text(encoding="utf-8")
-    assert "# 手工说明" in text
-    assert "手工备注" in text
-    assert "## Auto" in text
-    assert "旧内容" not in text
-```
-
-在 `test_webnovel_unified_cli.py` 增加:
-
-```python
-def test_webnovel_story_system_forwards_with_resolved_project_root(monkeypatch, tmp_path):
-    from data_modules import webnovel as cli
-
-    project_root = tmp_path / "book"
-    (project_root / ".webnovel").mkdir(parents=True, exist_ok=True)
-    (project_root / ".webnovel" / "state.json").write_text("{}", encoding="utf-8")
-
-    called = {}
-
-    def _fake_run_script(script_name, argv):
-        called["script_name"] = script_name
-        called["argv"] = argv
-        return 0
-
-    monkeypatch.setattr(cli, "_run_script", _fake_run_script)
-    monkeypatch.setattr(sys, "argv", ["webnovel", "--project-root", str(project_root), "story-system", "玄幻退婚流"])
-
-    cli.main()
-
-    assert called["script_name"] == "story_system.py"
-    assert called["argv"][:2] == ["--project-root", str(project_root.resolve())]
-```
-
-- [ ] **Step 2: 跑红灯**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_story_system_cli.py webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py -q --no-cov`
-
-Expected: 失败于 `No module named 'story_system'` 或 `write_marked_markdown` 未实现
-
-- [ ] **Step 3: 实现持久化写入器与 `story_system.py` CLI**
-
-在 `story_contracts.py` 增补持久化函数。这里明确一个边界:**每个 `.md` 文件只允许一组 `<!-- STORY-SYSTEM:BEGIN/END -->` marker**;如果检测到多组 marker,直接抛 `ValueError`,避免 Phase 2 以后出现局部覆盖残留。
-
-```python
-def write_marked_markdown(path: Path, generated_block: str) -> None:
-    wrapped = f"{MARKER_BEGIN}\n{generated_block.rstrip()}\n{MARKER_END}\n"
-    if path.exists():
-        current = path.read_text(encoding="utf-8")
-        if current.count(MARKER_BEGIN) > 1 or current.count(MARKER_END) > 1:
-            raise ValueError(f"{path} contains multiple STORY-SYSTEM markers")
-        if MARKER_BEGIN in current and MARKER_END in current:
-            before, _, rest = current.partition(MARKER_BEGIN)
-            _, _, after = rest.partition(MARKER_END)
-            path.write_text(f"{before}{wrapped}{after.lstrip()}", encoding="utf-8")
-            return
-    path.write_text(wrapped, encoding="utf-8")
-
-
-def render_master_markdown(master_payload: dict) -> str:
-    route = master_payload.get("route") or {}
-    constraints = master_payload.get("master_constraints") or {}
-    return "\n".join(
-        [
-            "# MASTER_SETTING",
-            f"- 题材:{route.get('primary_genre', '')}",
-            f"- 调性:{constraints.get('core_tone', '')}",
-            f"- 节奏:{constraints.get('pacing_strategy', '')}",
-        ]
-    )
-
-
-def render_anti_patterns_markdown(anti_patterns: list[dict]) -> str:
-    lines = ["# ANTI_PATTERNS"]
-    for row in anti_patterns:
-        lines.append(f"- {row.get('text', '')}")
-    return "\n".join(lines)
-
-
-def render_chapter_markdown(chapter_payload: dict) -> str:
-    focus = (chapter_payload.get("override_allowed") or {}).get("chapter_focus", "")
-    return "\n".join(
-        [
-            f"# CHAPTER_{int(chapter_payload['meta']['chapter']):03d}",
-            f"- 章节焦点:{focus}",
-        ]
-    )
-
-
-def persist_story_seed(project_root: Path, master_payload: dict, chapter_payload: dict | None, anti_patterns: list[dict]) -> None:
-    paths = StoryContractPaths.from_project_root(project_root)
-    paths.root.mkdir(parents=True, exist_ok=True)
-    paths.chapters_dir.mkdir(parents=True, exist_ok=True)
-    write_json(paths.master_json, master_payload)
-    write_json(paths.anti_patterns_json, anti_patterns)
-    write_marked_markdown(paths.master_json.with_suffix(".md"), render_master_markdown(master_payload))
-    write_marked_markdown(paths.anti_patterns_json.with_suffix(".md"), render_anti_patterns_markdown(anti_patterns))
-    if chapter_payload is not None:
-        chapter_num = int(chapter_payload["meta"]["chapter"])
-        write_json(paths.chapter_json(chapter_num), chapter_payload)
-        write_marked_markdown(paths.chapter_json(chapter_num).with_suffix(".md"), render_chapter_markdown(chapter_payload))
-```
-
-新增 CLI 入口:
-
-```python
-# webnovel-writer/scripts/story_system.py
-def main() -> None:
-    parser = argparse.ArgumentParser(description="Story System 聚合器")
-    parser.add_argument("query", help="题材描述或当前意图")
-    parser.add_argument("--project-root", help="书项目根目录或工作区根目录")
-    parser.add_argument("--genre", default="", help="显式题材")
-    parser.add_argument("--chapter", type=int, default=0, help="章节号")
-    parser.add_argument("--persist", action="store_true", help="写入 PROJECT_ROOT/.story-system/")
-    parser.add_argument("--format", choices=["json", "markdown", "both"], default="json")
-    parser.add_argument("--csv-dir", default="", help="测试时覆写 CSV 目录")
-    args = parser.parse_args()
-
-    project_root = resolve_project_root(args.project_root) if args.project_root else resolve_project_root()
-    csv_dir = Path(args.csv_dir) if args.csv_dir else Path(__file__).resolve().parent.parent / "references" / "csv"
-
-    engine = StorySystemEngine(csv_dir=csv_dir)
-    payload = engine.build(query=args.query, genre=args.genre or None, chapter=args.chapter or None)
-    if args.persist:
-        persist_story_seed(project_root, payload["master_setting"], payload.get("chapter_brief"), payload["anti_patterns"])
-```
-
-- [ ] **Step 4: 在统一 CLI `webnovel.py` 中挂接 `story-system`**
-
-```python
-# webnovel-writer/scripts/data_modules/webnovel.py
-p_story_system = sub.add_parser("story-system", help="转发到 story_system.py")
-p_story_system.add_argument("args", nargs=argparse.REMAINDER)
-
-# main() 路由分支
-elif args.tool == "story-system":
-    forward_args = ["--project-root", str(project_root), *(_strip_project_root_args(args.args))]
-    raise SystemExit(_run_script("story_system.py", forward_args))
-```
-
-- [ ] **Step 5: 回跑测试,确认持久化与 CLI 契约转绿**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_story_system_cli.py webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py -q --no-cov`
-
-Expected: 通过
-
-- [ ] **Step 6: 提交**
-
-```bash
-git add webnovel-writer/scripts/story_system.py \
-        webnovel-writer/scripts/data_modules/story_contracts.py \
-        webnovel-writer/scripts/data_modules/webnovel.py \
-        webnovel-writer/scripts/data_modules/tests/test_story_system_cli.py \
-        webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py
-git commit -m "feat: persist story system seed contracts and expose unified cli"
-```
-
----
-
-## Task 4: 把合同种子接到 `context_manager` 与 `extract_chapter_context`
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/context_manager.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_context_manager.py`
-- Modify: `webnovel-writer/scripts/extract_chapter_context.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py`
-
-- [ ] **Step 1: 先写合同读取入口的失败测试**
-
-在 `test_context_manager.py` 增加:
-
-```python
-def test_context_manager_includes_story_contract_section_before_genre_profile(temp_project):
-    state = {
-        "genre": "玄幻",
-        "protagonist_state": {"name": "萧炎"},
-        "chapter_meta": {},
-        "disambiguation_warnings": [],
-        "disambiguation_pending": [],
-    }
-    temp_project.state_file.write_text(json.dumps(state, ensure_ascii=False), encoding="utf-8")
-
-    story_root = temp_project.project_root / ".story-system"
-    (story_root / "chapters").mkdir(parents=True, exist_ok=True)
-    (story_root / "MASTER_SETTING.json").write_text(
-        json.dumps(
-            {
-                "meta": {"schema_version": "story-system/v1", "contract_type": "MASTER_SETTING", "query": "玄幻退婚流"},
-                "route": {"primary_genre": "玄幻退婚流"},
-                "master_constraints": {"core_tone": "先压后爆"},
-                "base_context": [],
-                "source_trace": [],
-                "override_policy": {"locked": ["route.primary_genre"], "append_only": ["anti_patterns"], "override_allowed": []},
-            },
-            ensure_ascii=False,
-        ),
-        encoding="utf-8",
-    )
-    (story_root / "anti_patterns.json").write_text(
-        json.dumps([{"text": "打脸不能软收尾"}], ensure_ascii=False),
-        encoding="utf-8",
-    )
-    (story_root / "chapters" / "chapter_001.json").write_text(
-        json.dumps(
-            {
-                "meta": {"schema_version": "story-system/v1", "contract_type": "CHAPTER_BRIEF", "chapter": 1},
-                "override_allowed": {"chapter_focus": "退婚现场反打"},
-            },
-            ensure_ascii=False,
-        ),
-        encoding="utf-8",
-    )
-
-    payload = ContextManager(temp_project).build_context(1, use_snapshot=False, save_snapshot=False)
-
-    assert "story_contract" in payload["sections"]
-    assert payload["sections"]["story_contract"]["content"]["route"]["primary_genre"] == "玄幻退婚流"
-    assert payload["sections"]["story_contract"]["content"]["chapter_brief"]["override_allowed"]["chapter_focus"] == "退婚现场反打"
-    assert ContextManager.SECTION_ORDER.index("story_contract") < ContextManager.SECTION_ORDER.index("genre_profile")
-```
-
-在 `test_extract_chapter_context.py` 增加:
-
-```python
-def test_build_chapter_context_payload_includes_story_contract(tmp_path):
-    from extract_chapter_context import build_chapter_context_payload
-
-    project_root = tmp_path
-    (project_root / ".webnovel").mkdir(parents=True, exist_ok=True)
-    (project_root / ".webnovel" / "state.json").write_text(
-        json.dumps({"protagonist_state": {}, "chapter_meta": {}}, ensure_ascii=False),
-        encoding="utf-8",
-    )
-    story_root = project_root / ".story-system"
-    (story_root / "chapters").mkdir(parents=True, exist_ok=True)
-    (story_root / "MASTER_SETTING.json").write_text(
-        json.dumps({"route": {"primary_genre": "规则动物园"}}, ensure_ascii=False),
-        encoding="utf-8",
-    )
-    (story_root / "anti_patterns.json").write_text(
-        json.dumps([{"text": "不要提前解释真相"}], ensure_ascii=False),
-        encoding="utf-8",
-    )
-    (story_root / "chapters" / "chapter_001.json").write_text(
-        json.dumps({"override_allowed": {"chapter_focus": "游客须知初次触发"}}, ensure_ascii=False),
-        encoding="utf-8",
-    )
-
-    payload = build_chapter_context_payload(project_root, 1)
-
-    assert payload["story_contract"]["route"]["primary_genre"] == "规则动物园"
-    assert payload["story_contract"]["anti_patterns"][0]["text"] == "不要提前解释真相"
-```
-
-- [ ] **Step 2: 跑红灯**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_context_manager.py webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py -q --no-cov`
-
-Expected: `story_contract` section / payload 字段不存在
-
-- [ ] **Step 3: 在 `context_manager.py` 增加合同加载与 section 注入**
-
-```python
-# webnovel-writer/scripts/data_modules/context_manager.py
-SECTION_ORDER = [
-    "core",
-    "story_contract",
-    "scene",
-    "global",
-    "reader_signal",
-    "genre_profile",
-    "writing_guidance",
-    "plot_structure",
-    "story_skeleton",
-    "memory",
-    "long_term_memory",
-    "preferences",
-    "alerts",
-]
-
-def _load_story_contract(self, chapter: int) -> Dict[str, Any]:
-    paths = StoryContractPaths.from_project_root(self.config.project_root)
-    master = read_json_if_exists(paths.master_json)
-    chapter_payload = read_json_if_exists(paths.chapter_json(chapter))
-    anti_patterns = read_json_if_exists(paths.anti_patterns_json) or []
-    if not master and not chapter_payload and not anti_patterns:
-        return {}
-    return {
-        "master_setting": master,
-        "chapter_brief": chapter_payload,
-        "route": (master or {}).get("route", {}),
-        "master_constraints": (master or {}).get("master_constraints", {}),
-        "anti_patterns": anti_patterns,
-    }
-
-# 在 _build_pack() 组装 pack 时插入:
-story_contract = self._load_story_contract(chapter)
-pack = {
-    "meta": {"chapter": chapter},
-    "core": core,
-    "scene": scene,
-    "global": global_ctx,
-    "reader_signal": reader_signal,
-    "genre_profile": genre_profile,
-    "writing_guidance": writing_guidance,
-    "plot_structure": plot_structure,
-    "story_skeleton": story_skeleton,
-    "memory": memory_ctx,
-    "long_term_memory": long_term_memory,
-    "preferences": preferences,
-    "alerts": alerts,
-}
-if story_contract:
-    pack["story_contract"] = story_contract
-```
-
-- [ ] **Step 4: 在 `extract_chapter_context.py` 中透出 `story_contract`**
-
-```python
-# webnovel-writer/scripts/extract_chapter_context.py
-def _load_contract_context(project_root: Path, chapter_num: int) -> Dict[str, Any]:
-    manager = ContextManager(get_config(project_root))
-    payload = manager.build_context(chapter_num, use_snapshot=False, save_snapshot=False)
-    sections = payload.get("sections") or {}
-    return {
-        "reader_signal": (sections.get("reader_signal") or {}).get("content", {}),
-        "genre_profile": (sections.get("genre_profile") or {}).get("content", {}),
-        "story_contract": (sections.get("story_contract") or {}).get("content", {}),
-        "writing_guidance": (sections.get("writing_guidance") or {}).get("content", {}),
-        "plot_structure": (sections.get("plot_structure") or {}).get("content", {}),
-        "long_term_memory": (sections.get("long_term_memory") or {}).get("content", {}),
-    }
-```
-
-文本渲染新增一个紧凑章节:
-
-```python
-story_contract = payload.get("story_contract") or {}
-if story_contract:
-    lines.append("## Story Contract")
-    route = story_contract.get("route") or {}
-    if route.get("primary_genre"):
-        lines.append(f"- 主路由题材: {route['primary_genre']}")
-    anti_patterns = story_contract.get("anti_patterns") or []
-    for row in anti_patterns[:5]:
-        lines.append(f"- 红线: {row.get('text')}")
-```
-
-- [ ] **Step 5: 回跑测试**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_context_manager.py webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py -q --no-cov`
-
-Expected: 通过
-
-- [ ] **Step 6: 提交**
-
-```bash
-git add webnovel-writer/scripts/data_modules/context_manager.py \
-        webnovel-writer/scripts/data_modules/tests/test_context_manager.py \
-        webnovel-writer/scripts/extract_chapter_context.py \
-        webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py
-git commit -m "feat: load story contract seed into context assembly"
-```
-
----
-
-## Task 5: 更新架构文档、命令文档与回归验证
-
-**Files:**
-- Create: `docs/architecture/story-system-phase1.md`
-- Modify: `README.md`
-- Modify: `docs/architecture/overview.md`
-- Modify: `docs/guides/commands.md`
-- Modify: `docs/superpowers/README.md`
-
-- [ ] **Step 1: 新建 Phase 1 架构文档**
-
-`docs/architecture/story-system-phase1.md` 至少写清楚以下四段,避免 Phase 1 代码上线后又变成“隐式约定”:
-
-```markdown
-# Story System Phase 1
-
-## JSON 真源
-- `PROJECT_ROOT/.story-system/MASTER_SETTING.json`
-- `PROJECT_ROOT/.story-system/anti_patterns.json`
-- `PROJECT_ROOT/.story-system/chapters/chapter_XXX.json`
-
-## 覆盖规则
-- `locked`:chapter 不得覆盖
-- `append_only`:chapter 只能补充
-- `override_allowed`:chapter 可局部覆盖
-
-## 运行时读取顺序
-1. chapter brief
-2. master setting
-3. anti-patterns
-4. genre profile fallback
-
-## 迁移边界
-- Phase 1 不引入 `VOLUME_BRIEF`
-- Phase 1 不改写后回写主链
-- `genre-profiles.md` 继续保留为回退层
-```
-
-- [ ] **Step 2: 更新命令与总览文档**
-
-在 `docs/guides/commands.md` 增加:
-
-````markdown
-## Story System(Phase 1)
-
-```bash
-python -X utf8 "<CLAUDE_PLUGIN_ROOT>/scripts/webnovel.py" \
-  --project-root "<WORKSPACE_ROOT>" \
-  story-system "玄幻退婚流" --chapter 1 --persist --format both
-```
-
-说明:
-- `--project-root` 允许传工作区根或书项目根
-- 真实落盘位置始终是 `PROJECT_ROOT/.story-system/`
-- `*.json` 为真源,`*.md` 为投影视图
-````
-
-在 `README.md` 与 `docs/architecture/overview.md` 补一条 Phase 1 说明:
-
-```markdown
-- Story System Phase 1:新增最小合同种子层(`MASTER_SETTING` / `chapter_XXX` / `anti_patterns`),
-  作为 `context_manager` 的合同输入前置层。
-```
-
-- [ ] **Step 3: 更新 `docs/superpowers/README.md` 导航**
-
-```markdown
-- [`plans/2026-04-12-story-system-phase1-contract-seed.md`](./plans/2026-04-12-story-system-phase1-contract-seed.md):Story System Phase 1 合同种子层实施计划
-```
-
-- [ ] **Step 4: 跑 Phase 1 目标测试集**
-
-Run:
-
-```bash
-python -m pytest \
-  webnovel-writer/scripts/data_modules/tests/test_story_contracts.py \
-  webnovel-writer/scripts/data_modules/tests/test_story_system_engine.py \
-  webnovel-writer/scripts/data_modules/tests/test_story_system_cli.py \
-  webnovel-writer/scripts/data_modules/tests/test_context_manager.py \
-  webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py \
-  webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py \
-  -q --no-cov
-```
-
-Expected: 全部通过
-
-- [ ] **Step 5: 跑 `reference_search.py` 回归,证明没有破坏底层 primitive**
-
-Run: `python -m pytest webnovel-writer/scripts/tests/test_reference_search.py -q --no-cov`
-
-Expected: 通过
-
-- [ ] **Step 6: 最终提交**
-
-```bash
-git add README.md \
-        docs/architecture/story-system-phase1.md \
-        docs/architecture/overview.md \
-        docs/guides/commands.md \
-        docs/superpowers/README.md
-git commit -m "docs: document story system phase1 contract seed layer"
-```
-
----
-
-## Spec Coverage Check
-
-本计划对 `2026-04-12-story-system-evolution-spec.md` 的覆盖关系如下:
-
-- `13.2 Phase 1:合同种子层`
-  - `题材与调性推理.csv`:Task 2
-  - 最小 `MASTER_SETTING`:Task 3
-  - 最小 `CHAPTER_BRIEF`:Task 3
-  - `anti_patterns.json`:Task 3
-  - `context_manager` 读取合同:Task 4
-
-- `14.1 / 14.1.1 路径解析约束`
-  - `PROJECT_ROOT/.story-system`:Task 1 / Task 3
-  - `resolve_project_root(args.project_root)` 经 unified CLI 注入:Task 3
-
-- `15.3 当前阶段结论`
-  - 保留 CSV + MD 双体系,不做自动迁移:Task 2
-
-- `17.1 文档更新要求`
-  - 合同 schema / 目录 / 运行流程 / 迁移说明文档:Task 5
-
-- `19. 实施建议`
-  - 明确先做合同种子层,不提前做 `CHAPTER_COMMIT` 或 event log:全计划范围
-
----
-
-## Placeholder Scan
-
-已避免以下占位式写法:
-
-- 没有使用 “TODO / TBD / 后续补”
-- 没有把“写测试”写成空泛口号,均给出测试骨架
-- 没有把文档更新写成一句“同步更新文档”,而是明确到目标文件
-- 没有把 Phase 2-4 内容混进 Phase 1 实施任务
-
----
-
-## Next Plan
-
-Phase 1 完成并稳定后,再进入下一份计划:
-
-1. `Phase 2 Contract-First Runtime`:`VOLUME_BRIEF`、`REVIEW_CONTRACT`、写前禁区、履约 diff
-2. `Phase 3 Chapter Commit Chain`:`CHAPTER_COMMIT`、accepted/rejected 语义、projection writers
-3. `Phase 4 Event Log + Override Ledger`:canonical event log、`contract_override` / `amend_proposal`

+ 0 - 809
docs/archive/superpowers/plans/2026-04-12-story-system-phase2-contract-first-runtime.md

@@ -1,809 +0,0 @@
-# Story System Phase 2 Contract-First Runtime Implementation Plan
-
-> **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:** 在 Phase 1 合同种子层之上,落地 `VOLUME_BRIEF`、`REVIEW_CONTRACT`、写前禁区与消歧域、大纲履约 diff、`context_manager` contract-first pack,让规划/写作/审查默认先消费合同而不是临时拼资料。
-
-**Architecture:** 以 `.story-system/*.json` 为唯一合同真源,在 Phase 1 的 `MASTER_SETTING / CHAPTER_BRIEF / anti_patterns` 基础上新增卷级与审查级合同,并把 Phase 1 的扁平聚合结果拆成 schema 化合同家族。运行时遵循 `chapter -> volume -> master -> old profile/reference fallback` 的固定优先级;Markdown 在本阶段退化为 JSON 的只读渲染产物。
-
-**Tech Stack:** Python 3.13, Pydantic, argparse, pytest, unified CLI `webnovel.py`, Markdown + JSON contract artifacts
-
-**Spec:** `docs/superpowers/specs/2026-04-12-story-system-evolution-spec.md`
-
-**Companion Specs:** `docs/superpowers/specs/2026-04-12-webnovel-story-intelligence-system-spec.md`, `docs/superpowers/plans/2026-04-12-story-system-phase1-contract-seed.md`
-
----
-
-## Scope Split
-
-本计划只覆盖 Phase 2:
-
-1. `VOLUME_BRIEF`
-2. `REVIEW_CONTRACT`
-3. 写前禁区与消歧域
-4. 大纲履约 diff
-5. `context_manager` contract-first pack
-
-明确不做:
-
-- 不引入 `CHAPTER_COMMIT`
-- 不把写后回写改成 commit 驱动
-- 不建立 canonical event log
-- 不做 override ledger 扩展迁移
-
-本阶段的退出标准:
-
-1. `MASTER / VOLUME / CHAPTER / REVIEW` 都有稳定 JSON schema
-2. `context_manager` 能按合同优先级输出 pack
-3. 写前能产出 `prewrite_validation` 与 `fulfillment_seed`
-4. `webnovel-plan` / `webnovel-write` / `webnovel-review` 的默认读取顺序已切到合同优先
-5. `genre-profiles.md` 与旧 reference 只作为 fallback,不再并列充当系统判断真源
-
-文档更新沿用 Phase 1 已建好的 `Story System` 段落:
-
-- Phase 2 只追加 `contract-first runtime`、`VOLUME_BRIEF`、`REVIEW_CONTRACT`
-- 不重写 `README.md / overview.md / commands.md` 的总体结构
-
----
-
-## File Structure
-
-### 要创建的文件
-
-- `webnovel-writer/scripts/data_modules/story_contract_schema.py`
-- `webnovel-writer/scripts/data_modules/runtime_contract_builder.py`
-- `webnovel-writer/scripts/data_modules/prewrite_validator.py`
-- `webnovel-writer/scripts/data_modules/tests/test_story_contract_schema.py`
-- `webnovel-writer/scripts/data_modules/tests/test_runtime_contract_builder.py`
-- `webnovel-writer/scripts/data_modules/tests/test_prewrite_validator.py`
-- `docs/architecture/story-system-phase2.md`
-
-### 要修改的文件
-
-- `webnovel-writer/scripts/data_modules/story_contracts.py`
-- `webnovel-writer/scripts/data_modules/story_system_engine.py`
-- `webnovel-writer/scripts/story_system.py`
-- `webnovel-writer/scripts/data_modules/context_manager.py`
-- `webnovel-writer/scripts/data_modules/tests/test_context_manager.py`
-- `webnovel-writer/scripts/extract_chapter_context.py`
-- `webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py`
-- `webnovel-writer/scripts/chapter_outline_loader.py`
-- `webnovel-writer/scripts/data_modules/webnovel.py`
-- `webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py`
-- `webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py`
-- `webnovel-writer/skills/webnovel-plan/SKILL.md`
-- `webnovel-writer/skills/webnovel-write/SKILL.md`
-- `webnovel-writer/skills/webnovel-review/SKILL.md`
-- `README.md`
-- `docs/architecture/overview.md`
-- `docs/guides/commands.md`
-- `docs/superpowers/README.md`
-
-### 文件职责
-
-- `story_contract_schema.py`:`MASTER_SETTING / VOLUME_BRIEF / CHAPTER_BRIEF / REVIEW_CONTRACT` 的 Pydantic schema 与版本元数据
-- `runtime_contract_builder.py`:从 Phase 1 seed、卷范围、大纲结构、plot structure 生成 `VOLUME_BRIEF` 与 `REVIEW_CONTRACT`
-- `prewrite_validator.py`:写前禁区检查、消歧域构建、履约 seed 与 must-check 列表生成
-- `story_contracts.py`:新增 `volumes/`、`reviews/` 路径、JSON 真源写入、只读 Markdown 重建
-- `context_manager.py`:把 contract-first pack 作为默认装配顺序
-- `skills/*/SKILL.md`:把运行时入口切换为先生成/读取合同,再写作或审查
-
----
-
-## Task 1: 建立 Phase 2 合同 schema 与目录扩展
-
-**Files:**
-- Create: `webnovel-writer/scripts/data_modules/story_contract_schema.py`
-- Create: `webnovel-writer/scripts/data_modules/tests/test_story_contract_schema.py`
-- Modify: `webnovel-writer/scripts/data_modules/story_contracts.py`
-
-- [ ] **Step 1: 先写 schema 失败测试**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_story_contract_schema.py
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-import pytest
-
-from data_modules.story_contract_schema import ChapterBrief, MasterSetting, ReviewContract, VolumeBrief
-
-
-def test_master_setting_and_chapter_brief_accept_phase1_seed_shape():
-    master = MasterSetting.model_validate(
-        {
-            "meta": {"schema_version": "story-system/v1", "contract_type": "MASTER_SETTING"},
-            "route": {"primary_genre": "玄幻退婚流"},
-            "master_constraints": {"core_tone": "先压后爆", "pacing_strategy": "三章内首个反打"},
-            "base_context": [],
-            "source_trace": [],
-            "override_policy": {"locked": ["route.primary_genre"], "append_only": ["anti_patterns"], "override_allowed": []},
-        }
-    )
-    chapter = ChapterBrief.model_validate(
-        {
-            "meta": {"schema_version": "story-system/v1", "contract_type": "CHAPTER_BRIEF"},
-            "override_allowed": {"chapter_focus": "退婚现场反打"},
-            "dynamic_context": [],
-            "source_trace": [],
-        }
-    )
-    assert master.route["primary_genre"] == "玄幻退婚流"
-    assert chapter.override_allowed["chapter_focus"] == "退婚现场反打"
-
-
-def test_volume_brief_requires_selected_fields():
-    payload = {
-        "meta": {"schema_version": "story-system/v1", "contract_type": "VOLUME_BRIEF"},
-        "volume_goal": {"summary": "卷一站稳脚跟"},
-        "selected_tropes": ["退婚反击"],
-        "selected_pacing": {"wave": "压抑后爆"},
-        "selected_scenes": ["宗门大厅", "资源争夺"],
-        "anti_patterns": ["配角抢主角兑现"],
-        "system_constraints": ["金手指每日限一次"],
-        "overrides": {"locked": {}, "append_only": {}, "override_allowed": {}},
-    }
-    model = VolumeBrief.model_validate(payload)
-    assert model.volume_goal["summary"] == "卷一站稳脚跟"
-
-
-def test_review_contract_requires_blocking_rules_list():
-    with pytest.raises(Exception):
-        ReviewContract.model_validate(
-            {
-                "meta": {"schema_version": "story-system/v1", "contract_type": "REVIEW_CONTRACT"},
-                "must_check": ["mandatory_nodes"],
-                "blocking_rules": "not-a-list",
-                "genre_specific_risks": [],
-                "anti_patterns": [],
-                "system_constraints": [],
-                "review_thresholds": {"blocking_count": 0},
-                "overrides": {"locked": {}, "append_only": {}, "override_allowed": {}},
-            }
-        )
-```
-
-- [ ] **Step 2: 运行测试确认红灯**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_story_contract_schema.py -q --no-cov`
-
-Expected: `ModuleNotFoundError: No module named 'data_modules.story_contract_schema'`
-
-- [ ] **Step 3: 实现 Phase 2 schema 与目录路径**
-
-```python
-# webnovel-writer/scripts/data_modules/story_contract_schema.py
-from __future__ import annotations
-
-from typing import Any, Dict, List
-
-from pydantic import BaseModel, Field
-
-
-class ContractMeta(BaseModel):
-    schema_version: str = "story-system/v1"
-    contract_type: str
-    generator_version: str = "phase2"
-    source_trace: List[Dict[str, Any]] = Field(default_factory=list)
-
-
-class OverrideBundle(BaseModel):
-    locked: Dict[str, Any] = Field(default_factory=dict)
-    append_only: Dict[str, Any] = Field(default_factory=dict)
-    override_allowed: Dict[str, Any] = Field(default_factory=dict)
-
-
-class MasterSetting(BaseModel):
-    meta: ContractMeta
-    route: Dict[str, Any] = Field(default_factory=dict)
-    master_constraints: Dict[str, Any] = Field(default_factory=dict)
-    base_context: List[Dict[str, Any]] = Field(default_factory=list)
-    source_trace: List[Dict[str, Any]] = Field(default_factory=list)
-    override_policy: Dict[str, List[str]] = Field(default_factory=dict)
-
-
-class ChapterBrief(BaseModel):
-    meta: ContractMeta
-    override_allowed: Dict[str, Any] = Field(default_factory=dict)
-    dynamic_context: List[Dict[str, Any]] = Field(default_factory=list)
-    source_trace: List[Dict[str, Any]] = Field(default_factory=list)
-
-
-class VolumeBrief(BaseModel):
-    meta: ContractMeta
-    volume_goal: Dict[str, Any]
-    selected_tropes: List[str] = Field(default_factory=list)
-    selected_pacing: Dict[str, Any] = Field(default_factory=dict)
-    selected_scenes: List[str] = Field(default_factory=list)
-    anti_patterns: List[str] = Field(default_factory=list)
-    system_constraints: List[str] = Field(default_factory=list)
-    overrides: OverrideBundle = Field(default_factory=OverrideBundle)
-
-
-class ReviewContract(BaseModel):
-    meta: ContractMeta
-    must_check: List[str] = Field(default_factory=list)
-    blocking_rules: List[str] = Field(default_factory=list)
-    genre_specific_risks: List[str] = Field(default_factory=list)
-    anti_patterns: List[str] = Field(default_factory=list)
-    system_constraints: List[str] = Field(default_factory=list)
-    review_thresholds: Dict[str, Any] = Field(default_factory=dict)
-    overrides: OverrideBundle = Field(default_factory=OverrideBundle)
-```
-
-`blocking_rules` 在 Phase 2 先保持 `List[str]`,避免过早引入复杂 schema;但在文档中明确标注它是 **Phase 5 可升级为 `List[BlockingRule]` 的预留位**,后续如果要附带严重级、来源和匹配模式,再做结构化收口。
-
-- [ ] **Step 4: 扩展 `story_contracts.py` 的卷级/审查级路径**
-
-```python
-# webnovel-writer/scripts/data_modules/story_contracts.py
-@property
-def volumes_dir(self) -> Path:
-    return self.root / "volumes"
-
-@property
-def reviews_dir(self) -> Path:
-    return self.root / "reviews"
-
-def volume_json(self, volume: int) -> Path:
-    return self.volumes_dir / f"volume_{volume:03d}.json"
-
-def review_json(self, chapter: int) -> Path:
-    return self.reviews_dir / f"chapter_{chapter:03d}.review.json"
-```
-
-- [ ] **Step 5: 回跑测试**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_story_contract_schema.py -q --no-cov`
-
-Expected: 通过
-
-- [ ] **Step 6: 提交**
-
-```bash
-git add webnovel-writer/scripts/data_modules/story_contract_schema.py \
-        webnovel-writer/scripts/data_modules/story_contracts.py \
-        webnovel-writer/scripts/data_modules/tests/test_story_contract_schema.py
-git commit -m "feat: add phase2 contract schemas and paths"
-```
-
----
-
-## Task 2: 生成 `VOLUME_BRIEF` 与 `REVIEW_CONTRACT`
-
-**Files:**
-- Create: `webnovel-writer/scripts/data_modules/runtime_contract_builder.py`
-- Create: `webnovel-writer/scripts/data_modules/tests/test_runtime_contract_builder.py`
-- Modify: `webnovel-writer/scripts/data_modules/story_system_engine.py`
-- Modify: `webnovel-writer/scripts/data_modules/story_contracts.py`
-- Modify: `webnovel-writer/scripts/story_system.py`
-- Modify: `webnovel-writer/scripts/chapter_outline_loader.py`
-
-- [ ] **Step 1: 先写生成器测试**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_runtime_contract_builder.py
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-import json
-
-from data_modules.runtime_contract_builder import RuntimeContractBuilder
-
-
-def test_runtime_contract_builder_creates_volume_and_review_contracts(tmp_path):
-    project_root = tmp_path
-    (project_root / ".webnovel").mkdir(parents=True, exist_ok=True)
-    (project_root / ".webnovel" / "state.json").write_text(
-        json.dumps(
-            {
-                "progress": {"volumes_planned": [{"volume": 1, "chapters_range": "1-20"}]},
-                "chapter_meta": {},
-                "disambiguation_pending": [],
-                "disambiguation_warnings": [],
-            },
-            ensure_ascii=False,
-        ),
-        encoding="utf-8",
-    )
-    (project_root / ".story-system" / "MASTER_SETTING.json").parent.mkdir(parents=True, exist_ok=True)
-    (project_root / ".story-system" / "MASTER_SETTING.json").write_text(
-        json.dumps(
-            {
-                "meta": {"schema_version": "story-system/v1", "contract_type": "MASTER_SETTING"},
-                "route": {"primary_genre": "玄幻退婚流"},
-                "master_constraints": {"core_tone": "先压后爆"},
-                "base_context": [],
-                "source_trace": [],
-                "override_policy": {"locked": ["route.primary_genre"], "append_only": ["anti_patterns"], "override_allowed": []},
-            },
-            ensure_ascii=False,
-        ),
-        encoding="utf-8",
-    )
-    (project_root / ".story-system" / "anti_patterns.json").write_text(
-        json.dumps([{"text": "配角不能抢主角兑现"}], ensure_ascii=False),
-        encoding="utf-8",
-    )
-    (project_root / "大纲").mkdir(parents=True, exist_ok=True)
-    (project_root / "大纲" / "第1卷-详细大纲.md").write_text(
-        "### 第3章:试压\\nCBN:继续压迫\\n必须覆盖节点:发现陷阱、决定隐忍\\n本章禁区:不可提前摊牌",
-        encoding="utf-8",
-    )
-
-    builder = RuntimeContractBuilder(project_root)
-    volume_brief, review_contract = builder.build_for_chapter(3)
-
-    assert volume_brief["meta"]["contract_type"] == "VOLUME_BRIEF"
-    assert review_contract["meta"]["contract_type"] == "REVIEW_CONTRACT"
-    assert "发现陷阱" in review_contract["must_check"]
-    assert "不可提前摊牌" in review_contract["blocking_rules"]
-```
-
-- [ ] **Step 2: 跑红灯**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_runtime_contract_builder.py -q --no-cov`
-
-Expected: `ModuleNotFoundError: No module named 'data_modules.runtime_contract_builder'`
-
-- [ ] **Step 3: 实现生成器**
-
-```python
-# webnovel-writer/scripts/data_modules/runtime_contract_builder.py
-from __future__ import annotations
-
-import json
-from pathlib import Path
-from typing import Any, Dict, Tuple
-
-from chapter_outline_loader import load_chapter_plot_structure, volume_num_for_chapter_from_state
-
-from data_modules.story_contract_schema import MasterSetting, ReviewContract, VolumeBrief
-
-
-class RuntimeContractBuilder:
-    def __init__(self, project_root: Path):
-        self.project_root = Path(project_root)
-
-    def build_for_chapter(self, chapter: int) -> Tuple[Dict[str, Any], Dict[str, Any]]:
-        master = self._load_master_setting()
-        anti_patterns = self._load_anti_patterns()
-        plot = self._load_plot_structure(chapter)
-        volume = self._resolve_volume(chapter)
-
-        volume_brief = VolumeBrief.model_validate(
-            {
-                "meta": {"schema_version": "story-system/v1", "contract_type": "VOLUME_BRIEF"},
-                "volume_goal": {"summary": f"第{volume}卷延续 {master.route.get('primary_genre', '')} 的主冲突"},
-                "selected_tropes": [master.route.get("primary_genre", "")],
-                "selected_pacing": {"wave": master.master_constraints.get("pacing_strategy", "")},
-                "selected_scenes": list(plot.get("cpns") or []),
-                "anti_patterns": [row.get("text", "") for row in anti_patterns if row.get("text")],
-                "system_constraints": [master.master_constraints.get("core_tone", "")],
-                "overrides": {"locked": {}, "append_only": {}, "override_allowed": {}},
-            }
-        ).model_dump()
-        review_contract = ReviewContract.model_validate(
-            {
-                "meta": {"schema_version": "story-system/v1", "contract_type": "REVIEW_CONTRACT"},
-                "must_check": list(plot.get("mandatory_nodes") or []),
-                "blocking_rules": list(plot.get("prohibitions") or []),
-                "genre_specific_risks": [master.route.get("primary_genre", "")],
-                "anti_patterns": volume_brief["anti_patterns"],
-                "system_constraints": volume_brief["system_constraints"],
-                "review_thresholds": {"blocking_count": 0, "missed_nodes": 0},
-                "overrides": {"locked": {}, "append_only": {}, "override_allowed": {}},
-            }
-        ).model_dump()
-        return volume_brief, review_contract
-
-    def _load_master_setting(self) -> MasterSetting:
-        raw = json.loads((self.project_root / ".story-system" / "MASTER_SETTING.json").read_text(encoding="utf-8"))
-        return MasterSetting.model_validate(raw)
-
-    def _load_anti_patterns(self) -> list[Dict[str, Any]]:
-        raw = json.loads((self.project_root / ".story-system" / "anti_patterns.json").read_text(encoding="utf-8"))
-        return list(raw or [])
-
-    def _load_plot_structure(self, chapter: int) -> Dict[str, Any]:
-        raw = load_chapter_plot_structure(self.project_root, chapter) or {}
-        return {
-            "mandatory_nodes": list(raw.get("mandatory_nodes") or []),
-            "prohibitions": list(raw.get("prohibitions") or []),
-            "cpns": list(raw.get("cpns") or []),
-        }
-
-    def _resolve_volume(self, chapter: int) -> int:
-        return volume_num_for_chapter_from_state(self.project_root, chapter) or 1
-```
-
-- [ ] **Step 4: 在 `story_system.py` 中新增 `build-runtime-contracts` 入口**
-
-```python
-# webnovel-writer/scripts/data_modules/story_contracts.py
-from chapter_outline_loader import volume_num_for_chapter_from_state
-
-
-def persist_runtime_contracts(project_root: Path, chapter: int, volume_brief: dict, review_contract: dict) -> None:
-    paths = StoryContractPaths.from_project_root(project_root)
-    volume = volume_num_for_chapter_from_state(project_root, chapter) or 1
-    paths.volumes_dir.mkdir(parents=True, exist_ok=True)
-    paths.reviews_dir.mkdir(parents=True, exist_ok=True)
-    write_json(paths.volume_json(volume), volume_brief)
-    write_json(paths.review_json(chapter), review_contract)
-
-# webnovel-writer/scripts/story_system.py
-parser.add_argument("--emit-runtime-contracts", action="store_true")
-
-if args.emit_runtime_contracts:
-    builder = RuntimeContractBuilder(project_root)
-    volume_brief, review_contract = builder.build_for_chapter(args.chapter)
-    persist_runtime_contracts(project_root, args.chapter, volume_brief, review_contract)
-```
-
-- [ ] **Step 5: 回跑测试**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_runtime_contract_builder.py -q --no-cov`
-
-Expected: 通过
-
-- [ ] **Step 6: 提交**
-
-```bash
-git add webnovel-writer/scripts/data_modules/runtime_contract_builder.py \
-        webnovel-writer/scripts/data_modules/tests/test_runtime_contract_builder.py \
-        webnovel-writer/scripts/data_modules/story_contracts.py \
-        webnovel-writer/scripts/story_system.py \
-        webnovel-writer/scripts/chapter_outline_loader.py
-git commit -m "feat: generate volume brief and review contract"
-```
-
----
-
-## Task 3: 写前禁区、消歧域与大纲履约 diff
-
-**Files:**
-- Create: `webnovel-writer/scripts/data_modules/prewrite_validator.py`
-- Create: `webnovel-writer/scripts/data_modules/tests/test_prewrite_validator.py`
-- Modify: `webnovel-writer/scripts/data_modules/context_manager.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_context_manager.py`
-
-- [ ] **Step 1: 先写 `prewrite_validator` 失败测试**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_prewrite_validator.py
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-import json
-
-from data_modules.prewrite_validator import PrewriteValidator
-
-
-def test_prewrite_validator_builds_disambiguation_domain_and_fulfillment_seed(tmp_path):
-    project_root = tmp_path
-    (project_root / ".webnovel").mkdir(parents=True, exist_ok=True)
-    (project_root / ".webnovel" / "state.json").write_text(
-        json.dumps(
-            {
-                "disambiguation_pending": [],
-                "disambiguation_warnings": [{"mention": "宗主"}],
-                "chapter_meta": {},
-            },
-            ensure_ascii=False,
-        ),
-        encoding="utf-8",
-    )
-    review_contract = {"must_check": ["发现陷阱"], "blocking_rules": ["不可提前摊牌"]}
-    plot_structure = {"mandatory_nodes": ["发现陷阱"], "prohibitions": ["不可提前摊牌"]}
-
-    payload = PrewriteValidator(project_root).build(chapter=3, review_contract=review_contract, plot_structure=plot_structure)
-
-    assert payload["blocking"] is False
-    assert payload["fulfillment_seed"]["planned_nodes"] == ["发现陷阱"]
-    assert payload["disambiguation_domain"]["pending_count"] == 0
-```
-
-- [ ] **Step 2: 跑红灯**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_prewrite_validator.py -q --no-cov`
-
-Expected: `ModuleNotFoundError: No module named 'data_modules.prewrite_validator'`
-
-- [ ] **Step 3: 实现写前校验器**
-
-```python
-# webnovel-writer/scripts/data_modules/prewrite_validator.py
-from __future__ import annotations
-
-import json
-from pathlib import Path
-from typing import Any, Dict
-
-
-class PrewriteValidator:
-    def __init__(self, project_root: Path):
-        self.project_root = Path(project_root)
-
-    def build(self, chapter: int, review_contract: Dict[str, Any], plot_structure: Dict[str, Any]) -> Dict[str, Any]:
-        state = json.loads((self.project_root / ".webnovel" / "state.json").read_text(encoding="utf-8"))
-        pending = state.get("disambiguation_pending") or []
-        warnings = state.get("disambiguation_warnings") or []
-        return {
-            "chapter": chapter,
-            "blocking": bool(pending),
-            "blocking_reasons": ["存在高优先级 disambiguation_pending"] if pending else [],
-            "forbidden_zones": list(review_contract.get("blocking_rules") or []),
-            "disambiguation_domain": {
-                "pending_count": len(pending),
-                "warning_count": len(warnings),
-                "allowed_mentions": [item.get("mention", "") for item in warnings if item.get("mention")],
-            },
-            "fulfillment_seed": {
-                "planned_nodes": list(plot_structure.get("mandatory_nodes") or []),
-                "prohibitions": list(plot_structure.get("prohibitions") or []),
-            },
-        }
-```
-
-- [ ] **Step 4: 在 `context_manager` 中新增 `prewrite_validation` section**
-
-```python
-# webnovel-writer/scripts/data_modules/context_manager.py
-SECTION_ORDER = [
-    "core",
-    "story_contract",
-    "prewrite_validation",
-    "scene",
-    "global",
-    "reader_signal",
-    "genre_profile",
-    "writing_guidance",
-    "plot_structure",
-    "story_skeleton",
-    "memory",
-    "long_term_memory",
-    "preferences",
-    "alerts",
-]
-
-validator = PrewriteValidator(self.config.project_root)
-pack["prewrite_validation"] = validator.build(
-    chapter=chapter,
-    review_contract=story_contract.get("review_contract") or {},
-    plot_structure=plot_structure,
-)
-```
-
-- [ ] **Step 5: 回跑测试**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_prewrite_validator.py webnovel-writer/scripts/data_modules/tests/test_context_manager.py -q --no-cov`
-
-Expected: 通过
-
-- [ ] **Step 6: 提交**
-
-```bash
-git add webnovel-writer/scripts/data_modules/prewrite_validator.py \
-        webnovel-writer/scripts/data_modules/tests/test_prewrite_validator.py \
-        webnovel-writer/scripts/data_modules/context_manager.py \
-        webnovel-writer/scripts/data_modules/tests/test_context_manager.py
-git commit -m "feat: add prewrite validation and fulfillment seed"
-```
-
----
-
-## Task 4: 切换 runtime 为 contract-first,并接入 skills / CLI
-
-**Files:**
-- Modify: `webnovel-writer/scripts/extract_chapter_context.py`
-- Modify: `webnovel-writer/scripts/data_modules/webnovel.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py`
-- Modify: `webnovel-writer/skills/webnovel-plan/SKILL.md`
-- Modify: `webnovel-writer/skills/webnovel-write/SKILL.md`
-- Modify: `webnovel-writer/skills/webnovel-review/SKILL.md`
-
-- [ ] **Step 1: 先补 contract-first 读取顺序测试**
-
-在 `test_prompt_integrity.py` 增加更稳的“步骤块”断言,而不是裸字符串包含:
-
-```python
-def test_story_system_runtime_contract_commands_exist():
-    import re
-
-    text = Path("webnovel-writer/skills/webnovel-write/SKILL.md").read_text(encoding="utf-8")
-    block = re.search(r"story-system[\\s\\S]+--emit-runtime-contracts[\\s\\S]+REVIEW_CONTRACT", text)
-    assert block, "webnovel-write skill 必须包含生成 runtime contracts 的完整步骤块"
-```
-
-在 `test_webnovel_unified_cli.py` 增加:
-
-```python
-def test_webnovel_story_system_runtime_forwards(monkeypatch, tmp_path):
-    from data_modules import webnovel as cli
-    project_root = tmp_path / "book"
-    (project_root / ".webnovel").mkdir(parents=True, exist_ok=True)
-    (project_root / ".webnovel" / "state.json").write_text("{}", encoding="utf-8")
-    called = {}
-
-    def _fake_run_script(script_name, argv):
-        called["script_name"] = script_name
-        called["argv"] = argv
-        return 0
-
-    monkeypatch.setattr(cli, "_run_script", _fake_run_script)
-    monkeypatch.setattr(sys, "argv", ["webnovel", "--project-root", str(project_root), "story-system", "玄幻退婚流", "--emit-runtime-contracts"])
-    cli.main()
-
-    assert called["script_name"] == "story_system.py"
-    assert "--emit-runtime-contracts" in called["argv"]
-```
-
-- [ ] **Step 2: 跑红灯**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py -q --no-cov`
-
-Expected: 新断言失败
-
-- [ ] **Step 3: 修改技能与文本提取脚本**
-
-在三个 skill 中统一插入 Phase 2 前置步骤:
-
-```bash
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${WORKSPACE_ROOT}" \
-  story-system "{chapter_goal}" --chapter {chapter_num} --persist --emit-runtime-contracts --format both
-```
-
-并在 `extract_chapter_context.py` 输出:
-
-```python
-story_contract = contract_context.get("story_contract") or {}
-review_contract = story_contract.get("review_contract") or {}
-prewrite_validation = payload.get("prewrite_validation") or {}
-
-lines.append("## Contract-First Runtime")
-lines.append(f"- Review blocking rules: {len(review_contract.get('blocking_rules') or [])}")
-lines.append(f"- Prewrite blocking: {prewrite_validation.get('blocking')}")
-```
-
-- [ ] **Step 4: 回跑测试**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py -q --no-cov`
-
-Expected: 通过
-
-- [ ] **Step 5: 提交**
-
-```bash
-git add webnovel-writer/scripts/extract_chapter_context.py \
-        webnovel-writer/scripts/data_modules/webnovel.py \
-        webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py \
-        webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py \
-        webnovel-writer/skills/webnovel-plan/SKILL.md \
-        webnovel-writer/skills/webnovel-write/SKILL.md \
-        webnovel-writer/skills/webnovel-review/SKILL.md
-git commit -m "feat: switch runtime entrypoints to contract-first flow"
-```
-
----
-
-## Task 5: 文档更新与 Phase 2 回归验证
-
-**Files:**
-- Create: `docs/architecture/story-system-phase2.md`
-- Modify: `README.md`
-- Modify: `docs/architecture/overview.md`
-- Modify: `docs/guides/commands.md`
-- Modify: `docs/superpowers/README.md`
-
-- [ ] **Step 1: 新建 Phase 2 架构文档**
-
-`docs/architecture/story-system-phase2.md` 必须至少包含:
-
-```markdown
-# Story System Phase 2
-
-## 合同真源
-- `MASTER_SETTING.json`
-- `volumes/volume_XXX.json`
-- `chapters/chapter_XXX.json`
-- `reviews/chapter_XXX.review.json`
-
-## 运行时顺序
-1. chapter brief
-2. volume brief
-3. master setting
-4. fallback references
-
-## 写前校验
-- forbidden zones
-- disambiguation domain
-- fulfillment seed
-
-## 非目标
-- 不引入 `CHAPTER_COMMIT`
-- 不引入 canonical event log
-```
-
-- [ ] **Step 2: 更新命令文档**
-
-在 `docs/guides/commands.md` 增加:
-
-```bash
-python -X utf8 "<CLAUDE_PLUGIN_ROOT>/scripts/webnovel.py" \
-  --project-root "<WORKSPACE_ROOT>" \
-  story-system "玄幻退婚流" --chapter 3 --persist --emit-runtime-contracts --format both
-```
-
-- [ ] **Step 3: 跑 Phase 2 最小验证集**
-
-Run:
-
-```bash
-python -m pytest \
-  webnovel-writer/scripts/data_modules/tests/test_story_contract_schema.py \
-  webnovel-writer/scripts/data_modules/tests/test_runtime_contract_builder.py \
-  webnovel-writer/scripts/data_modules/tests/test_prewrite_validator.py \
-  webnovel-writer/scripts/data_modules/tests/test_context_manager.py \
-  webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py \
-  webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py \
-  webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py \
-  -q --no-cov
-```
-
-Expected: 全部通过
-
-- [ ] **Step 4: 回归 `reference_search.py`**
-
-Run: `python -m pytest webnovel-writer/scripts/tests/test_reference_search.py -q --no-cov`
-
-Expected: 通过
-
-- [ ] **Step 5: 最终提交**
-
-```bash
-git add README.md \
-        docs/architecture/story-system-phase2.md \
-        docs/architecture/overview.md \
-        docs/guides/commands.md \
-        docs/superpowers/README.md
-git commit -m "docs: document story system phase2 contract-first runtime"
-```
-
----
-
-## Spec Coverage Check
-
-- `13.3 Phase 2:合同优先运行时`
-  - `VOLUME_BRIEF`:Task 1 / Task 2
-  - `REVIEW_CONTRACT`:Task 1 / Task 2
-  - 写前禁区与消歧域:Task 3
-  - 大纲履约 diff seed:Task 3
-  - `context_manager` contract-first pack:Task 3 / Task 4
-
-- `7.2 运行时优先级`
-  - `chapter -> volume -> master -> old profile`:Task 3 / Task 4
-
-- `11.1 写前校验`
-  - 可见合同、禁区、消歧 pending、must cover:Task 3
-
-- `17.1 文档更新要求`
-  - schema、目录、流程、命令:Task 5
-
----
-
-## Placeholder Scan
-
-- 没有使用 `TODO / TBD / implement later`
-- 没有把“接入 skill”写成空话,已给出具体 skill 文件
-- 没有把 Phase 3 的 `CHAPTER_COMMIT` 混入本阶段任务
-
----
-
-## Next Plan
-
-Phase 2 完成后进入:
-
-1. `Phase 3 Chapter Commit Chain`
-2. `Phase 4 Event Log And Override Ledger`

+ 0 - 669
docs/archive/superpowers/plans/2026-04-12-story-system-phase3-chapter-commit-chain.md

@@ -1,669 +0,0 @@
-# Story System Phase 3 Chapter Commit Chain Implementation Plan
-
-> **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:** 建立 `CHAPTER_COMMIT` 主链、accepted / rejected 语义与四类 projection writers,让章节事实写后回写统一经过提交对象,而不再散写到 `state / index / summary / memory`。
-
-**Architecture:** 在 Phase 2 的合同优先运行时之上,引入 `CHAPTER_COMMIT.json` 作为写后唯一事实入口。提交阶段先汇总 `review_result / fulfillment_result / disambiguation_result / accepted_events / deltas`,只有 `commit accepted` 才允许投影器分发到下游存储。`state_manager / memory writer / index_manager` 在本阶段重定位为投影写入器底座,而不是章节事实真源。
-
-**Tech Stack:** Python 3.13, Pydantic, argparse, pytest, SQLite (`index.db`), JSON commit artifacts under `.story-system/commits`
-
-**Spec:** `docs/superpowers/specs/2026-04-12-story-system-evolution-spec.md`
-
-**Companion Plans:** `docs/superpowers/plans/2026-04-12-story-system-phase1-contract-seed.md`, `docs/superpowers/plans/2026-04-12-story-system-phase2-contract-first-runtime.md`
-
----
-
-## Scope Split
-
-本计划只覆盖 Phase 3:
-
-1. `CHAPTER_COMMIT`
-2. 四类 projection writers
-3. accepted / rejected commit 语义
-4. 写后回写改为 commit 驱动
-
-明确不做:
-
-- 不引入 canonical event log 全局主链
-- 不把 override ledger 扩展成完整审计账本
-- 不做旧链路降级收尾
-
-退出标准:
-
-1. `PROJECT_ROOT/.story-system/commits/chapter_XXX.commit.json` 成为写后事实入口
-2. rejected commit 不写下游存储
-3. accepted commit 才触发 `state / index / summary / memory` 投影,其中 `StateProjectionWriter` 必须真实更新 `state.json`
-4. `projection_status` 可追踪每个 writer 的完成情况;写入失败只记录到对应 writer 状态,不回滚 `commit accepted/rejected` 判定
-
-文档更新继续追加到已有 `Story System` 段落,不重写 README 总体结构。
-
----
-
-## File Structure
-
-### 要创建的文件
-
-- `webnovel-writer/scripts/chapter_commit.py`
-- `webnovel-writer/scripts/data_modules/story_commit_schema.py`
-- `webnovel-writer/scripts/data_modules/chapter_commit_service.py`
-- `webnovel-writer/scripts/data_modules/state_projection_writer.py`
-- `webnovel-writer/scripts/data_modules/index_projection_writer.py`
-- `webnovel-writer/scripts/data_modules/summary_projection_writer.py`
-- `webnovel-writer/scripts/data_modules/memory_projection_writer.py`
-- `webnovel-writer/scripts/data_modules/tests/test_story_commit_schema.py`
-- `webnovel-writer/scripts/data_modules/tests/test_chapter_commit_service.py`
-- `webnovel-writer/scripts/data_modules/tests/test_projection_writers.py`
-- `docs/architecture/story-system-phase3.md`
-
-### 要修改的文件
-
-- `webnovel-writer/scripts/data_modules/story_contracts.py`
-- `webnovel-writer/scripts/data_modules/webnovel.py`
-- `webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py`
-- `webnovel-writer/scripts/review_pipeline.py`
-- `webnovel-writer/scripts/data_modules/state_manager.py`
-- `webnovel-writer/scripts/data_modules/memory/writer.py`
-- `webnovel-writer/skills/webnovel-write/SKILL.md`
-- `webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py`
-- `README.md`
-- `docs/architecture/overview.md`
-- `docs/guides/commands.md`
-- `docs/superpowers/README.md`
-
----
-
-## Task 1: 定义 `CHAPTER_COMMIT` schema 与落盘路径
-
-**Files:**
-- Create: `webnovel-writer/scripts/data_modules/story_commit_schema.py`
-- Create: `webnovel-writer/scripts/data_modules/tests/test_story_commit_schema.py`
-- Modify: `webnovel-writer/scripts/data_modules/story_contracts.py`
-
-- [ ] **Step 1: 先写 schema 测试**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_story_commit_schema.py
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-from data_modules.story_commit_schema import ChapterCommit
-
-
-def test_chapter_commit_accepts_required_sections():
-    payload = {
-        "meta": {"schema_version": "story-system/v1", "chapter": 3, "status": "accepted"},
-        "contract_refs": {"master": "MASTER_SETTING.json", "chapter": "chapter_003.json"},
-        "outline_snapshot": {"planned_nodes": ["发现陷阱"]},
-        "review_result": {"blocking_count": 0},
-        "fulfillment_result": {"missed_nodes": []},
-        "disambiguation_result": {"pending": []},
-        "accepted_events": [],
-        "state_deltas": [],
-        "entity_deltas": [],
-        "projection_status": {"state": "pending", "index": "pending", "summary": "pending", "memory": "pending"},
-    }
-    model = ChapterCommit.model_validate(payload)
-    assert model.meta["status"] == "accepted"
-```
-
-- [ ] **Step 2: 跑红灯**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_story_commit_schema.py -q --no-cov`
-
-Expected: `ModuleNotFoundError: No module named 'data_modules.story_commit_schema'`
-
-- [ ] **Step 3: 实现 schema 与 commit 路径**
-
-```python
-# webnovel-writer/scripts/data_modules/story_commit_schema.py
-from __future__ import annotations
-
-from typing import Any, Dict, List
-
-from pydantic import BaseModel, Field
-
-
-class ChapterCommit(BaseModel):
-    meta: Dict[str, Any]
-    contract_refs: Dict[str, str]
-    outline_snapshot: Dict[str, Any]
-    review_result: Dict[str, Any]
-    fulfillment_result: Dict[str, Any]
-    disambiguation_result: Dict[str, Any]
-    accepted_events: List[Dict[str, Any]] = Field(default_factory=list)
-    state_deltas: List[Dict[str, Any]] = Field(default_factory=list)
-    entity_deltas: List[Dict[str, Any]] = Field(default_factory=list)
-    projection_status: Dict[str, str]
-```
-
-```python
-# webnovel-writer/scripts/data_modules/story_contracts.py
-@property
-def commits_dir(self) -> Path:
-    return self.root / "commits"
-
-def commit_json(self, chapter: int) -> Path:
-    return self.commits_dir / f"chapter_{chapter:03d}.commit.json"
-```
-
-- [ ] **Step 4: 回跑测试**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_story_commit_schema.py -q --no-cov`
-
-Expected: 通过
-
-- [ ] **Step 5: 提交**
-
-```bash
-git add webnovel-writer/scripts/data_modules/story_commit_schema.py \
-        webnovel-writer/scripts/data_modules/story_contracts.py \
-        webnovel-writer/scripts/data_modules/tests/test_story_commit_schema.py
-git commit -m "feat: add chapter commit schema and paths"
-```
-
----
-
-## Task 2: 实现 `chapter_commit_service` 与提交校验
-
-**Files:**
-- Create: `webnovel-writer/scripts/data_modules/chapter_commit_service.py`
-- Create: `webnovel-writer/scripts/data_modules/tests/test_chapter_commit_service.py`
-- Modify: `webnovel-writer/scripts/review_pipeline.py`
-
-- [ ] **Step 1: 先写提交通过/阻断测试**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_chapter_commit_service.py
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-from data_modules.chapter_commit_service import ChapterCommitService
-
-
-def test_commit_service_rejects_when_missed_nodes_exist(tmp_path):
-    service = ChapterCommitService(tmp_path)
-    payload = service.build_commit(
-        chapter=3,
-        review_result={"blocking_count": 0},
-        fulfillment_result={"planned_nodes": ["发现陷阱"], "missed_nodes": ["发现陷阱"]},
-        disambiguation_result={"pending": []},
-        extraction_result={"state_deltas": [], "entity_deltas": [], "accepted_events": []},
-    )
-    assert payload["meta"]["status"] == "rejected"
-
-
-def test_commit_service_accepts_when_all_checks_pass(tmp_path):
-    service = ChapterCommitService(tmp_path)
-    payload = service.build_commit(
-        chapter=3,
-        review_result={"blocking_count": 0},
-        fulfillment_result={"planned_nodes": ["发现陷阱"], "covered_nodes": ["发现陷阱"], "missed_nodes": [], "extra_nodes": []},
-        disambiguation_result={"pending": []},
-        extraction_result={"state_deltas": [], "entity_deltas": [], "accepted_events": []},
-    )
-    assert payload["meta"]["status"] == "accepted"
-    assert payload["contract_refs"]["master"] == "MASTER_SETTING.json"
-    assert payload["contract_refs"]["chapter"] == "chapter_003.json"
-    assert payload["outline_snapshot"]["covered_nodes"] == ["发现陷阱"]
-```
-
-- [ ] **Step 2: 跑红灯**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_chapter_commit_service.py -q --no-cov`
-
-Expected: `ModuleNotFoundError: No module named 'data_modules.chapter_commit_service'`
-
-- [ ] **Step 3: 实现提交服务**
-
-```python
-# webnovel-writer/scripts/data_modules/chapter_commit_service.py
-from __future__ import annotations
-
-import json
-from pathlib import Path
-from typing import Any, Dict
-
-from data_modules.index_projection_writer import IndexProjectionWriter
-from data_modules.memory_projection_writer import MemoryProjectionWriter
-from data_modules.state_projection_writer import StateProjectionWriter
-from data_modules.summary_projection_writer import SummaryProjectionWriter
-
-
-class ChapterCommitService:
-    def __init__(self, project_root: Path):
-        self.project_root = Path(project_root)
-
-    def build_commit(
-        self,
-        chapter: int,
-        review_result: Dict[str, Any],
-        fulfillment_result: Dict[str, Any],
-        disambiguation_result: Dict[str, Any],
-        extraction_result: Dict[str, Any],
-    ) -> Dict[str, Any]:
-        rejected = bool(review_result.get("blocking_count")) or bool(fulfillment_result.get("missed_nodes")) or bool(disambiguation_result.get("pending"))
-        status = "rejected" if rejected else "accepted"
-        return {
-            "meta": {"schema_version": "story-system/v1", "chapter": chapter, "status": status},
-            "contract_refs": {
-                "master": "MASTER_SETTING.json",
-                "chapter": f"chapter_{chapter:03d}.json",
-                "review": f"chapter_{chapter:03d}.review.json",
-            },
-            "outline_snapshot": {
-                "planned_nodes": fulfillment_result.get("planned_nodes", []),
-                "covered_nodes": fulfillment_result.get("covered_nodes", []),
-                "missed_nodes": fulfillment_result.get("missed_nodes", []),
-                "extra_nodes": fulfillment_result.get("extra_nodes", []),
-            },
-            "review_result": review_result,
-            "fulfillment_result": fulfillment_result,
-            "disambiguation_result": disambiguation_result,
-            "accepted_events": extraction_result.get("accepted_events", []),
-            "state_deltas": extraction_result.get("state_deltas", []),
-            "entity_deltas": extraction_result.get("entity_deltas", []),
-            "projection_status": {"state": "pending", "index": "pending", "summary": "pending", "memory": "pending"},
-        }
-
-    def persist_commit(self, payload: Dict[str, Any]) -> Path:
-        target = self.project_root / ".story-system" / "commits"
-        target.mkdir(parents=True, exist_ok=True)
-        path = target / f"chapter_{int(payload['meta']['chapter']):03d}.commit.json"
-        path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
-        return path
-
-    def apply_projections(self, payload: Dict[str, Any]) -> Dict[str, Any]:
-        if payload["meta"]["status"] != "accepted":
-            return payload
-
-        writers = {
-            "state": StateProjectionWriter(self.project_root),
-            "index": IndexProjectionWriter(self.project_root),
-            "summary": SummaryProjectionWriter(self.project_root),
-            "memory": MemoryProjectionWriter(self.project_root),
-        }
-        for name, writer in writers.items():
-            try:
-                result = writer.apply(payload)
-                payload["projection_status"][name] = "done" if result.get("applied") else "skipped"
-            except Exception as exc:
-                payload["projection_status"][name] = f"failed:{exc}"
-        self.persist_commit(payload)
-        return payload
-```
-
-这里补一条 Phase 3 / Phase 4 的职责协议,后续实现必须遵守:
-
-- `ChapterCommitService.apply_projections()` 始终是唯一调度入口
-- Phase 4 引入的 `EventProjectionRouter` 只负责判定“哪些 writer 应被激活”
-- `EventProjectionRouter` **不单独再跑一轮投影**,避免 `state_deltas` 与 `accepted_events` 双重落库
-
-`review_pipeline.py` 在本 Task 必须补一条明确接线:
-
-- 汇总 `review_result / fulfillment_result / disambiguation_result / extraction_result`
-- 调 `ChapterCommitService.build_commit()`
-- 先 `persist_commit()`,再依据 `payload["meta"]["status"]` 决定是否进入投影阶段
-
-也就是说,`review_pipeline.py` 不再只是“被列进修改文件”,而是 Phase 3 写后主链真正的调用入口。
-
-- [ ] **Step 4: 回跑测试**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_chapter_commit_service.py -q --no-cov`
-
-Expected: 通过
-
-- [ ] **Step 5: 提交**
-
-```bash
-git add webnovel-writer/scripts/data_modules/chapter_commit_service.py \
-        webnovel-writer/scripts/data_modules/tests/test_chapter_commit_service.py \
-        webnovel-writer/scripts/review_pipeline.py
-git commit -m "feat: add chapter commit service and status semantics"
-```
-
----
-
-## Task 3: 落地四类 projection writers
-
-**Files:**
-- Create: `webnovel-writer/scripts/data_modules/state_projection_writer.py`
-- Create: `webnovel-writer/scripts/data_modules/index_projection_writer.py`
-- Create: `webnovel-writer/scripts/data_modules/summary_projection_writer.py`
-- Create: `webnovel-writer/scripts/data_modules/memory_projection_writer.py`
-- Create: `webnovel-writer/scripts/data_modules/tests/test_projection_writers.py`
-- Modify: `webnovel-writer/scripts/data_modules/index_manager.py`
-- Modify: `webnovel-writer/scripts/data_modules/state_manager.py`
-- Modify: `webnovel-writer/scripts/data_modules/memory/writer.py`
-
-- [ ] **Step 1: 先写 accepted / rejected 投影测试**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_projection_writers.py
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-import json
-
-from data_modules.chapter_commit_service import ChapterCommitService
-from data_modules.state_projection_writer import StateProjectionWriter
-
-
-def test_state_projection_writer_skips_rejected_commit(tmp_path):
-    writer = StateProjectionWriter(tmp_path)
-    result = writer.apply({"meta": {"status": "rejected"}, "state_deltas": []})
-    assert result["applied"] is False
-
-
-def test_state_projection_writer_applies_accepted_commit(tmp_path):
-    (tmp_path / ".webnovel").mkdir(parents=True, exist_ok=True)
-    (tmp_path / ".webnovel" / "state.json").write_text("{}", encoding="utf-8")
-    writer = StateProjectionWriter(tmp_path)
-    result = writer.apply({"meta": {"status": "accepted"}, "state_deltas": [{"entity_id": "x", "field": "realm", "new": "斗者"}]})
-    assert result["applied"] is True
-    payload = json.loads((tmp_path / ".webnovel" / "state.json").read_text(encoding="utf-8"))
-    assert payload["entity_state"]["x"]["realm"] == "斗者"
-
-
-def test_accepted_commit_updates_state_json_end_to_end(tmp_path):
-    (tmp_path / ".webnovel").mkdir(parents=True, exist_ok=True)
-    (tmp_path / ".webnovel" / "state.json").write_text("{}", encoding="utf-8")
-
-    service = ChapterCommitService(tmp_path)
-    commit_payload = service.build_commit(
-        chapter=3,
-        review_result={"blocking_count": 0},
-        fulfillment_result={"planned_nodes": ["发现陷阱"], "covered_nodes": ["发现陷阱"], "missed_nodes": [], "extra_nodes": []},
-        disambiguation_result={"pending": []},
-        extraction_result={"state_deltas": [{"entity_id": "x", "field": "realm", "new": "斗者"}], "entity_deltas": [], "accepted_events": []},
-    )
-
-    StateProjectionWriter(tmp_path).apply(commit_payload)
-    payload = json.loads((tmp_path / ".webnovel" / "state.json").read_text(encoding="utf-8"))
-    assert payload["entity_state"]["x"]["realm"] == "斗者"
-```
-
-- [ ] **Step 2: 跑红灯**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_projection_writers.py -q --no-cov`
-
-Expected: `ModuleNotFoundError` for projection writer modules
-
-- [ ] **Step 3: 实现四类 writer**
-
-```python
-# webnovel-writer/scripts/data_modules/state_projection_writer.py
-from data_modules.story_contracts import read_json_if_exists
-
-
-class StateProjectionWriter:
-    def __init__(self, project_root: Path):
-        self.project_root = Path(project_root)
-
-    def apply(self, commit_payload: dict) -> dict:
-        if commit_payload["meta"]["status"] != "accepted":
-            return {"applied": False, "writer": "state", "reason": "commit_rejected"}
-
-        state_path = self.project_root / ".webnovel" / "state.json"
-        state = read_json_if_exists(state_path) or {}
-        entity_state = state.setdefault("entity_state", {})
-        applied_count = 0
-        for delta in commit_payload.get("state_deltas", []):
-            entity_id = str(delta.get("entity_id") or "").strip()
-            field = str(delta.get("field") or "").strip()
-            if not entity_id or not field:
-                continue
-            entity_state.setdefault(entity_id, {})[field] = delta.get("new")
-            applied_count += 1
-
-        state_path.write_text(json.dumps(state, ensure_ascii=False, indent=2), encoding="utf-8")
-        return {"applied": applied_count > 0, "writer": "state", "applied_count": applied_count}
-```
-
-其他三个 writer 在 Phase 3 可以先保持“最小投影”,但**不能是 no-op stub**,至少要薄适配到现有底座:
-
-```python
-class IndexProjectionWriter:
-    def apply(self, commit_payload: dict) -> dict:
-        if commit_payload["meta"]["status"] != "accepted":
-            return {"applied": False, "writer": "index", "reason": "commit_rejected"}
-        manager = IndexManager(self.project_root)
-        for delta in commit_payload.get("entity_deltas", []):
-            manager.apply_entity_delta(delta)
-        return {"applied": True, "writer": "index", "applied_count": len(commit_payload.get("entity_deltas", []))}
-
-
-class SummaryProjectionWriter:
-    def apply(self, commit_payload: dict) -> dict:
-        if commit_payload["meta"]["status"] != "accepted":
-            return {"applied": False, "writer": "summary", "reason": "commit_rejected"}
-        return append_summary_projection(self.project_root, commit_payload)
-
-
-class MemoryProjectionWriter:
-    def apply(self, commit_payload: dict) -> dict:
-        if commit_payload["meta"]["status"] != "accepted":
-            return {"applied": False, "writer": "memory", "reason": "commit_rejected"}
-        return MemoryWriter(self.project_root).apply_commit_projection(commit_payload)
-```
-
-这里的交付要求写死:
-
-- `StateProjectionWriter` 必须真实落地
-- `Index / Summary / Memory` 允许是薄适配,但必须调用真实底座或真实文件写入
-- 如果仓库当前不存在 `IndexManager.apply_entity_delta()`、`append_summary_projection()`、`MemoryWriter.apply_commit_projection()`,就在本 Task 一并补最小适配器骨架;函数名可调整,但 writer 层对外协议不变
-- `projection_status` 记录 `"done"` / `"skipped"` / `"failed:..."`,不能一律回 `"pending"`
-
-- [ ] **Step 4: 回跑测试**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_projection_writers.py -q --no-cov`
-
-Expected: 通过
-
-- [ ] **Step 5: 提交**
-
-```bash
-git add webnovel-writer/scripts/data_modules/state_projection_writer.py \
-        webnovel-writer/scripts/data_modules/index_projection_writer.py \
-        webnovel-writer/scripts/data_modules/summary_projection_writer.py \
-        webnovel-writer/scripts/data_modules/memory_projection_writer.py \
-        webnovel-writer/scripts/data_modules/tests/test_projection_writers.py \
-        webnovel-writer/scripts/data_modules/state_manager.py \
-        webnovel-writer/scripts/data_modules/memory/writer.py
-git commit -m "feat: add commit-driven projection writers"
-```
-
----
-
-## Task 4: CLI / Skill 接入、文档与验证
-
-**Files:**
-- Create: `webnovel-writer/scripts/chapter_commit.py`
-- Modify: `webnovel-writer/scripts/data_modules/webnovel.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_chapter_commit_service.py`
-- Modify: `webnovel-writer/skills/webnovel-write/SKILL.md`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py`
-- Create: `docs/architecture/story-system-phase3.md`
-- Modify: `README.md`
-- Modify: `docs/architecture/overview.md`
-- Modify: `docs/guides/commands.md`
-- Modify: `docs/superpowers/README.md`
-
-- [ ] **Step 1: 增加统一 CLI 转发测试**
-
-```python
-def test_webnovel_commit_forwards(monkeypatch, tmp_path):
-    from data_modules import webnovel as cli
-    project_root = tmp_path / "book"
-    (project_root / ".webnovel").mkdir(parents=True, exist_ok=True)
-    (project_root / ".webnovel" / "state.json").write_text("{}", encoding="utf-8")
-    called = {}
-
-    def _fake_run_script(script_name, argv):
-        called["script_name"] = script_name
-        called["argv"] = argv
-        return 0
-
-    monkeypatch.setattr(cli, "_run_script", _fake_run_script)
-    monkeypatch.setattr(sys, "argv", ["webnovel", "--project-root", str(project_root), "chapter-commit", "--chapter", "3"])
-    cli.main()
-
-    assert called["script_name"] == "chapter_commit.py"
-
-
-def test_chapter_commit_cli_builds_and_persists_commit(tmp_path, monkeypatch):
-    review_path = tmp_path / "review.json"
-    fulfillment_path = tmp_path / "fulfillment.json"
-    disambiguation_path = tmp_path / "disambiguation.json"
-    extraction_path = tmp_path / "extraction.json"
-    review_path.write_text('{"blocking_count": 0}', encoding="utf-8")
-    fulfillment_path.write_text('{"planned_nodes": ["发现陷阱"], "covered_nodes": ["发现陷阱"], "missed_nodes": [], "extra_nodes": []}', encoding="utf-8")
-    disambiguation_path.write_text('{"pending": []}', encoding="utf-8")
-    extraction_path.write_text('{"state_deltas": [], "entity_deltas": [], "accepted_events": []}', encoding="utf-8")
-
-    from chapter_commit import main
-
-    monkeypatch.setattr(
-        sys,
-        "argv",
-        [
-            "chapter_commit",
-            "--project-root",
-            str(tmp_path),
-            "--chapter",
-            "3",
-            "--review-result",
-            str(review_path),
-            "--fulfillment-result",
-            str(fulfillment_path),
-            "--disambiguation-result",
-            str(disambiguation_path),
-            "--extraction-result",
-            str(extraction_path),
-        ],
-    )
-    main()
-
-    assert (tmp_path / ".story-system" / "commits" / "chapter_003.commit.json").is_file()
-```
-
-- [ ] **Step 2: 接入 CLI 与技能**
-
-在 `webnovel.py` 增加:
-
-```python
-# webnovel-writer/scripts/chapter_commit.py
-def _read_json(path: str) -> dict:
-    return json.loads(Path(path).read_text(encoding="utf-8"))
-
-
-def main() -> None:
-    parser = argparse.ArgumentParser(description="Chapter commit CLI")
-    parser.add_argument("--project-root", required=True)
-    parser.add_argument("--chapter", type=int, required=True)
-    parser.add_argument("--review-result", required=True)
-    parser.add_argument("--fulfillment-result", required=True)
-    parser.add_argument("--disambiguation-result", required=True)
-    parser.add_argument("--extraction-result", required=True)
-    args = parser.parse_args()
-
-    service = ChapterCommitService(Path(args.project_root))
-    payload = service.build_commit(
-        chapter=args.chapter,
-        review_result=_read_json(args.review_result),
-        fulfillment_result=_read_json(args.fulfillment_result),
-        disambiguation_result=_read_json(args.disambiguation_result),
-        extraction_result=_read_json(args.extraction_result),
-    )
-    service.persist_commit(payload)
-    if payload["meta"]["status"] == "accepted":
-        payload = service.apply_projections(payload)
-    print(json.dumps(payload, ensure_ascii=False))
-
-# webnovel-writer/scripts/data_modules/webnovel.py
-p_commit = sub.add_parser("chapter-commit", help="转发到 chapter_commit.py")
-p_commit.add_argument("args", nargs=argparse.REMAINDER)
-```
-
-在 `skills/webnovel-write/SKILL.md` 将原先“写完直接 state / index / summaries / memory 回写”替换为:
-
-```bash
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" \
-  chapter-commit --chapter {chapter_num} \
-  --review-result "{review_json}" \
-  --fulfillment-result "{fulfillment_json}" \
-  --disambiguation-result "{disambiguation_json}" \
-  --extraction-result "{extraction_json}"
-```
-
-同时在文档里明确一个运行约束:
-
-- `chapter_commit.py` 是独立人工/CLI 入口
-- `review_pipeline.py` 是 skill 主流程中的集成入口
-- 同一次写后流程只能走其中一个入口,禁止 `review_pipeline.py` 已提交后再补跑 `chapter_commit.py`
-
-- [ ] **Step 3: 新建 Phase 3 文档并跑回归**
-
-Run:
-
-```bash
-python -m pytest \
-  webnovel-writer/scripts/data_modules/tests/test_story_commit_schema.py \
-  webnovel-writer/scripts/data_modules/tests/test_chapter_commit_service.py \
-  webnovel-writer/scripts/data_modules/tests/test_projection_writers.py \
-  webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py \
-  webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py \
-  -q --no-cov
-```
-
-Expected: 全部通过
-
-- [ ] **Step 4: 最终提交**
-
-```bash
-git add webnovel-writer/scripts/chapter_commit.py \
-        webnovel-writer/scripts/data_modules/webnovel.py \
-        webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py \
-        webnovel-writer/scripts/data_modules/tests/test_chapter_commit_service.py \
-        webnovel-writer/skills/webnovel-write/SKILL.md \
-        webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py \
-        README.md \
-        docs/architecture/story-system-phase3.md \
-        docs/architecture/overview.md \
-        docs/guides/commands.md \
-        docs/superpowers/README.md
-git commit -m "docs: document story system phase3 chapter commit chain"
-```
-
----
-
-## Spec Coverage Check
-
-- `13.4 Phase 3:章节提交主链`
-  - `CHAPTER_COMMIT`:Task 1 / Task 2
-  - 四类 projection writers:Task 3
-  - accepted / rejected 语义:Task 2 / Task 3
-  - 写后回写改为 commit 驱动:Task 3 / Task 4
-
-- `9.2 / 9.3 / 9.5`
-  - 最小结构、提交流程、失败语义:Task 1 / Task 2
-
-- `11.2 / 11.3`
-  - 履约 / missed nodes 阻断:Task 2
-
----
-
-## Placeholder Scan
-
-- 没有使用 `TODO / TBD`
-- 没有把 projection writer 写成“后续补齐”
-- 没有提前把 Phase 4 的 canonical event log 混进本阶段
-
----
-
-## Next Plan
-
-Phase 3 完成后进入:
-
-1. `Phase 4 Event Log And Override Ledger`

+ 0 - 757
docs/archive/superpowers/plans/2026-04-12-story-system-phase4-event-log-and-override-ledger.md

@@ -1,757 +0,0 @@
-# Story System Phase 4 Event Log And Override Ledger Implementation Plan
-
-> **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:** 建立 canonical event log、事件到投影的稳定映射、事件到 `amend proposal` 的触发规则,并把现有 `override_contracts` 演进成统一 override ledger 的底座。
-
-**Architecture:** 在 Phase 3 的 `CHAPTER_COMMIT.accepted_events` 基础上,新增 `.story-system/events/` 持久化与 `index.db` 审计镜像,让事件成为正式输入而不是散落在 `state_changes / relationship_events / memory_facts` 的局部痕迹。同时把 `override_contracts` 从追读力债务专用扩展成包含 `soft_deviation / contract_override / amend_proposal` 的统一账本,但默认 runtime 只消费当前章相关摘要,不整包注入 prompt。
-
-**Tech Stack:** Python 3.13, Pydantic, pytest, SQLite (`index.db`), JSON event artifacts, dashboard / observability hooks
-
-**Spec:** `docs/superpowers/specs/2026-04-12-story-system-evolution-spec.md`
-
-**Companion Plans:** `docs/superpowers/plans/2026-04-12-story-system-phase3-chapter-commit-chain.md`, `docs/superpowers/specs/2026-04-12-webnovel-story-intelligence-system-spec.md`
-
----
-
-## Scope Split
-
-本计划只覆盖 Phase 4:
-
-1. canonical event log
-2. 事件到投影的稳定映射
-3. 事件到 `amend proposal` 的触发规则
-4. override ledger 扩展
-
-明确不做:
-
-- 不做 Phase 5 的旧链路降级
-- 不清理 `genre-profiles.md` 回退链
-- 不把所有历史 override 直接注入 runtime prompt
-
-退出标准:
-
-1. accepted commit 会稳定产出事件文件,并同步写入 `.webnovel/index.db.story_events`
-2. 投影层优先消费事件而不是最终覆盖值
-3. 需要上提的设定变更会生成 `amend proposal`
-4. `override_contracts` 可承载三类记录,并保留兼容旧追读力债务数据
-5. dashboard / preflight / health / backup 至少具备最小接入说明和只读检查入口
-
-文档更新继续追加到已有 `Story System` 段落,不重写 README 总体结构。
-
----
-
-## File Structure
-
-### 要创建的文件
-
-- `webnovel-writer/scripts/story_events.py`
-- `webnovel-writer/scripts/data_modules/story_event_schema.py`
-- `webnovel-writer/scripts/data_modules/event_log_store.py`
-- `webnovel-writer/scripts/data_modules/event_projection_router.py`
-- `webnovel-writer/scripts/data_modules/amend_proposal_schema.py`
-- `webnovel-writer/scripts/data_modules/override_ledger_service.py`
-- `webnovel-writer/scripts/data_modules/tests/test_story_event_schema.py`
-- `webnovel-writer/scripts/data_modules/tests/test_event_log_store.py`
-- `webnovel-writer/scripts/data_modules/tests/test_event_projection_router.py`
-- `webnovel-writer/scripts/data_modules/tests/test_override_ledger_service.py`
-- `docs/architecture/story-system-phase4.md`
-
-### 要修改的文件
-
-- `webnovel-writer/scripts/data_modules/chapter_commit_service.py`
-- `webnovel-writer/scripts/data_modules/index_manager.py`
-- `webnovel-writer/scripts/data_modules/index_debt_mixin.py`
-- `webnovel-writer/scripts/data_modules/index_observability_mixin.py`
-- `webnovel-writer/scripts/data_modules/webnovel.py`
-- `webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py`
-- `webnovel-writer/dashboard/app.py`
-- `README.md`
-- `docs/architecture/overview.md`
-- `docs/guides/commands.md`
-- `docs/operations/operations.md`
-- `docs/superpowers/README.md`
-
----
-
-## Task 1: 定义事件 schema 与 canonical event 持久化
-
-**Files:**
-- Create: `webnovel-writer/scripts/data_modules/story_event_schema.py`
-- Create: `webnovel-writer/scripts/data_modules/event_log_store.py`
-- Create: `webnovel-writer/scripts/data_modules/tests/test_story_event_schema.py`
-- Create: `webnovel-writer/scripts/data_modules/tests/test_event_log_store.py`
-- Modify: `webnovel-writer/scripts/data_modules/chapter_commit_service.py`
-
-- [ ] **Step 1: 先写事件 schema / store 测试**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_story_event_schema.py
-from data_modules.story_event_schema import StoryEvent
-
-
-def test_story_event_supports_power_breakthrough():
-    event = StoryEvent.model_validate(
-        {
-            "event_id": "evt-001",
-            "chapter": 3,
-            "event_type": "power_breakthrough",
-            "subject": "xiaoyan",
-            "payload": {"from": "斗之气三段", "to": "斗者"},
-        }
-    )
-    assert event.event_type == "power_breakthrough"
-```
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_event_log_store.py
-import sqlite3
-
-from data_modules.event_log_store import EventLogStore
-
-
-def test_event_log_store_writes_per_chapter_file_and_sqlite_mirror(tmp_path):
-    store = EventLogStore(tmp_path)
-    store.write_events(3, [{"event_id": "evt-001", "event_type": "open_loop_created", "subject": "三年之约", "payload": {}}])
-    assert (tmp_path / ".story-system" / "events" / "chapter_003.events.json").is_file()
-
-    conn = sqlite3.connect(tmp_path / ".webnovel" / "index.db")
-    try:
-        row = conn.execute("SELECT event_id, chapter, event_type FROM story_events").fetchone()
-    finally:
-        conn.close()
-    assert row == ("evt-001", 3, "open_loop_created")
-```
-
-- [ ] **Step 2: 跑红灯**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_story_event_schema.py webnovel-writer/scripts/data_modules/tests/test_event_log_store.py -q --no-cov`
-
-Expected: `ModuleNotFoundError`
-
-- [ ] **Step 3: 实现 schema 与 store**
-
-```python
-# webnovel-writer/scripts/data_modules/story_event_schema.py
-from __future__ import annotations
-
-from typing import Any, Dict, Literal
-
-from pydantic import BaseModel
-
-
-class StoryEvent(BaseModel):
-    event_id: str
-    chapter: int
-    event_type: Literal[
-        "character_state_changed",
-        "relationship_changed",
-        "world_rule_revealed",
-        "world_rule_broken",
-        "power_breakthrough",
-        "artifact_obtained",
-        "promise_created",
-        "promise_paid_off",
-        "open_loop_created",
-        "open_loop_closed",
-    ]
-    subject: str
-    payload: Dict[str, Any]
-```
-
-```python
-# webnovel-writer/scripts/data_modules/event_log_store.py
-import json
-import sqlite3
-from pathlib import Path
-
-
-class EventLogStore:
-    def __init__(self, project_root: Path):
-        self.project_root = Path(project_root)
-
-    def write_events(self, chapter: int, events: list[dict]) -> Path:
-        target = self.project_root / ".story-system" / "events"
-        target.mkdir(parents=True, exist_ok=True)
-        path = target / f"chapter_{chapter:03d}.events.json"
-        path.write_text(json.dumps(events, ensure_ascii=False, indent=2), encoding="utf-8")
-        self._write_sqlite_mirror(chapter, events)
-        return path
-
-    def _write_sqlite_mirror(self, chapter: int, events: list[dict]) -> None:
-        db_path = self.project_root / ".webnovel" / "index.db"
-        db_path.parent.mkdir(parents=True, exist_ok=True)
-        conn = sqlite3.connect(db_path)
-        try:
-            conn.execute(
-                """
-                CREATE TABLE IF NOT EXISTS story_events (
-                    id INTEGER PRIMARY KEY AUTOINCREMENT,
-                    event_id TEXT NOT NULL UNIQUE,
-                    chapter INTEGER NOT NULL,
-                    event_type TEXT NOT NULL,
-                    subject TEXT NOT NULL,
-                    payload_json TEXT NOT NULL
-                )
-                """
-            )
-            conn.executemany(
-                "INSERT OR IGNORE INTO story_events(event_id, chapter, event_type, subject, payload_json) VALUES (?, ?, ?, ?, ?)",
-                [
-                    (
-                        event["event_id"],
-                        chapter,
-                        event["event_type"],
-                        event["subject"],
-                        json.dumps(event.get("payload") or {}, ensure_ascii=False),
-                    )
-                    for event in events
-                ],
-            )
-            conn.commit()
-        finally:
-            conn.close()
-```
-
-- [ ] **Step 4: 让 `chapter_commit_service` 在 accepted commit 后写事件文件**
-
-```python
-if payload["meta"]["status"] == "accepted":
-    EventLogStore(self.project_root).write_events(chapter, payload["accepted_events"])
-```
-
-- [ ] **Step 5: 回跑测试**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_story_event_schema.py webnovel-writer/scripts/data_modules/tests/test_event_log_store.py -q --no-cov`
-
-Expected: 通过
-
-- [ ] **Step 6: 提交**
-
-```bash
-git add webnovel-writer/scripts/data_modules/story_event_schema.py \
-        webnovel-writer/scripts/data_modules/event_log_store.py \
-        webnovel-writer/scripts/data_modules/tests/test_story_event_schema.py \
-        webnovel-writer/scripts/data_modules/tests/test_event_log_store.py \
-        webnovel-writer/scripts/data_modules/chapter_commit_service.py
-git commit -m "feat: add canonical event schema and per-chapter event store"
-```
-
----
-
-## Task 2: 建立事件到投影的稳定映射
-
-**Files:**
-- Create: `webnovel-writer/scripts/data_modules/event_projection_router.py`
-- Create: `webnovel-writer/scripts/data_modules/tests/test_event_projection_router.py`
-- Modify: `webnovel-writer/scripts/data_modules/state_projection_writer.py`
-- Modify: `webnovel-writer/scripts/data_modules/index_projection_writer.py`
-- Modify: `webnovel-writer/scripts/data_modules/memory_projection_writer.py`
-
-- [ ] **Step 1: 先写事件路由测试**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_event_projection_router.py
-from data_modules.event_projection_router import EventProjectionRouter
-
-
-def test_router_maps_power_breakthrough_to_state_and_memory():
-    router = EventProjectionRouter()
-    targets = router.route({"event_type": "power_breakthrough", "subject": "xiaoyan", "payload": {}})
-    assert targets == ["state", "memory"]
-
-
-def test_router_maps_relationship_changed_to_index():
-    router = EventProjectionRouter()
-    targets = router.route({"event_type": "relationship_changed", "subject": "xiaoyan", "payload": {"to": "yaolao"}})
-    assert "index" in targets
-```
-
-- [ ] **Step 2: 跑红灯**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_event_projection_router.py -q --no-cov`
-
-Expected: `ModuleNotFoundError`
-
-- [ ] **Step 3: 实现路由器并让 writer 消费事件**
-
-```python
-# webnovel-writer/scripts/data_modules/event_projection_router.py
-class EventProjectionRouter:
-    TABLE = {
-        "character_state_changed": ["state", "memory"],
-        "power_breakthrough": ["state", "memory"],
-        "relationship_changed": ["index"],
-        "world_rule_revealed": ["memory", "index"],
-        "world_rule_broken": ["memory", "index"],
-        "open_loop_created": ["memory"],
-        "open_loop_closed": ["memory"],
-        "promise_created": ["memory"],
-        "promise_paid_off": ["memory"],
-        "artifact_obtained": ["state", "index"],
-    }
-
-    def route(self, event: dict) -> list[str]:
-        return list(self.TABLE.get(event.get("event_type"), []))
-```
-
-这里把 P3 / P4 的关系写死,避免实现时出现双重投影:
-
-- `EventProjectionRouter` 是**声明式激活表**
-- 真正的执行入口仍是 `ChapterCommitService.apply_projections()`
-- `apply_projections()` 先汇总 `accepted_events` 命中的 writer 集合,再只调需要的 writer
-- Phase 4 **不新增第二套独立投影循环**
-
-- [ ] **Step 4: 回跑测试**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_event_projection_router.py -q --no-cov`
-
-Expected: 通过
-
-- [ ] **Step 5: 提交**
-
-```bash
-git add webnovel-writer/scripts/data_modules/event_projection_router.py \
-        webnovel-writer/scripts/data_modules/tests/test_event_projection_router.py \
-        webnovel-writer/scripts/data_modules/state_projection_writer.py \
-        webnovel-writer/scripts/data_modules/index_projection_writer.py \
-        webnovel-writer/scripts/data_modules/memory_projection_writer.py
-git commit -m "feat: route accepted events into projection writers"
-```
-
----
-
-## Task 3: 把 `override_contracts` 扩展为统一 override ledger
-
-**Files:**
-- Create: `webnovel-writer/scripts/data_modules/amend_proposal_schema.py`
-- Create: `webnovel-writer/scripts/data_modules/override_ledger_service.py`
-- Create: `webnovel-writer/scripts/data_modules/tests/test_override_ledger_service.py`
-- Modify: `webnovel-writer/scripts/data_modules/index_manager.py`
-- Modify: `webnovel-writer/scripts/data_modules/index_debt_mixin.py`
-- Modify: `webnovel-writer/scripts/data_modules/index_observability_mixin.py`
-
-- [ ] **Step 1: 先写 ledger / amend proposal 测试**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_override_ledger_service.py
-from data_modules.override_ledger_service import AmendProposalTrigger, normalize_override_record
-
-
-def test_normalize_override_record_sets_record_type():
-    row = normalize_override_record(
-        record_type="contract_override",
-        field="core_tone",
-        base_value="先压后爆",
-        override_value="当场爆发",
-        source_level="chapter",
-    )
-    assert row["record_type"] == "contract_override"
-    assert row["field"] == "core_tone"
-
-
-def test_normalize_override_record_supports_amend_proposal():
-    row = normalize_override_record(
-        record_type="amend_proposal",
-        field="world_rule",
-        base_value="金手指每日一次",
-        override_value="金手指失控突破",
-        source_level="master",
-    )
-    assert row["record_type"] == "amend_proposal"
-
-
-def test_world_rule_broken_generates_amend_proposal():
-    trigger = AmendProposalTrigger()
-    proposals = trigger.check(
-        chapter=3,
-        events=[
-            {
-                "event_id": "evt-001",
-                "event_type": "world_rule_broken",
-                "subject": "金手指",
-                "payload": {"field": "world_rule", "base_value": "每日一次", "proposed_value": "短时失控突破"},
-            }
-        ],
-    )
-    assert len(proposals) == 1
-    assert proposals[0]["target_level"] == "master"
-    assert proposals[0]["field"] == "world_rule"
-```
-
-- [ ] **Step 2: 跑红灯**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_override_ledger_service.py -q --no-cov`
-
-Expected: `ModuleNotFoundError`
-
-- [ ] **Step 3: 实现 ledger 标准化与增量迁移**
-
-```python
-# webnovel-writer/scripts/data_modules/amend_proposal_schema.py
-from pydantic import BaseModel
-
-
-class AmendProposal(BaseModel):
-    proposal_id: str
-    chapter: int
-    target_level: str
-    field: str
-    base_value: str
-    proposed_value: str
-    reason_tag: str
-
-# webnovel-writer/scripts/data_modules/override_ledger_service.py
-def normalize_override_record(*, record_type: str, field: str, base_value: str, override_value: str, source_level: str) -> dict:
-    return {
-        "record_type": record_type,
-        "field": field,
-        "base_value": base_value,
-        "override_value": override_value,
-        "source_level": source_level,
-    }
-
-
-class AmendProposalTrigger:
-    RULES = {
-        "world_rule_broken": {"target_level": "master", "reason_tag": "world_rule_broken"},
-        "relationship_changed": None,
-        "power_breakthrough": None,
-        "artifact_obtained": None,
-    }
-
-    def check(self, chapter: int, events: list[dict]) -> list[dict]:
-        proposals: list[dict] = []
-        for event in events:
-            rule = self.RULES.get(event.get("event_type"))
-            if not rule:
-                continue
-            payload = event.get("payload") or {}
-            proposals.append(
-                {
-                    "proposal_id": f"amend-{chapter}-{event.get('event_id')}",
-                    "chapter": chapter,
-                    "target_level": rule["target_level"],
-                    "field": payload.get("field", ""),
-                    "base_value": payload.get("base_value", ""),
-                    "proposed_value": payload.get("proposed_value", ""),
-                    "reason_tag": rule["reason_tag"],
-                }
-            )
-        return proposals
-
-
-def persist_amend_proposals(conn, chapter: int, proposals: list[dict]) -> int:
-    inserted = 0
-    for proposal in proposals:
-        row = normalize_override_record(
-            record_type="amend_proposal",
-            field=proposal["field"],
-            base_value=proposal["base_value"],
-            override_value=proposal["proposed_value"],
-            source_level=proposal["target_level"],
-        )
-        conn.execute(
-            """
-            INSERT INTO override_contracts (
-                chapter,
-                record_type,
-                field,
-                base_value,
-                override_value,
-                source_level,
-                reason_tag,
-                rationale_type,
-                rationale_text,
-                status
-            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
-            """,
-            (
-                chapter,
-                row["record_type"],
-                row["field"],
-                row["base_value"],
-                row["override_value"],
-                row["source_level"],
-                proposal["reason_tag"],
-                "story_amend_proposal",
-                f"事件触发合同修订提案: {proposal['proposal_id']}",
-                "pending",
-            ),
-        )
-        inserted += 1
-    return inserted
-```
-
-在 `index_manager.py` 对 `override_contracts` 做兼容式扩列:
-
-```python
-def ensure_override_ledger_columns(conn) -> None:
-    existing = {row[1] for row in conn.execute("PRAGMA table_info(override_contracts)").fetchall()}
-    wanted = {
-        "record_type": "TEXT DEFAULT 'soft_deviation'",
-        "field": "TEXT DEFAULT ''",
-        "base_value": "TEXT DEFAULT ''",
-        "override_value": "TEXT DEFAULT ''",
-        "source_level": "TEXT DEFAULT ''",
-        "reason_tag": "TEXT DEFAULT ''",
-    }
-    for name, ddl in wanted.items():
-        if name not in existing:
-            conn.execute(f"ALTER TABLE override_contracts ADD COLUMN {name} {ddl}")
-```
-
-同时在 `chapter_commit_service.py` 的 accepted 分支里补一条完整调用:
-
-```python
-if payload["meta"]["status"] == "accepted":
-    proposals = AmendProposalTrigger().check(chapter, payload["accepted_events"])
-    if proposals:
-        with IndexManager(self.project_root)._get_conn() as conn:
-            ensure_override_ledger_columns(conn)
-            persist_amend_proposals(conn, chapter, proposals)
-            conn.commit()
-```
-
-这样 Phase 4 才算真正实现了“事件 -> amend proposal -> 人工确认后上提合同”的中间主链。
-
-- [ ] **Step 4: 回跑测试**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_override_ledger_service.py webnovel-writer/scripts/data_modules/tests/test_data_modules.py -q --no-cov`
-
-Expected: 通过
-
-- [ ] **Step 5: 提交**
-
-```bash
-git add webnovel-writer/scripts/data_modules/amend_proposal_schema.py \
-        webnovel-writer/scripts/data_modules/override_ledger_service.py \
-        webnovel-writer/scripts/data_modules/tests/test_override_ledger_service.py \
-        webnovel-writer/scripts/data_modules/index_manager.py \
-        webnovel-writer/scripts/data_modules/index_debt_mixin.py \
-        webnovel-writer/scripts/data_modules/index_observability_mixin.py
-git commit -m "feat: extend override contracts into story override ledger"
-```
-
----
-
-## Task 4: CLI / Dashboard / 文档与验证
-
-**Files:**
-- Create: `webnovel-writer/scripts/story_events.py`
-- Modify: `webnovel-writer/scripts/data_modules/webnovel.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_event_log_store.py`
-- Modify: `webnovel-writer/dashboard/app.py`
-- Create: `docs/architecture/story-system-phase4.md`
-- Modify: `README.md`
-- Modify: `docs/architecture/overview.md`
-- Modify: `docs/guides/commands.md`
-- Modify: `docs/operations/operations.md`
-- Modify: `docs/superpowers/README.md`
-
-- [ ] **Step 1: 增加 CLI 转发与读取测试**
-
-```python
-def test_webnovel_story_events_forwards(monkeypatch, tmp_path):
-    from data_modules import webnovel as cli
-    project_root = tmp_path / "book"
-    (project_root / ".webnovel").mkdir(parents=True, exist_ok=True)
-    (project_root / ".webnovel" / "state.json").write_text("{}", encoding="utf-8")
-    called = {}
-
-    def _fake_run_script(script_name, argv):
-        called["script_name"] = script_name
-        called["argv"] = argv
-        return 0
-
-    monkeypatch.setattr(cli, "_run_script", _fake_run_script)
-    monkeypatch.setattr(sys, "argv", ["webnovel", "--project-root", str(project_root), "story-events", "--chapter", "3"])
-    cli.main()
-    assert called["script_name"] == "story_events.py"
-```
-
-在 `test_event_log_store.py` 追加一个直接读取测试:
-
-```python
-def test_story_events_cli_reads_chapter_file(tmp_path, monkeypatch, capsys):
-    events_dir = tmp_path / ".story-system" / "events"
-    events_dir.mkdir(parents=True, exist_ok=True)
-    (events_dir / "chapter_003.events.json").write_text(
-        '[{"event_id":"evt-001","chapter":3,"event_type":"open_loop_created","subject":"三年之约","payload":{}}]',
-        encoding="utf-8",
-    )
-
-    from story_events import main
-
-    monkeypatch.setattr(sys, "argv", ["story_events", "--project-root", str(tmp_path), "--chapter", "3"])
-    main()
-
-    out = capsys.readouterr().out
-    assert "open_loop_created" in out
-```
-
-- [ ] **Step 2: 暴露查询入口并更新 dashboard**
-
-在 `webnovel.py` 增加:
-
-```python
-# webnovel-writer/scripts/story_events.py
-import json
-import sqlite3
-from pathlib import Path
-
-
-def _events_file(project_root: Path, chapter: int) -> Path:
-    return project_root / ".story-system" / "events" / f"chapter_{chapter:03d}.events.json"
-
-
-def main() -> None:
-    parser = argparse.ArgumentParser(description="Story events CLI")
-    parser.add_argument("--project-root", required=True)
-    parser.add_argument("--chapter", type=int, default=0)
-    parser.add_argument("--health", action="store_true")
-    args = parser.parse_args()
-    project_root = Path(args.project_root)
-
-    if args.health:
-        db_path = project_root / ".webnovel" / "index.db"
-        conn = sqlite3.connect(db_path)
-        try:
-            try:
-                row_count = conn.execute("SELECT COUNT(*) FROM story_events").fetchone()[0]
-            except sqlite3.OperationalError:
-                row_count = 0
-        finally:
-            conn.close()
-        file_count = len(list((project_root / ".story-system" / "events").glob("chapter_*.events.json")))
-        print(json.dumps({"ok": row_count >= 0, "sqlite_rows": row_count, "event_files": file_count}, ensure_ascii=False))
-        return
-
-    if args.chapter:
-        path = _events_file(project_root, args.chapter)
-        events = json.loads(path.read_text(encoding="utf-8")) if path.exists() else []
-        print(json.dumps({"chapter": args.chapter, "events": events}, ensure_ascii=False))
-        return
-
-    db_path = project_root / ".webnovel" / "index.db"
-    conn = sqlite3.connect(db_path)
-    try:
-        columns = ["event_id", "chapter", "event_type", "subject", "payload_json"]
-        rows = conn.execute(
-            "SELECT event_id, chapter, event_type, subject, payload_json FROM story_events ORDER BY chapter DESC, id DESC LIMIT 200"
-        ).fetchall()
-    finally:
-        conn.close()
-    print(json.dumps({"events": [dict(zip(columns, row)) for row in rows]}, ensure_ascii=False))
-
-# webnovel-writer/scripts/data_modules/webnovel.py
-p_story_events = sub.add_parser("story-events", help="转发到 story_events.py")
-p_story_events.add_argument("args", nargs=argparse.REMAINDER)
-```
-
-在 `dashboard/app.py` 按现有 `_get_db()` + `_fetchall_safe()` 模式增加只读接口:
-
-```python
-@app.get("/api/story-events")
-def list_story_events(chapter: Optional[int] = None, limit: int = 200):
-    with closing(_get_db()) as conn:
-        if chapter is not None:
-            return _fetchall_safe(
-                conn,
-                "SELECT * FROM story_events WHERE chapter = ? ORDER BY id DESC LIMIT ?",
-                (chapter, limit),
-            )
-        return _fetchall_safe(
-            conn,
-            "SELECT * FROM story_events ORDER BY chapter DESC, id DESC LIMIT ?",
-            (limit,),
-        )
-
-
-@app.get("/api/story-events/health")
-def story_event_health():
-    with closing(_get_db()) as conn:
-        event_rows = _fetchall_safe(conn, "SELECT COUNT(*) AS count FROM story_events")
-        proposal_rows = _fetchall_safe(
-            conn,
-            "SELECT COUNT(*) AS count FROM override_contracts WHERE record_type = 'amend_proposal' AND status = 'pending'",
-        )
-        return {
-            "story_events": event_rows[0]["count"] if event_rows else 0,
-            "pending_amend_proposals": proposal_rows[0]["count"] if proposal_rows else 0,
-        }
-```
-
-`docs/operations/operations.md` 这一轮必须补三段最小运维内容:
-
-- `preflight`:检查 `.story-system/events/` 是否存在、`story_events` 表是否可查
-- `health`:执行 `webnovel story-events --health`
-- `backup`:备份 `.story-system/` 与 `.webnovel/index.db`
-
-- [ ] **Step 3: 新建文档并跑 Phase 4 回归**
-
-Run:
-
-```bash
-python -m pytest \
-  webnovel-writer/scripts/data_modules/tests/test_story_event_schema.py \
-  webnovel-writer/scripts/data_modules/tests/test_event_log_store.py \
-  webnovel-writer/scripts/data_modules/tests/test_event_projection_router.py \
-  webnovel-writer/scripts/data_modules/tests/test_override_ledger_service.py \
-  webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py \
-  -q --no-cov
-```
-
-Expected: 全部通过
-
-- [ ] **Step 4: 最终提交**
-
-```bash
-git add webnovel-writer/scripts/story_events.py \
-        webnovel-writer/scripts/data_modules/webnovel.py \
-        webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py \
-        webnovel-writer/scripts/data_modules/tests/test_event_log_store.py \
-        webnovel-writer/dashboard/app.py \
-        README.md \
-        docs/architecture/story-system-phase4.md \
-        docs/architecture/overview.md \
-        docs/guides/commands.md \
-        docs/operations/operations.md \
-        docs/superpowers/README.md
-git commit -m "docs: document story system phase4 event log and override ledger"
-```
-
----
-
-## Spec Coverage Check
-
-- `13.5 Phase 4:统一事件主链`
-  - canonical event log:Task 1
-  - 事件到投影稳定映射:Task 2
-  - 事件到 amend proposal 触发规则:Task 3
-
-- `8.5 override ledger 的新定位`
-  - `soft_deviation / contract_override / amend_proposal`:Task 3
-
-- `10.2 / 10.4`
-  - accepted events 持久化与 amend proposal:Task 1 / Task 3
-
-- `17.2 / 17.3`
-  - dashboard / health / ops 接入:Task 4
-
----
-
-## Placeholder Scan
-
-- 没有使用 `TODO / TBD`
-- 没有把 override ledger 写成“以后再扩”
-- 没有提前进入 Phase 5 的旧链路降级
-
----
-
-## Next Plan
-
-Phase 4 之后才进入:
-
-1. `Phase 5 Legacy Downgrade`

+ 0 - 966
docs/archive/superpowers/plans/2026-04-13-story-system-phase5-legacy-downgrade.md

@@ -1,966 +0,0 @@
-# Story System Phase 5 Legacy Downgrade Implementation Plan
-
-> **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:** 把旧中枢正式降级为兼容/投影层,让写前默认输入统一到 Story Contracts,让写后默认事实统一到 accepted `CHAPTER_COMMIT`,并让 preflight / dashboard / skills / agents 都显式反映这条主链。
-
-**Architecture:** 新增统一 runtime 来源解析层,集中回答“本章现在应该信什么”。`context_manager`、`memory_contract_adapter`、`extract_chapter_context`、skills 与 dashboard 都只认 `MASTER / VOLUME / CHAPTER / REVIEW + latest accepted CHAPTER_COMMIT`;`state.json / index.db / summaries / memory_scratchpad` 保留,但只作为 commit 投影和查询 read-model。旧 `genre-profiles.md`、旧散写命令、旧 state-first 心智模型继续存在时,必须只以 fallback / compatibility 明示暴露,不能再伪装成主链。
-
-**Tech Stack:** Python 3.13, pytest, Pydantic, SQLite (`index.db`), FastAPI dashboard, React frontend, Story System JSON artifacts under `.story-system/`
-
-**Spec:** `docs/superpowers/specs/2026-04-12-story-system-evolution-spec.md`
-
-**Companion Plans:** `docs/superpowers/plans/2026-04-12-story-system-phase2-contract-first-runtime.md`, `docs/superpowers/plans/2026-04-12-story-system-phase3-chapter-commit-chain.md`, `docs/superpowers/plans/2026-04-12-story-system-phase4-event-log-and-override-ledger.md`
-
----
-
-## Scope Split
-
-本计划只覆盖 Phase 5:
-
-1. 合同成为默认主输入
-2. accepted `CHAPTER_COMMIT` 成为默认写后事实入口
-3. `state / index / summary / memory` 显式降级为投影/read-model
-4. `genre-profiles.md` 与旧 reference 判断链显式退化为 fallback
-5. preflight / dashboard / query / write / review / context-agent / data-agent 全部切到新主链认知
-
-明确不做:
-
-- 不重写 `StateManager / IndexManager / ScratchpadManager` 的底层存储
-- 不删除 `.webnovel/state.json`、`.webnovel/index.db`、`.webnovel/memory_scratchpad.json`
-- 不重建历史全量 commit / event 数据
-- 不新增第二套 commit / projection 体系
-- 不做与 Phase 5 无关的 UI 大改版
-
-退出标准:
-
-1. `context_manager`、`memory_contract_adapter`、`extract_chapter_context` 默认读取合同与 latest accepted commit,而不是先读旧状态再拼判断
-2. `ChapterCommitService` 写出的 commit 元数据足以声明其为唯一写后事实来源,并能稳定定位 `MASTER / VOLUME / CHAPTER / REVIEW`
-3. `webnovel-write` / `webnovel-query` / `webnovel-review` / `webnovel-plan` 与 `context-agent` / `data-agent` 的默认指令已切到 contract-first + commit-first
-4. preflight 与 dashboard 能直接暴露“主链是否就绪、是否仍落入 legacy fallback、是否存在 rejected / projection backlog”
-5. `genre-profiles.md` 被明示为 fallback-only;合同存在时不再参与全局系统判断
-6. 文档、命令说明、运维手册都能准确描述 `.story-system` 主链和 `.webnovel/*` 投影链的关系
-
----
-
-## File Structure
-
-### 要创建的文件
-
-- `webnovel-writer/scripts/data_modules/story_runtime_sources.py`
-- `webnovel-writer/scripts/data_modules/story_runtime_health.py`
-- `webnovel-writer/scripts/data_modules/tests/test_story_runtime_sources.py`
-- `webnovel-writer/scripts/data_modules/tests/test_story_runtime_health.py`
-- `docs/architecture/story-system-phase5.md`
-
-### 要修改的文件
-
-- `webnovel-writer/scripts/data_modules/chapter_commit_service.py`
-- `webnovel-writer/scripts/data_modules/context_manager.py`
-- `webnovel-writer/scripts/data_modules/memory_contract_adapter.py`
-- `webnovel-writer/scripts/extract_chapter_context.py`
-- `webnovel-writer/scripts/data_modules/tests/test_chapter_commit_service.py`
-- `webnovel-writer/scripts/data_modules/tests/test_context_manager.py`
-- `webnovel-writer/scripts/data_modules/tests/test_memory_contract_adapter.py`
-- `webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py`
-- `webnovel-writer/scripts/data_modules/webnovel.py`
-- `webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py`
-- `webnovel-writer/dashboard/app.py`
-- `webnovel-writer/dashboard/frontend/src/App.jsx`
-- `webnovel-writer/dashboard/frontend/src/api.js`
-- `webnovel-writer/skills/webnovel-write/SKILL.md`
-- `webnovel-writer/skills/webnovel-review/SKILL.md`
-- `webnovel-writer/skills/webnovel-query/SKILL.md`
-- `webnovel-writer/skills/webnovel-plan/SKILL.md`
-- `webnovel-writer/skills/webnovel-dashboard/SKILL.md`
-- `webnovel-writer/agents/context-agent.md`
-- `webnovel-writer/agents/data-agent.md`
-- `webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py`
-- `webnovel-writer/references/genre-profiles.md`
-- `README.md`
-- `docs/architecture/overview.md`
-- `docs/guides/commands.md`
-- `docs/operations/operations.md`
-- `docs/superpowers/README.md`
-
-### 文件职责
-
-- `story_runtime_sources.py`:统一解析本章 contracts、latest commit、fallback 状态,避免各入口各自判断
-- `story_runtime_health.py`:把主链状态、legacy fallback、rejected/backlog 汇总为 preflight / dashboard 共用健康报告
-- `chapter_commit_service.py`:补齐 write-fact provenance,确保 commit 能明确引用 volume 合同与写后真理角色
-- `context_manager.py`:把 runtime pack 切成“合同主链 + commit 摘要 + legacy fallback hints”
-- `memory_contract_adapter.py`:让 `load_context` 和 `commit_chapter` 都服从 contract/commit 主链
-- `extract_chapter_context.py`:把导出文本从“旧状态摘要”升级为“主链状态 + fallback 显示”
-- `webnovel.py`:preflight 暴露 story runtime health,统一 CLI 对外语义
-- `dashboard/app.py` + `frontend/src/*`:提供可视化 runtime health / commit 状态 / legacy fallback 观测
-- `skills/*` + `agents/*`:把人机提示词中的默认工作流从旧散写链切到 commit 主链
-
----
-
-## Task 1: 建立统一 runtime 来源解析与 commit provenance
-
-**Files:**
-- Create: `webnovel-writer/scripts/data_modules/story_runtime_sources.py`
-- Create: `webnovel-writer/scripts/data_modules/tests/test_story_runtime_sources.py`
-- Modify: `webnovel-writer/scripts/data_modules/chapter_commit_service.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_chapter_commit_service.py`
-
-- [ ] **Step 1: 先写 runtime 来源与 commit provenance 的失败测试**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_story_runtime_sources.py
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-import json
-
-from data_modules.story_runtime_sources import load_runtime_sources
-
-
-def test_load_runtime_sources_prefers_latest_accepted_commit(tmp_path):
-    story_root = tmp_path / ".story-system"
-    (story_root / "chapters").mkdir(parents=True, exist_ok=True)
-    (story_root / "volumes").mkdir(parents=True, exist_ok=True)
-    (story_root / "reviews").mkdir(parents=True, exist_ok=True)
-    (story_root / "commits").mkdir(parents=True, exist_ok=True)
-
-    (story_root / "MASTER_SETTING.json").write_text(
-        json.dumps({"meta": {"contract_type": "MASTER_SETTING"}, "route": {"primary_genre": "玄幻"}}),
-        encoding="utf-8",
-    )
-    (story_root / "chapters" / "chapter_003.json").write_text(
-        json.dumps({"meta": {"contract_type": "CHAPTER_BRIEF", "chapter": 3}}),
-        encoding="utf-8",
-    )
-    (story_root / "volumes" / "volume_001.json").write_text(
-        json.dumps({"meta": {"contract_type": "VOLUME_BRIEF", "volume": 1}}),
-        encoding="utf-8",
-    )
-    (story_root / "reviews" / "chapter_003.review.json").write_text(
-        json.dumps({"meta": {"contract_type": "REVIEW_CONTRACT", "chapter": 3}}),
-        encoding="utf-8",
-    )
-    (story_root / "commits" / "chapter_003.commit.json").write_text(
-        json.dumps(
-            {
-                "meta": {"schema_version": "story-system/v1", "chapter": 3, "status": "accepted"},
-                "provenance": {"write_fact_role": "chapter_commit"},
-                "projection_status": {"state": "done", "index": "done", "summary": "done", "memory": "done"},
-            }
-        ),
-        encoding="utf-8",
-    )
-
-    snapshot = load_runtime_sources(tmp_path, chapter=3)
-
-    assert snapshot.latest_accepted_commit["meta"]["status"] == "accepted"
-    assert snapshot.primary_write_source == "chapter_commit"
-    assert snapshot.fallback_sources == []
-```
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_chapter_commit_service.py
-def test_commit_service_includes_volume_ref_and_write_fact_provenance(tmp_path):
-    service = ChapterCommitService(tmp_path)
-    payload = service.build_commit(
-        chapter=3,
-        review_result={"blocking_count": 0},
-        fulfillment_result={"planned_nodes": ["发现陷阱"], "covered_nodes": ["发现陷阱"], "missed_nodes": [], "extra_nodes": []},
-        disambiguation_result={"pending": []},
-        extraction_result={"state_deltas": [], "entity_deltas": [], "accepted_events": []},
-    )
-
-    assert payload["contract_refs"]["volume"] == "volume_001.json"
-    assert payload["provenance"]["write_fact_role"] == "chapter_commit"
-    assert payload["provenance"]["projection_role"] == "derived_read_models"
-```
-
-- [ ] **Step 2: 跑红灯,确认新测试确实失败**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_story_runtime_sources.py webnovel-writer/scripts/data_modules/tests/test_chapter_commit_service.py -q --no-cov`
-
-Expected:
-- `ModuleNotFoundError: No module named 'data_modules.story_runtime_sources'`
-- 或 `KeyError: 'volume' / 'provenance'`
-
-- [ ] **Step 3: 实现统一 runtime 来源解析器**
-
-```python
-# webnovel-writer/scripts/data_modules/story_runtime_sources.py
-from __future__ import annotations
-
-from dataclasses import dataclass, field
-from pathlib import Path
-from typing import Any
-
-from .story_contracts import StoryContractPaths, read_json_if_exists
-
-
-@dataclass
-class RuntimeSourceSnapshot:
-    chapter: int
-    contracts: dict[str, dict[str, Any]]
-    latest_commit: dict[str, Any] | None
-    latest_accepted_commit: dict[str, Any] | None
-    fallback_sources: list[str] = field(default_factory=list)
-    primary_write_source: str = "chapter_commit"
-
-    def to_dict(self) -> dict[str, Any]:
-        return {
-            "chapter": self.chapter,
-            "contracts": self.contracts,
-            "latest_commit": self.latest_commit,
-            "latest_accepted_commit": self.latest_accepted_commit,
-            "fallback_sources": self.fallback_sources,
-            "primary_write_source": self.primary_write_source,
-        }
-
-
-def load_runtime_sources(project_root: Path, chapter: int) -> RuntimeSourceSnapshot:
-    paths = StoryContractPaths.from_project_root(project_root)
-    contracts = {
-        "master": read_json_if_exists(paths.master_json) or {},
-        "chapter": read_json_if_exists(paths.chapter_json(chapter)) or {},
-        "volume": read_json_if_exists(paths.volume_json(1)) or {},
-        "review": read_json_if_exists(paths.review_json(chapter)) or {},
-    }
-
-    latest_commit = read_json_if_exists(paths.commit_json(chapter))
-    latest_accepted_commit = latest_commit if (latest_commit or {}).get("meta", {}).get("status") == "accepted" else None
-
-    fallback_sources: list[str] = []
-    for key, payload in contracts.items():
-        if not payload:
-            fallback_sources.append(f"missing_{key}_contract")
-    if latest_accepted_commit is None:
-        fallback_sources.append("missing_accepted_commit")
-
-    return RuntimeSourceSnapshot(
-        chapter=chapter,
-        contracts=contracts,
-        latest_commit=latest_commit,
-        latest_accepted_commit=latest_accepted_commit,
-        fallback_sources=fallback_sources,
-    )
-```
-
-- [ ] **Step 4: 在 commit service 中补齐 provenance 字段和 volume 引用**
-
-```python
-# webnovel-writer/scripts/data_modules/chapter_commit_service.py
-from chapter_outline_loader import volume_num_for_chapter_from_state
-
-
-def build_commit(
-    self,
-    chapter: int,
-    review_result: Dict[str, Any],
-    fulfillment_result: Dict[str, Any],
-    disambiguation_result: Dict[str, Any],
-    extraction_result: Dict[str, Any],
-) -> Dict[str, Any]:
-    volume = volume_num_for_chapter_from_state(self.project_root, chapter) or 1
-    return {
-        "meta": {
-            "schema_version": "story-system/v1",
-            "chapter": chapter,
-            "status": status,
-        },
-        "contract_refs": {
-            "master": "MASTER_SETTING.json",
-            "volume": f"volume_{volume:03d}.json",
-            "chapter": f"chapter_{chapter:03d}.json",
-            "review": f"chapter_{chapter:03d}.review.json",
-        },
-        "provenance": {
-            "write_fact_role": "chapter_commit",
-            "projection_role": "derived_read_models",
-            "legacy_state_role": "projection_only",
-        },
-        "outline_snapshot": {
-            "planned_nodes": fulfillment_result.get("planned_nodes", []),
-            "covered_nodes": fulfillment_result.get("covered_nodes", []),
-            "missed_nodes": fulfillment_result.get("missed_nodes", []),
-            "extra_nodes": fulfillment_result.get("extra_nodes", []),
-        },
-        "review_result": review_result,
-        "fulfillment_result": fulfillment_result,
-        "disambiguation_result": disambiguation_result,
-        "accepted_events": extraction_result.get("accepted_events", []),
-        "state_deltas": extraction_result.get("state_deltas", []),
-        "entity_deltas": extraction_result.get("entity_deltas", []),
-        "summary_text": extraction_result.get("summary_text", ""),
-        "projection_status": {"state": "pending", "index": "pending", "summary": "pending", "memory": "pending"},
-    }
-```
-
-- [ ] **Step 5: 重新跑聚焦测试**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_story_runtime_sources.py webnovel-writer/scripts/data_modules/tests/test_chapter_commit_service.py -q --no-cov`
-
-Expected: `2 passed`
-
-- [ ] **Step 6: 提交本任务**
-
-```bash
-git add webnovel-writer/scripts/data_modules/story_runtime_sources.py \
-        webnovel-writer/scripts/data_modules/chapter_commit_service.py \
-        webnovel-writer/scripts/data_modules/tests/test_story_runtime_sources.py \
-        webnovel-writer/scripts/data_modules/tests/test_chapter_commit_service.py
-git commit -m "feat: add story runtime source resolver"
-```
-
----
-
-## Task 2: 把上下文入口切到 contract-first + commit-first
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/context_manager.py`
-- Modify: `webnovel-writer/scripts/data_modules/memory_contract_adapter.py`
-- Modify: `webnovel-writer/scripts/extract_chapter_context.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_context_manager.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_memory_contract_adapter.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py`
-
-- [ ] **Step 1: 先补三组失败测试,锁定新主链语义**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_context_manager.py
-def test_context_manager_prefers_contract_route_over_legacy_genre_profile(temp_project):
-    refs_dir = temp_project.project_root / ".claude" / "references"
-    refs_dir.mkdir(parents=True, exist_ok=True)
-    (refs_dir / "genre-profiles.md").write_text("## 都市\n- 旧画像提示", encoding="utf-8")
-    (refs_dir / "reading-power-taxonomy.md").write_text("## 都市\n- 旧分类", encoding="utf-8")
-
-    state = {
-        "project": {"genre": "都市"},
-        "protagonist_state": {"name": "林默"},
-        "chapter_meta": {},
-        "disambiguation_warnings": [],
-        "disambiguation_pending": [],
-    }
-    temp_project.state_file.write_text(json.dumps(state, ensure_ascii=False), encoding="utf-8")
-
-    story_root = temp_project.story_system_dir
-    story_root.mkdir(parents=True, exist_ok=True)
-    (story_root / "MASTER_SETTING.json").write_text(
-        json.dumps(
-            {
-                "meta": {"schema_version": "story-system/v1", "contract_type": "MASTER_SETTING"},
-                "route": {"primary_genre": "都市异能"},
-                "master_constraints": {"core_tone": "先压后爆"},
-            },
-            ensure_ascii=False,
-        ),
-        encoding="utf-8",
-    )
-
-    manager = ContextManager(temp_project)
-    payload = manager.build_context(3, use_snapshot=False, save_snapshot=False)
-
-    assert payload["sections"]["story_contract"]["content"]["master"]["route"]["primary_genre"] == "都市异能"
-    assert payload["sections"]["runtime_status"]["content"]["fallback_sources"] == ["missing_volume_contract", "missing_chapter_contract", "missing_review_contract", "missing_accepted_commit"]
-```
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_memory_contract_adapter.py
-def test_commit_chapter_delegates_to_chapter_commit_mainline(tmp_path):
-    cfg = _make_project(tmp_path)
-    adapter = MemoryContractAdapter(cfg)
-
-    result = adapter.commit_chapter(
-        3,
-        {
-            "review_result": {"blocking_count": 0},
-            "fulfillment_result": {"planned_nodes": ["发现陷阱"], "covered_nodes": ["发现陷阱"], "missed_nodes": [], "extra_nodes": []},
-            "disambiguation_result": {"pending": []},
-            "extraction_result": {"state_deltas": [], "entity_deltas": [], "accepted_events": [], "summary_text": "本章摘要"},
-        },
-    )
-
-    assert (tmp_path / ".story-system" / "commits" / "chapter_003.commit.json").is_file()
-    assert result.chapter == 3
-    assert "commit_status=accepted" in result.warnings
-```
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py
-def test_render_text_contains_runtime_status_section(tmp_path):
-    from extract_chapter_context import _render_text
-
-    text = _render_text(
-        {
-            "chapter": 3,
-            "outline": "测试大纲",
-            "previous_summaries": [],
-            "state_summary": "旧状态摘要",
-            "context_contract_version": "v2",
-            "reader_signal": {},
-            "genre_profile": {},
-            "writing_guidance": {},
-            "runtime_status": {
-                "primary_write_source": "chapter_commit",
-                "fallback_sources": ["missing_accepted_commit"],
-            },
-            "latest_commit": {"meta": {"chapter": 3, "status": "rejected"}},
-        }
-    )
-
-    assert "## Runtime Status" in text
-    assert "- 写后事实入口: chapter_commit" in text
-    assert "- Legacy Fallback: missing_accepted_commit" in text
-```
-
-- [ ] **Step 2: 跑红灯,确认当前入口仍然偏旧链路**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_context_manager.py webnovel-writer/scripts/data_modules/tests/test_memory_contract_adapter.py webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py -q --no-cov`
-
-Expected:
-- `KeyError: 'runtime_status'`
-- `AssertionError`(`commit_chapter` 仍未生成 `.story-system/commits/chapter_003.commit.json`)
-- `_render_text` 中不存在 runtime status 段
-
-- [ ] **Step 3: 重写 ContextManager 的 pack 组装顺序**
-
-```python
-# webnovel-writer/scripts/data_modules/context_manager.py
-from .story_runtime_sources import load_runtime_sources
-
-
-def _build_pack(self, chapter: int) -> Dict[str, Any]:
-    runtime_sources = load_runtime_sources(self.config.project_root, chapter)
-    state = self._load_state()
-
-    story_contract = {
-        "master": runtime_sources.contracts.get("master") or {},
-        "volume": runtime_sources.contracts.get("volume") or {},
-        "chapter": runtime_sources.contracts.get("chapter") or {},
-        "review_contract": runtime_sources.contracts.get("review") or {},
-    }
-
-    genre_profile = {}
-    if runtime_sources.fallback_sources:
-        genre_profile = self._load_genre_profile(state)
-        genre_profile["mode"] = "fallback_only"
-
-    reader_signal = self._load_reader_signal(chapter)
-    return {
-        "story_contract": story_contract,
-        "runtime_status": runtime_sources.to_dict(),
-        "latest_commit": runtime_sources.latest_accepted_commit or runtime_sources.latest_commit or {},
-        "genre_profile": genre_profile,
-        "reader_signal": reader_signal,
-        "preferences": self._load_json_optional(self.config.webnovel_dir / "preferences.json"),
-        "writing_guidance": self._build_writing_guidance(chapter, reader_signal, genre_profile),
-    }
-```
-
-- [ ] **Step 4: 让 MemoryContractAdapter 同时切换读链与写链**
-
-```python
-# webnovel-writer/scripts/data_modules/memory_contract_adapter.py
-from .chapter_commit_service import ChapterCommitService
-from .story_runtime_sources import load_runtime_sources
-
-
-def load_context(self, chapter: int, budget_tokens: int = 4000) -> ContextPack:
-    runtime_sources = load_runtime_sources(self.config.project_root, chapter)
-    sections = {
-        "story_contracts": runtime_sources.contracts,
-        "runtime_status": runtime_sources.to_dict(),
-        "latest_commit": runtime_sources.latest_accepted_commit or runtime_sources.latest_commit or {},
-    }
-    return ContextPack(chapter=chapter, sections=sections, budget_used_tokens=0)
-
-
-def commit_chapter(self, chapter: int, result: dict) -> CommitResult:
-    service = ChapterCommitService(self.config.project_root)
-    payload = service.build_commit(
-        chapter=chapter,
-        review_result=result.get("review_result", {}),
-        fulfillment_result=result.get("fulfillment_result", {}),
-        disambiguation_result=result.get("disambiguation_result", {}),
-        extraction_result=result.get("extraction_result", {}),
-    )
-    service.persist_commit(payload)
-    payload = service.apply_projections(payload) if payload["meta"]["status"] == "accepted" else payload
-    summary_path = str(self.config.webnovel_dir / "summaries" / f"ch{chapter:04d}.md")
-    return CommitResult(
-        chapter=chapter,
-        entities_added=len((payload.get("entity_deltas") or [])),
-        entities_updated=0,
-        state_changes_recorded=len((payload.get("state_deltas") or [])),
-        relationships_added=0,
-        memory_items_added=0,
-        summary_path=summary_path if Path(summary_path).exists() else "",
-        warnings=[f"commit_status={payload['meta']['status']}"],
-    )
-```
-
-- [ ] **Step 5: 更新 `extract_chapter_context.py` 的文本输出,让 legacy fallback 显式可见**
-
-```python
-# webnovel-writer/scripts/extract_chapter_context.py
-def _render_text(payload: Dict[str, Any]) -> str:
-    lines = [f"# 第{payload.get('chapter', 0)}章上下文"]
-    runtime_status = payload.get("runtime_status") or {}
-    latest_commit = payload.get("latest_commit") or {}
-    lines.extend(
-        [
-            "## Runtime Status",
-            f"- 写后事实入口: {runtime_status.get('primary_write_source', 'unknown')}",
-            f"- Legacy Fallback: {', '.join(runtime_status.get('fallback_sources') or ['none'])}",
-            f"- Latest Commit: {(latest_commit.get('meta') or {}).get('status', 'missing')}",
-        ]
-    )
-    return "\n".join(lines)
-```
-
-- [ ] **Step 6: 重跑聚焦测试**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_context_manager.py webnovel-writer/scripts/data_modules/tests/test_memory_contract_adapter.py webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py -q --no-cov`
-
-Expected: `3 passed` 或对应文件内全部通过
-
-- [ ] **Step 7: 提交本任务**
-
-```bash
-git add webnovel-writer/scripts/data_modules/context_manager.py \
-        webnovel-writer/scripts/data_modules/memory_contract_adapter.py \
-        webnovel-writer/scripts/extract_chapter_context.py \
-        webnovel-writer/scripts/data_modules/tests/test_context_manager.py \
-        webnovel-writer/scripts/data_modules/tests/test_memory_contract_adapter.py \
-        webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py
-git commit -m "feat: switch context loading to contract and commit chain"
-```
-
----
-
-## Task 3: 把 skills / agents 的默认工作流切到 commit 主链
-
-**Files:**
-- Modify: `webnovel-writer/skills/webnovel-write/SKILL.md`
-- Modify: `webnovel-writer/skills/webnovel-review/SKILL.md`
-- Modify: `webnovel-writer/skills/webnovel-query/SKILL.md`
-- Modify: `webnovel-writer/skills/webnovel-plan/SKILL.md`
-- Modify: `webnovel-writer/skills/webnovel-dashboard/SKILL.md`
-- Modify: `webnovel-writer/agents/context-agent.md`
-- Modify: `webnovel-writer/agents/data-agent.md`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py`
-
-- [ ] **Step 1: 先写 prompt integrity 失败测试,锁住新主链叙述**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py
-def test_webnovel_write_skill_uses_chapter_commit_as_step5_mainline():
-    text = (SKILLS_DIR / "webnovel-write" / "SKILL.md").read_text(encoding="utf-8")
-    assert "chapter-commit" in text
-    assert "accepted `CHAPTER_COMMIT`" in text
-    assert "state process-chapter" not in text
-
-
-def test_data_agent_is_described_as_extraction_only_not_direct_write_mainline():
-    text = (AGENTS_DIR / "data-agent.md").read_text(encoding="utf-8")
-    assert "chapter-commit" in text
-    assert "直接写入 index.db 和 state.json" not in text
-
-
-def test_webnovel_query_skill_prefers_story_system_and_memory_contract():
-    text = (SKILLS_DIR / "webnovel-query" / "SKILL.md").read_text(encoding="utf-8")
-    assert "memory-contract load-context" in text
-    assert ".story-system/" in text
-    assert 'cat "$PROJECT_ROOT/.webnovel/state.json"' not in text
-```
-
-- [ ] **Step 2: 跑红灯**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py -q --no-cov`
-
-Expected: 至少 1 个断言失败,说明提示词仍在使用旧散写心智模型
-
-- [ ] **Step 3: 改写 `webnovel-write`,让 Step 5 以 commit 为成功判定**
-
-````md
-<!-- webnovel-writer/skills/webnovel-write/SKILL.md -->
-### Step 5:构建 extraction artifacts 并提交 `CHAPTER_COMMIT`
-
-必须产出中间文件:
-- `${PROJECT_ROOT}/.webnovel/tmp/review_results.json`
-- `${PROJECT_ROOT}/.webnovel/tmp/fulfillment_result.json`
-- `${PROJECT_ROOT}/.webnovel/tmp/disambiguation_result.json`
-- `${PROJECT_ROOT}/.webnovel/tmp/extraction_result.json`
-
-主命令:
-
-```bash
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" chapter-commit \
-  --chapter {chapter_num} \
-  --review-result "${PROJECT_ROOT}/.webnovel/tmp/review_results.json" \
-  --fulfillment-result "${PROJECT_ROOT}/.webnovel/tmp/fulfillment_result.json" \
-  --disambiguation-result "${PROJECT_ROOT}/.webnovel/tmp/disambiguation_result.json" \
-  --extraction-result "${PROJECT_ROOT}/.webnovel/tmp/extraction_result.json"
-```
-
-成功标准:
-- `.story-system/commits/chapter_{chapter_num}.commit.json` 已存在
-- `meta.status == accepted`
-- `projection_status` 中 `state/index/summary/memory` 均为 `done` 或明确 `skipped`
-````
-
-- [ ] **Step 4: 改写 query / review / plan / context-agent / data-agent 的默认读取与写入叙述**
-
-```md
-<!-- webnovel-writer/agents/data-agent.md -->
-你负责生成 `extraction_result.json`,并为 `chapter-commit` 提供:
-- `accepted_events`
-- `state_deltas`
-- `entity_deltas`
-- `summary_text`
-
-你不是写后真理源。
-`state.json / index.db / summaries / memory_scratchpad` 的最终写入由 accepted `CHAPTER_COMMIT` 的 projection writers 完成。
-```
-
-```md
-<!-- webnovel-writer/skills/webnovel-query/SKILL.md -->
-查询顺序固定为:
-1. `.story-system/MASTER_SETTING.json`
-2. `.story-system/volumes/*.json`
-3. `.story-system/chapters/*.json`
-4. latest accepted `.story-system/commits/chapter_XXX.commit.json`
-5. `memory-contract load-context`
-6. `.webnovel/state.json` / `index.db`(仅 fallback/read-model)
-```
-
-- [ ] **Step 5: 重新跑 prompt integrity**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py -q --no-cov`
-
-Expected: `passed`
-
-- [ ] **Step 6: 提交本任务**
-
-```bash
-git add webnovel-writer/skills/webnovel-write/SKILL.md \
-        webnovel-writer/skills/webnovel-review/SKILL.md \
-        webnovel-writer/skills/webnovel-query/SKILL.md \
-        webnovel-writer/skills/webnovel-plan/SKILL.md \
-        webnovel-writer/skills/webnovel-dashboard/SKILL.md \
-        webnovel-writer/agents/context-agent.md \
-        webnovel-writer/agents/data-agent.md \
-        webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py
-git commit -m "docs: cut skills over to chapter commit mainline"
-```
-
----
-
-## Task 4: 暴露 story runtime health,消灭“看起来切了,实际上没切”
-
-**Files:**
-- Create: `webnovel-writer/scripts/data_modules/story_runtime_health.py`
-- Create: `webnovel-writer/scripts/data_modules/tests/test_story_runtime_health.py`
-- Modify: `webnovel-writer/scripts/data_modules/webnovel.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py`
-- Modify: `webnovel-writer/dashboard/app.py`
-- Modify: `webnovel-writer/dashboard/frontend/src/api.js`
-- Modify: `webnovel-writer/dashboard/frontend/src/App.jsx`
-
-- [ ] **Step 1: 先写 health helper 与 preflight 的失败测试**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_story_runtime_health.py
-from data_modules.story_runtime_health import build_story_runtime_health
-
-
-def test_story_runtime_health_reports_missing_commit_as_not_ready(tmp_path):
-    report = build_story_runtime_health(tmp_path, chapter=3)
-    assert report["mainline_ready"] is False
-    assert "missing_accepted_commit" in report["fallback_sources"]
-```
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py
-def test_preflight_includes_story_runtime_health(monkeypatch, tmp_path, capsys):
-    module = _load_webnovel_module()
-
-    project_root = tmp_path / "book"
-    (project_root / ".webnovel").mkdir(parents=True, exist_ok=True)
-    (project_root / ".webnovel" / "state.json").write_text("{}", encoding="utf-8")
-
-    monkeypatch.setattr(sys, "argv", ["webnovel", "--project-root", str(project_root), "preflight", "--format", "json"])
-
-    with pytest.raises(SystemExit):
-        module.main()
-
-    captured = capsys.readouterr()
-    assert '"story_runtime"' in captured.out
-    assert '"mainline_ready"' in captured.out
-```
-
-- [ ] **Step 2: 跑红灯**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_story_runtime_health.py webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py -q --no-cov`
-
-Expected:
-- `ModuleNotFoundError: No module named 'data_modules.story_runtime_health'`
-- preflight JSON 中不存在 `story_runtime`
-
-- [ ] **Step 3: 实现 health helper,并接到 preflight**
-
-```python
-# webnovel-writer/scripts/data_modules/story_runtime_health.py
-from __future__ import annotations
-
-from pathlib import Path
-from typing import Any
-
-from .story_runtime_sources import load_runtime_sources
-
-
-def build_story_runtime_health(project_root: Path, chapter: int | None = None) -> dict[str, Any]:
-    current_chapter = int(chapter or 0)
-    snapshot = load_runtime_sources(project_root, current_chapter) if current_chapter else None
-    fallback_sources = list((snapshot.fallback_sources if snapshot else ["chapter_unspecified"]))
-    latest_commit = (snapshot.latest_commit if snapshot else None) or {}
-    return {
-        "chapter": current_chapter,
-        "mainline_ready": bool(snapshot and not snapshot.fallback_sources),
-        "fallback_sources": fallback_sources,
-        "latest_commit_status": (latest_commit.get("meta") or {}).get("status", "missing"),
-        "primary_write_source": (snapshot.primary_write_source if snapshot else "chapter_commit"),
-    }
-```
-
-```python
-# webnovel-writer/scripts/data_modules/webnovel.py
-from data_modules.story_runtime_health import build_story_runtime_health
-
-
-def _build_preflight_report(explicit_project_root: Optional[str]) -> dict:
-    scripts_dir = _scripts_dir().resolve()
-    plugin_root = scripts_dir.parent
-    skill_root = plugin_root / "skills" / "webnovel-write"
-    entry_script = scripts_dir / "webnovel.py"
-    extract_script = scripts_dir / "extract_chapter_context.py"
-
-    checks = [
-        {"name": "scripts_dir", "ok": scripts_dir.is_dir(), "path": str(scripts_dir)},
-        {"name": "entry_script", "ok": entry_script.is_file(), "path": str(entry_script)},
-        {"name": "extract_context_script", "ok": extract_script.is_file(), "path": str(extract_script)},
-        {"name": "skill_root", "ok": skill_root.is_dir(), "path": str(skill_root)},
-    ]
-
-    project_root = ""
-    project_root_error = ""
-    try:
-        resolved_root = _resolve_root(explicit_project_root)
-        project_root = str(resolved_root)
-        checks.append({"name": "project_root", "ok": True, "path": project_root})
-    except Exception as exc:
-        project_root_error = str(exc)
-        checks.append({"name": "project_root", "ok": False, "path": explicit_project_root or "", "error": project_root_error})
-
-    story_runtime = build_story_runtime_health(Path(project_root)) if project_root else {}
-    return {
-        "ok": all(bool(item["ok"]) for item in checks),
-        "project_root": project_root,
-        "scripts_dir": str(scripts_dir),
-        "skill_root": str(skill_root),
-        "checks": checks,
-        "project_root_error": project_root_error,
-        "story_runtime": story_runtime,
-    }
-```
-
-- [ ] **Step 4: 在 dashboard 暴露 story runtime health 与 latest commit 状态**
-
-```python
-# webnovel-writer/dashboard/app.py
-from data_modules.story_runtime_health import build_story_runtime_health
-
-
-@app.get("/api/story-runtime/health")
-def story_runtime_health():
-    project_root = _get_project_root()
-    state_path = project_root / ".webnovel" / "state.json"
-    chapter = 0
-    if state_path.is_file():
-        state = json.loads(state_path.read_text(encoding="utf-8"))
-        chapter = int(((state.get("progress") or {}).get("current_chapter") or 0))
-    return build_story_runtime_health(project_root, chapter=chapter)
-```
-
-```jsx
-// webnovel-writer/dashboard/frontend/src/App.jsx
-const [runtimeHealth, setRuntimeHealth] = useState(null)
-
-useEffect(() => {
-  fetchJSON('/api/story-runtime/health').then(setRuntimeHealth).catch(() => setRuntimeHealth(null))
-}, [refreshKey])
-
-{runtimeHealth ? (
-  <div className="card stat-card">
-    <span className="stat-label">Story Runtime</span>
-    <span className="stat-value plain">{runtimeHealth.mainline_ready ? 'Mainline' : 'Fallback'}</span>
-    <span className="stat-sub">
-      {runtimeHealth.latest_commit_status} · {(runtimeHealth.fallback_sources || []).join(', ') || 'no fallback'}
-    </span>
-  </div>
-) : null}
-```
-
-- [ ] **Step 5: 跑后端测试和前端构建**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_story_runtime_health.py webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py -q --no-cov`
-
-Expected: `passed`
-
-Run: `npm --prefix webnovel-writer/dashboard/frontend run build`
-
-Expected: Vite build success,无新增 lint/blocking error
-
-- [ ] **Step 6: 提交本任务**
-
-```bash
-git add webnovel-writer/scripts/data_modules/story_runtime_health.py \
-        webnovel-writer/scripts/data_modules/webnovel.py \
-        webnovel-writer/scripts/data_modules/tests/test_story_runtime_health.py \
-        webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py \
-        webnovel-writer/dashboard/app.py \
-        webnovel-writer/dashboard/frontend/src/api.js \
-        webnovel-writer/dashboard/frontend/src/App.jsx
-git commit -m "feat: surface story runtime health in preflight and dashboard"
-```
-
----
-
-## Task 5: 封板 legacy fallback 文档与运行说明
-
-**Files:**
-- Create: `docs/architecture/story-system-phase5.md`
-- Modify: `webnovel-writer/references/genre-profiles.md`
-- Modify: `README.md`
-- Modify: `docs/architecture/overview.md`
-- Modify: `docs/guides/commands.md`
-- Modify: `docs/operations/operations.md`
-- Modify: `docs/superpowers/README.md`
-
-- [ ] **Step 1: 先补 architecture 文档骨架,明确主链已切换**
-
-````md
-<!-- docs/architecture/story-system-phase5.md -->
-# Story System Phase 5
-
-## 核心结论
-
-- 写前真源:`MASTER / VOLUME / CHAPTER / REVIEW`
-- 写后真源:accepted `CHAPTER_COMMIT`
-- `state / index / summary / memory`:投影/read-model
-- `genre-profiles.md`:fallback-only
-
-## 默认链路
-
-```text
-story-system --persist/--emit-runtime-contracts
-    -> context / query / write / review 读取合同
-chapter-commit --chapter N
-    -> accepted commit
-    -> projection writers
-    -> state / index / summaries / memory
-```
-````
-
-- [ ] **Step 2: 在 `genre-profiles.md` 文件头显式打上 fallback-only 标记**
-
-```md
-<!-- webnovel-writer/references/genre-profiles.md -->
-# genre-profiles
-
-> **状态:Fallback Only**
-> 高频题材的主判定、主调性、主禁忌已迁移到 Story Contract / CSV route seed。
-> 本文件只在合同缺失、项目未升级或显式 fallback 时提供补充提示。
-```
-
-- [ ] **Step 3: 更新 README / commands / operations,把主链写成可执行手册**
-
-```md
-<!-- docs/guides/commands.md -->
-## Story System 主链
-
-1. 生成合同:
-   `python -X utf8 "webnovel-writer/scripts/webnovel.py" --project-root "{WORKSPACE_ROOT}" story-system "{goal}" --chapter {N} --persist --emit-runtime-contracts --format both`
-2. 提交章节:
-   `python -X utf8 "webnovel-writer/scripts/webnovel.py" --project-root "{PROJECT_ROOT}" chapter-commit --chapter {N} --review-result ".webnovel/tmp/review_results.json" --fulfillment-result ".webnovel/tmp/fulfillment_result.json" --disambiguation-result ".webnovel/tmp/disambiguation_result.json" --extraction-result ".webnovel/tmp/extraction_result.json"`
-3. 检查健康:
-   `python -X utf8 "webnovel-writer/scripts/webnovel.py" --project-root "{PROJECT_ROOT}" preflight --format json`
-```
-
-- [ ] **Step 4: 做一次最终回归**
-
-Run: `python -m pytest webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py webnovel-writer/scripts/data_modules/tests/test_context_manager.py webnovel-writer/scripts/data_modules/tests/test_memory_contract_adapter.py -q --no-cov`
-
-Expected: 全部通过
-
-- [ ] **Step 5: 提交本任务**
-
-```bash
-git add docs/architecture/story-system-phase5.md \
-        webnovel-writer/references/genre-profiles.md \
-        README.md \
-        docs/architecture/overview.md \
-        docs/guides/commands.md \
-        docs/operations/operations.md \
-        docs/superpowers/README.md
-git commit -m "docs: finalize phase5 legacy downgrade"
-```
-
----
-
-## Self-Review
-
-### Spec Coverage
-
-- `13.6 Phase 5:旧链路降级`
-  - Task 1 负责统一 runtime 来源与 commit provenance
-  - Task 2 负责默认主输入与默认写后事实切换
-  - Task 3 负责 skills / agents 提示词切链
-  - Task 4 负责 preflight / dashboard / health 观测
-  - Task 5 负责 fallback-only 文档封板与命令手册
-
-- `7.2 运行时优先级`
-  - Task 2 显式把 `story_contracts -> latest accepted commit -> legacy fallback` 固化到入口层
-
-- `7.3 写后真理源`
-  - Task 1 与 Task 2 让 `CHAPTER_COMMIT` 成为唯一写后事实入口
-
-- `17.1 文档更新要求`
-  - Task 5 覆盖架构文档、命令文档、运维手册与总览文档
-
-### Placeholder Scan
-
-- 全文没有延后实现的占位表述
-- 每个任务都给了具体文件、测试、命令和提交信息
-- 没有用跨任务引用代替实际步骤
-
-### Type Consistency
-
-本计划统一使用以下命名,不在后续任务中换名:
-
-- `RuntimeSourceSnapshot`
-- `load_runtime_sources(project_root, chapter)`
-- `build_story_runtime_health(project_root, chapter=None)`
-- `latest_accepted_commit`
-- `fallback_sources`
-- `write_fact_role = "chapter_commit"`
-
----
-
-## Execution Handoff
-
-Plan complete and saved to `docs/superpowers/plans/2026-04-13-story-system-phase5-legacy-downgrade.md`. Two execution options:
-
-**1. Subagent-Driven (recommended)** - I dispatch a fresh subagent per task, review between tasks, fast iteration
-
-**2. Inline Execution** - Execute tasks in this session using executing-plans, batch execution with checkpoints
-
-Which approach?

+ 0 - 325
docs/archive/superpowers/plans/2026-04-14-context-agent-writing-brief-implementation.md

@@ -1,325 +0,0 @@
-# Context Agent 写作任务书收束 Implementation Plan
-
-> **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:** 让 `context-agent` 成为唯一写前入口,代码层只准备 research 底稿,最终由 `context-agent` 按示例直接写出给 Step 2 使用的写作任务书。
-
-**Architecture:** 不再引入模板生成器。`extract_chapter_context.py` 继续负责组装 research 底稿;`context-agent` 基于底稿、固定守则和文档内示例,直接产出最终写作任务书;`webnovel-write` 的 Step 2 只消费这份任务书,不再自己补规则或拼中间块。
-
-**Tech Stack:** Python 3、pytest、Markdown agent/skill prompt、`webnovel.py` CLI
-
----
-
-## 文件结构
-
-### 修改文件
-
-- `webnovel-writer/scripts/extract_chapter_context.py`
-  - 保持为底稿组装器,不负责最终文案生成
-- `webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py`
-  - 验证底稿字段与旧文本输出能力
-- `webnovel-writer/agents/context-agent.md`
-  - 收回 Step 0.5 职责,明确按示例直接输出写作任务书
-- `webnovel-writer/skills/webnovel-write/SKILL.md`
-  - 删除 Step 0.5,Step 2 改为只消费写作任务书
-- `webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py`
-  - 锁定新的提示词边界
-- `webnovel-writer/skills/webnovel-write/evals/evals.json`
-  - 更新流程预期
-- `docs/guides/commands.md`
-  - 更新 `/webnovel-write` 描述
-
-### 删除文件
-
-- `webnovel-writer/scripts/data_modules/writing_brief_renderer.py`
-- `webnovel-writer/scripts/data_modules/tests/test_writing_brief_renderer.py`
-
----
-
-### Task 1: 删除生成器路线并恢复底稿职责
-
-**Files:**
-- Delete: `webnovel-writer/scripts/data_modules/writing_brief_renderer.py`
-- Delete: `webnovel-writer/scripts/data_modules/tests/test_writing_brief_renderer.py`
-- Modify: `webnovel-writer/scripts/extract_chapter_context.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py`
-
-- [ ] **Step 1: 删除生成器文件**
-
-```bash
-git rm webnovel-writer/scripts/data_modules/writing_brief_renderer.py
-git rm webnovel-writer/scripts/data_modules/tests/test_writing_brief_renderer.py
-```
-
-- [ ] **Step 2: 移除 `extract_chapter_context.py` 中的生成器调用**
-
-```python
-# webnovel-writer/scripts/extract_chapter_context.py
-# 删除:
-from data_modules.writing_brief_renderer import render_writer_brief
-
-# 删除:
-def _plugin_root() -> Path: ...
-def _load_fixed_guides() -> Dict[str, str]: ...
-def _extract_book_title(project_root: Path, state: Dict[str, Any]) -> str: ...
-def _extract_chapter_title(outline: str, chapter_num: int) -> str: ...
-
-# build_chapter_context_payload 保留为底稿输出:
-return {
-    "chapter": chapter_num,
-    "outline": outline,
-    "previous_summaries": prev_summaries,
-    "state_summary": state_summary,
-    "context_contract_version": contract_context.get("context_contract_version"),
-    "context_weight_stage": contract_context.get("context_weight_stage"),
-    "story_contract": contract_context.get("story_contract", {}),
-    "runtime_status": contract_context.get("runtime_status", {}),
-    "latest_commit": contract_context.get("latest_commit", {}),
-    "prewrite_validation": contract_context.get("prewrite_validation", {}),
-    "reader_signal": contract_context.get("reader_signal", {}),
-    "genre_profile": contract_context.get("genre_profile", {}),
-    "writing_guidance": contract_context.get("writing_guidance", {}),
-    "plot_structure": plot_structure,
-    "long_term_memory": contract_context.get("long_term_memory", {}),
-    "scene": contract_context.get("scene", {}),
-    "core": contract_context.get("core", {}),
-    "rag_assist": rag_assist,
-}
-```
-
-- [ ] **Step 3: 删除 `writer_brief` 相关测试,恢复旧文本输出断言**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py
-# 删除:
-def test_build_chapter_context_payload_includes_writer_brief(...): ...
-def test_render_text_returns_writer_brief_instead_of_old_audit_sections(...): ...
-
-# 保留:
-def test_render_text_contains_writing_guidance_section(...): ...
-def test_render_text_contains_contract_first_runtime_section(...): ...
-```
-
-- [ ] **Step 4: 跑测试确认底稿链恢复正常**
-
-Run:
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py -q --no-cov
-```
-
-Expected:
-
-```text
-全部通过,0 失败
-```
-
-- [ ] **Step 5: 提交这一块**
-
-```bash
-git add webnovel-writer/scripts/extract_chapter_context.py \
-        webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py
-git commit -m "refactor: keep chapter context as research draft only"
-```
-
----
-
-### Task 2: 让 `context-agent` 直接按示例输出写作任务书
-
-**Files:**
-- Modify: `webnovel-writer/agents/context-agent.md`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py`
-
-- [ ] **Step 1: 先补静态测试,锁住示例驱动输出**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py
-def test_context_agent_loads_fixed_guides_and_outputs_writer_brief():
-    text = (AGENTS_DIR / "context-agent.md").read_text(encoding="utf-8")
-    assert "core-constraints.md" in text
-    assert "anti-ai-guide.md" in text
-    assert "写作任务书" in text
-    assert "### 示例" in text
-    assert "你现在要写《凡人修仙传》第47章《坊市试探》。" in text
-    assert "Step 2 直写提示词" not in text
-    assert "Context Contract" not in text
-```
-
-- [ ] **Step 2: 改 `context-agent.md`**
-
-```md
-### webnovel-writer/agents/context-agent.md
-
-## 8. 输出格式
-
-最终只输出一份写作任务书。
-
-任务书固定写成五段,每一段该织入哪些数据源见下方说明和示例。
-
-### 1. 开篇委托
-### 2. 这一章的故事
-### 3. 这章的人物
-### 4. 这章怎么写更顺
-### 5. 这章收在哪里
-
-### 示例
-
-你现在要写《凡人修仙传》第47章《坊市试探》。
-...
-让读者带着"这个人到底是谁"翻到下一章。
-```
-
-- [ ] **Step 3: 跑静态测试**
-
-Run:
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py -q --no-cov
-```
-
-Expected:
-
-```text
-相关断言全部通过
-```
-
-- [ ] **Step 4: 提交这一块**
-
-```bash
-git add webnovel-writer/agents/context-agent.md \
-        webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py
-git commit -m "feat: make context agent write briefs from example"
-```
-
----
-
-### Task 3: 收束 `/webnovel-write` 主链
-
-**Files:**
-- Modify: `webnovel-writer/skills/webnovel-write/SKILL.md`
-- Modify: `webnovel-writer/skills/webnovel-write/evals/evals.json`
-- Modify: `docs/guides/commands.md`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py`
-
-- [ ] **Step 1: 先补静态测试,锁住新流程**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py
-def test_webnovel_write_skill_routes_step2_through_writing_brief():
-    text = (SKILLS_DIR / "webnovel-write" / "SKILL.md").read_text(encoding="utf-8")
-    assert "写作任务书" in text
-    assert "context-agent" in text
-    assert "Step 0.5" not in text
-    assert 'cat "${SKILL_ROOT}/../../references/shared/core-constraints.md"' not in text
-    assert 'cat "${SKILL_ROOT}/references/anti-ai-guide.md"' not in text
-```
-
-- [ ] **Step 2: 改 `webnovel-write/SKILL.md`**
-
-```md
-### Step 1:调用 Context Agent 生成写作任务书
-
-### Step 2:起草正文
-
-硬要求:
-- Step 2 只根据 Step 1 生成的写作任务书起草正文
-- Step 2 不再直接加载 `core-constraints.md`
-- Step 2 不再直接加载 `anti-ai-guide.md`
-- Step 2 不再自己拼中间块或旧版直写块
-```
-
-- [ ] **Step 3: 改 eval 和命令文档**
-
-```json
-// webnovel-writer/skills/webnovel-write/evals/evals.json
-"expected_output": "完成 Step 1 到 Step 6 的完整流程,由 context-agent 先生成写作任务书,再起草第4章正文..."
-```
-
-```md
-### docs/guides/commands.md
-
-执行完整章节创作流程(`context-agent` 先 research 并生成写作任务书 → 按任务书起草正文 → 审查 → 润色 → 数据落盘)。
-```
-
-- [ ] **Step 4: 跑静态测试**
-
-Run:
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py -q --no-cov
-```
-
-Expected:
-
-```text
-相关断言全部通过
-```
-
-- [ ] **Step 5: 提交这一块**
-
-```bash
-git add webnovel-writer/skills/webnovel-write/SKILL.md \
-        webnovel-writer/skills/webnovel-write/evals/evals.json \
-        docs/guides/commands.md \
-        webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py
-git commit -m "feat: make write step consume only final brief"
-```
-
----
-
-### Task 4: 回归验证
-
-**Files:**
-- Test: `webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py`
-- Test: `webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py`
-
-- [ ] **Step 1: 跑回归测试**
-
-Run:
-
-```bash
-python -m pytest \
-  webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py \
-  webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py \
-  -q --no-cov
-```
-
-Expected:
-
-```text
-全部通过,0 失败
-```
-
-- [ ] **Step 2: grep 自检**
-
-Run:
-
-```bash
-rg -n "Step 0\\.5|writing_brief_renderer|test_writing_brief_renderer|Context Contract|Step 2 直写提示词|cat \\\"\\$\\{SKILL_ROOT\\}/../../references/shared/core-constraints.md\\\"|cat \\\"\\$\\{SKILL_ROOT\\}/references/anti-ai-guide.md\\\"" \
-  webnovel-writer \
-  docs/guides/commands.md
-```
-
-Expected:
-
-```text
-除计划文档历史记录外,无业务代码残留
-```
-
----
-
-## 覆盖检查
-
-这份计划覆盖当前真实方案:
-
-1. 代码层只准备底稿
-2. `context-agent` 直接按示例写任务书
-3. `/webnovel-write` Step 2 只消费任务书
-4. 不再使用模板生成器
-5. `anti-ai-guide.md` 与 `core-constraints.md` 继续存在,但由 `context-agent` 吸收后转写
-
-## 自检
-
-- 已移除模板生成器路线
-- 没再要求字段映射表
-- 与当前未提交实现方向一致

+ 0 - 388
docs/archive/superpowers/plans/2026-04-15-chain-integrity-fixes.md

@@ -1,388 +0,0 @@
-# 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: 运行测试确认**
-
-```bash
-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**
-
-```bash
-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 行):
-```bash
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${WORKSPACE_ROOT}" \
-  story-system "{chapter_goal}" --chapter {chapter_num} --persist --emit-runtime-contracts --format both
-```
-
-改为:
-```bash
-# 从 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
-```
-
-在这段命令前加说明:
-```markdown
-**genre 参数规范**:
-- `--genre` 必须从 `state.json` 的 `project.genre` 读取,不得手动填写
-- 第一个位置参数(query)填本章章纲的"目标"字段内容,用于 CSV 知识检索
-- 若章纲无明确目标,fallback 到 `"{题材} 第{chapter_num}章"`
-```
-
-- [ ] **Step 2: 修改 plan SKILL.md 的 story-system 调用**
-
-当前(第 59-61 行):
-```bash
-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 命令存在**
-
-```bash
-grep -n "get-field" webnovel-writer/scripts/data_modules/webnovel.py
-```
-
-如果不存在,需要补一个简单的 state 子命令来读取任意 JSON path。或者用 jq/python 一行脚本替代:
-
-```bash
-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**
-
-```bash
-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 总纲"之后、"验证与交付"之前,新增:
-
-```markdown
-### 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 的验证与交付段落**
-
-在验证检查中新增:
-```bash
-test -f "{project_root}/.story-system/MASTER_SETTING.json"
-```
-
-在成功标准中新增:
-- `.story-system/MASTER_SETTING.json` 存在且 `route.primary_genre` 非空
-
-- [ ] **Step 3: Commit**
-
-```bash
-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.json` 的 `chapter_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 主链"段落明确权重**
-
-在写前真源列表后新增权重说明:
-
-```markdown
-**数据权重(高→低)**:
-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 行的合同树说明后加:
-
-```markdown
-**注意**:`.story-system/chapters/chapter_{NNN}.json` 的 `chapter_focus` 是 CSV 检索派生的参考建议,不是本章的实际目标。本章目标以 `大纲/第X卷-详细大纲.md` 中的章纲原文为最高权重。`chapter_{NNN}.json` 的核心价值是 `reasoning` 字段中的裁决元数据(风格优先级、节奏策略、反模式)。
-```
-
-- [ ] **Step 3: Commit**
-
-```bash
-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 和总纲。改为:
-
-```markdown
-### 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 的"卷纲必须明确"列表后新增:
-
-```markdown
-跨卷一致性检查:
-- 上一卷未回收的伏笔必须出现在新卷的伏笔规划中(继续推进或标记回收)
-- 角色关系变化必须延续(不能当上一卷没发生过)
-- 主角能力/境界必须承接(不能回退也不能跳级,除非有剧情解释)
-```
-
-- [ ] **Step 3: Commit**
-
-```bash
-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 测试**
-
-```bash
-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 配置对齐测试**
-
-```bash
-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: 验证裁决表别名覆盖**
-
-```bash
-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**
-
-```bash
-git add -A && git commit -m "chore: final verification for chain integrity fixes"
-```

+ 0 - 1960
docs/archive/superpowers/plans/2026-04-15-story-system-final-convergence.md

@@ -1,1960 +0,0 @@
-# Story System 最终收束实施计划
-
-> **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:** 把 Story System 从"半成品并存"收束到"六层主链 + 消费端同步"的最终可用状态,覆盖 CSV_CONFIG 注册、裁决表、engine 改造、context_manager 瘦身、旧散写清理、projection 收束、消费端同步和向量索引增强共 9 个 section。
-
-**Architecture:** 自底向上串行推进。先在 `reference_search.py` 引入 per-table `CSV_CONFIG`,然后统一 CSV 毒点列名、新建裁决表、改造 `story_system_engine.py` 接入裁决层,接着瘦身 `context_manager.py`、清理旧散写路径、收束 projection 层,最后同步所有消费端 prompt 并增强向量索引。
-
-**Tech Stack:** Python 3.11+, pytest, CSV (UTF-8 BOM), SQLite FTS5, RAG embedding
-
-**Spec:** `docs/superpowers/specs/2026-04-14-story-system-final-convergence-spec.md`
-
----
-
-## 文件结构总览
-
-### 新建文件
-
-| 文件 | 职责 |
-|------|------|
-| `webnovel-writer/references/csv/裁决规则.csv` | reasoning 层,key=题材,裁决命中条目的优先级和注入位置 |
-| `webnovel-writer/scripts/data_modules/knowledge_query.py` | 时序查询接口,entity_state_at_chapter / relationships_at_chapter |
-| `webnovel-writer/scripts/data_modules/vector_projection_writer.py` | commit 后把事件/entity_delta 写入向量库 |
-| `webnovel-writer/scripts/data_modules/tests/test_csv_config.py` | CSV_CONFIG 与 CSV 表头对齐校验 |
-| `webnovel-writer/scripts/data_modules/tests/test_reasoning_engine.py` | 裁决层单元测试 |
-| `webnovel-writer/scripts/data_modules/tests/test_knowledge_query.py` | 时序查询单元测试 |
-| `webnovel-writer/scripts/data_modules/tests/test_vector_projection_writer.py` | 向量投影写入测试 |
-
-### 主要修改文件
-
-| 文件 | 改动摘要 |
-|------|---------|
-| `webnovel-writer/scripts/reference_search.py` | 引入 `CSV_CONFIG`,`search()` 按表使用不同 `search_cols` |
-| `webnovel-writer/scripts/data_modules/story_system_engine.py` | 接入裁决表,新增 `_load_reasoning` / `_apply_reasoning` / `_rank_anti_patterns` / `_assemble_contract` |
-| `webnovel-writer/scripts/data_modules/context_manager.py` | 删 snapshot 逻辑、删 `_compact_json_text` / text 渲染相关,压到 400 行以下 |
-| `webnovel-writer/scripts/extract_chapter_context.py` | `_render_text()` 改为纯 JSON 序列化,text 渲染不再由代码层负责(context-agent 按示例写任务书) |
-| `webnovel-writer/scripts/data_modules/event_projection_router.py` | 给 6 种事件加 `"vector"` 路由 |
-| `webnovel-writer/scripts/data_modules/chapter_commit_service.py` | `apply_projections` 接入 `VectorProjectionWriter` |
-| `webnovel-writer/scripts/data_modules/state_projection_writer.py` | 统一由 projection 推进 `chapter_status` |
-| `webnovel-writer/skills/webnovel-write/SKILL.md` | 删 Step 2/4 的 `set-chapter-status`、删 `core-constraints` / `anti-ai-guide` 直读 |
-| `webnovel-writer/agents/context-agent.md` | 确认工具段落、research 数据源路径与代码一致 |
-| `webnovel-writer/agents/data-agent.md` | 确认不直写 state/index/memory |
-| `webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py` | 新增散写检测断言 |
-| `webnovel-writer/scripts/data_modules/tests/test_event_projection_router.py` | 补 vector 路由测试 |
-| `webnovel-writer/scripts/data_modules/tests/test_story_system_engine.py` | 补裁决层测试 |
-| `webnovel-writer/scripts/tests/test_reference_search.py` | 补 per-table search_cols 测试 |
-| `webnovel-writer/references/csv/*.csv` | 毒点列统一 rename |
-
-### 删除文件
-
-| 文件 | 理由 |
-|------|------|
-| `webnovel-writer/scripts/data_modules/snapshot_manager.py` | snapshot 逻辑随 context_manager 瘦身一起删除 |
-
----
-
-## Task 1: CSV_CONFIG 注册层
-
-**Files:**
-- Modify: `webnovel-writer/scripts/reference_search.py:90-191`
-- Create: `webnovel-writer/scripts/data_modules/tests/test_csv_config.py`
-- Modify: `webnovel-writer/scripts/tests/test_reference_search.py`
-
-- [ ] **Step 1: 在 `reference_search.py` 新增 `CSV_CONFIG` dict**
-
-在 `_TOKEN_SPLIT_RE` 定义之前(约第 89 行),插入 `CSV_CONFIG`:
-
-```python
-# ---------------------------------------------------------------------------
-# Per-table configuration
-# ---------------------------------------------------------------------------
-
-CSV_CONFIG: Dict[str, Dict[str, Any]] = {
-    "命名规则": {
-        "file": "命名规则.csv",
-        "search_cols": {"关键词": 3, "意图与同义词": 4, "核心摘要": 2},
-        "output_cols": ["编号", "命名对象", "核心摘要", "大模型指令", "详细展开"],
-        "poison_col": "毒点",
-        "role": "base",
-    },
-    "场景写法": {
-        "file": "场景写法.csv",
-        "search_cols": {"关键词": 3, "意图与同义词": 4, "核心摘要": 2},
-        "output_cols": ["编号", "模式名称", "核心摘要", "大模型指令", "详细展开"],
-        "poison_col": "毒点",
-        "role": "base",
-    },
-    "写作技法": {
-        "file": "写作技法.csv",
-        "search_cols": {"关键词": 3, "意图与同义词": 4, "核心摘要": 2},
-        "output_cols": ["编号", "技法名称", "核心摘要", "大模型指令", "详细展开"],
-        "poison_col": "毒点",
-        "role": "base",
-    },
-    "桥段套路": {
-        "file": "桥段套路.csv",
-        "search_cols": {"关键词": 3, "意图与同义词": 4, "核心摘要": 2},
-        "output_cols": ["编号", "桥段名称", "核心摘要", "大模型指令", "详细展开"],
-        "poison_col": "毒点",
-        "role": "dynamic",
-    },
-    "爽点与节奏": {
-        "file": "爽点与节奏.csv",
-        "search_cols": {"关键词": 3, "意图与同义词": 4, "核心摘要": 2},
-        "output_cols": ["编号", "节奏类型", "核心摘要", "大模型指令", "详细展开"],
-        "poison_col": "毒点",
-        "role": "dynamic",
-    },
-    "人设与关系": {
-        "file": "人设与关系.csv",
-        "search_cols": {"关键词": 3, "意图与同义词": 4, "核心摘要": 2},
-        "output_cols": ["编号", "人设类型", "核心摘要", "大模型指令", "详细展开"],
-        "poison_col": "毒点",
-        "role": "base",
-    },
-    "金手指与设定": {
-        "file": "金手指与设定.csv",
-        "search_cols": {"关键词": 3, "意图与同义词": 4, "核心摘要": 2},
-        "output_cols": ["编号", "设定类型", "核心摘要", "大模型指令", "详细展开"],
-        "poison_col": "毒点",
-        "role": "base",
-    },
-    "题材与调性推理": {
-        "file": "题材与调性推理.csv",
-        "search_cols": {"关键词": 3, "意图与同义词": 4, "题材别名": 3},
-        "output_cols": ["编号", "题材/流派", "核心调性", "推荐基础检索表", "推荐动态检索表"],
-        "poison_col": "毒点",
-        "role": "route",
-    },
-    "裁决规则": {
-        "file": "裁决规则.csv",
-        "search_cols": {"题材": 4},
-        "output_cols": [
-            "题材", "风格优先级", "爽点优先级", "节奏默认策略",
-            "毒点权重", "冲突裁决", "contract注入层", "反模式",
-        ],
-        "poison_col": "",
-        "role": "reasoning",
-    },
-}
-```
-
-- [ ] **Step 2: 改造 `_build_doc_terms()` 使用 per-table `search_cols`**
-
-把旧的 `_SEARCH_FIELD_WEIGHTS` 全局 dict 替换为 per-table 参数:
-
-```python
-# 删除旧的全局常量
-# _SEARCH_FIELD_WEIGHTS = { ... }  # 删除
-
-# 保留作为默认 fallback
-_DEFAULT_SEARCH_WEIGHTS: Dict[str, int] = {
-    "意图与同义词": 4,
-    "关键词": 3,
-    "核心摘要": 2,
-    "详细展开": 1,
-}
-
-
-def _build_doc_terms(row: Dict[str, str], search_weights: Dict[str, int] | None = None) -> List[str]:
-    """Build weighted BM25 terms from the configured search fields."""
-    weights = search_weights or _DEFAULT_SEARCH_WEIGHTS
-    terms: List[str] = []
-    for field, weight in weights.items():
-        field_terms = _tokenize(row.get(field, ""))
-        if not field_terms:
-            continue
-        terms.extend(field_terms * weight)
-    return terms
-```
-
-- [ ] **Step 3: 改造 `search()` 从 `CSV_CONFIG` 读取配置**
-
-在 `search()` 函数里,根据 `table` 参数查 `CSV_CONFIG`:
-
-```python
-def search(
-    csv_dir: Path,
-    skill: str,
-    query: str,
-    table: Optional[str] = None,
-    genre: Optional[str] = None,
-    max_results: int = 5,
-) -> Dict[str, Any]:
-    # ... (error check 不变)
-
-    tables = load_tables(csv_dir, table=table)
-    if not tables:
-        # ... (不变)
-
-    # 按表查 search_cols
-    table_config = CSV_CONFIG.get(table) if table else None
-    search_weights = (
-        dict(table_config["search_cols"]) if table_config else None
-    )
-
-    # 1) Collect filtered rows
-    candidates: List[tuple] = []
-    for tbl_name, rows in tables.items():
-        for row in rows:
-            if _skill_matches(row, skill) and _genre_matches(row, genre):
-                candidates.append((tbl_name, row))
-
-    if not candidates:
-        # ... (不变)
-
-    # 2) Tokenize - 对每条用其所在表的 search_cols
-    query_terms = _tokenize(query)
-    doc_terms_list = []
-    for tbl_name, row in candidates:
-        tbl_cfg = CSV_CONFIG.get(tbl_name)
-        weights = dict(tbl_cfg["search_cols"]) if tbl_cfg else search_weights
-        doc_terms_list.append(_build_doc_terms(row, weights))
-
-    # 3-4) 不变 ...
-```
-
-- [ ] **Step 4: 删除 `_SEARCH_FIELD_WEIGHTS` 和 `_CONTENT_COLUMNS`**
-
-删除第 90-95 行的 `_SEARCH_FIELD_WEIGHTS` 和第 180-190 行的 `_CONTENT_COLUMNS`。
-
-`_build_summary()` 改为:如果有 `CSV_CONFIG` 里的 `output_cols`,就按那个顺序取字段;否则用原来的 fallback 逻辑。
-
-```python
-def _build_summary(row: Dict[str, str], table_name: str | None = None) -> str:
-    core_summary = row.get("核心摘要", "").strip()
-    if core_summary:
-        return core_summary
-
-    # 优先用 CSV_CONFIG 的 output_cols
-    if table_name and table_name in CSV_CONFIG:
-        cols = CSV_CONFIG[table_name]["output_cols"]
-    else:
-        cols = [
-            "技法名称", "桥段名称", "人设类型", "节奏类型", "设定类型",
-            "规则", "说明", "模式名称", "命名对象", "场景类型",
-        ]
-
-    parts: List[str] = []
-    for col in cols:
-        val = row.get(col, "").strip()
-        if val and col not in ("编号", "大模型指令", "详细展开", "核心摘要"):
-            parts.append(val)
-    if parts:
-        return ";".join(parts)
-    return row.get("详细展开", "").strip()
-```
-
-- [ ] **Step 5: 创建 CSV_CONFIG 对齐校验测试**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_csv_config.py
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""CSV_CONFIG 与实际 CSV 表头对齐校验。"""
-import csv
-from pathlib import Path
-
-import pytest
-
-# reference_search.py 在 scripts/ 下,需要加 sys.path
-import sys
-sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
-
-from reference_search import CSV_CONFIG
-
-CSV_DIR = Path(__file__).resolve().parent.parent.parent.parent / "references" / "csv"
-
-
-@pytest.mark.parametrize("table_name,config", list(CSV_CONFIG.items()))
-def test_csv_config_columns_exist_in_csv_header(table_name: str, config: dict):
-    """CSV_CONFIG 里声明的所有列名都必须在 CSV 文件头中找到。"""
-    csv_path = CSV_DIR / config["file"]
-    if not csv_path.exists():
-        pytest.skip(f"{config['file']} not yet created")
-
-    with open(csv_path, "r", encoding="utf-8-sig", newline="") as f:
-        reader = csv.DictReader(f)
-        headers = set(reader.fieldnames or [])
-
-    all_cols = set()
-    for col in config.get("search_cols", {}):
-        all_cols.add(col)
-    for col in config.get("output_cols", []):
-        all_cols.add(col)
-    poison = config.get("poison_col", "")
-    if poison:
-        all_cols.add(poison)
-
-    missing = all_cols - headers
-    assert not missing, f"表 {table_name} 缺少列: {missing}"
-
-
-def test_csv_config_file_field_matches_filename():
-    """CSV_CONFIG 的 file 字段必须与 key + '.csv' 对应。"""
-    for name, config in CSV_CONFIG.items():
-        assert config["file"] == f"{name}.csv", f"{name}: file 应为 '{name}.csv',实际为 '{config['file']}'"
-```
-
-- [ ] **Step 6: 运行测试验证**
-
-Run: `cd webnovel-writer && python -m pytest scripts/data_modules/tests/test_csv_config.py -v`
-
-预期:`裁决规则` 那条会 skip(文件还没创建),其余表全 pass。
-
-- [ ] **Step 7: 补充 per-table 检索测试**
-
-在 `webnovel-writer/scripts/tests/test_reference_search.py` 末尾新增:
-
-```python
-class TestPerTableSearchCols:
-    """CSV_CONFIG per-table search_cols 测试。"""
-
-    def test_different_tables_use_different_search_weights(self):
-        """确认不同表用不同的 search_cols 做检索。"""
-        # 命名规则和场景写法都应返回结果,但用各自表的 search_cols
-        out1 = run_search("--skill", "write", "--table", "命名规则", "--query", "角色命名")
-        out2 = run_search("--skill", "write", "--table", "场景写法", "--query", "战斗描写")
-        assert out1["status"] == "success"
-        assert out2["status"] == "success"
-        assert out1["data"]["total"] >= 1
-        assert out2["data"]["total"] >= 1
-```
-
-- [ ] **Step 8: 运行全量 reference_search 测试**
-
-Run: `cd webnovel-writer && python -m pytest scripts/tests/test_reference_search.py -v`
-
-预期:全部 PASS。
-
-- [ ] **Step 9: Commit**
-
-```bash
-git add webnovel-writer/scripts/reference_search.py webnovel-writer/scripts/data_modules/tests/test_csv_config.py webnovel-writer/scripts/tests/test_reference_search.py
-git commit -m "feat: introduce per-table CSV_CONFIG in reference_search"
-```
-
----
-
-## Task 2: CSV 毒点列统一
-
-**Files:**
-- Modify: `webnovel-writer/references/csv/场景写法.csv` (header rename)
-- Modify: `webnovel-writer/references/csv/写作技法.csv` (header rename)
-- Modify: `webnovel-writer/references/csv/爽点与节奏.csv` (header rename)
-- Modify: `webnovel-writer/references/csv/人设与关系.csv` (header rename)
-- Modify: `webnovel-writer/references/csv/桥段套路.csv` (header rename)
-- Modify: `webnovel-writer/references/csv/题材与调性推理.csv` (header rename)
-- Modify: `webnovel-writer/scripts/data_modules/story_system_engine.py:15-22`
-
-- [ ] **Step 1: 统计当前各表的毒点列名**
-
-当前列名映射:
-- `场景写法.csv` → `反面写法`
-- `写作技法.csv` → `常见误区`
-- `爽点与节奏.csv` → `常见崩盘误区`
-- `人设与关系.csv` → `忌讳写法`
-- `桥段套路.csv` → 有 `忌讳写法` 列
-- `题材与调性推理.csv` → `强制禁忌/毒点`
-- `命名规则.csv` → 无毒点列(header 里有 `反例`,保留不动,新增 `毒点` 列)
-- `金手指与设定.csv` → 无毒点列(新增 `毒点` 列)
-
-- [ ] **Step 2: 批量 rename CSV 列头**
-
-用脚本执行(一次性,不入库):
-
-```python
-# 在 bash 里直接执行
-python3 -c "
-import csv, sys
-from pathlib import Path
-
-csv_dir = Path('webnovel-writer/references/csv')
-
-renames = {
-    '场景写法.csv': {'反面写法': '毒点'},
-    '写作技法.csv': {'常见误区': '毒点'},
-    '爽点与节奏.csv': {'常见崩盘误区': '毒点'},
-    '人设与关系.csv': {'忌讳写法': '毒点'},
-    '桥段套路.csv': {'忌讳写法': '毒点'},
-    '题材与调性推理.csv': {'强制禁忌/毒点': '毒点'},
-}
-
-for filename, mapping in renames.items():
-    path = csv_dir / filename
-    with open(path, 'r', encoding='utf-8-sig', newline='') as f:
-        reader = csv.DictReader(f)
-        rows = list(reader)
-        old_fields = list(reader.fieldnames)
-
-    new_fields = [mapping.get(f, f) for f in old_fields]
-
-    new_rows = []
-    for row in rows:
-        new_row = {}
-        for old_f, new_f in zip(old_fields, new_fields):
-            new_row[new_f] = row.get(old_f, '')
-        new_rows.append(new_row)
-
-    with open(path, 'w', encoding='utf-8-sig', newline='') as f:
-        writer = csv.DictWriter(f, fieldnames=new_fields)
-        writer.writeheader()
-        writer.writerows(new_rows)
-
-print('Done')
-"
-```
-
-- [ ] **Step 3: 给 `命名规则.csv` 和 `金手指与设定.csv` 新增空 `毒点` 列**
-
-```python
-python3 -c "
-import csv
-from pathlib import Path
-
-csv_dir = Path('webnovel-writer/references/csv')
-
-for filename in ['命名规则.csv', '金手指与设定.csv']:
-    path = csv_dir / filename
-    with open(path, 'r', encoding='utf-8-sig', newline='') as f:
-        reader = csv.DictReader(f)
-        rows = list(reader)
-        fields = list(reader.fieldnames)
-
-    if '毒点' not in fields:
-        fields.append('毒点')
-        for row in rows:
-            row['毒点'] = ''
-
-    with open(path, 'w', encoding='utf-8-sig', newline='') as f:
-        writer = csv.DictWriter(f, fieldnames=fields)
-        writer.writeheader()
-        writer.writerows(rows)
-
-print('Done')
-"
-```
-
-- [ ] **Step 4: 更新 `story_system_engine.py` 的 `ANTI_PATTERN_SOURCE_FIELDS`**
-
-把第 15-22 行的旧映射:
-
-```python
-ANTI_PATTERN_SOURCE_FIELDS = {
-    "场景写法": ["反面写法"],
-    "写作技法": ["常见误区"],
-    "爽点与节奏": ["常见崩盘误区"],
-    "人设与关系": ["忌讳写法"],
-    "桥段套路": ["忌讳写法"],
-    "题材与调性推理": ["强制禁忌/毒点"],
-}
-```
-
-统一改为:
-
-```python
-ANTI_PATTERN_SOURCE_FIELDS = {
-    "场景写法": ["毒点"],
-    "写作技法": ["毒点"],
-    "爽点与节奏": ["毒点"],
-    "人设与关系": ["毒点"],
-    "桥段套路": ["毒点"],
-    "题材与调性推理": ["毒点"],
-    "命名规则": ["毒点"],
-    "金手指与设定": ["毒点"],
-}
-```
-
-- [ ] **Step 5: 运行测试**
-
-Run: `cd webnovel-writer && python -m pytest scripts/data_modules/tests/test_csv_config.py scripts/data_modules/tests/test_story_system_engine.py scripts/tests/test_reference_search.py -v`
-
-预期:test_story_system_engine 会因 fixture CSV 里用旧列名而失败。
-
-- [ ] **Step 6: 修复 `test_story_system_engine.py` fixture 列名**
-
-把 fixture CSV 里的 `忌讳写法` 和 `常见崩盘误区` 改为 `毒点`,`强制禁忌/毒点` 也改为 `毒点`。
-
-fixture 第 53 行的 `桥段套路.csv` headers 改为:
-```python
-["编号", "适用技能", "分类", "层级", "关键词", "适用题材", "核心摘要", "桥段名称", "毒点"],
-```
-对应行数据 key `忌讳写法` 改为 `毒点`。
-
-fixture 第 71 行的 `爽点与节奏.csv` headers 改为:
-```python
-["编号", "适用技能", "分类", "层级", "关键词", "适用题材", "核心摘要", "毒点", "节奏类型"],
-```
-对应行数据 key `常见崩盘误区` 改为 `毒点`。
-
-fixture 第 26 行的 `题材与调性推理.csv` headers 里 `强制禁忌/毒点` 改为 `毒点`,对应行数据 key 也改为 `毒点`。
-
-- [ ] **Step 7: 运行测试确认全 PASS**
-
-Run: `cd webnovel-writer && python -m pytest scripts/data_modules/tests/test_story_system_engine.py scripts/data_modules/tests/test_csv_config.py -v`
-
-预期:全部 PASS。
-
-- [ ] **Step 8: Commit**
-
-```bash
-git add webnovel-writer/references/csv/*.csv webnovel-writer/scripts/data_modules/story_system_engine.py webnovel-writer/scripts/data_modules/tests/test_story_system_engine.py
-git commit -m "refactor: unify poison column name to 毒点 across all CSV tables"
-```
-
----
-
-## Task 3: 新建裁决规则表
-
-**Files:**
-- Create: `webnovel-writer/references/csv/裁决规则.csv`
-
-- [ ] **Step 1: 创建裁决规则 CSV 文件**
-
-```csv
-编号,适用技能,分类,层级,关键词,意图与同义词,适用题材,大模型指令,核心摘要,详细展开,题材,风格优先级,爽点优先级,节奏默认策略,毒点权重,冲突裁决,contract注入层,反模式
-RS-001,write|plan,裁决,推理层,西方奇幻|奇幻,西方奇幻怎么写,西方奇幻,按冲突裁决排序命中条目,西方奇幻裁决规则,,西方奇幻,史诗感 > 冷硬算计 > 日常轻松,实力碾压 > 逆境翻盘 > 智谋博弈,快推慢收 对峙段拉长 过渡段压短,圣母病 > 情绪标签化 > 逻辑断裂,爽点与节奏 > 场景写法 > 写作技法,CHAPTER_BRIEF.writing_guidance,情绪标签化|角色行为无逻辑|战斗无代价
-RS-002,write|plan,裁决,推理层,东方仙侠|仙侠,仙侠怎么写,东方仙侠,按冲突裁决排序命中条目,东方仙侠裁决规则,,东方仙侠,冷硬算计 > 超然物外 > 热血冲突,境界碾压 > 底牌揭晓 > 因果兑现,慢蓄快爆 修炼段精简 斗法段拉满,修炼水字数 > 圣母病 > 逻辑断裂,爽点与节奏 > 桥段套路 > 场景写法,CHAPTER_BRIEF.writing_guidance,修炼变流水账|境界突破无代价|感悟靠顿悟标签
-RS-003,write|plan,裁决,推理层,科幻末世|末世|科幻,科幻末世怎么写,科幻末世,按冲突裁决排序命中条目,科幻末世裁决规则,,科幻末世,高压克制 > 冷硬算计 > 绝境反击,绝境生存 > 资源碾压 > 智谋博弈,紧凑推进 危机不断 喘息极短,主角无敌 > 科技无代价 > 末世无压迫感,场景写法 > 爽点与节奏 > 写作技法,CHAPTER_BRIEF.writing_guidance,末世没有生存压力|科技万能|角色行为无逻辑
-RS-004,write|plan,裁决,推理层,都市日常|都市,都市日常怎么写,都市日常,按冲突裁决排序命中条目,都市日常裁决规则,,都市日常,日常轻松 > 温情治愈 > 微妙张力,情感共鸣 > 生活逆袭 > 社交碾压,慢节奏 情感铺垫长 冲突柔和,假大空说教 > 情绪标签化 > 逻辑断裂,写作技法 > 人设与关系 > 场景写法,CHAPTER_BRIEF.writing_guidance,情感靠标签|日常无冲突|角色千人一面
-RS-005,write|plan,裁决,推理层,都市修真|修真|现代修真,都市修真怎么写,都市修真,按冲突裁决排序命中条目,都市修真裁决规则,,都市修真,隐秘低调 > 冷硬算计 > 热血爆发,身份反差 > 境界碾压 > 底牌揭晓,快慢交替 日常短 修真爆发长,修真体系与现代割裂 > 圣母病 > 装逼无代价,爽点与节奏 > 场景写法 > 桥段套路,CHAPTER_BRIEF.writing_guidance,修真体系照搬古代|现代元素没有影响|身份暴露无后果
-RS-006,write|plan,裁决,推理层,都市高武|高武|都市异能,都市高武怎么写,都市高武,按冲突裁决排序命中条目,都市高武裁决规则,,都市高武,热血冲突 > 冷硬算计 > 力量美学,实力碾压 > 以弱胜强 > 排名跃升,快节奏 战斗密集 过渡极短,战力崩盘 > 圣母病 > 无脑开挂,爽点与节奏 > 场景写法 > 桥段套路,CHAPTER_BRIEF.writing_guidance,战力体系自相矛盾|升级无代价|打斗无策略
-RS-007,write|plan,裁决,推理层,历史古代|历史|古代,历史古代怎么写,历史古代,按冲突裁决排序命中条目,历史古代裁决规则,,历史古代,沉稳厚重 > 权谋算计 > 家国情怀,权谋碾压 > 历史转折 > 身份反转,慢铺快收 权谋段拉长 战争段紧凑,现代价值观强加古人 > 逻辑断裂 > 历史常识错误,写作技法 > 人设与关系 > 场景写法,CHAPTER_BRIEF.writing_guidance,用现代口语写古代|权谋无逻辑|历史事件随意篡改
-```
-
-- [ ] **Step 2: 运行 CSV_CONFIG 校验确认新表列头对齐**
-
-Run: `cd webnovel-writer && python -m pytest scripts/data_modules/tests/test_csv_config.py -v`
-
-预期:`裁决规则` 现在有文件了,应该 PASS。
-
-- [ ] **Step 3: Commit**
-
-```bash
-git add webnovel-writer/references/csv/裁决规则.csv
-git commit -m "feat: add 裁决规则.csv reasoning table for 7 genres"
-```
-
----
-
-## Task 4: engine 接入裁决表
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/story_system_engine.py`
-- Create: `webnovel-writer/scripts/data_modules/tests/test_reasoning_engine.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_story_system_engine.py`
-
-- [ ] **Step 1: 写裁决层测试**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_reasoning_engine.py
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""裁决层集成测试。"""
-import csv
-
-from data_modules.story_system_engine import StorySystemEngine
-
-
-def _write_csv(path, headers, rows):
-    with open(path, "w", encoding="utf-8-sig", newline="") as f:
-        writer = csv.DictWriter(f, fieldnames=headers)
-        writer.writeheader()
-        writer.writerows(rows)
-
-
-ROUTE_HEADERS = [
-    "编号", "适用技能", "分类", "层级", "关键词", "意图与同义词", "适用题材",
-    "大模型指令", "核心摘要", "详细展开", "题材/流派", "题材别名", "核心调性",
-    "节奏策略", "毒点", "推荐基础检索表", "推荐动态检索表", "默认查询词",
-]
-
-REASONING_HEADERS = [
-    "编号", "适用技能", "分类", "层级", "关键词", "意图与同义词", "适用题材",
-    "大模型指令", "核心摘要", "详细展开",
-    "题材", "风格优先级", "爽点优先级", "节奏默认策略",
-    "毒点权重", "冲突裁决", "contract注入层", "反模式",
-]
-
-
-def _setup_csvs(csv_dir):
-    _write_csv(csv_dir / "题材与调性推理.csv", ROUTE_HEADERS, [{
-        "编号": "GR-001", "适用技能": "write|plan", "分类": "题材路由",
-        "层级": "知识补充", "关键词": "玄幻", "意图与同义词": "玄幻|仙侠",
-        "适用题材": "玄幻", "大模型指令": "", "核心摘要": "", "详细展开": "",
-        "题材/流派": "玄幻", "题材别名": "玄幻", "核心调性": "热血冲突",
-        "节奏策略": "快推慢收", "毒点": "圣母病",
-        "推荐基础检索表": "命名规则|人设与关系",
-        "推荐动态检索表": "桥段套路|爽点与节奏",
-        "默认查询词": "玄幻",
-    }])
-
-    _write_csv(csv_dir / "裁决规则.csv", REASONING_HEADERS, [{
-        "编号": "RS-001", "适用技能": "write|plan", "分类": "裁决",
-        "层级": "推理层", "关键词": "玄幻", "意图与同义词": "玄幻",
-        "适用题材": "玄幻", "大模型指令": "", "核心摘要": "", "详细展开": "",
-        "题材": "玄幻",
-        "风格优先级": "热血冲突 > 冷硬算计",
-        "爽点优先级": "实力碾压 > 逆境翻盘",
-        "节奏默认策略": "快推慢收",
-        "毒点权重": "圣母病 > 情绪标签化",
-        "冲突裁决": "爽点与节奏 > 场景写法 > 写作技法",
-        "contract注入层": "CHAPTER_BRIEF.writing_guidance",
-        "反模式": "情绪标签化|战斗无代价",
-    }])
-
-    _write_csv(csv_dir / "桥段套路.csv",
-        ["编号", "适用技能", "分类", "层级", "关键词", "适用题材", "核心摘要", "桥段名称", "毒点"],
-        [{"编号": "TR-001", "适用技能": "write", "分类": "桥段", "层级": "知识补充",
-          "关键词": "退婚", "适用题材": "玄幻", "核心摘要": "退婚反击",
-          "桥段名称": "退婚反击", "毒点": "配角代打"}])
-
-    _write_csv(csv_dir / "爽点与节奏.csv",
-        ["编号", "适用技能", "分类", "层级", "关键词", "适用题材", "核心摘要", "毒点", "节奏类型"],
-        [{"编号": "PA-001", "适用技能": "write", "分类": "节奏", "层级": "知识补充",
-          "关键词": "打脸", "适用题材": "玄幻", "核心摘要": "兑现必须补刀",
-          "毒点": "打脸软收尾", "节奏类型": "爆发期"}])
-
-
-def test_build_with_reasoning_includes_reasoning_rule_in_source_trace(tmp_path):
-    csv_dir = tmp_path / "csv"
-    csv_dir.mkdir()
-    _setup_csvs(csv_dir)
-
-    engine = StorySystemEngine(csv_dir=csv_dir)
-    contract = engine.build(query="玄幻", genre=None, chapter=5)
-
-    traces = contract["master_setting"]["source_trace"]
-    reasoning_traces = [t for t in traces if t.get("reasoning_rule")]
-    assert len(reasoning_traces) >= 1
-    assert reasoning_traces[0]["reasoning_rule"] == "玄幻"
-
-
-def test_reasoning_anti_patterns_sorted_by_weight(tmp_path):
-    csv_dir = tmp_path / "csv"
-    csv_dir.mkdir()
-    _setup_csvs(csv_dir)
-
-    engine = StorySystemEngine(csv_dir=csv_dir)
-    contract = engine.build(query="玄幻", genre=None, chapter=5)
-
-    anti = contract["anti_patterns"]
-    assert len(anti) >= 1
-
-
-def test_reasoning_not_found_falls_back_gracefully(tmp_path):
-    csv_dir = tmp_path / "csv"
-    csv_dir.mkdir()
-    _setup_csvs(csv_dir)
-
-    engine = StorySystemEngine(csv_dir=csv_dir)
-    contract = engine.build(query="末日生存", genre="末日", chapter=1)
-
-    # 没有裁决规则也不应报错
-    assert "master_setting" in contract
-    assert "anti_patterns" in contract
-```
-
-- [ ] **Step 2: 运行测试确认失败**
-
-Run: `cd webnovel-writer && python -m pytest scripts/data_modules/tests/test_reasoning_engine.py -v`
-
-预期:FAIL,因为 `_load_reasoning` 等方法还不存在。
-
-- [ ] **Step 3: 在 `story_system_engine.py` 新增裁决方法**
-
-在 `StorySystemEngine` 类末尾新增:
-
-```python
-def _load_reasoning(self, genre: str) -> Dict[str, Any]:
-    """从裁决表按题材查一行,返回裁决规则 dict。"""
-    rows = self._load_csv_rows("裁决规则")
-    genre_text = self._normalize_text(genre)
-    for row in rows:
-        row_genre = self._normalize_text(row.get("题材", ""))
-        if row_genre == genre_text:
-            return row
-        aliases = self._split_multi_value(row.get("关键词")) + self._split_multi_value(row.get("意图与同义词"))
-        if any(self._normalize_text(a) == genre_text for a in aliases):
-            return row
-    return {}
-
-def _apply_reasoning(
-    self,
-    reasoning: Dict[str, Any],
-    base_context: List[Dict[str, Any]],
-    dynamic_context: List[Dict[str, Any]],
-) -> List[Dict[str, Any]]:
-    """用冲突裁决字段对命中条目做优先级排序。"""
-    if not reasoning:
-        return base_context + dynamic_context
-
-    priority_order = [
-        t.strip() for t in str(reasoning.get("冲突裁决", "")).split(">") if t.strip()
-    ]
-    priority_map = {name: idx for idx, name in enumerate(priority_order)}
-
-    all_rows = base_context + dynamic_context
-    for row in all_rows:
-        table_name = str(row.get("_table", "")).strip()
-        row["_priority_rank"] = priority_map.get(table_name, len(priority_order))
-        row["_reasoning_rule"] = str(reasoning.get("题材", "")).strip()
-
-    all_rows.sort(key=lambda r: r.get("_priority_rank", 999))
-    return all_rows
-
-def _rank_anti_patterns(
-    self,
-    reasoning: Dict[str, Any],
-    anti_patterns: List[Dict[str, Any]],
-) -> List[Dict[str, Any]]:
-    """用毒点权重字段对毒点排序。"""
-    if not reasoning:
-        return anti_patterns
-
-    weight_order = [
-        t.strip() for t in str(reasoning.get("毒点权重", "")).split(">") if t.strip()
-    ]
-
-    def sort_key(item):
-        text = str(item.get("text", "")).strip()
-        for idx, keyword in enumerate(weight_order):
-            if keyword in text:
-                return idx
-        return len(weight_order)
-
-    anti_patterns.sort(key=sort_key)
-
-    # 追加裁决表自带的反模式
-    for text in self._split_multi_value(reasoning.get("反模式")):
-        anti_patterns.append({
-            "text": text,
-            "source_table": "裁决规则",
-            "source_id": reasoning.get("编号", ""),
-        })
-
-    return anti_patterns
-```
-
-- [ ] **Step 4: 改造 `build()` 方法接入裁决层**
-
-把 `build()` 方法(第 29-90 行)改为:
-
-```python
-def build(self, query: str, genre: Optional[str], chapter: Optional[int]) -> Dict[str, Any]:
-    route = self._route(query=query, genre=genre)
-    search_query = self._expand_query(query, route.get("default_query", ""))
-    base_context = self._collect_tables(
-        search_query,
-        route["recommended_base_tables"],
-        genre=route["genre_filter"],
-        top_k=1,
-    )
-    dynamic_context = self._collect_tables(
-        search_query,
-        route["recommended_dynamic_tables"],
-        genre=route["genre_filter"],
-        top_k=2,
-    )
-
-    # --- 裁决层 ---
-    primary_genre = str(
-        route.get("meta", {}).get("primary_genre", "") or genre or ""
-    ).strip()
-    reasoning = self._load_reasoning(primary_genre)
-    ranked = self._apply_reasoning(reasoning, base_context, dynamic_context)
-
-    source_trace = route["source_trace"] + self._build_source_trace_with_reasoning(ranked, reasoning)
-
-    raw_anti = merge_anti_patterns(
-        route["route_anti_patterns"],
-        self._extract_anti_patterns(base_context),
-        self._extract_anti_patterns(dynamic_context),
-    )
-    anti_patterns = self._rank_anti_patterns(reasoning, raw_anti)
-
-    return {
-        "meta": {"query": query, "chapter": chapter, "explicit_genre": genre or ""},
-        "master_setting": {
-            "meta": {
-                "schema_version": "story-system/v1",
-                "contract_type": "MASTER_SETTING",
-                "generator_version": "phase1",
-                "query": query,
-            },
-            "route": route["meta"],
-            "master_constraints": {
-                "core_tone": route["core_tone"],
-                "pacing_strategy": route["pacing_strategy"],
-            },
-            "base_context": [r for r in ranked if r.get("_priority_rank", 999) < 999],
-            "source_trace": source_trace,
-            "override_policy": {
-                "locked": ["route.primary_genre", "master_constraints.core_tone"],
-                "append_only": ["anti_patterns"],
-                "override_allowed": [],
-            },
-        },
-        "chapter_brief": (
-            {
-                "meta": {
-                    "schema_version": "story-system/v1",
-                    "contract_type": "CHAPTER_BRIEF",
-                    "generator_version": "phase1",
-                    "chapter": chapter,
-                },
-                "override_allowed": {
-                    "chapter_focus": self._suggest_chapter_focus(query, dynamic_context),
-                },
-                "dynamic_context": ranked,
-                "source_trace": source_trace,
-                "reasoning": {
-                    "genre": reasoning.get("题材", ""),
-                    "inject_target": reasoning.get("contract注入层", ""),
-                    "style_priority": reasoning.get("风格优先级", ""),
-                    "pacing_strategy": reasoning.get("节奏默认策略", ""),
-                } if reasoning else {},
-            }
-            if chapter is not None
-            else None
-        ),
-        "anti_patterns": anti_patterns,
-    }
-```
-
-- [ ] **Step 5: 新增 `_build_source_trace_with_reasoning` 方法**
-
-```python
-def _build_source_trace_with_reasoning(
-    self, ranked: List[Dict[str, Any]], reasoning: Dict[str, Any]
-) -> List[Dict[str, Any]]:
-    trace: List[Dict[str, Any]] = []
-    reasoning_rule = str(reasoning.get("题材", "")).strip() if reasoning else ""
-    for row in ranked:
-        trace.append({
-            "table": row.get("_table", ""),
-            "id": row.get("编号", ""),
-            "summary": row.get("核心摘要", ""),
-            "reasoning_rule": row.get("_reasoning_rule", reasoning_rule),
-            "priority_rank": row.get("_priority_rank", 999),
-            "inject_target": str(reasoning.get("contract注入层", "")).strip() if reasoning else "",
-        })
-    return trace
-```
-
-- [ ] **Step 6: 运行裁决层测试**
-
-Run: `cd webnovel-writer && python -m pytest scripts/data_modules/tests/test_reasoning_engine.py -v`
-
-预期:全部 PASS。
-
-- [ ] **Step 7: 运行现有 engine 测试确认不破坏**
-
-Run: `cd webnovel-writer && python -m pytest scripts/data_modules/tests/test_story_system_engine.py -v`
-
-预期:全部 PASS(无裁决表时 graceful fallback)。
-
-- [ ] **Step 8: Commit**
-
-```bash
-git add webnovel-writer/scripts/data_modules/story_system_engine.py webnovel-writer/scripts/data_modules/tests/test_reasoning_engine.py
-git commit -m "feat: integrate reasoning table into story_system_engine build pipeline"
-```
-
----
-
-## Task 5: context_manager 瘦身
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/context_manager.py`
-- Delete: `webnovel-writer/scripts/data_modules/snapshot_manager.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_context_manager.py`
-
-- [ ] **Step 1: 从 `context_manager.py` 删除 snapshot 相关代码**
-
-1. 删除 import:`from .snapshot_manager import SnapshotManager, SnapshotVersionMismatch`(第 33 行)
-2. 删除 `__init__` 中 `self.snapshot_manager` 赋值(第 101 行)
-3. 删除 `_is_snapshot_compatible` 方法(第 105-146 行)
-4. 删除 `build_context` 中 snapshot 加载和保存逻辑(第 162-169 行和第 176-181 行)
-5. 删除 `_story_contract_signature` 方法(第 794-817 行)
-6. 删除 `_payload_signature` 方法(第 819-823 行)
-7. 删除 `build_context` 的 `use_snapshot` 和 `save_snapshot` 参数
-
-- [ ] **Step 2: 简化 `build_context` 为纯 JSON 返回**
-
-改造后的 `build_context`:
-
-```python
-def build_context(
-    self,
-    chapter: int,
-    template: str | None = None,
-    max_chars: Optional[int] = None,
-) -> Dict[str, Any]:
-    template = template or self.DEFAULT_TEMPLATE
-    self._active_template = template
-    if template not in self.TEMPLATE_WEIGHTS:
-        template = self.DEFAULT_TEMPLATE
-        self._active_template = template
-
-    pack = self._build_pack(chapter)
-    if getattr(self.config, "context_ranker_enabled", True):
-        pack = self.context_ranker.rank_pack(pack, chapter)
-
-    return self._assemble_json_payload(pack, template=template, max_chars=max_chars)
-```
-
-- [ ] **Step 3: 把 `assemble_context` 重写为 `_assemble_json_payload`**
-
-直接返回 dict,不做 text 渲染:
-
-```python
-def _assemble_json_payload(
-    self,
-    pack: Dict[str, Any],
-    template: str = DEFAULT_TEMPLATE,
-    max_chars: Optional[int] = None,
-) -> Dict[str, Any]:
-    chapter = int((pack.get("meta") or {}).get("chapter") or 0)
-    weights = self._resolve_template_weights(template=template, chapter=chapter)
-
-    payload: Dict[str, Any] = {
-        "meta": {
-            **(pack.get("meta") or {}),
-            "context_contract_version": "v3",
-        },
-    }
-
-    for section_name in self.SECTION_ORDER:
-        if section_name in pack and section_name != "global":
-            content = pack[section_name]
-            weight = weights.get(section_name, 0.0)
-            if weight > 0 or section_name in self.EXTRA_SECTIONS:
-                payload[section_name] = content
-
-    if chapter > 0:
-        payload["meta"]["context_weight_stage"] = self._resolve_context_stage(chapter)
-
-    return payload
-```
-
-- [ ] **Step 4: 删除 `_compact_json_text` 方法**
-
-删除第 749-764 行。
-
-- [ ] **Step 5: 删除 `assemble_context` 旧方法**
-
-删除第 185-217 行的 `assemble_context`。
-
-- [ ] **Step 6: 更新 `__init__` 签名**
-
-```python
-def __init__(self, config=None):
-    self.config = config or get_config()
-    self.index_manager = IndexManager(self.config)
-    self.context_ranker = ContextRanker(self.config)
-```
-
-- [ ] **Step 7: 删除 `snapshot_manager.py`**
-
-```bash
-git rm webnovel-writer/scripts/data_modules/snapshot_manager.py
-```
-
-- [ ] **Step 8: 更新 `extract_chapter_context.py` 的 `_load_contract_context`**
-
-`_load_contract_context`(第 294-325 行)改为:
-
-```python
-def _load_contract_context(project_root: Path, chapter_num: int) -> Dict[str, Any]:
-    """Build context via ContextManager and return payload directly."""
-    _ensure_scripts_path()
-    from data_modules.config import DataModulesConfig
-    from data_modules.context_manager import ContextManager
-
-    config = DataModulesConfig.from_project_root(project_root)
-    manager = ContextManager(config)
-    payload = manager.build_context(chapter=chapter_num, template="plot")
-
-    return {
-        "context_contract_version": (payload.get("meta") or {}).get("context_contract_version"),
-        "context_weight_stage": (payload.get("meta") or {}).get("context_weight_stage"),
-        "story_contract": payload.get("story_contract", {}),
-        "runtime_status": payload.get("runtime_status", {}),
-        "latest_commit": payload.get("latest_commit", {}),
-        "prewrite_validation": payload.get("prewrite_validation", {}),
-        "reader_signal": payload.get("reader_signal", {}),
-        "genre_profile": payload.get("genre_profile", {}),
-        "writing_guidance": payload.get("writing_guidance", {}),
-        "plot_structure": payload.get("plot_structure", {}),
-        "long_term_memory": payload.get("long_term_memory", {}),
-        "scene": payload.get("scene", {}),
-        "core": payload.get("core", {}),
-    }
-```
-
-- [ ] **Step 9: 把 `_render_text()` 改为纯 JSON 序列化**
-
-当前 `_render_text()`(第 364-601 行)是一个 240 行的审计式文本渲染函数。按 spec 终局,text 渲染不再由代码层负责——context-agent 拿 JSON payload 按示例写任务书。
-
-把整个 `_render_text()` 替换为:
-
-```python
-def _render_text(payload: Dict[str, Any]) -> str:
-    """JSON 序列化输出,text 渲染由 context-agent 负责。"""
-    return json.dumps(payload, ensure_ascii=False, indent=2)
-```
-
-这意味着 `--format text` 和 `--format json` 现在输出相同内容。如果后续要区分,可以在 context-agent 侧处理,但代码层不再做 markdown 拼接。
-
-- [ ] **Step 10: 修复受影响的测试**
-
-在 `test_context_manager.py` 中:
-- 删除所有 `snapshot_manager` 相关的 mock 和 fixture
-- 删除 snapshot 相关的测试用例
-- 更新 `build_context` 调用移除 `use_snapshot` / `save_snapshot` 参数
-- 更新断言适配新的 payload 结构(直接 `payload["story_contract"]` 而不是 `payload["sections"]["story_contract"]["content"]`)
-
-在 `test_extract_chapter_context.py` 中:
-- 更新任何依赖旧 markdown 渲染输出的断言(如 `"## 本章大纲"` 等 markdown 标题检查改为 JSON key 检查)
-
-- [ ] **Step 11: 运行测试**
-
-Run: `cd webnovel-writer && python -m pytest scripts/data_modules/tests/test_context_manager.py scripts/data_modules/tests/test_extract_chapter_context.py -v`
-
-预期:全部 PASS。
-
-- [ ] **Step 12: 确认行数**
-
-Run: `wc -l webnovel-writer/scripts/data_modules/context_manager.py`
-
-预期:400 行以下。
-
-- [ ] **Step 13: Commit**
-
-```bash
-git add webnovel-writer/scripts/data_modules/context_manager.py webnovel-writer/scripts/extract_chapter_context.py webnovel-writer/scripts/data_modules/tests/test_context_manager.py
-git rm webnovel-writer/scripts/data_modules/snapshot_manager.py
-git commit -m "refactor: slim context_manager to pure JSON assembler, remove snapshot"
-```
-
----
-
-## Task 6: 旧散写路径清理
-
-**Files:**
-- Modify: `webnovel-writer/skills/webnovel-write/SKILL.md:184,254,323`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py`
-
-- [ ] **Step 1: 删除 SKILL.md 中 Step 2 的 `set-chapter-status`**
-
-删除第 182-184 行:
-
-```markdown
-状态推进:
-
-```bash
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" state set-chapter-status --chapter {chapter_num} --status chapter_drafted
-```
-```
-
-- [ ] **Step 2: 删除 SKILL.md 中 Step 4 的 `set-chapter-status`**
-
-删除第 250-254 行的 `状态推进(--minimal 除外):` 段和对应的 bash 块。
-
-- [ ] **Step 3: 删除 SKILL.md 中 Step 5 末尾的 `set-chapter-status`**
-
-删除第 320-323 行的状态推进 bash 块。Step 5 的状态推进现在由 `state_projection_writer.py` 在 commit accepted 时自动完成。
-
-在 Step 5.3 验证投影状态段落中补充说明:
-
-```markdown
-**chapter_status 推进**:
-- accepted commit → `state_projection_writer` 自动推进到 `chapter_committed`
-- rejected commit → `state_projection_writer` 自动推进到 `chapter_rejected`
-- 不再由 skill 手动调用 `set-chapter-status`
-```
-
-- [ ] **Step 4: 更新充分性闸门**
-
-把第 338-346 行的闸门条件中:
-- 删除 "2. `chapter_status` 已推进到 `chapter_drafted`(Step 2 完成)"
-- 把 "5. ... `chapter_status` 已推进到 `chapter_reviewed`" 中状态检查改为仅由投影确认
-- 把 "6. ... `chapter_status` 已推进到 `chapter_committed`" 改为 "6. ... projection_status 四项全部 done/skipped"
-
-改为:
-
-```markdown
-## 充分性闸门
-
-未满足以下条件前,不得结束流程:
-
-1. 章节正文文件存在且非空。
-2. Step 3 已产出审查结果并落库(`--minimal` 除外)。
-3. 若存在 `blocking=true` 的 issue,流程必须停在 Step 3。
-4. Step 4 的 `anti_ai_force_check=pass`(`--minimal` 除外)。
-5. Step 5 已生成 accepted `CHAPTER_COMMIT`,`projection_status` 四项全部为 `done` 或 `skipped`。
-6. `chapter_status` 为 `chapter_committed`(由 projection writer 自动推进,不手动写入)。
-7. 若启用观测,已读取最新 timing 记录并给出结论。
-```
-
-- [ ] **Step 5: 新增 prompt integrity 测试断言**
-
-在 `test_prompt_integrity.py` 末尾新增:
-
-```python
-def test_no_direct_state_writes_in_write_skill():
-    """webnovel-write SKILL.md 中不应有 set-chapter-status 调用(由 projection writer 统一推进)。"""
-    text = (SKILLS_DIR / "webnovel-write" / "SKILL.md").read_text(encoding="utf-8")
-    assert "state set-chapter-status" not in text, (
-        "webnovel-write 中不应直接调用 state set-chapter-status,"
-        "chapter_status 由 state_projection_writer 在 commit 时自动推进"
-    )
-
-
-def test_no_direct_state_writes_in_agents():
-    """agents 目录中不应有直接写 state/index 的指令。"""
-    for agent_file in AGENT_FILES:
-        text = _read_text(agent_file)
-        assert "state set-chapter-status" not in text, (
-            f"{agent_file.name}: 不应直接调用 state set-chapter-status"
-        )
-```
-
-- [ ] **Step 6: 运行测试**
-
-Run: `cd webnovel-writer && python -m pytest scripts/data_modules/tests/test_prompt_integrity.py -v`
-
-预期:全部 PASS。
-
-- [ ] **Step 7: Commit**
-
-```bash
-git add webnovel-writer/skills/webnovel-write/SKILL.md webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py
-git commit -m "refactor: remove direct set-chapter-status calls from write skill"
-```
-
----
-
-## Task 7: projection 层收束
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/event_projection_router.py`
-- Modify: `webnovel-writer/scripts/data_modules/state_projection_writer.py`
-- Modify: `webnovel-writer/scripts/data_modules/chapter_commit_service.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_event_projection_router.py`
-
-- [ ] **Step 1: 写 router vector 路由测试**
-
-在 `test_event_projection_router.py` 末尾新增:
-
-```python
-def test_router_maps_power_breakthrough_to_state_memory_vector():
-    router = EventProjectionRouter()
-    targets = router.route(
-        {"event_type": "power_breakthrough", "subject": "xiaoyan", "payload": {}}
-    )
-    assert "vector" in targets
-    assert "state" in targets
-    assert "memory" in targets
-
-
-def test_router_maps_relationship_changed_to_index_and_vector():
-    router = EventProjectionRouter()
-    targets = router.route(
-        {"event_type": "relationship_changed", "subject": "xiaoyan", "payload": {}}
-    )
-    assert "index" in targets
-    assert "vector" in targets
-
-
-def test_required_writers_includes_vector_for_key_events():
-    router = EventProjectionRouter()
-    payload = {
-        "meta": {"status": "accepted", "chapter": 5},
-        "accepted_events": [
-            {"event_type": "power_breakthrough", "subject": "xiaoyan", "payload": {}},
-        ],
-        "entity_deltas": [],
-        "summary_text": "摘要",
-    }
-    writers = router.required_writers(payload)
-    assert "vector" in writers
-```
-
-- [ ] **Step 2: 运行测试确认失败**
-
-Run: `cd webnovel-writer && python -m pytest scripts/data_modules/tests/test_event_projection_router.py -v`
-
-预期:新增的 3 个测试 FAIL。
-
-- [ ] **Step 3: 更新 `EventProjectionRouter.TABLE`**
-
-```python
-class EventProjectionRouter:
-    TABLE = {
-        "character_state_changed": ["state", "memory", "vector"],
-        "power_breakthrough": ["state", "memory", "vector"],
-        "relationship_changed": ["index", "vector"],
-        "world_rule_revealed": ["memory", "vector"],
-        "world_rule_broken": ["memory", "vector"],
-        "open_loop_created": ["memory"],
-        "open_loop_closed": ["memory"],
-        "promise_created": ["memory"],
-        "promise_paid_off": ["memory"],
-        "artifact_obtained": ["index", "vector"],
-    }
-```
-
-- [ ] **Step 4: 确认 `state_projection_writer` 已处理 `chapter_status`**
-
-当前 `state_projection_writer.py:34-35` 已有:
-
-```python
-if chapter > 0:
-    chapter_status[str(chapter)] = "chapter_committed"
-```
-
-这已经满足 Section 6/7 的要求(accepted commit 时自动推进到 `chapter_committed`)。
-
-确认 rejected commit 时不推进——当前第 15 行检查了 `status != "accepted"` 直接返回,不写状态。这是正确的。
-
-但 spec 要求 rejected 推进到 `chapter_rejected`。在 `apply` 方法开头加 rejected 处理:
-
-```python
-def apply(self, commit_payload: dict) -> dict:
-    chapter = int(commit_payload.get("meta", {}).get("chapter") or 0)
-    status = commit_payload["meta"]["status"]
-
-    if status == "rejected":
-        if chapter > 0:
-            state_path = self.project_root / ".webnovel" / "state.json"
-            state = read_json_if_exists(state_path) or {}
-            progress = state.setdefault("progress", {})
-            chapter_status = progress.setdefault("chapter_status", {})
-            chapter_status[str(chapter)] = "chapter_rejected"
-            write_json(state_path, state)
-        return {"applied": True, "writer": "state", "reason": "commit_rejected_status_updated"}
-
-    # ... rest of accepted logic
-```
-
-- [ ] **Step 5: 确认 `chapter_commit_service.apply_projections` 的失败隔离**
-
-当前第 115-119 行已有 try/except 隔离:
-
-```python
-try:
-    result = writer.apply(payload)
-    payload["projection_status"][name] = "done" if result.get("applied") else "skipped"
-except Exception as exc:
-    payload["projection_status"][name] = f"failed:{exc}"
-```
-
-并且第 120 行 `self.persist_commit(payload)` 在所有 writer 执行完后才写入——确保 `projection_status` 已更新。
-
-这已满足 spec 要求。不需要额外改动。
-
-- [ ] **Step 6: 运行测试**
-
-Run: `cd webnovel-writer && python -m pytest scripts/data_modules/tests/test_event_projection_router.py -v`
-
-预期:全部 PASS。
-
-- [ ] **Step 7: Commit**
-
-```bash
-git add webnovel-writer/scripts/data_modules/event_projection_router.py webnovel-writer/scripts/data_modules/state_projection_writer.py webnovel-writer/scripts/data_modules/tests/test_event_projection_router.py
-git commit -m "feat: add vector route to projection router, handle rejected status"
-```
-
----
-
-## Task 8: 消费端同步
-
-**Files:**
-- Modify: `webnovel-writer/agents/context-agent.md`
-- Modify: `webnovel-writer/agents/data-agent.md`
-- Modify: `webnovel-writer/skills/webnovel-write/SKILL.md`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py`
-
-- [ ] **Step 1: 更新 `context-agent.md` 删除旧引用**
-
-1. Section 2 工具段落中,确认 `extract-context` 命令标注为"备选"(已是,不动)
-2. 删除对 snapshot 的引用(grep 确认是否有)
-3. 确认 Section 8 的输出格式中有写作任务书示例(已在上一次改造中完成)
-
-- [ ] **Step 2: 确认 `data-agent.md` 不直写**
-
-当前 `data-agent.md` 已明确标注:
-- "你不直接写入这些文件" (第 111 行)
-- "不直接写入 `index.db` 和 `state.json`" (第 146 行)
-
-确认不需要改动。
-
-- [ ] **Step 3: 更新 `SKILL.md` 中 Step 5 简化描述**
-
-把 Step 5.4 的失败隔离表格中增加 vector 相关条目(如果还没有的话)。
-
-确认 Step 1 的写作任务书流程描述和当前代码对齐(已在上一次改造中完成)。
-
-- [ ] **Step 4: 在 `test_prompt_integrity.py` 中更新 `KNOWN_DELETED_FILES`**
-
-新增 `snapshot_manager.py` 到已删文件列表:
-
-```python
-KNOWN_DELETED_FILES = [
-    "step-1.5-contract.md",
-    "step-3-review-gate.md",
-    "step-5-debt-switch.md",
-    "workflow-details.md",
-    "checker-output-schema.md",
-    "workflow_manager.py",
-    "webnovel-resume",
-    "golden_three_checker.py",
-    "snapshot_manager.py",
-]
-```
-
-- [ ] **Step 5: 运行全量 prompt integrity 测试**
-
-Run: `cd webnovel-writer && python -m pytest scripts/data_modules/tests/test_prompt_integrity.py -v`
-
-预期:全部 PASS。
-
-- [ ] **Step 6: Commit**
-
-```bash
-git add webnovel-writer/agents/context-agent.md webnovel-writer/agents/data-agent.md webnovel-writer/skills/webnovel-write/SKILL.md webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py
-git commit -m "refactor: sync consumer prompts with new mainline"
-```
-
----
-
-## Task 9: 向量投影 Writer
-
-**Files:**
-- Create: `webnovel-writer/scripts/data_modules/vector_projection_writer.py`
-- Modify: `webnovel-writer/scripts/data_modules/chapter_commit_service.py`
-- Create: `webnovel-writer/scripts/data_modules/tests/test_vector_projection_writer.py`
-
-- [ ] **Step 1: 写测试**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_vector_projection_writer.py
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""VectorProjectionWriter 单元测试。"""
-from data_modules.vector_projection_writer import VectorProjectionWriter
-
-
-def test_event_to_text_formats_power_breakthrough():
-    writer = VectorProjectionWriter.__new__(VectorProjectionWriter)
-    event = {
-        "event_type": "power_breakthrough",
-        "chapter": 47,
-        "subject": "韩立",
-        "payload": {"field": "realm", "new": "筑基初期"},
-    }
-    text = writer._event_to_text(event)
-    assert "第47章" in text
-    assert "韩立" in text
-    assert "筑基初期" in text
-
-
-def test_delta_to_text_formats_relationship():
-    writer = VectorProjectionWriter.__new__(VectorProjectionWriter)
-    delta = {
-        "from_entity": "韩立",
-        "to_entity": "陈巧倩",
-        "relationship_type": "合作",
-        "chapter": 47,
-    }
-    text = writer._delta_to_text(delta)
-    assert "第47章" in text
-    assert "韩立" in text
-    assert "陈巧倩" in text
-    assert "合作" in text
-
-
-def test_collect_chunks_from_commit():
-    writer = VectorProjectionWriter.__new__(VectorProjectionWriter)
-    payload = {
-        "meta": {"chapter": 47, "status": "accepted"},
-        "accepted_events": [
-            {
-                "event_type": "power_breakthrough",
-                "chapter": 47,
-                "subject": "韩立",
-                "payload": {"field": "realm", "new": "筑基初期"},
-            },
-        ],
-        "entity_deltas": [
-            {
-                "from_entity": "韩立",
-                "to_entity": "陈巧倩",
-                "relationship_type": "合作",
-                "chapter": 47,
-            },
-        ],
-    }
-    chunks = writer._collect_chunks(payload)
-    assert len(chunks) == 2
-    assert chunks[0]["chunk_type"] == "event"
-    assert chunks[1]["chunk_type"] == "entity_delta"
-
-
-def test_rejected_commit_returns_not_applied():
-    writer = VectorProjectionWriter.__new__(VectorProjectionWriter)
-    writer.project_root = None  # won't be used
-    result = writer.apply({"meta": {"status": "rejected", "chapter": 1}})
-    assert result["applied"] is False
-```
-
-- [ ] **Step 2: 运行测试确认失败**
-
-Run: `cd webnovel-writer && python -m pytest scripts/data_modules/tests/test_vector_projection_writer.py -v`
-
-预期:FAIL(模块不存在)。
-
-- [ ] **Step 3: 实现 `vector_projection_writer.py`**
-
-```python
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-from __future__ import annotations
-
-import asyncio
-import logging
-from pathlib import Path
-from typing import Any, Dict, List
-
-logger = logging.getLogger(__name__)
-
-
-class VectorProjectionWriter:
-    def __init__(self, project_root: Path):
-        self.project_root = Path(project_root)
-
-    def apply(self, commit_payload: dict) -> dict:
-        if commit_payload["meta"]["status"] != "accepted":
-            return {"applied": False, "writer": "vector", "reason": "commit_rejected"}
-
-        chunks = self._collect_chunks(commit_payload)
-        if not chunks:
-            return {"applied": False, "writer": "vector", "reason": "no_chunks"}
-
-        try:
-            stored = self._store_chunks(chunks)
-            return {"applied": stored > 0, "writer": "vector", "stored": stored}
-        except Exception as exc:
-            logger.warning("vector_projection_failed: %s", exc)
-            return {"applied": False, "writer": "vector", "reason": f"error:{exc}"}
-
-    def _collect_chunks(self, commit_payload: dict) -> List[Dict[str, Any]]:
-        chunks: List[Dict[str, Any]] = []
-        chapter = int(commit_payload.get("meta", {}).get("chapter") or 0)
-
-        for event in commit_payload.get("accepted_events") or []:
-            if not isinstance(event, dict):
-                continue
-            text = self._event_to_text(event)
-            if text:
-                evt_chapter = int(event.get("chapter") or chapter)
-                chunks.append({
-                    "chapter": evt_chapter,
-                    "scene_index": 0,
-                    "content": text,
-                    "chunk_type": "event",
-                    "parent_chunk_id": f"ch{evt_chapter:04d}_summary",
-                    "source_file": f"commit:chapter_{evt_chapter:03d}",
-                })
-
-        for delta in commit_payload.get("entity_deltas") or []:
-            if not isinstance(delta, dict):
-                continue
-            text = self._delta_to_text(delta)
-            if text:
-                d_chapter = int(delta.get("chapter") or chapter)
-                chunks.append({
-                    "chapter": d_chapter,
-                    "scene_index": 0,
-                    "content": text,
-                    "chunk_type": "entity_delta",
-                    "parent_chunk_id": f"ch{d_chapter:04d}_summary",
-                    "source_file": f"commit:chapter_{d_chapter:03d}",
-                })
-
-        return chunks
-
-    def _event_to_text(self, event: dict) -> str:
-        chapter = int(event.get("chapter") or 0)
-        subject = str(event.get("subject") or "").strip()
-        event_type = str(event.get("event_type") or "").strip()
-        payload = event.get("payload") or {}
-
-        if event_type == "power_breakthrough":
-            new_val = str(payload.get("new") or payload.get("to") or "").strip()
-            return f"第{chapter}章:{subject}突破至{new_val}" if new_val else ""
-        elif event_type == "character_state_changed":
-            field = str(payload.get("field") or "").strip()
-            new_val = str(payload.get("new") or payload.get("to") or "").strip()
-            return f"第{chapter}章:{subject}的{field}变为{new_val}" if field and new_val else ""
-        elif event_type == "relationship_changed":
-            to_entity = str(payload.get("to_entity") or payload.get("to") or "").strip()
-            rel_type = str(
-                payload.get("relationship_type") or payload.get("type") or ""
-            ).strip()
-            return f"第{chapter}章:{subject}与{to_entity}关系变为{rel_type}" if to_entity else ""
-        elif event_type in ("world_rule_revealed", "world_rule_broken"):
-            desc = str(payload.get("description") or payload.get("rule") or "").strip()
-            action = "揭示" if "revealed" in event_type else "打破"
-            return f"第{chapter}章:{action}世界规则——{desc}" if desc else ""
-        elif event_type == "artifact_obtained":
-            name = str(payload.get("name") or subject or "").strip()
-            owner = str(payload.get("owner") or payload.get("holder") or "").strip()
-            return f"第{chapter}章:{owner}获得{name}" if owner else f"第{chapter}章:获得{name}"
-        return ""
-
-    def _delta_to_text(self, delta: dict) -> str:
-        chapter = int(delta.get("chapter") or 0)
-        from_e = str(delta.get("from_entity") or "").strip()
-        to_e = str(delta.get("to_entity") or "").strip()
-        rel = str(delta.get("relationship_type") or "").strip()
-
-        if from_e and to_e and rel:
-            return f"第{chapter}章:{from_e}与{to_e}关系变为{rel}"
-
-        entity_id = str(delta.get("entity_id") or "").strip()
-        canonical = str(delta.get("canonical_name") or entity_id).strip()
-        if entity_id:
-            return f"第{chapter}章:实体变更——{canonical}"
-        return ""
-
-    def _store_chunks(self, chunks: List[Dict[str, Any]]) -> int:
-        from .config import DataModulesConfig
-        from .rag_adapter import RAGAdapter
-
-        config = DataModulesConfig.from_project_root(self.project_root)
-        adapter = RAGAdapter(config)
-        try:
-            stored = asyncio.run(adapter.store_chunks(chunks))
-            return stored
-        except Exception as exc:
-            logger.warning("vector_store_failed: %s", exc)
-            return 0
-```
-
-- [ ] **Step 4: 在 `chapter_commit_service.py` 注册 vector writer**
-
-在 `apply_projections` 方法中(第 104-109 行),加入 vector writer:
-
-```python
-from .vector_projection_writer import VectorProjectionWriter
-
-writers = {
-    "state": StateProjectionWriter(self.project_root),
-    "index": IndexProjectionWriter(self.project_root),
-    "summary": SummaryProjectionWriter(self.project_root),
-    "memory": MemoryProjectionWriter(self.project_root),
-    "vector": VectorProjectionWriter(self.project_root),
-}
-```
-
-同时在 `build_commit` 的 `projection_status` 中加 `"vector": "pending"`:
-
-```python
-"projection_status": {
-    "state": "pending",
-    "index": "pending",
-    "summary": "pending",
-    "memory": "pending",
-    "vector": "pending",
-},
-```
-
-- [ ] **Step 5: 运行测试**
-
-Run: `cd webnovel-writer && python -m pytest scripts/data_modules/tests/test_vector_projection_writer.py -v`
-
-预期:全部 PASS。
-
-- [ ] **Step 6: Commit**
-
-```bash
-git add webnovel-writer/scripts/data_modules/vector_projection_writer.py webnovel-writer/scripts/data_modules/chapter_commit_service.py webnovel-writer/scripts/data_modules/tests/test_vector_projection_writer.py
-git commit -m "feat: add vector_projection_writer for event/entity embedding"
-```
-
----
-
-## Task 10: 时序查询接口
-
-**Files:**
-- Create: `webnovel-writer/scripts/data_modules/knowledge_query.py`
-- Create: `webnovel-writer/scripts/data_modules/tests/test_knowledge_query.py`
-- Modify: `webnovel-writer/scripts/data_modules/webnovel.py` (register CLI subcommand)
-
-- [ ] **Step 1: 写测试**
-
-```python
-# webnovel-writer/scripts/data_modules/tests/test_knowledge_query.py
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""KnowledgeQuery 时序查询测试。"""
-import json
-import sqlite3
-from pathlib import Path
-
-import pytest
-
-from data_modules.knowledge_query import KnowledgeQuery
-
-
-@pytest.fixture
-def setup_db(tmp_path):
-    """创建带 state_changes 和 relationship_events 表的测试 DB。"""
-    db_path = tmp_path / ".webnovel" / "index.db"
-    db_path.parent.mkdir(parents=True)
-
-    conn = sqlite3.connect(str(db_path))
-    conn.execute("""
-        CREATE TABLE IF NOT EXISTS entities (
-            id TEXT PRIMARY KEY,
-            canonical_name TEXT,
-            type TEXT DEFAULT '角色',
-            current_json TEXT DEFAULT '{}',
-            created_at TEXT,
-            updated_at TEXT
-        )
-    """)
-    conn.execute("""
-        CREATE TABLE IF NOT EXISTS state_changes (
-            id INTEGER PRIMARY KEY AUTOINCREMENT,
-            entity_id TEXT,
-            field TEXT,
-            old_value TEXT,
-            new_value TEXT,
-            chapter INTEGER,
-            created_at TEXT
-        )
-    """)
-    conn.execute("""
-        CREATE TABLE IF NOT EXISTS relationship_events (
-            id INTEGER PRIMARY KEY AUTOINCREMENT,
-            from_entity TEXT,
-            to_entity TEXT,
-            relationship_type TEXT,
-            description TEXT,
-            chapter INTEGER,
-            created_at TEXT
-        )
-    """)
-
-    # 插入测试数据
-    conn.execute(
-        "INSERT INTO entities (id, canonical_name, current_json) VALUES (?, ?, ?)",
-        ("hanli", "韩立", json.dumps({"realm": "筑基中期", "location": "乱星海"})),
-    )
-    conn.execute(
-        "INSERT INTO state_changes (entity_id, field, old_value, new_value, chapter) VALUES (?, ?, ?, ?, ?)",
-        ("hanli", "realm", "练气圆满", "筑基初期", 30),
-    )
-    conn.execute(
-        "INSERT INTO state_changes (entity_id, field, old_value, new_value, chapter) VALUES (?, ?, ?, ?, ?)",
-        ("hanli", "realm", "筑基初期", "筑基中期", 50),
-    )
-    conn.execute(
-        "INSERT INTO relationship_events (from_entity, to_entity, relationship_type, chapter) VALUES (?, ?, ?, ?)",
-        ("hanli", "陈巧倩", "同门", 20),
-    )
-    conn.execute(
-        "INSERT INTO relationship_events (from_entity, to_entity, relationship_type, chapter) VALUES (?, ?, ?, ?)",
-        ("hanli", "陈巧倩", "合作", 45),
-    )
-    conn.commit()
-    conn.close()
-
-    return tmp_path
-
-
-def test_entity_state_at_chapter_before_first_change(setup_db):
-    kq = KnowledgeQuery(setup_db)
-    result = kq.entity_state_at_chapter("hanli", 10)
-    # 第10章在第一次 state_change 之前,应返回空变更
-    assert result["entity_id"] == "hanli"
-    assert result["state_at_chapter"] == {}
-
-
-def test_entity_state_at_chapter_after_first_breakthrough(setup_db):
-    kq = KnowledgeQuery(setup_db)
-    result = kq.entity_state_at_chapter("hanli", 35)
-    assert result["state_at_chapter"]["realm"] == "筑基初期"
-
-
-def test_entity_state_at_chapter_after_second_breakthrough(setup_db):
-    kq = KnowledgeQuery(setup_db)
-    result = kq.entity_state_at_chapter("hanli", 60)
-    assert result["state_at_chapter"]["realm"] == "筑基中期"
-
-
-def test_relationships_at_chapter_before_any(setup_db):
-    kq = KnowledgeQuery(setup_db)
-    result = kq.entity_relationships_at_chapter("hanli", 10)
-    assert result["relationships"] == []
-
-
-def test_relationships_at_chapter_after_first(setup_db):
-    kq = KnowledgeQuery(setup_db)
-    result = kq.entity_relationships_at_chapter("hanli", 25)
-    assert len(result["relationships"]) == 1
-    assert result["relationships"][0]["to_entity"] == "陈巧倩"
-    assert result["relationships"][0]["relationship_type"] == "同门"
-
-
-def test_relationships_at_chapter_after_update(setup_db):
-    kq = KnowledgeQuery(setup_db)
-    result = kq.entity_relationships_at_chapter("hanli", 50)
-    rels = result["relationships"]
-    assert len(rels) == 1
-    assert rels[0]["relationship_type"] == "合作"
-```
-
-- [ ] **Step 2: 运行测试确认失败**
-
-Run: `cd webnovel-writer && python -m pytest scripts/data_modules/tests/test_knowledge_query.py -v`
-
-预期:FAIL。
-
-- [ ] **Step 3: 实现 `knowledge_query.py`**
-
-```python
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-from __future__ import annotations
-
-import sqlite3
-from pathlib import Path
-from typing import Any, Dict, List
-
-
-class KnowledgeQuery:
-    def __init__(self, project_root: Path):
-        self.project_root = Path(project_root)
-        self._db_path = self.project_root / ".webnovel" / "index.db"
-
-    def entity_state_at_chapter(self, entity_id: str, chapter: int) -> Dict[str, Any]:
-        """查询实体在指定章节时的状态(从 state_changes 反推)。"""
-        conn = sqlite3.connect(str(self._db_path))
-        conn.row_factory = sqlite3.Row
-        try:
-            rows = conn.execute(
-                """
-                SELECT field, new_value
-                FROM state_changes
-                WHERE entity_id = ? AND chapter <= ?
-                ORDER BY chapter ASC, id ASC
-                """,
-                (entity_id, chapter),
-            ).fetchall()
-
-            state: Dict[str, str] = {}
-            for row in rows:
-                field = str(row["field"] or "").strip()
-                if field:
-                    state[field] = str(row["new_value"] or "").strip()
-
-            return {
-                "entity_id": entity_id,
-                "at_chapter": chapter,
-                "state_at_chapter": state,
-            }
-        finally:
-            conn.close()
-
-    def entity_relationships_at_chapter(self, entity_id: str, chapter: int) -> Dict[str, Any]:
-        """查询实体在指定章节时的所有关系(从 relationship_events 计算快照)。"""
-        conn = sqlite3.connect(str(self._db_path))
-        conn.row_factory = sqlite3.Row
-        try:
-            rows = conn.execute(
-                """
-                SELECT from_entity, to_entity, relationship_type, description, chapter
-                FROM relationship_events
-                WHERE (from_entity = ? OR to_entity = ?) AND chapter <= ?
-                ORDER BY chapter ASC, id ASC
-                """,
-                (entity_id, entity_id, chapter),
-            ).fetchall()
-
-            # 用最新的关系覆盖旧关系(按 pair 去重,保留最新)
-            latest: Dict[str, Dict[str, Any]] = {}
-            for row in rows:
-                from_e = str(row["from_entity"] or "").strip()
-                to_e = str(row["to_entity"] or "").strip()
-                pair_key = tuple(sorted([from_e, to_e]))
-                latest[str(pair_key)] = {
-                    "from_entity": from_e,
-                    "to_entity": to_e,
-                    "relationship_type": str(row["relationship_type"] or "").strip(),
-                    "description": str(row["description"] or "").strip(),
-                    "since_chapter": int(row["chapter"] or 0),
-                }
-
-            return {
-                "entity_id": entity_id,
-                "at_chapter": chapter,
-                "relationships": list(latest.values()),
-            }
-        finally:
-            conn.close()
-```
-
-- [ ] **Step 4: 运行测试**
-
-Run: `cd webnovel-writer && python -m pytest scripts/data_modules/tests/test_knowledge_query.py -v`
-
-预期:全部 PASS。
-
-- [ ] **Step 5: 注册 `knowledge` CLI 子命令**
-
-在 `webnovel-writer/scripts/data_modules/webnovel.py` 中注册 `knowledge` 子命令。找到 subparser 注册区域(grep `add_parser`),新增:
-
-```python
-# knowledge 子命令
-knowledge_parser = subparsers.add_parser("knowledge", help="时序知识查询")
-knowledge_sub = knowledge_parser.add_subparsers(dest="knowledge_action")
-
-qs_parser = knowledge_sub.add_parser("query-entity-state", help="查询实体在指定章节的状态")
-qs_parser.add_argument("--entity", required=True, help="实体 ID")
-qs_parser.add_argument("--at-chapter", type=int, required=True, help="目标章节号")
-
-qr_parser = knowledge_sub.add_parser("query-relationships", help="查询实体在指定章节的关系")
-qr_parser.add_argument("--entity", required=True, help="实体 ID")
-qr_parser.add_argument("--at-chapter", type=int, required=True, help="目标章节号")
-```
-
-在命令分发区域新增 handler:
-
-```python
-if args.command == "knowledge":
-    from .knowledge_query import KnowledgeQuery
-    kq = KnowledgeQuery(project_root)
-    if args.knowledge_action == "query-entity-state":
-        result = kq.entity_state_at_chapter(args.entity, args.at_chapter)
-        print_success(result, message="entity_state_at_chapter")
-    elif args.knowledge_action == "query-relationships":
-        result = kq.entity_relationships_at_chapter(args.entity, args.at_chapter)
-        print_success(result, message="entity_relationships_at_chapter")
-```
-
-- [ ] **Step 6: 同步 `REGISTERED_CLI_SUBCOMMANDS` 和 `context-agent.md`**
-
-在 `test_prompt_integrity.py` 的 `REGISTERED_CLI_SUBCOMMANDS`(第 32-38 行)中新增 `"knowledge"`:
-
-```python
-REGISTERED_CLI_SUBCOMMANDS = {
-    "where", "preflight", "use",
-    "index", "state", "rag", "style", "entity", "context", "memory",
-    "migrate", "status", "update-state", "backup", "archive",
-    "init", "extract-context", "memory-contract", "review-pipeline",
-    "story-system", "chapter-commit", "story-events",
-    "knowledge",
-}
-```
-
-在 `context-agent.md` Section 2 的"补充命令"段落中新增:
-
-```bash
-# 时序知识查询(查询某实体在指定章节时的状态和关系)
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "{project_root}" knowledge query-entity-state --entity "{entity_id}" --at-chapter {N}
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "{project_root}" knowledge query-relationships --entity "{entity_id}" --at-chapter {N}
-```
-
-- [ ] **Step 7: 运行全量测试**
-
-Run: `cd webnovel-writer && python -m pytest scripts/data_modules/tests/ scripts/tests/ -v --timeout=60`
-
-预期:全部 PASS。
-
-- [ ] **Step 8: Commit**
-
-```bash
-git add webnovel-writer/scripts/data_modules/knowledge_query.py webnovel-writer/scripts/data_modules/tests/test_knowledge_query.py webnovel-writer/scripts/data_modules/webnovel.py webnovel-writer/agents/context-agent.md webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py
-git commit -m "feat: add knowledge_query temporal API with CLI and prompt sync"
-```
-
----
-
-## Task 11: 最终集成验证
-
-**Files:** (read-only verification)
-
-- [ ] **Step 1: 运行全量测试套件**
-
-```bash
-cd webnovel-writer && python -m pytest scripts/data_modules/tests/ scripts/tests/ -v --timeout=120
-```
-
-预期:全部 PASS,0 FAIL。
-
-- [ ] **Step 2: grep 确认无残留散写**
-
-```bash
-grep -rn "state set-chapter-status" webnovel-writer/skills/ webnovel-writer/agents/ || echo "CLEAN"
-grep -rn "index process-chapter" webnovel-writer/skills/ webnovel-writer/agents/ || echo "CLEAN"
-```
-
-预期:两条都输出 `CLEAN`。
-
-- [ ] **Step 3: 确认 context_manager.py 行数**
-
-```bash
-wc -l webnovel-writer/scripts/data_modules/context_manager.py
-```
-
-预期:< 400 行。
-
-- [ ] **Step 4: 确认 snapshot_manager.py 已删除**
-
-```bash
-test -f webnovel-writer/scripts/data_modules/snapshot_manager.py && echo "STILL EXISTS" || echo "DELETED"
-```
-
-预期:`DELETED`。
-
-- [ ] **Step 5: 确认裁决表覆盖 7 个题材**
-
-```bash
-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:
-    rows = list(csv.DictReader(f))
-genres = [r['题材'] for r in rows]
-print(f'题材数: {len(genres)}')
-print(f'题材: {genres}')
-assert len(genres) == 7
-"
-```
-
-预期:输出 7 个题材。
-
-- [ ] **Step 6: 确认 CSV_CONFIG 对齐**
-
-```bash
-cd webnovel-writer && python -m pytest scripts/data_modules/tests/test_csv_config.py -v
-```
-
-预期:全部 PASS。
-
-- [ ] **Step 7: Commit final**
-
-如果有任何 fix,commit:
-
-```bash
-git add -A
-git commit -m "chore: final integration fixes for story system convergence"
-```

+ 0 - 247
docs/archive/superpowers/plans/2026-04-16-dashboard-frontend-rebuild.md

@@ -1,247 +0,0 @@
-# Dashboard 前端重做计划
-
-> **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:** 重做 dashboard 前端:砍掉废数据、加数据可视化、拆分文件、新增伏笔追踪和系统状态页。
-
-**Architecture:** React 19 + Vite,新增图表库和路由。后端补聚合 API。
-
-**Tech Stack:** React 19, Vite, react-router-dom v7, Apache ECharts (echarts + echarts-for-react), Pixelarticons (pixelarticons)
-
-**Design:** 视觉风格和具体图表形式待确认,见 `dashboard/frontend/design.md`(待更新)
-
----
-
-## 文件结构
-
-### 前端(dashboard/frontend/src/)
-
-```
-src/
-├── main.jsx                    # 入口,挂载 Router
-├── App.jsx                     # Layout shell(侧边栏 + Router Outlet + SSE)
-├── api.js                      # API 请求(保留,补新端点)
-├── index.css                   # 全局样式
-├── components/
-│   ├── ChartWrapper.jsx        # 图表组件封装(统一风格)
-│   ├── Badge.jsx               # Badge 组件
-│   ├── Pager.jsx               # 翻页控制组件(大数据量分窗口浏览)
-│   └── DataTable.jsx           # 带分页的表格组件(从 MiniTable 提取)
-└── pages/
-    ├── OverviewPage.jsx        # 总览
-    ├── CharactersPage.jsx      # 角色图鉴(实体 + 关系图合并)
-    ├── PacingPage.jsx          # 节奏雷达
-    ├── ForeshadowingPage.jsx   # 伏笔追踪
-    ├── FilesPage.jsx           # 文档浏览(迁移)
-    └── SystemPage.jsx          # 系统状态
-```
-
-### 后端(dashboard/app.py 补充 API)
-
-```
-/api/stats/chapter-trend       # 聚合:每章字数 + 审查得分 + 钩子强度
-/api/commits                   # 最近 N 个 commit 的 meta + projection_status
-/api/contracts/summary         # MASTER_SETTING 摘要 + 当前卷/章合同存在性
-/api/env-status                # RAG/Rerank 环境配置状态(PR #50)
-/api/env-status/probe          # RAG/Rerank 主动探测(PR #50)
-```
-
----
-
-## Task 1: 后端补聚合 API
-
-**Files:**
-- Modify: `webnovel-writer/dashboard/app.py`
-
-- [ ] **Step 1: 加 `/api/stats/chapter-trend`**
-
-从 index.db 聚合查询,一次返回每章的字数、审查得分、钩子类型和强度。支持 `limit` 和 `offset` 参数,默认返回最近 50 章。
-
-- [ ] **Step 2: 加 `/api/commits`**
-
-读取 `.story-system/commits/` 目录下的 commit JSON 文件,返回 chapter、status、projection_status。支持 `limit` 参数。
-
-- [ ] **Step 3: 加 `/api/contracts/summary`**
-
-读取合同树文件存在性和关键字段(MASTER_SETTING 的 primary_genre/core_tone、volume/chapter/review 合同数量)。
-
-- [ ] **Step 4: 合并 PR #50 的 env-status API**
-
-从 PR #50 提取 `/api/env-status` 和 `/api/env-status/probe` 代码。
-
-- [ ] **Step 5: 测试 + Commit**
-
----
-
-## Task 2: 前端基础设施(路由 + 文件拆分 + 公共组件)
-
-**Files:**
-- Modify: `dashboard/frontend/package.json`
-- Modify: `dashboard/frontend/src/main.jsx`
-- Modify: `dashboard/frontend/src/App.jsx`
-- Modify: `dashboard/frontend/src/api.js`
-- Create: `dashboard/frontend/src/components/*.jsx`
-
-- [ ] **Step 1: 安装依赖**
-
-```bash
-npm install echarts echarts-for-react react-router-dom pixelarticons
-```
-
-- [ ] **Step 2: 提取公共组件**
-
-从现有 App.jsx 提取 Badge、DataTable(原 MiniTable)、Pager(翻页控制)。
-
-Pager 组件要点:
-- 支持窗口大小(每页 N 条)+ 翻页 + "跳到最新"
-- 适配 500+ 章数据量
-
-- [ ] **Step 3: 改造 main.jsx 加路由**
-
-每个 page 组件 lazy import + React Router。
-
-- [ ] **Step 4: 瘦身 App.jsx 为纯 Layout Shell**
-
-只保留:侧边栏导航 + SSE 连接 + Router Outlet。所有页面逻辑移到 pages/。
-
-- [ ] **Step 5: 更新 api.js**
-
-补新端点的 fetch 函数(chapter-trend, commits, contracts-summary, env-status, env-probe)。
-
-- [ ] **Step 6: 构建验证 + Commit**
-
----
-
-## Task 3: 总览页
-
-**Files:**
-- Create: `dashboard/frontend/src/pages/OverviewPage.jsx`
-
-**功能:**
-- 统计卡:总字数/进度、当前章节/卷、Story Runtime 状态、审查均分、紧急伏笔数
-- 审查得分可视化:ECharts 折线图,支持翻页浏览(默认最近 N 章)
-- 字数分布可视化:ECharts 柱状图,按卷分组
-- Strand Weave 整体分布
-- 紧急伏笔 Top 5 表格
-- 最近 3 章概要卡片
-
-**删除:**
-- MergedDataView(全量数据视图)
-- FULL_DATA_GROUPS / FULL_DATA_DOMAINS 常量
-
-**大数据量适配:**
-- 可视化组件默认显示最近窗口,支持翻页
-- 字数按卷分组,不一次性铺开所有章节
-
-- [ ] **Step 1: 实现 + 构建验证 + Commit**
-
----
-
-## Task 4: 角色图鉴页
-
-**Files:**
-- Create: `dashboard/frontend/src/pages/CharactersPage.jsx`
-
-**功能:**
-- Tab 1 列表视图:实体列表 + 筛选 + 详情面板 + 状态变化历史(保留现有逻辑)
-- Tab 2 关系图谱:ECharts graph series(力导向布局)+ 章节时间轴滑块
-
-关系图谱时间轴要点:
-- 滑块控制当前章节,节点/边按 `first_appearance` / `chapter` 过滤
-- 关系标签支持随章节变化(如"初识"→"宿敌")
-- 播放/暂停按钮,自动推进章节
-- 显示当前章节号 + 当前可见节点数
-
-- [ ] **Step 1: 实现 + Commit**
-
----
-
-## Task 5: 节奏雷达页
-
-**Files:**
-- Create: `dashboard/frontend/src/pages/PacingPage.jsx`
-
-**功能:**
-- 钩子强度走势:ECharts 面积折线图,支持翻页,每页 N 章
-- Strand 分布:ECharts 堆叠柱状图,逐章 Strand 分布
-- 字数分布:ECharts 柱状图,按卷分组
-
-**大数据量适配:**
-- 所有组件支持翻页 + "跳到最新"
-
-- [ ] **Step 1: 实现 + Commit**
-
----
-
-## Task 6: 伏笔追踪页
-
-**Files:**
-- Create: `dashboard/frontend/src/pages/ForeshadowingPage.jsx`
-
-**功能:**
-- 统计卡:总伏笔、活跃、已回收、紧急/超期
-- 状态筛选按钮:全部 / 紧急 / 活跃 / 已回收
-- 伏笔时间线:ECharts 自定义 bar series(横向甘特),埋设章→目标章,颜色按状态,蓝线标当前章
-- 完整伏笔表格:内容、状态、埋设章、目标章、紧急度
-
-**大数据量适配:**
-- 甘特图默认只显示活跃+紧急,已回收折叠
-- 横轴范围自动适配(不铺满 1-500)
-
-- [ ] **Step 1: 实现 + Commit**
-
----
-
-## Task 7: 系统状态页
-
-**Files:**
-- Create: `dashboard/frontend/src/pages/SystemPage.jsx`
-
-**功能:**
-- Story Runtime 健康状态(mainline/fallback、latest commit)
-- 合同树概览(MASTER_SETTING 题材/调性、volume/chapter/review 数量)
-- 最近 Commit 历史表格(章节、accepted/rejected、5 路 projection_status)
-- RAG 环境状态(embed/rerank key、vector_db 大小、rag_mode)+ 诊断按钮
-
-- [ ] **Step 1: 实现 + Commit**
-
----
-
-## Task 8: 文档浏览迁移 + 最终清理
-
-**Files:**
-- Create: `dashboard/frontend/src/pages/FilesPage.jsx`
-- Modify: `dashboard/frontend/src/App.jsx`
-
-- [ ] **Step 1: 迁移 FilesPage(逻辑不变)**
-- [ ] **Step 2: 最终清理 App.jsx(删除所有已迁移的组件和常量)**
-- [ ] **Step 3: 前端构建 + 启动验证所有 6 页 + Commit**
-
----
-
-## 导航变更
-
-| 旧导航 | 新导航 | 图标 (Pixelarticons) | 变化 |
-|--------|--------|---------------------|------|
-| 📊 数据总览 | 总览 | `chart-bar` | 删全量视图,加可视化 |
-| 👤 设定词典 | 角色图鉴 | `users` | 合并关系图谱,2D 替 3D,加时间轴 |
-| 🕸️ 关系图谱 | _(合并到角色)_ | — | 删除独立页面 |
-| 📝 章节一览 | 节奏雷达 | `trending-up` | 新页面 |
-| 📁 文档浏览 | 文档浏览 | `folder` | 不变 |
-| 🔥 追读力 | 伏笔追踪 | `bookmark` | 新页面 |
-| _(无)_ | 系统状态 | `sliders` | 新页面 |
-
-## 删除的数据
-
-- RAG 查询日志、工具调用统计、Override 合约明细、债务事件明细
-- 无效事实列表、写作清单评分原始数据、全量数据视图(MergedDataView)
-
-## 待确认项(设计讨论后更新)
-
-- [x] ~~视觉风格确认~~ → 保留像素风
-- [x] ~~图表库选型~~ → Apache ECharts(echarts + echarts-for-react)
-- [x] ~~关系图库选型~~ → ECharts graph series(力导向布局)
-- [x] ~~审查得分可视化形式~~ → 折线图
-- [x] ~~字数分布可视化形式~~ → 柱状图(按卷分组)
-- [x] ~~钩子强度可视化形式~~ → 面积折线图
-- [x] ~~Strand 分布可视化形式~~ → 堆叠柱状图

+ 0 - 1008
docs/archive/superpowers/plans/2026-04-16-references-genre-alignment.md

@@ -1,1008 +0,0 @@
-# References 题材体系对齐与结构补全 Implementation Plan
-
-> **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:** 建立权威题材枚举,让 CSV `适用题材` 列、路由表、裁决表、搜索脚本全部对齐到同一套枚举值;补上校验脚本和结构层缺口。
-
-**Architecture:** 两层题材体系(15 个 canonical_genre + 37 个 platform_tag)驱动全链路。`genre-canonical.md` 是人类可读真源,`GENRE_CANONICAL` Python 常量是机器可读真源,`validate_csv.py` 校验两者与实际 CSV 数据的一致性。所有改动只涉及 `references/csv/`、`scripts/reference_search.py`、`scripts/validate_csv.py` 和 `scripts/data_modules/story_system_engine.py`,不触碰 skill 文本或 contract 产出结构。
-
-**Tech Stack:** Python 3.10+, pytest, csv (stdlib)
-
-**Spec:** `docs/superpowers/specs/2026-04-16-references-completion-spec.md`
-**Genre Canonical:** `webnovel-writer/references/csv/genre-canonical.md`
-
-**Status:** Completed. The reference canonical pipeline is implemented, route/reasoning CSVs meet Phase 2 thresholds, and `validate_csv.py --format json` reports 0 errors / 0 warnings.
-
----
-
-### Task 1: 在 reference_search.py 中添加 GENRE_CANONICAL 常量与映射
-
-**Files:**
-- Modify: `webnovel-writer/scripts/reference_search.py:84-154`
-- Test: `webnovel-writer/scripts/tests/test_reference_search.py`
-
-- [ ] **Step 1: 写失败测试 — canonical 常量存在且完整**
-
-在 `webnovel-writer/scripts/tests/test_reference_search.py` 末尾添加:
-
-```python
-class TestGenreCanonical:
-    def test_canonical_genres_has_15_entries(self):
-        from reference_search import GENRE_CANONICAL
-        assert len(GENRE_CANONICAL) == 15
-        expected = {
-            "都市", "玄幻", "仙侠", "奇幻", "科幻",
-            "历史", "悬疑", "游戏", "古言", "现言",
-            "幻言", "年代", "种田", "快穿", "衍生",
-        }
-        assert GENRE_CANONICAL == expected
-
-    def test_platform_to_canonical_maps_all_37_tags(self):
-        from reference_search import PLATFORM_TO_CANONICAL
-        assert len(PLATFORM_TO_CANONICAL) == 37
-        # Every value must be a canonical genre
-        from reference_search import GENRE_CANONICAL
-        for tag, canonical in PLATFORM_TO_CANONICAL.items():
-            assert canonical in GENRE_CANONICAL, f"{tag} -> {canonical} not in GENRE_CANONICAL"
-
-    def test_platform_to_canonical_spot_checks(self):
-        from reference_search import PLATFORM_TO_CANONICAL
-        assert PLATFORM_TO_CANONICAL["都市日常"] == "都市"
-        assert PLATFORM_TO_CANONICAL["战神赘婿"] == "都市"
-        assert PLATFORM_TO_CANONICAL["东方仙侠"] == "仙侠"
-        assert PLATFORM_TO_CANONICAL["西方奇幻"] == "奇幻"
-        assert PLATFORM_TO_CANONICAL["古风世情"] == "古言"
-        assert PLATFORM_TO_CANONICAL["豪门总裁"] == "现言"
-        assert PLATFORM_TO_CANONICAL["快穿"] == "快穿"
-        assert PLATFORM_TO_CANONICAL["科幻末世"] == "科幻"
-```
-
-- [ ] **Step 2: 运行测试确认失败**
-
-Run: `cd "D:/wk/novel skill/webnovel-writer/webnovel-writer/scripts" && python -m pytest tests/test_reference_search.py::TestGenreCanonical -v`
-Expected: FAIL — `ImportError: cannot import name 'GENRE_CANONICAL'`
-
-- [ ] **Step 3: 在 reference_search.py 中添加常量**
-
-在 `reference_search.py` 的 `CSV_CONFIG` 之前(约第 84 行)插入:
-
-```python
-# ---------------------------------------------------------------------------
-# Genre canonical list & platform tag mapping
-# ---------------------------------------------------------------------------
-
-GENRE_CANONICAL: set[str] = {
-    "都市", "玄幻", "仙侠", "奇幻", "科幻",
-    "历史", "悬疑", "游戏", "古言", "现言",
-    "幻言", "年代", "种田", "快穿", "衍生",
-}
-
-PLATFORM_TO_CANONICAL: Dict[str, str] = {
-    # 男频
-    "都市日常": "都市", "都市修真": "都市", "都市高武": "都市",
-    "战神赘婿": "都市", "都市种田": "都市", "都市脑洞": "都市",
-    "传统玄幻": "玄幻", "玄幻脑洞": "玄幻",
-    "东方仙侠": "仙侠",
-    "西方奇幻": "奇幻",
-    "科幻末世": "科幻",
-    "历史古代": "历史", "历史脑洞": "历史", "抗战谍战": "历史",
-    "悬疑脑洞": "悬疑", "悬疑灵异": "悬疑",
-    "游戏体育": "游戏",
-    "动漫衍生": "衍生", "男频衍生": "衍生",
-    # 女频
-    "古风世情": "古言", "宫斗宅斗": "古言", "古言脑洞": "古言",
-    "现言脑洞": "现言", "青春甜宠": "现言", "星光璀璨": "现言",
-    "职场婚恋": "现言", "豪门总裁": "现言",
-    "玄幻言情": "幻言",
-    "年代": "年代", "民国言情": "年代",
-    "种田": "种田",
-    "快穿": "快穿",
-    "女频悬疑": "悬疑",
-    "女频衍生": "衍生",
-}
-
-# Legacy values that appeared in old CSV data → canonical mapping.
-# Used by _resolve_genre() during the migration period.
-_LEGACY_GENRE_MAP: Dict[str, str] = {
-    "东方仙侠": "仙侠", "西方奇幻": "奇幻", "科幻末世": "科幻",
-    "都市日常": "都市", "都市修真": "都市", "都市高武": "都市",
-    "历史古代": "历史",
-    "谍战": "历史", "军事": "历史", "武侠": "历史",
-    "刑侦": "悬疑", "惊悚": "悬疑", "推理": "悬疑", "规则怪谈": "悬疑",
-    "末世": "科幻", "赛博朋克": "科幻",
-    "网游": "游戏", "电竞": "游戏", "竞技": "游戏", "体育": "游戏",
-    "轻小说": "衍生", "同人": "衍生",
-    "校园": "现言", "青春": "现言", "娱乐圈": "现言", "职场": "现言",
-    "高武": "都市",
-}
-
-
-def resolve_genre(genre: Optional[str]) -> Optional[str]:
-    """Resolve a user-facing genre string to its canonical form.
-
-    Accepts canonical genres, platform tags, and legacy values.
-    Returns the canonical genre string, or the original input if unresolvable.
-    """
-    if genre is None:
-        return None
-    g = genre.strip()
-    if g in GENRE_CANONICAL or g == "全部":
-        return g
-    if g in PLATFORM_TO_CANONICAL:
-        return PLATFORM_TO_CANONICAL[g]
-    if g in _LEGACY_GENRE_MAP:
-        return _LEGACY_GENRE_MAP[g]
-    return g  # unresolvable — pass through
-```
-
-- [ ] **Step 4: 运行测试确认通过**
-
-Run: `cd "D:/wk/novel skill/webnovel-writer/webnovel-writer/scripts" && python -m pytest tests/test_reference_search.py::TestGenreCanonical -v`
-Expected: 3 tests PASS
-
-- [ ] **Step 5: 提交**
-
-```bash
-cd "D:/wk/novel skill/webnovel-writer"
-git add webnovel-writer/scripts/reference_search.py webnovel-writer/scripts/tests/test_reference_search.py
-git commit -m "feat: add GENRE_CANONICAL constants and resolve_genre() to reference_search"
-```
-
----
-
-### Task 2: 让 _genre_matches 和 search() 使用 resolve_genre
-
-**Files:**
-- Modify: `webnovel-writer/scripts/reference_search.py:74-82, 294-335`
-- Test: `webnovel-writer/scripts/tests/test_reference_search.py`
-
-- [ ] **Step 1: 写失败测试 — platform_tag 可作为 --genre 匹配 canonical 值**
-
-在 `test_reference_search.py` 的 `TestGenreCanonical` 类中追加:
-
-```python
-    def test_resolve_genre_canonical_passthrough(self):
-        from reference_search import resolve_genre
-        assert resolve_genre("都市") == "都市"
-        assert resolve_genre("全部") == "全部"
-        assert resolve_genre(None) is None
-
-    def test_resolve_genre_platform_tag(self):
-        from reference_search import resolve_genre
-        assert resolve_genre("都市日常") == "都市"
-        assert resolve_genre("战神赘婿") == "都市"
-        assert resolve_genre("古风世情") == "古言"
-
-    def test_resolve_genre_legacy(self):
-        from reference_search import resolve_genre
-        assert resolve_genre("武侠") == "历史"
-        assert resolve_genre("刑侦") == "悬疑"
-        assert resolve_genre("网游") == "游戏"
-
-    def test_search_with_platform_tag_genre(self):
-        """--genre 都市日常 should match rows with 适用题材=都市."""
-        out = run_search(
-            "--skill", "write",
-            "--table", "命名规则",
-            "--query", "角色命名",
-            "--genre", "都市日常",
-        )
-        assert out["status"] == "success"
-        # Should find results (都市日常 resolves to 都市, matching rows tagged 都市)
-        assert out["data"]["total"] >= 0  # may be 0 if no 都市 rows, but no error
-```
-
-- [ ] **Step 2: 运行测试确认失败**
-
-Run: `cd "D:/wk/novel skill/webnovel-writer/webnovel-writer/scripts" && python -m pytest tests/test_reference_search.py::TestGenreCanonical::test_search_with_platform_tag_genre -v`
-Expected: 可能 PASS(因为 `都市日常` 字符串包含 `都市` 子串恰好匹配),也可能匹配不精确。关键测试是 `resolve_genre` 相关的三个。
-
-- [ ] **Step 3: 修改 _genre_matches 和 search 函数**
-
-修改 `reference_search.py` 的 `_genre_matches` 函数(约第 74 行):
-
-```python
-def _genre_matches(row: Dict[str, str], genre: Optional[str]) -> bool:
-    """Return True if *genre* is None, or matches ``适用题材`` (``全部`` always matches).
-
-    Both the input *genre* and the cell values are resolved to canonical form
-    before comparison, so platform tags and legacy values work transparently.
-    """
-    if genre is None:
-        return True
-    cell = row.get("适用题材", "")
-    if cell.strip() == "全部":
-        return True
-    resolved_genre = resolve_genre(genre)
-    cell_genres = [resolve_genre(v) for v in _split_multi_value(cell)]
-    return resolved_genre in cell_genres
-```
-
-修改 `search` 函数(约第 294 行),在函数开头加 resolve:
-
-```python
-def search(
-    csv_dir: Path,
-    skill: str,
-    query: str,
-    table: Optional[str] = None,
-    genre: Optional[str] = None,
-    max_results: int = 5,
-) -> Dict[str, Any]:
-    resolved = resolve_genre(genre)
-    # ... rest of function uses resolved instead of genre for filtering,
-    # but keeps original genre in the response envelope for traceability
-```
-
-具体改动:在 `search()` 函数体第一行加 `resolved = resolve_genre(genre)`,然后将 `_genre_matches(row, genre)` 调用改为 `_genre_matches(row, resolved)`,返回结果中保持 `"genre": genre`(原始输入,便于追溯)。
-
-- [ ] **Step 4: 运行全部测试确认通过**
-
-Run: `cd "D:/wk/novel skill/webnovel-writer/webnovel-writer/scripts" && python -m pytest tests/test_reference_search.py -v`
-Expected: ALL PASS(包含旧有测试,确保不回归)
-
-- [ ] **Step 5: 提交**
-
-```bash
-cd "D:/wk/novel skill/webnovel-writer"
-git add webnovel-writer/scripts/reference_search.py webnovel-writer/scripts/tests/test_reference_search.py
-git commit -m "feat: _genre_matches resolves platform tags and legacy values to canonical"
-```
-
----
-
-### Task 3: CSV_CONFIG 增加 prefix 和 required_cols 字段
-
-**Files:**
-- Modify: `webnovel-writer/scripts/reference_search.py:88-154`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_csv_config.py`
-
-- [ ] **Step 1: 写失败测试 — 每个 CSV_CONFIG entry 都有 prefix 和 required_cols**
-
-在 `test_csv_config.py` 末尾追加:
-
-```python
-def test_csv_config_has_prefix_field():
-    for name, config in CSV_CONFIG.items():
-        assert "prefix" in config, f"表 {name} 缺少 prefix 字段"
-        assert isinstance(config["prefix"], str)
-        assert len(config["prefix"]) >= 2
-
-
-def test_csv_config_has_required_cols_field():
-    for name, config in CSV_CONFIG.items():
-        assert "required_cols" in config, f"表 {name} 缺少 required_cols 字段"
-        assert isinstance(config["required_cols"], list)
-        assert len(config["required_cols"]) >= 1
-
-
-def test_csv_config_prefix_matches_actual_data():
-    """Every row's 编号 must start with the declared prefix."""
-    for name, config in CSV_CONFIG.items():
-        csv_path = CSV_DIR / config["file"]
-        if not csv_path.exists():
-            continue
-        prefix = config["prefix"]
-        with open(csv_path, "r", encoding="utf-8-sig", newline="") as f:
-            for row in csv.DictReader(f):
-                row_id = row.get("编号", "")
-                assert row_id.startswith(prefix + "-"), (
-                    f"表 {name} 行 {row_id} 编号不以 {prefix}- 开头"
-                )
-```
-
-- [ ] **Step 2: 运行测试确认失败**
-
-Run: `cd "D:/wk/novel skill/webnovel-writer/webnovel-writer/scripts" && python -m pytest data_modules/tests/test_csv_config.py -v`
-Expected: FAIL — `KeyError: 'prefix'`
-
-- [ ] **Step 3: 给 CSV_CONFIG 每个 entry 补 prefix 和 required_cols**
-
-修改 `reference_search.py` 中的 `CSV_CONFIG`,给每张表追加两个字段:
-
-```python
-CSV_CONFIG: Dict[str, Dict[str, Any]] = {
-    "命名规则": {
-        "file": "命名规则.csv",
-        "search_cols": {"关键词": 3, "意图与同义词": 4, "核心摘要": 2},
-        "output_cols": ["编号", "命名对象", "核心摘要", "大模型指令", "详细展开"],
-        "poison_col": "毒点",
-        "role": "base",
-        "prefix": "NR",
-        "required_cols": ["编号", "适用技能", "分类", "层级", "关键词", "适用题材", "核心摘要"],
-    },
-    "场景写法": {
-        "file": "场景写法.csv",
-        "search_cols": {"关键词": 3, "意图与同义词": 4, "核心摘要": 2},
-        "output_cols": ["编号", "模式名称", "核心摘要", "大模型指令", "详细展开"],
-        "poison_col": "毒点",
-        "role": "base",
-        "prefix": "SP",
-        "required_cols": ["编号", "适用技能", "分类", "层级", "关键词", "适用题材", "核心摘要"],
-    },
-    "写作技法": {
-        "file": "写作技法.csv",
-        "search_cols": {"关键词": 3, "意图与同义词": 4, "核心摘要": 2},
-        "output_cols": ["编号", "技法名称", "核心摘要", "大模型指令", "详细展开"],
-        "poison_col": "毒点",
-        "role": "base",
-        "prefix": "WT",
-        "required_cols": ["编号", "适用技能", "分类", "层级", "关键词", "适用题材", "核心摘要"],
-    },
-    "桥段套路": {
-        "file": "桥段套路.csv",
-        "search_cols": {"关键词": 3, "意图与同义词": 4, "核心摘要": 2},
-        "output_cols": ["编号", "桥段名称", "核心摘要", "大模型指令", "详细展开"],
-        "poison_col": "毒点",
-        "role": "dynamic",
-        "prefix": "TR",
-        "required_cols": ["编号", "适用技能", "分类", "层级", "关键词", "适用题材", "核心摘要"],
-    },
-    "爽点与节奏": {
-        "file": "爽点与节奏.csv",
-        "search_cols": {"关键词": 3, "意图与同义词": 4, "核心摘要": 2},
-        "output_cols": ["编号", "节奏类型", "核心摘要", "大模型指令", "详细展开"],
-        "poison_col": "毒点",
-        "role": "dynamic",
-        "prefix": "PA",
-        "required_cols": ["编号", "适用技能", "分类", "层级", "关键词", "适用题材", "核心摘要"],
-    },
-    "人设与关系": {
-        "file": "人设与关系.csv",
-        "search_cols": {"关键词": 3, "意图与同义词": 4, "核心摘要": 2},
-        "output_cols": ["编号", "人设类型", "核心摘要", "大模型指令", "详细展开"],
-        "poison_col": "毒点",
-        "role": "base",
-        "prefix": "CH",
-        "required_cols": ["编号", "适用技能", "分类", "层级", "关键词", "适用题材", "核心摘要"],
-    },
-    "金手指与设定": {
-        "file": "金手指与设定.csv",
-        "search_cols": {"关键词": 3, "意图与同义词": 4, "核心摘要": 2},
-        "output_cols": ["编号", "设定类型", "核心摘要", "大模型指令", "详细展开"],
-        "poison_col": "毒点",
-        "role": "base",
-        "prefix": "SY",
-        "required_cols": ["编号", "适用技能", "分类", "层级", "关键词", "适用题材", "核心摘要"],
-    },
-    "题材与调性推理": {
-        "file": "题材与调性推理.csv",
-        "search_cols": {"关键词": 3, "意图与同义词": 4, "题材别名": 3},
-        "output_cols": ["编号", "题材/流派", "核心调性", "推荐基础检索表", "推荐动态检索表"],
-        "poison_col": "毒点",
-        "role": "route",
-        "prefix": "GR",
-        "required_cols": ["编号", "适用技能", "题材/流派", "核心调性", "推荐基础检索表", "推荐动态检索表"],
-    },
-    "裁决规则": {
-        "file": "裁决规则.csv",
-        "search_cols": {"题材": 4},
-        "output_cols": ["题材", "风格优先级", "爽点优先级", "节奏默认策略",
-                        "毒点权重", "冲突裁决", "contract注入层", "反模式"],
-        "poison_col": "",
-        "role": "reasoning",
-        "prefix": "RS",
-        "required_cols": ["编号", "题材", "风格优先级", "爽点优先级", "节奏默认策略", "冲突裁决"],
-    },
-}
-```
-
-- [ ] **Step 4: 运行测试确认通过**
-
-Run: `cd "D:/wk/novel skill/webnovel-writer/webnovel-writer/scripts" && python -m pytest data_modules/tests/test_csv_config.py -v`
-Expected: ALL PASS
-
-- [ ] **Step 5: 提交**
-
-```bash
-cd "D:/wk/novel skill/webnovel-writer"
-git add webnovel-writer/scripts/reference_search.py webnovel-writer/scripts/data_modules/tests/test_csv_config.py
-git commit -m "feat: add prefix and required_cols to CSV_CONFIG"
-```
-
----
-
-### Task 4: 创建 validate_csv.py 校验脚本
-
-**Files:**
-- Create: `webnovel-writer/scripts/validate_csv.py`
-- Create: `webnovel-writer/scripts/tests/test_validate_csv.py`
-
-- [ ] **Step 1: 写失败测试 — validate_csv 模块可导入并执行**
-
-创建 `webnovel-writer/scripts/tests/test_validate_csv.py`:
-
-```python
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""Tests for validate_csv.py."""
-import subprocess
-import sys
-from pathlib import Path
-
-import pytest
-
-SCRIPT = str(Path(__file__).resolve().parents[1] / "validate_csv.py")
-CSV_DIR = str(Path(__file__).resolve().parents[2] / "references" / "csv")
-
-
-def run_validate(*args: str) -> subprocess.CompletedProcess:
-    return subprocess.run(
-        [sys.executable, SCRIPT, "--csv-dir", CSV_DIR, *args],
-        capture_output=True,
-        text=True,
-    )
-
-
-class TestValidateCsvRuns:
-    def test_script_runs_without_crash(self):
-        result = run_validate()
-        # May exit 0 (all pass) or 1 (errors found), but must not crash
-        assert result.returncode in (0, 1)
-        assert "Traceback" not in result.stderr
-
-    def test_json_output_mode(self):
-        import json
-        result = run_validate("--format", "json")
-        assert result.returncode in (0, 1)
-        data = json.loads(result.stdout)
-        assert "errors" in data
-        assert "warnings" in data
-```
-
-- [ ] **Step 2: 运行测试确认失败**
-
-Run: `cd "D:/wk/novel skill/webnovel-writer/webnovel-writer/scripts" && python -m pytest tests/test_validate_csv.py -v`
-Expected: FAIL — script not found
-
-- [ ] **Step 3: 实现 validate_csv.py**
-
-创建 `webnovel-writer/scripts/validate_csv.py`:
-
-```python
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-CSV 数据校验工具。
-
-基于 CSV_CONFIG 和 GENRE_CANONICAL 校验 references/csv/ 下所有表的数据质量。
-
-用法:
-    python validate_csv.py
-    python validate_csv.py --csv-dir path/to/csv
-    python validate_csv.py --format json
-"""
-from __future__ import annotations
-
-import argparse
-import csv
-import json
-import re
-import sys
-from pathlib import Path
-from typing import Any, Dict, List, Optional
-
-# Import config from sibling module
-sys.path.insert(0, str(Path(__file__).resolve().parent))
-from reference_search import (
-    CSV_CONFIG,
-    GENRE_CANONICAL,
-    PLATFORM_TO_CANONICAL,
-    _LEGACY_GENRE_MAP,
-)
-
-_MULTI_SPLIT = re.compile(r"[|,,]+")
-_CHINESE_COMMA = re.compile(r",")
-
-
-def _split(cell: str) -> List[str]:
-    if not cell:
-        return []
-    return [p.strip() for p in _MULTI_SPLIT.split(cell) if p.strip()]
-
-
-def _load_csv(path: Path) -> List[Dict[str, str]]:
-    with open(path, "r", encoding="utf-8-sig", newline="") as f:
-        return list(csv.DictReader(f))
-
-
-def _default_csv_dir() -> Path:
-    return Path(__file__).resolve().parent.parent / "references" / "csv"
-
-
-def validate(csv_dir: Path) -> Dict[str, List[str]]:
-    errors: List[str] = []
-    warnings: List[str] = []
-
-    all_ids: Dict[str, str] = {}  # id -> table_name
-
-    valid_genres = GENRE_CANONICAL | {"全部"}
-
-    for table_name, config in CSV_CONFIG.items():
-        csv_path = csv_dir / config["file"]
-        if not csv_path.exists():
-            errors.append(f"[{table_name}] 文件不存在: {config['file']}")
-            continue
-
-        rows = _load_csv(csv_path)
-        headers = set(rows[0].keys()) if rows else set()
-        prefix = config.get("prefix", "")
-        required_cols = config.get("required_cols", [])
-
-        # Check: column headers include all declared columns
-        declared_cols = set()
-        for col in config.get("search_cols", {}):
-            declared_cols.add(col)
-        for col in config.get("output_cols", []):
-            declared_cols.add(col)
-        for col in required_cols:
-            declared_cols.add(col)
-        poison = config.get("poison_col", "")
-        if poison:
-            declared_cols.add(poison)
-        missing_headers = declared_cols - headers
-        if missing_headers:
-            errors.append(f"[{table_name}] CSV 缺少列头: {missing_headers}")
-
-        for i, row in enumerate(rows, start=2):  # row 1 is header
-            row_id = row.get("编号", "").strip()
-
-            # Check: ID uniqueness
-            if row_id:
-                if row_id in all_ids:
-                    errors.append(
-                        f"[{table_name}] 行{i} 编号 {row_id} 重复(首次出现于 {all_ids[row_id]})"
-                    )
-                else:
-                    all_ids[row_id] = table_name
-
-            # Check: prefix consistency
-            if prefix and row_id and not row_id.startswith(prefix + "-"):
-                errors.append(
-                    f"[{table_name}] 行{i} 编号 {row_id} 应以 {prefix}- 开头"
-                )
-
-            # Check: required columns non-empty
-            for col in required_cols:
-                val = row.get(col, "").strip()
-                if not val:
-                    errors.append(f"[{table_name}] 行{i} ({row_id}) 必填列 {col} 为空")
-
-            # Check: delimiter convention (no Chinese comma in multi-value fields)
-            for col in ("适用技能", "关键词", "意图与同义词", "适用题材"):
-                val = row.get(col, "")
-                if _CHINESE_COMMA.search(val):
-                    errors.append(
-                        f"[{table_name}] 行{i} ({row_id}) {col} 含中文逗号,应使用 |"
-                    )
-
-            # Check: 适用题材 values in canonical set
-            genre_cell = row.get("适用题材", "").strip()
-            if genre_cell:
-                for g in _split(genre_cell):
-                    if g not in valid_genres:
-                        warnings.append(
-                            f"[{table_name}] 行{i} ({row_id}) 适用题材值 '{g}' "
-                            f"不在 canonical 枚举中"
-                        )
-
-    # Cross-table check: route ↔ reasoning coverage
-    route_genres: set[str] = set()
-    reasoning_genres: set[str] = set()
-
-    route_path = csv_dir / "题材与调性推理.csv"
-    if route_path.exists():
-        for row in _load_csv(route_path):
-            val = row.get("题材/流派", "").strip()
-            if val:
-                route_genres.add(val)
-
-    reasoning_path = csv_dir / "裁决规则.csv"
-    if reasoning_path.exists():
-        for row in _load_csv(reasoning_path):
-            val = row.get("题材", "").strip()
-            if val:
-                reasoning_genres.add(val)
-
-    # Every canonical genre should have at least one reasoning row
-    for cg in GENRE_CANONICAL:
-        if cg not in reasoning_genres:
-            warnings.append(f"[裁决规则] canonical genre '{cg}' 无对应裁决行")
-
-    return {"errors": errors, "warnings": warnings}
-
-
-def main(argv: Optional[List[str]] = None) -> None:
-    parser = argparse.ArgumentParser(description="Validate reference CSV files")
-    parser.add_argument("--csv-dir", default=None, help="Override CSV directory")
-    parser.add_argument("--format", choices=["text", "json"], default="text")
-    args = parser.parse_args(argv)
-
-    csv_dir = Path(args.csv_dir) if args.csv_dir else _default_csv_dir()
-    result = validate(csv_dir)
-
-    if args.format == "json":
-        print(json.dumps(result, ensure_ascii=False, indent=2))
-    else:
-        for e in result["errors"]:
-            print(f"ERROR: {e}")
-        for w in result["warnings"]:
-            print(f"WARN:  {w}")
-        total_e = len(result["errors"])
-        total_w = len(result["warnings"])
-        print(f"\n--- {total_e} error(s), {total_w} warning(s) ---")
-
-    sys.exit(1 if result["errors"] else 0)
-
-
-if __name__ == "__main__":
-    main()
-```
-
-- [ ] **Step 4: 运行测试确认通过**
-
-Run: `cd "D:/wk/novel skill/webnovel-writer/webnovel-writer/scripts" && python -m pytest tests/test_validate_csv.py -v`
-Expected: 2 tests PASS
-
-- [ ] **Step 5: 运行校验脚本看实际输出,记录当前 warnings 数量**
-
-Run: `cd "D:/wk/novel skill/webnovel-writer/webnovel-writer/scripts" && python validate_csv.py`
-Expected: 0 errors(结构正确),多个 warnings(`适用题材` 非 canonical 值 + 裁决规则缺 canonical 覆盖)
-
-- [ ] **Step 6: 提交**
-
-```bash
-cd "D:/wk/novel skill/webnovel-writer"
-git add webnovel-writer/scripts/validate_csv.py webnovel-writer/scripts/tests/test_validate_csv.py
-git commit -m "feat: add validate_csv.py — schema, ID, prefix, genre, delimiter checks"
-```
-
----
-
-### Task 5: 给 题材与调性推理.csv 增加 canonical_genre 列
-
-**Files:**
-- Modify: `webnovel-writer/references/csv/题材与调性推理.csv`
-- Modify: `webnovel-writer/scripts/reference_search.py` (CSV_CONFIG for 题材与调性推理)
-- Modify: `webnovel-writer/scripts/data_modules/story_system_engine.py:115-159`
-
-- [ ] **Step 1: 写失败测试 — engine route 输出包含 canonical_genre**
-
-在 `webnovel-writer/scripts/data_modules/tests/` 下找到或创建 `test_story_system_engine.py`,追加:
-
-```python
-def test_route_output_includes_canonical_genre(tmp_path):
-    """_route() output must contain canonical_genre in meta."""
-    # Copy CSV dir to tmp
-    import shutil
-    csv_src = Path(__file__).resolve().parent.parent.parent.parent / "references" / "csv"
-    csv_dst = tmp_path / "csv"
-    shutil.copytree(csv_src, csv_dst)
-
-    sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
-    from data_modules.story_system_engine import StorySystemEngine
-
-    engine = StorySystemEngine(csv_dir=csv_dst)
-    route = engine._route("退婚流 三年之约", "玄幻")
-    assert "canonical_genre" in route["meta"]
-    assert route["meta"]["canonical_genre"] in {
-        "都市", "玄幻", "仙侠", "奇幻", "科幻",
-        "历史", "悬疑", "游戏", "古言", "现言",
-        "幻言", "年代", "种田", "快穿", "衍生",
-    }
-```
-
-- [ ] **Step 2: 运行测试确认失败**
-
-Expected: FAIL — `KeyError: 'canonical_genre'`
-
-- [ ] **Step 3: 修改 CSV 和代码**
-
-**3a.** 给 `题材与调性推理.csv` 加一列 `canonical_genre`,放在 `题材/流派` 后面。每行手工填入对应的 canonical 值。例如:
-
-| 编号 | 题材/流派 | canonical_genre | ... |
-|------|----------|----------------|-----|
-| GR-001 | 玄幻退婚流 | 玄幻 | ... |
-| GR-002 | 规则动物园 | 悬疑 | ... |
-
-当前 8 行全部需要填 `canonical_genre`。
-
-**3b.** 修改 `reference_search.py` 的 `CSV_CONFIG["题材与调性推理"]`,在 `output_cols` 中添加 `"canonical_genre"`:
-
-```python
-"题材与调性推理": {
-    "file": "题材与调性推理.csv",
-    "search_cols": {"关键词": 3, "意图与同义词": 4, "题材别名": 3},
-    "output_cols": ["编号", "题材/流派", "canonical_genre", "核心调性", "推荐基础检索表", "推荐动态检索表"],
-    "poison_col": "毒点",
-    "role": "route",
-    "prefix": "GR",
-    "required_cols": ["编号", "适用技能", "题材/流派", "canonical_genre", "核心调性", "推荐基础检索表", "推荐动态检索表"],
-},
-```
-
-**3c.** 修改 `story_system_engine.py` 的 `_route` 方法(约第 141-158 行),在 return dict 的 `meta` 中加入 `canonical_genre`:
-
-```python
-        primary_genre = str(matched.get("题材/流派") or genre or "").strip()
-        canonical = str(matched.get("canonical_genre") or "").strip()
-        if not canonical:
-            # Fallback: resolve via PLATFORM_TO_CANONICAL
-            from reference_search import resolve_genre
-            canonical = resolve_genre(primary_genre) or primary_genre
-        genre_filter = canonical  # Use canonical for downstream filtering
-        return {
-            "meta": {
-                "primary_genre": primary_genre,
-                "canonical_genre": canonical,
-                "route_source": route_source,
-                "genre_filter": genre_filter,
-                ...
-            },
-            ...
-            "genre_filter": genre_filter,
-            ...
-        }
-```
-
-- [ ] **Step 4: 运行测试确认通过**
-
-Run the new test + existing engine tests (if any).
-
-- [ ] **Step 5: 同步修改 _load_reasoning 使用 canonical_genre**
-
-修改 `story_system_engine.py` 的 `_load_reasoning` 方法(约第 261-276 行):engine 的 `build()` 在调用 `_load_reasoning` 时应传入 `canonical_genre` 而非 `primary_genre`,确保裁决规则匹配的是 canonical 值。
-
-找到 `build()` 中调用 `_load_reasoning` 的位置,将参数从 `route["meta"]["primary_genre"]` 改为 `route["meta"]["canonical_genre"]`。
-
-- [ ] **Step 6: 运行全部相关测试**
-
-Run: `cd "D:/wk/novel skill/webnovel-writer/webnovel-writer/scripts" && python -m pytest tests/ data_modules/tests/test_csv_config.py -v`
-Expected: ALL PASS
-
-- [ ] **Step 7: 提交**
-
-```bash
-cd "D:/wk/novel skill/webnovel-writer"
-git add webnovel-writer/references/csv/题材与调性推理.csv webnovel-writer/scripts/reference_search.py webnovel-writer/scripts/data_modules/story_system_engine.py
-git commit -m "feat: add canonical_genre column to route table, thread through engine"
-```
-
----
-
-### Task 6: 迁移现有 CSV 的 适用题材 列到 canonical 枚举
-
-**Files:**
-- Modify: `webnovel-writer/references/csv/命名规则.csv`
-- Modify: `webnovel-writer/references/csv/场景写法.csv`
-- Modify: `webnovel-writer/references/csv/写作技法.csv`
-- Modify: `webnovel-writer/references/csv/桥段套路.csv`
-- Modify: `webnovel-writer/references/csv/人设与关系.csv`
-- Modify: `webnovel-writer/references/csv/爽点与节奏.csv`
-- Modify: `webnovel-writer/references/csv/金手指与设定.csv`
-- Modify: `webnovel-writer/references/csv/裁决规则.csv`
-
-**注意:此任务是手工数据修改。每个 CSV 需要逐行检查 `适用题材` 列,将非 canonical 值替换为 canonical 枚举值。**
-
-- [ ] **Step 1: 运行 validate_csv.py 获取完整 warning 列表**
-
-Run: `cd "D:/wk/novel skill/webnovel-writer/webnovel-writer/scripts" && python validate_csv.py --format json > /tmp/csv_warnings.json`
-
-这将输出所有 `适用题材值不在 canonical 枚举中` 的 warning,作为迁移工作清单。
-
-- [ ] **Step 2: 按迁移映射表逐文件修改**
-
-打开每个 CSV 文件,根据 `genre-canonical.md` 的"现有非枚举值迁移映射"表做替换:
-
-迁移原则:
-- `谍战` → `历史`
-- `刑侦|惊悚|推理|规则怪谈` → `悬疑`
-- `末世|赛博朋克` → `科幻`
-- `军事|武侠` → `历史`
-- `网游|电竞|竞技|体育` → `游戏`
-- `高武` → `都市`
-- `系统文|无限流` → 改为实际背景题材(`玄幻`/`都市`/`悬疑`)
-- `狗血|爽文|深度剧情|现实向|群像|史诗` → 删除该值,改为具体适用题材或 `全部`
-- `动作|心理|战争|灾难|长篇|知乎短篇` → 删除该值,改为具体适用题材或 `全部`
-- `校园|青春|娱乐圈|职场` → `现言`
-- `商战|商业` → `都市` 或 `现言`
-- `同人|轻小说` → `衍生`
-
-每个文件改完后保存(保持 UTF-8 with BOM 编码)。
-
-- [ ] **Step 3: 运行 validate_csv.py 确认 warnings 归零或大幅减少**
-
-Run: `cd "D:/wk/novel skill/webnovel-writer/webnovel-writer/scripts" && python validate_csv.py`
-Expected: 0 errors, `适用题材` 相关 warnings 归零(只剩裁决规则覆盖 warnings)
-
-- [ ] **Step 4: 运行现有搜索测试确认不回归**
-
-Run: `cd "D:/wk/novel skill/webnovel-writer/webnovel-writer/scripts" && python -m pytest tests/test_reference_search.py -v`
-Expected: ALL PASS
-
-- [ ] **Step 5: 提交**
-
-```bash
-cd "D:/wk/novel skill/webnovel-writer"
-git add webnovel-writer/references/csv/*.csv
-git commit -m "refactor: migrate all CSV 适用题材 values to canonical genre enum"
-```
-
----
-
-### Task 7: 更新 CSV README 的题材分类章节
-
-**Files:**
-- Modify: `webnovel-writer/references/csv/README.md:166-170`
-
-- [ ] **Step 1: 替换旧的番茄分类为新的 canonical 枚举引用**
-
-将 README.md 第 166-170 行的旧分类:
-
-```markdown
-## 适用题材(番茄分类)
-
-**男频:** 都市、玄幻、仙侠、奇幻、武侠、历史、军事、科幻、悬疑、游戏、体育、轻小说
-
-**女频:** 现言、古言、幻言、悬疑、轻小说
-```
-
-替换为:
-
-```markdown
-## 适用题材枚举
-
-`适用题材` 列只允许填写以下 15 个 canonical 值或 `全部`:
-
-```
-都市  玄幻  仙侠  奇幻  科幻
-历史  悬疑  游戏  古言  现言
-幻言  年代  种田  快穿  衍生
-```
-
-完整的两层题材体系(canonical + 番茄 platform_tag 映射)见 `genre-canonical.md`。
-
-**禁止**在 `适用题材` 列中填写:
-- 番茄子分类名(如"都市日常""战神赘婿")——这些是 platform_tag,只用于路由表
-- 套路名(如"退婚流""系统流")——这些住在 `桥段套路.csv`
-- 调性/场景/形式标签(如"爽文""动作""短篇")——不属于题材体系
-```
-
-- [ ] **Step 2: 提交**
-
-```bash
-cd "D:/wk/novel skill/webnovel-writer"
-git add webnovel-writer/references/csv/README.md
-git commit -m "docs: update CSV README genre section to reference canonical enum"
-```
-
----
-
-### Task 8: 创建 references/README.md 顶层索引
-
-**Files:**
-- Create: `webnovel-writer/references/README.md`
-
-- [ ] **Step 1: 创建文件**
-
-```markdown
-# References
-
-本目录存放 webnovel-writer 的所有参考资料,供 skills 和 scripts 在运行时读取。
-
-## 目录结构
-
-| 子目录/文件 | 职责 | 消费方式 |
-|-------------|------|----------|
-| `csv/` | 结构化知识条目(9 张表) | `reference_search.py` BM25 检索 |
-| `csv/README.md` | CSV schema 规范与录入规则 | 人工参考 |
-| `csv/genre-canonical.md` | 题材权威枚举(canonical + platform_tag 映射) | 人工参考 + 代码常量对照 |
-| `genre-profiles.md` | 题材 profile(fallback,高频题材已迁入 Story Contracts) | ContextManager 直接 Read |
-| `reading-power-taxonomy.md` | 追读力分类学 | Skills 直接 Read |
-| `review-schema.md` | 审查输出格式定义 | webnovel-review Read |
-| `index/` | 元数据索引(loading-map、gap-register) | 人工参考 |
-| `outlining/` | 大纲相关参考 | webnovel-plan Read |
-| `review/` | 审查相关参考 | webnovel-review Read |
-| `shared/` | 跨 skill 共享参考 | 多 skill Read |
-
-## md vs CSV 边界
-
-- **md**:流程规范、方法论、审查 schema、硬约束、润色指导
-- **CSV**:可条目化的写作知识、命名规则、场景技法、桥段模板
-
-md 是写给大模型当行为闸门的,CSV 是写给搜索引擎当知识库的。
-
-## 消费链路
-
-init → plan → write → review 的完整 reference 消费路径见 `index/reference-loading-map.md`。
-
-## 校验
-
-```bash
-cd webnovel-writer/scripts
-python validate_csv.py          # 文本输出
-python validate_csv.py --format json  # JSON 输出
-```
-```
-
-- [ ] **Step 2: 提交**
-
-```bash
-cd "D:/wk/novel skill/webnovel-writer"
-git add webnovel-writer/references/README.md
-git commit -m "docs: add top-level references/README.md directory index"
-```
-
----
-
-### Task 9: 端到端冒烟测试
-
-**Files:**
-- Test: `webnovel-writer/scripts/tests/test_reference_search.py`
-
-- [ ] **Step 1: 添加端到端冒烟测试**
-
-在 `test_reference_search.py` 末尾添加:
-
-```python
-class TestEndToEndSmoke:
-    """Smoke tests: full pipeline from search to result, across genres."""
-
-    def test_xuanhuan_genre_returns_results(self):
-        out = run_search("--skill", "write", "--query", "升级打脸", "--genre", "玄幻")
-        assert out["status"] == "success"
-        assert out["data"]["total"] >= 1
-
-    def test_guyan_genre_returns_results(self):
-        out = run_search("--skill", "write", "--query", "宫斗 嫡庶", "--genre", "古言")
-        assert out["status"] == "success"
-        # May be 0 if no 古言 rows exist yet, but must not error
-        assert isinstance(out["data"]["results"], list)
-
-    def test_platform_tag_as_genre(self):
-        """Using a platform_tag like 都市日常 should work as --genre."""
-        out = run_search("--skill", "write", "--query", "日常搞笑", "--genre", "都市日常")
-        assert out["status"] == "success"
-        assert isinstance(out["data"]["results"], list)
-
-    def test_validate_csv_zero_errors(self):
-        """validate_csv.py must report 0 errors on current data."""
-        import subprocess
-        validate_script = str(Path(__file__).resolve().parents[1] / "validate_csv.py")
-        result = subprocess.run(
-            [sys.executable, validate_script, "--csv-dir", CSV_DIR, "--format", "json"],
-            capture_output=True, text=True,
-        )
-        import json
-        data = json.loads(result.stdout)
-        assert len(data["errors"]) == 0, f"CSV validation errors: {data['errors']}"
-```
-
-- [ ] **Step 2: 运行全部测试**
-
-Run: `cd "D:/wk/novel skill/webnovel-writer/webnovel-writer/scripts" && python -m pytest tests/test_reference_search.py tests/test_validate_csv.py data_modules/tests/test_csv_config.py -v`
-Expected: ALL PASS
-
-- [ ] **Step 3: 提交**
-
-```bash
-cd "D:/wk/novel skill/webnovel-writer"
-git add webnovel-writer/scripts/tests/test_reference_search.py
-git commit -m "test: add end-to-end smoke tests for genre canonical pipeline"
-```
-
----
-
-## Task Summary
-
-| Task | 内容 | 依赖 |
-|------|------|------|
-| 1 | GENRE_CANONICAL 常量 + PLATFORM_TO_CANONICAL 映射 + resolve_genre() | 无 |
-| 2 | _genre_matches 和 search() 接入 resolve_genre | Task 1 |
-| 3 | CSV_CONFIG 加 prefix / required_cols | 无(可与 1 并行) |
-| 4 | validate_csv.py 校验脚本 | Task 1 + 3 |
-| 5 | 题材与调性推理.csv 加 canonical_genre 列,engine 接入 | Task 1 |
-| 6 | 全部 CSV 适用题材列迁移到 canonical | Task 2 |
-| 7 | CSV README 更新题材章节 | Task 6 |
-| 8 | references/README.md 顶层索引 | 无(可与任何 task 并行) |
-| 9 | 端到端冒烟测试 | Task 2 + 4 + 6 |

+ 0 - 1253
docs/archive/superpowers/plans/2026-06-01-review-critical-fixes.md

@@ -1,1253 +0,0 @@
-# Review Critical Fixes Implementation Plan
-
-> **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:** 修复四份 review 报告交叉验证后的关键问题:CLI JSON 文件输入边界、rejected commit 投影、async 测试配置、KnowledgeQuery schema 漂移、StateManager 状态写入分叉、vector summary 覆盖、review 流程文档、安全边界。
-
-**Architecture:** 先恢复写作主链和测试套件可信度,再扩展 RAG 投影覆盖,最后收紧本地 dashboard/backup 的安全默认值。每个任务都包含回归测试,避免只改实现不固定行为。
-
-**Tech Stack:** Python 3.10+, pytest/pytest-asyncio, SQLite, FastAPI, Markdown skills
-
----
-
-## File Structure
-
-### Core Commit/Projection
-
-- `webnovel-writer/scripts/chapter_commit.py`: CLI entry point; rejected commit must also run state projection.
-- `webnovel-writer/scripts/data_modules/chapter_commit_service.py`: projection orchestration; non-accepted commits should still allow the state writer rejected branch.
-- `webnovel-writer/scripts/data_modules/event_projection_router.py`: writer selection; rejected commits require `state`.
-- `webnovel-writer/scripts/data_modules/tests/test_chapter_commit_service.py`: service-level projection behavior tests.
-- `webnovel-writer/scripts/data_modules/tests/test_projection_writers.py`: end-to-end projection regression tests.
-
-### Test Infrastructure
-
-- `pytest.ini`: remove async plugin bans; keep coverage settings.
-- `webnovel-writer/scripts/data_modules/tests/test_rag_adapter.py`: existing async tests should run normally after config fix.
-
-### CLI JSON Input Boundary
-
-- `webnovel-writer/scripts/data_modules/cli_args.py`: add optional base-dir containment for `@file` JSON arguments while keeping stdin/direct JSON behavior unchanged.
-- `webnovel-writer/scripts/data_modules/index_manager.py`: pass project-root containment to `load_json_arg()` for write/input JSON commands.
-- `webnovel-writer/scripts/data_modules/state_manager.py`: pass project-root containment to `load_json_arg()` for `process-chapter`.
-- `webnovel-writer/scripts/data_modules/sql_state_manager.py`: pass project-root containment to `load_json_arg()` for chapter entity processing.
-- `webnovel-writer/scripts/data_modules/memory/store.py`: pass project-root containment to `load_json_arg()` for memory upserts/imports.
-- `webnovel-writer/scripts/data_modules/rag_adapter.py`: pass project-root containment to `load_json_arg()` for scene indexing.
-- `webnovel-writer/scripts/data_modules/style_sampler.py`: pass project-root containment to `load_json_arg()` for scene extraction.
-- `webnovel-writer/scripts/data_modules/tests/test_coverage_boost.py`: extend existing `cli_args` tests.
-
-### Knowledge Query
-
-- `webnovel-writer/scripts/data_modules/knowledge_query.py`: query production `relationship_events.type` while returning stable JSON for callers.
-- `webnovel-writer/scripts/data_modules/tests/test_knowledge_query.py`: use production schema, not mock-only `relationship_type`.
-
-### State Manager
-
-- `webnovel-writer/scripts/data_modules/state_manager.py`: route `set_chapter_status()` through locked merge semantics.
-- `webnovel-writer/scripts/data_modules/tests/test_chapter_status.py`: preserve status monotonicity.
-- `webnovel-writer/scripts/data_modules/tests/test_state_manager_extra.py`: add merge regression for existing disk state.
-
-### Vector Projection/RAG
-
-- `webnovel-writer/scripts/data_modules/vector_projection_writer.py`: add summary and scene chunks to commit projection, and avoid `asyncio.run()` failure when projection is invoked from an active event loop.
-- `webnovel-writer/scripts/data_modules/tests/test_vector_projection_writer.py`: cover summary/scene chunk generation, stable IDs, and active-event-loop storage bridge.
-
-### Review Skill Flow
-
-- `webnovel-writer/skills/webnovel-review/SKILL.md`: switch to `review-pipeline --save-metrics`, remove obsolete second save command.
-- `webnovel-writer/skills/webnovel-write/SKILL.md`: verify already uses `--save-metrics`; no change unless wording drifts.
-
-### Local Security Defaults
-
-- `webnovel-writer/dashboard/app.py`: restrict CORS to localhost origins, add file size limit for read API.
-- `webnovel-writer/scripts/backup_manager.py`: ensure generated `.gitignore` excludes `.env`.
-- `webnovel-writer/scripts/data_modules/style_sampler.py`: tolerate corrupt JSON in `tags`.
-- New or existing tests under `webnovel-writer/scripts/tests/` or `webnovel-writer/scripts/data_modules/tests/` for these fixes.
-
----
-
-## Task 0: Bound `@file` JSON Arguments to Project Roots
-
-**Problem:** `load_json_arg("@path")` currently reads any local path. This is a local CLI feature rather than a remote vulnerability, but in Agent-driven pipelines the project root is the expected trust boundary.
-
-**Design:** Keep backward compatibility for direct callers by adding an optional `base_dir` argument. If `base_dir` is passed, `@file` must resolve inside it. `@-` stdin and direct JSON strings are unchanged.
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/cli_args.py`
-- Modify: `webnovel-writer/scripts/data_modules/index_manager.py`
-- Modify: `webnovel-writer/scripts/data_modules/state_manager.py`
-- Modify: `webnovel-writer/scripts/data_modules/sql_state_manager.py`
-- Modify: `webnovel-writer/scripts/data_modules/memory/store.py`
-- Modify: `webnovel-writer/scripts/data_modules/rag_adapter.py`
-- Modify: `webnovel-writer/scripts/data_modules/style_sampler.py`
-- Test: `webnovel-writer/scripts/data_modules/tests/test_coverage_boost.py`
-
-- [ ] **Step 1: Add failing containment tests**
-
-Append these tests to the `cli_args` section of `webnovel-writer/scripts/data_modules/tests/test_coverage_boost.py`:
-
-```python
-def test_load_json_arg_rejects_file_outside_base_dir(tmp_path):
-    project = tmp_path / "project"
-    project.mkdir()
-    outside = tmp_path / "secret.json"
-    outside.write_text('{"secret": true}', encoding="utf-8")
-
-    with pytest.raises(ValueError, match="outside allowed directory"):
-        load_json_arg(f"@{outside}", base_dir=project)
-
-
-def test_load_json_arg_allows_file_inside_base_dir(tmp_path):
-    project = tmp_path / "project"
-    project.mkdir()
-    payload = project / "payload.json"
-    payload.write_text('{"ok": true}', encoding="utf-8")
-
-    assert load_json_arg(f"@{payload}", base_dir=project) == {"ok": True}
-
-
-def test_load_json_arg_stdin_ignores_base_dir(monkeypatch, tmp_path):
-    monkeypatch.setattr(sys, "stdin", StringIO('{"stdin": true}'))
-
-    assert load_json_arg("@-", base_dir=tmp_path) == {"stdin": True}
-```
-
-- [ ] **Step 2: Run failing cli_args tests**
-
-Run:
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_coverage_boost.py::test_load_json_arg_rejects_file_outside_base_dir webnovel-writer/scripts/data_modules/tests/test_coverage_boost.py::test_load_json_arg_allows_file_inside_base_dir webnovel-writer/scripts/data_modules/tests/test_coverage_boost.py::test_load_json_arg_stdin_ignores_base_dir -q --no-cov
-```
-
-Expected before implementation: fail with `TypeError: load_json_arg() got an unexpected keyword argument 'base_dir'`.
-
-- [ ] **Step 3: Implement optional containment helper**
-
-Update `webnovel-writer/scripts/data_modules/cli_args.py`:
-
-```python
-def _resolve_json_arg_file(target: str, *, base_dir: str | Path | None = None) -> Path:
-    path = Path(target).expanduser()
-    if not path.is_absolute() and base_dir is not None:
-        path = Path(base_dir) / path
-    resolved = path.resolve()
-    if base_dir is not None:
-        base = Path(base_dir).expanduser().resolve()
-        try:
-            resolved.relative_to(base)
-        except ValueError as exc:
-            raise ValueError(f"json arg file outside allowed directory: {resolved}") from exc
-    return resolved
-```
-
-Change the signature and file read branch:
-
-```python
-def load_json_arg(raw: str, *, base_dir: str | Path | None = None) -> Any:
-    """
-    解析 CLI 传入的 JSON 参数,支持两种形式:
-    - 直接 JSON 字符串:'{"a":1}'
-    - @ 文件路径:'@data.json'(从文件读取 JSON,避免 shell 引号地狱)
-      - 特例:'@-' 表示从 stdin 读取
-      - 当传入 base_dir 时,@ 文件必须位于 base_dir 内
-    """
-    if raw is None:
-        raise ValueError("missing json arg")
-    text = str(raw).strip()
-    if text.startswith("@"):
-        target = text[1:].strip()
-        if not target:
-            raise ValueError("invalid json arg: '@' without path")
-        if target == "-":
-            content = sys.stdin.read()
-        else:
-            content = _resolve_json_arg_file(target, base_dir=base_dir).read_text(encoding="utf-8")
-        return json.loads(content)
-    return json.loads(text)
-```
-
-- [ ] **Step 4: Pass project root from CLI call sites**
-
-For each call site that has `args.project_root`, pass it as `base_dir=args.project_root`.
-
-In `webnovel-writer/scripts/data_modules/index_manager.py`, update all `load_json_arg(...)` calls in command handlers, for example:
-
-```python
-        entities = load_json_arg(args.entities, base_dir=args.project_root)
-        scenes = load_json_arg(args.scenes, base_dir=args.project_root)
-```
-
-and:
-
-```python
-        data = load_json_arg(args.data, base_dir=args.project_root)
-```
-
-In `webnovel-writer/scripts/data_modules/state_manager.py`:
-
-```python
-        data = load_json_arg(args.data, base_dir=args.project_root)
-```
-
-In `webnovel-writer/scripts/data_modules/sql_state_manager.py`:
-
-```python
-        data = load_json_arg(args.data, base_dir=args.project_root)
-```
-
-In `webnovel-writer/scripts/data_modules/memory/store.py`:
-
-```python
-        payload = load_json_arg(args.data, base_dir=args.project_root)
-```
-
-In `webnovel-writer/scripts/data_modules/rag_adapter.py`:
-
-```python
-        scenes = load_json_arg(args.scenes, base_dir=args.project_root)
-```
-
-In `webnovel-writer/scripts/data_modules/style_sampler.py`:
-
-```python
-        scenes = load_json_arg(args.scenes, base_dir=args.project_root)
-```
-
-If a specific module stores the resolved project root in `config.project_root` rather than `args.project_root`, use `base_dir=config.project_root`.
-
-- [ ] **Step 5: Run cli and unified CLI tests**
-
-Run:
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_coverage_boost.py webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py -q --no-cov
-```
-
-Expected: all tests pass.
-
-- [ ] **Step 6: Commit**
-
-```bash
-git add webnovel-writer/scripts/data_modules/cli_args.py webnovel-writer/scripts/data_modules/index_manager.py webnovel-writer/scripts/data_modules/state_manager.py webnovel-writer/scripts/data_modules/sql_state_manager.py webnovel-writer/scripts/data_modules/memory/store.py webnovel-writer/scripts/data_modules/rag_adapter.py webnovel-writer/scripts/data_modules/style_sampler.py webnovel-writer/scripts/data_modules/tests/test_coverage_boost.py
-git commit -m "fix: bound json file arguments to project root"
-```
-
----
-
-## Task 1: Restore Rejected Commit State Projection
-
-**Problem:** `StateProjectionWriter` supports `rejected -> chapter_rejected`, but both CLI and service skip projections for non-accepted commits.
-
-**Files:**
-- Modify: `webnovel-writer/scripts/chapter_commit.py`
-- Modify: `webnovel-writer/scripts/data_modules/chapter_commit_service.py`
-- Modify: `webnovel-writer/scripts/data_modules/event_projection_router.py`
-- Test: `webnovel-writer/scripts/data_modules/tests/test_chapter_commit_service.py`
-- Test: `webnovel-writer/scripts/data_modules/tests/test_projection_writers.py`
-
-- [ ] **Step 1: Add failing service test for rejected projection**
-
-Append this test to `webnovel-writer/scripts/data_modules/tests/test_chapter_commit_service.py`:
-
-```python
-import json
-
-
-def test_apply_projections_updates_state_for_rejected_commit(tmp_path):
-    (tmp_path / ".webnovel").mkdir(parents=True, exist_ok=True)
-    (tmp_path / ".webnovel" / "state.json").write_text("{}", encoding="utf-8")
-
-    service = ChapterCommitService(tmp_path)
-    payload = service.build_commit(
-        chapter=7,
-        review_result={"blocking_count": 1},
-        fulfillment_result={
-            "planned_nodes": ["进入坊市"],
-            "covered_nodes": ["进入坊市"],
-            "missed_nodes": [],
-            "extra_nodes": [],
-        },
-        disambiguation_result={"pending": []},
-        extraction_result={"state_deltas": [], "entity_deltas": [], "accepted_events": []},
-    )
-
-    projected = service.apply_projections(payload)
-
-    state = json.loads((tmp_path / ".webnovel" / "state.json").read_text(encoding="utf-8"))
-    assert projected["projection_status"]["state"] == "done"
-    assert state["progress"]["chapter_status"]["7"] == "chapter_rejected"
-```
-
-- [ ] **Step 2: Run the failing test**
-
-Run:
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_chapter_commit_service.py::test_apply_projections_updates_state_for_rejected_commit -q --no-cov
-```
-
-Expected before implementation: fail because `projection_status["state"]` remains `pending` or `state.json` has no `chapter_rejected`.
-
-- [ ] **Step 3: Route rejected commits to state writer**
-
-Update `webnovel-writer/scripts/data_modules/event_projection_router.py`:
-
-```python
-    def required_writers(self, commit_payload: Dict) -> List[str]:
-        writers: Set[str] = set()
-        status = str((commit_payload.get("meta") or {}).get("status") or "")
-        if status == "rejected":
-            writers.add("state")
-            return sorted(writers)
-        if status == "accepted":
-            writers.add("state")
-            writers.add("index")
-        if commit_payload.get("entity_deltas"):
-            writers.add("index")
-        if str(commit_payload.get("summary_text") or "").strip():
-            writers.add("summary")
-        for event in commit_payload.get("accepted_events") or []:
-            if not isinstance(event, dict):
-                continue
-            writers.update(self.route(event))
-        return sorted(writers)
-```
-
-- [ ] **Step 4: Allow service projection for rejected state only**
-
-Update the start of `ChapterCommitService.apply_projections()` in `webnovel-writer/scripts/data_modules/chapter_commit_service.py`:
-
-```python
-    def apply_projections(self, payload: Dict[str, Any]) -> Dict[str, Any]:
-        status = str((payload.get("meta") or {}).get("status") or "")
-        if status not in {"accepted", "rejected"}:
-            return payload
-
-        if status == "accepted":
-            chapter = int((payload.get("meta") or {}).get("chapter") or 0)
-            event_store = EventLogStore(self.project_root)
-            payload["accepted_events"] = event_store.normalize_events(
-                chapter, payload.get("accepted_events", [])
-            )
-            event_store.write_events(chapter, payload["accepted_events"])
-
-            proposals = AmendProposalTrigger().check(chapter, payload.get("accepted_events", []))
-            if proposals:
-                manager = IndexManager(DataModulesConfig.from_project_root(self.project_root))
-                with manager._get_conn() as conn:
-                    ensure_override_ledger_columns(conn)
-                    persist_amend_proposals(conn, chapter, proposals)
-                    conn.commit()
-```
-
-Keep the writer import block and writer loop after this block. This preserves event log writes and override proposals for accepted commits only, while letting rejected commits reach `StateProjectionWriter`.
-
-- [ ] **Step 5: Make CLI always call `apply_projections()`**
-
-Update `webnovel-writer/scripts/chapter_commit.py`:
-
-```python
-    service.persist_commit(payload)
-    payload = service.apply_projections(payload)
-    print(json.dumps(payload, ensure_ascii=False))
-```
-
-- [ ] **Step 6: Run targeted projection tests**
-
-Run:
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_chapter_commit_service.py webnovel-writer/scripts/data_modules/tests/test_projection_writers.py -q --no-cov
-```
-
-Expected: all tests pass.
-
-- [ ] **Step 7: Commit**
-
-```bash
-git add webnovel-writer/scripts/chapter_commit.py webnovel-writer/scripts/data_modules/chapter_commit_service.py webnovel-writer/scripts/data_modules/event_projection_router.py webnovel-writer/scripts/data_modules/tests/test_chapter_commit_service.py
-git commit -m "fix: project rejected chapter commits to state"
-```
-
----
-
-## Task 2: Restore Async Pytest Execution
-
-**Problem:** `pytest.ini` disables `asyncio` and `anyio`, so `@pytest.mark.asyncio` tests fail or are not exercised correctly.
-
-**Files:**
-- Modify: `pytest.ini`
-- Verify: existing async tests under `webnovel-writer/scripts/data_modules/tests/test_rag_adapter.py`
-
-- [ ] **Step 1: Confirm current async failure**
-
-Run:
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_rag_adapter.py::test_store_and_search -q --no-cov
-```
-
-Expected before implementation: fail or warn due to disabled async plugin.
-
-- [ ] **Step 2: Update pytest config**
-
-Change `pytest.ini` to:
-
-```ini
-[pytest]
-testpaths = webnovel-writer/scripts/data_modules/tests webnovel-writer/scripts/tests
-pythonpath = webnovel-writer/scripts
-asyncio_mode = auto
-addopts = -p no:debugging -p pytest_cov -q --cov --cov-report=term-missing --cov-fail-under=90 -p no:cacheprovider
-```
-
-Do not disable `pytest_asyncio` or `anyio`.
-
-- [ ] **Step 3: Run async-focused tests**
-
-Run:
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_rag_adapter.py -q --no-cov
-```
-
-Expected: async tests execute and pass.
-
-- [ ] **Step 4: Run full suite once**
-
-Run:
-
-```bash
-python -m pytest -q
-```
-
-Expected: tests run with coverage enforcement. If failures remain, record exact failing tests before touching unrelated code.
-
-- [ ] **Step 5: Commit**
-
-```bash
-git add pytest.ini
-git commit -m "test: enable pytest async plugins"
-```
-
----
-
-## Task 3: Fix KnowledgeQuery Relationship Schema Drift
-
-**Problem:** Production table uses `relationship_events.type`; `KnowledgeQuery` and its tests use `relationship_type`.
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/knowledge_query.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_knowledge_query.py`
-
-- [ ] **Step 1: Rewrite test fixture to production schema**
-
-In `test_knowledge_query.py`, replace the `relationship_events` table definition with:
-
-```python
-    conn.execute("""
-        CREATE TABLE IF NOT EXISTS relationship_events (
-            id INTEGER PRIMARY KEY AUTOINCREMENT,
-            from_entity TEXT,
-            to_entity TEXT,
-            type TEXT NOT NULL,
-            action TEXT DEFAULT '',
-            polarity TEXT DEFAULT '',
-            strength REAL DEFAULT 0.0,
-            description TEXT,
-            chapter INTEGER,
-            scene_index INTEGER DEFAULT 0,
-            evidence TEXT DEFAULT '',
-            confidence REAL DEFAULT 1.0,
-            created_at TEXT
-        )
-    """)
-```
-
-Replace inserts with:
-
-```python
-    conn.execute(
-        "INSERT INTO relationship_events (from_entity, to_entity, type, chapter) VALUES (?, ?, ?, ?)",
-        ("hanli", "陈巧倩", "同门", 20),
-    )
-    conn.execute(
-        "INSERT INTO relationship_events (from_entity, to_entity, type, chapter) VALUES (?, ?, ?, ?)",
-        ("hanli", "陈巧倩", "合作", 45),
-    )
-```
-
-Keep assertions against output key `relationship_type` for backward-compatible CLI JSON.
-
-- [ ] **Step 2: Run the failing KnowledgeQuery tests**
-
-Run:
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_knowledge_query.py -q --no-cov
-```
-
-Expected before implementation: fail with `no such column: relationship_type`.
-
-- [ ] **Step 3: Query production column with output compatibility**
-
-Update `KnowledgeQuery.entity_relationships_at_chapter()` in `knowledge_query.py`:
-
-```python
-            rows = conn.execute(
-                """
-                SELECT from_entity, to_entity, type AS relationship_type, description, chapter
-                FROM relationship_events
-                WHERE (from_entity = ? OR to_entity = ?) AND chapter <= ?
-                ORDER BY chapter ASC, id ASC
-                """,
-                (entity_id, entity_id, chapter),
-            ).fetchall()
-```
-
-Leave returned JSON as:
-
-```python
-                    "relationship_type": str(row["relationship_type"] or "").strip(),
-```
-
-- [ ] **Step 4: Run KnowledgeQuery and CLI-adjacent tests**
-
-Run:
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_knowledge_query.py webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py -q --no-cov
-```
-
-Expected: pass.
-
-- [ ] **Step 5: Commit**
-
-```bash
-git add webnovel-writer/scripts/data_modules/knowledge_query.py webnovel-writer/scripts/data_modules/tests/test_knowledge_query.py
-git commit -m "fix: query relationship events using production schema"
-```
-
----
-
-## Task 4: Route Chapter Status Writes Through Locked Merge Semantics
-
-**Problem:** `set_chapter_status()` mutates in-memory state and calls `_save_state()`, bypassing `save_state()` locking and disk merge.
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/state_manager.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_state_manager_extra.py`
-- Verify: `webnovel-writer/scripts/data_modules/tests/test_chapter_status.py`
-
-- [ ] **Step 1: Add failing locked-save regression tests**
-
-Append to `test_state_manager_extra.py`:
-
-```python
-def test_set_chapter_status_uses_locked_save_state(temp_project, monkeypatch):
-    manager = StateManager(temp_project, enable_sqlite_sync=False)
-    called = {}
-
-    def fake_save_state():
-        called["save_state"] = True
-
-    def fail_direct_save():
-        raise AssertionError("set_chapter_status must use save_state()")
-
-    monkeypatch.setattr(manager, "save_state", fake_save_state)
-    monkeypatch.setattr(manager, "_save_state", fail_direct_save)
-
-    manager.set_chapter_status(5, "chapter_drafted")
-
-    assert called["save_state"] is True
-    assert manager._pending_chapter_status == {"5": "chapter_drafted"}
-
-
-def test_set_chapter_status_preserves_existing_disk_state(temp_project):
-    temp_project.state_file.write_text(
-        json.dumps(
-            {
-                "progress": {"current_chapter": 4, "chapter_status": {"4": "chapter_committed"}},
-                "disambiguation_warnings": [{"chapter": 4, "mention": "宗主"}],
-            },
-            ensure_ascii=False,
-        ),
-        encoding="utf-8",
-    )
-
-    manager = StateManager(temp_project, enable_sqlite_sync=False)
-    manager.set_chapter_status(5, "chapter_drafted")
-
-    saved = json.loads(temp_project.state_file.read_text(encoding="utf-8"))
-    assert saved["progress"]["chapter_status"]["4"] == "chapter_committed"
-    assert saved["progress"]["chapter_status"]["5"] == "chapter_drafted"
-    assert saved["disambiguation_warnings"] == [{"chapter": 4, "mention": "宗主"}]
-```
-
-- [ ] **Step 2: Run status tests before implementation**
-
-Run:
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_chapter_status.py webnovel-writer/scripts/data_modules/tests/test_state_manager_extra.py::test_set_chapter_status_uses_locked_save_state webnovel-writer/scripts/data_modules/tests/test_state_manager_extra.py::test_set_chapter_status_preserves_existing_disk_state -q --no-cov
-```
-
-Expected before implementation: `test_set_chapter_status_uses_locked_save_state` fails because current code calls `_save_state()` directly and has no `_pending_chapter_status`.
-
-- [ ] **Step 3: Add pending chapter status field**
-
-In `StateManager.__init__`, after `_pending_progress_chapter`, add:
-
-```python
-        self._pending_chapter_status: Dict[str, str] = {}
-```
-
-Update `has_pending` in `save_state()` to include:
-
-```python
-                self._pending_chapter_status,
-```
-
-- [ ] **Step 4: Merge pending chapter statuses inside `save_state()`**
-
-Inside the locked `with lock:` block, immediately after `progress` is normalized for progress updates or before disambiguation merge, add:
-
-```python
-                if self._pending_chapter_status:
-                    progress = disk_state.get("progress", {})
-                    if not isinstance(progress, dict):
-                        progress = {}
-                        disk_state["progress"] = progress
-                    chapter_status = progress.get("chapter_status")
-                    if not isinstance(chapter_status, dict):
-                        chapter_status = {}
-                        progress["chapter_status"] = chapter_status
-                    chapter_status.update(self._pending_chapter_status)
-                    progress["last_updated"] = self._now_progress_timestamp()
-```
-
-- [ ] **Step 5: Clear pending chapter statuses only after successful write**
-
-Where `save_state()` clears other pending structures after `atomic_write_json`, add:
-
-```python
-                self._pending_chapter_status.clear()
-```
-
-Place it beside the existing pending clears, not before SQLite sync error handling.
-
-- [ ] **Step 6: Update `set_chapter_status()`**
-
-Replace the final mutation/write block with:
-
-```python
-        progress = self._state.setdefault("progress", {})
-        chapter_status = progress.setdefault("chapter_status", {})
-        chapter_status[str(chapter)] = status
-        self._pending_chapter_status[str(chapter)] = status
-        self.save_state()
-```
-
-Keep monotonicity checks unchanged.
-
-- [ ] **Step 7: Run StateManager status tests**
-
-Run:
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_chapter_status.py webnovel-writer/scripts/data_modules/tests/test_state_manager_extra.py -q --no-cov
-```
-
-Expected: pass.
-
-- [ ] **Step 8: Commit**
-
-```bash
-git add webnovel-writer/scripts/data_modules/state_manager.py webnovel-writer/scripts/data_modules/tests/test_state_manager_extra.py
-git commit -m "fix: merge chapter status updates under state lock"
-```
-
----
-
-## Task 5: Add Summary/Scene Chunks and Safe Async Bridge to Vector Projection
-
-**Problem:** Commit projection writes event/entity chunks only. RAG already supports `summary` and `scene`, but commit projection does not feed them. The writer also calls `asyncio.run()` directly, which fails if this synchronous projection path is ever invoked inside an active event loop.
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/vector_projection_writer.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_vector_projection_writer.py`
-
-- [ ] **Step 1: Add failing test for summary and scene chunks**
-
-Append to `test_vector_projection_writer.py`:
-
-```python
-def test_collect_chunks_includes_summary_and_scenes():
-    writer = VectorProjectionWriter.__new__(VectorProjectionWriter)
-    payload = {
-        "meta": {"chapter": 47, "status": "accepted"},
-        "summary_text": "韩立在坊市发现丹方线索。",
-        "scenes": [
-            {"index": 1, "summary": "韩立入坊市观察摊位", "location": "坊市"},
-            {"scene_index": 2, "content": "陈巧倩暗中提醒韩立有人跟踪。"},
-        ],
-        "accepted_events": [],
-        "entity_deltas": [],
-    }
-
-    chunks = writer._collect_chunks(payload)
-
-    by_type = {chunk["chunk_type"]: chunk for chunk in chunks}
-    assert by_type["summary"]["chunk_id"] == "ch0047_summary"
-    assert by_type["summary"]["parent_chunk_id"] is None
-    assert by_type["scene"]["parent_chunk_id"] == "ch0047_summary"
-    assert any(chunk["scene_index"] == 2 for chunk in chunks if chunk["chunk_type"] == "scene")
-```
-
-- [ ] **Step 2: Run failing vector test**
-
-Run:
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_vector_projection_writer.py::test_collect_chunks_includes_summary_and_scenes -q --no-cov
-```
-
-Expected before implementation: fail because no summary/scene chunks exist.
-
-- [ ] **Step 3: Add summary chunk collection**
-
-At the start of `_collect_chunks()` after `chunk_counts`, add:
-
-```python
-        summary_text = str(commit_payload.get("summary_text") or "").strip()
-        summary_chunk_id = f"ch{chapter:04d}_summary" if chapter > 0 else ""
-        if chapter > 0 and summary_text:
-            chunks.append({
-                "chunk_id": summary_chunk_id,
-                "chapter": chapter,
-                "scene_index": 0,
-                "content": summary_text,
-                "chunk_type": "summary",
-                "parent_chunk_id": None,
-                "source_file": f"commit:chapter_{chapter:03d}",
-            })
-```
-
-- [ ] **Step 4: Add scene chunk collection**
-
-After the summary block, add:
-
-```python
-        for idx, scene in enumerate(commit_payload.get("scenes") or [], start=1):
-            if not isinstance(scene, dict):
-                continue
-            scene_index = int(scene.get("scene_index") or scene.get("index") or idx)
-            text = str(scene.get("summary") or scene.get("content") or "").strip()
-            location = str(scene.get("location") or "").strip()
-            if location and text:
-                text = f"{location}:{text}"
-            if not text:
-                continue
-            chunk_id = self._chunk_id("scene", chapter, scene_index)
-            chunks.append({
-                "chunk_id": chunk_id,
-                "chapter": chapter,
-                "scene_index": scene_index,
-                "content": text,
-                "chunk_type": "scene",
-                "parent_chunk_id": summary_chunk_id or None,
-                "source_file": f"commit:chapter_{chapter:03d}",
-            })
-```
-
-- [ ] **Step 5: Run vector and RAG tests**
-
-Run:
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_vector_projection_writer.py webnovel-writer/scripts/data_modules/tests/test_rag_adapter.py -q --no-cov
-```
-
-Expected: pass before the async bridge change, except any failures introduced by summary/scene implementation should be fixed before continuing.
-
-- [ ] **Step 6: Add active event loop regression test**
-
-Append to `test_vector_projection_writer.py`:
-
-```python
-import pytest
-
-
-@pytest.mark.asyncio
-async def test_run_store_coro_works_inside_active_event_loop():
-    writer = VectorProjectionWriter.__new__(VectorProjectionWriter)
-
-    async def store():
-        return 3
-
-    assert writer._run_store_coro(store()) == 3
-```
-
-- [ ] **Step 7: Run failing active-loop test**
-
-Run:
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_vector_projection_writer.py::test_run_store_coro_works_inside_active_event_loop -q --no-cov
-```
-
-Expected before implementation: fail because `_run_store_coro` does not exist.
-
-- [ ] **Step 8: Implement safe coroutine bridge**
-
-In `webnovel-writer/scripts/data_modules/vector_projection_writer.py`, add imports:
-
-```python
-import threading
-from collections.abc import Coroutine
-```
-
-Add this helper method to `VectorProjectionWriter`:
-
-```python
-    def _run_store_coro(self, coro: Coroutine[Any, Any, int]) -> int:
-        try:
-            asyncio.get_running_loop()
-        except RuntimeError:
-            return int(asyncio.run(coro) or 0)
-
-        result: dict[str, Any] = {}
-
-        def runner() -> None:
-            try:
-                result["value"] = asyncio.run(coro)
-            except Exception as exc:
-                result["error"] = exc
-
-        thread = threading.Thread(target=runner, daemon=True)
-        thread.start()
-        thread.join()
-        if "error" in result:
-            raise result["error"]
-        return int(result.get("value") or 0)
-```
-
-Then change `_store_chunks()` from:
-
-```python
-            stored = asyncio.run(adapter.store_chunks(chunks))
-            return stored
-```
-
-to:
-
-```python
-            return self._run_store_coro(adapter.store_chunks(chunks))
-```
-
-This keeps the synchronous public API intact while moving the coroutine to a short-lived thread when the caller already owns an event loop.
-
-- [ ] **Step 9: Run vector and RAG tests**
-
-Run:
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_vector_projection_writer.py webnovel-writer/scripts/data_modules/tests/test_rag_adapter.py -q --no-cov
-```
-
-Expected: pass.
-
-- [ ] **Step 10: Commit**
-
-```bash
-git add webnovel-writer/scripts/data_modules/vector_projection_writer.py webnovel-writer/scripts/data_modules/tests/test_vector_projection_writer.py
-git commit -m "feat: project summaries and scenes to vectors safely"
-```
-
----
-
-## Task 6: Unify Review Skill Metrics Flow
-
-**Problem:** CLI supports `review-pipeline --save-metrics`; `webnovel-review` still documents the old two-step flow.
-
-**Files:**
-- Modify: `webnovel-writer/skills/webnovel-review/SKILL.md`
-- Verify: `webnovel-writer/skills/webnovel-write/SKILL.md`
-
-- [ ] **Step 1: Update common mistakes**
-
-In `webnovel-review/SKILL.md`, replace:
-
-```markdown
-- ❌ 把 report 文件生成等同于已落库(`save-review-metrics` 未跑)
-```
-
-with:
-
-```markdown
-- ❌ 把 report 文件生成等同于已落库(`review-pipeline --save-metrics` 未跑)
-```
-
-- [ ] **Step 2: Replace Step 5 command block**
-
-Replace the two-command standard flow with:
-
-```bash
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" review-pipeline \
-  --chapter {chapter_num} \
-  --review-results "${PROJECT_ROOT}/.webnovel/tmp/review_results.json" \
-  --metrics-out "${PROJECT_ROOT}/.webnovel/tmp/review_metrics.json" \
-  --report-file "审查报告/第{chapter_num}章审查报告.md" \
-  --save-metrics
-```
-
-Replace the requirement:
-
-```markdown
-- `review-pipeline` 生成的 `review_metrics.json` 必须可直接写入 `review_metrics` 表
-```
-
-with:
-
-```markdown
-- `review-pipeline --save-metrics` 必须完成报告生成、metrics 文件输出、`review_metrics` 表写入
-```
-
-- [ ] **Step 3: Grep for obsolete instruction**
-
-Run:
-
-```bash
-rg -n "save-review-metrics|--save-metrics" webnovel-writer/skills/webnovel-review/SKILL.md webnovel-writer/skills/webnovel-write/SKILL.md
-```
-
-Expected: `webnovel-review` no longer instructs a separate `index save-review-metrics` call; both skills mention `--save-metrics`.
-
-- [ ] **Step 4: Commit**
-
-```bash
-git add webnovel-writer/skills/webnovel-review/SKILL.md
-git commit -m "docs: unify review metrics persistence flow"
-```
-
----
-
-## Task 7: Tighten Local Dashboard and Backup Safety Defaults
-
-**Problem:** Dashboard allows `*` CORS while exposing local project text. Backup-created `.gitignore` does not exclude `.env`.
-
-**Files:**
-- Modify: `webnovel-writer/dashboard/app.py`
-- Modify: `webnovel-writer/scripts/backup_manager.py`
-- Test: add `webnovel-writer/scripts/tests/test_dashboard_security.py`
-- Test: add `webnovel-writer/scripts/tests/test_backup_manager.py`
-
-- [ ] **Step 1: Add dashboard security tests**
-
-Create `webnovel-writer/scripts/tests/test_dashboard_security.py`:
-
-```python
-from fastapi.testclient import TestClient
-
-from dashboard.app import create_app
-
-
-def test_dashboard_cors_allows_localhost_origin(tmp_path):
-    (tmp_path / ".webnovel").mkdir(parents=True)
-    (tmp_path / ".webnovel" / "state.json").write_text("{}", encoding="utf-8")
-    app = create_app(tmp_path)
-    client = TestClient(app)
-
-    response = client.options(
-        "/api/project/info",
-        headers={
-            "Origin": "http://localhost:5173",
-            "Access-Control-Request-Method": "GET",
-        },
-    )
-
-    assert response.headers["access-control-allow-origin"] == "http://localhost:5173"
-
-
-def test_dashboard_cors_rejects_untrusted_origin(tmp_path):
-    (tmp_path / ".webnovel").mkdir(parents=True)
-    (tmp_path / ".webnovel" / "state.json").write_text("{}", encoding="utf-8")
-    app = create_app(tmp_path)
-    client = TestClient(app)
-
-    response = client.options(
-        "/api/project/info",
-        headers={
-            "Origin": "https://example.com",
-            "Access-Control-Request-Method": "GET",
-        },
-    )
-
-    assert "access-control-allow-origin" not in response.headers
-```
-
-- [ ] **Step 2: Restrict CORS origins**
-
-In `dashboard/app.py`, add module-level constant:
-
-```python
-LOCAL_CORS_ORIGINS = [
-    "http://localhost",
-    "http://localhost:5173",
-    "http://localhost:8000",
-    "http://127.0.0.1",
-    "http://127.0.0.1:5173",
-    "http://127.0.0.1:8000",
-]
-```
-
-Change middleware setup to:
-
-```python
-    app.add_middleware(
-        CORSMiddleware,
-        allow_origins=LOCAL_CORS_ORIGINS,
-        allow_methods=["GET"],
-        allow_headers=["*"],
-    )
-```
-
-- [ ] **Step 3: Add file read size guard**
-
-In `file_read()`, before `read_text`, add:
-
-```python
-        max_bytes = 2 * 1024 * 1024
-        if resolved.stat().st_size > max_bytes:
-            raise HTTPException(413, "文件过大,无法预览")
-```
-
-- [ ] **Step 4: Add backup gitignore test**
-
-Create `webnovel-writer/scripts/tests/test_backup_manager.py`:
-
-```python
-import subprocess
-
-from backup_manager import GitBackupManager
-
-
-def test_backup_manager_gitignore_excludes_env(tmp_path, monkeypatch):
-    calls = []
-
-    def fake_run(args, cwd=None, check=False, capture_output=False, text=False):
-        calls.append(args)
-        if args == ["git", "init"]:
-            (tmp_path / ".git").mkdir()
-        return subprocess.CompletedProcess(args=args, returncode=0, stdout="", stderr="")
-
-    monkeypatch.setattr("backup_manager.is_git_available", lambda: True)
-    monkeypatch.setattr("backup_manager.subprocess.run", fake_run)
-
-    GitBackupManager(str(tmp_path))
-
-    gitignore = (tmp_path / ".gitignore").read_text(encoding="utf-8")
-    assert ".env" in gitignore
-    assert ".env.*" in gitignore
-    assert "!.env.example" in gitignore
-```
-
-- [ ] **Step 5: Update backup `.gitignore` template**
-
-In `backup_manager.py`, add this block to the generated `.gitignore`:
-
-```gitignore
-# Env (keep .env.example)
-.env
-.env.*
-!.env.example
-```
-
-- [ ] **Step 6: Run security tests**
-
-Run:
-
-```bash
-python -m pytest webnovel-writer/scripts/tests/test_dashboard_security.py webnovel-writer/scripts/tests/test_backup_manager.py -q --no-cov
-```
-
-Expected: pass.
-
-- [ ] **Step 7: Commit**
-
-```bash
-git add webnovel-writer/dashboard/app.py webnovel-writer/scripts/backup_manager.py webnovel-writer/scripts/tests/test_dashboard_security.py webnovel-writer/scripts/tests/test_backup_manager.py
-git commit -m "fix: tighten dashboard cors and backup gitignore defaults"
-```
-
----
-
-## Task 8: Harden StyleSampler Tag JSON Parsing
-
-**Problem:** A corrupt `tags` JSON value in `style_samples.db` crashes listing.
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/style_sampler.py`
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_style_sampler_cli.py`
-
-- [ ] **Step 1: Add corrupt tag regression test**
-
-Append to `test_style_sampler_cli.py`:
-
-```python
-def test_style_sampler_ignores_corrupt_tag_json(temp_project):
-    sampler = StyleSampler(temp_project)
-    with sampler._get_conn() as conn:
-        conn.execute(
-            """
-            INSERT INTO style_samples
-            (id, chapter, scene_type, content, score, tags, created_at)
-            VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
-            """,
-            ("bad-tags", 1, SceneType.BATTLE.value, "战斗描写" * 50, 0.8, "[bad-json"),
-        )
-        conn.commit()
-
-    samples = sampler.get_best_samples(limit=5)
-
-    assert samples[0].id == "bad-tags"
-    assert samples[0].tags == []
-```
-
-If the schema column order differs, inspect `_ensure_db()` in `style_sampler.py` and adjust column names exactly.
-
-- [ ] **Step 2: Run failing test**
-
-Run:
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_style_sampler_cli.py::test_style_sampler_ignores_corrupt_tag_json -q --no-cov
-```
-
-Expected before implementation: fail with `json.JSONDecodeError`.
-
-- [ ] **Step 3: Add safe JSON helper**
-
-In `style_sampler.py`, add:
-
-```python
-def _safe_json_list(raw) -> list:
-    if not raw:
-        return []
-    try:
-        value = json.loads(raw)
-    except (TypeError, json.JSONDecodeError):
-        return []
-    return value if isinstance(value, list) else []
-```
-
-Change row mapping from:
-
-```python
-            tags=json.loads(row[5]) if row[5] else [],
-```
-
-to:
-
-```python
-            tags=_safe_json_list(row[5]),
-```
-
-- [ ] **Step 4: Run style sampler tests**
-
-Run:
-
-```bash
-python -m pytest webnovel-writer/scripts/data_modules/tests/test_style_sampler_cli.py -q --no-cov
-```
-
-Expected: pass.
-
-- [ ] **Step 5: Commit**
-
-```bash
-git add webnovel-writer/scripts/data_modules/style_sampler.py webnovel-writer/scripts/data_modules/tests/test_style_sampler_cli.py
-git commit -m "fix: tolerate corrupt style sample tags"
-```
-
----
-
-## Task 9: Final Verification
-
-**Files:** read-only verification, plus fixes if tests reveal regressions.
-
-- [ ] **Step 1: Compile Python sources**
-
-Run:
-
-```bash
-python -m compileall -q webnovel-writer/scripts webnovel-writer/dashboard
-```
-
-Expected: exit code 0.
-
-- [ ] **Step 2: Run focused regression suite**
-
-Run:
-
-```bash
-python -m pytest \
-  webnovel-writer/scripts/data_modules/tests/test_chapter_commit_service.py \
-  webnovel-writer/scripts/data_modules/tests/test_coverage_boost.py \
-  webnovel-writer/scripts/data_modules/tests/test_projection_writers.py \
-  webnovel-writer/scripts/data_modules/tests/test_knowledge_query.py \
-  webnovel-writer/scripts/data_modules/tests/test_chapter_status.py \
-  webnovel-writer/scripts/data_modules/tests/test_state_manager_extra.py \
-  webnovel-writer/scripts/data_modules/tests/test_vector_projection_writer.py \
-  webnovel-writer/scripts/data_modules/tests/test_style_sampler_cli.py \
-  webnovel-writer/scripts/tests/test_dashboard_security.py \
-  webnovel-writer/scripts/tests/test_backup_manager.py \
-  -q --no-cov
-```
-
-Expected: all pass.
-
-- [ ] **Step 3: Run full suite**
-
-Run:
-
-```bash
-python -m pytest -q
-```
-
-Expected: all tests pass and coverage is at least 90%.
-
-- [ ] **Step 4: Inspect review skill command consistency**
-
-Run:
-
-```bash
-rg -n "save-review-metrics|review-pipeline|--save-metrics" webnovel-writer/skills/webnovel-review/SKILL.md webnovel-writer/skills/webnovel-write/SKILL.md
-```
-
-Expected: both review flows use `review-pipeline --save-metrics`; no active instruction requires separate `index save-review-metrics`.
-
-- [ ] **Step 5: Commit final test adjustments if needed**
-
-If verification required small test-only fixes:
-
-```bash
-git add <changed-files>
-git commit -m "test: cover critical review fixes"
-```
-
-If no further changes were needed, do not create an empty commit.
-
----
-
-## Out of Scope for This Plan
-
-- Full `/webnovel-resync` implementation for manually edited chapters.
-- Full automatic review-fix loop.
-- Full SQLite migration framework.
-- Large refactors of `index_manager.py`, `status_reporter.py`, or `DataModulesConfig`.
-- Dashboard frontend performance work beyond security defaults.
-- Anti-AI/style quality product line improvements.
-
-These should be planned separately after the correctness and test-trust fixes land.

+ 0 - 381
docs/archive/superpowers/specs/2026-04-02-harness-v6-design.md

@@ -1,381 +0,0 @@
-# Webnovel-Writer Harness v6 设计文档
-
-> 日期:2026-04-02(更新:2026-04-09)
-> 状态:草案 v7(v6 + 实现对齐修正,Phase 1/2A/3/4 已完成)
-> 基于:用户反馈 + issue#5 token 分析 + 23 条问题清单
-
----
-
-## 1. 终极目标
-
-写出一本长篇网络小说(500-2000 章),支持多种主流题材。要求:
-- 文笔优秀,减少 AI 味
-- 剧情跌宕起伏,出人意料(AI 主导创意,作者事后审核)
-- 长上下文后保持文风且不吃书
-- 完善的人机协作,可注入作者灵感
-- 系统稳定,减少上下文消耗
-- 合适的错误改善机制
-
-## 2. 核心原则
-
-- **Claude Code 本身就是 harness**——不另建编排层,充分利用原生能力(/resume、Task、子 agent 隔离、自动 compaction)
-- **卷纲是 harness**——给写作 AI 足够约束,防止跑偏、失控、遗忘
-- **记忆是根基**——防吃书靠记忆系统,不靠大纲节点
-- **减法优先**——砍掉不产生价值的环节,而非叠加更多流程
-
-### 2.1 本轮非目标(Out of Scope for v6 Migration Baseline)
-
-以下事项方向已确认但**不在 v6 迁移基线内**,避免 scope creep:
-
-- **Plan schema 全量落地**——章纲字段草案仅作为参考,plan skill 重构排在 Phase 6
-- **References 全量范例化**——P0 方法论先补,P1 允许后续迭代
-- **Memory 底层存储重构**——Phase 5,先冻结 v0 契约即可
-- **Init 迭代打磨机制**——待 init 方案细化,不阻塞主链
-- **Anti-AI 超越黑名单的新机制**——Phase 7,当前先用 reviewer ai_flavor 维度兜底
-
-本轮优先:**主流程收敛(废弃 → 合并 → 接口冻结)+ 迁移退出标准达成**。
-
----
-
-## 3. 问题清单(23 条)
-
-### 3.1 Init
-
-| # | 问题 | 根因 |
-|---|------|------|
-| 1 | 参考文件重结构轻范例 | 教了"格式"没教"品味",LLM 知道填什么字段但不知道什么内容算好 |
-| 2 | 缺少题材标杆 | 没有"好世界书长什么样"的真实小说范例作为 few-shot |
-| 3 | 生成不可迭代 | 总纲、设定集一次生成就结束,无打磨循环 |
-
-### 3.2 Plan
-
-| # | 问题 | 根因 |
-|---|------|------|
-| 4 | 约束了"发生什么"而非"方向和边界" | 章纲写"主角救了叫朵朵的小女孩"→全量灌入写作 AI→剧透 |
-| 5 | 缺少卷级叙事功能定义 | 每章在卷中承担什么角色(起/承/转/合)不明确 |
-| 6 | 时间约束太显性 | 时间锚点、倒计时直接写在章纲里,AI 反复在正文中提及 |
-| 7 | Strand 比例硬编码 60/20/20 | 不同题材节奏不同,末世文和甜宠文不可能一样 |
-| 8 | 四层产出下游利用率不明 | 节拍表、时间线、卷纲、章纲——write 阶段真正消费的只有章纲 |
-| 9 | 10 章/批生成质量递减 | 后面几章趋向套路化 |
-
-### 3.3 Write
-
-| # | 问题 | 根因 |
-|---|------|------|
-| 10 | 流水线太重 | 8 步 + workflow 记录,大量 token 花在流程管理而非写作 |
-| 11 | context-agent 是 token 黑洞 | 全量灌入所有数据,输出巨大执行包 |
-| 12 | 审查消耗大产出低 | 6 个 checker 各自独立 context,打 90 分但用户觉得很差 |
-| 13 | anti-AI 必须加强 | 黑名单只能挡已知口癖,挡不了叙事结构/情绪表达/节奏层面的 AI 味 |
-| 14 | data-agent 太重 | 9 个子步骤,归入记忆模块统一设计 |
-| 15 | 写作和回写耦合 | 回写失败卡住整条链,但回写时机不变(下一章前必须完成) |
-| 16 | workflow_manager + resume skill 浪费 | Claude Code 原生 /resume 即可恢复中断会话 |
-
-### 3.4 记忆
-
-| # | 问题 | 根因 |
-|---|------|------|
-| 17 | 6 种存储太分散 | state.json / index.db / scratchpad / summaries / vectors / snapshots 各自读写 |
-| 18 | 分层不符合写作直觉 | 应按时效分级:近期(详细)→ 中期(摘要)→ 远期(活跃事实) |
-| 19 | 时间线不是索引轴 | 所有记忆应挂在时间线上,支持"第 N 章时角色是什么状态"的查询 |
-| 20 | 记忆类型需明确 | 角色状态(可变)、世界规则(稳定)、伏笔(有生命周期)、时间线(单调递增) |
-
-### 3.5 系统级
-
-| # | 问题 | 根因 |
-|---|------|------|
-| 21 | Skill/Agent prompt 格式混乱 | 缺少统一模板,每个文件组织方式不同,LLM 抓不住重点 |
-| 22 | 参考资料需要清理和补充 | 删冗余、补方法论(含真实小说片段作为正面/反面范例) |
-| 23 | Token 消耗过高 | 单章 300-500 万,审查占大头但产出最低 |
-
----
-
-## 4. 已确认的设计方向
-
-### 4.1 废弃项
-
-| 废弃 | 替代 |
-|------|------|
-| workflow_manager.py | Claude Code 原生 /resume |
-| resume skill | Claude Code 原生 /resume |
-| Step 2B(独立风格适配步骤) | 合并到 Step 4 润色 |
-| 6 个独立 checker agent | 合并为 1 个审查 agent |
-| 审查评分机制 | 改为 code review 格式输出具体问题清单 |
-| memory_scratchpad.json(长记忆系统) | 基于远端无长记忆版本重新设计统一记忆模块 |
-
-### 4.2 Write 流程(新)
-
-```
-Step 0.5 预检
-  → Step 1 上下文搜集(context-agent,research 模式)
-  → Step 2 起草
-  → Step 3 审查(单 agent,code review 格式)
-  → Step 4 润色 + 风格适配 + anti-AI
-  → Step 5 数据回写(统一记忆模块,单次调用)
-  → Step 6 Git 备份
-```
-
-**章节状态模型(单调递进,不可回退):**
-
-| 状态 | 进入条件 | 允许结束会话 | 允许开始下一章 | 允许 git backup |
-|------|----------|:---:|:---:|:---:|
-| `chapter_drafted` | Step 2 完成,正文初稿存在 | ✅ | ❌ | ❌ |
-| `chapter_reviewed` | Step 3-4 完成,blocking 清零且 anti-AI 复检通过 | ✅ | ❌ | ❌ |
-| `chapter_committed` | Step 5 完成,记忆回写成功 | ✅ | ✅ | ✅ |
-
-状态推进规则:
-- 由 Step 2/4/5 完成时分别推进,写入 `state.json.progress.chapter_status[NNNN]`
-- Step 5 失败不回滚 Step 1-4,章节停留在 `chapter_reviewed`,不得开始下一章
-- 允许带着回写 debt 结束会话,但下次会话必须先补完 Step 5
-- 查询入口:`python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" state get-chapter-status --chapter {N}`
-
-### 4.3 Context-Agent(新模式)
-
-从"一次性全量灌入 → 输出巨大执行包"改为 research 模式:
-
-1. 调用记忆模块合并接口 → 拿到基础上下文(章纲目标、角色状态、未闭合伏笔)
-2. 思考:这章还需要什么额外信息?
-3. 按需调用记忆模块独立接口补充(某角色历史、某条世界规则、上章结尾)
-4. 确认信息充分 → 按固定格式输出写作提示
-
-### 4.4 审查(新模式)
-
-- 默认 1 个 agent,一次灌入正文 + 记忆中的角色状态/世界规则
-- 输出格式为结构化问题清单(code review 风格)
-- **保留拆分口**:若实测发现 ai_flavor 检出率显著下降或单次上下文过载,允许追加一次轻量二次专项检查(如仅 ai_flavor 维度),但不回到 6 agent 模式:
-
-**最小 schema:**
-
-```json
-{
-  "issues": [
-    {
-      "severity": "critical | high | medium | low",
-      "category": "continuity | setting | character | timeline | ai_flavor | logic | pacing | other",
-      "location": "第3段",
-      "description": "主角使用了第15章已失去的能力'xxx'",
-      "evidence": "原文:'萧炎催动xxx斗技' vs 记忆:第15章已失去该能力",
-      "fix_hint": "改为使用当前已有的yyy能力",
-      "blocking": true
-    }
-  ],
-  "blocking_count": 1,
-  "summary": "发现1个阻断问题,2个高优问题"
-}
-```
-
-**阻断规则:**
-- `blocking=true` 的问题替代原 `timeline_gate` 语义——存在任何 blocking issue 时,不得进入 Step 4
-- `severity=critical` 默认 `blocking=true`;其余 severity 由审查 agent 判断
-
-**blocking 修复循环:**
-- 存在 blocking issue 时,由主流程(非 reviewer)修复对应问题
-- 修复后必须重跑 Step 3(完整审查),确认 blocking 清零
-- 若用户明确覆盖(如判断为误报),可跳过重审直接进入 Step 4,需在 review report 中标注覆盖原因
-
-**指标沉淀(轻量):**
-- 每次审查结果写入 `index.db.review_metrics`,字段:`chapter, issues_count, blocking_count, categories, severity_counts, timestamp`
-- 用于趋势观测(连续 N 章某类问题反复出现 → 提示系统性问题)
-- `overall_score` 保留为**衍生兼容字段**(由 severity_weighted 扣分计算),仅用于排序/趋势,**不可用于 gate 决策**
-- gate 决策始终以 `blocking=true` 和 issue 明细为准
-- 落库 schema 以 `review-schema.md` 冻结版为准;逻辑观测以轻量字段(issues_count/blocking_count/categories)为主,物理落库可保留兼容字段(overall_score/dimension_scores),待 dashboard/consumer 完成迁移后再评估裁撤
-
-**anti-AI 职责划分:**
-- **Step 3 负责发现** anti-AI 问题(category="ai_flavor"),列入问题清单
-- **Step 4 负责修复**——消费 Step 3 的 ai_flavor issue 逐条修改
-- **Step 4 修复后,必须独立复检**——默认路径:重跑 `reviewer`,仅启用 `ai_flavor` 维度。Step 4 不得自判 pass/fail
-- 替代检查方案必须同时满足以下全部条件才视为等价:
-  1. 独立于 Step 4 执行(不可由润色流程自判)
-  2. 结构化 JSON 输出(含 `blocking_count`)
-  3. 结果可落盘、可追溯(写入 `.webnovel/tmp/`)
-- 理由:避免"自己改、自己说通过"的闭环偏差
-
-### 4.5 记忆模块(分两阶段交付)
-
-**阶段 A:接口契约(先定,不依赖存储实现)**
-
-上层消费者(context-agent、data-agent、审查 agent)只依赖以下契约:
-
-**v0(已实现,当前冻结):**
-
-> v0 接口已通过 `memory_contract.py`(Protocol + 类型定义)和 `memory_contract_adapter.py`(适配器)实现,CLI 入口为 `webnovel.py memory-contract` 子命令。context-agent 已在使用。
-
-```python
-# 合并接口
-memory.commit_chapter(chapter: int, result: dict) -> CommitResult
-memory.load_context(chapter: int, budget_tokens: int) -> ContextPack
-
-# 独立接口(context-agent research 模式按需调用)
-memory.query_entity(entity_id: str) -> EntitySnapshot
-memory.query_rules(domain: str) -> list[Rule]
-memory.read_summary(chapter: int) -> str
-memory.get_open_loops(status: str = "active") -> list[OpenLoop]
-memory.get_timeline(from_ch: int, to_ch: int) -> list[TimelineEvent]
-```
-
-**v1(Phase 3 增强目标,v0 冻结后再迭代):**
-
-- `commit_chapter` 的 `result: dict` → 替换为类型化 `CommitPayload`(明确字段:chapter_file、review_result、entities、summary 等)
-- `load_context` 增加 `intent: Literal["draft", "review", "repair"]` 参数,按意图裁剪返回内容
-- `query_rules` 的 `domain: str` → 细化为 `domain: str, scope: str = "all"` 支持子域过滤
-- 所有返回结构统一包含横切元数据:`source`、`chapter_range`、`confidence`
-
-v0 → v1 的升级不影响上层 prompt,仅影响 CLI 参数和返回字段丰富度。
-
-**阶段 B:存储实现(后做)**
-
-已确认方向:
-- 按时效分层:近期(详细)→ 中期(摘要)→ 远期(活跃事实)
-- 时间线作为索引轴
-- 记忆类型:角色状态(可变)、世界规则(稳定)、伏笔(有生命周期)
-- 具体实现方案搁置,待进一步思考
-
-### 4.6 Plan(章纲约束重构方向)
-
-章纲作为 harness 给 write 足够约束,但约束形式需要变:
-- **约束"方向和边界"**,不约束"具体发生什么"
-- **时间约束隐性化**——不在章纲里写死时间锚点,通过记忆系统间接传递,写后校验
-- **Strand 比例按题材预设**,不硬编码 60/20/20
-- **卷级叙事功能**——每章需要标注在卷中的叙事角色(起/承/转/合)
-- 具体章纲字段设计待定
-
-**章纲最小字段草案(待验证):**
-
-内容层(章纲本体):
-```json
-{
-  "chapter_goal": "主角通过考验进入迦南学院",
-  "must_payoff": ["第3章埋下的丹药伏笔"],
-  "forbidden_turns": ["主角不可直接暴露隐藏身份"],
-  "narrative_role_in_arc": "承",
-  "strand_profile": "main_heavy",
-  "time_pressure_source": "入学截止"
-}
-```
-
-消费策略层(控制章纲如何喂给写作 AI,不属于故事内容):
-```json
-{
-  "writer_exposure_policy": {
-    "verbatim_fields": ["chapter_goal", "must_payoff", "forbidden_turns"],
-    "transform_fields": {
-      "narrative_role_in_arc": "转为隐性节奏提示,不直接告知'承'",
-      "time_pressure_source": "仅作为校验依据,不在正文中直接提及具体数字或倒计时"
-    }
-  }
-}
-```
-
-说明:
-- `chapter_goal`:方向而非剧透,不写"主角救了叫朵朵的小女孩"
-- `forbidden_turns`:明确边界,防止跑偏
-- `strand_profile`:取代硬编码数值配比,由 genre-profiles 定义具体权重(如 `main_heavy` = 主线 70%+、`balanced` = 均衡、`relationship_heavy` = 感情线主导)
-- `writer_exposure_policy`:独立于内容层,由 context-agent 消费时解释,章纲 schema 本身不混入传输逻辑
-
-### 4.7 Skill/Agent Prompt 统一模板
-
-每个 skill/agent 文件按固定结构编写:
-
-```
-1. 身份与目标
-2. 可用工具与脚本(含调用方式)
-3. 思维链(ReAct / 其他)
-4. 输入
-5. 执行流程(每步:输入 → 动作 → 输出)
-6. 边界与禁区
-7. 检查清单
-8. 输出格式
-9. 错误处理
-```
-
-### 4.8 参考资料
-
-**删除**:冗余引用、已废弃文档、重复的 shared 引用(共 13 个文件)
-
-**补充**(P0,16 条):
-- 反派设计、镜像反派、对手梯度、人物关系动力学
-- 时间线设计、长篇升级节奏、反派压迫递进、伏笔埋设与回收
-- 感情线递进、身份隐藏与曝光
-- 暧昧/打脸/反转/对峙场景写法
-- 章节开头钩子、章节结尾 cliffhanger
-
-**要求**:参考资料按优先级分层补充真实小说片段:
-- **P0(关键方法论)**:必须包含真实小说片段作为正面/反面范例——追读力钩子、反转写法、对峙场景等直接影响写作质量的方法论
-- **P1(次级方法论)**:允许先用自写范例或抽象示例,后续逐步替换为真实片段
-
-**改造**:现有 genres/ 和 write/references/ 下的文件从"结构模板"改为"方法论 + 范例 + 反面教材"
-
----
-
-## 5. 未解决的设计问题
-
-| # | 问题 | 状态 |
-|---|------|------|
-| 1 | 记忆模块具体实现(分层、存储、接口) | 搁置,待进一步思考 |
-| 2 | 章纲具体字段设计(什么算"方向和边界") | 已有最小草案(见 4.6),待实际 plan 验证后定稿 |
-| 3 | anti-AI 的具体机制(超越黑名单的方案) | 待写作 prompt 设计时解决 |
-| 4 | context-agent 输出的写作提示具体格式 | 待 write 方案细化 |
-| 5 | 参考资料的真实小说片段收集 | 待用户收集 |
-| 6 | Init 的迭代打磨机制 | 待 init 方案细化 |
-| 7 | 节拍表/时间线是否保留 | 待确认下游是否消费 |
-| 8 | 批量生成章纲的最佳批次大小 | 待实验 |
-| 9 | Memory 契约 v0→v1 增强 | Phase 3 细化:CommitPayload 类型化、load_context 增加 intent、返回结构加横切元数据(详见 4.5 v1 目标) |
-
----
-
-## 6. Token 优化预估
-
-> 以下为方向性估算,具体取决于正文长度、审查轮次、research 命中率。需用真实运行日志验证。
-
-| 环节 | 当前 | 优化后(预估区间) | 节省 |
-|------|------|--------|------|
-| 审查 | ~200 万(6 agent × ~33 万) | 30-50 万(1 agent,含 Step 4 后 ai_flavor 复检) | ~75-85% |
-| Context-agent | ~50 万(全量灌入) | 10-20 万(按需检索) | ~60-80% |
-| 风格适配 | ~30 万(独立 Step 2B) | 0(合并到润色) | 100% |
-| Workflow 记录 | ~5 万(16 次 CLI) | 0(废弃) | 100% |
-| **单章总计** | 300-500 万 | **预估 80-150 万** | ~60-70% |
-
-假设条件:
-- 单章正文 2000-2500 字
-- 审查无 blocking 需返工的情况
-- context-agent research 模式命中率 > 80%
-
----
-
-## 7. 实施路径(建议)
-
-| 阶段 | 内容 | 依赖 | 状态 |
-|------|------|------|------|
-| Phase 1 | 废弃 workflow/resume + 审查合并 + Step 2B 合并 | 无 | **✅ 已完成** |
-| Phase 2A | Skill/Agent prompt 统一模板 + 参考资料清理(删冗余) | 无 | **✅ 已完成** |
-| Phase 2B | 参考资料范例补强(真实小说片段) | 用户收集素材 | 待定 |
-| Phase 3 | 记忆模块接口契约设计 | 无 | **✅ 已完成** |
-| Phase 4 | Context-agent research 模式重构 | Phase 3(契约) | **✅ 已完成** |
-| Phase 5 | 记忆模块存储实现 | Phase 3(契约)+ 用户设计确认 | 待定 |
-| Phase 6 | Plan 章纲约束重构 | Phase 4+5 | 待定 |
-| Phase 7 | anti-AI 加强 + 写作 prompt 优化 | Phase 2A+4 | 待定 |
-
-注:Phase 1/2A/3 可并行探索,但合并前需一次**接口冻结**,优先冻结以下两项:
-- `review-schema`(reviewer 输出格式 + metrics 落库字段)
-- `memory-contract CLI`(记忆模块接口签名 + 返回类型)
-
-冻结后 Phase 1/2A 的 prompt 改动才有稳定的接口可依赖。
-
----
-
-## 8. 迁移退出标准(Migration Exit Criteria)
-
-以下条件全部满足时,视为 v6 迁移完成:
-
-1. **仓库内不再存在对 6 个 checker 的运行时引用**——skill/agent prompt 中无 `continuity-checker`、`setting-checker` 等旧名
-2. **webnovel-write / webnovel-review 均只走 reviewer 流**——审查路径唯一
-3. **`workflow_manager` 相关代码完全移除**——不残留 import、调用或配置
-4. **legacy 术语分层清零**:
-   - 运行时引用(skill/agent/script 中):**0 处**
-   - prompt/skill 生效路径引用:**0 处**
-   - 测试 fixture / 迁移兼容样本:允许存在,但必须集中在 `evals/` 或 `tests/` 目录
-   - 历史文档(docs/):允许存在,但必须标注 `[deprecated]`
-5. **至少 1 个真实项目完成连续 10 章验证**,验收维度:
-   - 无流程阻断异常(全链路 plan → write → review → data 跑通)
-   - 无状态回写错乱(chapter_status 单调递进,state.json / index.db 一致)
-   - 无 review / write / data 契约不一致(reviewer 输出可被 review-pipeline 正确解析,data-agent 产物符合 memory v0 契约)
-6. **章节状态模型已落地到 state.json**——`chapter_drafted` / `chapter_reviewed` / `chapter_committed` 可通过 CLI 查询,状态推进由 Step 2/4/5 原子写入

+ 0 - 148
docs/archive/superpowers/specs/2026-04-03-ai-writing-quirks.md

@@ -1,148 +0,0 @@
-# AI 写作癖好全景图(网文场景)
-
-> 调研日期:2026-04-03
-> 来源:现有 polish-guide.md 词库 + 网络调研 + 学术检测研究
-> 用途:Phase 7 anti-AI 加强的输入材料
-
----
-
-## 一、词汇层(已有 200+ 词库,需补充的新增项)
-
-### 现有覆盖(polish-guide.md 9 类 200+ 词)
-A. 总结归纳词 / B. 枚举模板词 / C. 书面学术腔 / D. 逻辑连接滥用词 / E. 情绪直述词 / F. 动作套话 / G. 环境套话 / H. 叙事填充词 / I. 抽象空泛词 / J. 机械开场/收尾词
-
-### 需补充的新类别
-
-#### K. 神态模板词(网文特有高频)
-眸中闪过、眼底掠过、瞳孔微缩、嘴角微微上扬、嘴角勾起一抹弧度、眉头微蹙、眉心微皱、面色微变、神色一凝、目光微闪、眼神微动、唇角微翘、脸色微沉、面色如常、不露声色。
-
-#### L. 万能副词(AI 的标志性修饰)
-缓缓、淡淡、微微、轻轻、静静、默默、悄悄、慢慢、渐渐、暗暗。
-
-> 特征:AI 几乎在所有动作前都加这些副词,形成"副词+动词"的固定模式。如"缓缓开口""淡淡说道""微微点头""轻轻叹息"。
-
-#### M. 内心活动套话
-心中暗道、心中一凛、心中暗自思量、心下了然、心中不由得、心底涌起一股、心中升起一丝、暗自盘算、暗自揣测、心中有了计较。
-
-#### N. 转折/递进模板(中文网文版)
-话虽如此、不过、只是、然而就在这时、正当……之际、就在此刻、然而下一刻、可惜的是、殊不知。
-
----
-
-## 二、句式层(超越单词,到句群结构)
-
-### 已有覆盖
-- 三段式枚举(首先/其次/最后)
-- 连续同构句(≥3 句主谓宾一致)
-- 清单化叙事
-
-### 需补充
-
-| 编号 | 癖好名 | 表现 | 频率 |
-|------|--------|------|------|
-| S1 | **起因→经过→结果→感悟 四段式** | 每段/每节都走完整叙事闭环,无留白 | 极高 |
-| S2 | **情绪三连** | "他感到X,同时又有些Y,内心深处还有一丝Z" | 高 |
-| S3 | **对仗排比** | "XX是XX的XX,YY是YY的YY"连续 2-3 句 | 高 |
-| S4 | **总结式收尾** | 每段结尾都有一句"由此可见/这意味着/他终于明白了" | 高 |
-| S5 | **双重否定强调** | "并非不知道""不是没有想过""不能不承认" | 中 |
-| S6 | **先抑后扬标准弧** | 每个冲突都在同一段内解决:困难→灵机一动→轻松化解 | 高 |
-| S7 | **同义反复** | 同一信息用不同句式说 2-3 遍,填充字数 | 高 |
-| S8 | **万能过渡句** | "时间飞逝,转眼间……" "不知不觉,XX已经过去了" | 高 |
-| S9 | **机械递进** | "更重要的是""更令人惊讶的是""最让他在意的是" | 中 |
-| S10 | **解释性插入** | 叙事中途突然插入百科式解释(如力量体系说明) | 中 |
-
----
-
-## 三、叙事结构层(章节级 AI 味)
-
-| 编号 | 癖好名 | 表现 |
-|------|--------|------|
-| N1 | **匀速叙事** | 缺乏节奏变化,每段信息密度相近,无快慢之分 |
-| N2 | **全知全能视角滑移** | 明明是限制视角,却突然展示其他角色的内心想法 |
-| N3 | **戏剧性反讽提示** | "他不知道的是……""殊不知……""如果他知道未来会发生什么……" |
-| N4 | **每章完整闭环** | 每章都有独立的"提出问题→解决问题"弧线,缺乏跨章悬念 |
-| N5 | **均匀分配戏份** | 每个出场角色都得到差不多的描写篇幅,无主次之分 |
-| N6 | **安全着陆** | 冲突总在章末被完美解决,没有遗留问题和不安感 |
-| N7 | **顺序事件流** | 严格按时间线叙事,不用闪回、倒叙、并行叙事 |
-| N8 | **展示-解释二连** | 先用动作展示,紧接着用一句话解释刚才的动作含义 |
-| N9 | **正面结果偏好** | 角色决策几乎总是正确的,缺乏真正的失败和代价 |
-| N10 | **对称冲突** | 每个挑战都与角色当前能力刚好匹配,无碾压也无绝望 |
-
----
-
-## 四、情感表达层(AI 最薄弱的领域)
-
-| 编号 | 癖好名 | 表现 | 替代方向 |
-|------|--------|------|----------|
-| E1 | **情绪标签化** | "他感到愤怒""她非常紧张" | 用生理反应+行为暗示 |
-| E2 | **情绪即时切换** | 上一句愤怒,下一句就平静了 | 情绪需要过渡和残留 |
-| E3 | **全员理性人** | 角色在极端情境下仍然冷静分析 | 允许非理性反应 |
-| E4 | **情绪通胀** | 每个小事件都"震惊""难以置信" | 区分情绪等级 |
-| E5 | **观察者视角** | 写"他看到她哭了"而非沉浸式写哭的感受 | 减少距离感 |
-| E6 | **情绪结论前置** | 先说"他很伤心"再写行为 | 行为在前,读者自己判断 |
-| E7 | **千人一面的反应** | 所有角色面对惊讶都是"瞳孔微缩" | 个性化反应模式 |
-| E8 | **感悟式内心独白** | 角色动不动就"他终于明白了一个道理" | 减少顿悟,增加迷惑 |
-| E9 | **情感说明书** | 对白像在解释自己的情感"我之所以生气是因为……" | 潜台词和言行不一 |
-| E10 | **治愈式结尾** | 章末角色总能想通、释然、找到希望 | 允许不安和未解决 |
-
----
-
-## 五、对话层
-
-| 编号 | 癖好名 | 表现 |
-|------|--------|------|
-| D1 | **信息宣讲式对白** | 对话用来解释背景,而非推进冲突 |
-| D2 | **全员书面语** | 所有角色说话都是标准书面中文,无口语特征 |
-| D3 | **轮流发言** | A说→B说→A说→B说,严格交替,无打断 |
-| D4 | **回应过于完整** | 每句对话都完整回应上一句,无顾左右而言他 |
-| D5 | **说话带总结** | 角色动不动就"总之""也就是说" |
-| D6 | **万能 said tag** | "XX说道""XX淡淡道""XX沉声道",缺乏动作替代 |
-| D7 | **对白后解释** | 说完一句话后,叙述者解释这句话的含义 |
-| D8 | **情绪对白标注** | "他愤怒地说""她伤心地回应" |
-| D9 | **无效客套** | 角色之间过多"你还好吗""小心点"等无信息量对话 |
-| D10 | **独白替代对话** | 需要两人互动的场景,变成一人长篇独白 |
-
----
-
-## 六、统计/节奏层(可量化检测)
-
-| 指标 | AI 典型值 | 人类写作典型值 | 检测意义 |
-|------|-----------|---------------|----------|
-| 句长标准差 | 低(句子长度均匀) | 高(长短交替) | burstiness |
-| 段落长度标准差 | 低 | 高 | 节奏变化 |
-| 逗号/句号比 | 高(长句多) | 适中 | 句式简洁度 |
-| 对话占比 | 低(30% 以下) | 40-60%(网文) | 展示 vs 叙述 |
-| 形容词密度 | 高 | 中 | 词藻堆砌度 |
-| 副词+动词结构 | 极高 | 低 | "缓缓/淡淡"检测 |
-| 四字词密度 | 高(连续堆叠) | 中(分散使用) | 成语/套语 |
-| 章末悬念强度 | 低(安全着陆) | 高(cliffhanger) | 网文核心竞争力 |
-
----
-
-## 七、与现有系统的差距分析
-
-| 层级 | 现有覆盖 | 缺口 |
-|------|---------|------|
-| 词汇 | 200+ 词库(A-J 类) | 缺 K/L/M/N 类(神态模板、万能副词、内心套话、网文转折) |
-| 句式 | 4 条规则 | 缺 S1-S10(四段式、情绪三连、对仗排比等) |
-| 叙事结构 | 无 | 完全空白(N1-N10 全部需要新增) |
-| 情感表达 | 仅"情绪直述词"列表 | 缺 E1-E10 的系统化识别和改写规则 |
-| 对话 | 1 条规则(说明书式对话) | 缺 D1-D10 的细分 |
-| 统计/节奏 | 自然化程度表(4 指标) | 缺 burstiness、对话占比、副词密度等可量化指标 |
-
----
-
-## 参考来源
-
-- 现有 `polish-guide.md` 7 层规则 + 200 词库
-- [AI创作网文的AI味总结 - CSDN](https://blog.csdn.net/qq_52080901/article/details/148605845)
-- [拆解"AI味":我们为何反感AI写作 - 腾讯新闻](https://news.qq.com/rain/a/20250924A01QWG00)
-- [维基百科编辑总结识别 AI 写作的关键特征 - OSCHINA](https://www.oschina.net/news/368298)
-- [Wikipedia: Signs of AI writing](https://en.wikipedia.org/wiki/Wikipedia:Signs_of_AI_writing)
-- [AI写作:25个去"AI味"的通用润色指令 - 知乎](https://zhuanlan.zhihu.com/p/1904977486221117285)
-- [教授一眼识破AI论文的12个特征 - CSDN](https://blog.csdn.net/2509_91422757/article/details/147333254)
-- [Interpretable Text Classification for LLM-generated Creative Writing - arXiv](https://arxiv.org/html/2601.07368)
-- [Art or Artifice? LLMs and the False Promise of Creativity - arXiv](https://arxiv.org/html/2309.14556v3)
-- [用AI写网文时,有哪些「爆改文风」的提示词 - 运营派](https://www.yunyingpai.com/personal/1029559.html)
-- [去「AI味儿」大作战,AI到底能不能写出风格稳定的「网文」 - 人人都是产品经理](https://www.woshipm.com/share/6054640.html)
-- [Claude写小说功能实测 - Claude中文网](https://www.liehuxinghuo.com/claude_category-1/804.html)

+ 0 - 863
docs/archive/superpowers/specs/2026-04-09-skills-restructure-and-reference-gaps.md

@@ -1,863 +0,0 @@
-# Webnovel Writer Skills 重构与 Reference 缺口设计
-
-> 日期:2026-04-09
-> 状态:草案 v4.3(v4.2 + Codex 二轮 review 3 处修正)
-> 目标:在 Claude 已具备通用智能与通用写作能力的前提下,重构 `skills/` 体系,并为后续 `references/` 补全提供缺口清单。
-
----
-
-## 1. 为什么要写一份新的 spec
-
-### 1.1 与 v6 spec 的关系
-
-本 spec 是 `2026-04-02-harness-v6-design.md`(v7)的**补充**,不是替代。
-
-- **v6 spec** 解决主流程架构:废弃/合并、状态机、memory contract、审查流程、迁移退出标准。Phase 1/2A/3/4 已完成。
-- **本 spec** 解决 v6 完成后的文档体系收敛:skills 怎么瘦身、references 怎么设计和补缺。
-- **关于 reference 的设计原则**(渐进式披露、三层内容、按需加载、双轨知识库等),以本 spec 为准。
-
-v6 spec 中的以下设计决策本 spec 继承不变:
-- Write 流程 Step 0.5-6 及状态机
-- reviewer 单 agent + blocking 修复循环
-- memory contract v0/v1 接口
-- 章纲字段草案与 `writer_exposure_policy` 分层
-
-### 1.2 当前问题
-
-旧的 harness v6 spec 主要解决的是迁移问题(现已大部分完成)。当前真正的问题,已经从“主流程能力缺失”转为“文档体系不够收敛”:
-
-1. `skills/` 的结构风格混杂,有的像主链 SOP,有的像命令手册,有的像小型 spec。
-2. `references/` 的职责不稳定:有的在重复 Claude 本来就知道的常识,有的又缺少针对模型缺陷的关键约束。
-3. `skills`、`references`、`scripts` 的边界不够清楚,导致信息重复、维护成本高、补充方向模糊。
-4. 对中文网文场景下的模型缺陷补偿不足,例如人物命名复用、套路化桥段复用、题材语汇漂移、对话口吻趋同等。
-
-因此需要一份新的 spec,不再延续“迁移”叙事,而是回答一个更直接的问题:
-
-**在 Claude 已经足够聪明的前提下,这个系统还需要哪些最小必要 skills,以及哪些 references 缺口值得补?**
-
----
-
-## 2. 核心设计哲学
-
-### 2.1 Claude 足够聪明
-
-默认前提:Claude 已具备以下能力,不需要在技能文档里重复教学:
-- 通用写作能力
-- 通用总结、提炼、润色能力
-- 常见叙事技巧与常见角色塑造常识
-- 常规软件工程与文件读写能力
-- 一般性的“如何提问用户”“如何组织步骤”“如何做检查”的能力
-
-因此,`skills` 和 `references` 不应该承载这些通用常识。
-
-### 2.2 文档只补“Claude 不稳定、不知道、易做错”的部分
-
-应保留在文档里的内容,必须至少满足以下一种:
-- **项目私有知识**:本仓库特有的状态字段、CLI、文件路径、契约、产物位置。
-- **模型缺陷补偿**:Claude 高频犯错点,且不能稳定靠通识解决。
-- **题材/平台特异约束**:中文网文场景下的特殊要求、风格偏好、禁区。
-- **流程闸门**:必须显式满足的步骤顺序、验证条件、失败恢复。
-- **高价值参考**:真实范例、反例、命名规则、反模板约束等。
-
-不满足上述条件的信息,原则上不应出现在 `skills` 或 `references` 中。
-
-### 2.3 Reference 设计四原则
-
-以下四条原则是 reference 体系设计的核心约束,所有 reference 新增、保留、删除决策都必须以此为准。
-
-#### 原则 1:渐进式披露
-
-Reference 不是按“知识主题”组织,而是按“哪个 skill 的哪个 step 在执行时需要什么指导”组织。
-
-- 每个 reference **应尽量**能映射到至少一个具体的 skill + step。
-- 若为跨 skill 通用缺陷补偿,应明确**主服务场景**与**次服务场景**。
-- Skill 的引用加载策略必须写清楚“Step N 遇到 X 条件时加载 Y reference”。
-
-#### 原则 2:三层内容分级
-
-同一个 reference 文件内,内容按三个层级组织:
-
-| 层级 | 性质 | 说明 | 示例 |
-|------|------|------|------|
-| **提醒层** | Claude 知道,但长文写作时容易忘或不稳定执行 | 轻量条目,起“别忘了”的作用 | “对话不要全员书面语”、“不要每段都以总结句收尾” |
-| **缺陷补偿层** | Claude 的系统性弱点,靠通识解决不了 | 需要明确的禁止项、替代方案、判断标准 | 命名同质化防护、“缓缓/淡淡/微微”固定语式替换、四段闭环结构检测 |
-| **知识补充层** | Claude 知道但不够深/不够全,需要领域知识注入 | 提供中文网文特有的技法、模式、正反例 | 追读力钩子技法、特定题材节奏模式、真实小说片段 |
-
-文件内三层从上到下排列,加载时可根据 token 预算裁剪深度。
-
-#### 原则 3:按需条件加载
-
-Reference 只在当前章节/任务确实触发了对应场景时才加载。
-
-- 本章没有战斗 → 不加载战斗描写参考
-- 本章没有新角色首次出场 → 不加载命名参考
-- 本章不涉及时间跳跃 → 不加载时间过渡参考
-
-Skill 的引用加载策略必须写明**触发条件**,而不是“每次都加载全部”。
-
-#### 原则 4:粒度优先对齐稳定问题域
-
-**md 文件**:优先按稳定问题域、稳定决策点组织,而不是机械切成极小碎片。只有当不同场景确实会产生冲突时,才进一步细拆。
-
-**CSV 检索**:一次检索返回的结果集等于一个子任务的粒度(同一张 CSV 可包含多种子任务的条目,靠检索过滤)。
-
-- 这个 step 需要命名 → 检索只返回命名相关条目
-- 这个 step 需要战斗描写 → 检索只返回战斗相关条目
-- 不扩展到“相关但当前不需要”的知识
-
-### 2.4 references 不是越少越好,而是越“补缺”越好
-
-本轮不追求压缩 references 数量。判断标准不是“文件是否减少”,而是:
-
-**该 reference 是否真正补到了 Claude 的稳定性缺口或项目私有知识缺口。**
-
-因此:
-- 若现有 references 冗余,应删除或合并。
-- 若现有 references 缺失,而该缺失会稳定导致错误,应新增。
-- 新增 reference 的理由必须清楚说明“它补的是什么缺口”。
-
-例如:
-- 人物命名规则
-- 题材专属命名禁区
-- 中文网文对话口吻差异
-- 特定题材常见模板腔与避坑清单
-
-这些都属于合理新增。
-
-### 2.5 skills 是工作流入口,不是百科
-
-`SKILL.md` 负责:
-- 定义适用场景
-- 组织执行顺序
-- 指定必要输入输出
-- 指明何时读哪些 reference
-- 定义验证与恢复规则
-
-`SKILL.md` 不负责:
-- 展开大量常识性方法论
-- 承载完整范例库
-- 细讲脚本内部实现
-- 变成“小型全书教程”
-
-### 2.6 中文化与网文化优先
-
-本系统服务于中文网络小说写作,文档语言应优先使用中文网文领域词汇。原则如下:
-- 正文叙述默认中文。
-- 优先使用网文专用名词:章纲、卷纲、钩子、爽点、微兑现、追读力、吃书、毒点、设定冲突等。
-- 字段名、CLI 子命令、frontmatter、JSON key、协议名保留必要英文。
-- 同一概念尽量只保留一个主称呼。
-
-#### 题材分类中文化
-
-现有 `genres/` 目录使用英文名(`xuanhuan`、`realistic` 等),本轮需统一为中文题材名。**当前第一版默认以番茄小说网题材分类作为映射基准**,后续允许扩展平台映射层。
-
-**男频:** 都市、玄幻、仙侠、奇幻、武侠、历史、军事、科幻、悬疑、游戏、体育、轻小说
-**女频:** 现言、古言、幻言、悬疑、轻小说
-
-CSV 知识库中的 `适用题材` 列使用上述中文名。`全部` 表示不限题材。
-
-现有 `genres/` 目录的映射关系:
-
-| 现有目录名 | 对应番茄题材 |
-|-----------|------------|
-| `xuanhuan/` | 玄幻 |
-| `realistic/` | 都市 |
-| `dog-blood-romance/` | 现言 |
-| `rules-mystery/` | 悬疑 |
-| `period-drama/` | 古言、历史 |
-| `zhihu-short/` | 短篇 / 轻小说(临时映射) |
-
----
-
-## 3. 文档分层职责
-
-本轮只重构三层:
-- `skills/`
-- `references/`
-- `scripts/`
-
-### 3.1 skills 负责什么
-
-`skills` 负责:
-- 任务入口与适用场景
-- 主流程步骤
-- 前置条件
-- 必要 references 的加载策略
-- 交付物与成功标准
-- 失败恢复与补跑规则
-
-`skills` 不负责:
-- 大段方法论教学
-- 完整案例库
-- 模型常识补课
-- 重复解释底层脚本实现
-
-### 3.2 references 负责什么
-
-`references` 负责(遵循 §2.3 四原则):
-- 绑定到具体 skill + step 的子任务指导
-- Claude 不稳定的高频缺陷补偿(缺陷补偿层)
-- Claude 知道但容易遗忘的执行要点(提醒层)
-- Claude 知道但不够深的领域知识补充(知识补充层)
-- 项目私有规范(CLI、字段、路径等)
-- 高价值正反例(真实小说片段)
-- 题材特异规则与禁区清单
-
-`references` 不负责:
-- 通用写作常识(Claude 已知且稳定执行的部分)
-- 大而空的方法论概述
-- 不带判断标准的结构模板
-- 不绑定到任何 step 的知识堆积
-
-### 3.3 scripts 负责什么
-
-`scripts` 负责:
-- 容易出错、可执行、可验证的稳定操作
-- 数据处理、验证、生成、迁移、索引等可程序化步骤
-
-`scripts` 不负责:
-- 替代 skill 的流程设计
-- 承载主业务说明
-- 替代 reference 的领域规则解释
-
-### 3.4 脚本化触发条件
-
-出现以下任一情况时,应优先评估是否脚本化,而不是继续扩写 skill 或 reference:
-
-1. 同一操作会被多个 skill 重复调用
-2. 成败可程序化判断
-3. 人工描述过长且容易误解
-4. 需要稳定过滤 / 检索 / 校验
-5. 出错代价较高,且希望避免靠人工执行记忆
-
----
-
-## 4. 本轮重写原则
-
-### 4.1 不先写抽象模板文档,直接逐 skill 设计
-
-本轮不先产出一份“统一 skill 模板”。原因:
-- 不同 skill 的功能差异很大。
-- 先写抽象模板容易过早收敛,反而束缚设计。
-- 当前更适合直接对现有 skill 做问题导向重构。
-
-但所有 skill 重写后都应满足以下共同要求:
-- 适用场景清楚
-- 前置条件清楚
-- 流程主线清楚
-- references 加载策略清楚
-- 验证与恢复规则清楚
-- 正文语言中文化、网文化
-
-### 4.2 先 skill,后 reference 正文
-
-本轮顺序固定为:
-1. 先明确每个 skill 的职责与结构。
-2. 再列出它缺哪些 reference 文件。
-3. references 正文在下一阶段逐个补齐。
-
-因此本 spec 中,references 只写:
-- 文件名
-- 作用
-- 解决的缺陷类型
-- 归属 skill
-
-不展开全文内容设计。
-
-### 4.3 reference 新增判断标准
-
-新增 reference 必须同时满足:
-
-1. **绑定检查**:能指出“哪个 skill 的哪个 step 在什么条件下加载它”;若为跨 skill 通用 reference,需写清主服务场景与次服务场景。
-2. **缺口检查**:Claude 当前在这个子任务中是稳定犯错(缺陷补偿)、容易遗忘(提醒)、还是知识不足(补充);至少命中一种。
-3. **独立性检查**:这个指导是否无法靠 skill 主流程的一两句话自然覆盖;若能内联解决,不独立建文件。
-4. **必要性检查**:如果不新增这份 reference,是否会稳定导致错误重复发生;若不会,优先通过 skill 流程、脚本或验证机制解决。
-
-四条全过才新增。
-
-### 4.4 Skill 正文必须包含的结构要素
-
-除 §4.1 的共同要求外,按优先级分层:
-
-- **P0 skills**:必须包含红旗 / 常见误区、优先级链、决策树入口三项
-- **P1 skills**:至少显式包含其中 1-2 项
-- **P2 skills**:按需采用,不强制
-
-#### 红旗 / 常见误区区块
-
-每个主链 skill 必须包含一个专门的“常见误区”列表,描述 Claude **真实会犯的思维错误**而非抽象禁令。
-
-示例(`webnovel-write`):
-
-```text
-## 常见误区
-- ❌ 认为本章简单就跳过 Step 3 审查
-- ❌ Step 5 失败后直接开始下一章(状态还在 chapter_reviewed)
-- ❌ 把全部 reference 一次性读完再开始写
-- ❌ blocking issue 存在但觉得“不严重”就跳过
-- ❌ 用文件存在性替代 chapter_status 判断
-- ❌ 润色时改了事件顺序或设定
-```
-
-#### 优先级链
-
-当多个指令来源冲突时,skill 必须写明裁决顺序:
-
-```text
-1. 用户明确要求(最高)
-2. 状态机 / 流程硬门槛(chapter_status、blocking)
-3. 项目私有约束(设定集、已有剧情)
-4. skill 默认工作流
-5. reference 建议(最低)
-```
-
-#### 决策树入口
-
-Skill 正文不只是线性步骤列表,还必须在流程开头或关键分支处提供决策判断。最少覆盖:
-- 什么情况下继续下一步
-- 什么情况下阻断
-- 什么情况下回退到上一步
-- 什么情况下需要用户裁决
-
----
-
-## 5. 技能总览矩阵
-
-| Skill | 当前主要问题 | 重写目标 | 需补 references | 优先级 |
-|------|--------------|----------|-----------------|--------|
-| `webnovel-write` | 主链过重,已有大量规则堆叠;需进一步明确主流程与引用边界 | 作为主链总控 skill,只保留主流程、闸门、交付物、恢复规则 | 高 | P0 |
-| `webnovel-review` | 结构较简,但需与 reviewer / review-pipeline / 状态机更清晰衔接 | 成为独立审查流程入口,收敛报告与阻断处理 | 中 | P0 |
-| `webnovel-plan` | 内容多、层级杂,兼有规则与参考说明 | 收敛为规划流程入口,结构化节点与卷级规划清楚 | 高 | P0 |
-| `webnovel-init` | 过重,像 mini-spec;交互采集、规则、资料混在一起 | 收敛为初始化工作流 skill,复杂知识下沉到 reference | 高 | P1 |
-| `webnovel-query` | 查询逻辑清楚,但文档像操作手册;可进一步减少低层命令暴露 | 收敛为查询 / 分析型 skill | 中 | P1 |
-| `webnovel-dashboard` | 过轻,像启动命令说明;缺验证与失败恢复结构 | 收敛为工具启动型 skill | 低 | P2 |
-| `webnovel-learn` | 过轻,像命令说明;缺边界与恢复规则 | 收敛为轻量记录型 skill | 低 | P2 |
-
----
-
-## 6. 逐个 skill 改造方案
-
-### 6.1 `webnovel-write`
-
-#### 当前问题
-- 承载了太多细节,容易继续膨胀。
-- 某些 reference 内容仍偏“方法论说明”,未完全下沉。
-- 已有状态机闸门,但结构仍偏大而全。
-
-#### 重写目标
-- 明确它是**主链总控 skill**。
-- 保留:主流程、状态推进、阻断规则、必要 references、交付物、恢复规则。
-- 明确不承载:命名细则、对话风格方法论、题材命名库、桥段范例库,这些必须下沉到 references。
-
-#### 结构改动
-建议正文只保留这些块:
-1. 目标与适用场景
-2. 常见误区(§4.4 红旗区块)
-3. 优先级链(§4.4)
-4. 前置条件与环境准备
-5. 引用加载策略(Step 级,含 CSV 检索触发条件)
-6. 主流程(Step 0.5-6,含决策树入口)
-7. 状态推进与阻断规则
-8. 充分性闸门
-9. 验证与交付
-10. 失败恢复
-
-#### 当前 SKILL.md 段落去留表
-
-| 当前段落 | 处置 | 理由 |
-|---------|------|------|
-| 目标 | 保留 | 核心定义 |
-| 执行原则 | 保留 | 闸门硬约束 |
-| 模式定义 | 保留 | 标准 / fast / minimal 区分 |
-| 引用加载等级(L0/L1/L2) | 保留 | 按需加载策略 |
-| References 列表 | 重写 | 改为按 Step 触发条件组织,加条件加载说明 |
-| 工具策略 | 保留,精简 | 只保留 CLI 入口,不展开参数细节 |
-| 准备阶段 | 保留 | preflight + 环境变量 |
-| Step 0.5-6 | 保留 | 核心流程 |
-| 问题定向参考列表 | 下沉 | 移到 reference index 或 Step 内触发条件 |
-| 充分性闸门 | 保留 | 已切到状态机 |
-| 验证与交付 | 保留 | 已用 chapter_status |
-| 失败处理 | 保留 | 补跑规则 |
-
-#### references 触发绑定
-
-以 §2.3 四原则为标准,按 Step + 触发条件组织。**md 文件直接 Read,CSV 通过检索脚本按需获取。**
-
-##### md 必读(直接 Read)
-
-| Step | 触发条件 | 加载的 reference |
-|------|---------|-----------------|
-| Step 1 | 每次执行 | `references/reading-power-taxonomy.md`、`references/genre-profiles.md`、`skills/webnovel-write/references/style-variants.md` |
-| Step 2 | 每次执行 | `references/shared/core-constraints.md`、`skills/webnovel-write/references/anti-ai-guide.md` |
-| Step 4 | 每次执行 | `skills/webnovel-write/references/polish-guide.md`、`skills/webnovel-write/references/writing/typesetting.md`、`skills/webnovel-write/references/style-adapter.md` |
-
-##### CSV 检索(调用 `reference_search.py`)
-
-| Step | 触发条件 | 检索参数 |
-|------|---------|---------|
-| Step 2 | 本章有新角色首次出场 | `--skill write --table 命名规则 --query "角色命名" --genre {题材}` |
-| Step 2 | 本章有战斗 / 对峙场景 | `--skill write --query "战斗描写" --genre {题材}` |
-| Step 2 | 本章有多角色对话 | `--skill write --query "对话声线 口吻区分"` |
-| Step 2 | 本章有情感 / 心理描写 | `--skill write --query "情感描写 心理"` |
-| Step 2 | 本章涉及高频桥段 | `--skill write --table 场景写法 --query "{桥段类型}"` |
-| Step 4 | ai_flavor issue 存在 | `--skill write --query "AI味 反例 替换"` |
-
-#### scripts 需求
-- 暂不新增脚本作为前置条件。
-- 若后续发现命名校验可程序化,可新增轻量命名检查脚本。
-
-#### 验收点
-- 主流程阅读路径明显缩短
-- 每一步只在触发条件满足时加载对应 references
-- 状态机与交付物逻辑不弱化
-- 不再在 skill 正文中展开大段常识性写作建议
-
----
-
-### 6.2 `webnovel-review`
-
-#### 当前问题
-- 流程清楚,但与 `reviewer` / `review-pipeline` / 报告落盘的边界还可再清楚。
-- 阻断与返工逻辑可进一步明确成“审查型 workflow”。
-
-#### 重写目标
-- 成为独立审查 skill:从输入章节到输出审查报告、落库、写回记录。
-- 不承载过多审查理论;理论交给 reviewer 和 references。
-
-#### 结构改动
-保留:
-1. 适用场景
-2. 常见误区(§4.4)
-3. 优先级链(§4.4)
-4. 项目根解析
-5. 引用加载
-6. 调用 reviewer(含决策树:blocking → 返工 / override)
-7. 生成报告与落库
-8. 处理 blocking 与用户决策
-9. 成功标准与恢复规则
-
-#### references 触发绑定
-
-##### md 必读
-
-| Step | 触发条件 | 加载的 reference |
-|------|---------|-----------------|
-| Step 2(加载参考) | 每次执行 | `references/shared/core-constraints.md`、`references/review-schema.md` |
-| Step 6(处理阻断) | 存在 blocking issue 需用户决策 | `references/review/blocking-override-guidelines.md` |
-
-##### CSV 检索
-
-| Step | 触发条件 | 检索参数 |
-|------|---------|---------|
-| Step 4(调用 reviewer) | ai_flavor issue 数量 ≥ 3 | `--skill review --query "AI味 反例 替换"` |
-
-#### scripts 需求
-- 不新增主流程脚本。
-- 后续若独立复检需要稳定接口,可考虑加 `review-ai-flavor-check.py`。
-
-#### 验收点
-- 阻断处理路径清楚
-- 审查产物路径稳定
-- 不再重复 reviewer 本身的详细检查维度
-
----
-
-### 6.3 `webnovel-plan`
-
-#### 当前问题
-- 文档同时承担:设定补齐、卷节拍、时间线、章纲、节点规范、写回设定。
-- 很多规则适合作为 reference,而不是主 skill 正文。
-
-#### 重写目标
-- 成为**规划主流程入口**,重点强调:
-  - 卷级目标
-  - 时间线硬约束
-  - 批量拆章流程
-  - 结构化节点产物
-- 将题材节奏、冲突设计等下沉到 reference。
-
-#### 结构改动
-建议主结构为:
-1. 目标与适用范围
-2. 常见误区(§4.4 红旗区块)
-3. 优先级链(§4.4)
-4. 前置条件
-5. 引用加载策略
-6. 规划主流程(Step 1-9,含决策树入口)
-7. 结构化节点产物要求
-8. 硬失败条件
-9. 恢复规则
-
-#### references 触发绑定
-
-##### md 必读
-
-| Step | 触发条件 | 加载的 reference |
-|------|---------|-----------------|
-| 章纲拆分 | 每次执行 | `references/outlining/plot-signal-vs-spoiler.md` |
-
-##### CSV 检索
-
-| Step | 触发条件 | 检索参数 |
-|------|---------|---------|
-| 卷级规划 | 每次执行 | `--skill plan --table 场景写法 --query "卷级结构 叙事功能"` |
-| 章纲拆分 | 新增角色出现 | `--skill plan --table 命名规则 --query "角色命名" --genre {题材}` |
-
-#### scripts 需求
-- 暂不新增。
-- 后续若时间线验证继续复杂化,可考虑补独立校验脚本。
-
-#### 验收点
-- 主流程更像“规划入口”,不是“全量教程”
-- 题材与冲突知识显著下沉到 references
-- 批次、节点、时间线规则清楚且集中
-
----
-
-### 6.4 `webnovel-init`
-
-#### 当前问题
-- 体量过大,已接近 mini-spec。
-- 用户采集、题材策略、创意约束、资料索引混在一起。
-
-#### 重写目标
-- 成为**初始化访谈与生成工作流**。
-- 主 skill 只保留信息采集流程、充分性闸门、生成与验证。
-- 题材、命名、卖点、创意约束等知识下沉到 references。
-
-#### 结构改动
-建议主结构为:
-1. 目标与适用场景
-2. 交互原则
-3. 分步采集流程
-4. 充分性闸门
-5. 生成步骤
-6. 验证与交付
-7. 最小回滚
-
-#### references 触发绑定
-
-##### md 必读
-
-| Step | 触发条件 | 加载的 reference |
-|------|---------|-----------------|
-| 卖点 / 题材采集 | 每次执行 | `references/genre-profiles.md` |
-
-##### CSV 检索
-
-| Step | 触发条件 | 检索参数 |
-|------|---------|---------|
-| 起名采集 | 用户开始设定角色 / 书名 / 势力名 | `--skill init --table 命名规则 --query "{命名对象} {题材}" --genre {题材}` |
-
-注:原 `title-patterns-and-anti-patterns.md` 和 `protagonist-flaw-patterns.md` 不直接纳入本轮缺口清单,后续若实测出现稳定缺陷再补回。
-
-#### scripts 需求
-- 暂不新增,仍以现有 `init_project.py` 为主。
-
-#### 验收点
-- skill 正文显著瘦身
-- 交互流程更清楚
-- 创意 / 题材 / 命名类细节下沉
-
----
-
-### 6.5 `webnovel-query`
-
-#### 当前问题
-- 查询流程本身合理,但文档风格偏操作手册。
-- 暴露较多低层步骤,可进一步强调“查询类型识别 + 数据源选择 + 输出格式”。
-
-#### 重写目标
-- 成为**查询 / 分析型 skill**。
-- 强调:
-  - 查询意图识别
-  - 按需加载 reference
-  - 读取最少必要数据
-  - 输出结构化回答
-
-#### 结构改动
-建议保留:
-1. Use when
-2. 项目根保护
-3. 查询类型识别
-4. 引用加载等级
-5. 查询流程
-6. 输出格式
-7. 边界
-
-#### references 触发绑定
-
-无新增刚需。当前 query skill 的参考需求主要是项目私有知识(CLI 用法、数据源),已内联在 skill 中。
-
-注:`entity-alias-resolution.md`、`foreshadowing-urgency-rules.md` 暂列为候选,待实测若输出不稳则补回。
-
-#### scripts 需求
-- 无新增刚需。
-
-#### 验收点
-- 更像查询工作流,而不是大段说明书
-- 输出风格统一
-
----
-
-### 6.6 `webnovel-dashboard`(P2,方向简述)
-
-收敛为**工具启动型 skill**:补齐环境检查、启动步骤、成功判定、常见故障处理。P2 不强制三件套,不挂独立 reference,现有功能已基本自洽。
-
----
-
-### 6.7 `webnovel-learn`(P2,方向简述)
-
-收敛为**轻量记录型 skill**:重点明确何时记录、输入结构、幂等 / 去重、写回格式、失败处理。P2 不强制三件套,是否补 `pattern-taxonomy` 视实测而定。
-
----
-
-## 7. Reference 体系设计(双轨制)
-
-本轮 reference 采用**双轨制**:流程必读型(md)+ 写作知识库型(CSV + 检索脚本)。
-
-### 7.1 双轨分工
-
-| 轨道 | 格式 | 加载方式 | 承载内容 | 示例 |
-|------|------|---------|---------|------|
-| **流程必读型** | `.md` 文件 | Skill 指定 step 直接 `Read` | 闸门规则、schema 定义、核心约束、审查标准 | `core-constraints.md`、`review-schema.md`、`polish-guide.md` |
-| **写作知识库型** | `.csv` + Python 检索脚本 | Step 遇到特定场景时调用脚本,只返回命中条目 | 写作技法、题材写法、场景灵感、命名规则、正反例 | `写作技法.csv`、`命名规则.csv`、`场景写法.csv` |
-
-分界标准:
-- **流程/契约/闸门/schema** → md(必须完整读取,不能只看片段)
-- **写作知识/技法/灵感/正反例** → CSV(条目多、按需检索、只取相关的几条)
-
-### 7.2 CSV 知识库设计
-
-#### 文件位置
-
-```
-webnovel-writer/
-  data/                          # CSV 知识库根目录
-    写作技法.csv
-    命名规则.csv
-    场景写法.csv
-  scripts/
-    reference_search.py          # 统一检索脚本(BM25)
-```
-
-#### 编码
-
-所有 CSV 文件使用 **UTF-8 with BOM**(`utf-8-sig`),确保 Windows 下 Excel 可正确打开中文。
-
-#### 通用列(所有 CSV 共有)
-
-| 列名 | 类型 | 说明 | 示例值 |
-|------|------|------|-------|
-| `编号` | string | 唯一 ID,前缀区分表 | `WT-001`、`NR-012`、`SP-003` |
-| `适用技能` | string | 粗筛,逗号分隔 | `write` 或 `init,plan` 或 `init,plan,write` |
-| `分类` | string | 场景大类 | `战斗`、`对话`、`命名`、`情感`、`场景` |
-| `层级` | string | 三层标记 | `提醒` / `缺陷补偿` / `知识补充` |
-| `关键词` | string | BM25 检索用,逗号分隔 | `打斗,武斗,对决,境界压制` |
-| `适用题材` | string | 番茄分类题材名,逗号分隔 | `全部` 或 `玄幻,仙侠` |
-
-#### 写作技法表(`写作技法.csv`)
-
-| 列名 | 说明 |
-|------|------|
-| (通用列) | |
-| `技法名称` | 技法的简短名称 |
-| `说明` | 技法描述 |
-| `正例` | 正面示范,可放长片段(200-500 字) |
-| `反例` | 反面示范,可放长片段 |
-| `修复建议` | 从反例到正例的修改方向 |
-
-示例行:
-
-```
-编号,适用技能,分类,层级,关键词,适用题材,技法名称,说明,正例,反例,修复建议
-WT-001,write,对话,缺陷补偿,"口吻趋同,对话区分,角色声线",全部,对话声线差异化,不同角色的对话应有可辨识的口语特征和节奏差异,"老张头叼着烟袋锅子,""嘿,你小子又来蹭饭?""...","两人对话风格完全一致,都是标准书面语",给每个角色设定 1-2 个口语标记词和句式习惯
-```
-
-#### 命名规则表(`命名规则.csv`)
-
-| 列名 | 说明 |
-|------|------|
-| (通用列) | |
-| `命名对象` | `角色` / `地点` / `势力` / `功法` / `道具` / `书名` |
-| `规则` | 规则描述 |
-| `正例` | |
-| `反例` | |
-
-#### 场景写法表(`场景写法.csv`)
-
-| 列名 | 说明 |
-|------|------|
-| (通用列) | |
-| `场景类型` | `告白`、`打脸`、`觉醒`、`战斗`、`谈判`、`追逐`、`日常`、`离别` 等 |
-| `模式名称` | 这种写法的名称 |
-| `说明` | 模式描述 |
-| `示例片段` | 可放长片段(200-500 字) |
-| `反面写法` | 要避免的写法 |
-
-### 7.3 检索脚本设计
-
-#### CLI 接口
-
-```bash
-# 基本用法:按当前 skill 和关键词检索
-python -X utf8 "${SCRIPTS_DIR}/reference_search.py" \
-  --skill write \
-  --query "战斗描写 境界压制" \
-  --max-results 3
-
-# 指定题材过滤
-python -X utf8 "${SCRIPTS_DIR}/reference_search.py" \
-  --skill write \
-  --query "命名 角色" \
-  --genre "玄幻" \
-  --max-results 5
-
-# 指定表
-python -X utf8 "${SCRIPTS_DIR}/reference_search.py" \
-  --skill plan \
-  --table 命名规则 \
-  --query "跨卷 角色命名" \
-  --max-results 3
-```
-
-#### 检索流程
-
-```
-1. 按 `适用技能` 列过滤(粗筛)
-2. 按 `适用题材` 列过滤(可选,若指定 --genre)
-3. 在过滤后的结果集内做 BM25 关键词检索
-4. 返回 top N 条,格式化输出
-```
-
-#### 输出格式
-
-```
-## 检索结果(写作技法)
-查询:战斗描写 境界压制 | 技能:write | 题材:玄幻 | 命中:3 条
-
-### [WT-023] 境界压制的体感描写
-- 层级:知识补充
-- 说明:通过身体反应而非数值对比来表现境界差距
-- 正例:(片段...)
-- 反例:(片段...)
-- 修复建议:...
-
-### [WT-045] ...
-```
-
-### 7.4 流程必读型 reference(md 文件,保留/新增)
-
-以下 md reference 保留或新增,由 Skill 在指定 step 直接 `Read`:
-
-| 文件 | 类型 | 服务 skill | 加载时机 |
-|------|------|-----------|---------|
-| `references/shared/core-constraints.md` | 保留 | write/Step 2 | 每次执行 |
-| `references/review-schema.md` | 保留 | write/Step 3、review/Step 4 | 每次执行 |
-| `references/shared/cool-points-guide.md` | 保留 | review | 按需 |
-| `references/shared/strand-weave-pattern.md` | 保留 | review | 按需 |
-| `references/reading-power-taxonomy.md` | 保留 | write/Step 1 | 每次执行 |
-| `references/genre-profiles.md` | 保留 | write/Step 1、init | 每次执行 |
-| `skills/webnovel-write/references/style-variants.md` | 保留 | write/Step 1 | 每次执行(差异化设计) |
-| `skills/webnovel-write/references/polish-guide.md` | 保留 | write/Step 4 | 每次执行 |
-| `skills/webnovel-write/references/anti-ai-guide.md` | 保留 | write/Step 2 | 每次执行 |
-| `skills/webnovel-write/references/style-adapter.md` | 保留 | write/Step 4 | 每次执行 |
-| `skills/webnovel-write/references/writing/typesetting.md` | 保留 | write/Step 4 | 每次执行 |
-| `references/review/blocking-override-guidelines.md` | **新增** | review/Step 6 | 存在 blocking issue 需用户决策时 |
-| `references/outlining/plot-signal-vs-spoiler.md` | **新增** | plan/章纲拆分 | 每次执行 |
-
-### 7.5 写作知识库型 reference(CSV 文件,新增)
-
-以下知识从现有 md 文件迁移或新建到 CSV:
-
-| 现有 md 文件 | 迁移到 CSV | 迁移后 md 处置 |
-|-------------|-----------|--------------|
-| `writing/combat-scenes.md` | `场景写法.csv`(场景类型=战斗) | 删除或保留为空壳指向 CSV |
-| `writing/dialogue-writing.md` | `写作技法.csv`(分类=对话) | 同上 |
-| `writing/emotion-psychology.md` | `写作技法.csv`(分类=情感) | 同上 |
-| `writing/scene-description.md` | `写作技法.csv`(分类=场景) | 同上 |
-| `writing/desire-description.md` | `写作技法.csv`(分类=情感) | 同上 |
-| `writing/genre-hook-payoff-library.md` | `场景写法.csv`(场景类型=钩子/兑现) | 同上 |
-| (新增)命名规则 | `命名规则.csv` | 无现有文件 |
-| (新增)对话声线差异化 | `写作技法.csv`(分类=对话,层级=缺陷补偿) | 无现有文件 |
-| (新增)反模板桥段 | `场景写法.csv`(层级=缺陷补偿) | 无现有文件 |
-| (新增)AI味正反例 | `写作技法.csv`(分类=AI味,层级=缺陷补偿) | 无现有文件 |
-| (新增)卷级叙事功能模式 | `场景写法.csv`(场景类型=卷级结构,适用技能=plan) | 无现有文件 |
-
-### 7.6 被过滤掉的原提案
-
-以下提案当前不纳入,但不是永久排除。判断标准:Claude 在中文网文场景下若实测输出稳定性不足,仍可后续补回。
-
-| 原提案 | 当前过滤理由 | 恢复条件 |
-|--------|------------|---------|
-| `init/title-patterns-and-anti-patterns.md` | 书名命名可作为 `命名规则.csv` 中几行条目(`命名对象=书名`) | 若 CSV 几行不够覆盖、实测书名模板化严重,升级为独立条目组 |
-| `init/protagonist-flaw-patterns.md` | Claude 通用能力可覆盖 | 若实测缺陷设计在网文场景下空泛化、标签化严重,补为 CSV 条目 |
-| `query/entity-alias-resolution.md` | 别名解析是代码逻辑(entity_linker.py) | 若代码无法覆盖的语义歧义频发,补 reference |
-| `query/foreshadowing-urgency-rules.md` | 紧急度排序已在 context-agent 实现 | 若输出解释不稳定,补 reference |
-| `learn/pattern-taxonomy.md` | learn skill 低频,分类规则内联 skill 即可 | 若分类质量持续不稳,补 CSV 条目 |
-
----
-
-## 8. 分批实施顺序
-
-### 第零批:基础设施
-- 实现 `reference_search.py`(BM25 检索脚本)
-- 建立 `data/` 目录和 3 个 CSV 文件骨架(表头 + 少量种子数据)
-- 补测试:确认检索脚本的粗筛 → 题材过滤 → BM25 流程跑通
-- 现有 `genres/` 目录名中文化映射
-
-目标:CSV 知识库基础设施就绪,后续批次可直接往里填数据。
-
-### 第一批:主链 skills(P0)
-- `webnovel-write`
-- `webnovel-review`
-- `webnovel-plan`
-- 新增 P0 skill 依赖的 md reference:`blocking-override-guidelines.md`(review)、`plot-signal-vs-spoiler.md`(plan)
-
-目标:先收敛主链工作流入口,skill 正文切到双轨 reference 加载。
-
-**冻结点:** 第一批完成后,review 确认主链 skill 结构稳定、md/CSV 触发绑定表无遗漏,再进入第二批。
-
-### 第二批:初始化与查询(P1)
-- `webnovel-init`
-- `webnovel-query`
-
-目标:收敛前置采集和查询分析文档。
-
-### 第三批:内容填充 + 辅助类(P2)
-- `webnovel-dashboard`
-- `webnovel-learn`
-- 将现有 md reference 中的写作知识迁移到 CSV
-- 逐步扩充 CSV 条目(可持续进行,不阻塞主链)
-
-目标:完成轻量技能整理,CSV 知识库进入持续填充阶段。
-
----
-
-## 9. 验收标准
-
-本轮重构完成时,应满足:
-
-1. 所有 `skills` 都有清晰的主功能定位,不再混合成”手册 + 教程 + spec”。
-2. `skills` 正文明显收敛为:流程、闸门、交付物、恢复规则。
-3. **skill 结构要素分层落地**:P0 skill 包含红旗/优先级链/决策树全三项,P1 至少 1-2 项,P2 按需(§4.4)。
-4. **reference 双轨制落地**:流程必读型 md + 写作知识库型 CSV,职责不混。
-5. `reference_search.py` 可正常检索,粗筛(适用技能)→ 题材过滤 → BM25 全链路跑通。
-6. CSV 文件均为 UTF-8 with BOM 编码,Windows Excel 可正确打开。
-7. 题材分类已中文化(当前以番茄小说网分类为默认基准),现有 `genres/` 目录完成映射。
-8. 不再以”减少 reference 数量”为目标,而以”高价值补缺”为目标。
-9. 技能正文语言完成中文化/网文化,字段和协议保留必要英文。
-10. 新增或重写后的 skill 可通过 prompt integrity 检查与人工走读。
-
----
-
-## 10. 本 spec 的边界
-
-本 spec 不处理:
-- `agents/` 重写(agent 只有 prompt 本体,不挂 reference 文件)
-- references 正文具体内容撰写(仅定义文件名、触发绑定、内容层级)
-- scripts 的完整实现细节
-- v6 迁移 spec 中已确认的架构决策(状态机、memory contract、reviewer 流程等)
-
-本 spec 只回答:
-**skills 下一步应该怎么重写,以及 references 下一步该补哪些文件、在什么条件下加载。**

+ 0 - 1198
docs/archive/superpowers/specs/2026-04-12-story-system-evolution-spec.md

@@ -1,1198 +0,0 @@
-# Webnovel Writer Story System 渐进演进 Spec
-
-> **日期**: 2026-04-12
-> **状态**: 草案 v1
-> **定位**: 基于当前系统真实状态的 superpowers 架构收敛 spec
-
----
-
-## 1. 文档定位
-
-### 1.1 这份 spec 解决什么问题
-
-[`current-system-diagnosis.md`](../../architecture/current-system-diagnosis.md) 已经明确指出:当前系统的主要问题,不是模型能力不够,而是:
-
-1. 真理源分散
-2. 缺少统一故事合同
-3. 缺少统一章节提交主链
-4. 上下文装配与数据回写都存在结构性缺口
-
-但诊断报告只回答了“哪里有问题”,没有回答“如何从当前系统稳态演进到新系统”。
-
-这份 spec 补的就是中间层:
-
-- 不重写现有项目历史
-- 不假装当前系统一无所有
-- 不直接把理想态蓝图当实施方案
-- 而是定义一条**从现状出发、逐步收束为 Story Contract 主链**的演进路线
-
-### 1.2 与其他文档的关系
-
-这份文档与现有文档的关系如下:
-
-- [`current-system-diagnosis.md`](../../architecture/current-system-diagnosis.md)
-  - 负责诊断当前系统的结构性问题
-- [`2026-04-12-story-system-pro-max-retrofit-spec.md`](./2026-04-12-story-system-pro-max-retrofit-spec.md)
-  - 负责保守改造思路
-- [`2026-04-12-webnovel-story-intelligence-system-spec.md`](./2026-04-12-webnovel-story-intelligence-system-spec.md)
-  - 负责理想态目标架构
-
-这份 spec 的定位是:
-
-- **以上三者之间的桥**
-- 用“现状基线 -> 阶段性收束 -> 最终合同系统”的方式,把诊断、retrofit、理想态串起来
-
-### 1.2.1 与 diagnosis / retrofit / ideal spec 的边界
-
-为避免这份文档与另外两份 story system spec 打架,这里明确边界:
-
-#### `current-system-diagnosis`
-
-负责回答:
-
-- 当前系统到底哪里有结构性缺口
-- 哪些问题是真问题
-- 哪些地方其实已经有半成品底座
-
-它不负责给出演进实施路径。
-
-#### `retrofit spec`
-
-负责回答:
-
-- 如何在尽量不破坏现有主链的前提下,补出最小可用的 `story_system`
-
-它偏向:
-
-- phase 1
-- 保守落地
-- 最小侵入
-
-#### `理想态 spec`
-
-负责回答:
-
-- 如果不考虑最小改动原则,最终最佳架构应该长什么样
-
-它偏向:
-
-- phase 2 以后
-- 最终目标
-- 完整合同系统
-
-#### 本 spec
-
-负责回答:
-
-- 如何从真实现状出发,把半成品中枢逐步收束成合同主链
-- 哪些旧链路先接入、哪些后降级、哪些最后替换
-
-换句话说:
-
-- 诊断文档定义问题
-- retrofit spec 定义保守补法
-- 理想态 spec 定义终局目标
-- **本 spec 定义从现状走到终局的渐进路径**
-
-### 1.3 一句话结论
-
-当前系统不是“推倒重来”型问题,而是“多个半成品中枢需要收束成一个主链”型问题。
-
-因此最优策略不是:
-
-- 再堆一个新脚本
-- 再加一层提示词补丁
-- 再给 `context_manager` 增加几条预算规则
-
-而是建立:
-
-- **统一故事合同层**
-- **统一章节提交主链**
-- **统一覆盖账本**
-- **统一事件主链**
-
-再把 `state / index / summary / memory / RAG` 统统降级为合同提交后的投影层。
-
----
-
-## 2. 当前基线
-
-### 2.1 当前系统已经存在的半成品中枢
-
-根据现状诊断,当前系统并不是完全无结构,而是已经存在以下可复用底座:
-
-1. `context_manager + context_ranker`
-   - 已能做上下文聚合、预算控制、优先级排序
-2. `genre_aliases.py + genre_profile_builder.py + genre-profiles.md`
-   - 已能做题材归一化、题材 hints 生成、题材画像构建
-3. `reference_search.py + references/csv`
-   - 已能做条目检索与轻量打分
-4. `state_manager`
-   - 已能做结构化状态回写,并承担 `state.json + SQLite` 的双写同步
-5. `index.db`
-   - 已承载实体、关系、事件、索引等结构化事实
-6. `memory writer / orchestrator / summaries`
-   - 已承担长期事实沉淀与章节摘要
-7. `override_contracts`
-   - 已经存在“违背记录”的雏形机制
-
-### 2.2 当前系统最根本的缺失
-
-真正缺失的不是某一个模块,而是四个主链能力:
-
-1. **统一故事真理源**
-   - 当前没有 `MASTER / VOLUME / CHAPTER` 合同家族作为单一真理源
-2. **统一章节提交主链**
-   - 写作后的状态回写仍是多处散写,不是一次章节提交、再多投影分发
-3. **统一设定演进账本**
-   - 当前只有追读力债务的 override 雏形,没有全局设定演进账本
-4. **统一事件主链**
-   - 当前已有很多 Delta 事件痕迹,但没有 canonical event log
-
-### 2.3 演进判断
-
-这意味着演进不能按“新增一个大模块,旧链路先不管”的方式做。
-
-必须遵守:
-
-1. 先建立统一合同
-2. 再让运行时消费合同
-3. 再让章节提交变成主链
-4. 最后把旧链路降级为投影与回退
-
-如果顺序反过来,系统只会多一个并行中枢,而不是更收敛。
-
----
-
-## 3. 设计目标与非目标
-
-### 3.1 目标
-
-本演进 spec 要达成 8 个目标:
-
-1. 建立基于当前系统的统一故事合同体系
-2. 建立合同优先的上下文装配顺序
-3. 建立写前而不是写后的强约束输入
-4. 建立大纲履约校验与 review contract
-5. 建立统一章节提交主链
-6. 建立统一 override ledger
-7. 建立统一 canonical event log
-8. 把现有模块重定位为合同系统的底盘和投影层
-
-### 3.2 非目标
-
-本 spec 明确不做以下承诺:
-
-1. 不要求一次性替换全部旧链路
-2. 不要求一次性废弃 `context_manager`、`genre_*`、`reference_search.py`
-3. 不要求知识内容自动从 md 迁移到 csv
-4. 不要求对每个 CSV 条目编写测试
-5. 不要求在第一阶段就实现完全事件驱动架构
-
-### 3.3 硬约束
-
-整个演进过程必须持续遵守以下约束:
-
-1. `AI味`、anti-AI、润色替换规则继续保留在 md,不进入 CSV
-2. CSV 知识迁移只允许人工整理与人工录入
-3. 演进不得制造新的双真理源
-4. 任一阶段的 implementation plan 都必须把**文档更新**作为显式任务
-
----
-
-## 4. 核心设计原则
-
-### 4.1 收束,不并列
-
-新系统的职责必须是**收束旧中枢**,不是与旧中枢并列竞争。
-
-反模式:
-
-- 新增 `story_system`,但 `context_manager` 仍单独输出另一套题材判断
-- 新增合同文件,但 `genre-profiles.md` 仍作为同级真源参与写作输入
-- 新增章节提交对象,但状态回写仍绕过它直接散写
-
-### 4.2 合同优先,运行时补充
-
-运行时输入应拆成两层:
-
-1. **合同层**
-   - 题材调性、毒点红线、系统边界、卷章目标
-2. **事实层**
-   - state、recent summaries、entity facts、reader signals
-
-原则上:
-
-- 合同负责“应该怎么写”
-- 事实负责“已经发生了什么”
-
-### 4.3 提交优先,投影随后
-
-章节完成后的正确顺序必须是:
-
-1. 形成结构化章节提交对象
-2. 校验其是否满足合同和大纲
-3. 只有提交通过,才向 `state / index / summary / memory` 投影
-
-而不是:
-
-- 先散写
-- 再在各模块里尽量圆回来
-
-### 4.4 显式覆盖,禁止静默漂移
-
-任何设定变化必须回答四个问题:
-
-1. 变了什么
-2. 基于哪一层改的
-3. 为什么改
-4. 是否影响上层合同
-
-如果回答不了,就不允许把它当作合法设定演进。
-
-### 4.5 先补主链,再补精度
-
-本项目当前最缺的是主链,不是精度。
-
-因此演进顺序必须是:
-
-1. 先补合同主链
-2. 再补提交主链
-3. 再补事件主链
-4. 最后再提升检索、推理、多标签融合精度
-
----
-
-## 5. 诊断问题到演进工作流的映射
-
-现状诊断中的 10 个问题,可以归并为 5 条演进工作流。
-
-### 5.1 真理源收束工作流
-
-对应问题:
-
-- 1. 多头真理
-- 2. Override Ledger 缺口
-
-要解决的事:
-
-- 引入合同家族作为唯一故事真理源
-- 把 `override_contracts` 从追读力债务专用,扩展为故事设定演进账本
-
-### 5.2 上下文合同化工作流
-
-对应问题:
-
-- 3. 上下文截断黑洞
-- 4. 知识延迟绑定与泛化割裂
-
-要解决的事:
-
-- 让 `context_manager` 合同优先
-- 把通用知识预聚合为当前书的专属合同,而不是让写作时临时散查
-
-### 5.3 写前校验工作流
-
-对应问题:
-
-- 5. 事后验尸
-- 6. 缺乏大纲履约校验
-
-要解决的事:
-
-- 建立 review contract
-- 建立写前禁区、写前消歧域、写后履约 diff
-
-### 5.4 章节提交工作流
-
-对应问题:
-
-- 7. 跨存储事务割裂
-- 8. 后置消歧污染风险
-- 9. 消歧警告向后传染
-
-要解决的事:
-
-- 引入统一章节提交对象
-- 把消歧和事实提取前移到提交校验链,而不是回写尾部兜底
-
-### 5.5 事件主链工作流
-
-对应问题:
-
-- 10. 事件只有痕迹没有主链
-
-要解决的事:
-
-- 定义 canonical event log
-- 让“事件”而不是“覆盖后的状态”成为演进触发器
-
----
-
-## 6. 演进后的总体架构
-
-### 6.1 总体分层
-
-演进后的系统分为六层:
-
-1. `Knowledge Layer`
-2. `Reasoning Layer`
-3. `Contract Layer`
-4. `Runtime Assembly Layer`
-5. `Chapter Commit Layer`
-6. `Projection Layer`
-
-### 6.2 总体链路
-
-```text
-用户意图 / 书籍题材诉求
-        ↓
-Reasoning Layer
-        ↓
-Story Contract Generator
-        ↓
-MASTER / VOLUME / CHAPTER / REVIEW / ANTI_PATTERNS
-        ↓
-context_manager(contract-first)
-        ↓
-规划 / 写作 / 审查
-        ↓
-CHAPTER_COMMIT
-        ↓
-Projection Writers
-        ↓
-state / index / summaries / memory / rag
-```
-
-### 6.3 最终判断
-
-未来系统的主链不应是:
-
-- `reference_search -> prompt -> reviewer -> data agent -> state`
-
-而应是:
-
-- `knowledge -> reasoning -> contract -> runtime pack -> chapter commit -> projections`
-
----
-
-## 7. 单一真理源与优先级规则
-
-### 7.1 故事真理源
-
-故事系统的唯一真理源应为合同家族:
-
-1. `MASTER_SETTING.json`
-2. `VOLUME_BRIEF.json`
-3. `CHAPTER_BRIEF.json`
-4. `REVIEW_CONTRACT.json`
-5. `anti_patterns.json`
-
-其中:
-
-- 分层真源是 `MASTER / VOLUME / CHAPTER`
-- `anti_patterns.json` 是派生视图,不反向成为真源
-- `REVIEW_CONTRACT.json` 是审查用派生合同
-
-### 7.2 运行时优先级
-
-运行时拼上下文时,优先级固定为:
-
-1. `chapter contract`
-2. `volume contract`
-3. `master contract`
-4. `题材与调性推理.csv`
-5. `genre-profiles.md`
-6. `templates/genres/*.md`
-7. 其他局部 reference
-
-一旦合同存在:
-
-- `context_manager`
-- `webnovel-plan`
-- `webnovel-write`
-- `webnovel-review`
-
-都不得再输出与合同冲突的全局系统判断。
-
-### 7.3 写后真理源
-
-章节完成后,真理链固定为:
-
-1. `CHAPTER_COMMIT`
-2. 投影到 `state`
-3. 投影到 `index`
-4. 投影到 `summaries`
-5. 投影到 `memory`
-
-也就是说:
-
-- `state / index / summary / memory` 不再是章节事实的并列真源
-- 它们是 `CHAPTER_COMMIT` 的派生投影
-
----
-
-## 8. 合同体系设计
-
-### 8.1 合同家族
-
-完整合同家族定义如下:
-
-1. `MASTER_SETTING`
-2. `VOLUME_BRIEF`
-3. `CHAPTER_BRIEF`
-4. `REVIEW_CONTRACT`
-5. `ANTI_PATTERNS`
-6. `CHAPTER_COMMIT`
-
-这里新增 `CHAPTER_COMMIT`,原因很简单:
-
-- 前五类合同负责“写之前”
-- `CHAPTER_COMMIT` 负责“写之后”
-
-如果没有它,系统就无法真正修复诊断里关于回写割裂、履约丢失、事件断链的问题。
-
-### 8.2 合同职责
-
-#### `MASTER_SETTING`
-
-负责全书级稳定系统:
-
-- 题材与调性
-- 世界规则
-- 核心人设
-- 金手指边界
-- 全局毒点
-- 全局 override policy
-
-#### `VOLUME_BRIEF`
-
-负责卷级系统:
-
-- 本卷冲突轴
-- 本卷兑现目标
-- 本卷阶段性角色关系
-- 本卷节奏波形
-- 本卷补充红线
-
-#### `CHAPTER_BRIEF`
-
-负责本章执行:
-
-- 本章目标
-- 本章场景策略
-- 本章 hook
-- must cover 节点
-- 本章禁区
-- 本章局部 override
-
-#### `REVIEW_CONTRACT`
-
-负责本次审查必须检查的事项:
-
-- blocking rules
-- 题材特定风险
-- 大纲履约检查项
-- 消歧检查项
-- 高风险事实校验项
-
-#### `ANTI_PATTERNS`
-
-负责将可见层级的所有红线聚合为运行时平面视图。
-
-#### `CHAPTER_COMMIT`
-
-负责承载本章最终提交结果:
-
-- 使用了哪些合同
-- 实际发生了哪些事实
-- 实际产生了哪些事件
-- 是否完成了 outline/mandatory nodes
-- 消歧是否通过
-- review 是否通过
-- 哪些投影已完成
-
-### 8.3 字段类型
-
-仍采用三类字段策略:
-
-1. `locked`
-2. `append_only`
-3. `override_allowed`
-
-并保留 `lock_policy`:
-
-1. `system_locked`
-2. `user_locked`
-3. `story_locked`
-
-### 8.4 覆盖规则
-
-优先级固定为:
-
-1. `chapter`
-2. `volume`
-3. `master`
-
-但这不是静默覆盖,而是必须记录:
-
-- `field`
-- `base_value`
-- `override_value`
-- `source_level`
-- `reason`
-- `reason_tag`
-- `approved_by`
-
-### 8.5 override ledger 的新定位
-
-当前 `override_contracts` 不能废弃,而应演进为统一 override ledger 的底座。
-
-演进后的职责:
-
-1. 记录追读力债务层面的软违背
-2. 记录卷章对上层故事合同的受控覆盖
-3. 记录需要上提到 `amend-master / amend-volume` 的提案
-
-最终应区分三类记录:
-
-1. `soft_deviation`
-2. `contract_override`
-3. `amend_proposal`
-
----
-
-## 9. 章节提交主链设计
-
-### 9.1 为什么必须新增提交主链
-
-当前系统的问题不是“写完后没保存”,而是“写完后被拆成多处异步散写”。
-
-这会导致:
-
-- 状态不同步
-- 消歧后置污染
-- 履约丢失
-- 事件断链
-
-所以必须把章节完成后的提交改为:
-
-- 先形成一个统一提交对象
-- 再由投影器把它分发到各存储
-
-### 9.2 `CHAPTER_COMMIT` 最小结构
-
-最小应包含:
-
-- `meta`
-- `contract_refs`
-- `outline_snapshot`
-- `review_result`
-- `fulfillment_result`
-- `disambiguation_result`
-- `accepted_events`
-- `state_deltas`
-- `entity_deltas`
-- `projection_status`
-
-### 9.3 提交流程
-
-标准链路应为:
-
-1. 读取 `CHAPTER_BRIEF`
-2. 写作完成
-3. 生成 `REVIEW_CONTRACT`
-4. 完成审查
-5. 生成 `CHAPTER_COMMIT` 草案
-6. 校验履约、消歧、blocking rules
-7. 通过后标记 commit accepted
-8. 投影到各存储
-
-### 9.4 投影层职责
-
-投影层至少分成四个 writer:
-
-1. `state_projection_writer`
-2. `index_projection_writer`
-3. `summary_projection_writer`
-4. `memory_projection_writer`
-
-未来可选:
-
-5. `rag_projection_writer`
-
-### 9.5 失败语义
-
-一旦 `CHAPTER_COMMIT` 未通过,不允许:
-
-1. 部分写入 `state`
-2. 部分写入 `index`
-3. 先写摘要再回头补状态
-4. 让下一章读取未确认事实
-
-也就是说:
-
-- “章节已生成”不等于“章节已提交”
-- 只有 `commit accepted` 才能进入事实主链
-
----
-
-## 10. canonical event log 设计
-
-### 10.1 当前问题
-
-当前系统已经有:
-
-- `state_changes`
-- `relationship_events`
-- `timeline_events`
-- `world_rules`
-- `open_loops`
-- `reader_promises`
-
-但这些都还是局部事件,没有统一事件主链。
-
-### 10.2 演进目标
-
-应新增统一事件视角:
-
-- 所有章节提交都必须产出 `accepted_events`
-- 投影层基于事件更新状态,而不是只根据最终值覆盖
-
-### 10.3 事件类型
-
-最小事件族建议包括:
-
-1. `character_state_changed`
-2. `relationship_changed`
-3. `world_rule_revealed`
-4. `world_rule_broken`
-5. `power_breakthrough`
-6. `artifact_obtained`
-7. `promise_created`
-8. `promise_paid_off`
-9. `open_loop_created`
-10. `open_loop_closed`
-
-### 10.4 事件与合同的关系
-
-事件不直接修改 `MASTER / VOLUME / CHAPTER`。
-
-正确做法是:
-
-1. 事件先进入 `CHAPTER_COMMIT.accepted_events`
-2. 若事件触发上层设定变更条件,则生成 `amend proposal`
-3. 人工确认后再更新上层合同
-
-这样才能兼顾:
-
-- 文学反转的灵活性
-- 合同系统的稳定性
-
----
-
-## 11. 写前校验与写后校验
-
-### 11.1 写前校验
-
-写前必须显式检查:
-
-1. 当前章可见合同是否存在
-2. 本章禁区是否完整
-3. 是否存在高优先级消歧 pending
-4. must cover 是否明确
-5. 当前章是否需要局部 override 摘要
-
-### 11.2 写后校验
-
-写后必须显式检查:
-
-1. `blocking rules`
-2. `mandatory_nodes` 是否履约
-3. `anti_patterns` 是否命中
-4. 关键实体命名是否稳定
-5. 是否产生需要上提的设定修改
-
-### 11.3 大纲履约机制
-
-当前系统的缺口之一,是只记录“写了什么”,不校验“该写的写了没有”。
-
-因此 `CHAPTER_COMMIT` 必须新增:
-
-- `planned_nodes`
-- `covered_nodes`
-- `missed_nodes`
-- `extra_nodes`
-
-最低要求:
-
-- `missed_nodes` 非空时,不允许静默提交成功
-
-### 11.4 消歧机制前移
-
-消歧不能只在尾部回写阶段兜底。
-
-应拆成两段:
-
-1. 写前提供 `disambiguation domain`
-   - 当前章允许出现的高频实体、别名、称谓集合
-2. 写后在 `CHAPTER_COMMIT` 阶段再做提交校验
-
-只有无法通过两段机制解决时,才进入 `disambiguation_pending`。
-
----
-
-## 12. 现有模块的演进路径
-
-### 12.1 `reference_search.py`
-
-新定位:
-
-- 底层 primitive
-- CSV 搜索内核
-- 不再承担系统聚合职责
-
-### 12.2 `context_manager.py`
-
-新定位:
-
-- 合同优先的运行时装配器
-
-演进顺序:
-
-1. phase 1 保留原有结构
-2. phase 1 新增 `story_contract` section
-3. phase 2 固定 pack 优先级为 `chapter -> volume -> master -> old profile`
-4. phase 3 移除与“全局系统生成”重叠的逻辑
-
-### 12.3 `genre_aliases.py / genre_profile_builder.py / genre-profiles.md`
-
-新定位:
-
-- route table 建设期间的活跃种子源
-
-演进顺序:
-
-1. 人工把稳定字段迁入 `题材与调性推理.csv`
-2. `story_system` 优先读 CSV
-3. `genre-profiles.md` 退化为回退源和参考源
-
-### 12.4 `state_manager`
-
-新定位:
-
-- `CHAPTER_COMMIT` 的投影写入器协调层
-
-它不再承担“章节事实真源”的角色,而只负责:
-
-- 把已接受 commit 的状态投影写入 `state.json`
-- 与 SQLite 同步
-- 维护局部原子写和恢复机制
-
-### 12.5 `index.db`
-
-新定位:
-
-- 事实检索层
-- 事件索引层
-- 审计回溯层
-
-不再承担“故事规则主源”职责。
-
-### 12.6 `memory writer / orchestrator / summaries`
-
-新定位:
-
-- 事实补充层
-- 历史回顾层
-- 伏笔回收层
-
-阶段要求:
-
-1. phase 1 可继续沿用现状
-2. phase 2 开始只消费已 accepted 的 `CHAPTER_COMMIT`
-3. phase 3 与 canonical event log 对齐
-
-### 12.7 `override_contracts`
-
-新定位:
-
-- 统一 override ledger 的底座
-
-不能继续只服务追读力债务。
-
-### 12.8 `scripts/data_modules/config.py`
-
-新定位:
-
-- 继续作为项目级统一配置入口
-
-演进约束:
-
-1. phase 1 直接复用现有 `DataModulesConfig`
-2. `story_system` 新增配置必须挂在明确命名空间下
-3. 禁止为 `story_system` 平行再造一套完全独立的配置树
-
-否则后果会非常直接:
-
-- runtime 一套预算
-- contract 一套预算
-- projection 一套路径
-
-三套配置分叉后,合同系统会在工程层面重新失真。
-
----
-
-## 13. 分阶段演进计划
-
-### 13.1 Phase 0:现状收束准备
-
-目标:
-
-- 不改主流程,只明确真源和优先级
-
-交付:
-
-1. 统一文档化当前真源优先级
-2. 明确 `context_manager` 的 contract 注入口
-3. 明确 `genre-profiles.md` 的过渡期定位
-4. 明确 `override_contracts` 的扩展方向
-
-### 13.2 Phase 1:合同种子层
-
-目标:
-
-- 让系统第一次拥有稳定合同
-
-交付:
-
-1. `题材与调性推理.csv`
-2. 最小 `MASTER_SETTING`
-3. 最小 `CHAPTER_BRIEF`
-4. `anti_patterns.json`
-5. `context_manager` 读取合同
-
-此阶段仍允许:
-
-- 保留旧写作链
-- 保留 `genre-profiles.md` 回退
-- 不引入 `VOLUME_BRIEF`
-
-### 13.3 Phase 2:合同优先运行时
-
-目标:
-
-- 让写作、规划、审查都以合同为主输入
-
-交付:
-
-1. `VOLUME_BRIEF`
-2. `REVIEW_CONTRACT`
-3. 写前禁区与消歧域
-4. 大纲履约 diff
-5. `context_manager` contract-first pack
-
-此阶段的关键变化:
-
-- “临时拼资料”不再是默认路径
-- “合同优先 + 局部 reference 按需加载”成为默认路径
-
-### 13.4 Phase 3:章节提交主链
-
-目标:
-
-- 让所有事实回写经过统一章节提交对象
-
-交付:
-
-1. `CHAPTER_COMMIT`
-2. 四类 projection writers
-3. accepted / rejected commit 语义
-4. 写后回写改为 commit 驱动
-
-此阶段完成后:
-
-- 章节事实真源将从散写转为统一提交对象
-
-### 13.5 Phase 4:统一事件主链
-
-目标:
-
-- 让事件成为系统演进的正式输入
-
-交付:
-
-1. canonical event log
-2. 事件到投影的稳定映射
-3. 事件到 amend proposal 的触发规则
-
-### 13.6 Phase 5:旧链路降级
-
-目标:
-
-- 把旧中枢从主链降级为回退或投影层
-
-退出条件:
-
-1. 合同已成为默认主输入
-2. `CHAPTER_COMMIT` 已成为默认提交主链
-3. `genre-profiles.md` 已完成高频题材迁移
-4. `context_manager` 不再独立生成与合同冲突的全局系统判断
-
----
-
-## 14. 数据与目录建议
-
-### 14.1 合同目录
-
-本项目已有权威**运行时**目录术语,后续 story system 必须复用,不得自造新说法。
-
-需要先明确一件事:
-
-- 当前这个仓库只是**插件源码开发目录**
-- 它不是插件安装后的运行时根目录
-- story system 的落盘路径必须基于**Claude Code 安装后的真实运行目录模型**来定义
-
-因此后续所有路径说明,都应基于运行时的三层目录:
-
-1. `CLAUDE_PLUGIN_ROOT`
-   - Claude Code 插件安装目录
-   - 存放 `skills/ agents/ scripts/ references/`
-   - **不允许**在这里写入小说项目数据
-2. `WORKSPACE_ROOT`
-   - 当前工作区根目录,可通过 `.claude/.webnovel-current-project` 指针解析当前书项目
-3. `PROJECT_ROOT`
-   - **书项目根目录**
-   - 定义为:**包含 `.webnovel/state.json` 的目录**
-
-`Story Contract` 和 `CHAPTER_COMMIT` 都是**某一本书**的持久化产物,因此必须落在 `PROJECT_ROOT`,而不是:
-
-- 当前源码仓库目录
-- `CLAUDE_PLUGIN_ROOT`
-- `WORKSPACE_ROOT`
-
-基于当前已有真实目录,可以直接参考:
-
-- `WORKSPACE_ROOT = D:\wk\xiaoshuo`
-- `PROJECT_ROOT = D:\wk\xiaoshuo\凡人资本论`
-- 指针文件 = `D:\wk\xiaoshuo\.claude\.webnovel-current-project`
-
-该例也说明两件事:
-
-1. `PROJECT_ROOT` 是工作区内的某一本书目录
-2. `PROJECT_ROOT` 自身可以是一个独立 git 仓库(例如当前示例里就存在 `.git/`)
-
-建议目录如下:
-
-```text
-WORKSPACE_ROOT/
-├── .claude/
-│   └── .webnovel-current-project
-├── 小说A/
-├── 小说B/
-└── ...
-
-PROJECT_ROOT/
-├── .git/                  # 可选:书项目自身可独立版本管理
-├── 正文/
-├── 大纲/
-├── 设定集/
-├── 审查报告/
-├── .webnovel/
-└── .story-system/
-    ├── MASTER_SETTING.json
-    ├── MASTER_SETTING.md
-    ├── anti_patterns.json
-    ├── anti_patterns.md
-    ├── volumes/
-    │   ├── volume_001.json
-    │   └── volume_001.md
-    ├── chapters/
-    │   ├── chapter_001.json
-    │   └── chapter_001.md
-    ├── reviews/
-    │   ├── chapter_001.review.json
-    │   └── chapter_001.review.md
-    └── commits/
-        ├── chapter_001.commit.json
-        └── chapter_001.commit.md
-```
-
-这里的 `.story-system/` 必须明确为:
-
-- **相对于 `PROJECT_ROOT`**
-
-而不是:
-
-- 当前源码仓库目录
-- `CLAUDE_PLUGIN_ROOT`
-- `WORKSPACE_ROOT`
-- 任意当前工作目录
-
-如果后续 implementation plan 中出现“根目录”表述,必须显式区分:
-
-1. `CLAUDE_PLUGIN_ROOT`
-2. `WORKSPACE_ROOT`
-3. `PROJECT_ROOT`
-
-### 14.1.1 路径解析约束
-
-所有运行时入口都应遵守现有统一规则:
-
-1. 允许外部传入 `WORKSPACE_ROOT`
-2. 统一经 `resolve_project_root(...)` 解析到真实 `PROJECT_ROOT`
-3. 所有 `.story-system` 读写都基于解析后的 `PROJECT_ROOT`
-
-这意味着:
-
-- CLI 可以接受 `--project-root`,但它实际允许传入“书项目根目录或工作区根目录”
-- 入口层负责解析
-- 合同层和提交层只认最终解析后的 `PROJECT_ROOT`
-- 如果用户当前只站在 `WORKSPACE_ROOT`,也应先经统一入口解析到真实书项目,再操作 `.story-system/`
-
-### 14.2 真源规则
-
-规则固定为:
-
-1. `*.json` 是真源
-2. `*.md` 是只读渲染产物
-3. `reviews/` 和 `commits/` 也遵循相同规则
-
-### 14.3 命名规范
-
-统一使用零填充:
-
-- `volume_001`
-- `chapter_001`
-
-不要把自然语言标题写进文件名。
-
----
-
-## 15. 知识层承接策略
-
-### 15.1 CSV 的职责
-
-CSV 继续承担:
-
-- 条目知识
-- 路由知识
-- 结构化红线
-- 检索触发词与同义词
-
-### 15.2 MD 的职责
-
-MD 继续承担:
-
-- 方法论
-- 审查规则
-- anti-AI
-- 润色、口吻、排版规范
-
-### 15.3 当前阶段的结论
-
-在演进完成前,不应说“CSV 已完全替代 MD”。
-
-正确说法是:
-
-- CSV 承担规则与路由
-- MD 承担方法论与执行手册
-- 合同负责把当前书真正需要的那部分结构化知识前置收束出来
-
----
-
-## 16. 测试与验证策略
-
-### 16.1 总原则
-
-不做“每条知识点一个测试”的重型方案。
-
-### 16.2 必须验证的内容
-
-最小验证集应覆盖:
-
-1. 合同生成成功
-2. 覆盖规则正确
-3. `anti_patterns` 聚合正确
-4. `context_manager` 能正确读取合同
-5. `REVIEW_CONTRACT` 能正确表达 blocking rules
-6. `CHAPTER_COMMIT` 能阻止未通过校验的回写
-7. projection writers 只消费 accepted commit
-8. 事件能进入 canonical event log
-
-### 16.3 不需要做的事
-
-不需要:
-
-1. 为每条 CSV 内容单独断言
-2. 为每个知识点做机械语义测试
-3. 把数据内容测试做成主工作量
-
----
-
-## 17. 文档与运维要求
-
-### 17.1 文档更新要求
-
-后续任何 implementation plan 都必须显式包含:
-
-1. 合同 schema 文档更新
-2. 目录结构文档更新
-3. 运行流程文档更新
-4. 迁移说明文档更新
-
-### 17.2 运维接入要求
-
-`.story-system` 不得成为运维盲区。
-
-至少应接入:
-
-1. preflight
-2. dashboard
-3. health check
-4. backup / restore
-
-### 17.3 健康检查最低项
-
-最低应检查:
-
-1. `MASTER_SETTING.json` 是否存在
-2. 合同 schema 是否可读
-3. `chapter commit` 是否存在 rejected 未处理积压
-4. projection status 是否出现长期未完成
-
----
-
-## 18. 最终架构结论
-
-这份演进 spec 的最终判断可以压缩成四句话:
-
-1. 当前系统的主要问题不是模块太少,而是主链缺失
-2. 现有的 `context_manager / genre_* / state_manager / memory / override_contracts` 都应保留,但要重定位
-3. 真正要新增的核心不是又一个检索入口,而是 `Story Contract + Chapter Commit + Override Ledger + Canonical Event Log`
-4. 当合同成为写前真源、提交成为写后真源后,系统才算真正从“多中枢缝合”进化为“统一故事操作系统”
-
----
-
-## 19. 实施建议
-
-如果后续进入开发,不建议直接从“事件总线”开工。
-
-正确顺序应是:
-
-1. 先把合同种子层跑通
-2. 再让运行时 contract-first
-3. 再建立 `CHAPTER_COMMIT`
-4. 最后统一事件主链
-
-否则项目会先陷入“事件建模很完整,但写作主链仍然散乱”的伪进展。
-
-这份 spec 的直接后续产物应当是:
-
-- implementation plan
-
-并且该 plan 必须显式包含:
-
-1. 文档更新任务
-2. 目录与路径语义更新任务
-3. runtime 接入点更新任务

+ 0 - 878
docs/archive/superpowers/specs/2026-04-12-story-system-pro-max-retrofit-spec.md

@@ -1,878 +0,0 @@
-# Webnovel Writer Story System Pro Max 架构改造 Spec
-
-> 日期:2026-04-12
-> 状态:草案 v1
-> 目标:在不破坏现有 `reference_search.py` 与 CSV 检索契约的前提下,为 Webnovel Writer 增加“题材推理 + 多表聚合 + Master/Chapter 持久化覆盖”能力。
-
----
-
-## 1. 文档定位
-
-### 1.1 与既有 spec 的关系
-
-本 spec 是以下文档的补充,不替代它们:
-
-- `2026-04-02-harness-v6-design.md`
-- `2026-04-09-skills-restructure-and-reference-gaps.md`
-
-其中:
-
-- `2026-04-02-harness-v6-design.md` 负责主流程与 harness 架构。
-- `2026-04-09-skills-restructure-and-reference-gaps.md` 负责 `skills / references / scripts` 的职责边界与 reference 设计原则。
-- **本 spec** 负责在现有 CSV 知识库之上,补出一个新的系统层:`Story System`。
-
-### 1.2 本 spec 要解决的问题
-
-当前系统已经能做离散检索,但仍存在三个结构性问题:
-
-1. 检索结果是“散条目”,不是“系统设定”。
-2. 大模型看完单次搜索结果后,容易遗忘全局约束与毒点。
-3. 不同章节缺少稳定的“局部覆盖全局”的持久化承载方式。
-
-因此需要引入一个新的上层能力:
-
-- 先做题材/流派层面的全局路由
-- 再做多表聚合
-- 最后持久化为 `MASTER + Chapter Overrides`
-
----
-
-## 2. 目标与非目标
-
-### 2.1 目标
-
-本轮改造目标只有五个:
-
-1. 新增一张“题材与调性推理”路由表,用来把模糊题材输入转成结构化写作方向。
-2. 新增 `story_system.py`,作为现有 CSV 检索之上的聚合器。
-3. 定义 `StorySystemDict` 的稳定数据契约。
-4. 建立 `.story-system/` 的持久化规范,并明确 `Master + Overrides` 的覆盖规则。
-5. 为后续 skills / prompt 集成预留稳定挂点。
-
-### 2.2 非目标
-
-本轮**不做**以下事情:
-
-1. 不替换或改写现有 `reference_search.py` 的职责。
-2. 不把所有 md reference 全量迁入 CSV。
-3. 不强制把所有现有 CSV 表的毒点字段改成同一个列名。
-4. 不在本 spec 中假定某个具体 `SKILL.md` 文件一定存在于当前仓库。
-5. 不要求重型测试矩阵,也不要求对每个知识点逐条写测试。
-
----
-
-## 3. 设计原则
-
-### 3.1 保留底层检索 primitive
-
-[`reference_search.py`](D:/wk/novel%20skill/webnovel-writer/webnovel-writer/scripts/reference_search.py) 当前已经稳定承担以下职责:
-
-- 读取 CSV
-- 按 `skill / table / query / genre` 过滤
-- 做 BM25-lite 评分
-- 返回稳定 JSON envelope
-
-该脚本已有回归测试保护,见:
-
-- [`test_reference_search.py`](D:/wk/novel%20skill/webnovel-writer/webnovel-writer/scripts/tests/test_reference_search.py)
-
-因此本轮不应把它改造成系统级聚合器。正确分层应为:
-
-- `reference_search.py`:底层单次搜索 primitive
-- `story_system.py`:系统级 orchestration 与持久化入口
-
-### 3.2 不强推现有表结构大改
-
-现有 CSV 契约已在 [`README.md`](D:/wk/novel%20skill/webnovel-writer/webnovel-writer/references/csv/README.md) 中明确,当前内容量也已经较大。
-
-本轮应坚持:
-
-- **新增优于重写**
-- **映射优于大规模改列名**
-- **只对真正缺失的能力加新结构**
-
-### 3.3 Master 与 Chapter 必须可机读
-
-如果 `.story-system/` 只保存“给人看的 markdown”,后续 agent 仍需要再次解析半结构化文本,容易变脆。
-
-因此本轮持久化必须采用**双产物**:
-
-- Markdown:给人读
-- JSON:给脚本和 agent 读
-
-### 3.4 覆盖规则必须显式,不允许模糊覆盖
-
-不能只写“chapter 覆盖 master”。
-
-必须把字段分成三类:
-
-1. `locked`:禁止局部覆盖
-2. `append_only`:局部只能补充
-3. `override_allowed`:局部允许覆盖
-
-否则局部设定会轻易冲掉全局设定与全局红线。
-
-### 3.5 Anti-Patterns 只做归一化聚合,不做强行同名
-
-现有各表的“毒点/误区”字段不统一,例如:
-
-- `场景写法.csv`:`反面写法`
-- `写作技法.csv`:`常见误区`
-- `爽点与节奏.csv`:`常见崩盘误区`
-- `人设与关系.csv`:`忌讳写法`
-
-这本身没有问题。
-
-本轮正确做法是:
-
-- 在 `story_system.py` 内建立 anti-pattern source field 映射
-- 在渲染阶段统一归一化为 `Anti-Patterns`
-- 只对真正缺失毒点承载的表,新增专门字段
-
----
-
-## 4. 当前基线
-
-### 4.1 当前可用 CSV 表
-
-当前 `references/csv` 中已存在:
-
-- `命名规则.csv`
-- `场景写法.csv`
-- `写作技法.csv`
-- `桥段套路.csv`
-- `人设与关系.csv`
-- `爽点与节奏.csv`
-- `金手指与设定.csv`
-
-### 4.2 当前 CSV 通用契约
-
-现有通用列已稳定:
-
-- `编号`
-- `适用技能`
-- `分类`
-- `层级`
-- `关键词`
-- `意图与同义词`
-- `适用题材`
-- `大模型指令`
-- `核心摘要`
-- `详细展开`
-
-因此新增表应继续遵守这个契约,而不是重新发明一套列体系。
-
-### 4.3 当前缺失能力
-
-当前系统缺失的不是“知识量”,而是以下能力:
-
-1. 对模糊题材输入做全局路由的能力
-2. 在多张表之间做分层聚合的能力
-3. 把聚合结果稳定落地为章节级覆盖文档的能力
-
----
-
-## 5. 新增数据结构:题材与调性推理.csv
-
-### 5.1 文件位置
-
-新增文件:
-
-- `webnovel-writer/references/csv/题材与调性推理.csv`
-
-建议前缀:
-
-- `GR-`
-
-### 5.2 文件职责
-
-这不是普通知识条目表,而是**路由表**。
-
-它的职责不是提供某一个桥段/某一种技法,而是回答:
-
-- 这个题材的大基调是什么
-- 这个题材的节奏应该怎么起
-- 这个题材优先查哪些基础表
-- 这个题材优先查哪些动态表
-- 这个题材绝对不能碰哪些毒点
-
-### 5.3 列设计
-
-除通用列外,新增以下专属列:
-
-| 列名 | 必填 | 说明 |
-|------|------|------|
-| `题材/流派` | 是 | 主标签,如“赘婿流”“赛博朋克黑客流”“规则动物园” |
-| `题材别名` | 是 | 同义词、平台黑话、俗称,使用 `|` |
-| `核心调性` | 是 | 全局情绪与气质 |
-| `节奏策略` | 是 | 开局节奏、兑现节奏、章节节拍 |
-| `主冲突模板` | 否 | 默认冲突骨架 |
-| `必选爽点` | 否 | 该题材高频必备交付 |
-| `强制禁忌/毒点` | 是 | 题材级全局红线 |
-| `推荐基础检索表` | 是 | 如 `命名规则|人设与关系|金手指与设定` |
-| `推荐动态检索表` | 是 | 如 `桥段套路|爽点与节奏|场景写法` |
-| `基础检索权重` | 否 | 对基础表的排序提示 |
-| `动态检索权重` | 否 | 对动态表的排序提示 |
-| `默认查询词` | 否 | 当用户输入过于模糊时的默认扩展检索词 |
-
-### 5.4 设计说明
-
-这张表必须能同时服务三类输入:
-
-1. 明确题材输入
-   例如:`赘婿流`
-
-2. 组合题材输入
-   例如:`赛博朋克黑客流`
-
-3. 模糊风格输入
-   例如:`压抑一点,后面爆`
-
-因此 `题材别名` 和 `默认查询词` 是必要字段,不是可有可无。
-
----
-
-## 6. Anti-Patterns 归一化规则
-
-### 6.1 现有表的归一化映射
-
-本轮不强制全表改名,采用映射层:
-
-```python
-ANTI_PATTERN_SOURCE_FIELDS = {
-    "场景写法": ["反面写法"],
-    "写作技法": ["常见误区"],
-    "爽点与节奏": ["常见崩盘误区"],
-    "人设与关系": ["忌讳写法"],
-    "桥段套路": ["忌讳写法"],
-    "题材与调性推理": ["强制禁忌/毒点"],
-}
-```
-
-### 6.2 桥段套路.csv 的处理
-
-当前 `桥段套路.csv` 只有:
-
-- `桥段名称`
-- `前置铺垫`
-- `核心爽点`
-- `转折设计`
-- `反套路变种`
-
-其中 `反套路变种` 不是 anti-pattern 字段,不能直接拿来当“毒点”。
-
-因此本轮建议:
-
-- 为 `桥段套路.csv` **新增** `忌讳写法` 列
-- 新录入条目逐步补齐
-- 老条目允许暂时为空
-
-### 6.3 最终聚合规则
-
-系统渲染时的 `Anti-Patterns` 为以下内容的并集:
-
-1. `题材与调性推理.csv` 的全局毒点
-2. 动态检索命中的条目级毒点
-3. Chapter 局部追加毒点
-
-注意:
-
-- Chapter 可以**新增**局部毒点
-- Chapter 不能删除 Master 的全局毒点
-
----
-
-## 7. 新增脚本:story_system.py
-
-### 7.1 文件位置
-
-新增:
-
-- `webnovel-writer/scripts/story_system.py`
-
-### 7.2 角色定位
-
-`story_system.py` 是系统聚合层,不是底层检索层。
-
-它负责:
-
-1. 题材路由
-2. 多表查询编排
-3. 结果聚合
-4. Markdown / JSON 双格式输出
-5. `.story-system/` 持久化
-
-它不负责:
-
-1. 替代 `reference_search.py`
-2. 直接生成正文
-3. 解析或执行具体 skill 主流程
-
-### 7.3 CLI 设计
-
-建议 CLI:
-
-```bash
-python story_system.py "玄幻退婚流"
-python story_system.py "玄幻退婚流" --persist
-python story_system.py "拍卖会打脸" --persist --chapter chapter_015
-python story_system.py "压抑一点,后面爆" --genre 现言 --persist
-python story_system.py "规则动物园" --format json
-```
-
-建议参数:
-
-| 参数 | 必填 | 说明 |
-|------|------|------|
-| 位置参数 `query` | 是 | 用户当前意图或题材描述 |
-| `--genre` | 否 | 手动指定题材 |
-| `--chapter` | 否 | 章节 override 名,如 `chapter_015` |
-| `--persist` | 否 | 是否写入 `PROJECT_ROOT/.story-system/` |
-| `--format` | 否 | `markdown` / `json` / `both` |
-| `--csv-dir` | 否 | 兼容测试与自定义目录 |
-| `--story-root` | 否 | 指定显式 `.story-system/` 目录;仅开发/测试场景使用,主链运行时默认基于 `PROJECT_ROOT` 解析 |
-
-### 7.4 主流程
-
-`story_system.py` 的执行顺序应固定为:
-
-#### Step 1:题材推理
-
-输入 `query` 后,优先查 `题材与调性推理.csv`:
-
-- 若命中明确题材 → 使用题材路由结果
-- 若命中多个题材 → 做加权排序并返回主路由 + 候选路由
-- 若未命中 → 进入 fallback
-
-#### Step 2:fallback 路由
-
-未命中时,按以下顺序降级:
-
-1. 用户手动传入 `--genre` 时,以 `--genre` 为主
-2. 从 `query` 中抽取与现有各表高频关键词最接近的题材名
-3. 若仍失败,进入“通用写作路由”:
-   - 基础表默认查:`命名规则|人设与关系|金手指与设定`
-   - 动态表默认查:`桥段套路|爽点与节奏|场景写法`
-
-#### Step 3:基础表检索
-
-根据题材路由表给出的 `推荐基础检索表`:
-
-- 对每张基础表取 Top 1
-- 基础表默认包括:
-  - `命名规则`
-  - `人设与关系`
-  - `金手指与设定`
-  - 未来可扩展:`题材与调性推理`
-
-#### Step 4:动态表检索
-
-根据题材路由表给出的 `推荐动态检索表`:
-
-- 对每张动态表取 Top 2
-- 动态表默认包括:
-  - `桥段套路`
-  - `爽点与节奏`
-  - `场景写法`
-  - 可选补充:`写作技法`
-
-#### Step 5:Anti-Patterns 聚合
-
-把题材级毒点和条目级误区字段做统一归并。
-
-#### Step 6:渲染与持久化
-
-输出:
-
-- `StorySystemDict`
-- Markdown 视图
-- 可选写入 `.story-system/`
-
----
-
-## 8. StorySystemDict 数据契约
-
-### 8.1 顶层结构
-
-建议数据结构:
-
-```json
-{
-  "meta": {},
-  "route": {},
-  "master_constraints": {},
-  "base_context": {},
-  "dynamic_context": {},
-  "anti_patterns": [],
-  "override_policy": {},
-  "source_trace": []
-}
-```
-
-### 8.2 详细字段
-
-#### `meta`
-
-记录:
-
-- 查询词
-- 显式题材
-- 推理命中题材
-- 是否 chapter 模式
-- 生成时间
-
-#### `route`
-
-记录:
-
-- 主路由题材
-- 候选题材
-- 路由命中依据
-- 推荐基础表
-- 推荐动态表
-
-#### `master_constraints`
-
-记录全局不可轻易改动的内容:
-
-- 核心调性
-- 节奏策略
-- 主冲突模板
-- 金手指硬限制
-- 主角硬约束
-- 全局毒点
-
-#### `base_context`
-
-保存基础表检索结果:
-
-- `命名规则`
-- `人设与关系`
-- `金手指与设定`
-- 可选 `写作技法`
-
-#### `dynamic_context`
-
-保存动态表检索结果:
-
-- `桥段套路`
-- `爽点与节奏`
-- `场景写法`
-
-#### `anti_patterns`
-
-统一格式:
-
-```json
-[
-  {
-    "source_table": "爽点与节奏",
-    "source_id": "PA-054",
-    "text": "质疑太弱没有压迫"
-  }
-]
-```
-
-#### `override_policy`
-
-显式写出:
-
-- `locked`
-- `append_only`
-- `override_allowed`
-
-#### `source_trace`
-
-记录本次命中的来源:
-
-- 表名
-- 编号
-- 摘要
-- 指令
-
-这部分主要用于调试和回溯。
-
----
-
-## 9. Markdown Formatter 规范
-
-### 9.1 输出目标
-
-Markdown 不是原始数据,而是人类和 agent 的可读视图。
-
-它必须做到:
-
-1. 有明显层次
-2. 一眼能看见全局基调
-3. 一眼能看见本章覆盖项
-4. 一眼能看见绝对毒点
-
-### 9.2 建议结构
-
-建议 Markdown 输出结构:
-
-```markdown
-# Story System
-
-## Meta
-
-## Route
-
-## Master Constraints
-
-## Base Context
-
-## Dynamic Context
-
-## Anti-Patterns
-
-## Source Trace
-```
-
-### 9.3 Anti-Patterns 章节要求
-
-必须保留一个显著章节:
-
-```markdown
-## Anti-Patterns(绝对毒点,切勿触碰)
-```
-
-展示规则:
-
-- 每条红线单独成条
-- 用反引号包裹短句
-- 标明来源表与编号
-
-例如:
-
-```markdown
-- `严禁配角连续抢戏超过 300 字`(来源:GR-003)
-- `打脸节奏不能缺最后一拍补刀`(来源:PA-054)
-```
-
-### 9.4 JSON 与 Markdown 的关系
-
-规则必须明确:
-
-- JSON 是真实源数据
-- Markdown 是 JSON 的投影视图
-- 任何覆盖合并逻辑以 JSON 为准,不以 Markdown 反向解析为准
-
----
-
-## 10. 持久化架构:.story-system
-
-### 10.1 目录结构
-
-这里必须遵守项目现有运行时目录术语,而不是使用当前源码仓库目录做推断:
-
-1. `CLAUDE_PLUGIN_ROOT`
-   - 插件安装目录
-   - 存放 `skills / scripts / references`
-   - 不写入书项目数据
-2. `WORKSPACE_ROOT`
-   - Claude 工作区根目录
-   - 通过 `.claude/.webnovel-current-project` 指针解析当前书项目
-3. `PROJECT_ROOT`
-   - 真实书项目根目录
-   - 定义为:包含 `.webnovel/state.json` 的目录
-
-因此 `.story-system/` 的默认落盘位置必须是:
-
-- `PROJECT_ROOT/.story-system/`
-
-而不是:
-
-- 当前源码仓库目录
-- `CLAUDE_PLUGIN_ROOT`
-- `WORKSPACE_ROOT`
-
-建议目录:
-
-```text
-PROJECT_ROOT/
-  .webnovel/
-  正文/
-  大纲/
-  设定集/
-  .story-system/
-    MASTER_SETTING.md
-    MASTER_SETTING.json
-    chapters/
-      chapter_001.md
-      chapter_001.json
-      chapter_015.md
-      chapter_015.json
-```
-
-运行时约束补充:
-
-1. skills 默认从 `WORKSPACE_ROOT` 出发
-2. 统一经 `webnovel.py --project-root "${WORKSPACE_ROOT}" where` 解析到真实 `PROJECT_ROOT`
-3. `story_system` 若进入主链,应只认解析后的 `PROJECT_ROOT`
-4. `--story-root` 只作为开发期 override,不应成为 skills 的默认输入
-
-### 10.2 MASTER 的职责
-
-`MASTER_SETTING.*` 负责保存:
-
-- 题材主路由
-- 全局调性
-- 主角硬约束
-- 金手指硬约束
-- 世界级约束
-- 长线节奏策略
-- 全局 anti-patterns
-
-### 10.3 Chapter 的职责
-
-`chapters/chapter_xxx.*` 负责保存:
-
-- 本章桥段
-- 本章场景
-- 本章局部节奏
-- 本章局部额外毒点
-- 本章特殊强调项
-
-### 10.4 覆盖分类
-
-#### 10.4.1 `locked`
-
-以下内容只能从 Master 读取,chapter 不允许覆盖:
-
-- 主路由题材
-- 核心调性
-- 主角底层人设
-- 金手指硬限制
-- 世界规则闭环
-- 全局 anti-patterns
-
-#### 10.4.2 `append_only`
-
-以下内容 chapter 只能补充,不可删除 master 已有内容:
-
-- 命名语系
-- 长线伏笔
-- 长线关系提醒
-- 全局禁区补充
-- 重要 source trace
-
-合并方式:
-
-- 并集
-- 去重
-
-#### 10.4.3 `override_allowed`
-
-以下内容 chapter 可覆盖 master 默认值:
-
-- 本章桥段
-- 本章场景
-- 本章节奏目标
-- 本章局部情绪侧重
-- 本章输出重点
-
-### 10.5 Anti-Patterns 合并规则
-
-最终写作时生效的红线为:
-
-```text
-生效红线 = Master 全局红线 ∪ Chapter 局部红线
-```
-
-注意:
-
-- Chapter 可以加新红线
-- Chapter 不能移除 Master 红线
-
-### 10.6 文件更新策略
-
-为避免覆盖人工修改,本轮建议采用以下策略:
-
-1. JSON 文件由脚本完全管理
-2. Markdown 文件可带人工备注区
-3. 脚本只更新带 marker 的自动生成区块
-
-建议 marker:
-
-```markdown
-<!-- STORY-SYSTEM:BEGIN -->
-... 自动生成内容 ...
-<!-- STORY-SYSTEM:END -->
-```
-
-marker 外的手写内容,脚本不得改动。
-
----
-
-## 11. Prompt / Skill 集成契约
-
-### 11.1 本轮只定义契约,不假定具体挂点
-
-当前仓库中未直接暴露可改的 `webnovel-plan` / `webnovel-write` 的 `SKILL.md` 文件落点。
-
-因此本 spec 不假定:
-
-- prompt 一定在本仓库
-- skill 一定在本仓库
-- 运行时一定从哪个路径自动读取
-
-本轮只定义**接入契约**。
-
-### 11.2 标准读取逻辑
-
-未来接入层必须满足:
-
-1. 如果存在 `chapters/[当前章节].json`,优先读取它。
-2. 对于 `override_allowed` 字段,chapter 优先。
-3. 对于 `append_only` 字段,chapter 与 master 合并。
-4. 对于 `locked` 字段,只能读取 master。
-5. 最终始终附带全局与局部合并后的 `Anti-Patterns`。
-
-### 11.3 标准提示词片段
-
-接入层建议使用如下约束:
-
-```markdown
-【核心状态读取逻辑】
-1. 若存在 `.story-system/chapters/[当前章节].json`,先读 chapter。
-2. `override_allowed` 字段以 chapter 为准。
-3. `append_only` 字段按并集合并。
-4. `locked` 字段只能服从 `.story-system/MASTER_SETTING.json`。
-5. `Anti-Patterns` 为全局与局部的并集,任何一条都不可违反。
-```
-
----
-
-## 12. 实施阶段
-
-### Phase 1:数据层
-
-交付:
-
-1. 新增 `题材与调性推理.csv`
-2. 为 `桥段套路.csv` 新增 `忌讳写法`
-3. 更新 `references/csv/README.md`
-
-本阶段不要求批量改写既有表内容。
-
-### Phase 2:聚合脚本
-
-交付:
-
-1. 新增 `story_system.py`
-2. 保持 `reference_search.py` 不改职责
-3. 实现 `StorySystemDict`
-4. 实现 Markdown / JSON 输出
-
-### Phase 3:持久化
-
-交付:
-
-1. `.story-system/` 自动创建
-2. `MASTER_SETTING.*` 持久化
-3. `chapter_xxx.*` 持久化
-4. marker 区块更新逻辑
-
-### Phase 4:接入层
-
-交付:
-
-1. 定位真实的 skill / prompt 挂点
-2. 按本 spec 的读取优先级接入
-3. 验证 chapter override 能正确覆盖局部字段
-
----
-
-## 13. 测试策略
-
-### 13.1 测试原则
-
-本项目不需要把每个知识点都写成测试。
-
-本轮测试只保护系统契约,不保护内容细节。
-
-### 13.2 必要测试
-
-至少新增以下轻量测试:
-
-1. **题材路由测试**
-   - 输入明确题材时能命中 `题材与调性推理.csv`
-   - 输入模糊 query 时能走 fallback
-
-2. **聚合结构测试**
-   - `StorySystemDict` 顶层字段完整
-   - 基础表与动态表结果落在正确 section
-
-3. **anti-pattern 聚合测试**
-   - 不同表的误区字段能被统一提取
-   - Master 与 Chapter 的红线会做并集
-
-4. **覆盖规则测试**
-   - `locked` 不会被 chapter 覆盖
-   - `append_only` 正确并集
-   - `override_allowed` chapter 优先
-
-5. **持久化测试**
-   - `--persist` 会写出 md + json
-   - marker 外的人工内容不会被覆盖
-
-6. **底层搜索回归**
-   - 现有 `reference_search.py` 测试继续通过
-
-### 13.3 不要求的测试
-
-本轮不要求:
-
-1. 每条 CSV 知识点逐条断言
-2. Markdown 渲染的视觉细节快照测试
-3. 复杂端到端 agent 测试
-
----
-
-## 14. 风险与约束
-
-### 14.1 最大风险
-
-最大风险不是代码复杂度,而是职责混淆。
-
-必须避免:
-
-1. 把 `reference_search.py` 改坏
-2. 把 Markdown 当源数据
-3. 让 Chapter 冲掉 Master 硬限制
-4. 把 `反套路变种` 错当成毒点字段
-
-### 14.2 数据录入约束
-
-本轮仍遵守当前 CSV 原则:
-
-- 新内容以人工整理为主
-- 不做 md 全量自动迁移
-- 不要求一次性补全所有题材
-
-### 14.3 上线顺序约束
-
-正确上线顺序必须是:
-
-1. 先补路由表
-2. 再写聚合器
-3. 再做持久化
-4. 最后才接入 prompt / skill
-
-不能跳过中间层,直接让 prompt 去拼 CSV 检索结果。
-
----
-
-## 15. 最终结论
-
-本轮架构升级的核心不是“把检索做得更大”,而是:
-
-1. **保留现有检索 primitive 的稳定性**
-2. **新增一层 Story System 聚合器**
-3. **把全局约束与局部覆盖持久化**
-4. **让大模型服从结构化系统,而不是每次重新理解散乱结果**
-
-最终目标不是“搜索更花”,而是:
-
-**开书有 Master,写章有 Override,任何时刻都有可追溯、可持久、可服从的系统级上下文。**

+ 0 - 1845
docs/archive/superpowers/specs/2026-04-12-webnovel-story-intelligence-system-spec.md

@@ -1,1845 +0,0 @@
-# Webnovel Writer Story Intelligence System 理想态重构 Spec
-
-> 日期:2026-04-12
-> 状态:草案 v2
-> 定位:理想态总架构蓝图
-
----
-
-## 1. 文档定位
-
-### 1.1 这份 spec 解决什么问题
-
-当前 `webnovel-writer` 已经具备以下能力:
-
-- `references/csv/` 提供条目化知识检索
-- `references/*.md` 提供方法论与流程约束
-- `scripts/data_modules/` 提供 state / index / memory / context 的运行时能力
-- `skills/webnovel-*` 提供初始化、规划、写作、审查等执行入口
-
-但当前系统更准确的状态是:
-
-- 已经具备 `L0 / L1 / L2` 的按步加载能力
-- 已经具备 `md 必读 + CSV 检索` 的双轨 reference 体系
-- 已经具备 `context_manager + genre_*` 这类半成品聚合能力
-
-真正缺的不是“会不会按需读取资料”,而是:
-
-1. 缺少统一的**聚合中间层**,把题材、节奏、桥段、毒点、人设、金手指装成一个系统
-2. `reference_search.py` 只能返回散条目,不能生成稳定的故事系统合同
-3. `context_manager / genre_*` 还没有演进成可持久化的 `Story Contract`
-4. 全局设定与章节局部要求没有稳定的 `Master + Overrides` 承载结构
-
-本 spec 的目标不是继续增强“检索”,而是把项目升级成一个:
-
-- 先推理
-- 再聚合
-- 再持久化
-- 最后由 skill 消费合同
-
-的 `Story Intelligence System`。
-
-### 1.2 与现有 spec 的关系
-
-仓库中已有:
-
-- `2026-04-09-skills-restructure-and-reference-gaps.md`
-- `2026-04-12-story-system-pro-max-retrofit-spec.md`
-
-它们分别解决:
-
-- `2026-04-09`:`skills / references / scripts` 的职责边界与资料缺口
-- `2026-04-12 retrofit`:在不大改现有链路的前提下,为现系统补一个较保守的 `story_system`
-
-本 spec 与它们不同:
-
-- 本 spec **不以兼容旧入口为前提**
-- 本 spec **不遵守最小改动原则**
-- 本 spec 讨论的是项目的**理想态重构目标**
-
-换句话说:
-
-- `retrofit spec` 是“怎么稳妥地补”
-- 本 spec 是“如果重做一版,最优架构应该长什么样”
-
-### 1.2.1 与 retrofit spec 的复用 / 替代边界
-
-为避免后续实施时两份 spec 相互打架,这里明确边界:
-
-#### 可直接复用的部分
-
-- CSV 通用列契约
-- `reference_search.py` 作为底层 primitive 的定位
-- `.story-system/` 作为持久化目录的基本方向
-- anti-pattern 的“显式负面字段优先”原则
-- `StorySystemDict` 作为 phase 1 JSON contract 的种子结构
-
-#### 本 spec 对 retrofit 的超集扩展
-
-- 把两层持久化扩展为 `Master / Volume / Chapter` 三层
-- 把 `StorySystemDict` 扩展为合同家族,而不再只是一份聚合结果
-- 把 `skills` 的定位从“直接读 reference”升级为“合同优先 + 局部按需加载”
-- 把 `state / memory / index` 明确下沉为运行时事实层
-
-#### 本 spec 对 retrofit 的替代决策
-
-- `story_system.py` 不再只是一个附加脚本,而是未来的一线中枢
-- `genre-profiles.md` 不再担任核心配置中心
-- `templates/genres/*.md` 不再担任题材主知识源
-
-如果两份 spec 出现冲突,裁决原则为:
-
-1. phase 1 工程落地,优先服从 `retrofit spec`
-2. phase 2 及以后重构目标,优先服从本 spec
-
-### 1.3 借鉴对象
-
-本 spec 明确借鉴 `ui-ux-pro-max-skill` 的核心链路,而不是照搬它的文件名:
-
-1. 多域数据仓
-2. reasoning engine
-3. 聚合器
-4. anti-patterns 汇总
-5. 稳定输出 contract
-6. skill 以 contract 为主消费系统信息
-
-对 `webnovel-writer` 来说,真正需要学习的是这条系统链,而不是“多几个 CSV”。
-
----
-
-## 2. 设计结论
-
-### 2.1 一句话结论
-
-`webnovel-writer` 的理想态,不应再是:
-
-- `md reference + csv search + skill 手动拼装`
-
-而应重构为:
-
-- `Reasoning Layer + Multi-domain Knowledge Layer + Story System Generator + Persistence Contracts + Skill Runtime`
-
-### 2.2 核心判断
-
-#### 判断 1:CSV 应成为主知识层
-
-`references/csv/` 已经具备良好的条目化方向,理想态中它应升级为:
-
-- 主知识层
-- 主检索层
-- 主规则层
-
-而不是“补充资料”。
-
-#### 判断 2:MD 应降级为方法论层
-
-`references/*.md` 中的方法论、审查规则、AI 味规避、流程规范,仍应保留为 md。
-
-但它们不再直接承担:
-
-- 题材配置中心
-- 故事系统主设定
-- skill 主输入源
-
-#### 判断 3:skill 不应再直接读散乱 reference
-
-`webnovel-init / plan / write / review` 的最佳状态是:
-
-- 不直接拼装多份 reference
-- 不直接决定先查什么表、再查什么 md
-- 只消费统一生成的 story contract
-
-但这里的“只消费合同”,不是说 skill 从此不再按步加载任何 reference,而是指:
-
-- 全局设定、题材调性、毒点红线、系统边界应优先来自 contract
-- 局部写作技法、场景模式、命名规则、排版和审查规范仍可按 step 按需加载
-
-#### 判断 4:原有 data_modules 不是废弃,而是下沉为运行时底盘
-
-现有:
-
-- `state.json`
-- `memory_contract`
-- `index.db / vectors / bm25`
-- `context_manager`
-- `query_router`
-
-都仍然有价值。
-
-但它们的定位应从“主流程拼装器”变为:
-
-- 运行时事实层
-- 检索证据层
-- 写作执行支持层
-
-### 2.3 Contract 与渐进式披露的分工
-
-本 spec 不推翻 `2026-04-09` 中“渐进式披露 + 双轨制 + 按需加载”的哲学,而是重新划分消费边界。
-
-#### 由 contract 优先承接的内容
-
-- 题材推理结果
-- 全局调性与节奏承诺
-- `anti_patterns`
-- `system_constraints`
-- 卷级 / 章级目标
-- override ledger
-
-这些内容的共同特点是:
-
-- 影响全局
-- 需要跨 step 稳定一致
-- 不适合每个 skill 临时再拼一次
-
-但运行时还必须补一条消费边界:
-
-- skill 默认消费的是**最终结算态**
-- 完整 override ledger 默认属于**审计 / debug / dashboard 数据**
-- 写作 prompt 只应拿到“本章相关的 override 摘要”,不应注入全量历史账本
-
-#### 继续由按需 reference 承接的内容
-
-- 写作技法
-- 场景模式
-- 命名规则
-- 对话口吻
-- 排版规范
-- 润色与 anti-ai 修正
-- review schema 与 blocking override 规则
-
-这些内容的共同特点是:
-
-- 强依赖具体 step
-- 触发条件明确
-- 更像“局部执行手册”而不是“全局故事合同”
-
-因此理想态不是“contract 替代所有 reference”,而是:
-
-- contract 接管全局系统信息
-- step-bound reference 保留局部执行知识
-
----
-
-## 3. 目标与非目标
-
-### 3.1 目标
-
-理想态重构要达成 8 个目标:
-
-1. 建立统一的题材推理层
-2. 建立统一的多域知识仓
-3. 建立统一的故事系统生成器
-4. 建立统一的 anti-pattern 汇总机制
-5. 建立统一的 `Master / Volume / Chapter` 合同结构
-6. 建立统一的持久化目录 `.story-system/`
-7. 让 `skills` 只消费合同,不再直接拼 reference
-8. 让现有 state / index / memory 数据链继续作为运行时事实层
-
-### 3.2 非目标
-
-本 spec 明确不追求以下事情:
-
-1. 不保留旧 CLI 的完全兼容调用方式
-2. 不要求 `reference_search.py` 继续作为一线入口
-3. 不要求 `genre-profiles.md` 继续担任核心配置中心
-4. 不要求每个旧模板都被保留原职责
-5. 不要求为每条知识点编写测试
-
-### 3.3 知识迁移边界
-
-知识迁移必须遵守以下硬规则:
-
-1. `AI味`、去 AI 腔、润色替换规则,不进入 CSV
-2. 这类内容继续保留在独立 md 文件
-3. 知识迁移只允许人工整理和人工录入
-4. 禁止编写“把 md 自动抽成 csv 条目”的迁移脚本
-
-这里的“禁止脚本迁移”,针对的是知识内容迁移,不针对正常的工程脚本开发。
-
----
-
-## 4. 理想态总架构
-
-### 4.1 总体分层
-
-理想态系统分为五层:
-
-1. `Reasoning Layer`
-2. `Knowledge Layer`
-3. `Contract Layer`
-4. `Persistence Layer`
-5. `Skill Runtime Layer`
-
-### 4.2 分层关系
-
-```text
-用户意图 / 题材诉求
-        ↓
-Reasoning Layer
-        ↓
-Knowledge Layer
-        ↓
-Story System Generator
-        ↓
-Contract Layer
-        ↓
-Persistence Layer
-        ↓
-Skill Runtime Layer
-        ↓
-正文 / 大纲 / 审查 / 状态回写
-```
-
-### 4.3 推荐目录
-
-```text
-${CLAUDE_PLUGIN_ROOT}/
-├── story_system/
-│   ├── data/
-│   │   ├── reasoning/
-│   │   ├── rules/
-│   │   ├── tropes/
-│   │   ├── characters/
-│   │   ├── pacing/
-│   │   └── naming/
-│   ├── engine/
-│   │   ├── search_engine.py
-│   │   ├── reasoning_engine.py
-│   │   ├── aggregator.py
-│   │   ├── anti_pattern_engine.py
-│   │   ├── contract_builder.py
-│   │   ├── renderer.py
-│   │   └── persistence.py
-│   ├── runtime/
-│   │   ├── memory_bridge.py
-│   │   ├── state_bridge.py
-│   │   ├── index_bridge.py
-│   │   └── review_bridge.py
-│   ├── templates/
-│   │   ├── master_setting.md.j2
-│   │   ├── volume_brief.md.j2
-│   │   ├── chapter_brief.md.j2
-│   │   ├── anti_patterns.md.j2
-│   │   └── review_contract.md.j2
-│   └── cli.py
-├── references/
-│   ├── csv/
-│   ├── shared/
-│   ├── outlining/
-│   └── review/
-├── skills/
-└── scripts/
-
-${PROJECT_ROOT}/
-├── .webnovel/
-└── .story-system/
-```
-
-说明:
-
-- 这里采用的是**插件运行时目录模型**,不是当前源码仓库目录模型
-- 当前开发仓库只用于产出插件,不参与运行时 story contract 的落盘路径判定
-- `CLAUDE_PLUGIN_ROOT` 是插件安装目录,负责承载代码、skills、scripts、references
-- `PROJECT_ROOT` 是真实书项目根目录,定义为包含 `.webnovel/state.json` 的目录
-- `.story-system/` 必须落在 `PROJECT_ROOT` 下,而不是 `CLAUDE_PLUGIN_ROOT`
-- `story_system/` 是新的一线中枢
-- 旧 `scripts/reference_search.py` 与 `scripts/data_modules/` 可继续存在,但不再承载主设计中心
-- `engine/` 下的多个文件表示“职责分层目标”,不是 day 1 必须拆成 7 个文件
-
-### 4.4 初始实现颗粒度
-
-理想态需要清晰的职责边界,但不意味着第一版工程实现必须把每个职责拆成独立文件。
-
-推荐做法:
-
-1. phase 1 可先合并为 `search_reasoning.py + contract_runtime.py + persistence.py`
-2. phase 2 再按职责拆成更细模块
-
-也就是说:
-
-- 架构分层要先定清
-- 文件颗粒度可以渐进收敛
-
----
-
-## 5. Reasoning Layer 设计
-
-### 5.1 职责
-
-`Reasoning Layer` 是整个系统的大脑,负责把模糊的创作意图,转成可执行的系统约束。
-
-它回答的不是:
-
-- “某个技巧怎么写”
-
-而是:
-
-- 这本书本质上是什么题材
-- 核心读者承诺是什么
-- 应该优先交付什么爽点
-- 节奏应该偏快还是偏压抑
-- 哪些毒点绝对不能碰
-- 后续应该优先检索哪些知识域
-
-### 5.2 核心数据表
-
-新增核心路由表:
-
-- 文件名:`题材与调性推理.csv`
-- 中文名:`题材与调性推理`
-
-### 5.3 建议字段
-
-`题材与调性推理.csv` 仍然必须遵守现有 CSV 通用契约。
-
-这里列出的,是**通用列之外的专属列**:
-
-| 字段 | 说明 |
-|------|------|
-| `题材/流派` | 主题材名 |
-| `中文名` | 供人读的标准名 |
-| `题材别名` | 黑话、俗称、平台常用叫法 |
-| `核心调性` | 草根逆袭、极致拉扯、压抑蓄爆、诡异压迫等 |
-| `节奏策略` | 黄金三章、慢热蓄势、持续兑现、节点爆发等 |
-| `主爽点` | 该题材最核心的兑现类型 |
-| `辅助爽点` | 次级交付 |
-| `冲突引擎` | 冲突主要如何生成 |
-| `适配人设` | 推荐的人设框架 |
-| `适配金手指` | 推荐的外挂或设定类型 |
-| `适配桥段` | 高频桥段方向 |
-| `强制禁忌/毒点` | 题材级红线 |
-| `推荐基础检索表` | 生成系统时优先查的基础域 |
-| `推荐动态检索表` | 生成卷/章简报时优先查的动态域 |
-| `默认查询词` | 当用户输入模糊时的扩展查询词 |
-
-### 5.4 题材推理原则
-
-推理顺序应为:
-
-1. 识别主题材
-2. 识别辅题材
-3. 识别核心承诺
-4. 锁定调性
-5. 锁定节奏
-6. 锁定毒点
-7. 生成后续多域检索计划
-
-这里必须允许“主辅题材”结构,而不是只识别单一标签。
-
-### 5.5 多标签推理规则
-
-网文题材经常不是单标签,而是:
-
-- `赛博朋克 + 克苏鲁`
-- `修仙 + 直播`
-- `都市异能 + 恋爱修罗场`
-
-因此 reasoning engine 不应采用简单的 `Top 1` 命中逻辑,而应支持多标签加权融合。
-
-建议规则如下:
-
-1. 先识别 `主题材`
-2. 再识别 `辅题材`
-3. `核心调性` 以主题材为主,辅题材只允许调味,不得反客为主
-4. `强制禁忌/毒点` 采用并集策略
-5. `主爽点` 按主题材排序,`辅助爽点` 允许来自辅题材
-6. `推荐基础检索表 / 推荐动态检索表` 采用加权合并,而不是单条覆盖
-
-简化说法:
-
-- 调性允许主次
-- 毒点必须并集
-- 检索计划必须融合
-
-这条规则必须写进实现,而不能只停留在概念层。
-
-### 5.6 Reasoning Engine 实现边界
-
-reasoning engine 不能走向两个极端:
-
-- 不能试图用纯规则代码“理解一切文学语义”
-- 也不能把题材识别、调性融合、毒点判断全部放给 LLM 黑盒完成
-
-推荐采用 `L0 / L1 / L2` 三层:
-
-1. `L0 Deterministic Router`
-2. `L1 Deterministic Fusion`
-3. `L2 LLM Classifier / Synthesizer`
-
-#### L0:确定性路由
-
-负责:
-
-- alias 归一化
-- 显式标签提取
-- BM25 / 关键词候选召回
-- 置信度初算
-
-这层的职责是把自然语言意图收敛成**候选集合**,而不是直接输出最终文学判断。
-
-#### L1:确定性融合
-
-负责:
-
-- 主辅题材候选加权
-- 毒点并集
-- 基础检索计划融合
-- 生成“可离线运行”的 baseline contract
-
-这层保证:即便没有 LLM,也能对清晰输入产出一个可用但偏保守的 `MASTER`。
-
-#### L2:LLM 分类 / 融合
-
-只有在以下场景才启用:
-
-- 输入低置信
-- 题材混搭明显
-- 调性描述高度模糊
-- 多候选之间差距不足以稳定裁决
-
-LLM 的职责不是自由发挥,而是:
-
-- 在候选集合内做分类或重排
-- 解释为什么某个融合更合理
-- 产出严格结构化 JSON
-
-随后必须经过本地 schema 校验,失败则回退到 L1 baseline。
-
-默认要求:
-
-- 离线可运行
-- 不依赖 LLM 也能生成可用的 `MASTER`
-- LLM 负责语义补洞与模糊融合,不负责绕过规则层直接产生命令式合同
-
-fallback 策略:
-
-1. 若 `L0 + L1` 已得到高置信结果,则不调用 LLM
-2. 若结果低置信,先输出候选题材列表和冲突点
-3. 若启用 LLM 辅助,再让 LLM 仅基于候选集合输出结构化融合建议
-4. 若 LLM 输出未通过校验,则丢弃该结果并回退到确定性 baseline
-
-这样做的目的,是把:
-
-- 成本
-- 延迟
-- 可用性
-- 可解释性
-- 可测试性
-
-同时控制在工程可接受范围内
-
----
-
-## 6. Knowledge Layer 设计
-
-### 6.1 知识分层
-
-理想态中,知识层应分成三类,而不是全塞在一起:
-
-1. `Reasoning Tables`
-2. `Rule Tables`
-3. `Methodology Docs`
-
-### 6.2 Reasoning Tables
-
-这些表负责“全局推理与路由”:
-
-| 文件名 | 中文名 | 角色 |
-|------|------|------|
-| `题材与调性推理.csv` | 题材与调性推理 | 题材路由与全局调度 |
-
-### 6.3 Rule Tables
-
-这些表负责“结构化规则与条目知识”:
-
-| 文件名 | 中文名 | 角色 |
-|------|------|------|
-| `命名规则.csv` | 命名规则 | 人名、地名、势力、功法等命名规则 |
-| `场景写法.csv` | 场景写法 | 战斗、对话、冲突、桥段场景的写法模式 |
-| `写作技法.csv` | 写作技法 | 技法与误区 |
-| `桥段套路.csv` | 桥段套路 | 套路、铺垫、反转、变种 |
-| `人设与关系.csv` | 人设与关系 | 人设原型、关系互动、禁区 |
-| `爽点与节奏.csv` | 爽点与节奏 | 节奏阶段、情绪调度、崩盘误区 |
-| `金手指与设定.csv` | 金手指与设定 | 金手指、世界规则、限制、代价 |
-
-建议后续补两张表:
-
-| 文件名 | 中文名 | 角色 |
-|------|------|------|
-| `冲突设计.csv` | 冲突设计 | 冲突触发源、升级链、回收方式 |
-| `反派机制.csv` | 反派机制 | 反派逻辑、层级、失败方式、禁区 |
-
-### 6.4 Methodology Docs
-
-这些内容继续保留 md,不进 csv:
-
-- `review-schema.md`
-- `reading-power-taxonomy.md`
-- `shared/core-constraints.md`
-- `webnovel-write/references/anti-ai-guide.md`
-- `webnovel-write/references/polish-guide.md`
-- `webnovel-write/references/style-adapter.md`
-
-原因很简单:
-
-- 它们是流程与方法论
-- 不是检索条目
-- 强行塞入 csv 会劣化表达
-
----
-
-## 7. Contract Layer 设计
-
-### 7.1 核心思想
-
-skill 不应再直接消费:
-
-- 散乱 CSV 结果
-- 多份 md 引用
-- 零散模板
-
-skill 只应消费标准合同。
-
-### 7.2 合同类型
-
-理想态至少有五类合同:
-
-1. `MASTER_SETTING`
-2. `VOLUME_BRIEF`
-3. `CHAPTER_BRIEF`
-4. `ANTI_PATTERNS`
-5. `REVIEW_CONTRACT`
-
-### 7.3 每类合同的职责
-
-#### MASTER_SETTING
-
-负责全书级稳定设定:
-
-- 题材与调性
-- 主角与核心角色基线
-- 世界规则与金手指边界
-- 全局节奏策略
-- 全局毒点
-
-#### VOLUME_BRIEF
-
-负责卷级目标:
-
-- 本卷核心冲突
-- 本卷兑现目标
-- 本卷反派层级
-- 本卷关键桥段
-- 本卷节奏波形
-
-#### CHAPTER_BRIEF
-
-负责本章执行要求:
-
-- 本章目标
-- 本章桥段
-- 本章场景策略
-- 本章情绪预期
-- 本章禁区
-
-#### ANTI_PATTERNS
-
-负责把所有题材级、桥段级、角色级、节奏级毒点聚合成显式红线。
-
-#### REVIEW_CONTRACT
-
-负责告诉审查环节:
-
-- 本章/本卷必须检查什么
-- 哪些红线最优先
-- 哪些风险点是题材特定风险
-
-### 7.4 双产物要求与单一真理源
-
-每份合同可以同时存在两种产物:
-
-1. Markdown
-2. JSON
-
-但必须明确:
-
-- `JSON` 是唯一真理源
-- `Markdown` 只是从 `JSON` 渲染出的只读产物
-
-原因:
-
-- Markdown 给人看
-- JSON 给程序和 skill 稳定消费
-- 一旦允许人手改 Markdown,就会出现双重真理源
-
-因此必须执行以下硬规则:
-
-1. 所有持久化更新先改 JSON,再渲染 Markdown
-2. Markdown 顶部必须显式标注 `GENERATED FILE / DO NOT EDIT`
-3. skill、runtime、dashboard、测试一律只消费 JSON,不消费 Markdown 作为真值
-4. 人工修订只能通过 CLI / Dashboard / 显式 JSON 编辑入口完成,不支持“手改 Markdown 自动回写”
-
-如果 Markdown 被人工修改:
-
-- 视为非受支持操作
-- 下次渲染时允许被覆盖
-
-### 7.4.1 与 Retrofit Spec 的 Markdown 迁移裁决
-
-这里需要与 `retrofit spec` 的 marker 方案明确裁决,避免两份 spec 在 phase 1/2 打架。
-
-#### phase 1
-
-- 服从 `retrofit spec`
-- 若 Markdown 中存在 `<!-- STORY-SYSTEM:BEGIN -->` / `END` 自动生成区块,则脚本只更新 marker 内内容
-- marker 外的人工备注区可暂时保留
-
-#### phase 2 及以后
-
-- 服从本 spec
-- Markdown 退化为从 JSON 全量重建的只读渲染产物
-- phase 1 的 marker 兼容逻辑可以废弃
-
-无论 phase 1 还是 phase 2,都必须坚持:
-
-- `JSON` 是唯一真理源
-- marker 外人工备注不构成合同真值
-- skill/runtime/test 一律不以 Markdown 作为真值输入
-
-### 7.5 JSON 合同校验
-
-既然 JSON 要作为 skill 的稳定输入,就不能只“尽量输出正确”,而必须做结构校验。
-
-建议实现:
-
-1. 为 `MASTER_SETTING / VOLUME_BRIEF / CHAPTER_BRIEF / REVIEW_CONTRACT` 定义显式 schema
-2. 使用 `Pydantic` 或等价 schema 校验机制
-3. 在生成 JSON 后先做本地校验,再落盘
-4. 校验失败时不得生成“看起来成功、实际结构不稳定”的伪合同
-
-最低要求:
-
-- `anti_patterns` 必须始终是列表
-- `overrides` 必须始终是结构化对象
-- `locked / append_only / override_allowed` 必须可机读
-- `lock_policy` 必须始终可机读
-- 缺省值要稳定,不能时而为空字符串、时而为空数组
-
-这不是“测试增强”,而是合同系统的基础完整性约束。
-
-### 7.5.1 Schema Version 与兼容策略
-
-所有合同 JSON 都必须带版本元数据,最低要求:
-
-- `schema_version`
-- `contract_type`
-- `generator_version`
-
-推荐规则:
-
-1. `schema_version` 使用显式字符串,例如 `story-system/v1`
-2. 读取方必须先校验 `schema_version`,再决定:
-   - 直接读取
-   - 运行显式 migrator
-   - 报错并要求人工升级
-3. 禁止在 schema 不兼容时静默忽略字段
-4. phase 1 由 `StorySystemDict` 过渡来的合同,也必须显式写入版本号,而不是默认“无版本”
-
-否则 phase 1 生成的旧合同进入 phase 2 后,会在新代码下出现“静默丢字段”或“校验失败但原因不明”的问题。
-
-### 7.6 合同 JSON 基线
-
-本 spec 不从零重新发明 JSON contract,而采用以下策略:
-
-1. phase 1 复用 `retrofit spec` 中的 `StorySystemDict` 作为基础结构
-2. phase 2 在其外层扩展出 `MASTER / VOLUME / CHAPTER / REVIEW / ANTI_PATTERNS` 五类合同
-
-最小字段要求如下。
-
-#### `MASTER_SETTING.json`
-
-至少包含:
-
-- `meta`
-- `genre_reasoning`
-- `core_rules`
-- `anti_patterns`
-- `system_constraints`
-- `contracts`
-- `overrides`
-
-#### `VOLUME_BRIEF.json`
-
-至少包含:
-
-- `meta`
-- `volume_goal`
-- `selected_tropes`
-- `selected_pacing`
-- `selected_scenes`
-- `anti_patterns`
-- `system_constraints`
-- `overrides`
-
-#### `CHAPTER_BRIEF.json`
-
-至少包含:
-
-- `meta`
-- `chapter_goal`
-- `scene_strategy`
-- `hook_strategy`
-- `must_cover`
-- `anti_patterns`
-- `system_constraints`
-- `overrides`
-
-这里的 `system_constraints` 与 `anti_patterns` 必须区分:
-
-- `anti_patterns`:明确禁止项
-- `system_constraints`:能力边界、世界规则、数值限制
-
-一个实用判据是:
-
-- 违反后会直接让读者觉得“有毒、崩盘、降智”的,归入 `anti_patterns`
-- 违反后会先造成设定自洽性破裂、逻辑漏洞或数值失衡的,归入 `system_constraints`
-
-例如:
-
-- `金手指每天只能用三次` 属于 `system_constraints`
-- `打脸桥段必须靠反派强行降智才能成立` 属于 `anti_patterns`
-
-#### `REVIEW_CONTRACT.json`
-
-至少包含:
-
-- `meta`
-- `must_check`
-- `blocking_rules`
-- `genre_specific_risks`
-- `anti_patterns`
-- `system_constraints`
-- `review_thresholds`
-- `overrides`
-
-其中:
-
-- `must_check`:本章/本卷必须重点检查的审查项
-- `blocking_rules`:命中后直接阻断通过的规则
-- `genre_specific_risks`:题材特定高风险点
-- `review_thresholds`:各维度最低通过阈值或 blocking 条件
-
-### 7.6.1 `StorySystemDict` 到合同家族的映射
-
-为避免 phase 1 的 `StorySystemDict` 到 phase 2 的合同家族迁移时产生歧义,这里给出最小映射。
-
-| `StorySystemDict` 字段 | phase 2 目标位置 | 说明 |
-|------|------|------|
-| `meta` | `MASTER_SETTING.meta` | 生成来源、查询词、时间戳等元信息 |
-| `route` | `MASTER_SETTING.genre_reasoning` | 题材路由、候选题材、命中依据 |
-| `master_constraints` | `MASTER_SETTING.core_rules` + `MASTER_SETTING.system_constraints` | 题材调性、世界边界、硬约束分拆进入核心规则与系统边界 |
-| `base_context` | `MASTER_SETTING.contracts.base_context_seed` | 基础表命中结果作为全书级种子上下文 |
-| `dynamic_context` | `VOLUME_BRIEF.selected_*` + `CHAPTER_BRIEF.scene_strategy / hook_strategy / must_cover` | phase 2 由“动态上下文”拆成卷级选择与章级执行要求 |
-| `anti_patterns` | 各级合同中的 `anti_patterns` + 派生 `anti_patterns.json` | 条目级与题材级毒点进入各合同,再生成运行时聚合视图 |
-| `override_policy` | `MASTER_SETTING.contracts.override_policy` | 字段覆盖规则与默认 policy |
-| `source_trace` | 各合同 `meta.source_trace` 或审计区 | 调试、审计与追溯信息 |
-
-这张映射表的作用不是冻结最终字段名,而是明确:
-
-- phase 1 的扁平聚合结果不会直接消失
-- 它会被拆分并沉淀到更细粒度的合同家族中
-- 实施时不得靠“语义猜测”去决定字段归属
-
----
-
-## 8. Persistence Layer 设计
-
-### 8.1 目录规范
-
-在真实书项目根目录 `PROJECT_ROOT` 下建立:
-
-```text
-PROJECT_ROOT/
-├── .webnovel/
-└── .story-system/
-    ├── MASTER_SETTING.md
-    ├── MASTER_SETTING.json
-    ├── anti_patterns.md
-    ├── anti_patterns.json
-    ├── volumes/
-    │   ├── volume_001.md
-    │   ├── volume_001.json
-    │   └── ...
-    └── chapters/
-        ├── chapter_001.md
-        ├── chapter_001.json
-        └── ...
-```
-
-补充规则:
-
-- `*.json` 是合同真源
-- `*.md` 是渲染产物
-- 渲染器必须支持“从 JSON 全量重建 Markdown”
-- skills/agents 默认从 `WORKSPACE_ROOT` 出发,但必须先统一解析到真实 `PROJECT_ROOT` 再读写 `.story-system/`
-
-### 8.1.1 `ANTI_PATTERNS` 独立文件与各级合同的关系
-
-这里必须避免双真理源:
-
-1. `MASTER_SETTING / VOLUME_BRIEF / CHAPTER_BRIEF` 中各自的 `anti_patterns` 字段,才是**分层真源**
-2. `.story-system/` 根目录下的 `anti_patterns.json` 是**派生聚合视图**
-3. `anti_patterns.md` 只从 `anti_patterns.json` 渲染
-
-也就是说:
-
-- `MASTER_SETTING.json.anti_patterns` 负责全书级红线
-- `VOLUME_BRIEF.json.anti_patterns` 负责卷级补充红线
-- `CHAPTER_BRIEF.json.anti_patterns` 负责章级补充红线
-- `anti_patterns.json` 负责把“当前可见层级”的红线汇总成运行时/审计友好的扁平视图
-
-若出现冲突,以各级合同中的分层字段为准,`anti_patterns.json` 必须重算,不得反向成为真源。
-
-### 8.1.2 命名与版本控制约束
-
-文件命名规范:
-
-- 卷文件统一使用零填充编号:`volume_001.json`
-- 章文件统一使用零填充编号:`chapter_001.json`
-- 人类可读标题放在 `meta.title` 中,而不是写进文件名
-
-版本控制建议:
-
-- `.story-system/*.json` 与其对应的主 Markdown 渲染文件应默认纳入 git 追踪
-- 它们属于项目级合同,不是临时缓存
-- 仅调试 diff、health report、临时审计快照等可放入 `.gitignore`
-
-### 8.2 覆盖规则
-
-覆盖优先级固定为:
-
-1. `chapter_xxx`
-2. `volume_xxx`
-3. `MASTER_SETTING`
-
-但覆盖不是“静默替换”,而必须显式记录覆盖行为。
-
-### 8.2.1 三层覆盖矩阵
-
-`Master / Volume / Chapter` 的覆盖关系必须按字段类型区分,而不是统一“下层覆盖上层”。
-
-| 字段类型 | Master -> Volume | Volume -> Chapter | 说明 |
-|------|------|------|------|
-| `locked` | 默认不可直接覆盖 | 默认不可直接覆盖 | 是否允许通过 `amend-*` 突破,取决于 `lock_policy` |
-| `append_only` | 可追加,不可删除 | 可追加,不可删除 | 最终值为并集 |
-| `override_allowed` | 可覆盖,需记录 reason | 可覆盖,需记录 reason | 最终值取最近一层 |
-
-补充规则:
-
-1. `Master.locked` 对 `Volume / Chapter` 都生效
-2. `Volume.locked` 只约束本卷下属 `Chapter`
-3. `Chapter` 不能直接“解锁”上层已锁定字段
-4. `append_only` 字段的最终值始终是 `Master + Volume + Chapter` 的合并结果
-5. `override_allowed` 字段的最终值采用最近一层,但必须保留 override ledger
-
-### 8.2.1.1 `lock_policy`
-
-为避免把小说创作中的“合理反转”也物理锁死,所有 `locked` 字段都必须额外带一个 `lock_policy`:
-
-1. `system_locked`
-2. `user_locked`
-3. `story_locked`
-
-含义如下:
-
-- `system_locked`:系统 schema、基础运行约束、不可被下游合同修改
-- `user_locked`:用户明确给出的硬约束,运行时不得自动修改,只能由用户显式确认后上游修订
-- `story_locked`:当前故事系统中的核心稳定设定,不能被 `Volume / Chapter` 直接覆盖,但允许通过 `amend-master / amend-volume proposal + 人工确认` 上游改写
-
-也就是说:
-
-- `locked` 仍然存在
-- 但不是所有 `locked` 都是同一强度
-- 真正允许剧情反转的出口,是“先提修订建议,再改上层合同”,而不是让 `Chapter` 直接冲破锁
-
-### 8.2.2 冲突覆盖记录
-
-当 `chapter_xxx` 或 `volume_xxx` 与上层合同发生冲突时,生成器必须产出显式 override 标记,而不是只保留最终值。
-
-Markdown 层建议使用类似格式:
-
-- `本章节奏:[Override 自 MASTER: 慢热蓄势 -> 极限爆发]`
-- `本章桥段:[Override 自 VOLUME: 试探对峙 -> 当场打脸]`
-
-JSON 层至少要记录:
-
-- `field`
-- `base_value`
-- `override_value`
-- `source_level`
-- `reason`
-
-目的不是给人审美,而是让模型和后续脚本明确知道:
-
-- 这里发生了状态切换
-- 这是受控覆盖,不是随机漂移
-
-### 8.2.3 Override Ledger 消费边界
-
-override ledger 必须保留,但默认不应整包注入写作 prompt。
-
-推荐分成两种消费模式:
-
-1. `audit/debug mode`
-2. `runtime prompt mode`
-
-#### audit/debug mode
-
-允许读取完整账本,用于:
-
-- dashboard 审计
-- 合同 diff
-- 健康检查
-- 问题排查
-
-#### runtime prompt mode
-
-只应暴露:
-
-- 当前字段的最终生效值
-- 本章 / 本卷直接相关的 override 摘要
-- 必要的覆盖原因短句
-
-默认禁止:
-
-- 把历史全量 override ledger 直接塞进 `webnovel-write` prompt
-- 把几十章前的覆盖记录反复注入当前章节上下文
-
-原因很简单:
-
-- 这会浪费 token
-- 会制造噪音
-- 会让模型更关注“历史解释”而不是“当前执行约束”
-
-### 8.2.4 覆盖原因字段
-
-所有 `override_allowed` 字段在发生覆盖时,都应尽量附带 `reason`。
-
-推荐原因标签:
-
-- `ARC_ESCALATION`
-- `CHAPTER_PAYOFF`
-- `TWIST_REQUIREMENT`
-- `POV_SWITCH`
-- `CONFLICT_INTENSIFICATION`
-
-这些标签是推荐值,不是封闭枚举。
-
-允许格式:
-
-- `reason_tag + free_text`
-- 纯 `free_text`
-
-没有原因的覆盖,只能视为低可信覆盖。
-
-### 8.3 字段类型
-
-字段必须被标记为三类之一:
-
-1. `locked`
-2. `append_only`
-3. `override_allowed`
-
-示例:
-
-- `世界规则`:`locked`
-- `全局毒点`:`append_only`
-- `本章桥段策略`:`override_allowed`
-
-没有这个字段级规则,后续局部覆盖会失控。
-
-补充规则:
-
-- 若字段为 `locked`,则必须同时声明 `lock_policy`
-- `lock_policy` 未声明时,默认按 `story_locked` 处理,不允许静默放宽
-
-对于 `append_only` 字段,还应补一条规则:
-
-- 允许新增
-- 不允许删除既有上层约束
-
-尤其是:
-
-- `anti_patterns`
-- `世界规则限制`
-- `金手指边界`
-
-这些字段一旦下层能删除上层内容,合同体系就会失去刚性。
-
-此外必须明确 phase 1 的去重策略:
-
-1. `append_only` 默认**不做语义级自动合并**
-2. 对字符串列表,去重键为规范化后的文本:
-   - 去首尾空白
-   - 折叠连续空白
-   - 统一常见全角/半角分隔差异
-3. 对结构化对象,去重键必须显式定义;若未定义,则保留并标记为待审计
-4. 对 `anti_patterns`:
-   - 文本完全一致时去重
-   - 文本不同但语义相近时,phase 1 默认**同时保留**
-   - 若上层和下层只是数值阈值不同(如 300 字 vs 200 字),不得静默删除更严格版本
-
-也就是说,phase 1 的策略是:
-
-- 宁可保留重叠项
-- 不可静默弱化上层红线
-- 更激进的语义去重留到后续显式规则或人工审计
-
-### 8.4 持久化入口
-
-对 skill / agent / 测试体系可见的**稳定入口**,必须挂到现有统一 CLI `webnovel.py` 下,而不是在主链里直接引入第二套平行入口。
-
-理想态 CLI 应支持:
-
-```bash
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" story-system generate-master --genre "修仙退婚流"
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" story-system generate-volume --title "拍卖会卷"
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" story-system generate-chapter --query "拍卖会打脸"
-```
-
-如果保留 `story_system/cli.py`,它也只能作为:
-
-- 开发期本地调试入口
-- 内部封装入口
-
-而不应成为 skill/prompt 直接依赖的外部命令。
-
-原因:
-
-- 当前项目已有统一 CLI、project_root 解析、workspace pointer、registry、prompt 完整性校验
-- 若 story system 另起一套外部 CLI,skills、tests、dashboard、文档会立刻分叉
-- 因此对外只保留 `webnovel.py story-system ...` 一条稳定入口
-
-参数设计补充规则:
-
-- 优先使用命名参数,而不是依赖带空格的位置参数
-- 这样可以降低 PowerShell / Bash / zsh 下的转义差异
-- 对 skill 暴露的示例命令也应遵守这个约束
-
-此外必须预留显式合同修订入口:
-
-```bash
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" story-system amend-master --event "主角获得第二核心金手指"
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" story-system amend-volume --volume 2 --event "阵营关系反转"
-python -X utf8 "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" story-system refresh-chapter --chapter 15
-```
-
-这类命令的职责不是“随便重算”,而是:
-
-1. 读取当前合同
-2. 读取运行时重大事件
-3. 只在允许变更的字段上做受控修订
-4. 记录修订原因与修订时间
-
-### 8.4.1 幂等性与覆盖策略
-
-合同生成与修订命令必须遵守幂等性规则。
-
-#### `generate-*`
-
-1. 若目标合同不存在:创建
-2. 若目标合同已存在且输入指纹相同:直接返回现有合同,不重复覆盖
-3. 若目标合同已存在且输入指纹不同:默认拒绝静默覆盖,并提示使用:
-   - `amend-*`
-   - 或显式 `--force-rebuild`
-
-也就是说:
-
-- `generate-master` 不是“无限次覆盖”
-- 它是“首次生成 + 相同输入幂等返回”
-
-#### `amend-*`
-
-- 是合同的**语义修订入口**
-- 用于在已有合同基础上追加或修改受控字段
-- 不应伪装成重新生成
-
-#### `--force-rebuild`
-
-- 属于显式破坏性再生成
-- 必须由调用方主动声明
-- 执行前应先备份旧 JSON 或写入历史快照
-
-### 8.4.2 phase 1 并发约束
-
-phase 1 不应假定 `.story-system/*.json` 已具备复杂的多写者并发合并能力。
-
-因此最低要求是:
-
-1. `generate-* / amend-* / refresh-*` 对同一目标文件必须串行执行
-2. 实现上应使用文件锁或等价互斥机制
-3. 若目标已被其他进程持有写锁,应返回显式 `BUSY / RETRY` 错误,而不是继续写入
-4. 真正的多写者合并策略留到 day 2+ 单独设计
-
----
-
-## 9. Skill Runtime Layer 设计
-
-### 9.1 总原则
-
-skill 只负责执行,不负责系统拼装。
-
-### 9.2 `webnovel-init`
-
-职责应收敛为:
-
-1. 理解开书意图
-2. 调用 `generate-master`
-3. 先落 `.story-system/MASTER_SETTING.json`
-4. 再渲染 `.story-system/MASTER_SETTING.md`
-5. 最后把结果写入设定集骨架
-
-### 9.3 `webnovel-plan`
-
-职责应收敛为:
-
-1. 读取 `MASTER_SETTING`
-2. 调用 `generate-volume`
-3. 调用 `generate-chapter`
-4. 产出卷纲、章纲、时间线
-
-如果规划阶段发现以下事件,允许提出 `amend-master / amend-volume`:
-
-- 新核心阵营形成
-- 世界规则新增硬限制
-- 金手指边界发生明确扩展
-- 题材承诺发生结构性偏移
-
-默认流程应为:
-
-1. plan skill 识别到“可能需要修订”
-2. 调用 `contract-auditor / diff analyzer` 生成结构化修订建议
-3. 输出人类可读的修订摘要与受影响字段
-4. 由用户确认后再执行 `amend-master / amend-volume`
-
-除非系统明确配置了自动修订策略,否则不应由 plan skill 自行改写 `MASTER`
-
-它不应该再自己决定:
-
-- 先读哪些题材 md
-- 再查哪些 csv
-- 再拼哪些规则
-
-这些应该由 story system 统一处理。
-
-### 9.4 `webnovel-write`
-
-职责应收敛为:
-
-1. 读取 `chapter brief`
-2. 若不存在则回退 `volume brief`
-3. 再回退 `master`
-4. 读取该层级最终结算后的有效字段
-5. 只注入本章直接相关的 override 摘要
-6. 再结合 runtime memory/state/context 写正文
-
-写作阶段默认不允许直接修改 `MASTER_SETTING`。
-
-只有在检测到“重大结构事件”时,才允许通过显式 hook 进入:
-
-- `amend-master`
-- `amend-volume`
-
-重大结构事件示例:
-
-- 主角新增长期保留的核心能力
-- 世界观新增不可逆规则
-- 核心阵营关系永久翻转
-- 主线承诺发生升级而非临时偏移
-
-也就是说:
-
-- 运行时事实可以推动合同修订
-- 但必须通过显式修订入口
-- 不能让 `MASTER` 因章节波动而持续漂移
-
-推荐增加一个独立钩子:
-
-- `contract-auditor`
-
-它的职责不是改合同,而是:
-
-1. 比较当前运行时事实与上层合同
-2. 判断是“临时章节偏移”还是“应升级为合同修订”
-3. 生成 `amend proposal`
-4. 等待用户确认
-
-### 9.5 `webnovel-review`
-
-职责应收敛为:
-
-1. 读取 `review contract`
-2. 读取 `anti_patterns`
-3. 按合同做结构化审查
-4. 将结果回写 review/index/state
-
----
-
-## 10. 原有数据链的承接方案
-
-这是本 spec 最关键的部分之一:不是抛弃旧数据链,而是重新定位。
-
-### 10.1 `references/csv/`
-
-新定位:
-
-- 主知识层
-- 主规则层
-- 主检索层
-
-### 10.2 `references/*.md`
-
-新定位:
-
-- 方法论层
-- 流程规范层
-- 审查规则层
-- 在 CSV 覆盖度不足的阶段,仍可作为过渡期活跃知识源
-
-### 10.3 `templates/genres/*.md`
-
-新定位:
-
-- 降级为“样例模板层”
-- 可作为人工参考和初始化草稿
-- 在 route table 覆盖度不足前,仍可作为补充题材源
-
-### 10.4 `templates/output/*.md`
-
-新定位:
-
-- 升级为 story-system 的渲染模板来源
-
-### 10.5 `scripts/reference_search.py`
-
-新定位:
-
-- 底层 primitive
-- 可复用的 CSV 搜索内核
-- 不再作为 skill 主入口
-
-### 10.6 `scripts/data_modules/context_manager.py`
-
-新定位:
-
-- 运行时上下文装配器
-- 消费 `story-system contract + memory/state/index`
-- 不再负责“全局故事系统生成”
-
-演进路径应为:
-
-1. phase 1 保留 `context_manager`,不做平地重写
-2. phase 1 让它优先消费 contract,同时继续聚合 state / summaries / reader_signal / plot_structure
-3. phase 2 把其中与“全局系统生成”重叠的逻辑逐步抽到 `story_system`
-4. phase 3 再把 `context_manager` 收敛为纯运行时上下文装配器
-
-也就是说:
-
-- 不是直接废弃
-- 而是先接入,再抽离,再收敛
-
-### 10.6.1 Contract 注入口
-
-Day 2 以后,`context_manager` 必须有明确的 contract 注入口,而不是只在文档层说“合同优先”。
-
-最小要求:
-
-1. `_build_pack()` 读取 `.story-system/` 中当前章可见的合同
-2. pack 中新增显式 section,例如 `story_contract`
-3. `story_contract` 的优先级高于旧的 `genre_profile` 摘要和临时全局拼装结果
-4. 若合同缺失,再回退到现有 `genre_profile + global + reader_signal` 组装链
-
-推荐顺序:
-
-1. `chapter contract`
-2. `volume contract`
-3. `master contract`
-4. 现有 `genre_profile / global / references`
-
-换句话说,contract-first 必须落成真实的 pack 组装顺序,而不是停留在 skill 文本。
-
-### 10.6.2 `genre_aliases.py` / `genre_profile_builder.py` / `genre-profiles.md`
-
-这些模块和文档不是“旧包袱”,而是 route table 建设前的重要种子源。
-
-新定位:
-
-- `genre_aliases.py`:继续作为题材归一化字典的现役来源
-- `genre_profile_builder.py`:继续作为复合题材 hints 的现役来源
-- `genre-profiles.md`:在 phase 1 / phase 2 期间,继续作为结构化题材参考源
-
-它们的退出方式不应是“一刀切降级”,而应是:
-
-1. 先把其中稳定字段人工迁入 `题材与调性推理.csv`
-2. 再让 `story_system` 优先读 CSV、缺省回退 md
-3. 只有在 CSV 覆盖度达到阈值后,`genre-profiles.md` 才从主源降级为参考源
-
-### 10.6.3 过渡期题材真源优先级
-
-在 phase 1 / phase 2 期间,题材相关信息不能出现“双真源并列”。
-
-优先级必须固定为:
-
-1. `.story-system` 中的 contract
-2. `题材与调性推理.csv`
-3. `genre-profiles.md`
-4. `templates/genres/*.md`
-
-约束:
-
-- 一旦 contract 已存在,`context_manager` 不得再独立输出与 contract 冲突的题材结论
-- `genre-profiles.md` 在过渡期只能作为回退源或补充说明源
-- `templates/genres/*.md` 在过渡期只能作为样例/补充题材源
-
-否则就会出现:
-
-- contract 说 A
-- context pack 仍在塞 B
-- skill 最终又混用了 C
-
-这会直接破坏 contract-first 目标
-
-### 10.7 `state.json`
-
-新定位:
-
-- 写作过程状态机
-- 角色当前状态
-- 章节推进状态
-
-它不是故事总设定中心。
-
-### 10.8 `index.db / vectors / bm25`
-
-新定位:
-
-- 运行期检索证据层
-- 剧情事实回溯层
-- 不是规则知识层
-
-### 10.9 `memory_contract / orchestrator / summaries`
-
-新定位:
-
-- 长期记忆层
-- 已发生事实层
-- 伏笔与回收跟踪层
-
-这一层与 CSV 的关系应为:
-
-- `CSV` 给规则
-- `memory` 给事实
-- `story_system` 负责把规则和事实一起装进合同
-
-### 10.9.1 Memory 最低可用标准
-
-理想态合同系统不能假设 memory 所有子模块都已经工业化完成。
-
-phase 1 的最低事实输入标准应为:
-
-1. `state.json`
-2. 最近章节摘要
-3. `index.db` 中可直接读取的实体、关系、状态变化
-
-`MemoryOrchestrator` 可作为增强项,但不是 phase 1 的硬依赖。
-
-如果 memory 子系统某些组件不可用,合同系统的降级策略应为:
-
-- 继续生成规则合同
-- 对事实合同降级为“state + summaries + index facts”
-- 不因 memory 半成品状态阻断 `generate-master / generate-volume`
-
-### 10.10 迁移阶段划分
-
-这部分虽然是理想态 spec,也需要给出最基本的迁移判断标准。
-
-#### Day 1 变更
-
-- 新增 `题材与调性推理.csv`
-- 新增最小 `generate-master`
-- 保留现有 `reference_search.py`
-- 保留现有 `context_manager`
-- 保留 `genre-profiles.md` 作为活跃题材源
-- skill 继续按 `2026-04-09` 的 L0/L1/L2 策略运行
-- `story-system` 先以内核模块存在,不强制对 skill 暴露
-- 若提供 CLI,仅先以 `webnovel.py story-system ...` 形式接入统一入口
-
-#### Day 2 变更
-
-- 引入 `Volume` 层合同
-- skill 改为“合同优先 + 局部 reference 按需加载”
-- `genre-profiles.md` 仍可作为回退源,不应提前降级
-- `context_manager` 正式注入 `story_contract`
-- dashboard / preflight / health / backup 开始识别 `.story-system`
-
-#### Day 3 及以后
-
-- 在满足知识密度阈值后,`templates/genres/*.md` 逐步退出主链
-- `reference_search.py` 只作为底层 primitive
-- `story_system` 成为技能层的唯一系统入口
-
-### 10.10.1 知识密度切换阈值
-
-在以下条件满足前,不应把 `genre-profiles.md` 和 `templates/genres/*.md` 提前降级:
-
-1. `题材与调性推理.csv` 已覆盖当前系统支持的主流主题材
-2. `genre_aliases.py` 中的高频别名能在 route table 中找到稳定归一化目标
-3. init / plan / write 的代表性题材输入,经人工抽样验证后,大部分可以直接由 `CSV + contract` 生成可用结果
-4. 对当前活跃题材家族,至少已有一轮真实项目验证,而不是只靠静态录入
-
-简单说:
-
-- 不是 CSV 一出现就替换 md
-- 而是 CSV 达到“可稳定承接主链”的知识密度后,才逐步切换
-
-### 10.10.2 Contract 接管边界
-
-Day 2 以后,contract 应优先接管:
-
-- 题材调性
-- 毒点红线
-- 全局系统边界
-- 卷级 / 章级执行目标
-
-继续由 step-bound reference 保留:
-
-- 局部写作技法
-- 局部场景模式
-- 命名与语汇
-- 润色、排版、anti-ai、review schema
-
-运行时还必须补一条:
-
-- `context_manager` 和 `webnovel-write` 默认读取**最终结算态合同**
-- 完整 override ledger 只在 debug / dashboard / health report 中默认展开
-
-### 10.11 `scripts/data_modules/config.py`
-
-当前 `DataModulesConfig` 已经是项目级事实上的统一配置入口,覆盖:
-
-- 路径
-- 检索参数
-- 上下文预算
-- memory / index / review 相关调优
-
-理想态 `story_system` 不应再平行发明第二套完全独立的配置树。
-
-推荐策略:
-
-1. phase 1 直接复用 `DataModulesConfig`
-2. 新增配置统一采用 `story_system_*` 命名空间
-3. 若后续单独拆出 `StorySystemConfig`,也应只是 `DataModulesConfig` 的薄包装或子视图,而不是完全分家
-
-必须避免的反模式:
-
-- 一套配置给 runtime 用
-- 另一套配置给 story system 用
-- 两边字段重名但语义不同
-
-那样会让 contract、context、memory 三条链路重新分叉。
-
-### 10.12 `.story-system` 的运维接入
-
-既然 `.story-system` 被定义为新的持久化真源,它就不能处于现有运维链路的盲区。
-
-至少需要接入以下四类系统:
-
-#### preflight
-
-在 Day 2 以后,`preflight` 至少应增加:
-
-- `.story-system/MASTER_SETTING.md` 是否存在
-- `.story-system/MASTER_SETTING.json` 是否存在
-- JSON 合同是否可读
-
-#### dashboard / watcher
-
-dashboard 不应只监听 `.webnovel/`。
-
-至少还应关注:
-
-- `.story-system/MASTER_SETTING.*`
-- `.story-system/volumes/*.json`
-- `.story-system/chapters/*.json`
-
-否则合同变更后,前端不会刷新。
-
-#### health / status report
-
-状态报告不应只覆盖 `.webnovel/state.json` 和现有运行时数据。
-
-还应增加:
-
-- 合同存在性检查
-- 合同新鲜度检查
-- override ledger 摘要
-- contract / state 是否明显冲突的健康提醒
-
-#### backup / fallback backup
-
-Git 模式下通常天然覆盖 `.story-system/`,但本地降级备份也必须覆盖它。
-
-最低要求:
-
-- Git 不可用时,fallback backup 不能只备份 `.webnovel/state.json`
-- 至少应同时备份 `.story-system/` 的当前合同文件
-
-否则恢复点会丢失新的系统真源。
-
----
-
-## 11. 旧模块去留决策
-
-### 11.1 保留并降级
-
-以下模块保留,但降级为下层能力:
-
-- `reference_search.py`
-- `query_router.py`
-- `context_manager.py`
-- `genre_aliases.py`
-- `genre_profile_builder.py`
-
-其中:
-
-- `context_manager.py` 更准确的路径是“保留并演进后收敛”
-- `genre_aliases.py / genre_profile_builder.py` 更准确的路径是“保留并迁移其知识到 route table”
-
-### 11.2 保留并重定位
-
-以下目录保留,但职责改变:
-
-- `references/csv/`
-- `references/shared/`
-- `references/outlining/`
-- `templates/output/`
-
-### 11.3 保留但不再担任主源
-
-以下内容继续保留,但不再是主系统输入:
-
-- `references/genre-profiles.md`
-- `templates/genres/*.md`
-
-### 11.4 应新增的一线模块
-
-理想态应新增:
-
-- `story_system/engine/search_engine.py`
-- `story_system/engine/reasoning_engine.py`
-- `story_system/engine/aggregator.py`
-- `story_system/engine/anti_pattern_engine.py`
-- `story_system/engine/contract_builder.py`
-- `story_system/engine/renderer.py`
-- `story_system/engine/persistence.py`
-
----
-
-## 12. Anti-Patterns 统一机制
-
-### 12.1 统一原则
-
-理想态不要求所有旧表立即统一字段名,但要求 story system 统一抽取。
-
-### 12.2 建议映射
-
-```python
-ANTI_PATTERN_SOURCE_FIELDS = {
-    "题材与调性推理": ["强制禁忌/毒点"],
-    "人设与关系": ["忌讳写法"],
-    "爽点与节奏": ["常见崩盘误区"],
-    "场景写法": ["反面写法"],
-    "写作技法": ["常见误区"],
-    "桥段套路": ["忌讳写法"],
-}
-```
-
-说明:
-
-- 若 phase 1 按 `retrofit spec` 为 `桥段套路.csv` 新增了 `忌讳写法` 列,phase 2 必须继续读取该列,避免两份 spec 再次分叉
-- `桥段套路.反套路变种` 是变体灵感来源,不默认纳入 anti-pattern 聚合
-- `金手指与设定.数值控制边界` 属于 `system_constraints`,不应直接等同于 anti-pattern
-- 只有显式负面字段,才进入 `ANTI_PATTERN_SOURCE_FIELDS`
-
-### 12.3 输出要求
-
-所有生成合同中,必须存在醒目的:
-
-- `## Anti-Patterns`
-
-该区块是不可逾越的硬红线。
-
----
-
-## 13. 运行时工作流
-
-### 13.1 开书阶段
-
-输入:
-
-- `写个赛博朋克黑客流`
-
-流程:
-
-1. reasoning engine 锁定主辅题材
-2. 多域聚合基础表
-3. 生成 `MASTER_SETTING`
-4. 写入 `.story-system/`
-5. 初始化设定集
-
-### 13.2 规划阶段
-
-输入:
-
-- `规划第一卷拍卖会逆袭`
-
-流程:
-
-1. 读取 `MASTER`
-2. 聚合桥段、节奏、场景、人设补充
-3. 生成 `VOLUME_BRIEF`
-4. 再拆 `CHAPTER_BRIEF`
-
-### 13.3 写作阶段
-
-输入:
-
-- `写第 15 章`
-
-流程:
-
-1. 读取 `chapter_015`
-2. 若不存在则回退 `volume`
-3. 再回退 `master`
-4. 读取该层级最终结算后的有效字段
-5. 只注入本章直接相关的 override 摘要,而不是完整历史账本
-6. 结合 state / memory / recent context 起草正文
-
-### 13.4 审查阶段
-
-输入:
-
-- `审查第 15 章`
-
-流程:
-
-1. 读取 `review contract`
-2. 读取 `anti_patterns`
-3. 结合 review schema 输出结构化问题
-
----
-
-## 14. 测试与验证策略
-
-### 14.1 总原则
-
-这个 CSV 数据库不需要做“每条知识点一个测试”的重型方案。
-
-测试只需要覆盖:
-
-1. 合同生成是否成功
-2. 覆盖规则是否正确
-3. 关键字段是否落地
-4. anti-pattern 是否被聚合
-5. runtime 是否能读取合同
-
-### 14.2 不建议的测试
-
-不建议:
-
-1. 为每个 CSV 条目写测试
-2. 为每个知识点写断言
-3. 为内容语义质量写机械化单测
-
-### 14.3 应保留的验证
-
-建议保留:
-
-1. reasoning 命中 smoke test
-2. contract schema test
-3. persistence 覆盖规则 test
-4. CLI 生成最小链路 test
-
-建议额外补两类低成本高价值验证:
-
-5. 多标签推理融合 test
-6. override ledger 生成 test
-7. JSON -> Markdown 渲染一致性 test
-8. `locked + lock_policy` 行为 test
-
-验证重点不是“知识点对不对”,而是:
-
-- 合同结构稳不稳定
-- 多标签毒点是否并集
-- 覆盖是否有显式记录
-- JSON 是否通过 schema 校验
-
----
-
-## 15. 实施约束
-
-### 15.1 知识迁移约束
-
-知识迁移必须人工完成。
-
-禁止:
-
-- 自动抽取 md 为 csv
-- 自动翻译后批量入库
-- 自动拆句生成知识条目
-
-允许:
-
-- 人工阅读旧资料
-- 人工提炼摘要、关键词、同义词
-- 人工翻译英文 prompt 后录入
-
-### 15.2 工程实现约束
-
-工程层可以写脚本、CLI、渲染器、聚合器、持久化模块。
-
-禁止脚本迁移,不等于禁止工程脚本开发。
-
----
-
-## 16. 最终架构决策
-
-本 spec 给出的理想态决策如下:
-
-1. `references/csv` 升级为主知识仓
-2. `references/*.md` 保留为方法论层
-3. `templates/genres` 降级为样例模板层
-4. `templates/output` 升级为合同模板层
-5. `reference_search.py` 退出一线,降级为底层 primitive
-6. `genre-profiles.md` 退出核心配置中心,职责转入结构化 reasoning 数据
-7. `skills/webnovel-*` 改为“contract 优先 + 局部 step-bound reference 按需加载”
-8. `state / index / memory` 继续保留,作为运行时事实与证据层
-9. `story_system` 对外只通过统一 CLI `webnovel.py story-system ...` 暴露稳定入口
-10. `.story-system` 必须接入 preflight / dashboard / health / backup,不能成为运维盲区
-11. `.story-system/*.json` 是唯一真理源,`*.md` 只是只读渲染产物
-12. reasoning engine 采用 `L0 / L1 / L2`,由确定性路由保底、LLM 负责低置信语义融合
-13. `locked` 字段必须带 `lock_policy`,剧情级核心设定的变更只能走 `amend proposal + 用户确认`
-
----
-
-## 17. 结语
-
-这次重构真正要完成的,不是“把参考资料查得更准一点”。
-
-真正的目标是:
-
-- 不再让模型每一章都从散资料临时拼世界
-- 而是先建立一个稳定的故事系统
-- 再让规划、写作、审查都机械地服从这个系统
-
-这就是 `webnovel-writer` 从“离散检索工具”升级为“系统级智能写作底座”的关键跃迁。

+ 0 - 398
docs/archive/superpowers/specs/2026-04-14-context-agent-writing-brief-design.md

@@ -1,398 +0,0 @@
-# Context Agent 写作任务书收束设计
-
-> **日期**: 2026-04-14
-> **状态**: 草案 v1
-> **定位**: 收束写前入口,改造 `context-agent` 与 `writer` 的输入边界
-
----
-
-## 1. 文档定位
-
-### 1.1 这份设计要解决什么问题
-
-当前写前链路有三个问题:
-
-1. `Step 0.5` 与 `context-agent` 职责重叠
-2. 现有创作包像审计表,不像作者愿意接的任务书
-3. `writer` 还能间接接触过多原始材料,导致输入面没有真正收束
-
-这份设计要做的事不是再造一套新底稿,而是:
-
-- 让 `context-agent` 成为唯一写前组装入口
-- 让当前 research 拼出来的任务包继续作为**内部底稿**
-- 在其上生成一份只给 `writer` 读的**写作任务书**
-
-### 1.2 一句话结论
-
-后续主链应改为:
-
-```text
-context-agent
-  -> research
-  -> 当前任务包(内部底稿)
-  -> 写作任务书
-  -> writer
-```
-
-其中:
-
-- 内部底稿继续服务 agent 自己
-- `writer` 只接收 `写作任务书`
-- 这件事作为**调用层事实**实现,不写进 `writer` 的明面约束
-
----
-
-## 2. 设计目标与非目标
-
-### 2.1 目标
-
-这次改造只达成 6 个目标:
-
-1. 收回 `Step 0.5`,并入 `context-agent`
-2. 保留现有任务包作为内部底稿,不发明新底稿格式
-3. 新增最终产物 `写作任务书`
-4. 让 `writer` 的输入面事实上收束到一份任务书
-5. 把生硬规则翻译成自然的约稿口吻
-6. 保留 `anti-ai-guide.md` 与 `core-constraints.md` 作为写前事实源
-
-### 2.2 非目标
-
-这次设计明确不做:
-
-1. 不重做 `story_contract` schema
-2. 不要求给 `writer` 展示合同、检查清单、规则来源
-3. 不要求为任务书建立字段映射表
-4. 不把 `anti-ai-guide.md` 拆进 CSV
-5. 不把所有 `core-constraints.md` 内容都翻给 `writer`
-
----
-
-## 3. 核心原则
-
-### 3.1 事实隔离,不写成口头约束
-
-`writer` 只读任务书,这必须通过调用层实现。
-
-但不要在 `writer` 提示词里写:
-
-- “你只能看任务书”
-- “不要读取其他材料”
-- “合同在某处”
-
-原因很简单:
-
-- 一旦要靠提示词强调边界,说明边界没有在编排层真正成立
-- 如果 `writer` 还觉得信息不够,问题应回溯到任务书质量,而不是放权给 `writer`
-
-### 3.2 写作任务书要像约稿,不像说明书
-
-`writer` 看到的文案不应再是:
-
-- 合同条目
-- 检查清单
-- 禁止事项平铺
-- 审计式字段展开
-
-而应像一个懂创作的搭档在交代这一章:
-
-- 这章写什么
-- 谁在场
-- 会发生什么
-- 该抓什么感觉
-- 收在哪里
-
-### 3.3 语义生成优先,不做硬映射表
-
-`context-agent` 应基于内部底稿语义生成任务书。
-
-这意味着:
-
-- 不做“字段 A 必须去段落 B”的映射表
-- 只规定任务书要说清什么
-- 不规定必须按什么字段顺序硬拼
-
-### 3.4 通用守则保留全文,最终文案自然化
-
-`anti-ai-guide.md` 与 `core-constraints.md` 继续作为写前事实源存在。
-
-但它们进入最终任务书时,必须被翻译成自然口吻。
-
-正文中不要出现:
-
-- `Anti-AI`
-- `检查`
-- `约束`
-- `合同`
-- `blocking_rules`
-- 文件名或路径
-
----
-
-## 4. 两层结构
-
-### 4.1 内部底稿
-
-内部底稿不新增新结构体,直接沿用当前 `context-agent` research 拼出来的任务包。
-
-它继续包含:
-
-- 章纲
-- 前文摘要
-- `story_contract`
-- latest accepted `CHAPTER_COMMIT`
-- `plot_structure`
-- `prewrite_validation`
-- `reader_signal`
-- `genre_profile`
-- `writing_guidance`
-- `long_term_memory`
-- 其他按需 research 结果
-
-同时,在生成任务书前,`context-agent` 还必须全文读取:
-
-- `references/shared/core-constraints.md`
-- `skills/webnovel-write/references/anti-ai-guide.md`
-
-这两份材料进入内部底稿,但不原样进入最终任务书。
-
-### 4.2 写作任务书
-
-`写作任务书` 是给 `writer` 的唯一输入。
-
-它只保留对成文直接有用的信息,并改写成约稿口吻。
-
-它不暴露:
-
-- 来源
-- 文件名
-- 合同层级
-- 检查流程
-- 数据处理流程
-
----
-
-## 5. 哪些内容不进 CSV
-
-### 5.1 明确不进 CSV
-
-以下内容继续保留为 md 或模板层资产:
-
-1. `anti-ai-guide.md`
-2. `core-constraints.md` 中的全局流程与通用守则
-3. 写作任务书模板本身
-4. `context-agent` / `writer` 的角色边界
-5. 完整风格锚点正文
-
-原因一致:
-
-- 这些内容不是按需检索条目
-- 不是命中几条就够
-- 顺序、语气和整体结构本身就是信息
-
-### 5.2 后续可继续进 CSV 的内容
-
-以下内容仍适合继续结构化:
-
-1. 题材化爽点类型
-2. 题材化节奏风险
-3. 场景化写作重定向
-4. 题材特定毒点与误区
-5. 可复用桥段推进方式
-
-也就是说:
-
-- 通用写作运行守则,不进 CSV
-- 题材化、场景化、可检索知识,继续进 CSV
-
----
-
-## 6. `core-constraints` 与 `anti-ai-guide` 的处理方式
-
-### 6.1 `anti-ai-guide.md`
-
-规则固定为:
-
-1. 全文读取
-2. 不拆 CSV
-3. 不原样给 `writer`
-4. 只翻译成任务书里的自然提醒
-
-### 6.2 `core-constraints.md`
-
-规则固定为:
-
-1. 全文读取
-2. 不原样给 `writer`
-3. 只提炼其中会直接影响本章成文的部分
-
-以下内容通常不应进入最终任务书:
-
-- Data Agent
-- `index.db`
-- 新实体处理流程
-- 路径、命令、系统流程
-- 只对系统运转有意义的后台规则
-
-### 6.3 最终呈现方式
-
-最终任务书中,这两份材料都只能变成这种表达:
-
-- “这章先把人和事往前推,不要急着解释”
-- “情绪尽量落在动作和反应里,不要一上来把话说透”
-- “结尾别把局面放平,留一点还没彻底落地的东西”
-
-而不是:
-
-- “执行 Anti-AI 第 3 条”
-- “遵守三大定律”
-- “检查 blocking rules”
-
----
-
-## 7. 写作任务书的固定结构
-
-最终任务书固定为五段式。
-
-### 7.1 开篇委托
-
-先说清这是什么书、哪一章、这一章一句话要写成什么。
-
-应像:
-
-- 你现在要写《某书》第 12 章《某标题》
-- 这一章主要写……
-- 重点不是……而是……
-
-### 7.2 这一章的故事
-
-说清:
-
-- 这一章谁要做什么
-- 为什么非做不可
-- 真正难的地方在哪
-- 局面会怎么推进
-
-### 7.3 这章的人物
-
-只写对成文直接有用的人物信息:
-
-- 当前状态
-- 眼前驱动力
-- 这章里的主要作用
-- 说话和行动倾向
-
-### 7.4 这章怎么写更顺
-
-这一段负责承接:
-
-- 节奏提醒
-- 情绪写法
-- 对话写法
-- 参考气质
-- 风格锚点
-- 从 `anti-ai-guide` 和 `core-constraints` 翻译来的自然提醒
-
-这一段是整个任务书最关键的部分。
-
-### 7.5 这章收在哪里
-
-只说明结尾应该停在什么感觉上:
-
-- 某个动作
-- 某个画面
-- 某句对话
-- 某种未完感
-
-不把事情全部讲平。
-
----
-
-## 8. 语言要求
-
-`写作任务书` 的语言必须满足:
-
-1. 简洁中文
-2. 短句优先
-3. 不端着
-4. 不训话
-5. 不写制度口吻
-6. 不暴露系统术语
-
-推荐语气:
-
-- 像懂创作的搭档作者
-- 像在交代这一章怎么写会更顺
-- 像约稿,不像操作手册
-
----
-
-## 9. 流程改造
-
-### 9.1 新流程
-
-后续写前主链改为:
-
-```text
-Step 1: context-agent
-  -> research
-  -> 生成内部底稿
-  -> 生成写作任务书
-
-Step 2: writer
-  -> 只接收写作任务书
-  -> 直接起草正文
-```
-
-### 9.2 `Step 0.5` 的处理
-
-现有 `Step 0.5` 的职责应全部回收给 `context-agent`。
-
-不再允许:
-
-- 一部分信息由 `context-agent` 做
-- 一部分信息由 `writer` 自己补
-- 一部分提醒在写时临时再拼
-
-### 9.3 失败语义
-
-如果 `writer` 在只读任务书的前提下仍然表现出:
-
-- 信息不够
-- 情绪不对
-- 冲突不清
-- 节奏发硬
-
-应优先判断为:
-
-- `context-agent` 的任务书写得不够好
-
-而不是给 `writer` 继续开放原始材料。
-
----
-
-## 10. 验收标准
-
-这次改造完成后,至少要满足:
-
-1. `context-agent` 成为唯一写前组装入口
-2. 现有任务包继续作为内部底稿使用
-3. `writer` 的最终输入只剩一份写作任务书
-4. 任务书正文不出现“约束 / 检查 / 合同 / Anti-AI”等词
-5. 任务书正文读起来像约稿,不像制度说明
-6. `anti-ai-guide.md` 全文继续参与生成,但不原样暴露
-7. `core-constraints.md` 全文继续参与生成,但只挑成文相关内容翻译
-
----
-
-## 11. 实施建议
-
-实现顺序建议如下:
-
-1. 先改 `context-agent` 的职责说明
-2. 再改任务书生成模板
-3. 再收掉 `Step 0.5` 的重复职责
-4. 最后把 `writer` 的输入面切到只读任务书
-
-这次不要先动合同 schema,也不要先扩 CSV。
-
-先把写前入口和最终文案收束,收益最大,风险最小。

+ 0 - 701
docs/archive/superpowers/specs/2026-04-14-story-system-final-convergence-spec.md

@@ -1,701 +0,0 @@
-# Story System 最终收束 Spec
-
-> **日期**: 2026-04-14
-> **状态**: 草案 v1
-> **定位**: 基于当前代码真实状态,把 Story System 从“半成品并存”收束到“六层主链 + 消费端同步”的最终 spec
-
----
-
-## 1. 文档定位
-
-### 1.1 这份 spec 解决什么问题
-
-前面的文档已经分别回答了三个问题:
-
-- [`current-system-diagnosis.md`](../../architecture/current-system-diagnosis.md)
-  - 当前系统哪里散、哪里重复、哪里有半成品
-- [`2026-04-12-story-system-evolution-spec.md`](./2026-04-12-story-system-evolution-spec.md)
-  - 六层主链应该长什么样
-- [`2026-04-14-context-agent-writing-brief-design.md`](./2026-04-14-context-agent-writing-brief-design.md)
-  - 写前任务书入口怎么收束
-
-这份 spec 解决的是更直接的问题:
-
-> 在不考虑向后兼容的前提下,如何把现在的代码、CSV、合同、提交链、投影链和消费端,一次性收束到最终可用状态。
-
-### 1.2 一句话结论
-
-这次收束的目标不是“再补一个模块”,而是:
-
-- 六层全做
-- 旧散写路径直接删
-- `context_manager` 降级为纯 JSON 组装器
-- `CSV_CONFIG + 裁决表 + 合同树 + commit/projection` 成为唯一主链
-- 所有消费者只吃合同和投影视图
-
----
-
-## 2. 已确认的关键决策
-
-这轮 brainstorming 已经确认以下决策,不再反复摇摆:
-
-### 2.1 范围
-
-- 做 **六层全覆盖**
-- 不只改代码层
-- 必须同步改 skill / agent / CLI / eval / docs 等消费端
-
-### 2.2 向后兼容
-
-- **不考虑向后兼容**
-- 旧散写路径、旧直读路径、旧 fallback 路径,能删就删
-- 不保留 deprecated 双路径
-
-### 2.3 `context_manager`
-
-- 降级为 **纯 JSON payload 组装器**
-- 不再负责 text 渲染
-- 不再保留为旧说明书式文本输出服务的 snapshot 逻辑
-
-### 2.4 `CSV_CONFIG`
-
-- 放在 `reference_search.py` 里
-- 形态模仿 `ui-ux-pro-max/core.py`
-- 每张表显式声明 `file / search_cols / output_cols / poison_cols / role`
-
-### 2.5 裁决层
-
-- 新建独立 `裁决规则.csv`
-- key 为题材
-- 它回答的是“命中后怎么裁决、优先谁、注入哪层”,不是“查哪些表”
-
-### 2.6 `anti-ai-guide.md` 与 `core-constraints.md`
-
-- `anti-ai-guide.md` 保留为 md 真源
-  - 不拆 CSV
-  - 不给消费者直读
-  - 只在需要的步骤被上游吸收后转写
-- `core-constraints.md` 不再整篇读取
-  - 拆成具体约束
-  - 分步骤落到合同、runtime、review、commit 校验和消费端文案里
-
-### 2.7 消费端真源
-
-- 所有消费者只允许直接吃:
-  - 合同树
-  - `CHAPTER_COMMIT`
-  - 投影视图
-- 不再允许运行时直接读 CSV / md / reference 来补洞
-
-### 2.8 首批题材范围
-
-这次先做好 7 个题材:
-
-1. 西方奇幻
-2. 东方仙侠
-3. 科幻末世
-4. 都市日常
-5. 都市修真
-6. 都市高武
-7. 历史古代
-
-其余题材不在本轮收束范围内。
-
-### 2.9 实施顺序
-
-- 从底层往上推
-- 走串行,不走并行
-- 先把基础收稳,再做消费者同步
-
----
-
-## 3. 总体链路
-
-最终链路固定为:
-
-```text
-知识表 / 裁决表
-        ↓
-CSV_CONFIG + reference_search
-        ↓
-story_system_engine
-        ↓
-MASTER / VOLUME / CHAPTER / REVIEW
-        ↓
-context_manager(只出 JSON)
-        ↓
-context-agent(按示例写任务书)
-        ↓
-webnovel-write Step 2(只吃任务书)
-        ↓
-review / data-agent
-        ↓
-CHAPTER_COMMIT
-        ↓
-projection writers
-        ↓
-state / index / summaries / memory / vector
-```
-
-这里有两个硬边界:
-
-1. 消费端不再运行时直读知识层
-2. 写后事实只允许经 `CHAPTER_COMMIT -> projection` 进入各存储
-
----
-
-## 4. Section 1:`CSV_CONFIG` 注册层
-
-### 4.1 要做什么
-
-在 `reference_search.py` 里引入注册式 `CSV_CONFIG`,替代当前全局硬编码的:
-
-- `_SEARCH_FIELD_WEIGHTS`
-- `_CONTENT_COLUMNS`
-
-改成每张表自己定义:
-
-- `file`
-- `search_cols`
-- `output_cols`
-- `poison_cols`
-- `role`
-
-### 4.2 目标形态
-
-```python
-CSV_CONFIG = {
-    "命名规则": {
-        "file": "命名规则.csv",
-        "search_cols": ["关键词", "意图与同义词", "核心摘要"],
-        "output_cols": ["编号", "命名对象", "核心摘要", "大模型指令", "详细展开"],
-        "poison_cols": ["毒点"],
-        "role": "base",
-    },
-    "场景写法": {
-        "file": "场景写法.csv",
-        "search_cols": ["关键词", "意图与同义词", "核心摘要"],
-        "output_cols": ["编号", "模式名称", "核心摘要", "大模型指令", "详细展开"],
-        "poison_cols": ["毒点"],
-        "role": "base",
-    },
-    "题材与调性推理": {
-        "file": "题材与调性推理.csv",
-        "search_cols": ["题材关键词", "别名"],
-        "output_cols": ["题材", "默认调性", "推荐基础表", "推荐动态表"],
-        "poison_cols": ["毒点"],
-        "role": "route",
-    },
-    "裁决规则": {
-        "file": "裁决规则.csv",
-        "search_cols": ["题材"],
-        "output_cols": [
-            "题材",
-            "风格优先级",
-            "爽点优先级",
-            "节奏默认策略",
-            "毒点权重",
-            "冲突裁决",
-            "contract注入层",
-            "反模式",
-        ],
-        "poison_cols": [],
-        "role": "reasoning",
-    },
-}
-```
-
-### 4.3 改动要求
-
-- `reference_search.py`
-  - 改为从 `CSV_CONFIG` 读取 per-table 配置
-- `story_system_engine.py`
-  - 不再内部硬编码表角色
-  - 改为使用 `CSV_CONFIG`
-- 新增校验脚本
-  - 校验 `CSV_CONFIG`
-  - 校验 CSV 表头
-  - 校验 `README.md`
-
-### 4.4 验收
-
-- 同一 CLI 在不同表上确实用的是不同 `search_cols`
-- `CSV_CONFIG` 的每张表字段都能在 CSV 头里找到
-- 校验脚本通过
-
----
-
-## 5. Section 2:CSV 内容修补
-
-### 5.1 要做什么
-
-对现有表做一次统一审计和修补,让它们能稳定被裁决层和合同层消费。
-
-### 5.2 重点问题
-
-- 毒点列命名不统一
-- 路由表字段不足
-- 同义词填充不足
-- 各题材覆盖不均衡
-
-### 5.3 具体改造
-
-#### 5.3.1 毒点列统一
-
-把以下旧列统一改名为 `毒点`:
-
-- `反面写法`
-- `忌讳写法`
-- `常见误区`
-- `常见崩盘误区`
-
-#### 5.3.2 路由表补字段
-
-`题材与调性推理.csv` 至少补齐:
-
-- `推荐基础表`
-- `推荐动态表`
-- `默认调性`
-- `风格锚点`
-
-#### 5.3.3 内容补全规则
-
-所有补全都手工做,不写自动迁移脚本。
-
-最低要求:
-
-- 每张表至少覆盖这 7 个题材的常见场景
-- 每条 `意图与同义词` 至少 3 个同义表达
-- 每条 `毒点` 非空
-
-#### 5.3.4 README 同步
-
-`README.md` 必须同步更新:
-
-- 毒点列统一命名
-- 路由表新字段
-- 各表 schema 描述
-
-### 5.4 验收
-
-- 所有表的毒点列统一叫 `毒点`
-- 路由表能返回推荐基础表和动态表
-- `README` 与 `CSV_CONFIG` 对齐
-
----
-
-## 6. Section 3:裁决表
-
-### 6.1 要做什么
-
-新建 `裁决规则.csv`,作为独立 `Reasoning Layer`。
-
-它不负责“查哪些表”,只负责:
-
-- 多条命中后怎么选
-- 哪类爽点优先
-- 哪类毒点更致命
-- 结果注入合同树哪一层
-
-### 6.2 与路由表的边界
-
-- 路由表:回答“查哪些表”
-- 裁决表:回答“查到之后怎么用”
-
-这正是 `ui-ux-pro-max` 里:
-
-- `products.csv`
-- `ui-reasoning.csv`
-
-的对应关系。
-
-### 6.3 字段
-
-`裁决规则.csv` 至少包含:
-
-- `题材`
-- `风格优先级`
-- `爽点优先级`
-- `节奏默认策略`
-- `毒点权重`
-- `冲突裁决`
-- `contract注入层`
-- `反模式`
-
-### 6.4 首批覆盖
-
-先覆盖 7 个题材,各 1 行。
-
-### 6.5 验收
-
-- `source_trace` 能追溯到裁决表
-- 多表命中时,顺序符合 `冲突裁决`
-- 同题材输出能解释“为什么是这些条目进入合同”
-
----
-
-## 7. Section 4:engine 接入裁决表
-
-### 7.1 当前问题
-
-现在的 `story_system_engine.build()` 只有:
-
-1. 路由
-2. 检索
-3. 直接组装
-
-没有显式裁决层。
-
-### 7.2 目标流程
-
-改成:
-
-1. `_route()`
-2. `_collect_tables()`
-3. `_load_reasoning()`
-4. `_apply_reasoning()`
-5. `_rank_anti_patterns()`
-6. `_assemble_contract()`
-
-### 7.3 新增方法
-
-- `_load_reasoning(genre)`
-- `_apply_reasoning(reasoning, base, dynamic)`
-- `_rank_anti_patterns(reasoning, ...)`
-- `_assemble_contract(ranked, anti_patterns, reasoning)`
-
-### 7.4 `source_trace`
-
-最终进入合同的内容都要带:
-
-- `source_table`
-- `source_id`
-- `reasoning_rule`
-- `priority_rank`
-- `inject_target`
-
-### 7.5 验收
-
-- `writing_guidance` 顺序符合裁决表
-- `source_trace` 都有 `reasoning_rule`
-- 低优先级冲突条目能被过滤或降级
-
----
-
-## 8. Section 5:`context_manager` 瘦身
-
-### 8.1 目标
-
-把 `context_manager.py` 从:
-
-- 数据组装
-- 文本渲染
-- snapshot 缓存
-- checklist/评分说明书
-
-降级为:
-
-- **纯 JSON payload 组装器**
-
-### 8.2 保留职责
-
-- 读 contracts
-- 读 runtime sources
-- 组装 `genre_profile`
-- 组装 `writing_guidance`
-- 组装 `reader_signal`
-- 组装 `plot_structure`
-- 组装 `prewrite_validation`
-- 返回统一 dict
-
-### 8.3 删除职责
-
-- 所有 `_render_*`
-- 所有旧说明书式 text 输出
-- 为 text 渲染服务的 snapshot 管理
-- 与已拆 builder 重复的内联逻辑
-
-### 8.4 输出形态
-
-`build_context()` 只返回:
-
-```json
-{
-  "meta": {"context_contract_version": "v3", "chapter": 12},
-  "story_contract": {},
-  "runtime_status": {},
-  "latest_commit": {},
-  "prewrite_validation": {},
-  "plot_structure": {},
-  "scene": {},
-  "writing_guidance": {},
-  "reader_signal": {},
-  "genre_profile": {},
-  "long_term_memory": {},
-  "core": {}
-}
-```
-
-### 8.5 相关要求
-
-- `snapshot_manager.py`
-  - 若无其他消费方,整文件删除
-- `extract_chapter_context.py`
-  - 不再承担旧审计式文本说明书职责
-- 最终写作任务书
-  - 由 `context-agent` 直接根据 JSON payload + 示例生成
-
-### 8.6 验收
-
-- `context_manager.py` 行数压到 400 行以下
-- 不再有字符串拼接型说明书输出
-- snapshot 逻辑已删除
-
----
-
-## 9. Section 6:旧散写路径清理
-
-### 9.1 问题
-
-现在仍有两条写入路径并存:
-
-- 新链:`data-agent -> chapter-commit -> projection`
-- 旧链:skill / agent 直接写 `state / index / summaries / memory`
-
-这必须收束成一条。
-
-### 9.2 要删的路径
-
-#### `webnovel-write`
-
-- Step 2 直接 `state set-chapter-status`
-- Step 4 直接 `state set-chapter-status`
-
-#### 其他直写入口
-
-- skill / agent 中任何 `index process-chapter`
-- data-agent 中任何直接写 `state/index/memory` 的描述
-
-### 9.3 合法直写保留
-
-只保留:
-
-- `webnovel-init`
-- `webnovel-plan`
-- 运维类人工修复命令
-
-都不在创作主链内。
-
-### 9.4 `chapter_status`
-
-不再由 skill 分步手推。
-
-改成:
-
-- accepted commit -> projection 推到 `chapter_committed`
-- rejected commit -> projection 推到 `chapter_rejected`
-
-### 9.5 验收
-
-- `skills/` 和 `agents/` 里不再有 `state set-chapter-status`
-- `skills/` 和 `agents/` 里不再有 `index process-chapter`
-- 各存储只由对应 projection writer 写入
-
----
-
-## 10. Section 7:projection 层收束
-
-### 10.1 目标
-
-确认 `CHAPTER_COMMIT -> projection` 是唯一写入链路,并补完 event 路由覆盖。
-
-### 10.2 要做的事
-
-#### 10.2.1 补全 `EventProjectionRouter`
-
-对照 `story_event_schema.py`,确保事件类型覆盖完整。
-
-#### 10.2.2 `chapter_status` 统一由 `state_projection_writer` 推进
-
-- accepted -> `chapter_committed`
-- rejected -> `chapter_rejected`
-
-#### 10.2.3 `projection_status` 回写 commit
-
-最终 commit 文件里不允许残留 `pending`。
-
-#### 10.2.4 失败隔离
-
-单个 writer 失败:
-
-- 不阻断其他 writer
-- 失败项写 `failed`
-- 允许只补跑失败 writer
-
-### 10.3 验收
-
-- `projection_status` 最终都是 `done / failed / skipped`
-- accepted commit 后状态自动推进
-- 单个 writer 失败不拖死全链
-
----
-
-## 11. Section 8:消费端同步
-
-### 11.1 要改的文件
-
-- `agents/context-agent.md`
-- `agents/data-agent.md`
-- `agents/reviewer.md`(必要时)
-- `skills/webnovel-write/SKILL.md`
-- `skills/webnovel-review/SKILL.md`
-- `skills/webnovel-query/SKILL.md`
-- `skills/webnovel-plan/SKILL.md`(检查)
-- `skills/webnovel-init/SKILL.md`(检查)
-- `skills/webnovel-dashboard/SKILL.md`
-- `skills/webnovel-write/evals/evals.json`
-- `docs/guides/commands.md`
-
-### 11.2 关键改动
-
-#### `webnovel-write`
-
-- 删除 Step 2/4 的状态直写
-- 删除 Step 2 直接加载 `core-constraints` / `anti-ai-guide`
-- Step 1 生成任务书
-- Step 2 只吃任务书
-- Step 5 简化为:
-  - 调 data-agent
-  - 调 chapter-commit
-  - 确认 projection 完成
-
-#### `context-agent`
-
-- research 用底稿
-- 最终按示例写任务书
-- 不再输出旧执行包三层结构
-
-#### `data-agent`
-
-- 只提取事实
-- 只产出 commit artifacts
-- 不再直接写 `state/index/memory`
-
-#### `webnovel-query`
-
-- 查询路径改成 commit / projection
-- 不再运行时直接拼旧真源
-
-### 11.3 静态测试
-
-`test_prompt_integrity.py` 至少新增:
-
-- skill / agent 不得引用已删散写命令
-- `data-agent` 不直写
-- CLI 子命令引用都在注册表里
-
-### 11.4 验收
-
-- skills / agents 不再引用旧散写路径
-- 静态测试通过
-- eval 与命令文档同步
-
----
-
-## 12. Section 9:向量索引增强 + 时序查询接口
-
-### 12.1 这轮只做轻量增强
-
-不引入真正的图引擎,不做新项目级图谱系统。
-
-这轮只做两件轻量事:
-
-1. commit 后把实体/事件写进向量索引
-2. 给现有 SQLite 表加统一的时序查询接口
-
-### 12.2 向量索引补实体语义
-
-#### `EventProjectionRouter`
-
-给关键事件加 `vector` 路由:
-
-- `character_state_changed`
-- `power_breakthrough`
-- `relationship_changed`
-- `world_rule_revealed`
-- `world_rule_broken`
-- `artifact_obtained`
-
-#### 新增 `vector_projection_writer.py`
-
-职责:
-
-- 把 `accepted_events`
-- 把 `entity_deltas`
-
-转成自然语言句子,再写入向量库。
-
-例如:
-
-- `第47章:韩立突破筑基初期`
-- `第47章:韩立与陈巧倩关系变为合作`
-
-### 12.3 时序查询接口
-
-新建 `knowledge_query.py`,提供:
-
-- `entity_state_at_chapter(entity_id, chapter)`
-- `entity_relationships_at_chapter(entity_id, chapter)`
-
-供 `context-agent` research 阶段调用。
-
-### 12.4 不做的事
-
-- 不引入 `neo4j`
-- 不引入 `petgraph`
-- 不做图遍历
-- 不做关系可视化
-
-### 12.5 验收
-
-- 事件 chunk 能进向量库
-- 指定章节状态查询可用
-- 指定章节关系查询可用
-- RAG 能同时命中正文 chunk 和事件 chunk
-
----
-
-## 13. 实施顺序
-
-这轮按严格串行走:
-
-1. `CSV_CONFIG`
-2. CSV 内容修补
-3. 裁决表
-4. engine 接入裁决表
-5. `context_manager` 瘦身
-6. 旧散写路径清理
-7. projection 层收束
-8. 消费端同步
-9. 向量索引增强 + 时序查询
-
-原因很简单:
-
-- 这不是日常迭代
-- 这是一次性系统收束
-- 中间态存在多久不重要
-- 重要的是每一步的验收标准清楚,最终状态正确
-
----
-
-## 14. 最终判断
-
-这份 spec 的最终判断可以压成五句话:
-
-1. 六层必须一起收束,不能只补前四层
-2. 消费端不能再直读知识层,必须只吃合同和投影视图
-3. `core-constraints` 要被拆成具体约束,不再整篇读
-4. `anti-ai-guide` 保留,但只由上游吸收后转写
-5. `CHAPTER_COMMIT -> projection` 是唯一写后真源路径
-
-做到这一步,`webnovel-writer` 才算真正从“半成品并存”进入“统一主链”状态。

+ 0 - 475
docs/archive/superpowers/specs/2026-04-16-references-completion-spec.md

@@ -1,475 +0,0 @@
-# References 完善 Spec
-
-> 文档状态:`implemented`(2026-04-16)
-> 依赖:`2026-04-09-skills-restructure-and-reference-gaps.md`、`2026-04-12-story-system-evolution-spec.md`、`2026-04-14-ui-ux-pro-max-skill-architecture-research.md`
-> 配套:`references/csv/genre-canonical.md`(题材权威枚举表)
-
-## 完成记录
-
-- Phase 1 结构层已完成:`CSV_CONFIG` 已补 `prefix` / `required_cols` / `contract_inject`,`validate_csv.py`、`references/README.md`、loading-map、gap-register 已落位。
-- Phase 2 裁决层已完成:`题材与调性推理.csv` 已扩展到 26 行,`裁决规则.csv` 已扩展到 17 行,覆盖 15 个 canonical genre。
-- 验证状态:`validate_csv.py --format json` 当前输出 0 errors / 0 warnings;相关 reference/story-system 测试通过。
-- Phase 3 知识层补录未在本 spec 内继续扩大范围,后续缺口已登记到 `references/index/reference-gap-register.md`。
-
-## 目标
-
-把 `webnovel-writer/references/` 从"骨架已就位但裁决层极薄、缺少校验闭环"的状态,推进到"init → plan → write → review 全链路可依赖"的状态。
-
-本 spec 不做知识条目补录——条目缺口另见 [附录 A](#附录-a知识条目缺口登记表)。本 spec 只解决结构、配置、校验、索引四类问题。
-
-## 现状诊断
-
-### 当前资产清单
-
-```
-references/
-├── csv/                         # 9 张 CSV
-│   ├── README.md                # schema 文档(人类可读)
-│   ├── 命名规则.csv      (45 行)  # NR- | base
-│   ├── 场景写法.csv      (52 行)  # SP- | base
-│   ├── 写作技法.csv      (64 行)  # WT- | base
-│   ├── 桥段套路.csv      (62 行)  # TR- | dynamic
-│   ├── 人设与关系.csv    (58 行)  # CH- | base
-│   ├── 爽点与节奏.csv    (60 行)  # PA- | dynamic
-│   ├── 金手指与设定.csv  (59 行)  # SY- | base
-│   ├── 题材与调性推理.csv  (8 行)  # GR- | route    ← 极薄
-│   └── 裁决规则.csv        (7 行)  # RS- | reasoning ← 极薄
-├── genre-profiles.md            # 题材 profile (已标记 fallback only)
-├── reading-power-taxonomy.md    # 追读力分类
-├── review-schema.md             # 审查输出 schema
-├── index/
-│   ├── reference-loading-map.md # skill→step→trigger→ref 映射
-│   └── reference-gap-register.md# 基线缺口登记
-├── outlining/
-│   └── plot-signal-vs-spoiler.md
-├── review/
-│   └── blocking-override-guidelines.md
-└── shared/
-    ├── core-constraints.md
-    ├── cool-points-guide.md
-    ├── naming-and-voice-gaps.md
-    └── strand-weave-pattern.md
-```
-
-### 代码侧已就位的配套设施
-
-| 组件 | 位置 | 状态 |
-|------|------|------|
-| `CSV_CONFIG` 注册字典 | `reference_search.py:89-154` | ✅ 已存在,per-table `search_cols`/`output_cols`/`poison_col`/`role` |
-| BM25 搜索 primitive | `reference_search.py:160-244` | ✅ |
-| `StorySystemEngine._route()` | `story_system_engine.py:115-159` | ✅ 消费 `题材与调性推理.csv` |
-| `StorySystemEngine._collect_tables()` | `story_system_engine.py:161-185` | ✅ 按 route 推荐表查询 |
-| `StorySystemEngine._apply_reasoning()` | `story_system_engine.py:278-338` | ✅ 消费 `裁决规则.csv` |
-| `RuntimeContractBuilder` | `runtime_contract_builder.py` | ✅ 读 MASTER + plot → volume_brief + review_contract |
-| `ContextManager` | `context_manager.py` | ✅ 读 contracts + genre-profiles + state + summaries |
-
-### 核心问题
-
-| # | 问题 | 影响范围 | 严重度 |
-|---|------|---------|--------|
-| P1 | **裁决规则.csv 只有 7 条**(西方奇幻/东方仙侠/科幻末世/都市日常/悬疑惊悚/历史武侠/玄幻)。大量子流派无裁决规则,`_apply_reasoning()` 退化为无优先级排序 | write 全链路 | 高 |
-| P2 | **题材与调性推理.csv 只有 8 条**(退婚流/规则怪谈/压抑后爆/赘婿流/系统流/无限流/重生流/宫斗流)。未覆盖的题材走 `default_seed_fallback`,路由退化 | init → write 全链路 | 高 |
-| P3 | **无校验脚本**。编号唯一性、前缀一致性、必填列、分隔符规范、列头与 README 对齐——全靠人工自觉 | 数据质量 | 中 |
-| P4 | **CSV_CONFIG 与 README.md 存在双源漂移风险**。README 定义的 schema 和代码里的 `CSV_CONFIG` 没有自动化校验保证对齐 | 维护成本 | 中 |
-| P5 | **`reference-loading-map.md` 与实际 skill 实现有偏移**。部分 skill 已新增/修改 reference 触发条件,map 未同步 | 可审查性 | 低 |
-| P6 | **`references/` 目录缺顶层 README**。新读者无法快速理解 csv vs md vs index vs shared 的边界 | 可读性 | 低 |
-| P7 | **CSV_CONFIG 缺少 `contract_inject` 字段**。裁决规则有 `contract注入层` 列,但 CSV_CONFIG 没有声明这个映射关系,注入点散落在 engine 代码中 | 可审查性 | 低 |
-
----
-
-## 全链路 Reference 消费分析
-
-### init 阶段
-
-```
-用户输入题材/卖点
-  → Read genre-tropes.md, genre-profiles.md
-  → Read worldbuilding/*.md (faction, world-rules, power-systems, character-design)
-  → Read creativity/*.md (constraints, selling-points, combination, inspiration)
-  → CSV: 命名规则 (--skill init --query "{object} {genre}")
-  → story-system CLI (--persist, MASTER_SETTING only)
-      → StorySystemEngine._route()    消费 题材与调性推理.csv
-      → StorySystemEngine._collect()  消费 推荐的 base/dynamic 表
-      → StorySystemEngine._reason()   消费 裁决规则.csv
-  → 输出: .story-system/MASTER_SETTING.json + anti_patterns.json
-```
-
-**init 对 references 的需求**:
-- 题材路由必须命中——用户在 init 时给出的题材/流派/标签是整个系统的起点
-- 如果 `题材与调性推理.csv` 没有匹配行,MASTER_SETTING 的 `core_tone`、`pacing_strategy`、推荐表列表全部为空或退化
-- 如果 `裁决规则.csv` 没有匹配行,anti_patterns 缺少 `反模式` 和 `毒点权重`
-
-### plan 阶段
-
-```
-用户输入卷/章规划
-  → Read genre-profiles.md, strand-weave-pattern.md
-  → Read plot-signal-vs-spoiler.md
-  → Read cool-points-guide.md (按需)
-  → Read reading-power-taxonomy.md (按需)
-  → Read outlining/*.md (conflict-design, chapter-planning, genre-volume-pacing)
-  → CSV: 场景写法 (--skill plan --query "卷级结构 叙事功能")
-  → CSV: 命名规则 (新角色命名时)
-  → CSV: 爽点与节奏 (冲突设计时)
-  → CSV: 桥段套路 (冲突设计时)
-  → story-system CLI (--emit-runtime-contracts)
-      → RuntimeContractBuilder.build_for_chapter()
-  → 输出: volume_brief + review_contract
-```
-
-**plan 对 references 的需求**:
-- 卷级规划需要从 `场景写法` 和 `爽点与节奏` 获取结构性指导
-- `桥段套路` 在冲突设计时提供可选套路模板
-- 命名规则在新角色出场时触发
-- plan 阶段的 outlining 子目录目前只有 `plot-signal-vs-spoiler.md`,但 skill 引用了 `conflict-design.md`、`chapter-planning.md`、`genre-volume-pacing.md`(均为 skill-local references)
-
-### write 阶段
-
-```
-context-agent 组装写作任务书
-  → ContextManager.build_context()
-      → 读 .story-system/ 下所有 contracts
-      → 读 genre-profiles.md (fallback)
-      → 读 reading-power-taxonomy.md
-      → 读 设定集/*.md
-      → 读 state.json, summaries, outlines, index.db
-  → 输出: JSON context pack
-
-Step 2 (起草)
-  → Read core-constraints.md
-  → CSV: 命名规则 (新角色)
-  → CSV: 场景写法 (战斗/对峙)
-  → CSV: 写作技法 (对话/情感)
-  → CSV: 场景写法 (高频桥段)
-
-Step 3 (审查)
-  → Read review-schema.md, core-constraints.md
-  → Read cool-points-guide.md (按需)
-  → Read strand-weave-pattern.md (按需)
-  → Read blocking-override-guidelines.md (按需)
-
-Step 4 (润色)
-  → Read polish-guide.md, typesetting.md, style-adapter.md
-  → Read anti-ai-guide.md (ai_flavor issue 存在)
-```
-
-**write 对 references 的需求**:
-- contracts(来自 init + plan 的持久化产物)是第一真源
-- CSV 在 Step 2 按条件触发,是对 contract 的补充
-- md references 在 Step 3-4 是流程闸门和润色指南
-- 如果 init 阶段的 MASTER_SETTING 因路由/裁决空缺而质量差,这里的 contracts 就质量差
-
-### review 阶段
-
-```
-  → Read core-constraints.md, review-schema.md
-  → Read blocking-override-guidelines.md (blocking issue)
-  → Read cool-points-guide.md (爽点分析)
-  → Read strand-weave-pattern.md (多线审查)
-  → Read anti-ai-guide.md (ai_flavor >= 3)
-```
-
-**review 对 references 的需求**:
-- 纯 md 消费,不直接查 CSV
-- 依赖 review_contract(来自 plan 阶段的 RuntimeContractBuilder)
-- 如果 review_contract 的 `genre_specific_risks` 空缺,genre-specific 审查项缺失
-
----
-
-## 设计决策
-
-### D1: 裁决规则.csv 的补全策略
-
-**目标**:覆盖 `genre-profiles.md` 中定义的全部高频题材 + `题材与调性推理.csv` 中出现的全部流派。
-
-**当前覆盖**(7 条):西方奇幻、东方仙侠、科幻末世、都市日常、悬疑惊悚、历史武侠、玄幻
-
-**需要新增**(至少):
-
-| 题材 | 理由 |
-|------|------|
-| 系统流 | `题材与调性推理.csv` 已有路由 GR-005,但裁决规则无对应 |
-| 无限流 | 同上 GR-006 |
-| 重生流 | 同上 GR-007 |
-| 宫斗/权谋 | 同上 GR-008 |
-| 现代言情 | 女频高频题材,当前完全空缺 |
-| 古代言情 | 同上 |
-| 轻小说 | 番茄分类中的独立题材 |
-| 游戏/电竞 | 番茄分类中的独立题材 |
-
-**方法**:人工逐条编写。每条裁决行需要填写:`风格优先级`、`爽点优先级`、`节奏默认策略`、`毒点权重`、`冲突裁决`、`contract注入层`、`反模式`。
-
-**硬约束**:裁决规则内容必须人工提炼,禁止程序生成。
-
-### D2: 题材与调性推理.csv 的补全策略
-
-**目标**:覆盖用户在 init 阶段可能输入的全部常见题材/流派/标签组合。
-
-**当前覆盖**(8 条):退婚流、规则怪谈、压抑后爆、赘婿流、系统流、无限流、重生流、宫斗流
-
-**需要新增**:参见 [附录 A](#附录-a知识条目缺口登记表) 中的 `题材与调性推理` 缺口表。
-
-**关键原则**:`题材别名` 列要充分——这是路由命中率的关键。一个流派的常见叫法、黑话、俗语都应该作为别名录入。
-
-### D3: CSV_CONFIG 增强
-
-在 `reference_search.py` 的 `CSV_CONFIG` 中为每张表补充:
-
-```python
-"裁决规则": {
-    "file": "裁决规则.csv",
-    "search_cols": {"题材": 4},
-    "output_cols": [...],
-    "poison_col": "",
-    "role": "reasoning",
-    # ---- 新增 ----
-    "contract_inject": "CHAPTER_BRIEF.writing_guidance",  # 注入目标
-    "prefix": "RS",                                        # 编号前缀
-    "required_cols": ["题材", "风格优先级", "爽点优先级",     # 必填列
-                      "节奏默认策略", "毒点权重", "冲突裁决"],
-},
-```
-
-新增字段说明:
-
-| 字段 | 用途 |
-|------|------|
-| `contract_inject` | 声明该表的检索结果最终注入 contract 的哪个位置,使注入点从散落在 engine 代码中收束到注册层 |
-| `prefix` | 编号前缀,供校验脚本验证一致性 |
-| `required_cols` | 必填列清单,供校验脚本检查非空 |
-
-### D4: 校验脚本设计
-
-新增 `scripts/validate_csv.py`,检查项:
-
-| 检查项 | 规则 | 退出码 |
-|--------|------|--------|
-| 编号唯一性 | 所有 CSV 中 `编号` 列全局唯一 | 1 |
-| 前缀一致性 | 每张表的编号前缀必须与 `CSV_CONFIG[table].prefix` 匹配 | 1 |
-| 必填列非空 | `CSV_CONFIG[table].required_cols` + 通用必填列(编号/适用技能/分类/层级/关键词/适用题材/核心摘要)不为空 | 1 |
-| 分隔符规范 | `适用技能`/`关键词`/`意图与同义词`/`适用题材` 中不含中文逗号 `,` | 1 |
-| 列头对齐 | CSV 文件的实际列头是 `CSV_CONFIG[table].search_cols` + `output_cols` + `required_cols` 的超集 | 1 |
-| 适用题材范围 | `适用题材` 值(拆分后)在番茄分类范围内,或为 `全部` | 警告 |
-| 路由覆盖 | 每条 `裁决规则.csv` 的 `题材` 在 `题材与调性推理.csv` 中至少有一条对应行 | 警告 |
-| 裁决覆盖 | 每条 `题材与调性推理.csv` 的 `题材/流派` 在 `裁决规则.csv` 中至少有一条对应行 | 警告 |
-
-脚本从 `CSV_CONFIG` 读取元数据,不硬编码表名或列名。
-
-### D5: 顶层 README
-
-在 `references/README.md` 新增目录级索引:
-
-```markdown
-# References
-
-## 目录结构
-
-| 子目录/文件 | 职责 | 消费方式 |
-|-------------|------|----------|
-| `csv/` | 结构化知识条目 | `reference_search.py` BM25 检索 |
-| `csv/README.md` | CSV schema 规范 | 人工参考 |
-| `genre-profiles.md` | 题材 profile (fallback) | ContextManager 直接 Read |
-| `reading-power-taxonomy.md` | 追读力分类学 | Skills 直接 Read |
-| `review-schema.md` | 审查输出格式 | webnovel-review Read |
-| `index/` | 元数据索引 | 人工参考 |
-| `outlining/` | 大纲相关参考 | webnovel-plan Read |
-| `review/` | 审查相关参考 | webnovel-review Read |
-| `shared/` | 跨 skill 共享参考 | 多 skill Read |
-
-## md vs CSV 边界
-
-- **md**:流程规范、方法论、审查 schema、硬约束、润色指导
-- **CSV**:可条目化的写作知识、命名规则、场景技法、桥段模板
-
-## 消费链路
-
-init → plan → write → review 的完整 reference 消费路径见
-`index/reference-loading-map.md`。
-```
-
-### D6: reference-loading-map 同步
-
-对照实际 skill 文件更新 `index/reference-loading-map.md`,补充:
-
-- webnovel-plan 引用的 skill-local references(`conflict-design.md`、`chapter-planning.md`、`genre-volume-pacing.md`)
-- webnovel-init 引用的 worldbuilding 和 creativity 子目录中的全部条件加载项
-- webnovel-write 通过 `StorySystemEngine` 间接消费的 CSV 表
-
-### D7: reference-gap-register 更新
-
-当前 gap register 中部分项已完成但未标记,需要刷新:
-
-- `blocking-override-guidelines.md` → 已创建 ✅
-- `plot-signal-vs-spoiler.md` → 已创建 ✅
-- `naming-and-voice-gaps.md` → 已创建 ✅
-- 三张初始 CSV(命名规则/场景写法/写作技法)→ 已创建 ✅
-- 追加当前 spec 新发现的缺口
-
-### D8: shared md 条目迁移审查
-
-对 `shared/` 下的 md 进行内容审查,判断是否有可迁移到 CSV 的条目:
-
-| 文件 | 处置建议 |
-|------|---------|
-| `core-constraints.md` | **保留原样**——流程硬约束,不适合条目化 |
-| `strand-weave-pattern.md` | **保留原样**——方法论型(三线比例/警告规则),不是条目库 |
-| `cool-points-guide.md` | **审查**——其中"六种爽点执行模式"和"打脸四步法"可能提炼为 `爽点与节奏.csv` 条目,但"信息不对称设计"和"密度指南"保留 md |
-| `naming-and-voice-gaps.md` | **审查**——其中"题材命名风格表"和"口吻区分表"可能提炼为 `命名规则.csv`/`写作技法.csv` 条目,但"缺陷补偿策略"段保留 md |
-
-审查结果记入 [附录 A](#附录-a知识条目缺口登记表),实际迁移留待后续执行。
-
----
-
-## 实施计划
-
-### Phase 1: 结构层(不涉及内容填充)
-
-| 任务 | 产出 | 依赖 |
-|------|------|------|
-| 1.1 CSV_CONFIG 增强 | `reference_search.py` 中每张表补 `contract_inject`/`prefix`/`required_cols` | 无 |
-| 1.2 校验脚本 | `scripts/validate_csv.py` | 1.1 |
-| 1.3 顶层 README | `references/README.md` | 无 |
-| 1.4 loading-map 同步 | `index/reference-loading-map.md` 更新 | 无 |
-| 1.5 gap-register 刷新 | `index/reference-gap-register.md` 更新 | 无 |
-
-### Phase 2: 裁决层补厚(人工内容填充)
-
-| 任务 | 产出 | 依赖 |
-|------|------|------|
-| 2.1 裁决规则.csv 补全 | 从 7 条扩至 15+ 条 | 附录 A 缺口表 |
-| 2.2 题材与调性推理.csv 补全 | 从 8 条扩至 20+ 条 | 附录 A 缺口表 |
-| 2.3 校验脚本通过 | `validate_csv.py` 全量通过 | 1.2, 2.1, 2.2 |
-
-### Phase 3: 知识层补充(人工内容填充)
-
-| 任务 | 产出 | 依赖 |
-|------|------|------|
-| 3.1 shared md 审查 | 标记可迁移条目 | D8 |
-| 3.2 可迁移条目手工录入 CSV | 相关 CSV 新增条目 | 3.1 |
-| 3.3 7 张知识表查漏 | 基于全链路分析补充遗漏主题 | 附录 A |
-
-### Phase 4: 验证
-
-| 任务 | 产出 | 依赖 |
-|------|------|------|
-| 4.1 端到端冒烟测试 | 对 3 个不同题材执行 `story_system.py`,验证 route → collect → reason 全链路不退化 | 2.3 |
-| 4.2 loading-map 回归 | 对照更新后的 map,逐条验证 skill 实际加载行为 | 1.4 |
-
----
-
-## 附录 A:知识条目缺口登记表
-
-> 本附录只登记缺口,不做内容填充。所有内容必须人工逐条编写。
-
-### A1: 题材与调性推理.csv 缺口
-
-当前 8 条覆盖:退婚流、规则怪谈、压抑后爆、赘婿流、系统流、无限流、重生流、宫斗流。
-
-| 缺失题材/流派 | 优先级 | 理由 |
-|---------------|--------|------|
-| 穿越流(男频/女频) | P0 | 高频流派,影响古言/历史/玄幻多种题材路由 |
-| 都市异能 | P0 | 与"都市日常"的裁决规则完全不同(有战斗、有体系) |
-| 修真/仙侠(区分东方仙侠大类的传统修真子类) | P1 | 修炼-斗法-宗门-天劫 有独立节奏 |
-| 末世求生 | P1 | 区分于"科幻末世"——不一定有科幻要素 |
-| 甜宠/轻甜 | P1 | 女频主流,当前完全无路由 |
-| 悬疑推理 | P1 | 区分于"悬疑惊悚"——强调逻辑链和信息控制 |
-| 种田/经营 | P2 | 近年热门流派(男频种田、女频种田) |
-| 娱乐圈 | P2 | 女频热门 |
-| 体育竞技 | P2 | 番茄分类独立题材 |
-| 克苏鲁/诡秘 | P2 | 近年热门,有独特节奏和裁决需求 |
-| 学院流 | P3 | 横跨多题材的通用叙事结构 |
-| 副本流 | P3 | 与无限流相近但有差异 |
-
-### A2: 裁决规则.csv 缺口
-
-当前 7 条覆盖:西方奇幻、东方仙侠、科幻末世、都市日常、悬疑惊悚、历史武侠、玄幻。
-
-原则:`裁决规则.csv` 的粒度是**大题材类型**,不是子流派——子流派差异由 `题材与调性推理.csv` 的路由参数处理。
-
-| 缺失题材 | 优先级 | 理由 |
-|----------|--------|------|
-| 现代言情 | P0 | 女频最大流量入口,裁决逻辑(情感驱动 > 冲突驱动)与当前全部男频裁决不同 |
-| 古代言情 | P0 | 古言特有的身份/礼教/宫廷约束需要独立裁决 |
-| 系统流/游戏化 | P0 | `题材与调性推理` 已路由到此,但裁决层无对应——数值、面板、升级构成独立裁决维度 |
-| 轻小说 | P1 | 番茄分类独立题材,二次元审美/节奏/爽点逻辑独特 |
-| 游戏/电竞 | P1 | 赛事结构+团队配合+技术描写有独立裁决需求 |
-| 种田/日常经营 | P2 | 低冲突高积累型叙事,与当前所有裁决模式不同 |
-| 克苏鲁/诡秘 | P2 | 未知恐惧+信息限制+理智值裁决 |
-
-### A3: 7 张知识表缺口审查
-
-> 此部分需要对每张表的现有条目做覆盖度分析后填写。当前为初始框架。
-
-#### 命名规则.csv (45 行)
-
-| 缺失主题 | 优先级 | 来源线索 |
-|----------|--------|---------|
-| 女频命名规范(古言/现言/甜宠) | P1 | 当前条目偏男频 |
-| 势力/组织命名(宗门/帮派/公司/家族) | P1 | 只有角色和地点,缺组织实体 |
-| 书名/标题命名规则 | P2 | gap-register 曾提及但延迟 |
-
-#### 场景写法.csv (52 行)
-
-| 缺失主题 | 优先级 | 来源线索 |
-|----------|--------|---------|
-| 日常/种田/经营场景 | P1 | 当前偏战斗/对峙 |
-| 言情核心场景(暧昧/误会/重逢/分手) | P1 | 女频主线场景空缺 |
-| 悬疑推理场景(线索发现/推理对质/真相揭露) | P2 | |
-
-#### 写作技法.csv (64 行)
-
-| 缺失主题 | 优先级 | 来源线索 |
-|----------|--------|---------|
-| 信息控制技法(悬念设置/信息差/视角限制) | P1 | 悬疑/推理/诡秘类需要 |
-| 甜宠/糖分技法(心动描写/CP 互动设计) | P1 | 女频需求 |
-| 幽默/吐槽技法(轻小说/都市轻喜剧) | P2 | |
-
-#### 桥段套路.csv (62 行)
-
-| 缺失主题 | 优先级 | 来源线索 |
-|----------|--------|---------|
-| 女频经典桥段(替嫁/冲喜/和离/重生复仇) | P1 | 完全空缺 |
-| 系统流桥段(首次激活/隐藏任务/系统升级) | P1 | |
-| 悬疑桥段(密室/不在场证明/真凶反转) | P2 | |
-
-#### 人设与关系.csv (58 行)
-
-| 缺失主题 | 优先级 | 来源线索 |
-|----------|--------|---------|
-| 女频核心人设(白月光/绿茶/霸总/病娇/竹马) | P1 | |
-| 团队/CP 关系模板(搭档/对手/师徒) | P2 | |
-
-#### 爽点与节奏.csv (60 行)
-
-| 缺失主题 | 优先级 | 来源线索 |
-|----------|--------|---------|
-| 女频爽点类型(打脸白莲花/甜蜜暴击/身份揭露) | P1 | |
-| 种田/经营类积累爽点 | P2 | |
-| cool-points-guide.md 中可迁移的执行模式条目 | P2 | D8 审查结果 |
-
-#### 金手指与设定.csv (59 行)
-
-| 缺失主题 | 优先级 | 来源线索 |
-|----------|--------|---------|
-| 女频金手指(空间/药园/前世记忆/读心术) | P1 | |
-| 非战斗型金手指(鉴定/制造/交易/信息) | P2 | |
-
-### A4: shared md 可迁移条目审查
-
-> 待 Phase 3 审查后填写。
-
-| 源文件 | 可迁移段落 | 目标 CSV | 预估条目数 | 状态 |
-|--------|-----------|---------|-----------|------|
-| `cool-points-guide.md` | 待审查 | `爽点与节奏.csv` | - | 未开始 |
-| `naming-and-voice-gaps.md` | 待审查 | `命名规则.csv` / `写作技法.csv` | - | 未开始 |
-
----
-
-## 验收标准
-
-| 阶段 | 验收条件 |
-|------|---------|
-| Phase 1 完成 | `validate_csv.py` 可运行,当前数据全部通过(warnings 允许,errors 不允许);`references/README.md` 存在;loading-map 与实际 skill 一致 |
-| Phase 2 完成 | `裁决规则.csv` ≥ 14 条;`题材与调性推理.csv` ≥ 16 条;`validate_csv.py` 零 warning;3 个不同题材的 `story_system.py` 端到端不退化 |
-| Phase 3 完成 | shared md 审查完毕,可迁移条目已录入 CSV;7 张知识表 P1 缺口已补 |
-| Phase 4 完成 | 全链路冒烟测试通过;loading-map 回归通过 |

+ 0 - 681
docs/archive/superpowers/specs/2026-04-30-system-quality-fixes.md

@@ -1,681 +0,0 @@
-# 灵石庄测试发现的系统级修复 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` 提取 `goal`、`obstacles`、`cost`、`time_anchor`、`chapter_span`、`countdown`、`cbn`、`cpns`、`cen`、`must_cover_nodes`、`forbidden_zones`、`chapter_end_open_question`、`hook_type`、`hook_strength`、`key_entities`、`strand`、`antagonist_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_anchor`、`must_cover_nodes`、`forbidden_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.py`、`webnovel-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 记录包含 `text`、`source_table="review_extracted"`、`source_id="ch0002_issue_N"`、`category`、`added_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 行)字段构成:
-
-```json
-{
-  "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]`、`strand`、`antagonist_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):
-   ```python
-   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 **之前**,并加显式约束语:
-   ```markdown
-   ## 本章硬性约束(优先级最高)
-   - 时间锚点:{directive.time_anchor}
-   - 倒计时状态:{directive.countdown}
-   - 必须覆盖节点:{directive.must_cover_nodes}
-   - 章末必须留下:{directive.chapter_end_open_question}
-   - 本章禁区(违反即不通过):{directive.forbidden_zones}
-   ```
-
-**验收标准**:
-
-```python
-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.md`、`webnovel-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.goal`、`chapter_directive.key_entities`、`chapter_directive.strand`、`chapter_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`
-
-**验收标准**:
-
-```python
-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_bank` 的 `protagonist_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.py`、`webnovel-writer/templates/output/` 中仍需保留的模板,以及 `webnovel-writer/skills/webnovel-init/SKILL.md` 生成步骤段。
-
-**验收标准**:
-
-```python
-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 来自大纲目标)。补充:
-
-```python
-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 blocker**。`webnovel-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>` 让用户主动检查
-
-**验收标准**:
-
-```python
-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 空行
-
-这一路径最贴合写作流:当前卷规划完成时,系统只把必要的跨卷承诺固化到总纲,保留下一卷正式规划时的创作空间。
-
-**验收标准**:
-
-```python
-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:
-   ```python
-   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 不重复入库)
-
-**验收标准**:
-
-```python
-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-plan`、`webnovel-write`、`webnovel-review` 三个 skill 的真实 query 要求。
-5. 在 `story_system_engine.py` 增加 chapter-aware reference ranking,用 `goal/key_entities/strand/antagonist_tier` 影响排序。
-6. 修改 `init_project.py` 与 `webnovel-init/SKILL.md` 的模板生成清单,先完成单主角 / 无女主 / 死模板剪枝测试。
-7. 新增 `placeholder_scanner.py` 与 `webnovel.py placeholder-scan`,先实现全项目扫描,再接入 plan 警告。
-8. 将 placeholder guardrail 接入 write 前检查,使用当前章节相关实体缩小阻断范围。
-9. 修改总纲模板与 `webnovel-plan/SKILL.md`,移除 V2-V20 预生成空行;在当前卷规划完成并验证后,只写回 V+1 的卷名 / 核心冲突 / 卷末高潮,并仅追加当前规划输出显式结构化给出的新伏笔 / 持续开放环。
-10. 在 `review_schema.py` 增加 `ai_flavor` → `anti_patterns.json` 回流与去重,再验证下一章 brief 能读到 review-extracted 避雷模式。

+ 0 - 237
docs/guides/commands.md

@@ -1,237 +0,0 @@
-# 命令详解
-
-## Skill 命令(在 Claude Code 中使用)
-
-### `/webnovel-init`
-
-初始化小说项目,生成目录结构、设定模板和状态文件。
-
-产出:
-
-- `.webnovel/state.json`(运行时状态)
-- `设定集/`(世界观、力量体系、主角卡、金手指设计、反派设计等)
-- `大纲/总纲.md`、`大纲/爽点规划.md`
-- `.env.example`(RAG 配置模板)
-
-### `/webnovel-plan [卷号]`
-
-生成卷级规划与章节大纲。
-
-```bash
-/webnovel-plan 1
-/webnovel-plan 2-3
-```
-
-### `/webnovel-write [章号]`
-
-执行完整章节创作流程(`context-agent` 先 research 并生成写作任务书 → 按任务书起草正文 → 审查 → 润色 → 数据落盘)。
-
-```bash
-/webnovel-write 1
-/webnovel-write 45
-```
-
-### `/webnovel-review [范围]`
-
-对已有章节做多维质量审查。
-
-```bash
-/webnovel-review 1-5
-/webnovel-review 45
-```
-
-### `/webnovel-query [关键词]`
-
-查询角色、伏笔、节奏、状态等运行时信息。
-
-```bash
-/webnovel-query 萧炎
-/webnovel-query 伏笔
-```
-
-### `/webnovel-learn [内容]`
-
-从当前会话或用户输入中提取可复用写作模式,写入项目记忆。
-
-```bash
-/webnovel-learn "本章的危机钩设计很有效,悬念拉满"
-```
-
-产出:`.webnovel/project_memory.json`
-
-### `/webnovel-dashboard`
-
-启动只读可视化面板,查看项目状态、实体关系、章节与大纲内容。
-
-```bash
-/webnovel-dashboard
-```
-
-说明:
-
-- 默认只读,不会修改项目文件
-- 前端构建产物已随插件发布,无需本地 `npm build`
-
-### `/webnovel-doctor [--chapter N] [--deep]`
-
-只读体检当前网文项目,检查阶段应有文件、JSON、SQLite、RAG 配置、Python 依赖与 Dashboard 产物,并给出影响和修复建议。
-
-```bash
-/webnovel-doctor
-/webnovel-doctor --chapter 12
-/webnovel-doctor --deep
-```
-
-说明:
-
-- 不写入项目,不安装依赖,不启动服务
-- 会先判断当前项目阶段,init 刚结束时不会按终态项目误报
-
-## 统一 CLI(命令行使用)
-
-所有 CLI 命令的入口都是 `webnovel.py`,格式:
-
-```bash
-python -X utf8 "<CLAUDE_PLUGIN_ROOT>/scripts/webnovel.py" --project-root "<PROJECT_ROOT>" <子命令> [参数]
-```
-
-### 作者友好运行体验
-
-`/webnovel-init`、`/webnovel-plan`、`/webnovel-write` 和 `/webnovel-review` 结束时都会输出统一最终报告。报告不直接输出原始 JSON、traceback 或长命令日志,而是先给一句总状态,再分三段说明:产生的文件与完成情况、过程中遇到的问题与异常耗时、下一步建议。
-
-总状态有四种:
-
-- **已完成**:目标产物和关键校验都通过。
-- **部分完成**:主要产物已保留,但存在跳过项、自动处理项或待确认事项。
-- **需要你处理**:系统停在安全位置,需要作者裁决创作方向、事实取舍、文件覆盖或 blocking 问题。
-- **未完成**:关键产物没有可信生成,需要按报告建议重跑或排查。
-
-长流程执行中只显示少量过程提示,说明当前阶段和会产生什么。自动补跑投影、重新 emit 缺失合同这类幂等操作不会打断作者,但会出现在最终报告里。重复执行同一条主命令时,系统会优先检查可信断点;首版断点续跑重点覆盖 `/webnovel-write`,尽量从失败点继续,而不是重写已可信完成的正文、审查、提交或备份。
-
-## Story System 主链
-
-推荐按以下顺序执行:
-
-1. 生成合同
-
-```bash
-python -X utf8 "<CLAUDE_PLUGIN_ROOT>/scripts/webnovel.py" --project-root "<PROJECT_ROOT>" story-system "玄幻退婚流" --chapter 12 --persist --emit-runtime-contracts --format both
-```
-
-2. 提交章节
-
-```bash
-python -X utf8 "<CLAUDE_PLUGIN_ROOT>/scripts/webnovel.py" --project-root "<PROJECT_ROOT>" chapter-commit \
-  --chapter 12 \
-  --review-result ".webnovel/tmp/review_results.json" \
-  --fulfillment-result ".webnovel/tmp/fulfillment_result.json" \
-  --disambiguation-result ".webnovel/tmp/disambiguation_result.json" \
-  --extraction-result ".webnovel/tmp/extraction_result.json"
-```
-
-3. 检查主链健康
-
-```bash
-python -X utf8 "<CLAUDE_PLUGIN_ROOT>/scripts/webnovel.py" --project-root "<PROJECT_ROOT>" preflight --format json
-```
-
-其中 `.story-system/` 是主链真源,`.webnovel/*` 是投影/read-model。
-
-### 常用工具子命令
-
-| 子命令 | 说明 |
-|--------|------|
-| `where` | 打印当前解析出的项目根目录 |
-| `preflight` | 校验 CLI 环境、脚本路径和项目根是否可用 |
-| `project-status` | 输出机器可读短状态(phase、目标章节、下一步),不占用旧 `status` |
-| `doctor` | 阶段感知项目体检(目录、文件、DB、RAG、依赖、Dashboard) |
-| `write-gate` | 写章自然边界校验(`prewrite` / `precommit` / `postcommit`) |
-| `projections` | 从已有 commit 补跑或重放 projection |
-| `user-report` | 渲染作者友好的最终报告,可输出 text/json |
-| `run-ledger` | 记录写章步骤状态,或生成 `/webnovel-write` 断点续跑建议 |
-| `run-log` | 写入脱敏运行日志 `.webnovel/logs/run_last.log` |
-| `use <路径>` | 绑定当前工作区使用的书项目 |
-
-示例:
-
-```bash
-python -X utf8 "<CLAUDE_PLUGIN_ROOT>/scripts/webnovel.py" --project-root "<PROJECT_ROOT>" user-report --stage write --chapter 12 --format text
-python -X utf8 "<CLAUDE_PLUGIN_ROOT>/scripts/webnovel.py" --project-root "<PROJECT_ROOT>" run-ledger write-resume --chapter 12 --format text
-python -X utf8 "<CLAUDE_PLUGIN_ROOT>/scripts/webnovel.py" --project-root "<PROJECT_ROOT>" run-log --event write_failed --payload-json "{\"chapter\":12,\"reason\":\"projection timeout\"}"
-```
-
-### 数据模块子命令
-
-| 子命令 | 说明 |
-|--------|------|
-| `index` | 索引管理(`process-chapter`、`stats` 等) |
-| `state` | 状态管理 |
-| `rag` | RAG 向量索引(`index-chapter`、`stats` 等) |
-| `entity` | 实体链接 |
-| `context` | 上下文管理 |
-| `style` | 风格采样 |
-| `migrate` | state.json → SQLite 迁移 |
-
-### 运维子命令
-
-| 子命令 | 说明 |
-|--------|------|
-| `status` | 宏观创作健康报告(`--focus all` / `--focus urgency`),仍转发到 `status_reporter.py` |
-| `update-state` | 手动更新状态 |
-| `backup` | 备份管理 |
-| `archive` | 归档管理 |
-| `extract-context` | 提取章节上下文(`--chapter N --format json`) |
-
-### 长期记忆子命令
-
-| 子命令 | 说明 |
-|--------|------|
-| `memory stats` | 查看总量、分类统计 |
-| `memory query` | 按 category/subject/status 过滤查询 |
-| `memory dump` | 导出完整 scratchpad 内容 |
-| `memory conflicts` | 查看同主键 active 冲突项 |
-| `memory bootstrap` | 从 index.db 与 summaries 回填初始长期记忆 |
-| `memory update` | 对指定章节结果执行手动映射写入 |
-
-示例:
-
-```bash
-python -X utf8 "<CLAUDE_PLUGIN_ROOT>/scripts/webnovel.py" --project-root "<PROJECT_ROOT>" memory stats
-python -X utf8 "<CLAUDE_PLUGIN_ROOT>/scripts/webnovel.py" --project-root "<PROJECT_ROOT>" memory query --category character_state --subject xiaoyan
-```
-
-### Story System 子命令
-
-| 子命令 | 说明 |
-|--------|------|
-| `story-system "<题材>" --persist` | 写入合同种子(`MASTER_SETTING.json` 等) |
-| `story-system "<题材>" --emit-runtime-contracts --chapter N` | 生成运行时合同 + 写前校验 |
-| `chapter-commit --chapter N` | 提交章节 commit(可附带 review/fulfillment/disambiguation/extraction 结果) |
-| `write-gate --chapter N --stage prewrite` | 写前检查项目阶段、Story System 合同和占位符 |
-| `write-gate --chapter N --stage precommit` | 提交前检查正文和四类 commit artifacts |
-| `write-gate --chapter N --stage postcommit` | 提交后检查 commit 与 projection 状态 |
-| `projections retry --chapter N` | 基于已有 commit 补跑单章 projection |
-| `projections replay --from-chapter A --to-chapter B` | 按章节范围重放 projection |
-| `user-report --stage write --chapter N` | 汇总本次写章产物、问题和下一步建议 |
-| `run-ledger record-write-step --chapter N` | 记录写章关键步骤的状态、输入输出、问题和耗时 |
-| `run-ledger write-resume --chapter N` | 根据可信断点输出续跑建议,不自动覆盖文件 |
-| `run-log --event <name>` | 写入脱敏日志,供不可恢复故障排查 |
-| `story-events --chapter N` | 查询指定章节事件 |
-| `story-events --health` | 事件链健康检查 |
-| `memory-contract` | 记忆合同管理 |
-| `review-pipeline --chapter N --review-results <file>` | 审查流水线 |
-
-示例:
-
-```bash
-python -X utf8 "<CLAUDE_PLUGIN_ROOT>/scripts/webnovel.py" --project-root "<PROJECT_ROOT>" story-system "玄幻退婚流" --persist
-python -X utf8 "<CLAUDE_PLUGIN_ROOT>/scripts/webnovel.py" --project-root "<PROJECT_ROOT>" chapter-commit --chapter 12 --review-result .webnovel/tmp/review.json
-python -X utf8 "<CLAUDE_PLUGIN_ROOT>/scripts/webnovel.py" --project-root "<PROJECT_ROOT>" story-events --health
-```
-
-产物:
-
-- `story-system --persist` → `.story-system/MASTER_SETTING.json`
-- `--emit-runtime-contracts` → `volumes/*.json` 与 `reviews/*.review.json`
-- `chapter-commit` → `commits/*.commit.json`
-- `story-events` → 读取 `events/*.events.json` 或 `index.db.story_events`

+ 0 - 54
docs/guides/rag-and-config.md

@@ -1,54 +0,0 @@
-# RAG 与配置说明
-
-## RAG 检索流程
-
-系统在写作时自动从历史章节中检索相关内容,辅助保持一致性。
-
-```text
-查询 → QueryRouter(auto) → vector / bm25 / hybrid / graph_hybrid
-                     └→ RRF 融合 + Rerank → Top-K
-```
-
-- 默认模式为 `auto`:优先用向量检索,失败时自动回退到 BM25
-- `graph_hybrid` 模式会叠加实体图谱关联
-
-### 默认模型
-
-| 组件 | 默认模型 |
-|------|----------|
-| Embedding | `Qwen/Qwen3-Embedding-8B`(ModelScope 托管) |
-| Reranker | `jina-reranker-v3`(Jina AI 托管) |
-
-## 环境变量加载顺序
-
-系统按以下优先级加载配置(靠前的优先):
-
-1. **进程环境变量**(最高优先级)
-2. **书项目根目录**下的 `.env`
-3. **用户级全局**:`~/.claude/webnovel-writer/.env`
-
-## `.env` 最小配置
-
-初始化项目后会自动生成 `.env.example`,复制为 `.env` 后填写 API Key 即可:
-
-```bash
-cp .env.example .env
-```
-
-必填内容:
-
-```bash
-EMBED_BASE_URL=https://api-inference.modelscope.cn/v1
-EMBED_MODEL=Qwen/Qwen3-Embedding-8B
-EMBED_API_KEY=your_embed_api_key
-
-RERANK_BASE_URL=https://api.jina.ai/v1
-RERANK_MODEL=jina-reranker-v3
-RERANK_API_KEY=your_rerank_api_key
-```
-
-## 注意事项
-
-- 未配置 Embedding Key 时,语义检索会自动回退到 BM25(仍可正常使用,但效果弱于向量检索)。
-- 推荐每本书单独配置 `${PROJECT_ROOT}/.env`,避免多项目之间串配置。
-- Embedding 和 Rerank 的模型可以替换为任何兼容 OpenAI 格式的 API。

+ 0 - 385
docs/memory/long-term-memory-architecture-v2.md

@@ -1,385 +0,0 @@
-# 长期记忆现有架构设计
-
-## 文档定位
-
-本文只描述当前仓库里已经落地、并被主写作链路实际消费的长期记忆架构。
-
-这里不再保留历史改造计划,也不把理想态蓝图混进来。判断标准只有一个:当前代码里是否真实存在、是否已经接入主链路。
-
-## 一句话结论
-
-当前长期记忆的真实实现是:
-
-- 以 `.webnovel/memory_scratchpad.json` 作为长期语义记忆缓存
-- 以 `index.db`、`summaries/`、`state.json` 作为近期状态和历史证据层
-- 由 `MemoryOrchestrator` 组装 `working / episodic / semantic` 三层结果
-- 再由 `ContextManager`、`extract_chapter_context.py` 和 `MemoryContractAdapter` 对外消费
-
-它已经是一条可运行的数据链,但还不是完全独立的记忆子系统。
-
-## 核心边界
-
-### 已经属于长期记忆主链路
-
-- `scripts/data_modules/memory/schema.py`
-- `scripts/data_modules/memory/store.py`
-- `scripts/data_modules/memory/writer.py`
-- `scripts/data_modules/memory/orchestrator.py`
-- `scripts/data_modules/memory/bootstrap.py`
-- `scripts/data_modules/memory/compactor.py`
-- `scripts/data_modules/memory_contract_adapter.py`
-- `scripts/memory_cli.py`
-- `scripts/data_modules/context_manager.py` 中的 `long_term_memory` 注入
-
-### 仍然是旁路或相邻能力
-
-- `.webnovel/project_memory.json`
-- `/webnovel-learn` 产出的项目经验记忆
-- `ContextManager` 里的 `memory` section
-
-这部分会被上下文一起读取,但不等同于 `memory_scratchpad.json` 这条长期记忆主链路。
-
-## 现有架构图
-
-```text
-章节结果 / 审查产物
-        │
-        ▼
-MemoryWriter
-        │
-        ├── 写入 memory_scratchpad.json
-        ├── 维护 active/outdated/contradicted/tentative
-        └── 触发压缩与冲突检查
-
-state.json / index.db / summaries/ / memory_scratchpad.json
-        │
-        ▼
-MemoryOrchestrator
-        │
-        ├── working_memory
-        ├── episodic_memory
-        ├── semantic_memory
-        └── long_term_facts / active_constraints / warnings / stats
-
-        ▼
-ContextManager
-        │
-        ├── long_term_memory section
-        ├── extract_chapter_context.py
-        └── MemoryContractAdapter / memory CLI
-
-        ▼
-webnovel-write / review / query / 其他消费端
-```
-
-## 数据分层
-
-### 1. Working Memory
-
-当前不是单独存储,而是运行时临时拼装,主要来源于:
-
-- 本章章纲
-- 最近几章摘要
-- `state.json` 中的主角状态、情节线程、待消歧项
-
-这层由 `MemoryOrchestrator._build_working_memory()` 生成。
-
-### 2. Episodic Memory
-
-当前主要来自 `index.db` 的近期结构化证据,而不是独立的通用历史检索系统。
-
-主要来源:
-
-- 最近状态变化
-- 最近关系变化
-- 最近出场记录
-
-这层由 `MemoryOrchestrator._build_episodic_memory()` 生成,特点是偏“最近证据”,不是全量全库语义召回。
-
-### 3. Semantic Memory
-
-当前长期记忆的核心存储是 `.webnovel/memory_scratchpad.json`。
-
-它由 `ScratchpadManager` 读写,按分类分桶保存:
-
-- `character_state`
-- `story_facts`
-- `world_rules`
-- `timeline`
-- `open_loops`
-- `reader_promises`
-- `relationships`
-
-每条记忆项统一使用 `MemoryItem` 结构,核心字段包括:
-
-- `id`
-- `layer`
-- `category`
-- `subject`
-- `field`
-- `value`
-- `payload`
-- `status`
-- `source_chapter`
-- `evidence`
-- `updated_at`
-
-支持的状态为:
-
-- `active`
-- `outdated`
-- `contradicted`
-- `tentative`
-
-## 写入链路
-
-### 1. 章节结果进入 `MemoryWriter`
-
-写作主链在章节提交后,会把结构化结果交给 `MemoryWriter.update_from_chapter_result()`。
-
-当前已实现的写入来源包括:
-
-- `state_changes`
-- `entities_new`
-- `relationships_new`
-- `chapter_meta.hook`
-- `memory_facts`
-
-其中 `memory_facts` 用于更深一层的结构化映射,当前支持:
-
-- `timeline_events`
-- `world_rules`
-- `open_loops`
-- `reader_promises`
-
-### 2. `ScratchpadManager` 做去重与状态收敛
-
-`ScratchpadManager.upsert_item()` 的核心规则是:
-
-- 按分类主键规则计算去重 key
-- 同 key 的旧值降级为 `outdated`
-- 新值写成当前有效项
-- 保留旧值用于审计和回溯
-
-当前各分类的主键规则由 `schema.py` 统一定义,例如:
-
-- `character_state`:`subject + field`
-- `relationship`:`subject + field`
-- `world_rule`:`subject + field`
-- `open_loop`:`subject`
-
-### 3. 超阈值时压缩
-
-`ScratchpadManager.save()` 会在达到阈值后调用 `compactor.py`。
-
-当前压缩策略包括:
-
-- 同 key 的 `outdated` 只保留最新一条
-- 清理已回收的伏笔
-- 过旧时间线合并为摘要型 `story_fact`
-- 总量仍超限时按状态和新鲜度做全局截断
-
-### 4. 历史项目可回填
-
-`memory bootstrap` 会从现有 `index.db` 与 `summaries/` 回填出一版初始长期记忆。
-
-当前可回填的内容包括:
-
-- 角色当前状态
-- 历史状态变化
-- 最近关系
-- 摘要中的“伏笔”区块
-
-## 读取与编排链路
-
-### 1. `MemoryOrchestrator` 负责统一出包
-
-`MemoryOrchestrator.build_memory_pack()` 是当前长期记忆的统一读取入口。
-
-它会做四件事:
-
-1. 构建 `working_memory`
-2. 构建 `episodic_memory`
-3. 读取 `memory_scratchpad.json` 中的 `active` 项
-4. 过滤、限额、补充告警与统计信息
-
-输出的核心字段包括:
-
-- `working_memory`
-- `episodic_memory`
-- `semantic_memory`
-- `long_term_facts`
-- `active_constraints`
-- `recent_changes`
-- `warnings`
-- `stats`
-
-其中:
-
-- `semantic_memory` 与 `long_term_facts` 当前是同一批可直接注入的长期语义事实
-- `active_constraints` 主要抽取 `world_rule` 和 `open_loop`
-- `warnings` 当前主要用于暴露记忆冲突
-
-### 2. 当前过滤规则
-
-`semantic_memory` 不是全量注入,而是先做一轮轻量过滤。
-
-现有过滤依据:
-
-- 记忆项的 `subject / field / value` 是否出现在本章章纲中
-- 来源章节是否落在配置允许的窗口内
-
-然后再按预算截断。当前预算由 `budget.py` 和配置项共同控制。
-
-## 消费层
-
-### 1. `ContextManager`
-
-`ContextManager` 仍然是写作上下文的总装配器。
-
-当 `context_use_memory_orchestrator=true` 时,它会:
-
-- 调用 `MemoryOrchestrator.build_memory_pack()`
-- 把结果注入到 `long_term_memory` section
-- 再和 `reader_signal / genre_profile / writing_guidance / plot_structure` 一起组装最终 context
-
-所以当前真实关系不是“记忆系统完全替代 ContextManager”,而是“记忆系统已经接入 ContextManager”。
-
-### 2. `extract_chapter_context.py`
-
-写作前置上下文脚本会从 `ContextManager` 里抽取几个关键 section,其中已经包含:
-
-- `reader_signal`
-- `genre_profile`
-- `writing_guidance`
-- `plot_structure`
-- `long_term_memory`
-
-这意味着长期记忆已经进入主写作上下文,而不是停留在独立实验脚本里。
-
-### 3. `MemoryContractAdapter`
-
-`MemoryContractAdapter` 是对外的薄适配层。
-
-它会把现有模块包装成统一接口,提供:
-
-- `commit_chapter()`
-- `load_context()`
-- `query_entity()`
-- `query_rules()`
-- `read_summary()`
-- `get_open_loops()`
-- `get_timeline()`
-
-这层的意义是:当前记忆链路已经有稳定接口,但底层存储仍然复用现有 `state / index / scratchpad / summaries`。
-
-### 4. CLI
-
-当前长期记忆相关的 CLI 分成两类:
-
-- `webnovel.py memory ...`
-- `memory_cli.py`
-
-常用命令包括:
-
-- `memory stats`
-- `memory query`
-- `memory dump`
-- `memory conflicts`
-- `memory bootstrap`
-- `memory update`
-
-## 存储职责划分
-
-### `state.json`
-
-负责运行时状态,不负责长期知识沉淀。
-
-当前主要承载:
-
-- 主角快照
-- 进度
-- 情节线程
-- 待消歧项
-
-### `index.db`
-
-负责结构化历史证据。
-
-当前长期记忆在读取 `episodic_memory` 时,主要从这里拿:
-
-- 状态变化
-- 关系
-- 出场记录
-- 部分追读力与审查数据
-
-### `summaries/`
-
-负责章节摘要与最近写作上下文。
-
-在长期记忆链路里,它有两个作用:
-
-- 作为 `working_memory` 的最近摘要来源
-- 在 `bootstrap` 时用于回填“伏笔”类开放问题
-
-### `memory_scratchpad.json`
-
-负责长期语义记忆缓存,是当前长期记忆的主真源。
-
-### `project_memory.json`
-
-负责项目经验/学习沉淀,当前仍是独立旁路,不与 `memory_scratchpad.json` 合并。
-
-## 当前已实现的关键能力
-
-- 长期记忆已可持久化读写
-- 已有统一 schema、状态枚举和分桶结构
-- 已有冲突检测与简单状态降级
-- 已有压缩器防止 scratchpad 持续膨胀
-- 已能把长期记忆注入主写作上下文
-- 已有 CLI 查询、回填和手工更新入口
-- 已有 `MemoryContractAdapter` 作为稳定外部接口
-
-## 当前边界与限制
-
-### 1. 不是完全独立的 memory runtime
-
-当前仍依赖:
-
-- `ContextManager` 做最终装配
-- `index.db` 提供近期历史证据
-- `state.json` 提供运行时快照
-
-### 2. `episodic_memory` 偏近期,不是全量历史召回
-
-目前更像“最近结构化证据层”,不是统一的跨全书语义回忆系统。
-
-### 3. `semantic_memory` 仍是 JSON scratchpad
-
-当前没有单独的长期语义向量层,也没有图数据库层。
-
-### 4. `project_memory.json` 与长期记忆主链还未统一
-
-项目经验记忆和剧情长期记忆现在是两套并行数据源。
-
-### 5. 冲突裁决还是轻量规则
-
-当前主要是:
-
-- 主键去重
-- 旧值降级
-- 冲突统计
-
-还没有更重的跨章节语义裁决流程。
-
-## 结论
-
-当前仓库里的长期记忆已经从“方案讨论”进入“可运行架构”阶段,但它的真实定位应当是:
-
-- 已接入主写作链路
-- 已有持久化、编排、消费和运维入口
-- 仍然建立在现有 `ContextManager + state/index/summaries` 生态之上
-
-因此,这份文档只保留“现状架构说明”。
-
-历史计划文档已经移除;如果后续继续演进,应重新按当时的真实代码状态单独写新 spec。

+ 0 - 482
docs/operations/genre-taxonomy-convergence-plan-2026-06-04.md

@@ -1,482 +0,0 @@
-# Genre Taxonomy Convergence Plan
-
-日期:2026-06-04
-状态:二次修订版
-
-## 目标
-
-把题材体系收敛到 CSV 已采用的 15 个 `canonical_genre`,同时保留 37 个中文题材模板作为初始化阶段的可叠加 preset。
-
-一句话原则:
-
-> CSV canonical 是检索主干;taxonomy index 是用户输入层真源;模板文件是 preset;平台细分、套路、形式全部标签化。
-
-## 已核实事实
-
-- `webnovel-writer/templates/genres/*.md` 当前实际数量是 37 个。
-- 37 个模板只在初始化链路中直接读取:
-  - `skills/webnovel-init/SKILL.md` 提示按用户题材读取 `templates/genres/`。
-  - `scripts/init_project.py` 通过 `_normalize_genre_key()` 后拼 `templates/genres/{key}.md`。
-- 当前至少有三处题材输入归一逻辑,且输出命名空间不同:
-  - `scripts/init_project.py::_normalize_genre_key()`:用户输入 -> 模板文件名。
-  - `scripts/reference_search.py::resolve_genre()`:用户输入/平台标签/legacy -> 15 canonical。
-  - `scripts/data_modules/genre_aliases.py::GENRE_INPUT_ALIASES`:用户输入 -> 模板/profile 旧画像 key 的前置标签。
-- 现有映射规模不能再粗略按几十条估算:
-  - `PLATFORM_TO_CANONICAL` 34 keys。
-  - `_LEGACY_GENRE_MAP` 27 keys。
-  - 两者去重后 54 keys,重叠 7 keys。
-  - 加上 15 canonical 和 `全部`,当前 `resolve_genre()` 可处理 67 个 distinct 输入。
-  - 再并入 `_normalize_genre_key()` / `GENRE_INPUT_ALIASES` 的 15 条输入 alias,taxonomy 输入覆盖集合是 78 个 distinct labels。
-  - 再并入 37 个模板文件 stem,完整覆盖集合当前是 92 个 distinct labels/stems。
-- `_normalize_genre_key()` 与 `GENRE_INPUT_ALIASES` 当前 15 条内容完全一致,是重复真源。
-- `state.json` 当前 schema 是 `project_info.genre`,不是顶层 `project.genre`。
-- legacy `project.genre` 消费者不止 plan/write:
-  - `skills/webnovel-init/SKILL.md`
-  - `skills/webnovel-plan/SKILL.md`
-  - `skills/webnovel-write/SKILL.md`
-  - `skills/webnovel-review/SKILL.md`
-  - `scripts/data_modules/context_manager.py`
-  - `scripts/data_modules/memory_contract_adapter.py`
-- `context_manager.py` 当前是 `project.genre` 优先,`project_info.genre` 兜底;目标状态应反转为 `project_info` 优先。
-- `memory_contract_adapter.py` 当前 fallback 链是 Story Contracts route -> protagonist genre -> legacy `state.project.genre`,不读取 `project_info.genre`;目标状态应增加 `project_info` 并把 legacy `project.genre` 放最后。
-- `story_system_engine.py::_route()` 包含 keyword/alias match、explicit genre fallback、inferred genre fallback;全部未命中时会抛 `StorySystemRoutingError`,不是静默 fallback。
-- `references/csv/题材与调性推理.csv` 当前实际 route rows 是 26 条;测试应覆盖真实 CSV 全量 rows,不写死 26 或 27。
-- `references/genre-profiles.md` 已定位为 fallback,高频题材主链已迁入 Story Contracts。
-
-## 核心设计边界
-
-### 1. 两个命名空间不能混淆
-
-统一 resolver 不代表只有一个输出值。必须显式区分:
-
-- `canonical_genre`:用于 CSV 检索、Story System、裁决规则、新项目 `project_info.genre`。
-- `template_files`:用于 init 加载 `templates/genres/*.md`。
-
-典型冲突:
-
-- 旧 init 行为:`玄幻 -> 修仙.md`。
-- 旧 reference 行为:`resolve_genre("玄幻") -> 玄幻`。
-
-新 resolver 必须同时表达这两件事:
-
-```python
-GenreResolution(
-    raw_label="玄幻",
-    canonical_genre="玄幻",
-    template_files=["修仙.md"],
-    matched_labels=["玄幻"],
-    route_tags=[],
-    trope_tags=[],
-    format_tags=[],
-    unresolved=[],
-    warnings=[]
-)
-```
-
-### 2. 硬题材枚举
-
-唯一硬枚举继续使用 15 个 canonical:
-
-```text
-都市 玄幻 仙侠 奇幻 科幻
-历史 悬疑 游戏 古言 现言
-幻言 年代 种田 快穿 衍生
-```
-
-这些值用于:
-
-- CSV `适用题材`
-- `裁决规则.csv` 的 `题材`
-- Story System 的 `canonical_genre`
-- `reference_search.py --genre`
-- 新项目 `state.json.project_info.genre`
-
-### 3. Taxonomy Index
-
-新增 `webnovel-writer/references/taxonomy/genre-index.csv`。它不是单纯模板清单,而是用户输入层的唯一 taxonomy 数据真源。
-
-建议字段:
-
-```csv
-label,canonical_genre,label_type,template_file,route_tags,trope_tags,format_tags,aliases,notes
-修仙,玄幻,preset,修仙.md,,,,"玄幻;玄幻修仙;修仙/玄幻;修真","preserve old init: 玄幻 loads 修仙.md"
-都市脑洞,都市,platform,都市脑洞.md,都市脑洞,,,"都市奇闻",
-高武,都市,platform,高武.md,高武,,,"都市高武",
-电竞,游戏,platform,电竞.md,游戏电竞,,,"电竞文;游戏电竞",
-直播文,现言,format,直播文.md,,,直播文,"直播;直播带货;主播",
-克苏鲁,悬疑,preset,克苏鲁.md,克苏鲁,,,"克系;克系悬疑",
-规则怪谈,悬疑,route,规则怪谈.md,规则怪谈,,,"规则动物园;规则类",
-知乎短篇,现言,format,知乎短篇.md,,,知乎短篇,"知乎体;盐选;小程序短篇",
-历史古代,历史,platform,历史古代.md,历史古代,,,"",
-青春甜宠,现言,platform,青春甜宠.md,青春甜宠,,,"青春",
-游戏体育,游戏,platform,游戏体育.md,游戏体育,,,"网游;竞技;体育",
-民国言情,年代,platform,民国言情.md,民国言情,,,"",
-武侠,历史,legacy,,,,,,legacy without template
-```
-
-规则:
-
-- 每个 `templates/genres/*.md` 必须在 index 中有且只有一行 `template_file` 指向它。
-- `label` 与 `aliases` 使用同一查找空间,必须唯一,不能映射到多个 canonical。
-- `aliases` 使用 `;` 分隔;如字段含逗号,必须用 CSV 引号包裹。
-- 不带模板文件的 platform/legacy alias 也必须进入 index,不能留在 Python 硬编码字典里。
-- `canonical_genre` 必须属于 15 canonical 或 `全部`。
-- `label_type` 取值建议:`canonical`、`platform`、`route`、`trope`、`format`、`preset`、`legacy`。
-- 不再单独设 `template_type`,避免与 `label_type` 重叠;模板用途由 `label_type` 与 tag 列共同表达。
-- `GENRE_PROFILE_KEY_ALIASES` 暂不迁入 index。它输出的是英文 profile section key,与 canonical/template 命名空间不同;Phase 1-5 只迁移输入 alias,保留 profile key 映射并重命名/注释清楚。
-
-### 4. Resolver Contract
-
-新增共享 loader/resolver,例如 `scripts/genre_taxonomy.py`:
-
-```python
-GenreResolution(
-    raw_label="知乎短篇风的规则怪谈",
-    canonical_genre="悬疑",
-    matched_labels=["规则怪谈", "知乎短篇"],
-    template_files=["规则怪谈.md", "知乎短篇.md"],
-    route_tags=["规则怪谈"],
-    trope_tags=[],
-    format_tags=["知乎短篇"],
-    unresolved=[],
-    warnings=[]
-)
-```
-
-兼容原则:
-
-- `reference_search.resolve_genre()` 保留为 wrapper,只返回 canonical 或原值,用于现有调用点。
-- `_normalize_genre_key()` 不再拥有 alias 字典;如果暂时保留,只能委托 taxonomy resolver 返回首个 `template_file` 的 stem。
-- `data_modules/genre_aliases.py` 不再维护 `GENRE_INPUT_ALIASES`;只保留 profile key 映射,或通过 taxonomy 先得到 template/profile lookup label。
-- Story System 不改变 `_route()` 的 route table 匹配语义,只把输入 canonical 化能力接到同一 wrapper。
-- loader 使用缓存,例如 `functools.lru_cache`,避免高频路径重复读 CSV。
-
-### 5. Resolver 匹配算法
-
-Phase 1.5 必须先定义并测试算法,不靠隐式行为:
-
-1. 归一化输入:trim、全角/半角符号统一、大小写无关、去除多余空白。
-2. 分隔符拆 token:支持 `+`、`+`、`/`、`、`、`,`、`,`、`|`、`与`。
-3. exact match 优先:token 命中 `label` 或任一 alias 时直接加入匹配结果。
-4. longest substring match 兜底:对完整原始输入按 label/alias 长度倒序扫描,支持 `知乎短篇风的规则怪谈` 这种复合自然语言输入。
-5. 去重与冲突处理:
-   - 同一 `template_file` 只保留一次。
-   - `route/platform/canonical/preset` 优先决定 `canonical_genre`。
-   - `format/trope` 可追加 tags 和模板,但不应压过 route/platform 的 canonical。
-   - 多个高优先级标签指向不同 canonical 时,返回 `warnings=["ambiguous_canonical"]`;init 交互层应展示推断结果并允许用户确认。
-6. 未匹配片段进入 `unresolved`,wrapper 保持旧行为:`resolve_genre()` 返回原值而不是直接报错。
-
-## State Schema
-
-新 init 项目写入:
-
-```json
-{
-  "project_info": {
-    "genre": "悬疑",
-    "genre_label": "知乎短篇风的规则怪谈",
-    "genre_tags": {
-      "route": ["规则怪谈"],
-      "trope": [],
-      "format": ["知乎短篇"],
-      "templates": ["规则怪谈", "知乎短篇"]
-    }
-  }
-}
-```
-
-兼容读取顺序:
-
-1. `project_info.genre`
-2. `project_info.genre_label`
-3. legacy `project.genre`
-4. 配置 fallback
-
-写入新项目时不再新增顶层 `project.genre`。
-
-## 改动范围
-
-### 必改
-
-- `templates/genres/*.md`
-  - H1 标题中文化。
-  - 不移动文件。
-- `references/taxonomy/genre-index.csv`
-  - 覆盖 37 个模板。
-  - 覆盖 `PLATFORM_TO_CANONICAL`、`_LEGACY_GENRE_MAP`、`_normalize_genre_key()`、`GENRE_INPUT_ALIASES` 的 label/alias 集合。
-- `scripts/genre_taxonomy.py`
-  - 新增共享 CSV loader/resolver。
-- `scripts/reference_search.py`
-  - 删除硬编码 `PLATFORM_TO_CANONICAL` 与 `_LEGACY_GENRE_MAP`。
-  - `resolve_genre()` 改为调用 taxonomy wrapper。
-- `scripts/init_project.py`
-  - init 时用 taxonomy 解析用户原始题材。
-  - `project_info.genre` 写 canonical。
-  - 读取模板时按 `template_file` 加载 preset,不再按原始输入精确拼路径。
-- `scripts/data_modules/genre_aliases.py`
-  - 移除 `GENRE_INPUT_ALIASES`。
-  - 保留并注释 `GENRE_PROFILE_KEY_ALIASES`,说明它属于 fallback profile key 命名空间。
-- `scripts/data_modules/context_manager.py`
-  - 当前是 `project.genre` 优先;改为 `project_info.genre` / `genre_label` 优先,legacy `project.genre` 兜底。
-- `scripts/data_modules/memory_contract_adapter.py`
-  - 当前不读 `project_info`;增加 `project_info.genre` / `genre_label` 到 fallback 链,并把 legacy `project.genre` 放最后。
-- `skills/webnovel-init/SKILL.md`
-  - 主体题材只展示 15 canonical。
-  - 修正 legacy `project.genre` shell snippet。
-  - 说明可输入 preset/套路/形式,但运行时会映射到 canonical。
-- `skills/webnovel-plan/SKILL.md`
-  - 修正所有 legacy `project.genre` shell snippet。
-- `skills/webnovel-write/SKILL.md`
-  - 修正 legacy `project.genre` shell snippet。
-- `skills/webnovel-review/SKILL.md`
-  - 修正 legacy `project.genre` shell snippet。
-- `templates/output/state-schema.md`
-  - 加入 `project_info.genre_label` 与 `project_info.genre_tags` 示例。
-- `scripts/validate_csv.py`
-  - 增加 taxonomy index 双向校验。
-  - 增加三份旧字典到 index 的 symmetric diff 校验。
-- 相关测试
-  - `reference_search` resolver 兼容测试。
-  - `init_project` state/schema/template 加载测试。
-  - Story System 真实 CSV route 端到端测试。
-  - 所有 SKILL.md 读取 `genre` 的 grep/fixture 校验。
-
-### 应改
-
-- `references/csv/genre-canonical.md`
-  - 明确 `题材与调性推理.csv` 的 `题材/流派` 是 route tag,不是 canonical enum。
-- `references/csv/README.md`
-  - 补充 taxonomy index、template preset、canonical 的关系。
-- `references/index/reference-loading-map.md`
-  - 更新 init 阶段题材模板加载规则。
-- `references/genre-profiles.md`
-  - 把 `project.genre` 文档表述修正为 `project_info.genre`,并标注 fallback 定位。
-
-### 暂不改
-
-- 不大规模重写 9 张核心 CSV 内容。
-- 不删除 37 个模板。
-- 不把 `templates/genres/` 立即拆成 `canonical/` 和 `presets/` 子目录。
-- 不批量迁移用户已有项目的 `state.json`,只提供兼容读取。
-- 不把 `genre-profiles.md` 重新升级为主真源。
-- 不在 Phase 1-5 迁移 `GENRE_PROFILE_KEY_ALIASES` 到 index;它属于 profile fallback 命名空间,后续单独评估。
-
-## 分阶段计划
-
-### Phase 1: Taxonomy Index 与模板校验
-
-范围:
-
-- 新增 `references/taxonomy/genre-index.csv`。
-- 覆盖现有 37 个模板文件。
-- 把以下集合全部纳入 index 的 `label` 或 `aliases`:
-  - `GENRE_CANONICAL` 15 项和 `全部`。
-  - `PLATFORM_TO_CANONICAL` 34 keys。
-  - `_LEGACY_GENRE_MAP` 27 keys。
-  - `_normalize_genre_key()` 15 keys。
-  - `GENRE_INPUT_ALIASES` 15 keys。
-  - 37 个模板文件 stem。
-- index 可用一行承载多个 alias,所以不要求 92 行,但要求 coverage 集合无遗漏。
-- 所有模板 H1 中文化,去掉英文括号。
-- 新增校验:
-  - 实际 `templates/genres/*.md` 数量与 index `template_file` 双向一致。
-  - 每个 `template_file` 存在且唯一。
-  - 每个 `canonical_genre` 属于 15 canonical 或 `全部`。
-  - 每个 `label`/`alias` 唯一,不能映射到多个 canonical。
-  - 旧字典 keys 与 index label/alias 做 symmetric diff,diff 必须为空或显式列入 allowlist。
-
-不改运行逻辑。
-
-验证:
-
-```powershell
-(Get-ChildItem -Path webnovel-writer\templates\genres -Filter *.md | Measure-Object).Count
-python -X utf8 webnovel-writer\scripts\validate_csv.py
-```
-
-### Phase 1.5: Resolver Contract 先落地
-
-范围:
-
-- 新增共享 taxonomy loader/resolver。
-- 定义结构化 `GenreResolution`。
-- 实现并测试第 5 节的 exact + longest substring 匹配算法。
-- 给 `reference_search.resolve_genre()`、`init_project` 模板解析、`genre_aliases` profile key lookup 写清楚委托关系。
-- 明确 `GENRE_PROFILE_KEY_ALIASES` 保留在 `genre_aliases.py`,但输入 alias 来源改为 taxonomy。
-- 在测试中先证明旧行为不丢:
-  - `PLATFORM_TO_CANONICAL` 原有用例全部通过 index resolver。
-  - `_LEGACY_GENRE_MAP` 原有用例全部通过 index resolver。
-  - `_normalize_genre_key()` 原 alias 用例全部能解析到相同模板文件。
-  - `GENRE_INPUT_ALIASES` 原 alias 用例全部能得到相同 profile lookup label。
-
-这一阶段的目标是拆掉“多真源”的设计风险,再进入调用点迁移。
-
-### Phase 2: 迁移运行时调用点
-
-范围:
-
-- `reference_search.py` 删除硬编码映射,改用 taxonomy。
-- `init_project.py` 删除本地 alias 字典,按 `GenreResolution.template_files` 加载模板。
-- `story_system_engine.py` 保持 `_route()` 的 keyword/alias/fallback/exception 顺序,内部 canonical resolve 改用同一 wrapper。
-- `genre_aliases.py` 输入 alias 迁移到 taxonomy,profile key 只处理 profile section/key 兼容。
-- 增加 lint/grep,禁止新增 `PLATFORM_TO_CANONICAL`、`_LEGACY_GENRE_MAP`、`GENRE_INPUT_ALIASES` 这类硬编码输入 dict。
-
-验证:
-
-- `都市日常 -> 都市`
-- `宫斗宅斗 -> 古言`
-- `玄幻言情 -> 幻言`
-- `规则怪谈 -> 悬疑`
-- `网游 -> 游戏`
-- `玄幻 -> canonical 玄幻,同时 init 模板选中修仙.md`
-- `克系 -> canonical 悬疑或按 index 配置,同时 init 模板选中克苏鲁.md`
-- `知乎短篇风的规则怪谈 -> canonical 悬疑,同时模板包含规则怪谈.md 和 知乎短篇.md`
-
-### Phase 3: Init 写入与 schema 消费者修正
-
-范围:
-
-- `init_project.py` 写入 `project_info.genre`、`project_info.genre_label`、`project_info.genre_tags`。
-- `skills/webnovel-init/SKILL.md`、`skills/webnovel-plan/SKILL.md`、`skills/webnovel-write/SKILL.md`、`skills/webnovel-review/SKILL.md` 的 genre 读取改为:
-  - `project_info.genre` 优先。
-  - `project_info.genre_label` 可作为展示/诊断。
-  - legacy `project.genre` 兜底。
-- `memory_contract_adapter.py` 与 `context_manager.py` 同样改为 `project_info` 优先。
-- 更新 `templates/output/state-schema.md`。
-
-兼容策略:
-
-- 老项目只含 `project.genre` 时继续可读。
-- 新项目不再写 `project.genre`。
-- 非 canonical 老值通过 taxonomy resolver 兼容,不直接崩溃。
-
-验证:
-
-- init 新项目 state schema 测试。
-- 四个 SKILL.md 中 shell snippet 的读取逻辑测试或 grep 校验。
-- memory/context fallback 测试。
-
-### Phase 4: Story System 真实 CSV 端到端验证
-
-范围:
-
-- 增加真实 CSV route 覆盖测试,使用 `webnovel-writer/references/csv/题材与调性推理.csv`。
-- 对每个 route row:
-  - 如果 `关键词` / `意图与同义词` / `题材别名` 有值,取第一个可用 alias 作为 query,断言 `_route()` 不抛异常,且通常为 `keyword_or_alias_match`。
-  - 如果 alias 字段为空,则用 `题材/流派` 或 `canonical_genre` 作为 explicit genre fallback 输入,断言不抛异常。
-- 断言:
-  - 不抛 `StorySystemRoutingError`。
-  - `route.canonical_genre` 属于 15 canonical。
-  - `route.genre_filter == route.canonical_genre`,除非 canonical 是空或 `全部`。
-  - 未知 query + 未知 genre 仍应抛 `StorySystemRoutingError`,保持现有失败语义。
-- 当前真实 CSV 是 26 rows,但测试应按实际行数动态覆盖,不写死 26 或 27。
-
-验证:
-
-```powershell
-$env:PYTHONUTF8='1'; python -m pytest webnovel-writer\scripts\data_modules\tests\test_story_system_engine.py -q --no-cov
-$env:PYTHONUTF8='1'; python -m pytest webnovel-writer\scripts\data_modules\tests\test_story_system_cli.py -q --no-cov
-```
-
-### Phase 5: Skill 与文档收口
-
-范围:
-
-- `webnovel-init/SKILL.md`
-  - 主体题材展示 15 canonical。
-  - preset/套路/形式用示例说明,不混入硬枚举。
-- `webnovel-plan/SKILL.md`、`webnovel-write/SKILL.md`、`webnovel-review/SKILL.md`
-  - 确认 genre snippet 均为 `project_info` 优先。
-- `references/csv/genre-canonical.md`
-  - 明确 canonical、route tag、trope tag、format tag 的边界。
-- `references/csv/README.md`
-  - 写明 CSV 只接受 canonical,taxonomy index 负责用户输入层。
-- `references/index/reference-loading-map.md`
-  - 更新模板加载规则。
-- `references/genre-profiles.md`
-  - 明确 fallback 触发条件。
-
-### Phase 6: 可选目录重构
-
-只有前五阶段稳定后再做。
-
-目标结构:
-
-```text
-templates/genres/
-  index.csv
-  canonical/
-    都市.md
-    玄幻.md
-  presets/
-    都市异能.md
-    规则怪谈.md
-    知乎短篇.md
-```
-
-这一步路径影响大,必须单独提交。
-
-## genre-profiles.md Fallback 规则
-
-`genre-profiles.md` 只在以下场景使用:
-
-1. 老项目没有 Story Contracts,无法从 `.story-system` 取得 route/profile。
-2. `story_contracts.master.route.primary_genre` 为空,且 protagonist/state fallback 有 genre。
-3. 用户显式启用了 legacy profile fallback。
-
-目标优先级:
-
-1. Story Contracts 的 route/profile。
-2. `project_info.genre_label` 或 `project_info.genre` 经 taxonomy resolve 后的结果。
-3. legacy `project.genre`。
-4. 配置项 fallback genre。
-
-`genre_profile_excerpt` 只能作为补充 context,不能覆盖 Story System contract 的 route 决策。
-
-## 建议提交拆分
-
-1. `docs(genres): address taxonomy plan review`
-2. `chore(genres): add taxonomy index and normalize headings`
-3. `feat(genres): add taxonomy resolver`
-4. `refactor(genres): migrate genre resolution call sites`
-5. `feat(init): persist canonical genre and genre tags`
-6. `docs(genres): update skill and csv taxonomy guidance`
-7. 可选:`refactor(genres): split canonical and preset templates`
-
-## 风险与控制
-
-- 风险:CSV index 变成又一份真源。
-  控制:Phase 2 必须删除 Python 硬编码输入映射,并加 grep/lint 防回潮。
-
-- 风险:模板命名空间与 canonical 命名空间混淆。
-  控制:`GenreResolution` 同时返回 `canonical_genre` 与 `template_files`,调用点只取自己需要的字段。
-
-- 风险:`玄幻 -> 修仙.md` 这类历史行为丢失。
-  控制:在 index 中显式建模,并加回归测试。
-
-- 风险:`系统流`、`知乎短篇` 等默认 canonical 有争议。
-  控制:index 中标注 `label_type`;init 交互层展示推断结果,用户不同意时可显式指定 canonical。
-
-- 风险:Story System route 被 resolver 行为变化破坏。
-  控制:Phase 4 使用真实 `题材与调性推理.csv` 全量 route rows 做端到端测试,并保留未知输入抛错语义。
-
-- 风险:schema 读取点漏改,继续读 `project.genre`。
-  控制:Phase 3 增加四个 SKILL.md、memory/context 的 grep 校验和兼容读取测试。
-
-- 风险:`GENRE_PROFILE_KEY_ALIASES` 孤立。
-  控制:Phase 1-5 明确保留它,但移除输入 alias;文件注释清楚它只服务 fallback profile key。
-
-## 完成标准
-
-- 37 个模板全部有 index 映射,且 index 与实际文件双向一致。
-- index 覆盖旧映射和模板 stem 的 label/alias 集合,symmetric diff 为空或仅有显式 allowlist。
-- 所有模板标题纯中文。
-- `PLATFORM_TO_CANONICAL`、`_LEGACY_GENRE_MAP`、`GENRE_INPUT_ALIASES` 不再以硬编码输入 dict 存在。
-- `_normalize_genre_key()` 不再维护本地 alias。
-- `GENRE_PROFILE_KEY_ALIASES` 的归属已明确,且不与 canonical/template resolver 混用。
-- `reference_search.py`、`init_project.py`、`genre_aliases.py` 使用同一 taxonomy resolver 作为输入归一真源。
-- 新 init 项目写入 canonical `project_info.genre`,并保存 `genre_label` 与 `genre_tags`。
-- 老项目 `project.genre` 仍可兼容读取,但不是新写入 schema。
-- 所有 SKILL.md 中 genre 读取均为 `project_info.genre` 优先。
-- 真实 Story System route CSV 全量端到端测试通过,未知输入仍抛 `StorySystemRoutingError`。
-- `validate_csv.py`、prompt integrity 与全量 pytest 通过。

+ 0 - 223
docs/operations/operations.md

@@ -1,223 +0,0 @@
-# 项目结构与运维
-
-## 目录层级
-
-## 运维口径
-
-- `.story-system/`:主链真源
-- accepted `CHAPTER_COMMIT`:唯一写后事实入口
-- `.webnovel/state.json`、`index.db`、`summaries/`、`memory_scratchpad.json`:投影/read-model
-- `references/genre-profiles.md`:fallback-only
-- `preflight` 与 dashboard 的 `story_runtime` / `story-runtime/health` 是第一观察点
-
-系统涉及 4 层目录,使用前需要了解它们的区别:
-
-| 层级 | 说明 | 示例 |
-|------|------|------|
-| `WORKSPACE_ROOT` | Claude Code 工作区根目录 | `D:\wk\novels` |
-| `.claude/` | 工作区级配置与项目指针 | `D:\wk\novels\.claude\` |
-| `PROJECT_ROOT` | 某本书的项目根目录(由 `/webnovel-init` 创建) | `D:\wk\novels\凡人资本论` |
-| `CLAUDE_PLUGIN_ROOT` | 插件缓存目录(不在项目内,由 Marketplace 安装管理) | 自动管理 |
-
-### 工作区目录
-
-```text
-workspace-root/
-├── .claude/
-│   ├── .webnovel-current-project   # 指向当前书项目根
-│   └── settings.json
-├── 小说A/                          # PROJECT_ROOT
-├── 小说B/
-└── ...
-```
-
-一个工作区可以包含多本书,通过 `.webnovel-current-project` 指针切换当前操作的书。
-
-### 书项目目录(PROJECT_ROOT)
-
-```text
-project-root/
-├── .webnovel/            # 运行时数据
-│   ├── state.json        # 项目状态
-│   ├── index.db          # SQLite 索引(实体/关系/章节数据)
-│   ├── vectors.db        # 向量索引
-│   ├── projection_log.jsonl # 投影执行日志
-│   ├── summaries/        # 章节摘要
-│   ├── backups/          # 自动备份
-│   └── archive/          # 归档
-├── .story-system/        # Story System 数据
-│   ├── MASTER_SETTING.json
-│   ├── chapters/
-│   ├── volumes/
-│   ├── reviews/
-│   ├── commits/
-│   └── events/
-├── 正文/                  # 正文章节
-├── 大纲/                  # 总纲与卷纲
-├── 设定集/                # 世界观、角色、力量体系
-└── 审查报告/              # 审查输出
-```
-
-### 插件目录
-
-插件安装在 Claude 插件缓存目录,不在书项目内。运行时通过 `CLAUDE_PLUGIN_ROOT` 引用:
-
-```text
-${CLAUDE_PLUGIN_ROOT}/
-├── skills/       # 8 个 Skill 命令定义
-├── agents/       # 4 个 Agent 定义
-├── scripts/      # Python 脚本与数据模块
-├── hooks/        # Claude Code 会话钩子
-├── references/   # 参考文档(题材画像、追读力分类法等)
-├── templates/    # 初始化模板
-├── genres/       # 精调题材配置
-└── dashboard/    # 可视化面板前端
-```
-
-### 用户级全局映射
-
-当工作区指针不可用时,系统会从用户级 registry 查找 workspace → project 映射:
-
-```text
-${CLAUDE_HOME:-~/.claude}/webnovel-writer/workspaces.json
-```
-
-## 常用运维命令
-
-### 环境预检
-
-```bash
-python -X utf8 "${CLAUDE_PLUGIN_ROOT}/scripts/webnovel.py" --project-root "${WORKSPACE_ROOT}" preflight
-python -X utf8 "${CLAUDE_PLUGIN_ROOT}/scripts/webnovel.py" --project-root "${WORKSPACE_ROOT}" project-status --format summary
-python -X utf8 "${CLAUDE_PLUGIN_ROOT}/scripts/webnovel.py" --project-root "${WORKSPACE_ROOT}" doctor --format text
-```
-
-`preflight` 是快速检查,`project-status` 给短状态和下一步,`doctor` 是阶段感知体检。
-
-检查项:插件脚本路径 / 项目根是否可解析 / Skill 目录是否存在 / 阶段应有文件 / JSON / SQLite / RAG 配置 / Python 依赖 / Dashboard 产物。
-
-若 `story_runtime.mainline_ready=false`,说明当前项目仍在 legacy fallback 或 commit 主链不完整。
-
-### 写章关卡
-
-```bash
-python -X utf8 "${CLAUDE_PLUGIN_ROOT}/scripts/webnovel.py" --project-root "${PROJECT_ROOT}" write-gate --chapter 12 --stage prewrite --format text
-python -X utf8 "${CLAUDE_PLUGIN_ROOT}/scripts/webnovel.py" --project-root "${PROJECT_ROOT}" write-gate --chapter 12 --stage precommit --format text
-python -X utf8 "${CLAUDE_PLUGIN_ROOT}/scripts/webnovel.py" --project-root "${PROJECT_ROOT}" write-gate --chapter 12 --stage postcommit --format text
-```
-
-- `prewrite`:检查项目阶段、runtime contract、占位符和写前必要文件。
-- `precommit`:检查正文和 review / fulfillment / disambiguation / extraction 四类提交产物。
-- `postcommit`:检查 commit 和 projection 状态。
-
-### 索引重建
-
-```bash
-python "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" index process-chapter --chapter 1
-python "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" index stats
-```
-
-### 健康报告
-
-```bash
-python "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" status -- --focus all
-python "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" status -- --focus urgency
-```
-
-`status` 保留宏观创作健康报告语义;需要机器可读短状态时使用 `project-status`。
-
-### 向量重建
-
-```bash
-python "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" rag index-chapter --chapter 1
-python "${SCRIPTS_DIR}/webnovel.py" --project-root "${PROJECT_ROOT}" rag stats
-```
-
-### 投影补跑
-
-```bash
-python -X utf8 "${CLAUDE_PLUGIN_ROOT}/scripts/webnovel.py" --project-root "${PROJECT_ROOT}" projections retry --chapter 12 --format text
-python -X utf8 "${CLAUDE_PLUGIN_ROOT}/scripts/webnovel.py" --project-root "${PROJECT_ROOT}" projections replay --from-chapter 1 --to-chapter 12 --format text
-```
-
-投影补跑只从已有 `.story-system/commits/*.commit.json` 读取事实,并重新生成 `.webnovel/state.json`、`index.db`、`summaries/`、`memory_scratchpad.json`、`vectors.db` 等 read-model。每次执行会追加 `.webnovel/projection_log.jsonl`。
-
-### 作者友好报告与恢复
-
-主 Skill 的最终报告统一使用四种总状态:已完成、部分完成、需要你处理、未完成。报告只给作者需要知道的结论、产物、问题和下一步建议;内部 JSON、traceback 和长命令日志不直接展示。
-
-异常分三类处理:
-
-- **已自动处理**:幂等、可重试、不碰作者内容的问题,例如 projection retry 成功、缺失 runtime contract 后重新生成。流程默认继续,但最终报告必须说明处理过什么。
-- **需要确认**:会影响创作方向、事实取舍、是否覆盖文件或断点续跑边界的问题,例如正文被手动改过、章纲更新晚于正文、本章已 accepted 后再次写章。系统应给 2-3 个有限选项。
-- **必须处理**:blocking 审查问题、关键产物缺失、commit 被拒、投影补跑仍失败等。系统停在安全位置,报告说明已完成内容、卡点和恢复建议。
-
-`/webnovel-write` 会记录写章断点,用于重跑时判断可信完成项:
-
-```bash
-python -X utf8 "${CLAUDE_PLUGIN_ROOT}/scripts/webnovel.py" --project-root "${PROJECT_ROOT}" run-ledger write-resume --chapter 12 --format text
-```
-
-断点建议只负责判断和提示,不自动覆盖文件。凡是涉及作者手改正文、旧正文是否沿用、accepted commit 是否重做,都必须先询问。
-
-不可恢复故障会提示查看:
-
-```text
-.webnovel/logs/run_last.log
-```
-
-该日志用于保留最近一次运行的脱敏技术细节,便于排查。写入日志时会遮蔽常见敏感字段和值,包括 `api_key`、`secret`、`token`、`authorization`、`password`、`passwd`、`credential` 以及形如 `KEY=value` 的内联密钥片段。日志仍可能包含文件路径和错误上下文,提交 issue 前建议再人工扫一眼。
-
-### 测试
-
-```bash
-pwsh "${CLAUDE_PLUGIN_ROOT}/scripts/run_tests.ps1" -Mode smoke
-pwsh "${CLAUDE_PLUGIN_ROOT}/scripts/run_tests.ps1" -Mode full
-python -X utf8 "${CLAUDE_PLUGIN_ROOT}/scripts/run_behavior_evals.py" --format text
-python -X utf8 "${CLAUDE_PLUGIN_ROOT}/scripts/validate_plugin_package.py" --format text
-```
-
-`run_behavior_evals.py` 是快速行为契约检查;`validate_plugin_package.py` 按 plugin-dev 思路检查 manifest、Skill / Agent frontmatter、hooks wrapper、README 版本和路径可移植性。
-
-### Hook 开关
-
-插件级 hook 默认很轻:
-
-- `SessionStart`:只运行 `project-status --format summary`,不写文件、不启动服务。
-- `PreToolUse`:对直接写主链 / read-model 文件和绕过 runtime 的危险命令做兜底阻断。
-
-需要临时关闭时设置环境变量:
-
-```bash
-WEBNOVEL_DISABLE_SESSION_STATUS_HOOK=1
-WEBNOVEL_DISABLE_RUNTIME_GUARD_HOOK=1
-```
-
-## Story System 运维
-
-### 健康检查
-
-```bash
-python -X utf8 "${CLAUDE_PLUGIN_ROOT}/scripts/webnovel.py" --project-root "${PROJECT_ROOT}" story-events --health
-```
-
-返回字段:`sqlite_rows` / `event_files` / `ok`
-
-重点关注:
-
-- `.story-system/commits/chapter_XXX.commit.json` 是否存在且为 accepted
-- `projection_status` 是否全部为 `done` / `skipped`
-- `.story-system/events/` 是否可读
-- `index.db` 中 `story_events` 表是否可查
-- `override_contracts` 是否能统计 `amend_proposal`
-
-### 备份
-
-做 Story System 相关备份时,至少同时备份以下内容:
-
-```text
-.story-system/
-.webnovel/index.db
-```
-
-如果要做章节级回溯,建议连同 `.webnovel/summaries/` 一起备份。

+ 0 - 79
docs/operations/plugin-best-practices-deep-review-2026-06-03.md

@@ -1,79 +0,0 @@
-# Plugin Best Practices Deep Review
-
-审查对象:`webnovel-writer/` Claude Code 插件源目录  
-审查日期:2026-06-03  
-回滚校正:2026-06-04
-
-## 当前结论
-
-**最佳实践符合度:中高。**
-
-插件的工程基础、目录结构、版本同步、测试覆盖和 source 包装都比较扎实;但按 Anthropic 官方 `plugin-dev` 的严格 agent/skill 元数据口径,仍保留一些非阻断差距。
-
-本报告已按 2026-06-04 的回滚结果修正:此前 Codex 额外加入的 agent 英文触发描述、`When to invoke` 小节,以及 `deconstruction-agent` 的 `magenta` 颜色改动已经撤回。
-
-## 已完成/已核实
-
-- `.claude-plugin/plugin.json` 存在且 manifest 可读取。
-- 版本同步检查通过:`Versions are in sync: 6.0.0`。
-- prompt integrity 测试通过。
-- 全量 pytest 通过。
-- 插件 source 子目录已有 `README.md` 与 `LICENSE`。
-- `webnovel-init` 的初始化采集模型已下沉到 `references/init-collection-schema.md`。
-- `webnovel-plan` 的结构化节点示例已下沉到 `references/outlining/chapter-planning.md`。
-- `webnovel-query/references/system-data-flow.md` 已标注 legacy,并修正旧 `/webnovel-resume` 与“6 个 Agent 并行”漂移描述。
-- `docs/superpowers/` 已归档到 `docs/archive/superpowers/`,活跃入口链接已修正。
-- `docs/architecture/` 的历史快照已归档到 `docs/archive/architecture/`,活跃入口链接已修正。
-
-## 回滚说明
-
-已撤回以下 Codex 额外改动:
-
-- 4 个 agent 的英文 `Use this agent when...` 触发描述。
-- 4 个 agent 正文顶部的 `## When to invoke` 场景小节。
-- `deconstruction-agent` 的 `color: magenta`,已恢复为 `color: purple`。
-
-当前 agent frontmatter 状态:
-
-- `context-agent`: 短中文 description,`model: inherit`,`color: blue`
-- `data-agent`: 短中文 description,`model: inherit`,`color: green`
-- `deconstruction-agent`: 中文 description,`model: inherit`,`color: purple`
-- `reviewer`: 中文 description,`model: inherit`,`color: yellow`
-
-## 剩余最佳实践差距
-
-### Agent metadata
-
-官方 `agent-development` 文档建议 agent description 使用更明确的触发句式,并在正文提供触发场景。当前已按要求回滚到 Claude 原对话状态,因此仍存在:
-
-- description 偏短或偏内部术语。
-- 没有 `When to invoke` 场景。
-- `deconstruction-agent` 的 `purple` 不是官方 `validate-agent.sh` 列出的颜色之一;官方脚本列出的颜色是 `blue`、`cyan`、`green`、`yellow`、`magenta`、`red`。
-
-这不是运行时阻断项,但若以后要按官方 validator 严格过检,需要再单独确认是否接受这类 metadata 改动。
-
-### Cross-platform shell assumptions
-
-多个 SKILL.md 仍包含 Bash 风格片段,例如 `export`、`${VAR}`、`cat`、`for ch in $(seq ...)`。建议在 README 或 operations 文档中说明这些片段假设在 Claude Code Bash tool 或兼容 shell 中执行。
-
-### 真机加载验证
-
-文件级与测试级验证已通过,但尚未在干净目录中执行真实 Claude Code 插件 enable/load 流程。
-
-## 验证记录
-
-```powershell
-python -X utf8 webnovel-writer\scripts\sync_plugin_version.py --check --expected-version 6.0.0
-$env:PYTHONUTF8='1'; python -m pytest webnovel-writer\scripts\data_modules\tests\test_prompt_integrity.py -q --no-cov
-$env:PYTHONUTF8='1'; python -m pytest -q --no-cov
-```
-
-结果:
-
-- 版本同步:通过
-- prompt integrity:通过
-- 全量 pytest:通过
-
-## Final Assessment
-
-当前状态已经完成 Claude 原会话提出的 B/C 收尾主线,并保留了回滚后的 agent 元数据。若下一步目标是“严格官方 validator 过检”,需要用户明确同意再调整 agent description、触发场景和 `deconstruction-agent` 颜色。

+ 0 - 404
docs/superpowers/plans/2026-06-10-audit-fix-plan.md

@@ -1,404 +0,0 @@
-# 2026-06-10 全项目审查修复计划
-
-> **状态(2026-06-11 截断):** 本计划随 v7 绞杀式收敛(`docs/architecture/story-repo-spec-2026-06-10.md`)截断收口。
-> - **已完成并保留**:Phase 0 全部(Task 1-6,正文数据安全)+ Task 7——这是 v6 用户数据与 v7 迁移器读取的地基。
-> - **作废**:Task 8-24(Phase 1 数据链)、Task 26-27、Task 29-34——目标模块(SQLite 投影、event log、v6 提示词、dashboard、CLI 样板)在 v7 中整体删除,不再修缮。
-> - **例外保留为独立候选**:Task 25(嵌入默认出网,隐私问题,若 v6 分支再发维护版则必修)、Task 28(CI 加固,仓库层面,v7 继续复用,可随时单独做)。
-> - 分支 `fix/audit-2026-06-10` 以 Phase 0 + Task 7 收口合入 master,作为 v6 最后一批数据安全维护。
-
-> **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:** 修复 2026-06-10 深度审查发现的全部高/中危问题:数据丢失路径、数据链不一致、skill 流程死锁、隐私出网默认值与守卫绕过。
-
-**Architecture:** 按伤害优先级分四个阶段(P0 数据安全 → P1 数据链与流程 → P2 安全隐私 → P3 质量卫生)。每个阶段独立可交付、全绿后再进下一阶段。修复以"先写探针测试复现问题 → 修复 → 验证"为节奏;过时的文案级断言按《测试是探针不是约束》原则直接改写。
-
-**Tech Stack:** Python 3.10+ / pytest(Windows 下设 `PYTHONUTF8=1`)/ SQLite / FastAPI。
-
-**审查报告来源:** 本计划对应 2026-06-10 会话中六个维度的审查结论(数据链、提示词、代码质量、dashboard+hooks、数据安全+CI、仓库卫生+残余模块)。
-
-**运行测试:** `python -m pytest`(根目录 pytest.ini 已配置 testpaths 与 cov-fail-under=90)。
-
----
-
-## Phase 0 — P0 数据安全(正文是不可再生数据)
-
-### Task 1: backup_manager 备份失败不得报告成功
-
-**Files:**
-- Modify: `webnovel-writer/scripts/backup_manager.py:150-166, 228-254`
-- Test: `webnovel-writer/scripts/tests/test_backup_manager.py`
-
-- [x] **Step 1: 写失败测试**:在 tmp git 仓库中故意不配置 `user.name/user.email`(`git config --local --unset` 或 `-c user.useConfigOnly=true`),调用 `backup()`,断言返回失败且输出包含"备份失败"、不产生 `ch{N}` tag。
-- [x] **Step 2: 运行确认现状是假成功**(当前会打印 ✅ 并打 tag 在旧 HEAD)。
-- [x] **Step 3: 修复 `_run_git_command`**:`check=False` 分支改为返回 `(result.returncode == 0, stdout, stderr)`;调用方据真实退出码判断。"nothing to commit" 改为从 stdout/stderr 文本判断(当前 `:233` 的 `if not success and "nothing to commit"` 是永假死代码,一并删除重写):
-
-```python
-def _run_git_command(self, args, check=True):
-    result = subprocess.run(
-        ["git", *args], cwd=self.project_root,
-        capture_output=True, text=True, encoding="utf-8",
-    )
-    ok = result.returncode == 0
-    if check and not ok:
-        raise BackupError(f"git {' '.join(args)} 失败: {result.stderr.strip()}")
-    return ok, result.stdout, result.stderr
-```
-
-- [x] **Step 4: `backup()` 中 commit 失败时中止**:不打 tag、返回非零、输出含修复指引(提示运行 `git config user.name/user.email`);"nothing to commit" 视为成功但提示"本章无变更"。
-- [x] **Step 5: 跑全部 backup 测试通过后提交** `fix: backup reports real git failures and aborts tagging`。
-
-### Task 2: rollback 改为前滚式恢复,去掉 detached HEAD 与硬编码 master
-
-**Files:**
-- Modify: `webnovel-writer/scripts/backup_manager.py:294-307`
-- Test: `webnovel-writer/scripts/tests/test_backup_manager.py`
-
-- [x] **Step 1: 写测试**:建 tmp 仓库(默认分支命名为 `main`),打两个 ch tag,回滚到 ch1 后断言:(a) 仍在原分支(`git symbolic-ref HEAD` 成功且为 main);(b) 工作区内容等于 ch1;(c) `git log` 多出一个"rollback"提交(历史不丢)。
-- [x] **Step 2: 实现前滚式回滚**:
-
-```python
-def rollback(self, chapter: int) -> bool:
-    tag = f"ch{chapter}"
-    ok, _, _ = self._run_git_command(["rev-parse", "--verify", tag], check=False)
-    if not ok:
-        print(f"❌ 备份点 {tag} 不存在"); return False
-    ok, _, err = self._run_git_command(["checkout", tag, "--", "."], check=False)
-    if not ok:
-        print(f"❌ 回滚失败: {err}"); return False
-    self._run_git_command(["add", "-A"], check=False)
-    ok, _, err = self._run_git_command(
-        ["commit", "-m", f"rollback: 恢复到 {tag} 备份点"], check=False)
-    # 工作区与 tag 相同则 commit 报 nothing to commit,视为成功
-    return True
-```
-
-- [x] **Step 3: 删除所有 `checkout master` 硬编码**;任何需要分支名的地方用 `git symbolic-ref --short HEAD` 探测。
-- [x] **Step 4: 测试通过后提交** `fix: rollback is forward-only, never detaches HEAD`。
-
-### Task 3: 无 Git 时的降级备份必须覆盖正文,或醒目声明没有
-
-**Files:**
-- Modify: `webnovel-writer/scripts/backup_manager.py:175-195`
-- Test: `webnovel-writer/scripts/tests/test_backup_manager.py`
-
-- [x] **Step 1: 写测试**:模拟 git 不可用(monkeypatch `_git_available` 为 False),项目含 `正文/第0001章-x.md`,调用 `backup()` 后断言备份目录里存在该正文文件副本。
-- [x] **Step 2: 实现**:降级路径把 `正文/`、`大纲/`、`设定集/`、`.webnovel/state.json` 全部 `shutil.copytree/copy2` 进 `.webnovel/backups/snapshot_ch{N}_{ts}/`;输出明确列出备份了什么。保留按数量滚动清理(最多 10 个 snapshot)。
-- [x] **Step 3: 提交** `fix: degraded backup covers manuscript files`。
-
-### Task 4: init 重跑不得静默覆盖损坏的 state.json
-
-**Files:**
-- Modify: `webnovel-writer/scripts/init_project.py:294-300,366`
-- Test: `webnovel-writer/scripts/data_modules/tests/test_init_project_pruning.py`
-
-- [x] **Step 1: 写测试**:项目里放一个非法 JSON 的 state.json,重跑 init,断言 (a) 生成 `state.corrupt_*.json` 副本且内容等于原损坏文本;(b) 输出包含警告。
-- [x] **Step 2: 实现**:捕获 `json.JSONDecodeError` 时先 `shutil.copy2(state_path, state_path.with_name(f"state.corrupt_{ts}.json"))` 再重建,打印"⚠️ 原 state.json 已损坏,已另存为 ... 供手工抢救"。
-- [x] **Step 3: 提交** `fix: preserve corrupt state.json before rebuilding`。
-
-### Task 5: 迁移脚本带错不精简、写回原子化
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/migrate_state_to_sqlite.py:235-258`
-- Test: `webnovel-writer/scripts/data_modules/tests/test_migrate_state_to_sqlite.py`
-
-- [x] **Step 1: 写测试**:构造一条会迁移失败的实体(如非法类型触发 `stats["errors"] += 1`),跑迁移,断言 state.json 中 `entities_v3` 字段仍在、CLI 退出码非 0。
-- [x] **Step 2: 实现**:`if stats["errors"]: 跳过步骤5精简,输出"存在迁移错误,已保留原字段"`;步骤 5 的裸 `open('w')+json.dump` 改为 `security_utils.atomic_write_json(state_path, state, use_lock=True)`。
-- [x] **Step 3: 提交** `fix: migration never prunes state on partial failure`。
-
-### Task 6: archive_manager 原子写 + 恢复顺序反转
-
-**Files:**
-- Modify: `webnovel-writer/scripts/archive_manager.py:125-128, 494-508`
-- Test: `webnovel-writer/scripts/data_modules/tests/test_archive_manager.py`
-
-- [x] **Step 1: `save_archive` 改用 `atomic_write_json`**(归档是数据被移出 state 后的唯一副本)。
-- [x] **Step 2: `restore_character` 顺序反转**:先恢复 SQLite,确认成功后才从归档 JSON 删除该角色;SQLite 失败时归档保持原样并返回错误。写测试:monkeypatch SQLite 恢复抛异常,断言归档文件未被修改。
-- [x] **Step 3: 提交** `fix: archive writes atomic, restore is delete-last`。
-
----
-
-## Phase 1 — P1 数据链一致性与流程死锁
-
-### Task 7: SQLite 同步失败必须可见
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/state_manager.py:393-416, 450-451, 606-609`
-- Test: `webnovel-writer/scripts/data_modules/tests/test_state_manager_extra.py`
-
-- [x] `_sync_to_sqlite` 失败时:`save_state` 返回值携带 `sqlite_sync_ok=False`;`process-chapter` CLI 据此 `emit_error`(退出码非 0),错误信息提示运行 `webnovel.py projections retry --chapter N` 补偿。测试:monkeypatch `_sync_pending_patches_to_sqlite` 抛异常,断言 CLI 退出非 0 且 stdout JSON 含补偿指引。
-- [x] 提交 `fix: surface sqlite sync failures in process-chapter`。
-
-### Task 8: get_state_changes / get_relationships 走 SQLite 回退
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/state_manager.py:972-977, 1005-1013`
-- Test: `webnovel-writer/scripts/data_modules/tests/test_state_manager_extra.py`
-
-- [ ] 仿照 `get_entity` 的 SQLite-first 模式:先查 `self._sql_state_manager.get_entity_state_changes / get_recent_relationships`,无结果再回退内存。测试:用一个新建 StateManager 实例(模拟跨进程)读取此前保存的 state_changes,断言非空。
-- [ ] 同步把 `record_state_change`(:953-966)改为只进 `_pending_state_changes`,删除向 `self._state["state_changes"]` 的追加。
-- [ ] 提交 `fix: state change reads hit sqlite, not stale memory`。
-
-### Task 9: 事件镜像按章先删后插
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/event_log_store.py:109-146`
-- Test: `webnovel-writer/scripts/data_modules/tests/test_event_log_store.py`
-
-- [ ] 测试:同章先写 events A,再整体覆盖写 events B(不同 event_id),断言 `story_events` 表里只剩 B。实现:`_write_sqlite_mirror` 在同一事务内 `DELETE FROM story_events WHERE chapter = ?` 后再 INSERT(JSON 文件是该章事实源)。
-- [ ] 提交 `fix: event mirror mirrors, not accumulates`。
-
-### Task 10: 投影 writer 复用 chapter_status 单调状态机
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/state_projection_writer.py:59-65, 95`
-- Test: `webnovel-writer/scripts/data_modules/tests/test_projection_writers.py`
-
-- [ ] 把 `StateManager.set_chapter_status` 的 rank 比较逻辑提取为模块级函数 `should_transition(old, new) -> bool`(同文件或 schemas.py),两处共用。测试:先投影 accepted commit 再重放历史 rejected commit,断言状态仍是 `chapter_committed`。
-- [ ] 提交 `fix: projection respects chapter status monotonicity`。
-
-### Task 11: total_words 统一为投影重算口径
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/state_manager.py:280-285`
-- Test: 改写涉及 `update_progress` 增量口径的既有断言(探针原则)
-
-- [ ] `update_progress` 不再累加 `total_words`,只更新 `current_chapter/last_updated`;字数一律由 `StateProjectionWriter` 全量重算。grep 全仓库 `total_words` 的写入点确认只剩投影一处。
-- [ ] 提交 `fix: single source of truth for total_words`。
-
-### Task 12: add_entity 别名并入 pending 事务
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/state_manager.py:839-854`
-- Test: `webnovel-writer/scripts/data_modules/tests/test_state_manager_extra.py`
-
-- [ ] 别名写入改为追加到 `_pending_alias_entries`,统一在 `_sync_pending_patches_to_sqlite` 落库。测试:`add_entity` 后不调 `save_state` 直接查 SQLite,断言别名尚未落库;`save_state` 后断言已落库。
-- [ ] 提交 `fix: alias writes go through pending patch transaction`。
-
-### Task 13: SQLite 连接统一 WAL + busy_timeout + 批量事务
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/index_manager.py:626-634`(`_get_conn`)
-- Test: 现有测试回归即可
-
-- [ ] `_get_conn` 中执行 `PRAGMA journal_mode=WAL; PRAGMA busy_timeout=8000`。投影路径上把"每方法一次 commit"合并为单连接单事务(`IndexProjectionWriter.apply` 持有一个连接传入各写方法)。
-- [ ] 提交 `perf: WAL + single transaction per projection`。
-
-### Task 14: projection_log 持锁追加 + compact
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/projection_log.py:101-119`
-- Test: `webnovel-writer/scripts/data_modules/tests/test_projection_log.py`
-
-- [ ] 追加前持 `FileLock(path + ".lock")`;新增 `compact_projection_log(project_root, keep_per_chapter=3)` 函数并挂到 `webnovel.py projections compact` 子命令。测试:写 5 条同章 run 后 compact,断言只剩 3 条且为最新。
-- [ ] 提交 `fix: projection log locked appends + compact command`。
-
-### Task 15: legacy 提交路径加护栏
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/memory_contract_adapter.py:71-120, 147-156`
-- Test: `webnovel-writer/scripts/data_modules/tests/test_memory_contract_adapter.py`
-
-- [ ] `_commit_chapter_legacy` 入口检测:该章已存在 accepted commit 文件时拒绝执行并报错"该章已走 Story System 主链,禁止 legacy 双写"。docstring 标注 deprecated。
-- [ ] `chapter_commit_service.py:182-188`:amend proposal 的持久化挪到投影成功之后(或写入时带 `projection_run_id`,投影失败的 run 对应提案在 `projections retry` 成功前不进 pending 列表)。测试:投影全失败时断言 override_ledger 无新增 pending 提案。
-- [ ] 提交 `fix: legacy commit path refuses to double-write mainline chapters`。
-
-### Task 16: 清理危险死代码
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/state_manager.py:708-711`
-
-- [ ] 删除 `_save_state`(全仓库无调用方、绕过 pending 合并语义)。grep 确认无引用后提交 `chore: remove dangerous dead _save_state`。
-
-### Task 17: write SKILL blocking 死锁解除
-
-**Files:**
-- Modify: `webnovel-writer/skills/webnovel-write/SKILL.md`(L162 Step 3、L245、L327-331)
-- Test: `webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py`(若有相关断言联动改)
-
-- [ ] Step 3 改为:blocking 定点修复后**必须重跑 review-pipeline 重新生成 review_results.json**(清零已修复项),然后才进 Step 4;并补"作者裁决保留当前版本"出口:引用 `references/review/blocking-override-guidelines.md`,写明用 override ledger 命令记录后 commit 可带 `--override-ref` 通过。两条路径都要与 `chapter_commit_service.py:45` 的 `rejected = bool(review.blocking_count)` 判定自洽(override 路径需确认 service 支持,不支持则在 service 增加 override_ref 豁免逻辑——先读 `chapter_commit_service.py` 与 `override_ledger_service.py` 确认现有机制再动笔)。
-- [ ] 提交 `fix(skill): unblock the blocking-fix path in write flow`。
-
-### Task 18: 提示词中的错误命令修正
-
-**Files:**
-- Modify: `webnovel-writer/skills/webnovel-query/SKILL.md:78`、`webnovel-writer/agents/reviewer.md:30`
-
-- [ ] `memory-contract query-rules --chapter {n}` → `--domain {domain}`(对照 `memory_cli.py:90` 实参);reviewer.md 的 `index get-state-changes --limit 20` 补必填 `--entity "{entity_id}"`。逐条在本地实际执行一次验证不报 argparse 错。
-- [ ] 提交 `fix(skill): correct CLI invocations in query skill and reviewer agent`。
-
-### Task 19: 统一入口与 chapter_commit 参数口径对齐
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/webnovel.py:554-559` 或 `webnovel-writer/scripts/chapter_commit.py:23-26`
-- Test: `webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py`
-
-- [ ] 统一为 required(推荐,强制契约):`webnovel.py` 侧四个参数加 `required=True`,缺参时报错发生在统一入口层、信息面向作者。测试:缺 `--review-result` 调用,断言错误信息含参数名与示例。
-- [ ] 提交 `fix: align chapter-commit arg contract between entrypoints`。
-
-### Task 20: 修复 genre-profiles 死链(题材画像复活)
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/config.py`(新增 `references_dir` 解析)、`webnovel-writer/scripts/data_modules/context_manager.py:337-338`、`webnovel-writer/scripts/data_modules/memory_contract_adapter.py:245`
-- Test: `webnovel-writer/scripts/data_modules/tests/test_context_manager.py`
-
-- [ ] config 新增解析顺序:`{project_root}/.claude/references/`(用户覆盖)→ `{plugin_root}/references/`(默认,由 `Path(__file__).resolve().parents[2] / "references"` 推导)。两处读取点改用 `config.references_dir / "genre-profiles.md"`。
-- [ ] 测试改造:现有手搓 `.claude/references` 的测试保留(验证覆盖优先级),新增"无项目级文件时回退插件目录"的测试。
-- [ ] 提交 `fix: genre profiles resolve to plugin references by default`。
-
-### Task 21: story_system_engine base_context 必空 bug
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/story_system_engine.py:125, 412-423`
-- Test: `webnovel-writer/scripts/data_modules/tests/test_story_system_engine.py`
-
-- [ ] `_apply_reasoning` 的早退分支也为每行设置 `_priority_rank`:base_context 来源行设 0、dynamic 行设 1(保持 base 优先),保证 `build()` 的 `< 999` 过滤不再误杀。测试:用没有裁决规则行的题材跑 `build()`,断言 `master_setting.base_context` 非空。
-- [ ] 提交 `fix: base_context survives the no-reasoning path`。
-
-### Task 22: extract_chapter_context 载荷去重
-
-**Files:**
-- Modify: `webnovel-writer/scripts/extract_chapter_context.py:321-354`
-- Test: `webnovel-writer/scripts/data_modules/tests/test_extract_chapter_context.py`
-
-- [ ] 顶层 `outline`/`previous_summaries`/`state_summary` 与 `core.*` 二选一:保留 `core.*`(ContextManager 已排序),顶层字段删除;消费方(context-agent.md 中引用了哪些键先 grep 确认)同步更新。测试断言 payload 中大纲文本只出现一次。
-- [ ] 提交 `fix: dedupe chapter context payload`。
-
-### Task 23: 伏笔紧急度量纲统一
-
-**Files:**
-- Modify: `webnovel-writer/skills/webnovel-query/references/advanced/foreshadowing.md`
-
-- [ ] 公式改为 0-100 量纲:`紧急度 = min(100, (已过章节/目标章节) × 层级权重 × 33.3)` 或直接改为与 `urgency_utils` 的 high/medium/low≈100/60/20 对齐的阈值表;删除与"核心 50-300 章"矛盾的"核心 >50 章未回收即 Critical"行,改为"超过该伏笔自身 target 章节数即 Critical"。示例数值同步重算。
-- [ ] 提交 `fix(ref): foreshadowing urgency uses runtime 0-100 scale`。
-
-### Task 24: 散件正确性修复打包
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/rag_adapter.py:1477, 615-649`、`api_client.py`(9 处 print)、`knowledge_query.py:17,46`、`webnovel.py:127, 583`、`update_state.py:344-351,609-611`、`config.py:30-48,60`、`context_manager.py:790-829`
-- Test: 各对应测试文件
-
-- [ ] rag_adapter `index-chapter` 用 `adapter.config.project_root` 替代未定义 `config`;`vector_search` 行解包 `chapter` 改名 `row_chapter`。
-- [ ] api_client 全部 `[WARN]/[ERR]/[WARMUP]` 改 `file=sys.stderr`。
-- [ ] knowledge_query 连接前 `is_file()` 检查,缺失时输出含修复建议的友好错误。
-- [ ] `webnovel.py` `int(e.code or 0)` 对非 int code 打印后返回 1;`knowledge` subparsers 加 `required=True`。
-- [ ] update_state `update_strand_tracker` 失败累计并以非零退出。
-- [ ] config `.env` 值 `strip().strip("\"'")`;`_load_dotenv` 从模块导入时移到 `from_project_root` 显式调用。
-- [ ] context_manager CLI 失败路径 `sys.exit(1)`。
-- [ ] 每修一处先在对应测试文件加探针测试。完成后提交 `fix: batch correctness fixes from audit`。
-
----
-
-## Phase 2 — P2 安全与隐私
-
-### Task 25: 嵌入出网需要显式配置
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/api_client.py`(embed/rerank 调用入口)、`vector_projection_writer.py:236-246`
-- Test: `webnovel-writer/scripts/data_modules/tests/test_vector_projection_writer.py`
-- Docs: `webnovel-writer/README.md`、`docs/guides/rag-and-config.md`
-
-- [ ] `EMBED_API_KEY` 为空时 vector 投影直接返回 `{"status": "skipped", "reason": "no_api_key"}`,不发任何 HTTP 请求。测试:key 为空 + monkeypatch aiohttp 断言零请求。
-- [ ] 文档新增"数据出网说明"小节:明确写出默认端点、发送内容(摘要/场景/事件文本)、如何关闭。
-- [ ] 提交 `fix: no network egress without explicit api key`。
-
-### Task 26: 写守卫 hook 加固
-
-**Files:**
-- Modify: `webnovel-writer/hooks/guard_runtime_write.py:61-67, 101-110`
-- Test: `webnovel-writer/scripts/tests/test_hooks.py`
-
-- [ ] 正则修复:`\b(>|out-file|...)` 中 `>` 单独处理(`(?:^|\s)>{1,2}(?:\s|$)|\b(out-file|set-content|add-content|copy-item|move-item|cp|mv|rm|tee|sed|python|python3)\b`);测试用例覆盖 `echo x > .webnovel/state.json`、`mv a .webnovel/state.json`、`tee .webnovel/index.db`。
-- [ ] `_deny` 的 JSON 决策输出改打 stdout(退出码 2 保留),`systemMessage` 才能被宿主解析。
-- [ ] 提交 `fix(hooks): close redirect/unix-command bypass in write guard`。
-
-### Task 27: dashboard 最小防护
-
-**Files:**
-- Modify: `webnovel-writer/dashboard/app.py`、`server.py`
-- Test: `webnovel-writer/scripts/tests/test_dashboard_security.py`
-
-- [ ] 加 `TrustedHostMiddleware(allowed_hosts=["localhost", "127.0.0.1"])`(防 DNS rebinding);`--host` 非回环地址时打印醒目警告"整个项目将对网络可见";支持 `WEBNOVEL_DASHBOARD_TOKEN` 环境变量,设置后所有 `/api/*` 校验 `Authorization: Bearer`。测试:伪 Host 头请求返回 400;带错误 token 返回 401。
-- [ ] 顺手修 `app.py:175` `_inspect_vector_db` 连接泄漏(包 `closing()`)。
-- [ ] 提交 `fix(dashboard): trusted host + optional token auth`。
-
-### Task 28: CI 加固
-
-**Files:**
-- Modify: `.github/workflows/plugin-version.yml`、`.github/workflows/plugin-release.yml:43-51, 108`
-
-- [ ] plugin-version.yml 顶部加 `permissions: contents: read`。
-- [ ] release 的 `workflow_dispatch` version 输入加 `[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || exit 1` 前置校验;`softprops/action-gh-release@v2` pin 到具体 commit SHA;`git ls-remote` 区分查询失败(退出码非 0 且非"未找到")与 tag 不存在。
-- [ ] 注意:不要动 README 版本表相关检查(CI 硬约束)。提交 `ci: least privilege + input validation + pinned action`。
-
-### Task 29: 首次运行体验
-
-**Files:**
-- Modify: `webnovel-writer/skills/webnovel-init/SKILL.md`(Step 0 预检)、`webnovel-writer/hooks/hooks.json`
-- Test: 手动验证
-
-- [ ] init SKILL 的 Step 0 增加一行指令:先运行 `python -X utf8 "{plugin_root}/scripts/webnovel.py" doctor --format json`,存在 blocker(缺 pydantic/aiohttp 等)时向作者展示一键安装命令 `python -m pip install -r "{plugin_root}/scripts/requirements.txt"` 并等待完成再继续(doctor 已有 `python.import.*` 检查,无需新代码)。
-- [ ] hooks.json 保持 `python`,但 `session_start.py` 在 `webnovel.py` 调用失败(FileNotFoundError/非零退出)时输出一行"⚠️ Python 环境异常,运行 /webnovel-doctor 检查"——守卫 hook 的 fail-open 风险在 doctor 报告中体现(新增 check:`shutil.which("python")` 探测)。
-- [ ] 提交 `feat: dependency preflight in init + doctor python check`。
-
----
-
-## Phase 3 — P3 质量与卫生(可与 Phase 2 并行)
-
-### Task 30: 提示词重复下沉
-
-**Files:**
-- Create: `webnovel-writer/references/shared/author-report-contract.md`
-- Modify: `webnovel-writer/skills/{webnovel-init,webnovel-plan,webnovel-write,webnovel-review}/SKILL.md`、`webnovel-writer/scripts/data_modules/tests/test_prompt_integrity.py`、`webnovel-writer/references/index/reference-loading-map.md`
-
-- [ ] "作者友好过程提示与恢复契约"+"最终报告契约"两节(4 个 skill 重复约 60-70 行)抽到共享文件,各 SKILL 留一行引用 + stage 差异参数。SubagentRun JSON 模板同理只在共享文件保留一份。`test_prompt_integrity.py` 的文案断言按探针原则改为断言引用行存在。loading-map 登记新文件。SKILL 改动遵循"只写指令"原则——不留解释性注释。
-- [ ] 提交 `refactor(skill): hoist author report contract to shared reference`。
-
-### Task 31: 提示词过期内容清理
-
-**Files:**
-- Modify: `webnovel-writer/templates/genres/*.md`(34 个 XML 实体段 + 30 个 Pack 编号)、`webnovel-writer/references/index/reference-loading-map.md`、`webnovel-writer/references/genre-profiles.md`、`webnovel-writer/references/shared/core-constraints.md:6-7`、`webnovel-writer/references/review-schema.md:3`、`webnovel-writer/references/review/blocking-override-guidelines.md:3,8,39`、`webnovel-writer/skills/webnovel-write/references/style-adapter.md:9`、`webnovel-writer/skills/webnovel-write/references/anti-ai-guide.md`
-
-- [ ] 批量删 genre 模板的 `<entity .../>` 扩展段与悬空 Pack 编号行(脚本化 sed/python 批改后抽查 3 个文件)。
-- [ ] loading-map 对照 8 个 SKILL 重新核账步骤号;genre-profiles 为缺失题材补段或在 §2 头部写明 fallback 规则(命中失败 → 使用 shuangwen 基线段);§3 删除 Checkers/`project.genre` 旧引用。
-- [ ] anti-ai-guide 三方矛盾裁决:保留文件、头部"加载时机"改为"润色阶段按需",与 polish-guide 重叠词条合并,登记进 loading-map 非直接调用表。
-- [ ] 各文件头部步骤号修正(core-constraints/review-schema/blocking-override/style-adapter)。
-- [ ] 提交 `docs(ref): purge stale protocol fragments and fix loading map drift`。
-
-### Task 32: CLI 样板抽取
-
-**Files:**
-- Create: `webnovel-writer/scripts/data_modules/cli_runtime.py`
-- Modify: `entity_linker.py`、`index_manager.py`、`rag_adapter.py`、`state_manager.py`、`sql_state_manager.py`、`style_sampler.py` 各 `main()`,`webnovel.py:306-381`
-
-- [ ] 提供 `resolve_config(args) -> DataModulesConfig`(封装 normalize_global_project_root + resolve_project_root + from_project_root)与 `run_cli(fn)` 装饰器(统一 emit_success/emit_error + 退出码)。六个入口迁移;`webnovel.py` 的 run-ledger/run-log 复抄段改为转发。现有 CLI 测试全量回归。
-- [ ] 提交 `refactor: extract cli_runtime, dedupe six entrypoints`。
-
-### Task 33: 性能修复
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/rag_adapter.py:583-650, 721-758`、`webnovel-writer/scripts/status_reporter.py:370-460`
-
-- [ ] vector 直连路径复用 hybrid 的 `vector_full_scan_max_vectors` 预筛;bm25 命中 chunk 改 `WHERE chunk_id IN (...)` 一次取回;status_reporter 开头一次性 `SELECT * FROM entities/chapters` 建 dict,删除循环内单查。用现有测试回归 + 千章模拟数据手测对比耗时(可选)。
-- [ ] 提交 `perf: kill N+1 queries in rag and status reporter`。
-
-### Task 34: 仓库与测试卫生
-
-**Files:**
-- Modify: `webnovel-writer/scripts/data_modules/tests/test_story_system_engine.py`(或其 fixture)、`sitecustomize.py`、`.github/workflows/plugin-version.yml`、根 `requirements.txt` 三份、`webnovel-writer/scripts/update_state.py:159-178`、`webnovel-writer/scripts/data_modules/summary_projection_writer.py:20`
-
-- [ ] 找到向仓库根写 `.tmp_story_system_engine/case_*` 的测试,改用 pytest `tmp_path`;删除根目录现存 231 个残留目录。
-- [ ] `sitecustomize.py`:移出仓库(或改名 `sitecustomize.py.example` + README 说明),避免影响分发用户。
-- [ ] CI 增加 dist 同步校验 step:`npm ci && npm run build` 后 `git diff --exit-code webnovel-writer/dashboard/frontend/dist`(或对比构建哈希),防止前端源码与 dist 漂移。
-- [ ] requirements 关键依赖加上界(`fastapi>=0.110,<1`、`pydantic>=2,<3` 等);确认 dashboard 的 httpx 是否仅测试用,是则移到测试依赖。
-- [ ] update_state 备份文件按数量滚动清理(保留最近 20 个);summary 投影写改 tmp+replace。
-- [ ] 提交 `chore: test/repo hygiene batch`。
-
----
-
-## 验收清单(整体)
-
-- [ ] `python -m pytest`(PYTHONUTF8=1)全绿,覆盖率 ≥90% 不回退
-- [ ] 手动冒烟:新建 tmp 项目跑 init → 写一章 → review → chapter-commit → projections retry → dashboard 启动
-- [ ] `git grep -n "checkout master"` 在 scripts 下零命中
-- [ ] 无 key 环境跑一次 chapter-commit,抓包/日志确认零出网请求
-- [ ] README 版本表未被改动(CI 硬约束)

+ 0 - 220
docs/superpowers/specs/2026-06-07-author-friendly-experience-design.md

@@ -1,220 +0,0 @@
-# 作者友好体验层设计:工程内核 + 作者外壳
-
-- 日期:2026-06-07
-- 状态:草案 v2 · 双向交叉验证后合并
-- 目标作者画像:**已在用 Claude Code、但非程序员的中文网文作者** —— 会敲 `/webnovel-write`,但被术语黑话、出错信息、长流程劝退
-- 选定方向:方向 C「面向作者重构」,以方向 B「作者呈现层」为地基分期落地
-- 定位与分工:本 spec 是**产品分层规格**(分层 / 组件职责 / 边界 / 红线);配套的 `docs/architecture/author-friendly-reporting-plan-2026-06-07.md`(codex plan)是**工程落地计划**(Phase / 文件 / 测试 / 施工顺序)。两份互补、配套使用,非二选一。
-
----
-
-## 1. 背景与问题
-
-`webnovel-writer` 有一条严谨的工程事实链:`Story System → 闸门(write-gate) → CHAPTER_COMMIT → 投影(projection)`,外加 RAG 与长期记忆。这条链保证「写到几百章仍不写崩」的一致性,是产品的核心价值。
-
-问题在于:**这条链把工程语言、工程过程、工程错误,原样喷给了写作者。** 作者实际接触的不是「设计精良的写作工具」,而是 `preflight`、`write-gate prewrite/precommit/postcommit`、`projection_status`、`CHAPTER_COMMIT accepted/rejected`、`mainline_ready=false` 这类面向开发者的输出。
-
-用户在四类痛点上明确表达了改善意愿(四项全选):
-
-| 痛点 | 作者的真实体验 |
-|------|----------------|
-| 进度像黑箱 | 写一章时 Claude 刷一屏命令和 JSON,不知道在干嘛、是否正常、还要多久 |
-| 出错看不懂没法救 | 报错讲技术原因,不告诉作者「现在该敲哪条命令 / 改哪个文件」 |
-| 审查报告不好用 | 每章报告偏技术、偏长,难快速抓住「这章行不行、要不要改、改哪」 |
-| 术语劝退 | 合同 / 提交 / 投影 / 闸门 / commit accepted 等工程词贯穿全程,作者无对应心智模型,产生距离感 |
-
-## 2. 核心洞察
-
-这**不是四个独立问题,是同一个缺口的四个切面**:
-
-> 系统缺少一层**「作者界面层」** —— 把工程事实翻译成作者语言、把工程过程压缩成作者关心的进度、把工程错误映射成作者能执行的下一步、把审查产物渲染成作者能决策的结论。
-
-| 痛点 | 本质 | 该层补什么 |
-|------|------|------------|
-| 进度像黑箱 | 过程语言没翻译 | 把多步流水线压成作者里程碑 |
-| 出错没法救 | 错误没翻译 + 没给出路 | 错误码 → 人话 + 下一步行动;可重跑续跑 |
-| 审查报告不好用 | 结果没翻译 + 不可执行 | 一句结论 + 最多 3 条「怎么改」,技术细节按需展开 |
-| 术语劝退 | 概念没翻译 | 统一术语对照 |
-
-结论:方案的核心是**在工程链与作者之间补一层翻译/呈现,工程事实源一个字不动**。工程严谨不牺牲,作者那一侧完全换一张脸。
-
-## 3. 目标与非目标
-
-**目标**
-- 让目标作者在日常使用中,默认不直接接触工程语言、工程过程、工程错误。
-- 出错时作者总能拿到「人话 + 下一步该做什么」,且能靠重跑同一命令自动续跑。
-- 审查结论可在三秒内被作者判读并据以决策。
-- 全程术语统一、可理解。
-
-**非目标(YAGNI)**
-- 不改写工程内核(Story System / 闸门 / commit / 投影 / RAG)的任何逻辑或数据结构。
-- 不重命名现有 `/webnovel-*` 命令,不破坏现有用户习惯与既有文档。
-- 不为非技术作者降低一致性校验强度。
-- **不引入任何新 UI**(无折叠面板、无进度条、无按钮)。作者外壳只落到文本提示、Skill 规范、Python helper、日志文件。
-- 不做「自动修复大循环」(见 §8)。
-
-## 4. 约束与红线
-
-两条贯穿全程的原则:
-
-1. **对作者隐去 ≠ 放松校验,也 ≠ 静默。** 所有 gate 照常跑、照常拦,事实链正确性零妥协。系统自动处理过的事(如投影补跑)**必须在最终报告说明**,绝不静默;区别只在「默认不主动展示工程细节、不为过程性小事打断作者」。
-2. **C 以 B 为地基,分三期落地。** 每期独立交付价值、风险递增、可随时叫停,避免一次性大爆炸。
-
-> 关于「Claude Code 能力边界」:本设计不假设宿主有折叠 UI、进度条、后台任务或图形按钮。所有「隐去工程细节」都通过 **Skill 约束少输出 + Python helper 渲染精简报告** 实现,不是任何界面级折叠。
-
-## 5. 架构:工程内核 + 作者外壳
-
-三层。作者只跟最上层打交道,工程内核退到幕后。作者外壳**不是新程序/新界面**,而是 Skill 提示规范 + Python helper + 文本输出 + 日志这四样东西的合称。
-
-```
-┌─────────────────────────────────────────────┐
-│  作者外壳 (Author Shell)   ← 物理载体:Skill规范+helper+文本+日志 │
-│  · 导航尾巴:"接下来你可以…"                       │
-│  · 命令任务化(仅提示语言,不改命令名)               │
-│  · 可自动处理项:不打扰作者,但最终报告必说明          │
-│  · 工程细节默认不展示:作者看里程碑+结论,需要时给路径/命令 │
-├─────────────────────────────────────────────┤
-│  呈现/翻译层 (Author Layer)  ← 地基,零侵入          │
-│  · 进度播报规范  · 错误→行动映射表(catalog)        │
-│  · 审查作者视图  · 术语对照表                      │
-├─────────────────────────────────────────────┤
-│  工程内核 (Engine)  ← 一个字不动                   │
-│  Story System · 闸门 · commit · 投影 · RAG        │
-└─────────────────────────────────────────────┘
-```
-
-## 6. 组件设计
-
-七个职责单一、可独立交付的组件,按期归位。每个组件都明确了在 codex plan 里的工程落点。
-
-### 6.1 术语对照表(Glossary)· 第一期
-- **职责**:工程词 → 作者词 + 一句话作者解释的单一事实源;其余组件都引用它。
-- **消费**:无(自身是词库)。
-- **产出**:受控词汇表,例:合同→设定档案、提交/commit→入账存档、投影/projection→同步到各处、闸门/gate→自检关卡、`mainline_ready`→这本书的档案是否就绪。
-- **依赖**:无。是其他三件的底座。
-- **落地形态**:单一事实源文件(`references/author_glossary.md` 或结构化 `author_glossary.json`),SKILL prompt 与脚本输出统一引用,禁止各处自造译法。codex plan §5 已给出初始译表,并入本组件。
-
-### 6.2 进度播报规范(Progress Narration)· 第一期
-- **职责**:把写章流水线压成作者里程碑并定义措辞模板。
-- **消费**:写章流程当前所处步骤。
-- **产出**:作者只看见「查前情 → 写正文 → 体检 → 入账存档 → 完成」。
-- **依赖**:术语对照表。
-- **落地形态**:写入各多步命令的播报约定,规定每个里程碑何时报、报什么。**默认不主动展示工程命令与 JSON**(靠 Skill 约束少输出 + helper 渲染精简,非 UI 折叠);**失败信息必须浮出**。对应 codex plan §8。
-
-### 6.3 错误→行动映射表(Error Catalog)· 第一期
-- **职责**:把已知错误映射为「人话描述 + 下一步行动 + 严重度」。本方案价值最高的组件(codex 评审认同应纳入)。
-- **消费**:`preflight` / `write-gate` / `doctor` 等产出的错误码或特征。
-- **产出**:例 `mainline_ready=false` → 「这本书还没建档,先运行 `/webnovel-init`」。
-- **依赖**:术语对照表。
-- **落地形态**:`error_catalog.py` + `author_error_catalog.json`;未命中条目时降级到「诚实报错 + 指向 `/webnovel-doctor`」,绝不崩溃或乱翻译。
-
-### 6.4 审查作者视图(Review Author View)· 第一期
-- **职责**:在现有审查报告之上渲染作者视图。
-- **消费**:`review-pipeline` 产出的 review_result(含 `blocking_count` 等)。
-- **产出**:报告顶部一句结论(✅过 / ⚠️建议改 / ⛔必须改)+ 最多 3 条「怎么改」可执行项,技术细节折在下面(按需展开,不是 UI 折叠,是排版分段)。
-- **依赖**:术语对照表;现有 review 产物结构。
-- **落地形态**:`review_pipeline.py` 报告渲染环节新增作者视图段,不改 reviewer schema、不改判定逻辑。
-
-### 6.5 导航尾巴(Next-Step Advisor)· 第二期
-- **职责**:回答「我现在该干嘛」。**决策:嵌进各命令收尾**,不新增独立入口命令(codex 评审认同此判断)。
-- **消费**:`project-status` 输出的 phase 与下一步。
-- **产出**:每条命令跑完追加统一格式提示,例「接下来可以**写下一章** `/webnovel-write 5`」。天然寄生在 codex plan 三段式报告的第三段「下一步建议」。
-- **依赖**:术语对照表;`project-status`。
-- **落地形态**:作为 `user_report.py` 的一部分,被各命令收尾调用,输出统一格式 + 可复制命令。
-
-### 6.6 命令任务化(Task Language)· 第二期
-- **职责**:用作者任务语言表达动作,降低「记 8 个命令名」的负担。
-- **消费**:导航尾巴的下一步建议。
-- **产出**:提示中以任务语言呈现(开新书 / 写下一章 / 看看这章怎么样 / 查个设定),并附原命令名。
-- **依赖**:导航尾巴;术语对照表。
-- **落地形态**:仅提示层映射,**保留 `/webnovel-*` 原命令名不动**,不做真实别名命令。
-
-### 6.7 可自动处理项 + 工程细节默认不展示(Auto-handled & Quiet)· 第三期
-- **职责**:保留系统已有的有限自动处理;默认不为过程性小事打扰作者;工程细节默认不主动展示。
-- **消费**:错误目录的分类;可重试的过程性错误信号。
-- **产出**:投影失败→自动 retry、合同缺失→重新 emit 等**继续保留**,但**过程不中断作者 + 最终报告必须说明处理了什么 + 细节写入日志**;默认只看里程碑与结论,需要时给路径/命令。
-- **依赖**:错误目录;进度播报规范;最终报告。
-- **落地形态**:在错误目录的「可自动处理」类目上挂动作,受 §8 边界约束。**不做自动修复大循环**;真正的「出错恢复」靠作者重跑 → 幂等续跑(见 §8,工程实现引 codex plan §9)。
-
-## 7. 数据流
-
-翻译层是**只读消费 + 重新渲染,绝不插进工程链的写路径**。
-
-```
-工程链照常产出事实产物
-(state.json / review_result.json / gate JSON / 错误码 / projection_log.jsonl)
-        │  (只读消费)
-        ▼
-翻译层  查 术语表 / 错误目录 / 审查视图规范  → 渲染成作者语言
-        │
-        ▼
-作者看到人话;他选择的"下一步"最终仍调用原工程命令
-```
-
-**事实源永远是工程产物,不是翻译产物。** 翻译错了改翻译层即可,底层数据从没动过 —— 这是「隐去但不放松校验」的技术保证,也使翻译层可被独立测试与替换。
-
-## 8. 错误处理与恢复边界
-
-错误分三类区别对待,对应 codex plan 的异常分类(已自动处理 / 建议确认 / 必须处理):
-
-1. **可自动处理(不打扰作者,但必须说明)** —— 仅限幂等、可重试、不碰作者内容的过程性错误(投影失败→retry、合同缺失→重 emit)。过程中不打断作者,**最终报告如实说明处理了什么、是否影响结果**,细节写日志。**绝不静默。**
-2. **请作者决策(浮出 + 给有限选项)** —— 涉及内容取舍或不可逆(审查 blocking 改不动、commit 因 `missed_nodes` 被 rejected)→ 人话说清 + 2-3 个有限选项(接受 / 手改 / 放弃)。
-3. **诚实报错 + 求助路径** —— 环境依赖级(RAG key 没配、Python 依赖缺失)→ 说人话 + 指向 `/webnovel-doctor`。
-
-**出错恢复 = 重跑即续跑(吸收 codex plan §9)。** 作者不需记补跑命令;重跑同一条命令,系统据「可信完成判据」识别已完成步骤,从失败点续,不重写已有成果。首版只覆盖 `/webnovel-write`。
-
-**重跑必须停下问作者的边界(codex §9.5,完全认同):**
-- 正文被手动改过 → 问:沿用还是重写(**绝不覆盖作者手改**)。
-- 章纲更新晚于正文 → 问:旧正文还是重起草。
-- 同章已 accepted 又重跑 → 问:重写还是只看状态。
-
-**红线**:绝不自动跳过校验、绝不把 rejected 伪装成 accepted、绝不吞掉影响事实正确性的错误、绝不静默修复(修了就要说)。**不做自动修复大循环**(自愈白名单极易膨胀成新故障点,风险/收益比最差,两边独立评审都否决)。
-
-## 9. 验证策略
-
-测行为、不测文案字面(与项目「测试是探针不是约束」取向一致):
-
-- **忠实性探针**:同一工程产物,翻译输出必须保住关键事实(过/不过、缺哪些节点、blocking 数),不得篡改或丢失。
-- **错误目录覆盖**:未知错误码自动降级到「诚实报错」,不崩、不乱翻译。
-- **不静默探针**:自动处理过的事(如投影补跑)必须出现在最终报告,不得被吞掉。
-- **续跑边界探针**:正文被手改 / 章纲更新晚于正文 / 已 accepted 等场景,重跑必须停下问作者,不得擅自覆盖。
-
-## 10. 分期落地路线
-
-| 期 | 范围 | 交付物 | 退出条件 |
-|----|------|--------|----------|
-| 一(地基) | 翻译层四件套 | 术语对照表、进度播报规范、错误目录、审查作者视图 | 四件套上线且通过忠实性/覆盖探针;零侵入工程内核 |
-| 二(外壳上半) | 导航尾巴 + 命令任务化 | 各命令收尾统一「下一步」提示、任务语言映射 | 每条命令收尾给出正确下一步;原命令名不变 |
-| 三(外壳下半) | 可自动处理项说明 + 工程细节默认不展示 + 重跑续跑 | 自动处理项写进报告、默认精简呈现、`/webnovel-write` 重跑续跑 | 不静默/续跑边界探针全过;默认视图只见里程碑与结论 |
-
-每期独立交付、可随时叫停。该排序与 codex plan 的优先级一致(见 §11)。
-
-## 11. 双向交叉验证结论与合并方案
-
-**更正一处自我推测**:本 spec v1 第 11 节曾推测 codex plan「聚焦 reporting、范围更窄」。经读其全文,此推测**错误** —— codex plan 范围很广,断点续跑、subagent 返回协议、耗时呈现、脱敏日志均已覆盖,在这些面上比本 spec 更全、更可落地。
-
-**双向交叉验证(两份独立产出 + 互审)达成的共识:**
-
-- 一致点:工程内核不动、只做呈现层;术语翻译/过程提示/错误可理解/下一步建议是核心;问题不静默、不把 rejected 伪装 accepted;runtime/结构化数据驱动优先于纯 prompt;作者默认不看工程细节但保留排查日志。
-- 本 spec 更强:Error Catalog、Review Author View、Next-Step Advisor(嵌收尾)、命令任务化、分期更克制 —— codex 评审认同,建议纳入其 plan。
-- codex plan 更强(本 spec 已吸收):统一最终报告三段式 + 四状态、断点续跑/run ledger、subagent 返回协议、耗时呈现、脱敏日志、Phase 0-7 可施工细度。
-- codex 指出并已在本 v2 修正的本 spec 风险:①「折叠」改为「默认不主动展示工程细节」(CC 无折叠 UI);②「静默自愈」改为「可自动处理但必须说明」;③「作者外壳」钉死物理载体为 Skill 规范 + helper + 文本 + 日志,杜绝范围膨胀。
-
-**合并分工**:本 spec 定**产品分层**(分层 / 组件职责 / 边界 / 红线),codex plan 定**工程落地**(Phase / 文件 / 测试 / 施工顺序)。本 spec 七组件并入 codex plan 的落点见各组件「落地形态」。
-
-**合并后实施优先级(与 codex 评审对齐):**
-1. 术语表单一事实源
-2. Error Catalog
-3. Review Author View
-4. Next-Step Advisor
-5. 过程提示规范
-6. `user_report` helper
-7. 断点续跑 / run ledger
-8. 可自动处理白名单(最克制,最后做)
-
-## 12. 风险与开放问题
-
-- **风险:翻译层与工程内核漂移。** 工程新增错误码/产物字段时,错误目录与作者视图需同步;靠「未命中即诚实降级」兜底,但需把同步纳入发布校验。
-- **风险:进度精简掩盖真实卡顿。** 默认不主动展示工程细节后,须保证失败信号一定浮出、自动处理一定写报告(由 §9 不静默探针保证)。
-- **开放问题:术语对照表的载体**(纯 markdown 规范 vs 结构化数据 + 校验)—— 影响可测试性,留待实现计划定夺。
-- **开放问题:可自动处理白名单具体含哪些错误码** —— 最低优先级,进入第三期前基于错误目录真实条目逐条评定,宁缺毋滥。