W13 旧用例承接补充:作者层路由与中文方法 Skill 的已跟踪文件改动

作者场景路由与指令、完善故事基础设定/合并故事方案/制定作品规划的技能中文化重构及其脚本、对应旧测试修改、SOT 导读、技能目录与实现回顾。此为 W13 阶段对已跟踪文件的改动,随批次按阶段整理补提交。
This commit is contained in:
zizi 2026-09-10 19:39:47 +08:00
parent f7a57f68e6
commit 7f9114eca5
23 changed files with 304 additions and 663 deletions

View File

@ -16,5 +16,5 @@
## 交付与交接 ## 交付与交接
- 先给正文候选,再给必要且简短的检查状态和 `采用`、`修改`、`丢弃` 动作。 - 先给正文候选,再给必要且简短的检查状态和 `采用`、`修改`、`丢弃` 动作。
- 候选始终是 Shadow。作者明确 `采用` 或 `丢弃` 后,才交给 [决定正文候选去留](../../../muse/content/work/skills/sovereignty/决定正文候选去留/SKILL.md);`修改` 进入 [改正文](改正文.md)。 - 候选始终是 Shadow。作者明确 `采用` 或 `丢弃` 后,才交给 [决定正文候选去留](../../skills/操作/决定正文候选去留/SKILL.md);`修改` 进入 [改正文](改正文.md)。
- 作者要求“只给正文”时,只交付正文,不附内部分析、路由或检查清单。 - 作者要求“只给正文”时,只交付正文,不附内部分析、路由或检查清单。

View File

@ -2,18 +2,16 @@
## 进入条件 ## 进入条件
作者只有点子、素材、题材方向或多份未合并方案,还没有确认能支撑长篇的故事主心骨。先读取现有作品信息和本会话决定;只有缺口会改变候选方向时,才问一个关键问题。 作者想从点子、片段、人物、关系、题材方向或已有设计继续构思作品。先读取当前作品、相关草稿与本会话决定;已有明确内容直接沿用,不要求先确定全书中心。
## 正式技能 ## 正式技能
- 点子还只是陈述时,用 [提炼故事概念](../../skills/规划/提炼故事概念/SKILL.md) 找到能持续制造冲突的核心疑问。 正式入口是 [完善故事基础设定](../../skills/操作/完善故事基础设定/SKILL.md):私人探索稿的保存、读回、最终格式检查与明确选定都走新版作者入口,不把探索过程写成正式内容。素材、联系、中心、结构可以往返发展;不把概念四问、九章完整度或候选数量作为开始探索的条件。
- 需要形成可比较的完整前期方案时,用 [完善故事基础设定](../../../muse/lifecycle/flow/skills/book/完善故事基础设定/SKILL.md)。
- 作者已经有多份完整方案并明确要求综合时,才用 [合并故事方案](../../../muse/lifecycle/flow/skills/book/合并故事方案/SKILL.md)。
一次只设一个主入口。方法技能可以辅助判断,不能代替规划角色产出候选。 主会话负责对话与派发,规划角色发展内容。
## 交付与交接 ## 交付与交接
- 先给可比较的故事候选和关键差异,再说明哪些内容使用了假设。 - 每轮先给本次发展的内容,只追问最影响继续的一项;日常探索不套整书模板、不运行代码校验。
- 不替作者选故事主心骨,不把候选当作已确认规划。 - 日常探索稿由新版入口保存;整理交付时才检查最终格式。方法子阶段在方法技能迁移前可参考旧版 [完善故事基础设定](../../../muse/lifecycle/flow/skills/book/完善故事基础设定/SKILL.md) 主文档;方法参考不改变新版入口的存储与选定合同。
- 作者选择 `采用` 后,带着被选方案进入 [排故事](排故事.md);选择 `修改` 时只改点名部分;选择 `丢弃` 时不保留为当前方向。 - 作者采纳一个点子只更新该处。作者明确选择当前方向并要求正式规划时,按主文档的「整理交付」进入 [排故事](排故事.md);具体 setting 仍需另一次确认。

View File

@ -8,9 +8,10 @@
- 全书或分卷规划的主入口是 [制定作品规划](../../../muse/lifecycle/flow/skills/book/制定作品规划/SKILL.md)。按实际短板选用 [搭建故事蓝图](../../skills/规划/搭建故事蓝图/SKILL.md)、[搭建故事结构](../../skills/规划/搭建故事结构/SKILL.md)、[增强叙事动力](../../skills/规划/增强叙事动力/SKILL.md)、[铺设并回收伏笔](../../skills/规划/铺设并回收伏笔/SKILL.md) 或 [设计故事结尾](../../skills/规划/设计故事结尾/SKILL.md),不把五种方法一次全塞进任务。 - 全书或分卷规划的主入口是 [制定作品规划](../../../muse/lifecycle/flow/skills/book/制定作品规划/SKILL.md)。按实际短板选用 [搭建故事蓝图](../../skills/规划/搭建故事蓝图/SKILL.md)、[搭建故事结构](../../skills/规划/搭建故事结构/SKILL.md)、[增强叙事动力](../../skills/规划/增强叙事动力/SKILL.md)、[铺设并回收伏笔](../../skills/规划/铺设并回收伏笔/SKILL.md) 或 [设计故事结尾](../../skills/规划/设计故事结尾/SKILL.md),不把五种方法一次全塞进任务。
- 下一章细纲的主入口是 [规划下一章](../../../muse/lifecycle/flow/skills/chapter/规划下一章/SKILL.md)。书级规划和章级细纲不能由同一次调用混写。 - 下一章细纲的主入口是 [规划下一章](../../../muse/lifecycle/flow/skills/chapter/规划下一章/SKILL.md)。书级规划和章级细纲不能由同一次调用混写。
- 新版规划候选的读取、编辑、审阅与作者明确决定走 [确认规划候选](../../skills/操作/确认规划候选/SKILL.md);旧入口只维护旧库记录。
## 交付与交接 ## 交付与交接
- 交付可执行的规划草稿,明确已确认事实、待作者决定项和必要假设,不用正文段落充当规划。 - 交付可执行的规划草稿,明确已确认事实、待作者决定项和必要假设,不用正文段落充当规划。
- 规划先保持 Shadow;作者明确确认后,才能成为下层规划或正文写作依据。 - 规划先保持 Shadow;作者明确确认后,才能成为下层规划或正文写作依据。确认经 [确认规划候选](../../skills/操作/确认规划候选/SKILL.md) 执行,主会话不代替作者采纳。
- 单章细纲齐备并被确认后,交给 [写下一章](写下一章.md)。 - 单章细纲齐备并被确认后,交给 [写下一章](写下一章.md)。

View File

@ -20,5 +20,5 @@
## 交付与交接 ## 交付与交接
- 只处理作者指定范围,范围外正文保持原样;不顺手改规划、设定或其它段落。 - 只处理作者指定范围,范围外正文保持原样;不顺手改规划、设定或其它段落。
- 修改结果仍是 Shadow 候选。作者选择 `采用` 或 `丢弃` 后,交给 [决定正文候选去留](../../../muse/content/work/skills/sovereignty/决定正文候选去留/SKILL.md);继续修改时仍留在本场景。 - 修改结果仍是 Shadow 候选。作者选择 `采用` 或 `丢弃` 后,交给 [决定正文候选去留](../../skills/操作/决定正文候选去留/SKILL.md);继续修改时仍留在本场景。
- 作者要求“只给正文”时,只交付修改后的正文。 - 作者要求“只给正文”时,只交付修改后的正文。

View File

@ -1,6 +1,6 @@
| 名称 | 相对地址 | 内容描述 | 使用场景 | 使用要求 | | 名称 | 相对地址 | 内容描述 | 使用场景 | 使用要求 |
|------|----------|----------|----------|----------| |------|----------|----------|----------|----------|
| 定故事 | [定故事.md](定故事.md) | 把点子、素材或模糊方向变成可比较的长篇方案 | 作者还没有确认故事主心骨时 | 只交候选,不替作者选定方向 | | 定故事 | [定故事.md](定故事.md) | 通过流程主文档和子阶段提示词逐步发展素材、中心与结构 | 作者想从点子、片段或已有设计继续构思时 | 按需往返探索,整理交付时才校验格式,保留方向与正式设定两次确认 |
| 排故事 | [排故事.md](排故事.md) | 处理全书、分卷、结构、节拍、伏笔、结尾和单章细纲 | 故事方向已明确,需要可执行规划时 | 规划保持草稿态,确认后才能供正文使用 | | 排故事 | [排故事.md](排故事.md) | 处理全书、分卷、结构、节拍、伏笔、结尾和单章细纲 | 故事方向已明确,需要可执行规划时 | 规划保持草稿态,确认后才能供正文使用 |
| 塑人物 | [塑人物.md](塑人物.md) | 处理人物动机、关系、声音与正文呈现 | 作者要创建或补强人物时 | 不替作者决定人物命运、主题立场或重大关系转折 | | 塑人物 | [塑人物.md](塑人物.md) | 处理人物动机、关系、声音与正文呈现 | 作者要创建或补强人物时 | 不替作者决定人物命运、主题立场或重大关系转折 |
| 写下一章 | [写下一章.md](写下一章.md) | 按正式事实和已确认细纲生成整章正文候选 | 目标章写作输入齐备时 | 同时读取原生中文写作合同;候选不得直接入正典 | | 写下一章 | [写下一章.md](写下一章.md) | 按正式事实和已确认细纲生成整章正文候选 | 目标章写作输入齐备时 | 同时读取原生中文写作合同;候选不得直接入正典 |

View File

@ -5,7 +5,7 @@
## 进入顺序 ## 进入顺序
1. 先从当前作品、已确认规划和本会话决策中取事实,不让作者重复提供已经能查到的信息。 1. 先从当前作品、已确认规划和本会话决策中取事实,不让作者重复提供已经能查到的信息。
2. 根据作者这轮的主要结果选择一个场景。一个请求横跨多步时,先完成最前面的阻断步骤,再明确交接下一步。 2. 根据作者这轮的主要结果选择一个场景。正式产物按已有依赖交接;定故事的探索过程按主文档选用子阶段,可以往返,不按代码状态逐步通关。
3. 路由确定后,只读命中的场景文件。生成或修改正文时,同时读 [原生中文写作合同](原生中文写作.md)。 3. 路由确定后,只读命中的场景文件。生成或修改正文时,同时读 [原生中文写作合同](原生中文写作.md)。
4. 在方法目录或编排目录中读取场景指定的正式技能,再按功能链派发合同指定角色。作者层和主会话不代替角色创作、检测或评分;正式技能名只用于内部执行,普通作者回复不列技能清单。 4. 在方法目录或编排目录中读取场景指定的正式技能,再按功能链派发合同指定角色。作者层和主会话不代替角色创作、检测或评分;正式技能名只用于内部执行,普通作者回复不列技能清单。
5. 先交付作者要的内容,再用一句话说明候选状态或下一项决定。 5. 先交付作者要的内容,再用一句话说明候选状态或下一项决定。
@ -14,7 +14,7 @@
| 作者常见说法 | 进入场景 | 本轮目标 | | 作者常见说法 | 进入场景 | 本轮目标 |
|---|---|---| |---|---|---|
| 我只有一个点子 | [定故事](场景/定故事.md) | 把模糊方向变成可比较的故事候选 | | 我只有一个点子 | [定故事](场景/定故事.md) | 从已有素材逐步发展作品,按需试探中心与结构 |
| 帮我排一下整本书 | [排故事](场景/排故事.md) | 做全书、分卷、结构或下一章规划 | | 帮我排一下整本书 | [排故事](场景/排故事.md) | 做全书、分卷、结构或下一章规划 |
| 这个人物还站不住 | [塑人物](场景/塑人物.md) | 补人物动机、关系、声音或呈现方式 | | 这个人物还站不住 | [塑人物](场景/塑人物.md) | 补人物动机、关系、声音或呈现方式 |
| 接着写下一章 | [写下一章](场景/写下一章.md) | 根据当前正式事实与已确认细纲生成整章候选 | | 接着写下一章 | [写下一章](场景/写下一章.md) | 根据当前正式事实与已确认细纲生成整章候选 |
@ -27,11 +27,11 @@
- 缺少会改变结果的决定时,一轮只问一个关键问题;非关键缺口使用明确假设,并在交付后指出。 - 缺少会改变结果的决定时,一轮只问一个关键问题;非关键缺口使用明确假设,并在交付后指出。
- 普通回复不展示运行编号、哈希、模型参数、数据库字段、内部英文标识或门禁实现。 - 普通回复不展示运行编号、哈希、模型参数、数据库字段、内部英文标识或门禁实现。
- 作者没有明确确认时,正文、规划和知识产物保持 Shadow,不进入 Canonical。 - 作者没有明确确认时,正式正文、规划和知识候选保持 Shadow,不进入 Canonical;定故事探索稿是本地草稿,尚不属于 Shadow。
- 候选内容统一给出 `采用`、`修改`、`丢弃` 三个动作。具体写入由正式主权技能执行,路由层不写库。 - 正式候选给出 `采用`、`修改`、`丢弃` 三个动作。探索中的自然反馈只作用于作者点名的内容,不把局部采纳升级为方向或正式规划确认。具体写入由正式主权技能执行,路由层不写库。
- 诊断请求默认只查不改;修改请求只处理作者授权的范围。 - 诊断请求默认只查不改;修改请求只处理作者授权的范围。
- 作者说“只给正文”时,只给正文,不附分析、路由、检查清单或状态说明。 - 作者说“只给正文”时,只给正文,不附分析、路由、检查清单或状态说明。
## 最小作者旅程 ## 最小作者旅程
自然中文点子先进入 [定故事](场景/定故事.md),方向确认后进入 [排故事](场景/排故事.md),细纲确认后进入 [写下一章](场景/写下一章.md)。正文候选可先由 [查作品](场景/查作品.md) 定位问题,再由 [改正文](场景/改正文.md) 处理指定范围。作者最终选择 `采用` 或 `丢弃` 时,交给 [决定正文候选去留](../../muse/content/work/skills/sovereignty/决定正文候选去留/SKILL.md) 执行;选择 `修改` 时仍留在 Shadow。 自然中文点子先进入 [定故事](场景/定故事.md),方向确认后进入 [排故事](场景/排故事.md),细纲确认后进入 [写下一章](场景/写下一章.md)。正文候选可先由 [查作品](场景/查作品.md) 定位问题,再由 [改正文](场景/改正文.md) 处理指定范围。作者最终选择 `采用` 或 `丢弃` 时,交给 [决定正文候选去留](../skills/操作/决定正文候选去留/SKILL.md) 执行;选择 `修改` 时仍留在 Shadow。

