Jelajahi Sumber

fix(outline): 模型建议两章章纲时补写第 2 章

模型自己提出分成两章章纲后,生成仍停在第 1 章,除非用户再说「连续生成」。
只在这个建议成立时补写两份章纲,其他范围规则不变。

Co-authored-by: Cursor <cursoragent@cursor.com>
darknessomi 1 hari lalu
induk
melakukan
6a9c869e42

+ 2 - 0
skills/SkillHub/ZhanggangSkill/chapter-outline-builder/SKILL.md

@@ -53,6 +53,8 @@ description: 当用户需要把小说大纲、分卷大纲、上一章状态或
 - 下一章的新钩子。
 - 下一章的新钩子。
 - 旧伏笔和新伏笔的交接关系。
 - 旧伏笔和新伏笔的交接关系。
 
 
+如果你在本轮或上一轮主动建议分成两章章纲,这次章纲生成就按完整流程写出这两章章纲,各一份,后一章承接前一章钩子。「下一章交接」不能代替第 2 章章纲。不要等用户再说「连续生成」。用户拒绝该建议、建议不是两章、或明确只要一章时,不要用这段。
+
 不要把多章写成一个拉长的剧情摘要;每章都必须有自己的情绪标签、章节属性、核心记忆点和追读理由。
 不要把多章写成一个拉长的剧情摘要;每章都必须有自己的情绪标签、章节属性、核心记忆点和追读理由。
 
 
 ## 最终交付要求
 ## 最终交付要求

+ 13 - 0
src/components/sources/outline-chat-panel.spec.tsx

@@ -987,6 +987,7 @@ describe("OutlineChatPanel controls", () => {
     expect(prompt).toContain("outlineSaveRequest")
     expect(prompt).toContain("outlineSaveRequest")
     expect(prompt).toContain("Markdown 格式约束:结构化资料使用一级标题")
     expect(prompt).toContain("Markdown 格式约束:结构化资料使用一级标题")
     expect(prompt).toContain("像普通对话一样直接出结果")
     expect(prompt).toContain("像普通对话一样直接出结果")
+    expect(prompt).toContain("主动建议分成两章章纲")
     expect(prompt).not.toContain("必须按 PRD 3.1 主流程执行")
     expect(prompt).not.toContain("必须按 PRD 3.1 主流程执行")
     expect(prompt).not.toContain("先提出最少必要澄清问题")
     expect(prompt).not.toContain("先提出最少必要澄清问题")
   })
   })
