From 7f9114eca5ca3973cd2ec206219d1bd91e330ddd Mon Sep 17 00:00:00 2001 From: zizi Date: Thu, 10 Sep 2026 19:39:47 +0800 Subject: [PATCH] =?UTF-8?q?W13=20=E6=97=A7=E7=94=A8=E4=BE=8B=E6=89=BF?= =?UTF-8?q?=E6=8E=A5=E8=A1=A5=E5=85=85=EF=BC=9A=E4=BD=9C=E8=80=85=E5=B1=82?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E4=B8=8E=E4=B8=AD=E6=96=87=E6=96=B9=E6=B3=95?= =?UTF-8?q?=20Skill=20=E7=9A=84=E5=B7=B2=E8=B7=9F=E8=B8=AA=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E6=94=B9=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 作者场景路由与指令、完善故事基础设定/合并故事方案/制定作品规划的技能中文化重构及其脚本、对应旧测试修改、SOT 导读、技能目录与实现回顾。此为 W13 阶段对已跟踪文件的改动,随批次按阶段整理补提交。 --- .agent/作者/场景/写下一章.md | 2 +- .agent/作者/场景/定故事.md | 14 +- .agent/作者/场景/排故事.md | 3 +- .agent/作者/场景/改正文.md | 2 +- .agent/作者/场景/目录.md | 2 +- .agent/作者/指令.md | 10 +- .agent/作者/目录.md | 4 +- docs/实现回顾/技能身份中文化迁移实现回顾.md | 2 + .../flow/skills/book/制定作品规划/SKILL.md | 4 +- .../flow/skills/book/合并故事方案/SKILL.md | 56 +-- .../skills/book/完善故事基础设定/SKILL.md | 77 ++-- .../references/candidate-contract.md | 162 ++------ .../references/chapter-content-spec-chapters-1-5.md | 2 + .../references/chapter-content-spec-chapters-6-9.md | 2 + .../references/chapter-content-spec.md | 22 +- .../scripts/assert_selection_handoff.py | 16 +- .../scripts/candidate_tree.json | 2 +- .../scripts/validate_candidates.py | 392 ++++-------------- muse/sot/domains/05-创作流程领域.md | 14 +- muse/sot/创作周期与Skill导读.md | 12 +- muse/技能目录.md | 4 +- .../test_assert_selection_handoff.py | 8 +- .../test_validate_candidates.py | 155 ++++--- 23 files changed, 304 insertions(+), 663 deletions(-) diff --git a/.agent/作者/场景/写下一章.md b/.agent/作者/场景/写下一章.md index 3bcbdf1..3a22fec 100644 --- a/.agent/作者/场景/写下一章.md +++ b/.agent/作者/场景/写下一章.md @@ -16,5 +16,5 @@ ## 交付与交接 - 先给正文候选,再给必要且简短的检查状态和 `采用`、`修改`、`丢弃` 动作。 -- 候选始终是 Shadow。作者明确 `采用` 或 `丢弃` 后,才交给 [决定正文候选去留](../../../muse/content/work/skills/sovereignty/决定正文候选去留/SKILL.md);`修改` 进入 [改正文](改正文.md)。 +- 候选始终是 Shadow。作者明确 `采用` 或 `丢弃` 后,才交给 [决定正文候选去留](../../skills/操作/决定正文候选去留/SKILL.md);`修改` 进入 [改正文](改正文.md)。 - 作者要求“只给正文”时,只交付正文,不附内部分析、路由或检查清单。 diff --git a/.agent/作者/场景/定故事.md b/.agent/作者/场景/定故事.md index 390a9d8..e16f6a4 100644 --- a/.agent/作者/场景/定故事.md +++ b/.agent/作者/场景/定故事.md @@ -2,18 +2,16 @@ ## 进入条件 -作者只有点子、素材、题材方向或多份未合并方案,还没有确认能支撑长篇的故事主心骨。先读取现有作品信息和本会话决定;只有缺口会改变候选方向时,才问一个关键问题。 +作者想从点子、片段、人物、关系、题材方向或已有设计继续构思作品。先读取当前作品、相关草稿与本会话决定;已有明确内容直接沿用,不要求先确定全书中心。 ## 正式技能 -- 点子还只是陈述时,用 [提炼故事概念](../../skills/规划/提炼故事概念/SKILL.md) 找到能持续制造冲突的核心疑问。 -- 需要形成可比较的完整前期方案时,用 [完善故事基础设定](../../../muse/lifecycle/flow/skills/book/完善故事基础设定/SKILL.md)。 -- 作者已经有多份完整方案并明确要求综合时,才用 [合并故事方案](../../../muse/lifecycle/flow/skills/book/合并故事方案/SKILL.md)。 +正式入口是 [完善故事基础设定](../../skills/操作/完善故事基础设定/SKILL.md):私人探索稿的保存、读回、最终格式检查与明确选定都走新版作者入口,不把探索过程写成正式内容。素材、联系、中心、结构可以往返发展;不把概念四问、九章完整度或候选数量作为开始探索的条件。 -一次只设一个主入口。方法技能可以辅助判断,不能代替规划角色产出候选。 +主会话负责对话与派发,规划角色发展内容。 ## 交付与交接 -- 先给可比较的故事候选和关键差异,再说明哪些内容使用了假设。 -- 不替作者选故事主心骨,不把候选当作已确认规划。 -- 作者选择 `采用` 后,带着被选方案进入 [排故事](排故事.md);选择 `修改` 时只改点名部分;选择 `丢弃` 时不保留为当前方向。 +- 每轮先给本次发展的内容,只追问最影响继续的一项;日常探索不套整书模板、不运行代码校验。 +- 日常探索稿由新版入口保存;整理交付时才检查最终格式。方法子阶段在方法技能迁移前可参考旧版 [完善故事基础设定](../../../muse/lifecycle/flow/skills/book/完善故事基础设定/SKILL.md) 主文档;方法参考不改变新版入口的存储与选定合同。 +- 作者采纳一个点子只更新该处。作者明确选择当前方向并要求正式规划时,按主文档的「整理交付」进入 [排故事](排故事.md);具体 setting 仍需另一次确认。 diff --git a/.agent/作者/场景/排故事.md b/.agent/作者/场景/排故事.md index d164493..b373bb7 100644 --- a/.agent/作者/场景/排故事.md +++ b/.agent/作者/场景/排故事.md @@ -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/chapter/规划下一章/SKILL.md)。书级规划和章级细纲不能由同一次调用混写。 +- 新版规划候选的读取、编辑、审阅与作者明确决定走 [确认规划候选](../../skills/操作/确认规划候选/SKILL.md);旧入口只维护旧库记录。 ## 交付与交接 - 交付可执行的规划草稿,明确已确认事实、待作者决定项和必要假设,不用正文段落充当规划。 -- 规划先保持 Shadow;作者明确确认后,才能成为下层规划或正文写作依据。 +- 规划先保持 Shadow;作者明确确认后,才能成为下层规划或正文写作依据。确认经 [确认规划候选](../../skills/操作/确认规划候选/SKILL.md) 执行,主会话不代替作者采纳。 - 单章细纲齐备并被确认后,交给 [写下一章](写下一章.md)。 diff --git a/.agent/作者/场景/改正文.md b/.agent/作者/场景/改正文.md index 1d8c756..200f5dd 100644 --- a/.agent/作者/场景/改正文.md +++ b/.agent/作者/场景/改正文.md @@ -20,5 +20,5 @@ ## 交付与交接 - 只处理作者指定范围,范围外正文保持原样;不顺手改规划、设定或其它段落。 -- 修改结果仍是 Shadow 候选。作者选择 `采用` 或 `丢弃` 后,交给 [决定正文候选去留](../../../muse/content/work/skills/sovereignty/决定正文候选去留/SKILL.md);继续修改时仍留在本场景。 +- 修改结果仍是 Shadow 候选。作者选择 `采用` 或 `丢弃` 后,交给 [决定正文候选去留](../../skills/操作/决定正文候选去留/SKILL.md);继续修改时仍留在本场景。 - 作者要求“只给正文”时,只交付修改后的正文。 diff --git a/.agent/作者/场景/目录.md b/.agent/作者/场景/目录.md index 8c767f0..49beb31 100644 --- a/.agent/作者/场景/目录.md +++ b/.agent/作者/场景/目录.md @@ -1,6 +1,6 @@ | 名称 | 相对地址 | 内容描述 | 使用场景 | 使用要求 | |------|----------|----------|----------|----------| -| 定故事 | [定故事.md](定故事.md) | 把点子、素材或模糊方向变成可比较的长篇方案 | 作者还没有确认故事主心骨时 | 只交候选,不替作者选定方向 | +| 定故事 | [定故事.md](定故事.md) | 通过流程主文档和子阶段提示词逐步发展素材、中心与结构 | 作者想从点子、片段或已有设计继续构思时 | 按需往返探索,整理交付时才校验格式,保留方向与正式设定两次确认 | | 排故事 | [排故事.md](排故事.md) | 处理全书、分卷、结构、节拍、伏笔、结尾和单章细纲 | 故事方向已明确,需要可执行规划时 | 规划保持草稿态,确认后才能供正文使用 | | 塑人物 | [塑人物.md](塑人物.md) | 处理人物动机、关系、声音与正文呈现 | 作者要创建或补强人物时 | 不替作者决定人物命运、主题立场或重大关系转折 | | 写下一章 | [写下一章.md](写下一章.md) | 按正式事实和已确认细纲生成整章正文候选 | 目标章写作输入齐备时 | 同时读取原生中文写作合同;候选不得直接入正典 | diff --git a/.agent/作者/指令.md b/.agent/作者/指令.md index e5ea5aa..c336217 100644 --- a/.agent/作者/指令.md +++ b/.agent/作者/指令.md @@ -5,7 +5,7 @@ ## 进入顺序 1. 先从当前作品、已确认规划和本会话决策中取事实,不让作者重复提供已经能查到的信息。 -2. 根据作者这轮的主要结果选择一个场景。一个请求横跨多步时,先完成最前面的阻断步骤,再明确交接下一步。 +2. 根据作者这轮的主要结果选择一个场景。正式产物按已有依赖交接;定故事的探索过程按主文档选用子阶段,可以往返,不按代码状态逐步通关。 3. 路由确定后,只读命中的场景文件。生成或修改正文时,同时读 [原生中文写作合同](原生中文写作.md)。 4. 在方法目录或编排目录中读取场景指定的正式技能,再按功能链派发合同指定角色。作者层和主会话不代替角色创作、检测或评分;正式技能名只用于内部执行,普通作者回复不列技能清单。 5. 先交付作者要的内容,再用一句话说明候选状态或下一项决定。 @@ -14,7 +14,7 @@ | 作者常见说法 | 进入场景 | 本轮目标 | |---|---|---| -| 我只有一个点子 | [定故事](场景/定故事.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。 diff --git a/.agent/作者/目录.md b/.agent/作者/目录.md index 992f4ff..402330b 100644 --- a/.agent/作者/目录.md +++ b/.agent/作者/目录.md @@ -1,5 +1,5 @@ | 名称 | 相对地址 | 内容描述 | 使用场景 | 使用要求 | |------|----------|----------|----------|----------| -| 作者路由指令 | [指令.md](指令.md) | 把作者自然中文请求分到一个创作场景,并约束提问、结果说明和决策动作 | 收到作者创作请求时 | 先读本文件;一次只进入一个主场景,不向作者展示内部路由过程 | +| 作者路由指令 | [指令.md](指令.md) | 把自然中文请求分到创作场景,区分自由探索、局部采纳与正式确认 | 收到作者创作请求时 | 先读本文件;一次只进入一个主场景,不向作者展示内部路由过程 | | 原生中文写作合同 | [原生中文写作.md](原生中文写作.md) | 生成和修改正文共同遵守的中文语序、声音、事实与范围边界 | 进入写下一章或改正文场景时 | 与命中的场景文件一并读取,不替代具体技能合同 | -| 作者场景 | [目录.md](场景/目录.md) | 定故事、排故事、塑人物、写下一章、改正文和查作品六个场景入口 | 路由指令已识别作者主意图后 | 只打开命中的场景;场景只负责分流和交接 | +| 作者场景 | [目录.md](场景/目录.md) | 含提示词驱动的定故事,以及排故事、塑人物、续写、修改和诊断入口 | 路由指令已识别作者主意图后 | 只打开命中的场景;场景只负责分流和交接 | diff --git a/docs/实现回顾/技能身份中文化迁移实现回顾.md b/docs/实现回顾/技能身份中文化迁移实现回顾.md index d87de01..99c1887 100644 --- a/docs/实现回顾/技能身份中文化迁移实现回顾.md +++ b/docs/实现回顾/技能身份中文化迁移实现回顾.md @@ -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,再由 `准备正文回放数据` 装配新授权配置。 +撤回后按不放行外部依赖的完整离线清单复验:128 个登记入口中 119 个通过,9 个因 PostgreSQL、模型或凭据依赖明确阻断,实际失败 0;未再次调用 M3 或 Opus。 + ## 5. 清理边界 本回顾、`chinese_only`、永久退役名称门和正常数据库对账已经覆盖一次性迁移能力。英文到中文名称迁移器、机器映射、数据库迁移命令及其专用测试均已删除,仓库内相关程序符号与文件路径引用为 0。 diff --git a/muse/lifecycle/flow/skills/book/制定作品规划/SKILL.md b/muse/lifecycle/flow/skills/book/制定作品规划/SKILL.md index 43c161b..c11b062 100644 --- a/muse/lifecycle/flow/skills/book/制定作品规划/SKILL.md +++ b/muse/lifecycle/flow/skills/book/制定作品规划/SKILL.md @@ -6,7 +6,7 @@ disable-model-invocation: true # 规划(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 须展示给用户另行确认。 ## 失败关闭 diff --git a/muse/lifecycle/flow/skills/book/合并故事方案/SKILL.md b/muse/lifecycle/flow/skills/book/合并故事方案/SKILL.md index 578891a..3e7e1b9 100644 --- a/muse/lifecycle/flow/skills/book/合并故事方案/SKILL.md +++ b/muse/lifecycle/flow/skills/book/合并故事方案/SKILL.md @@ -1,60 +1,32 @@ --- name: 合并故事方案 -description: 把多份已完成的前期设计候选逐章串行统合成一份新候选,一级章节一个 fresh 代理,权威顺序固定为根设定优先。用户明确要求综合多案时使用;不生成候选、不做机械门校验、不改前期设计 SoT。 +description: 作者明确要求结合几种已有故事设想时,按指定范围比较兼容性、解释取舍并形成合并建议。可合并局部片段或整稿,产物留在 .agents.local/草稿/;多轮探索归 完善故事基础设定,正式规划落库归 制定作品规划。 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 -# ① 为当前一级章节构建输入包(最新根设定 + 已统合前文 + 各来源候选的本章) -.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 高推理代理;不复用上一节点会话,不把来源候选的后续章节提前交给当前代理。 - -## 输出 - -每章产出 ``(主轴、主要取舍、发现的前文冲突及处理)与 ``(可直接审定的章节草案)两个区块。主会话审定落盘后,该章成为下一节点的"已统合前文"。全部章节完成后得到一份新的统合候选文件。 +需要形成完整交付稿时,返回 [完善故事基础设定](../完善故事基础设定/SKILL.md) 的「整理交付」,统一使用其 [产出范式](../完善故事基础设定/references/candidate-contract.md) 和最终格式校验。局部合并过程不运行校验脚本。 ## 数据边界 -- 允许读取:最新根设定、已统合前文、来源候选的当前章节。 -- 允许写入:统合候选文件与 `/private/tmp` 下的任务包、原始输出。 -- 数据库:不读不写任何表。 -- Git:不执行 `add`、`commit` 或删除候选。 - -## 红线 - -- 权威顺序固定:最新根设定 > 已统合前文 > 当前来源章节 > 最小补缝新增。来源候选没有投票权,不按票数取多数。 -- 后章不得推翻前章已审定的因果、人物关系、数值、专名和披露节点。 -- 不叠加多案互斥的卖点;故事只能有一条主发动机。 -- 某章校验或语义复核失败时停在当前章,不得让后续代理基于未审定草案继续。 -- 统合结果仍是待选候选,不自动进入唯一前期设计 SoT,也不自动交给 `制定作品规划`。 -- 本 Skill 不提供模型 CLI 子命令。主代理必须按 07 领域派发合同启动 planner 子代理;不得把宿主命令、模型名或凭据固化进脚本。 +- 用户最新明确决定优先;不能按来源数量投票,也不把所有互斥卖点叠加。 +- 可以保留尚未解决的部分与多个中心设想。合并范围由作者的实际问题决定,不强制逐章、固定顺序或统一发动机。 +- 涉及已确认约束时说明影响,不能静默改写。源稿保留,不自动删除。 +- 不写正式正文、不写数据库、不执行 Git 提交。合并建议与局部采纳都不替代方向选择及正式 setting 确认。 ## 复利合同 -- **消费**:多路候选方案与用户选定约束。 -- **回写**:统合本身不落 `example_lesson`;选定方案进入 `制定作品规划` / `决定正文候选去留` 后由规划或确认链登记效果信号。 +消费来源材料和作者取舍;在探索稿保留有效选择的理由。合并阶段不写 `example_lesson`,正式规划或正文确认链负责后续效果登记。 diff --git a/muse/lifecycle/flow/skills/book/完善故事基础设定/SKILL.md b/muse/lifecycle/flow/skills/book/完善故事基础设定/SKILL.md index ddf196b..8d27828 100644 --- a/muse/lifecycle/flow/skills/book/完善故事基础设定/SKILL.md +++ b/muse/lifecycle/flow/skills/book/完善故事基础设定/SKILL.md @@ -1,70 +1,63 @@ --- name: 完善故事基础设定 -description: 在正式规划前固化作品根设定,按固定三级章节树生成可比较的前期设计候选。用户仍在单文档前期设计阶段时使用;不写正文、不落库、不替用户定稿。不适用于合并多案(归 合并故事方案)或正式规划落库(归 制定作品规划)。 +description: 作者从点子、片段或已有设计继续构思作品时,用提示词引导多轮探索,在 .agents.local/草稿/ 逐步形成作品探索稿。按需发展素材、联系、中心与结构,整理交付时才校验格式;指定的多案合并归 合并故事方案,正式规划落库归 制定作品规划。 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 -.venv/bin/python muse/lifecycle/flow/skills/book/完善故事基础设定/scripts/validate_candidates.py \ - --root-doc docs/design/<作品>-前期设计.md --min-candidates 5 docs/design/candidates/*.md -``` +默认持续维护 `.agents.local/草稿/<作品>/作品探索.md`;局部备选只有需要单独比较时才另存。首次有内容时再建文件,不预建九章空表,不自动生成多份整书候选。 -机械门查标题同构、号段、占位符、根设定句长;内容完整靠语义复核。失败码:`SETTING_INIT_VALIDATION_FAILED`。 +落盘优先级:用户指定路径 > 仓库各 `目录.md` 中已登记的约定 > 本 Skill 默认路径。探索稿属于私有草稿;项目规则与设计的修改遵守三树及父子索引契约。 -## 用户选择与交接 +## 边界与最终交接 -比较报告不自动合并。统合交给 `合并故事方案`;用户确认方向后整理回 SoT。 +本阶段不写正式正文、不写数据库、不自动提交 Git。探索过程由指令和作者反馈推进;代码只在最终交付时核对产物格式与选定记录格式,不决定题材、中心、质量或下一子阶段。 -进入 `制定作品规划` 前必须有用户选定记录,并用机械门校验: - -```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.md) 生成 setting Shadow。作者确认这份具体 setting 后才转 confirmed;本地探索稿与方向选择都不产生 Canonical。 ## 复利合同 -- **消费**:用户对话与参考书方法证据,指导后续 `制定作品规划` 正式规划。 -- **回写**:本阶段不落 `example_lesson`;选定方案进入规划链后由 `制定作品规划` / `决定正文候选去留` 登记效果信号。 +消费已有写作方法与参考材料,只借观察、人物、因果等方法,不照搬参考作品设定。本阶段保留有效取舍及其原因;进入正式规划或正文确认链后,由对应 Skill 登记效果信号,本阶段不写 `example_lesson`。 diff --git a/muse/lifecycle/flow/skills/book/完善故事基础设定/references/candidate-contract.md b/muse/lifecycle/flow/skills/book/完善故事基础设定/references/candidate-contract.md index 41b242f..8c3274c 100644 --- a/muse/lifecycle/flow/skills/book/完善故事基础设定/references/candidate-contract.md +++ b/muse/lifecycle/flow/skills/book/完善故事基础设定/references/candidate-contract.md @@ -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 -# 候选标题 +# 作品名|作品探索 + +## 根设定 +作者已明确的约束。尚无约束时如实说明。 ## 一、作品定位与核心命题 +## 二、故事世界与社会系统 +## 三、角色与关系 +## 四、故事机制与关键实体 +## 五、阶段与状态变化 +## 六、情节架构与全书大纲 +## 七、主题与读者体验弧线 +## 八、信息、悬念与伏笔管理 +## 九、叙事执行与一致性 - - -### 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 -一级标题:一个创作设计领域 -二级标题:这个领域要回答的一组问题 -三级标题:可以独立填写、审查和引用的内容单元 -S 条目:三级标题下的具体设定、规则、实体、关系、阶段状态或事件 -``` +章节内可以直接写概述。需要细分时,使用 [九章导航](chapter-content-spec.md) 中相关组或内容单元的标题,均写为 `###`,按导航顺序排列。允许只展开其中一部分;不要求全稿包含 36 组、161 个单元,也不要求各方案展开相同的部分。不使用四级及更深标题。 -1. 号段按一级章节预留,写在章标题之后;三级章节不单独分号段,避免号段过碎。 -2. `S` 编号全文连续、唯一、递增,必须落在本章号段内;空号允许,越界、倒序和重复不允许。 -3. 禁止四级标题;禁止在二级标题下直接放 `S` 条目。 -4. 阶段名称(初期、中期、后期或卷、篇、幕)只能出现在卡片正文、表格列和第五章中,不得成为二、三级标题。 -5. 所有候选标题逐字同构;候选序号只出现在一级标题,不进入共同目录。 +## 内容表达 -## 四、节奏与披露 +- 根设定只记录作者明确要求或已确认的约束;提出新的设想不自动成为根设定。 +- 已形成的内容写具体,建议与疑问明确标注。中心、终局和远期阶段可保留待定,不因模板要求而虚构完整答案。 +- 需要引用具体条目时可用 `- **S001|名称:** 内容`;编号可选,使用时全稿唯一。没有预分号段、数量下限或句长要求。 +- 同一事实一个主归属,其他章节写引用或本章视角的增量。未归位素材留在最后一节。 +- 不留下九章空节或裸露的 `TODO`、`TBD`、`[待填]`;把尚未解决的事说清,不以“未探索”冒充已经设计完成。 -候选先完成追读链,再扩写设定。三个早期里程碑都要同时具备“当期问题、行动目标、实际兑现、付出代价、章末新问题”。 +## 最终代码校验的范围 -- 前三章让读者看见异常和价值,不解释最终来源,不把终局阵营拉到台前。 -- 前十章让优势经过对手或任务验证,同时暴露限制,完成第一个局部闭环。 -- 前五十章完成初期主冲突和一次身份或能力抬升,再打开更大舞台。 -- 后续阶段继续扩大问题尺度;早期劳动、训练、资源或关系线要换规模延续,不能用完即丢。 +[最终格式校验器](../scripts/validate_candidates.py) 使用 [标题树](../scripts/candidate_tree.json) 核对固定目录和已展开标题,检查空节、裸占位符及可选 S 编号重复。一次可交付一份稿,也可分别校验作者要求的多份稿;不比较方案数量、篇幅、完整度或优劣。 -信息分三线记录:世界实际状态、角色知道的状态、读者已看到的状态。一个谜底可以在作者侧确定,但它的征兆、误判、局部解释和正式揭示必须分开放置,归口见 chapter-content-spec 第七章。 - -## 五、独立性 - -候选子代理只能读取任务包、共同参考证据和自己的文件。不得搜索候选目录,不得读取其他候选,不得询问主会话“前一份怎么写”。主会话也不能把某份候选的优点转述给尚未完成的代理。 - -长文需要多轮时,在同一候选内部维护简短设计账本:已经确定的因果、数值、人物关系、阶段披露点和未完成章节。账本只属于该候选,续跑时与任务包一起提供。 - -## 六、表达复核 - -完成内容复核后再处理语言。表达层重点清理: - -- 空泛评价代替具体事件,例如只说“极具张力”“层层递进”。 -- 每段都用“不是……而是……”或三项排比制造伪力度。 -- 所有条目使用相同句式、相同结尾或固定总结句。 -- 频繁使用“同时、此外、值得注意的是、总而言之”等连接词。 -- 用旁白宣布人物多强、世界多危险,却没有任务、损失和对比支撑。 -- 为了显得完整,提前解释终局真相或最终敌人的全貌。 - -复核可以改句子长短、用词和段落节奏,不能改事实、编号、目录、量级、代价或披露节点。 - -## 七、完成判定 - -一份候选只有同时满足以下条件才算完成。机械可查项由校验器把关,其余由语义复核逐项核对: - -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 会话。当前章只有经主会话审定并写入统合候选后,才可作为下一节点的权威前文。未审定的代理输出、分析摘要和舍弃方案不能进入下一节点上下文。 +格式通过只表示可读、可交接。根设定是否确为作者决定、内容是否自洽、何时选方向,由指令和作者判断。所选稿仍是本地探索材料,正式 setting 的字段与确认合同归 [制定作品规划](../../制定作品规划/SKILL.md)。 diff --git a/muse/lifecycle/flow/skills/book/完善故事基础设定/references/chapter-content-spec-chapters-1-5.md b/muse/lifecycle/flow/skills/book/完善故事基础设定/references/chapter-content-spec-chapters-1-5.md index d480460..4e0e9dd 100644 --- a/muse/lifecycle/flow/skills/book/完善故事基础设定/references/chapter-content-spec-chapters-1-5.md +++ b/muse/lifecycle/flow/skills/book/完善故事基础设定/references/chapter-content-spec-chapters-1-5.md @@ -2,6 +2,8 @@ > 六至九章见 [chapter-content-spec-chapters-6-9.md](chapter-content-spec-chapters-6-9.md)。 +按当前问题查阅其中一节即可。下列字段和判断用于深化已展开的设计,不要求从头填完;未讨论的内容可以留待后续。使用方式见 [章节内容规范](chapter-content-spec.md)。 + 本章回答:这部作品是什么、写给谁、承诺什么、边界在哪。不写世界内部设定、情节和角色细节。 diff --git a/muse/lifecycle/flow/skills/book/完善故事基础设定/references/chapter-content-spec-chapters-6-9.md b/muse/lifecycle/flow/skills/book/完善故事基础设定/references/chapter-content-spec-chapters-6-9.md index 884c3fa..78acbe5 100644 --- a/muse/lifecycle/flow/skills/book/完善故事基础设定/references/chapter-content-spec-chapters-6-9.md +++ b/muse/lifecycle/flow/skills/book/完善故事基础设定/references/chapter-content-spec-chapters-6-9.md @@ -2,6 +2,8 @@ > 一至五章见 [chapter-content-spec-chapters-1-5.md](chapter-content-spec-chapters-1-5.md)。 +按当前问题查阅其中一节即可。下列字段和判断用于深化已展开的设计,不要求预先完成全书阶段、结局与披露计划。使用方式见 [章节内容规范](chapter-content-spec.md)。 + 本章回答:故事如何一步步发生。调用第三、四章设定,不得在事件中偷偷新增未定义的核心规则。 diff --git a/muse/lifecycle/flow/skills/book/完善故事基础设定/references/chapter-content-spec.md b/muse/lifecycle/flow/skills/book/完善故事基础设定/references/chapter-content-spec.md index 77e41fa..89be1fc 100644 --- a/muse/lifecycle/flow/skills/book/完善故事基础设定/references/chapter-content-spec.md +++ b/muse/lifecycle/flow/skills/book/完善故事基础设定/references/chapter-content-spec.md @@ -1,6 +1,6 @@ -# 章节内容规范(完善故事基础设定/chapter-spec-v2) +# 章节内容规范 -本规范是 `完善故事基础设定/v2` 的内容事实源:定义固定三级章节树、每级标题职责、内容单元模板、阶段状态规范和跨章归属规则。候选子代理、统合代理和语义复核都以本文件为准;任务包只引用本文件,不重复保存章节合同。 +本规范保留九章设计知识,供探索和深化时按需查阅。每个内容单元说明可以继续想什么,不要求在某一轮全部回答。探索怎么推进见 [流程主文档](../SKILL.md),最终交付的 Markdown 形状见 [产出范式](candidate-contract.md)。 ## 一、层级规则 @@ -11,12 +11,12 @@ S 条目:三级标题下的具体设定、规则、实体、关系、阶段状态或事件 ``` -1. 九章、全部二级与三级标题固定,顺序不得调整,所有候选逐字同构。 -2. 不允许四级标题;不允许在二级标题下直接放 `S` 条目。 -3. 三级标题不按阶段复制。阶段变化由第五章状态矩阵和实体卡阶段字段表达。 -4. 每个三级章节开头写四字段说明(回答/应写/不写/关联),正文内容以本规范为准。 -5. 三级章节无适用内容时写“不适用及原因”,不得留空。 -6. `## 目录`可选,不属于内容章节。 +1. 九章是最终稿的固定主目录;平时只展开当前需要的部分,不比较各稿是否逐字同构。 +2. 章内可直接写概述或具体设想。需要细分时,从下方标题树选用相关组与单元,按原顺序排列;没有探索的单元不必建空标题。 +3. 已涉及阶段变化时,由第五章状态矩阵和实体卡阶段字段表达;不为填表强行划分全书阶段。 +4. 分章参考中的“回答/应写/不写/关联”是给规划角色的提示,不要求复制进作者每轮产物。 +5. 未探索、暂定、不适用都应按实际情况说明。只有“尚未探索”时,不把它改写为“不适用”。 +6. 上述一、二、三级是设计知识层级;最终 Markdown 使用一个作品 H1、章 H2、组和单元 H3,具体格式只由产出范式规定。 ## 二、固定章节树总览 @@ -86,11 +86,11 @@ S 条目:三级标题下的具体设定、规则、实体、关系、阶段状 ## 四、内容单元模板 -不同三级章节使用不同的内容单元。`S` 条目正文围绕同一单元自然展开,不使用整齐划一的表单腔,但字段必须覆盖。 +以下字段用于深化已选中的内容单元,提醒规划角色发现联系与缺口。围绕本轮问题自然展开;未讨论的字段可以留待后续,不把整张卡的字段覆盖当成探索通行条件。`S` 编号仅在需要引用时使用。 ### 1. 作品命题单元(第一章) -必须覆盖:作品定位、目标读者、一句话故事、主角起点、核心目标、外部期限或主要压力、核心卖点、主题问题、读者体验、题材边界。不写代理过程、候选比较、工具模型、尚未确认的作品事实。 +可逐步考虑:作品定位、目标读者、一句话故事、主角起点、核心目标、外部期限或主要压力、核心卖点、主题问题、读者体验、题材边界。尚未确认的内容标明是设想;不因存在该字段就强定答案。工具与比较过程留在取舍说明中。 ### 2. 稳定规则单元(第二、四章) @@ -126,7 +126,7 @@ S 条目:三级标题下的具体设定、规则、实体、关系、阶段状 ## 五、阶段状态规范 -阶段是全书的横向维度,不是某几个章节的专属分类。 +当作品已经需要设计阶段变化时,阶段是全书的横向维度,不是某几个章节的专属分类。以下规则只约束已经展开的阶段;未探索的远期变化明确保留未知,不要求先设计全书再继续人物或场景。 1. 动态实体至少记录:不变量;初始状态;各阶段状态;状态变化原因;终局状态或未知状态。 2. 同一实体在某阶段没有变化时,必须显式写“本阶段保持不变”,不得跳过,否则无法区分“保持不变”与“设计遗漏”。 diff --git a/muse/lifecycle/flow/skills/book/完善故事基础设定/scripts/assert_selection_handoff.py b/muse/lifecycle/flow/skills/book/完善故事基础设定/scripts/assert_selection_handoff.py index c587e84..8639c9e 100644 --- a/muse/lifecycle/flow/skills/book/完善故事基础设定/scripts/assert_selection_handoff.py +++ b/muse/lifecycle/flow/skills/book/完善故事基础设定/scripts/assert_selection_handoff.py @@ -30,17 +30,15 @@ def assert_selection_handoff(selection: dict) -> dict: + ",".join(missing) + "。用户未选定候选时不得进入 制定作品规划。" ) - if selection.get("confirmedByUser") is not True and str(selection.get("confirmedByUser")).lower() not in ( - "true", "yes", "1", "user", - ): - # 允许字符串用户标识,但禁止空/false - if selection.get("confirmedByUser") in (False, None, "", 0): - raise HandoffError("SELECTION_HANDOFF_UNCONFIRMED:confirmedByUser 未确认") - candidate = str(selection["selectedCandidate"]).strip() - if not candidate: + if selection["confirmedByUser"] is not True: + raise HandoffError("SELECTION_HANDOFF_UNCONFIRMED:confirmedByUser 必须为布尔值 true") + candidate = selection["selectedCandidate"] + if not isinstance(candidate, str) or not candidate.strip(): raise HandoffError("SELECTION_HANDOFF_EMPTY:selectedCandidate 为空") + if not isinstance(selection["confirmedAt"], str) or not selection["confirmedAt"].strip(): + raise HandoffError("SELECTION_HANDOFF_INVALID:confirmedAt 必须为非空时间文本") return { - "selectedCandidate": candidate, + "selectedCandidate": candidate.strip(), "confirmedByUser": selection["confirmedByUser"], "confirmedAt": selection["confirmedAt"], } diff --git a/muse/lifecycle/flow/skills/book/完善故事基础设定/scripts/candidate_tree.json b/muse/lifecycle/flow/skills/book/完善故事基础设定/scripts/candidate_tree.json index 240c539..245624f 100644 --- a/muse/lifecycle/flow/skills/book/完善故事基础设定/scripts/candidate_tree.json +++ b/muse/lifecycle/flow/skills/book/完善故事基础设定/scripts/candidate_tree.json @@ -1,5 +1,5 @@ { - "contractVersion": "完善故事基础设定/v2", + "contractVersion": "完善故事基础设定/output-v3", "chapters": [ { "heading": "一、作品定位与核心命题", diff --git a/muse/lifecycle/flow/skills/book/完善故事基础设定/scripts/validate_candidates.py b/muse/lifecycle/flow/skills/book/完善故事基础设定/scripts/validate_candidates.py index fd1b13e..08b0623 100644 --- a/muse/lifecycle/flow/skills/book/完善故事基础设定/scripts/validate_candidates.py +++ b/muse/lifecycle/flow/skills/book/完善故事基础设定/scripts/validate_candidates.py @@ -1,41 +1,23 @@ #!/usr/bin/env python3 -"""校验作品设定初始化的根设定与同构候选。 - -只检查机械易错且人眼难以抽查的项:三级标题树同构、S 编号唯一递增不越号段、 -占位符残留、根设定格式与句长。内容是否完整由语义复核按完成判定核对, -本脚本不做数量、字数或内容质量门槛。 -""" +"""只校验阶段 0 最终交付稿的格式,不参与探索调度或创作判断。""" from __future__ import annotations import argparse -import json -import re -import sys +from collections import Counter from dataclasses import asdict, dataclass +import json from pathlib import Path +import re from typing import Iterable TREE_PATH = Path(__file__).with_name("candidate_tree.json") -HEADING_RE = re.compile(r"^(#{1,6})\s+(.+?)\s*$", re.MULTILINE) -RANGE_RE = re.compile(r"") -SETTING_RE = re.compile(r"^\s*[-*]\s+(?:\*\*)?(S\d{3,})(?=[^\d])", re.MULTILINE) +HEADING_RE = re.compile(r"^(#{1,6})[ \t]+(.+?)[ \t]*$", re.MULTILINE) +SETTING_RE = re.compile(r"^\s*[-*]\s+(?:\*\*)?S(\d{3,})(?=[^\d]|$)", re.MULTILINE) PLACEHOLDER_RE = re.compile( - r"(?]\s*)?(?:TODO|TBD|PLACEHOLDER|\[待填\])\s*$", + 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) @@ -45,315 +27,115 @@ class Problem: message: str -@dataclass(frozen=True) -class SectionReport: - heading: str - range_start: int - range_end: int - setting_count: int - - @dataclass(frozen=True) class CandidateReport: file: str setting_count: int - headings: tuple[str, ...] - sections: tuple[SectionReport, ...] -def visible_chars(text: str) -> int: - """去掉常见 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]]: +def _validate(path: Path, tree: dict) -> tuple[CandidateReport, list[Problem]]: text = path.read_text(encoding="utf-8") + headings = list(HEADING_RE.finditer(text)) 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): - 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)] - h1_count = sum(1 for level, _, _, _ in headings if level == 1) - if h1_count != 1: - problems.append(_problem("CANDIDATE_STRUCTURE", path, f"候选应有且只有一个一级标题,实际 {h1_count} 个")) - deep = [heading for level, heading, _, _ in headings if level >= 4] - if deep: - problems.append(_problem("CANDIDATE_STRUCTURE", path, f"候选出现四级或更深标题:{deep[0]}")) + chapters = {chapter["heading"]: chapter for chapter in tree["chapters"]} + expected = ["根设定", *chapters, "待决与取舍"] + h2 = [h for h in headings if len(h.group(1)) == 2] + titles = [h.group(2) for h in h2] + if titles and titles[0] == "目录": + titles = titles[1:] + if titles != expected: + fail("TREE_MISMATCH", "二级标题须按顺序包含根设定、九章和待决与取舍;目录仅可置于最前") - h2_list = [(heading, start, end) for level, heading, start, end in headings if level == 2] - toc_seen = False - chapter_index = 0 - all_ids: list[int] = [] - previous_range_end = 0 - sections: list[SectionReport] = [] - chapter_titles = [chapter["heading"] for chapter in tree["chapters"]] + if any( + len(h.group(1)) == 3 and (not h2 or h.start() < h2[0].start()) + for h in headings + ): + fail("CANDIDATE_STRUCTURE", "三级标题须位于对应章节内") - for position, (heading, start, end) in enumerate(h2_list): - body_end = h2_list[position + 1][1] if position + 1 < len(h2_list) else len(text) - body = text[end:body_end] + for index, heading in enumerate(h2): + title = heading.group(2) + 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 == "目录": - toc_seen = True - if re.search(r"^###\s+", body, re.MULTILINE): - problems.append(_problem("CANDIDATE_STRUCTURE", path, "目录下不得出现三级标题")) - continue + actual = [ + h.group(2) for h in headings + if len(h.group(1)) == 3 and heading.end() <= h.start() < end + ] + 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): - problems.append(_problem("TREE_MISMATCH", path, f"多出章节标题:{heading}")) - 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}) + ids = [int(value) for value in SETTING_RE.findall(text)] + duplicates = [value for value, count in Counter(ids).items() if count > 1] if duplicates: - display = ", ".join(f"S{setting_id:03d}" for setting_id in duplicates[:10]) - problems.append(_problem("SETTING_ID_DUPLICATE", path, f"全文编号重复:{display}")) - 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 + fail("SETTING_ID_DUPLICATE", "编号重复:" + "、".join(f"S{value:03d}" for value in duplicates)) + return CandidateReport(str(path), len(ids)), problems -def validate_candidates( - paths: Iterable[Path], - config: ValidationConfig, - 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] = [] +def validate_candidates(paths: Iterable[Path]) -> tuple[list[CandidateReport], list[Problem]]: + """各稿独立校验;不要求候选间同构,也不裁决是否已经完成创作。""" + tree = json.loads(TREE_PATH.read_text(encoding="utf-8")) reports: list[CandidateReport] = [] - - if len(candidate_paths) < config.min_candidates: - problems.append( - Problem( - code="CANDIDATE_COUNT", - file="", - message=f"只有 {len(candidate_paths)} 份候选,要求至少 {config.min_candidates} 份", - ) - ) - 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, "候选文件不存在")) + problems: list[Problem] = [] + for item in paths: + path = Path(item) + try: + report, found = _validate(path, tree) + except (OSError, UnicodeError) as exc: + problems.append(Problem("CANDIDATE_FILE_UNREADABLE", str(path), str(exc))) continue - report, file_problems = _parse_candidate(path, tree, config) reports.append(report) - problems.extend(file_problems) - - if len(reports) > 1: - 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} 不一致", - ) - ) + problems.extend(found) + if not reports and not problems: + problems.append(Problem("CANDIDATE_FILE_MISSING", "", "请提供一份最终交付稿")) 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: - 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) - if args.root_min_chars > args.root_max_chars: - parser.error("--root-min-chars 不能大于 --root-max-chars") - - config = ValidationConfig( - min_candidates=args.min_candidates, - 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, - "reports": [asdict(report) for report in reports], - "problems": [asdict(problem) for problem in problems], - } - + reports, problems = validate_candidates(args.files) + code = "SETTING_INIT_VALIDATION_FAILED" if problems else "SETTING_INIT_OK" if args.json: - print(json.dumps(payload, ensure_ascii=False, indent=2)) + print(json.dumps({ + "status": "invalid" if problems else "ok", + "code": code, + "reports": [asdict(report) for report in reports], + "problems": [asdict(problem) for problem in problems], + }, ensure_ascii=False, indent=2)) else: - print(f"{code}: {len(reports)} 份候选,{len(problems)} 个问题") - for report in reports: - print(f"- {report.file}: {report.setting_count} 项") + print(f"{code}: {len(reports)} 份交付稿,{len(problems)} 个格式问题") for problem in problems: print(f"[{problem.code}] {problem.file}: {problem.message}") - return 0 if not problems else 1 + return 1 if problems else 0 if __name__ == "__main__": - sys.exit(main()) + raise SystemExit(main()) diff --git a/muse/sot/domains/05-创作流程领域.md b/muse/sot/domains/05-创作流程领域.md index b78afce..220f1d0 100644 --- a/muse/sot/domains/05-创作流程领域.md +++ b/muse/sot/domains/05-创作流程领域.md @@ -17,20 +17,20 @@ 贯穿五阶段的硬合同(均为既有合同,本节只引用): - **横切总闸**:一切正式规划候选先落 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 · 定盘** - 进入条件:作品已创建(作品行存在,见 [01-作品领域](01-作品领域.md))。 -- 探索产出:冻结根设定后生成可比较候选;候选只供用户选方向,不是 Shadow,也不是正式内容。统合多案仍只得到一份待选候选。 -- 正式产出:通过选定交接门后,`制定作品规划` 按 `work_core`(题材定位 / 核心卖点 / 主题立意 / 基调 / 禁区 / 核心悬念 / 谜底与真相 / 结局方向 / 目标读者,字段权威见 `muse/content/meta/schemas/work_core.yaml`)和 `novel_work` 的篇幅目标(目标章数 × 单章字数区间 × 分卷数,见 `muse/content/meta/schemas/novel_work.yaml`)生成 `section_type='setting'` Shadow。 -- 机械门禁:**篇幅目标三要素齐全**——当前为文档纪律,待升级为落库校验。 -- 退出条件:用户选定记录通过交接门,且 `work_core` 九字段与篇幅目标三要素齐备的 `setting` 已由用户确认(`confirmed`)。 +- 探索产出:持续充实的作品探索稿,按需附局部方案。素材、联系、中心与结构可以往返发展;中心和远期内容可以暂定。九章清单用于导航,整理交付时才按固定目录归位,已展开内容具体写明,未探索部分保留说明。完整指令唯一入口为 [完善故事基础设定](../../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。探索中的未知不伪装为已确认事实;必要缺口在这次正式交接时向用户明确。 +- 最终产出校验:`validate_candidates.py` 只查探索交付稿格式;`assert_selection_handoff.py` 只查选定记录形状,不能证明真实用户授权,也未接入 `persist_planning` 自动拦截。创作意义、完成程度与方向选择由提示词和用户判断。正式 setting 的九字段与篇幅三要素当前仍靠规划指令及人审核对,字段覆盖的落库校验待建;不将其变成逐轮探索门槛。 +- 退出条件:真实用户方向选择及选定记录已核对,且 `work_core` 九字段与篇幅目标三要素齐备的 `setting` 已由用户确认(`confirmed`)。确认当前版本不承诺永不修订;正文带来的新发现按正式规划修订合同处理。 **阶段 1 · 大纲 / 卷纲** @@ -95,7 +95,7 @@ 1. 设定全书闭环校验 + 全书设定台账(把「演变历程」从只追加日志升级为闭环义务,新增设定 × 章消费矩阵视图;见 [02-实体领域 §8](02-实体领域.md))。 2. 卷数合同:`novel_work` 篇幅目标增「分卷数」,`outline` 分卷粗纲卷数须与之一致并机械校验(见 [01-作品领域 §6](01-作品领域.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`,不在本节。 ### 本节验收条件 diff --git a/muse/sot/创作周期与Skill导读.md b/muse/sot/创作周期与Skill导读.md index 1bd2419..e76383d 100644 --- a/muse/sot/创作周期与Skill导读.md +++ b/muse/sot/创作周期与Skill导读.md @@ -118,7 +118,7 @@ Skill 还有两个实现形态: 0 1 2 3 4 ``` -正式规划产物先落 Shadow,你确认后才变 Confirmed,才允许进入下一步的生成上下文。阶段 0 的多案比较稿是唯一例外:它只供选方向,不入库;选定方向必须经 `制定作品规划` 写成 `setting` Shadow 并确认,才算定盘完成。下层不得跟已确认的上层打架。 +正式规划产物先落 Shadow,你确认后才变 Confirmed,才允许进入下一步的生成上下文。阶段 0 的作品探索稿与局部比较稿是唯一例外:它们由提示词引导逐步充实,保留在本地草稿,不入库;选定方向必须经 `制定作品规划` 写成 `setting` Shadow 并确认,才算定盘完成。下层不得跟已确认的上层打架。 ### 章级:三个节点 @@ -183,15 +183,17 @@ flowchart TD ### 阶段 0 · 定盘(节点 P) -**要完成**:先从多案中选定方向,再把这本书是什么、写给谁、禁区在哪、篇幅目标(目标章数 × 单章字数 × 分卷数)写成正式 `setting`。九个核心字段齐且正式规划已确认,才能往下。 +**怎么推进**:从作者已有材料进入,由 [流程主文档](../lifecycle/flow/skills/book/完善故事基础设定/SKILL.md) 调度六份子阶段提示词,允许素材、联系、中心与结构往返发展。九章清单按需展开;只在实际分歧处比较局部方案,整理交付时才核对最终格式。 + +**如何交接**:作者明确选择当前方向后,再把已有约束和待决问题交给规划角色,按正式字段合同整理 `setting`。九个核心字段与篇幅目标(目标章数 × 单章字数区间 × 分卷数)齐备、具体 setting 再经用户确认,才进入下一阶段。局部采纳不自动触发交接,中心也不因一次选择而永久冻结。 功能链:`setting_init` → [完善故事基础设定](../lifecycle/flow/skills/book/完善故事基础设定/SKILL.md) → 规划师。 | Skill | 意图 | 规则 | 工具 / 方法 | |---|---|---|---| -| [完善故事基础设定](../lifecycle/flow/skills/book/完善故事基础设定/SKILL.md) | 固化根设定,按固定三级章节树出可比较候选 | 候选不落库、不进生成上下文;选中只完成方向交接,不等于规划确认 | `validate_candidates.py`、`assert_selection_handoff.py`;合同在 `references/candidate-contract.md` | -| [合并故事方案](../lifecycle/flow/skills/book/合并故事方案/SKILL.md) | 多案逐章合成一案 | 一级章节一个新代理;根设定优先 | `serial_merge.py` | -| [提炼故事概念](../../.agent/skills/规划/提炼故事概念/SKILL.md) | 把「我想写 X」升级成能撑长篇的疑问 | idea 是陈述,concept 是疑问;四问(新鲜 / 角度 / 冲突 / 可撑长),可撑长必须过 | 无脚本。见 [方法:概念设计](#提炼故事概念-概念设计) | +| [完善故事基础设定](../lifecycle/flow/skills/book/完善故事基础设定/SKILL.md) | 一份主文档与六份子阶段指令,逐步形成作品探索稿 | 过程可往返,草稿不入库;方向与正式 setting 两次确认 | 仅整理交付时用 `validate_candidates.py`、`assert_selection_handoff.py` 校验最终格式 | +| [合并故事方案](../lifecycle/flow/skills/book/合并故事方案/SKILL.md) | 按作者指定范围结合设想并解释取舍 | 不要求全书候选、逐章流水或一次收束中心 | 提示词;完整稿交回「整理交付」统一校验 | +| [提炼故事概念](../../.agent/skills/规划/提炼故事概念/SKILL.md) | 需要时从已有材料试探持续冲突 | 四问作为深化概念的方法,不作为开始探索的前置门槛 | 无脚本。见 [方法:概念设计](#提炼故事概念-概念设计) | --- diff --git a/muse/技能目录.md b/muse/技能目录.md index 83abf1c..fe3a6fa 100644 --- a/muse/技能目录.md +++ b/muse/技能目录.md @@ -46,8 +46,8 @@ | 技能名 | 技能文件 | 技能说明 | |---|---|---| -| 合并故事方案 | [SKILL.md](lifecycle/flow/skills/book/合并故事方案/SKILL.md) | 把多份已完成的前期设计候选逐章串行统合成一份新候选,一级章节一个 fresh 代理,权威顺序固定为根设定优先。用户明确要求综合多案时使用;不生成候选、不做机械门校验、不改前期设计 SoT。 | -| 完善故事基础设定 | [SKILL.md](lifecycle/flow/skills/book/完善故事基础设定/SKILL.md) | 在正式规划前固化作品根设定,按固定三级章节树生成可比较的前期设计候选。用户仍在单文档前期设计阶段时使用;不写正文、不落库、不替用户定稿。不适用于合并多案(归 合并故事方案)或正式规划落库(归 制定作品规划)。 | +| 合并故事方案 | [SKILL.md](lifecycle/flow/skills/book/合并故事方案/SKILL.md) | 作者明确要求结合几种已有故事设想时,按指定范围比较兼容性、解释取舍并形成合并建议。可合并局部片段或整稿,产物留在 .agents.local/草稿/;多轮探索归 完善故事基础设定,正式规划落库归 制定作品规划。 | +| 完善故事基础设定 | [SKILL.md](lifecycle/flow/skills/book/完善故事基础设定/SKILL.md) | 作者从点子、片段或已有设计继续构思作品时,用提示词引导多轮探索,在 .agents.local/草稿/ 逐步形成作品探索稿。按需发展素材、联系、中心与结构,整理交付时才校验格式;指定的多案合并归 合并故事方案,正式规划落库归 制定作品规划。 | ## 4 结构与规划 diff --git a/tests/skills/完善故事基础设定/test_assert_selection_handoff.py b/tests/skills/完善故事基础设定/test_assert_selection_handoff.py index f4f46af..9024046 100644 --- a/tests/skills/完善故事基础设定/test_assert_selection_handoff.py +++ b/tests/skills/完善故事基础设定/test_assert_selection_handoff.py @@ -22,14 +22,18 @@ def expect_fail(payload) -> None: def main() -> None: ok = assert_selection_handoff({ - "selectedCandidate": "docs/design/candidates/A.md", + "selectedCandidate": ".agents.local/草稿/作品/作品探索.md", "confirmedByUser": True, "confirmedAt": "2026-08-20T12:00:00+08:00", }) - assert ok["selectedCandidate"].endswith("A.md") + assert ok["selectedCandidate"].endswith("作品探索.md") expect_fail({}) 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": ["x"], "confirmedByUser": True, "confirmedAt": "t"}) + expect_fail({"selectedCandidate": "x", "confirmedByUser": True, "confirmedAt": " "}) try: raise HandoffError("x") except click.ClickException: diff --git a/tests/skills/完善故事基础设定/test_validate_candidates.py b/tests/skills/完善故事基础设定/test_validate_candidates.py index d706c6e..b9cdb79 100644 --- a/tests/skills/完善故事基础设定/test_validate_candidates.py +++ b/tests/skills/完善故事基础设定/test_validate_candidates.py @@ -1,123 +1,108 @@ #!/usr/bin/env python3 -"""前期设计候选校验器的离线测试:只覆盖机械门禁自身的正确性。""" +"""最终交付格式校验器的回归检查;不模拟或约束创作对话。""" -import json import sys from pathlib import Path import tempfile import unittest - 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)) -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")) -BLOCK = "> 回答:本节回答什么。\n> 应写:本节写什么。\n> 不写:本节不写什么。\n> 关联:其他节。\n" -ITEM = "- **S{sid:03d}|条目:** 这是带条件、代价和剧情用途的完整设定内容。" +DRAFT = """# 雨伞|作品探索 +## 根设定 +作者希望保留雨天的兄妹关系。 -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\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): def setUp(self) -> None: self.temp_dir = tempfile.TemporaryDirectory() + self.addCleanup(self.temp_dir.cleanup) 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]: - paths = [self.write(f"c{index}.md", content) for index, content in enumerate(contents)] - _, problems = validate_candidates(paths, self.config) + paths = [] + 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} - def test_two_same_shape_candidates_pass(self) -> None: - self.assertEqual(set(), self.codes(make_candidate(), make_candidate())) + def test_one_partial_draft_without_ids_or_length_limits_passes(self) -> None: + self.assertEqual(set(), self.codes(DRAFT)) - def test_missing_unit_heading_fails(self) -> None: - broken = make_candidate().replace("### 1.1.3 同类作品中的差异\n", "") - self.assertIn("TREE_MISMATCH", self.codes(make_candidate(), broken)) - - def test_deep_heading_fails(self) -> None: - broken = make_candidate().replace( - "### 1.1.1 题材、载体与目标读者\n", "### 1.1.1 题材、载体与目标读者\n\n#### 细分\n" + def test_drafts_can_expand_different_subsets(self) -> None: + expanded = DRAFT.replace( + "待定:先从人物行动中寻找中心。", + "### 1.1 作品形态与定位\n\n### 1.1.1 题材、载体与目标读者\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: - broken = make_candidate().replace("S002|条目:", "S001|条目:").replace("S003|条目:", "S002|条目:") - codes = self.codes(make_candidate(), broken) - self.assertIn("SETTING_ID_DUPLICATE", codes) - self.assertIn("SETTING_ORDER", codes) + def test_unknown_missing_or_reordered_headings_fail(self) -> None: + for content in ( + DRAFT.replace("## 三、角色与关系", "## 三、人物"), + DRAFT.replace("## 三、角色与关系\n", ""), + 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: - broken = make_candidate().replace("完整设定内容", "TODO") - self.assertIn("CANDIDATE_PLACEHOLDER", self.codes(make_candidate(), broken)) + def test_empty_chapter_and_bare_placeholder_fail_but_explained_unknown_passes(self) -> None: + self.assertIn("SECTION_EMPTY", self.codes(DRAFT.replace("未探索:尚未讨论社会制度。", ""))) + self.assertIn("CANDIDATE_PLACEHOLDER", self.codes(DRAFT.replace("待定:哥哥为何留着伞。", "TODO"))) + self.assertEqual(set(), self.codes(DRAFT)) - def test_root_contract_accepts_clean_items(self) -> None: - root_doc = self.write( - "root.md", - """# 前期设计 - -## 一、根设定 - -- **作品前提:**主角刚结束高考,随后进入拥有星际机甲的陌生时代并寻找立足之地。 -- **披露要求:**前三章只呈现当前危机和能力征兆,终局答案留到后续阶段逐步揭开。 - -## 二、定盘 -""", + def test_optional_ids_are_unique_but_have_no_frozen_bands(self) -> None: + numbered = DRAFT.replace("哥哥每天", "- **S090|哥哥:** 哥哥每天").replace( + "先从动作里表现关系。", "- **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: - root_doc = self.write( - "root.md", - """# 前期设计 + def test_missing_title_or_deep_heading_fails(self) -> None: + self.assertIn("CANDIDATE_STRUCTURE", self.codes(DRAFT.replace("# 雨伞|作品探索\n", ""))) + self.assertIn("CANDIDATE_STRUCTURE", self.codes(DRAFT + "\n#### 额外层级\n说明。")) -## 一、根设定 - -- **代理要求:**Claude 子代理分别生成候选文档,再由主会话比较和选择最终方向。 - -## 二、定盘 -""", - ) - self.assertIn("ROOT_PROCESS_LEAK", {problem.code for problem in validate_root(root_doc, self.config)}) + def test_missing_file_is_reported(self) -> None: + _, problems = validate_candidates([self.root / "missing.md"]) + self.assertEqual(["CANDIDATE_FILE_UNREADABLE"], [problem.code for problem in problems]) if __name__ == "__main__":