View File

@ -1,5 +1,5 @@
| 名称 | 相对地址 | 内容描述 | 使用场景 | 使用要求 | | 名称 | 相对地址 | 内容描述 | 使用场景 | 使用要求 |
|------|----------|----------|----------|----------| |------|----------|----------|----------|----------|
| 作者路由指令 | [指令.md](指令.md) | 把作者自然中文请求分到一个创作场景,并约束提问、结果说明和决策动作 | 收到作者创作请求时 | 先读本文件;一次只进入一个主场景,不向作者展示内部路由过程 | | 作者路由指令 | [指令.md](指令.md) | 把自然中文请求分到创作场景,区分自由探索、局部采纳与正式确认 | 收到作者创作请求时 | 先读本文件;一次只进入一个主场景,不向作者展示内部路由过程 |
| 原生中文写作合同 | [原生中文写作.md](原生中文写作.md) | 生成和修改正文共同遵守的中文语序、声音、事实与范围边界 | 进入写下一章或改正文场景时 | 与命中的场景文件一并读取,不替代具体技能合同 | | 原生中文写作合同 | [原生中文写作.md](原生中文写作.md) | 生成和修改正文共同遵守的中文语序、声音、事实与范围边界 | 进入写下一章或改正文场景时 | 与命中的场景文件一并读取,不替代具体技能合同 |
| 作者场景 | [目录.md](场景/目录.md) | 定故事、排故事、塑人物、写下一章、改正文和查作品六个场景入口 | 路由指令已识别作者主意图后 | 只打开命中的场景;场景只负责分流和交接 | | 作者场景 | [目录.md](场景/目录.md) | 含提示词驱动的定故事,以及排故事、塑人物、续写、修改和诊断入口 | 路由指令已识别作者主意图后 | 只打开命中的场景;场景只负责分流和交接 |

View File

@ -42,6 +42,8 @@ PostgreSQL `muse-example` 的 `example_skill` 使用单事务原位更新,没
角色合同仍为 `muse-role-v4 / fixed-opus-v1`:writer、planner、judge 固定 Opus,extractor、detector 只有在 profile 明确登记时可走内容治理链。M3 测试产生的持久化角色策略、默认配置和本机 provider 已撤回;以后测试 M3 只通过单次命令或测试夹具显式传入。仓内五章授权快照也已过期,完整回放仍须先刷新固定 Opus successful probe,再由 `准备正文回放数据` 装配新授权配置。 角色合同仍为 `muse-role-v4 / fixed-opus-v1`:writer、planner、judge 固定 Opus,extractor、detector 只有在 profile 明确登记时可走内容治理链。M3 测试产生的持久化角色策略、默认配置和本机 provider 已撤回;以后测试 M3 只通过单次命令或测试夹具显式传入。仓内五章授权快照也已过期,完整回放仍须先刷新固定 Opus successful probe,再由 `准备正文回放数据` 装配新授权配置。
撤回后按不放行外部依赖的完整离线清单复验:128 个登记入口中 119 个通过,9 个因 PostgreSQL、模型或凭据依赖明确阻断,实际失败 0;未再次调用 M3 或 Opus。
## 5. 清理边界 ## 5. 清理边界
本回顾、`chinese_only`、永久退役名称门和正常数据库对账已经覆盖一次性迁移能力。英文到中文名称迁移器、机器映射、数据库迁移命令及其专用测试均已删除,仓库内相关程序符号与文件路径引用为 0。 本回顾、`chinese_only`、永久退役名称门和正常数据库对账已经覆盖一次性迁移能力。英文到中文名称迁移器、机器映射、数据库迁移命令及其专用测试均已删除,仓库内相关程序符号与文件路径引用为 0。

View File

@ -6,7 +6,7 @@ disable-model-invocation: true
# 规划(scenario: planning | purpose: planning | 槽位: 规划→planner) # 规划(scenario: planning | purpose: planning | 槽位: 规划→planner)
何时用:立项产全套设定包与大纲;续作期按用户意图修订规划。产出全为草稿(未确认规划不进生成上下文)。 何时用:按当前阶段生成指定类型的规划,或按用户意图修订规划。阶段 0 交接先生成 setting,不因此一次产出全部设定包与大纲。产出全为草稿,未确认规划不进生成上下文。
## 元数据驱动(本功能的核心机制) ## 元数据驱动(本功能的核心机制)
@ -56,7 +56,7 @@ disable-model-invocation: true
## 红线 ## 红线
不写正文;修订场景只动用户点名的规划面,并列出波及(哪些细纲/卡受影响)。 不写正文;修订场景只动用户点名的规划面,并列出波及(哪些细纲/卡受影响)。
从前期设计进入时,须先通过 `完善故事基础设定/scripts/assert_selection_handoff.py`;不得从候选目录偷读未选方案当 setting。 从前期设计进入时,先按 [整理交付](../完善故事基础设定/references/整理交付.md) 接收作者明确选择的当前稿、约束与待决问题;最终选定记录须通过 `完善故事基础设定/scripts/assert_selection_handoff.py` 格式检查。该脚本不证明用户授权,也未接入 `persist_planning` 自动拦截,主会话必须核对真实用户回复。不得读未选方案冒充已定事实;不能用“待定”假装补齐必要字段。所生成 setting Shadow 须展示给用户另行确认。
## 失败关闭 ## 失败关闭

View File

@ -1,60 +1,32 @@
--- ---
name: 合并故事方案 name: 合并故事方案
description: 把多份已完成的前期设计候选逐章串行统合成一份新候选,一级章节一个 fresh 代理,权威顺序固定为根设定优先。用户明确要求综合多案时使用;不生成候选、不做机械门校验、不改前期设计 SoT。 description: 作者明确要求结合几种已有故事设想时,按指定范围比较兼容性、解释取舍并形成合并建议。可合并局部片段或整稿,产物留在 .agents.local/草稿/;多轮探索归 完善故事基础设定,正式规划落库归 制定作品规划。
disable-model-invocation: true disable-model-invocation: true
--- ---
# 逐章串行统合前期设计候选 # 合并故事方案
何时用:`完善故事基础设定` 已产出多份通过机械门的候选,用户明确要求"把这几案统合成一份"。用户直接指定主案时不用本 Skill。 ## 用途与输入
候选的生成、冻结三级树与机械门校验归 `完善故事基础设定`;本 Skill 只做统合,产物仍是待选候选。 作者指定的来源材料、合并范围、已有偏好、当前探索稿与已确认约束。只有明确要求结合不同设想时使用;不要求来源先写成完整九章候选。
## 依赖边界 按 [合并指令](references/合并指令.md) 组织本轮 planner 任务。角色、模型、工具权限和运行留痕沿用既有派发合同;不按章节创建固定代理流水线。
统合的每章任务包引用 `完善故事基础设定` 的冻结三级树与 [章节内容规范](../完善故事基础设定/references/chapter-content-spec.md);统合完成后的机械门仍由 `完善故事基础设定/scripts/validate_candidates.py` 执行,本 Skill 不复制第二套校验。 ## 推进与输出
完整节点合同见 [串行统合合同](references/serial-merge-contract.md)。 先比较需要合并的局部,再形成可读的合并建议,并说明保留、舍弃和新增连接的理由。出现影响方向的冲突时交由作者取舍;已经获得的合并授权不重复询问,授权未覆盖的改变明确提出。
## 输入 默认把建议保留在 `.agents.local/草稿/<作品>/`。落盘优先级:用户指定路径 > 仓库各 `目录.md` 中已登记的约定 > 本 Skill 默认路径。确认采用后,按授权范围更新当前探索稿。
```bash 需要形成完整交付稿时,返回 [完善故事基础设定](../完善故事基础设定/SKILL.md) 的「整理交付」,统一使用其 [产出范式](../完善故事基础设定/references/candidate-contract.md) 和最终格式校验。局部合并过程不运行校验脚本。
# ① 为当前一级章节构建输入包(最新根设定 + 已统合前文 + 各来源候选的本章)
.venv/bin/python muse/lifecycle/flow/skills/book/合并故事方案/scripts/serial_merge.py packet \
--heading "<冻结的一级章标题>" --output /private/tmp/merge-packet-01.md <来源候选>...
# ② 由主代理派发 planner 子代理
# 主代理把 planner 身份提示与 muse/sot/角色合同.md 对应章节作为角色 prompt,输入仅包含
# serial-merge-contract.md 与当前 merge-packet-01.md;子代理无工具、fresh 会话,
# 输出写到 /private/tmp/merge-raw-01.md,并由 记录运行证据 保存派发回执。
# ③ 校验并接纳本章草案
.venv/bin/python muse/lifecycle/flow/skills/book/合并故事方案/scripts/serial_merge.py accept ...
```
每章一个 fresh 高推理代理;不复用上一节点会话,不把来源候选的后续章节提前交给当前代理。
## 输出
每章产出 `<decision>`(主轴、主要取舍、发现的前文冲突及处理)与 `<chapter>`(可直接审定的章节草案)两个区块。主会话审定落盘后,该章成为下一节点的"已统合前文"。全部章节完成后得到一份新的统合候选文件。
## 数据边界 ## 数据边界
- 允许读取:最新根设定、已统合前文、来源候选的当前章节。 - 用户最新明确决定优先;不能按来源数量投票,也不把所有互斥卖点叠加。
- 允许写入:统合候选文件与 `/private/tmp` 下的任务包、原始输出。 - 可以保留尚未解决的部分与多个中心设想。合并范围由作者的实际问题决定,不强制逐章、固定顺序或统一发动机。
- 数据库:不读不写任何表。 - 涉及已确认约束时说明影响,不能静默改写。源稿保留,不自动删除。
- Git:不执行 `add`、`commit` 或删除候选。 - 不写正式正文、不写数据库、不执行 Git 提交。合并建议与局部采纳都不替代方向选择及正式 setting 确认。
## 红线
- 权威顺序固定:最新根设定 > 已统合前文 > 当前来源章节 > 最小补缝新增。来源候选没有投票权,不按票数取多数。
- 后章不得推翻前章已审定的因果、人物关系、数值、专名和披露节点。
- 不叠加多案互斥的卖点;故事只能有一条主发动机。
- 某章校验或语义复核失败时停在当前章,不得让后续代理基于未审定草案继续。
- 统合结果仍是待选候选,不自动进入唯一前期设计 SoT,也不自动交给 `制定作品规划`。
- 本 Skill 不提供模型 CLI 子命令。主代理必须按 07 领域派发合同启动 planner 子代理;不得把宿主命令、模型名或凭据固化进脚本。
## 复利合同 ## 复利合同
- **消费**:多路候选方案与用户选定约束。 消费来源材料和作者取舍;在探索稿保留有效选择的理由。合并阶段不写 `example_lesson`,正式规划或正文确认链负责后续效果登记。
- **回写**:统合本身不落 `example_lesson`;选定方案进入 `制定作品规划` / `决定正文候选去留` 后由规划或确认链登记效果信号。