@@ -1000,6 +1001,18 @@ describe("OutlineChatPanel controls", () => {
     expect(prompt).toContain("outlineSaveRequest")
     expect(prompt).toContain("outlineSaveRequest")
     expect(prompt).toContain("必须按 PRD 3.1 主流程执行")
     expect(prompt).toContain("必须按 PRD 3.1 主流程执行")
     expect(prompt).toContain("先提出最少必要澄清问题")
     expect(prompt).toContain("先提出最少必要澄清问题")
+    expect(prompt).toContain("主动建议分成两章章纲")
+  })
+
+  it("快速模式提示分支引用两章章纲建议补丁,讨论轮不注入", () => {
+    expect(source).toContain("TWO_CHAPTER_OUTLINE_SUGGESTION_RULE")
+    expect(source).toMatch(/mode === "fast"[\s\S]{0,500}TWO_CHAPTER_OUTLINE_SUGGESTION_RULE/)
+    const discussPrompt = buildOutlineAgentSystemPrompt({
+      mode: "discuss",
+      discussModule: "章节细纲",
+    })
+    expect(discussPrompt).not.toContain("主动建议分成两章章纲")
+    expect(buildOutlineAgentSystemPrompt({ mode: "discuss" })).toContain("主动建议分成两章章纲")
   })
   })
 
 
   it("快速模式源码跳过意图分析和多 Agent,人物小传不再降级为 analysis 预算", () => {
   it("快速模式源码跳过意图分析和多 Agent,人物小传不再降级为 analysis 预算", () => {

+ 9 - 1
src/components/sources/outline-chat-panel.tsx

@@ -545,6 +545,10 @@ const OUTLINE_WORKFLOW_MODE_OPTIONS: Array<{
   },
   },
 ];
 ];
 
 
+/** 只在模型自己建议拆成两章章纲时补写第 2 章章纲,不改其他范围规则。 */
+const TWO_CHAPTER_OUTLINE_SUGGESTION_RULE =
+  "如果你在本轮或上一轮主动建议分成两章章纲,这次章纲生成必须按完整流程写出这两章章纲,各一份,后一章章纲承接前一章章纲的钩子。「下一章交接」不能代替第 2 章章纲,不要等用户再说「连续生成」。用户拒绝该建议、建议不是两章章纲、或明确只要一章时,不要应用这条。";
+
 export function buildOutlineAgentSystemPrompt(options: {
 export function buildOutlineAgentSystemPrompt(options: {
   projectName?: string;
   projectName?: string;
   webResearchContext?: string;
   webResearchContext?: string;
@@ -578,6 +582,7 @@ export function buildOutlineAgentSystemPrompt(options: {
     ? [
     ? [
       "快速模式下像普通对话一样直接出结果。可以按需读取必要上下文,但不要主动进入需求分析、意图分析或多 Agent 编排。",
       "快速模式下像普通对话一样直接出结果。可以按需读取必要上下文,但不要主动进入需求分析、意图分析或多 Agent 编排。",
       "用户要求生成或修改大纲时,直接输出可保存的大纲正文;不要先追问方案或等待确认才开始写。",
       "用户要求生成或修改大纲时,直接输出可保存的大纲正文;不要先追问方案或等待确认才开始写。",
+      TWO_CHAPTER_OUTLINE_SUGGESTION_RULE,
     ]
     ]
     : mode === "discuss"
     : mode === "discuss"
     ? discussTurn
     ? discussTurn
@@ -628,11 +633,14 @@ export function buildOutlineAgentSystemPrompt(options: {
       "推荐选项必须包含:A.全部缺失项 B.基于已有内容推断 C.最近范围 D.自定义",
       "推荐选项必须包含:A.全部缺失项 B.基于已有内容推断 C.最近范围 D.自定义",
       "用户选择或回复后,直接进入生成流程,不再二次分析。",
       "用户选择或回复后,直接进入生成流程,不再二次分析。",
     ];
     ];
+  const appliedWorkflowRules = mode !== "fast" && !discussTurn
+    ? [...workflowRules, TWO_CHAPTER_OUTLINE_SUGGESTION_RULE]
+    : workflowRules;
   return [
   return [
     "你是专业小说大纲分析与创作助手。",
     "你是专业小说大纲分析与创作助手。",
     "如果用户提供 @ 引用,必须优先按路径、标题或会话ID调用对应读取工具获取正文内容。",
     "如果用户提供 @ 引用,必须优先按路径、标题或会话ID调用对应读取工具获取正文内容。",
     "需要保存大纲时只输出 outlineSaveRequest 或 outlineSaveRequests JSON 块,禁止调用 write_outline_node;系统解析后弹出确认,用户确认后才写入文件。",
     "需要保存大纲时只输出 outlineSaveRequest 或 outlineSaveRequests JSON 块,禁止调用 write_outline_node;系统解析后弹出确认,用户确认后才写入文件。",
-    ...workflowRules,
+    ...appliedWorkflowRules,
     "",
     "",
     // 协议轮只允许输出对应协议块,不能再要求附加下一步推荐
     // 协议轮只允许输出对应协议块,不能再要求附加下一步推荐
     ...(protocolTurn ? [] : [
     ...(protocolTurn ? [] : [

+ 4 - 0
src/lib/novel/skill-hub-seed.spec.ts

@@ -29,6 +29,10 @@ describe("SkillHub seed", () => {
       .toBe(SKILL_ROUTE_CATEGORY_IDS.topic)
       .toBe(SKILL_ROUTE_CATEGORY_IDS.topic)
     expect(DEFAULT_SKILL_HUB_SKILLS.find((skill) => skill.name === "chapter-outline-builder")?.categoryId)
     expect(DEFAULT_SKILL_HUB_SKILLS.find((skill) => skill.name === "chapter-outline-builder")?.categoryId)
       .toBe(SKILL_ROUTE_CATEGORY_IDS.outline)
       .toBe(SKILL_ROUTE_CATEGORY_IDS.outline)
+    const chapterOutlineBuilder = DEFAULT_SKILL_HUB_SKILLS.find((skill) => skill.name === "chapter-outline-builder")
+    expect(chapterOutlineBuilder?.content).toContain("用户要求连续生成多章章纲时")
+    expect(chapterOutlineBuilder?.content).toContain("主动建议分成两章章纲")
+    expect(chapterOutlineBuilder?.content).toContain("不要等用户再说「连续生成」")
     expect(DEFAULT_SKILL_HUB_SKILLS.find((skill) => skill.name === "outline-quality-check")?.categoryId)
     expect(DEFAULT_SKILL_HUB_SKILLS.find((skill) => skill.name === "outline-quality-check")?.categoryId)
       .toBe(SKILL_ROUTE_CATEGORY_IDS.outline)
       .toBe(SKILL_ROUTE_CATEGORY_IDS.outline)
   })
   })