View File

@ -1,70 +1,63 @@
--- ---
name: 完善故事基础设定 name: 完善故事基础设定
description: 在正式规划前固化作品根设定,按固定三级章节树生成可比较的前期设计候选。用户仍在单文档前期设计阶段时使用;不写正文、不落库、不替用户定稿。不适用于合并多案(归 合并故事方案)或正式规划落库(归 制定作品规划)。 description: 作者从点子、片段或已有设计继续构思作品时,用提示词引导多轮探索,在 .agents.local/草稿/ 逐步形成作品探索稿。按需发展素材、联系、中心与结构,整理交付时才校验格式;指定的多案合并归 合并故事方案,正式规划落库归 制定作品规划。
disable-model-invocation: true disable-model-invocation: true
--- ---
# 作品设定初始化 # 完善故事基础设定
## 唯一目的 ## 流程主文档
把零散对话收束成根设定与若干独立候选,让用户比较后再进 `制定作品规划`。不写正文、不落 PostgreSQL、不替用户定稿。 帮助作者把散乱的想法逐步发展成作品。边缘素材与中心设想相互影响,结构从已有内容中长出来;中心可以暂定,也可以在正文写作后调整。
## 元数据驱动:输入与权威顺序 本文件统一管理阶段 0 的探索指令。下表是可往返的子阶段,不是必须顺序执行的流水线。每轮只加载当前需要的一份子文档;九章清单用于找方向和整理产物,不要求一次填完。
先冻结 `完善故事基础设定/v2` 任务包(作品名、SoT 路径、候选数、根设定、对话记录、节奏要求、参考书证据、候选路径)。冲突顺序:用户最新明确要求 > 根设定已定事实 > 已接受方案 > 待定建议。 ## 输入与已有事实
字段与完成判定见 [候选生成合同](references/candidate-contract.md);三级内容见 [章节内容规范](references/chapter-content-spec.md)、[一至五章](references/chapter-content-spec-chapters-1-5.md)、[六至九章](references/chapter-content-spec-chapters-6-9.md)。冻结后派发;新要求先改任务包,旧候选作废。 作品身份、作者本轮想推进的内容、已有探索稿和本会话决定。进入阶段 0 前已有作品行;缺少作品身份时由作者入口交给作品管理能力处理,本 Skill 不建表或写库。
## 根设定与候选章节树 先读相关内容再提问。用户最新明确要求优先;涉及已确认正式事实的改变,说明影响并留作修订建议。作者说过的决定不重复问,未作决定的内容保留为设想或待决。
根设定 = 上游硬约束(不占九章);三级候选树 = 本 Skill 主输出。根设定只写稳定事实,不写过程/工具/方案来源;每点 30–60 字;歧义进「待决问题」。用户确认的稳定事实才回写根设定。 ## 子阶段调度
## 固定三级章节树与节奏 | 当前需要 | 读取子文档 | 本轮留下什么 |
|---|---|---|
| 只有零散点子,或只有题材、感受与写法偏好 | [收集素材](references/收集素材.md) | 原始偏好、已有素材与 AI 提出的少量设想 |
| 素材之间出现情绪、意象、气氛上的呼应,或产生冲突与因果 | [发展联系](references/发展联系.md) | 素材间的呼应、关系与可能后果,保留尚未说清的感觉 |
| 想试探“这部作品在围绕什么生长” | [试探中心](references/试探中心.md) | 能解释部分素材、仍可改动的中心设想 |
| 已有内容需要归位,或准备展开某一设计面 | [组织结构](references/组织结构.md) | 九章中的局部结构与清楚的缺口 |
| 同一问题有互斥走法,需要比较或合并 | [比较分歧](references/比较分歧.md) | 小范围差异、代价与作者取舍 |
| 作者要回看当前成果、选择方向或交给正式规划 | [整理交付](references/整理交付.md) | 按固定范式整理的当前作品探索稿 |
九章三级树与层级硬规则见章节内容规范。设定从追读节奏反推:前三章立处境与钩子、前十章局部闭环、前五十章阶段高潮、全书逐级扩舞台;区分世界事实 / 角色知情 / 读者披露。 作者指定要谈什么,就从那里进入。没有指定时,围绕已有材料提出一个具体推进点;不因为清单里有空栏就改问卷。
## 参考书与冻结合同 ## 每轮协作
参考书先读窗级大纲再校验前五十章正文;只借方法不照搬专名骨架。派发前冻结三级标题与号段(见候选生成合同);同构只为比较。 1. 主会话确认本轮范围,将相关原文、用户决定、本主文档与当前子文档交给 planner;角色、模型与运行留痕沿用既有派发合同。
2. 规划角色先发展内容,再指出其中最影响下一步的疑问。需要作者决定时,一轮只问一个关键问题,并说明它会改变什么。
3. 按作者反馈补写当前探索稿,只整理本轮涉及部分。用户原话、作者接受的内容、模型设想与暂缓问题用自然文字分清。
4. 简短说明这轮留下了什么;下一轮可以继续、转向、退回或暂停。暂缓、矛盾和推翻旧设想都不触发程序失败。
## 独立生成 “这个点子保留”只采纳该点子;“按这个方向继续”只选择当前方向;确认正式 setting 针对之后展示的具体规划产物。三者不能互相代替。
每候选一个独立 planner 子代理,只读共同输入与自己的文件;会话和工具授权隔离。调用走 `执行角色任务`,回执由 `记录运行证据` 保存。超时从最后一个完整单元续写,禁止空话补篇幅。 ## 元数据驱动:设计导航与产出范式
## 复核与机械门 - 需要查某一设计面的内容时,读 [章节内容规范](references/chapter-content-spec.md),再按需读 [一至五章](references/chapter-content-spec-chapters-1-5.md) 或 [六至九章](references/chapter-content-spec-chapters-6-9.md)。
- 整理交付时才读 [作品探索稿产出范式](references/candidate-contract.md)。日常素材和局部方案不套完整模板、不运行格式校验。
- 人物动作、关系与声音使用 [呈现人物](../../../../../../.agent/skills/写作/呈现人物/SKILL.md);试探冲突使用 [提炼故事概念](../../../../../../.agent/skills/规划/提炼故事概念/SKILL.md);已有材料需要因果安排时使用 [搭建故事蓝图](../../../../../../.agent/skills/规划/搭建故事蓝图/SKILL.md)。方法按问题选用,其完整工作表不作为本阶段前置要求。
表达层复核(可用 `humanizer-zh`)后重跑机械门与语义复核清单。 ## 输出与落盘
```bash 默认持续维护 `.agents.local/草稿/<作品>/作品探索.md`;局部备选只有需要单独比较时才另存。首次有内容时再建文件,不预建九章空表,不自动生成多份整书候选。
.venv/bin/python muse/lifecycle/flow/skills/book/完善故事基础设定/scripts/validate_candidates.py \
--root-doc docs/design/<作品>-前期设计.md --min-candidates 5 docs/design/candidates/*.md
```
机械门查标题同构、号段、占位符、根设定句长;内容完整靠语义复核。失败码:`SETTING_INIT_VALIDATION_FAILED`。 落盘优先级:用户指定路径 > 仓库各 `目录.md` 中已登记的约定 > 本 Skill 默认路径。探索稿属于私有草稿;项目规则与设计的修改遵守三树及父子索引契约。
## 用户选择与交接 ## 边界与最终交接
比较报告不自动合并。统合交给 `合并故事方案`;用户确认方向后整理回 SoT。 本阶段不写正式正文、不写数据库、不自动提交 Git。探索过程由指令和作者反馈推进;代码只在最终交付时核对产物格式与选定记录格式,不决定题材、中心、质量或下一子阶段。
进入 `制定作品规划` 前必须有用户选定记录,并用机械门校验: 作者明确选择当前方向后,按「整理交付」记录所选稿,再交给 [制定作品规划](../制定作品规划/SKILL.md) 生成 setting Shadow。作者确认这份具体 setting 后才转 confirmed;本地探索稿与方向选择都不产生 Canonical。
```bash
.venv/bin/python muse/lifecycle/flow/skills/book/完善故事基础设定/scripts/assert_selection_handoff.py \
docs/design/<作品>-selection.json
```
选定 JSON 必含 `selectedCandidate` / `confirmedByUser` / `confirmedAt`。未通过交接闸不得把未选候选当正式设定写入规划。
## 数据与失败边界
只读写用户点名的前期设计 SoT 与临时候选目录;不碰正文/正式规划/知识卡/任何表;无授权不 Git 提交。输入不全、合同漂移、候选污染或机械门失败时稳定失败关闭。
## 输出
可比较的候选 Markdown + 比较报告;不写 Canonical。
## 复利合同 ## 复利合同
- **消费**:用户对话与参考书方法证据,指导后续 `制定作品规划` 正式规划。 消费已有写作方法与参考材料,只借观察、人物、因果等方法,不照搬参考作品设定。本阶段保留有效取舍及其原因;进入正式规划或正文确认链后,由对应 Skill 登记效果信号,本阶段不写 `example_lesson`。
- **回写**:本阶段不落 `example_lesson`;选定方案进入规划链后由 `制定作品规划` / `决定正文候选去留` 登记效果信号。

View File

@ -1,149 +1,49 @@
# 候选生成合同(完善故事基础设定/v2) # 作品探索稿产出范式
本合同在派发作品设定候选前读取。它定义共同输入、候选结构、层级编号和完成判定,不保存任何具体作品设定。各章节“应该写什么”的内容合同见 [章节内容规范](chapter-content-spec.md)。 本文件只定义阶段 0 整理交付时的 Markdown 形状。交互推进由 [流程主文档](../SKILL.md) 及其子文档负责;日常片段、局部比较和未整理草稿不受本格式约束。
## 一、冻结任务包 ## 固定目录,按需展开
主会话先组装任务包,再复制给全部候选子代理。只有 `candidateId` 和 `outputPath` 可以因候选不同而变化。 一份稿只用一个 `# 作品名|作品探索` 标题。正文依次包含 `## 根设定`、下列九章、`## 待决与取舍`;可在根设定之前增加 `## 目录`。
| 字段 | 内容 |
|---|---|
| `contractVersion` | 固定为 `完善故事基础设定/v2` |
| `workTitle` | 当前作品名 |
| `sotPath` | 唯一前期设计文档路径 |
| `rootSetting` | 根设定全文,不用摘要替代 |
| `conversationRecord` | 与作品有关的用户原话,按时间排序 |
| `decisionLedger` | 每项标为已接受、已否决、待定或被新要求覆盖 |
| `unresolvedQuestions` | 会实质改变故事方向、仍需用户拍板的问题 |
| `retentionMilestones` | 前三章、前十章、前五十章及后续阶段落点 |
| `disclosureRules` | 世界事实、角色知情、读者披露三线分界 |
| `phaseRequirements` | 用户指定的阶段模型;为空时由候选自行划分并写明边界依据 |
| `referenceEvidence` | 大纲直证、正文归纳、可借鉴、不可照搬四栏 |
| `sectionContract` | 固定九章三级树(引用 chapter-content-spec),含各一级章节预留号段 |
| `thresholdOverrides` | 用户显式指定的数量或字数门槛;机械门不查数量,为空时不设任何数量要求 |
| `candidateId` | 当前子代理唯一编号 |
| `outputPath` | 当前子代理唯一输出文件 |
对话不能只给总结。原话用于防止整理时改义,`decisionLedger` 用来阻断已经否决的旧方案复活。出现矛盾时,子代理不得自行选择;它只按任务包里已写明的优先级执行,并把真正未决项留给用户。
## 二、统一文档形状
每份候选只允许一个一级标题。`## 目录`可选,不参与号段和判定。正文使用固定的二级、三级标题,顺序逐字一致:
```markdown ```markdown
# 候选标题 # 作品名|作品探索
## 根设定
作者已明确的约束。尚无约束时如实说明。
## 一、作品定位与核心命题 ## 一、作品定位与核心命题
## 二、故事世界与社会系统
## 三、角色与关系
## 四、故事机制与关键实体
## 五、阶段与状态变化
## 六、情节架构与全书大纲
## 七、主题与读者体验弧线
## 八、信息、悬念与伏笔管理
## 九、叙事执行与一致性
<!-- S001-S050 --> ## 待决与取舍
保留的零散素材、未决分歧、已有取舍及下一步关注点;没有时明确说明。
### 1.1 作品形态与定位
> 回答:这是什么样的作品、写给谁、以什么形态连载、与同类有何差别。
> 应写:题材标签、载体与读者、篇幅与连载结构、差异化定位。
> 不写:故事内部设定、情节、角色。
> 关联:1.2、1.4、6.2。
### 1.1.1 题材、载体与目标读者
> 回答:题材元素、发布载体与目标读者分别是什么。
> 应写:题材标签、载体与阅读场景、读者群体与其期待。
> 不写:篇幅结构归 1.1.2,差异比较归 1.1.3。
> 关联:1.1.2、1.1.3。
- **S001|设定名称:** 设定正文。
### 1.1.2 篇幅、连载方式与结构规模
> 不适用:本作品尚未确定连载形态,待用户确认后补充。
``` ```
规则: 以上仅列目录;实际交付各节必须有正文或明确说明,例如:
1. 四字段说明中“回答、应写、关联”必填,“不写”用于标出最常见的越界写法;内容以 chapter-content-spec 为准,子代理不得自行改写合同语义。 > 未探索:目前只有兄妹关系,尚未讨论社会制度。
2. 每个三级章节要么至少一条 `S` 条目,要么一条“不适用及原因”标注;两者不得同时缺省。
3. 一项 `S` 可以写多段并包含表格,但必须围绕同一内容单元;阶段状态矩阵、伏笔登记、时间线核对允许以表格为主体。
## 三、层级与编号规则 > 待定:暂不决定是否设置超自然力量,后续先发展人物处境。
```text 章节内可以直接写概述。需要细分时,使用 [九章导航](chapter-content-spec.md) 中相关组或内容单元的标题,均写为 `###`,按导航顺序排列。允许只展开其中一部分;不要求全稿包含 36 组、161 个单元,也不要求各方案展开相同的部分。不使用四级及更深标题。
一级标题:一个创作设计领域
二级标题:这个领域要回答的一组问题
三级标题:可以独立填写、审查和引用的内容单元
S 条目:三级标题下的具体设定、规则、实体、关系、阶段状态或事件
```
1. 号段按一级章节预留,写在章标题之后;三级章节不单独分号段,避免号段过碎。 ## 内容表达
2. `S` 编号全文连续、唯一、递增,必须落在本章号段内;空号允许,越界、倒序和重复不允许。
3. 禁止四级标题;禁止在二级标题下直接放 `S` 条目。
4. 阶段名称(初期、中期、后期或卷、篇、幕)只能出现在卡片正文、表格列和第五章中,不得成为二、三级标题。
5. 所有候选标题逐字同构;候选序号只出现在一级标题,不进入共同目录。
## 四、节奏与披露 - 根设定只记录作者明确要求或已确认的约束;提出新的设想不自动成为根设定。
- 已形成的内容写具体,建议与疑问明确标注。中心、终局和远期阶段可保留待定,不因模板要求而虚构完整答案。
- 需要引用具体条目时可用 `- **S001|名称:** 内容`;编号可选,使用时全稿唯一。没有预分号段、数量下限或句长要求。
- 同一事实一个主归属,其他章节写引用或本章视角的增量。未归位素材留在最后一节。
- 不留下九章空节或裸露的 `TODO`、`TBD`、`[待填]`;把尚未解决的事说清,不以“未探索”冒充已经设计完成。
候选先完成追读链,再扩写设定。三个早期里程碑都要同时具备“当期问题、行动目标、实际兑现、付出代价、章末新问题”。 ## 最终代码校验的范围
- 前三章让读者看见异常和价值,不解释最终来源,不把终局阵营拉到台前。 [最终格式校验器](../scripts/validate_candidates.py) 使用 [标题树](../scripts/candidate_tree.json) 核对固定目录和已展开标题,检查空节、裸占位符及可选 S 编号重复。一次可交付一份稿,也可分别校验作者要求的多份稿;不比较方案数量、篇幅、完整度或优劣。
- 前十章让优势经过对手或任务验证,同时暴露限制,完成第一个局部闭环。
- 前五十章完成初期主冲突和一次身份或能力抬升,再打开更大舞台。
- 后续阶段继续扩大问题尺度;早期劳动、训练、资源或关系线要换规模延续,不能用完即丢。
信息分三线记录:世界实际状态、角色知道的状态、读者已看到的状态。一个谜底可以在作者侧确定,但它的征兆、误判、局部解释和正式揭示必须分开放置,归口见 chapter-content-spec 第七章。 格式通过只表示可读、可交接。根设定是否确为作者决定、内容是否自洽、何时选方向,由指令和作者判断。所选稿仍是本地探索材料,正式 setting 的字段与确认合同归 [制定作品规划](../../制定作品规划/SKILL.md)。
## 五、独立性
候选子代理只能读取任务包、共同参考证据和自己的文件。不得搜索候选目录,不得读取其他候选,不得询问主会话“前一份怎么写”。主会话也不能把某份候选的优点转述给尚未完成的代理。
长文需要多轮时,在同一候选内部维护简短设计账本:已经确定的因果、数值、人物关系、阶段披露点和未完成章节。账本只属于该候选,续跑时与任务包一起提供。
## 六、表达复核
完成内容复核后再处理语言。表达层重点清理:
- 空泛评价代替具体事件,例如只说“极具张力”“层层递进”。
- 每段都用“不是……而是……”或三项排比制造伪力度。
- 所有条目使用相同句式、相同结尾或固定总结句。
- 频繁使用“同时、此外、值得注意的是、总而言之”等连接词。
- 用旁白宣布人物多强、世界多危险,却没有任务、损失和对比支撑。
- 为了显得完整,提前解释终局真相或最终敌人的全貌。
复核可以改句子长短、用词和段落节奏,不能改事实、编号、目录、量级、代价或披露节点。
## 七、完成判定
一份候选只有同时满足以下条件才算完成。机械可查项由校验器把关,其余由语义复核逐项核对:
1. 九个一级章节齐全,全部二级、三级标题齐全且顺序一致。
2. 每个三级章节有内容说明,并有 `S` 条目或显式不适用标注。
3. 每个核心实体都有稳定定义和阶段状态。
4. 每个阶段都有进入条件、核心问题、状态矩阵和退出条件。
5. 每个阶段转变都有事件或明确原因。
6. 主要关系都有初始状态、变化触发和后续状态。
7. 主要规则都有触发、边界、代价和可观察结果。
8. 主要事件都改变至少一项状态或推进一项承诺。
9. 世界事实、角色知情和读者披露三条线没有混写。
10. 前三章、前十章、前五十章分别有目标、兑现、代价和新问题。
11. 没有把终局答案提前泄露给不该知道的角色或读者。
12. 没有重复定义同一事实,也没有同名异义。
13. 数字、单位、术语和时间线一致。
14. 候选之间结构相同,但故事方案可以不同。
15. 任务包指定了数量或字数门槛时同时满足;未指定时不以篇幅论完成。
## 八、交付报告
所有候选过门后才生成比较报告。报告逐案回答:
1. 前三章靠什么让读者继续。
2. 前十章兑现了什么,暴露了什么限制。
3. 前五十章在哪个事件形成初期高潮。
4. 初期机制如何换规模进入中期和后期。
5. 阶段状态矩阵是否闭合,转段事件是否成立。
6. 这套方案最大的收益、代价和失速风险是什么。
报告只供用户选择,不宣告胜者。用户没有拍板前,不合并、不落库、不写正文。
## 九、用户授权后的逐章统合
用户明确要求综合多份候选时,独立生成阶段结束,进入串行统合阶段。统合以一级章节为节点,代理必须读取该章完整的三级结构。统合不再要求来源隔离,但每个代理仍只处理一个当前章节,不能提前读取后续来源章节。详细输入、权威顺序、输出格式与失败边界见 [`合并故事方案` 的串行统合合同](../../合并故事方案/references/serial-merge-contract.md)。
串行链的每个节点都要生成 fresh 会话。当前章只有经主会话审定并写入统合候选后,才可作为下一节点的权威前文。未审定的代理输出、分析摘要和舍弃方案不能进入下一节点上下文。

View File

@ -2,6 +2,8 @@
> 六至九章见 [chapter-content-spec-chapters-6-9.md](chapter-content-spec-chapters-6-9.md)。 > 六至九章见 [chapter-content-spec-chapters-6-9.md](chapter-content-spec-chapters-6-9.md)。
按当前问题查阅其中一节即可。下列字段和判断用于深化已展开的设计,不要求从头填完;未讨论的内容可以留待后续。使用方式见 [章节内容规范](chapter-content-spec.md)。
本章回答:这部作品是什么、写给谁、承诺什么、边界在哪。不写世界内部设定、情节和角色细节。 本章回答:这部作品是什么、写给谁、承诺什么、边界在哪。不写世界内部设定、情节和角色细节。

View File

@ -2,6 +2,8 @@
> 一至五章见 [chapter-content-spec-chapters-1-5.md](chapter-content-spec-chapters-1-5.md)。 > 一至五章见 [chapter-content-spec-chapters-1-5.md](chapter-content-spec-chapters-1-5.md)。
按当前问题查阅其中一节即可。下列字段和判断用于深化已展开的设计,不要求预先完成全书阶段、结局与披露计划。使用方式见 [章节内容规范](chapter-content-spec.md)。
本章回答:故事如何一步步发生。调用第三、四章设定,不得在事件中偷偷新增未定义的核心规则。 本章回答:故事如何一步步发生。调用第三、四章设定,不得在事件中偷偷新增未定义的核心规则。

View File

@ -1,6 +1,6 @@
# 章节内容规范(完善故事基础设定/chapter-spec-v2) # 章节内容规范
本规范是 `完善故事基础设定/v2` 的内容事实源:定义固定三级章节树、每级标题职责、内容单元模板、阶段状态规范和跨章归属规则。候选子代理、统合代理和语义复核都以本文件为准;任务包只引用本文件,不重复保存章节合同。 本规范保留九章设计知识,供探索和深化时按需查阅。每个内容单元说明可以继续想什么,不要求在某一轮全部回答。探索怎么推进见 [流程主文档](../SKILL.md),最终交付的 Markdown 形状见 [产出范式](candidate-contract.md)。
## 一、层级规则 ## 一、层级规则
@ -11,12 +11,12 @@
S 条目:三级标题下的具体设定、规则、实体、关系、阶段状态或事件 S 条目:三级标题下的具体设定、规则、实体、关系、阶段状态或事件
``` ```
1. 九章、全部二级与三级标题固定,顺序不得调整,所有候选逐字同构。 1. 九章是最终稿的固定主目录;平时只展开当前需要的部分,不比较各稿是否逐字同构。
2. 不允许四级标题;不允许在二级标题下直接放 `S` 条目。 2. 章内可直接写概述或具体设想。需要细分时,从下方标题树选用相关组与单元,按原顺序排列;没有探索的单元不必建空标题。
3. 三级标题不按阶段复制。阶段变化由第五章状态矩阵和实体卡阶段字段表达。 3. 已涉及阶段变化时,由第五章状态矩阵和实体卡阶段字段表达;不为填表强行划分全书阶段。
4. 每个三级章节开头写四字段说明(回答/应写/不写/关联),正文内容以本规范为准。 4. 分章参考中的“回答/应写/不写/关联”是给规划角色的提示,不要求复制进作者每轮产物。
5. 三级章节无适用内容时写“不适用及原因”,不得留空。 5. 未探索、暂定、不适用都应按实际情况说明。只有“尚未探索”时,不把它改写为“不适用”。
6. `## 目录`可选,不属于内容章节。 6. 上述一、二、三级是设计知识层级;最终 Markdown 使用一个作品 H1、章 H2、组和单元 H3,具体格式只由产出范式规定。
## 二、固定章节树总览 ## 二、固定章节树总览
@ -86,11 +86,11 @@ S 条目:三级标题下的具体设定、规则、实体、关系、阶段状
## 四、内容单元模板 ## 四、内容单元模板
不同三级章节使用不同的内容单元。`S` 条目正文围绕同一单元自然展开,不使用整齐划一的表单腔,但字段必须覆盖。 以下字段用于深化已选中的内容单元,提醒规划角色发现联系与缺口。围绕本轮问题自然展开;未讨论的字段可以留待后续,不把整张卡的字段覆盖当成探索通行条件。`S` 编号仅在需要引用时使用。
### 1. 作品命题单元(第一章) ### 1. 作品命题单元(第一章)
必须覆盖:作品定位、目标读者、一句话故事、主角起点、核心目标、外部期限或主要压力、核心卖点、主题问题、读者体验、题材边界。不写代理过程、候选比较、工具模型、尚未确认的作品事实。 可逐步考虑:作品定位、目标读者、一句话故事、主角起点、核心目标、外部期限或主要压力、核心卖点、主题问题、读者体验、题材边界。尚未确认的内容标明是设想;不因存在该字段就强定答案。工具与比较过程留在取舍说明中。
### 2. 稳定规则单元(第二、四章) ### 2. 稳定规则单元(第二、四章)
@ -126,7 +126,7 @@ S 条目:三级标题下的具体设定、规则、实体、关系、阶段状
## 五、阶段状态规范 ## 五、阶段状态规范
阶段是全书的横向维度,不是某几个章节的专属分类。 当作品已经需要设计阶段变化时,阶段是全书的横向维度,不是某几个章节的专属分类。以下规则只约束已经展开的阶段;未探索的远期变化明确保留未知,不要求先设计全书再继续人物或场景。
1. 动态实体至少记录:不变量;初始状态;各阶段状态;状态变化原因;终局状态或未知状态。 1. 动态实体至少记录:不变量;初始状态;各阶段状态;状态变化原因;终局状态或未知状态。
2. 同一实体在某阶段没有变化时,必须显式写“本阶段保持不变”,不得跳过,否则无法区分“保持不变”与“设计遗漏”。 2. 同一实体在某阶段没有变化时,必须显式写“本阶段保持不变”,不得跳过,否则无法区分“保持不变”与“设计遗漏”。

View File

@ -30,17 +30,15 @@ def assert_selection_handoff(selection: dict) -> dict:
+ ",".join(missing) + ",".join(missing)
+ "。用户未选定候选时不得进入 制定作品规划。" + "。用户未选定候选时不得进入 制定作品规划。"
) )
if selection.get("confirmedByUser") is not True and str(selection.get("confirmedByUser")).lower() not in ( if selection["confirmedByUser"] is not True:
"true", "yes", "1", "user", raise HandoffError("SELECTION_HANDOFF_UNCONFIRMED:confirmedByUser 必须为布尔值 true")
): candidate = selection["selectedCandidate"]
# 允许字符串用户标识,但禁止空/false if not isinstance(candidate, str) or not candidate.strip():
if selection.get("confirmedByUser") in (False, None, "", 0):
raise HandoffError("SELECTION_HANDOFF_UNCONFIRMED:confirmedByUser 未确认")
candidate = str(selection["selectedCandidate"]).strip()
if not candidate:
raise HandoffError("SELECTION_HANDOFF_EMPTY:selectedCandidate 为空") raise HandoffError("SELECTION_HANDOFF_EMPTY:selectedCandidate 为空")
if not isinstance(selection["confirmedAt"], str) or not selection["confirmedAt"].strip():
raise HandoffError("SELECTION_HANDOFF_INVALID:confirmedAt 必须为非空时间文本")
return { return {
"selectedCandidate": candidate, "selectedCandidate": candidate.strip(),
"confirmedByUser": selection["confirmedByUser"], "confirmedByUser": selection["confirmedByUser"],
"confirmedAt": selection["confirmedAt"], "confirmedAt": selection["confirmedAt"],
} }

View File

@ -1,5 +1,5 @@
{ {
"contractVersion": "完善故事基础设定/v2", "contractVersion": "完善故事基础设定/output-v3",
"chapters": [ "chapters": [
{ {
"heading": "一、作品定位与核心命题", "heading": "一、作品定位与核心命题",

View File

@ -1,41 +1,23 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""校验作品设定初始化的根设定与同构候选。 """只校验阶段 0 最终交付稿的格式,不参与探索调度或创作判断。"""
只检查机械易错且人眼难以抽查的项:三级标题树同构、S 编号唯一递增不越号段、
占位符残留、根设定格式与句长。内容是否完整由语义复核按完成判定核对,
本脚本不做数量、字数或内容质量门槛。
"""
from __future__ import annotations from __future__ import annotations
import argparse import argparse
import json from collections import Counter
import re
import sys
from dataclasses import asdict, dataclass from dataclasses import asdict, dataclass
import json
from pathlib import Path from pathlib import Path
import re
from typing import Iterable from typing import Iterable
TREE_PATH = Path(__file__).with_name("candidate_tree.json") TREE_PATH = Path(__file__).with_name("candidate_tree.json")
HEADING_RE = re.compile(r"^(#{1,6})\s+(.+?)\s*$", re.MULTILINE) HEADING_RE = re.compile(r"^(#{1,6})[ \t]+(.+?)[ \t]*$", re.MULTILINE)
RANGE_RE = re.compile(r"<!--\s*S(\d+)\s*[-—–]\s*S(\d+)\s*-->") SETTING_RE = re.compile(r"^\s*[-*]\s+(?:\*\*)?S(\d{3,})(?=[^\d]|$)", re.MULTILINE)
SETTING_RE = re.compile(r"^\s*[-*]\s+(?:\*\*)?(S\d{3,})(?=[^\d])", re.MULTILINE)
PLACEHOLDER_RE = re.compile( PLACEHOLDER_RE = re.compile(
r"(?<![A-Za-z0-9])(?:TODO|TBD|PLACEHOLDER)(?![A-Za-z0-9])|\[待填\]|待补充|待生成|在此填写", r"^\s*(?:[-*>]\s*)?(?:TODO|TBD|PLACEHOLDER|\[待填\])\s*$",
re.IGNORECASE, re.MULTILINE | re.IGNORECASE,
) )
ROOT_ITEM_RE = re.compile(r"^\s*-\s+\*\*([^*]+)\*\*(.+)$")
ROOT_H2_RE = re.compile(r"^##\s+(.+?)\s*$", re.MULTILINE)
ROOT_NESTED_RE = re.compile(r"^#{3,6}\s+", re.MULTILINE)
PROCESS_TERMS = ("Claude", "claude", "子代理", "候选文档", "提示词", "工具调用", "设计过程", "方案来源")
@dataclass(frozen=True)
class ValidationConfig:
min_candidates: int = 1
min_item_chars: int = 20
root_min_chars: int = 30
root_max_chars: int = 60
@dataclass(frozen=True) @dataclass(frozen=True)
@ -45,315 +27,115 @@ class Problem:
message: str message: str
@dataclass(frozen=True)
class SectionReport:
heading: str
range_start: int
range_end: int
setting_count: int
@dataclass(frozen=True) @dataclass(frozen=True)
class CandidateReport: class CandidateReport:
file: str file: str
setting_count: int setting_count: int
headings: tuple[str, ...]
sections: tuple[SectionReport, ...]
def visible_chars(text: str) -> int: def _validate(path: Path, tree: dict) -> tuple[CandidateReport, list[Problem]]:
"""去掉常见 Markdown 标记后统计非空白可见字符。"""
cleaned = re.sub(r"[*_`#>\[\]()]", "", text)
return len(re.sub(r"\s+", "", cleaned))
def _problem(code: str, path: Path, message: str) -> Problem:
return Problem(code=code, file=str(path), message=message)
def load_tree(path: Path) -> dict:
return json.loads(path.read_text(encoding="utf-8"))
def expected_heading_sequence(tree: dict) -> list[str]:
"""按冻结树返回 章→节→单元 的扁平标题序列。"""
sequence: list[str] = []
for chapter in tree["chapters"]:
sequence.append(chapter["heading"])
for section in chapter["sections"]:
sequence.append(section["heading"])
sequence.extend(section["units"])
return sequence
def validate_root(path: Path, config: ValidationConfig) -> list[Problem]:
problems: list[Problem] = []
text = path.read_text(encoding="utf-8")
headings = list(ROOT_H2_RE.finditer(text))
root_index = next((index for index, match in enumerate(headings) if "根设定" in match.group(1)), None)
if root_index is None:
return [_problem("ROOT_SECTION_MISSING", path, "未找到包含“根设定”的二级标题")]
start = headings[root_index].end()
end = headings[root_index + 1].start() if root_index + 1 < len(headings) else len(text)
body = text[start:end]
if ROOT_NESTED_RE.search(body):
problems.append(_problem("ROOT_STRUCTURE", path, "根设定内部出现三级或更深标题"))
for term in PROCESS_TERMS:
if term in body:
problems.append(_problem("ROOT_PROCESS_LEAK", path, f"根设定包含设计过程词:{term}"))
labels: set[str] = set()
items = 0
for line_number, raw_line in enumerate(body.splitlines(), start=1):
line = raw_line.strip()
if not line or line == "---":
continue
match = ROOT_ITEM_RE.match(line)
if not match:
problems.append(
_problem("ROOT_ITEM_FORMAT", path, f"根设定第 {line_number} 个相对行不是单行加粗标签条目")
)
continue
items += 1
label = match.group(1).strip()
if label in labels:
problems.append(_problem("ROOT_LABEL_DUPLICATE", path, f"根设定标签重复:{label}"))
labels.add(label)
length = visible_chars(line.lstrip("- "))
if not config.root_min_chars <= length <= config.root_max_chars:
problems.append(
_problem(
"ROOT_ITEM_LENGTH",
path,
f"根设定“{label}”为 {length} 个可见字符,要求 {config.root_min_chars}—{config.root_max_chars}",
)
)
if items == 0:
problems.append(_problem("ROOT_EMPTY", path, "根设定没有可校验条目"))
return problems
def _parse_candidate(path: Path, tree: dict, config: ValidationConfig) -> tuple[CandidateReport, list[Problem]]:
text = path.read_text(encoding="utf-8") text = path.read_text(encoding="utf-8")
headings = list(HEADING_RE.finditer(text))
problems: list[Problem] = [] problems: list[Problem] = []
def fail(code: str, message: str) -> None:
problems.append(Problem(code, str(path), message))
if sum(len(h.group(1)) == 1 for h in headings) != 1:
fail("CANDIDATE_STRUCTURE", "交付稿须有且只有一个一级标题")
if headings and len(headings[0].group(1)) != 1:
fail("CANDIDATE_STRUCTURE", "首个标题须为作品一级标题")
if any(len(h.group(1)) >= 4 for h in headings):
fail("CANDIDATE_STRUCTURE", "交付稿不使用四级及更深标题")
if PLACEHOLDER_RE.search(text): if PLACEHOLDER_RE.search(text):
problems.append(_problem("CANDIDATE_PLACEHOLDER", path, "候选仍含待填占位文字")) fail("CANDIDATE_PLACEHOLDER", "请把裸占位符改为具体的待定或未探索说明")
headings = [(len(match.group(1)), match.group(2).strip(), match.start(), match.end()) for match in HEADING_RE.finditer(text)] chapters = {chapter["heading"]: chapter for chapter in tree["chapters"]}
h1_count = sum(1 for level, _, _, _ in headings if level == 1) expected = ["根设定", *chapters, "待决与取舍"]
if h1_count != 1: h2 = [h for h in headings if len(h.group(1)) == 2]
problems.append(_problem("CANDIDATE_STRUCTURE", path, f"候选应有且只有一个一级标题,实际 {h1_count} 个")) titles = [h.group(2) for h in h2]
deep = [heading for level, heading, _, _ in headings if level >= 4] if titles and titles[0] == "目录":
if deep: titles = titles[1:]
problems.append(_problem("CANDIDATE_STRUCTURE", path, f"候选出现四级或更深标题:{deep[0]}")) if titles != expected:
fail("TREE_MISMATCH", "二级标题须按顺序包含根设定、九章和待决与取舍;目录仅可置于最前")
h2_list = [(heading, start, end) for level, heading, start, end in headings if level == 2] if any(
toc_seen = False len(h.group(1)) == 3 and (not h2 or h.start() < h2[0].start())
chapter_index = 0 for h in headings
all_ids: list[int] = [] ):
previous_range_end = 0 fail("CANDIDATE_STRUCTURE", "三级标题须位于对应章节内")
sections: list[SectionReport] = []
chapter_titles = [chapter["heading"] for chapter in tree["chapters"]]
for position, (heading, start, end) in enumerate(h2_list): for index, heading in enumerate(h2):
body_end = h2_list[position + 1][1] if position + 1 < len(h2_list) else len(text) title = heading.group(2)
body = text[end:body_end] end = h2[index + 1].start() if index + 1 < len(h2) else len(text)
body = text[heading.end():end]
# 标题和注释不能代替说明;未知内容可以用一句明确说明表达。
prose = HEADING_RE.sub("", body)
prose = re.sub(r"<!--.*?-->", "", prose, flags=re.DOTALL)
if not re.sub(r"[\s>*_\x60-]", "", prose):
fail("SECTION_EMPTY", f"“{title}”为空,请写已有内容或说明尚未探索的部分")
if heading == "目录": actual = [
toc_seen = True h.group(2) for h in headings
if re.search(r"^###\s+", body, re.MULTILINE): if len(h.group(1)) == 3 and heading.end() <= h.start() < end
problems.append(_problem("CANDIDATE_STRUCTURE", path, "目录下不得出现三级标题")) ]
continue allowed: list[str] = []
if title in chapters:
for section in chapters[title]["sections"]:
allowed.append(section["heading"])
allowed.extend(section["units"])
positions = {name: position for position, name in enumerate(allowed)}
if any(name not in positions for name in actual):
fail("TREE_MISMATCH", f"“{title}”含导航之外的三级标题")
elif any(positions[right] <= positions[left] for left, right in zip(actual, actual[1:])):
fail("TREE_MISMATCH", f"“{title}”的已展开标题重复或顺序错误")
if chapter_index >= len(chapter_titles): ids = [int(value) for value in SETTING_RE.findall(text)]
problems.append(_problem("TREE_MISMATCH", path, f"多出章节标题:{heading}")) duplicates = [value for value, count in Counter(ids).items() if count > 1]
continue
expected_title = chapter_titles[chapter_index]
if heading != expected_title:
problems.append(
_problem("TREE_MISMATCH", path, f"第 {chapter_index + 1} 章应为“{expected_title}”,实际为“{heading}”")
)
chapter_index += 1
continue
chapter_index += 1
expected_subs: list[str] = []
for section in tree["chapters"][chapter_index - 1]["sections"]:
expected_subs.append(section["heading"])
expected_subs.extend(section["units"])
actual_subs = [sub_heading for level, sub_heading, sub_start, _ in headings if level == 3 and start < sub_start < body_end]
if actual_subs != expected_subs:
expected_set = set(expected_subs)
missing = [item for item in expected_subs if item not in set(actual_subs)]
extra = [item for item in actual_subs if item not in expected_set]
detail = []
if missing:
detail.append(f"缺 {len(missing)} 个(首个:{missing[0]})")
if extra:
detail.append(f"多 {len(extra)} 个(首个:{extra[0]})")
if not detail:
detail.append("顺序与冻结树不一致")
problems.append(_problem("TREE_MISMATCH", path, f"章节“{heading}”标题树漂移:{';'.join(detail)}"))
ranges = RANGE_RE.findall(body)
if len(ranges) != 1:
problems.append(_problem("SECTION_RANGE", path, f"章节“{heading}”必须且只能有一个号段标记"))
continue
range_start, range_end = (int(value) for value in ranges[0])
if range_start > range_end:
problems.append(_problem("SECTION_RANGE", path, f"章节号段倒置:S{range_start:03d}-S{range_end:03d}"))
if range_start <= previous_range_end:
problems.append(_problem("SECTION_RANGE", path, "章节号段未按顺序递增或发生重叠"))
previous_range_end = max(previous_range_end, range_end)
setting_matches = list(SETTING_RE.finditer(body))
ids = [int(match.group(1)[1:]) for match in setting_matches]
all_ids.extend(ids)
for setting_id in ids:
if not range_start <= setting_id <= range_end:
problems.append(
_problem(
"SETTING_ID_OUT_OF_RANGE",
path,
f"S{setting_id:03d} 不在章节号段 S{range_start:03d}-S{range_end:03d} 内",
)
)
for item_index, setting_match in enumerate(setting_matches):
item_end = setting_matches[item_index + 1].start() if item_index + 1 < len(setting_matches) else len(body)
item_text = body[setting_match.start():item_end]
if visible_chars(item_text) < config.min_item_chars:
problems.append(
_problem("SETTING_ITEM_TOO_SHORT", path, f"{setting_match.group(1)} 内容过短,疑似只有标题或占位句")
)
sections.append(
SectionReport(heading=heading, range_start=range_start, range_end=range_end, setting_count=len(ids))
)
if chapter_index < len(chapter_titles):
problems.append(
_problem(
"TREE_MISMATCH",
path,
f"缺少章节:{ '、'.join(chapter_titles[chapter_index:]) }",
)
)
if toc_seen and chapter_index == 0:
problems.append(_problem("CANDIDATE_STRUCTURE", path, "候选只有目录,没有内容章节"))
duplicates = sorted({setting_id for setting_id in all_ids if all_ids.count(setting_id) > 1})
if duplicates: if duplicates:
display = ", ".join(f"S{setting_id:03d}" for setting_id in duplicates[:10]) fail("SETTING_ID_DUPLICATE", "编号重复:" + "、".join(f"S{value:03d}" for value in duplicates))
problems.append(_problem("SETTING_ID_DUPLICATE", path, f"全文编号重复:{display}")) return CandidateReport(str(path), len(ids)), problems
if any(later <= earlier for earlier, later in zip(all_ids, all_ids[1:])):
problems.append(_problem("SETTING_ORDER", path, "全文 S 编号必须严格递增"))
report = CandidateReport(
file=str(path),
setting_count=len(all_ids),
headings=tuple(heading for heading, _, _ in h2_list),
sections=tuple(sections),
)
return report, problems
def validate_candidates( def validate_candidates(paths: Iterable[Path]) -> tuple[list[CandidateReport], list[Problem]]:
paths: Iterable[Path], """各稿独立校验;不要求候选间同构,也不裁决是否已经完成创作。"""
config: ValidationConfig, tree = json.loads(TREE_PATH.read_text(encoding="utf-8"))
root_doc: Path | None = None,
tree_path: Path = TREE_PATH,
) -> tuple[list[CandidateReport], list[Problem]]:
candidate_paths = sorted((Path(path) for path in paths), key=lambda item: str(item))
problems: list[Problem] = []
reports: list[CandidateReport] = [] reports: list[CandidateReport] = []
problems: list[Problem] = []
if len(candidate_paths) < config.min_candidates: for item in paths:
problems.append( path = Path(item)
Problem( try:
code="CANDIDATE_COUNT", report, found = _validate(path, tree)
file="<group>", except (OSError, UnicodeError) as exc:
message=f"只有 {len(candidate_paths)} 份候选,要求至少 {config.min_candidates} 份", problems.append(Problem("CANDIDATE_FILE_UNREADABLE", str(path), str(exc)))
)
)
if root_doc is not None:
if not root_doc.is_file():
problems.append(_problem("ROOT_FILE_MISSING", root_doc, "根设定文档不存在"))
else:
problems.extend(validate_root(root_doc, config))
tree = load_tree(tree_path)
for path in candidate_paths:
if not path.is_file():
problems.append(_problem("CANDIDATE_FILE_MISSING", path, "候选文件不存在"))
continue continue
report, file_problems = _parse_candidate(path, tree, config)
reports.append(report) reports.append(report)
problems.extend(file_problems) problems.extend(found)
if not reports and not problems:
if len(reports) > 1: problems.append(Problem("CANDIDATE_FILE_MISSING", "<input>", "请提供一份最终交付稿"))
baseline = reports[0]
baseline_shape = tuple((section.heading, section.range_start, section.range_end) for section in baseline.sections)
for report in reports[1:]:
shape = tuple((section.heading, section.range_start, section.range_end) for section in report.sections)
if report.headings != baseline.headings or shape != baseline_shape:
problems.append(
Problem(
code="CANDIDATE_STRUCTURE_MISMATCH",
file=report.file,
message=f"目录或号段与基准候选 {baseline.file} 不一致",
)
)
return reports, problems return reports, problems
def _build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="校验作品设定初始化候选")
parser.add_argument("files", nargs="+", type=Path, help="候选 Markdown 文件")
parser.add_argument("--root-doc", type=Path, help="含根设定的唯一前期设计文档")
parser.add_argument("--min-candidates", type=int, default=1)
parser.add_argument("--min-item-chars", type=int, default=20)
parser.add_argument("--root-min-chars", type=int, default=30)
parser.add_argument("--root-max-chars", type=int, default=60)
parser.add_argument("--tree", type=Path, default=TREE_PATH, help="冻结三级树 JSON")
parser.add_argument("--json", action="store_true", help="输出稳定 JSON")
return parser
def main(argv: list[str] | None = None) -> int: def main(argv: list[str] | None = None) -> int:
parser = _build_parser() parser = argparse.ArgumentParser(description="校验阶段 0 最终交付稿格式")
parser.add_argument("files", nargs="+", type=Path, help="已整理的作品探索稿")
parser.add_argument("--json", action="store_true", help="输出稳定 JSON")
args = parser.parse_args(argv) args = parser.parse_args(argv)
if args.root_min_chars > args.root_max_chars: reports, problems = validate_candidates(args.files)
parser.error("--root-min-chars 不能大于 --root-max-chars") code = "SETTING_INIT_VALIDATION_FAILED" if problems else "SETTING_INIT_OK"
if args.json:
config = ValidationConfig( print(json.dumps({
min_candidates=args.min_candidates, "status": "invalid" if problems else "ok",
min_item_chars=args.min_item_chars,
root_min_chars=args.root_min_chars,
root_max_chars=args.root_max_chars,
)
reports, problems = validate_candidates(args.files, config, args.root_doc, args.tree)
status = "ok" if not problems else "invalid"
code = "SETTING_INIT_OK" if not problems else "SETTING_INIT_VALIDATION_FAILED"
payload = {
"status": status,
"code": code, "code": code,
"reports": [asdict(report) for report in reports], "reports": [asdict(report) for report in reports],
"problems": [asdict(problem) for problem in problems], "problems": [asdict(problem) for problem in problems],
} }, ensure_ascii=False, indent=2))
if args.json:
print(json.dumps(payload, ensure_ascii=False, indent=2))
else: else:
print(f"{code}: {len(reports)} 份候选,{len(problems)} 个问题") print(f"{code}: {len(reports)} 份交付稿,{len(problems)} 个格式问题")
for report in reports:
print(f"- {report.file}: {report.setting_count} 项")
for problem in problems: for problem in problems:
print(f"[{problem.code}] {problem.file}: {problem.message}") print(f"[{problem.code}] {problem.file}: {problem.message}")
return 0 if not problems else 1 return 1 if problems else 0
if __name__ == "__main__": if __name__ == "__main__":
sys.exit(main()) raise SystemExit(main())

View File

@ -17,20 +17,20 @@
贯穿五阶段的硬合同(均为既有合同,本节只引用): 贯穿五阶段的硬合同(均为既有合同,本节只引用):
- **横切总闸**:一切正式规划候选先落 Shadow,经用户确认翻 Confirmed 后才进入生成上下文(Shadow/Canonical 双轨,见 [架构-02](../../../../design-docs/架构-02-核心数据结构与双轨模型.md))。 - **横切总闸**:一切正式规划候选先落 Shadow,经用户确认翻 Confirmed 后才进入生成上下文(Shadow/Canonical 双轨,见 [架构-02](../../../../design-docs/架构-02-核心数据结构与双轨模型.md))。
- **阶段 0 探索例外**:`完善故事基础设定` 产出的多案比较稿属于正式规划之前的仓内探索材料,只留在用户指定的设计文档与候选目录,不进 PostgreSQL,也不得进入生成上下文。用户选定方向并通过 `assert_selection_handoff.py` 后,由 `制定作品规划` 把选定内容重写为 `setting` Shadow;该行经用户确认成为 Confirmed,才算阶段 0 正式退出。候选被选中不等于规划已确认。 - **阶段 0 探索例外**:`完善故事基础设定` 通过主文档及子阶段提示词管理多轮探索,材料默认留在 `.agents.local/草稿/`,不进 PostgreSQL,也不得进入正文生成上下文。代码只在整理交付时核对最终稿及选定记录的格式,不管理创作步骤。用户明确选定当前方向后,由 `制定作品规划` 整理为 `setting` Shadow;用户再确认这份具体 setting,才算阶段 0 正式退出。局部点子被采纳、方向被选中和正式规划确认是不同决定。
- **上层约束下层**:大纲派生卷纲、卷纲派生细纲;下层规划不得与已确认的上层规划相悖,冲突时以下层退回为准。 - **上层约束下层**:大纲派生卷纲、卷纲派生细纲;下层规划不得与已确认的上层规划相悖,冲突时以下层退回为准。
### 阶段定义 ### 阶段定义
阶段 0 的多案比较稿按上面的探索例外处理;选定方向的正式 `setting` 与阶段 1–4 的规划产出都经 `persist_planning` 落 `example_planning_section`(默认 `state='shadow'`),用户确认后翻 `confirmed`。 阶段 0 的作品探索稿与局部比较稿按上面的探索例外处理;选定方向的正式 `setting` 与阶段 1–4 的规划产出都经 `persist_planning` 落 `example_planning_section`(默认 `state='shadow'`),用户确认后翻 `confirmed`。
**阶段 0 · 定盘** **阶段 0 · 定盘**
- 进入条件:作品已创建(作品行存在,见 [01-作品领域](01-作品领域.md))。 - 进入条件:作品已创建(作品行存在,见 [01-作品领域](01-作品领域.md))。
- 探索产出:冻结根设定后生成可比较候选;候选只供用户选方向,不是 Shadow,也不是正式内容。统合多案仍只得到一份待选候选。 - 探索产出:持续充实的作品探索稿,按需附局部方案。素材、联系、中心与结构可以往返发展;中心和远期内容可以暂定。九章清单用于导航,整理交付时才按固定目录归位,已展开内容具体写明,未探索部分保留说明。完整指令唯一入口为 [完善故事基础设定](../../lifecycle/flow/skills/book/完善故事基础设定/SKILL.md)。
- 正式产出:通过选定交接门后,`制定作品规划` 按 `work_core`(题材定位 / 核心卖点 / 主题立意 / 基调 / 禁区 / 核心悬念 / 谜底与真相 / 结局方向 / 目标读者,字段权威见 `muse/content/meta/schemas/work_core.yaml`)和 `novel_work` 的篇幅目标(目标章数 × 单章字数区间 × 分卷数,见 `muse/content/meta/schemas/novel_work.yaml`)生成 `section_type='setting'` Shadow。 - 正式产出:用户选定当前方向后,`制定作品规划` 按 `work_core`(题材定位 / 核心卖点 / 主题立意 / 基调 / 禁区 / 核心悬念 / 谜底与真相 / 结局方向 / 目标读者,字段权威见 `muse/content/meta/schemas/work_core.yaml`)和 `novel_work` 的篇幅目标(目标章数 × 单章字数区间 × 分卷数,见 `muse/content/meta/schemas/novel_work.yaml`)生成 `section_type='setting'` Shadow。探索中的未知不伪装为已确认事实;必要缺口在这次正式交接时向用户明确。
- 机械门禁:**篇幅目标三要素齐全**——当前为文档纪律,待升级为落库校验。 - 最终产出校验:`validate_candidates.py` 只查探索交付稿格式;`assert_selection_handoff.py` 只查选定记录形状,不能证明真实用户授权,也未接入 `persist_planning` 自动拦截。创作意义、完成程度与方向选择由提示词和用户判断。正式 setting 的九字段与篇幅三要素当前仍靠规划指令及人审核对,字段覆盖的落库校验待建;不将其变成逐轮探索门槛。
- 退出条件:用户选定记录通过交接门,且 `work_core` 九字段与篇幅目标三要素齐备的 `setting` 已由用户确认(`confirmed`)。 - 退出条件:真实用户方向选择及选定记录已核对,且 `work_core` 九字段与篇幅目标三要素齐备的 `setting` 已由用户确认(`confirmed`)。确认当前版本不承诺永不修订;正文带来的新发现按正式规划修订合同处理。
**阶段 1 · 大纲 / 卷纲** **阶段 1 · 大纲 / 卷纲**
@ -95,7 +95,7 @@
1. 设定全书闭环校验 + 全书设定台账(把「演变历程」从只追加日志升级为闭环义务,新增设定 × 章消费矩阵视图;见 [02-实体领域 §8](02-实体领域.md))。 1. 设定全书闭环校验 + 全书设定台账(把「演变历程」从只追加日志升级为闭环义务,新增设定 × 章消费矩阵视图;见 [02-实体领域 §8](02-实体领域.md))。
2. 卷数合同:`novel_work` 篇幅目标增「分卷数」,`outline` 分卷粗纲卷数须与之一致并机械校验(见 [01-作品领域 §6](01-作品领域.md))。 2. 卷数合同:`novel_work` 篇幅目标增「分卷数」,`outline` 分卷粗纲卷数须与之一致并机械校验(见 [01-作品领域 §6](01-作品领域.md))。
3. 范式规划期绑定:消费侧已通;作品行 `pattern_bindings` 承载列为主仓生产方向(实验仓暂以已确认 assembly 行承载,见 [03-范式领域 §6](03-范式领域.md))。 3. 范式规划期绑定:消费侧已通;作品行 `pattern_bindings` 承载列为主仓生产方向(实验仓暂以已确认 assembly 行承载,见 [03-范式领域 §6](03-范式领域.md))。
- **待建(其余门禁升级与取数补全)**:阶段 0–1 的字数 / 比例 / 四要素校验、阶段 2 字段覆盖门禁扩到其余型(中文键 schema 与英文 payload 对齐后纳入)、阶段 4 细纲硬约束覆盖率门禁、结构化 `style` 八字段画像的书级抽取(当前注入设定行一句话文风)、写作期范式须可回指 confirmed 绑定的机械门禁。字段层面的回填(`novel_work` 分卷数、`pattern_bindings` 库列)属各 owner 文档与 `meta/schemas`,不在本节。 - **待建(其余门禁升级与取数补全)**:阶段 0 正式 setting 字段覆盖校验、阶段 1 的字数 / 比例 / 四要素校验、阶段 2 字段覆盖门禁扩到其余型(中文键 schema 与英文 payload 对齐后纳入)、阶段 4 细纲硬约束覆盖率门禁、结构化 `style` 八字段画像的书级抽取(当前注入设定行一句话文风)、写作期范式须可回指 confirmed 绑定的机械门禁。阶段 0 探索过程不建设字数、条目数量或全书完成度门禁。字段层面的回填(`novel_work` 分卷数、`pattern_bindings` 库列)属各 owner 文档与 `meta/schemas`,不在本节。
### 本节验收条件 ### 本节验收条件

View File

@ -118,7 +118,7 @@ Skill 还有两个实现形态:
0 1 2 3 4 0 1 2 3 4
``` ```
正式规划产物先落 Shadow,你确认后才变 Confirmed,才允许进入下一步的生成上下文。阶段 0 的多案比较稿是唯一例外:它只供选方向,不入库;选定方向必须经 `制定作品规划` 写成 `setting` Shadow 并确认,才算定盘完成。下层不得跟已确认的上层打架。 正式规划产物先落 Shadow,你确认后才变 Confirmed,才允许进入下一步的生成上下文。阶段 0 的作品探索稿与局部比较稿是唯一例外:它们由提示词引导逐步充实,保留在本地草稿,不入库;选定方向必须经 `制定作品规划` 写成 `setting` Shadow 并确认,才算定盘完成。下层不得跟已确认的上层打架。
### 章级:三个节点 ### 章级:三个节点
@ -183,15 +183,17 @@ flowchart TD
### 阶段 0 · 定盘(节点 P) ### 阶段 0 · 定盘(节点 P)
**要完成**:先从多案中选定方向,再把这本书是什么、写给谁、禁区在哪、篇幅目标(目标章数 × 单章字数 × 分卷数)写成正式 `setting`。九个核心字段齐且正式规划已确认,才能往下。 **怎么推进**:从作者已有材料进入,由 [流程主文档](../lifecycle/flow/skills/book/完善故事基础设定/SKILL.md) 调度六份子阶段提示词,允许素材、联系、中心与结构往返发展。九章清单按需展开;只在实际分歧处比较局部方案,整理交付时才核对最终格式。
**如何交接**:作者明确选择当前方向后,再把已有约束和待决问题交给规划角色,按正式字段合同整理 `setting`。九个核心字段与篇幅目标(目标章数 × 单章字数区间 × 分卷数)齐备、具体 setting 再经用户确认,才进入下一阶段。局部采纳不自动触发交接,中心也不因一次选择而永久冻结。
功能链:`setting_init` → [完善故事基础设定](../lifecycle/flow/skills/book/完善故事基础设定/SKILL.md) → 规划师。 功能链:`setting_init` → [完善故事基础设定](../lifecycle/flow/skills/book/完善故事基础设定/SKILL.md) → 规划师。
| Skill | 意图 | 规则 | 工具 / 方法 | | Skill | 意图 | 规则 | 工具 / 方法 |
|---|---|---|---| |---|---|---|---|
| [完善故事基础设定](../lifecycle/flow/skills/book/完善故事基础设定/SKILL.md) | 固化根设定,按固定三级章节树出可比较候选 | 候选不落库、不进生成上下文;选中只完成方向交接,不等于规划确认 | `validate_candidates.py`、`assert_selection_handoff.py`;合同在 `references/candidate-contract.md` | | [完善故事基础设定](../lifecycle/flow/skills/book/完善故事基础设定/SKILL.md) | 一份主文档与六份子阶段指令,逐步形成作品探索稿 | 过程可往返,草稿不入库;方向与正式 setting 两次确认 | 仅整理交付时用 `validate_candidates.py`、`assert_selection_handoff.py` 校验最终格式 |
| [合并故事方案](../lifecycle/flow/skills/book/合并故事方案/SKILL.md) | 多案逐章合成一案 | 一级章节一个新代理;根设定优先 | `serial_merge.py` | | [合并故事方案](../lifecycle/flow/skills/book/合并故事方案/SKILL.md) | 按作者指定范围结合设想并解释取舍 | 不要求全书候选、逐章流水或一次收束中心 | 提示词;完整稿交回「整理交付」统一校验 |
| [提炼故事概念](../../.agent/skills/规划/提炼故事概念/SKILL.md) | 把「我想写 X」升级成能撑长篇的疑问 | idea 是陈述,concept 是疑问;四问(新鲜 / 角度 / 冲突 / 可撑长),可撑长必须过 | 无脚本。见 [方法:概念设计](#提炼故事概念-概念设计) | | [提炼故事概念](../../.agent/skills/规划/提炼故事概念/SKILL.md) | 需要时从已有材料试探持续冲突 | 四问作为深化概念的方法,不作为开始探索的前置门槛 | 无脚本。见 [方法:概念设计](#提炼故事概念-概念设计) |
--- ---

View File

@ -46,8 +46,8 @@
| 技能名 | 技能文件 | 技能说明 | | 技能名 | 技能文件 | 技能说明 |
|---|---|---| |---|---|---|
| 合并故事方案 | [SKILL.md](lifecycle/flow/skills/book/合并故事方案/SKILL.md) | 把多份已完成的前期设计候选逐章串行统合成一份新候选,一级章节一个 fresh 代理,权威顺序固定为根设定优先。用户明确要求综合多案时使用;不生成候选、不做机械门校验、不改前期设计 SoT。 | | 合并故事方案 | [SKILL.md](lifecycle/flow/skills/book/合并故事方案/SKILL.md) | 作者明确要求结合几种已有故事设想时,按指定范围比较兼容性、解释取舍并形成合并建议。可合并局部片段或整稿,产物留在 .agents.local/草稿/;多轮探索归 完善故事基础设定,正式规划落库归 制定作品规划。 |
| 完善故事基础设定 | [SKILL.md](lifecycle/flow/skills/book/完善故事基础设定/SKILL.md) | 在正式规划前固化作品根设定,按固定三级章节树生成可比较的前期设计候选。用户仍在单文档前期设计阶段时使用;不写正文、不落库、不替用户定稿。不适用于合并多案(归 合并故事方案)或正式规划落库(归 制定作品规划)。 | | 完善故事基础设定 | [SKILL.md](lifecycle/flow/skills/book/完善故事基础设定/SKILL.md) | 作者从点子、片段或已有设计继续构思作品时,用提示词引导多轮探索,在 .agents.local/草稿/ 逐步形成作品探索稿。按需发展素材、联系、中心与结构,整理交付时才校验格式;指定的多案合并归 合并故事方案,正式规划落库归 制定作品规划。 |
## 4 结构与规划 ## 4 结构与规划

View File

@ -22,14 +22,18 @@ def expect_fail(payload) -> None:
def main() -> None: def main() -> None:
ok = assert_selection_handoff({ ok = assert_selection_handoff({
"selectedCandidate": "docs/design/candidates/A.md", "selectedCandidate": ".agents.local/草稿/作品/作品探索.md",
"confirmedByUser": True, "confirmedByUser": True,
"confirmedAt": "2026-08-20T12:00:00+08:00", "confirmedAt": "2026-08-20T12:00:00+08:00",
}) })
assert ok["selectedCandidate"].endswith("A.md") assert ok["selectedCandidate"].endswith("作品探索.md")
expect_fail({}) expect_fail({})
expect_fail({"selectedCandidate": "x", "confirmedByUser": False, "confirmedAt": "t"}) expect_fail({"selectedCandidate": "x", "confirmedByUser": False, "confirmedAt": "t"})
for unconfirmed in ("false", "true", "user", 1, ["true"]):
expect_fail({"selectedCandidate": "x", "confirmedByUser": unconfirmed, "confirmedAt": "t"})
expect_fail({"selectedCandidate": "", "confirmedByUser": True, "confirmedAt": "t"}) expect_fail({"selectedCandidate": "", "confirmedByUser": True, "confirmedAt": "t"})
expect_fail({"selectedCandidate": ["x"], "confirmedByUser": True, "confirmedAt": "t"})
expect_fail({"selectedCandidate": "x", "confirmedByUser": True, "confirmedAt": " "})
try: try:
raise HandoffError("x") raise HandoffError("x")
except click.ClickException: except click.ClickException:

View File

@ -1,123 +1,108 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""前期设计候选校验器的离线测试:只覆盖机械门禁自身的正确性。""" """最终交付格式校验器的回归检查;不模拟或约束创作对话。"""
import json
import sys import sys
from pathlib import Path from pathlib import Path
import tempfile import tempfile
import unittest import unittest
PROJECT_ROOT = Path(__file__).resolve().parents[3] PROJECT_ROOT = Path(__file__).resolve().parents[3]
SCRIPT_DIR = PROJECT_ROOT / "muse" / "lifecycle" / "flow" / "skills" / "book" / "完善故事基础设定" / "scripts" SCRIPT_DIR = PROJECT_ROOT / "muse/lifecycle/flow/skills/book/完善故事基础设定/scripts"
sys.path.insert(0, str(SCRIPT_DIR)) sys.path.insert(0, str(SCRIPT_DIR))
from validate_candidates import ValidationConfig, validate_candidates, validate_root # noqa: E402 from validate_candidates import validate_candidates # noqa: E402
TREE = json.loads((SCRIPT_DIR / "candidate_tree.json").read_text(encoding="utf-8")) DRAFT = """# 雨伞|作品探索
BLOCK = "> 回答:本节回答什么。\n> 应写:本节写什么。\n> 不写:本节不写什么。\n> 关联:其他节。\n"
ITEM = "- **S{sid:03d}|条目:** 这是带条件、代价和剧情用途的完整设定内容。"
## 根设定
作者希望保留雨天的兄妹关系。
def chapter_bands() -> list[tuple[int, int]]: ## 一、作品定位与核心命题
"""按每章实际条目数切分号段,保证全局连续编号都落在本章号段内。""" 待定:先从人物行动中寻找中心。
bands = []
cursor = 1
for chapter in TREE["chapters"]:
count = sum(len(section["units"]) for section in chapter["sections"])
bands.append((cursor, cursor + count - 1))
cursor += count
return bands
## 二、故事世界与社会系统
未探索:尚未讨论社会制度。
BANDS = chapter_bands() ## 三、角色与关系
哥哥每天擦一把从不用的伞,妹妹故意把它拿走。
## 四、故事机制与关键实体
未探索:尚未决定是否有超自然力量。
def make_candidate() -> str: ## 五、阶段与状态变化
parts = ["# 候选\n\n## 目录\n\n- 章节列表\n"] 未探索:只有当前这一幕。
counter = 1
for index, chapter in enumerate(TREE["chapters"]): ## 六、情节架构与全书大纲
start, end = BANDS[index] 未探索:兄妹争执后的事情还没想好。
parts.append(f"## {chapter['heading']}\n\n<!-- S{start:03d}-S{end:03d} -->\n")
for section in chapter["sections"]: ## 七、主题与读者体验弧线
parts.append(f"### {section['heading']}\n\n{BLOCK}") 暂想保留亲近中带着疑问的感觉。
for unit in section["units"]:
parts.append(f"### {unit}\n\n{BLOCK}") ## 八、信息、悬念与伏笔管理
parts.append(ITEM.format(sid=counter)) 待定:哥哥为何留着伞。
counter += 1
return "\n".join(parts) ## 九、叙事执行与一致性
先从动作里表现关系。
## 待决与取舍
保留争执这一幕,暂缓决定雨伞的来历。
"""
class CandidateValidatorTest(unittest.TestCase): class CandidateValidatorTest(unittest.TestCase):
def setUp(self) -> None: def setUp(self) -> None:
self.temp_dir = tempfile.TemporaryDirectory() self.temp_dir = tempfile.TemporaryDirectory()
self.addCleanup(self.temp_dir.cleanup)
self.root = Path(self.temp_dir.name) self.root = Path(self.temp_dir.name)
self.config = ValidationConfig(min_candidates=2, min_item_chars=10)
def tearDown(self) -> None:
self.temp_dir.cleanup()
def write(self, name: str, content: str) -> Path:
path = self.root / name
path.write_text(content, encoding="utf-8")
return path
def codes(self, *contents: str) -> set[str]: def codes(self, *contents: str) -> set[str]:
paths = [self.write(f"c{index}.md", content) for index, content in enumerate(contents)] paths = []
_, problems = validate_candidates(paths, self.config) for index, content in enumerate(contents):
path = self.root / f"draft-{index}.md"
path.write_text(content, encoding="utf-8")
paths.append(path)
_, problems = validate_candidates(paths)
return {problem.code for problem in problems} return {problem.code for problem in problems}
def test_two_same_shape_candidates_pass(self) -> None: def test_one_partial_draft_without_ids_or_length_limits_passes(self) -> None:
self.assertEqual(set(), self.codes(make_candidate(), make_candidate())) self.assertEqual(set(), self.codes(DRAFT))
def test_missing_unit_heading_fails(self) -> None: def test_drafts_can_expand_different_subsets(self) -> None:
broken = make_candidate().replace("### 1.1.3 同类作品中的差异\n", "") expanded = DRAFT.replace(
self.assertIn("TREE_MISMATCH", self.codes(make_candidate(), broken)) "待定:先从人物行动中寻找中心。",
"### 1.1 作品形态与定位\n\n### 1.1.1 题材、载体与目标读者\n\n暂定现实题材。",
def test_deep_heading_fails(self) -> None:
broken = make_candidate().replace(
"### 1.1.1 题材、载体与目标读者\n", "### 1.1.1 题材、载体与目标读者\n\n#### 细分\n"
) )
self.assertIn("CANDIDATE_STRUCTURE", self.codes(make_candidate(), broken)) self.assertEqual(set(), self.codes(DRAFT, expanded))
def test_duplicate_and_out_of_range_ids_fail(self) -> None: def test_unknown_missing_or_reordered_headings_fail(self) -> None:
broken = make_candidate().replace("S002|条目:", "S001|条目:").replace("S003|条目:", "S002|条目:") for content in (
codes = self.codes(make_candidate(), broken) DRAFT.replace("## 三、角色与关系", "## 三、人物"),
self.assertIn("SETTING_ID_DUPLICATE", codes) DRAFT.replace("## 三、角色与关系\n", ""),
self.assertIn("SETTING_ORDER", codes) DRAFT.replace("待定:先从人物行动中寻找中心。",
"### 1.2 故事承诺与一句话梗概\n\n设想。\n\n### 1.1 作品形态与定位\n\n设想。"),
):
with self.subTest(content=content):
self.assertIn("TREE_MISMATCH", self.codes(content))
def test_placeholder_fails(self) -> None: def test_empty_chapter_and_bare_placeholder_fail_but_explained_unknown_passes(self) -> None:
broken = make_candidate().replace("完整设定内容", "TODO") self.assertIn("SECTION_EMPTY", self.codes(DRAFT.replace("未探索:尚未讨论社会制度。", "<!-- 留空 -->")))
self.assertIn("CANDIDATE_PLACEHOLDER", self.codes(make_candidate(), broken)) self.assertIn("CANDIDATE_PLACEHOLDER", self.codes(DRAFT.replace("待定:哥哥为何留着伞。", "TODO")))
self.assertEqual(set(), self.codes(DRAFT))
def test_root_contract_accepts_clean_items(self) -> None: def test_optional_ids_are_unique_but_have_no_frozen_bands(self) -> None:
root_doc = self.write( numbered = DRAFT.replace("哥哥每天", "- **S090|哥哥:** 哥哥每天").replace(
"root.md", "先从动作里表现关系。", "- **S003|表达:** 先从动作里表现关系。"
"""# 前期设计
## 一、根设定
- **作品前提:**主角刚结束高考,随后进入拥有星际机甲的陌生时代并寻找立足之地。
- **披露要求:**前三章只呈现当前危机和能力征兆,终局答案留到后续阶段逐步揭开。
## 二、定盘
""",
) )
self.assertEqual([], validate_root(root_doc, self.config)) self.assertEqual(set(), self.codes(numbered))
self.assertIn("SETTING_ID_DUPLICATE", self.codes(numbered.replace("S003", "S090")))
def test_root_rejects_process_language(self) -> None: def test_missing_title_or_deep_heading_fails(self) -> None:
root_doc = self.write( self.assertIn("CANDIDATE_STRUCTURE", self.codes(DRAFT.replace("# 雨伞|作品探索\n", "")))
"root.md", self.assertIn("CANDIDATE_STRUCTURE", self.codes(DRAFT + "\n#### 额外层级\n说明。"))
"""# 前期设计
## 一、根设定 def test_missing_file_is_reported(self) -> None:
_, problems = validate_candidates([self.root / "missing.md"])
- **代理要求:**Claude 子代理分别生成候选文档,再由主会话比较和选择最终方向。 self.assertEqual(["CANDIDATE_FILE_UNREADABLE"], [problem.code for problem in problems])
## 二、定盘
""",
)
self.assertIn("ROOT_PROCESS_LEAK", {problem.code for problem in validate_root(root_doc, self.config)})
if __name__ == "__main__": if __name__ == "__main__":